[REF] web: convert FieldBoolean to owl

This commit does 4 things:
- adds a file for deprecated legacy fields.
- moves legacy FieldBoolean into this file.
- converts FieldBoolean to owl component.
- adds a new xml file for component fields' templates.

task id: 2193996
This commit is contained in:
Michael Mattiello (mcm)
2020-03-31 07:11:01 +00:00
parent 9324282b01
commit 4520a09f2c
7 changed files with 285 additions and 130 deletions
+1
View File
@@ -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",
@@ -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,
};
});
+3 -128
View File
@@ -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',
@@ -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,
};
});
+9
View File
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates id="template" xml:space="preserve">
<t t-name="web.FieldBoolean" owl="1">
<CustomCheckbox disabled="mode === 'readonly'" value="value"
class="o_field_boolean" t-on-change="_onChange"/>
</t>
</templates>
@@ -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");
+2
View File
@@ -290,6 +290,7 @@
<script type="text/javascript" src="/web/static/src/js/components/pager.js"></script>
<script type="text/javascript" src="/web/static/src/js/apps.js"></script>
<script type="text/javascript" src="/web/static/src/js/_deprecated/basic_fields.js"></script>
<script type="text/javascript" src="/web/static/src/js/fields/abstract_field.js"></script>
<script type="text/javascript" src="/web/static/src/js/fields/basic_fields.js"></script>
<script type="text/javascript" src="/web/static/src/js/fields/field_registry.js"></script>
@@ -301,6 +302,7 @@
<script type="text/javascript" src="/web/static/src/js/fields/upgrade_fields.js"></script>
<script type="text/javascript" src="/web/static/src/js/fields/field_wrapper.js"></script>
<script type="text/javascript" src="/web/static/src/js/fields/abstract_field_owl.js"></script>
<script type="text/javascript" src="/web/static/src/js/fields/basic_fields_owl.js"></script>
<script type="text/javascript" src="/web/static/src/js/views/abstract_view.js"></script>
<script type="text/javascript" src="/web/static/src/js/views/abstract_renderer.js"></script>