diff options
| author | Antony Lesuisse <al@openerp.com> | 2011-04-13 03:37:11 +0200 |
|---|---|---|
| committer | Antony Lesuisse <al@openerp.com> | 2011-04-13 03:37:11 +0200 |
| commit | 3ac70cbecfda63682f4667994d64cd9526a8e1a3 (patch) | |
| tree | d42d6267d79b9211a0c6c5027cc1c22506c95b7a /addons/base/static/src | |
| parent | be257a50087140885759e5cd7c8f59f9423129eb (diff) | |
| parent | 3b38e2c68873fdc4ab40c16afd0193ca7c49b6a9 (diff) | |
[MERGE] xmo listview, bug in the form pager
bzr revid: al@openerp.com-20110413013711-vp63eigz5aadm6wc
Diffstat (limited to 'addons/base/static/src')
| -rw-r--r-- | addons/base/static/src/css/base.css | 71 | ||||
| -rw-r--r-- | addons/base/static/src/js/data.js | 2 | ||||
| -rw-r--r-- | addons/base/static/src/js/form.js | 120 | ||||
| -rw-r--r-- | addons/base/static/src/js/list.js | 167 | ||||
| -rw-r--r-- | addons/base/static/src/js/views.js | 57 | ||||
| -rw-r--r-- | addons/base/static/src/xml/base.xml | 92 |
6 files changed, 368 insertions, 141 deletions
diff --git a/addons/base/static/src/css/base.css b/addons/base/static/src/css/base.css index c6040f1b0fd..cf6d0beabd4 100644 --- a/addons/base/static/src/css/base.css +++ b/addons/base/static/src/css/base.css @@ -443,6 +443,7 @@ body.openerp { padding-left: 10px; } + .openerp .oe-searchview-render-line { width:100%; } @@ -462,6 +463,75 @@ body.openerp { background: url(../img/icons/gtk-remove.png) repeat-y; padding-left: 18px; } +/* List */ +.openerp .oe-listview table { + clear: right; + width: 100%; + border-spacing: 0; + border: 1px solid silver; +} + +.openerp .oe-listview tr.odd { + background-color: #f3f3f3; +} +.openerp .oe-listview tbody tr:hover { + background-color: #ecebf2; +} +.openerp .oe-listview tbody tr:hover { + background-color: #eae9f0; +} + +.openerp .oe-listview td, +.openerp .oe-listview th { + vertical-align: middle; +} + +.openerp .oe-listview .oe-field-cell button { + padding: 0; + border: none; + background: none; +} +.openerp .oe-listview .oe-field-cell button:active { + opacity: 0.5; +} + +.openerp .oe-listview th.oe-actions { + text-align: left; +} +.openerp .oe-listview th.oe-list-pager { + text-align: right; +} + +/** list rounded corners + + rounded corners are a pain on tables: need to round not only table, but + also on the first and last children of the first and last row + */ +.openerp .oe-listview table { + -webkit-border-radius: 7px; + -moz-border-radius: 7px; + border-radius: 7px; +} +.openerp .oe-listview table thead tr:first-child th:first-child { + -webkit-border-top-left-radius: 7px; + -moz-border-radius-topleft: 7px; + border-top-left-radius: 7px; +} +.openerp .oe-listview table thead tr:first-child th:last-child { + -webkit-border-top-right-radius: 7px; + -moz-border-radius-topright: 7px; + border-top-right-radius: 7px; +} +.openerp .oe-listview table tbody tr:last-child th:first-child { + -webkit-border-bottom-left-radius: 7px; + -moz-border-radius-bottomleft: 7px; + border-bottom-left-radius: 7px; +} +.openerp .oe-listview table tbody tr:last-child td:last-child { + -webkit-border-bottom-right-radius: 7px; + -moz-border-radius-bottomright: 7px; + border-bottom-right-radius: 7px; +} /* Notebook */ .openerp .oe_form_notebook { @@ -489,7 +559,6 @@ body.openerp { background: #f9f9f9; } - /* Form */ .openerp table.oe_frame td { color: #4c4c4c; diff --git a/addons/base/static/src/js/data.js b/addons/base/static/src/js/data.js index 7b1a930c88f..5ed21be533c 100644 --- a/addons/base/static/src/js/data.js +++ b/addons/base/static/src/js/data.js @@ -108,6 +108,8 @@ openerp.base.DataSet = openerp.base.Controller.extend( /** @lends openerp.base. this.notification.notify("Unlink", ids); }, call: function (method, ids, args, callback) { + this.notification.notify( + "Calling", this.model + '#' + method + '(' + ids + ')'); ids = ids || []; args = args || []; return this.rpc('/base/dataset/call', { diff --git a/addons/base/static/src/js/form.js b/addons/base/static/src/js/form.js index b13ac7d8332..350d3e019bb 100644 --- a/addons/base/static/src/js/form.js +++ b/addons/base/static/src/js/form.js @@ -311,6 +311,64 @@ openerp.base.FormView = openerp.base.Controller.extend( /** @lends openerp.base /** @namespace */ openerp.base.form = {}; +openerp.base.form.compute_domain = function(expr, fields) { + var stack = []; + for (var i = expr.length - 1; i >= 0; i--) { + var ex = expr[i]; + if (ex.length == 1) { + var top = stack.pop(); + switch (ex[0]) { + case '|': + stack.push(stack.pop() || top); + continue; + case '&': + stack.push(stack.pop() && top); + continue; + case '!': + stack.push(!top); + continue; + default: + throw new Error('Unknown domain operator ' + ex[0]); + } + } + + var field = fields[ex[0]].value; + var op = ex[1]; + var val = ex[2]; + + switch (op.toLowerCase()) { + case '=': + case '==': + stack.push(field == val); + break; + case '!=': + case '<>': + stack.push(field != val); + break; + case '<': + stack.push(field < val); + break; + case '>': + stack.push(field > val); + break; + case '<=': + stack.push(field <= val); + break; + case '>=': + stack.push(field >= val); + break; + case 'in': + stack.push(_.indexOf(val, field) > -1); + break; + case 'not in': + stack.push(_.indexOf(val, field) == -1); + break; + default: + this.log("Unsupported operator in attrs :", op); + } + } + return _.indexOf(stack, false) == -1; +}, openerp.base.form.Widget = openerp.base.Controller.extend({ init: function(view, node) { this.view = view; @@ -335,68 +393,10 @@ openerp.base.form.Widget = openerp.base.Controller.extend({ this.$element = $('#' + this.element_id); }, process_attrs: function() { + var compute_domain = openerp.base.form.compute_domain; for (var a in this.attrs) { - this[a] = this.eval_attrs(this.attrs[a]); - } - }, - eval_attrs: function(expr) { - var stack = []; - for (var i = 0; i < expr.length; i++) { - var ex = expr[i]; - if (ex.length == 1) { - stack.push(ex[0]); - continue; - } - - var field = this.view.fields[ex[0]].value; - var op = ex[1]; - var val = ex[2]; - - switch (op.toLowerCase()) { - case '=': - case '==': - stack.push(field == val); - break; - case '!=': - case '<>': - stack.push(field != val); - break; - case '<': - stack.push(field < val); - break; - case '>': - stack.push(field > val); - break; - case '<=': - stack.push(field <= val); - break; - case '>=': - stack.push(field >= val); - break; - case 'in': - stack.push(_.indexOf(val, field) > -1); - break; - case 'not in': - stack.push(_.indexOf(val, field) == -1); - break; - default: - this.log("Unsupported operator in attrs :", op); - } - } - - for (var j = stack.length-1; j >- 1; j--) { - switch (stack[j]) { - case '|': - var result = stack[j + 1] || stack[j + 2]; - stack.splice(j, 3, result); - break; - case '&': - var result = stack[j + 1] && stack[j + 2]; - stack.splice(j, 3, result); - break; - } + this[a] = compute_domain(this.attrs[a], this.view.fields); } - return _.indexOf(stack, false) == -1; }, update_dom: function() { this.$element.toggle(!this.invisible); diff --git a/addons/base/static/src/js/list.js b/addons/base/static/src/js/list.js index 4b1eeaaf993..d823bc81489 100644 --- a/addons/base/static/src/js/list.js +++ b/addons/base/static/src/js/list.js @@ -8,7 +8,9 @@ openerp.base.ListView = openerp.base.Controller.extend( // list rows can be deleted 'deletable': true, // whether the column headers should be displayed - 'header': true + 'header': true, + // display addition button, with that label + 'addable': "New" }, /** * @constructs @@ -21,6 +23,7 @@ openerp.base.ListView = openerp.base.Controller.extend( * @param {Boolean} [options.selectable=true] determines whether view rows are selectable (e.g. via a checkbox) * @param {Boolean} [options.header=true] should the list's header be displayed * @param {Boolean} [options.deletable=true] are the list rows deletable + * @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. */ init: function(view_manager, session, element_id, dataset, view_id, options) { this._super(session, element_id); @@ -35,20 +38,37 @@ openerp.base.ListView = openerp.base.Controller.extend( this.options = _.extend({}, this.defaults, options || {}); }, start: function() { - //this.log('Starting ListView '+this.model+this.view_id) + this.$element.addClass('oe-listview'); return this.rpc("/base/listview/load", {"model": this.model, "view_id":this.view_id, toolbar:!!this.view_manager.sidebar}, this.on_loaded); }, on_loaded: function(data) { + var self = this; this.fields_view = data.fields_view; //this.log(this.fields_view); this.name = "" + this.fields_view.arch.attrs.string; var fields = this.fields_view.fields; + var domain_computer = openerp.base.form.compute_domain; this.columns = _(this.fields_view.arch.children).chain() .map(function (field) { var name = field.attrs.name; - return _.extend({id: name, tag: field.tag}, field.attrs, fields[name]); + var column = _.extend({id: name, tag: field.tag}, + field.attrs, fields[name]); + // attrs computer + if (column.attrs) { + var attrs = eval('(' + column.attrs + ')'); + column.attrs_for = function (fields) { + var result = {}; + for (var attr in attrs) { + result[attr] = domain_computer(attrs[attr], fields); + } + return result; + }; + } else { + column.attrs_for = function () { return {}; }; + } + return column; }).value(); this.visible_columns = _.filter(this.columns, function (column) { @@ -57,20 +77,46 @@ openerp.base.ListView = openerp.base.Controller.extend( this.$element.html(QWeb.render("ListView", this)); // Head hook - this.$element.find('#oe-list-delete').click(this.do_delete_selected); + this.$element.find('#oe-list-add').click(this.do_add_record); + this.$element.find('#oe-list-delete') + .hide() + .click(this.do_delete_selected); + var $table = this.$element.find('table'); // Cell events - this.$element.find('table').delegate( - 'th.oe-record-selector', 'click', function (e) { - // A click in the selection cell should not activate the - // linking feature - e.stopImmediatePropagation(); + $table.delegate( + 'th.oe-record-selector', 'click', function (e) { + // TODO: ~linear performances, would a simple counter work? + if ($table.find('th.oe-record-selector input:checked').length) { + $table.find('#oe-list-delete').show(); + } else { + $table.find('#oe-list-delete').hide(); + } + // A click in the selection cell should not activate the + // linking feature + e.stopImmediatePropagation(); }); - this.$element.find('table').delegate( + $table.delegate( + 'td.oe-field-cell button', 'click', function (e) { + 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.dataset.call(action, [row.data.id.value], [context], + self.do_reload); + e.stopImmediatePropagation(); + }); + $table.delegate( 'td.oe-record-delete button', 'click', this.do_delete); // Global rows handlers - this.$element.find('table').delegate( + $table.delegate( 'tr', 'click', this.on_select_row); // sidebar stuff @@ -85,11 +131,27 @@ openerp.base.ListView = openerp.base.Controller.extend( * @returns {Promise} promise to the end of view rendering (list views are asynchronously filled for improved responsiveness) */ do_fill_table: function(records) { + var $table = this.$element.find('table'); this.rows = records; - var $table = this.$element.find('table'); + // Keep current selected record, if it's still in our new search + var current_record_id = this.dataset.ids[this.dataset.index]; + this.dataset.ids = _(records).chain().map(function (record) { + return record.data.id.value; + }).value(); + this.dataset.index = _.indexOf(this.dataset.ids, current_record_id); + if (this.dataset.index === -1) { + this.dataset.index = 0; + } + + // TODO: offset, length, count + var results = this.rows.length; + $table.find('.oe-pager-last').text(results); + $table.find('.oe-pager-total').text(results); + + // remove all data lines - $table.find('tbody').remove(); + var $old_body = $table.find('tbody'); // add new content var columns = this.columns, @@ -100,10 +162,10 @@ openerp.base.ListView = openerp.base.Controller.extend( var PAGE_SIZE = 50, bodies_count = Math.ceil(this.rows.length / PAGE_SIZE), body = 0, - $body = $('<tbody>').appendTo($table); + $body = $('<tbody class="ui-widget-content">').appendTo($table); + var rendered = $.Deferred(); var render_body = function () { - var rendered = $.Deferred(); setTimeout(function () { $body.append( QWeb.render("ListView.rows", { @@ -118,9 +180,29 @@ openerp.base.ListView = openerp.base.Controller.extend( rendered.resolve(); } }, 0); - return rendered.promise(); }; - return render_body(); + render_body(); + + return rendered.promise().then(function () { + $old_body.remove(); + }); + }, + /** + * Asks the view manager to switch to a different view, using the provided + * record index (within the current dataset). + * + * If the index is null, ``switch_to_record`` asks for the creation of a + * new record. + * + * @param {Number|null} index the record index (in the current dataset) to switch to + * @param {String} [view="form"] the view to switch to + */ + switch_to_record:function (index, view) { + view = view || 'form'; + this.dataset.index = index; + _.delay(_.bind(function () { + this.view_manager.on_mode_switch(view); + }, this)); }, on_select_row: function (event) { var $target = $(event.currentTarget); @@ -130,26 +212,41 @@ openerp.base.ListView = openerp.base.Controller.extend( // count number of preceding siblings to line clicked var row = this.rows[$target.prevAll().length]; - var index = _.indexOf(this.dataset.ids, row.id); + var index = _.indexOf(this.dataset.ids, row.data.id.value); if (index == undefined || index === -1) { return; } - this.dataset.index = index; - _.delay(_.bind(function () { - this.view_manager.on_mode_switch('form'); - }, this)); - - }, + this.switch_to_record(index); + }, do_show: function () { - // TODO: re-trigger search this.$element.show(); + if (this.hidden) { + this.do_reload(); + this.hidden = false; + } }, do_hide: function () { this.$element.hide(); + this.hidden = true; + }, + /** + * Reloads the search view based on the current settings (dataset & al) + */ + do_reload: function () { + // TODO: need to do 5 billion tons of pre-processing, bypass + // DataSet for now + //self.dataset.read_slice(self.dataset.fields, 0, self.limit, + // self.do_fill_table); + return this.rpc('/base/listview/fill', { + 'model': this.dataset.model, + 'id': this.view_id, + 'context': this.dataset.context, + 'domain': this.dataset.domain + }, this.do_fill_table); }, do_search: function (domains, contexts, groupbys) { var self = this; - this.rpc('/base/session/eval_domain_and_context', { + return this.rpc('/base/session/eval_domain_and_context', { domains: domains, contexts: contexts, group_by_seq: groupbys @@ -157,12 +254,12 @@ openerp.base.ListView = openerp.base.Controller.extend( // TODO: handle non-empty results.group_by with read_group self.dataset.context = results.context; self.dataset.domain = results.domain; - self.dataset.read_slice(self.dataset.fields, 0, self.limit, self.do_fill_table); + return self.do_reload(); }); }, do_update: function () { var self = this; - self.dataset.read_ids(self.dataset.ids, self.dataset.fields, self.do_fill_table); + //self.dataset.read_ids(self.dataset.ids, self.dataset.fields, self.do_fill_table); }, /** * Handles the signal to delete a line from the DOM @@ -173,7 +270,17 @@ openerp.base.ListView = openerp.base.Controller.extend( // don't link to forms e.stopImmediatePropagation(); this.dataset.unlink( - [this.rows[$(e.currentTarget).closest('tr').prevAll().length].id]); + [this.rows[$(e.currentTarget).closest('tr').prevAll().length].data.id.value]); + }, + /** + * Handles signal for the addition of a new record (can be a creation, + * can be the addition from a remote source, ...) + * + * The default implementation is to switch to a new record on the form view + */ + do_add_record: function () { + this.notification.notify('Add', "New record"); + this.switch_to_record(null); }, /** * Handles deletion of all selected lines @@ -195,7 +302,7 @@ openerp.base.ListView = openerp.base.Controller.extend( var rows = this.rows; return this.$element.find('th.oe-record-selector input:checked') .closest('tr').map(function () { - return rows[$(this).prevAll().length].id; + return rows[$(this).prevAll().length].data.id.value; }).get(); } }); diff --git a/addons/base/static/src/js/views.js b/addons/base/static/src/js/views.js index a0bb706714c..a1825824c2e 100644 --- a/addons/base/static/src/js/views.js +++ b/addons/base/static/src/js/views.js @@ -144,8 +144,10 @@ openerp.base.ViewManager = openerp.base.Controller.extend({ this.searchview.stop(); } this.searchview = new openerp.base.SearchView(this, this.session, this.element_id + "_search", this.dataset, view_id, search_defaults); - this.searchview.on_search.add(function() { - self.views[self.active_view].controller.do_search.apply(self, arguments); + this.searchview.on_search.add(function(domains, contexts, groupbys) { + self.views[self.active_view].controller.do_search.call( + self, domains.concat(self.domains()), + contexts.concat(self.contexts()), groupbys); }); return this.searchview.start(); }, @@ -159,6 +161,23 @@ openerp.base.ViewManager = openerp.base.Controller.extend({ on_remove: function() { }, on_edit: function() { + }, + /** + * Domains added on searches by the view manager, to override in subsequent + * view manager in order to add new pieces of domains to searches + * + * @returns an empty list + */ + domains: function () { + return []; + }, + /** + * Contexts added on searches by the view manager. + * + * @returns an empty list + */ + contexts: function () { + return []; } }); @@ -180,8 +199,6 @@ openerp.base.ViewManagerAction = openerp.base.ViewManager.extend({ this.sidebar.start(); } - // init search view - var view_id = this.action.search_view_id ? this.action.search_view_id[0] || false : false; var search_defaults = {}; _.each(this.action.context, function (value, key) { var match = /^search_default_(.*)$/.exec(key); @@ -189,10 +206,12 @@ openerp.base.ViewManagerAction = openerp.base.ViewManager.extend({ search_defaults[match[1]] = value; } }); - var searchview_loaded = null; - if (view_id) { - searchview_loaded = this.setup_search_view(view_id,search_defaults); - } + + // init search view + var searchview_id = this.action.search_view_id && this.action.search_view_id[0]; + + var searchview_loaded = this.setup_search_view( + searchview_id || false, search_defaults); // schedule auto_search if (searchview_loaded != null && this.action['auto_search']) { @@ -206,6 +225,28 @@ openerp.base.ViewManagerAction = openerp.base.ViewManager.extend({ this.sidebar.stop(); } this._super(); + }, + /** + * adds action domain to the search domains + * + * @returns the action's domain + */ + domains: function () { + if (!this.action.domain) { + return []; + } + return [this.action.domain]; + }, + /** + * adds action context to the search contexts + * + * @returns the action's context + */ + contexts: function () { + if (!this.action.context) { + return []; + } + return [this.action.context]; } }); diff --git a/addons/base/static/src/xml/base.xml b/addons/base/static/src/xml/base.xml index 9c73bbc66f3..129198f0ea4 100644 --- a/addons/base/static/src/xml/base.xml +++ b/addons/base/static/src/xml/base.xml @@ -154,38 +154,37 @@ </tr> </table> </t> -<t t-name="ListView"> - <!-- - <h3><t t-esc="fields_view.arch['@string']"/></h3> - --> - <div class="oe_list_header"> - <div class="oe_list_buttons"> - <!--<button type="button" class="oe_list_button_new">New</button>--> - </div> - <div class="oe_list_pager"> - <button type="button" data-pager-action="first">First</button> - <button type="button" data-pager-action="previous"><<</button> +<table t-name="ListView"> + <t t-set="columns_count" t-value="visible_columns.length + (options.selectable ? 1 : 0) + (options.deletable ? 1 : 0)"/> + <t t-set="actions_span" t-value="Math.floor((options.deletable or options.addable) ? columns_count/2 : 0)"/> + <thead class="ui-widget-header"> + <tr t-if="options.selectable"> + <th t-if="actions_span" t-att-colspan="actions_span" + class="oe-actions"> + <button type="button" id="oe-list-add" + t-if="options.addable"> + <t t-esc="options.addable"/> + </button> + <button type="button" id="oe-list-delete" + t-if="options.deletable"> + Delete + </button> + </th> + <th t-att-colspan="columns_count - actions_span" + class="oe-list-pager"> + <button type="button" data-pager-action="first">First</button> + <button type="button" data-pager-action="previous" + ><<</button> - <span class="oe_pager_index">0</span> / <span class="oe_pager_count">0</span> + <span class="oe-pager-first">1</span> + to <span class="oe-pager-last">1</span> + of <span class="oe-pager-total">1</span> - <button type="button" data-pager-action="next">>></button> - <button type="button" data-pager-action="last">Last</button> - </div> - </div> - <table> - <t t-call="ListView.header"/> - </table> -</t> -<t t-name="ListView.header"> - <thead t-if="options.header"> - <tr t-if="options.selectable and options.deletable"> - <th t-att-colspan="visible_columns.length + 2"> - <button type="button" id="oe-list-delete"> - Delete Selected - </button> + <button type="button" data-pager-action="next">>></button> + <button type="button" data-pager-action="last">Last</button> </th> </tr> - <tr> + <tr t-if="options.header"> <th t-if="options.selectable"/> <t t-foreach="columns" t-as="column"> <th t-if="column.invisible !== '1'"> @@ -197,27 +196,36 @@ <th t-if="options.deletable"/> </tr> </thead> -</t> +</table> <t t-name="ListView.rows" t-foreach="rows" t-as="row"> - <t t-call="ListView.row"/> + <t t-call="ListView.row"> + <t t-set="style" t-value="null"/> + <t-if test="row.color"> + <t t-set="style" t-value="'color: ' + row.color"/> + </t-if> + </t> </t> -<tr t-name="ListView.row"> +<tr t-name="ListView.row" t-att-style="style" + t-att-class="row_parity"> <th t-if="options.selectable" class="oe-record-selector"> <input type="checkbox"/> </th> <t t-foreach="columns" t-as="column"> - <!-- TODO: handle attrs --> - <td t-if="column.invisible !== '1'" t-att-title="column.help"> - <t t-set="is_button" t-value="column.tag === 'button'"/> - <!-- TODO: get correct widget from form --> - <t t-if="!is_button and row[column.id]"> - <t t-esc="row[column.id]"/> + <t t-set="attrs" t-value="column.attrs_for(row.data)"/> + <td t-if="column.invisible !== '1'" t-att-title="column.help" + class="oe-field-cell"> + <t t-if="!attrs.invisible"> + <t t-set="is_button" t-value="column.tag === 'button'"/> + <!-- TODO: get correct widget from form --> + <t t-if="!is_button and row['data'][column.id].value"> + <t t-esc="row['data'][column.id].value"/> + </t> + <button type="button" t-att-title="column.help" + t-if="is_button"> + <img t-att-src="'/base/static/src/img/icons/' + column.icon + '.png'" + t-att-alt="column.string"/> + </button> </t> - <button type="button" t-att-title="column.help" - t-if="is_button"> - <img t-att-src="'/base/static/src/img/icons/' + column.icon + '.png'" - t-att-alt="column.string"/> - </button> </td> </t> <td t-if="options.deletable" class='oe-record-delete'> |
