4 window['model'] = window['model'] || {};
6 /** @type {function(Object,function(Object))} */
8 /** @type {function(Object,function(Object))} */
12 var requiredTemplates = {};
15 Object.observe(requiredTemplates,function(changes){
16 changes.forEach(function(ch){
18 loadTemplate(ch.name);
26 function Template(template){
27 template.removeAttribute("data-template");
28 var attrName = template.templateName;
29 template.template = this;
33 function Instance(scope){
36 Object.defineProperty(scope,"this",{
45 var getterCacheByProperty = {};
47 var setterCacheByProperty = {};
49 this.containingTemplateInstances = [];
51 function evaluateExpression(expr){
53 return (new Function("v","return v."+expr+";"))(scope);
58 function update(name,modelAction){
60 for(var i in mapping[name]){(function(){
61 var b = mapping[name][i];
64 something = evaluateExpression(b.expr);
69 = getterCache[name+b.expr]
70 = getterCacheByProperty[b.which]
71 = getterCache[name+b.expr]
72 || getterCacheByProperty[b.which]
74 if(setterCacheByProperty[b.which]){
75 cache.relatedSetter = setterCacheByProperty[b.which];
77 var update = function(value){
78 cache.lastValue = value;
79 while(cache.reqList.length){
80 var name = cache.reqList.pop();
81 (new Function("s","n","v","s[n]"+b.expr+"=v;")).call(scope,scope,name,value);
84 cache.reqList = cache.reqList || [];
85 if(!cache.reqList.length){
87 if(value instanceof Function){
88 value(update); // value is a function which takes a callback function
93 cache.reqList.push(b.which);
97 = setterCache[name+b.expr]
98 = setterCacheByProperty[b.which]
99 = setterCache[name+b.expr]
100 || setterCacheByProperty[b.which]
103 = getterCacheByProperty[b.which]
104 || getterCacheByProperty[b.which]
106 gcache.relatedSetter = scache;
107 scache.func = something;
110 var value = something;
112 !(name in getterCacheByProperty) ||
113 getterCacheByProperty[name].lastValue != value
114 ){ // value wasn't changed by a getter
116 if(name in getterCacheByProperty){
117 setter = getterCacheByProperty[name].relatedSetter.func;
118 delete getterCacheByProperty[name].lastValue;
120 if( !setter && (name in setterCacheByProperty) )
121 setter = setterCacheByProperty[name].func;
123 var callback = setter(value);
128 if(b.what=="attribute"){
129 setAttr(b.element,b.which,value);
130 }else if(b.what=="style"){
131 setStyle(b.element,b.which,value);
132 }else if(b.what=="content"){
133 setContent(b.element,value);
143 this.root = (template instanceof HTMLHtmlElement)?template:template.cloneNode(true);
144 this.root.templateInstance = this;
145 this.root.classList.add(attrName);
147 function clearContent(el){
148 while(el.childNodes.length){
149 var e = el.childNodes[el.childNodes.length-1];
150 if(e.templateInstance)
151 e.templateInstance._cleanup();
157 this._cleanup = function(){
158 Object.unobserve(this.observer.obj,this.observer.func);
159 for(var i=0;i<this.containingTemplateInstances.length;i++){
160 var templateInstance = this.containingTemplateInstances[i];
161 templateInstance._cleanup();
165 function setAttr(element,name,value){
167 element[name] = value;
169 element.setAttribute(name,value===undefined?"":value);
172 function setStyle(element,name,value){
173 element.style[name] = value===undefined?"":value;
176 function setContent(element,value){
177 clearContent(element);
178 if(value instanceof Node){
179 var node = value.cloneNode(true);
181 element.appendChild(node);
183 element.appendChild(document.createTextNode(value||''));
188 var bind = e.getAttribute("data-bind");
190 bind = bind.split("¦");
191 for(var i=0;i<bind.length;i++){
192 var b = bind[i].split(":");
194 var name = expr.match(/^([a-zA-Z_$][a-zA-Z0-9_$]*)(.*)/)[1];
195 var style = b[0].substr(0,1) == "@";
196 var attr = style?b[0].substr(1):b[0];
197 var value = evaluateExpression(expr);
199 setStyle(e,attr,value);
201 setAttr(e,attr,value);
202 mapping[name] = mapping[name] || [];
205 what: style?"style":"attribute",
212 var content = e.getAttribute("data-content");
214 var expr = content.match(/^([a-zA-Z_$][a-zA-Z0-9_$]*)(.*)/);
217 var value = evaluateExpression(expr);
219 mapping[name] = mapping[name] || [];
227 var getter = e.getAttribute("data-getter");
229 getter = getter.split("¦");
230 for(var i=0;i<getter.length;i++){
231 var g = getter[i].split(":");
233 var name = expr.match(/^([a-zA-Z_$][a-zA-Z0-9_$]*)(.*)/)[1];
235 mapping[name] = mapping[name] || [];
244 var setter = e.getAttribute("data-setter");
246 setter = setter.split("¦");
247 for(var i=0;i<setter.length;i++){
248 var s = setter[i].split(":");
250 var name = expr.match(/^([a-zA-Z_$][a-zA-Z0-9_$]*)(.*)/)[1];
252 mapping[name] = mapping[name] || [];
261 var updateScope = function(attr){
262 var value = e[attr] || e.getAttribute(attr);
263 for(var name in mapping){
264 var maps = mapping[name];
267 if(map.type!="value"||map.which!=attr)
269 (new Function("s","v","if(s."+map.expr+".toString()!=v)s."+map.expr+"=Object(s."+map.expr+").constructor(v);")).call(scope,scope,value);
273 if("MutationObserver" in window){
274 var observer = new MutationObserver(function(mutations){
275 mutations.forEach(function(mutation){;
276 updateScope(mutation.attributeName);
279 observer.observe( e, /** @type {!MutationObserverInit} */ ({ attributes: true }) );
280 }else if("value" in e){
281 e.addEventListener("change",updateScope.bind(null,"value"),false);
282 e.addEventListener("input",updateScope.bind(null,"value"),false);
285 e.addEventListener("change",function(){
286 e.setAttribute("value",e.value);
289 var map = e.getAttribute("data-map");
291 var v = map.split(":");
292 var expr = v[0].match(/^([a-zA-Z_$][a-zA-Z0-9_$]*)(.*)/);
295 var setup_mapping = function(){
296 if(!(v[1] in templates)){
297 ( requiredTemplates[v[1]] = requiredTemplates[v[1]] || [] ).push( setup_mapping );
300 var target = scope[name];
302 target = Object((new Function("v","return v"+expr+";"))(target));
306 if(!("length" in target))
308 e._content = e._content || {
312 Object.observe(target,function(changes){
313 syncLists(e._content,target,e,templates[v[1]]);
315 syncLists(e._content,target,e,templates[v[1]]);
317 ( mapping[v[0]] = mapping[v[0]] || [] ).push({
319 "update": setup_mapping
325 for(var i=0;i<e.children.length;i++)
326 setup(e.children[i]);
329 function syncLists(contentDatas,orig,e,t){
331 for(var i=0;i<orig.length;i++){
332 if(!(orig[i] instanceof Object))
334 if(b.indexOf(orig[i])!=-1)
338 var a = contentDatas.subScopes;
339 var d = contentDatas.subScopeInfos;
340 for(var i=a.length;i--;){ // remove elements / objects
341 if(b.indexOf(a[i])!=-1)
343 if(d[i].element.parentNode)
344 d[i].element.parentNode.removeChild(d[i].element);
348 for(var i=0;i<b.length;i++){ // add elements / objects
349 var j = a.indexOf(b[i]);
354 element: t.instance(b[i]),
358 e.appendChild(newInfo.element);
360 var last = d[d.length-1].element;
361 if(last.parentNode==e){
362 e.insertBefore(newInfo.element,last.nextSibling);
364 e.appendChild(newInfo.element);
371 for(var i=0;i<d.length;i++){ // move elements / objects to desired index
376 var be = d[x.index].element;
377 arraySwapValues(d,i,x.index);
378 arraySwapValues(a,i,x.index);
379 var ap = ae.parentNode;
380 var bp = be.parentNode;
382 var an = ae.nextSibling;
383 var bn = be.nextSibling;
384 bp.insertBefore(ae,bn);
385 ap.insertBefore(be,an);
392 var observerFunc = function(changes){
393 changes.forEach(function(ch){
394 update(ch.name,ch.type);
397 var observerObj = Object.observe(scope,observerFunc);
408 this.instance = function(scope){
409 var instance = new Instance(scope);
410 return instance.root;
414 function arraySwapValues(a,i,j){
415 a[i]=[a[j],a[j]=a[i]][0];
418 function compileTemplate(e){
419 var name = e.getAttribute("data-template");
420 if(!name&&e instanceof HTMLHtmlElement)
422 if(e.parentNode&&!(e instanceof HTMLHtmlElement))
423 e.parentNode.removeChild(e);
424 e.templateName = name;
425 var t = templates[name] = new Template(e);
426 if(name in requiredTemplates){
427 while(requiredTemplates[name].length){
428 requiredTemplates[name].pop()();
430 delete requiredTemplates[name];
435 function compileTemplates( templates ){
436 if( "querySelectorAll" in templates )
437 templates = templates.querySelectorAll("[data-template]");
438 for(var i=0;i<templates.length;i++){
439 compileTemplate(templates[i]);
445 function loadTemplate(name){
446 var url = base+"templates/"+name+".html";
447 var xhr = new XMLHttpRequest();
448 xhr.open("GET",url,true);
449 xhr.onload = function(e){
450 var result = null;//this.responseXML;
452 result = document.createDocumentFragment();
453 var div = document.createElement("div");
454 div.innerHTML = this.responseText;
455 result.appendChild(div);
457 compileTemplates(result);
462 var initialized = false;
464 function initMVSync(ignoreReadystate){
467 if(!( document.readyState == "complete"
468 || document.readyState == "loaded"
469 || document.readyState == "interactive"
472 if(!("observe" in Object))
475 if(window['templateRoot']){
476 base = window['templateRoot'] + "/";
477 }else if(document.querySelector("[data-template-root]")){
478 base = document.querySelector("[data-template-root]").getAttribute("data-template-root") + "/";
481 compileTemplates(document);
482 var t = compileTemplate(document.documentElement);
486 window['initMVSync'] = initMVSync;
490 addEventListener("load",initMVSync.bind(null,true));
491 addEventListener("DOMContentLoaded",initMVSync.bind(null,true));