diff --git a/addons/web/static/src/js/fields/field_utils.js b/addons/web/static/src/js/fields/field_utils.js index c359a014b4e..406a86ac7a2 100644 --- a/addons/web/static/src/js/fields/field_utils.js +++ b/addons/web/static/src/js/fields/field_utils.js @@ -336,6 +336,34 @@ function parseMonetary(formatted_value) { return parseFloat(value); } +/** + * Creates an object with id and display_name. + * + * @param {Array|number|string|Object} value + * The given value can be : + * - an array with id as first element and display_name as second element + * - a number or a string representing the id (the display_name will be + * returned as undefined) + * - an object, simply returned untouched + * @returns {Object} (contains the id and display_name) + * Note: if the given value is not an array, a string or a + * number, the value is returned untouched. + */ +function parseMany2one(value) { + if (_.isArray(value)) { + return { + id: value[0], + display_name: value[1], + }; + } + if (_.isNumber(value) || _.isString(value)) { + return { + id: parseInt(value, 10), + }; + } + return value; +} + return { format: { binary: _.identity, // todo @@ -368,7 +396,7 @@ return { id: _.identity, integer: parseInteger, many2many: _.identity, // todo - many2one: _.identity, + many2one: parseMany2one, monetary: parseMonetary, one2many: _.identity, reference: _.identity, // todo diff --git a/addons/web/static/src/js/fields/relational_fields.js b/addons/web/static/src/js/fields/relational_fields.js index 68bd7a52343..4663c40f755 100644 --- a/addons/web/static/src/js/fields/relational_fields.js +++ b/addons/web/static/src/js/fields/relational_fields.js @@ -1166,13 +1166,15 @@ var FieldMany2ManyCheckBoxes = AbstractField.extend({ //------------------------------------------------------------------------------ var FieldStatus = AbstractField.extend({ - template: "FieldStatus", className: 'o_statusbar_status', events: { - 'click .o_arrow_button': '_onClickStage', + 'click button:not(.dropdown-toggle)': '_onClickStage', }, specialData: "_fetchSpecialStatus", supportedFieldTypes: ['selection', 'many2one'], + /** + * @override init from AbstractField + */ init: function () { this._super.apply(this, arguments); this._setState(); @@ -1183,6 +1185,7 @@ var FieldStatus = AbstractField.extend({ //-------------------------------------------------------------------------- /** + * @override _reset from AbstractField * @private */ _reset: function () { @@ -1190,45 +1193,51 @@ var FieldStatus = AbstractField.extend({ this._setState(); }, /** + * Prepares the rendering data from the field and record data. * @private */ _setState: function () { + var self = this; if (this.field.type === 'many2one') { - this.status_information = this.record.specialData[this.name]; - var status = _.findWhere(this.status_information, {id: this.value.res_id}); - if (status) { - status.selected = true; - } + this.status_information = _.map(this.record.specialData[this.name], function (info) { + return _.extend({ + selected: info.id === self.value.res_id, + }, info); + }); } else { - var self = this; - this.status_information = _.map(this.field.selection, function (val) { - return { id: val[0], display_name: val[1], selected: val[0] === self.value }; + var selection = this.field.selection; + if (this.attrs.statusbar_visible) { + var restriction = this.attrs.statusbar_visible.split(","); + selection = _.filter(selection, function (val) { + return _.contains(restriction, val[0]) || val[0] === self.value; + }); + } + this.status_information = _.map(selection, function (val) { + return { id: val[0], display_name: val[1], selected: val[0] === self.value, fold: false }; }); } }, /** + * @override _render from AbstractField * @private */ _render: function () { - var visible = this.attrs.statusbar_visible; - var selection_folded = []; - var selection_unfolded = []; - _.each(this.status_information, function (info) { - if (info.selected && (info.fold || visible && visible.indexOf(info.display_name) === -1)) { - selection_folded.push(info); - } else { - selection_unfolded.push(info); - } - }); - var mobile = config.device.size_class <= config.device.SIZES.XS; - // grep: FieldStatus.content.mobile, FieldStatus.content.desktop - var content = qweb.render("FieldStatus.content." + (mobile ? 'mobile' : 'desktop'), { - selection_folded: selection_folded.reverse(), - selection_unfolded: selection_unfolded.reverse(), - clickable: !!this.attrs.clickable, - }); - this.$el.html(content); - this.$('.disabled').prop('disabled', true); + if (config.device.size_class > config.device.SIZES.XS) { + var selections = _.partition(this.status_information, function (info) { + return (info.selected || !info.fold); + }); + this.$el.html(qweb.render("FieldStatus.content.desktop", { + selection_unfolded: selections[0], + selection_folded: selections[1], + clickable: !!this.attrs.clickable, + })); + } else { + this.$el.html(qweb.render("FieldStatus.content.mobile", { + selection: this.status_information, + value: _.findWhere(this.status_information, {selected: true}).display_name, + clickable: !!this.attrs.clickable, + })); + } }, //-------------------------------------------------------------------------- @@ -1236,22 +1245,14 @@ var FieldStatus = AbstractField.extend({ //-------------------------------------------------------------------------- /** - * Note that this function is debounced... + * Called when on status stage is clicked -> sets the field value. + * Note: this function is debounced... * * @private - * @param {MouseEvent} event + * @param {MouseEvent} e */ - _onClickStage: _.debounce(function (event) { - var $stage = $(event.currentTarget); - var val; - if (this.field.type === "many2one") { - val = parseInt($stage.data("value"), 10); - } else { - val = $stage.data("value"); - } - if (val) { - this._setValue(val); - } + _onClickStage: _.debounce(function (e) { + this._setValue($(e.currentTarget).data("value")); }, 300, true), }); diff --git a/addons/web/static/src/js/views/basic/basic_model.js b/addons/web/static/src/js/views/basic/basic_model.js index cdb8bb61f7c..afac5c1c5ff 100644 --- a/addons/web/static/src/js/views/basic/basic_model.js +++ b/addons/web/static/src/js/views/basic/basic_model.js @@ -964,7 +964,7 @@ var BasicModel = AbstractModel.extend({ } var rel_data = _.pick(data, 'id', 'display_name'); var def; - if (!('display_name' in rel_data)) { + if (rel_data.display_name === undefined) { var field = record.fields[fieldName]; def = this._rpc(field.relation, 'name_get') .args([data.id]) @@ -1498,7 +1498,12 @@ var BasicModel = AbstractModel.extend({ _fetchSpecialStatus: function (record, fieldName) { var foldField = record.fieldAttrs[fieldName].options.fold_field; var fieldsToRead = foldField ? [foldField] : []; - return this._fetchSpecialMany2ones(record, fieldName, fieldsToRead); + return this._fetchSpecialMany2ones(record, fieldName, fieldsToRead).then(function (m2os) { + _.each(m2os, function (m2o) { + m2o.fold = foldField ? m2o[foldField] : false; + }); + return m2os; + }); }, /** * Fetch all data in a ungrouped list diff --git a/addons/web/static/src/js/views/form/form_renderer.js b/addons/web/static/src/js/views/form/form_renderer.js index a83c7d653b5..ce7c3b0bc16 100644 --- a/addons/web/static/src/js/views/form/form_renderer.js +++ b/addons/web/static/src/js/views/form/form_renderer.js @@ -477,14 +477,13 @@ return BasicRenderer.extend({ }, _renderTagHeader: function (node) { var self = this; - var $statusbar = $('
').addClass('o_form_statusbar'); - var $buttons = $('
').addClass('o_statusbar_buttons'); + var $statusbar = $('
', {class: 'o_form_statusbar'}); + var $buttons = $('
', {class: 'o_statusbar_buttons'}); $statusbar.append($buttons); _.each(node.children, function (child) { if (child.tag === 'button') { $buttons.append(self._renderHeaderButton(child)); - } - if (child.tag === 'field') { + } else if (child.tag === 'field') { var widget = self._renderFieldWidget(child); $statusbar.append(widget.$el); } diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml index 1f95503441a..3976a41d783 100644 --- a/addons/web/static/src/xml/base.xml +++ b/addons/web/static/src/xml/base.xml @@ -926,13 +926,7 @@
- -
    - - - - + + + - diff --git a/addons/web/static/test/fields/relational_fields_tests.js b/addons/web/static/test/fields/relational_fields_tests.js index f3d4ef5708c..5ef4d75591d 100644 --- a/addons/web/static/test/fields/relational_fields_tests.js +++ b/addons/web/static/test/fields/relational_fields_tests.js @@ -2323,9 +2323,8 @@ QUnit.module('relational_fields', { assert.expect(5); this.data.partner.fields.trululu.domain = "[('bar', '=', True)]"; - this.data.partner.records.forEach(function (record) { - record.bar = true; - }); + this.data.partner.records[1].bar = false; + var count = 0; var nb_fields_fetched; var form = createView({ @@ -2333,7 +2332,7 @@ QUnit.module('relational_fields', { model: 'partner', data: this.data, arch:'
    ' + - '' + + '
    ' + // the following field seem useless, but its presence was the // cause of a crash when evaluating the field domain. '' + @@ -2350,8 +2349,8 @@ QUnit.module('relational_fields', { assert.strictEqual(count, 1, 'once search_read should have been done to fetch the relational values'); assert.strictEqual(nb_fields_fetched, 1, 'search_read should only fetch field id'); - assert.strictEqual(form.$('.o_statusbar_status button').length, 3, "should have 3 status"); - assert.strictEqual(form.$('.o_statusbar_status button.disabled').length, 3, + assert.strictEqual(form.$('.o_statusbar_status button:not(.dropdown-toggle)').length, 2, "should have 2 status"); + assert.strictEqual(form.$('.o_statusbar_status button:disabled').length, 2, "all status should be disabled"); assert.ok(form.$('.o_statusbar_status button[data-value="4"]').hasClass('btn-primary'), "selected status should be btn-primary"); @@ -2360,22 +2359,27 @@ QUnit.module('relational_fields', { }); QUnit.test('clickable statusbar widget on many2one field', function (assert) { - assert.expect(2); + assert.expect(3); var form = createView({ View: FormView, model: 'partner', data: this.data, arch:'' + - '' + + '
    ' + '', res_id: 1, }); assert.ok(form.$('.o_statusbar_status button[data-value="4"]').hasClass('btn-primary disabled'), "selected status should be btn-primary and disabled"); - assert.strictEqual(form.$('.o_statusbar_status button.btn-default:not(.disabled)').length, 2, + var $clickable = form.$('.o_statusbar_status button.btn-default:not(.dropdown-toggle):not(:disabled)'); + assert.strictEqual($clickable.length, 2, "other status should be btn-default and not disabled"); + $clickable.last().click(); // (last is visually the first here (css)) + assert.ok(form.$('.o_statusbar_status button[data-value="1"]').hasClass("btn-primary disabled"), + "value should have been updated"); + form.destroy(); }); @@ -2388,7 +2392,7 @@ QUnit.module('relational_fields', { model: 'partner', data: this.data, arch:'
    ' + - '' + + '
    ' + '', res_id: 1, }); @@ -2400,6 +2404,31 @@ QUnit.module('relational_fields', { form.destroy(); }); + QUnit.test('statusbar fold_field option and statusbar_visible attribute', function (assert) { + assert.expect(2); + + this.data.partner.records[0].bar = false; + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: + '
    ' + + '
    ' + + '
    ' + + '
    ', + res_id: 1, + }); + + form.$buttons.find('.o_form_button_edit').click(); + + assert.strictEqual(form.$('.o_statusbar_status:first .dropdown-menu button.disabled').length, 1, "should have 1 folded status"); + assert.strictEqual(form.$('.o_statusbar_status:last button.disabled').length, 1, "should have 1 status (other discarded)"); + + form.destroy(); + }); + QUnit.test('statusbar with dynamic domain', function (assert) { assert.expect(5); @@ -2413,7 +2442,7 @@ QUnit.module('relational_fields', { data: this.data, arch: '
    ' + - '' + + '
    ' + '' + '' + '',