-
-
Save jcgregorio/7fa68cdced1181416559 to your computer and use it in GitHub Desktop.
<!DOCTYPE html> | |
<html> | |
<head> | |
<title></title> | |
<meta charset="utf-8" /> | |
<script src="templating.js" type="text/javascript" charset="utf-8"></script> | |
</head> | |
<body> | |
<template id=t> | |
<div> | |
<p><a href="{{ url }}">{{ foo.bar.baz }} {{ quux }}</a>!</p> | |
<!-- | |
Loop over arrays. Use data attributes of | |
data-repeat-[name]="{{ x.y.z }}" and reference | |
the values iterated over using {{ [name] }}. | |
For arrays the 'i' state variable is also set. | |
--> | |
<ul data-repeat-num="{{ list }}"> | |
<li>{{ num }} {{ i }}</li> | |
</ul> | |
<!-- | |
Loop over Objects. For objects the 'key' state variable is also set. | |
--> | |
<ul data-repeat-o="{{ anobj }}"> | |
<li>{{ key }}={{ o.name }}</li> | |
</ul> | |
</div> | |
</template> | |
<script type="text/javascript" charset="utf-8"> | |
var clone = document.importNode(document.querySelector('#t').content, true); | |
data = { | |
foo: { bar: { baz: "Hello"}}, | |
quux: "World", | |
list: ["a", "b", "c"], | |
anobj: { | |
foo: {name: "Fred"}, | |
bar: {name: "Barney"} | |
}, | |
url: "http://example.com", | |
}; | |
Expand(clone, data); | |
document.body.appendChild(clone); | |
</script> | |
</body> | |
</html> |
// Copyright (c) 2014 Google Inc. All rights reserved. | |
// | |
// Redistribution and use in source and binary forms, with or without | |
// modification, are permitted provided that the following conditions are | |
// met: | |
// | |
// * Redistributions of source code must retain the above copyright | |
// notice, this list of conditions and the following disclaimer. | |
// * Redistributions in binary form must reproduce the above | |
// copyright notice, this list of conditions and the following disclaimer | |
// in the documentation and/or other materials provided with the | |
// distribution. | |
// * Neither the name of Google Inc. nor the names of its | |
// contributors may be used to endorse or promote products derived from | |
// this software without specific prior written permission. | |
// | |
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
(function(ns) { | |
var root = ns; | |
var re = /{{\s([\w\.]+)\s}}/; | |
function filterState(address, state) { | |
var mystate = state; | |
address.forEach(function(a) { | |
if (mystate.hasOwnProperty(a)) { | |
mystate = mystate[a]; | |
} else { | |
throw a + " is not a valid property of " + JSON.stringify(mystate); | |
} | |
}); | |
return mystate; | |
} | |
function ssplice(str, index, count, add) { | |
return str.slice(0, index) + add + str.slice(index + count); | |
} | |
function addressOf(s) { | |
if ((match = re.exec(s)) != null) { | |
return match[1].split("."); | |
} else { | |
return null; | |
} | |
} | |
function expandString(s, state) { | |
var match; | |
var found = false; | |
while ((match = re.exec(s)) != null) { | |
found = true; | |
address = match[1].split("."); | |
m = filterState(address, state); | |
s = ssplice(s, match.index, match[0].length, m); | |
} | |
if (found) { | |
return s; | |
} | |
return null; | |
} | |
function expand(e, state) { | |
if (e.nodeName == "#text") { | |
m = expandString(e.textContent, state); | |
if (m != null) { | |
e.textContent = m; | |
} | |
} | |
if (e.attributes != undefined) { | |
for (var i=0; i<e.attributes.length; i++) { | |
var attr = e.attributes[i]; | |
if (attr.name.indexOf('data-repeat') === 0) { | |
var parts = attr.name.split('-'); | |
if (parts.length != 3) { | |
throw "Repeat format is data-repeat-[name]. Got " + attr.name; | |
} | |
var name = parts[2]; | |
var tpl = e.removeChild(e.firstElementChild); | |
var address = addressOf(attr.value); | |
if (address == null) { | |
throw attr.value + " doesn't contain an address."; | |
} | |
var childState = filterState(address, state); | |
if ('forEach' in childState) { | |
childState.forEach(function(item, i) { | |
var cl = tpl.cloneNode(true); | |
var instanceState = {}; | |
instanceState[name] = item; | |
instanceState["i"] = i; | |
expand(cl, instanceState); | |
e.appendChild(cl); | |
}); | |
} else { | |
Object.keys(childState).forEach(function(key) { | |
var cl = tpl.cloneNode(true); | |
var instanceState = {}; | |
instanceState[name] = childState[key]; | |
instanceState["key"] = key; | |
expand(cl, instanceState); | |
e.appendChild(cl); | |
}); | |
} | |
} else { | |
m = expandString(attr.value, state); | |
if (m != null) { | |
e[attr.name] = m; | |
} | |
} | |
} | |
} | |
for (var i=0; i<e.childNodes.length; i++) { | |
expand(e.childNodes[i], state); | |
} | |
} | |
root.Expand = expand; | |
})(this); |
Wouldn't be cleaner change the i
and key
state variables for repeat_index
and repeat_key
? It's more readable and reduce the posibility of variables conflict, and also other template mechanism Templator do the same.
trying to see if you can nest data-repeat-[keys] it seems not possible with this iteration, anyone plan on implementing it?
A use case would be looping over an array of objects.
Kudos, this is great @jcgregorio. And nice work, @xeoncross expanding upon it.
I think the cleaned up version of this code, which now lives in https://github.com/jcgregorio/stamp/ addresses the issues you've raised:
@piranna The i
and key
state variable names can now optionally be set by appending a -name
to the data attribute.
@gabrielcsapo Nesting now works for data-repeat.
@jcgregorio I added the most basic data-binding and controller support to expand this example to be a mini-angular of sorts... sort-of. Change the text input and watch as the model updates and then the controller re-complies the template and updates the DOM.
http://jsfiddle.net/Xeoncross/jtk10tL5/