Gregory's Blog

Kendo Server Side Validation

This post describes how to use Kendo's validator for server side validation. There are very few posts showing how to use Kendo's validator with server side validation (none of them are really clear), it took me a bit of time to figure it out, and want to share my approach and will provide extensive comments.

One of the reasons that there are very few posts concerning server side validation with the Kendo validator is that it is not really built to do this. Unlike the majority of the other Kendo widgets which allow for customization, the validator was meant for simple validation. The built in validation is quite useful for simple client side validation, but it is not an extensive validation library and anytime that you need to extend it you will find yourself wanting more. I felt like I was trying to hammer a square peg into a circle while coding this. However, since one of the main goals of this blog is to share how ColdFusion can use the Kendo UI, I felt the need to dig into the kendo validator.

I have a captcha form on this blog that is used to verify that the user is an actual user, and it encrypts a token and passes it off to the server side for validation. You can see this in action by making a comment on this post below.

The meat and potatoes of this function, like most of the other Kendo widgets, lies in the Javascript. This script is heavily commented. Click the more button below to inspect the script.

Javascript:

view plain about
1$(document).ready(function() {
2    
3    // Validation.
4    // Preset our sessionStorage var. This is set to '' initially to indicate that server side validation has not yet occurred.
5    sessionStorage.setItem("captchaValidated", "");
6    // Set the initial value of the captchaValidatedValue form element. We need to store this in order to know when to hit the server with a new validation request. We don't want to hit the server 3 times a second unless the text value has actually changed.
7    sessionStorage.setItem("captchaValidatedValue", "");
8    // Since the kendo validator occurs so quickly, it may send an erroneous value to the server the a few times before it picks up the new value that was entered. We need to allow several attempts to occur when we hit the server. This is a numeric value that will be incremented.
9    sessionStorage.setItem("captchaValidatedAttempts", "0");
10
11    // Invoked when the submit button is clicked. Instead of using '$("form").submit(function(event) {' and 'event.preventDefault();', We are using direct binding here to speed up the event.
12    var addCommentSubmit = $('#addCommentSubmit');
13    addCommentSubmit.on('click', function(e){
14        // Prevent any other action.
15        e.preventDefault();
16        // Set the attempts var to 0
17        sessionStorage.setItem("captchaValidatedAttempts", 0);
18        // Note: when using server side logic, this function may not post the data to the server due to the time required to return the validation from the server.
19        // If the form has been successfully validated.
20        if (addCommentFormValidator.validate()) {
21            // Submit the form. We need to have a quick timeout function as the captcha resonse does not come back for 150 milliseconds.
22            setTimeout(function () {
23                // Note: when testing the ui validator, comment out the post line below. It will only validate and not actually do anything when you post.
24                postCommentSubscribe(<cfoutput>'#URL.Id#'</cfoutput>, <cfoutput>'#URL.uiElement#'</cfoutput>);
25            }, 300);//..setTimeout(function () {
26        }//..if (addCommentFormValidator.validate()) {
27    });//..addCommentSubmit.on('click', function(e){
28
29    // !!! Note on the validators, all forms need a name attribute, otherwise the positioning of the messages will not work. Also data attributes that are dash separated become camel cased when retrieved using jQuery. --->

30    addCommentFormValidator = $("#addCommentSubscribe").kendoValidator({
31        // Set up custom validation rules
32        rules: {
33            // Name of custom rule.
34            // This can be any name, but I typically put the name of the field and a verb to indicate what I am enforcing ('nameIsRequired'). Note: if you just want to check to see if something was entered you can specify 'required' in the form element.
35            // This rule is quite different as it relies upon server side processing. I used https://www.telerik.com/blogs/extending-the-kendo-ui-validator-with-custom-rules as an example to build this.
36            captcha:
37                function(input) {
38                    if (input.is("[id='captchaText']")){
39                        // The captchaValidated value is set in storage session and set in the function below. Note, until the form loses focus, this function is constantly being validated until validation passes. Be careful not to go into an endless loop without exits.
40                        var captchaValidated = getCapthchaValidated();
41
42                        // If the captcha has not been validated on the server...
43                        if (captchaValidated == ''){
44                            // Check the captcha
45                            captchaText.check(input);
46                            // And stop...
47                            return false;
48                        }
49
50                        // If the server validation failed, try again...
51                        if (captchaValidated == 'no'){
52                            // Check the captcha
53                            captchaText.check(input);
54                            // And stop...
55                            return false;
56                        }    
57
58                        if (captchaValidated == 'yes'){
59                            // The captha text was succuessfully validated. Exit this function.
60                            return true;
61                        }
62                    }//..if (input.is("[id='captchaText']")){
63                    // This rule does not apply to the captha text input.
64                    return true;
65                }//..function(input) {
66            }
67        //..captcha:
68    }).data("kendoValidator");
69
70    // Create a variable for this function as we will use the properties in the captch validation function above when it returns results.
71    var captchaText = {
72        check: function(element) {
73
74            // Note: the validator will fire off a new request 3 times a second, and we need to make sure that we are not hitting the server with stale data every time. We are going to see if the value has changed before firing off a new request to the server.
75            // Compare the input value to the value that was stored in sessionStorage. If the data has changed, and there has been fewer than 5 validation attempts that have failed, hit the server.
76            if (element.val() != getCapthchaValidatedValue() || getCaptchaValidatedAttempts() &lt;= 5){
77                // Post to the server side method that will validate the captcha text.
78                $.ajax({
79                    url: "<cfoutput>#application.proxyController#</cfoutput>?method=validateCaptcha",
80                    dataType: 'json', // Use json for same domain posts. Use jsonp for crossdomain.
81                    data: {
82                        // Send in the arguments.
83                        captchaText: element.val(),
84                        captchaHash: $( "#captchaHash" ).val()
85                    },
86                    success: function(data) { // The `data` object is a boolean value that is returned from the server.
87                        var captchaValidated = getCapthchaValidated();
88                        if (data){
89                            // debugging alert('Yes!');
90                            // Set the value on the cache object so that it can be referenced in the next validation run. Note: sessionStorage can only store strings.
91                            sessionStorage.setItem("captchaValidated", "yes");
92                            // At the tail end of the validation process, when the validated data is complete, post the data. Since we have passed validation, we don't need to hit the 'captcha' custom rule above again.
93                            if (addCommentFormValidator.validate()) {
94                                // Hide the custom window message
95                                kendo.ui.ExtAlertDialog.hide;
96                                // submit the form. We need to have a quick timeout function as the captcha resonse does not come back for 150 milliseconds.
97                                setTimeout(function () {
98                                    // Note: when testing the ui validator, comment out the post line below. It will only validate and not actually do anything when you post.
99                                    postCommentSubscribe(<cfoutput>'#URL.Id#'</cfoutput>, <cfoutput>'#URL.uiElement#'</cfoutput>);
100                                }, 300);//..setTimeout(function () {
101                            }
102                        } else {
103                            // Get the number of validation attempts.
104                            var captchaValidatedAttempts = getCaptchaValidatedAttempts();
105                            // Increment the validation attempt.
106                            var currentCaptchaValidatedAttempt = (captchaValidatedAttempts + 1);
107                            // Store the number of validation attempts in sessionStorage.
108                            sessionStorage.setItem("captchaValidatedAttempts", currentCaptchaValidatedAttempt);
109                            // After the 5th bad attempt, set the validation var and use a quick set timeout in order for the data to come back and be validated on the server before launching our custom error popup. Otherwise, if there was a previous captch error from the server, this custom error will pop up as the new data has not had a chance to be returned from the server yet.
110                            if (currentCaptchaValidatedAttempt == 6){
111                                // Store that we tried to validate, but it was not correct.
112                                sessionStorage.setItem("captchaValidated", "no");
113                                // Load a new captcha image (this is my own custom requirement and it has no bearing to the validator logic).
114                                reloadCaptcha();
115                                // Popup an error message.
116                                setTimeout(function() {
117                                    if (getCapthchaValidated() == 'no'){
118                                        // Note: this is a custom library that I am using. The ExtAlertDialog is not a part of Kendo but an extension.
119                                        $.when(kendo.ui.ExtAlertDialog.show({ title: "The text did not match", message: "We have reloaded a new captcha image. If you're having issues with the captcha text, click on the 'new captcha' button to and enter the new text.", icon: "k-ext-warning", width: "<cfoutput>#application.kendoExtendedUiWindowWidth#</cfoutput>", height: "215px" }) // or k-ext-error, k-ext-question
120                                            ).done(function () {
121                                            // Do nothing
122                                        });//..$.when(kendo.ui.ExtAlertDialog.show...
123                                    }//..if (addCommentFormValidator.validate()) {
124                                }, 500);// A half of a second should allow the server to validate the captcha and return the result.
125                            }
126                        }
127                        // Store the validated value. We will use this to determine when to hit the server for validation again if the value was not correctly typed in.
128                        sessionStorage.setItem("captchaValidatedValue", element.val());
129                        // Trigger the validation routine again. We need to validate each time, even if the value is validated on the server as we need to eliminate the error message raised in the validation script and will be popped up when the form loses focus on the onBlue event.
130                        setTimeout(function() {
131                            addCommentFormValidator.validate();
132                        }, 2000);// Wait 2 seconds to hit the server again.
133                    }//..success: function(data) {
134                    // Notes: success() only gets called if your webserver responds with a 200 OK HTTP header - basically when everything is fine. However, complete() will always get called no matter if the ajax call was successful or not. It's worth mentioning that .complete() will get called after .success() gets called - if it matters to you.
135
136                });//..$.ajax({
137            }//..if (element.val() != getCapthchaValidatedValue()){
138        }//..check: function(element, settings) {
139    };//..var captchaText = {
140
141});//...document.ready
142
143// Validation helper functions. These must be oustide of the document ready block in order to work.
144// Note: due to the latency of the data coming back from the server, we need to have two points to post a completely validated form to the server for processing. The first point is when the user clicks the submit form button, and the second point is at the tail end of the processing when the server has validated data.
145
146// I am using sessionStorage to store the value from the server in order to effect the captach widget that I developed. I don't want to have to ask the user to go thru the captha validation process multiple times within the same session and don't want to have to write out the logic every time.
147function getCapthchaValidated(){
148    return sessionStorage.getItem("captchaValidated");
149}
150
151// Prior to validation, what did the user enter?
152function getCapthchaValidatedValue(){
153    // Since sessionStorage only stores strings reliably, this will be either: '', 'no', or 'yes'.
154    return sessionStorage.getItem("captchaValidatedValue");
155}
156
157// Returns the number of attempts that the server tried to validate the data. This only gets incremented when the server comes back with a false (not validated).
158function getCaptchaValidatedAttempts(){
159    var attemps = sessionStorage.getItem("captchaValidatedAttempts");
160    return(parseInt(attemps));
161}

Server side ColdFusion:

5) The server side logic that determines if the text that the user entered matches the text that is shown in the captcha image.

5a) Does the text match the captcha image? Will return a boolean value (true/false).

5b) We need to eliminate any chance that a positive result is not overwritten. The client is firing off server side ajax requests 3 times a second, and we need to be careful not to allow a subsequent ajax request overwrite our value. We are using a server side cookie to ensure that this does not happen.

view plain about
1<!--- 5) Helper functions for interfaces (addComments, addSub, etc.). Important note on function tags- they must have a returnFormat="json". Otherwise, ColdFusion will return the value wraped in a wddx tag.--->
2<cffunction name="validateCaptcha" access="remote" returnType="boolean" returnFormat="json" output="false" hint="Remote method accessed via ajax. Returns a boolean value to determine if the users entered value matches the captcha image.">
3    <cfargument name="captchaText" required="yes" hint="What did the user enter into the form?" />
4    <cfargument name="captchaHash" required="yes" hint="The hashed value of the proper answer. This must match the captcha text in order to pass true." />
5    <cfargument name="debugging" required="no" type="boolean" default="false" hint="For testing purposes, we may need to not use the session.captchValidated value to prevent a true value from being incorreclty reset." />
6
7    <!---5a) Does the text that the user entered match the hashed value?--->
8    <cfif application.captcha.validateCaptcha(arguments.captchaHash,arguments.captchaText)>
9        <cfset captchaPass = true />
10        <!--- Set the captcha validated cookie to true. It will expire in one minute. --->
11        <cfcookie name="captchaValidated" expires="#dateAdd('n', 1, now())#" value="true">
12
13    <cfelse>
14        <!--- 5b) Note: the captcha will only be validated true one time as the encryption tokens get changed on true. However, the kendo validator validates quickly on blur, so there many be a true value overwritten by a false a millisecond later. We don't want to ever change a true value to false and will use session vars to prevent this behavior. You can override this behavior by setting debugging to true. --->
15        <cfif not debugging and isDefined("cookie.captchaValidated")>
16            <cfset captchaPass = true />
17        <cfelse>
18            <cfset captchaPass = false />
19        </cfif>
20    </cfif>
21
22    <!---Return it.--->
23    <cfreturn captchaPass />
24
25</cffunction>

The HTML is rather simple, the key here are the custom messages are displayed in the 'data-required-msg="Captcha text is required."' and 'data-captcha-msg="The text does not match."'. These tags will pop up the required message when the captcha text has not been filled out, and when the text that the user has entered does not match the text in the captcha image. I am not dealing with any other custom messages here. The rest of the code does not apply, but I am including it for reference.

HTML: 6) The captcha HTML input.

view plain about
1<!-- Captcha -->
2<tr height="35px" class="k-alt">
3    <td>
4        <!--- Captcha logic in its own table. This is a Kendo Mvvm template. --->
5        <div id="captchaImage" class="container k-alt">
6            <table align="left" class="k-alt" width="100%" cellpadding="0" cellspacing="0">
7                <!--- The source refers to the javascript code that will be used to populate the control, the template is the UI and it is not associated with the javascript code. --->
8                <tbody data-bind="source: captchaTextObj" data-template="captchaTemplate" data-visible="true"></tbody>
9            </table>
10            <!--- Create a Kendo template. We will use this to refresh the captcha hash and image on the page.--->
11            <InvalidTag type="text/x-kendo-template" id="captchaTemplate">
12                <tr class='k-alt'>
13                    <td><label for="captchaText">Enter image text:</label></td>
14                </tr>
15                <tr class='k-alt'>
16                    <td>
17                    <input type="hidden" id="captchaHash" name="captchaHash" value="#: captchaHashReference #" />
18                    <!--- 6) Create the captcha input with the custom messages. --->
19                    <input type="text" name="captchaText" id="captchaText" size="6" class="k-textbox" style="width: 250px"
20                        placeholder="Enter Captcha Text" required
21                        data-required-msg="Captcha text is required."
22                        data-captcha-msg="The text does not match." />

23                    </td>
24                </tr>
25                <tr class='k-alt'>
26                    <td>
27                        <img src="#: captchaImageUrl #" alt="Captcha" align="left" vspace="5" border="1" />
28                    </td>
29                </tr>
30                <tr class='k-alt'>
31                    <td>
32                        <button type="button" class="k-button" onClick="reloadCaptcha()">
33                            <i class="fas fa-redo" style="alignment-baseline:middle;"></i>&nbsp;&nbsp;New Captcha
34                        </button>
35                    </td>
36                </tr>    
37            </script>
38        </div><!---<div id="captchaImage" class="container">--->
39    </td>
40</tr>

This entry was posted on March 1, 2019 at 5:34 PM and has received 51 views.

There are currently 0 comments.




Your input and contributions are welcomed!

If you have an idea, BlogCfc based code, or a theme that you have built using this site that you want to share, please contribute by making a post here or share it by contacting us! This community can only thrive if we continue to work together.

Images and Photography:

Gregory Alexander either owns the copyright, or has the rights to use, all images and photographs on the site. If an image is not part of the "Gregory's Blog" open sourced distribution package, and instead is part of a personal blog post or a comment, please contact us and the author of the post or comment to obtain permission if you would like to use a personal image or photograph found on this site.

Credits:

Portions of Gregory's Blog are powered on the server side by BlogCfc, an open source blog developed by Raymond Camden. Revitalizing BlogCfc was a part of my orginal inspiration that prompted me to design this site. Some of the major open source contributers to BlogCfc include:

  1. Peter Farrell: the author of 'Lyla Captcha' that is used on this blog.
  2. Pete Freitag: the author of the 'ColdFish' code formatter that is also used on this blog.