From 069da6ed5e5702196c271f396b6f09dc94836884 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Tue, 24 May 2011 16:06:48 +0200 Subject: [PATCH 01/11] [FIX] aggregate field value can be returned as 'false', which should be translated to '0' bzr revid: xmo@openerp.com-20110524140648-79ukx88gl61eedd9 --- addons/base/static/src/js/data.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/base/static/src/js/data.js b/addons/base/static/src/js/data.js index 1d2fab9b9bc..60bffc8bff3 100644 --- a/addons/base/static/src/js/data.js +++ b/addons/base/static/src/js/data.js @@ -104,7 +104,7 @@ openerp.base.ContainerDataGroup = openerp.base.DataGroup.extend( || key === field_name + '_count') { return; } - aggregates[key] = value; + aggregates[key] = value || 0; }); return { From 3dbc88c6960dbfb16577d7ac8a4180f9a5f2d95b Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Wed, 25 May 2011 11:28:13 +0200 Subject: [PATCH 02/11] [IMP] make get_selection call and selected ListView event provide both ids of selected records and the selected records themselves bzr revid: xmo@openerp.com-20110525092813-ciahklygwshkpdl0 --- addons/base/static/src/js/list.js | 59 +++++++++++++++++++++---------- 1 file changed, 41 insertions(+), 18 deletions(-) diff --git a/addons/base/static/src/js/list.js b/addons/base/static/src/js/list.js index 1a03b7d4526..7d7c53e18b6 100644 --- a/addons/base/static/src/js/list.js +++ b/addons/base/static/src/js/list.js @@ -61,9 +61,8 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi columns: this.columns }); $(this.groups).bind({ - 'selected': function (e, selection) { - self.$element.find('#oe-list-delete') - .toggle(!!selection.length); + 'selected': function (e, ids, records) { + self.do_select(ids, records); }, 'deleted': function (e, ids) { self.do_delete(ids); @@ -330,6 +329,16 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi // TODO only refresh modified rows }); }, + /** + * Handles the signal indicating that a new record has been selected + * + * @param {Array} ids selected record ids + * @param {Array} records selected record values + */ + do_select: function (ids, records) { + this.$element.find('#oe-list-delete') + .toggle(!!ids.length); + }, /** * Handles signal for the addition of a new record (can be a creation, * can be the addition from a remote source, ...) @@ -344,7 +353,7 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi * Handles deletion of all selected lines */ do_delete_selected: function () { - this.do_delete(this.groups.get_selection()); + this.do_delete(this.groups.get_selection().ids); } // TODO: implement reorder (drag and drop rows) }); @@ -389,7 +398,8 @@ openerp.base.ListView.List = Class.extend( /** @lends openerp.base.ListView.List .appendTo(document.body) .delegate('th.oe-record-selector', 'click', function (e) { e.stopPropagation(); - $(self).trigger('selected', [self.get_selection()]); + var selection = self.get_selection(); + $(self).trigger('selected', [selection.ids, selection.records]); }) .delegate('td.oe-record-delete button', 'click', function (e) { e.stopPropagation(); @@ -422,17 +432,24 @@ openerp.base.ListView.List = Class.extend( /** @lends openerp.base.ListView.List }, /** * Gets the ids of all currently selected records, if any - * @returns {Array} empty if no record is selected (or the list view is not selectable) + * @returns {Object} object with the keys ``ids`` and ``records``, holding respectively the ids of all selected records and the records themselves. */ get_selection: function () { if (!this.options.selectable) { return []; } var rows = this.rows; - return this.$current.find('th.oe-record-selector input:checked') - .closest('tr').map(function () { - return rows[$(this).prevAll().length].data.id.value; - }).get(); + var result = {ids: [], records: []}; + this.$current.find('th.oe-record-selector input:checked') + .closest('tr').each(function () { + var record = {}; + _(rows[$(this).prevAll().length].data).each(function (obj, key) { + record[key] = obj.value; + }); + result.ids.push(record.id); + result.records.push(record); + }); + return result; }, /** * Returns the index of the row in the list of rows. @@ -615,7 +632,8 @@ openerp.base.ListView.Groups = Class.extend( /** @lends openerp.base.ListView.Gr self = this; $(child).bind('selected', function (e) { // can have selections spanning multiple links - $this.trigger(e, [self.get_selection()]); + var selection = self.get_selection(); + $this.trigger(e, [selection.ids, selection.records]); }).bind('action', function (e, name, id, callback) { if (!callback) { callback = function () { @@ -683,15 +701,20 @@ openerp.base.ListView.Groups = Class.extend( /** @lends openerp.base.ListView.Gr return $element; }, /** - * Returns the ids of all selected records for this group + * Returns the ids of all selected records for this group, and the records + * themselves */ get_selection: function () { - return _(this.children).chain() - .map(function (child) { - return child.get_selection(); - }) - .flatten() - .value(); + var ids = [], records = []; + + _(this.children) + .each(function (child) { + var selection = child.get_selection(); + ids.push.apply(ids, selection.ids); + records.push.apply(records, selection.records); + }); + + return {ids: ids, records: records}; }, apoptosis: function () { _(this.children).each(function (child) { From 88773e6c89cec6a0d30f8d6977f7556aa06c81fc Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Wed, 25 May 2011 15:04:14 +0200 Subject: [PATCH 03/11] [ADD] basic implementation of list-wise aggregation functions bzr revid: xmo@openerp.com-20110525130414-a1wkfb4inix2q4we --- addons/base/static/src/css/base.css | 2 + addons/base/static/src/js/list.js | 119 ++++++++++++++++++++++++++++ addons/base/static/src/xml/base.xml | 7 ++ 3 files changed, 128 insertions(+) diff --git a/addons/base/static/src/css/base.css b/addons/base/static/src/css/base.css index 3eefefa5c5e..4387e62f58e 100644 --- a/addons/base/static/src/css/base.css +++ b/addons/base/static/src/css/base.css @@ -542,11 +542,13 @@ background: linear-gradient(top, #c6c6c6 0%,#5c5c5c 7%,#969595 86%); /* W3C */ -moz-border-radius-topright: 7px; border-top-right-radius: 7px; } +.openerp .oe-listview table tfoot td:first-child, .openerp .oe-listview table tbody:last-child 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 tfoot td:last-child, .openerp .oe-listview table tbody:last-child tr:last-child td:last-child { -webkit-border-bottom-right-radius: 7px; -moz-border-radius-bottomright: 7px; diff --git a/addons/base/static/src/js/list.js b/addons/base/static/src/js/list.js index 7d7c53e18b6..d6843e68c50 100644 --- a/addons/base/static/src/js/list.js +++ b/addons/base/static/src/js/list.js @@ -205,6 +205,19 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi this.visible_columns = _.filter(this.columns, function (column) { return column.invisible !== '1'; }); + + this.aggregate_columns = _(this.columns).chain() + .filter(function (column) { + return column['sum'] || column['avg'];}) + .map(function (column) { + var func = column['sum'] ? 'sum' : 'avg'; + return { + field: column.id, + type: column.type, + 'function': func, + label: column[func] + }; + }).value(); }, /** * Used to handle a click on a table row, if no other handler caught the @@ -294,6 +307,8 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi } self.do_reload(results.group_by).then(function () { self.$element.find('table').append(self.groups.render()); + }).then(function () { + self.compute_aggregates(); }); }); }, @@ -338,6 +353,12 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi do_select: function (ids, records) { this.$element.find('#oe-list-delete') .toggle(!!ids.length); + + if (!records.length) { + this.compute_aggregates(); + return; + } + this.compute_aggregates(records); }, /** * Handles signal for the addition of a new record (can be a creation, @@ -354,6 +375,89 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi */ do_delete_selected: function () { this.do_delete(this.groups.get_selection().ids); + }, + /** + * Computes the aggregates for the current list view, either on the + * records provided or on the records of the internal + * :js:class:`~openerp.base.ListView.Group`, by calling + * :js:func:`~openerp.base.ListView.group.get_records`. + * + * Then displays the aggregates in the table through + * :js:method:`~openerp.base.ListView.display_aggregates`. + * + * @param {Array} [records] + */ + compute_aggregates: function (records) { + if (_.isEmpty(this.aggregate_columns)) { + return; + } + if (_.isEmpty(records)) { + records = this.groups.get_records(); + } + + var aggregator = this.build_aggregator(this.aggregate_columns); + this.display_aggregates( + _(records).reduce(aggregator, aggregator).value()); + }, + /** + * Creates a stateful callable aggregator object, which can be reduced over + * a collection of records in order to build the aggregations described + * by the parameter + * + * @param {Array} aggregation_descriptors + */ + build_aggregator: function (aggregation_descriptors) { + var values = {}; + var descriptors = {}; + _(aggregation_descriptors).each(function (descriptor) { + values[descriptor.field] = []; + descriptors[descriptor.field] = descriptor; + }); + + var aggregator = function (_i, record) { + _(values).each(function (collection, key) { + collection.push(record[key]); + }); + + return aggregator; + }; + aggregator.value = function () { + var result = {}; + + _(values).each(function (collection, key) { + var value; + switch(descriptors[key]['function']) { + case 'avg': + value = (_(collection).chain() + .filter(function (item) { + return !_.isUndefined(item); }) + .reduce(function (total, item) { + return total + item; }, 0).value() + / collection.length); + break; + case 'sum': + value = (_(collection).chain() + .filter(function (item) { + return !_.isUndefined(item); }) + .reduce(function (total, item) { + return total + item; }, 0).value()); + break; + } + result[key] = value; + }); + + return result; + }; + + return aggregator; + }, + display_aggregates: function (aggregation) { + var $footer = this.$element.find('.oe-list-footer'); + $footer.text( + _(this.aggregate_columns).map(function (column) { + return _.sprintf( + "%s: %.2f", column.label, aggregation[column.field]); + }).join(' ')); } // TODO: implement reorder (drag and drop rows) }); @@ -477,6 +581,15 @@ openerp.base.ListView.List = Class.extend( /** @lends openerp.base.ListView.List if (!this.$current) { return; } this.$current.remove(); this.$current = null; + }, + get_records: function () { + return _(this.rows).map(function (row) { + var record = {}; + _(row.data).each(function (obj, key) { + record[key] = obj.value; + }); + return record; + }); } // drag and drop // editable? @@ -722,6 +835,12 @@ openerp.base.ListView.Groups = Class.extend( /** @lends openerp.base.ListView.Gr }); $(this.elements).remove(); return this; + }, + get_records: function () { + return _(this.children).chain() + .map(function (child) { + return child.get_records(); + }).flatten().value(); } }); }; diff --git a/addons/base/static/src/xml/base.xml b/addons/base/static/src/xml/base.xml index 70016538b10..37a0b132a4f 100644 --- a/addons/base/static/src/xml/base.xml +++ b/addons/base/static/src/xml/base.xml @@ -205,6 +205,13 @@ + + + + + + + From a49729461d19a70cbd5047f85a417070d8caaa20 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Wed, 25 May 2011 16:04:56 +0200 Subject: [PATCH 04/11] [FIX] aggregates display on initial list loading bzr revid: xmo@openerp.com-20110525140456-ylo5e0kf0kp3xe15 --- addons/base/static/src/js/list.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/addons/base/static/src/js/list.js b/addons/base/static/src/js/list.js index d6843e68c50..ed71e6c3241 100644 --- a/addons/base/static/src/js/list.js +++ b/addons/base/static/src/js/list.js @@ -306,10 +306,9 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi results.group_by = null; } self.do_reload(results.group_by).then(function () { - self.$element.find('table').append(self.groups.render()); - }).then(function () { - self.compute_aggregates(); - }); + self.$element.find('table').append( + self.groups.render(function () { + self.compute_aggregates();}));}); }); }, /** @@ -797,18 +796,20 @@ openerp.base.ListView.Groups = Class.extend( /** @lends openerp.base.ListView.Gr }); return d.promise(); }, - render: function () { + render: function (post_render) { var self = this; var $element = $(''); this.elements = [$element[0]]; this.datagroup.list(function (groups) { $element[0].appendChild( self.render_groups(groups)); + if (post_render) { post_render(); } }, function (dataset) { self.render_dataset(dataset).then(function (list) { self.children[null] = list; self.elements = [list.$current.replaceAll($element)[0]]; + if (post_render) { post_render(); } }); }); return $element; From 47f79225547fcb7b260f63b6b9add06871919c0f Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Wed, 25 May 2011 16:35:16 +0200 Subject: [PATCH 05/11] [IMP] look of list-wise aggregates bzr revid: xmo@openerp.com-20110525143516-wmav0tly51uw6ly5 --- addons/base/static/src/css/base.css | 11 +++++++++++ addons/base/static/src/js/list.js | 13 +++++++------ 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/addons/base/static/src/css/base.css b/addons/base/static/src/css/base.css index 4387e62f58e..279d6bafe71 100644 --- a/addons/base/static/src/css/base.css +++ b/addons/base/static/src/css/base.css @@ -522,6 +522,17 @@ background: linear-gradient(top, #c6c6c6 0%,#5c5c5c 7%,#969595 86%); /* W3C */ text-align: right; } +.openerp .oe-listview tfoot td { + padding: 3px 3px 0; +} +.openerp .oe-listview .oe-list-footer { + text-align: center; + white-space: nowrap; +} +.openerp .oe-listview .oe-list-footer span { + margin: 0 1em; +} + /** list rounded corners rounded corners are a pain on tables: need to round not only table, but diff --git a/addons/base/static/src/js/list.js b/addons/base/static/src/js/list.js index ed71e6c3241..0ec6ae06391 100644 --- a/addons/base/static/src/js/list.js +++ b/addons/base/static/src/js/list.js @@ -451,12 +451,13 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi return aggregator; }, display_aggregates: function (aggregation) { - var $footer = this.$element.find('.oe-list-footer'); - $footer.text( - _(this.aggregate_columns).map(function (column) { - return _.sprintf( - "%s: %.2f", column.label, aggregation[column.field]); - }).join(' ')); + var $footer = this.$element.find('.oe-list-footer').empty(); + _(this.aggregate_columns).each(function (column) { + $(_.sprintf( + "%s: %.2f", + column.label, aggregation[column.field])) + .appendTo($footer); + }); } // TODO: implement reorder (drag and drop rows) }); From 7d0ceef7d4219492182782657264214ce4358ad5 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Wed, 25 May 2011 17:02:40 +0200 Subject: [PATCH 06/11] [FIX] apoptosis on lists bzr revid: xmo@openerp.com-20110525150240-brxcbyfh2lltz4ai --- addons/base/static/src/js/list.js | 1 + 1 file changed, 1 insertion(+) diff --git a/addons/base/static/src/js/list.js b/addons/base/static/src/js/list.js index 0ec6ae06391..a7659a6450d 100644 --- a/addons/base/static/src/js/list.js +++ b/addons/base/static/src/js/list.js @@ -581,6 +581,7 @@ openerp.base.ListView.List = Class.extend( /** @lends openerp.base.ListView.List if (!this.$current) { return; } this.$current.remove(); this.$current = null; + this.$_element.remove(); }, get_records: function () { return _(this.rows).map(function (row) { From d84f9df4dff739b25ada779796882f76df0129dc Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Fri, 27 May 2011 08:18:20 +0200 Subject: [PATCH 07/11] [IMP] display buttons header (pagination & al) when list not selectable, but not deletion button bzr revid: xmo@openerp.com-20110527061820-9fsengtgnhf7qp4u --- addons/base/static/src/xml/base.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/base/static/src/xml/base.xml b/addons/base/static/src/xml/base.xml index 37a0b132a4f..21f54b0998e 100644 --- a/addons/base/static/src/xml/base.xml +++ b/addons/base/static/src/xml/base.xml @@ -156,7 +156,7 @@ - + @@ -165,7 +165,7 @@ From 1f247b96e1c6232a73fe963b76a34c0e5ca72c74 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Fri, 27 May 2011 09:13:09 +0200 Subject: [PATCH 08/11] [IMP] Remove colors handling so ListView.fill is not necessary anymore, and can go back to ListView.load and DataSet.search_read bzr revid: xmo@openerp.com-20110527071309-rtdkawbu4v7xfepd --- addons/base/controllers/main.py | 50 ------------- addons/base/static/src/js/form.js | 10 +-- addons/base/static/src/js/list.js | 119 ++++++++++++++++-------------- 3 files changed, 67 insertions(+), 112 deletions(-) diff --git a/addons/base/controllers/main.py b/addons/base/controllers/main.py index 0a5b3ba64b0..7856216ac14 100644 --- a/addons/base/controllers/main.py +++ b/addons/base/controllers/main.py @@ -580,56 +580,6 @@ class ListView(View): view_attributes['editable'] = 'bottom' return view - @openerpweb.jsonrequest - def fill(self, request, model, id, domain, - offset=0, limit=False, sort=None): - return self.do_fill(request, model, id, domain, offset, limit, sort) - - def do_fill(self, request, model, id, domain, - offset=0, limit=False, sort=None): - """ Returns all information needed to fill a table: - - * view with processed ``editable`` flag - * fields (columns) with processed ``invisible`` flag - * rows with processed ``attrs`` and ``colors`` - - .. note:: context is passed through ``request`` parameter - - :param request: OpenERP request - :type request: openerpweb.openerpweb.JsonRequest - :type str model: OpenERP model for this list view - :type int id: view_id, or False if none provided - :param list domain: the search domain to search for - :param int offset: search offset, for pagination - :param int limit: search limit, for pagination - :returns: hell if I have any idea yet - """ - view = self.fields_view_get(request, model, id, toolbar=True) - - rows = DataSet().do_search_read(request, model, - offset=offset, limit=limit, - domain=domain, sort=sort) - eval_context = request.session.evaluation_context( - request.context) - - if sort: - sort_criteria = sort.split(',')[0].split(' ') - view['sorted'] = { - 'field': sort_criteria[0], - 'reversed': sort_criteria[1] == 'DESC' - } - else: - view['sorted'] = {} - return { - 'view': view, - 'records': [ - {'data': dict((key, {'value': value}) - for key, value in row.iteritems()), - 'color': self.process_colors(view, row, eval_context)} - for row in rows - ] - } - def process_colors(self, view, row, context): colors = view['arch']['attrs'].get('colors') diff --git a/addons/base/static/src/js/form.js b/addons/base/static/src/js/form.js index 2d24f99c365..9ff05de4a84 100644 --- a/addons/base/static/src/js/form.js +++ b/addons/base/static/src/js/form.js @@ -1035,7 +1035,7 @@ openerp.base.form.FieldMany2Many = openerp.base.form.Field.extend({ }, check_load: function() { if(this.is_started && this.is_setted) { - this.list_view.do_reload(); + this.list_view.reload_view(); } } }); @@ -1045,11 +1045,11 @@ openerp.base.form.Many2ManyListView = openerp.base.ListView.extend({ this.dataset.ids = _.without.apply(null, [this.dataset.ids].concat(ids)); this.dataset.count = this.dataset.ids.length; // there may be a faster way - this.do_reload(); + this.reload_view(); this.m2m_field.on_ui_change(); }, - do_reload: function () { + reload_view: function () { /* Dear xmo, according to your comments, this method's implementation in list view seems * to be a little bit bullshit. * I assume the list view will be changed later, so I hope it will support static datasets. @@ -1070,7 +1070,7 @@ openerp.base.form.Many2ManyListView = openerp.base.ListView.extend({ if(! _.detect(self.dataset.ids, function(x) {return x == element_id;})) { self.dataset.ids.push(element_id); self.dataset.count = self.dataset.ids.length; - self.do_reload(); + self.reload_view(); } pop.stop(); }); @@ -1143,7 +1143,7 @@ openerp.base.form.Many2XSelectPopup = openerp.base.BaseWidget.extend({ var tmphack = {"loaded": false}; self.view_list.on_loaded.add_last(function() { if ( !tmphack.loaded ) { - self.view_list.do_reload(); + self.view_list.reload_view(); tmphack.loaded = true; }; }); diff --git a/addons/base/static/src/js/list.js b/addons/base/static/src/js/list.js index a7659a6450d..b46730b3511 100644 --- a/addons/base/static/src/js/list.js +++ b/addons/base/static/src/js/list.js @@ -44,7 +44,6 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi * @borrows openerp.base.ActionExecutor#execute_action as #execute_action */ init: function(view_manager, session, element_id, dataset, view_id, options) { - var self = this; this._super(session, element_id); this.view_manager = view_manager || new openerp.base.NullViewManager(); this.dataset = dataset; @@ -56,10 +55,21 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi this.options = _.extend({}, this.defaults, options || {}); this.flags = this.view_manager.action.flags; - this.groups = new openerp.base.ListView.Groups(this, { - options: this.options, - columns: this.columns - }); + this.set_groups(new openerp.base.ListView.Groups(this)); + }, + /** + * Set a custom Group construct as the root of the List View. + * + * @param {openerp.base.ListView.Groups} groups + */ + set_groups: function (groups) { + var self = this; + if (this.groups) { + $(this.groups).unbind("selected deleted action row_link"); + delete this.groups; + } + + this.groups = groups; $(this.groups).bind({ 'selected': function (e, ids, records) { self.do_select(ids, records); @@ -89,7 +99,6 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi }); } }); - }, /** * View startup method, the default behavior is to set the ``oe-listview`` @@ -99,11 +108,7 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi */ start: function() { this.$element.addClass('oe-listview'); - return this.rpc("/base/listview/load", { - model: this.model, - view_id: this.view_id, - toolbar: !!this.flags.sidebar - }, this.on_loaded); + return this.reload_view(); }, /** * Called after loading the list view's description, sets up such things @@ -126,15 +131,15 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi * @param {Object} data.fields_view fields_view_get result (processed) * @param {Object} data.fields_view.fields mapping of fields for the current model * @param {Object} data.fields_view.arch current list view descriptor - * @param {Array} columns columns to move to the front (and make visible) + * @param {Boolean} grouped Is the list view grouped */ - on_loaded: function(data, columns) { + on_loaded: function(data, grouped) { var self = this; this.fields_view = data.fields_view; //this.log(this.fields_view); this.name = "" + this.fields_view.arch.attrs.string; - this.setup_columns(this.fields_view.fields, columns); + this.setup_columns(this.fields_view.fields, grouped); if (!this.fields_view.sorted) { this.fields_view.sorted = {}; } @@ -151,7 +156,7 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi self.dataset.sort($(this).data('id')); // TODO: should only reload content (and set the right column to a sorted display state) - self.do_reload(); + self.reload_view(); }); this.view_manager.sidebar.set_toolbar(data.fields_view.toolbar); @@ -162,9 +167,9 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi * visible. * * @param {Object} fields fields_view_get's fields section - * @param {Array} groupby_columns columns the ListView is grouped by + * @param {Boolean} [grouped] Should the grouping columns (group and count) be displayed */ - setup_columns: function (fields, groupby_columns) { + setup_columns: function (fields, grouped) { var domain_computer = openerp.base.form.compute_domain; var noop = function () { return {}; }; @@ -192,7 +197,7 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi this.columns.push.apply( this.columns, _(this.fields_view.arch.children).map(field_to_column)); - if (groupby_columns) { + if (grouped) { this.columns.unshift({ id: '_group', tag: '', string: "Group", meta: true, attrs_for: function () { return {}; } @@ -257,26 +262,20 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi this.hidden = true; }, /** - * Reloads the search view based on the current settings (dataset & al) + * Reloads the list view based on the current settings (dataset & al) * - * @param {Array} [primary_columns] columns to bring to the front of the - * sequence + * @param {Boolean} [grouped] Should the list be displayed grouped */ - do_reload: function (primary_columns) { - // TODO: should just fields_view_get I think + reload_view: function (grouped) { var self = this; this.dataset.offset = 0; this.dataset.limit = false; - return this.rpc('/base/listview/fill', { - 'model': this.dataset.model, - 'id': this.view_id, - 'context': this.dataset.context, - 'domain': this.dataset.domain, - 'sort': this.dataset.sort && this.dataset.sort() - }, function (result) { - if (result.view) { - self.on_loaded({fields_view: result.view}, primary_columns); - } + return this.rpc('/base/listview/load', { + model: this.model, + view_id: this.view_id, + toolbar: !!this.flags.sidebar + }, function (field_view_get) { + self.on_loaded(field_view_get, grouped); }); }, /** @@ -298,17 +297,17 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi self.dataset.context = results.context; self.dataset.domain = results.domain; self.groups.datagroup = new openerp.base.DataGroup( - self.session, self.dataset.model, + self.session, self.model, results.domain, results.context, results.group_by); if (_.isEmpty(results.group_by) && !results.context['group_by_no_leaf']) { results.group_by = null; } - self.do_reload(results.group_by).then(function () { + self.reload_view(!!results.group_by).then(function () { self.$element.find('table').append( - self.groups.render(function () { - self.compute_aggregates();}));}); + self.groups.render(function () { + self.compute_aggregates();}));}); }); }, /** @@ -366,7 +365,6 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi * 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.select_record(null); }, /** @@ -435,7 +433,7 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi / collection.length); break; case 'sum': - value = (_(collection).chain() + value = (_(collection).chain() .filter(function (item) { return !_.isUndefined(item); }) .reduce(function (total, item) { @@ -491,7 +489,6 @@ openerp.base.ListView.List = Class.extend( /** @lends openerp.base.ListView.List */ init: function (opts) { var self = this; - // columns, rows, options this.options = opts.options; this.columns = opts.columns; @@ -603,11 +600,11 @@ openerp.base.ListView.Groups = Class.extend( /** @lends openerp.base.ListView.Gr * Provides events similar to those of * :js:class:`~openerp.base.ListView.List` */ - init: function (view, opts) { + init: function (view) { this.view = view; - this.options = opts.options; - this.columns = opts.columns; - this.datagroup = {}; + this.options = view.options; + this.columns = view.columns; + this.datagroup = null; this.sections = []; this.children = {}; @@ -701,7 +698,6 @@ openerp.base.ListView.Groups = Class.extend( /** @lends openerp.base.ListView.Gr } placeholder.appendChild($row[0]); - var $group_column = $('').appendTo($row); if (group.grouped_on) { // Don't fill this if group_by_no_leaf but no group_by @@ -784,18 +780,27 @@ openerp.base.ListView.Groups = Class.extend( /** @lends openerp.base.ListView.Gr this.bind_child_events(list); var d = new $.Deferred(); - this.view.rpc('/base/listview/fill', { - model: dataset.model, - id: this.view.view_id, - context: dataset.context, - domain: dataset.domain, - sort: dataset.sort && dataset.sort() - }, function (result) { - rows.splice(0, rows.length); - rows.push.apply(rows, result.records); - list.render(); - d.resolve(list); - }); + dataset.read_slice( + _.filter(_.pluck(this.columns, 'name'), _.identity), + 0, false, + function (records) { + var form_records = _(records).map(function (record) { + // TODO: colors handling + var form_data = {}, + form_record = {data: form_data}; + + _(record).each(function (value, key) { + form_data[key] = {value: value}; + }); + + return form_record; + }); + + rows.splice(0, rows.length); + rows.push.apply(rows, form_records); + list.render(); + d.resolve(list); + }); return d.promise(); }, render: function (post_render) { From fa284d7c09a5bee5425afb88a4e9d1316887a55f Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Fri, 27 May 2011 09:27:51 +0200 Subject: [PATCH 09/11] [FIX] typo in variable name bzr revid: xmo@openerp.com-20110527072751-38qzsszozbryf8g8 --- addons/base/static/src/js/list.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/addons/base/static/src/js/list.js b/addons/base/static/src/js/list.js index b46730b3511..c20e5637693 100644 --- a/addons/base/static/src/js/list.js +++ b/addons/base/static/src/js/list.js @@ -630,19 +630,19 @@ openerp.base.ListView.Groups = Class.extend( /** @lends openerp.base.ListView.Gr */ point_insertion: function (row) { var $row = $(row); - var red_letter_tbody = $row.closest('tbody')[0]; + var red_letter_tboday = $row.closest('tbody')[0]; var $next_siblings = $row.nextAll(); if ($next_siblings.length) { - var $root_kanal = $('').insertAfter(red_letter_tbody); + var $root_kanal = $('').insertAfter(red_letter_tboday); $root_kanal.append($next_siblings); this.elements.splice( - _.indexOf(this.elements, red_letter_tbody), + _.indexOf(this.elements, red_letter_tboday), 0, $root_kanal[0]); } - return red_letter_tbody; + return red_letter_tboday; }, open_group: function (e, group) { var row = e.currentTarget; From f86bc1c6a398d5ff5da9e517b92bb8cffeb5225d Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Fri, 27 May 2011 09:44:42 +0200 Subject: [PATCH 10/11] [IMP] extract handlers for ListView.Groups events into separate method, so they're not a pain to override bzr revid: xmo@openerp.com-20110527074442-2wjw9aya7m8t0tlp --- addons/base/static/src/js/list.js | 55 +++++++++++++++++++++--------- addons/base/static/src/js/views.js | 2 +- 2 files changed, 39 insertions(+), 18 deletions(-) diff --git a/addons/base/static/src/js/list.js b/addons/base/static/src/js/list.js index c20e5637693..05fc586a14d 100644 --- a/addons/base/static/src/js/list.js +++ b/addons/base/static/src/js/list.js @@ -78,25 +78,10 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi self.do_delete(ids); }, 'action': function (e, action_name, id, callback) { - var action = _.detect(self.columns, function (field) { - return field.name === action_name; - }); - if (!action) { return; } - self.execute_action( - action, self.dataset, self.session.action_manager, - id, function () { - if (callback) { - callback(); - } - }); + self.do_action(action_name, id, callback); }, 'row_link': function (e, index, id, dataset) { - _.extend(self.dataset, { - domain: dataset.domain, - context: dataset.context - }).read_slice([], null, null, function () { - self.select_record(index); - }); + self.do_activate_record(index, id, dataset); } }); }, @@ -358,6 +343,42 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi } this.compute_aggregates(records); }, + /** + * Handles action button signals on a record + * + * @param {String} name action name + * @param {Object} id id of the record the action should be called on + * @param {Function} callback should be called after the action is executed, if non-null + */ + do_action: function (name, id, callback) { + var action = _.detect(this.columns, function (field) { + return field.name === name; + }); + if (!action) { return; } + this.execute_action( + action, this.dataset, this.session.action_manager, + id, function () { + if (callback) { + callback(); + } + }); + }, + /** + * Handles the activation of a record (clicking on it) + * + * @param {Number} index index of the record in the dataset + * @param {Object} id identifier of the activated record + * @param {openobject.base.DataSet} dataset dataset in which the record is available (may not be the listview's dataset in case of nested groups) + */ + do_activate_record: function (index, id, dataset) { + var self = this; + _.extend(this.dataset, { + domain: dataset.domain, + context: dataset.context + }).read_slice([], 0, false, function () { + self.select_record(index); + }); + }, /** * Handles signal for the addition of a new record (can be a creation, * can be the addition from a remote source, ...) diff --git a/addons/base/static/src/js/views.js b/addons/base/static/src/js/views.js index bb52009ae40..d4ab23af0d0 100644 --- a/addons/base/static/src/js/views.js +++ b/addons/base/static/src/js/views.js @@ -356,7 +356,7 @@ openerp.base.View = openerp.base.Controller.extend({ * @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 {Object} [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) { From 4ee9ba6d57ed6b37adb426e9947542af034dccc7 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Fri, 27 May 2011 11:16:49 +0200 Subject: [PATCH 11/11] [ADD] some listview doc bzr revid: xmo@openerp.com-20110527091649-gafhkh5gahcxwy3u --- doc/source/development.rst | 99 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) diff --git a/doc/source/development.rst b/doc/source/development.rst index 514e094b689..bfb176cb5c5 100644 --- a/doc/source/development.rst +++ b/doc/source/development.rst @@ -281,6 +281,105 @@ abstract types, used to implement input widgets: .. TODO: insert Input, Field, Filter, and just about every Field subclass +List View ++++++++++ + +OpenERP Web's list views don't actually exist as such in OpenERP itself: a +list view is an OpenERP tree view in the ``view_mode`` form. + +The overall purpose of a list view is to display collections of objects in two +main forms: per-object, where each object is a row in and of itself, and +grouped, where multiple objects are represented with a single row providing +an aggregated view of all grouped objects. + +These two forms can be mixed within a single list view, if needed. + +The root of a list view is :js:class:`openerp.base.ListView`, which may need +to be overridden (partially or fully) to control list behavior in non-view +cases (when using a list view as sub-component of a form widget for instance). + +Creation and Initialization +""""""""""""""""""""""""""" + +As with most OpenERP Web views, the list view's +:js:func:`~openerp.base.ListView.init` takes quite a number of arguments. + +While most of them are the standard view constructor arguments +(``view_manager``, ``session``, ``element_id``, ``dataset`` and an +optional ``view_id``), the list view adds a number of options for basic +customization (without having to override methods or templates): + +``selectable`` (default: ``true``) + Indicates that the list view should allow records to be selected + individually. Displays selection check boxes to the left of all record rows, + and allows for the triggering of the + :ref:`selection event `. +``deletable`` (default: ``true``) + Indicates that the list view should allow records to be removed + individually. Displays a deletion button to the right of all record rows, + and allows for the triggering of the + :ref:`deletion event `. +``header`` (default: ``true``) + Indicates that list columns should bear a header sporting their name (for + non-action columns). +``addable`` (default: ``"New"``) + Indicates that a record addition/creation button should be displayed in + the list's header, along with its label. Also allows for the triggering of + the :ref:`record addition event `. +``sortable`` (default: ``true``) + Indicates that the list view can be sorted per-column (by clicking on its + column headers). + + .. TODO: event? +``reorderable`` (default: ``true``) + Indicates that the list view records can be reordered (and re-sequenced) + by drag and drop. + + .. TODO: event? + +Events +"""""" +.. _listview-events-addition: + +Addition +'''''''' +The addition event is used to add a record to an existing list view. The +default behavior is to switch to the form view, on a new record. + +Addition behavior can be overridden by replacing the +:js:func:`~openerp.base.ListView.do_add_record` method. + +.. _listview-events-selection: + +Selection +''''''''' +The selection event is triggered when a given record is selected in the list +view. + +It can be overridden by replacing the +:js:func:`~openerp.base.ListView.do_select` method. + +The default behavior is simply to hide or display the list-wise deletion button +depending on whether there are selected records or not. + +.. _listview-events-deletion: + +Deletion +'''''''' +The deletion event is triggered when the user tries to remove 1..n records from +the list view, either individually or globally (via the header button). + +Deletion can be overridden by replacing the +:js:func:`~openerp.base.ListView.do_delete` method. By default, this method +calls :js:func:`~openerp.base.DataSet.unlink` in order to remove the records +entirely. + +.. note:: the list-wise deletion button (next to the record addition button) + simply proxies to :js:func:`~openerp.base.ListView.do_delete` after + obtaining all selected record ids, but it is possible to override it + alone by replacing + :js:func:`~openerp.base.ListView.do_delete_selected`. + Internal API Doc ----------------