diff --git a/addons/web/__manifest__.py b/addons/web/__manifest__.py
index ccfbd9b8499..e72fabaff2a 100644
--- a/addons/web/__manifest__.py
+++ b/addons/web/__manifest__.py
@@ -23,6 +23,7 @@ This module provides the core of the Odoo Web Client.
'qweb': [
"static/src/xml/base.xml",
"static/src/xml/chart.xml",
+ "static/src/xml/fields.xml",
"static/src/xml/file_upload_progress_bar.xml",
"static/src/xml/file_upload_progress_card.xml",
"static/src/xml/kanban.xml",
diff --git a/addons/web/static/src/js/_deprecated/basic_fields.js b/addons/web/static/src/js/_deprecated/basic_fields.js
new file mode 100644
index 00000000000..2a70c3793c0
--- /dev/null
+++ b/addons/web/static/src/js/_deprecated/basic_fields.js
@@ -0,0 +1,154 @@
+////////////////////////////////////////////////////////////////////////////////
+// /!\ DEPRECATED
+//
+// Legacy Field Widgets are added in this file when they are converted into
+// Owl Component.
+////////////////////////////////////////////////////////////////////////////////
+
+odoo.define('web.basic_fields.deprecated', function (require) {
+"use strict";
+
+/**
+ * This module contains most of the basic (meaning: non relational) field
+ * widgets. Field widgets are supposed to be used in views inheriting from
+ * BasicView, so, they can work with the records obtained from a BasicModel.
+ */
+
+var AbstractField = require('web.AbstractField');
+var core = require('web.core');
+
+var _lt = core._lt;
+
+var FieldBoolean = AbstractField.extend({
+ className: 'o_field_boolean',
+ description: _lt("Checkbox"),
+ events: _.extend({}, AbstractField.prototype.events, {
+ change: '_onChange',
+ }),
+ supportedFieldTypes: ['boolean'],
+
+ //--------------------------------------------------------------------------
+ // Public
+ //--------------------------------------------------------------------------
+
+ /**
+ * Toggle the checkbox if it is activated due to a click on itself.
+ *
+ * @override
+ */
+ activate: function (options) {
+ var activated = this._super.apply(this, arguments);
+ // The formatValue of boolean fields renders HTML elements similar to
+ // the one rendered by the widget itself. Even though the event might
+ // have been fired on the non-widget version of this field, we can still
+ // test the presence of its custom class.
+ if (activated && options && options.event && $(options.event.target).closest('.custom-control.custom-checkbox').length) {
+ this._setValue(!this.value); // Toggle the checkbox
+ }
+ return activated;
+ },
+
+ /**
+ * @override
+ * @returns {jQuery} the focusable checkbox input
+ */
+ getFocusableElement: function () {
+ return this.mode === 'readonly' ? $() : this.$input;
+ },
+ /**
+ * A boolean field is always set since false is a valid value.
+ *
+ * @override
+ */
+ isSet: function () {
+ return true;
+ },
+ /**
+ * When the checkbox is rerendered, we need to check if it was the actual
+ * origin of the reset. If it is, we need to activate it back so it looks
+ * like it was not rerendered but is still the same input.
+ *
+ * @override
+ */
+ reset: function (record, event) {
+ var rendered = this._super.apply(this, arguments);
+ if (event && event.target.name === this.name) {
+ this.activate();
+ }
+ return rendered;
+ },
+ /**
+ * Associates the 'for' attribute of the internal label.
+ *
+ * @override
+ */
+ setIDForLabel: function (id) {
+ this._super.apply(this, arguments);
+ this.$('.custom-control-label').attr('for', id);
+ },
+
+ //--------------------------------------------------------------------------
+ // Private
+ //--------------------------------------------------------------------------
+
+ /**
+ * The actual checkbox is designed in css to have full control over its
+ * appearance, as opposed to letting the browser and the os decide how
+ * a checkbox should look. The actual input is disabled and hidden. In
+ * readonly mode, the checkbox is disabled.
+ *
+ * @override
+ * @private
+ */
+ _render: function () {
+ var $checkbox = this._formatValue(this.value);
+ this.$input = $checkbox.find('input');
+ this.$input.prop('disabled', this.mode === 'readonly');
+ this.$el.addClass($checkbox.attr('class'));
+ this.$el.empty().append($checkbox.contents());
+ },
+
+ //--------------------------------------------------------------------------
+ // Handlers
+ //--------------------------------------------------------------------------
+
+ /**
+ * Properly update the value when the checkbox is (un)ticked to trigger
+ * possible onchanges.
+ *
+ * @private
+ */
+ _onChange: function () {
+ this._setValue(this.$input[0].checked);
+ },
+ /**
+ * Implement keyboard movements. Mostly useful for its environment, such
+ * as a list view.
+ *
+ * @override
+ * @private
+ * @param {KeyEvent} ev
+ */
+ _onKeydown: function (ev) {
+ switch (ev.which) {
+ case $.ui.keyCode.ENTER:
+ // prevent subsequent 'click' event (see _onKeydown of AbstractField)
+ ev.preventDefault();
+ this.$input.prop('checked', !this.value);
+ this._setValue(!this.value);
+ return;
+ case $.ui.keyCode.UP:
+ case $.ui.keyCode.RIGHT:
+ case $.ui.keyCode.DOWN:
+ case $.ui.keyCode.LEFT:
+ ev.preventDefault();
+ }
+ this._super.apply(this, arguments);
+ },
+});
+
+return {
+ FieldBoolean: FieldBoolean,
+};
+
+});
diff --git a/addons/web/static/src/js/fields/basic_fields.js b/addons/web/static/src/js/fields/basic_fields.js
index 3a3a037a0f0..ce7a7fac07d 100644
--- a/addons/web/static/src/js/fields/basic_fields.js
+++ b/addons/web/static/src/js/fields/basic_fields.js
@@ -11,6 +11,7 @@ var AbstractField = require('web.AbstractField');
var config = require('web.config');
var core = require('web.core');
var datepicker = require('web.datepicker');
+var deprecatedFields = require('web.basic_fields.deprecated');
var dom = require('web.dom');
var Domain = require('web.Domain');
var DomainSelector = require('web.DomainSelector');
@@ -24,6 +25,8 @@ var field_utils = require('web.field_utils');
var time = require('web.time');
var ColorpickerDialog = require('web.ColorpickerDialog');
+let FieldBoolean = deprecatedFields.FieldBoolean;
+
require("web.zoomodoo");
var qweb = core.qweb;
@@ -986,134 +989,6 @@ var FieldMonetary = NumericField.extend({
},
});
-var FieldBoolean = AbstractField.extend({
- className: 'o_field_boolean',
- description: _lt("Checkbox"),
- events: _.extend({}, AbstractField.prototype.events, {
- change: '_onChange',
- }),
- supportedFieldTypes: ['boolean'],
-
- //--------------------------------------------------------------------------
- // Public
- //--------------------------------------------------------------------------
-
- /**
- * Toggle the checkbox if it is activated due to a click on itself.
- *
- * @override
- */
- activate: function (options) {
- var activated = this._super.apply(this, arguments);
- // The formatValue of boolean fields renders HTML elements similar to
- // the one rendered by the widget itself. Even though the event might
- // have been fired on the non-widget version of this field, we can still
- // test the presence of its custom class.
- if (activated && options && options.event && $(options.event.target).closest('.custom-control.custom-checkbox').length) {
- this._setValue(!this.value); // Toggle the checkbox
- }
- return activated;
- },
-
- /**
- * @override
- * @returns {jQuery} the focusable checkbox input
- */
- getFocusableElement: function () {
- return this.mode === 'readonly' ? $() : this.$input;
- },
- /**
- * A boolean field is always set since false is a valid value.
- *
- * @override
- */
- isSet: function () {
- return true;
- },
- /**
- * When the checkbox is rerendered, we need to check if it was the actual
- * origin of the reset. If it is, we need to activate it back so it looks
- * like it was not rerendered but is still the same input.
- *
- * @override
- */
- reset: function (record, event) {
- var rendered = this._super.apply(this, arguments);
- if (event && event.target.name === this.name) {
- this.activate();
- }
- return rendered;
- },
- /**
- * Associates the 'for' attribute of the internal label.
- *
- * @override
- */
- setIDForLabel: function (id) {
- this._super.apply(this, arguments);
- this.$('.custom-control-label').attr('for', id);
- },
-
- //--------------------------------------------------------------------------
- // Private
- //--------------------------------------------------------------------------
-
- /**
- * The actual checkbox is designed in css to have full control over its
- * appearance, as opposed to letting the browser and the os decide how
- * a checkbox should look. The actual input is disabled and hidden. In
- * readonly mode, the checkbox is disabled.
- *
- * @override
- * @private
- */
- _render: function () {
- var $checkbox = this._formatValue(this.value);
- this.$input = $checkbox.find('input');
- this.$input.prop('disabled', this.mode === 'readonly');
- this.$el.addClass($checkbox.attr('class'));
- this.$el.empty().append($checkbox.contents());
- },
-
- //--------------------------------------------------------------------------
- // Handlers
- //--------------------------------------------------------------------------
-
- /**
- * Properly update the value when the checkbox is (un)ticked to trigger
- * possible onchanges.
- *
- * @private
- */
- _onChange: function () {
- this._setValue(this.$input[0].checked);
- },
- /**
- * Implement keyboard movements. Mostly useful for its environment, such
- * as a list view.
- *
- * @override
- * @private
- * @param {KeyEvent} ev
- */
- _onKeydown: function (ev) {
- switch (ev.which) {
- case $.ui.keyCode.ENTER:
- // prevent subsequent 'click' event (see _onKeydown of AbstractField)
- ev.preventDefault();
- this.$input.prop('checked', !this.value);
- this._setValue(!this.value);
- return;
- case $.ui.keyCode.UP:
- case $.ui.keyCode.RIGHT:
- case $.ui.keyCode.DOWN:
- case $.ui.keyCode.LEFT:
- ev.preventDefault();
- }
- this._super.apply(this, arguments);
- },
-});
-
var FieldInteger = NumericField.extend({
description: _lt("Integer"),
className: 'o_field_integer o_field_number',
diff --git a/addons/web/static/src/js/fields/basic_fields_owl.js b/addons/web/static/src/js/fields/basic_fields_owl.js
new file mode 100644
index 00000000000..a00b14fb9ee
--- /dev/null
+++ b/addons/web/static/src/js/fields/basic_fields_owl.js
@@ -0,0 +1,111 @@
+odoo.define('web.basic_fields_owl', function (require) {
+ "use strict";
+
+ const AbstractField = require('web.AbstractFieldOwl');
+ const CustomCheckbox = require('web.CustomCheckbox');
+ const core = require('web.core');
+
+ const _lt = core._lt;
+
+ class FieldBoolean extends AbstractField {
+ patched() {
+ super.patched();
+ if (this.props.event && this.props.event.target === this) {
+ this.activate();
+ }
+ }
+
+ //----------------------------------------------------------------------
+ // Public
+ //----------------------------------------------------------------------
+
+ /**
+ * @override
+ * @returns {HTMLElement|null} the focusable checkbox input
+ */
+ get focusableElement() {
+ return this.mode === 'readonly' ? null : this.el.querySelector('input');
+ }
+ /**
+ * A boolean field is always set since false is a valid value.
+ *
+ * @override
+ */
+ get isSet() {
+ return true;
+ }
+ /**
+ * Toggle the checkbox if it is activated due to a click on itself.
+ *
+ * @override
+ * @param {Object} [options]
+ * @param {Event} [options.event] the event which fired this activation
+ * @returns {boolean} true if the component was activated, false if the
+ * focusable element was not found or invisible
+ */
+ activate(options) {
+ const activated = super.activate(options);
+ // The event might have been fired on the non field version of
+ // this field, we can still test the presence of its custom class.
+ if (activated && options && options.event && options.event.target
+ .closest('.custom-control.custom-checkbox')) {
+ this._setValue(!this.value); // Toggle the checkbox
+ }
+ return activated;
+ }
+ /**
+ * Associates the 'for' attribute of the internal label.
+ *
+ * @override
+ */
+ setIdForLabel(id) {
+ super.setIdForLabel(id);
+ this.el.querySelector('label').setAttribute('for', id);
+ }
+
+ //----------------------------------------------------------------------
+ // Handlers
+ //----------------------------------------------------------------------
+
+ /**
+ * Properly update the value when the checkbox is (un)ticked to trigger
+ * possible onchanges.
+ *
+ * @private
+ */
+ _onChange(ev) {
+ this._setValue(ev.target.checked);
+ }
+ /**
+ * Implement keyboard movements. Mostly useful for its environment, such
+ * as a list view.
+ *
+ * @override
+ * @private
+ * @param {KeyEvent} ev
+ */
+ _onKeydown(ev) {
+ switch (ev.which) {
+ case $.ui.keyCode.ENTER:
+ // prevent subsequent 'click' event (see _onKeydown of AbstractField)
+ ev.preventDefault();
+ this._setValue(!this.value);
+ return;
+ case $.ui.keyCode.UP:
+ case $.ui.keyCode.RIGHT:
+ case $.ui.keyCode.DOWN:
+ case $.ui.keyCode.LEFT:
+ ev.preventDefault();
+ }
+ super._onKeydown(ev);
+ }
+ }
+ FieldBoolean.components = { CustomCheckbox };
+ FieldBoolean.description = _lt("Checkbox");
+ FieldBoolean.supportedFieldTypes = ['boolean'];
+ FieldBoolean.template = 'web.FieldBoolean';
+
+ return {
+ FieldBoolean,
+ };
+});
diff --git a/addons/web/static/src/xml/fields.xml b/addons/web/static/src/xml/fields.xml
new file mode 100644
index 00000000000..ff7939f36a3
--- /dev/null
+++ b/addons/web/static/src/xml/fields.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
diff --git a/addons/web/static/tests/fields/basic_fields_tests.js b/addons/web/static/tests/fields/basic_fields_tests.js
index 6bd62b2d25c..710dd66e40e 100644
--- a/addons/web/static/tests/fields/basic_fields_tests.js
+++ b/addons/web/static/tests/fields/basic_fields_tests.js
@@ -267,12 +267,14 @@ QUnit.module('basic_fields', {
assert.containsOnce(form, '.o_field_boolean input:checked',
"checkbox should now be checked");
// blindly press enter again, it should uncheck the checkbox
- $(document.activeElement).trigger({type: "keydown", which: $.ui.keyCode.ENTER});
+ await testUtils.dom.triggerEvent(document.activeElement, "keydown",
+ {which: $.ui.keyCode.ENTER});
assert.containsNone(form, '.o_field_boolean input:checked',
"checkbox should not be checked");
await testUtils.nextTick();
// blindly press enter again, it should check the checkbox back
- $(document.activeElement).trigger({type: "keydown", which: $.ui.keyCode.ENTER});
+ await testUtils.dom.triggerEvent(document.activeElement, "keydown",
+ {which: $.ui.keyCode.ENTER});
assert.containsOnce(form, '.o_field_boolean input:checked',
"checkbox should still be checked");
@@ -333,6 +335,7 @@ QUnit.module('basic_fields', {
// the checkbox directly in readonly mode !
$cell = list.$('tr.o_data_row:has(.custom-checkbox input:not(:checked)) td:not(.o_list_record_selector)').first();
await testUtils.dom.click($cell.find('.custom-checkbox .custom-control-label'));
+ await testUtils.nextTick();
assert.strictEqual(list.$('tbody td:not(.o_list_record_selector) .custom-checkbox input').length, 5,
"should still have 5 checkboxes");
diff --git a/addons/web/views/webclient_templates.xml b/addons/web/views/webclient_templates.xml
index 4778e5a328c..20bf8b43205 100644
--- a/addons/web/views/webclient_templates.xml
+++ b/addons/web/views/webclient_templates.xml
@@ -290,6 +290,7 @@
+
@@ -301,6 +302,7 @@
+