diff --git a/addons/web/static/src/js/fields/abstract_field.js b/addons/web/static/src/js/fields/abstract_field.js index 725a237750d..1a59f5b4d26 100644 --- a/addons/web/static/src/js/fields/abstract_field.js +++ b/addons/web/static/src/js/fields/abstract_field.js @@ -371,6 +371,7 @@ var AbstractField = Widget.extend({ this.trigger_up('field_changed', { dataPointID: this.dataPointID, changes: changes, + viewType: this.viewType, }); }, 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 37adddfcd75..76dc10d0238 100644 --- a/addons/web/static/src/js/views/basic/basic_model.js +++ b/addons/web/static/src/js/views/basic/basic_model.js @@ -615,10 +615,12 @@ var BasicModel = AbstractModel.extend({ * * @param {string} record_id * @param {Object} changes a map field => new value + * @param {string} [viewType] current viewType. If not set, we will assume + * main viewType from the record * @returns {string[]} list of changed fields */ - notifyChanges: function (record_id, changes) { - return this.mutex.exec(this._applyChange.bind(this, record_id, changes)); + notifyChanges: function (record_id, changes, viewType) { + return this.mutex.exec(this._applyChange.bind(this, record_id, changes, viewType)); }, /** * Reload all data for a given resource @@ -902,9 +904,11 @@ var BasicModel = AbstractModel.extend({ * * @param {string} recordID * @param {Object} changes + * @param {string} [viewType] current viewType. If not set, we will assume + * main viewType from the record * @returns {Deferred} */ - _applyChange: function (recordID, changes) { + _applyChange: function (recordID, changes, viewType) { var self = this; var record = this.localData[recordID]; var field; @@ -937,7 +941,7 @@ var BasicModel = AbstractModel.extend({ } var onchangeDef; if (onChangeFields.length) { - onchangeDef = self._performOnChange(record, onChangeFields).then(function (result) { + onchangeDef = self._performOnChange(record, onChangeFields, viewType).then(function (result) { delete record._warning; return _.keys(changes).concat(Object.keys(result && result.value || {})); }); @@ -1237,22 +1241,34 @@ var BasicModel = AbstractModel.extend({ * @see _performOnChange * * @param {Object} record resource object of type 'record' - * @returns {Object} an onchange spec + * @param {string} [viewType] current viewType. If not set, we will assume + * main viewType from the record + * @returns {Object|false} an onchange spec, or false if no onchange should + * be applied */ - _buildOnchangeSpecs: function (record) { - // TODO: replace this function by some generic tree function in utils + _buildOnchangeSpecs: function (record, viewType) { + var hasOnchange = false; var specs = {}; - _.each(record.getFieldNames(), function (name) { + var fieldsInfo = record.fieldsInfo[viewType || record.viewType]; + + _.each(Object.keys(fieldsInfo), function (name) { var field = record.fields[name]; - var fieldInfo = record.fieldsInfo[record.viewType][name]; + var fieldInfo = fieldsInfo[name]; specs[name] = (field.onChange) || ""; + if (field.onChange) { + hasOnchange = true; + } _.each(fieldInfo.views, function (view) { _.each(view.fieldsInfo[view.type], function (field, subname) { - specs[name + '.' + subname] = (view.fields[subname].onChange) || ""; + var onChange = view.fields[subname].onChange; + specs[name + '.' + subname] = onChange || ""; + if (onChange) { + hasOnchange = true; + } }); }); }); - return specs; + return hasOnchange ? specs : false; }, /** * Fetch all name_gets for the many2ones in a group @@ -2349,23 +2365,11 @@ var BasicModel = AbstractModel.extend({ }); return $.when.apply($, defs) .then(function () { - var shouldApplyOnchange = false; - var field; - for (var field_name in record.data) { - field = record.fields[field_name]; - if (field.onChange) { - shouldApplyOnchange = true; + return self._performOnChange(record, fields_key).then(function () { + if (record._warning) { + return $.Deferred().reject(); } - } - if (shouldApplyOnchange) { - return self._performOnChange(record, fields_key).then(function () { - if (record._warning) { - return $.Deferred().reject(); - } - }); - } else { - return $.when(); - } + }); }) .then(function () { return self._fetchRelationalData(record); @@ -2449,12 +2453,17 @@ var BasicModel = AbstractModel.extend({ * * @param {Object} record * @param {string[]} fields changed fields + * @param {string} [viewType] current viewType. If not set, we will assume + * main viewType from the record * @returns {Deferred} The returned deferred can fail, in which case the * fail value will be the warning message received from the server */ - _performOnChange: function (record, fields) { + _performOnChange: function (record, fields, viewType) { var self = this; - var onchange_spec = this._buildOnchangeSpecs(record); + var onchangeSpec = this._buildOnchangeSpecs(record, viewType); + if (!onchangeSpec) { + return $.when(); + } var idList = record.data.id ? [record.data.id] : []; var options = { full: true, @@ -2470,7 +2479,7 @@ var BasicModel = AbstractModel.extend({ return self._rpc({ model: record.model, method: 'onchange', - args: [idList, currentData, fields, onchange_spec, context], + args: [idList, currentData, fields, onchangeSpec, context], }) .then(function (result) { if (!record._changes) { diff --git a/addons/web/static/src/js/views/field_manager_mixin.js b/addons/web/static/src/js/views/field_manager_mixin.js index ceedbb2df6e..9578e568558 100644 --- a/addons/web/static/src/js/views/field_manager_mixin.js +++ b/addons/web/static/src/js/views/field_manager_mixin.js @@ -43,7 +43,7 @@ var FieldManagerMixin = { */ _applyChanges: function (dataPointID, changes, event) { var self = this; - return this.model.notifyChanges(dataPointID, changes) + return this.model.notifyChanges(dataPointID, changes, event.data.viewType) .then(function (result) { if (event.data.force_save) { return self.model.save(dataPointID).then(function () { diff --git a/addons/web/static/tests/fields/relational_fields_tests.js b/addons/web/static/tests/fields/relational_fields_tests.js index 70dc16c6a40..1a8f2742e22 100644 --- a/addons/web/static/tests/fields/relational_fields_tests.js +++ b/addons/web/static/tests/fields/relational_fields_tests.js @@ -20,7 +20,7 @@ QUnit.module('relational_fields', { int_field: {string: "int_field", type: "integer", sortable: true}, qux: {string: "Qux", type: "float", digits: [16,1] }, p: {string: "one2many field", type: "one2many", relation: 'partner', relation_field: 'trululu'}, - turtles: {string: "one2many turtle field", type: "one2many", relation: 'turtle'}, + turtles: {string: "one2many turtle field", type: "one2many", relation: 'turtle', relation_field: 'turtle_trululu'}, trululu: {string: "Trululu", type: "many2one", relation: 'partner'}, timmy: { string: "pokemon", type: "many2many", relation: 'partner_type'}, product_id: {string: "Product", type: "many2one", relation: 'product'}, @@ -1336,12 +1336,14 @@ QUnit.module('relational_fields', { QUnit.test('one2many list (editable): edition, part 4', function (assert) { assert.expect(3); + var i = 0; this.data.turtle.onchanges = { turtle_trululu: function (obj) { - if (obj.turtle_trululu) { + if (i) { obj.turtle_description = "Some Description"; } + i++; }, }; @@ -2334,25 +2336,26 @@ QUnit.module('relational_fields', { }); QUnit.test('parent data is properly sent on an onchange rpc, new record', function (assert) { - assert.expect(1); + assert.expect(6); - this.data.partner.onchanges = {bar: function () {}}; + this.data.turtle.onchanges = {turtle_bar: function () {}}; var form = createView({ View: FormView, model: 'partner', data: this.data, arch: '
' + '' + - '' + + '' + '' + - '' + + '' + '' + '' + '', mockRPC: function (route, args) { - if (args.method === 'onchange') { + assert.step(args.method); + if (args.method === 'onchange' && args.model === 'turtle') { var fieldValues = args.args[1]; - assert.strictEqual(fieldValues.trululu.foo, "My little Foo Value", + assert.strictEqual(fieldValues.turtle_trululu.foo, "My little Foo Value", "should have properly sent the parent foo value"); } return this._super.apply(this, arguments); @@ -2361,6 +2364,7 @@ QUnit.module('relational_fields', { form.$buttons.find('.o_form_button_edit').click(); form.$('tbody td.o_field_x2many_list_row_add a').click(); + assert.verifySteps(['default_get', 'onchange', 'default_get', 'onchange']); form.destroy(); }); diff --git a/addons/web/static/tests/views/form_tests.js b/addons/web/static/tests/views/form_tests.js index 7afb8545934..e5efb3f9b7a 100644 --- a/addons/web/static/tests/views/form_tests.js +++ b/addons/web/static/tests/views/form_tests.js @@ -1159,6 +1159,38 @@ QUnit.module('Views', { form.destroy(); }); + QUnit.test('default record with a one2many and an onchange on sub field', function (assert) { + assert.expect(4); + + this.data.partner.onchanges.foo = function () {}; + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '
', + mockRPC: function (route, args) { + assert.step(args.method); + if (args.method === 'onchange') { + assert.deepEqual(args.args[3], { + p: '', + 'p.display_name': '', + 'p.foo': '1' + }, "onchangeSpec should be correct (with sub fields)"); + } + return this._super.apply(this, arguments); + }, + }); + assert.verifySteps(['default_get', 'onchange']); + form.destroy(); + }); + QUnit.test('sidebar is hidden when switching to edit mode', function (assert) { assert.expect(3);