Roo/DomTemplate.js
[roojs1] / Roo / DomTemplate.js
1 /*
2  * Based on:
3  * Roo JS
4  * (c)) Alan Knowles
5  * Licence : LGPL
6  */
7
8
9 /**
10  * @class Roo.DomTemplate
11  * @extends Roo.Template
12  * An effort at a dom based template engine..
13  *
14  * Similar to XTemplate, except it uses dom parsing to create the template..
15  *
16  * Supported features:
17  *
18  *  Tags:
19
20 <pre><code>
21       {a_variable} - output encoded.
22       {a_variable.format:("Y-m-d")} - call a method on the variable
23       {a_variable:raw} - unencoded output
24       {a_variable:toFixed(1,2)} - Roo.util.Format."toFixed"
25       {a_variable:this.method_on_template(...)} - call a method on the template object.
26  
27 </code></pre>
28  *  The tpl tag:
29 <pre><code>
30         &lt;div roo-for="a_variable or condition.."&gt;&lt;/div&gt;
31         &lt;div roo-if="a_variable or condition"&gt;&lt;/div&gt;
32         &lt;div roo-exec="some javascript"&gt;&lt;/div&gt;
33         &lt;div roo-name="named_template"&gt;&lt;/div&gt; 
34   
35 </code></pre>
36  *      
37  */
38 Roo.DomTemplate = function()
39 {
40      Roo.DomTemplate.superclass.constructor.apply(this, arguments);
41      if (this.html) {
42         this.compile();
43      }
44 };
45
46
47 Roo.extend(Roo.DomTemplate, Roo.Template, {
48     /**
49      * id counter for sub templates.
50      */
51     id : 0,
52     /**
53      * flag to indicate if dom parser is inside a pre,
54      * it will strip whitespace if not.
55      */
56     inPre : false,
57     
58     /**
59      * The various sub templates
60      */
61     tpls : false,
62     
63     
64     
65     /**
66      *
67      * basic tag replacing syntax
68      * WORD:WORD()
69      *
70      * // you can fake an object call by doing this
71      *  x.t:(test,tesT) 
72      * 
73      */
74     re : /(\{|\%7B)([\w-\.]+)(?:\:([\w\.]*)(?:\(([^)]*?)?\))?)?(\}|\%7D)/g,
75     //re : /\{([\w-\.]+)(?:\:([\w\.]*)(?:\((.*?)?\))?)?\}/g,
76     
77     iterChild : function (node, method) {
78         
79         var oldPre = this.inPre;
80         if (node.tagName == 'PRE') {
81             this.inPre = true;
82         }
83         for( var i = 0; i < node.childNodes.length; i++) {
84             method.call(this, node.childNodes[i]);
85         }
86         this.inPre = oldPre;
87     },
88     
89     
90     
91     /**
92      * compile the template
93      *
94      * This is not recursive, so I'm not sure how nested templates are really going to be handled..
95      *
96      */
97     compile: function()
98     {
99         var s = this.html;
100         
101         // covert the html into DOM...
102         var doc = false;
103         try {
104             doc = document.implementation.createHTMLDocument("");
105         } catch (e) {
106             // old IE...
107             doc = new ActiveXObject("htmlfile");
108         }
109         //doc.documentElement.innerHTML = htmlBody
110         var div = doc.documentElement;
111         div.innerHTML =   this.html  ;
112         
113         this.tpls = [];
114         var _t = this;
115         this.iterChild(div, function(n) {_t.compileNode(n, true); });
116         
117         var tpls = this.tpls;
118         
119         // create a top level template from the snippet..
120         
121         //Roo.log(div.innerHTML);
122         
123         var tpl = {
124             uid : 'master',
125             id : this.id++,
126             attr : false,
127             value : false,
128             body : div.innerHTML,
129             
130             forCall : false,
131             execCall : false,
132             dom : div,
133             isTop : true
134             
135         };
136         tpls.unshift(tpl);
137         
138         
139         // compile them...
140         this.tpls = [];
141         Roo.each(tpls, function(tp){
142             this.compileTpl(tp);
143             this.tpls[tp.id] = tp;
144         }, this);
145         
146         this.master = tpls[0];
147         return this;
148         
149         
150     },
151     
152     compileNode : function(node, istop) {
153         // test for
154         //Roo.log(node);
155         
156         
157         // skip anything not a tag..
158         if (node.nodeType != 1) {
159             if (node.nodeType == 3 && !this.inPre) {
160                 // reduce white space..
161                 node.nodeValue = node.nodeValue.replace(/\s+/g, ' '); 
162                 
163             }
164             return;
165         }
166         
167         var tpl = {
168             uid : false,
169             id : false,
170             attr : false,
171             value : false,
172             body : '',
173             
174             forCall : false,
175             execCall : false,
176             dom : false,
177             isTop : istop
178             
179             
180         };
181         
182         
183         switch(true) {
184             case (node.hasAttribute('roo-for')): tpl.attr = 'for'; break;
185             case (node.hasAttribute('roo-if')): tpl.attr = 'if'; break;
186             case (node.hasAttribute('roo-name')): tpl.attr = 'name'; break;
187             case (node.hasAttribute('roo-exec')): tpl.attr = 'exec'; break;
188             // no default..
189         }
190         
191         
192         if (!tpl.attr) {
193             // just itterate children..
194             this.iterChild(node,this.compileNode);
195             return;
196         }
197         tpl.uid = this.id++;
198         tpl.value = node.getAttribute('roo-' +  tpl.attr);
199         node.removeAttribute('roo-'+ tpl.attr);
200         if (tpl.attr != 'name') {
201             var placeholder = document.createTextNode('{domtpl' + tpl.uid + '}');
202             node.parentNode.replaceChild(placeholder,  node);
203         } else {
204             
205             var placeholder =  document.createElement('span');
206             placeholder.className = 'roo-tpl-' + tpl.value;
207             node.parentNode.replaceChild(placeholder,  node);
208         }
209         
210         // parent now sees '{domtplXXXX}
211         this.iterChild(node,this.compileNode);
212         
213         // we should now have node body...
214         var div = document.createElement('div');
215         div.appendChild(node);
216         tpl.dom = node;
217         // this has the unfortunate side effect of converting tagged attributes
218         // eg. href="{...}" into %7C...%7D
219         // this has been fixed by searching for those combo's although it's a bit hacky..
220         
221         
222         tpl.body = div.innerHTML;
223         
224         
225          
226         tpl.id = tpl.uid;
227         switch(tpl.attr) {
228             case 'for' :
229                 switch (tpl.value) {
230                     case '.':  tpl.forCall = new Function('values', 'parent', 'with(values){ return values; }'); break;
231                     case '..': tpl.forCall= new Function('values', 'parent', 'with(values){ return parent; }'); break;
232                     default:   tpl.forCall= new Function('values', 'parent', 'with(values){ return '+tpl.value+'; }');
233                 }
234                 break;
235             
236             case 'exec':
237                 tpl.execCall = new Function('values', 'parent', 'with(values){ '+(Roo.util.Format.htmlDecode(tpl.value))+'; }');
238                 break;
239             
240             case 'if':     
241                 tpl.ifCall = new Function('values', 'parent', 'with(values){ return '+(Roo.util.Format.htmlDecode(tpl.value))+'; }');
242                 break;
243             
244             case 'name':
245                 tpl.id  = tpl.value; // replace non characters???
246                 break;
247             
248         }
249         
250         
251         this.tpls.push(tpl);
252         
253         
254         
255     },
256     
257     
258     
259     
260     /**
261      * Compile a segment of the template into a 'sub-template'
262      *
263      * 
264      * 
265      *
266      */
267     compileTpl : function(tpl)
268     {
269         var fm = Roo.util.Format;
270         var useF = this.disableFormats !== true;
271         
272         var sep = Roo.isGecko ? "+\n" : ",\n";
273         
274         var undef = function(str) {
275             Roo.debug && Roo.log("Property not found :"  + str);
276             return '';
277         };
278           
279         //Roo.log(tpl.body);
280         
281         
282         
283         var fn = function(m, lbrace, name, format, args)
284         {
285             //Roo.log("ARGS");
286             //Roo.log(arguments);
287             args = args ? args.replace(/\\'/g,"'") : args;
288             //["{TEST:(a,b,c)}", "TEST", "", "a,b,c", 0, "{TEST:(a,b,c)}"]
289             if (typeof(format) == 'undefined') {
290                 format =  'htmlEncode'; 
291             }
292             if (format == 'raw' ) {
293                 format = false;
294             }
295             
296             if(name.substr(0, 6) == 'domtpl'){
297                 return "'"+ sep +'this.applySubTemplate('+name.substr(6)+', values, parent)'+sep+"'";
298             }
299             
300             // build an array of options to determine if value is undefined..
301             
302             // basically get 'xxxx.yyyy' then do
303             // (typeof(xxxx) == 'undefined' || typeof(xxx.yyyy) == 'undefined') ?
304             //    (function () { Roo.log("Property not found"); return ''; })() :
305             //    ......
306             
307             var udef_ar = [];
308             var lookfor = '';
309             Roo.each(name.split('.'), function(st) {
310                 lookfor += (lookfor.length ? '.': '') + st;
311                 udef_ar.push(  "(typeof(" + lookfor + ") == 'undefined')"  );
312             });
313             
314             var udef_st = '((' + udef_ar.join(" || ") +") ? undef('" + name + "') : "; // .. needs )
315             
316             
317             if(format && useF){
318                 
319                 args = args ? ',' + args : "";
320                  
321                 if(format.substr(0, 5) != "this."){
322                     format = "fm." + format + '(';
323                 }else{
324                     format = 'this.call("'+ format.substr(5) + '", ';
325                     args = ", values";
326                 }
327                 
328                 return "'"+ sep +   udef_st   +    format + name + args + "))"+sep+"'";
329             }
330              
331             if (args.length) {
332                 // called with xxyx.yuu:(test,test)
333                 // change to ()
334                 return "'"+ sep + udef_st  + name + '(' +  args + "))"+sep+"'";
335             }
336             // raw.. - :raw modifier..
337             return "'"+ sep + udef_st  + name + ")"+sep+"'";
338             
339         };
340         var body;
341         // branched to use + in gecko and [].join() in others
342         if(Roo.isGecko){
343             body = "tpl.compiled = function(values, parent){  with(values) { return '" +
344                    tpl.body.replace(/(\r\n|\n)/g, '\\n').replace(/'/g, "\\'").replace(this.re, fn) +
345                     "';};};";
346         }else{
347             body = ["tpl.compiled = function(values, parent){  with (values) { return ['"];
348             body.push(tpl.body.replace(/(\r\n|\n)/g,
349                             '\\n').replace(/'/g, "\\'").replace(this.re, fn));
350             body.push("'].join('');};};");
351             body = body.join('');
352         }
353         
354         Roo.debug && Roo.log(body.replace(/\\n/,'\n'));
355        
356         /** eval:var:tpl eval:var:fm eval:var:useF eval:var:undef  */
357         eval(body);
358         
359         return this;
360     },
361      
362     /**
363      * same as applyTemplate, except it's done to one of the subTemplates
364      * when using named templates, you can do:
365      *
366      * var str = pl.applySubTemplate('your-name', values);
367      *
368      * 
369      * @param {Number} id of the template
370      * @param {Object} values to apply to template
371      * @param {Object} parent (normaly the instance of this object)
372      */
373     applySubTemplate : function(id, values, parent)
374     {
375         
376         
377         var t = this.tpls[id];
378         
379         
380         try { 
381             if(t.ifCall && !t.ifCall.call(this, values, parent)){
382                 Roo.debug && Roo.log('if call on ' + t.value + ' return false');
383                 return '';
384             }
385         } catch(e) {
386             Roo.log('Xtemplate.applySubTemplate('+ id+ '): Exception thrown on roo-if="' + t.value + '" - ' + e.toString());
387             Roo.log(values);
388           
389             return '';
390         }
391         try { 
392             
393             if(t.execCall && t.execCall.call(this, values, parent)){
394                 return '';
395             }
396         } catch(e) {
397             Roo.log('Xtemplate.applySubTemplate('+ id+ '): Exception thrown on roo-for="' + t.value + '" - ' + e.toString());
398             Roo.log(values);
399             return '';
400         }
401         
402         try {
403             var vs = t.forCall ? t.forCall.call(this, values, parent) : values;
404             parent = t.target ? values : parent;
405             if(t.forCall && vs instanceof Array){
406                 var buf = [];
407                 for(var i = 0, len = vs.length; i < len; i++){
408                     try {
409                         buf[buf.length] = t.compiled.call(this, vs[i], parent);
410                     } catch (e) {
411                         Roo.log('Xtemplate.applySubTemplate('+ id+ '): Exception thrown on body="' + t.value + '" - ' + e.toString());
412                         Roo.log(e.body);
413                         //Roo.log(t.compiled);
414                         Roo.log(vs[i]);
415                     }   
416                 }
417                 return buf.join('');
418             }
419         } catch (e) {
420             Roo.log('Xtemplate.applySubTemplate('+ id+ '): Exception thrown on roo-for="' + t.value + '" - ' + e.toString());
421             Roo.log(values);
422             return '';
423         }
424         try {
425             return t.compiled.call(this, vs, parent);
426         } catch (e) {
427             Roo.log('Xtemplate.applySubTemplate('+ id+ '): Exception thrown on body="' + t.value + '" - ' + e.toString());
428             Roo.log(e.body);
429             //Roo.log(t.compiled);
430             Roo.log(values);
431             return '';
432         }
433     },
434
435    
436
437     applyTemplate : function(values){
438         return this.master.compiled.call(this, values, {});
439         //var s = this.subs;
440     },
441
442     apply : function(){
443         return this.applyTemplate.apply(this, arguments);
444     }
445
446  });
447
448 Roo.DomTemplate.from = function(el){
449     el = Roo.getDom(el);
450     return new Roo.Domtemplate(el.value || el.innerHTML);
451 };