Template loading and switching
In this post, we’ll start applying all the things we’ve met in previous posts and, at the same time, we’ll be making reader William Apken really happy :)
William commented a previous post right before preview 6 was released. He was interested in seeing how he could switch templates dynamically. At the time, there wasn’t nothing in the code samples that would help him with that. Things changed with preview 6 and it already has one sample that shows how to do this.
The problem: it defines templates in a declarative way and he’s interested in getting the second template’s HTML from a web service call. So simplify things, I won’t be showing how to get the HTML: that should be the easy part if you understand the rest of the things we’ll be discussing in this post. Ok, now it’s time to get started…
The idea is to have two templates, one for browsing and another for editing, and switch them at runtime when the user clicks on a button. The preview 6 already has a somewhat similar sample , but we’ll change it so that both templates are created on the fly (ie, they aren’t previously defined in the markup). Let’s look at the code and then we’ll analyze it block by block:
<head runat="server">
<title></title>
<script src="Scripts/MicrosoftAjax/start.debug.js"
type="text/javascript">
</script>
<script type="text/javascript">
Sys.require([Sys.components.dataView]);
var templates = {};
Sys.onReady(function() {
var items = [
{ name: "luis", address: "fx" },
{ name: "john", address: "london" },
{ name: "rita", address: "lx" }
];
function createHtml(html) {
var aux = document.createElement("div");
aux.innerHTML = html;
return aux.childNodes[0];
}
var browseHtml = "<ul id='browse'>”+
”<li>{{name}}</li></ul>";
var editHtml = "<div id='edit'>”+
”<input type='text' sys:value='{binding name, mode=twoWay}' />”+
”<input type='text' sys:value='{binding address, mode=twoWay}' />”+
”</div>";
templates.browseTemplate = new Sys.UI.Template(createHtml(browseHtml));
templates.editTemplate = new Sys.UI.Template(createHtml(editHtml));
Sys.create.dataView(Sys.get("#view"),
{
data: items,
itemTemplate: templates.browseTemplate
});
$addHandler( Sys.get("#change"),
"click",
function(){
var dv = Sys.get("$view");
dv.set_itemTemplate(
dv.get_itemTemplate() === templates.editTemplate
? templates.browseTemplate : templates.editTemplate);
});
});
Sys.converters.updateText = function(val) {
return (val === templates.editTemplate ? 'browse' : 'edit');
};
</script>
</head>
<body xmlns:sys="BLOCKED SCRIPTSys">
<input type="button" id="change"
sys:value="{binding itemTemplate, source={{Sys.get('$view')}}, convert=updateText}" />
<div id="view"></div>
</body>
There are several interesting things going on here:
- we’re using the Sys.onReady “event” for running all the code necessary for creating the templates and hooking up the click event which lead to the template switch;
- we need two templates, so we start by creating the HTML for those templates (notice that these are just simple strings that simulate the HTML obtained from a possible server side call). Notice that we’re also specifying binding expressions. The astute reader will also notice that we’re not using the sys-template class. This makes sense because that CSS class is used for “finding” templates during the DOM parsing and in this case we’re explicitly indicating the HTML element that should be used for the template;
- the HTML strings end up being passed into a helper method named createHtml. This method creates new DIV node so that it can parse the HTML string by setting its innerHTML property. This is (probably) the easiest way to parse an HTML string (into a DOM node);
- as we’ve seen, templates don’t require a reference to a page’s DOM element. That means that we can simply create the nodes and pass them to the constructor of the Sys.UI.Template.
- since we’ll be using these templates for the entire life cycle of the page, I’ve opted for saving them in a global object named templates. This means that we’ll only be creating each template once and then we’ll simply reuse them while the user clicks in the button;
- we’ve used a binding for ensuring proper update of the button’s text.
Notice that you can also use a slightly different approach if you don’t mind “embedding” the templates in the DOM. For instance, take a look at the following modified JavaScript which embeds the HTML in the page’s DOM:
Sys.require([Sys.components.dataView]);
Sys.onReady(function() {
var items = [
{ name: "luis", address: "fx" },
{ name: "john", address: "london" },
{ name: "rita", address: "lx" }
];
function createHtml(html) {
var aux = document.createElement("div");
aux.innerHTML = html;
return aux.childNodes[0];
}
var browseHtml = "...";//same as before
var editHtml = "...";//same as before
document.body.appendChild(createHtml(browseHtml));
document.body.appendChild(createHtml(editHtml));
Sys.create.dataView(Sys.get("#view"),
{
data: items,
itemTemplate: "#browse"
});
$addHandler(Sys.get("#change"),
"click",
function() {
var dv = Sys.get("$view");
dv.set_itemTemplate(
dv.get_itemTemplate() === "#browse"
? "#edit" : "#browse" );
});
Sys.converters.updateText = function(val) {
return (val === "#edit" ? 'browse' : 'edit');
}
});
We start by creating the nodes and appending them to the page’s DOM. Notice that in this case, we don’t create a template explicitly; we rely, instead, on passing a string (on the form expected by Sys.get helper) to the itemTemplate property. The DataView control is smart enough for checking the type of the property and for instantiating a template when it finds a string.
This might be a good approach for those getting HTML through the jquery’s load method. Stay tuned for more in MS AJAX.