Gregory's Blog

How to make a round Kendo UI button

How to make a round button with Kendo UI

Background

There are plenty of posts on the web showing you how to make a typical square button with Kendo UI, but I have not seen a post describing how to make a round Kendo button. Since there are no other posts that cover this, I had to learn this through trial and error. If you're using Kendo, it is a good idea to try to use as many native Kendo widgets as possible as these widgets will be incorporated into the chosen Kendo theme. The reasons for this are simple, native Kendo widgets will perform and look the same and inherit the properties of the selected theme.

Border Radius 50% can make nearly every element round, including a normal Kendo button.

Using the border-radius: 50%CSS can make nearly any HTML into a circle. Indeed, it can also make a normal Kendo UI button into a circle. However, on the desktop, Kendo UI's button also has an outline around its buttons, and the bottom part of the outline is larger than the upper part making the button look weird. Let's take a look:

#primaryTextButton {
	height: 35px;
	width: 35px;
	border-radius: 50%;
}
<button id="primaryTextButton" class="k-primary">i</button> looks OK with mobile- but not so good on the desktop.

This button's outline looks weird. It only gets worse when the button becomes smaller.

A different approach

The code below is one method to make a round Kendo UI button widget using inline code. The element can be anything. Here I am using the button to expand the comments for Galaxie Blog, so I am using the id of comment Control. The collapse class is also not important here. What is important is the k-i-sort-desc-sm class, and k-primary. The k-i-sort-desc-sm is the Kendo UI icon that is being displayed, and the k-primary class indicates that the color of the button must be the primary color of the selected theme. The width and height of the button are set by the width and height property, and the border-radius: 50%argument takes the width and height properties to make a circular button image.

<span id="commentControl" class="collapse k-icon k-i-sort-desc-sm k-primary" style="width: 35px; height:35px; border-radius: 50%;"></span>

This Kendo button takes on the primary color of the selected theme, and without the funny outline, it looks much better!

This entry was posted on December 13, 2019 at 4:24 PM and has received 1976 views.

How to add additional descriptive elements to an element with 'data-'


While coding logic for a Kendo tooltip, I had to send both the anchor's title and other information that the Kendo tooltip would display. I wanted to display the location where the image was taken, and a description of the image like this: "Grand Prismatic Spring, Yellowstone National Park. The vibrant colors of this spring is best captured from over-head. Wouldn't it be cool to fly a drone over this and take a few pictures?" I wanted both elements to be separated with a horizontal rule, and I needed to isolate the location and the description. However, the anchor tag only has a 'title' and an alt tag to store this information. If you want to store additional information in an element does not support, you can easily use the 'data-' + name prefix like so:

<span title="Grand Prismatic Spring, Yellowstone National Park." data-desc="The vibrant colors of this spring is best captured from over-head. Wouldn't it be cool to fly a drone over this and take a few pictures?">
</span>

To get the information that the data element contains, in this case, a Kendo template, use the data- prefix. You can name the prefix anything you want, and within the javascript template, don't need to specify the actual data tag- just leave it blank but name the variable after the 'data-' element (see '#=target.data('desc')#: below).

<!--- Kendo tooltip template--->
<script id="aboutTemplate" type="text/x-kendo-template">
	<div class="template-wrapper">
		<h3> #=target.data('title')# </h3>
		<p>#=target.data('desc')#</p>
	</div>
</script>

This is a neat way to store additional data into HTML elements, such as a span tag, or any other element as well.

This entry was posted on March 29, 2019 at 4:16 PM and has received 620 views.

Kendo Server Side Validation


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, lie in Javascript. This script is heavily commented on. 

$(document).ready(function() {
	
	// Validation.
	// Preset our sessionStorage var. This is set to '' initially to indicate that server side validation has not yet occurred.
	sessionStorage.setItem("captchaValidated", "");
	// 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.
	sessionStorage.setItem("captchaValidatedValue", "");
	// 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.
	sessionStorage.setItem("captchaValidatedAttempts", "0");

	// 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.
	var addCommentSubmit = $('#addCommentSubmit');
	addCommentSubmit.on('click', function(e){      
		// Prevent any other action.
		e.preventDefault();   
		// Set the attempts var to 0
		sessionStorage.setItem("captchaValidatedAttempts", 0);
		// 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. 
		// If the form has been successfully validated.
		if (addCommentFormValidator.validate()) {
			// Submit the form. We need to have a quick timeout function as the captcha resonse does not come back for 150 milliseconds.
			setTimeout(function () {
				// Note: when testing the ui validator, comment out the post line below. It will only validate and not actually do anything when you post.
				postCommentSubscribe(<cfoutput>'#URL.Id#'</cfoutput>, <cfoutput>'#URL.uiElement#'</cfoutput>);
			}, 300);//..setTimeout(function () {
		}//..if (addCommentFormValidator.validate()) {
	});//..addCommentSubmit.on('click', function(e){ 

	// !!! 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. --->
	addCommentFormValidator = $("#addCommentSubscribe").kendoValidator({
		// Set up custom validation rules 
		rules: {
			// Name of custom rule. 
			// 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.
			// 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.
			captcha: 
				function(input) {
					if (input.is("[id='captchaText']")){
						// 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.
						var captchaValidated = getCapthchaValidated();

						// If the captcha has not been validated on the server...
						if (captchaValidated == ''){
							// Check the captcha
							captchaText.check(input);
							// And stop...
							return false;
						}

						// If the server validation failed, try again...
						if (captchaValidated == 'no'){
							// Check the captcha
							captchaText.check(input);
							// And stop...
							return false;
						}	

						if (captchaValidated == 'yes'){
							// The captha text was succuessfully validated. Exit this function. 
							return true;
						}
					}//..if (input.is("[id='captchaText']")){
					// This rule does not apply to the captha text input.
					return true;
				}//..function(input) {
			}
		//..captcha:
	}).data("kendoValidator");

	// Create a variable for this function as we will use the properties in the captch validation function above when it returns results.
	var captchaText = {
		check: function(element) {

			// 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.
			// 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.
			if (element.val() != getCapthchaValidatedValue() || getCaptchaValidatedAttempts() &lt;= 5){
				// Post to the server side method that will validate the captcha text.
				$.ajax({
					url: "<cfoutput>#application.proxyController#</cfoutput>?method=validateCaptcha",
					dataType: 'json', // Use json for same domain posts. Use jsonp for crossdomain. 
					data: { 
						// Send in the arguments.
						captchaText: element.val(), 
						captchaHash: $( "#captchaHash" ).val()
					},
					success: function(data) { // The `data` object is a boolean value that is returned from the server.
						var captchaValidated = getCapthchaValidated();
						if (data){
							// debugging alert('Yes!');
							// Set the value on the cache object so that it can be referenced in the next validation run. Note: sessionStorage can only store strings.
							sessionStorage.setItem("captchaValidated", "yes");
							// 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.
							if (addCommentFormValidator.validate()) {
								// Hide the custom window message
								kendo.ui.ExtAlertDialog.hide;
								// submit the form. We need to have a quick timeout function as the captcha resonse does not come back for 150 milliseconds.
								setTimeout(function () {
									// Note: when testing the ui validator, comment out the post line below. It will only validate and not actually do anything when you post.
									postCommentSubscribe(<cfoutput>'#URL.Id#'</cfoutput>, <cfoutput>'#URL.uiElement#'</cfoutput>);
								}, 300);//..setTimeout(function () {
							}
						} else {
							// Get the number of validation attempts.
							var captchaValidatedAttempts = getCaptchaValidatedAttempts();
							// Increment the validation attempt.
							var currentCaptchaValidatedAttempt = (captchaValidatedAttempts + 1);
							// Store the number of validation attempts in sessionStorage.
							sessionStorage.setItem("captchaValidatedAttempts", currentCaptchaValidatedAttempt);
							// 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.
							if (currentCaptchaValidatedAttempt  == 6){
								// Store that we tried to validate, but it was not correct.
								sessionStorage.setItem("captchaValidated", "no");
								// Load a new captcha image (this is my own custom requirement and it has no bearing to the validator logic).
								reloadCaptcha();
								// Popup an error message.
								setTimeout(function() {
									if (getCapthchaValidated() == 'no'){
										// Note: this is a custom library that I am using. The ExtAlertDialog is not a part of Kendo but an extension.
										$.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
											).done(function () {
											// Do nothing
										});//..$.when(kendo.ui.ExtAlertDialog.show...
									}//..if (addCommentFormValidator.validate()) {
								}, 500);// A half of a second should allow the server to validate the captcha and return the result.
							}
						}
						// 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.
						sessionStorage.setItem("captchaValidatedValue", element.val());
						// 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.
						setTimeout(function() {
							addCommentFormValidator.validate();
						}, 2000);// Wait 2 seconds to hit the server again.
					}//..success: function(data) {
					// 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.

				});//..$.ajax({
			}//..if (element.val() != getCapthchaValidatedValue()){
		}//..check: function(element, settings) {
	};//..var captchaText = {

});//...document.ready

// Validation helper functions. These must be oustide of the document ready block in order to work.
// 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. 

// 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.
function getCapthchaValidated(){
	return sessionStorage.getItem("captchaValidated");
}

// Prior to validation, what did the user enter?
function getCapthchaValidatedValue(){
	// Since sessionStorage only stores strings reliably, this will be either: '', 'no', or 'yes'.
	return sessionStorage.getItem("captchaValidatedValue");
}

// 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).
function getCaptchaValidatedAttempts(){
	var attemps = sessionStorage.getItem("captchaValidatedAttempts");
	return(parseInt(attemps));
}

Server-side ColdFusion:

5) The server-side logic 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 to overwrite our value. We are using a server-side cookie to ensure that this does not happen.

<!--- 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.--->
<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.">
	<cfargument name="captchaText" required="yes" hint="What did the user enter into the form?" />
	<cfargument name="captchaHash" required="yes" hint="The hashed value of the proper answer. This must match the captcha text in order to pass true." />
	<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." />

	<!---5a) Does the text that the user entered match the hashed value?--->
	<cfif application.captcha.validateCaptcha(arguments.captchaHash,arguments.captchaText)>
		<cfset captchaPass  = true />
		<!--- Set the captcha validated cookie to true. It will expire in one minute. --->
		<cfcookie name="captchaValidated" expires="#dateAdd('n', 1, now())#" value="true">

	<cfelse>
		<!--- 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. --->
		<cfif not debugging and isDefined("cookie.captchaValidated")>
			<cfset captchaPass  = true />
		<cfelse>
			<cfset captchaPass  = false />
		</cfif>
	</cfif>

	<!---Return it.--->
	<cfreturn captchaPass />

</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.

<!-- Captcha -->
<tr height="35px" class="k-alt">
	<td>
		<!--- Captcha logic in its own table. This is a Kendo Mvvm template.  --->
		<div id="captchaImage" class="container k-alt">
			<table align="left" class="k-alt" width="100%" cellpadding="0" cellspacing="0">
				<!--- 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. --->
				<tbody data-bind="source: captchaTextObj" data-template="captchaTemplate" data-visible="true"></tbody>
			</table>
			<!--- Create a Kendo template. We will use this to refresh the captcha hash and image on the page.--->
			<InvalidTag type="text/x-kendo-template" id="captchaTemplate">
				<tr class='k-alt'>
					<td><label for="captchaText">Enter image text:</label></td>
				</tr>
				<tr class='k-alt'>
					<td>
					<input type="hidden" id="captchaHash" name="captchaHash" value="#: captchaHashReference #" />
					<!--- 6) Create the captcha input with the custom messages. --->
					<input type="text" name="captchaText" id="captchaText" size="6" class="k-textbox" style="width: 250px" 
						placeholder="Enter Captcha Text" required 
						data-required-msg="Captcha text is required." 
						data-captcha-msg="The text does not match." />
					</td>
				</tr>
				<tr class='k-alt'>
					<td>
						<img src="#: captchaImageUrl #" alt="Captcha" align="left" vspace="5" border="1" />
					</td>
				</tr>
				<tr class='k-alt'>
					<td>
						<button type="button" class="k-button" onClick="reloadCaptcha()">
							<i class="fas fa-redo" style="alignment-baseline:middle;"></i>&nbsp;&nbsp;New Captcha
						</button>
					</td>
				</tr>	
			</script>
		</div><!---<div id="captchaImage" class="container">--->
	</td>
</tr>

 

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




Footer Logo

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 "Galaxie 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 Galaxie 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.

Version:

Galaxie Blog Version 3.0 (Toby's Edition) June 14th 2022 Blue Wave theme