From 5d416152174d740aaa4b4e86a98f4dbd421ac541 Mon Sep 17 00:00:00 2001 From: niv-openerp Date: Wed, 8 Aug 2012 11:15:32 +0200 Subject: [PATCH 01/14] [FIX] fixed problems with images in kanban o2m bzr revid: nicolas.vanhoren@openerp.com-20120808091532-zjnvgpx33nkwj1z8 --- addons/web/static/src/js/view_form.js | 2 +- addons/web_kanban/static/src/js/kanban.js | 15 +++++++++------ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/addons/web/static/src/js/view_form.js b/addons/web/static/src/js/view_form.js index c3f941305d5..8276a1b219c 100644 --- a/addons/web/static/src/js/view_form.js +++ b/addons/web/static/src/js/view_form.js @@ -4606,7 +4606,7 @@ instance.web.form.FieldBinaryImage = instance.web.form.FieldBinary.extend({ }, render_value: function() { var url; - if (this.get('value') && this.get('value').substr(0, 10).indexOf(' ') == -1) { + if (this.get('value') && ! /^\d+(\.\d*)? \w+$/.test(this.get('value'))) { url = 'data:image/png;base64,' + this.get('value'); } else if (this.get('value')) { url = '/web/binary/image?session_id=' + this.session.session_id + '&model=' + diff --git a/addons/web_kanban/static/src/js/kanban.js b/addons/web_kanban/static/src/js/kanban.js index 4856c5d174a..8761b88690f 100644 --- a/addons/web_kanban/static/src/js/kanban.js +++ b/addons/web_kanban/static/src/js/kanban.js @@ -750,14 +750,17 @@ instance.web_kanban.KanbanRecord = instance.web.OldWidget.extend({ return 'http://www.gravatar.com/avatar/' + email_md5 + '.png?s=' + size + '&d=' + default_; }, kanban_image: function(model, field, id, cache) { - id = id || ''; - var url = instance.connection.prefix + '/web/binary/image?session_id=' + this.session.session_id + '&model=' + model + '&field=' + field + '&id=' + id; - if (cache !== undefined) { - // Set the cache duration in seconds. - url += '&cache=' + parseInt(cache, 10); - } + var url; if (this.record[field] && this.record[field].value && ! /^\d+(\.\d*)? \w+$/.test(this.record[field].value)) { url = 'data:image/png;base64,' + this.record[field].value; + } else if (this.record[field] && ! this.record[field].value) { + url = "/web/static/src/img/placeholder.png"; + } else { + url = instance.connection.prefix + '/web/binary/image?session_id=' + this.session.session_id + '&model=' + model + '&field=' + field + '&id=' + id; + if (cache !== undefined) { + // Set the cache duration in seconds. + url += '&cache=' + parseInt(cache, 10); + } } return url; }, From 7527c01df0ed264a8362576daf643f9d3bb75086 Mon Sep 17 00:00:00 2001 From: Fabien Meghazi Date: Wed, 8 Aug 2012 11:47:43 +0200 Subject: [PATCH 02/14] [FIX] Restore translatable fields bzr revid: fme@openerp.com-20120808094743-gw5boichxy3ka737 --- addons/web/static/src/js/views.js | 29 ++++++++++++----------------- addons/web/static/src/xml/base.xml | 13 +++++++------ 2 files changed, 19 insertions(+), 23 deletions(-) diff --git a/addons/web/static/src/js/views.js b/addons/web/static/src/js/views.js index ae46d3d9ff1..e9e8bc500e5 100644 --- a/addons/web/static/src/js/views.js +++ b/addons/web/static/src/js/views.js @@ -1079,14 +1079,15 @@ instance.web.TranslateDialog = instance.web.Dialog.extend({ this['on_button_' + _t("Close")] = this.on_btn_close; this._super(view, { width: '80%', - height: '80%' + height: '80%', + destroy_on_close: false, }); this.view = view; this.view_type = view.fields_view.type || ''; this.$fields_form = null; this.$view_form = null; this.$sidebar_form = null; - this.translatable_fields_keys = _.map(this.view.translatable_fields || [], function(i) { return i.name }); + this.translatable_fields_keys = _.map(this.view.translatable_fields || [], function(i) { return i.name; }); this.languages = null; this.languages_loaded = $.Deferred(); (new instance.web.DataSetSearch(this, 'res.lang', this.view.dataset.get_context(), @@ -1117,7 +1118,8 @@ instance.web.TranslateDialog = instance.web.Dialog.extend({ deffered.push(deff); var callback = function(values) { _.each(self.translatable_fields_keys, function(f) { - self.$fields_form.find('.oe_trad_field[name="' + lg.code + '-' + f + '"]').val(values[0][f] || '').attr('data-value', values[0][f] || ''); + var value = values[0][f] || ''; + self.$fields_form.find('.oe_trad_field[name="' + lg.code + '-' + f + '"]').val(value).attr('data-value', value); }); deff.resolve(); }; @@ -1128,31 +1130,24 @@ instance.web.TranslateDialog = instance.web.Dialog.extend({ }); callback([values]); } else { - self.rpc('/web/dataset/get', { - model: self.view.dataset.model, - ids: [self.view.datarecord.id], - fields: self.translatable_fields_keys, - context: self.view.dataset.get_context({ - 'lang': lg.code - })}, callback); + self.view.dataset.read_ids([self.view.datarecord.id], self.translatable_fields_keys, { + context: { 'lang': lg.code } + }).then(callback); } }); $.when.apply(null, deffered).then(callback); }, open: function(field) { - var self = this, - sup = this._super; + var self = this; + this._super(); $.when(this.languages_loaded).then(function() { if (self.view.translatable_fields && self.view.translatable_fields.length) { self.do_load_fields_values(function() { - sup.call(self); - // desactivated because it created an exception, plus it does not seem very useful - /* if (field) { var $field_input = self.$element.find('tr[data-field="' + field.name + '"] td:nth-child(2) *:first-child'); self.$element.scrollTo($field_input); $field_input.focus(); - }*/ + } }); } else { sup.call(self); @@ -1241,7 +1236,7 @@ instance.web.View = instance.web.Widget.extend({ }, open_translate_dialog: function(field) { if (!this.translate_dialog) { - this.translate_dialog = new instance.web.TranslateDialog(this).start(); + this.translate_dialog = new instance.web.TranslateDialog(this); } this.translate_dialog.open(field); }, diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml index 4e3ffa0acb4..35d42d624c5 100644 --- a/addons/web/static/src/xml/base.xml +++ b/addons/web/static/src/xml/base.xml @@ -930,9 +930,10 @@ t-att-tabindex="widget.node.attrs.tabindex" t-att-autofocus="widget.node.attrs.autofocus" t-att-placeholder="! widget.get('effective_readonly') ? widget.node.attrs.placeholder : ''" - > - + > @@ -1193,11 +1194,11 @@ - + - - + + From f92a925ec80508fe7a80584fe0a19db48b7fad5f Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Tue, 7 Aug 2012 15:43:43 +0200 Subject: [PATCH 03/14] [CHG] resequence on arbitrary field with @widget=handle bzr revid: xmo@openerp.com-20120807134343-ch16bcp5s7goqxsl --- addons/web/static/src/css/base.css | 13 ++++++++++++ addons/web/static/src/css/base.sass | 5 +++++ addons/web/static/src/js/formats.js | 2 ++ addons/web/static/src/js/view_list.js | 29 ++++++++++++++++++++------- 4 files changed, 42 insertions(+), 7 deletions(-) diff --git a/addons/web/static/src/css/base.css b/addons/web/static/src/css/base.css index 434ee9377ab..295f2689a6d 100644 --- a/addons/web/static/src/css/base.css +++ b/addons/web/static/src/css/base.css @@ -2477,6 +2477,19 @@ .openerp .oe_list_content .numeric input { text-align: right; } +.openerp .oe_list_content .oe_list_field_handle { + width: 1em; +} +.openerp .oe_list_content .oe_list_field_handle .oe_list_handle { + font-size: 1px; + letter-spacing: -1px; + color: transparent; +} +.openerp .oe_list_content .oe_list_field_handle .oe_list_handle:before { + font: 21px "mnmliconsRegular"; + content: "ö"; + color: #404040; +} .openerp .tree_header { background-color: #f0f0f0; border-bottom: 1px solid #cacaca; diff --git a/addons/web/static/src/css/base.sass b/addons/web/static/src/css/base.sass index 949befdef77..8005449b226 100644 --- a/addons/web/static/src/css/base.sass +++ b/addons/web/static/src/css/base.sass @@ -1926,6 +1926,11 @@ $sheet-max-width: 860px width: 82px input text-align: right + .oe_list_field_handle + width: 1em + .oe_list_handle + @include text-to-icon("ö") + // }}} // Tree view {{{ .tree_header diff --git a/addons/web/static/src/js/formats.js b/addons/web/static/src/js/formats.js index 6ceb7090e44..6aae9677ec4 100644 --- a/addons/web/static/src/js/formats.js +++ b/addons/web/static/src/js/formats.js @@ -343,6 +343,8 @@ instance.web.format_cell = function (row_data, column, options) { '<%-value%>%', { value: _.str.sprintf("%.0f", row_data[column.id].value || 0) }); + case 'handle': + return '
'; } return _.escape(instance.web.format_value( diff --git a/addons/web/static/src/js/view_list.js b/addons/web/static/src/js/view_list.js index 4bce8d639f8..d78a732fc58 100644 --- a/addons/web/static/src/js/view_list.js +++ b/addons/web/static/src/js/view_list.js @@ -1468,18 +1468,31 @@ instance.web.ListView.Groups = instance.web.Class.extend( /** @lends instance.we }, setup_resequence_rows: function (list, dataset) { // drag and drop enabled if list is not sorted and there is a - // "sequence" column in the view. + // visible column with @widget=handle or "sequence" column in the view. if ((dataset.sort && dataset.sort()) || !_(this.columns).any(function (column) { - return column.name === 'sequence'; })) { + return column.widget === 'handle' + || column.name === 'sequence'; })) { return; } + var sequence_field = _(this.columns).find(function (c) { + return c.widget === 'handle'; + }); + var seqname = sequence_field ? sequence_field.name : 'sequence'; + // ondrop, move relevant record & fix sequences list.$current.sortable({ axis: 'y', items: '> tr[data-id]', - containment: 'parent', - helper: 'clone', + helper: 'clone' + }); + if (sequence_field) { + list.$current.sortable('option', 'handle', '.oe_list_field_handle'); + } + list.$current.sortable('option', { + start: function (e, ui) { + ui.placeholder.height(ui.item.height()); + }, stop: function (event, ui) { var to_move = list.records.get(ui.item.data('id')), target_id = ui.item.prev().data('id'), @@ -1497,7 +1510,7 @@ instance.web.ListView.Groups = instance.web.Class.extend( /** @lends instance.we var record, index = to, // if drag to 1st row (to = 0), start sequencing from 0 // (exclusive lower bound) - seq = to ? list.records.at(to - 1).get('sequence') : 0; + seq = to ? list.records.at(to - 1).get(seqname) : 0; while (++seq, record = list.records.at(index++)) { // write are independent from one another, so we can just // launch them all at the same time and we don't really @@ -1507,10 +1520,12 @@ instance.web.ListView.Groups = instance.web.Class.extend( /** @lends instance.we // when synchronous (without setTimeout) (function (dataset, id, seq) { $.async_when().then(function () { - dataset.write(id, {sequence: seq}); + var attrs = {}; + attrs[seqname] = seq; + dataset.write(id, attrs); }); }(dataset, record.get('id'), seq)); - record.set('sequence', seq); + record.set(seqname, seq); } } }); From 73264632e7c90f8404fc4d34ef2842e466ea2586 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Tue, 7 Aug 2012 16:13:51 +0200 Subject: [PATCH 04/14] [IMP] set an NS-resize cursor on handle bzr revid: xmo@openerp.com-20120807141351-6d9vtnjpwjsx7wmj --- addons/web/static/src/css/base.css | 1 + addons/web/static/src/css/base.sass | 1 + 2 files changed, 2 insertions(+) diff --git a/addons/web/static/src/css/base.css b/addons/web/static/src/css/base.css index 295f2689a6d..346b6f25f54 100644 --- a/addons/web/static/src/css/base.css +++ b/addons/web/static/src/css/base.css @@ -2479,6 +2479,7 @@ } .openerp .oe_list_content .oe_list_field_handle { width: 1em; + cursor: ns-resize; } .openerp .oe_list_content .oe_list_field_handle .oe_list_handle { font-size: 1px; diff --git a/addons/web/static/src/css/base.sass b/addons/web/static/src/css/base.sass index 8005449b226..67bb855eb39 100644 --- a/addons/web/static/src/css/base.sass +++ b/addons/web/static/src/css/base.sass @@ -1928,6 +1928,7 @@ $sheet-max-width: 860px text-align: right .oe_list_field_handle width: 1em + cursor: ns-resize .oe_list_handle @include text-to-icon("ö") From 2447e00d220107d03a57841f18532a70774ca3b6 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Wed, 8 Aug 2012 09:56:29 +0200 Subject: [PATCH 05/14] [IMP] styling of handle column headers bzr revid: xmo@openerp.com-20120808075629-3xfkuxht8m9a1c02 --- addons/web/static/src/css/base.css | 11 ++++++++--- addons/web/static/src/css/base.sass | 6 +++++- addons/web/static/src/xml/base.xml | 2 +- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/addons/web/static/src/css/base.css b/addons/web/static/src/css/base.css index 346b6f25f54..d24657de195 100644 --- a/addons/web/static/src/css/base.css +++ b/addons/web/static/src/css/base.css @@ -2477,16 +2477,21 @@ .openerp .oe_list_content .numeric input { text-align: right; } -.openerp .oe_list_content .oe_list_field_handle { +.openerp .oe_list_content th.oe_list_header_handle { + font-size: 1px; + overflow: hidden; + text-indent: -9001px; +} +.openerp .oe_list_content td.oe_list_field_handle { width: 1em; cursor: ns-resize; } -.openerp .oe_list_content .oe_list_field_handle .oe_list_handle { +.openerp .oe_list_content td.oe_list_field_handle .oe_list_handle { font-size: 1px; letter-spacing: -1px; color: transparent; } -.openerp .oe_list_content .oe_list_field_handle .oe_list_handle:before { +.openerp .oe_list_content td.oe_list_field_handle .oe_list_handle:before { font: 21px "mnmliconsRegular"; content: "ö"; color: #404040; diff --git a/addons/web/static/src/css/base.sass b/addons/web/static/src/css/base.sass index 67bb855eb39..4963369ecbb 100644 --- a/addons/web/static/src/css/base.sass +++ b/addons/web/static/src/css/base.sass @@ -1926,7 +1926,11 @@ $sheet-max-width: 860px width: 82px input text-align: right - .oe_list_field_handle + th.oe_list_header_handle + font-size: 1px + overflow: hidden + text-indent: -9001px + td.oe_list_field_handle width: 1em cursor: ns-resize .oe_list_handle diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml index 35d42d624c5..db5bed27db0 100644 --- a/addons/web/static/src/xml/base.xml +++ b/addons/web/static/src/xml/base.xml @@ -614,7 +614,7 @@ + t-attf-class="oe_list_header_#{column.widget or column.type} #{((options.sortable and column.tag !== 'button') ? 'oe_sortable' : null)}"> From 5c225be25ed19b5d55b255cca5d1ec810af7bdfc Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Wed, 8 Aug 2012 12:34:04 +0200 Subject: [PATCH 06/14] [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 --- addons/web/static/src/js/formats.js | 80 ------- addons/web/static/src/js/view_list.js | 286 +++++++++++++++++++------- doc/list-view.rst | 50 +++++ 3 files changed, 265 insertions(+), 151 deletions(-) diff --git a/addons/web/static/src/js/formats.js b/addons/web/static/src/js/formats.js index 6aae9677ec4..797e1a2cbdf 100644 --- a/addons/web/static/src/js/formats.js +++ b/addons/web/static/src/js/formats.js @@ -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 ``', { - 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('', - 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('<%-text%> (%<-size%>)', { - text: text, - href: download_url, - size: row_data[column.id].value - }); - case 'progressbar': - return _.template( - '<%-value%>%', { - value: _.str.sprintf("%.0f", row_data[column.id].value || 0) - }); - case 'handle': - return '
'; - } - - return _.escape(instance.web.format_value( - row_data[column.id].value, column, options.value_if_empty)); -} - }; diff --git a/addons/web/static/src/js/view_list.js b/addons/web/static/src/js/view_list.js index d78a732fc58..986b0e49faf 100644 --- a/addons/web/static/src/js/view_list.js +++ b/addons/web/static/src/js/view_list.js @@ -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(''); } _(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]}; $('') - .html(instance.web.format_cell( - r, column, {process_modifiers: false})) + .html(column.format(r, {process_modifiers: false})) .appendTo($row); } else { $row.append(''); @@ -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 ``', { + 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('', + 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('<%-text%> (%<-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( + '<%-value%>%', { + 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 '
'; + } +}); }; // vim:et fdc=0 fdl=0 foldnestmax=3 fdm=syntax: diff --git a/doc/list-view.rst b/doc/list-view.rst index 24c7bb3beb4..91edf423bbe 100644 --- a/doc/list-view.rst +++ b/doc/list-view.rst @@ -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 ++++++++++++++++++ From 67518796c641e46563a69395caf3464632f1bc52 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Wed, 8 Aug 2012 12:43:11 +0200 Subject: [PATCH 07/14] [ADD] editable flag on list views for fp bzr revid: xmo@openerp.com-20120808104311-ajrne9fb9emtaoe6 --- addons/web/static/src/js/view_list_editable.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/addons/web/static/src/js/view_list_editable.js b/addons/web/static/src/js/view_list_editable.js index 78fef767f5a..b5890af2a03 100644 --- a/addons/web/static/src/js/view_list_editable.js +++ b/addons/web/static/src/js/view_list_editable.js @@ -99,6 +99,7 @@ openerp.web.list_editable = function (instance) { // tree/@editable takes priority on everything else if present. var result = this._super(data, grouped); if (this.editable()) { + this.$element.addClass('oe_list_editable'); // FIXME: any hook available to ensure this is only done once? this.$buttons .off('click', '.oe_list_save') @@ -123,6 +124,8 @@ openerp.web.list_editable = function (instance) { .then(this.proxy('setup_events')); return $.when(result, editor_ready); + } else { + this.$element.removeClass('oe_list_editable'); } return result; From 2dce7f3ef85a71d55323181c5b8817cc7e30e109 Mon Sep 17 00:00:00 2001 From: Stephane Wirtel Date: Wed, 8 Aug 2012 12:43:54 +0200 Subject: [PATCH 08/14] [FIX] module: Use the button_immediate_XYZ of the upgrade and uninstall buttons in the form view bzr revid: stw@openerp.com-20120808104354-xzwue50utz50wspu --- openerp/addons/base/module/module.py | 53 +++++++++++++++------- openerp/addons/base/module/module_view.xml | 4 +- 2 files changed, 38 insertions(+), 19 deletions(-) diff --git a/openerp/addons/base/module/module.py b/openerp/addons/base/module/module.py index 4717ea63a84..f7f67cf323f 100644 --- a/openerp/addons/base/module/module.py +++ b/openerp/addons/base/module/module.py @@ -355,22 +355,7 @@ class module(osv.osv): :returns: next res.config item to execute :rtype: dict[str, object] """ - self.button_install(cr, uid, ids, context=context) - cr.commit() - _, pool = pooler.restart_pool(cr.dbname, update_module=True) - - config = pool.get('res.config').next(cr, uid, [], context=context) or {} - if config.get('type') not in ('ir.actions.reload', 'ir.actions.act_window_close'): - return config - - # reload the client; open the first available root menu - menu_obj = self.pool.get('ir.ui.menu') - menu_ids = menu_obj.search(cr, uid, [('parent_id', '=', False)], context=context) - return { - 'type': 'ir.actions.client', - 'tag': 'reload', - 'params': {'menu_id': menu_ids and menu_ids[0] or False}, - } + return self._button_immediate_function(cr, uid, ids, self.button_install, context=context) def button_install_cancel(self, cr, uid, ids, context=None): self.write(cr, uid, ids, {'state': 'uninstalled', 'demo':False}) @@ -415,8 +400,35 @@ class module(osv.osv): known_dep_ids, exclude_states,context)) return list(known_dep_ids) + def _button_immediate_function(self, cr, uid, ids, function, context=None): + function(cr, uid, ids, context=context) + + cr.commit() + _, pool = pooler.restart_pool(cr.dbname, update_module=True) + + config = pool.get('res.config').next(cr, uid, [], context=context) or {} + if config.get('type') not in ('ir.actions.reload', 'ir.actions.act_window_close'): + return config + + # reload the client; open the first available root menu + menu_obj = self.pool.get('ir.ui.menu') + menu_ids = menu_obj.search(cr, uid, [('parent_id', '=', False)], context=context) + + return { + 'type' : 'ir.actions.client', + 'tag' : 'reload', + 'params' : {'menu_id' : menu_ids and menu_ids[0] or False} + } + + def button_immediate_uninstall(self, cr, uid, ids, context=None): + """ + Uninstall the selected module(s) immediately and fully, + returns the next res.config action to execute + """ + return self._button_immediate_function(cr, uid, ids, self.button_uninstall, context=context) + def button_uninstall(self, cr, uid, ids, context=None): - if any(m.name == 'base' for m in self.browse(cr, uid, ids)): + if any(m.name == 'base' for m in self.browse(cr, uid, ids, context=context)): raise orm.except_orm(_('Error'), _("The `base` module cannot be uninstalled")) dep_ids = self.downstream_dependencies(cr, uid, ids, context=context) self.write(cr, uid, ids + dep_ids, {'state': 'to remove'}) @@ -426,6 +438,13 @@ class module(osv.osv): self.write(cr, uid, ids, {'state': 'installed'}) return True + def button_immediate_upgrade(self, cr, uid, ids, context=None): + """ + Upgrade the selected module(s) immediately and fully, + return the next res.config action to execute + """ + return self._button_immediate_function(cr, uid, ids, self.button_upgrade, context=context) + def button_upgrade(self, cr, uid, ids, context=None): depobj = self.pool.get('ir.module.module.dependency') todo = self.browse(cr, uid, ids, context=context) diff --git a/openerp/addons/base/module/module_view.xml b/openerp/addons/base/module/module_view.xml index 3eddea86e1f..6139895021b 100644 --- a/openerp/addons/base/module/module_view.xml +++ b/openerp/addons/base/module/module_view.xml @@ -117,8 +117,8 @@