5 window['model'] = window['model'] || {};
7 /** @type {function(Object,function(Object))} */
11 var requiredTemplates = {};
13 Object.observe(requiredTemplates,function(changes){
14 changes.forEach(function(ch){
16 loadTemplate(ch.name);
23 function Template(template){
24 template.removeAttribute("data-template");
25 var attrName = template.templateName;
26 template.template = this;
30 function Instance(scope){
33 Object.defineProperty(scope,"this",{
42 var getterCacheByProperty = {};
44 var setterCacheByProperty = {};
46 this.containingTemplateInstances = [];
48 function evaluateExpression(expr){
50 return (new Function("v","return v."+expr+";"))(scope);
55 function update(name,modelAction){
57 for(var i in mapping[name]){(function(){
58 var b = mapping[name][i];
61 something = evaluateExpression(b.expr);
66 = getterCache[name+b.expr]
67 = getterCacheByProperty[b.which]
68 = getterCache[name+b.expr]
69 || getterCacheByProperty[b.which]
71 if(setterCacheByProperty[b.which]){
72 cache.relatedSetter = setterCacheByProperty[b.which];
74 var update = function(value){
75 cache.lastValue = value;
76 while(cache.reqList.length){
77 var name = cache.reqList.pop();
78 (new Function("s","n","v","s[n]"+b.expr+"=v;")).call(scope,scope,name,value);
81 cache.reqList = cache.reqList || [];
82 if(!cache.reqList.length){
84 if(value instanceof Function){
85 value(update); // value is a function which takes a callback function
90 cache.reqList.push(b.which);
94 = setterCache[name+b.expr]
95 = setterCacheByProperty[b.which]
96 = setterCache[name+b.expr]
97 || setterCacheByProperty[b.which]
100 = getterCacheByProperty[b.which]
101 || getterCacheByProperty[b.which]
103 gcache.relatedSetter = scache;
104 scache.func = something;
107 var value = something;
109 !(name in getterCacheByProperty) ||
110 getterCacheByProperty[name].lastValue != value
111 ){ // value wasn't changed by a getter
113 if(name in getterCacheByProperty){
114 setter = getterCacheByProperty[name].relatedSetter.func;
115 delete getterCacheByProperty[name].lastValue;
117 if( !setter && (name in setterCacheByProperty) )
118 setter = setterCacheByProperty[name].func;
120 var callback = setter(value);
125 if(b.what=="attribute"){
126 setAttr(b.element,b.which,value);
127 }else if(b.what=="content"){
128 setContent(b.element,value);
146 this.root = template.cloneNode(true);
147 this.root.templateInstance = this;
148 this.root.classList.add(attrName);
150 function clearContent(el){
151 while(el.childNodes.length){
152 var e = el.childNodes[el.childNodes.length-1];
153 if(e.templateInstance)
154 e.templateInstance._cleanup();
158 this._cleanup = function(){
159 Object.unobserve(this.observer.obj,this.observer.func);
160 for(var i=0;i<this.containingTemplateInstances.length;i++){
161 var templateInstance = this.containingTemplateInstances[i];
162 templateInstance._cleanup();
166 function setAttr(element,name,value){
168 element[name] = value;
170 element.setAttribute(name,value);
173 function setContent(element,value){
174 clearContent(element);
175 if(value instanceof Node){
176 var node = value.cloneNode();
178 element.appendChild(node);
180 element.appendChild(document.createTextNode(value||''));
185 var bind = e.getAttribute("data-bind");
187 bind = bind.split("¦");
188 for(var i=0;i<bind.length;i++){
189 var b = bind[i].split(":");
191 var name = expr.match(/^([a-zA-Z_$][a-zA-Z0-9_$]*)(.*)/)[1];
193 var value = evaluateExpression(expr);
194 setAttr(e,attr,value);
195 mapping[name] = mapping[name] || [];
205 var content = e.getAttribute("data-content");
207 var expr = content.match(/^([a-zA-Z_$][a-zA-Z0-9_$]*)(.*)/);
210 var value = evaluateExpression(expr);
212 mapping[name] = mapping[name] || [];
220 var getter = e.getAttribute("data-getter");
222 getter = getter.split("¦");
223 for(var i=0;i<getter.length;i++){
224 var g = getter[i].split(":");
226 var name = expr.match(/^([a-zA-Z_$][a-zA-Z0-9_$]*)(.*)/)[1];
228 mapping[name] = mapping[name] || [];
237 var setter = e.getAttribute("data-setter");
239 setter = setter.split("¦");
240 for(var i=0;i<setter.length;i++){
241 var s = setter[i].split(":");
243 var name = expr.match(/^([a-zA-Z_$][a-zA-Z0-9_$]*)(.*)/)[1];
245 mapping[name] = mapping[name] || [];
254 var updateScope = function(attr){
255 var value = e.getAttribute(attr);
256 for(var name in mapping){
257 var maps = mapping[name];
260 if(map.type!="value"||map.which!=attr)
262 (new Function("s","v","if(s."+map.expr+".toString()!=v)s."+map.expr+"=Object(s."+map.expr+").constructor(v);")).call(scope,scope,value);
266 var observer = new MutationObserver(function(mutations){
267 mutations.forEach(function(mutation){;
268 updateScope(mutation.attributeName);
271 observer.observe( e, /** @type {!MutationObserverInit} */ ({ attributes: true }) );
273 e.addEventListener("change",function(){
274 e.setAttribute("value",e.value);
277 var map = e.getAttribute("data-map");
279 var v = map.split(":");
280 var expr = v[0].match(/^([a-zA-Z_$][a-zA-Z0-9_$]*)(.*)/);
283 var setup_mapping = function(){
284 if(!(v[1] in templates)){
285 ( requiredTemplates[v[1]] = requiredTemplates[v[1]] || [] ).push( setup_mapping );
288 var target = scope[name];
291 target = Object((new Function("v","return v"+expr+";"))(target));
295 if("length" in target){
297 Object.observe(target,function(changes){
298 syncLists(e.content,target,e,templates[v[1]]);
300 syncLists(e.content,target,e,templates[v[1]]);
302 var instance = templates[v[1]].instance(target);
303 sc.containingTemplateInstances.push(instance.templateInstance);
304 e.appendChild(instance);
307 ( mapping[v[0]] = mapping[v[0]] || [] ).push({
309 "update": setup_mapping,
310 "clear": clearContent.bind(null,e)
316 for(var i=0;i<e.children.length;i++)
317 setup(e.children[i]);
320 function syncLists(a,b,e,t){
321 for(var i=a.length;i--;)
322 if(b.indexOf(a[i])==-1){
324 e.removeChild(e.children[i]);
326 for(var i=0;i<b.length;i++)
327 if(a.indexOf(b[i])==-1){
329 e.appendChild(t.instance(b[i]));
335 var observerFunc = function(changes){
336 changes.forEach(function(ch){
337 update(ch.name,ch.type);
340 var observerObj = Object.observe(scope,observerFunc);
351 this.instance = function(scope){
352 var instance = new Instance(scope);
353 return instance.root;
357 function compileTemplate(e){
358 var name = e.getAttribute("data-template");
362 e.parentElement.removeChild(e);
363 e.templateName = name;
364 var t = templates[name] = new Template(e);
365 if(name in requiredTemplates){
366 while(requiredTemplates[name].length){
367 requiredTemplates[name].pop()();
369 delete requiredTemplates[name];
374 function compileTemplates( templates ){
375 if( "querySelectorAll" in templates )
376 templates = templates.querySelectorAll("[data-template]");
377 for(var i=0;i<templates.length;i++){
378 compileTemplate(templates[i]);
384 function loadTemplate(name){
385 var url = base+"templates/"+name+".html";
386 var xhr = new XMLHttpRequest();
387 xhr.open("GET",url,true);
388 xhr.onload = function(e){
389 var result = null;//this.responseXML;
391 result = document.createDocumentFragment();
392 var div = document.createElement("div");
393 div.innerHTML = this.responseText;
394 result.appendChild(div);
396 compileTemplates(result);
401 addEventListener("load",function(){
402 if(window['templateRoot']){
403 base = window['templateRoot'] + "/";
404 }else if(document.querySelector("[data-template-root]")){
405 base = document.querySelector("[data-template-root]").getAttribute("data-template-root") + "/";
407 compileTemplates(document);
408 var t = compileTemplate(document.body);
409 document.documentElement.appendChild(document.body=t.instance(model));