[FIX] split formatting of list cells to formatting objects
simpler to override in order to have new list field widgets bzr revid: xmo@openerp.com-20120808103404-jj6w04x2mp2lrwl1
This commit is contained in:
parent
2447e00d22
commit
5c225be25e
|
@ -271,84 +271,4 @@ instance.web.auto_date_to_str = function(value, type) {
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Formats a provided cell based on its field type. Most of the field types
|
||||
* return a correctly formatted value, but some tags and fields are
|
||||
* special-cased in their handling:
|
||||
*
|
||||
* * buttons will return an actual ``<button>`` tag with a bunch of error handling
|
||||
*
|
||||
* * boolean fields will return a checkbox input, potentially disabled
|
||||
*
|
||||
* * binary fields will return a link to download the binary data as a file
|
||||
*
|
||||
* @param {Object} row_data record whose values should be displayed in the cell
|
||||
* @param {Object} column column descriptor
|
||||
* @param {"button"|"field"} column.tag base control type
|
||||
* @param {String} column.type widget type for a field control
|
||||
* @param {String} [column.string] button label
|
||||
* @param {String} [column.icon] button icon
|
||||
* @param {Object} [options]
|
||||
* @param {String} [options.value_if_empty=''] what to display if the field's value is ``false``
|
||||
* @param {Boolean} [options.process_modifiers=true] should the modifiers be computed ?
|
||||
* @param {String} [options.model] current record's model
|
||||
* @param {Number} [options.id] current record's id
|
||||
*
|
||||
*/
|
||||
instance.web.format_cell = function (row_data, column, options) {
|
||||
options = options || {};
|
||||
var attrs = {};
|
||||
if (options.process_modifiers !== false) {
|
||||
attrs = column.modifiers_for(row_data);
|
||||
}
|
||||
if (attrs.invisible) { return ''; }
|
||||
|
||||
if (column.tag === 'button') {
|
||||
return _.template('<button type="button" title="<%-title%>" <%=additional_attributes%> >' +
|
||||
'<img src="<%-prefix%>/web/static/src/img/icons/<%-icon%>.png" alt="<%-alt%>"/>' +
|
||||
'</button>', {
|
||||
title: column.string || '',
|
||||
additional_attributes: isNaN(row_data["id"].value) && instance.web.BufferedDataSet.virtual_id_regex.test(row_data["id"].value) ?
|
||||
'disabled="disabled" class="oe_list_button_disabled"' : '',
|
||||
prefix: instance.connection.prefix,
|
||||
icon: column.icon,
|
||||
alt: column.string || ''
|
||||
});
|
||||
}
|
||||
if (!row_data[column.id]) {
|
||||
return options.value_if_empty === undefined ? '' : options.value_if_empty;
|
||||
}
|
||||
|
||||
switch (column.widget || column.type) {
|
||||
case "boolean":
|
||||
return _.str.sprintf('<input type="checkbox" %s disabled="disabled"/>',
|
||||
row_data[column.id].value ? 'checked="checked"' : '');
|
||||
case "binary":
|
||||
var text = _t("Download"),
|
||||
download_url = _.str.sprintf('/web/binary/saveas?session_id=%s&model=%s&field=%s&id=%d', instance.connection.session_id, options.model, column.id, options.id);
|
||||
if (column.filename) {
|
||||
download_url += '&filename_field=' + column.filename;
|
||||
if (row_data[column.filename]) {
|
||||
text = _.str.sprintf(_t("Download \"%s\""), instance.web.format_value(
|
||||
row_data[column.filename].value, {type: 'char'}));
|
||||
}
|
||||
}
|
||||
return _.template('<a href="<%-href%>"><%-text%></a> (%<-size%>)', {
|
||||
text: text,
|
||||
href: download_url,
|
||||
size: row_data[column.id].value
|
||||
});
|
||||
case 'progressbar':
|
||||
return _.template(
|
||||
'<progress value="<%-value%>" max="100"><%-value%>%</progress>', {
|
||||
value: _.str.sprintf("%.0f", row_data[column.id].value || 0)
|
||||
});
|
||||
case 'handle':
|
||||
return '<div class="oe_list_handle">';
|
||||
}
|
||||
|
||||
return _.escape(instance.web.format_value(
|
||||
row_data[column.id].value, column, options.value_if_empty));
|
||||
}
|
||||
|
||||
};
|
||||
|
|
|
@ -399,73 +399,23 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi
|
|||
* @param {Boolean} [grouped] Should the grouping columns (group and count) be displayed
|
||||
*/
|
||||
setup_columns: function (fields, grouped) {
|
||||
var domain_computer = instance.web.form.compute_domain;
|
||||
|
||||
var noop = function () { return {}; };
|
||||
var field_to_column = function (field) {
|
||||
var name = field.attrs.name;
|
||||
var column = _.extend({id: name, tag: field.tag},
|
||||
fields[name], field.attrs);
|
||||
// modifiers computer
|
||||
if (column.modifiers) {
|
||||
var modifiers = JSON.parse(column.modifiers);
|
||||
column.modifiers_for = function (fields) {
|
||||
var out = {};
|
||||
|
||||
for (var attr in modifiers) {
|
||||
if (!modifiers.hasOwnProperty(attr)) { continue; }
|
||||
var modifier = modifiers[attr];
|
||||
out[attr] = _.isBoolean(modifier)
|
||||
? modifier
|
||||
: domain_computer(modifier, fields);
|
||||
}
|
||||
|
||||
return out;
|
||||
};
|
||||
if (modifiers['tree_invisible']) {
|
||||
column.invisible = '1';
|
||||
} else {
|
||||
delete column.invisible;
|
||||
}
|
||||
column.modifiers = modifiers;
|
||||
} else {
|
||||
column.modifiers_for = noop;
|
||||
column.modifiers = {};
|
||||
}
|
||||
return column;
|
||||
};
|
||||
|
||||
var registry = instance.web.list.columns;
|
||||
this.columns.splice(0, this.columns.length);
|
||||
this.columns.push.apply(
|
||||
this.columns,
|
||||
_(this.fields_view.arch.children).map(field_to_column));
|
||||
this.columns.push.apply(this.columns,
|
||||
_(this.fields_view.arch.children).map(function (field) {
|
||||
var id = field.attrs.name;
|
||||
return registry.for_(id, fields[id], field);
|
||||
}));
|
||||
if (grouped) {
|
||||
this.columns.unshift({
|
||||
id: '_group', tag: '', string: _t("Group"), meta: true,
|
||||
modifiers_for: function () { return {}; },
|
||||
modifiers: {}
|
||||
});
|
||||
this.columns.unshift(
|
||||
new instance.web.list.MetaColumn('_group', _t("Group")));
|
||||
}
|
||||
|
||||
this.visible_columns = _.filter(this.columns, function (column) {
|
||||
return column.invisible !== '1';
|
||||
});
|
||||
|
||||
this.aggregate_columns = _(this.visible_columns)
|
||||
.map(function (column) {
|
||||
if (column.type !== 'integer' && column.type !== 'float') {
|
||||
return {};
|
||||
}
|
||||
var aggregation_func = column['group_operator'] || 'sum';
|
||||
if (!(aggregation_func in column)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return _.extend({}, column, {
|
||||
'function': aggregation_func,
|
||||
label: column[aggregation_func]
|
||||
});
|
||||
});
|
||||
this.aggregate_columns = _(this.visible_columns).invoke('to_aggregate');
|
||||
},
|
||||
/**
|
||||
* Used to handle a click on a table row, if no other handler caught the
|
||||
|
@ -820,9 +770,7 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi
|
|||
}
|
||||
|
||||
$footer_cells.filter(_.str.sprintf('[data-field=%s]', column.id))
|
||||
.html(instance.web.format_cell(aggregation, column, {
|
||||
process_modifiers: false
|
||||
}));
|
||||
.html(column.format(aggregation, { process_modifiers: false }));
|
||||
});
|
||||
},
|
||||
get_selected_ids: function() {
|
||||
|
@ -1058,7 +1006,7 @@ instance.web.ListView.List = instance.web.Class.extend( /** @lends instance.web.
|
|||
});
|
||||
}
|
||||
}
|
||||
return instance.web.format_cell(record.toForm().data, column, {
|
||||
return column.format(record.toForm().data, {
|
||||
model: this.dataset.model,
|
||||
id: record.get('id')
|
||||
});
|
||||
|
@ -1345,10 +1293,9 @@ instance.web.ListView.Groups = instance.web.Class.extend( /** @lends instance.we
|
|||
}
|
||||
var group_label;
|
||||
try {
|
||||
group_label = instance.web.format_cell(
|
||||
row_data, group_column, {
|
||||
value_if_empty: _t("Undefined"),
|
||||
process_modifiers: false
|
||||
group_label = group_column.format(row_data, {
|
||||
value_if_empty: _t("Undefined"),
|
||||
process_modifiers: false
|
||||
});
|
||||
} catch (e) {
|
||||
group_label = row_data[group_column.id].value;
|
||||
|
@ -1372,7 +1319,7 @@ instance.web.ListView.Groups = instance.web.Class.extend( /** @lends instance.we
|
|||
$row.append('<td>');
|
||||
}
|
||||
_(self.columns).chain()
|
||||
.filter(function (column) {return !column.invisible;})
|
||||
.filter(function (column) { return column.invisible !== '1'; })
|
||||
.each(function (column) {
|
||||
if (column.meta) {
|
||||
// do not do anything
|
||||
|
@ -1380,8 +1327,7 @@ instance.web.ListView.Groups = instance.web.Class.extend( /** @lends instance.we
|
|||
var r = {};
|
||||
r[column.id] = {value: group.aggregates[column.id]};
|
||||
$('<td class="oe_number">')
|
||||
.html(instance.web.format_cell(
|
||||
r, column, {process_modifiers: false}))
|
||||
.html(column.format(r, {process_modifiers: false}))
|
||||
.appendTo($row);
|
||||
} else {
|
||||
$row.append('<td>');
|
||||
|
@ -1979,6 +1925,204 @@ instance.web.list = {
|
|||
Events: Events,
|
||||
Record: Record,
|
||||
Collection: Collection
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Registry for column objects used to format table cells (and some other tasks
|
||||
* e.g. aggregation computations).
|
||||
*
|
||||
* Maps a field or button to a Column type via its ``$tag.$widget``,
|
||||
* ``$tag.$type`` or its ``$tag`` (alone).
|
||||
*
|
||||
* This specific registry has a dedicated utility method ``for_`` taking a
|
||||
* field (from fields_get/fields_view_get.field) and a node (from a view) and
|
||||
* returning the right object *already instantiated from the data provided*.
|
||||
*
|
||||
* @type {instance.web.Registry}
|
||||
*/
|
||||
instance.web.list.columns = new instance.web.Registry({
|
||||
'field': 'instance.web.list.Column',
|
||||
'field.boolean': 'instance.web.list.Boolean',
|
||||
'field.binary': 'instance.web.list.Binary',
|
||||
'field.progressbar': 'instance.web.list.ProgressBar',
|
||||
'field.handle': 'instance.web.list.Handle',
|
||||
'button': 'instance.web.list.Button',
|
||||
});
|
||||
instance.web.list.columns.for_ = function (id, field, node) {
|
||||
var description = _.extend({tag: node.tag}, field, node.attrs);
|
||||
var tag = description.tag;
|
||||
var Type = this.get_any([
|
||||
tag + '.' + description.widget,
|
||||
tag + '.'+ description.type,
|
||||
tag
|
||||
]);
|
||||
return new Type(id, node.tag, description)
|
||||
};
|
||||
|
||||
instance.web.list.Column = instance.web.Class.extend({
|
||||
init: function (id, tag, attrs) {
|
||||
_.extend(attrs, {
|
||||
id: id,
|
||||
tag: tag
|
||||
});
|
||||
|
||||
this.modifiers = attrs.modifiers ? JSON.parse(attrs.modifiers) : {};
|
||||
delete attrs.modifiers;
|
||||
_.extend(this, attrs);
|
||||
|
||||
if (this.modifiers['tree_invisible']) {
|
||||
this.invisible = '1';
|
||||
} else { delete this.invisible; }
|
||||
},
|
||||
modifiers_for: function (fields) {
|
||||
var out = {};
|
||||
var domain_computer = instance.web.form.compute_domain;
|
||||
|
||||
for (var attr in this.modifiers) {
|
||||
if (!this.modifiers.hasOwnProperty(attr)) { continue; }
|
||||
var modifier = this.modifiers[attr];
|
||||
out[attr] = _.isBoolean(modifier)
|
||||
? modifier
|
||||
: domain_computer(modifier, fields);
|
||||
}
|
||||
|
||||
return out;
|
||||
},
|
||||
to_aggregate: function () {
|
||||
if (this.type !== 'integer' && this.type !== 'float') {
|
||||
return {};
|
||||
}
|
||||
var aggregation_func = this['group_operator'] || 'sum';
|
||||
if (!(aggregation_func in this)) {
|
||||
return {};
|
||||
}
|
||||
var C = function (label, fn) {
|
||||
this['function'] = fn;
|
||||
this.label = label;
|
||||
};
|
||||
C.prototype = this;
|
||||
return new C(aggregation_func, this[aggregation_func]);
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param row_data record whose values should be displayed in the cell
|
||||
* @param {Object} [options]
|
||||
* @param {String} [options.value_if_empty=''] what to display if the field's value is ``false``
|
||||
* @param {Boolean} [options.process_modifiers=true] should the modifiers be computed ?
|
||||
* @param {String} [options.model] current record's model
|
||||
* @param {Number} [options.id] current record's id
|
||||
* @return {String}
|
||||
*/
|
||||
format: function (row_data, options) {
|
||||
options = options || {};
|
||||
var attrs = {};
|
||||
if (options.process_modifiers !== false) {
|
||||
attrs = this.modifiers_for(row_data);
|
||||
}
|
||||
if (attrs.invisible) { return ''; }
|
||||
|
||||
if (!row_data[this.id]) {
|
||||
return options.value_if_empty === undefined
|
||||
? ''
|
||||
: options.value_if_empty;
|
||||
}
|
||||
return this._format(row_data, options);
|
||||
},
|
||||
/**
|
||||
* Method to override in order to provide alternative HTML content for the
|
||||
* cell. Column._format will simply call ``instance.web.format_value`` and
|
||||
* escape the output.
|
||||
*
|
||||
* The output of ``_format`` will *not* be escaped by ``format``, any
|
||||
* escaping *must be done* by ``format``.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_format: function (row_data, options) {
|
||||
return _.escape(instance.web.format_value(
|
||||
row_data[this.id].value, this, options.value_if_empty));
|
||||
}
|
||||
});
|
||||
instance.web.list.MetaColumn = instance.web.list.Column.extend({
|
||||
meta: true,
|
||||
init: function (id, string) {
|
||||
this._super(id, '', {string: string});
|
||||
}
|
||||
});
|
||||
instance.web.list.Button = instance.web.list.Column.extend({
|
||||
/**
|
||||
* Return an actual ``<button>`` tag
|
||||
*/
|
||||
format: function (row_data, options) {
|
||||
return _.template('<button type="button" title="<%-title%>" <%=additional_attributes%> >' +
|
||||
'<img src="<%-prefix%>/web/static/src/img/icons/<%-icon%>.png" alt="<%-alt%>"/>' +
|
||||
'</button>', {
|
||||
title: this.string || '',
|
||||
additional_attributes: isNaN(row_data["id"].value) && instance.web.BufferedDataSet.virtual_id_regex.test(row_data["id"].value) ?
|
||||
'disabled="disabled" class="oe_list_button_disabled"' : '',
|
||||
prefix: instance.connection.prefix,
|
||||
icon: this.icon,
|
||||
alt: this.string || ''
|
||||
});
|
||||
}
|
||||
});
|
||||
instance.web.list.Boolean = instance.web.list.Column.extend({
|
||||
/**
|
||||
* Return a potentially disabled checkbox input
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_format: function (row_data, options) {
|
||||
return _.str.sprintf('<input type="checkbox" %s disabled="disabled"/>',
|
||||
row_data[this.id].value ? 'checked="checked"' : '');
|
||||
}
|
||||
});
|
||||
instance.web.list.Binary = instance.web.list.Column.extend({
|
||||
/**
|
||||
* Return a link to the binary data as a file
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_format: function (row_data, options) {
|
||||
var text = _t("Download");
|
||||
var download_url = _.str.sprintf(
|
||||
'/web/binary/saveas?session_id=%s&model=%s&field=%s&id=%d',
|
||||
instance.connection.session_id, options.model, this.id, options.id);
|
||||
if (this.filename) {
|
||||
download_url += '&filename_field=' + this.filename;
|
||||
if (row_data[this.filename]) {
|
||||
text = _.str.sprintf(_t("Download \"%s\""), instance.web.format_value(
|
||||
row_data[this.filename].value, {type: 'char'}));
|
||||
}
|
||||
}
|
||||
return _.template('<a href="<%-href%>"><%-text%></a> (%<-size%>)', {
|
||||
text: text,
|
||||
href: download_url,
|
||||
size: row_data[this.id].value
|
||||
});
|
||||
}
|
||||
});
|
||||
instance.web.list.ProgressBar = instance.web.list.Column.extend({
|
||||
/**
|
||||
* Return a formatted progress bar display
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_format: function (row_data, options) {
|
||||
return _.template(
|
||||
'<progress value="<%-value%>" max="100"><%-value%>%</progress>', {
|
||||
value: _.str.sprintf("%.0f", row_data[this.id].value || 0)
|
||||
});
|
||||
}
|
||||
});
|
||||
instance.web.list.Handle = instance.web.list.Column.extend({
|
||||
/**
|
||||
* Return styling hooks for a drag handle
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_format: function (row_data, options) {
|
||||
return '<div class="oe_list_handle">';
|
||||
}
|
||||
});
|
||||
};
|
||||
// vim:et fdc=0 fdl=0 foldnestmax=3 fdm=syntax:
|
||||
|
|
|
@ -60,6 +60,56 @@ various situations:
|
|||
|
||||
Selector cells
|
||||
|
||||
Columns display customization
|
||||
+++++++++++++++++++++++++++++
|
||||
|
||||
The list view provides a registry to
|
||||
:js:class:`openerp.web.list.Column` objects allowing for the
|
||||
customization of a column's display (e.g. so that a binary field is
|
||||
rendered as a link to the binary file directly in the list view).
|
||||
|
||||
The registry is ``instance.web.list.columns``, the keys are of the
|
||||
form ``tag.type`` where ``tag`` can be ``field`` or ``button``, and
|
||||
``type`` can be either the field's type or the field's ``@widget`` (in
|
||||
the view).
|
||||
|
||||
Most of the time, you'll want to define a ``tag.widget`` key
|
||||
(e.g. ``field.progressbar``).
|
||||
|
||||
.. js:class:: openerp.web.list.Column(id, tag, attrs)
|
||||
|
||||
.. js:method:: openerp.web.list.Column.format(record_data, options)
|
||||
|
||||
Top-level formatting method, returns an empty string if the
|
||||
column is invisible (unless the ``process_modifiers=false``
|
||||
option is provided); returns ``options.value_if_empty`` or an
|
||||
empty string if there is no value in the record for the
|
||||
column.
|
||||
|
||||
Otherwise calls :js:func:`~openerp.web.list.Column._format`
|
||||
and returns its result.
|
||||
|
||||
This method only needs to be overridden if the column has no
|
||||
concept of values (and needs to bypass that check), for a
|
||||
button for instance.
|
||||
|
||||
Otherwise, custom columns should generally override
|
||||
:js:func:`~openerp.web.list.Column._format` instead.
|
||||
|
||||
.. js:method:: openerp,web.list.Column._format(record_data, options)
|
||||
|
||||
Never called directly, called if the column is visible and has
|
||||
a value.
|
||||
|
||||
The default implementation calls
|
||||
:js:func:`~openerp.web.format_value` and htmlescapes the
|
||||
result (via ``_.escape``).
|
||||
|
||||
Note that the implementation of
|
||||
:js:func:`~openerp.web.list.Column._format` *must* escape the
|
||||
data provided to it, its output will *not* be escaped by
|
||||
:js:func:`~openerp.web.list.Column.format`.
|
||||
|
||||
Editable list view
|
||||
++++++++++++++++++
|
||||
|
||||
|
|
Loading…
Reference in New Issue