jQuery/JS How-Tos And Hints

Cache Selectors

When referencing selectors (DOM elements) in jQuery every call to $( something } asks jQuery to rescan for the matching element, wrap it in a jQuery object, and create a new instance of something you already have in memory. You can save client/browser memory by caching the selectors as follows:

var $window = $( window ),
    $document = $( document ),
    $footer = $( '#footer' ),
    $sidebar = $( '#sidebar' ),
    $images = $( 'img' );

Event Listener

jQuery supports different types of listeners (change, submit, onBlur, mouseOut, etc.) and ways of assigning them. We typically utilize change and submit event listeners as users interact with a web page. Assigning a change listener to a DOM element/selector in jQuery is straightforward:

var $car = $(#Car);
$car.change(function () {
	//stuff to be done here
});

Alternatively, the .on() handler can be used to assign the event listener:

var $car = $(#Car);
$car.on("change", function () {
	//stuff to be done here
});

The benefit of the .on() listener is that we can assign multiple events to a function simultaneously:

var $car = $(#Car);
$car.on('keyup keypress blur change', function(e) {
    // e.type is the type of event fired
});

Because .on() can easily (and neatly) assign multiple events to a function, depreciates the previous bind(), live() and delegate() methods, and the fact that it has a corresponding .off() function for removing the event listener, the .on() method is our preferred way of assigning event listeners.

Finally, if you want to trigger the change handler when it is initially loaded, it can be added to the change handler to manually by chaining the .trigger() event handler attachment as so:

var $car = $(#Car);
$car.change(function () {
	//stuff to be done here
}).trigger('change'); 

Form Submission Redirect In Submit Button

Use the HTML "formaction" attribute in the input tag to redirect a form submission from the established action path of the form.

<form action="/path/file.cfm"> <input type="submit" value="Submit"> <input type="submit" value="Go Elsewhere" formaction="/elsewhere"> </form>

Passing JSON to jQuery

When invoking backend cfc functions via Ajax or jQuery post(), it is often easiest to provide a response to the calling method via a JSON string, which is then parsed using the .parse() function into a javascript object. In the following example, the text variable represents what would be returned from the server instead of a static variable.

var $demo-container = $('#demo-container');
var text = '{ "name":"John", "age":"30", "city":"New York"}';
var obj = JSON.parse(text);
obj.age = eval("(" + obj.age + ")");

$demo-container.html() = obj.name + ", " + obj.age();

Posting Data From Multiple Forms

Occasionally there are multiple forms on a page (e.g. a form in Bootstrap modal window and a form in the calling page for the window) and data from elements in the forms needs to be combined and submitted together to a backend cfc function. This can be accomplished by serializing the data in each form and concatenated into a single string:

$('#saveSearchForm').on('submit', function(e) {
	//concatenate form field values
	var formFields = $("#saveSearchForm").serialize() + '&' + $("#candidateFilters").serialize();
	$.post('inc/handlers/saveSearch.cfm',formFields,function(data,status){ 
		//Do stuff here with results returned in data object
	}); 
	return false;
});

Dynamically Populating Divs/Layers

In making pages more interactive, we typical modularize the pages by populating and updating content in various areas on the page dynamically in response to user interaction. This often necessitates the fetching and processing of back-end data. To respond to a user event and update content on the page asynchronously (to avoid an entire page refresh), we make a simple jQuery Ajax call:

function loadOverviewDiv() {
	jQuery.ajax({
		dataType: 'html',
		type: 'POST',
		evalScript: true,
		data: $('##searchFilter').serialize(),
		url: '/resource/ajax/admin/candidateSourcesOverview.cfm',
		success: function (data, textStatus) {
			jQuery('##candidateSourcesOverview').html(data);
		}
	});
};
loadOverviewDiv();

//Where searchFilter is the ID of our form and candidateSourcesOverview
//is the ID of the div we're updating.

We manually call the function at the end to perform the initial content population of the page when the page is loaded (if so desired). If we need any data from a form on the page, that data is serialized and passed to the invoked .cfm file, where it will be available as data elements in the "form" structure. Likewise, we could pass other non-form derived data here as well, such as data from previous Javascript actions.

Preventing Default HTML Form Submit Action

There are times where we want to process a form's submission in Javascript vs. the default handler of submitting the form's content to the file identified in the form tag's "action" parameter. For this, we typically use the preventDefault() JS function as follows:

$('#jobEmailForm').on('submit', function(e) {
	e.preventDefault();
	$.post('inc/handlers/emailJob.cfm',$("#jobEmailForm").serialize(),function(data,status){ 
		if(data == 1) {
			//we successfully forwarded the candidate
			$('#successMessageSent').show(); 
		} else {
			//login failed - post failure and reload login form
			$('#failureMessageSent').show();
		}
	}); 
	return false;
});	

With the above example, we've assigned a submit (onSubmit) handler to the form with an ID of jobEmailForm and our first action was to prevent the default behavior that would result in the page being submitted to itself or an alternate location as specified in the action parameter of the form tag.

It should be noted that the use of preventDefault() stops the default behavior, but doesn't stop the event from "bubbling up" the DOM. Thus, an action like clicking on a button, the click event is also called on all of the parent elements in the DOM. Typically this is not an issue, but if it is (such as if there are listeners for these events, then the stopPropagation() function should be called.

Finally, with respect to preventDefault() and stopPropagation(), it should be noted that return false; can also be called. This will have the same effect as preventDefault() and stopPropagation() used together (the event doesn't trigger its default action and doesn't bubble up the DOM), but also returns execution from the function that it is called in.

The above described action of return false; only applies to when it is used in the context of jQuery (used in a jQuery function). In regular a JavaScript function, it doesn’t have any effect on the default behavior or event propagation of the element.

Miscellaneous Javascript Code

Preventing Form Validation in HTML5

For testing or other purposes, you can add the "novalidate" attribute to a form element to prevent the browser from validating the form by processing required field settings, etc.

Adding and Deleting Values from A String

Occasionally we use hidden form fields to store comma separated values (csv) to record multiple user selections and we need to be able to add and remove values from these comma separated value strings. To do this we use a couple of helper JavaScript functions where we simply pass the list and the desired value to be added or removed. Since JavaScript doesn't natively deal with lists and, instead uses arrays, we convert our lists to arrays and back again.

var removeValue = function(list, value, separator) {
	separator = separator || ",";
	var values = list.split(separator);
	for(var i = 0 ; i < values.length ; i++) {
		if(values[i] == value) {
			values.splice(i, 1);
			return values.join(separator);
		}
	}
	return list;
}
var addValue = function(list, value) {
	if(list.length > 0) {
		var arry = list.split(',');
		arry.push(value);
		list = arry.join();
	} else list = value;
	return list;
}

Simple jQuery Form Submit

Occasionally we need to submit a form in response to a button click, but need to be able to assign the target URL at the same time. This enables use to have different buttons on the form that submit the form's data to different locations. The jQuery .submit() and .attr() functions can be combined to handle this for us. Since this isn't an Ajax or .post() jQuery call, this isn't ansynchronis and is treated just like a regular form submission, where the user's browser is redirected to the specified URL.

$('#myForm').attr('action', '/target/file.cfm').submit();

On Submit and Other Event Listener Failures

We often utilize jQuery to assign event listeners to various DOM elements, such as on click and on submit events as follows:

$(document).ready(function() {
    $('#updateSettingsForm').on('submit', function(e) {
    ....

Occasionally we run into issues where these event listeners fail to function but we receive no javascript errors (no console output) that explains the issue. The problem is generally one of two issues: either the event listener is assigned incorrectly or there is a structural problem with the DOM.

In the case of the event listener being assigned incorrectly, this is typically do to an incorrect selector specification through a formatting or naming issue. For example $('#updateSettingsForm') selects the DOM element with an id value of updateSettingsForm. If that id value isn't the id value of your form element, then the on-submit event listener won't be assigned to the form. Make sure that you're using the correct selector for the correct element.

Structural problems with the DOM can be less obvious. For example, if we assigned the on-submit listener in the example above to the code below, we will get a failure and our submit won't trigger the listener.

<div class="modal-content">
	<input type="hidden" name="recruiterID" value="#session.user.ID#"/>
	<div class="modal-header">
		<button type="button" class="close" data-dismiss="modal">&times;</button>
		<h4 class="modal-title">This is my modal</h4>
	</div>
	<div class="modal-body">
		<div class="row">
			<div class="col-xs-12">
				<p>This is our description of this modal</p>
			</div>
		</div>
		<form name="updateSettingsForm" id="updateSettingsForm" action="" method="post">
			<div class="row">
				<div class="col-xs-12">
					<div class="form-group">
						<label for="candidateSorting">Sort Results By</label>
						<select class="form-control" name="candidateSorting" id="candidateSorting">
							<cfloop query="sortOptions">
								<option value="#SortingOrder#">#SortingOrder#</option>
							</cfloop>
						</select>
					</div>
				</div>
			</div>
		</div>
		<div class="modal-footer">
			<div class="row">
				<div class="col-xs-12" align="center">
					<button type="button" class="btn btn-default" data-dismiss="modal" id="closeBttn">Cancel</button>&nbsp;&nbsp;
					<button type="submit" class="btn btn-primary" id="submitBtn" tabindex="4">Update</button>
				</div>
			</div>
		</div>
	</form>
</div>

It may not be immediately obvious, but the issue here in the example above in an invalid DOM. The browser will likely render this modal to the viewer just fine, but jQuery will have an issue in traversing the DOM due to its invalid structure. In this case, the <form> tag is within the modal's modal-body div, but the associated closing </form> tag is nested outside of the closing div for modal-body. As a result, jQuery will fail to properly assign the event listener and the submit event that should be associated with clicking on the "Update" button will never be detected and no error will be thrown.

Make sure that you are assigning your event to the appropriate selector and that your DOM is valid.

Passing Extra Data to Event Handlers

In supporting the integration of back-end and front-end services, it is often need to have the front-end pass/provide extra information to the back-end function calls, particularly when a given back-end function services multiple front-end tasks. For example, take the example of where we have a dashboard in an application that lists both candidates and jobs for a given user and we want the ability to flag some of those jobs and candidates as favorites. Ideally we would have one back-end function that persists the chosen favorite status in the candidate or job record, but that function will need to know if a candidate or job was selected and, if so, which one. By using data attributes, we can accomplish this.

NOTE: The data-* attribute gives us the ability to embed custom data attributes on all HTML elements. The attribute name should not contain any uppercase letters, and must be at least one character long after the prefix "data-" The attribute value can be any string.

The following code exemplifies looping through candidate and job records, displaying an icon associated with their favorite state (whether the record is a favorite or not). The jobs listing section also exemplifies using the Bootstrap popover() function to display HTML-formatted additional information when mousing over the job title.

In the code below we are assigning the same listener to both classes of displayed favorite icons (favorite and non-favorite) that must determine which state the record is in (favorite or non-favorite) and toggle that state by changing the icon and making a synchronous ajax call to a CFC method handler that will change the db record. It is noteworthy here that we are not using two separate functions, assigning a listener for one to the non-favorite icon class (.favorite-deselected) and another to the favorite class (.favorite-selected). This is because these listeners are assigned to elements when the DOM is created, so as we toggle the status from favorite/non-favorite, the listener will 'remember' the initial state of the element and always treat it as it was originally. Thus we assign the same function to both conditions and let the listener determine the favorite state of the element by reading the data-value attribute (which we toggle along with the icon class on each click).

Finally it should be noted in the ajax call in the sample script below, we're making the call asynchronous. We're doing this because we want to return status from the CFC method call (via the handler) that indicates if the toggling of the favorite status is successful or not. We're not going to display an error message if there is a failure, but we're also not going to toggle the icon status on the screen, falsely leading the user to think that the favorite status was changed when it wasn't.

<script>
	$(document).ready(function() {
		//ajax call to set candidate as non-favorite and toggle status display
		$(".favorite-selected, .favorite-deselected").on('click', function () {
			var $this = $(this);//cache object for use in callback
			//invert current value for toggle effect
			var setFavValue = 1 - $(this).data('value');
			$(this).data('value',setFavValue);
			/* update db via synchronous call so that failure/success can be used to trigger 
				icon change*/
			$.ajax({
				type: 'POST',
				url: '/resource/modals/handlers/dashboard/putFavoriteStatus.cfm',
				data: {ID: $(this).data('id'),
					recruiterID: '#session.user.ID#',
					favorite: setFavValue, 
					type: $(this).data('favtype')},
				success: function(data) {
					if(data == 1) {
						//db record was successfully updated - change the displayed status
						$this.toggleClass('favorite-selected favorite-deselected');
					}
				},
				async:false
			});
		});
		//initialize bootstrap popover listener
		$(function () {
			$('[data-toggle="popover"]').popover()
		})
	});
</script>

<cfloop query="candidates">
	<cfif favorite EQ 1>
		<cfset favoriteIcon = "favorite-selected"/>
	<cfelse>
		<cfset favoriteIcon = "favorite-deselected"/>
	</cfif>
	<tr>
		<td><a href="[link to candidate profile]">#FirstName# #LastName#</a></td>
		<td>#Title#</td>
		<td>#city#, #state#</td>
		<td nowrap>
			<i class="new-icon"></i>
			<i class="#favoriteIcon#" 
				data-id="#JobSeekerID#" 
				data-favtype="candidate" 
				id="cand#JobseekerID#"></i>
		</td>
	</tr>
</cfloop>
	
<cfloop query="jobs">
	<cfif favorite EQ 1>
		<cfset favoriteIcon = "favorite-selected"/>
		<cfset currentValue = 1/>
	<cfelse>
		<cfset favoriteIcon = "favorite-deselected"/>
		<cfset currentValue = 0/>
	</cfif>
	<tr>
		<td><a href="[link to job details]" 
			   data-title="Job Activity" data-trigger="hover" 
			   data-toggle="popover" data-placement="top" 
			   data-html="true" data-content="<ul>
					<li><strong>Views:</strong> #Views#</li>
					<li><strong>Reg. Applications:</strong> #NumOfApplicants#</li>
					<li><strong>Unreg. Applications:</strong> #NumOfUnregApplicants#</li>
					</ul>">#PositionTitle#</a>
		</td>
		<td>#Region1Name#, #Region3Name#</td>
		<td nowrap>
			<i class="new-icon"></i>
			<i class="hot-icon"></i>
			<i class="#favoriteIcon#" 
				data-id="#JobID#" 
				data-value="#currentValue#" 
				id="job#JobID#" 
				data-favtype="job"></i>
		</td>
	</tr>
</cfloop>

CSS formatting is needed to handle the displaying of the favorite status:

CSS Classes for favorite icon
i.favorite-selected {
    content: url(http://[link to favorite image]/Favorite-Full.svg);
	padding: 0px 2px;
}
i.favorite-deselected {
    content: url(http://[link to non-favorite image]/Favorite-Empty.svg);
	padding: 0px 2px;
}
i.favorite-selected:hover,i.favorite-deselected:hover  {
	cursor: pointer;
}

The jQuery data() function is used to retrieve the extra data attribute fields added to the icons. In this scenario the favorite icon has data-id and data-favtype attributes added. These don't affect the HTML/browser display, but allow us to pass the data elements back to the on-click event listener that can then, in turn, pass them back to the back-end cfc functions via a jQuery post() call. The data-id attribute passes the record ID of the candidate or job and the data-favtype attribute passes a string to indicate if the record is a job or candidate record. This data is then serialized into the post feed as form fields with the following code:

Serialized data for CFC method handler
{ID: $(this).data('id'),
    recruiterID: '#session.user.ID#',
    favorite: setFavValue,
    type: $(this).data('favtype')
});

NOTE: in the above code the var setFavValue is the value (true or false) that we're passing to the handler for the CFC function to update the db record. We obtain this value by taking the current favorite value of the record and inverting it by subtracting it from one (1). So, if it was zero before, the results will be one and if it was one before, the result will be zero.

Finally a handler is used to call the CFC function as follows:

putFacoriteStatus.cfm
<cfscript>
	setting enablecfoutputonly=true;
	dashboardObj = createObject("component", "objects.dashboard2019");
	results = dashboardObj.putFavoriteStatus(argumentcollection=form);
	//send status back to calling function
	if(results == 1) writeOutput(1);
	else writeOutput(0);
</cfscript>

The handler code shown above echos the status of the putFavoriteStatus() method call back to the synchronous ajax success function so we can toggle the display of the favorite icon if the CFC method call was successful and the database was successfully updated with the user-selected status. This handler is used for updating the favorite status of candidates, jobs, etc. by calling a single CFC method that uses the data-type value (passed as a form field in the ajax call to the handler via the serialized data) to the CFC method where the controller can determine the correct SQL calls to make to update the correct tables and records.

NOTE: We have used a handler to call CFC putFavoriteStatus() instead of simply declaring putFavoriteStatus() a remote function and calling it from our JavaScript directly. This is primarily for security purposes, allowing us to eventually add some additional security to putFavoriteStatus.cfm before calling the function. As this simply changes the favorite status on a database record and otherwise doesn't allow modifying or deleting the record.

Monitoring a Form for Changes and Resetting Values on Error

We utilize some forms that don't incorporate a traditional 'submit' button. Instead, we use the jQuery onChange handler to listen for any changes to any of the form elements and then pass them to a controller (call back-end services) to process each change as it is made. Occasionally a selection can produce an error in the form of a conflict with another setting on the form. When possible we attempt to capture these conflicts in the view and handle conflict detection and resolution through the local Javascript. However, sometimes the conflict is only discoverable by the controller as it examines additional information related to the change (e.g. current database values). In these cases we need the ability to not only display the error message, but also reset the last changed form field back to it's original value.

To 'reset' a form field back to its original value, we utilize jQuery to store initial values in 'data' attributes ('data-initialValue') so we can then retrieve the value if we make a change to the related DOM element that produces a controller error. Here is some example code of a form being loaded into a modal where we desire the ability to reset form fields of the modal:

$(document).ready(function() {
	//load modal when user clicks on 'Settings' button
	$('##dashboardSettings').on('click', function() {
		$('##configurator-container').modal({show:true});
		//save initial DOM element values so they can be 'reset' if user makes a change that produces an error
		$(':input').each(function() {
			$(this).data('initialValue', $(this).val());
		});
	});
	$('##updateDashboardSettingsForm').on('change', function(e) {
		e.preventDefault;
		var formFields = $("##updateDashboardSettingsForm").serialize();
		$.post('/resource/modals/handlers/dashboard/putDashboardSettings.cfm',formFields,function(data,status){ 
			if(data == 1) {
				//we successfully update the settings
				//reload initial DOM values w/ current value into data fields
				$(':input').each(function() {
					$(this).data('initialValue', $(this).val());
				});
				//hide any error message, display success message for 2 seconds w/fade
				$('##failureMessageDashboardModal').hide();
				$('##successMessageDashboardModal').fadeIn('fast');
				setTimeout(function() {
					//auto-clear success message as user may be making multiple updates
					$('##successMessageDashboardModal').fadeOut('fast');
				}, 2000);
			} else {
				//message send failure - display error, disable submit, and relable cancel button
				$('##failureMessageDashboardModal').show();
				$('##errorMsgDashboard').html('<strong>ERROR: </strong>'+data);
				//reset the offending value back to its original value to reject change
				$(':input').each(function () {
					if($(this).data('initialValue') != $(this).val()){
						$(this).val($(this).data('initialValue'));
					}
				});
			}
		}); 
		return false;
	});
});

The code shown in the snipped below is used to loop through all of the form fields, create a 'data-initialValue' attribute, and then store the current value of the DOM element into the new attribute:

$(':input').each(function() {
    $(this).data('initialValue', $(this).val());
});

Every time a change is made (as recorded by the $('##updateDashboardSettingsForm').on('change', function(e) {} code), we serialize the form data and submit it to the controller, referenced in the example code as putDashboardSettings.cfm. If there is no error, then we display a success message and reload the data-initialValue attributes again with the current values by re-running the above code snippet. However, if there is an error, we execute a variation of this script that looks for any form field (DOM element) that has a current value that is different than the data-initialValue attribute recorded value. Where there is a difference, the value is reset to the stored attribute value using the following code:

//reset the offending value back to its original value to reject change
$(':input').each(function () {
	if($(this).data('initialValue') != $(this).val()){
		$(this).val($(this).data('initialValue'));
	}
});

NOTE: Credit goes to Dustin Martin (and others) that created a portion of this script for a solution to being able to display a warning when trying to leave a webpage if they had entered any information on the form w/o saving/submitting it. https://www.dustinmartin.net/monitoring-a-form-for-changes-with-jquery/

Enabling Autocomplete to return different data than displayed

jQuery provides a nifty feature called autocomplete that allows a user to start typing in a text field with autocomplete suggesting the finalized result of what they are trying to enter. This is similar to the suggestions Google makes as you type in a search expression. Autocomplete can be configured to return a different amount of matching suggestions and the suggestions can be pulled from server side code using Ajax. Since autocomplete is used on a normal text input, the data submitted in a form submission will be the data in the text field, but sometimes this is undesirable. For example, then autocomplete is used to complete a geographic location a user is entering, the desired value to be submitted in the form may be the unique ID value associated with the location instead of the location string.

Several method are available to handle this situation, but one of the easiest ways to create a hidden form field to pass the desired information in as part of the form submission. The following code exemplifies this solution:

$(document).ready(function(){ 	
	$( "##hint" ).each(function() {
		var autoCompelteElement = this;
		var formElementName = $(this).attr('name');
		var hiddenElementID  = formElementName + '_autocomplete_hidden';
		// change name of orig input 
		$(this).attr('name', formElementName + '_autocomplete_label');
		// create new hidden input with name of orig input 
		$(this).after("<input type=\"hidden\" name=\"" + formElementName + "\" id=\"" + hiddenElementID + "\" />");
		//now assign the autocomplete function to this field
		$(this).autocomplete({
			source:function( request, response ) {
				$.ajax({
					url: "/objects/locations.cfc?method=getAutoCompleteLocations&location="+request.term+"&returnFormat=JSON&locationPrioritization=#dashboardPreferences.LocationPrioritization#",
					dataType: "json",
					success: function(data) {response(data);}
				});
			}, 
			select: function(event, ui) {
				var selectedObj = ui.item;
				$(autoCompelteElement).val(selectedObj.label);
				$('##'+hiddenElementID).val(selectedObj.value);
				return false;
			}
		});
	});
}); 

The associated HTML code is as follows:

<input type="text" class="form-control" 
id="hint" data-dbid="" name="location" placeholder="location">

In this example, the getAutoCompleteLocations() function is called in the associated locations.cfc file to perform the location lookup in the database, based on the data provided by the user and passed in the location argument/parameter. Separately and previously a lookup of the user preferences is performed to determine what order they want the locations ordered by and is passed to this same function in the locationPrioritization argument/parameter.

Event though we are using only one autocomplete input field and the jQuery selector reference the field using the element ID, the jQuery each() function is used to assign the autocomplete functionality so that this could be expanded to additional fields on the page using a class or other selector reference in place of the element ID selector.

Example CF code for the getAutoCompleteLocations() function:

remote function getAutoCompleteLocations(string location='',locationPrioritization=''){
	var list = arguments.location;
	var returnArray = arrayNew(1);
	if(!len(list)) return returnArray ;

	try {
		if(listlen(list) == 1) {
			if(trim(arguments.location) == 'US' OR trim(arguments.location) == 'USA') {
				var formatStruct = structNew();
				formatStruct["label"] = 'USA - All States';
				formatStruct["value"] = 0;
				arrayAppend(returnArray,formatStruct);
				return returnArray;
			}
			var sqlString 	= "SELECT TOP(10) ID, postalCode, placeName, admin1_state, admin1_state_abb, Country_Name  
						FROM postalCodes WHERE postalCode LIKE :pLocation OR placeName LIKE :pLocation OR admin1_State LIKE :pLocation ORDER BY CASE WHEN countrycode = :pLocationPrioritization THEN 1 ELSE 2 END";
			queryObj = new Query(
				name="qryGet", 
				datasource="LocationsDatabase",
				sql = sqlString
			);
			list = trim(replace(list,",","","all"));
			queryObj.addParam(name="pLocation",value='#list#%', cfsqltype="cf_sql_varchar");
			queryObj.addParam(name="pLocation",value='#list#%', cfsqltype="cf_sql_varchar");
			queryObj.addParam(name="pLocation",value='#list#%', cfsqltype="cf_sql_varchar");
			queryObj.addParam(name="pLocationPrioritization",value="#arguments.locationPrioritization#", cfsqltype="cf_sql_varchar");
			queryResults = queryObj.execute().getResult();
		} else if(listlen(list) > 1){
			var sqlString 	= "SELECT TOP(10) ID, postalCode, placeName, admin1_state, admin1_state_abb, Country_Name  
						FROM postalCodes WHERE (placeName LIKE :pLocation1 AND admin1_State LIKE :pLocation2) OR (admin1_State LIKE :pLocation1 AND Country_Name LIKE :pLocation2) ORDER BY CASE WHEN countrycode = :pLocationPrioritization THEN 1 ELSE 2 END";
			queryObj = new Query(
				name="qryGet", 
				datasource="LocationsDatabase",
				sql = sqlString
			);
			var location1 = trim(listGetAt(list,1));
			var location2 = trim(listGetAt(list,2));
			queryObj.addParam(name="pLocation1",value='#location1#%', cfsqltype="cf_sql_varchar");
			queryObj.addParam(name="pLocation1",value='#location1#%', cfsqltype="cf_sql_varchar");
			queryObj.addParam(name="pLocation2",value='#location2#%', cfsqltype="cf_sql_varchar");
			queryObj.addParam(name="pLocation2",value='#location2#%', cfsqltype="cf_sql_varchar");
			queryObj.addParam(name="pLocationPrioritization",value="#arguments.locationPrioritization#", cfsqltype="cf_sql_varchar");
			queryResults = queryObj.execute().getResult();
		} 

		//Determine if there are multiple-countries (if so, we need to add the country to the location)
		var country = '';
		var multipleCountries = false;
		for(location IN queryResults){
			if(len(country)) {
				if(country != location.Country_Name) {
					multipleCountries = true;
					break;
				}
			} else country = location.Country_Name;
		}

		//Format return results
		for(location IN queryResults){
			var formatStruct = structNew();
			//display with state abbreviation if there is one
			if(len(location.admin1_state_abb))
				locationString = location.placeName & ", " & location.admin1_state_abb;
			else 
				locationString = location.placeName & ", " & location.admin1_state;
			if(len(location.postalCode)) locationString &= " (#location.postalCode#)";
			if(multipleCountries) locationString &= " - #location.Country_Name#";
			formatStruct["label"] = locationString;
			formatStruct["value"] = location.ID;
			arrayAppend(returnArray,formatStruct);
		}
		return returnArray;
	} catch (any e)  {
		return returnArray;
	}
}

Dynamically Adding Elements

This is an example of how to dynamically add rows to a table (the table is predefined in the HTML simply as <table id="relocations"><tbody></tbody></table>).

$(document).ready(function(){ 
    ...
    var rowID = 0;
    $('##addLocation').on('click',function() {
        rowID++;
        rowContent = 'This is my text';
        $('##relocations > tbody:first').append('<tr data-relo="'+rowID+'"><td>'+rowContent+'</td><td><i class="fa fa-minus-circle" aria-hidden="true"></i></td></tr>');
    });
    ...
});

Removing Dynamically Added Elements

In continuing the above example, it is worth exploring how to delete a dynamically added element; in this case, a dynamically added table row. To perform this function, an icon is added in a table sell (shown in the above javascript) that jQuery assigns a delegated event listener to based on the icon style, "fa-minus-circule". For more on event delegation selectors, see "Assigning Selector Listeners to Dynamically Added Elements" later in this chapter.

$(document).ready(function(){ 
    ...
    $(document).on("click", ".fa-minus-circle", function()	{
        
    });
    ...
});

Assigning Select Listeners to Dynamically Added Elements

Using the traditional selectors for listeners with the default assignment as shown below will not work for elements that are added to the DOM after the DOM has been created and the document.ready() event has been triggered. For example, the code below will attempt to display the data-relo attribute value to the parent <TR> element when an icon in a <TD> cell with a style of ".fa-minue-circle" is clicked on. If the table and the icon exist at the time the code below executes, then the code will work, but if the icon is added dynamically by JS after the DOM has rendered, then the on click event assignment will fail to bind.

$(document).ready(function(){ 
    ...
    $('.fa-minus-circle).on('click',function(){
        alert($(this).closest('tr').data('relo'));
    });
    ...
});

To resolve this issue we need to use event delegation: the on click event assignment for the style needs to be bound to the style at the document delegate so the DOM will react to the associated selector for matching elements that exist when the DOM is created, or are added afterwards dynamically. The above code would be re-written as follows to implement event delegation:

$(document).ready(function(){ 
    ...
    $(document).on("click", ".fa-minus-circle", function()	{
        alert($(this).closest('tr').data('relo'));
    });
    ...
});

Last updated

Was this helpful?