diff --git a/addons/base/controllers/main.py b/addons/base/controllers/main.py index aa870dfc78f..1179eb37d42 100644 --- a/addons/base/controllers/main.py +++ b/addons/base/controllers/main.py @@ -594,36 +594,6 @@ class View(openerpweb.Controller): return {'result': True} return {'result': False} - def normalize_attrs(self, elem, context): - """ Normalize @attrs, @invisible, @required, @readonly and @states, so - the client only has to deal with @attrs. - - See `the discoveries pad `_ for - the rationale. - - :param elem: the current view node (Python object) - :type elem: xml.etree.ElementTree.Element - :param dict context: evaluation context - """ - # If @attrs is normalized in json by server, the eval should be replaced by simplejson.loads - attrs = openerpweb.ast.literal_eval(elem.get('attrs', '{}')) - if 'states' in elem.attrib: - attrs.setdefault('invisible', [])\ - .append(('state', 'not in', elem.attrib.pop('states').split(','))) - if attrs: - elem.set('attrs', simplejson.dumps(attrs)) - for a in ['invisible', 'readonly', 'required']: - if a in elem.attrib: - # In the XML we trust - avalue = bool(eval(elem.get(a, 'False'), - {'context': context or {}})) - if not avalue: - del elem.attrib[a] - else: - elem.attrib[a] = '1' - if a == 'invisible' and 'attrs' in elem.attrib: - del elem.attrib['attrs'] - def transform_view(self, view_string, session, context=None): # transform nodes on the fly via iterparse, instead of # doing it statically on the parsing result @@ -633,7 +603,6 @@ class View(openerpweb.Controller): if event == "start": if root is None: root = elem - self.normalize_attrs(elem, context) self.parse_domains_and_contexts(elem, session) return root diff --git a/addons/base/static/src/js/form.js b/addons/base/static/src/js/form.js index ad9bd74614d..7517ec23e01 100644 --- a/addons/base/static/src/js/form.js +++ b/addons/base/static/src/js/form.js @@ -132,7 +132,7 @@ openerp.base.FormView = openerp.base.View.extend( /** @lends openerp.base.FormV on_form_changed: function() { for (var w in this.widgets) { w = this.widgets[w]; - w.process_attrs(); + w.process_modifiers(); w.update_dom(); } }, @@ -503,7 +503,7 @@ openerp.base.form.compute_domain = function(expr, fields) { stack.push(!_(val).contains(field)); break; default: - this.log("Unsupported operator in attrs :", op); + this.log("Unsupported operator in modifiers :", op); } } return _.all(stack); @@ -514,7 +514,7 @@ openerp.base.form.Widget = openerp.base.Controller.extend({ init: function(view, node) { this.view = view; this.node = node; - this.attrs = JSON.parse(this.node.attrs.attrs || '{}'); + this.modifiers = JSON.parse(this.node.attrs.modifiers || '{}'); this.type = this.type || node.tag; this.element_name = this.element_name || this.type; this.element_id = [this.view.element_id, this.element_name, this.view.widgets_counter++].join("_"); @@ -527,15 +527,15 @@ openerp.base.form.Widget = openerp.base.Controller.extend({ this.string = this.string || node.attrs.string; this.help = this.help || node.attrs.help; - this.invisible = (node.attrs.invisible == '1'); + this.invisible = this.modifiers['invisible'] === true; }, start: function() { this.$element = $('#' + this.element_id); }, - process_attrs: function() { + process_modifiers: function() { var compute_domain = openerp.base.form.compute_domain; - for (var a in this.attrs) { - this[a] = compute_domain(this.attrs[a], this.view.fields); + for (var a in this.modifiers) { + this[a] = compute_domain(this.modifiers[a], this.view.fields); } }, update_dom: function() { @@ -736,10 +736,9 @@ openerp.base.form.Field = openerp.base.form.Widget.extend({ this.field = view.fields_view.fields[node.attrs.name] || {}; this.string = node.attrs.string || this.field.string; this.help = node.attrs.help || this.field.help; - this.invisible = (this.invisible || this.field.invisible == '1'); - this.nolabel = (this.field.nolabel || node.attrs.nolabel) == '1'; - this.readonly = (this.field.readonly || node.attrs.readonly) == '1'; - this.required = (this.field.required || node.attrs.required) == '1'; + this.nolabel = (this.field.nolabel || node.attrs.nolabel) === '1'; + this.readonly = this.modifiers['readonly'] === true; + this.required = this.modifiers['required'] === true; this.invalid = false; this.touched = false; }, @@ -1117,28 +1116,54 @@ openerp.base.form.FieldSelection = openerp.base.form.Field.extend({ init: function(view, node) { this._super(view, node); this.template = "FieldSelection"; + this.field_index = _.map(this.field.selection, function(x, index) { + return {"ikey": "" + index, "ekey": x[0], "label": x[1]}; + }); }, start: function() { + // Flag indicating whether we're in an event chain containing a change + // event on the select, in order to know what to do on keyup[RETURN]: + // * If the user presses [RETURN] as part of changing the value of a + // selection, we should just let the value change and not let the + // event broadcast further (e.g. to validating the current state of + // the form in editable list view, which would lead to saving the + // current row or switching to the next one) + // * If the user presses [RETURN] with a select closed (side-effect: + // also if the user opened the select and pressed [RETURN] without + // changing the selected value), takes the action as validating the + // row + var ischanging = false; this._super.apply(this, arguments); - this.$element.find('select').change(this.on_ui_change); + this.$element.find('select') + .change(this.on_ui_change) + .change(function () { ischanging = true; }) + .click(function () { ischanging = false; }) + .keyup(function (e) { + if (e.which !== 13 || !ischanging) { return; } + e.stopPropagation(); + ischanging = false; + }); }, set_value: function(value) { - this._super.apply(this, arguments); - if (value != null && value !== false) { - this.$element.find('select').val(value); - } else { - this.$element.find('select').val('false'); - } + value = value === null ? false : value; + value = value instanceof Array ? value[0] : value; + this._super(value); + var option = _.detect(this.field_index, function(x) {return x.ekey === value;}); + this.$element.find('select').val(option === undefined ? '' : option.ikey); }, set_value_from_ui: function() { - this.value = this.$element.find('select').val(); + var ikey = this.$element.find('select').val(); + var option = _.detect(this.field_index, function(x) {return x.ikey === ikey;}); + this.value = option === undefined ? false : option.ekey; }, update_dom: function() { this._super.apply(this, arguments); this.$element.find('select').attr('disabled', this.readonly); }, validate: function() { - this.invalid = this.required && this.$element.find('select').val() === ""; + var ikey = this.$element.find('select').val(); + var option = _.detect(this.field_index, function(x) {return x.ikey === ikey;}); + this.invalid = this.required && (option === undefined || option.ekey === false); }, focus: function() { this.$element.find('select').focus(); diff --git a/addons/base/static/src/js/list-editable.js b/addons/base/static/src/js/list-editable.js index 8b4b16faf04..a460dfcff41 100644 --- a/addons/base/static/src/js/list-editable.js +++ b/addons/base/static/src/js/list-editable.js @@ -268,10 +268,12 @@ openerp.base.list.editable = function (openerp) { openerp.base.list.form[key] = (form_widgets.get_object(key)).extend({ update_dom: function () { this.$element.children().css('visibility', ''); - if (this.invisible && this.node.attrs.invisible !== '1') { + if (this.invisible) { this.$element.children().css('visibility', 'hidden'); } else { + this.invisible = !!this.modifiers.tree_invisible; this._super(); + this.invisible = false; } } }); diff --git a/addons/base/static/src/js/list.js b/addons/base/static/src/js/list.js index aa3fe3f658b..c4aea837a4f 100644 --- a/addons/base/static/src/js/list.js +++ b/addons/base/static/src/js/list.js @@ -252,18 +252,18 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi var name = field.attrs.name; var column = _.extend({id: name, tag: field.tag}, field.attrs, fields[name]); - // attrs computer - if (column.attrs) { - var attrs = JSON.parse(column.attrs); - column.attrs_for = function (fields) { + // modifiers computer + if (column.modifiers) { + var modifiers = JSON.parse(column.modifiers); + column.modifiers_for = function (fields) { var result = {}; - for (var attr in attrs) { - result[attr] = domain_computer(attrs[attr], fields); + for (var modifier in modifiers) { + result[modifier] = domain_computer(modifiers[modifier], fields); } return result; }; } else { - column.attrs_for = noop; + column.modifiers_for = noop; } return column; }; @@ -275,10 +275,10 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi if (grouped) { this.columns.unshift({ id: '_group', tag: '', string: "Group", meta: true, - attrs_for: function () { return {}; } + modifiers_for: function () { return {}; } }, { id: '_count', tag: '', string: '#', meta: true, - attrs_for: function () { return {}; } + modifiers_for: function () { return {}; } }); } @@ -409,9 +409,9 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi $.proxy(this, 'reload_content')); }, /** - * Handles the signal to delete a line from the DOM + * Handles the signal to delete lines from the records list * - * @param {Array} ids the id of the object to delete + * @param {Array} ids the ids of the records to delete */ do_delete: function (ids) { if (!ids.length) { @@ -419,7 +419,7 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi } var self = this; return $.when(this.dataset.unlink(ids)).then(function () { - self.reload_content(); + self.groups.drop_records(ids); }); }, /** @@ -637,7 +637,9 @@ openerp.base.ListView.List = Class.extend( /** @lends openerp.base.ListView.List this.$current.remove(); } this.$current = this.$_element.clone(true); - this.$current.empty().append(QWeb.render('ListView.rows', this)); + this.$current.empty().append( + QWeb.render('ListView.rows', _.extend({ + render_cell: this.render_cell}, this))); }, /** * Gets the ids of all currently selected records, if any @@ -767,8 +769,67 @@ openerp.base.ListView.List = Class.extend( /** @lends openerp.base.ListView.List options: this.options, row: this.rows[record_index], row_parity: (record_index % 2 === 0) ? 'even' : 'odd', - row_index: record_index + row_index: record_index, + render_cell: this.render_cell }); + }, + /** + * Formats the rendring of a given value based on its field type + * + * @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 + */ + render_cell: function (row_data, column) { + var attrs = column.modifiers_for(row_data); + if (attrs.invisible) { return ''; } + if (column.tag === 'button') { + return [ + '' + ].join('') + } + + var record = row_data[column.id]; + if (record.value === false) { return ''; } + switch (column.widget || column.type) { + case 'many2one': + // name_get value format + return record.value[1]; + case 'float_time': + return _.sprintf("%02d:%02d", + Math.floor(record.value), + Math.round((record.value % 1) * 60)); + case 'progressbar': + return _.sprintf( + '%.2f%%', + record.value, record.value); + default: + return record.value; + } + }, + /** + * Stops displaying the records matching the provided ids. + * + * @param {Array} ids identifiers of the records to remove + */ + drop_records: function (ids) { + var self = this; + _(this.rows).chain() + .map(function (record, index) { + return {index: index, id: record.data.id.value}; + }).filter(function (record) { + return _(ids).contains(record.id); + }).reverse() + .each(function (record) { + self.$current.find('tr:eq(' + record.index + ')').remove(); + self.rows.splice(record.index, 1); + }) } // drag and drop }); @@ -1062,6 +1123,19 @@ openerp.base.ListView.Groups = Class.extend( /** @lends openerp.base.ListView.Gr .map(function (child) { return child.get_records(); }).flatten().value(); + }, + /** + * Stops displaying the records with the linked ids, assumes these records + * were deleted from the DB. + * + * This is the up-signal from the `deleted` event on groups and lists. + * + * @param {Array} ids list of identifier of the records to remove. + */ + drop_records: function (ids) { + _.each(this.children, function (child) { + child.drop_records(ids); + }); } }); }; diff --git a/addons/base/static/src/js/views.js b/addons/base/static/src/js/views.js index 8a04e73f6a6..a43f79527dd 100644 --- a/addons/base/static/src/js/views.js +++ b/addons/base/static/src/js/views.js @@ -459,7 +459,8 @@ openerp.base.View = openerp.base.Controller.extend({ search_view : false, views_switcher : false, action_buttons : false, - pager : false + pager : false, + new_window: true }; action_manager.do_action(action, on_closed); if (self.view_manager.is_dialog && action.type != 'ir.actions.act_window_close') { diff --git a/addons/base/static/src/xml/base.xml b/addons/base/static/src/xml/base.xml index d37e1ddd958..a550c62cf45 100644 --- a/addons/base/static/src/xml/base.xml +++ b/addons/base/static/src/xml/base.xml @@ -324,20 +324,7 @@ - - - - - - - - - - + @@ -517,9 +504,9 @@ t-att-id="widget.element_id + '_field'" t-att-class="'field_' + widget.type" style="width: 100%"> - -