From 706255d2fc49f16cb822c052e61e290614424c67 Mon Sep 17 00:00:00 2001 From: Fabien Meghazi Date: Wed, 4 May 2011 11:29:26 +0200 Subject: [PATCH] [ADD] Added QWeb2 (WIP) bzr revid: fme@openerp.com-20110504092926-eh18f9z6l0bh59i9 --- addons/base/static/lib/qweb/qweb2.js | 566 +++++++++++++++++++++++++++ 1 file changed, 566 insertions(+) create mode 100644 addons/base/static/lib/qweb/qweb2.js diff --git a/addons/base/static/lib/qweb/qweb2.js b/addons/base/static/lib/qweb/qweb2.js new file mode 100644 index 00000000000..4c12b6cc046 --- /dev/null +++ b/addons/base/static/lib/qweb/qweb2.js @@ -0,0 +1,566 @@ +// TODO: trim support +var QWeb2 = { + expressions_cache: {}, + reserved_words: 'true,false,NaN,null,undefined,debugger,in,instanceof,new,function,return,this,typeof,eval,Math,RegExp,Array,Object'.split(','), + actions_precedence: 'if,foreach,call,set,esc,escf,raw,rawf,js,debug,log'.split(','), + word_replacement: { + 'and': '&&', + 'or': '||', + 'gt': '>', + 'gte': '>=', + 'lt': '<', + 'lte': '<=' + }, + tools: { + exception: function(message, context) { + context = context || {}; + var prefix = 'QWeb2'; + if (context.template) { + prefix += " - template['" + context.template + "']"; + } + throw prefix + ": " + message; + }, + js_escape: function(s) { + return "'" + s.replace(/\r?\n/g, "\\n").replace(/'/g, "\\'") + "'"; + }, + html_escape: function(s, attribute) { + if (s === null || s === undefined) { + return ''; + } + s = (new String(s)).replace(/&/g, '&').replace(//g, '>'); + if (attribute) { + s = s.replace(/"/g, '"'); + } + return s; + }, + gen_attribute: function(o) { + if (o !== null && o !== undefined) { + if (o.constructor === Array) { + if (o[1] !== null && o[1] !== undefined) { + return ' ' + o[0] + '="' + this.html_escape(o[1]) + '"'; + } + } else if (typeof o === 'object') { + var r = ''; + for (var k in o) { + if (o.hasOwnProperty(k)) { + r += this.gen_attribute([k, o[k]]); + } + } + return r; + } + } + return ''; + }, + extend: function(dst, src, exclude) { + for (var p in src) { + if (src.hasOwnProperty(p) && !(exclude && this.arrayIndexOf(exclude, p))) { + dst[p] = src[p]; + } + } + return dst + }, + arrayIndexOf : function(array, item) { + for (var i = 0, ilen = array.length; i < ilen; i++) { + if (array[i] === item) { + return i; + } + } + return -1; + }, + call: function(context, template, old_dict, _import, callback) { + var new_dict = this.extend({}, old_dict); + new_dict['__caller__'] = old_dict['__template__']; + if (callback) { + new_dict[0] = callback(context, new_dict); + } + var r = context.engine._render(template, new_dict); + if (_import) { + if (_import === '*') { + this.extend(old_dict, new_dict, ['__caller__', '__template__']); + } else { + _import = _import.split(','); + for (var i = 0, ilen = _import.length; i < ilen; i++) { + var v = _import[i]; + old_dict[v] = new_dict[v]; + } + } + } + return r; + }, + foreach: function(context, enu, as, old_dict, callback) { + if (enu != null) { + var size, new_dict = this.extend({}, old_dict); + new_dict[as + "_all"] = enu; + var as_value = as + "_value", + as_index = as + "_index", + as_first = as + "_first", + as_last = as + "_last", + as_parity = as + "_parity"; + if (size = enu.length) { + new_dict[as + "_size"] = size; + for (var j = 0, jlen = enu.length; j < jlen; j++) { + var cur = enu[j]; + new_dict[as_value] = cur; + new_dict[as_index] = j; + new_dict[as_first] = j === 0; + new_dict[as_last] = j + 1 === size; + new_dict[as_parity] = (j % 2 == 1 ? 'odd' : 'even'); + if (cur.constructor === Object) { + this.extend(new_dict, cur); + } + new_dict[as] = cur; + callback(context, new_dict); + } + } else if (enu.constructor == Number) { + var _enu = []; + for (var i = 0; i < enu; i++) { + _enu.push(i); + } + this.foreach(content, enu, as, old_dict, callback); + } else { + var index = 0; + for (var k in enu) { + if (enu.hasOwnProperty(k)) { + var v = enu[k]; + new_dict[as_value] = v; + new_dict[as_index] = index; + new_dict[as_first] = index === 0; + new_dict[as_parity] = (j % 2 == 1 ? 'odd' : 'even'); + new_dict[as] = v; + callback(context, new_dict); + index += 1 + } + } + } + } else { + this.exception("No enumerator given to foreach", context); + } + } + } +}; + +QWeb2.Engine = (function() { + function Engine() { + // TODO: handle prefix at template level : t-prefix="x" + this.prefix = 't'; + this.templates_resources = []; // TODO: implement this.reload() + this.templates = {}; + this.compiled_templates = {}; + this.dict = {}; + this.tools = QWeb2.tools; + for (var i = 0; i < arguments.length; i++) { + this.add_template(arguments[i]); + } + } + + QWeb2.tools.extend(Engine.prototype, { + add_template : function(template) { + this.templates_resources.push(template); + if (template.constructor === String) { + template = this.load_xml(template); + } + var ec = (template.documentElement && template.documentElement.childNodes) || template.childNodes || []; + for (var i = 0; i < ec.length; i++) { + var node = ec[i]; + if (node.nodeType === 1) { + if (node.nodeName == 'parsererror') { + return this.tools.exception(node.innerText); + } + var name = node.getAttribute(this.prefix + '-name'); + if (name) { + this.templates[name] = node; + } + } + } + return true; + }, + load_xml : function(s) { + s = s.replace(/^\s*|\s*$/g, ''); + if (s[0] === '"); + this.bottom_string(""); + } else { + this.top_string("/>"); + } + } + }, + compile_action_if : function(value) { + this.top("if (" + (this.format_expression(value)) + ") {"); + this.bottom("}"); + this.indent(); + }, + compile_action_foreach : function(value) { + var as = this.actions['as'] || value.replace(/[^a-zA-Z0-9]/g, '_'); + //TODO: exception if t-as not valid + this.top("context.engine.tools.foreach(context, " + (this.format_expression(value)) + ", " + (this.engine.tools.js_escape(as)) + ", dict, function(context, dict) {"); + this.bottom("});"); + this.indent(); + }, + compile_action_call : function(value) { + var _import = this.actions['import'] || '' + if (this.children.length === 0) { + return this.top("r.push(context.engine.tools.call(context, " + (this.engine.tools.js_escape(value)) + ", dict, " + (this.engine.tools.js_escape(_import)) + "));"); + } else { + this.top("r.push(context.engine.tools.call(context, " + (this.engine.tools.js_escape(value)) + ", dict, " + (this.engine.tools.js_escape(_import)) + ", function(context, dict) {"); + this.bottom("}));"); + this.indent(); + this.top("var r = [];"); + return this.bottom("return r.join('');"); + } + }, + compile_action_set : function(value) { + if (this.actions['value']) { + this.top("dict['" + value + "'] = (" + (this.format_expression(this.actions['value'])) + ");"); + this.process_children = false; + } else { + if (this.children.length === 0) { + this.top("dict['" + value + "'] = '';"); + } else if (this.children.length === 1 && this.children[0].node.nodeType === 3) { + this.top("dict['" + value + "'] = " + (this.engine.tools.js_escape(this.children[0].node.data)) + ";"); + this.process_children = false; + } else { + this.top("dict['" + value + "'] = (function(dict) {"); + this.bottom("})(dict);"); + this.indent(); + this.top("var r = [];"); + this.bottom("return r.join('');"); + } + } + }, + compile_action_esc : function(value) { + this.top("r.push(context.engine.tools.html_escape(" + (this.format_expression(value)) + "));"); + }, + compile_action_escf : function(value) { + this.top("r.push(context.engine.tools.html_escape(" + (this.string_interpolation(value)) + "));"); + }, + compile_action_raw : function(value) { + this.top("r.push(" + (this.format_expression(value)) + ");"); + }, + compile_action_rawf : function(value) { + this.top("r.push(" + (this.string_interpolation(value)) + ");"); + }, + compile_action_js : function(value) { + this.top("(function(" + value + ") {"); + this.bottom("})(dict);"); + this.indent(); + if (this.children.length === 1) { + var lines = this.children[0].node.data.split(/\r?\n/); + for (var i = 0, ilen = lines.length; i < ilen; i++) { + this.top(lines[i]); + } + } else { + this.engine.tools.exception("'js' code block contains " + this.children.length + " nodes instead of 1"); + } + // Maybe I could handle the children ? + this.process_children = false; + }, + compile_action_debug : function(value) { + this.top("debugger;"); + }, + compile_action_log : function(value) { + this.top("console.log(" + this.format_expression(value) + "});"); + } + }); + return Element; +})(); + +QWeb = new QWeb2.Engine();