Roo/XTemplate.js
[roojs1] / Roo / XTemplate.js
1 /*
2  * Based on:
3  * Ext JS Library 1.1.1
4  * Copyright(c) 2006-2007, Ext JS, LLC.
5  *
6  * Originally Released Under LGPL - original licence link has changed is not relivant.
7  *
8  * Fork - LGPL
9  * <script type="text/javascript">
10  */
11
12
13 /**
14  * @class Roo.XTemplate
15  * @extends Roo.Template
16  * Provides a template that can have nested templates for loops or conditionals. The syntax is:
17 <pre><code>
18 var t = new Roo.MasterTemplate(
19         '&lt;select name="{name}"&gt;',
20                 '&lt;tpl for="options"&gt;&lt;option value="{value:trim}"&gt;{text:ellipsis(10)}&lt;/option&gt;&lt;/tpl&gt;',
21         '&lt;/select&gt;'
22 );
23  
24 // then append, applying the master template values
25  </code></pre>
26  *
27  * Supported features:
28  *
29  *  Tags:
30  *    {a_variable} - output encoded.
31  *    {a_variable.format:("Y-m-d")} - call a method on the variable
32  *    {a_variable:raw} - unencoded output
33  *    {a_variable:toFixed(1,2)} - Roo.util.Format."toFixed"
34  *    {a_variable:this.method_on_template(...)} - call a method on the template object.
35  *  
36  *
37  *
38  */
39 Roo.XTemplate = function()
40 {
41     Roo.XTemplate.superclass.constructor.apply(this, arguments);
42     if (this.html) {
43         this.compile();
44     }
45 };
46
47
48 Roo.extend(Roo.XTemplate, Roo.Template, {
49
50     /**
51      *
52      * basic tag replacing syntax
53      * WORD:WORD()
54      *
55      * // you can fake an object call by doing this
56      *  x.t:(test,tesT) 
57      * 
58      */
59     re : /\{([\w-\.]+)(?:\:([\w\.]*)(?:\((.*?)?\))?)?\}/g,
60
61     
62     compile: function()
63     {
64         var s = this.html;
65      
66         s = ['<tpl>', s, '</tpl>'].join('');
67     
68         var re = /<tpl\b[^>]*>((?:(?=([^<]+))\2|<(?!tpl\b[^>]*>))*?)<\/tpl>/;
69     
70         var nameRe = /^<tpl\b[^>]*?for="(.*?)"/;
71         var ifRe   = /^<tpl\b[^>]*?if="(.*?)"/;
72         var execRe = /^<tpl\b[^>]*?exec="(.*?)"/;
73         var m, id = 0;
74         var tpls = [];
75     
76         while(true == !!(m = s.match(re))){
77            var m2 = m[0].match(nameRe);
78            var m3 = m[0].match(ifRe);
79            var m4 = m[0].match(execRe);
80            var exp = null, 
81                 fn = null,
82                 exec = null;
83            var name = m2 && m2[1] ? m2[1] : '';
84            if(m3){
85                 // if - puts fn into test..
86                 exp = m3 && m3[1] ? m3[1] : null;
87                 if(exp){
88                    fn = new Function('values', 'parent', 'with(values){ return '+(Roo.util.Format.htmlDecode(exp))+'; }');
89                 }
90            }
91            if(m4){
92                 // exec - calls a function... returns empty if true is  returned.
93                exp = m4 && m4[1] ? m4[1] : null;
94                if(exp){
95                    exec = new Function('values', 'parent', 'with(values){ '+(Roo.util.Format.htmlDecode(exp))+'; }');
96                }
97            }
98            if(name){
99                 // for = 
100                switch(name){
101                    case '.':  name = new Function('values', 'parent', 'with(values){ return values; }'); break;
102                    case '..': name = new Function('values', 'parent', 'with(values){ return parent; }'); break;
103                    default:   name = new Function('values', 'parent', 'with(values){ return '+name+'; }');
104                }
105            }
106            tpls.push({
107                 id: id,
108                 target: name,
109                 exec: exec,
110                 test: fn,
111                 body: m[1]||''
112             });
113            s = s.replace(m[0], '{xtpl'+ id + '}');
114            ++id;
115         }
116         for(var i = tpls.length-1; i >= 0; --i){
117             this.compileTpl(tpls[i]);
118         }
119         this.master = tpls[tpls.length-1];
120         this.tpls = tpls;
121         return this;
122     },
123     
124     applySubTemplate : function(id, values, parent)
125     {
126         var t = this.tpls[id];
127         if(t.test && !t.test.call(this, values, parent)){
128             return '';
129         }
130         if(t.exec && t.exec.call(this, values, parent)){
131             return '';
132         }
133         var vs = t.target ? t.target.call(this, values, parent) : values;
134         parent = t.target ? values : parent;
135         if(t.target && vs instanceof Array){
136             var buf = [];
137             for(var i = 0, len = vs.length; i < len; i++){
138                 buf[buf.length] = t.compiled.call(this, vs[i], parent);
139             }
140             return buf.join('');
141         }
142         return t.compiled.call(this, vs, parent);
143     },
144
145     compileTpl : function(tpl)
146     {
147         var fm = Roo.util.Format;
148         var useF = this.disableFormats !== true;
149         var sep = Roo.isGecko ? "+" : ",";
150         var fn = function(m, name, format, args){
151             //["{TEST:(a,b,c)}", "TEST", "", "a,b,c", 0, "{TEST:(a,b,c)}"]
152             if (typeof(format) == 'undefined') {
153                 format= 'htmlEncode';
154             }
155             if (format == 'raw' ) {
156                 format = false;
157             }
158             
159             if(name.substr(0, 4) == 'xtpl'){
160                 return "'"+ sep +'this.applySubTemplate('+name.substr(4)+', values, parent)'+sep+"'";
161             }
162             
163             var v;
164             //if(name.indexOf('.') != -1){
165                 v = name;
166             //}else{
167             //    v = "values['" + name + "']";
168             //}
169             if(format && useF){
170                 
171                 args = args ? ',' + args : "";
172                  
173                 if(format.substr(0, 5) != "this."){
174                     format = "fm." + format + '(';
175                 }else{
176                     format = 'this.call("'+ format.substr(5) + '", ';
177                     args = ", values";
178                 }
179                 return "'"+ sep + format + v + args + ")"+sep+"'";
180             }
181              
182             if (args.length) {
183                 // called with xxyx.yuu:(test,test)
184                 // change to ()
185                 return "'"+ sep + "("+v+" === undefined ? '' : " + v + '(' +  args + "))"+sep+"'";
186             }
187             // raw.. - :raw modifier..
188             return "'"+ sep + "("+v+" === undefined ? '' : " + v + ")"+sep+"'";
189             
190         };
191         var body;
192         // branched to use + in gecko and [].join() in others
193         if(Roo.isGecko){
194             body = "tpl.compiled = function(values, parent){ with(values) { return '" +
195                    tpl.body.replace(/(\r\n|\n)/g, '\\n').replace(/'/g, "\\'").replace(this.re, fn) +
196                     "';};};";
197         }else{
198             body = ["tpl.compiled = function(values, parent){ with (values) { return ['"];
199             body.push(tpl.body.replace(/(\r\n|\n)/g, '\\n').replace(/'/g, "\\'").replace(this.re, fn));
200             body.push("'].join('');};};");
201             body = body.join('');
202         }
203         
204         /** eval:var:zzzzzzz */
205         eval(body);
206         Roo.log(body.replace(/\\n/,'\n'));
207         
208         return this;
209     },
210
211     applyTemplate : function(values){
212         return this.master.compiled.call(this, values, {});
213         //var s = this.subs;
214     },
215
216     apply : function(){
217         return this.applyTemplate.apply(this, arguments);
218     }
219
220  });
221
222 Roo.XTemplate.from = function(el){
223     el = Roo.getDom(el);
224     return new Roo.XTemplate(el.value || el.innerHTML);
225 };