diff --git a/addons/web/static/src/js/fields/field_wrapper.js b/addons/web/static/src/js/fields/field_wrapper.js index f396f7d6f37..64b44c98be4 100644 --- a/addons/web/static/src/js/fields/field_wrapper.js +++ b/addons/web/static/src/js/fields/field_wrapper.js @@ -75,9 +75,6 @@ odoo.define('web.FieldWrapper', function (require) { // Getters //---------------------------------------------------------------------- - get $el() { - return $(this.el); - } get fieldDependencies() { return this.Component.fieldDependencies; } diff --git a/addons/web/static/src/js/owl_compatibility.js b/addons/web/static/src/js/owl_compatibility.js index b20ad7890f0..aa2d2e20751 100644 --- a/addons/web/static/src/js/owl_compatibility.js +++ b/addons/web/static/src/js/owl_compatibility.js @@ -518,6 +518,14 @@ odoo.define('web.OwlCompatibility', function () { this._mountArgs = arguments; return super.mount(...arguments); } + + //---------------------------------------------------------------------- + // Getters + //---------------------------------------------------------------------- + + get $el() { + return $(this.el); + } } ComponentWrapper.template = xml``; 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 bfc4ee4a0ea..acc512aaf9d 100644 --- a/addons/web/static/src/js/views/basic/basic_renderer.js +++ b/addons/web/static/src/js/views/basic/basic_renderer.js @@ -13,10 +13,12 @@ var core = require('web.core'); var dom = require('web.dom'); const session = require('web.session'); const utils = require('web.utils'); -var widgetRegistry = require('web.widget_registry'); +const widgetRegistry = require('web.widget_registry'); +const widgetRegistryOwl = require("web.widgetRegistry"); const { WidgetAdapterMixin } = require('web.OwlCompatibility'); const FieldWrapper = require('web.FieldWrapper'); +const WidgetWrapper = require("web.WidgetWrapper"); var qweb = core.qweb; const _t = core._t; @@ -804,13 +806,29 @@ var BasicRenderer = AbstractRenderer.extend(WidgetAdapterMixin, { * @returns {jQueryElement} */ _renderWidget: function (record, node) { - var Widget = widgetRegistry.get(node.attrs.name); - var widget = new Widget(this, record, node); + const name = node.attrs.name; + const Widget = widgetRegistryOwl.get(name) || widgetRegistry.get(name); + const legacy = !(Widget.prototype instanceof owl.Component); + let widget; + if (legacy) { + widget = new Widget(this, record, node, { mode: this.mode }); + } else { + widget = new WidgetWrapper(this, Widget, { + record, + node, + options: { mode: this.mode }, + }); + } this.widgets.push(widget); // Prepare widget rendering and save the related promise - var def = widget._widgetRenderAndInsert(function () {}); + let def; + if (legacy) { + def = widget._widgetRenderAndInsert(function () {}); + } else { + def = widget.mount(document.createDocumentFragment()); + } this.defs.push(def); var $el = $('
'); diff --git a/addons/web/static/src/js/views/basic/widget_registry.js b/addons/web/static/src/js/views/basic/widget_registry.js index 470127bd12e..9c44281036b 100644 --- a/addons/web/static/src/js/views/basic/widget_registry.js +++ b/addons/web/static/src/js/views/basic/widget_registry.js @@ -1,6 +1,16 @@ odoo.define('web.widget_registry', function (require) { "use strict"; + //////////////////////////////////////////////////////////////////////////// + // /!\ DEPRECATED + // + // /!\ This registry is deprecated. It is used to register legacy Widgets. + // /!\ Use 'web.widgetRegistry' to register Owl Components. + // /!\ Existing Widgets will be at some point converted into Owl Components. + // /!\ All new code should be directly written using Owl Components. + //////////////////////////////////////////////////////////////////////////// + + // This registry is supposed to contain all custom widgets that will be // available in the basic views, with the tag . There are // currently no such widget in the web client, but the functionality is @@ -23,5 +33,5 @@ odoo.define('web.widget_registry', function (require) { var Registry = require('web.Registry'); - return new Registry(); + return new Registry(null, (value) => !(value.prototype instanceof owl.Component)); }); diff --git a/addons/web/static/src/js/views/basic/widget_registry_owl.js b/addons/web/static/src/js/views/basic/widget_registry_owl.js new file mode 100644 index 00000000000..567d0dbef9c --- /dev/null +++ b/addons/web/static/src/js/views/basic/widget_registry_owl.js @@ -0,0 +1,24 @@ +odoo.define("web.widgetRegistry", function (require) { + "use strict"; + + // This registry is supposed to contain all custom components that will be + // available in the basic views, with the tag . Those components + // allow to cleanly add custom behavior in basic views. + // + // The way custom components work is that they register themselves to this + // registry: + // + // widgetRegistry.add('some_name', MyComponent); + // + // Then, they are available with the tag (in the arch): + // + // + // + // Those components will be then properly instantiated, rendered and + // destroyed at the appropriate time, and will receive the current state in + // props. + + const Registry = require("web.Registry"); + + return new Registry(null, (value) => value.prototype instanceof owl.Component); +}); diff --git a/addons/web/static/src/js/views/basic/widget_wrapper.js b/addons/web/static/src/js/views/basic/widget_wrapper.js new file mode 100644 index 00000000000..cbc795a1a3f --- /dev/null +++ b/addons/web/static/src/js/views/basic/widget_wrapper.js @@ -0,0 +1,19 @@ +odoo.define('web.WidgetWrapper', function (require) { + "use strict"; + + const { ComponentWrapper } = require('web.OwlCompatibility'); + + class WidgetWrapper extends ComponentWrapper { + + //---------------------------------------------------------------------- + // Public + //---------------------------------------------------------------------- + + updateState(state) { + if (this.componentRef.comp.updateState) { + this.componentRef.comp.updateState(state); + } + } + } + return WidgetWrapper; +}); diff --git a/addons/web/static/src/js/views/kanban/kanban_record.js b/addons/web/static/src/js/views/kanban/kanban_record.js index fe24b0ebb58..b2b978b6f90 100644 --- a/addons/web/static/src/js/views/kanban/kanban_record.js +++ b/addons/web/static/src/js/views/kanban/kanban_record.js @@ -13,7 +13,10 @@ var field_utils = require('web.field_utils'); const FieldWrapper = require('web.FieldWrapper'); var utils = require('web.utils'); var Widget = require('web.Widget'); +const { WidgetAdapterMixin } = require('web.OwlCompatibility'); var widgetRegistry = require('web.widget_registry'); +const widgetRegistryOwl = require('web.widgetRegistry'); +const WidgetWrapper = require("web.WidgetWrapper"); var _t = core._t; var QWeb = core.qweb; @@ -21,7 +24,7 @@ var QWeb = core.qweb; var KANBAN_RECORD_COLORS = require('web.basic_fields').FieldColorPicker.prototype.RECORD_COLORS; var NB_KANBAN_RECORD_COLORS = KANBAN_RECORD_COLORS.length; -var KanbanRecord = Widget.extend({ +var KanbanRecord = Widget.extend(WidgetAdapterMixin, { events: { 'click .oe_kanban_action': '_onKanbanActionClicked', 'click .o_kanban_manage_toggle_button': '_onManageTogglerClicked', @@ -54,12 +57,21 @@ var KanbanRecord = Widget.extend({ start: function () { return Promise.all([this._super.apply(this, arguments), this._render()]); }, + /** + * @override + */ + destroy: function () { + this._super(...arguments); + WidgetAdapterMixin.destroy.call(this); + }, /** * Called each time the record is attached to the DOM. */ on_attach_callback: function () { this.isInDOM = true; _.invoke(this.subWidgets, 'on_attach_callback'); + // call on_attach_callback on child components (tag ) + WidgetAdapterMixin.on_attach_callback.call(this); }, /** * Called each time the record is detached from the DOM. @@ -67,6 +79,8 @@ var KanbanRecord = Widget.extend({ on_detach_callback: function () { this.isInDOM = false; _.invoke(this.subWidgets, 'on_detach_callback'); + // call on_detach_callback on child components (tag ) + WidgetAdapterMixin.on_detach_callback.call(this); }, //-------------------------------------------------------------------------- @@ -330,10 +344,24 @@ var KanbanRecord = Widget.extend({ var self = this; this.$("widget").each(function () { var $field = $(this); - var Widget = widgetRegistry.get($field.attr('name')); - var widget = new Widget(self, self.state); - var def = widget._widgetRenderAndInsert(function () { }); + const name = $field.attr('name'); + const Widget = widgetRegistryOwl.get(name) || widgetRegistry.get(name); + const legacy = !(Widget.prototype instanceof owl.Component); + let widget; + if (legacy) { + widget = new Widget(self, self.state); + } else { + widget = new WidgetWrapper(this, Widget, { record: self.state }); + } + // Prepare widget rendering and save the related promise + let def; + if (legacy) { + def = widget._widgetRenderAndInsert(function () { }); + } else { + def = widget.mount(document.createDocumentFragment()); + } + self.defs.push(def); def.then(function () { $field.replaceWith(widget.$el); diff --git a/addons/web/static/tests/views/form_tests.js b/addons/web/static/tests/views/form_tests.js index cc611fefc39..79ef2a7919d 100644 --- a/addons/web/static/tests/views/form_tests.js +++ b/addons/web/static/tests/views/form_tests.js @@ -16,6 +16,7 @@ var pyUtils = require('web.py_utils'); var RamStorage = require('web.RamStorage'); var testUtils = require('web.test_utils'); var widgetRegistry = require('web.widget_registry'); +const widgetRegistryOwl = require('web.widgetRegistry'); var Widget = require('web.Widget'); var _t = core._t; @@ -7854,6 +7855,8 @@ QUnit.module('Views', { }); QUnit.test('basic support for widgets', async function (assert) { + // This test could be removed as soon as we drop the support of legacy widgets (see test + // below, which is a duplicate of this one, but with an Owl Component instead). assert.expect(1); var MyWidget = Widget.extend({ @@ -7884,6 +7887,35 @@ QUnit.module('Views', { delete widgetRegistry.map.test; }); + QUnit.test('basic support for widgets (being Owl Components)', async function (assert) { + assert.expect(1); + + class MyComponent extends owl.Component { + get value() { + return JSON.stringify(this.props.record.data); + } + } + MyComponent.template = owl.tags.xml`
`; + widgetRegistryOwl.add('test', MyComponent); + + const form = await createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: ` +
+ + + + `, + }); + + assert.strictEqual(form.$('.o_widget').text(), '{"foo":"My little Foo Value","bar":false}'); + + form.destroy(); + delete widgetRegistryOwl.map.test; + }); + QUnit.test('attach document widget calls action with attachment ids', async function (assert) { assert.expect(1); diff --git a/addons/web/static/tests/views/kanban_tests.js b/addons/web/static/tests/views/kanban_tests.js index f3de29c8b42..b548b5838e3 100644 --- a/addons/web/static/tests/views/kanban_tests.js +++ b/addons/web/static/tests/views/kanban_tests.js @@ -12,6 +12,7 @@ var mixins = require('web.mixins'); var testUtils = require('web.test_utils'); var Widget = require('web.Widget'); var widgetRegistry = require('web.widget_registry'); +const widgetRegistryOwl = require('web.widgetRegistry'); var makeTestPromise = testUtils.makeTestPromise; var nextTick = testUtils.nextTick; @@ -5709,6 +5710,8 @@ QUnit.module('Views', { }); QUnit.test('basic support for widgets', async function (assert) { + // This test could be removed as soon as we drop the support of legacy widgets (see test + // below, which is a duplicate of this one, but with an Owl Component instead). assert.expect(1); var MyWidget = Widget.extend({ @@ -5741,6 +5744,41 @@ QUnit.module('Views', { delete widgetRegistry.map.test; }); + QUnit.test('basic support for widgets (being Owl Components)', async function (assert) { + assert.expect(1); + + class MyComponent extends owl.Component { + get value() { + return JSON.stringify(this.props.record.data); + } + } + MyComponent.template = owl.tags.xml`
`; + widgetRegistryOwl.add('test', MyComponent); + + const kanban = await createView({ + View: KanbanView, + model: 'partner', + data: this.data, + arch: ` + + + + +
+ + +
+
+
+
`, + }); + + assert.strictEqual(kanban.$('.o_widget:eq(2)').text(), '{"foo":"gnap","id":3}'); + + kanban.destroy(); + delete widgetRegistryOwl.map.test; + }); + QUnit.test('subwidgets with on_attach_callback when changing record color', async function (assert) { assert.expect(3); diff --git a/addons/web/static/tests/views/list_tests.js b/addons/web/static/tests/views/list_tests.js index 5b6d2430b50..d146a538b18 100644 --- a/addons/web/static/tests/views/list_tests.js +++ b/addons/web/static/tests/views/list_tests.js @@ -18,6 +18,7 @@ var RamStorage = require('web.RamStorage'); var testUtils = require('web.test_utils'); const { patch, unpatch } = require('web.utils'); var widgetRegistry = require('web.widget_registry'); +const widgetRegistryOwl = require('web.widgetRegistry'); var Widget = require('web.Widget'); @@ -8647,6 +8648,8 @@ QUnit.module('Views', { }); QUnit.test('basic support for widgets', async function (assert) { + // This test could be removed as soon as we drop the support of legacy widgets (see test + // below, which is a duplicate of this one, but with an Owl Component instead). assert.expect(1); var MyWidget = Widget.extend({ @@ -8673,6 +8676,30 @@ QUnit.module('Views', { delete widgetRegistry.map.test; }); + QUnit.test('basic support for widgets (being Owl Components)', async function (assert) { + assert.expect(1); + + class MyComponent extends owl.Component { + get value() { + return JSON.stringify(this.props.record.data); + } + } + MyComponent.template = owl.tags.xml`
`; + widgetRegistryOwl.add('test', MyComponent); + + const list = await createView({ + View: ListView, + model: 'foo', + data: this.data, + arch: '', + }); + + assert.strictEqual(list.$('.o_widget').first().text(), '{"foo":"yop","int_field":10,"id":1}'); + + list.destroy(); + delete widgetRegistryOwl.map.test; + }); + QUnit.test('use the limit attribute in arch', async function (assert) { assert.expect(4); diff --git a/addons/web/views/webclient_templates.xml b/addons/web/views/webclient_templates.xml index 3f83e9616b1..a7b0487ad5a 100644 --- a/addons/web/views/webclient_templates.xml +++ b/addons/web/views/webclient_templates.xml @@ -305,6 +305,8 @@ + +