cca0270535353594fb9f22992d980fd626f90db5
[odoo/odoo.git] / addons / base / static / qweb / qweb.js
1 // vim:set noet fdm=syntax fdl=0 fdc=3 fdn=2:
2 //---------------------------------------------------------
3 // QWeb javascript
4 //---------------------------------------------------------
5
6 /*
7         TODO
8
9                 String parsing
10                         if (window.DOMParser) {
11                                 parser=new DOMParser();
12                                 xmlDoc=parser.parseFromString(text,"text/xml");
13                         } else {
14                                 xmlDoc=new ActiveXObject("Msxml2.DOMDocument.4.0");
15                                 xmlDoc=new ActiveXObject("Microsoft.XMLDOM");
16                                         Which versions to try, it's confusing...
17                                 xmlDoc.async="false";
18                                 xmlDoc.async=false;
19                                 xmlDoc.preserveWhiteSpace=true;
20                                 xmlDoc.load("f.xml");
21                                 xmlDoc.loadXML(text);  ?
22                         }
23
24                 Support space in IE by reparsing the responseText
25                         xmlhttp.responseXML.loadXML(xmlhttp.responseText); ?
26
27                 Preprocess: (nice optimization) 
28                         preprocess by flattening all non t- element to a TEXT_NODE.
29                         count the number of "\n" in text nodes to give an aproximate LINE NUMBER on elements for error reporting
30                         if from IE HTMLDOM use if(a[i].specified) to avoid 88 empty attributes per element during the preprocess, 
31
32                 implement t-trim 'left' 'right' 'both', is it needed ? inner=render_trim(l_inner.join(), t_att)
33
34                 Ruby/python: to backport from javascript to python/ruby render_node to use regexp, factorize foreach %var, t-att test for tuple(attname,value)
35
36         DONE
37                 we reintroduced t-att-id, no more t-esc-id because of the new convention t-att="["id","val"]"
38 */
39
40 var QWeb = {
41     templates:{},
42     prefix:"t",
43     reg:"",
44     tag:{},
45     att:{},
46     eval_object:function(e, v) {
47         // TODO: Currently this will also replace and, or, ... in strings. Try
48         // 'hi boys and girls' != '' and 1 == 1  -- will be replaced to : 'hi boys && girls' != '' && 1 == 1
49         // try to find a solution without tokenizing
50         e = e.replace(/\Wand\W/g, " && ");
51         e = e.replace(/\Wor\W/g, " and ");
52         e = e.replace(/\Wgt\W/g, " > ");
53         e = e.replace(/\Wgte\W/g, " >= ");
54         e = e.replace(/\Wlt\W/g, " < ");
55         e = e.replace(/\Wlte\W/g, " <= ");
56         if (v[e] != undefined) {
57             return v[e];
58         } else {
59             with (v) return eval(e);
60         }
61     },
62     eval_str:function(e, v) {
63         var r = this.eval_object(e, v);
64         r = (typeof(r) == "undefined" || r == null) ? "" : r.toString();
65         return e == "0" ? v["0"] : r;
66     },
67     eval_format:function(e, v) {
68         var i,m,r,src = e.split(/#/);
69         r = src[0];
70         for (i = 1; i < src.length; i++) {
71             if (m = src[i].match(/^{(.*)}(.*)/)) {
72                 r += this.eval_str(m[1], v) + m[2];
73             } else {
74                 r += "#" + src[i];
75             }
76         }
77         return r;
78     },
79     eval_bool:function(e, v) {
80         return this.eval_object(e, v) ? true : false;
81     },
82     trim : function(v, mode) {
83         if (!v || !mode) return v;
84         if (mode == 'both') {
85             return v.replace(/^\s*|\s*$/g, "");
86         } else if (mode == "left") {
87             return v.replace(/^\s*/, "");
88         } else if (mode == "right") {
89             return v.replace(/\s*$/, "");
90         } else {
91             return v;
92         }
93     },
94     escape_text:function(s) {
95         return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
96     },
97     escape_att:function(s) {
98         return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
99     },
100     render_node : function(e, v, inner_trim) {
101         var r = "";
102         if (e.nodeType == 3) {
103             r = inner_trim ? this.trim(e.data, inner_trim) : e.data;
104         } else if (e.nodeType == 1) {
105             var g_att = {};
106             var t_att = {};
107             var t_render = null;
108             var a = e.attributes;
109             for (var i = 0; i < a.length; i++) {
110                 var an = a[i].name,av = a[i].value;
111                 var m,n;
112                 if (m = an.match(this.reg)) {
113                     n = m[1];
114                     if (n == "eval") {
115                         n = m[2].substring(1);
116                         av = this.eval_str(av, v);
117                     }
118                     if (f = this.att[n]) {
119                         this[f](e, t_att, g_att, v, m[2], av);
120                     } else if (f = this.tag[n]) {
121                         t_render = f;
122                     }
123                     t_att[n] = av;
124                 } else {
125                     g_att[an] = av;
126                 }
127             }
128             if (inner_trim && !t_att["trim"]) {
129                 t_att["trim"] = "inner " + inner_trim;
130             }
131             if (t_render) {
132                 r = this[t_render](e, t_att, g_att, v);
133             } else {
134                 r = this.render_element(e, t_att, g_att, v);
135             }
136         }
137         return r;
138     },
139     render_element:function(e, t_att, g_att, v) {
140         var inner = "", ec = e.childNodes, trim = t_att["trim"], inner_trim;
141         if (trim) {
142             if (trim.match(/(^|\W)(inner)($|\W)/)) {
143                 inner_trim = true;
144                 trim = "both";
145             }
146             var tm = trim.match(/(^|\W)(both|left|right)($|\W)/);
147             if (tm) trim = tm[2];
148         }
149         for (var i = 0; i < ec.length; i++) {
150             inner += inner_trim ? this.trim(this.render_node(ec[i], v, inner_trim ? trim : null), trim) : this.render_node(ec[i], v, inner_trim ? trim : null);
151         }
152         if (trim && !inner_trim) {
153             inner = this.trim(inner, trim);
154         }
155         if (e.tagName == this.prefix) {
156             return inner;
157         } else {
158             var att = "";
159             for (var an in g_att) {
160                 av = g_att[an];
161                 att += " " + an + '="' + this.escape_att(av) + '"';
162             }
163             return inner.length ? "<" + e.tagName + att + ">" + inner + "</" + e.tagName + ">" : "<" + e.tagName + att + "/>";
164         }
165     },
166     render_att_att:function(e, t_att, g_att, v, ext, av) {
167         if (ext) {
168             g_att[ext.substring(1)] = this.eval_str(av, v);
169         } else {
170             o = this.eval_object(av, v);
171             g_att[o[0]] = o[1];
172         }
173     },
174     render_att_attf:function(e, t_att, g_att, v, ext, av) {
175         g_att[ext.substring(1)] = this.eval_format(av, v);
176     },
177     render_tag_raw:function(e, t_att, g_att, v) {
178         return this.eval_str(t_att["raw"], v);
179     },
180     render_tag_rawf:function(e, t_att, g_att, v) {
181         return this.eval_format(t_att["raw"], v);
182     },
183     render_tag_esc:function(e, t_att, g_att, v) {
184         return this.escape_text(this.eval_str(t_att["esc"], v));
185     },
186     render_tag_escf:function(e, t_att, g_att, v) {
187         return this.escape_text(this.eval_format(t_att["esc"], v));
188     },
189     render_tag_if:function(e, t_att, g_att, v) {
190         return this.eval_bool(t_att["if"], v) ? this.render_element(e, t_att, g_att, v) : "";
191     },
192     render_tag_set:function(e, t_att, g_att, v) {
193         var ev = t_att["value"];
194         if (ev && ev.constructor != Function) {
195             v[t_att["set"]] = this.eval_object(ev, v);
196         } else {
197             v[t_att["set"]] = this.render_element(e, t_att, g_att, v);
198         }
199         return "";
200     },
201     render_tag_call:function(e, t_att, g_att, v) {
202         var d = v;
203         if (!t_att["import"]) {
204             d = {};
205             for (var i in v) {
206                 d[i] = v[i];
207             }
208         }
209         d["0"] = this.render_element(e, t_att, g_att, d);
210         return this.render(t_att["call"], d);
211     },
212     render_tag_js:function(e, t_att, g_att, v) {
213         var r = this.eval_str(this.render_element(e, t_att, g_att, v), v);
214         return t_att["js"] != "quiet" ? r : "";
215     },
216     render_tag_foreach:function(e, t_att, g_att, v) {
217         var expr = t_att["foreach"];
218         var enu = this.eval_object(expr, v);
219         var ru = [];
220         if (enu) {
221             var val = t_att['as'] || expr.replace(/[^a-zA-Z0-9]/g, '_');
222             d = {};
223             for (var i in v) {
224                 d[i] = v[i];
225             }
226             d[val + "_all"] = enu;
227             val_value = val + "_value";
228             val_index = val + "_index";
229             val_first = val + "_first";
230             val_last = val + "_last";
231             val_parity = val + "_parity";
232             var size = enu.length;
233             if (size) {
234                 d[val + "_size"] = size;
235                 for (var i = 0; i < size; i++) {
236                     var cur = enu[i];
237                     d[val_value] = cur;
238                     d[val_index] = i;
239                     d[val_first] = i == 0;
240                     d[val_last] = i + 1 == size;
241                     d[val_parity] = (i % 2 == 1 ? 'odd' : 'even');
242                     if (cur.constructor == Object) {
243                         for (var j in cur) {
244                             d[j] = cur[j];
245                         }
246                     }
247                     d[val] = cur;
248                     var r = this.render_element(e, t_att, g_att, d);
249                     ru.push(r);
250                 }
251             } else {
252                 index = 0;
253                 for (cur in enu) {
254                     d[val_value] = cur;
255                     d[val_index] = index;
256                     d[val_first] = index == 0;
257                     d[val_parity] = (index % 2 == 1 ? 'odd' : 'even');
258                     d[val] = cur;
259                     ru.push(this.render_element(e, t_att, g_att, d));
260                     index += 1;
261                 }
262             }
263             return ru.join("");
264         } else {
265             return "qweb: foreach " + expr + " not found.";
266         }
267     },
268     hash:function() {
269         var l = [];
270         for (var i in this) {
271             if (m = i.match(/render_tag_(.*)/)) {
272                 this.tag[m[1]] = i;
273                 l.push(m[1]);
274             } else if (m = i.match(/render_att_(.*)/)) {
275                 this.att[m[1]] = i;
276                 l.push(m[1]);
277             }
278         }
279         l.sort(function(a, b) {
280             return a.length > b.length ? -1 : 1;
281         });
282         var s = "^" + this.prefix + "-(eval|" + l.join("|") + "|.*)(.*)$";
283         this.reg = new RegExp(s);
284     },
285     load_xml:function(s) {
286         var xml;
287         if (s[0] == "<") {
288             /*
289              manque ca pour sarrisa
290              if(window.DOMParser){
291              mozilla
292              if(!window.DOMParser){
293              var doc = Sarissa.getDomDocument();
294              doc.loadXML(sXml);
295              return doc;
296              };
297              };
298              */
299         } else {
300             var w = window,r = w.XMLHttpRequest,j;
301             if (r)r = new r(); else for (j in{"Msxml2":1,"Microsoft":1})try {
302                 r = new ActiveXObject(j + ".XMLHTTP");
303                 break;
304             } catch(e) {
305             }
306             if (r) {
307                 r.open("GET", s, false);
308                 r.send(null);
309                 //if ie r.setRequestHeader("If-Modified-Since", "Sat, 1 Jan 2000 00:00:00 GMT");
310                 xml = r.responseXML;
311                 /*
312                  TODO
313                  if intsernetexploror
314                  getdomimplmentation() for try catch
315                  responseXML.getImplet
316                  d=domimple()
317                  d.preserverWhitespace=1
318                  d.loadXML()
319
320                  xml.preserverWhitespace=1
321                  xml.loadXML(r.reponseText)
322                  */
323                 return xml;
324             }
325         }
326     },
327     add_template:function(e) {
328         // TODO: keep sources so we can implement reload()
329         this.hash();
330         if (e.constructor == String) {
331             e = this.load_xml(e);
332         }
333         var ec = [];
334         if (e.documentElement) {
335             ec = e.documentElement.childNodes;
336         } else if (e.childNodes) {
337             ec = e.childNodes;
338         }
339         for (var i = 0; i < ec.length; i++) {
340             var n = ec[i];
341             if (n.nodeType == 1) {
342                 var name = n.getAttribute(this.prefix + "-name");
343                 this.templates[name] = n;
344             }
345         }
346     },
347     render:function(name, v) {
348         if (e = this.templates[name]) {
349             return this.render_node(e, v);
350         } else {
351             return "template " + name + " not found";
352         }
353     },
354     dump:function(o) {
355         var r = "";
356         if (typeof(o) == "object") {
357             for (var i in o) {
358                 r += i + " : " + this.dump(s) + "\n";
359             }
360             r = s + "{\n" + r + "}\n";
361         } else {
362             r = s + "";
363         }
364         return r;
365     },
366     debug:function(s) {
367         var r = this.dump(s);
368         $("#debug")[0].append(this.escape_text(r) + "<br/>\n");
369     }
370 };
371