summaryrefslogtreecommitdiff
path: root/addons/web/static/lib/qweb/qweb.js
diff options
context:
space:
mode:
authorstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
committerstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
commit3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch)
treea44932296ef4a9b71d5f010906253d8c53727726 /addons/web/static/lib/qweb/qweb.js
parent0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff)
initial commit 2
Diffstat (limited to 'addons/web/static/lib/qweb/qweb.js')
-rw-r--r--addons/web/static/lib/qweb/qweb.js435
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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
+ },
+ escape_att:function(s) {
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
+ },
+ 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";
+ }
+};
+