Upgrade to ExtJS 4.0.0 - Released 04/26/2011
[extjs.git] / src / XTemplate.js
similarity index 53%
rename from src/util/XTemplate.js
rename to src/XTemplate.js
index 506fc70..aaad1d1 100644 (file)
@@ -1,9 +1,3 @@
-/*!
- * Ext JS Library 3.3.1
- * Copyright(c) 2006-2010 Sencha Inc.
- * licensing@sencha.com
- * http://www.sencha.com/license
- */
 /**
  * @class Ext.XTemplate
  * @extends Ext.Template
  * the API, but are supported in the templates that can be created</li>
  * </ul></div></p>
  * <p>XTemplate provides the templating mechanism built into:<div class="mdetail-params"><ul>
- * <li>{@link Ext.DataView}</li>
- * <li>{@link Ext.ListView}</li>
- * <li>{@link Ext.form.ComboBox}</li>
- * <li>{@link Ext.grid.TemplateColumn}</li>
- * <li>{@link Ext.grid.GroupingView}</li>
- * <li>{@link Ext.menu.Item}</li>
- * <li>{@link Ext.layout.MenuLayout}</li>
- * <li>{@link Ext.ColorPalette}</li>
+ * <li>{@link Ext.view.View}</li>
  * </ul></div></p>
  *
- * <p>For example usage {@link #XTemplate see the constructor}.</p>
- *
- * @constructor
- * The {@link Ext.Template#Template Ext.Template constructor} describes
+ * The {@link Ext.Template} describes
  * the acceptable parameters to pass to the constructor. The following
  * examples demonstrate all of the supported features.</p>
  *
  * <p>This is the data object used for reference in each code example:</p>
  * <pre><code>
 var data = {
-    name: 'Jack Slocum',
-    title: 'Lead Developer',
-    company: 'Ext JS, LLC',
-    email: 'jack@extjs.com',
-    address: '4 Red Bulls Drive',
-    city: 'Cleveland',
-    state: 'Ohio',
-    zip: '44102',
-    drinks: ['Red Bull', 'Coffee', 'Water'],
-    kids: [{
-        name: 'Sara Grace',
+name: 'Tommy Maintz',
+title: 'Lead Developer',
+company: 'Sencha Inc.',
+email: 'tommy@sencha.com',
+address: '5 Cups Drive',
+city: 'Palo Alto',
+state: 'CA',
+zip: '44102',
+drinks: ['Coffee', 'Soda', 'Water'],
+kids: [{
+        name: 'Joshua',
         age:3
     },{
-        name: 'Zachary',
+        name: 'Matthew',
         age:2
     },{
-        name: 'John James',
+        name: 'Solomon',
         age:0
-    }]
+}]
 };
</code></pre>
+ </code></pre>
  * </div>
  * </li>
  *
@@ -83,7 +67,7 @@ var data = {
 &lt;tpl <b>for</b>=".">...&lt;/tpl>       // loop through array at root node
 &lt;tpl <b>for</b>="foo">...&lt;/tpl>     // loop through array at foo node
 &lt;tpl <b>for</b>="foo.bar">...&lt;/tpl> // loop through array at foo.bar node
</code></pre>
+ </code></pre>
  * Using the sample data above:
  * <pre><code>
 var tpl = new Ext.XTemplate(
@@ -93,7 +77,7 @@ var tpl = new Ext.XTemplate(
     '&lt;/tpl>&lt;/p>'
 );
 tpl.overwrite(panel.body, data.kids); // pass the kids property of the data object
</code></pre>
+ </code></pre>
  * <p>An example illustrating how the <b><tt>for</tt></b> property can be leveraged
  * to access specified members of the provided data object to populate the template:</p>
  * <pre><code>
@@ -107,7 +91,7 @@ var tpl = new Ext.XTemplate(
     '&lt;/tpl>&lt;/p>'
 );
 tpl.overwrite(panel.body, data);  // pass the root node of the data object
</code></pre>
+ </code></pre>
  * <p>Flat arrays that contain values (and not objects) can be auto-rendered
  * using the special <b><tt>{.}</tt></b> variable inside a loop.  This variable
  * will represent the value of the array at the current index:</p>
@@ -115,11 +99,11 @@ tpl.overwrite(panel.body, data);  // pass the root node of the data object
 var tpl = new Ext.XTemplate(
     '&lt;p>{name}\&#39;s favorite beverages:&lt;/p>',
     '&lt;tpl for="drinks">',
-       '&lt;div> - {.}&lt;/div>',
+        '&lt;div> - {.}&lt;/div>',
     '&lt;/tpl>'
 );
 tpl.overwrite(panel.body, data);
</code></pre>
+ </code></pre>
  * <p>When processing a sub-template, for example while looping through a child array,
  * you can access the parent object's members via the <b><tt>parent</tt></b> object:</p>
  * <pre><code>
@@ -127,14 +111,14 @@ var tpl = new Ext.XTemplate(
     '&lt;p>Name: {name}&lt;/p>',
     '&lt;p>Kids: ',
     '&lt;tpl for="kids">',
-        '&lt;tpl if="age > 1">',
+        '&lt;tpl if="age &amp;gt; 1">',
             '&lt;p>{name}&lt;/p>',
             '&lt;p>Dad: {<b>parent</b>.name}&lt;/p>',
         '&lt;/tpl>',
     '&lt;/tpl>&lt;/p>'
 );
 tpl.overwrite(panel.body, data);
</code></pre>
+ </code></pre>
  * </div>
  * </li>
  *
@@ -155,9 +139,9 @@ tpl.overwrite(panel.body, data);
 &lt;tpl <b>if</b>="id==\'download\'">...&lt;/tpl>
 &lt;tpl <b>if</b>="needsIcon">&lt;img src="{icon}" class="{iconCls}"/>&lt;/tpl>
 // no good:
-&lt;tpl if="name == "Jack"">Hello&lt;/tpl>
+&lt;tpl if="name == "Tommy"">Hello&lt;/tpl>
 // encode &#34; if it is part of the condition, e.g.
-&lt;tpl if="name == &#38;quot;Jack&#38;quot;">Hello&lt;/tpl>
+&lt;tpl if="name == &#38;quot;Tommy&#38;quot;">Hello&lt;/tpl>
  * </code></pre>
  * Using the sample data above:
  * <pre><code>
@@ -165,13 +149,13 @@ var tpl = new Ext.XTemplate(
     '&lt;p>Name: {name}&lt;/p>',
     '&lt;p>Kids: ',
     '&lt;tpl for="kids">',
-        '&lt;tpl if="age > 1">',
+        '&lt;tpl if="age &amp;gt; 1">',
             '&lt;p>{name}&lt;/p>',
         '&lt;/tpl>',
     '&lt;/tpl>&lt;/p>'
 );
 tpl.overwrite(panel.body, data);
</code></pre>
+ </code></pre>
  * </div>
  * </li>
  *
@@ -196,7 +180,7 @@ var tpl = new Ext.XTemplate(
     '&lt;/tpl>&lt;/p>'
 );
 tpl.overwrite(panel.body, data);
-</code></pre>
+ </code></pre>
  * </div>
  * </li>
  *
@@ -213,7 +197,6 @@ tpl.overwrite(panel.body, data);
  * loop you are in (1-based).</li>
  * <li><b><tt>xcount</tt></b>: If you are in a looping template, the total length
  * of the array you are looping.</li>
- * <li><b><tt>fm</tt></b>: An alias for <tt>Ext.util.Format</tt>.</li>
  * </ul>
  * This example demonstrates basic row striping using an inline code block and the
  * <tt>xindex</tt> variable:</p>
@@ -223,13 +206,13 @@ var tpl = new Ext.XTemplate(
     '&lt;p>Company: {[values.company.toUpperCase() + ", " + values.title]}&lt;/p>',
     '&lt;p>Kids: ',
     '&lt;tpl for="kids">',
-       '&lt;div class="{[xindex % 2 === 0 ? "even" : "odd"]}">',
+        '&lt;div class="{[xindex % 2 === 0 ? "even" : "odd"]}">',
         '{name}',
         '&lt;/div>',
     '&lt;/tpl>&lt;/p>'
-);
+ );
 tpl.overwrite(panel.body, data);
</code></pre>
+ </code></pre>
  * </div>
  * </li>
  *
@@ -245,7 +228,7 @@ var tpl = new Ext.XTemplate(
         '&lt;tpl if="this.isGirl(name)">',
             '&lt;p>Girl: {name} - {age}&lt;/p>',
         '&lt;/tpl>',
-        // use opposite if statement to simulate 'else' processing:
+         // use opposite if statement to simulate 'else' processing:
         '&lt;tpl if="this.isGirl(name) == false">',
             '&lt;p>Boy: {name} - {age}&lt;/p>',
         '&lt;/tpl>',
@@ -256,18 +239,17 @@ var tpl = new Ext.XTemplate(
     {
         // XTemplate configuration:
         compiled: true,
-        disableFormats: true,
         // member functions:
         isGirl: function(name){
-            return name == 'Sara Grace';
+           return name == 'Sara Grace';
         },
         isBaby: function(age){
-            return age < 1;
+           return age < 1;
         }
     }
 );
 tpl.overwrite(panel.body, data);
</code></pre>
+ </code></pre>
  * </div>
  * </li>
  *
@@ -275,155 +257,214 @@ tpl.overwrite(panel.body, data);
  *
  * @param {Mixed} config
  */
-Ext.XTemplate = function(){
-    Ext.XTemplate.superclass.constructor.apply(this, arguments);
 
-    var me = this,
-        s = me.html,
-        re = /<tpl\b[^>]*>((?:(?=([^<]+))\2|<(?!tpl\b[^>]*>))*?)<\/tpl>/,
-        nameRe = /^<tpl\b[^>]*?for="(.*?)"/,
-        ifRe = /^<tpl\b[^>]*?if="(.*?)"/,
-        execRe = /^<tpl\b[^>]*?exec="(.*?)"/,
-        m,
-        id = 0,
-        tpls = [],
-        VALUES = 'values',
-        PARENT = 'parent',
-        XINDEX = 'xindex',
-        XCOUNT = 'xcount',
-        RETURN = 'return ',
-        WITHVALUES = 'with(values){ ';
+Ext.define('Ext.XTemplate', {
 
-    s = ['<tpl>', s, '</tpl>'].join('');
+    /* Begin Definitions */
 
-    while((m = s.match(re))){
-        var m2 = m[0].match(nameRe),
-            m3 = m[0].match(ifRe),
-            m4 = m[0].match(execRe),
-            exp = null,
-            fn = null,
-            exec = null,
-            name = m2 && m2[1] ? m2[1] : '';
+    extend: 'Ext.Template',
 
-       if (m3) {
-           exp = m3 && m3[1] ? m3[1] : null;
-           if(exp){
-               fn = new Function(VALUES, PARENT, XINDEX, XCOUNT, WITHVALUES + RETURN +(Ext.util.Format.htmlDecode(exp))+'; }');
-           }
-       }
-       if (m4) {
-           exp = m4 && m4[1] ? m4[1] : null;
-           if(exp){
-               exec = new Function(VALUES, PARENT, XINDEX, XCOUNT, WITHVALUES +(Ext.util.Format.htmlDecode(exp))+'; }');
-           }
-       }
-       if(name){
-           switch(name){
-               case '.': name = new Function(VALUES, PARENT, WITHVALUES + RETURN + VALUES + '; }'); break;
-               case '..': name = new Function(VALUES, PARENT, WITHVALUES + RETURN + PARENT + '; }'); break;
-               default: name = new Function(VALUES, PARENT, WITHVALUES + RETURN + name + '; }');
-           }
-       }
-       tpls.push({
-            id: id,
-            target: name,
-            exec: exec,
-            test: fn,
-            body: m[1]||''
-        });
-       s = s.replace(m[0], '{xtpl'+ id + '}');
-       ++id;
-    }
-    for(var i = tpls.length-1; i >= 0; --i){
-        me.compileTpl(tpls[i]);
-    }
-    me.master = tpls[tpls.length-1];
-    me.tpls = tpls;
-};
-Ext.extend(Ext.XTemplate, Ext.Template, {
-    // private
-    re : /\{([\w-\.\#]+)(?:\:([\w\.]*)(?:\((.*?)?\))?)?(\s?[\+\-\*\\]\s?[\d\.\+\-\*\\\(\)]+)?\}/g,
-    // private
-    codeRe : /\{\[((?:\\\]|.|\n)*?)\]\}/g,
+    statics: {
+        /**
+         * Creates a template from the passed element's value (<i>display:none</i> textarea, preferred) or innerHTML.
+         * @param {String/HTMLElement} el A DOM element or its id
+         * @return {Ext.Template} The created template
+         * @static
+         */
+        from: function(el, config) {
+            el = Ext.getDom(el);
+            return new this(el.value || el.innerHTML, config || {});
+        }
+    },
+
+    /* End Definitions */
+
+    argsRe: /<tpl\b[^>]*>((?:(?=([^<]+))\2|<(?!tpl\b[^>]*>))*?)<\/tpl>/,
+    nameRe: /^<tpl\b[^>]*?for="(.*?)"/,
+    ifRe: /^<tpl\b[^>]*?if="(.*?)"/,
+    execRe: /^<tpl\b[^>]*?exec="(.*?)"/,
+    constructor: function() {
+        this.callParent(arguments);
 
-    // private
-    applySubTemplate : function(id, values, parent, xindex, xcount){
         var me = this,
-            len,
-            t = me.tpls[id],
-            vs,
-            buf = [];
-        if ((t.test && !t.test.call(me, values, parent, xindex, xcount)) ||
-            (t.exec && t.exec.call(me, values, parent, xindex, xcount))) {
-            return '';
-        }
-        vs = t.target ? t.target.call(me, values, parent) : values;
-        len = vs.length;
-        parent = t.target ? values : parent;
-        if(t.target && Ext.isArray(vs)){
-            for(var i = 0, len = vs.length; i < len; i++){
-                buf[buf.length] = t.compiled.call(me, vs[i], parent, i+1, len);
+            html = me.html,
+            argsRe = me.argsRe,
+            nameRe = me.nameRe,
+            ifRe = me.ifRe,
+            execRe = me.execRe,
+            id = 0,
+            tpls = [],
+            VALUES = 'values',
+            PARENT = 'parent',
+            XINDEX = 'xindex',
+            XCOUNT = 'xcount',
+            RETURN = 'return ',
+            WITHVALUES = 'with(values){ ',
+            m, matchName, matchIf, matchExec, exp, fn, exec, name, i;
+
+        html = ['<tpl>', html, '</tpl>'].join('');
+
+        while ((m = html.match(argsRe))) {
+            exp = null;
+            fn = null;
+            exec = null;
+            matchName = m[0].match(nameRe);
+            matchIf = m[0].match(ifRe);
+            matchExec = m[0].match(execRe);
+
+            exp = matchIf ? matchIf[1] : null;
+            if (exp) {
+                fn = Ext.functionFactory(VALUES, PARENT, XINDEX, XCOUNT, WITHVALUES + 'try{' + RETURN + Ext.String.htmlDecode(exp) + ';}catch(e){return;}}');
+            }
+
+            exp = matchExec ? matchExec[1] : null;
+            if (exp) {
+                exec = Ext.functionFactory(VALUES, PARENT, XINDEX, XCOUNT, WITHVALUES + Ext.String.htmlDecode(exp) + ';}');
             }
-            return buf.join('');
+
+            name = matchName ? matchName[1] : null;
+            if (name) {
+                if (name === '.') {
+                    name = VALUES;
+                } else if (name === '..') {
+                    name = PARENT;
+                }
+                name = Ext.functionFactory(VALUES, PARENT, 'try{' + WITHVALUES + RETURN + name + ';}}catch(e){return;}');
+            }
+
+            tpls.push({
+                id: id,
+                target: name,
+                exec: exec,
+                test: fn,
+                body: m[1] || ''
+            });
+
+            html = html.replace(m[0], '{xtpl' + id + '}');
+            id = id + 1;
+        }
+
+        for (i = tpls.length - 1; i >= 0; --i) {
+            me.compileTpl(tpls[i]);
         }
-        return t.compiled.call(me, vs, parent, xindex, xcount);
+        me.master = tpls[tpls.length - 1];
+        me.tpls = tpls;
     },
 
-    // private
-    compileTpl : function(tpl){
+    // @private
+    applySubTemplate: function(id, values, parent, xindex, xcount) {
+        var me = this, t = me.tpls[id];
+        return t.compiled.call(me, values, parent, xindex, xcount);
+    },
+    /**
+     * @cfg {RegExp} codeRe The regular expression used to match code variables (default: matches <tt>{[expression]}</tt>).
+     */
+    codeRe: /\{\[((?:\\\]|.|\n)*?)\]\}/g,
+
+    re: /\{([\w-\.\#]+)(?:\:([\w\.]*)(?:\((.*?)?\))?)?(\s?[\+\-\*\/]\s?[\d\.\+\-\*\/\(\)]+)?\}/g,
+
+    // @private
+    compileTpl: function(tpl) {
         var fm = Ext.util.Format,
-            useF = this.disableFormats !== true,
-            sep = Ext.isGecko ? "+" : ",",
-            body;
+            me = this,
+            useFormat = me.disableFormats !== true,
+            body, bodyReturn, evaluatedFn;
 
-        function fn(m, name, format, args, math){
-            if(name.substr(0, 4) == 'xtpl'){
-                return "'"+ sep +'this.applySubTemplate('+name.substr(4)+', values, parent, xindex, xcount)'+sep+"'";
-            }
+        function fn(m, name, format, args, math) {
             var v;
-            if(name === '.'){
-                v = 'values';
-            }else if(name === '#'){
+            // name is what is inside the {}
+            // Name begins with xtpl, use a Sub Template
+            if (name.substr(0, 4) == 'xtpl') {
+                return "',this.applySubTemplate(" + name.substr(4) + ", values, parent, xindex, xcount),'";
+            }
+            // name = "." - Just use the values object.
+            if (name == '.') {
+                // filter to not include arrays/objects/nulls
+                v = 'Ext.Array.indexOf(["string", "number", "boolean"], typeof values) > -1 || Ext.isDate(values) ? values : ""';
+            }
+
+            // name = "#" - Use the xindex
+            else if (name == '#') {
                 v = 'xindex';
-            }else if(name.indexOf('.') != -1){
+            }
+            else if (name.substr(0, 7) == "parent.") {
                 v = name;
-            }else{
+            }
+            // name has a . in it - Use object literal notation, starting from values
+            else if (name.indexOf('.') != -1) {
+                v = "values." + name;
+            }
+
+            // name is a property of values
+            else {
                 v = "values['" + name + "']";
             }
-            if(math){
+            if (math) {
                 v = '(' + v + math + ')';
             }
-            if (format && useF) {
+            if (format && useFormat) {
                 args = args ? ',' + args : "";
-                if(format.substr(0, 5) != "this."){
+                if (format.substr(0, 5) != "this.") {
                     format = "fm." + format + '(';
-                }else{
-                    format = 'this.call("'+ format.substr(5) + '", ';
-                    args = ", values";
                 }
-            } else {
-                args= ''; format = "("+v+" === undefined ? '' : ";
+                else {
+                    format = 'this.' + format.substr(5) + '(';
+                }
             }
-            return "'"+ sep + format + v + args + ")"+sep+"'";
+            else {
+                args = '';
+                format = "(" + v + " === undefined ? '' : ";
+            }
+            return "'," + format + v + args + "),'";
         }
 
-        function codeFn(m, code){
+        function codeFn(m, code) {
             // Single quotes get escaped when the template is compiled, however we want to undo this when running code.
-            return "'" + sep + '(' + code.replace(/\\'/g, "'") + ')' + sep + "'";
+            return "',(" + code.replace(me.compileARe, "'") + "),'";
         }
 
-        // branched to use + in gecko and [].join() in others
-        if(Ext.isGecko){
-            body = "tpl.compiled = function(values, parent, xindex, xcount){ return '" +
-                   tpl.body.replace(/(\r\n|\n)/g, '\\n').replace(/'/g, "\\'").replace(this.re, fn).replace(this.codeRe, codeFn) +
-                    "';};";
-        }else{
-            body = ["tpl.compiled = function(values, parent, xindex, xcount){ return ['"];
-            body.push(tpl.body.replace(/(\r\n|\n)/g, '\\n').replace(/'/g, "\\'").replace(this.re, fn).replace(this.codeRe, codeFn));
-            body.push("'].join('');};");
-            body = body.join('');
-        }
+        bodyReturn = tpl.body.replace(me.compileBRe, '\\n').replace(me.compileCRe, "\\'").replace(me.re, fn).replace(me.codeRe, codeFn);
+        body = "evaluatedFn = function(values, parent, xindex, xcount){return ['" + bodyReturn + "'].join('');};";
         eval(body);
+
+        tpl.compiled = function(values, parent, xindex, xcount) {
+            var vs,
+                length,
+                buffer,
+                i;
+
+            if (tpl.test && !tpl.test.call(me, values, parent, xindex, xcount)) {
+                return '';
+            }
+
+            vs = tpl.target ? tpl.target.call(me, values, parent) : values;
+            if (!vs) {
+               return '';
+            }
+
+            parent = tpl.target ? values : parent;
+            if (tpl.target && Ext.isArray(vs)) {
+                buffer = [];
+                length = vs.length;
+                if (tpl.exec) {
+                    for (i = 0; i < length; i++) {
+                        buffer[buffer.length] = evaluatedFn.call(me, vs[i], parent, i + 1, length);
+                        tpl.exec.call(me, vs[i], parent, i + 1, length);
+                    }
+                } else {
+                    for (i = 0; i < length; i++) {
+                        buffer[buffer.length] = evaluatedFn.call(me, vs[i], parent, i + 1, length);
+                    }
+                }
+                return buffer.join('');
+            }
+
+            if (tpl.exec) {
+                tpl.exec.call(me, vs, parent, xindex, xcount);
+            }
+            return evaluatedFn.call(me, vs, parent, xindex, xcount);
+        };
+
         return this;
     },
 
@@ -432,7 +473,7 @@ Ext.extend(Ext.XTemplate, Ext.Template, {
      * @param {Object} values The template values. Can be an array if your params are numeric (i.e. {0}) or an object (i.e. {foo: 'bar'})
      * @return {String} The HTML fragment
      */
-    applyTemplate : function(values){
+    applyTemplate: function(values) {
         return this.master.compiled.call(this, values, {}, 1, 1);
     },
 
@@ -440,39 +481,17 @@ Ext.extend(Ext.XTemplate, Ext.Template, {
      * Compile the template to a function for optimized performance.  Recommended if the template will be used frequently.
      * @return {Function} The compiled function
      */
-    compile : function(){return this;}
-
-    /**
-     * @property re
-     * @hide
-     */
-    /**
-     * @property disableFormats
-     * @hide
-     */
+    compile: function() {
+        return this;
+    }
+}, function() {
     /**
-     * @method set
-     * @hide
+     * Alias for {@link #applyTemplate}
+     * Returns an HTML fragment of this template with the specified values applied.
+     * @param {Object/Array} values The template values. Can be an array if your params are numeric (i.e. {0}) or an object (i.e. {foo: 'bar'})
+     * @return {String} The HTML fragment
+     * @member Ext.XTemplate
+     * @method apply
      */
-
+    this.createAlias('apply', 'applyTemplate');
 });
-/**
- * Alias for {@link #applyTemplate}
- * Returns an HTML fragment of this template with the specified values applied.
- * @param {Object/Array} values The template values. Can be an array if your params are numeric (i.e. {0}) or an object (i.e. {foo: 'bar'})
- * @return {String} The HTML fragment
- * @member Ext.XTemplate
- * @method apply
- */
-Ext.XTemplate.prototype.apply = Ext.XTemplate.prototype.applyTemplate;
-
-/**
- * Creates a template from the passed element's value (<i>display:none</i> textarea, preferred) or innerHTML.
- * @param {String/HTMLElement} el A DOM element or its id
- * @return {Ext.Template} The created template
- * @static
- */
-Ext.XTemplate.from = function(el){
-    el = Ext.getDom(el);
-    return new Ext.XTemplate(el.value || el.innerHTML);
-};