[FIX] web: toggle optional column in list with owl component

Have a list view with an Owl field component (e.g. widget="badge"),
visible. Open the optional field dropdown and make another field
visible.

Before this commit, the '_onToggleOptionalColumn' handler was
executed twice, because two click events where triggered: one on
the label, and one on its associated input (because of the for
attribute, see [1]).

As a consequence, the new field was added, and directly removed
from the view. This could even cause subsequent crashes.

The difference between an Owl component and a legacy widget is
that the Owl component is async (more than a nextTick) as we wait
for the nextAnimationFrame to render. When the rendering is fast
enough (e.g. without Owl components), the click event isn't remap
to the input yet (because we empty the list HTML node), so the
handler is executed only once.

[1] https://developer.mozilla.org/en-US/docs/Web/HTML/Element/label

Issue reported on task 2204306

closes odoo/odoo#49920

X-original-commit: b02d347e8770835ad28a6487071930a904bce101
Signed-off-by: Lucas Perais (lpe) <lpe@odoo.com>
Signed-off-by: Aaron Bohy (aab) <aab@odoo.com>
This commit is contained in:
Aaron Bohy
2020-04-22 07:50:00 +00:00
parent 7c0c947b0e
commit ae4c9d1eca
2 changed files with 62 additions and 0 deletions
@@ -1109,6 +1109,11 @@ var ListRenderer = BasicRenderer.extend({
_onToggleOptionalColumn: function (ev) {
var self = this;
ev.stopPropagation();
// when the input's label is clicked, the click event is also raised on the
// input itself (https://developer.mozilla.org/en-US/docs/Web/HTML/Element/label),
// so this handler is executed twice (except if the rendering is quick enough,
// as when we render, we empty the HTML)
ev.preventDefault();
var input = ev.currentTarget.querySelector('input');
var fieldIndex = this.optionalColumnsEnabled.indexOf(input.name);
if (fieldIndex >= 0) {
@@ -5,6 +5,7 @@ var AbstractStorageService = require('web.AbstractStorageService');
var BasicModel = require('web.BasicModel');
var core = require('web.core');
var basicFields = require('web.basic_fields');
var fieldRegistry = require('web.field_registry');
var FormView = require('web.FormView');
var ListRenderer = require('web.ListRenderer');
var ListView = require('web.ListView');
@@ -8961,6 +8962,62 @@ QUnit.module('Views', {
list.destroy();
});
QUnit.test('list view with optional fields and async rendering', async function (assert) {
assert.expect(14);
const prom = testUtils.makeTestPromise();
const FieldChar = fieldRegistry.get('char');
fieldRegistry.add('asyncwidget', FieldChar.extend({
async _render() {
assert.ok(true, 'the rendering must be async');
this._super(...arguments);
await prom;
},
}));
const RamStorageService = AbstractStorageService.extend({
storage: new RamStorage(),
});
const list = await createView({
View: ListView,
model: 'foo',
data: this.data,
arch: `
<tree>
<field name="m2o"/>
<field name="foo" widget="asyncwidget" optional="hide"/>
</tree>`,
services: {
local_storage: RamStorageService,
},
});
assert.containsN(list, 'th', 2);
assert.isNotVisible(list.$('.o_optional_columns_dropdown'));
// add an optional field (we click on the label on purpose, as it will trigger
// a second event on the input)
await testUtils.dom.click(list.$('table .o_optional_columns_dropdown_toggle'));
assert.isVisible(list.$('.o_optional_columns_dropdown'));
assert.containsNone(list.$('.o_optional_columns_dropdown'), 'input:checked');
await testUtils.dom.click(list.$('div.o_optional_columns div.dropdown-item:first label'));
assert.containsN(list, 'th', 2);
assert.isVisible(list.$('.o_optional_columns_dropdown'));
assert.containsNone(list.$('.o_optional_columns_dropdown'), 'input:checked');
prom.resolve();
await testUtils.nextTick();
assert.containsN(list, 'th', 3);
assert.isVisible(list.$('.o_optional_columns_dropdown'));
assert.containsOnce(list.$('.o_optional_columns_dropdown'), 'input:checked');
list.destroy();
delete fieldRegistry.map.asyncwidget;
});
QUnit.test('change the viewType of the current action', async function (assert) {
assert.expect(25);