Some time ago I was working on optimizing the client side code of my website, plnnr.com, an online trip planner.
This website does automatic trip planning, and the problem was that recalculating trips was slow. After profiling, I found out that most of the time wasn’t actually taken up by the algorithm, but by the UI. Rendering the trip to html was the costly part. The process was like so:
Client-side Javascript code generates new trip prefs -> application calculates new trip -> Client-side Javascript gets the new trip, and creates new html.
It’s important to note that the app is “ajax based”, so the actual trip html was generated by the Javascript code, and not the server. At the time I was using Mochikit to generate the new html. Mochikit has a pretty nifty API for generating html, but it’s quite a bit on the slow side. Basically, this API is a wrapper around createElement.
Well, first I did a little test, and found out that generating html with cloneNode and innerHTML is much faster than createElement. Still, there was a problem – I needed to generate many similar elements – similar but not identical. Consider entries on a trip itinerary – they all look the same, yet each one has a different name, a different time string, and a different onclick event.
What I needed was a Javascript based html template library. My requirements:
1. Speed. Html had to be generated quickly.
2. Expressiveness. It had to be able to create pretty arbitrary html with a given context. For example, an anchor element (<a> tag) with a given href property, and a given text content.
3. References to inner elements: Many elements inside the generated html need various events attached to them, or various code processing. This should be easy to achieve.
4. The library has to allow the template html to be written as html, and not only as javascript strings.
So, I sat down with Benny, and we wrote the Javascript Element Creator, which we are now releasing under the BSD license. I originally wrote it to work with Mochikit and the Sizzle library, and Benny changed his version to worked with jquery.
After adding the code to my project, I got two things: first, everything worked much, much faster. Second, it was much easier changing the generated html when it was generated according to a template, and not directly in code.
Instructions
1. Write your html somewhere visible to the javascript code. Add the “template” class to the upper node, and the id will be the name of the template. For example:
<body> <div id="some_div" class="template"> </div> </body> |
2. Similarly to other template engines, add double brackets to signify where text should be inserted:
<body> <div id="some_div" class="template"> <a href="[[link_url]]">[[link_text]]</a> </div> </body> |
3. Create a creator object. It will “collect” your template, and will make it available to your code.
var creator = new ElementCreator(); |
4. Generate your DOM object, and add it to the document;
var obj = creator.generate("some_div", {link_url: '/url/', link_text: 'hello world'}); appendChildNodes(foo, obj); |
The code
We decided to publish for now only the jquery version. I might publish the mochikit version as well at a later date. Since Benny wrote the jquery version, he also wrote the tests for that version.
All in all, the final code is pretty short, and could probably be even shorter. Still, it’s good enough, and gave me a very real performance boost.
Here is the code, have fun with it.
Peace, I & B! ;o)
I’m curious:
1. I don’t like it that the template is in the DOM, and presumably you make it invisible with CSS? What about progressive enhancement (or unobtrusive JS ;-)? Ugh. If it’s not a fragment of the document you’re “adding more of”, well, then put the template in a the JS, as a string constant (literal), and interpolate somehow (jQuery?), hmm?
2. innerHTML slower than cloneNode?! Hmm, Googling (lots; want my bookmarks?) would suggest that’s debatable. How deep the fragment you’re cloning? Can we see the code?
3. Your back-end returns the “new trip” in JSON? Why not HTML so you won’t have to render nothing client-side?
I’d love to see more details/URLs, if you can afford any?
(Anyway, see you Monday! ;o)
Ilan:
1. Yes, I make it invisble with css. I don’t really like templates in the JS code. On the contrary, I prefer it in the html, with other similar code (in purpose and in form).
2. I didn’t say that innerHTML is slower than cloneNode. They are mostly equivalent although cloneNode
is somewhat faster in a test I ran right now (on firefox). Still, they are both much faster than createElement, which was my actual point.
3. Various reasons, including historical ones. Among other things, the trip’s presentation is more complicated than plain html, and has a lot of events and behaviors attached to it. I guess I could do it that way, but I see no clear advantage at the moment.
Details & urls: what do you want to see?
And indeed, see you Monday! :)