diff options
Diffstat (limited to 'addons/web/static/src/js/fields')
| -rw-r--r-- | addons/web/static/src/js/fields/abstract_field.js | 83 | ||||
| -rw-r--r-- | addons/web/static/src/js/fields/basic_fields.js | 38 | ||||
| -rw-r--r-- | addons/web/static/src/js/fields/relational_fields.js | 58 |
3 files changed, 86 insertions, 93 deletions
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,7 +32,6 @@ 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', @@ -40,22 +39,12 @@ var AbstractField = Widget.extend({ '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 * through this.record.specialData[this.name]. @@ -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 @@ -202,6 +188,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 <input/> 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('<div>').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 = $('<textarea>').appendTo(this.$el); @@ -704,14 +694,12 @@ var FieldText = DebouncedField.extend({ //-------------------------------------------------------------------------- /** - * Automatically selects the content of the field. + * Returns the associated <textarea/> element. * * @override */ - activate: function () { - var $textarea = this.$('textarea'); - $textarea.focus(); - setTimeout($textarea.select.bind($textarea), 0); + getFocusableElement: function () { + return this.$textarea || $(); }, //-------------------------------------------------------------------------- @@ -753,8 +741,6 @@ var HandleWidget = AbstractField.extend({ tagName: 'span', className: 'o_row_handle fa fa-arrows ui-sortable-handle', description: "", - readonly: true, - replace_element: true, supportedFieldTypes: ['integer'], }); @@ -1093,7 +1079,6 @@ var PriorityWidget = AbstractField.extend({ 'mouseout > a': '_onMouseOut', 'click > a': '_onClick', }, - readonly: true, supportedFieldTypes: ['selection'], //-------------------------------------------------------------------------- @@ -1195,7 +1180,6 @@ var PriorityWidget = AbstractField.extend({ this.$('.o_priority_star').removeClass('fa-star-o').addClass('fa-star'); $(event.currentTarget).nextAll().removeClass('fa-star').addClass('fa-star-o'); }, - }); var AttachmentImage = AbstractField.extend({ @@ -1210,7 +1194,6 @@ var StateSelectionWidget = AbstractField.extend({ }, 'click li': '_setSelection' }, - readonly: true, supportedFieldTypes: ['selection'], //-------------------------------------------------------------------------- @@ -1300,7 +1283,6 @@ var StateSelectionWidget = AbstractField.extend({ }); var LabelSelection = AbstractField.extend({ - readonly: true, supportedFieldTypes: ['selection'], //-------------------------------------------------------------------------- diff --git a/addons/web/static/src/js/fields/relational_fields.js b/addons/web/static/src/js/fields/relational_fields.js index 06f38abffbb..abe3de97779 100644 --- a/addons/web/static/src/js/fields/relational_fields.js +++ b/addons/web/static/src/js/fields/relational_fields.js @@ -116,18 +116,16 @@ var FieldMany2One = AbstractField.extend({ // Public //-------------------------------------------------------------------------- - activate: function () { - this.$input.focus(); - setTimeout(this.$input.select.bind(this.$input), 0); - }, /** - * Focuses the input. + * @override + * @returns {jQuery} */ - focus: function () { - if (this.mode === "edit") { - this.$input.focus(); - } + getFocusableElement: function () { + return this.$input || $(); }, + /** + * TODO + */ reinitialize: function (value) { this.floating = false; this._setValue(value); @@ -366,7 +364,7 @@ var FieldMany2One = AbstractField.extend({ disable_multiple_selection: true, on_selected: function (element_ids) { self.reinitialize({id: element_ids[0]}); - self.focus(); + self.activate(); } })).open(); }, @@ -409,7 +407,7 @@ var FieldMany2One = AbstractField.extend({ */ _onExternalButtonClick: function () { if (!this.value) { - this.focus(); + this.activate(); return; } var self = this; @@ -578,8 +576,7 @@ var FieldX2Many = AbstractField.extend({ } var def = $.Deferred(); this.renderer.canBeSaved() - .then(def.resolve.bind(def, true)) - .fail(def.resolve.bind(def, false)); + .then(def.resolve.bind(def, true), def.resolve.bind(def, false)); return def; }, @@ -610,6 +607,7 @@ var FieldX2Many = AbstractField.extend({ mode: this.mode, addCreateLine: !this.isReadonly && this.activeActions.create, addTrashIcon: !this.isReadonly, + viewType: 'list', }); } if (arch.tag === 'kanban') { @@ -620,7 +618,8 @@ var FieldX2Many = AbstractField.extend({ }; this.renderer = new KanbanRenderer(this, this.value, { arch: arch, - record_options: record_options + record_options: record_options, + viewType: 'kanban', }); } return this.renderer ? this.renderer.appendTo(this.$el) : this._super(); @@ -940,7 +939,6 @@ var FieldMany2Many = FieldX2Many.extend({ var FieldMany2ManyTags = AbstractField.extend({ tag_template: "FieldMany2ManyTag", className: "o_form_field o_form_field_many2manytags", - replace_element: true, supportedFieldTypes: ['many2many'], custom_events: { field_changed: '_onFieldChanged', @@ -957,9 +955,23 @@ var FieldMany2ManyTags = AbstractField.extend({ // Public //-------------------------------------------------------------------------- + /** + * @override + */ activate: function () { - this.many2one.focus(); + return this.many2one ? this.many2one.activate() : false; + }, + /** + * @override + * @returns {jQuery} + */ + getFocusableElement: function () { + return this.many2one ? this.many2one.getFocusableElement() : $(); }, + /** + * @override + * @returns {boolean} + */ isSet: function () { return !!this.value && this.value.count; }, @@ -1012,7 +1024,6 @@ var FieldMany2ManyTags = AbstractField.extend({ this.many2one.destroy(); } this.many2one = new FieldMany2One(this, this.name, this.record, { - idForLabel: this.idForLabel, mode: 'edit', viewType: this.viewType, }); @@ -1343,7 +1354,6 @@ var FieldSelection = AbstractField.extend({ events: _.extend({}, AbstractField.prototype.events, { 'change': '_onChange', }), - replace_element: true, /** * @override */ @@ -1365,6 +1375,18 @@ var FieldSelection = AbstractField.extend({ /** * @override + * @returns {jQuery} + */ + getFocusableElement: function () { + return this.$el.is('select') ? this.$el : $(); + }, + + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + + /** + * @override * @private */ _renderEdit: function () { |
