bzr revid: nicolas.vanhoren@openerp.com-20110504120348-dnwkd6ea22xfgsjp
This commit is contained in:
niv-openerp
2011-05-04 14:03:48 +02:00
15 changed files with 669 additions and 164 deletions
@@ -1,40 +1,17 @@
<templates>
<t t-name="repetition-text-content" t-trim="both">
<t t-foreach="seq">*</t>
</t>
<t t-name="repetition-dom-content" t-trim="both">
<t t-foreach="seq"><b/></t>
</t>
<t t-name="repetition-self" t-trim="both">
<b t-foreach="seq"/>
</t>
<t t-name="repetition-text-content" t-foreach="seq">*</t>
<t t-name="repetition-dom-content" t-foreach="seq"><b/></t>
<b t-name="repetition-self" t-foreach="seq"/>
<t t-name="scope-self" t-trim="both">
<t t-foreach="seq"><t t-esc="seq"/></t>
</t>
<t t-name="scope-value" t-trim="both">
<t t-foreach="seq"><t t-esc="seq_value"/></t>
</t>
<t t-name="scope-index" t-trim="both">
<t t-foreach="seq"><t t-esc="seq_index"/></t>
</t>
<t t-name="scope-first" t-trim="both">
<t t-foreach="seq"><t t-esc="seq_first"/> </t>
</t>
<t t-name="scope-last" t-trim="both">
<t t-foreach="seq"><t t-esc="seq_last"/> </t>
</t>
<t t-name="scope-parity" t-trim="both">
<t t-foreach="seq"><t t-esc="seq_parity"/> </t>
</t>
<t t-name="scope-size" t-trim="both">
<t t-foreach="seq"><t t-esc="seq_size"/> </t>
</t>
<t t-name="scope-self" t-foreach="seq" t-esc="seq"/>
<t t-name="scope-value" t-foreach="seq" t-esc="seq_value"/>
<t t-name="scope-index" t-foreach="seq" t-esc="seq_index"/>
<t t-name="scope-first" t-foreach="seq"><t t-esc="seq_first"/> </t>
<t t-name="scope-last" t-foreach="seq"><t t-esc="seq_last"/> </t>
<t t-name="scope-parity" t-foreach="seq"><t t-esc="seq_parity"/> </t>
<t t-name="scope-size" t-foreach="seq"><t t-esc="seq_size"/> </t>
<t t-name="aliasing" t-trim="both">
<t t-foreach="seq" t-as="item"><t t-esc="item"/></t>
</t>
<t t-name="loopvars-aliasing" t-trim="both">
<t t-foreach="seq" t-as="item"><t t-esc="item_parity"/> </t>
</t>
<t t-name="aliasing" t-foreach="seq" t-as="item" t-esc="item"/>
<t t-name="loopvars-aliasing"
t-foreach="seq" t-as="item"><t t-esc="item_parity"/> </t>
</templates>
@@ -1,19 +0,0 @@
<templates>
<t t-name="trim-content-both" t-trim="both">
ok
</t>
<t t-name="trim-content-before" t-trim="left"> ok </t>
<t t-name="trim-content-after" t-trim="right"> ok </t>
<t t-name="trim-content-unknown" t-trim="stuff"> ok </t>
<t t-name="trim-children-text" t-trim="inner">o k</t>
<t t-name="trim-children-elements" t-trim="inner">
<foo/>
<bar/>
</t>
<t t-name="trim-mixed-children" t-trim="inner">
[ <foo/> ]
</t>
<t t-name='trim-children-left' t-trim="left inner"> <foo/> <bar/> </t>
<t t-name='trim-children-right' t-trim="right inner"> <foo/> <bar/> </t>
</templates>
+7 -39
View File
@@ -5,9 +5,10 @@
<link rel="stylesheet" href="http://code.jquery.com/qunit/git/qunit.css" type="text/css" media="screen"/>
<script type="text/javascript" src="http://code.jquery.com/qunit/git/qunit.js"></script>
<script type="text/javascript" src="qweb.js"></script>
<script type="text/javascript" src="qweb2.js"></script>
<script>
QWeb = new QWeb2.Engine();
function trim(s) {
return s.replace(/(^\s+|\s+$)/g, '');
}
@@ -193,39 +194,6 @@
"Call with t-import (calls in current context)");
});
module("Trim", {
setup: function () {
QWeb.add_template('qweb-test-trim.xml');
},
teardown: function () {
QWeb.templates = [];
QWeb.tag = {};
QWeb.att = {};
}
});
test('Trim content', function () {
equals(QWeb.render('trim-content-both', {}), 'ok',
"Trims before and after content");
equals(QWeb.render('trim-content-before', {}), 'ok ',
"Trims before content");
equals(QWeb.render('trim-content-after', {}), ' ok',
"Trims after content");
raises(function () {QWeb.render('trim-content-unknown', {});},
"Trims with an unknown mode");
});
test('Trim content *and* children', function () {
equals(QWeb.render('trim-children-text', {}), 'o k',
"Trims text children");
equals(QWeb.render('trim-children-elements', {}), '<foo/><bar/>',
"Full trim");
equals(QWeb.render('trim-mixed-children', {}), '[<foo/>]',
"Trims mixed content of text and elements");
equals(QWeb.render('trim-children-left', {}), '<foo/><bar/> ',
"Trims children and left of content");
equals(QWeb.render('trim-children-right', {}), ' <foo/><bar/>',
"Trims children and right of content");
});
module("Foreach", {
setup: function () {
QWeb.add_template('qweb-test-foreach.xml');
@@ -252,19 +220,19 @@
"each value of the sequence is also via the _value");
equals(QWeb.render('scope-index', {seq:seq}), '01234',
"the current 0-based index is available via _index");
equals(QWeb.render('scope-first', {seq:seq}), 'true false false false false',
equals(QWeb.render('scope-first', {seq:seq}), 'true false false false false ',
"_first says whether the current item is the first of the sequence");
equals(QWeb.render('scope-last', {seq:seq}), 'false false false false true',
equals(QWeb.render('scope-last', {seq:seq}), 'false false false false true ',
"_last says whether the current item is the last of the sequence");
equals(QWeb.render('scope-parity', {seq:seq}), 'even odd even odd even',
equals(QWeb.render('scope-parity', {seq:seq}), 'even odd even odd even ',
"the parity (odd/even) of the current row is available via _parity");
equals(QWeb.render('scope-size', {seq:seq}), '5 5 5 5 5',
equals(QWeb.render('scope-size', {seq:seq}), '5 5 5 5 5 ',
"the total length of the sequence is available through _size");
});
test('Name aliasing via t-as', function () {
equals(QWeb.render('aliasing', {seq:seq}), '43210',
"the inner value can be re-bound via t-as");
equals(QWeb.render('loopvars-aliasing', {seq:seq}), 'even odd even odd even',
equals(QWeb.render('loopvars-aliasing', {seq:seq}), 'even odd even odd even ',
"inner loop variables should be rebound as well");
});
});
+4 -1
View File
@@ -234,8 +234,11 @@ var QWeb = {
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);
return t_att["js"] != "quiet" ? r : "";
delete(v[dict_name]);
return r || '';
},
/**
* Renders a foreach loop (@t-foreach).
+562
View File
@@ -0,0 +1,562 @@
// 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: 'foreach,if,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, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
if (attribute) {
s = s.replace(/"/g, '&quot;');
}
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) !== -1)) {
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(context, 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] === '<?xml') {
return this.load_xml_string(s);
} else {
var req = this.get_xhr();
if (req) {
// TODO: third parameter is async : https://developer.mozilla.org/en/XMLHttpRequest#open()
// do an on_ready in QWeb2{} that could be passed to add_template
req.open('GET', s, false);
req.send(null);
if (req.responseXML) {
if (req.responseXML.documentElement.nodeName == "parsererror") {
return this.tools.exception(req.responseXML.documentElement.childNodes[0].nodeValue);
}
return req.responseXML;
} else {
return this.load_xml_string(req.responseText);
}
}
}
},
load_xml_string : function(s) {
if (window.DOMParser) {
var dp = new DOMParser();
var r = dp.parseFromString(s, "text/xml");
if (r.body && r.body.firstChild && r.body.firstChild.nodeName == 'parsererror') {
return this.tools.exception(r.body.innerText);
}
return r;
}
var xDoc;
try {
// new ActiveXObject("Msxml2.DOMDocument.4.0");
xDoc = new ActiveXObject("MSXML2.DOMDocument");
} catch (e) {
return this.tools.exception("Could not find a DOM Parser");
}
xDoc.async = false;
xDoc.preserveWhiteSpace = true;
xDoc.loadXML(s);
return xDoc;
},
get_xhr : function() {
if (window.XMLHttpRequest) {
return new window.XMLHttpRequest();
}
try {
return new ActiveXObject('MSXML2.XMLHTTP.3.0');
} catch (e) {
return null;
}
},
compile : function(node) {
var e = new QWeb2.Element(this, node);
var template = node.getAttribute(this.prefix + '-name');
return "(function(dict) {\n" +
" /* 'this' refers to Qweb2.Engine instance */\n" +
" var context = { engine : this, template : " + (this.tools.js_escape(template)) + " };\n" +
" dict = dict || {};\n" +
" dict['__template__'] = '" + template + "';\n" +
" var r = [];\n" +
" /* START TEMPLATE */ try {\n" +
(e.compile()) + "\n" +
" /* END OF TEMPLATE */ } catch(error) {\n" +
" context.engine.tools.exception('Runtime Error: ' + error, context);\n" +
" }\n" +
" return r.join('');\n" +
"});";
},
render : function(template, dict) {
//console.time("QWeb render template " + template);
var r = this._render(template, dict);
//console.timeEnd("QWeb render template " + template);
return r;
},
_render : function(template, dict) {
if (this.compiled_templates[template]) {
return this.compiled_templates[template].apply(this, [dict || {}]);
} else if (this.templates[template]) {
var code = this.compile(this.templates[template]), tcompiled;
try {
// tcompiled = new Function(code); TODO: Check if could use this in order to avoid eval
tcompiled = eval(code);
} catch (error) {
console.log(code);
this.tools.exception("Error evaluating template: " + error, { template: name });
}
this.compiled_templates[template] = tcompiled;
return this.render(template, dict);
} else {
return this.tools.exception("Template '" + template + "' not found");
}
}
});
return Engine;
})();
QWeb2.Element = (function() {
function Element(engine, node) {
this.engine = engine;
this.node = node;
this.tag = node.tagName;
this.actions = {};
this.actions_done = [];
this.attributes = {};
this.children = [];
this._top = [];
this._bottom = [];
this._indent = 1;
this.process_children = true;
var childs = this.node.childNodes;
if (childs) {
for (var i = 0, ilen = childs.length; i < ilen; i++) {
this.children.push(new QWeb2.Element(this.engine, childs[i]));
}
}
var attrs = this.node.attributes;
if (attrs) {
for (var j = 0, jlen = attrs.length; j < jlen; j++) {
var attr = attrs[j];
var name = attr.name;
var m = name.match(new RegExp("^" + this.engine.prefix + "-(.+)"));
if (m) {
name = m[1];
if (name === 'name') {
continue;
}
this.actions[name] = attr.value;
} else {
this.attributes[name] = attr.value;
}
}
}
}
QWeb2.tools.extend(Element.prototype, {
compile : function() {
switch (this.node.nodeType) {
case 3:
case 4:
this.top_string(this.node.data);
break;
case 1:
this.compile_element();
}
var r = this.flatten_output(this._top);
if (this.process_children) {
for (var i = 0, ilen = this.children.length; i < ilen; i++) {
var child = this.children[i];
child._indent = this._indent;
r += child.compile();
}
}
r += this.flatten_output(this._bottom);
return r;
},
format_expression : function(e) {
/* Naive format expression builder. Replace reserved words and variables to dict[variable]
* Does not handle spaces before dot yet, and causes problems for anonymous functions. Use t-js="" for that */
if (e === '0') {
return "dict['0']";
}
if (QWeb2.expressions_cache[e]) {
return QWeb2.expressions_cache[e];
}
var chars = e.split(''),
instring = '',
invar = '',
invar_pos = 0,
r = '';
chars.push(' ');
for (var i = 0, ilen = chars.length; i < ilen; i++) {
var c = chars[i];
if (instring.length) {
if (c === instring && chars[i - 1] !== "\\") {
instring = '';
}
} else if (c === '"' || c === "'") {
instring = c;
} else if (c.match(/[a-zA-Z_\$]/) && !invar.length) {
invar = c;
invar_pos = i;
continue;
} else if (c.match(/\W/) && invar.length) {
// TODO: Should check for possible spaces before dot
if (chars[invar_pos - 1] !== '.' && QWeb2.tools.arrayIndexOf(QWeb2.reserved_words, invar) < 0) {
invar = QWeb2.word_replacement[invar] || ("dict['" + invar + "']");
}
r += invar;
invar = '';
} else if (invar.length) {
invar += c;
continue;
}
r += c;
}
r = r.slice(0, -1);
QWeb2.expressions_cache[e] = r;
return r;
},
string_interpolation : function(s) {
if (!s) {
return "''";
}
var regex = /^{(.*)}(.*)/,
src = s.split(/#/),
r = [];
for (var i = 0, ilen = src.length; i < ilen; i++) {
var val = src[i],
m = val.match(regex);
if (m) {
r.push("(" + this.format_expression(m[1]) + ")");
if (m[2]) {
r.push(this.engine.tools.js_escape(m[2]));
}
} else if (!(i === 0 && val === '')) {
r.push(this.engine.tools.js_escape((i === 0 ? '' : '#') + val));
}
}
return r.join(' + ');
},
indent : function() {
return this._indent++;
},
dedent : function() {
if (this._indent !== 0) {
return this._indent--;
}
},
get_indent : function() {
return new Array(this._indent + 1).join("\t");
},
flatten_output : function(out) {
var r = '';
for (var i = 0, ilen = out.length; i < ilen; i++) {
var val = out[i];
if (val.constructor === Array) {
r += val[0] + "r.push(" + this.engine.tools.js_escape(val[1]) + ");\n";
} else {
r += val;
}
}
return r;
},
top : function(s) {
return this._top.push(this.get_indent() + s + '\n');
},
top_string : function(s) {
var top = this._top;
if (top.length && top[top.length - 1].constructor === Array) {
return top[top.length - 1][1] += s;
} else {
return top.push([this.get_indent(), s]);
}
},
bottom : function(s) {
return this._bottom.unshift(this.get_indent() + s + '\n');
},
bottom_string : function(s) {
var bottom = this._bottom;
if (bottom.length && bottom[0].constructor === Array) {
return bottom[0][1] += s;
} else {
return bottom.unshift([this.get_indent(), s]);
}
},
compile_element : function() {
for (var i = 0, ilen = QWeb2.actions_precedence.length; i < ilen; i++) {
var a = QWeb2.actions_precedence[i];
if (a in this.actions) {
var value = this.actions[a];
var key = 'compile_action_' + a;
if (!this[key]) {
this.engine.tools.exception("No handler method for action '" + a + "'");
}
this[key](value);
}
}
if (this.tag !== this.engine.prefix) {
var tag = "<" + this.tag;
for (var a in this.attributes) {
tag += this.engine.tools.gen_attribute([a, this.attributes[a]]);
}
this.top_string(tag);
if (this.actions.att) {
this.top("r.push(context.engine.tools.gen_attribute(" + (this.format_expression(this.actions.att)) + "));");
}
for (var a in this.actions) {
var v = this.actions[a];
var m = a.match(/att-(.+)/);
if (m) {
this.top("r.push(context.engine.tools.gen_attribute(['" + m[1] + "', (" + (this.format_expression(v)) + ")]));");
}
var m = a.match(/attf-(.+)/);
if (m) {
this.top("r.push(context.engine.tools.gen_attribute(['" + m[1] + "', (" + (this.string_interpolation(v)) + ")]));");
}
}
if (this.children.length || this.actions.opentag === 'true') {
this.top_string(">");
this.bottom_string("</" + this.tag + ">");
} 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;
})();
+4
View File
@@ -8,7 +8,10 @@
<script type="text/javascript" src="/base/static/lib/LABjs/LAB.js"></script>
<script type="text/javascript" src="/base/static/lib/underscore/underscore.js"></script>
<script type="text/javascript" src="/base/static/lib/underscore/underscore.strings.js"></script>
<script type="text/javascript" src="/base/static/lib/qweb/qweb2.js"></script>
<!-- Uncomment in order to use legacy QWeb
<script type="text/javascript" src="/base/static/lib/qweb/qweb.js"></script>
-->
<script type="text/javascript" src="/base/static/lib/jquery/jquery-1.5.2.js"></script>
<script type="text/javascript" src="/base/static/lib/jquery.ui/js/jquery-ui-1.8.9.custom.min.js"></script>
<script type="text/javascript" src="/base/static/lib/jquery.ui/js/jquery-ui-timepicker-addon.js"></script>
@@ -35,6 +38,7 @@
<script type="text/javascript">
$(function() {
QWeb = window.QWeb || new QWeb2.Engine();
var oe = openerp.init();
oe.base.webclient("oe");
});
+10 -25
View File
@@ -530,34 +530,19 @@ openerp.base.form.WidgetButton = openerp.base.form.Widget.extend({
}
},
on_confirmed: function() {
var attrs = this.node.attrs;
if (attrs.special) {
this.on_button_action({
result : { type: 'ir.actions.act_window_close' }
var self = this;
this.execute_action(
this.node.attrs, this.view.dataset, this.session.action_manager,
this.view.datarecord.id, function (result) {
self.log("Button returned", result);
self.view.reload();
});
} else {
var type = attrs.type || 'workflow';
var context = _.extend({}, this.view.dataset.context, attrs.context || {});
switch(type) {
case 'object':
return this.view.dataset.call(attrs.name, [this.view.datarecord.id], [context], this.on_button_action);
case 'action':
return this.rpc('/base/action/load', { action_id: parseInt(attrs.name) }, this.on_button_action);
default:
return this.view.dataset.exec_workflow(this.view.datarecord.id, attrs.name, this.on_button_action);
}
}
},
on_button_action: function(r) {
console.log("Got reesonse button", r)
if (r.result && r.result.constructor == Object) {
this.session.action_manager.do_action(r.result);
} else {
this.log("Button returned", r.result);
this.view.reload();
}
}
});
// let WidgetButton execute actions
_.extend(openerp.base.form.WidgetButton.prototype,
openerp.base.ActionExecutor);
openerp.base.form.WidgetLabel = openerp.base.form.Widget.extend({
init: function(view, node) {
+10 -7
View File
@@ -40,6 +40,8 @@ openerp.base.ListView = openerp.base.Controller.extend(
* @param {null|String} [options.addable="New"] should the new-record button be displayed, and what should its label be. Use ``null`` to hide the button.
* @param {Boolean} [options.sortable=true] is it possible to sort the table by clicking on column headers
* @param {Boolean} [options.reorderable=true] is it possible to reorder list rows
*
* @borrows openerp.base.ActionExecutor#execute_action as #execute_action
*/
init: function(view_manager, session, element_id, dataset, view_id, options) {
this._super(session, element_id);
@@ -145,20 +147,20 @@ openerp.base.ListView = openerp.base.Controller.extend(
});
$table.delegate(
'td.oe-field-cell button', 'click', function (e) {
e.stopImmediatePropagation();
var $cell = $(e.currentTarget).closest('td');
var col_index = $cell.prevAll('td').length;
var field = self.visible_columns[col_index];
var action = field.name;
var $row = $cell.parent('tr');
var row = self.rows[$row.prevAll().length];
var context = _.extend(
{}, self.dataset.context, field.context || {});
self.notification.notify(
'[Dummy] Execute action', self.dataset.model + '#' + action + "([" + row.data.id.value + '])');
// TODO: implement actions correctly once the corresponding manager has been extracted (and filled)
e.stopImmediatePropagation();
// TODO: we should probably only reload content, also maybe diff records or something, instead of replacing every single row
self.execute_action(
field, self.dataset, self.session.action_manager,
row.data.id.value, self.do_reload);
});
$table.delegate(
'td.oe-record-delete button', 'click', this.do_delete);
@@ -393,6 +395,7 @@ openerp.base.ListView = openerp.base.Controller.extend(
// TODO: implement sort (click on column headers), if sorted, the list can not be reordered anymore
// TODO: implement reorder (drag and drop rows)
});
_.extend(openerp.base.ListView.prototype, openerp.base.ActionExecutor);
openerp.base.TreeView = openerp.base.Controller.extend({
});
+52
View File
@@ -63,6 +63,58 @@ openerp.base.ActionManager = openerp.base.Controller.extend({
}
});
/**
* Mixin for action-executing objects, provides handling of OpenERP actions to
* all clients.
*
* Mix into existing classes via ``_.extend`` of the class's prototype.
*
* @class
*/
openerp.base.ActionExecutor =
/**
* @lends openerp.base.ActionExecutor#
*/ {
/**
* Fetches and executes the action identified by ``action_data``.
*
* @param {Object} action_data the action descriptor data
* @param {String} action_data.name the action name, used to uniquely identify the action to find and execute it
* @param {String} [action_data.special=null] special action handlers (currently: only ``'cancel'``)
* @param {String} [action_data.type='workflow'] the action type, if present, one of ``'object'``, ``'action'`` or ``'workflow'``
* @param {Object} [action_data.context=null] additional action context, to add to the current context
* @param {openerp.base.DataSet} dataset a dataset object used to communicate with the server
* @param {openerp.base.ActionManager} action_manager object able to actually execute the action, if any is fetched
* @param {Number} [record_id] the identifier of the object on which the action is to be applied
* @param {Function} on_no_action callback to execute if the action does not generate any result (no new action)
*/
execute_action: function (action_data, dataset, action_manager, record_id, on_no_action) {
var handler = function (r) {
if (r.result && r.result.constructor == Object) {
action_manager.do_action(r.result);
} else {
on_no_action(r.result);
}
};
if (action_data.special) {
handler({
result : { type: 'ir.actions.act_window_close' }
});
} else {
var context = _.extend({}, dataset.context, action_data.context || {});
switch(action_data.type) {
case 'object':
return dataset.call(action_data.name, [record_id], [context], handler);
case 'action':
return this.rpc('/base/action/load', { action_id: parseInt(action_data.name, 10) }, handler);
default:
return dataset.exec_workflow(record_id, action_data.name, handler);
}
}
}
};
/**
* Registry for all the main views
*/
+5 -2
View File
@@ -534,8 +534,11 @@
</div>
</t>
<t t-name="ViewManager.sidebar.internal">
<t t-set="the_condition" t-value="sections.length &gt; 0 &amp;&amp; _.detect(sections, function(x)
{return x.elements.length &gt; 0;}) != undefined"/>
<t t-js="d">
d.the_condition = d.sections.length &gt; 0 &amp;&amp; d._.detect(d.sections, function(x) {
return x.elements.length &gt; 0;
}) != undefined
</t>
<t t-if="the_condition">
<a class="toggle-sidebar"></a>
<div t-att-id="element_id" class="sidebar-sub-div">
-1
View File
@@ -1 +0,0 @@
// TODO when the tests will not be broken anymore
+1
View File
@@ -7,6 +7,7 @@ $(document).ready(function () {
// views loader stuff
window.openerp.base.data(openerp);
window.openerp.base.views(openerp);
window.openerp.base.list(openerp);
window.openerp.base.form(openerp);
}
});
+1 -1
View File
@@ -26,8 +26,8 @@ $(document).ready(function () {
// views loader stuff
window.openerp.base.data(openerp);
window.openerp.base.views(openerp);
window.openerp.base.form(openerp);
window.openerp.base.list(openerp);
window.openerp.base.form(openerp);
}
});
-3
View File
@@ -16,7 +16,6 @@ import cherrypy.lib.static
import simplejson
import nonliterals
import xmlrpctimeout
import logging
#-----------------------------------------------------------
@@ -93,8 +92,6 @@ class OpenERPSession(object):
self.client_timezone = False
def proxy(self, service):
# TODO: correct this bug with timeout connections not working in python 2.7
# s = xmlrpctimeout.TimeoutServerProxy('http://%s:%s/xmlrpc/%s' % (self._server, self._port, service), timeout=5)
s = xmlrpclib.ServerProxy('http://%s:%s/xmlrpc/%s' % (self._server, self._port, service))
return s
-30
View File
@@ -1,30 +0,0 @@
import xmlrpclib
import httplib
class TimeoutHTTPConnection(httplib.HTTPConnection):
def connect(self):
httplib.HTTPConnection.connect(self)
self.sock.settimeout(self.timeout)
class TimeoutHTTP(httplib.HTTP):
_connection_class = TimeoutHTTPConnection
def set_timeout(self, timeout):
self._conn.timeout = timeout
class TimeoutTransport(xmlrpclib.Transport):
def __init__(self, timeout=10, *l, **kw):
xmlrpclib.Transport.__init__(self,*l,**kw)
self.timeout=timeout
def make_connection(self, host):
conn = TimeoutHTTP(host)
conn.set_timeout(self.timeout)
return conn
class TimeoutServerProxy(xmlrpclib.ServerProxy):
def __init__(self,uri,timeout=10,*l,**kw):
kw['transport']=TimeoutTransport(timeout=timeout, use_datetime=kw.get('use_datetime',0))
xmlrpclib.ServerProxy.__init__(self,uri,*l,**kw)
if __name__ == "__main__":
s=TimeoutServerProxy('http://127.0.0.1:9090',timeout=2)
s.dummy()