Duncan’s blog

October 18, 2012

ColdFusion cfloop versus cfscript for loop performance

Filed under: Coldfusion — duncan @ 7:01 pm
Tags: , ,

I’m sure most web developers are aware that, in Javascript, it’s generally considered best practice to do this:

for (var i = 0, len = array.length; i < len; i++) { }

instead of this:

for (var i = 0; i < array.length; i++) { }

The theory being that you improve performance by caching the array length once, instead of each time you iterate over the loop (although it’s open to debate how important this is; it really only benefits IE).

I decided to see if there was any benefit in doing the same thing with array loops in ColdFusion. Initially just tested with CFML, then again with CFScript, to see if there were any differences.

<!--- start with a blank file each time --->
<cfif fileExists("arraylen.txt")>
	<cffile action="delete" file="arraylen.txt">
</cfif>

<cfloop index="j" from="1" to="1000">
	<!--- create a large array to loop over later --->
	<cfset myArray = []>	
	<cfloop index="i" from="1" to="10000000"><cfset arrayAppend(myArray, "")></cfloop>

	<cfscript>
		// Method 1
		tick = GetTickCount();

		for (i=1; i <= arrayLen(myArray); i++) {}

		recordTime(
			type	= "CFScript (arrayLen)",
			tick	= tick
		);

		// Method 2
		tick = GetTickCount();

		len = arrayLen(myArray);

		for (i=1; i <= len; i++) {}

		recordTime(
			type	= "CFScript (var'ed)",
			tick	= tick
		);
	</cfscript>

	<!--- Method 1 --->
	<cfset tick = GetTickCount()>

	<cfloop index="i" from="1" to="#arrayLen(myArray)#"></cfloop>

	<cfset recordTime(
		type	= "CFML (arrayLen)",
		tick	= tick
	)>

	<!--- Method 2 --->
	<cfset tick = GetTickCount()>

	<cfset len = arrayLen(myArray)>

	<cfloop index="i" from="1" to="#len#"></cfloop>

	<cfset recordTime(
		type	= "CFML (var'ed)",
		tick	= tick
	)>
</cfloop>

<cffunction name="recordTime" output="false" returntype="void">
	<cfargument name="type" type="string" required="true">
	<cfargument name="tick" type="numeric" required="true">

	<cfset var tock = GetTickCount()>
	<cfset var time = tock - arguments.tick>

	<cffile action="append" output="#arguments.type##chr(9)##time#ms" file="arraylen.txt">
</cffunction>

What I did find was that the two methods, of either calculating arrayLen() within the FOR loop, or storing it as a variable, hardly differed at all in performance.

However there was a big difference in performance between CFML and CFScript. In this case, the looping was much slower in CFScript. This isn’t something new, I’m sure I’ve heard this before, but it was an interesting reminder for me that sometimes it’s not always best to write everything in CFScript.

So here’s the results showing the average times from running this 1000 times:

CFML (arrayLen) 257.883ms
CFML (var’ed) 258.284ms
CFScript (arrayLen) 1259.294ms
CFScript (var’ed) 1180.07ms

As we can see, the difference between caching the array length in a variable, or using arrayLen() for the loop limit, is negligible in CFML, less than 0.5 of a millisecond after 1000 iterations!

However in CFScript, it seems slightly quicker to use a variable instead of referring to arrayLen() within the iterator. But not so much that I’d say I’d favour one method over the other. Although I’ll certainly think twice before writing array loops in CFScript instead of CFML… although in reality the difference in performance is relatively minor (unless you’re working on massive arrays and/or have a high-traffic application).

PS: I’m running this on Adobe ColdFusion 9,0,1,274733 on Windows 7, YMMV :-)

September 11, 2012

Regex to find 6 repeating characters

I wanted to check our codebase for where we had six repeating characters in HTML and CSS for colours, e.g. #FFFFFF for white.  Here’s the regular expression for it, basically blogged here so I can remember where to find it later:

([a-fA-F0-9])\1{5}

So [a-fA-F0-9] looks for a single character that is one of the letters A – F (only need to go up to F because we’re dealing with hexadecimal numbers), or the digits 0 – 9.

The ( ) parentheses around that turns it into a backreference.

The \1 then refers to that backreferenced matched, and the {5} says to match it exactly 5 times.

So it’ll find where there’s a single matching character that is then repeated five times.

What I was doing this for was to replace all six character codes (that repeat) for colours with three character codes (because #FFF is equivalent to #FFFFFF). So to then replace it, I used the following (in Eclipse):

Find: ([a-fA-F0-9])\1{5}
Replace with: \1\1\1

We can also modify the regular expression so it also catches colours like #FF0000 or #33FF99, where we have three repeating sets of identical digits. These can also be shortened, to #F00 or #3F9.

This modified regular expression will cover this too. In this case we’re looking three times for a single character that gets repeated once each time. We then replace with just the three different single characters, ignore the repeating characters:

Find: ([a-fA-F0-9])\1{1}([a-fA-F0-9])\1{1}([a-fA-F0-9])\1{1}
Replace with: \1\2\3

March 12, 2012

ColdFusion regular expression backreferences and numeric strings

Supposing you have a string that starts with a number, e.g. “25% discount this weekend”, and you’re using that along with a regular expression.  If you’ve got a backreference immediately prior to that “25%…”, ColdFusion treats it as \125 instead of \1. 

For instance:

<cfset myString = "25% Discount this weekend">

<cfset originalString = "Look out for our special offers, including XXX and much more">

<cfset newString = reReplace(originalString, "(.*)XXX(.*)", "\1#myString#\2")>

<cfoutput>#newString#</cfoutput>

This just outputs:
% Discount this weekend and much more
as it thinks that you’re trying to do \125 instead of \1 followed by “25%…”

To get around this, you can use \E to separate the backreference from the rest of the string.
\E is meant to indicate when you’ve reached the end of an uppercase or lowercase block as set by \U or \L. However in this case it simply indicates to the regular expression process where the end of the backreference occurs.

<cfset newString = reReplace(originalString, "(.*)XXX(.*)", "\1\E#myString#\2")>

<cfoutput>#newString#</cfoutput>

This time it correctly outputs:
Look out for our special offers, including 25% Discount this weekend and much more

November 10, 2011

ColdFusion query-of-queries, tips for adding dynamic columns

Filed under: Coldfusion — duncan @ 5:35 pm
Tags: , , ,

Supposing you’re doing a query of queries, and you want to add a dynamic column, perhaps based on a mixture of existing CF variables and values from the original query. There are a few gotchas to watch out for.

Firstly, you have to use single quotes ‘ ‘ instead of double quotes ” ” for anything in the string. This is contrary to ColdFusion’s normal allowance for any mixture, e.g. this is usually perfectly fine:

<cfset x = "hi" & 'hi'>

However when trying to use ” ” in the query-of-queries, it throws the error:

Query Of Queries syntax error.
Encountered “. Lexical error at line 3, column 25. Encountered: “\”" (34), after : “”

This query will throw that error:

<cfquery name="rstGetStuff" dbtype="query">
	SELECT 	"Hello World" AS columnName
	FROM rstOriginalQuery
</cfquery>

But this is fine:

<cfquery name="rstGetStuff" dbtype="query">
	SELECT 	'Hello World' AS columnName
	FROM rstOriginalQuery
</cfquery>

Secondly, if you’re wanting to refer to variables within that string, just add them into the string surrounded by # #. Again, contrary to normal CFML, where these are equivalent:

<cfset x = "hi " & variables.strName>
<cfset x = "hi #variables.strName#">

So this throws the error:

Query Of Queries syntax error.
Encountered “.. Incorrect Select Statement, Expecting a ‘FROM’, but encountered ‘.’ instead, A select statement should have a ‘FROM’ construct.

<cfset strName = "Duncan">

<cfquery name="rstGetStuff" dbtype="query">
	SELECT 	'Hello ' + strName AS columnName
	FROM rstOriginalQuery
</cfquery>

Instead you have to do

<cfquery name="rstGetStuff" dbtype="query">
	SELECT 	'Hello #strName#' AS columnName
	FROM rstOriginalQuery
</cfquery>

Thirdly, if you’re wanting to add values from the original query into that dynamic column, you might need to CAST it to the correct type. In this case I have a column in the original query, intIDColumn, that is a numeric ID.

This throws the error:

Query Of Queries runtime error.
Cannot mix types INTEGER and VARCHAR in a + binary operation.

<cfquery name="rstGetStuff" dbtype="query">
	SELECT 	'/some/url/path/' + intIDColumn + '/' AS strURL
	FROM rstOriginalQuery
</cfquery>

Instead I have to cast it to a VARCHAR when trying to add it into my string.

<cfquery name="rstGetStuff" dbtype="query">
	SELECT 	'/some/url/path/' + CAST(intIDColumn AS VARCHAR) + '/' AS strURL
	FROM rstOriginalQuery
</cfquery>

November 4, 2011

Google Maps API – adding infowindows in response to user clicks

In response to my previous post about adding infowindows with the Google Maps API, Frank B asked if it was possible “to write a script where the user clicks on the map, and an infowindow pops up in that location containing information about that place, say the county it belongs to”. Here’s a quick something I rustled up in response to that.

Firstly, let’s just open an infowindow on a map in response to user clicks on the map.

<script type="text/javascript">
function initialize() {
	var myOptions = {
		zoom: 10,
		center: new google.maps.LatLng(50.820645,-0.137376),
		mapTypeId: google.maps.MapTypeId.ROADMAP
	};
	
	var map = new google.maps.Map(document.getElementById("map_canvas"), myOptions);
	
	var infowindow =  new google.maps.InfoWindow({
		content: 'Hello World!'
	});

	google.maps.event.addListener(map, 'click', function(event) {
		infowindow.setPosition(event.latLng);
		infowindow.open(map);
	}); 
}

google.maps.event.addDomListener(window, 'load', initialize);
</script>

So we simply have an event listener for any click events on the map itself. We can then find out the position of that click using event.latLng, which we can then pass to the setPosition function on the infowindow. The final .open() simply shows the infowindow; if it’s already open, this won’t do anything (but it causes no harm to call it again).

This opens the same infowindow anywhere we click on the map:

Now, let’s get some content about each location we click on, and use that to dynamically set the content of the infowindow. To do this we need to do a reverse Geocoding request (i.e. getting address details from a latlng coordinates). The usual use of the Geocoding service is to do the opposite – find out the coordinates of an address.

Interestingly Google’s API reference says to use a ‘location’ parameter but their Developer documentation says to use a ‘latLng’ parameter. Both seemed to work for me, but I’d say the API reference should be considered the correct source in general.

<script type="text/javascript">
var map, geocoder, infowindow;
	
function initialize() {
	var myOptions = {
		zoom: 10,
		center: new google.maps.LatLng(50.820645,-0.137376),
		mapTypeId: google.maps.MapTypeId.ROADMAP
	};
	
	map = new google.maps.Map(document.getElementById("map_canvas"), myOptions);
	
	geocoder = new google.maps.Geocoder();
	
	infowindow =  new google.maps.InfoWindow({
		content: ''
	});

	google.maps.event.addListener(map, 'click', function(event) {
		doGeocoding(event.latLng);
	});
}

function doGeocoding(latLng) {	
	geocoder.geocode({'location': latLng}, function(results, status) {
		if (status == google.maps.GeocoderStatus.OK) {
			if (results[1]) {	// use 2nd array item, a less-specific address
				infowindow.setContent(results[1].formatted_address);
			} else {
				infowindow.setContent('');
			}
		} else {
			infowindow.setContent('no address found');
		}
	});
	
	infowindow.setPosition(latLng);
	infowindow.open(map);
}

google.maps.event.addDomListener(window, 'load', initialize);
</script>

This code does strange things for me when clicking on the area at sea. If it’s the first click on the map, it doesn’t display any infowindow at all. If I initially click on the land to get an address, subsequent clicks on the sea use that same address, unless I click further out from the land, in which case it gives me ‘no address’. Think it’s an issue with trying to do setContent(”). I’m publishing this article despite it not being quite code-complete, in the hope it helps Frank (and I’m happy to take suggestions on how to get it fully working as it should).

October 8, 2011

Google Maps API – infowindows

So previously I showed how to create a basic map, then how to add markers to it. Now suppose you’re wanting to have one of those little bubbles pop up when you click on your map with some information in it. These are called infowindows in Google’s terminology.

Let’s start with the very basics – creating a map which has an infowindow already visible on it.

function initialize() {
	var homeLatlng = new google.maps.LatLng(51.476706,0);

	var myOptions = {
		zoom: 15,
		center: homeLatlng,
		mapTypeId: google.maps.MapTypeId.ROADMAP
	};

	var map = new google.maps.Map(document.getElementById("map_canvas"), myOptions);

	var infowindow =  new google.maps.InfoWindow({
		content: 'Hello World!',
		map: map,
		position: homeLatlng
	});
}

google.maps.event.addDomListener(window, 'load', initialize);

So we create an infowindow, specify some content for it and which map it’s for, and give it a latlong position (which coincidentally also happens to be where I’ve specified this map is to be centered, although that doesn’t have to be the case). The documentation says that “After constructing an InfoWindow, you must call open to display it on the map“; however I find the above code will open the infowindow without needing to explicitly call the open function.

But this shows the infowindow immediately. Perhaps you’d rather it only appeared after some user interaction. Typically you’d put markers on the map, and when the user clicks the marker, then you’d show the infowindow.

So let’s create the infowindow slightly differently. This time, don’t specify the position attribute. Then create a marker as normal, and finally we can open the infowindow, and we pass the marker as the second parameter. So the infowindow gets anchored to the marker’s position.

var infowindow =  new google.maps.InfoWindow({
	content: 'Hello World!',
	map: map
});

var marker = new google.maps.Marker({
	position: homeLatlng, 
	map: map
});

infowindow.open(map, marker);

So we’ve now added a marker to the map, which the infowindow is then anchored to.

You probably only want this to happen after the user has clicked on that marker. For that we need to have an event listener function. There are many of these available for all the different map elements, in this case we want to listen to the mouseclick event on that marker.

google.maps.event.addListener(marker, 'click', function() {
	infowindow.open(map, this);
});

So we say to the Google Maps event handler framework, on the marker, listen for any ‘click’ events. And when that happens, call this anonymous function which currently only has one line, to open the marker.

We could also make this infowindow appear in response to other events, e.g. if you simply hovered the mouse over the marker.

google.maps.event.addListener(marker, 'mouseover', function() {
	infowindow.open(map, this);
});

And then to make the infowindow disappear again when you move the mouse away from the marker, just call the close() function on mouseout.

google.maps.event.addListener(marker, 'mouseout', function() {
	infowindow.close();
});

Suppose you had several markers on your map, each of which would have its own infowindow. Here’s one way to deal with that. Firstly have an array of everything you need for each marker, arrDestinations. Then loop over them, adding a marker for each. We only have one infowindow variable, and we just update the content for it in response to user clicks. We need to delegate the event handler to an external function, bindInfoWindow, otherwise you end up just getting the content of the last array item for each marker.

<script type="text/javascript">
function initialize() {
	var i;
	var arrDestinations = [
		{
			lat: 50.815155, 
			lon: -0.137072, 
			title: "Brighton Pier", 
			description: "Brighton Palace Pier dates to 1899"
		},
		{
			lat: 50.822638, 
			lon: -0.137361, 
			title: "Brighton Pavilion", 
			description: "The Pavilion was built for the Prince of Wales in 1787"
		},
		{
			lat: 50.821226, 
			lon: -0.139372, 
			title: "English's", 
			description: "English's Seafood Restaurant and Oyster Bar"
		}
	];
	
	var myOptions = {
		zoom: 15,
		center: new google.maps.LatLng(50.820645,-0.137376),
		mapTypeId: google.maps.MapTypeId.ROADMAP
	};
	
	var map = new google.maps.Map(document.getElementById("map_canvas"), myOptions);
	
	var infowindow =  new google.maps.InfoWindow({
		content: ''
	});
	
	// loop over our array
	for (i = 0; i < arrDestinations.length; i++) {
		// create a marker
		var marker = new google.maps.Marker({
			title: arrDestinations[i].title,
			position: new google.maps.LatLng(arrDestinations[i].lat, arrDestinations[i].lon),
			map: map
		});
		
		// add an event listener for this marker
		bindInfoWindow(marker, map, infowindow, "<p>" + arrDestinations[i].description + "</p>");  
	}
}

function bindInfoWindow(marker, map, infowindow, html) { 
	google.maps.event.addListener(marker, 'click', function() { 
		infowindow.setContent(html); 
		infowindow.open(map, marker); 
	}); 
} 

google.maps.event.addDomListener(window, 'load', initialize);
</script>

In production you’d probably use a server-side language to populate the array and just pass it into the initialize function. There are other ways of doing this as well. Another approach is to have multiple infowindows, one for each marker. On click, you close any open infowindows, and then just open the one that corresponds to the current marker. I don’t believe that approach is as efficient as my example though.

September 25, 2011

Google Maps API – adding markers

I previously showed how to create a very simple map. But usually you’ll want to do more than that. Let’s start with adding markers to the map.

So, using the code from the previous post, I just add this to the end of the initialize function:

var homeMarker = new google.maps.Marker({
	position: homeLatlng
});

That’s all you need to create a Marker object! There are many other attributes you can specify here, but only position is required.

But you’ll notice that using this code, no marker will be visible. That’s because we haven’t said what map this marker is for. Sometimes you might want to deliberately do this, e.g. by creating markers in advance for showing/hiding dynamically later on.

So there are two ways to associate a marker with a map. When you create the Marker object, just specify the map attribute. The value of which should refer to the variable name you used when you created the Map object.

var homeMarker = new google.maps.Marker({
	position: homeLatlng,
	map: map
});

Alternatively, you can call the setMap() function:

homeMarker.setMap(map);

If you ever wanted to remove a marker from a map, just call marker.setMap(null).

So at this point you’ve got a map that looks something like this (showing the brilliantly-titled No Name Key in Florida):

Great, right? Well maybe you want to do just a bit more than that. Let’s examine some of the other options.

Firstly, you can specify a title. This will be displayed as a tooltip when you mouse-over the marker.

var homeMarker = new google.maps.Marker({
	position: homeLatlng,
	map: map,
	title: "Check this cool location"
});

Maybe you’re not keen on that shadow. Easy, just set the flat property or use the marker.setFlat() function:

var homeMarker = new google.maps.Marker({
	position: homeLatlng,
	map: map,
	title: "Check this cool location",
	flat: true
});

Perhaps you need users to be able to drag the markers around (this will come in useful later if you’re doing more advanced dynamic things on the map). Simple, just set the draggable property or use the marker.setDraggable() function:

var homeMarker = new google.maps.Marker({
	position: homeLatlng,
	map: map,
	title: "Check this cool location",
	draggable: true
});

Fantastic. But supposing you’re not keen on the default marker image? Not a problem. Google have lots of different marker images you can use:

To use one of these images, just specify the icon property when creating the marker, or use the marker.setIcon() function. You can use either an absolute or relative URL (for images you’re hosting yourself).

var homeMarker = new google.maps.Marker({
	position: homeLatlng,
	map: map,
	title: "Check this cool location"
	icon: "http://maps.google.com/mapfiles/ms/micons/blue.png"
});

One difference you’ll notice from when we just used the default image, is that no shadow is displayed underneath the marker. You might not care, although it’s a nice visual touch that gives the map a bit of depth. If you want to specify a shadow, you need to have a shadow image as well. This is the shadow image for the Google markers:

One way to setup a shadow is just specify the shadow property on the MarkerOptions, or use marker.setShadow().

var homeMarker = new google.maps.Marker({
	position: homeLatlng,
	map: map,
	title: "Check this cool location",
	icon: "http://maps.google.com/mapfiles/ms/micons/blue.png",
	shadow: "http://maps.google.com/mapfiles/ms/micons/msmarker.shadow.png"
});

However this doesn’t actually place the shadow in the correct location for the above example (see image on the right). I think it’s important to understand what’s happening here.
The marker image is 32 x 32 pixels in dimensions. The shadow image is also 32 pixels high, but is wider, 59 pixels. Both images are currently being centred horizontally on the same point on the map.

Shadow:

Marker:

Together:

What we need to do is create MarkerImage objects for both the marker and its shadow, which will allow us to offset the shadow image to be slightly more to the right, so that it lines up nicely with the marker.

var image = new google.maps.MarkerImage(
	'http://maps.google.com/mapfiles/ms/micons/blue-dot.png',
	new google.maps.Size(32, 32),	// size
	new google.maps.Point(0,0),	// origin
	new google.maps.Point(16, 32)	// anchor
);

var shadow = new google.maps.MarkerImage(
	'http://maps.google.com/mapfiles/ms/micons/msmarker.shadow.png',
	new google.maps.Size(59, 32),	// size
	new google.maps.Point(0,0),	// origin
	new google.maps.Point(16, 32)	// anchor
);

So what’s going on here? We define the URL of the image, then the size of the image (width,height). Then the origin, which is the x,y coordinates of where our marker starts on that image (useful if you’re using one sprite image to do lots of markers). The origin locates the top-left corner of our marker. The anchor is the important one here for lining up our marker and our image. This is where the image ‘anchors’ itself to our latlng coordinates on the map. So as the marker comes to a point half-way along it, we say it’s 16 pixels along and 32 points down (i.e. at the bottom of the image). We then repeat this with the shadow, where the tip of the shadow is also 16 pixels along that image, coincidentally.

We then attach the icon and its shadow to the marker like so:

var homeMarker = new google.maps.Marker({
	position: homeLatlng,
	map: map,
	title: "Check this cool location",
	icon: image,
	shadow: shadow
});

You can also get markers from other sources, e.g.

Finally, here’s the complete code showing how to add custom marker images as outlined above:

<!DOCTYPE html>
<html>
<head>
<title>Google Maps API V3 lesson 2</title>

<meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
<style type="text/css">
html { height: 100% }
body { height: 100%; margin: 0; padding: 0 }
#map_canvas { height: 300px; width:600px }
</style>

<script type="text/javascript" src="http://maps.googleapis.com/maps/api/js?sensor=false"></script>

<script type="text/javascript">
	function initialize() {
		var homeLatlng = new google.maps.LatLng(24.696554,-81.328238);

		var myOptions = {
			zoom: 15,
			center: homeLatlng,
			mapTypeId: google.maps.MapTypeId.ROADMAP
		};

		var map = new google.maps.Map(document.getElementById("map_canvas"), myOptions);

		var image = new google.maps.MarkerImage(
			'http://maps.google.com/mapfiles/ms/micons/green-dot.png',
			new google.maps.Size(32, 32),	// size
			new google.maps.Point(0,0),	// origin
			new google.maps.Point(16, 32)	// anchor
		);

		var shadow = new google.maps.MarkerImage(
			'http://maps.google.com/mapfiles/ms/micons/msmarker.shadow.png',
			new google.maps.Size(59, 32),	// size
			new google.maps.Point(0,0),	// origin
			new google.maps.Point(16, 32)	// anchor
		);

		var homeMarker = new google.maps.Marker({
			position: homeLatlng, 
			map: map,
			title: "Check this cool location",
			icon: image,
			shadow: shadow
		});
	}

	google.maps.event.addDomListener(window, 'load', initialize);
</script>
</head>
<body>
	<div id="map_canvas"></div>
</body>
</html>

September 21, 2011

ColdFusion LSCurrencyFormat inserts hard spaces?

Filed under: Coldfusion — duncan @ 8:31 pm
Tags: , , , , , ,

Today I was doing some work with the LSCurrencyFormat function for the very first time. Along the way I discovered something slightly unexpected. If the locale you specify uses spaces for a thousands separator (e.g. French), then Adobe ColdFusion (no idea for Railo or BlueDragon), doesn’t just add a normal space, it uses a ‘hard’ space.

I wouldn’t have found this out at all if it wasn’t for the fact I was doing some unit tests with MXUnit at the time. My test case was something like this:

<cfset strPrice = lsCurrencyFormat("12345678.9", "none", "fr_FR")>
<cfset assertEquals("12 345 678,90",  strPrice)>

So initially that failed, giving me the useful message that
Expected ’12 345 678,90′ but received ’12 345 678,90′. These values should be the same.
Puzzling. I was doing this in Firefox; if I selected just the error message and then chose View Selection Source, I could see it actually said
Expected ’12 345 678,90′ but received ’12&nbsp;345&nbsp;678,90′.

Ahah, so I assumed it was inserting the ‘&nbsp;’ non-breaking space HTML entity into the value instead of just a normal single space. So I updated my unit test assertion to be like this:

<cfset assertEquals("12&nbsp;345&nbsp;678,90",  strPrice)>

But that also failed, and this time when checking the source, it said
Expected ’12&nbsp;345&nbsp;678,90′ but received ’12&nbsp;345&nbsp;678,90′.
Curiouser and curiouser!

So at this point, I decided to loop over the actual string returned by lsCurrencyFormat to examine it more closely:

<cfoutput>
	<cfset french = lsCurrencyFormat("12345678.90", "none", "fr_FR")>
	
	<cfloop index="i" from="1" to="#Len(french)#">
		<cfset char = mid(french, i, 1)>
		
		#char#:#Asc(char)#<br/>
	</cfloop>
</cfoutput>

Which produced output that looked like:

1:49
2:50
 :160
3:51
4:52
5:53
 :160
6:54
7:55
8:56
,:44
9:57
0:48

The normal HTML space is ASCII character 32. But in this case it’s inserting ASCII character 160, which the browser interprets as &nbsp;. So for my unit test to work, I’ll have to do something like:

<cfset assertEquals("12" & Chr(160) & "345" & Chr(160) & "678,90",  strPrice)>

Dunno why Adobe in their infinite wisdom (to be honest this probably dates back to Allaire or Macromedia) would use character 160 instead just a normal single space character 32. And I expect other locale-specific formatting functions must do the same thing.
I’m getting exactly the same results with java.text.DecimalFormatSymbols.getGroupingSeparator(), so I expect ColdFusion is using that functionality in the background.

I suppose it shouldn’t really cause any problems, apart from perhaps when you’re needing to unit test (or perhaps if parsing some HTML you’ve received by CFHTTP). Just, unexpected is all.

September 18, 2011

Google Maps API – an introduction

This is the first in a series of posts I intend to do looking into some uses of the Google Maps Javascript API, V3. Let’s start at the very beginning – you want to display a map on a webpage, so what’s required. This is the bare minimum you need to do.

Add an empty <div> to your HTML page, with a suitable ID attribute:

<div id="map_canvas"></div>

Add some CSS like this:

<style type="text/css">
html { height: 100% }
body { height: 100%; margin: 0; padding: 0 }
#map_canvas { height: 100% }
</style>

Include the Google Maps javascript file:

<script type="text/javascript" src="http://maps.googleapis.com/maps/api/js?sensor=false"></script>

Create a javascript function that includes all the next few lines of code; let’s stick to convention and call it initialize.

Create a latlong object which indicates the latitude and longitude where your map is centred:

var homeLatlng = new google.maps.LatLng(51.42838,0.0); // London

If you don’t already know the latitude and longitude for where you want to place your map, there’s a very easy way to find out:

  1. Go to Google Maps
  2. Search for your desired destination, and zoom right in
  3. Right-click on the map at your destination, and choose the “What’s here?” option
  4. The latitude and longitude coordinates will appear in the search box

Create a structure with the following properties:

var myOptions = {
	zoom: 10,
	center: homeLatlng,
	mapTypeId: google.maps.MapTypeId.ROADMAP
};

The zoom value can be any integer between 0 (fully zoomed out) and about 18. Some cities will support zoom up to about 20, and there are isolated cases of parts of maps having a zoom level up to 23 or more! In general though you won’t want to zoom in that far. Typically I’d set this around 7 – 15 depending on what kind of map I’m displaying.

The center value uses the latlong object you already created.

The mapTypeId value can be one of four options:

  1. google.maps.MapTypeId.ROADMAP – normal street map
  2. google.maps.MapTypeId.SATELLITE – satellite photos (might not be available for all locations if you’re zoomed in too far)
  3. google.maps.MapTypeId.HYBRID – combination of a satellite map with streets overlaid on top
  4. google.maps.MapTypeId.TERRAIN – what it sounds like, indicates natural features, height of land etc

I generally stick with ROADMAP, as that’s the most typical map type you see by default. Here’s a demonstration of the different types:

Create a new map with the structure of options you just created, and specifying the DIV where you want the map to appear:

var map = new google.maps.Map(document.getElementById("map_canvas"), myOptions);

That’s the end of your initialize function. You could just call this using the onload attribute of the body tag. Or if you were using jQuery, with $(document).ready(). However, Google has an event listener, that does the same thing:

google.maps.event.addDomListener(window, 'load', initialize);

Wrapping it all up, here’s the complete page for simply displaying a map at a given location:

<!DOCTYPE html>
<html>
<head>
<title>Google Maps API V3 lesson 1</title>

<meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
<style type="text/css">
html { height: 100% }
body { height: 100%; margin: 0; padding: 0 }
#map_canvas { height: 100% }
</style>

<script type="text/javascript" src="http://maps.googleapis.com/maps/api/js?sensor=false"></script>

<script type="text/javascript">
	function initialize() {
		var homeLatlng = new google.maps.LatLng(51.476706,0); // London
		
		var myOptions = {
			zoom: 15,
			center: homeLatlng,
			mapTypeId: google.maps.MapTypeId.ROADMAP
		};
		
		var map = new google.maps.Map(document.getElementById("map_canvas"), myOptions);
	}
	
	google.maps.event.addDomListener(window, 'load', initialize);
</script>
</head>
<body>
	<div id="map_canvas"></div>
</body>
</html>

September 17, 2011

Using jQuery to animate table rows

Filed under: Javascript,jQuery — duncan @ 8:00 am
Tags: , , ,

jQuery has some nice ways to animate content, e.g. sliding or fading it into view. These don’t really work on <tr> or <td> tags though. I had a situation recently where we wanted to add an extra <tr> into a table, and make it slide in. The answer’s very simple (thanks to stackoverflow) – wrap the table content in a div, and add the slide animation to that instead.

So imagine you have a very simple example table like this:

<table>
	<tr id="row1">
		<td><a href="">Show another row</a></td>
	</tr>
</table>

This javascript would then make the second <tr> slide into view.

<script type="text/javascript">
$(document).ready(function() {
	// hidden content to add after the current row
	$('#row1').after("<tr id='row2'><td><div style='display:none'>There's a voice that keeps on calling me. Down the road, that's where I'll always be. Every stop I make, I make a new friend. Can't stay for long, just turn around and I'm gone again. Maybe tomorrow, I'll want to settle down, Until tomorrow, I'll just keep moving on.</div></td></tr>");

	$('#row1 a').click(function() {
		if ($('#row2 div').is(":visible")) {
			// hide the div
			$('#row2 div').slideUp(700);
			
		} else {
			// slide the div into view
			$('#row2 div').slideDown(700);
		}
		
		// prevent the click on the link from propagating
		return false;
	});
});
</script>

Of course it gets more complicated if you have more than one <td>. For example:

<table>
	<tr id="row1">
		<td>A</td>
		<td>One</td>
		<td>Red</td>
		<td>Apple</td>
		<td>$0.99</td>
		<td><a href="">Show row 2</a></td>
	</tr>
</table>

In that case you can use the .wrapInner function to add a hidden div to all the <td> tags.

<script type="text/javascript">
$(document).ready(function() {
	// content to add after the current row
	var $row2 =	$('<tr id="row2">' +
					'<td>B</td>' +
					'<td>Two</td>' +
					'<td>Yellow</td>' +
					'<td>Banana</td>' +
					'<td>$1.23</td>' +
					'<td> </td>' +
				'</tr>');
	
	// add hidden divs around the content of all the TD tags
	$row2.find('td').wrapInner('<div style="display:none" />');
	
	// add this row after the first row
	$('#row1').after($row2);

	$('#row1 a').click(function() {
		if ($('#row2 div').is(":visible")) {
			// hide the div
			$('#row2 div').slideUp(700);
			
			// update link text
			$('#row1 a').text('show row 2');
			
		} else {
			// slide the div into view
			$('#row2 div').slideDown(700);
			
			// update link text
			$('#row1 a').text('hide row 2');
		}
		
		// prevent the click on the link from propagating
		return false;
	});
});
</script>
Next Page »

Theme: Rubric. Blog at WordPress.com.

Follow

Get every new post delivered to your Inbox.