Monthly Archives: January 2009

Using previously highlighted text in TextMate snippets

In a previous (and very verbose) post on TextMate snippets, I showed a snippet to easily and simply use a tab trigger to insert a try... catch statement. If you missed it feel free to catch up here. The idea was that you type in try, press tab and TextMate spits out:

try {

} catch(e) {

}

Now, the cursor is automatically positioned inside the try block, and the next time you press tab it moves to the catch block. Pressing tab again moves you outside the block. This is great, but not hugely useful in the real world. What would be more likely than writing an empty try... catch statement up-front would be to write some code, and choose to surround it in the try block. I promised to tell you how to do that, and it is much simpler than I thought.

The answer is the TextMate variable $TM_SELECTED_TEXT. It does exactly what it says it does, but you can’t use a tab trigger to achieve our goal here. Highlight some text, and start typing and of course, the text is overwritten. In this instance, a tab trigger will not do. You need to use a "Key Equivalent" (or hotkey or shortcut key to the rest of us). In my instance, I have more than enough of those to remember already, thankyouverymuch, and so I highlight the text and do one of the following:

  • Click the Bundle menu item at the top of the screen and drill down to my snippet
  • Press Ctrl+Escape and drill down to my snippet
  • Or, my favourite, press Ctrl+Command+T to bring up a list of all the snippets, type "try" to filter the list, and press return

There is nothing to stop you just typing try and pressing return either.

Here is the final snippet:

Name:
try ... catch
Tab trigger:
try
Snippet:
try {
	$TM_SELECTED_TEXT$1
} catch(e) {
	$2
}


try... catch TextMate Bundle Editor Example

Note that there should be a trailing extra carriage return, to ensure the final tab takes us out on to the line following the block.

Six Ways to Iterate Through an Array (or Chrome’s V8 is a Monster)

There are actually all kinds of ways to iterate through an array in JavaScript. Here, I will concentrate on six of them, describe their advantages, and do performance testing to actually prove which of them is the faster.

Loop 1 – the native for loop

for (var i=0; i < arrTestData.length; i++) {
	/* do stuff */
};

We’ve all seen this a million times. It is the easiest to read, and the most common. It is also the least efficient.

Loop 2 – the native for loop improved

for (var i=0,j=arrTestData.length; i<j; i++) {
	/* do stuff */
};

The improvement here, is that a second variable j is employed, to stop us having to query the length property of the array with every iteration. Getting the length can be a costly process and it makes more sense to just do this once. Where it is important that iterate incrementally, and in ascending order (i.e. 0,1,2,3,4,5) this is the fastest method.

Loop 3 – the native for loop in reverse

for (var i = arrTestData.length - 1; i >= 0; i--){
	/* do stuff */
};

The main advantage of this method is that only one variable is employed. It is slightly faster than the other for loops, but goes in descending order (i.e. 5,4,3,2,1,0) which may make it inappropriate for your needs.

Loop 4 – the native while loop

var i = 0;
while (i < arrTestData.length) {
	/* do stuff */
	i++;
};

This is essentially the same as Loop 1. We are still querying the length of the array with every iteration.

Loop 5 – the native while loop in reverse

var i = arrTestData.length - 1;
while (i > 0) {
	/* do stuff */
	i--;
}

This is essentially the same as Loop 2. We are storing the length of the array, to avoid querying with every iteration.

Loop 6 – the native while loop in reverse improved

var i=arrTestData.length;
while (i--) {
	/* do stuff */
}

This is the tidiest code, and the fastest. Since a zero integeric value is falsy, our condition is as simple as it could be, and only one variable is employed. I give credit to Richard Hubbard (get a blog already, Richard!) for showing me this.

Below, are the results of my testing.

OS Browser Test Result 1 Result 2 Result 3 Average
OSX 10.5.6 Firefox 3.0.5 Loop 1 46ms 47ms 46ms 46ms
Loop 2 31ms 32ms 31ms 31ms
Loop 3 34ms 33ms 33ms 33ms
Loop 4 46ms 45ms 46ms 45ms
Loop 5 32ms 32ms 32ms 32ms
Loop 6 29ms 29ms 29ms 29ms
OSX 10.5.6 Safari 3.2.1 Loop 1 44ms 45ms 43ms 44ms
Loop 2 33ms 32ms 34ms 33ms
Loop 3 31ms 33ms 31ms 31ms
Loop 4 45ms 45ms 45ms 45ms
Loop 5 33ms 33ms 33ms 33ms
Loop 6 29ms 29ms 29ms 29ms
OSX 10.5.6 Camino 1.6.4 Loop 1 106ms 93ms 91ms 96ms
Loop 2 67ms 64ms 61ms 64ms
Loop 3 61ms 64ms 61ms 62ms
Loop 4 91ms 90ms 90ms 90ms
Loop 5 61ms 64ms 67ms 64ms
Loop 6 60ms 60ms 60ms 60ms
OSX 10.5.6 Opera 9.61 Loop 1 34ms 32ms 31ms 32ms
Loop 2 23ms 24ms 29ms 25ms
Loop 3 24ms 23ms 23ms 23ms
Loop 4 32ms 31ms 32ms 31ms
Loop 5 22ms 26ms 23ms 23ms
Loop 6 26ms 25ms 25ms 25ms
Windows XP SP2 Chrome 1.0.154.36 Loop 1 2ms 3ms 2ms 2ms
Loop 2 2ms 2ms 2ms 2ms
Loop 3 2ms 2ms 2ms 2ms
Loop 4 3ms 3ms 3ms 3ms
Loop 5 2ms 2ms 2ms 2ms
Loop 6 2ms 2ms 2ms 2ms
Windows XP SP2 IE 7.0.5730.13 Loop 1 171ms 172ms 172ms 171ms
Loop 2 125ms 125ms 125ms 125ms
Loop 3 125ms 125ms 125ms 125ms
Loop 4 1172*ms 172ms 172ms 172ms
Loop 5 125ms 125ms 141ms 130ms
Loop 6 125ms 125ms 125ms 125ms
Windows XP SP2 FF 3.0.5 Loop 1 37ms 37ms 37ms 37ms
Loop 2 26ms 26ms 26ms 26ms
Loop 3 25ms 26ms 25ms 25ms
Loop 4 37ms 36ms 37ms 36ms
Loop 5 25ms 25ms 25ms 25ms
Loop 6 27ms 26ms 27ms 26ms
Windows XP SP2 Safari 3.2.1 Loop 1 53ms 52ms 53ms 52ms
Loop 2 40ms 39ms 40ms 39ms
Loop 3 39ms 40ms 40ms 39ms
Loop 4 55ms 55ms 54ms 54ms
Loop 5 41ms 41ms 40ms 40ms
Loop 6 37ms 37ms 37ms 37ms
Windows XP SP2 IE 6 (multiple IEs) Loop 1 156ms 172ms 156ms 161ms
Loop 2 109ms 125ms 109ms 114ms
Loop 3 110ms 125ms 125ms 120ms
Loop 4 156ms 1313*ms 156ms 541ms
Loop 5 125ms 109ms 125ms 119ms
Loop 6 125ms 110ms 125ms 120ms
Windows XP SP2 Opera 9.63 Loop 1 16ms 31ms 16ms 21ms
Loop 2 16ms 15ms 16ms 15ms
Loop 3 15ms 16ms 16ms 15ms
Loop 4 31ms 16ms 31ms 26ms
Loop 5 15ms 16ms 16ms 15ms
Loop 6 0ms 15ms 16ms 10ms

I did every test three times, and took the average as the result. Internet Explorer 6 and 7 both presented a confirm dialogue box, asking if I wanted to keep running the script, which paused execution of the code. This resulted in two vastly bloated results, which I have generously ignored. It didn’t help – IE is the obvious loser here in every instance, surprisingly showing very little improvement between IE6 and IE7.

Copying the data was a pain – although in OSX I could copy and paste from pretty much any source, including alert dialogue boxes. In Windows only Firefox would let me do this.

A shock in all of this, was just how fast Chrome performed, clocking in at 100,000 iterations per millisecond! I actually wrote different versions of the code just for Chrome, just to ensure it was actually doing something. That V8 engine they have in there is amazing.

The point of this, though, is not to compare browser speed. Of course, we can’t compare between OSX and Windows XP in this instance, the hardware was different. Also, this is just a very specific and small set of tests and cannot be considered a fair test of browser speed. Really, what we wanted to achieve was to see which method of iterating through an array is the most efficient.

To that end, here is a final table, showing the average result across browsers for each loop.

Test Average Result (ms)
Loop 1 73.6
Loop 2 52.6
Loop 3 52.7
Loop 4 73.1
Loop 5 53.6
Loop 6 51.4

The obvious conclusion is that although Loop 6 is the fastest, there is not that much in it – whereas Loop 1 and Loop 4 are dramatically slower than the others, demonstrating just how expensive the querying of the length property is.

Centre div in window, in iFrame, from iFrame, without an iFrame ID

Update: This code has been replaced.

Centring a div is not so hard. Centring a div on a window regardless of the windows scroll position is a little harder. Centring a div in the viewport in an iFrame that is full height (i.e. does not scroll) in a window that does scroll when that window is scrolled and you do not know the ID of the iFrame is a bit of a pain in the arse. So I record it here, in case it helps anyone. This function accepts a jQuery object as its only argument.

function centerIt($el) {
	var frm = $('iframe',top.document.body);
	var iframeXOffset = 0, iframeYOffset = 0, windowHeight = 0, windowWidth = 0;
	var i=frm.length;
	while (i--) {
		if (frm[i].contentDocument) {
			doc = frm[i].contentDocument;
		} else {
			doc = frm[i].contentWindow.document;
		}
		if (doc === document) {
			//located our iframe!
			iframeXOffset = $(frm[i]).offset().left;
			iframeYOffset = $(frm[i]).offset().top;
			break;
		}
	};
	if (jQuery.browser.msie) {
		windowWidth = top.window.document.documentElement.clientWidth;
		windowHeight = top.window.document.documentElement.clientHeight;
	} else {
		windowWidth = top.window.innerWidth;
		windowHeight = top.window.innerHeight;
	}
	var elHeight = $el.height();
	var newTop = ((windowHeight/2) - (elHeight/2)) - iframeYOffset + $(parent.document.documentElement).scrollTop();
	if ((newTop + elHeight) > $(document).height()) {
		newTop = $(document).height() - elHeight;
	}
	$el.css ({
		left: ((windowWidth/2) - ($el.width()/2)) - iframeXOffset + $(parent.document.documentElement).scrollLeft(),
		top: newTop
	});
}

Of course, this makes much more sense as a jQuery chainable plug-in:

/******************************************************************
Name: center
Description: Center a div on page, even in an iframe
Author: AK (www.zeroedandnoughted.com)
Date: 8th Jan 2009
Version: 0.1
Dependencies: jQuery
Notes:
******************************************************************/
(function($) {
    $.fn.center = function() {
        return this.each(function() {
			var $this = $(this)
			var frm = $('iframe',top.document.body);
			var iframeXOffset = 0, iframeYOffset = 0, windowHeight = 0, windowWidth = 0;
			var i=frm.length;
			while (i--) {
				if (frm[i].contentDocument) {
					doc = frm[i].contentDocument;
				} else {
					doc = frm[i].contentWindow.document;
				}
				if (doc === document) {
					//located our iframe!
					iframeXOffset = $(frm[i]).offset().left;
					iframeYOffset = $(frm[i]).offset().top;
					break;
				}
			};
			if (jQuery.browser.msie) {
				windowWidth = top.window.document.documentElement.clientWidth;
				windowHeight = top.window.document.documentElement.clientHeight;
			} else {
				windowWidth = top.window.innerWidth;
				windowHeight = top.window.innerHeight;
			}
			var elHeight = $this.height();
			var newTop = ((windowHeight/2) - (elHeight/2)) - iframeYOffset + $(parent.document.documentElement).scrollTop();
			if ((newTop + elHeight) > $(document).height()) {
				newTop = $(document).height() - elHeight;
			}
			$this.css ({
				left: ((windowWidth/2) - ($this.width()/2)) - iframeXOffset + $(parent.document.documentElement).scrollLeft(),
				top: newTop
			});
		});
	};
})(jQuery);

Yes, I’m British. But yes, I spelt it “center”. I hate inconsistency worse than bastardised spelling. Comments welcomed, as always.

Update: It was possible, if you resized the frame, to get the centred box to appear off-screen. This has been fixed.

Bookmarklet to de-cache CSS and images – Version 2

You may recall, in this previous post I showed you how to use a bookmarklet (typically a small piece of JavaScript you can save as a bookmark and run against a page) to get the latest versions of CSS files and images, and not use the cached files. The previous version had some limitations. Namely:

  • Background images set in CSS would not be refreshed
  • Frame and iframe contents would not be refreshed
  • In some instances it was one-hit only – i.e. you could only run it once, without having to manually refresh the page anyway

So, I would like to present to you – CacheBuster version 2. Or CacheBusterizer the second. Or MegaCacheKiller 2. You get the idea.

This time, I will step through the code and how it works.

Getting the current values of styles set via CSS can be a pain. Unless they were explicitly set via JavaScript, we can only fetch them using currentStyle or getComputedStyle, complete of course with their own browser idiosyncrasies. Therefore, the first thing we need is a function to do this for us in a cross-browser fashion. I have amended a version I found on Incoherent Babble.

function gcs(e,s) {
	if (typeof e.currentStyle != 'undefined') {
		return e.currentStyle[s];
	} else {
		return document.defaultView.getComputedStyle(e, null)[s];
	}
}

In this instance, e represents the element, and s is a string representing the style we are looking for. Remember that we are looking JavaScript termed styles, not CSS – so backgroundImage, rather than background-image.

Since I am going to loop through all of the frames in the page, as well as the main window itself, I have decided to join these together in a single array, rather than writing the code out twice.

var z =[];
z.push(self);
z.concat(self.frames);

To get the latest version of the files, I will just amend a querystring with the current time on the end of the filename.

var y = new Date().getTime();

And finally, we do the work:

for (k=0,l=z.length;k<l;k++) {
	x=z[k].document.getElementsByTagName('*');
	for (i=0,j=x.length;i<j;i++){
		bi = gcs(x[i],'backgroundImage');
		if (bi.indexOf(')') > -1){
			bi = bi.replace(/'/gi,'').replace(/"/gi,'');
			x[i].style.backgroundImage=bi.replace(')',(bi.indexOf('?')==-1?'?':'')+y+')');
		}
	}
	x=z[k].document.getElementsByTagName('link');
	for(i=0,j=x.length;i<j;i++){
		x[i].href+=(x[i].href.indexOf('?')==-1?'?':'')+y;
	}
	x=z[k].document.getElementsByTagName('img');
	for(i=0,j=x.length;i<j;i++){
		x[i].src+=(x[i].src.indexOf('?')==-1?'?':'')+y;
	}
}

This code:

  • Loops through all elements on the page, checks to see if they have a background image set and appends the time on the end of the URI if they do
  • Loops through all the link tags on a page for stylesheets, and appends the time on the end of the href of any it finds
  • Loops through all the img tags on a page, and appends the time on the end of the src of any it finds

Finally, we wrap the whole lot in a closure to make sure we don’t mess with the main window namespace, tidy up, and we get:

(function(){
	var gcs = function(e,s) {
	  if (typeof e.currentStyle != 'undefined')
	    { return e.currentStyle[s]; }
	  else
	    { return document.defaultView.getComputedStyle(e, null)[s]; }
	};
	var i,j,k,l,x,y,z,bi;
	z =[];
	z.push(self);
	z.concat(self.frames);
	y = new Date().getTime();
	for (k=0,l=z.length;k<l;k++) {
		x=z[k].document.getElementsByTagName('*');
		for (i=0,j=x.length;i<j;i++){
			bi = gcs(x[i],'backgroundImage');
			if (bi.indexOf(')') > -1){
				bi = bi.replace(/'/gi,'').replace(/"/gi,'');
				x[i].style.backgroundImage=bi.replace(')',(bi.indexOf('?')==-1?'?':'')+y+')');
			};
		};
		x=z[k].document.getElementsByTagName('link');
		for(i=0,j=x.length;i<j;i++){
			x[i].href+=(x[i].href.indexOf('?')==-1?'?':'')+y;
		};
		x=z[k].document.getElementsByTagName('img');
		for(i=0,j=x.length;i<j;i++){
			x[i].src+=(x[i].src.indexOf('?')==-1?'?':'')+y;
		};
	};
})();

The last thing to do, is stick it all on one line, and prefix javascript: and we’re done.

javascript:(function(){var gcs=function(e,s){if(typeof e.currentStyle!='undefined'){return e.currentStyle[s];}else{return document.defaultView.getComputedStyle(e,null)[s];}};var i,j,k,l,x,y,z,bi;z=[];z.push(self);z.concat(self.frames);y=new Date().getTime();for(k=0,l=z.length;k<l;k++){x=z[k].document.getElementsByTagName('*');for(i=0,j=x.length;i<j;i++){bi=gcs(x[i],'backgroundImage');if(bi.indexOf(')')>-1){bi=bi.replace(/'/gi,'').replace(/"/gi,'');x[i].style.backgroundImage=bi.replace(')',(bi.indexOf('?')==-1?'?':'')+y+')');}};x=z[k].document.getElementsByTagName('link');for(i=0,j=x.length;i<j;i++){x[i].href+=(x[i].href.indexOf('?')==-1?'?':'')+y;};x=z[k].document.getElementsByTagName('img');for(i=0,j=x.length;i<j;i++){x[i].src+=(x[i].src.indexOf('?')==-1?'?':'')+y;};};})();

Save this result as a bookmark, and any page you run the bookmark against will load all images and CSS anew, bypassing the cache! Phew.

The only remaining caveat I can think of: this won’t work for CSS included with the @import directive. Any ideas?

Comments, as always, are welcome.