summaryrefslogtreecommitdiff
path: root/addons/web/static/src/js
diff options
context:
space:
mode:
Diffstat (limited to 'addons/web/static/src/js')
-rw-r--r--addons/web/static/src/js/chrome/abstract_web_client.js17
-rw-r--r--addons/web/static/src/js/fields/abstract_field.js24
-rw-r--r--addons/web/static/src/js/fields/basic_fields.js22
-rw-r--r--addons/web/static/src/js/fields/relational_fields.js30
-rw-r--r--addons/web/static/src/js/services/data_manager.js1
-rw-r--r--addons/web/static/src/js/views/basic/basic_controller.js11
-rw-r--r--addons/web/static/src/js/views/basic/basic_model.js6
-rw-r--r--addons/web/static/src/js/views/field_manager_mixin.js4
-rw-r--r--addons/web/static/src/js/views/form/form_controller.js20
-rw-r--r--addons/web/static/src/js/views/form/form_renderer.js40
-rw-r--r--addons/web/static/src/js/views/list/list_editable_renderer.js2
-rw-r--r--addons/web/static/src/js/views/view_dialogs.js13
-rw-r--r--addons/web/static/src/js/widgets/date_picker.js1
-rw-r--r--addons/web/static/src/js/widgets/model_field_selector.js5
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.