How to Use jQuery UI Sliders and ColdFusion for Scaled Questions

This tutorial will assume that you know what jQuery and jQuery UI are and you know how to get them. We will be using a little bit of ColdFusion as well, so if necessary, apply any other programming language to query, loop, and output data. By scaled questions, I mean, “On a scale of 1 to 5, how much do you agree with the following?” The scale numbers can change, but that is my meaning. My current work in mystery shop and online survey questionnaires involves a lot of these.

Here is the function we will be working with:

<script>
var fnCreateSlider = function(idQuestion,iSliderAnswer,iMin,iMax,steps) {
	var handle = $("#handle-value-" + idQuestion + "");
	var SliderTextObj = $("#slider-text-" + idQuestion + "");
	var SliderValueObj = $("#slider-value-" + idQuestion + "");

	$("#slider-" + idQuestion + "").slider({

		value: SliderAnswer,
		min: iMin,
		max: iMax,
		range: "min",
		create: function() {
			var iSliderVal = $(this).slider("value"); 				
			handle.text(iSliderVal);
			SliderTextObj.text(steps[iSliderVal]);
			SliderValueObj.val(iSliderVal);
		},
		slide: function(event, ui) {
			handle.text(ui.value);
			SliderTextObj.text(steps[ui.value]);
			SliderValueObj.val(ui.value);
		}
	});
}
</script>

We will have multiple questions so that is why we have question ID numbers being passed. The iSliderAnswer will tell us where already existing answers will go. The iMin and iMax variables help us track minimum and maximum variables. As for steps, let’s go over that now.

The scale is not going to be only numbers of, for example, 1 through 5. Each response has actual text to represent what it means, such as:
1 – Strongly Disagree
2 – Disagree
3 – Neither Agree nor Disagree
4 – Agree
5 – Strongly Agree

The “steps” variable is an array of our text responses that will be displayed for each value on the scale. We need to both get and pass that information.

So, on some place before or after the script, here’s our query.

<cfquery name=” qryResp” datasource=”master”>
Select iNumber, vcRespValue, vcRespText, fDisplayOrder
From tblResponse
Where idQuestion = <cfqueryPARAM value="#qryQuestions.idQuestion#" cfsqltype="CF_SQL_INTEGER">
Order by fDisplayOrder
</cfquery>

We still have a lot to take care of before we actually call the fnCreateSlider function.

Query to see if a matching answer already exists. Users page through our questionnaires and have the ability to go back if necessary. Handle that process elsewhere according to the needs of your project and/or application.

<cfquery name=”qryRespMatch” datasource=”master”>
Select iNumber, vcRespValue, vcRespText, fDisplayOrder
From tblResponse
Where idQuestion = <cfqueryPARAM value="#qryQuestions.idQuestion#" cfsqltype="CF_SQL_INTEGER">
and vcRespValue = <cfqueryPARAM value="#PrevAnswer#" cfsqltype="CF_SQL_VARCHAR">
order by fDisplayOrder
</cfquery>

Let’s initialize some parameters for our minimum and maximum.

<cfset iSliderMin = 1>
<cfset iSliderMax = 1>

Now let’s get the real minimum and maximum

<cfloop query="qryResp">
	
<cfif qryResp.iNumber lt iSliderMin >
	<cfset iSliderMin = qryResp.iChoiceNumber>
</cfif>
	
<cfif qryResp.iNumber gt iSliderMax>
	<cfset iSliderMax = qryResp.iChoiceNumber>
</cfif>	
	
</cfloop>

Due to the nature of a slider, we don’t really have a null or blank value. That can be a problem for recognizing an unanswered question. If the default value is 1, which is currently “Strongly Disagree” we want to make sure it is because the user wanted it to be 1 as their answer, not because they forgot or tried to skip a required question.

With no blank option, we can put whatever number we want that isn’t on the real scale we are measuring as the default answer. So my approach to that was to drop the real minimum by 1.

<cfset iSliderMin = iSliderMin – 1>

This tutorial is focused on showing you how to get the sliders to display and work though I felt acknowledging a lack of blank answer important enough to note since I didn’t see it mentioned in my searches for how to handle for it. You can use my post on Passing Form Data from a jQuery Ajax Call to a CFC Function and Returning It to learn about passing form data., which is extremely useful for validating it client-side and before the form is submitted and data saved. In the CFC itself, you’d look up the questions minimum/maximum options, see if the chosen value is between them and if not, send back a message to the user saying they have to pick between x and y (1 and 5 in our case). I have the numbers show on the actual slider itself to make it clear what the user is selecting. Enough about validation, let’s move on.

Our query for responses is actually happening within a query outputting question data. It looks something like this:

<cfquery name="qryQuestions" datasource="master">
Select idQuestion, vcQuestionText, vcFieldName
From tblQuestion
</cfquery>

<cfoutput>
<cfloop query="qryQuestions">
	[response queries are in here]
	[slider div displays in here too]
</cfloop>
</cfoutput>

We have two divs, one for displaying the response text and the other for the actual slider. Additionally, we will have a hidden input value to store the response being entered, which will be useful for tracking previous answers and for validating the value. Here is the HTML and ColdFusion for that:

<div id="slider-text-#qryQuestions.idQuestion#" class="slider-text">
	<cfif qryResp.recordcount>
		#qryResp.vcChoiceText#
	<cfelse>
		No answer
	</cfif>										
</div>

<div id="slider-#qryQuestions.idQuestion #" class="div-slider">
	<div id="handle-value-#qryQuestions.idQuestion #" class="ui-slider-handle"></div>
</div>

<input type="hidden" id="slider-value-#qryQuestions.idQuestion#" name="#qryQuestions.vcFieldName#" value=”#PrevAnswer#”>

<cfset iSliderAnswer = PrevAnswer>

At long last, we create our steps array and call our function. I put “No answer” as the first text response on the scale, as noted earlier to be a value that is not the real scale being measured. Again, I want to make sure our users have to pick something between 1 and 5. In this particular example, the questions always start at 1, so our replacement for a blank answer will be 0. I’m not hard-coding them because in my experience, sometimes the scale starts at 0 instead of 1, and I want it to be available for the day when it starts at some other number too.

<script>
$(function() {
	var steps = [
		"No answer",
		<cfloop query="qryResp">
			"#qryResp.vcRespText #",
		</cfloop>
	];			

fnCreateSlider('#qryQuestions.idQuestion#','#iSliderAnswer#','#iSliderMin#','#iSliderMax#',steps);
});	
</script>

All of these things together will give a slider of scaled responses. To recap and summarize what we did:

  1. Created a Function to create a slider based on variables passed in
  2. Within a looped query of questions:
    1. Queried for response information
    2. Set variables to necessary values to pass based on response query and any previous data
    3. Passed the variables into a call of our function to create a slider.

Working display example (no form submission or validation):

Demo of jQuery UI Sliders and ColdFusion for Scaled Questions


If you appreciate any of the work that went into making this post, please consider giving a tip to my PayPal account:
https://www.paypal.me/sonkitty

Break a List Down into Smaller Lists in ColdFusion

The task at hand is to take a list of 50 emails and separate them into batches of 10.

First, I’m going to make a pretend list so as not to use anyone’s real email addresses:

<cfset EmailList = "">
<cfloop from="1" to="50" index="i">
	<cfset EmailList = ListAppend(EmailList,"sample#NumberFormat(i,"00")#@email.com")>
</cfloop>

The list turns out to be:

sample01@email.com,sample02@email.com,sample03@email.com,sample04@email.com,sample05@email.com,sample06@email.com,sample07@email.com,sample08@email.com,sample09@email.com,sample10@email.com,sample11@email.com,sample12@email.com,sample13@email.com,sample14@email.com,sample15@email.com,sample16@email.com,sample17@email.com,sample18@email.com,sample19@email.com,sample20@email.com,sample21@email.com,sample22@email.com,sample23@email.com,sample24@email.com,sample25@email.com,sample26@email.com,sample27@email.com,sample28@email.com,sample29@email.com,sample30@email.com,sample31@email.com,sample32@email.com,sample33@email.com,sample34@email.com,sample35@email.com,sample36@email.com,sample37@email.com,sample38@email.com,sample39@email.com,sample40@email.com,sample41@email.com,sample42@email.com,sample43@email.com,sample44@email.com,sample45@email.com,sample46@email.com,sample47@email.com,sample48@email.com,sample49@email.com,sample50@email.com

Now then, let’s put our list an array because this post on StackOverflow suggests to do so in case of performance issues. Then we’ll loop through it, note our index and length to help us compile a given smaller list, a batch. Here is the exampe code with documentation:

<!--- Put list in an array. --->
<cfset BatchArray = ListToArray(EmailList)/>

<!--- Initialize current batch--->
<cfset CurrentBatch = "">	

<!--- Note full email list length. --->
<cfset ELLen = ListLen(EmailList)>

<!--- Loop up to length of the list.--->
<cfloop from="1" to="#ListLen(EmailList)#" index="idx">
	
	<!--- Check the list length of the current batch to be less than 10 and that the index is not on the length of the entire list. --->
	<cfif ListLen(CurrentBatch) lt 10 and idx neq ELLen>
		
		<!--- Append current email to current batch. --->
		<cfset CurrentBatch = ListAppend(CurrentBatch,BatchArray[idx])>
	
	<!--- Check if the length of the current batch is 10 or the length of the entire list. --->
	<cfelseif ListLen(CurrentBatch) eq 10 OR idx eq ELLen>
		
		<!--- Check if the index is the same as the list length. Append item. --->
		<cfif idx eq ELLen>		
			<cfset CurrentBatch = ListAppend(CurrentBatch,BatchArray[idx])>
		</cfif>		

		<!--- 
		Do what you want with the desired batch, such as storing it in a table.
		For the purposes of this example, we are simply going to output it. 
		--->
		<cfoutput>#CurrentBatch#</cfoutput><br/>
		<br/>

		<!--- Reset the batch for the next one --->
		<cfset CurrentBatch = "">
		
		<!--- 
		Add first item to the reset list. The last item in the full list
		will go here but not matter since our action is done above.  
		--->
		<cfset CurrentBatch = ListAppend(CurrentBatch,BatchArray[idx])>

	</cfif>
	
</cfloop>

Our output looks like this to show we got the list how we want it:
sample01@email.com,sample02@email.com,sample03@email.com,sample04@email.com,sample05@email.com,sample06@email.com,sample07@email.com,sample08@email.com,sample09@email.com,sample10@email.com

sample11@email.com,sample12@email.com,sample13@email.com,sample14@email.com,sample15@email.com,sample16@email.com,sample17@email.com,sample18@email.com,sample19@email.com,sample20@email.com

sample21@email.com,sample22@email.com,sample23@email.com,sample24@email.com,sample25@email.com,sample26@email.com,sample27@email.com,sample28@email.com,sample29@email.com,sample30@email.com

sample31@email.com,sample32@email.com,sample33@email.com,sample34@email.com,sample35@email.com,sample36@email.com,sample37@email.com,sample38@email.com,sample39@email.com,sample40@email.com

sample41@email.com,sample42@email.com,sample43@email.com,sample44@email.com,sample45@email.com,sample46@email.com,sample47@email.com,sample48@email.com,sample49@email.com,sample50@email.com

Passing Form Data from a jQuery Ajax Call to a CFC Function and Returning It

One of the bigger upgrades I did at my job some years ago was to pass form data using jQuery into a CFC, validating it, and then returning any necessary validations or allowing a user to proceed the next step. I used two functions to prepare the data before sending it to a CFC.

Here are the necessary script files:

<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>

json2.js can be downloaded here:
https://github.com/douglascrockford/JSON-js

The two functions:

<script>
	$.fn.serializeObject = function()
	{
	    var o = {};
	    var a = this.serializeArray();
	    $.each(a, function() {
	        if (o[this.name] !== undefined) {
	            if (!o[this.name].push) {
	                o[this.name] = [o[this.name]];
	            }
	            o[this.name].push(this.value || '');
	        } else {
	            o[this.name] = this.value || '';
	        }
	    });
	    return o;
	};
	
	var PrepJSON = function(frmObj) {
		var o = {};
		o = JSON.stringify(frmObj.serializeObject());
		return o;
	};
</script>

The serializeObject function was found here: https://stackoverflow.com/a/5181003 and the JSON stringify part here: https://stackoverflow.com/questions/1184624/convert-form-data-to-javascript-object-with-jquery?page=1&tab=votes#comment4948246_1184624

With those called earlier in an application, we can now tell a submit button to ready our form data before passing it into a CFC using jQuery’s Ajax call.

<script>
$(function() {
	$("#btnSubmit").click(function() {
		//Create variable to store form object
		var frmObj = $("#myForm");

		//Store json string of form data
		var frmData = PrepJSON(frmObj);

		//Call CFC to process the form.
		$.ajax({
			type: 'POST',
			url: "CFCUpdate.cfc?method=ProcessForm"
			,data: ({ frmData: frmData })
			,success: function(data) {
				//Next step goes here.
			  }
			  ,error: function (xhr, textStatus, errorThrown){}	
		});
		return false;
	});
});
</script>

The CFC itself that we are calling:

<cffunction name="ProcessForm" access="remote" output="false" returntype="string" returnformat="plain">

	<cfargument name="frmData" required="yes" type="string">
	<cfset var formStruct = {} />

	<cfset formStruct = DeserializeJSON(arguments.frmData)>

	<!--- Do any necessary processing here--->

	<cfreturn "OK">
</cffunction>

Make sure you have debugging off so that none of it returns through the CFC string. I put something along the lines of the following in any relevant application.cfm or Application.cfc files.

<cfif LCase(Right(script_name,3)) is "cfc">
	<cfsetting showdebugoutput="no">
</cfif>

At a point in time, I thought I might need to return and update the form data as well and encountered a few issues. Since then, I have actually not had a situation where I need to return the entirety of form data and reflect an update, but since I went through the trouble of learning it and logging it at work, I may as well note it on this website as well for any visitors and as possible reference for if I do ever need it after all.

Get the jQuery Field plugin here: https://pengoworks.com/workshop/jquery/field/field.plugin.htm

Here, you’ll see a few changes from our function earlier that I will highlight:

$(function() {
	$("#btnSubmit").click(function() {
		//Create variable to store form object
		var frmObj = $("#myForm");

		//Store json string of form data
		var frmData = PrepJSON(frmObj);

		//Call CFC to process the form.
		$.ajax({
			type: 'POST',
			url: "CFCUpdate.cfc?method=ProcessFormv1
			,data: ({ frmData: frmData })
			,dataType: "json"
			,success: function(data) {
				frmObj.formHash(data);
			  }
			  ,error: function (xhr, textStatus, errorThrown){}	
		});

		return false;
	});
});

This is the function in our CFC where we will update at least one form value. I am changing the return type and return format from “plain” to “json” to demonstrate a point.

<cffunction name="ProcessFormv1" access="remote" output="false" returnType="struct" returnFormat="json">
	<cfargument name="frmData" required="yes" type="string">
	<cfset var formStruct = {} />

	<cfset formStruct = DeserializeJSON(arguments.frmData)>

	<cfset formStruct.myField1 = "I've been updated!">

	<cfreturn formStruct>
</cffunction>

I do not have ColdFusion for my portfolio web space, so you’re going to have to take my word for the following. The above has a problem. In CF8, it would eliminate leading zeroes from a text input. That seems to be resolved in CF10, but another problem remains. If you put in a considerably long number, such as 1245678901234567890123, when the application puts the form data back, that number would look like 1.245678901234568e+21, and we don’t want that!

The solution I found was the JSONUtil project: http://jsonutil.riaforge.org/. I downloaded the project and put the two CFCs into my CFC directory. With that done, here is an updated function:

<cffunction name="ProcessFormv2" access="remote" output="false" returnType="string" returnFormat="plain">
	<cfargument name="frmData" required="yes" type="string">
	<cfset var formStruct = {} />

	<cfset formStruct = DeserializeJSON(arguments.frmData)>									
	
	<cfset formStruct.myField1 = "I've been updated!">
	
	<cfset JUtil = CreateObject('component','JSONUtil')>
	<cfset formString = JUtil.serializeToJSON(formStruct,"false","true")>			

	<cfreturn formString>
</cffunction>

Other issues that one may encounter though I could not reproduce them at a later point is date formats returning with “\/” as in 11\/\08\/2017 instead of 11/08/2017. You can add the following under the assumption you would never need \/ in a string:

<cfset formString = Replace(formString,"\/","/","ALL")>

If you see a debugging message that says something like “Cannot use ‘in’ operator to…”, this might solve your problem. It happened within the jQueryField plugin.

Find this piece of code in jquery.field.js:

// if we're setting values, set them now
} else if( n in map ){

Change it to:

// if we're setting values, set them now
} else if (map.hasOwnProperty(n)) {

I used that as my solution based on what I found here: Javascript’s hasOwnProperty() Method Is More Consistent Than The IN Operator.

All of these items together allow us to pass form data back and forth as needed and is especially helpful with web application development, such as allowing validation before a form is submitted.

If you appreciate any of the work that went into making this post, please consider giving a tip to my PayPal account or supporting me on Patreon.