From 877ef5552ba6d7b3bfc39d83075e4a10121942ae Mon Sep 17 00:00:00 2001 From: Mohammed Shekha Date: Fri, 11 Sep 2020 07:12:42 +0000 Subject: [PATCH] [IMP] web: add support of owl custom widget add support of tag for owl, In order to prepare the future, we want to convert everything in Owl, in future widgets generated by tag will also be converted to owl. with this commit we support widget to instantiate using ComponentWrapper. task-2337692 Co-authored-by: Aaron Bohy --- .../web/static/src/js/fields/field_wrapper.js | 3 -- addons/web/static/src/js/owl_compatibility.js | 8 ++++ .../src/js/views/basic/basic_renderer.js | 26 +++++++++++-- .../src/js/views/basic/widget_registry.js | 12 +++++- .../src/js/views/basic/widget_registry_owl.js | 24 ++++++++++++ .../src/js/views/basic/widget_wrapper.js | 19 ++++++++++ .../src/js/views/kanban/kanban_record.js | 36 ++++++++++++++++-- addons/web/static/tests/views/form_tests.js | 32 ++++++++++++++++ addons/web/static/tests/views/kanban_tests.js | 38 +++++++++++++++++++ addons/web/static/tests/views/list_tests.js | 27 +++++++++++++ addons/web/views/webclient_templates.xml | 2 + 11 files changed, 215 insertions(+), 12 deletions(-) create mode 100644 addons/web/static/src/js/views/basic/widget_registry_owl.js create mode 100644 addons/web/static/src/js/views/basic/widget_wrapper.js 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 @@ + +