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 = $('