While working on round-trip html5 <-> Rdfa-lite query and editing, I’ve been struck by the primitive feeling of html template languages. {{mustache}} , <%escapes%> <–%ssi escapes %–> all work the same way as C preprocessor macros – string concatenation style – which ignores the underlying structure that we’re working with.
Thankfully, HTML5 is changing everything – it has / will have a template tag
Along the way to finding that, I came across Weld.js (dead?), Transparency or Plates and Pure and not forgetting Knockout.js.
None of them quite goes where I want – as they either use class/id, or their own data- attributes.
What I’m after, is leveraging the already existing semantic annotations of existing (or template tag) elements to add new ones.
For example, I might have the following Link menu, and then want to dynamically add others from a remote query
<ul id="page-list"> <template id="page-item-template" style="display:none;"> <li typeof="WebPage" resource="/"> <a property="url" href="/" tabindex="-1" id="index"> <span property="name">Home</span></a> </li> </template> <li typeof="WebPage" resource="/"> <a property="url" href="/" tabindex="-1" id="index"> <span property="name">Home</span></a> </li> <li typeof="WebPage" resource="/SvenDowideit.html"> <a property="url" href="/SvenDowideit.html" tabindex="-1" id="SvenDowideit"> <span property="name">Sven Dowideit</span></a> </li> </ul>
The following works for browsers that don’t support the new HTML5 template tag:
var node = document.querySelector('#page-list [typeof=WebPage]').cloneNode(true); node.querySelector('[property=name]').textContent = 'TODO'; node.querySelector('[property=url]').href = '/TODO.html'; document.querySelector('#page-list').appendChild(node);
A nice start, but to me, there’s still something not right with the addressing scheme –
which is where some of the above template engines use the @ symbol to denote that the value should be set on the named attribute.
something like this might work
var node = document.querySelector('#page-list [typeof=WebPage]').cloneNode(true); node.render( { '[property=name]@textContent': 'TODO', '[property=url]@href': '/TODO.html' }); document.querySelector('#page-list').appendChild(node);
but setting up the relationships beforehand, and making the clone implicit
var template = getTemplate('#page-list [typeof=WebPage]', { name: '[property=name]@textContent', url: '[property=url]@href' }); document.appendChild(template([ {name: 'Sven Dowideit', url: '/SvenDowideit.html'}, {name: 'TODO', url: '/TODO.html'} ]);
Which looks an awful lot like Weld.js’ (and the inverse of Pure?) API. And, should be trivial to implement using Transparancy.
in the process of thinking it through, I wrote a simplistic version that deserves replacing when I’m thinking about something else:
getTemplate: function(templateSelector, map) { if (this.Template === undefined) { var template = document.querySelector(templateSelector); var template_map = {}; for (var key in map) { var address = map[key].match(/^([^@]*)@?(.*)?$/); template_map[key] = { node: address[1] || '*', attr: address[2] || 'textContent' } } this.Template = function(values) { var new_elements = []; for (var idx in values) { var elem = template.cloneNode(true) for (var key in values[idx]) { if (template_map[key] !== undefined) { var node = elem.querySelector(template_map[key].node); //TODO: detect if key is a method, and call it? if (template_map[key].attr === 'textContent') { node.textContent = values[idx][key]; } else { node.setAttribute(template_map[key].attr, values[idx][key]); } } } //TODO: if it where a real template tag, apparently there would be an elem.content new_elements.push(elem.children[0]); } return new_elements; }; } return this.Template; }}