diff --git a/addons/web/static/src/js/views/basic/basic_renderer.js b/addons/web/static/src/js/views/basic/basic_renderer.js index d051ba08edf052cf61a7638b53cdbb15c57bacde..f1b24d9c5b3df6b63710e588069d3a5c1e99d7fa 100644 --- a/addons/web/static/src/js/views/basic/basic_renderer.js +++ b/addons/web/static/src/js/views/basic/basic_renderer.js @@ -749,13 +749,22 @@ var BasicRenderer = AbstractRenderer.extend({ _rerenderFieldWidget: function (widget, record, options) { // Render the new field widget var $el = this._renderFieldWidget(widget.__node, record, options); - widget.$el.replaceWith($el); - // Destroy the old widget and position the new one at the old one's - var oldIndex = this._destroyFieldWidget(record.id, widget); + // get the new widget that has just been pushed in allFieldWidgets var recordWidgets = this.allFieldWidgets[record.id]; - var newWidget = recordWidgets.pop(); - recordWidgets.splice(oldIndex, 0, newWidget); + var newWidget = recordWidgets[recordWidgets.length - 1]; + const def = this.defs[this.defs.length - 1]; // this is the widget's def, resolved when it is ready + const $div = $('<div>'); + $div.append($el); // $el will be replaced when widget is ready (see _renderFieldWidget) + def.then(() => { + widget.$el.replaceWith($div.children()); + + // Destroy the old widget and position the new one at the old one's + // (it has been temporarily inserted at the end of the list) + recordWidgets.splice(recordWidgets.indexOf(newWidget), 1); + var oldIndex = this._destroyFieldWidget(record.id, widget); + recordWidgets.splice(oldIndex, 0, newWidget); + }) }, /** * Unregisters an element of the modifiers data associated to the given diff --git a/addons/web/static/tests/views/form_tests.js b/addons/web/static/tests/views/form_tests.js index ea9fbb5b1002bf291d41428951331e0ff6d8c0a8..d6a2f7b76f347f58450b8595358fd25363208113 100644 --- a/addons/web/static/tests/views/form_tests.js +++ b/addons/web/static/tests/views/form_tests.js @@ -1036,6 +1036,60 @@ QUnit.module('Views', { form.destroy(); }); + QUnit.test('readonly attrs on lines are re-evaluated on field change 2', async function (assert) { + assert.expect(4); + + this.data.partner.records[0].product_ids = [37]; + this.data.partner.records[0].trululu = false; + this.data.partner.onchanges = { + trululu(record) { + // when trululu changes, push another record in product_ids. + // only push a second record once. + if (record.product_ids.map(command => command[1]).includes(41)) { + return; + } + // copy the list to force it as different from the original + record.product_ids = record.product_ids.slice(); + record.product_ids.push([4,41,false]); + } + }; + + this.data.product.records[0].name = 'test'; + // This one is necessary to have a valid, rendered widget + this.data.product.fields.int_field = { type:"integer", string: "intField" }; + + var form = await createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: ` + <form> + <field name="trululu"/> + <field name="product_ids" attrs="{'readonly': [['trululu', '=', False]]}"> + <tree editable="top"><field name="int_field" widget="handle" /><field name="name"/></tree> + </field> + </form> + `, + res_id: 1, + viewOptions: { + mode: 'edit', + }, + }); + + for (let value of [true, false, true, false]) { + if (value) { + await testUtils.fields.many2one.clickOpenDropdown('trululu') + await testUtils.fields.many2one.clickHighlightedItem('trululu') + assert.notOk($('.o_field_one2many[name="product_ids"]').hasClass("o_readonly_modifier"), 'lines should not be readonly') + } else { + await testUtils.fields.editAndTrigger(form.$('.o_field_many2one[name="trululu"] input'), '', ['keyup']) + assert.ok($('.o_field_one2many[name="product_ids"]').hasClass("o_readonly_modifier"), 'lines should be readonly') + } + } + + form.destroy(); + }); + QUnit.test('empty fields have o_form_empty class in readonly mode', async function (assert) { assert.expect(8);