« Archives in March, 2013

Creating an Image Preloader

Image Preloader using JQuery

I recently was creating an image viewer for a customer.  It had the usual qualities of an image viewer where you need preload images to improve response times for the user.

Requirements

  • Load X images behind/infront of current image
  • Handle image load errors
  • Callback for load complete
  • Tie attributes to an image

In addition, we will use JQuery to facilitate this.

Lets Get Started

So if we abstract this a little, we want to have an Image Object with the following attributes…

ImageObject

  • url – The url of the image to load
  • callback – A callback function for when the image load is complete
  • scope – With javascript, closure, we want to call the callback within a scope other than window.  This will hold this information.
  • loaded – Boolean so we can track the images state.
  • loadFailed – So with an image, we may have failed to load or timeout.  So we need to track that.
  • timeout - How long to wait on things to load.
		/**
		 * @classDescription Image object which holds information about the image which is preloading.
		 * This object can be prototyped with extendImage.
		 * @param {String} url The url of image
		 * @param {Function} callback The users callback function
		 * @param {Object} scope The scope to call callback function in
		 * @param {Boolean} loaded Whether the image has been loaded
		 * @param {Jquery Object} $img The reference to the jquery image object in preloading node.
		 * @param {Object} objRef This object's reference
		 * @see extendImage()
		 */
		_Image : function(url, callback, scope, loaded, $img, objRef){
			this.url = url;
			this.callback = callback;
			this.scope = scope;
			this.loaded = loaded;
			this.$img = $img;
			this.func = objRef._imageLoadComplete;
			this.loadFailed = true;
			this.timeout = 5000;//5 seconds
			this.start = new Date().getTime();
			this.finish = 0;
		}

Now We Need to Store the Image Objects

So if we are going to have an image object to represent each image, then we need a way to store these and be able to get them.  Unfortunately javascript doesn’t by default have a good way for use to store and retrieve data.  The default array, is somewhat limited.  Fortunately we can create indexes of the array with strings.  Basically giving us a simple hash table.

However, I kinda wanted to spice this up a little.  I could have used the URL of the image as the index, but what fun is that.  So lets create a hash of the URL and use that as an index.

hashCode = function(str){
	str = str + "";
	var hash = 0;
	if (str.length == 0) return hash;
	var len = str.length;
	for (var i = 0; i < len; i=i+1) {
		char = str.charCodeAt(i);

		hash = ((hash<<5)-hash)+char;
		hash = hash & hash; // Convert to 32bit integer
	}

	return hash >>> 1; //I want positive numbers
}

Now you will probably argue that looping over each character  is bad, why not just use the URL.  Well, your probably right, but this is more fun.

hash = ((hash<<5)-hash)+char;

This is better explained here.

Initiate the Pre-loading of an Image

We will need a simple function to load images. Everything that refers to “this” refers to the object that wraps all these functions.

preload = function(url, callback, scope, timeout){
	var hash = this._hashCode(url);
	if(this.isLoaded(hash)) return;

	if(typeof scope === "undefined") scope = window;

	//create image object
	var $img = $(document.createElement('img'))
		.attr('src',url)
		.attr('id',hash);
	this._images[hash] = new this._Image(url, callback, scope, false, $img, this);

	//bind the onload...pass vars using closure ;) 
	this._images[hash].$img.load(function(obj, hash){
		//only executes on successful load
		return function(){obj._images[hash].func.call(obj, obj._images[hash]);};
	}(this, hash));

	//we always want to trigger our functions, but jquery only
	//callsback on success, so have to keep polling at the image timeout
	if(typeof timeout !== "undefined") this._images[hash].timeout = timeout;
	this.waitOnImage(url, this._imageLoadComplete, this, this._images[hash].timeout, 2);

	this._$loadNode.append(this._images[hash].$img); //append loaded image to the loading DOM element

	return this._images[hash];
};

There are actually a couple interesting segments of this code.

scope - This will allow us to scope the context to the callback.  This is useful to avoid namespace collisions and ensure you are triggering the correct event to specified image loading.

this._images[hash].$img.load(function(obj, hash){
		//only executes on successful load
		return function(){obj._images[hash].func.call(obj, obj._images[hash]);};
	}(this, hash));

This uses the JQuery default load callback.  The problem with the load event is it is unreliable.  It won’t fire if the image fails to load and won’t fire reliably cross-browser and in other circumstances.  These are describer in the jquery link.

Handle the load event not working

To do this we basically need some sort of polling to check if the image is loaded.  That is the waitOnImage() function.

WaitOnImage Function

		/**
		 * Bind specific callback to wait on image
		 * @param {String} url The url of image to wait on load for
		 * @param {Function} callback Callback function to call when loaded
		 * @param {Object} scope The scope to call callback in.  Default to window.
		 * @param {Integer} perdiod Time in milliseconds between recalling wait
		 * @param {Integer} maxTries max attempts to call this function
		 * @param {Integer} tryCount defaults to 0.  Used for cancelling this.
		 */
		waitOnImage : function(url, callback, scope, period, maxTries, tryCount){
			if(typeof period === "undefined") period = 50;
			if(typeof maxTries === "undefined") maxTries = 100;
			if(typeof scope === "undefined") scope = window;
			if(typeof tryCount == "undefined") tryCount = 0;

			var hash = this._hashCode(url);
			var imgObj = this._images[hash];

			if(typeof imgObj === "undefined"){
				this.preload(url, null, null);
			} else if(imgObj.loaded || tryCount > maxTries){
				callback.call(scope, imgObj);
				return;
			}

			//use closures to call self
			setTimeout(
				function(url, callback, scope, period, maxTries, tryCount, _this){
					return function(){
						_this.waitOnImage(url, callback, scope, period, maxTries, tryCount);
					};
				}(url, callback, scope, period, maxTries, ++tryCount, this)
			,period);

		}

The interesting part of the code here is the setTimeout function call.  This accomplishes 2 things.

  • Keeps this from blocking.  Meaning, setTimeout will execute in parallel to this code blocks execution.  If you look at the preload function you see it call this function, which if it just continuously called itself, it would block and prevent the function from completing until the image finished loading.  This would be bad as images can sometimes take quite a while to load.
  • allows for closure to scope the variables to this execution.

Extensibility?

So we have covered the basics of creating a preloader, but its so basic we really need a mechanism to extend its functionality.

/**
		 * Access the prototype to _Image to add your own attributes.
		 * @param {String} name Name of the reference in prototype.
		 * @param {Object} obj The function that will be referenced by name.
		 */
		extendImage : function(name, func){
			//todo add prototype stuff
			this._Image.prototype[name] = func;
		}

This alows us to add whatever we want to the _Image object.  Its really just for accessibility, but a nice feature none the less.  Check out the source code and documentation here.

Hash Function in Javascript

Writing a Hash Function in JavaScript

So when I was writing my image preloader I had the idea where I wanted to store an image object in an array to easily look up.  Now the key would be the url, but URLs are big and can be ugly, so I really wanted to use a hashmap.  But javascript does not have a native implementation.  In addition, why should it, its arrays are associated and I could very well just use the url as the key.

Anyway, although it probably made more sense to use the URL as the key I wanted to use a hash for lookup.  Thought it would be fun.

So I did a quick google and found this http://werxltd.com/wp/2010/05/13/javascript-implementation-of-javas-string-hashcode-method.

The Algorithm

s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]

Straight Forward Implementation

function hashCode(str){
	len = str.length;

	hash = 0;
	for(i=1; i<=len; i++){
		char = str.charCodeAt((i-1));
		hash += char*Math.pow(31,(len-i));
		hash = hash & hash; //javascript limitation to force to 32 bits
	}

	return hash;
}

Now this is very straight forward, but isnt the implementation they gave in the link above.  That implementation is actually rather clever and avoids using an expensive multiplication and power function.

The Clever Approach

function hashCode2(str){
	var hash = 0;
	if (str.length == 0) return hash;
	for (i = 0; i < str.length; i++) {
		char = str.charCodeAt(i);		hash = ((hash<<5)-hash)+char;		hash = hash & hash; // Convert to 32bit integer
	}
	return hash;
}

The Break Down

So I was a little confused and wanted to understand how these can be the same, so I dusted off my binary math hat and stepped through it.

char = str.charCodeAt(i)

Just gets the ansii value or utf-8 character code value.  So for example if str = “hello”, then str.charCodeAt(0) would return 104.  Check out this character chart.

hash = ((hash<<5)-hash)+char;

This is the nitty gritty scary part right.  Its actually not that bad and make sense once we step through it.

hash<<5

This is a simple bit shift operator of 5 to the left, but lets review bit shifts of the value 0001.

0001 is 1 in base 10
0010 is a bit shift left 1 and is 2 in base 10

var x = 1; //0001 in binary

Binary Value Bit Shifts Left Code Example Base 10 Value Algebra Equivalent Exponential
0001 0 x<<0 1 x * 1 x * 2 0
0010 1 x<<1 2 x * 2 x * 2 1
0100 2 x<<2 4 x * 2 * 2 x * 2 2
1000 3 x<<3 8 x * 2 * 2 * 2 x * 2 3

As you can see a bit shift is the same as multiplying by 2 to some power.  So lets look at what we can rewrite things to.

hash<<5 or
hash * 2^5 or
hash * 32

So we can rewrite

hash = ((hash<<5)-hash)+char; as

hash = ((hash * 32) - hash) + char; or

hash = hash * (1 * 32 - 1) + char; or

hash = hash * 31 + char;

Whew we figured out where the 31 came from.  Originally I thought it had to do with a prime number or something, but I was way off.

But what about the exponential?

To figure this out we need to step through the for loop

for (i = 0; i < str.length; i++) {
		char = str.charCodeAt(i);
		hash = ((hash<<5)-hash)+char;
		hash = hash & hash; // Convert to 32bit integer
	}

1: hash = hash*31 + char1
2: hash = (hash * 31 + char1) * 31 + char2
3: hash = ((hash * 31 + char1) * 31 + char2) * 31 + char3
etc….

What I did above was replace hash with the value from the previous iteration to show the expansion of the for loop.

So at iteration 3 of the for loop we have…

hash = ((hash * 31 + char1) * 31 + char2) * 31 + char3

Lets do some algebra on this…

hash = ((hash * 31 * 31 + char1 * 31) + char2) * 31 + char3

hash = ((hash * 31 * 31 * 31 + char1 * 31 * 31 + char2 * 31 + char3

or…

hash = hash * 31^3 + char1 * 31^2 + char2 * 31^1 + char3 * 31^0

which gives us what we were looking for…where hash = 0  to start

hash = 0 * 31^3 + char1 * 31^2 + char2 * 31^1 + char3 * 31^0

which gives….

hash = char1 * 31^2 + char2 * 31^1 + char3 * 31^0

hash = s[0] * 31 ^ (n-1) + s[1] * 31^(n-2) + s[2] * 31^(n-3)

There we go.  We have proven it works!

Arcgis, How to Find the Point User Clicked On

About This Post

The main goal of this post is to explain how I figured out finding which point a user clicked on in an arcgisonline.com generated map.  You would think the API would cover this, but unfortunately it is a problem that others found.  Here is thread about the problem.  I spent considerable time trying to solve this problem.  Because I knew it was possible, but why wasn’t it in the javascript API?

Goals

  • Load a Feature Layer with your data
  • Listen for a map click and identify which point the user clicked on.

Simple goal right?  You wouldn’t believe that this was actually quite difficult.  If there is now a simple solution in the API, I swear as of 1/1/2013 there was not.

Side Note:
The infoTemplate is passed the data, but I couldnt figure out for the life of me how to get this content.  We know its there, but I didn’t want to spend forever unraveling the arcgis code base looking for a way to do this.  I never ended up being able to do it this way, so I had to force it with the arcgis javascript API.  We could wait till the infoTemplate loaded and then parse its HTML elements, but that isn’t a very good solution either.

What we need?

Solution / Example

1. Setup Our Feature Layer

Lets example this part of the code in initOperationalLayer function from the downloaded example above.

featureLayer = new esri.layers.FeatureLayer("http://sampleserver6.arcgisonline.com/arcgis/rest/services/USA/MapServer/2",{

mode: esri.layers.FeatureLayer.MODE_ONDEMAND,
outFields: ["*"],
infoTemplate: infoTemplate
});

We want to replace our feature layer in bold and underlined above with the our recreational one I referenced above.

Replace

http://sampleserver6.arcgisonline.com/arcgis/rest/services/USA/MapServer/2

with

http://sampleserver6.arcgisonline.com/arcgis/rest/services/Recreation/FeatureServer/0

If you run this, you will now see our map shows the available recreation centers that were in our feature layer map.

2. Lets Hook into the Map onClick Event

So at the bottom of the initOperationalLayer function we want to add the following code:

//hook into the onClick event
dojo.connect(featureLayer, "onClick", function(evt){

	//we need to query the feature layer for this point
	var query = new esri.tasks.Query();

	//get the location of mouse click..The API doesnt tell you about this.  This is the magic part
	//I stumbled across this value by using debugger and console.log(evt) and searching the object.
	query.geometry = evt.graphic.geometry;
	query.spatialRelationship = esri.tasks.Query.SPATIAL_REL_CONTAINS;

	//select the actual point
	featureLayer.selectFeatures(query, esri.layers.FeatureLayer.SELECTION_NEW,
		function(features){
			//user clicked on something, then we should have features
			if(features.length > 0){
				var data = "";
				for(var x in features){
					data = "Feature "+x+":n";

					//building a string of the queried data
					for(var y in featureLayer.fields){
						var name = featureLayer.fields[y].name;
						data+= name+" : "+features[x].attributes[name]+"n";
					}
			}
				alert(data);
			}else{
				alert("Failed to select the feature");
		}
	});

});

The special part is this line:

query.geometry = evt.graphic.geometry;

This gives the query the “spatial reference” (I guess that is what you might call it) to perform query on Feature Layer for.

3.  That it, we are done.  You can get the solution here.