[IMP] final fixes to list view/groups for behavior which should be correct in list view itself:

* Handle aggregate fields (at the list level)
* remove color handling for now, go back to using vanilla datasets for fetching records

TODO: Ensure that APIs available are useable for third-parties to use list views as components, or fix those APIs

bzr revid: xmo@openerp.com-20110527092811-ldxd4oufvjtqvha3
This commit is contained in:
Xavier Morel 2011-05-27 11:28:11 +02:00
commit d7e17b06cf
8 changed files with 395 additions and 155 deletions

View File

@ -585,56 +585,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')

View File

@ -552,6 +552,17 @@ background: linear-gradient(top, #ffffff 0%,#d8d8d8 11%,#afafaf 86%,#333333 91%,
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
@ -572,11 +583,13 @@ background: linear-gradient(top, #ffffff 0%,#d8d8d8 11%,#afafaf 86%,#333333 91%,
-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;

View File

@ -104,7 +104,7 @@ openerp.base.ContainerDataGroup = openerp.base.DataGroup.extend(
|| key === field_name + '_count') {
return;
}
aggregates[key] = value;
aggregates[key] = value || 0;
});
return {

View File

@ -1080,7 +1080,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();
}
}
});
@ -1090,11 +1090,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.
@ -1115,7 +1115,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();
});
@ -1188,7 +1188,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;
};
});

View File

@ -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,41 +55,35 @@ 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, 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);
},
'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);
}
});
},
/**
* View startup method, the default behavior is to set the ``oe-listview``
@ -100,11 +93,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
@ -127,15 +116,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 = {}; }
@ -152,7 +141,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);
@ -163,9 +152,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 {}; };
@ -193,7 +182,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 {}; }
@ -206,6 +195,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
@ -245,26 +247,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);
});
},
/**
@ -286,16 +282,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.$element.find('table').append(self.groups.render());
});
self.reload_view(!!results.group_by).then(function () {
self.$element.find('table').append(
self.groups.render(function () {
self.compute_aggregates();}));});
});
},
/**
@ -330,6 +327,58 @@ 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);
if (!records.length) {
this.compute_aggregates();
return;
}
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, ...)
@ -337,14 +386,96 @@ 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);
},
/**
* 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);
},
/**
* 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').empty();
_(this.aggregate_columns).each(function (column) {
$(_.sprintf(
"<span>%s: %.2f</span>",
column.label, aggregation[column.field]))
.appendTo($footer);
});
}
// TODO: implement reorder (drag and drop rows)
});
@ -378,7 +509,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;
@ -389,7 +519,9 @@ 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 +554,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.
@ -460,6 +599,16 @@ 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) {
var record = {};
_(row.data).each(function (obj, key) {
record[key] = obj.value;
});
return record;
});
}
// drag and drop
// editable?
@ -472,11 +621,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 = {};
@ -502,19 +651,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 = $('<tbody>').insertAfter(red_letter_tbody);
var $root_kanal = $('<tbody>').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;
@ -570,7 +719,6 @@ openerp.base.ListView.Groups = Class.extend( /** @lends openerp.base.ListView.Gr
}
placeholder.appendChild($row[0]);
var $group_column = $('<th>').appendTo($row);
if (group.grouped_on) {
// Don't fill this if group_by_no_leaf but no group_by
@ -615,7 +763,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 () {
@ -652,46 +801,62 @@ 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 () {
render: function (post_render) {
var self = this;
var $element = $('<tbody>');
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;
},
/**
* 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) {
@ -699,6 +864,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();
}
});
};

View File

@ -357,7 +357,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) {

View File

@ -156,7 +156,7 @@
<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">
<tr>
<th t-if="actions_span" t-att-colspan="actions_span"
class="oe-actions">
<t t-if="flags.action_buttons !== false">
@ -165,7 +165,7 @@
<t t-esc="options.addable"/>
</button>
<button type="button" id="oe-list-delete"
t-if="options.deletable">
t-if="options.selectable and options.deletable">
Delete
</button>
</t>
@ -205,6 +205,13 @@
<th t-if="options.deletable"/>
</tr>
</thead>
<tfoot class="ui-widget-header" t-if="aggregate_columns.length">
<tr>
<td t-att-colspan="columns_count" class='oe-list-footer'>
</td>
</tr>
</tfoot>
</table>
<t t-name="ListView.rows" t-foreach="rows" t-as="row">
<t t-call="ListView.row">

View File

@ -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 <listview-events-selection>`.
``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 <listview-events-deletion>`.
``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 <listview-events-addition>`.
``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
----------------