diff options
| author | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
|---|---|---|
| committer | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
| commit | 3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch) | |
| tree | a44932296ef4a9b71d5f010906253d8c53727726 /addons/web/static/lib/qweb/qweb.js | |
| parent | 0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff) | |
initial commit 2
Diffstat (limited to 'addons/web/static/lib/qweb/qweb.js')
| -rw-r--r-- | addons/web/static/lib/qweb/qweb.js | 435 |
1 files changed, 435 insertions, 0 deletions
diff --git a/addons/web/static/lib/qweb/qweb.js b/addons/web/static/lib/qweb/qweb.js new file mode 100644 index 00000000..cd652485 --- /dev/null +++ b/addons/web/static/lib/qweb/qweb.js @@ -0,0 +1,435 @@ +/* +Copyright (c) 2013, Fabien Meghazi + +Released under the MIT license + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the +Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +//--------------------------------------------------------- +// QWeb javascript +//--------------------------------------------------------- + +/* + TODO + + String parsing + if (window.DOMParser) { + parser=new DOMParser(); + xmlDoc=parser.parseFromString(text,"text/xml"); + } else { + xmlDoc=new ActiveXObject("Msxml2.DOMDocument.4.0"); + xmlDoc=new ActiveXObject("Microsoft.XMLDOM"); + Which versions to try, it's confusing... + xmlDoc.async="false"; + xmlDoc.async=false; + xmlDoc.preserveWhiteSpace=true; + xmlDoc.load("f.xml"); + xmlDoc.loadXML(text); ? + } + + Support space in IE by reparsing the responseText + xmlhttp.responseXML.loadXML(xmlhttp.responseText); ? + + Preprocess: (nice optimization) + preprocess by flattening all non t- element to a TEXT_NODE. + count the number of "\n" in text nodes to give an aproximate LINE NUMBER on elements for error reporting + if from IE HTMLDOM use if(a[i].specified) to avoid 88 empty attributes per element during the preprocess, + + implement t-trim 'left' 'right' 'both', is it needed ? inner=render_trim(l_inner.join(), t_att) + + Ruby/python: to backport from javascript to python/ruby render_node to use regexp, factorize foreach %var, t-att test for tuple(attname,value) + + DONE + we reintroduced t-att-id, no more t-esc-id because of the new convention t-att="["id","val"]" +*/ + +var QWeb = { + templates:{}, + prefix:"t", + reg:new RegExp(), + tag:{}, + att:{}, + ValueException: function (value, message) { + this.value = value; + this.message = message; + }, + eval_object:function(e, v) { + // TODO: Currently this will also replace and, or, ... in strings. Try + // 'hi boys and girls' != '' and 1 == 1 -- will be replaced to : 'hi boys && girls' != '' && 1 == 1 + // try to find a solution without tokenizing + e = '(' + e + ')'; + e = e.replace(/\band\b/g, " && "); + e = e.replace(/\bor\b/g, " || "); + e = e.replace(/\bgt\b/g, " > "); + e = e.replace(/\bgte\b/g, " >= "); + e = e.replace(/\blt\b/g, " < "); + e = e.replace(/\blte\b/g, " <= "); + if (v[e] != undefined) { + return v[e]; + } else { + with (v) return eval(e); + } + }, + eval_str:function(e, v) { + var r = this.eval_object(e, v); + r = (typeof(r) == "undefined" || r == null) ? "" : r.toString(); + return e == "0" ? v["0"] : r; + }, + eval_format:function(e, v) { + var m, src = e.split(/#/), r = src[0]; + for (var i = 1; i < src.length; i++) { + if (m = src[i].match(/^{(.*)}(.*)/)) { + r += this.eval_str(m[1], v) + m[2]; + } else { + r += "#" + src[i]; + } + } + return r; + }, + eval_bool:function(e, v) { + return !!this.eval_object(e, v); + }, + trim : function(v, mode) { + if (!v || !mode) return v; + switch (mode) { + case 'both': + return v.replace(/^\s*|\s*$/g, ""); + case "left": + return v.replace(/^\s*/, ""); + case "right": + return v.replace(/\s*$/, ""); + } + throw new QWeb.ValueException( + mode, "unknown trimming mode, trim mode must follow the pattern '[inner] (left|right|both)'"); + }, + escape_text:function(s) { + return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">"); + }, + escape_att:function(s) { + return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """); + }, + render_node : function(e, v, inner_trim) { + if (e.nodeType == 3) { + return inner_trim ? this.trim(e.data, inner_trim) : e.data; + } + if (e.nodeType == 1) { + var g_att = {}; + var t_att = {}; + var t_render = null; + var a = e.attributes; + for (var i = 0; i < a.length; i++) { + var an = a[i].name,av = a[i].value; + var m; + if (m = an.match(this.reg)) { + var n = m[1]; + if (n == "eval") { + n = m[2].substring(1); + av = this.eval_str(av, v); + } + var f; + if (f = this.att[n]) { + this[f](e, t_att, g_att, v, m[2], av); + } else if (f = this.tag[n]) { + t_render = f; + } + t_att[n] = av; + } else { + g_att[an] = av; + } + } + if (inner_trim && !t_att["trim"]) { + t_att["trim"] = "inner " + inner_trim; + } + if (t_render) { + return this[t_render](e, t_att, g_att, v); + } + return this.render_element(e, t_att, g_att, v); + } + return ""; + }, + render_element:function(e, t_att, g_att, v) { + var inner = "", ec = e.childNodes, trim = t_att["trim"], inner_trim; + if (trim) { + if (/\binner\b/.test(trim)) { + inner_trim = true; + if (trim == 'inner') { + trim = "both"; + } + } + var tm = /\b(both|left|right)\b/.exec(trim); + if (tm) trim = tm[1]; + } + for (var i = 0; i < ec.length; i++) { + 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); + } + if (trim && !inner_trim) { + inner = this.trim(inner, trim); + } + if (e.tagName == this.prefix) { + return inner; + } + var att = ""; + for (var an in g_att) { + att += " " + an + '="' + this.escape_att(g_att[an]) + '"'; + } + // Some IE versions have problems with closed tags + var opentag = !!t_att['opentag'] && this.eval_bool(t_att["opentag"], v); + return inner.length || opentag ? "<" + e.tagName + att + ">" + inner + "</" + e.tagName + ">" : "<" + e.tagName + att + "/>"; + }, + render_att_att:function(e, t_att, g_att, v, ext, av) { + if (ext) { + var attv = this.eval_object(av, v); + if (attv != null) { + g_att[ext.substring(1)] = attv.toString(); + } + } else { + var o = this.eval_object(av, v); + if (o != null) { + // TODO: http://bonsaiden.github.com/JavaScript-Garden/#types.typeof + if (o.constructor == Array && o.length > 1 && o[1] != null) { + g_att[o[0]] = new String(o[1]); + } else if (o.constructor == Object) { + for (var i in o) { + if(o[i]!=null) { + g_att[i] = new String(o[i]); + } + } + } + } + } + }, + render_att_attf:function(e, t_att, g_att, v, ext, av) { + g_att[ext.substring(1)] = this.eval_format(av, v); + }, + render_tag_raw:function(e, t_att, g_att, v) { + return this.eval_str(t_att["raw"], v); + }, + render_tag_rawf:function(e, t_att, g_att, v) { + return this.eval_format(t_att["rawf"], v); + }, + /* + * Idea: if the name of the tag != t render the tag around the value <a name="a" t-esc="label"/> + */ + render_tag_esc:function(e, t_att, g_att, v) { + return this.escape_text(this.eval_str(t_att["esc"], v)); + }, + render_tag_escf:function(e, t_att, g_att, v) { + return this.escape_text(this.eval_format(t_att["escf"], v)); + }, + render_tag_if:function(e, t_att, g_att, v) { + return this.eval_bool(t_att["if"], v) ? this.render_element(e, t_att, g_att, v) : ""; + }, + render_tag_set:function(e, t_att, g_att, v) { + var ev = t_att["value"]; + if (ev && ev.constructor != Function) { + v[t_att["set"]] = this.eval_object(ev, v); + } else { + v[t_att["set"]] = this.render_element(e, t_att, g_att, v); + } + return ""; + }, + render_tag_call:function(e, t_att, g_att, v) { + var d = v; + if (!t_att["import"]) { + d = {}; + for (var i in v) { + d[i] = v[i]; + } + } + d["0"] = this.render_element(e, t_att, g_att, d); + return this.render(t_att["call"], d); + }, + render_tag_js:function(e, t_att, g_att, v) { + var dict_name = t_att["js"] || "dict"; + v[dict_name] = v; + var r = this.eval_str(this.render_element(e, t_att, g_att, v), v); + delete(v[dict_name]); + return r || ''; + }, + /** + * Renders a foreach loop (@t-foreach). + * + * Adds the following elements to its context, where <code>${name}</code> + * is specified via <code>@t-as</code>: + * * <code>${name}</code> The current element itself + * * <code>${name}_value</code> Same as <code>${name}</code> + * * <code>${name}_index</code> The 0-based index of the current element + * * <code>${name}_first</code> Whether the current element is the first one + * * <code>${name}_parity</code> odd|even (as strings) + * * <code>${name}_all</code> The iterated collection itself + * + * If the collection being iterated is an array, also adds: + * * <code>${name}_last</code> Whether the current element is the last one + * * All members of the current object + * + * If the collection being iterated is an object, the value is actually the object's key + * + * @param e ? + * @param t_att attributes of the element being <code>t-foreach</code>'d + * @param g_att ? + * @param old_context the context in which the foreach is evaluated + */ + render_tag_foreach:function(e, t_att, g_att, old_context) { + var expr = t_att["foreach"]; + var enu = this.eval_object(expr, old_context); + var ru = []; + if (enu) { + var val = t_att['as'] || expr.replace(/[^a-zA-Z0-9]/g, '_'); + var context = {}; + for (var i in old_context) { + context[i] = old_context[i]; + } + context[val + "_all"] = enu; + var val_value = val + "_value", + val_index = val + "_index", + val_first = val + "_first", + val_last = val + "_last", + val_parity = val + "_parity"; + var size = enu.length; + if (size) { + context[val + "_size"] = size; + for (var j = 0; j < size; j++) { + var cur = enu[j]; + context[val_value] = cur; + context[val_index] = j; + context[val_first] = j == 0; + context[val_last] = j + 1 == size; + context[val_parity] = (j % 2 == 1 ? 'odd' : 'even'); + if (cur.constructor == Object) { + for (var k in cur) { + context[k] = cur[k]; + } + } + context[val] = cur; + var r = this.render_element(e, t_att, g_att, context); + ru.push(r); + } + } else { + var index = 0; + for (cur in enu) { + context[val_value] = cur; + context[val_index] = index; + context[val_first] = index == 0; + context[val_parity] = (index % 2 == 1 ? 'odd' : 'even'); + context[val] = cur; + ru.push(this.render_element(e, t_att, g_att, context)); + index += 1; + } + } + return ru.join(""); + } else { + return "qweb: foreach " + expr + " not found."; + } + }, + hash:function() { + var l = [], m; + for (var i in this) { + if (m = i.match(/render_tag_(.*)/)) { + this.tag[m[1]] = i; + l.push(m[1]); + } else if (m = i.match(/render_att_(.*)/)) { + this.att[m[1]] = i; + l.push(m[1]); + } + } + l.sort(function(a, b) { + return a.length > b.length ? -1 : 1; + }); + var s = "^" + this.prefix + "-(eval|" + l.join("|") + "|.*)(.*)$"; + this.reg = new RegExp(s); + }, + /** + * returns the correct XMLHttpRequest instance for the browser, or null if + * it was not able to build any XHR instance. + * + * @returns XMLHttpRequest|MSXML2.XMLHTTP.3.0|null + */ + get_xhr:function () { + if (window.XMLHttpRequest) { + return new window.XMLHttpRequest(); + } + try { + return new ActiveXObject('MSXML2.XMLHTTP.3.0'); + } catch(e) { + return null; + } + }, + load_xml:function(s) { + var xml; + if (s[0] == "<") { + /* + manque ca pour sarrisa + if(window.DOMParser){ + mozilla + if(!window.DOMParser){ + var doc = Sarissa.getDomDocument(); + doc.loadXML(sXml); + return doc; + }; + }; + */ + } else { + var req = this.get_xhr(); + if (req) { + req.open("GET", s, false); + req.send(null); + //if ie r.setRequestHeader("If-Modified-Since", "Sat, 1 Jan 2000 00:00:00 GMT"); + xml = req.responseXML; + /* + TODO + if intsernetexploror + getdomimplmentation() for try catch + responseXML.getImplet + d=domimple() + d.preserverWhitespace=1 + d.loadXML() + + xml.preserverWhitespace=1 + xml.loadXML(r.reponseText) + */ + return xml; + } + } + }, + add_template:function(e) { + // TODO: keep sources so we can implement reload() + this.hash(); + if (e.constructor == String) { + e = this.load_xml(e); + } + + var ec = e.documentElement ? e.documentElement.childNodes : ( e.childNodes ? e.childNodes : [] ); + + for (var i = 0; i < ec.length; i++) { + var n = ec[i]; + if (n.nodeType == 1) { + var name = n.getAttribute(this.prefix + "-name"); + this.templates[name] = n; + } + } + }, + render:function(name, v) { + var e; + if (e = this.templates[name]) { + return this.render_node(e, v); + } + return "template " + name + " not found"; + } +}; + |
