diff options
Diffstat (limited to 'addons/web/static/src/js')
14 files changed, 150 insertions, 46 deletions
diff --git a/addons/web/static/src/js/chrome/abstract_web_client.js b/addons/web/static/src/js/chrome/abstract_web_client.js index 9f56cd01ef4..481e9a7fc54 100644 --- a/addons/web/static/src/js/chrome/abstract_web_client.js +++ b/addons/web/static/src/js/chrome/abstract_web_client.js @@ -82,6 +82,7 @@ var AbstractWebClient = Widget.extend(mixins.ServiceProvider, { } }); }, + show_wow: '_onShowWow', }, init: function (parent) { this.client_options = {}; @@ -319,6 +320,22 @@ var AbstractWebClient = Widget.extend(mixins.ServiceProvider, { this.notification_manager.warn(e.data.title, e.data.message, e.data.sticky); } }, + /** + * Displays a thumb up, heart or peace image (randomly) for a moment (e.g. + * used when an opportunity is won) + * + * @private + */ + _onShowWow: function () { + var className = 'o_wow_thumbs'; + if (Math.random() > 0.9) { + var otherClasses = ['o_wow_peace', 'o_wow_heart']; + className = otherClasses[Math.floor(Math.random()*otherClasses.length)]; + } + var $body = $('body'); + $body.addClass(className); + setTimeout($body.removeClass.bind($body, className), 1000); + }, }); return AbstractWebClient; diff --git a/addons/web/static/src/js/fields/abstract_field.js b/addons/web/static/src/js/fields/abstract_field.js index 5908f7f5a8b..492d9f26552 100644 --- a/addons/web/static/src/js/fields/abstract_field.js +++ b/addons/web/static/src/js/fields/abstract_field.js @@ -176,8 +176,8 @@ var AbstractField = Widget.extend({ * focusable element was not found or invisible */ activate: function (options) { - var $focusable = this.getFocusableElement(); - if ($focusable.length && $focusable.is(':visible')) { + if (this.isFocusable()) { + var $focusable = this.getFocusableElement(); $focusable.focus(); if ($focusable.is('input[type="text"], textarea')) { $focusable[0].selectionStart = $focusable[0].selectionEnd = $focusable[0].value.length; @@ -212,6 +212,15 @@ var AbstractField = Widget.extend({ return $(); }, /** + * Returns true iff the widget has a visible element that can take the focus + * + * @returns {boolean} + */ + isFocusable: function () { + var $focusable = this.getFocusableElement(); + return $focusable.length && $focusable.is(':visible'); + }, + /** * 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 * @@ -351,12 +360,13 @@ var AbstractField = Widget.extend({ * @param {Object} [options] * @param {boolean} [options.forceChange=false] if true, the change event will be * triggered even if the new value is the same as the old one + * @returns {Deferred} */ _setValue: function (value, options) { // we try to avoid doing useless work, if the value given has not // changed. Note that we compare the unparsed values. if (this.lastSetValue === value || (this.value === false && value === '')) { - return; + return $.when(); } this.lastSetValue = value; try { @@ -364,18 +374,22 @@ var AbstractField = Widget.extend({ this._isValid = true; } catch (e) { this._isValid = false; - return; + return $.Deferred().reject(); } if (!(options && options.forceChange) && this._isSameValue(value)) { - return; + return $.when(); } + var def = $.Deferred(); var changes = {}; changes[this.name] = value; this.trigger_up('field_changed', { dataPointID: this.dataPointID, changes: changes, viewType: this.viewType, + onSuccess: def.resolve.bind(def), + onFailure: def.reject.bind(def), }); + return def; }, //-------------------------------------------------------------------------- diff --git a/addons/web/static/src/js/fields/basic_fields.js b/addons/web/static/src/js/fields/basic_fields.js index a5ceba50d3f..d4013a8bcdb 100644 --- a/addons/web/static/src/js/fields/basic_fields.js +++ b/addons/web/static/src/js/fields/basic_fields.js @@ -1281,8 +1281,26 @@ var PriorityWidget = AbstractField.extend({ }, }); -var AttachmentImage = AbstractField.extend({ - template: 'AttachmentImage', +var AttachmentImage = AbstractField.extend({ + className: 'o_attachment_image', + + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + + /** + * Reset cover image when widget value change + * + * @private + */ + _render: function () { + if (this.value) { + this.$el.empty().append($('<img>/', { + src: "/web/image/" + this.value.data.id + "?unique=1", + title: this.value.data.display_name + })); + } + } }); var StateSelectionWidget = AbstractField.extend({ diff --git a/addons/web/static/src/js/fields/relational_fields.js b/addons/web/static/src/js/fields/relational_fields.js index e13c3d29004..ae3b06d3a02 100644 --- a/addons/web/static/src/js/fields/relational_fields.js +++ b/addons/web/static/src/js/fields/relational_fields.js @@ -213,17 +213,20 @@ var FieldMany2One = AbstractField.extend({ var self = this; var slowCreate = this._searchCreatePopup.bind(this, "form", false, this._createContext(name)); if (this.nodeOptions.quick_create) { - return this._rpc({ - model: this.field.relation, - method: 'name_create', - args: [name], - context: this.record.getContext(this.recordParams), - }) - .then(function (result) { - if (self.mode === "edit") { - self.reinitialize({id: result[0], display_name: result[1]}); - } - }, slowCreate); + this.trigger_up('mutexify', { + action: function () { + return self._rpc({ + model: self.field.relation, + method: 'name_create', + args: [name], + context: self.record.getContext(self.recordParams), + }).then(function (result) { + if (self.mode === "edit") { + self.reinitialize({id: result[0], display_name: result[1]}); + } + }, slowCreate); + }, + }); } else { slowCreate(); } @@ -964,7 +967,6 @@ var FieldOne2Many = FieldX2Many.extend({ var index = self.editable === 'top' ? 0 : self.value.data.length - 1; var newID = self.value.data[index].id; self.renderer.editRecord(newID); - self.creatingRecord = false; } } }); @@ -1014,6 +1016,7 @@ var FieldOne2Many = FieldX2Many.extend({ * in the kanban view */ _onAddRecord: function (ev) { + var self = this; // we don't want interference with the components upstream. ev.stopPropagation(); @@ -1023,10 +1026,11 @@ var FieldOne2Many = FieldX2Many.extend({ this._setValue({ operation: 'CREATE', position: this.editable, + }).always(function () { + self.creatingRecord = false; }); } } else { - var self = this; this._openFormDialog({ on_saved: function (record) { self._setValue({ operation: 'ADD', id: record.id }); diff --git a/addons/web/static/src/js/services/data_manager.js b/addons/web/static/src/js/services/data_manager.js index 5131db19028..41fc293e383 100644 --- a/addons/web/static/src/js/services/data_manager.js +++ b/addons/web/static/src/js/services/data_manager.js @@ -11,6 +11,7 @@ var utils = require('web.utils'); return core.Class.extend({ init: function () { this._init_cache(); + core.bus.on('clear_cache', this, this.invalidate.bind(this)); }, _init_cache: function () { diff --git a/addons/web/static/src/js/views/basic/basic_controller.js b/addons/web/static/src/js/views/basic/basic_controller.js index 53e5be8ff6e..16a066db495 100644 --- a/addons/web/static/src/js/views/basic/basic_controller.js +++ b/addons/web/static/src/js/views/basic/basic_controller.js @@ -46,6 +46,9 @@ var BasicController = AbstractController.extend(FieldManagerMixin, { * @returns {Deferred} */ start: function () { + // add classname to reflect the (absence of) access rights (used to + // correctly display the nocontent helper) + this.$el.toggleClass('o_cannot_create', !this.activeActions.create); return this._super.apply(this, arguments) .then(this._updateEnv.bind(this)); }, @@ -186,8 +189,12 @@ var BasicController = AbstractController.extend(FieldManagerMixin, { // user if some discarding has to be made). This operation must also be // mutex-protected as commitChanges function of x2m has to be aware of // all final changes made to a row. - this.mutex.exec(this.renderer.commitChanges.bind(this.renderer, recordID || this.handle)); // TODO write a test for this - return this.mutex.exec(this._saveRecord.bind(this, recordID, options)); + var self = this; + return this.mutex + .exec(this.renderer.commitChanges.bind(this.renderer, recordID || this.handle)) + .then(function () { + return self.mutex.exec(self._saveRecord.bind(self, recordID, options)); + }); }, /** * @override diff --git a/addons/web/static/src/js/views/basic/basic_model.js b/addons/web/static/src/js/views/basic/basic_model.js index 60abfde4344..61c285d6581 100644 --- a/addons/web/static/src/js/views/basic/basic_model.js +++ b/addons/web/static/src/js/views/basic/basic_model.js @@ -1841,7 +1841,6 @@ var BasicModel = AbstractModel.extend({ return d.res_id === res_id; }); m2mList.data.push(dataPoint.id); - m2mList.count++; }); }); }); @@ -2476,7 +2475,10 @@ var BasicModel = AbstractModel.extend({ // REPLACE_WITH x2manyList.res_ids = value[2]; x2manyList.count = x2manyList.res_ids.length; - defs.push(self._readUngroupedList(x2manyList)); + var def = self._readUngroupedList(x2manyList).then(function () { + return self._fetchX2ManysBatched(x2manyList); + }); + defs.push(def); } }); diff --git a/addons/web/static/src/js/views/field_manager_mixin.js b/addons/web/static/src/js/views/field_manager_mixin.js index 9578e568558..422af121d96 100644 --- a/addons/web/static/src/js/views/field_manager_mixin.js +++ b/addons/web/static/src/js/views/field_manager_mixin.js @@ -99,7 +99,9 @@ var FieldManagerMixin = { // subrecord's form view), otherwise it bubbles up to the main form view // but its model doesn't have any data related to the given dataPointID event.stopPropagation(); - this._applyChanges(event.data.dataPointID, event.data.changes, event); + this._applyChanges(event.data.dataPointID, event.data.changes, event) + .done(event.data.onSuccess || function () {}) + .fail(event.data.onFailure || function () {}); }, /** * Some widgets need to trigger a reload of their data. For example, a diff --git a/addons/web/static/src/js/views/form/form_controller.js b/addons/web/static/src/js/views/form/form_controller.js index ce449f2dcc9..8eef70a916c 100644 --- a/addons/web/static/src/js/views/form/form_controller.js +++ b/addons/web/static/src/js/views/form/form_controller.js @@ -39,12 +39,10 @@ var FormController = BasicController.extend({ //-------------------------------------------------------------------------- /** - * This method is supposed to focus the first active control, I think. It - * is currently only called by the FormViewDialog. - * - * @todo To be implemented + * Calls autofocus on the renderer */ autofocus: function () { + this.renderer.autofocus(); }, /** * This method switches the form view in edit mode, with a new record. @@ -93,6 +91,14 @@ var FormController = BasicController.extend({ return dataPoint.data.display_name || _t('New'); }, /** + * Called each time the form view is attached into the DOM + * + * @todo convert to new style + */ + on_attach_callback: function () { + this.autofocus(); + }, + /** * Render buttons for the control panel. The form view can be rendered in * a dialog, and in that case, if we have buttons defined in the footer, we * have to use them instead of the standard buttons. @@ -374,10 +380,8 @@ var FormController = BasicController.extend({ }); } - if (event.data.show_wow) { - def.then(function () { - self.show_wow(); - }); + if (event.data.showWow) { + def.then(this.trigger_up.bind(this, 'show_wow')); } def.always(this._enableButtons.bind(this)); diff --git a/addons/web/static/src/js/views/form/form_renderer.js b/addons/web/static/src/js/views/form/form_renderer.js index ddd77ee5dc6..08f28438368 100644 --- a/addons/web/static/src/js/views/form/form_renderer.js +++ b/addons/web/static/src/js/views/form/form_renderer.js @@ -26,6 +26,29 @@ var FormRenderer = BasicRenderer.extend({ //-------------------------------------------------------------------------- /** + * Focuses the field having attribute 'default_focus' set, if any, or the + * first focusable field otherwise. + */ + autofocus: function () { + if (this.mode === 'readonly') { + return; + } + var focusWidget = this.defaultFocusField; + if (!focusWidget || !focusWidget.isFocusable()) { + var widgets = this.allFieldWidgets[this.state.id]; + for (var i = 0; i < (widgets ? widgets.length : 0); i++) { + var widget = widgets[i]; + if (widget.isFocusable()) { + focusWidget = widget; + break; + } + } + } + if (focusWidget) { + focusWidget.activate({noselect: true}); + } + }, + /** * Extend the method so that labels also receive the 'o_field_invalid' class * if necessary. * @@ -183,7 +206,7 @@ var FormRenderer = BasicRenderer.extend({ self.trigger_up('button_clicked', { attrs: node.attrs, record: self.state, - show_wow: self.$el.hasClass('o_wow'), // TODO: implement this (in view) + showWow: $el.hasClass('o_wow'), }); }); }, @@ -709,7 +732,10 @@ var FormRenderer = BasicRenderer.extend({ * @returns {jQueryElement} */ _renderTagSeparator: function (node) { - return $('<div/>').addClass('o_horizontal_separator').text(node.attrs.string); + var $separator = $('<div/>').addClass('o_horizontal_separator').text(node.attrs.string); + this._handleAttributes($separator, node); + this._registerModifiers(node, this.state, $separator); + return $separator; }, /** * @private @@ -769,20 +795,16 @@ var FormRenderer = BasicRenderer.extend({ core.bus.trigger('DOM_updated'); // Attach the tooltips on the fields' label - var focusWidget = this.defaultFocusField; _.each(this.allFieldWidgets[this.state.id], function (widget) { - if (!focusWidget) { - focusWidget = widget; - } if (core.debug || widget.attrs.help || widget.field.help) { var idForLabel = self.idsForLabels[widget.name]; var $label = idForLabel ? self.$('label[for=' + idForLabel + ']') : $(); self._addFieldTooltip(widget, $label); } }); - if (focusWidget) { - focusWidget.activate({noselect: true}); - } + + // Focus the default field (in edit mode) + self.autofocus(); }, /** * Sets id attribute of given widget to idForLabel diff --git a/addons/web/static/src/js/views/list/list_editable_renderer.js b/addons/web/static/src/js/views/list/list_editable_renderer.js index 9364d1ba9fe..08b9e7f1602 100644 --- a/addons/web/static/src/js/views/list/list_editable_renderer.js +++ b/addons/web/static/src/js/views/list/list_editable_renderer.js @@ -155,7 +155,7 @@ ListRenderer.include({ var self = this; var rowIndex = _.findIndex(this.state.data, {id: recordID}); if (rowIndex < 0) { - return; + return $.when(); } var editMode = (mode === 'edit'); var record = this.state.data[rowIndex]; diff --git a/addons/web/static/src/js/views/view_dialogs.js b/addons/web/static/src/js/views/view_dialogs.js index b84d60d5b7c..a19e440913e 100644 --- a/addons/web/static/src/js/views/view_dialogs.js +++ b/addons/web/static/src/js/views/view_dialogs.js @@ -4,6 +4,7 @@ odoo.define('web.view_dialogs', function (require) { var core = require('web.core'); var data = require('web.data'); var Dialog = require('web.Dialog'); +var dom = require('web.dom'); var ListController = require('web.ListController'); var ListView = require('web.ListView'); var pyeval = require('web.pyeval'); @@ -194,8 +195,10 @@ var FormViewDialog = ViewDialog.extend({ if ($buttons.children().length) { self.$footer.empty().append($buttons.contents()); } - _super().$el.append(fragment); - self.form_view.autofocus(); + dom.append(_super().$el, fragment, { + callbacks: [{widget: self.form_view}], + in_DOM: true, + }); }); }); @@ -281,6 +284,7 @@ var SelectCreateDialog = ViewDialog.extend({ if(this.options.initial_view !== "search") { return this.create_edit_record(); } + var self = this; var user_context = this.getSession().user_context; var _super = this._super.bind(this); @@ -298,7 +302,10 @@ var SelectCreateDialog = ViewDialog.extend({ this.loadViews(this.dataset.model, this.dataset.get_context(), [[false, 'list'], [false, 'search']], {}) .then(this.setup.bind(this, search_defaults)) .then(function (fragment) { - _super().$el.append(fragment); + dom.append(_super().$el, fragment, { + callbacks: [{widget: self.list_controller}], + in_DOM: true, + }); }); return this; }, diff --git a/addons/web/static/src/js/widgets/date_picker.js b/addons/web/static/src/js/widgets/date_picker.js index 353a5dc78f2..76281d9366a 100644 --- a/addons/web/static/src/js/widgets/date_picker.js +++ b/addons/web/static/src/js/widgets/date_picker.js @@ -42,6 +42,7 @@ var DateWidget = Widget.extend({ locale : moment.locale(), allowInputToggle: true, keyBinds: null, + widgetParent: 'body', }); }, /** diff --git a/addons/web/static/src/js/widgets/model_field_selector.js b/addons/web/static/src/js/widgets/model_field_selector.js index 279428e8939..6607561a2bb 100644 --- a/addons/web/static/src/js/widgets/model_field_selector.js +++ b/addons/web/static/src/js/widgets/model_field_selector.js @@ -16,6 +16,11 @@ var modelFieldsCache = { cacheDefs: {}, }; +core.bus.on('clear_cache', null, function () { + modelFieldsCache.cache = {}; + modelFieldsCache.cacheDefs = {}; +}); + /** * The ModelFieldSelector widget can be used to display/select a particular * field chain from a given model. |
