So how do we get to this end? Its actually easier than it may seem. It takes a little from the school of thought that favors convention to configuration. It does not bar configuration, but if you don’t do any configuration, and simply adhere to convention, things should just work.
Lets think about what we need our bootstrap to do:
- Load the right version of dojo
- Load the right version of the environment
- Require any necessary scripts
- Parse the page
Loading the right version of dojo
In all of my projects, I put my library on the same server as the one serving Dojo. This may or may not be the same as the HTML page that is including the libraries (so we still have to deal with XD issues), but it does simplify a few things. If I know the path to my bootstrap script, then I can derive my paths to everything else, regardless of where the bootstrap lives.
So the first thing the bootstrap does, is some introspection. What is the path to the bootstrap file, relative to the calling page?
This is actually a cinch to do with Dojo. But at the time of loading, we don’t yet have Dojo loaded. So it requires some old fashioned DOM scripting:
So in this snippet I am simply loading all the “script” tags in the already loaded DOM. Since this script is executing, then by definition, at the very least the script tag that called this script is loaded in the DOM. Once I find the script tag that loaded this script, grab the path to this script, and map the path to dojo relative to this script. Not too bad, but a little but of a funky concept if you don’t get your head around it.
So now we can create the script element to load dojo onto the page. But remember, the DOM is probably not fully loaded yet. So the safest thing to do will be to us a document.write to load dojo. But using a document.write to “inject” dojo into a page is not the normally expected use of dojo. And you don’t necessarily know that the moment after you perform your write statement that your script will have access to dojo.addOnLoad. As such, you must define a djConfig variable (for many reasons) before you do your document.write, and that djConfig should have an addOnLoad method defined. This is your first method that “knows” that it has access to Dojo. So the next bit of code looks something like:
So djConfig declares the appropriate modulePaths (note that the dojo/dijit/dojox are included in case this is a cross-domain load). If defines the addOnLoad method. Then we put together our path to dojo, and decide which version of dojo to use based on the useXDomain flag in djConfig.
Loading the right environment
Dojo will call BOOTSTRAP as soon as it is loaded. Which is where we will start to load our application. The first thing our BOOTSTRAP method does is figure out which environment to load. The first place it looks is in a custom configuration in djConfig called “environment”. If that value is set to “page”, then it will look for a custom attribute on the script tag that loaded the bootstrap to see if any environment was described there. This is useful in development or testing, if for example, you want to temporarily force a page to use a particular environment. So the modified djConfig and bootstrap looks like this:
Okay, so its getting a little more complicated, but still not overwhelmingly so. This script did everything I described above, and added just a bit more. First, it declared the global namespace of “medryx” and it created a config, as well as its own addOnLoad method. Don’t worry too much about that right now except to say, its used to be sure certain scripts are executed after all the dojo.addOnLoad methods are executed. Again in a local build the order is pretty reliable, so its not a big deal, but when using a cross-domain “compiled” script, getting the order of things right can be a bit tricky, so this helps solve that. More on that in another post in the future. I also check if djConfig.baseUrl gets set, if it doesn’t, we get a meaningful error message.
So now I know what environment I want. I’ve loaded dojo. Next step dojo.require() the environment. And once I have the environment, I am ready to load any scripts needed to run the page.
This is where convention over configuration comes back to play. You may have noticed my “pages” namespace. This is actually a special namespace. Each page has a script in the pages namespace. And in fact, you can figure out the pages.* script you need by looking at the calling html page’s pathname. Pick whatever convention you want, but just be consistent. Then you can deduce what js needs to be loaded next and you can just do it, without the page have to do any configuration. Of course, if you choose, you could create another “custom attribute” on the bootstrap script tag with the name of the page module to load, or you could simply require() it into the HTML page on the HTML page itself. But following the convention gives you the benefit of zero configuration. Less code, less configuration = fewer silly bugs.
So here is a snippet in my BOOTSTRAP method that first checks if any of the script tags has the special “module” attribute. If they don’t it tries to figure out the module attribute based on a path name. This version assumes all HTML pages will be under a folder called “pages”.
So its time do some dojo.require()’ing. But before we do, lets consider the case of the “optimized” flag I have in my djConfig. Note that this is NOT a standard djConfig attribute. I simply use this to decide if I want to include a script on the page which is my compiled library. This is the version of the library that takes my hundreds of files and folders and compresses them into a single js file. With that in mind:
I’ll describe the optimize flag and the location/name of the “built” js file later (when I discuss my profile layers). You can see I do a simple dojo.require for the environment. Also note that you must build your compiled scripts as one-per-environment, since you can only have one environment included on a page (or the “last one loaded wins”). The module dojo.require is in a try/catch block. I chose this instead of other ways to load the module that don’t throw an error when not found because I wanted to create the appropriate development warning to remind people to create their “module” file in the pages namespace.
Loading the style sheets
One more step, and we are done with our bootstrap. I wanted to be sure that our appropriate CSS file is loaded on the page. This is the CSS that is needed for widgets to work at all. Its a silly error when everything seems broken, and its just because the CSS didn’t import and failed silently. I also add the class name to the body (though I should probably check if none exists first).
Okay, so what have we done here? We now have a single script that gets dojo into your page, loads you environment, decides if you need a cross-domain build, and handles incorporating your widget stylesheets. Not too shabby. And now with the use of a single <script> tag, your HTML page just works.
But aside from the niceness of everything just working, how else does this help us? Did you notice my comments strings above and below the djConfig variable? While I will describe in detail in my next post how I replace this depending on the environment desired, suffice it to say for now, that my build system can swap out the djConfig section depending on the kind of build I would like done. So a simple call to:
…builds the entire library, and makes all the pages use the pretty optimized JS if needed and use all the right resources. Voila.
Next post: how to modify the dojo build system to handle environment swaps (warning – some mild dojo hacking is required for now until I can get them to fix some issues in the build system…)