This is a new (at least I think it's new; I've not seen anything similar) way to do client-side templating without having to keep a separate element containing the template HTML.
Before you get any further understand that this is a proof of concept; the code is not meant to be perfect. Do not use in production.
Bored already? Head over to the example, and don't forget to check out the script.
There are a lot of client-side templating engines around at the moment. Most famous is probably Handlebars which is built on mustache.js. There are also about a bazillion jQuery plugins, and probably a load of other solutions as well.
That's all great, but all of the ones I've seen start off by saying something like this:
The template is written in
<script type="text/html"></script>
tags...
Handlebars templates use a different script type:
You can deliver a template to the browser by including it in a
<script>
tag.<script id="entry-template" type="text/x-handlebars-template"> template content </script>
But what if you wanted to use the rendered HTML itself as the template? When your page is already being rendered by the server you already have the template itself in HTML, populated with the first items. Here's an example to illustrate what I mean.
You develop a website selling, I dunno, houses (completely random example, honest). The user sends a particular search request, normally using a querystring parameter:
www.buymyhouse.com/search?q=leeds
At that point the server would run the search for houses in Leeds and return a complete HTML page including the details for the first X houses. Let's say you're displaying 5 houses per page. Your HTML would look something like this (this example is heavily truncated):
<!DOCTYPE html> <html> <head> <title>Houses for sale in Leeds</title> </head> <body> <h1>Houses for sale in Leeds</h1> <ol> <li> <h2><a href="/house/1">29 Acacia Road</a></h2> <p>This charming semi-detached property was once the home of famous super-hero Bananaman.</p> <h3>Price: £135,000</h3> </li> <li> <h2><a href="/house/2">742 Evergreen Terrace</a></h2> <p>Situated in a leafy suburb of Springfield, this delightful family home has every modern convenience.</p> <h3>Price: £135,000</h3> </li> <!-- More houses would appear here... --> </ol> </body> </html>
This bit:
<li> <h2><a href="/house/123">29 Acacia Road</a></h2> <p>This charming semi-detached property was once the home of famous super-hero Bananaman...</p> <h3>Price: £135,000</h3> </li>
Is a HTML template (although it is populated with data). I thought perhaps client-side code and some clever markup could use this HTML element as the template instead of having a separate <script>
template element containing the same HTML. So, because I have no social life, I coded it.
In the future, when we're all buzzing around in our flying cars and using replicators to make our meals, we'll get to use the HTML template element. But that's not yet.
So our user sees the first page of results, but nothing takes their fancy. So they click the 'Page 2' link to see houses numbered 6 to 10. At that point some JavaScript intercepts the link and, instead of requesting the entire page of HTML for the next set of houses from the server it just requests the *data* for the next 5 houses which it will bind to the template. This data is returned as a JSON object, and that's where the Data Binder script kicks in.
So the process goes like this:
<a href="/search?q=leeds&page=2">Page 2</a>
This script uses data
attributes. If you don't know about those then look them up; they are pretty awesome. It also needs the data to be given to it in JSON, which is a lower-fat alternative to XML.
OK. I know what HTML element I want to use as a template (it's the li
element containing the house details), but how do I tell my script to use it? A data attribute will do it:
<li data-binder-template="house-details">
This tells the script that this HTML is a template with the name 'house-details'. If there is more than one element on the page with this data attribute value the script uses the first one (more on that later).
Now we need to tell the script what data from the JSON object to put into the elements in the template. For that let's have a look at the JSON that may be returned from the server.
For our simple example this is the data that is returned:
{ "houses": [ { "id":6, "address":"12 Green Street" , "description":"Terraced house close to town centre...", "price":120000.00 }, { "id":7, "address":"9 The Willows" , "description":"Semi-detached house with large garden...", "price":150000.00 }, { "id":8, "address":"471 Main Road" , "description":"Detached executive home...", "price":250000.00 }, { "id":9, "address":"Flat 2, 9 Windermere Crescent" , "description":"First-floor flat...", "price":90000.00 }, { "id":10, "address":"Lindley Manor, Lindley" , "description":"Georgian manor house with extensive grounds...", "price":950000.00 } ] }
Even if you've never seen JSON before this should make sense. Each house in the array has an ID, an address, a description and a price. Accessing the description property for the third house needs this JavaScript:
houses[2].description
If we're looping over each house to get the description property we'd do something like this:
for(var x = 0; x < houses.length; x++) { var thisDescription = houses[x].description; }
We could do what we want with the thisDescription
variable, for example put it in an element.
So if we're looping over each house to bind the data for that house to our template it makes sense that all we need to do to populate the description is tell the Data Binder script what property we want to put inside that element; what property we want the content to be. For that we use another data attribute (you'll recognise the syntax from other popular client-side templating libraries):
<p data-binder-content="{{description}}">
This will put the description for each house inside a p
element. Remember, the template will be used for all houses as we loop over them so we know when we say the property is 'description' that we know we will see the description for the current house in the loop.
So, using the data-binder-content attribute will put the value of that property as the whole HTML content in that element.
But sometimes you'll need to bind multiple properties, for example our house h2
element looks like this:
<h2><a href="/house/1">29 Acacia Road</a></h2>
There's a link where the href
attribute contains the ID of the house, and the content of the link is the house address. We clearly need to bind those two pieces of data, and in the case of the href
attribute some of the value is static (the '/house/' bit). Here's how we do that:
<h2><a href="/house/1" data-binder-attribute-href="/house/{{id}}" data-binder-content="{{address}}">29 Acacia Road</a></h2>
When you want to bind a property to a particular attribute, not the element content, use the data-binder-attribute-
then add on the name of the attribute you want to set the value of; in this case href
. So when the data for house ID 6 is bound to this template the resulting HTML will be:
<h2><a href="/house/6">12 Green Street</a></h2>
This shows that you can combine static content and data from your JSON object. In fact you can put almost anything you like in the data-binder-content and data-binder-attribute-* attributes. We could have coded the h2
element like this:
<h2 data-binder-content="<a href='/house/{{id}}'>{{address}}</a>"><a href="/house/1">29 Acacia Road</a></h2>
Which would have exactly the same result. The only tricky thing we've had to do is change the "
characters to '
in our data-binder-content attribute as, obviously, a real " would close the value of that attribute.
We've seen how to mark up your HTML with data attributes so the Data Binder script knows what is a template. We've also seen how to mark up the elements in a template so particular properties can be bound to them. But how do you actually get the system to work? What you need to do is call the Data Binder script and give it the name of the template and the JSON data you want to bind. Here's ho we'd do it for our houses example (assuming the JSON data is in a variable named 'houses'):
DataBinder.Bind('house-details', houses);
That's it, the Data Binder script will bind the data to the templates and you'll see the new data in your web page. What the script does is this:
ol
element) and removes all it's children (in our example all the li
elements for the houses on page 1)All of this happens in a few milliseconds, so users see the new houses very quickly. If the data in the JSON object is not an array of items then the script doesn't do any looping.
To help you understand what's going on here's a full bit of JavaScript which will show you exactly what happens:
// for each link which is a page link (i.e. Page 2 etc) intercept the click and keypress events $('a.pagelink').on('click keypress', function(e){ // get the number of the page the user has requested and their query // I am leaving this section of the code as an exercise for the reader var pg = 2, q = 'Leeds'; // perform an AJAX request to get the JSON data for the search $.getJSON( '/ajax/search?q=' + q + '&pg=' + pg, function(data) { // if the data could be fetched OK tell the Data Binder script to bind the data to the 'house-details' template DataBinder.Bind('house-details', data); // return false to stop the link from navigating the user to the new page e.preventDefault; return false; }); });
OK, got the gist of it? Take a look at the working example, and you can also check out the script.