In part two of before your scripts load series we are going to figure out how to use the techniques that Flickr uses to handle this problem. To recap, if we use an async JS loader, or even if we just put our script tags at the bottom of our pages we have a problem. The is potential for a user to interact with the page before the code loads. This means there might not be any code around to handle what the user just did.
Flickr uses something they call an actionQueue, and there code to do that is in a very tight, isolated bunch of code. With very little modification I think we could use that exact same piece of code. We are going to build a simple webpage that exercises the code. To start we need to make a few changes to the original code so that we could have multiple modules loading instead of just one.
Note: I am using QUnit to run the tests
The code:
https://gist.github.com/1398040
I had to make a couple of changes to the stock code for the purposes of generalizing the library, and to make it more testable. First, the module by default hard to test because it kept it's state private. I wouldn't expect anything less, the state is only used in the actionQueue function. When you are testing the code we need to be able to reset the internal state of the actionQueue.
One possible way to do this is to write like an actionQueue factory. The function will create a brand new actionQueue every time you call it, and then you can just assign it to A.actionQueue over, and over again. This seemed overkill for our purposes. So, I just created a new function that could reset the internal state of the module. It's called refresh_module. Now we can just call this function at the end of every test to reset that state of the actionQueue to new.
The second thing I did was to do some jslint cleanup. Looking at the code there were a few places that they were assigning a variable inside of for loop, over and over again like this.
for(var i in an_array){
}
This is usually a bad idea because you are re-initializing i every iteration of the loop. A better form of this snippet would look like this.
var i;
for(i in an_array){
}
This will pass jslints tests. This by no means the engineers who wrote this code didn't know about this. I don't even think everything needs to pass jslint, it's just a rubric that one can use to begin the process of evaluating code. What I think happened here is that they used the first version to save space. In the second version i is repeated twice, it's not as small the first version. Also, they were also looping over a very small array. They probably optimized their code for the most pressing priority which was code size. Given that this code is going to show up in many, many pages on Flickr, which is one of the most trafficked websites on the web, the size argument makes sense.
After doing some style cleanup, and hooking in some testing tools I need to make one major functional modification. When I started looking at Flickrs code I could see that there was this internal bit called queueing, when it was true, which is its default, interim functions get executed. When queueing is false, nothing happens, things just pass through the queue_click function.
What was curious about this bit was that it was global. actionQueue was clearly written with the idea of multiple modules, but apparently that part ended up not being needed. When the first module gets loaded queueing is set to false. Meaning all queue_click function calls now pass through, even for modules that haven't yet called module_load. I have a feeling that flickr loads all of it's modules in one large JS file. They aren't seeing any repercussions in this code because if one module has loaded, they have all loaded. I don't think that this will be the story for everyone who might want to use this code. So, I modified it so that each module has it's own queueing state.
You can see that I added the code on line 38 to set queueing to true for any new modules that get added by the register function. Then as module_loaded gets called it will only turn queueing off for that module. You can see all my tests, and how I tested the multiple module section here.
To demo this code, we are going to build a simple like button, just like Flickrs. There will be two steps. When you click it will change state, and then there will be an action for when the module loads.
https://gist.github.com/1456035
We are going to start by using a very simple markup. A created a very simple like button with two states. The visual state is controlled by flipping a class name. Then I set up some initial code building our global container, and include the actionQueue code.
Line 20 is where we start to see how the like button is going work. I setup two steps. In the interim step the like button will get a new class. This will flip the visual state. In the cleanup state, which will fire after the module, like-handler, loads, we just mark the like button as done.
Then the HTML creates the markup for the like button. It's important to note here that the anchor tag has an onclick, I know, this looks dirty, but really it the easiest way to do click events before all your code loads. Crazy as it seems it works. So, when a user clicks the like button an event gets fired into the action queue which will first fire the interim step.
Later I setup a link that will 'load' the module. This will then fire the cleanup function.