4 window['model'] = window['model'] || {};
6 /** @type {function(Object,function(Object))} */
8 /** @type {function(Object,function(Object))} */
12 var requiredTemplates = {};
14 Object.observe(requiredTemplates,function(changes){
15 changes.forEach(function(ch){
17 loadTemplate(ch.name);
24 function Template(template){
25 template.removeAttribute("data-template");
26 var attrName = template.templateName;
27 template.template = this;
31 function Instance(scope){
34 Object.defineProperty(scope,"this",{
43 var getterCacheByProperty = {};
45 var setterCacheByProperty = {};
47 this.containingTemplateInstances = [];
49 function evaluateExpression(expr){
51 return (new Function("v","return v."+expr+";"))(scope);
56 function update(name,modelAction){
58 for(var i in mapping[name]){(function(){
59 var b = mapping[name][i];
62 something = evaluateExpression(b.expr);
67 = getterCache[name+b.expr]
68 = getterCacheByProperty[b.which]
69 = getterCache[name+b.expr]
70 || getterCacheByProperty[b.which]
72 if(setterCacheByProperty[b.which]){
73 cache.relatedSetter = setterCacheByProperty[b.which];
75 var update = function(value){
76 cache.lastValue = value;
77 while(cache.reqList.length){
78 var name = cache.reqList.pop();
79 (new Function("s","n","v","s[n]"+b.expr+"=v;")).call(scope,scope,name,value);
82 cache.reqList = cache.reqList || [];
83 if(!cache.reqList.length){
85 if(value instanceof Function){
86 value(update); // value is a function which takes a callback function
91 cache.reqList.push(b.which);
95 = setterCache[name+b.expr]
96 = setterCacheByProperty[b.which]
97 = setterCache[name+b.expr]
98 || setterCacheByProperty[b.which]
101 = getterCacheByProperty[b.which]
102 || getterCacheByProperty[b.which]
104 gcache.relatedSetter = scache;
105 scache.func = something;
108 var value = something;
110 !(name in getterCacheByProperty) ||
111 getterCacheByProperty[name].lastValue != value
112 ){ // value wasn't changed by a getter
114 if(name in getterCacheByProperty){
115 setter = getterCacheByProperty[name].relatedSetter.func;
116 delete getterCacheByProperty[name].lastValue;
118 if( !setter && (name in setterCacheByProperty) )
119 setter = setterCacheByProperty[name].func;
121 var callback = setter(value);
126 if(b.what=="attribute"){
127 setAttr(b.element,b.which,value);
128 }else if(b.what=="content"){
129 setContent(b.element,value);
139 this.root = template.cloneNode(true);
140 this.root.templateInstance = this;
141 this.root.classList.add(attrName);
143 function clearContent(el){
144 while(el.childNodes.length){
145 var e = el.childNodes[el.childNodes.length-1];
146 if(e.templateInstance)
147 e.templateInstance._cleanup();
153 this._cleanup = function(){
154 Object.unobserve(this.observer.obj,this.observer.func);
155 for(var i=0;i<this.containingTemplateInstances.length;i++){
156 var templateInstance = this.containingTemplateInstances[i];
157 templateInstance._cleanup();
161 function setAttr(element,name,value){
163 element[name] = value;
165 element.setAttribute(name,value);
168 function setContent(element,value){
169 clearContent(element);
170 if(value instanceof Node){
171 var node = value.cloneNode(true);
173 element.appendChild(node);
175 element.appendChild(document.createTextNode(value||''));
180 var bind = e.getAttribute("data-bind");
182 bind = bind.split("¦");
183 for(var i=0;i<bind.length;i++){
184 var b = bind[i].split(":");
186 var name = expr.match(/^([a-zA-Z_$][a-zA-Z0-9_$]*)(.*)/)[1];
188 var value = evaluateExpression(expr);
189 setAttr(e,attr,value);
190 mapping[name] = mapping[name] || [];
200 var content = e.getAttribute("data-content");
202 var expr = content.match(/^([a-zA-Z_$][a-zA-Z0-9_$]*)(.*)/);
205 var value = evaluateExpression(expr);
207 mapping[name] = mapping[name] || [];
215 var getter = e.getAttribute("data-getter");
217 getter = getter.split("¦");
218 for(var i=0;i<getter.length;i++){
219 var g = getter[i].split(":");
221 var name = expr.match(/^([a-zA-Z_$][a-zA-Z0-9_$]*)(.*)/)[1];
223 mapping[name] = mapping[name] || [];
232 var setter = e.getAttribute("data-setter");
234 setter = setter.split("¦");
235 for(var i=0;i<setter.length;i++){
236 var s = setter[i].split(":");
238 var name = expr.match(/^([a-zA-Z_$][a-zA-Z0-9_$]*)(.*)/)[1];
240 mapping[name] = mapping[name] || [];
249 var updateScope = function(attr){
250 var value = e.getAttribute(attr);
251 for(var name in mapping){
252 var maps = mapping[name];
255 if(map.type!="value"||map.which!=attr)
257 (new Function("s","v","if(s."+map.expr+".toString()!=v)s."+map.expr+"=Object(s."+map.expr+").constructor(v);")).call(scope,scope,value);
261 var observer = new MutationObserver(function(mutations){
262 mutations.forEach(function(mutation){;
263 updateScope(mutation.attributeName);
266 observer.observe( e, /** @type {!MutationObserverInit} */ ({ attributes: true }) );
268 e.addEventListener("change",function(){
269 e.setAttribute("value",e.value);
272 var map = e.getAttribute("data-map");
274 var v = map.split(":");
275 var expr = v[0].match(/^([a-zA-Z_$][a-zA-Z0-9_$]*)(.*)/);
278 var setup_mapping = function(){
279 if(!(v[1] in templates)){
280 ( requiredTemplates[v[1]] = requiredTemplates[v[1]] || [] ).push( setup_mapping );
283 var target = scope[name];
285 target = Object((new Function("v","return v"+expr+";"))(target));
289 if(!("length" in target))
291 e._content = e._content || {
295 Object.observe(target,function(changes){
296 syncLists(e._content,target,e,templates[v[1]]);
298 syncLists(e._content,target,e,templates[v[1]]);
300 ( mapping[v[0]] = mapping[v[0]] || [] ).push({
302 "update": setup_mapping
308 for(var i=0;i<e.children.length;i++)
309 setup(e.children[i]);
312 function syncLists(contentDatas,b,e,t){
313 var a = contentDatas.subScopes;
314 var d = contentDatas.subScopeInfos;
315 for(var i=a.length;i--;){ // remove elements / objects
316 if(b.indexOf(a[i])!=-1)
318 if(d[i].element.parentNode)
319 d[i].element.parentNode.removeChild(d[i].element);
323 for(var i=0;i<b.length;i++){ // add elements / objects
324 var j = a.indexOf(b[i]);
329 element: t.instance(b[i]),
333 e.appendChild(newInfo.element);
335 var last = d[d.length-1].element;
336 if(last.parentNode==e){
337 e.insertBefore(newInfo.element,last.nextSibling);
339 e.appendChild(newInfo.element);
346 for(var i=0;i<d.length;i++){ // move elements / objects to desired index
351 var be = d[x.index].element;
352 arraySwapValues(d,i,x.index);
353 arraySwapValues(a,i,x.index);
354 var ap = ae.parentNode;
355 var bp = be.parentNode;
357 var an = ae.nextSibling;
358 var bn = be.nextSibling;
359 bp.insertBefore(ae,bn);
360 ap.insertBefore(be,an);
367 var observerFunc = function(changes){
368 changes.forEach(function(ch){
369 update(ch.name,ch.type);
372 var observerObj = Object.observe(scope,observerFunc);
383 this.instance = function(scope){
384 var instance = new Instance(scope);
385 return instance.root;
389 function arraySwapValues(a,i,j){
390 a[i]=[a[j],a[j]=a[i]][0];
393 function compileTemplate(e){
394 var name = e.getAttribute("data-template");
398 e.parentNode.removeChild(e);
399 e.templateName = name;
400 var t = templates[name] = new Template(e);
401 if(name in requiredTemplates){
402 while(requiredTemplates[name].length){
403 requiredTemplates[name].pop()();
405 delete requiredTemplates[name];
410 function compileTemplates( templates ){
411 if( "querySelectorAll" in templates )
412 templates = templates.querySelectorAll("[data-template]");
413 for(var i=0;i<templates.length;i++){
414 compileTemplate(templates[i]);
420 function loadTemplate(name){
421 var url = base+"templates/"+name+".html";
422 var xhr = new XMLHttpRequest();
423 xhr.open("GET",url,true);
424 xhr.onload = function(e){
425 var result = null;//this.responseXML;
427 result = document.createDocumentFragment();
428 var div = document.createElement("div");
429 div.innerHTML = this.responseText;
430 result.appendChild(div);
432 compileTemplates(result);
437 addEventListener("load",function(){
438 if(window['templateRoot']){
439 base = window['templateRoot'] + "/";
440 }else if(document.querySelector("[data-template-root]")){
441 base = document.querySelector("[data-template-root]").getAttribute("data-template-root") + "/";
443 compileTemplates(document);
444 var t = compileTemplate(document.body);
445 document.documentElement.appendChild(document.body=t.instance(model));