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