bzr revid: nicolas.vanhoren@openerp.com-20110506115607-c23lh5skwx5qtt82
This commit is contained in:
niv-openerp
2011-05-06 13:56:07 +02:00
9 changed files with 247 additions and 83 deletions
+20 -5
View File
@@ -349,10 +349,14 @@ class DataSet(openerpweb.Controller):
if not ids:
ids = Model.search(domain or [], offset or 0, limit or False,
sort or False, request.context)
if fields and fields == ['id']:
# shortcut read if we only want the ids
return map(lambda id: {'id': id}, ids)
return Model.read(ids, fields or False, request.context)
reads = Model.read(ids, fields or False, request.context)
reads.sort(key=lambda obj: ids.index(obj['id']))
return reads
@openerpweb.jsonrequest
def get(self, request, model, ids, fields=False):
@@ -563,11 +567,11 @@ class ListView(View):
@openerpweb.jsonrequest
def fill(self, request, model, id, domain,
offset=0, limit=False, ids=False):
return self.do_fill(request, model, id, domain, offset, limit, ids)
offset=0, limit=False, sort=None, ids=False):
return self.do_fill(request, model, id, domain, offset, limit, sort=sort, ids=ids)
def do_fill(self, request, model, id, domain,
offset=0, limit=False, ids=False):
offset=0, limit=False, sort=None, ids=False):
""" Returns all information needed to fill a table:
* view with processed ``editable`` flag
@@ -587,11 +591,22 @@ class ListView(View):
"""
view = self.fields_view_get(request, model, id, toolbar=True)
print sort
rows = DataSet().do_search_read(request, model,
offset=offset, limit=limit,
domain=domain, ids=ids)
domain=domain, sort=sort, ids=ids)
eval_context = request.session.evaluation_context(
request.context)
if sort:
sort_criteria = sort.split(',')[0].split(' ')
print sort, sort_criteria
view['sorted'] = {
'field': sort_criteria[0],
'reversed': sort_criteria[1] == 'DESC'
}
else:
view['sorted'] = {}
return {
'view': view,
'records': [
@@ -0,0 +1,43 @@
<!DOCTYPE html>
<html style="height: 100%">
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<script type="text/javascript" src="qweb.js"></script>
<script type="text/javascript" src="qweb2.js"></script>
<script type="text/javascript">
var dict = {
session : true,
testing : 'yes',
name : 'AGR'
}
console.time("Load template with QWeb");
QWeb.add_template("qweb-benchmark.xml");
console.timeEnd("Load template with QWeb");
console.time("Load template with QWeb2");
var engine = new QWeb2.Engine("qweb-benchmark.xml")
engine.debug = true;
console.timeEnd("Load template with QWeb2")
var iter = 1000;
console.log("Rendering...");
console.time("Render " + iter + " templates with QWeb");
for (var i = 0; i < iter; i++) {
var qweb = QWeb.render('benchmark', dict);
}
console.timeEnd("Render " + iter + " templates with QWeb");
console.time("Render " + iter + " templates with QWeb2");
for (var i = 0; i < iter; i++) {
var qweb2 = engine.render('benchmark', dict);
}
console.timeEnd("Render " + iter + " templates with QWeb2");
</script>
</head>
<body>
Please, check your console for results
</body>
</html>
@@ -0,0 +1,60 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- vim:fdl=1:
-->
<templates id="template">
<t t-name="benchmark"><div id="oe_notification" class="oe_notification">
<div id="oe_notification_default">
<a class="ui-notify-cross ui-notify-close" href="#">x</a>
<h1>title</h1>
<p>text</p>
</div>
<div id="oe_notification_alert" class="ui-state-error">
<a class="ui-notify-cross ui-notify-close" href="#">x</a>
<span style="float:left; margin:2px 5px 0 0;" class="ui-icon ui-icon-alert"></span>
<h1>title</h1>
<p>text</p>
</div>
</div>
<t t-js="d">
d.iter = 'one,two,three,four,five'.split(',')
</t>
<t t-foreach="iter" t-as="i">
<t t-call="benchmark_call">
+ <t t-esc="i"/>
</t>
</t>
<t t-set="enplus">1</t>
<t t-set="novar">true</t>
<div t-attf-class="id_#{enplus}"/>
<div t-if="testing || true" t-att-class="novar || 'yes'" style="display: none">
<t t-set="novar"></t>
<t t-set="style">height: 200px; border: 1px solid red;</t>
<div t-att="{ 'style' : style, 'disabled' : 'false', 'readonly' : novar or undefined }" t-opentag="true"/>
<t t-foreach="{'my': 'first', 'my2': 'second' }" t-as="v">
* <t t-esc="v"/> : <t t-esc="v_value"/>
</t>
Ok this is good <t t-esc="name"/>!
<t t-set="myvar">Hi there !</t>
[<t t-raw="myvar"/>]
<t t-set="myvar2" t-value="'a,b,c,d,e'.split(',')"/>
<t t-foreach="myvar2" t-as="i">
(<t t-esc="i"/>)
</t>
</div>
<div id="oe_notification" class="oe_notification">
<div id="oe_notification_default">
<a class="ui-notify-cross ui-notify-close" href="#">x</a>
<h1>title</h1>
<p>text</p>
</div>
</div>
</t>
<t t-name="benchmark_call">
<div id="oe_notification_alert" class="ui-state-error">
<a class="ui-notify-cross ui-notify-close" href="#">x</a>
<span style="float:left; margin:2px 5px 0 0;" class="ui-icon ui-icon-alert"></span>
<h1>Here's your value : (<t t-esc="0"/>) !!</h1>
</div>
</t>
</templates>
+48 -31
View File
@@ -20,8 +20,18 @@ var QWeb2 = {
}
throw prefix + ": " + message;
},
js_escape: function(s) {
return "'" + s.replace(/\r?\n/g, "\\n").replace(/'/g, "\\'") + "'";
trim: function(s, mode) {
switch (mode) {
case "left":
return s.replace(/^\s*/, "");
case "right":
return s.replace(/\s*$/, "");
default:
return s.replace(/^\s*|\s*$/g, "");
}
},
js_escape: function(s, noquotes) {
return (noquotes ? '' : "'") + s.replace(/\r?\n/g, "\\n").replace(/'/g, "\\'") + (noquotes ? '' : "'");
},
html_escape: function(s, attribute) {
if (s === null || s === undefined) {
@@ -126,7 +136,7 @@ var QWeb2 = {
new_dict[as_index] = index;
new_dict[as_first] = index === 0;
new_dict[as_parity] = (j % 2 == 1 ? 'odd' : 'even');
new_dict[as] = v;
new_dict[as] = k;
callback(context, new_dict);
index += 1;
}
@@ -143,6 +153,7 @@ QWeb2.Engine = (function() {
function Engine() {
// TODO: handle prefix at template level : t-prefix="x"
this.prefix = 't';
this.debug = false;
this.templates_resources = []; // TODO: implement this.reload()
this.templates = {};
this.compiled_templates = {};
@@ -176,7 +187,7 @@ QWeb2.Engine = (function() {
},
load_xml : function(s) {
s = s.replace(/^\s*|\s*$/g, '');
if (s[0] === '<?xml') {
if (this.tools.trim(s)[0] === '<') {
return this.load_xml_string(s);
} else {
var req = this.get_xhr();
@@ -312,6 +323,34 @@ QWeb2.Element = (function() {
QWeb2.tools.extend(Element.prototype, {
compile : function() {
var r = [],
instring = false,
lines = this._compile().split('\n');
for (var i = 0, ilen = lines.length; i < ilen; i++) {
var m, line = lines[i];
if (m = line.match(/^(\s*)\/\/@string=(.*)/)) {
if (instring) {
if (this.engine.debug) {
// Split string lines in indented r.push arguments
r.push((m[2].indexOf("\\n") != -1 ? "',\n\t" + m[1] + "'" : '') + m[2]);
} else {
r.push(m[2]);
}
} else {
r.push(m[1] + "r.push('" + m[2]);
instring = true;
}
} else {
if (instring) {
r.push("');\n");
}
instring = false;
r.push(line + '\n');
}
}
return r.join('');
},
_compile : function() {
switch (this.node.nodeType) {
case 3:
case 4:
@@ -320,15 +359,15 @@ QWeb2.Element = (function() {
case 1:
this.compile_element();
}
var r = this.flatten_output(this._top);
var r = this._top.join('');
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 += child._compile();
}
}
r += this.flatten_output(this._bottom);
r += this._bottom.join('');
return r;
},
format_expression : function(e) {
@@ -407,39 +446,17 @@ QWeb2.Element = (function() {
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]);
}
return this._top.push(this.get_indent() + "//@string=" + this.engine.tools.js_escape(s, true) + '\n');
},
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]);
}
return this._bottom.unshift(this.get_indent() + "//@string=" + this.engine.tools.js_escape(s, true) + '\n');
},
compile_element : function() {
for (var i = 0, ilen = QWeb2.actions_precedence.length; i < ilen; i++) {
+31 -36
View File
@@ -1,8 +1,8 @@
/* TODO: seperate openerp webclient page css from openerp views css */
/* TODO: separate openerp web client page css from openerp views css */
body {
padding: 0;
margin: 0;
font-family: helvetica, sans, arial;
font-family: helvetica, arial, sans-serif;
font-size: 80%;
}
@@ -85,7 +85,7 @@ body.openerp {
/* Menu */
.openerp .sf-menu {
margin-bottom: 0px;
margin-bottom: 0;
}
/*
.sf-menu a {
@@ -169,10 +169,9 @@ body.openerp {
border: none;
}
.openerp .secondary_menu h4 {
padding: 0 0 2px;
padding: 0 0 2px 10px;
border: none;
background: none;
padding-left: 10px;
}
.openerp .secondary_menu h3 span, .openerp .secondary_menu h4 span {
left: 0 !important;
@@ -224,10 +223,9 @@ body.openerp {
margin-right: 10px;
line-height: 63px;
text-align: center;
border-bottom: 1px solid black;
border-right: 1px solid black;
border-top: 1px solid white;
border-left: 1px solid white;
border: 1px solid white;
border-right-color: black;
border-bottom-color: black;
background: #FFFFFF;
background: -moz-linear-gradient(top, #FFFFFF 0%, #CECECE 100%);
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#FFFFFF), color-stop(100%,#CECECE));
@@ -301,7 +299,6 @@ body.openerp {
right: 0;
top: 5px;
padding: 1px 4px 2px;
background: #333;
background: rgba(0, 0, 0, 0.75);
border-radius: 7px;
-moz-border-radius: 7px;
@@ -316,9 +313,9 @@ body.openerp {
overflow: hidden;
padding: 5px 0;
position: relative;
-moz-box-shadow: inset 0px 3px 5px rgba(0, 0, 0, 0.4);
-webkit-box-shadow: inset 0px 3px 5px rgba(0, 0, 0, 0.4);
box-shadow: inset 0px 3px 5px rgba(0, 0, 0, 0.4);
-moz-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.4);
-webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.4);
box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.4);
}
.openerp div.oe_footer p.oe_footer_powered {
left: 50%;
@@ -337,7 +334,7 @@ body.openerp {
/* Main Application */
.openerp .oe-application {
padding: 0px;
padding: 0;
height: 100%;
}
@@ -380,7 +377,7 @@ body.openerp {
background: #AAAAAA;
}
.openerp .filter_icon {
padding: 1px 2px 0px 2px;
padding: 1px 2px 0 2px;
margin-left: 0;
margin-right: 0;
}
@@ -485,6 +482,11 @@ body.openerp {
.openerp .oe-listview td,
.openerp .oe-listview th {
vertical-align: middle;
text-align: left;
}
.openerp .oe-listview th.oe-sortable,
.openerp .oe-listview th.oe-sortable .ui-icon {
cursor: pointer;
}
.openerp .oe-listview .oe-field-cell {
@@ -539,17 +541,17 @@ body.openerp {
/* Notebook */
.openerp .oe_form_notebook {
padding: 0px;
padding: 0;
background: none;
border-width: 0px;
border-width: 0;
}
.openerp .oe_form_notebook ul.ui-tabs-nav {
padding-left: 0px;
padding-left: 0;
background: transparent;
border-width: 0px 0px 1px 0px;
border-radius: 0px;
-moz-border-radius: 0px;
-webkit-border-radius: 0px;
border-width: 0 0 1px 0;
border-radius: 0;
-moz-border-radius: 0;
-webkit-border-radius: 0;
line-height: 0.5em;
}
.openerp .oe_form_notebook ul.ui-tabs-nav li {
@@ -557,7 +559,7 @@ body.openerp {
}
.openerp .oe_form_notebook .ui-tabs-panel {
background: #f9f9f9;
border-width: 0px 1px 1px 1px;
border-width: 0 1px 1px 1px;
}
.openerp .oe_form_notebook .ui-tabs-selected {
background: #f9f9f9;
@@ -566,7 +568,7 @@ body.openerp {
/* Form */
.openerp table.oe_frame td {
color: #4c4c4c;
font-weight: 80%;
font-weight: bold;
}
.openerp .required.error {
border: 1px solid #900;
@@ -695,9 +697,7 @@ body.openerp {
padding:0;
margin:0;
width:180px;
border-left-color: #D2CFCF;
border-left-style: solid;
border-left-width: 1px;
border-left: 1px solid #D2CFCF;
height:100%;
font-family: Ubuntu, Helvetica, sans-serif;
font-size: 0.9em;
@@ -725,17 +725,14 @@ body.openerp {
font-family: Ubuntu, Helvetica, sans-serif;
font-size: 1.15em;
color: #8E8E8E;
text-shadow: white 0px 1px 0px;
text-shadow: white 0 1px 0;
padding-left: 10px;
padding-right: 21px;
height: 21px;
background: url(../img/sideheader-a-bg.png) repeat-x;
border-color: #D2CFCF;
border-style: solid;
border-top-width: 1px;
border-bottom-width: 1px;
border-left-width: 0;
border: 1px solid #D2CFCF;
border-right-width: 0;
border-left-width: 0;
}
.openerp .view-manager-main-sidebar ul {
@@ -752,9 +749,7 @@ body.openerp {
.openerp .toggle-sidebar {
cursor: pointer;
border-color: #D2CFCF;
border-style: solid;
border-width: 1px;
border: 1px solid #D2CFCF;
display: block;
background: url(../img/toggle-a-bg.png);
width: 21px;
+31 -2
View File
@@ -154,7 +154,7 @@ openerp.base.DataSetSearch = openerp.base.DataSet.extend({
init: function(session, model) {
this._super(session, model);
this.domain = [];
this.sort = [];
this._sort = [];
this.offset = 0;
// subset records[offset:offset+limit]
// is it necessary ?
@@ -176,7 +176,7 @@ openerp.base.DataSetSearch = openerp.base.DataSet.extend({
fields: fields,
domain: this.domain,
context: this.context,
sort: this.sort,
sort: this.sort(),
offset: offset,
limit: limit
}, function (records) {
@@ -187,6 +187,35 @@ openerp.base.DataSetSearch = openerp.base.DataSet.extend({
}
callback(records);
});
},
/**
* Reads or changes sort criteria on the dataset.
*
* If not provided with any argument, serializes the sort criteria to
* an SQL-like form usable by OpenERP's ORM.
*
* If given a field, will set that field as first sorting criteria or,
* if the field is already the first sorting criteria, will reverse it.
*
* @param {String} [field] field to sort on, reverses it (toggle from ASC to DESC) if already the main sort criteria
* @param {Boolean} [force_reverse=false] forces inserting the field as DESC
* @returns {String|undefined}
*/
sort: function (field, force_reverse) {
if (!field) {
return _.map(this._sort, function (criteria) {
if (criteria[0] === '-') {
return criteria.slice(1) + ' DESC';
}
return criteria + ' ASC';
}).join(', ');
}
var reverse = force_reverse || (this._sort[0] === field);
this._sort = _.without(this._sort, field, '-' + field);
this._sort.unshift((reverse ? '-' : '') + field);
return undefined;
}
});
+11
View File
@@ -123,6 +123,8 @@ openerp.base.ListView = openerp.base.Controller.extend(
this.visible_columns = _.filter(this.columns, function (column) {
return column.invisible !== '1';
});
if (!this.fields_view.sorted) { this.fields_view.sorted = {}; }
this.$element.html(QWeb.render("ListView", this));
// Head hook
@@ -130,6 +132,14 @@ openerp.base.ListView = openerp.base.Controller.extend(
this.$element.find('#oe-list-delete')
.hide()
.click(this.do_delete_selected);
this.$element.find('thead').delegate('th[data-id]', 'click', function (e) {
e.stopPropagation();
self.dataset.sort($(this).data('id'));
// TODO: should only reload content (and set the right column to a sorted display state)
self.do_reload();
});
var $table = this.$element.find('table');
// Cell events
@@ -319,6 +329,7 @@ openerp.base.ListView = openerp.base.Controller.extend(
'id': this.view_id,
'context': this.dataset.context,
'domain': this.dataset.domain,
'sort': this.dataset.sort && this.dataset.sort(),
'ids': this.dataset.ids
}, this.do_fill_table);
},
+3 -1
View File
@@ -181,9 +181,11 @@
<tr t-if="options.header">
<th t-if="options.selectable"/>
<t t-foreach="columns" t-as="column">
<th t-if="column.invisible !== '1'">
<th t-if="column.invisible !== '1'" t-att-data-id="column.id"
t-att-class="((options.sortable and column.tag !== 'button') ? 'oe-sortable' : null)">
<t t-if="column.tag !== 'button'">
<t t-esc="column.string"/>
<span t-att-class="(fields_view.sorted.field === column.id) ? ('ui-icon' + (fields_view.sorted.reversed ? ' ui-icon-triangle-1-n' : ' ui-icon-triangle-1-s')) : ''"/>
</t>
</th>
</t>
-8
View File
@@ -80,10 +80,6 @@ class Domain(object):
def get_domain_string(self):
""" Retrieves the domain string linked to this non-literal domain in
the provided session.
:param session: the OpenERP Session used to store the domain string in
the first place.
:type session: openerpweb.openerpweb.OpenERPSession
"""
return self.session.domains_store[self.key]
@@ -129,10 +125,6 @@ class Context(object):
def get_context_string(self):
""" Retrieves the context string linked to this non-literal context in
the provided session.
:param session: the OpenERP Session used to store the context string in
the first place.
:type session: openerpweb.openerpweb.OpenERPSession
"""
return self.session.contexts_store[self.key]