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 @@
+
+