Gregory's Blog
long meetins stink

ColdFusion Like JavaScript Functions


While writing an article with complex JavaScript for my next Kendo UI article, I noticed that I used some ColdFusion-like JavaScript functions to compare comma-separated lists and realized that I should first share the code. I don't want to confuse my readers and have them wonder where this JavaScript function came from or to confuse the reader by thinking that a snippet may be ColdFusion code.

I have collected these ColdFusion-like JavaScript functions over the last 20 years. I am not sure where some of these originated from- or if I wrote them myself. I believe that a few of these scripts may have originated from an old repository of JavaScripts tailored for the ColdFusion community, but I have not found this source for the last several years. I wrote or modified many of these scripts, and some of these are based on other ColdFusion custom functions and have tried my best to recognize the original source. 

If you have any functions to add or suggest revisions, please let me know!



replaceNoCase JavaScript Function

I wrote this to replicate the replaceNoCase ColdFusion function. This replaces the occurrences of substring1 with substring2 in the specified scope and is case insensitive. The scope is either 'one' or 'all' and defaults to one if not supplied.

If you want to use a case sensitive replace function, remove the 'i' flag

Example: replaceNoCase('ColdFusion is the best server-side language','ColdFusion','Lucee', 'all')

This will substitute Lucee for ColdFusion in the string 'ColdFusion is the best server-side language' and return 'Lucee is the best server-side language'.

I am not trying to degrade ACF in any way (I don't yet use Lucee), but hopefully, the Lucee fans may get a kick out of this!

// Gregory Alexander <www.gregoryalexander.com>
function replaceNoCase(string, subString, replacement, scope){
	if(scope == null) { scope = 'one'; }
	if (scope == 'all'){
		// i is a RegEx ignore case flag, g is global flag
		var regEx = new RegExp(subString, "ig");
	} else {
		// i is an RegEx ignore case flag
		var regEx = new RegExp(subString, "i");
	}
	// i is an ignore case flag, g is global flag
	var regEx = new RegExp(subString, "ig");
	var result = string.replace(regEx, replacement);
	return result;
}

listLen JavaScript function

This is a simple function that provides the length of a list and replicates the listLen ColdFusion function.

Usage: listLen(list [, delimiters ])

Example: listLen('Mount Rainier National Park,Olympic National Park,North Cascades National Park');

This will return the numeric value of 3 representing the number of National Parks in Washington State.

function listLen(list, delimiter){
	// Gregory Alexander <www.gregoryalexander.com>
	if(delimiter == null) { delimiter = ','; }
	var thisLen = list.split(delimiter);
	return thisLen.length;
}

listGetAt JavaScript Function

Gets a list value found at a certain position in a list. This should be identical to the ColdFusion listGetAt function.

Usage: listGetAt(list, position [, delimiters])

Example: listGetAt('Arches National Park,Bryce Canyon National Park,Canyonlands National Park,Capitol Reef National Park,Zion National Park', 1, ',');

This will return 'Arches National Park' which is the first element in the list. 

function listGetAt(list, position, delimiter) {
	// Gregory Alexander <www.gregoryalexander.com>
	if(delimiter == null) { delimiter = ','; }
	list = list.split(delimiter);
	if(list.length > position) {
		return list[position-1];
	} else {
		return 0;
	}
}

listFind JavaScript Function

Like the listFind ColdFusion function, this will return the index position in a list if finds the value, or return zero if nothing was found. I am not sure of the original authorship. The search is case-sensitive.

Usage: ListFind(list, value [, delimiters ])

Example: listFind('1,2,3,4,5,6','5');

This example will return a 5 as it is the 5th index in the list.

function listFind(list, value, delimiter) {
	// Adapted from a variety of sources by Gregory Alexander <www.gregoryalexander.com>

	var result = 0;
	if(delimiter == null) delimiter = ',';
	list = list.split(delimiter);
	for ( var i = 0; i < list.length; i++ ) {
		if ( value == list[i] ) {
			result = i + 1;
			return result;
		}
	}
	return result;
}

See https://copyprogramming.com/howto/what-is-the-fastest-implementation-of-coldfusion-s-listfindnocase-function-in-javascript for a different approach to this solution.


ListAppend JavaScript Function

Identical to the listAppend ColdFusion method. This function concatenates a list or element to a list and returns a string.

Usage: listAppend(list, value)

Example: listAppend('Glacier National Park', 'Yellowstone National Park');

This example will append Yellowstone National Park to the list and return 'Glacier National Park', 'Yellowstone National Park' which are the national parks within Montana.

// Adds a value to a comma-separated list. Will not add the value if the list already contains the value.
function listAppend(list, value) {
  // Adapted from a variety of sources by Gregory Alexander <www.gregoryalexander.com>
  var re = new RegExp('(^|)' + value + '(|$)');
  if (!re.test(list)) {
	return list + (list.length? ',' : '') + value;
  }
  return list;
}

listDeleteValue JavaScript function

This JavaScript function deletes a value within a list and is based on Ben Nadel's listDeleteValue function found on GitHub

Usage: listDeleteValue(list, value)

Example: listDeleteValue('Grand Canyon National Park,Saguro National Park,Indian Ocean', 'Indian Ocean');

This will delete 'Indian Ocean' from a list of parks in Arizona and will return 'Grand Canyon National Park,Saguro National Park'

// Removes a value in a comma separated list. Based on the ListDeleteValue function by Ben Nadel CF fuction https://gist.github.com/bennadel/9753040
var listDeleteValue = function(list, value){
	// Adapted from a variety of sources by Gregory Alexander <www.gregoryalexander.com>
	var values = list.split(",");
	for(var i = 0 ; i < values.length ; i++) {
		if (values[i] == value) {
			values.splice(i, 1);
			return values.join(",");
		}
	}
	return list;
}

MyDump JavaScript function that replicates the cfdump ColdFusion function

This handy function dumps out a JavaScript object to the console log. It was meant to provide functionality similar to the cfdump tag in ColdFusion. 

Usage: mydump(arr, level)

Note: this should be used carefully as it contains a lot of data which could consume a lot of resources.

// function to dump out a a javascript object.
function mydump(arr,level) {
	var dumped_text = "";
	if(!level) level = 0;

	var level_padding = "";
	for(var j=0;j<level+1;j++) level_padding += "    ";

	if(typeof(arr) == 'object') {  
		for(var item in arr) {
			var value = arr[item];

			if(typeof(value) == 'object') { 
				dumped_text += level_padding + "'" + item + "' ...
";
				dumped_text += mydump(value,level+1);
			} else {
				dumped_text += level_padding + "'" + item + "' => "" + value + ""
";
			}
		}
	} else { 
		dumped_text = "===>"+arr+"<===("+typeof(arr)+")";
	}
	console.log(dumped_text);
}

Further Reading:

  • If you want a more comprehensive library, check out the cfjs project that has replicated 90 ColdFusion functions on GitHub.
  • James Molberg has developed a comprehensive tool to replicate the functionality of cfdump. See Javascript version of ColdFusion CFDump

This entry was posted on October 5, 2022 at 10:44 PM and has received 380 views.

Using ColdFusion to Synchronize Time Across Time Zones


ColdFusion has plenty of date/time functions, however, it is lacking a robust set of native functions to calculate time stamps across time zones. Thankfully ColdFusion has a vibrant open-source community. In this article, I will walk you through the process of converting the client timestamp to the timestamp on the server for a given timezone using TimeZone.cfc.



Real-World Scenario

Galaxie Blog allows the author to select a post date in the past or in the future. When a future date is selected, Galaxie Blog will create a scheduled task to send the post to the subscriber base via email. However, we must convert the post date in the author's time zone to the proper time in the time zone of the server. We need to make sure that the emails are sent out at the right time if the time zones are different between the author and the server. To accomplish this we will use an open-source library, TimeZone.cfc.


Real World Example

Click the button below to see this example in action.


TimeZone.cfc

TimeZone.cfc is a small ColdFusion component that has many different methods to convert timestamp information between various timezones. This component allows for robust time zone conversion using Java's  java.util.TimeZone class.

It can be found on GitHub at https://github.com/rip747/TimeZone-CFC. However, the current version of TimeZone.cfc has a small bug caused by a nested comment and a small bug with CF2021.

I documented these bugs and made proposed changes on the original TimeZone repo and have forked my own TimeZone.cfc at https://github.com/GregoryAlexander77/TimeZone-CFC. Once you have downloaded the cfc you will need to upload it to the server. In this example, I placed the cfc in /blog/common/cfc/TimeZone.cfc.


Server-side function to convert a selected date and timezone between the client and the server

I created the following function to convert the timestamp on the client's device to the time found on the server. This is the same function that I am using in the demo below.

The required arguments are the date and time selected by the user, along with the author's time zone.

The TimeZone's getServerId() retrieves the time zone string found on the server. My server, hosted by Hostek, has the following identifier  'America/Chicago'.

Code on the client-side is sending the Time Zone's GMT Offset that the user selected. GMT is Greenwich Mean Time and is the time displayed by the Shepherd Gate Clock at the Royal Observatory in Greenwich, London. The GMT Offset is a numeric value that is used to determine the time from the GMT and is a string like so '-8'. If it is noon in London, you would subtract 8 hours to find the time in the Pacific Time Zone, which would be 4 AM, for example.

TimeZone cfc takes this offset value and extracts a list of time zone identifiers. The time zone identifier for my location is 'America/Los_Angeles' but you can select your own.

As there are many time zone identifiers for a given GMT Offset (-8 for example), and the blogTimeZoneList[1] code picks the first value found in this list. It is better to use the Time Zone Identifiers instead of calculating it using GMT Offset, but here we are using the GMT Offset for this example.

Near the bottom of the function, the convertTz function takes the date and timezone selected by the user and converts it to the date on the server. See the documentation in the TimeZone.cfc for more information.

<cffunction name="getServerDateTime" access="remote" returntype="date" returnformat="json" output="true"
		hint="Takes a date from the client and returns the date that it should be on the Server. This is used to schedule tasks on the server from the front end.">
	<cfargument name="selectedDate" type="date" required="true" />
	<cfargument name="selectedTime" type="date" required="true" />
	<cfargument name="timeZone" type="string" required="true" />

	<cfset thisDate = selectedDate & ' ' & selectedTime>

	<!--- Invoke the Time Zone cfc --->
	<cfobject component=".blog.common.cfc.TimeZone" name="TimeZoneObj">

	<!--- Get the time zone identifier on the server (ie America/Los_Angeles) from the TimeZone component --->
	<cfset serverTimeZoneId = TimeZoneObj.getServerId()>
	<!--- Get the blog time zone offset (-8) from the database and is populated by the Blog Time interface. We probably should be storing the actual identifier (America/Los_Angeles) in the database in the future to get the proper DST --->
	<cfset blogTimeZone = arguments.timeZone>
	<!--- Get the time zone identifier (America/Los_Angeles) by the GMT offset. This will pull up multiple options, but we just need a working identifier and will select the first one.  --->
	<cfset blogTimeZoneList = TimeZoneObj.getTZByOffset(blogTimeZone)>
	<!--- Get the first value in the array. We don't need this to be accurate, we just need a valid identifier to use. --->
	<cfset blogTimeZoneId = blogTimeZoneList[1]>

	<!--- Now use the convertTZ function to convert the blog time to server time. The blog time is the time zone of the site administrator that is writing the articles. We may want to add time zones for all blog users with the edit post role in the future. 
	convertTz(thisDate,	fromTZ, toTZ) --->
	<cfset serverDateTime = TimeZoneObj.convertTZ(thisDate, blogTimeZoneId, serverTimeZoneId)>

	<!--- Return it. --->
	<cfreturn serverDateTime>

</cffunction>

Client-Side Code

The server-side code found above should be a useful introduction to this library for most users, but for clarity, I will also include the client-side code. The client-side code uses the Kendo Core library, but you can use normal dropdowns and date pickers on the client-side if you choose. 


Client-Side Javascript

The following Javascript creates the necessary data and functionality of the dropdowns on the client.

The Kendo Time Picker widget prompts the user to select a valid date/time.

The Time Zone dropdown displays the label in the tzInts Javascript array, for example '(GMT-12:00) International Date Line West' and extracts the GMT Offset found in the selected value.

The getServerDateTime function sends these values using Ajax to the getServerDateTime function (found above) on the server.

The serverDateTimeResult function returns the getServerDateTime value back to the client and populates the serverTime HTML form.

<script>

var todaysDate = new Date();

// Kendo Dropdowns
// Date/time picker			
$("#selectedDate").kendoDateTimePicker({
	componentType: "modern",
	value: todaysDate
});

var tzInts = [
	{"label":"(GMT-12:00) International Date Line West","value":"-12"},
	{"label":"(GMT-11:00) Midway Island, Samoa","value":"-11"},
	{"label":"(GMT-10:00) Hawaii","value":"-10"},
	{"label":"(GMT-09:00) Alaska","value":"-9"},
	{"label":"(GMT-08:00) Pacific Time (US & Canada)","value":"-8"},
	{"label":"(GMT-08:00) Tijuana, Baja California","value":"-8"},
	{"label":"(GMT-07:00) Arizona","value":"-7"},
	{"label":"(GMT-07:00) Chihuahua, La Paz, Mazatlan","value":"-7"},
	{"label":"(GMT-07:00) Mountain Time (US & Canada)","value":"-7"},
	{"label":"(GMT-06:00) Central America","value":"-6"},
	{"label":"(GMT-06:00) Central Time (US & Canada)","value":"-6"},
	{"label":"(GMT-05:00) Bogota, Lima, Quito, Rio Branco","value":"-5"},
	{"label":"(GMT-05:00) Eastern Time (US & Canada)","value":"-5"},
	{"label":"(GMT-05:00) Indiana (East)","value":"-5"},
	{"label":"(GMT-04:00) Atlantic Time (Canada)","value":"-4"},
	{"label":"(GMT-04:00) Caracas, La Paz","value":"-4"},
	{"label":"(GMT-04:00) Manaus","value":"-4"},
	{"label":"(GMT-04:00) Santiago","value":"-4"},
	{"label":"(GMT-03:30) Newfoundland","value":"-3.5"},
	{"label":"(GMT-03:00) Brasilia","value":"-3"},
	{"label":"(GMT-03:00) Buenos Aires, Georgetown","value":"-3"},
	{"label":"(GMT-03:00) Greenland","value":"-3"},
	{"label":"(GMT-03:00) Montevideo","value":"-3"},
	{"label":"(GMT-02:00) Mid-Atlantic","value":"-2"},
	{"label":"(GMT-01:00) Cape Verde Is.","value":"-1"},
	{"label":"(GMT-01:00) Azores","value":"-1"},
	{"label":"(GMT+00:00) Casablanca, Monrovia, Reykjavik","value":"0"},
	{"label":"(GMT+00:00) Greenwich Mean Time : Dublin, Edinburgh, Lisbon, London","value":"0"},
	{"label":"(GMT+01:00) Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna","value":"1"},
	{"label":"(GMT+01:00) Belgrade, Bratislava, Budapest, Ljubljana, Prague","value":"1"},
	{"label":"(GMT+01:00) Brussels, Copenhagen, Madrid, Paris","value":"1"},
	{"label":"(GMT+01:00) Sarajevo, Skopje, Warsaw, Zagreb","value":"1"},
	{"label":"(GMT+01:00) West Central Africa","value":"1"},
	{"label":"(GMT+02:00) Amman","value":"2"},
	{"label":"(GMT+02:00) Athens, Bucharest, Istanbul","value":"2"},
	{"label":"(GMT+02:00) Beirut","value":"2"},
	{"label":"(GMT+02:00) Cairo","value":"2"},
	{"label":"(GMT+02:00) Harare, Pretoria","value":"2"},
	{"label":"(GMT+02:00) Helsinki, Kyiv, Riga, Sofia, Tallinn, Vilnius","value":"2"},
	{"label":"(GMT+02:00) Jerusalem","value":"2"},
	{"label":"(GMT+02:00) Minsk","value":"2"},
	{"label":"(GMT+02:00) Windhoek","value":"2"},
	{"label":"(GMT+03:00) Kuwait, Riyadh, Baghdad","value":"3"},
	{"label":"(GMT+03:00) Moscow, St. Petersburg, Volgograd","value":"3"},
	{"label":"(GMT+03:00) Nairobi","value":"3"},
	{"label":"(GMT+03:00) Tbilisi","value":"3"},
	{"label":"(GMT+03:30) Tehran","value":"3.5"},
	{"label":"(GMT+04:00) Abu Dhabi, Muscat","value":"4"},
	{"label":"(GMT+04:00) Baku","value":"4"},
	{"label":"(GMT+04:00) Yerevan","value":"4"},
	{"label":"(GMT+04:30) Kabul","value":"4.5"},
	{"label":"(GMT+05:00) Yekaterinburg","value":"5"},
	{"label":"(GMT+05:00) Islamabad, Karachi, Tashkent","value":"5"},
	{"label":"(GMT+05:30) Sri Jayawardenapura","value":"5.5"},
	{"label":"(GMT+05:30) Chennai, Kolkata, Mumbai, New Delhi","value":"5.5"},
	{"label":"(GMT+05:45) Kathmandu","value":"5.75"},
	{"label":"(GMT+06:00) Almaty, Novosibirsk","value":"6"},{"label":"(GMT+06:00) Astana, Dhaka","value":"6"},
	{"label":"(GMT+06:30) Yangon (Rangoon)","value":"6.5"},
	{"label":"(GMT+07:00) Bangkok, Hanoi, Jakarta","value":"7"},
	{"label":"(GMT+07:00) Krasnoyarsk","value":"7"},
	{"label":"(GMT+08:00) Beijing, Chongqing, Hong Kong, Urumqi","value":"8"},
	{"label":"(GMT+08:00) Kuala Lumpur, Singapore","value":"8"},
	{"label":"(GMT+08:00) Irkutsk, Ulaan Bataar","value":"8"},
	{"label":"(GMT+08:00) Perth","value":"8"},
	{"label":"(GMT+08:00) Taipei","value":"8"},
	{"label":"(GMT+09:00) Osaka, Sapporo, Tokyo","value":"9"},
	{"label":"(GMT+09:00) Seoul","value":"9"},
	{"label":"(GMT+09:00) Yakutsk","value":"9"},
	{"label":"(GMT+09:30) Adelaide","value":"9.5"},
	{"label":"(GMT+09:30) Darwin","value":"9.5"},
	{"label":"(GMT+10:00) Brisbane","value":"10"},
	{"label":"(GMT+10:00) Canberra, Melbourne, Sydney","value":"10"},
	{"label":"(GMT+10:00) Hobart","value":"10"},
	{"label":"(GMT+10:00) Guam, Port Moresby","value":"10"},
	{"label":"(GMT+10:00) Vladivostok","value":"10"},
	{"label":"(GMT+11:00) Magadan, Solomon Is., New Caledonia","value":"11"},
	{"label":"(GMT+12:00) Auckland, Wellington","value":"12"},
	{"label":"(GMT+12:00) Fiji, Kamchatka, Marshall Is.","value":"12"},
	{"label":"(GMT+13:00) Nuku'alofa","value":"13"}
]	

// timezone dropdown
var timeZoneDropdown = $("#timeZoneDropdown").kendoDropDownList({
	optionLabel: "Select...",
	dataTextField: "label",
	dataValueField: "value",
	filter: "contains",
	dataSource: tzInts,
	change: onTimeZoneChange,
}).data("kendoDropDownList");

// Save the selected date in a hidden field.
function saveTimeZoneValue(timeZoneId){
	$("#timeZoneValue").val(timeZoneId);
}

// Calculate the server time
function onTimeZoneChange(e){
	// Get the value
	getServerDateTime();
}//...function onBlogTimeZoneChange(e)

function getServerDateTime(){

	jQuery.ajax({
		type: 'post', 
		url: '<cfoutput>#application.baseUrl#</cfoutput>/demo/Demo.cfc?method=getServerDateTime',
		data: { // arguments
			selectedDate: kendo.toString($("#selectedDate").data("kendoDateTimePicker").value(), 'MM/dd/yyyy'),
			selectedTime: kendo.toString($("#selectedDate").data("kendoDateTimePicker").value(), 'hh:mm tt'),
			timeZone: $("#timeZoneValue").val()
		},
		dataType: "json",
		success: serverDateTimeResult, // calls the result function.
		error: function(ErrorMsg) {
			console.log('Error' + ErrorMsg);
		}
	// Extract any errors. This is a new jQuery promise based function as of jQuery 1.8.
	}).fail(function (jqXHR, textStatus, error) {
		// The full response is: jqXHR.responseText, but we just want to extract the error.
		$.when(kendo.ui.ExtAlertDialog.show({ title: "Error while consuming the getServerDateTime function", message: error, icon: "k-ext-error", width: "<cfoutput>#application.kendoExtendedUiWindowWidth#</cfoutput>" }) // or k-ext-error, k-ext-information, k-ext-question, k-ext-warning.  You can also specify height.
			).done(function () {
			// Do nothing
		});
	});//...jQuery.ajax({
};

function serverDateTimeResult(response){
	// alert(response);
	// The server is returning the time. Save this into the form for display
	$("#serverTime").val(response);
}

Client-Side HTML

<div class="content k-content">
<table align="center" class="k-content" width="100%" cellpadding="2" cellspacing="0">
  <input type="hidden" name="selectedDateValue" id="selectedDateValue" value="">
  <input type="hidden" name="timeZoneValue" id="timeZoneValue" value="">
  <input type="hidden" name="serverTimeZoneValue" id="serverTimeZoneValue" value="">
  <cfsilent>
	<!---The first content class in the table should be empty. --->
	<cfset thisContentClass = HtmlUtilsObj.getKendoClass('')>
	<!--- Set the colspan property for borders --->
	<cfset thisColSpan = "2">
  </cfsilent>

  <tr height="1px">
	  <td align="left" valign="top" colspan="2" class="<cfoutput>#thisContentClass#</cfoutput>"></td>
  </tr>
  <tr height="1px">
	  <td></td>
	  <td align="left" valign="top" class="<cfoutput>#thisContentClass#</cfoutput>">
		Your hosting provider or server may reside in a different time-zone. These settings are critical when this is the case. If your server is in a different time-zone, you will want the post date to show the  time that you are in- not necessarilly where the server is.
	  </td>
  </tr>
  <!-- Form content -->
<cfif session.isMobile>
<tr valign="middle">
<td class="<cfoutput>#thisContentClass#</cfoutput>" colspan="2">
	<label for="selectedDate">Date</label>
</td>
</tr>
<tr>
<td class="<cfoutput>#thisContentClass#</cfoutput>" colspan="2">
	<input id="selectedDate" name="selectedDate" value="<cfoutput>#dateTimeFormat(selectedDate, 'medium')#</cfoutput>" style="width: <cfif session.isMobile>95<cfelse>45</cfif>%" /> 
</td>
</tr>
<cfelse><!---<cfif session.isMobile>--->
<tr valign="middle" height="30px">
<td align="right" valign="middle" class="<cfoutput>#thisContentClass#</cfoutput>" width="20%">
	<label for="selectedDate">Date</label>
</td>
<td align="left" class="<cfoutput>#thisContentClass#</cfoutput>">
	<input id="selectedDate" name="selectedDate" value="<cfoutput>#dateTimeFormat(now(), 'medium')#</cfoutput>" style="width: <cfif session.isMobile>95<cfelse>45</cfif>%" /> 
</td>
</tr>
</cfif>
  <!-- Border -->
  <tr height="2px"> 
	  <td align="left" valign="top" colspan="<cfoutput>#thisColSpan#</cfoutput>" class="<cfoutput>#thisContentClass#</cfoutput>"></td>
  </tr>
<cfif session.isMobile>
  <tr valign="middle">
	<td class="<cfoutput>#thisContentClass#</cfoutput>" colspan="2">
		<label for="timeZoneDropdown">Your time-zone:</label>
	</td>
   </tr>
   <tr>
	<td class="<cfoutput>#thisContentClass#</cfoutput>" colspan="2">
		<select id="timeZoneDropdown" name="timeZoneDropdown" style="width:95%" onchange="saveTimeZoneValue(this.value)"></select>
	</td>
  </tr>
<cfelse><!---<cfif session.isMobile>---> 
  <tr>
	<td align="right" class="<cfoutput>#thisContentClass#</cfoutput>" style="width: 20%"> 
		<label for="timeZoneDropdown">Your time-zone:</label>
	</td>
	<td class="<cfoutput>#thisContentClass#</cfoutput>">
		<select id="timeZoneDropdown" name="timeZoneDropdown" style="width:50%" onchange="saveTimeZoneValue(this.value)"></select>
	</td>
  </tr>
</cfif>	  
  <!-- Border -->
  <tr height="2px">
	  <td align="left" valign="top" colspan="<cfoutput>#thisColSpan#</cfoutput>" class="<cfoutput>#thisContentClass#</cfoutput>"></td>
  </tr>
  <cfsilent>
  <!--- Set the class for alternating rows. --->
  <!---After the first row, the content class should be the current class. --->
  <cfset thisContentClass = HtmlUtilsObj.getKendoClass(thisContentClass)>
  </cfsilent>
<cfif session.isMobile>
  <tr valign="middle">
	<td class="<cfoutput>#thisContentClass#</cfoutput>" colspan="2">
		<label for="serverTime">Hostek Server Time:</label>
	</td>
   </tr>
   <tr>
	<td class="<cfoutput>#thisContentClass#</cfoutput>" colspan="2">
		<input type="text" id="serverTime" name="serverTime" value="" class="k-textbox" required> (Oklahoma)
	</td>
  </tr>
<cfelse><!---<cfif session.isMobile>---> 
  <tr>
	<td align="right" class="<cfoutput>#thisContentClass#</cfoutput>" style="width: 20%"> 
		<label for="serverTime">Hostek Server Time:</label>
	</td>
	<td class="<cfoutput>#thisContentClass#</cfoutput>">
		<input type="text" id="serverTime" name="serverTime" value="" class="k-textbox" required> (Oklahoma)
	</td>
  </tr>
</cfif>
</table>

This entry was posted on June 21, 2022 at 2:40 AM and has received 778 views.

Using Cookies to Pass JavaScript Variables to ColdFusion


Understanding the differences between Javascript and ColdFusion


You can't directly pass a Javascript variable to ColdFusion as the two technologies are quite different.

ColdFusion is processed on the server side prior to delivering the content to the client. ColdFusion prepares the entire page and then renders it to the client before any client-side activity, such as Javascript, can take place.

Javascript is used on the client-side, and these variables are only available after ColdFusion initially delivers the HTML to the client. These two environments can not be running at the same time. 

To pass Javascript variables to ColdFusion, you must an assistive technology, such as Ajax, or pass the Javascript variables to the server-side once the initial page has been rendered. Here we will focus on using Cookies to transfer information from the client-side to the server-side.


Real-world scenario

In Galaxie Blog, I developed a complex create post interface with TinyMce. There are scores of different interfaces depending upon the client's screen resolution and device. I could have used media queries and other client-side techniques to build the page, however, the enormous complexity of the various interfaces made it much easier to design these interfaces on the server-side. 

To accomplish this, I sniffed the client's device by intercepting the HTTP user agent string with ColdFusion scripts provided by detectmobilebrowers.com. This works flawlessly and detects mobile and desktop devices but does not work with newer iPads. Unfortunately, Apple recently removed any identifying strings and made it impossible to detect iPads using the HTTP user agent string.To determine iPad clients, I would have to use Javascript to obtain the screen resolution and pass it to ColdFusion.


Using cookies to pass Javascript variables to ColdFusion

You can use cookies to transfer information from Javascript to ColdFusion. This particular technique requires that the user land on a page, such as a login form, to obtain the needed information from Javascript. 

In my scenario, the user will first hit a login page. The login page has the following Javascripts to set a cookie that ColdFusion can read. Note the path argument ('/') at the end of the script. You must store the cookie that Javascript creates in the root directory for ColdFusion to be able to read the cookie.


Logic on the client-side

/* Cookie functions. The original author of this script is unknown */
function setCookie(name,value,days) {
	var expires = "";
	if (days) {
		var date = new Date();
		date.setTime(date.getTime() + (days*24*60*60*1000));
		expires = "; expires=" + date.toUTCString();
	}
	// The path must be stored in the root in order for ColdFusion to read these cookies
	document.cookie = name + "=" + (value || "")  + expires + "; path=/";
}

Once the user hits the login page, set a cookie using the Javascript function above. We are naming our cookies 'screenWidth' and 'screenHeight' and passing the width and the height of the device determined by Javascript and keeping the cookie alive for one day.

// Set a cookie indicating the screen size. We are going to use this to determine what interfaces to use when the the screens are narrow.
// (setCookie(name,value,days))
setCookie('screenWidth',$(window).width(),1);
setCookie('screenHeight',$(window).height(),1);

Server-side logic using ColdFusion

To read the cookie using ColdFusion, it is best to use a try block to read the cookie. The syntax to read the cookie from Javascript is different than the native ColdFusion cookie methods- note the brackets surrounding the cookie name. 

<!--- Get client properties. This will be used to set the interfaces depending upon the screen size --->
<cftry>
	<cfset screenHeight = cookie['screenHeight']>
	<cfset screenWidth = cookie['screenWidth']>
	<cfcatch type="any">
		<cfset screenHeight = 9999>
		<cfset screenWidth = 9999>	   
	</cfcatch>
</cftry>
<!--- Outputting the screen sizes --->
<cfoutput>
screenHeight: #screenHeight#
screenWidth: #screenWidth#
</cfoutput>

You can use this particular method to transfer any variable obtained by Javascript to ColdFusion as long as you have the user first hit a landing page. 

I may write other articles using other methods, such as Ajax, in the future. 

 

This entry was posted on June 16, 2022 at 1:08 AM and has received 428 views.

Enabling Search Engine Friendly Links with Url Re-write in Galaxie Blog


To enable search engine-friendly URL's in Galaxie blog, follow the steps below.

  1. If you're using IIS, the following rule should be copied and pasted into the web.config in the root directory on your web server. The URL redirection rule is between the rewrite tags below. It essentially matches all strings with 'index.cfm', and removes this string. Additionally, it sets a permanent redirect that the search engine uses when crawling your page.  If you're using a different web server, see the links at the bottom of this article or consult with your server administrator. 
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <system.webServer>
        <directoryBrowse enabled="false" />
        <urlCompression doStaticCompression="true" doDynamicCompression="true" />
		<rewrite>
		<rules>
		    <rule name="GregorysBlog" stopProcessing="true">
                        <match url="(.*)index.cfm" />
                            <conditions logicalGrouping="MatchAll">
                                <add input="{SCRIPT_FILENAME}" matchType="IsFile" negate="true" />
                                <add input="{QUERY_STRING}" pattern=".+" ignoreCase="false" negate="true" />
                        </conditions>
                        <action type="Redirect" url="{R:1}" appendQueryString="true" redirectType="Permanent" />
                  </rule>
	    </rules>
	</rewrite>
        <security>
            <requestFiltering>
                <fileExtensions>
                    <add fileExtension=".pl" allowed="false" />
                </fileExtensions>
            </requestFiltering>
        </security>
        <httpErrors errorMode="Detailed" />
        <staticContent>
            <remove fileExtension=".woff2" />
            <mimeMap fileExtension=".woff2" mimeType="font/woff2" />
            <remove fileExtension=".webp" />
            <mimeMap fileExtension=".webp" mimeType="image/webp" />
        </staticContent>
    </system.webServer>
</configuration>
  1. Log into the Galaxie Blog Administrator and click on the Blog options icon. 
  2. Check the Server Rewrite Rule in place checkbox.
  3. Click on the submit button at the bottom of the page.

That's it! Your blog should now be using search-friendly URLs.

Credits:

  • I especially want to thank Caleb C. from Hostek for helping me get the IIS rule straight. The folks at Hostek have been nothing but outstanding in their service and support!

Further Reading:

This entry was posted on April 26, 2022 at 7:24 PM and has received 1260 views.

Extending Application.cfc's using mappings and proxies


This is a rather long article, if you want to jump to the condensed summary, scroll down to the bottom of this page. Many years ago, the first time that I tried to perform this, I received the following message no matter what I tried: "Could not find the ColdFusion component or interface xxx'. In a nutshell, the problem using this approach is that both the root and the subfolders have the same name, i.e. Application.cfc, and ColdFusion can't properly identify what component to extend. Finally, after some serious investigation, someone came up with the idea to create a proxy.cfc that resides in the same root directory as the root Application.cfc, and the Application.cfc in the subfolder extends an empty proxy.cfc that extends the root cfc like so:
root directory: Application.cfc
This root Application.cfc does not extend anything

Also in the root directory: Proxy.cfc
Proxy.cfc has the following code, its essentially empty. The only thing that the Proxy.cfc does is to extend the Application.cfc that is in the same directory:
<cfcomponent extends="Application">

</cfcomponent>


Subdirectory such as a folder named admin.
This subdirectory has another Application.cfc. Let's say that this component is responsible for securing the application and has login logic as well as debugging settings for example. This Application.cfc will extend the Proxy.cfc to gain the methods and properties of the Application.cfc in the root directory like so:
<cfcomponent displayname="Admin" extends="Proxy.cfc">
<!-- Lots of code --->

</cfcomponent>

This approach was a godsend and it was heavily blogged about. Ben Nadel has made a number of very helpful posts which I will share at the bottom of this article. This works quite well unless you're on a hosted domain or a server that uses virtual directories. In this case, we are in the same original boat in which we started from. Now we are back into the "Could not find the ColdFusion component or interface xxx' hell! There is a solution for this tricky problem though, we need to also use mapping! It is a common misnomer that you can't use mapping to extend components. I am not quite sure where this misconception originally came about, but it has been proven that this is just not true. There are occasions where we must use mapping to solve some annoying problems, like here. This particular site is hosted by hostek.com. They are a fine company to deal with, but the server that my site is hosted on has some idiosyncrasies due to the directory structure. Here, when I use the Proxy.cfc method to extend the logic from the base Application.cfc to the Application.cfc in the admin folder I receive the dreaded 'could not find the ... component' error. When I first saw it I was dismayed thinking not this again, so I turned to ColdFusion CFC mapping. Mapping tells ColdFusion where to find the file and what the file relationships are.
Let's review CFC structure that was just discussed. For example, imagine the following directory structure:
root directory: i.e. www.gregoryalexander.com/
subdirectory: www.gregoryalexander.com/admin/
As discussed, we have an Application.cfc and the Proxy.cfc in the root directory, and we have the Application.cfc in the 'admin' subdirectory. The Proxy.cfc extends the Application.cfc, also in the root directory, and the Application.cfc in the subdirectory (admin) extends the Proxy.cfc in the root directory. root directory: contains both Application.cfc and Proxy.cfc (that extends the root Application.cfc).
subdirectory: Application.cfc (that extends Proxy.cfc).
Now we need to also add the following mapping in the root Application.cfc. This mapping logic should be near the top of the root Application.cfc, and it should not be within any of the Application.cfc event handlers (onApplicationStart, onApplicationRequest, etc). This mapping code does not need to be anywhere else other than the root Application.cfc:
<!-- Define application-specific mappings. These will be used to point to this application.cfc when we extend it in the admin/Administrator.cfc template using the Proxy.cfc that resides in the same folder as this Application.cfc. --->

<cfset this.mappings="structNew()" />
<!-- Mapping for the ROOT Application.cfc --->

<cfset this.mappings["rootCfc"]="getDirectoryFromPath(getCurrentTemplatePath())" />
<!-- Mapping for the admin SUBDIRECTORY Application.cfc. Note the admin prefix is attached at the end of this line. This points to the admin folder. --->

<cfset this.mappings["adminCfc"]="getDirectoryFromPath(" getCurrentTemplatePath()="" &="" "="" admin"="" )="" />
I used rootCfc to identify the Application.cfc in the root directory, whereas adminCfc applies to the Application in the admin directory. These variables can be named anything. Note that the "/admin" string at the end of the adminCfc mapping points to the 'admin' folder, which is a subdirectory. Now that we have the mappings in the root Application.cfc, we need to apply them to the extends statement in Application.cfc located in the subdirectory. In the /admin/Application.cfc template use:
/admin/Application.cfc
<cfcomponent displayname="xxx" sessionmanagement="xx" clientmanagement="xx" extends="rootCfc.Proxy">
<!-- Logic --->

</cfcomponent>
Of course, rootCfc tells the Application.cfc in the subdirectory to look for the Proxy.cfc template in the root directory. Like other 'extend' statements, you don't need to specify '.cfc' at the end of Proxy. You don't need to use this 'extend' mapping in either the root Proxy.cfc or Application.cfc templates. They can already find each other as they are both in the same root directory. /Proxy.cfc
<cfcomponent extends="Application">

</cfcomponent>

Summary

For the sake of absolute clarity:
root Application.cfc
Contains the mapping logic. Has the mappings for both of the root and subdirectory.
This mapping logic should be near the top of the root Application.cfc, and it should not be within any of the Application.cfc event handlers (onApplicationStart, onApplicationRequest, etc).
Does not use an 'extend' statement
<cfset this.mappings="structNew()" />

<cfset this.mappings["rootCfc"]="getDirectoryFromPath(getCurrentTemplatePath())" />

<cfset this.mappings["adminCfc"]="getDirectoryFromPath(" getCurrentTemplatePath()="" &="" "="" admin"="" )="" />
root Proxy.cfm
A simple 'extends="Application" works.
No mapping logic.
<cfcomponent extends="Application">

</cfcomponent>
subdirectory Application.cfc
The extends statement must be the mapping variable name of the folder (rootCfc), a dot (.), and finally the name of the Proxy.cfc template without the .cfc prefix (Proxy)
No mapping logic.
<cfcomponent displayname="Admin" sessionmanagement="yes" clientmanagement="yes" extends="rootCfc.Proxy">

</cfcomponent>
My apologies for being so verbose. I annoyed myself while writing this- but not as annoyed when I was while trying to solve this problem! Take care! Related External Posts:

This entry was posted on January 30, 2021 at 12:31 AM and has received 1173 views.