Archive for the ‘javascript’ Category

Found Code: Optimizing Large Form Performance in JavaScript

As I’ve covered before, ill-used JavaScript can lead to some serious performance problems, most of which are caused by simply not thinking about what the code is really doing. Recently I came across a site that provided digital photo printing, This site had a nice interface that allowed my to upload close to three hundred photos. On the resulting page, each photo was displayed with all of the available sizes as input boxes, which looked something like this. I liked the interface, but came across a very serious problem. The event handlers that updated the totals box ran on the keyup event and recalculated the total of the entire form! This worked fine with ten or twenty photos, but the 300 that I provided brought my browser to a screeching halt.

I’ve taken the liberty of creating a very simplistic mock-up of the form and a simplified version of the JavaScript, which is available in my examples section. The demo uses Firebug and Firebug Lite for logging just like I did in my dollar function article, and the benchmark class from that article as well. The site’s JavaScript was a bit more complex and actually did an AJAX lookup of the price on each keyup, but I’m more concerned with the JavaScript performance here, so I simplified the code to something like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
var PhotoSelector = Class.create();
PhotoSelector.prototype = {
	initialize: function( name ) {
		var init = new Benchmark();
		init.start();
		console.debug( "Beginning Initialization!" );
 
		$A(document.getElementsByTagName("input")).each( 
			function( inp ) {
				inp.value = 0;
				if( Element.hasClassName(inp,"qtyInput") ) {
					inp.onkeyup = this.recalculate.bindAsEventListener( this );
				}
			}, this
		);
 
		init.end();
		console.debug( "Initialization Complete in " + init.inMillis() + " milli(s)." );
	},
 
	recalculate: function(e) {
		var calc = new Benchmark();
		calc.start();
 
		$("fourby").value = 0;
		$("fiveby").value = 0;
		$("eightby").value = 0;
		$("wallet").value = 0;
 
		var inputs = $("pictures").getElementsByTagName("input");
		for( var i = 0; i < inputs.length; i++ ) {
			var totalId = inputs[i].id.match(/([a-z]+)[0-9]+/)[1];
			var total = $(totalId);
			total.value = parseInt(total.value) + parseInt($(inputs[i]).value);
		}
 
		calc.end();
		console.debug( "Recalculation Complete in " + calc.inMillis() + " milli(s)." );
	}
}
var ps;
Event.observe(window,"load",function(e){ps = new PhotoSelector()});

Basically, on window load, this code grabs every input element, sets its value to zero, and binds an event handler to it. The event handler runs on key up and loops through every input box in the “pictures” list, and updates the totals inputs at the top of the page. As I said above, this code works fine with 20 pictures, but it starts getting slow around 300, and becomes almost unusable at 1000. Care to try 10,000? (Be careful, it crashes my browser!) To test it, simply enter values in the photo inputs and watch the totals boxes increment.

The main problem with this code comes from the recalculate function. Problem number one is my personal pet peeve, the dollar sign function is called at least six times! Well, I guess six times wouldn’t be terrible for the entire page, but it’s called at least six times on every key up event! Problem number two, the biggest problem, is the fact that this code re-crawls what amounts to the entire DOM every time the event fires. Obviously the larger the DOM, the more time this is going to take.

So, how do we fix it? Well, here’s how I fixed it, I’ll explain the details below:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
var PhotoSelector = Class.create();
PhotoSelector.prototype = {
	initialize: function( name ) {
		var init = new Benchmark();
		init.start();
		console.debug( "Beginning Initialization!" );
 
		this.old = 0;
		var totals = {
			fourby: $("fourby"),
			fiveby: $("fiveby"),
			eightby: $("eightby"),
			wallet: $("wallet")
		};
		totals.fourby.value = 0;
		totals.fiveby.value = 0;
		totals.eightby.value = 0;
		totals.wallet.value = 0;
 
		$$(".qtyInput").each( 
			function( inp, index ) {
				inp.onfocus = this.enter( inp, this ).bindAsEventListener( this );
				inp.onblur = this.recalculate( inp, totals, this ).bindAsEventListener( this );
			}, this
		);
 
		init.end();
		console.debug( "Initialization Complete in " + init.inMillis() + " milli(s)." );
	},
 
	enter: function( inp, me ) {
		return function(e) {
			me.old = parseInt(inp.value);
		}
	},
 
	recalculate: function( inp, totals, me ) {
		var type = inp.id.match(/([a-z]+)[0-9]+/)[1];
		var total = totals[type];
		inp.value = 0;
		return function(e) {
			var calc = new Benchmark();
			calc.start();
 
			var newVal = parseInt(inp.value);
			if( me.old > newVal ) {
				newVal = ( me.old - newVal ) * -1;
			}
			total.value = parseInt(total.value) + newVal;
 
			calc.end();
			console.debug( "Recalculation Complete in " + calc.inMillis() + " milli(s)." );
		}
	}
}
var ps;
Event.observe(window,"load",function(e){ps = new PhotoSelector()});

To solve problem number one from above I created a simple object for storing references to all of the total input boxes (lines 9-14), now we have a simple associative array lookup whenever we need to update a total. Problem number two is mainly solved by recording the original value of the input on focus (line 22, 31-35), and then comparing them on blur (line 23, 37-54). Because we’re doing this on blur, we can update only the necessary total input (lines 45-49) instead of recalculating the entire form. I made one final tweak, mainly to make solving problem number one easier, and that is the recalculate function now returns a specific event handler for the given input so that the event handler itself does not need to call the dollar function.

So, comparing these in my regular, not-very-scientific fashion, I came up with the following results. I chose to measure the startup time, which will increase with the size of the page, as well as the event handler time. I also measured these times across a pretty decent amount of pictures, and across a few browsers.

Safari (OS X)

Optimized Time Unoptimized Time
Pictures Load Handler Load Handler
10 4 ms 0 ms 6 ms 3 ms
50 17 ms 0 ms 14 ms 13 ms
100 33 ms 0 ms 24 ms 26 ms
1000 365 ms 0 ms 452 ms 178 ms

Internet Explorer 7 (Windows Vista)

Optimized Time Unoptimized Time
Pictures Load Handler Load Handler
10 56 ms 0 ms 48 ms 9 ms
50 238 ms 0 ms 213 ms 76 ms
100 457 ms 0 ms 424 ms 235 ms
1000 4642 ms 0 ms 4584 ms 28110 ms

28 seconds!? Why!?

Firefox 2 (Windows Vista)

Optimized Time Unoptimized Time
Pictures Load Handler Load Handler
10 12 ms 0 ms 8 ms 7 ms
50 45 ms 0 ms 31 ms 30 ms
100 87 ms 1 ms 60 ms 59 ms
1000 985 ms 3 ms 584 ms 581 ms

The results pretty obviously speak for themselves, but there is one caveat, be sure to notice the initial load time. Since the event handlers still need to be assigned to each input on the page the more inputs there are the longer the page load takes, and the load time is even slightly slower on the optimized page. Be sure to consider this time, possibly by capping the number of inputs displayed, since the code itself is very processor intensive and appears to actually hang the entire computer while processing. Obviously, these fixes become more important as the number of inputs grows, but any speed increase when the user is directly interacting with the page is a good one!

Letting Google Help With Your Site Performance

Let’s face it, cross-browser JavaScript and AJAX without a helper library or framework is pretty difficult. However, these libraries can be pretty hefty when it comes to page download size, especially if sent uncompressed and un-optimized. Most of the libraries have a statement in their docs that says something along the lines of “grow up and compress your JavaScript” but not all of us have sufficient access to their web host to actually be able to do that. Enter Google. Google’s AJAX Libraries API serves as a content distribution network for providing pre-compressed versions of the web’s favorite Web 2.0 JavaScript libraries:

Access to these libraries is quite simple, in fact loading prototype can be accomplished with the following four lines of code:

<script src="http://www.google.com/jsapi"></script>
<script>
	google.load("prototype", "1.6.0.2");
</script>

Pretty simple, but let’s look at some metrics.

With Google Without Google
(no cache) (local cache) (no cache) (local cache)
KB Time KB Time KB Time KB Time
66 982ms 4 980ms 252 2.14s 0 1.19s

As you can see, the Google version is significantly faster and smaller on the initial load, subsequent loads a little less obvious, but still slightly faster. However, this test was not all that scientific, since I really only have the ability to do this with Firefox and Firebug, I only did it once, and my network speed can vary significantly from request to request. Despite all of that, testing this across a few other browser/OS combinations does reveal a pattern where the Google AJAX Library API pages do feel faster, even if only by a fraction of a second. I’ve provided my test pages, with Google and without-Google, for you to perform your own tests, and I’d love to hear what other people think and see their results.

The verdict, I like it, but I have a few caveats. First, as pointed out to me by the Unscrutable Designer, you’re relying on a third party site to host your scripts, in this case it means you need to trust Google to not be evil. Personally, I do, but that is a decision to be made on a case-by-case basis. Secondly, you have to trust the reliability of your content distribution network, can you risk your JavaScript functionality if Google’s server goes down? Thanks to progressive enhancement, having no library should be basically the same as no JavaScript, so my properly implemented site should still function, so this is personally not a show-stopper for me.

Now, one last thought. Keep in mind that this is not the most optimal solution since it still makes a request for each library you load and the libraries themselves are not optimized, but it does bring along with it an interesting benefit. The more sites that use this service the higher the chance of getting a local cache hit on one of these files, which of course means one less download.

Duck Punching JavaScript - Metaprogramming with Prototype

First, let me explain the title for those of you who live in Java world. The term Duck Punch comes from Ruby programmers and refers to the concept of duck typing used by Ruby and other languages; if it walks like a duck and quacks like a duck, I would call it a duck. With that being said, duck punching is summed up nicely by Patrick Ewing with the following:

Well, I was just totally sold by Adam, the idea being that if it walks like a duck and talks like a duck, it’s a duck, right? So if this duck is not giving you the noise that you want, you’ve got to just punch that duck until it returns what you expect.

It is slightly violent, but makes for a good article title. Now, getting to the point, metaprogramming is writing programs that write or manipulate programs, including themselves, at runtime. This type of programming can be accomplished in many languages but is currently a pretty hot topic in languages such as Ruby and Groovy. Java has a similar concept known as Aspect Oriented Programming (AOP), which introduces pointcuts to a program that can then be modified at runtime via bytecode manipulation or dynamic method proxies.

My example library/method below allows a Prototype-enabled class to have every one of its methods wrapped with a before and/or after method to introduce additional functionality at runtime. This concept is demonstrated by this example, note that the modification occurs on the class level and any existing or future instances of that class are affected. Thanks to Prototype JS, this code is pretty simple:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
var Aspect = Class.create();
Aspect.prototype = {
	initialize: function( clazz, before, after ) {
		var members = Object.keys( clazz.prototype );
		members.each(
			function( name ) {
				if( Object.isFunction( clazz.prototype[name] ) ) {
					var oldName = "old_" + name;
					clazz.prototype[oldName] = clazz.prototype[name];
					clazz.prototype[name] = function() {
						if ( before ) before( name );
						var me = this;
						var args = $A( arguments );
						args.each( 
							function( arg ) {
								me[oldName] = me[oldName].curry( arg );
							}
						);
						me[oldName]();
						if( after ) after( name );
					}
				}
			}
		);
	}
}

This code relies on one of the core concepts of JavaScript OOP; a JavaScript object is nothing more than an associative array. Knowing this, when passed a class, we can get a list of all class member names using the Prototype Object.keys method as shown on line 4. Once we have the member names, we loop through all members (line 5) of the class. For each member we check if it is a function (line 7). If it is, we alias the function (line 9) and create a new function (line 10) in its place that wraps the aliased function with calls to the before (line 11) and after (line 20) functions we pass in. Notice the use of clazz.prototype starting on line 9, the prototype keyword is necessary to modify the class. If you rewrote this method slightly and didn’t specify the prototype keyword you could modify an individual instance of the class instead of all running instances. Also notice the curry method on line 16, currying is a metaprogramming technique (provided as a helper by Prototype JS) where a function is modified with each call so that the arguments passed into it become part of the function and are no longer necessary. This allows us to pass an arbitrary number of arguments into the aliased method. (Remember, in JavaScript, arguments do not need to be defined for a function and can be accessed via the arguments array.)

The above code can be called on any class in the document by running the following code, assuming before and after are pre-existing functions and SampleClass is the desired class:

1
new Aspect( SampleClass, before, after );

You can see a complete (although pretty useless) example of this method here, or view the source code for the test or the sample class.

So, where is this useful? Since we’re simulating AOP, we can use this for any cross-cutting concern, a perfect example being logging or profiling. Watch for a future article where I use this technique to automatically implement JavaScript profiling similar to what I did in my article on the dollar function using this technique. One final note for the Prototype geniuses out there, Prototype does provide a wrap method that provides this functionality, but if you used that you never would have learned all the neat things about JavaScript in this example!

Progressive Enhancement and LightWindow

Nothing says Web 2.0 like a lightbox, those nifty, little, modal dialogs that let you create pop-up-like functionality without the worry of pop-up blockers. There are quite a few variations of this concept, including Lightbox JS (and its many variants including Lightbox Gone Wild by the makers of Wufoo), Slimbox, and my personal favorite LightWindow. In general, they’re all pretty unobtrusive, but in this little tutorial I’m going to kick up the progressive enhancement a notch and make LightWindow even better!

Let’s start with a client who wants to use a lightbox to display a multi-view image viewer for an e-commerce site. In true progressive enhancement fashion, if JavaScript is disabled, the user should still be able to perform the basic functionality of the site. So, let’s assume our site has a simple layout with a header, a footer, and a main content area that looks something like this. When JavaScript is disabled the image viewer should show up with the header and footer, when it’s enabled, it should show up in a lightbox without the header and footer. So how do we do it?

LightWindow can be used to display any given webpage by simply setting the class of a link to that webpage to lightwindow. Simple enough. When the lightwindow.js file loads it searches your document for anchor elements with a class of lightwindow and adds the necessary onclick handler to make the link open in a lightbox instead of as a normal page, as unobtrusive as it gets. However, since we want our result page to look differently depending on whether it’s being displayed as a lightwindow or a regular page, we need some way to tell our back-end the display form-factor. To accomplish that we’ll add my own little progressive enhancement to the page.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Event.observe( window, "load",
	function(e) {
		$$('.lightwindow').each( 
			function( elem ) {
				if( elem.href ) {
					if( elem.href.indexOf('?') == -1 ) {
						elem.href += '?';
					}
					elem.href += "lightwindow=true";
				}
			}
		);
	}
);

This is simple enough prototype.js JavaScript code that crawls the DOM for elements with a class set to lightwindow and adds a parameter of lightwindow=true to the href attribute. With this parameter in place, the back-end now knows which form-factor to use when rendering the page and we can accomplish our goal with some simple PHP code. (Pardon my PHP, I’m by no means a PHP expert, this is only meant to be a simple example.):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?php if( $_GET['lightwindow'] != 'true' ) { ?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" dir="ltr" debug="true">
<head>
	<meta http-equiv="Content-Type" content="text/html; charset=UTF-16" />
	<title>Example Lighwindow (JavaScript Disabled)</title>
	<link rel="stylesheet" href="css/style.css" type="text/css" media="screen" />
</head>
<body>
<?php } ?>
	<div class="container" <?php if( $_GET['lightwindow'] == 'true' ) { echo "style=\"width: 300px;\""; } ?>>
		<?php if( $_GET['lightwindow'] != 'true' ) { ?><h1 id="header">Header</h1><?php } ?>
		<p>
			This is my lightbox content, if JavaScript is enabled on your browser you should be seeing it in a LightWindow, 
			if it's not, you should see it with a header and footer similar to <a href="index.html">this page</a>.
		</p>
		<?php if( $_GET['lightwindow'] != 'true' ) { ?>
		<ul id="footer">
			<li><a href="/">Back to EricDeLabar.com</a></li>
		</ul>
		<?php } ?>
	</div>
<?php if( $_GET['lightwindow'] != 'true' ) { ?>
</body>
</html>
<?php } ?>

Problem solved, have a look here and click on the “this link” link. Of course there are a few more programming alternative along these lines, including modifying the LightWindow object at runtime to prevent a second crawl of the DOM for elements with the lightwindow class, theoretically improving performance, but I’m going to leave that for another article.

As an interesting side benefit to this technique, web crawlers like the Googlebot, which don’t interpret JavaScript, will follow the link to the lightbox page and receive a full page as opposed to just the page content. This Allows the robot to continue crawling your site through the navigation and also allows it to see your page title and meta tags. Now, just a little forewarning, be careful with this technique, if you abuse it, it may be considered cloaking any you could have your site delisted for violating the webmaster guidelines. So in other words, use this technique to provide context and don’t try and stuff the pages with keywords or do anything underhanded.

Matt Snider, You’ve Been Blogrolled

I haven’t said anything about it yet, but I love Google Reader. Not only is it an amazing feed reader, it has a neat little service where it recommends feeds to you based on your existing reading list. Yesterday night it recommended Matt Snider’s blog to me.

Matt writes about JavaScript and JavaScript frameworks, and includes some very in depth analysis and excellent code examples. Matt, like myself, seems to prefer a very OO approach to JavaScript, so I will probably be referencing his articles in future posts. If JavaScript is your thing, go check out his site, subscribe to his feed, and be sure to checkout the archives for some great stuff like this article about an isDOMReady implementation, and a great overview of JavaScript frameworks and libraries.

As I said yesterday, if you or someone you know blogs about topics similar to mine, please let me know. If I like the site, I’ll gladly add it to my blogroll, write about it, and probably use it as a conversation started in a future article.

Found Code: JavaScript getElementById, Performance, and the $ Function.

From what I’ve seen, most of the popular JavaScript frameworks out there provide some form of the $ method. This method is usually a cross-browser translation of document.getElementById with a few extras. What this means is instead of always having to type document.getElementById("myId") you can now type $("myId"). All-in-all it’s a very nice shortcut and will probably cut quite a bit of typing out of your JavaScript projects. My problem is with the number of times I’ve come across inefficient uses of this helper function because it’s easier to type. This usually manifests itself as something along these lines (this example written using prototype.js):

1
2
3
4
5
6
if( $("myId").style.display.toLowerCase() == 'none' ) {
	$("myId").style.color = 'red';
	$("myId").style.height = '10px';
	$("myId").style.width = '100px';
	$("myId").style.display = 'block';
}

This code looks pretty concise, if you know what the $ function does it’s also pretty simple, but take a second to think about performance. In this code it’s possible that you’re calling document.getElementById five times! Ignoring whatever else the $ function does, the document.getElementById function could traverse the entire DOM on each call. (I’m not saying it does, but you really don’t know what the browser is doing under the covers and since you’re designing for all JavaScript enabled browsers, it’s better safe than sorry!) If you actually had to type out the document.getElementById you would probably consider something like this:

1
2
3
4
5
6
7
var myId = document.getElementById("myId");
if( myId.style.display.toLowerCase() == 'none' ) {
	myId.style.color = 'red';
	myId.style.height = '10px';
	myId.style.width = '100px';
	myId.style.display = 'block';
}

With this code, you’re only calling document.getElementById once, and therefore only traversing the DOM once. Makes a little more sense right? Let’s look at some metrics.

For this experiment, let’s make the assumption that the larger your document is the longer it will take to perform a DOM traversal, so in order to see some results we’ll need a decent sized document. For that we’ll go to wikipedia and grab something off the front page, in this case it’ll be Harry Potter. To get my test document I viewed the source, grabbed the main body content, and pasted it into a new HTML document. I also removed all of the images since I didn’t want broken images or to be hitting wikipedia’s servers for my experiment. I wrote a quick JavaScript class that will do my benchmarking, and a quick test case that calls my two methods above as well as two other methods and single calls to both the $ function and the document.getElementById function. To perform the benchmark, I run each method 1000 times. I initially tried smaller numbers but there was not enough visual difference to prove my theory since JavaScript is only accurate to the millisecond. You can find the test code here, the benchmark class here, and you can run the test here. I’m using Firebug Lite for the console logging, but if you have Firebug installed it will use the Firebug console. As I said before I just wrote the benchmarking class, but look for a future post and improved version now that the seed has been planted.

I have successfully run the test on Safari 3.1 on OSX 10.5, Firefox 2.0.0.13 and IE 7.0.6000.16643 on Windows Vista Business 64-bit, and IE 6.0.2900… and Safari 3.0.4 on Windows XP Pro. My results were as follows:

getElementById $ function Un-optimized $ function Optimized $ function Un-optimized getElementById Optimized getElementById
Safari Mac 1 ms 4 ms 67 ms 37 ms 42 ms 33 ms
Firefox 7 ms 9 ms 177 ms 121 ms 154 ms 118 ms
IE 7 273 ms 291 ms 1829 ms 364 ms 1688 ms 337 ms
IE 6 312 ms 297 ms 1960 ms 484 ms 1735 ms 375 ms
Safari Windows 0 ms 0 ms 94 ms 47 ms 47 ms 46 ms

Across the board the optimized functions performed better, and in most cases the $ function was slightly slower than the document.getElementById function. The most surprising result is Safari on Windows because it’s actually the slowest machine that these tests were run on. The only problem that I can think of with this test is that it does quite a bit of looking up by id, and that’s probably not an accurate test case, but even if you’re cutting out only a few milliseconds on an event, somebody will notice the improvement. I’m welcome to any suggestions or comments on my testing methodology.