diff --git a/addons/account/static/src/js/reconciliation/reconciliation_renderer.js b/addons/account/static/src/js/reconciliation/reconciliation_renderer.js
index 6a43610596d..c2174fdce7f 100644
--- a/addons/account/static/src/js/reconciliation/reconciliation_renderer.js
+++ b/addons/account/static/src/js/reconciliation/reconciliation_renderer.js
@@ -52,9 +52,11 @@ var StatementRenderer = Widget.extend(FieldManagerMixin, {
self.handleNameRecord = recordID;
self.name = new basic_fields.FieldChar(self,
'name', self.model.get(self.handleNameRecord),
- {mode: 'edit', required: true});
+ {mode: 'edit'});
- self.name.appendTo(self.$('.statement_name_edition'));
+ self.name.appendTo(self.$('.statement_name_edition')).then(function () {
+ self.name.$el.addClass('o_form_required');
+ });
self.$('.statement_name').text(self._initialState.bank_statement_id.display_name);
});
defs.push(def);
@@ -422,7 +424,7 @@ var LineRenderer = Widget.extend(FieldManagerMixin, {
var record = self.model.get(self.handleCreateRecord);
self.fields.account_id = new relational_fields.FieldMany2One(self,
- 'account_id', record, {mode: 'edit', required: true});
+ 'account_id', record, {mode: 'edit'});
self.fields.journal_id = new relational_fields.FieldMany2One(self,
'journal_id', record, {mode: 'edit'});
@@ -434,19 +436,26 @@ var LineRenderer = Widget.extend(FieldManagerMixin, {
'analytic_account_id', record, {mode: 'edit'});
self.fields.label = new basic_fields.FieldChar(self,
- 'label', record, {mode: 'edit', required: true});
+ 'label', record, {mode: 'edit'});
self.fields.amount = new basic_fields.FieldFloat(self,
- 'amount', record, {mode: 'edit', required: true});
+ 'amount', record, {mode: 'edit'});
var $create = $(qweb.render("reconciliation.line.create", {'state': state}));
- self.fields.account_id.appendTo($create.find('.create_account_id .o_td_field'));
+ self.fields.account_id.appendTo($create.find('.create_account_id .o_td_field'))
+ .then(addRequiredStyle.bind(self, self.fields.account_id));
self.fields.journal_id.appendTo($create.find('.create_journal_id .o_td_field'));
self.fields.tax_id.appendTo($create.find('.create_tax_id .o_td_field'));
self.fields.analytic_account_id.appendTo($create.find('.create_analytic_account_id .o_td_field'));
- self.fields.label.appendTo($create.find('.create_label .o_td_field'));
- self.fields.amount.appendTo($create.find('.create_amount .o_td_field'));
+ self.fields.label.appendTo($create.find('.create_label .o_td_field'))
+ .then(addRequiredStyle.bind(self, self.fields.label));
+ self.fields.amount.appendTo($create.find('.create_amount .o_td_field'))
+ .then(addRequiredStyle.bind(self, self.fields.amount));
self.$('.create').append($create);
+
+ function addRequiredStyle(widget) {
+ widget.$el.addClass('o_form_required');
+ }
});
},
diff --git a/addons/mail/static/src/js/form_renderer.js b/addons/mail/static/src/js/form_renderer.js
index cca9d18cd15..4d07168e501 100644
--- a/addons/mail/static/src/js/form_renderer.js
+++ b/addons/mail/static/src/js/form_renderer.js
@@ -27,7 +27,7 @@ FormRenderer.include({
*
* @override
*/
- updateWidgets: function (fields, state) {
+ confirmChange: function (state, id, fields) {
if (this.chatter) {
var updatedMailFields = _.intersection(fields, _.values(this.mailFields));
if (updatedMailFields.length) {
diff --git a/addons/sale/static/src/js/tour.js b/addons/sale/static/src/js/tour.js
index 241014c74ce..2a3ec907b8b 100644
--- a/addons/sale/static/src/js/tour.js
+++ b/addons/sale/static/src/js/tour.js
@@ -39,7 +39,7 @@ tour.register('sale_tour', {
content: _t("Click here to add some lines to your quotations."),
position: "bottom",
}, {
- trigger: ".modal-body .o_form_required input, .o_list_view .o_required_field input",
+ trigger: ".modal-body .o_form_required input, .o_list_view .o_form_required input",
extra_trigger: ".o_sale_order",
content: _t("Select a product, or create a new one on the fly. The product will define the default sales price (that you can change), taxes and description automatically."),
position: "right",
diff --git a/addons/web/static/src/js/fields/abstract_field.js b/addons/web/static/src/js/fields/abstract_field.js
index f36418e3b8b..0d7ed2f4fe2 100644
--- a/addons/web/static/src/js/fields/abstract_field.js
+++ b/addons/web/static/src/js/fields/abstract_field.js
@@ -32,29 +32,18 @@ odoo.define('web.AbstractField', function (require) {
var field_utils = require('web.field_utils');
var Widget = require('web.Widget');
-var Domain = require('web.Domain');
var AbstractField = Widget.extend({
className: 'o_field_widget',
events: {
'keydown': '_onKeydown',
},
- /**
- * if this flag is set to true, the rest of the web client will assume that
- * it is not editable. For example, the list view in editable mode will
- * skip the widget when pressing tab
- */
- readonly: false,
/**
* If this flag is set to true, the field widget will be reset on every
* change which is made in the view (if the view supports it). This is
* currently a form view feature.
*/
resetOnAnyFieldChange: false,
- /**
- * if true, the widget will replace a cell in an editable list view
- */
- replace_element: false,
/**
* If this flag is given a string, the related BasicModel will be used to
* initialize specialData the field might need. This data will be available
@@ -77,8 +66,6 @@ var AbstractField = Widget.extend({
* @param {Object} record A record object (result of the get method of a basic model)
* @param {Object} [options]
* @param {string} [options.mode=readonly] should be 'readonly' or 'edit'
- * @param {string} [options.required=false]
- * @param {string} [options.idForLabel]
*/
init: function (parent, name, record, options) {
this._super(parent);
@@ -103,9 +90,6 @@ var AbstractField = Widget.extend({
var fieldsInfo = record.fieldsInfo[this.viewType];
this.attrs = fieldsInfo && fieldsInfo[name] || {};
- // modifiers
- this.modifiers = JSON.parse(this.attrs.modifiers || "{}");
-
// this property tracks the current (parsed if needed) value of the field.
// Note that we don't use an event system anymore, using this.get('value')
// is no longer valid.
@@ -153,15 +137,6 @@ var AbstractField = Widget.extend({
// a string.
this._isValid = true;
- // the 'required' flag is basically only needed to determine if the widget
- // is in a valid state (not valid if empty and required)
- this.required = options.required || this.field.required;
-
- // the 'idForLabel' is the (html) id that should be set to the relevent
- // dom entity. If done correctly, clicking on the corresponding label
- // (in form view) will focus and select the value.
- this.idForLabel = options.idForLabel;
-
// this is the last value that was set by the user, unparsed. This is
// used to avoid setting the value twice in a row with the exact value.
this.lastSetValue = undefined;
@@ -175,8 +150,8 @@ var AbstractField = Widget.extend({
start: function () {
var self = this;
return this._super.apply(this, arguments).then(function () {
- self._render();
self.$el.attr('name', self.name);
+ return self._render();
});
},
@@ -185,11 +160,22 @@ var AbstractField = Widget.extend({
//--------------------------------------------------------------------------
/**
- * Right now, this function is only used in editable list view when the user
- * select a cell. The corresponding widget will be activated, which in general
- * means that the input text will be focused and selected
+ * Activates the field widget. By default, activation means focusing and
+ * selecting the associated focusable element.
+ *
+ * @returns {boolean} true if the widget was activated, false if the
+ * focusable element was not found or invisible
*/
activate: function () {
+ var $focusable = this.getFocusableElement();
+ if ($focusable.length && $focusable.is(':visible')) {
+ $focusable.focus();
+ if ($focusable.is('input[type="text"], textarea')) {
+ setTimeout($focusable.select.bind($focusable), 0);
+ }
+ return true;
+ }
+ return false;
},
/**
* This function should be implemented by widgets that are not able to
@@ -201,6 +187,15 @@ var AbstractField = Widget.extend({
*/
commitChanges: function () {
},
+ /**
+ * Returns the main field's DOM element (jQuery form) which can be focused
+ * by the browser.
+ *
+ * @returns {jQuery} main focusable element inside the widget
+ */
+ getFocusableElement: function () {
+ return $();
+ },
/**
* this method is used to determine if the field value is set to a meaningful
* value. This is useful to determine if a field should be displayed as empty
@@ -211,23 +206,23 @@ var AbstractField = Widget.extend({
return !!this.value;
},
/**
- * a field widget is valid if its value is valid and if there is a value when
- * it is required. This is checked before saving a record, by the form view.
+ * A field widget is valid if it was checked as valid the last time its
+ * value was changed by the user. This is checked before saving a record, by
+ * the view.
*
- * Note: the return value may be a boolean. It is necessary for some fields
- * (x2many) to sometimes ask confirmation to the user if the current dirty
- * invalid line can be discarded. This is an asynchronous operation, so we
- * need the expressivity of something like a deferred.
+ * Note: the return value may not be a boolean. It is necessary for some
+ * fields (x2many) to sometimes ask confirmation to the user if the current
+ * dirty invalid line can be discarded. This is an asynchronous operation,
+ * so we need the expressivity of something like a deferred.
+ *
+ * Note2: this is the responsability of the view to check that required
+ * fields have a set value.
*
* @returns {boolean|Deferred} true/false if the widget is valid, or a
* deferred which resolves to true/false if the widget is valid or not
*/
isValid: function () {
- var is_required = this.required;
- if ('required' in this.modifiers) {
- is_required = new Domain(this.modifiers.required).compute(this.recordData);
- }
- return this._isValid && !(is_required && !this.isSet());
+ return this._isValid;
},
/**
* this method is supposed to be called from the outside of field widgets.
@@ -285,12 +280,6 @@ var AbstractField = Widget.extend({
* @returns {Deferred}
*/
_render: function () {
- var is_required = this.required;
- if ('required' in this.modifiers) {
- is_required = new Domain(this.modifiers.required).compute(this.recordData);
- }
- this.$el.toggleClass('o_form_required', is_required);
-
if (this.mode === 'edit') {
return this._renderEdit();
} else if (this.mode === 'readonly') {
@@ -347,7 +336,7 @@ var AbstractField = Widget.extend({
try {
value = this._parseValue(value);
this._isValid = true;
- } catch(e) {
+ } catch (e) {
this._isValid = false;
}
var changes = {};
diff --git a/addons/web/static/src/js/fields/basic_fields.js b/addons/web/static/src/js/fields/basic_fields.js
index d2649195111..9f37240e0cc 100644
--- a/addons/web/static/src/js/fields/basic_fields.js
+++ b/addons/web/static/src/js/fields/basic_fields.js
@@ -122,13 +122,12 @@ var InputField = DebouncedField.extend({
//--------------------------------------------------------------------------
/**
- * Automatically selects the content of the field.
+ * Returns the associated element.
*
* @override
*/
- activate: function () {
- this.$input.focus();
- setTimeout(this.$input.select.bind(this.$input), 0);
+ getFocusableElement: function () {
+ return this.$input || $();
},
/**
* Do not re-render this field if it was the origin of the onchange call.
@@ -171,7 +170,6 @@ var InputField = DebouncedField.extend({
this.$input.addClass('o_form_input');
this.$input.attr({
type: 'text',
- id: this.idForLabel,
placeholder: this.attrs.placeholder || "",
});
// Save cursor position to restore it after updating value
@@ -253,7 +251,6 @@ var FieldChar = InputField.extend({
var FieldDate = InputField.extend({
className: "o_form_field_date",
tagName: "span",
- replace_element: true,
supportedFieldTypes: ['date'],
/**
@@ -274,8 +271,7 @@ var FieldDate = InputField.extend({
});
def = this.datewidget.appendTo('
').done(function () {
self.datewidget.$el.addClass(self.$el.attr('class'));
- self.datewidget.$input.addClass('o_form_input');
- self.datewidget.$input.attr('id', self.idForLabel);
+ self._prepareInput(self.datewidget.$input);
self.replaceElement(self.datewidget.$el);
});
}
@@ -371,7 +367,6 @@ var FieldDateTime = FieldDate.extend({
var FieldMonetary = InputField.extend({
className: 'o_form_field_monetary o_list_number',
- replace_element: true,
supportedFieldTypes: ['float', 'monetary'],
resetOnAnyFieldChange: true, // Have to listen to currency changes
@@ -484,7 +479,6 @@ var FieldBoolean = AbstractField.extend({
events: _.extend({}, AbstractField.prototype.events, {
change: '_onChange',
}),
- replace_element: true,
supportedFieldTypes: ['boolean'],
//--------------------------------------------------------------------------
@@ -492,15 +486,12 @@ var FieldBoolean = AbstractField.extend({
//--------------------------------------------------------------------------
/**
- * Automatically selects the field.
- *
* @override
+ * @returns {jQuery} the focusable checkbox input
*/
- activate: function () {
- this.$input.focus();
- setTimeout(this.$input.select.bind(this.$input), 0);
+ getFocusableElement: function () {
+ return this.$input || $();
},
-
/**
* A boolean field is always set since false is a valid value.
*
@@ -686,7 +677,6 @@ var FieldText = DebouncedField.extend({
*/
start: function () {
this.$el.addClass('o_list_text o_form_textarea');
- this.$el.attr('id', this.idForLabel);
if (this.mode === 'edit') {
this.$textarea = $('