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/core/ajax.js11
-rw-r--r--addons/web/static/src/js/core/misc.js17
-rw-r--r--addons/web/static/src/js/core/session.js2
-rw-r--r--addons/web/static/src/js/fields/basic_fields.js34
-rw-r--r--addons/web/static/src/js/report/qwebactionmanager.js7
-rw-r--r--addons/web/static/src/js/views/basic/basic_model.js36
-rw-r--r--addons/web/static/src/js/views/calendar/calendar_controller.js21
-rw-r--r--addons/web/static/src/js/views/form/form_renderer.js8
-rw-r--r--addons/web/static/src/js/views/list/list_editable_renderer.js14
9 files changed, 121 insertions, 29 deletions
diff --git a/addons/web/static/src/js/core/ajax.js b/addons/web/static/src/js/core/ajax.js
index d69574d2e10..0ee6d2ad1ed 100644
--- a/addons/web/static/src/js/core/ajax.js
+++ b/addons/web/static/src/js/core/ajax.js
@@ -212,6 +212,9 @@ var loadJS = (function () {
* @param {Function} [options.success] callback in case of download success
* @param {Function} [options.error] callback in case of request error, provided with the error body
* @param {Function} [options.complete] called after both ``success`` and ``error`` callbacks have executed
+ * @returns {boolean} a false value means that a popup window was blocked. This
+ * mean that we probably need to inform the user that something needs to be
+ * changed to make it work.
*/
function get_file(options) {
// need to detect when the file is done downloading (not used
@@ -232,7 +235,12 @@ function get_file(options) {
var url = options.session.url(options.url, params);
if (options.complete) { options.complete(); }
- return window.open(url);
+ var w = window.open(url);
+ if (!w || w.closed || typeof w.closed === 'undefined') {
+ // popup was blocked
+ return false;
+ }
+ return true;
}
var $form, $form_data = $('<div>');
@@ -309,6 +317,7 @@ function get_file(options) {
}
};
timer = setTimeout(waitLoop, CHECK_INTERVAL);
+ return true;
};
function post (controller_url, data) {
diff --git a/addons/web/static/src/js/core/misc.js b/addons/web/static/src/js/core/misc.js
index 64ebeeb1794..983b26d3606 100644
--- a/addons/web/static/src/js/core/misc.js
+++ b/addons/web/static/src/js/core/misc.js
@@ -173,6 +173,23 @@ function ReloadContext (parent, action) {
}
core.action_registry.add("reload_context", ReloadContext);
+// In Internet Explorer, document doesn't have the contains method, so we make a
+// polyfill for the method in order to be compatible.
+if (!document.contains) {
+ document.contains = function contains (node) {
+ if (!(0 in arguments)) {
+ throw new TypeError('1 argument is required');
+ }
+
+ do {
+ if (this === node) {
+ return true;
+ }
+ } while (node = node && node.parentNode);
+
+ return false;
+ };
+}
return {
blockUI: blockUI,
diff --git a/addons/web/static/src/js/core/session.js b/addons/web/static/src/js/core/session.js
index 0ecea92fb71..c3181a7abe6 100644
--- a/addons/web/static/src/js/core/session.js
+++ b/addons/web/static/src/js/core/session.js
@@ -278,7 +278,7 @@ var Session = core.Class.extend(mixins.EventDispatcherMixin, {
options.data.session_id = this.session_id;
}
options.session = this;
- ajax.get_file(options);
+ return ajax.get_file(options);
},
/**
* (re)loads the content of a session: db name, username, user id, session
diff --git a/addons/web/static/src/js/fields/basic_fields.js b/addons/web/static/src/js/fields/basic_fields.js
index 6392870dc90..8eae8f5b8f1 100644
--- a/addons/web/static/src/js/fields/basic_fields.js
+++ b/addons/web/static/src/js/fields/basic_fields.js
@@ -394,7 +394,7 @@ var LinkButton = AbstractField.extend({
var FieldDate = InputField.extend({
className: "o_field_date",
tagName: "span",
- supportedFieldTypes: ['date'],
+ supportedFieldTypes: ['date', 'datetime'],
/**
* @override
@@ -453,12 +453,13 @@ var FieldDate = InputField.extend({
* @override
* @private
* @param {Moment|false} value
+ * @returns {boolean}
*/
_isSameValue: function (value) {
if (value === false) {
return this.value === false;
}
- return value.isSame(this.value);
+ return value.isSame(this.value, 'day');
},
/**
* Instantiates a new DateWidget datepicker.
@@ -497,7 +498,16 @@ var FieldDateTime = FieldDate.extend({
var value = this.datewidget.getValue();
return value && value.add(-this.getSession().getTZOffset(value), 'minutes');
},
-
+ /**
+ * @override
+ * @private
+ */
+ _isSameValue: function (value) {
+ if (value === false) {
+ return this.value === false;
+ }
+ return value.isSame(this.value);
+ },
/**
* Instantiates a new DateTimeWidget datepicker rather than DateWidget.
*
@@ -966,7 +976,7 @@ var FieldPhone = FieldEmail.extend({
* @override
*/
getFocusableElement: function () {
- if (this._canCall()) {
+ if (this.mode !== 'readonly' || this._canCall()) {
return this._super.apply(this, arguments);
}
return $();
@@ -1171,7 +1181,6 @@ var FieldBinaryImage = AbstractFieldBinary.extend({
supportedFieldTypes: ['binary'],
_render: function () {
var self = this;
- var attrs = this.attrs;
var url = this.placeholder;
if (this.value) {
if (!utils.is_bin_size(this.value)) {
@@ -1186,11 +1195,7 @@ var FieldBinaryImage = AbstractFieldBinary.extend({
});
}
}
- var $img = $('<img>').attr('src', url);
- $img.css({
- width: this.nodeOptions.size ? this.nodeOptions.size[0] : attrs.img_width || attrs.width,
- height: this.nodeOptions.size ? this.nodeOptions.size[1] : attrs.img_height || attrs.height,
- });
+ var $img = $(qweb.render("FieldBinaryImage-img", {widget: this, url: url}));
this.$('> img').remove();
this.$el.prepend($img);
$img.on('error', function () {
@@ -2175,6 +2180,15 @@ var FieldDomain = AbstractField.extend({
//--------------------------------------------------------------------------
/**
+ * A domain field is always set since the false value is considered to be
+ * equal to "[]" (match all records).
+ *
+ * @override
+ */
+ isSet: function () {
+ return true;
+ },
+ /**
* @override isValid from AbstractField.isValid
* Parsing the char value is not enough for this field. It is considered
* valid if the internal domain selector was built correctly and that the
diff --git a/addons/web/static/src/js/report/qwebactionmanager.js b/addons/web/static/src/js/report/qwebactionmanager.js
index 3c8fee859e3..cc875882ce2 100644
--- a/addons/web/static/src/js/report/qwebactionmanager.js
+++ b/addons/web/static/src/js/report/qwebactionmanager.js
@@ -21,7 +21,7 @@ var WKHTMLTOPDF_MESSAGES = {
};
var trigger_download = function (session, response, c, action, options) {
- session.get_file({
+ return session.get_file({
url: '/report/download',
data: {data: JSON.stringify(response)},
complete: framework.unblockUI,
@@ -107,7 +107,10 @@ ActionManager.include({
report_urls['qweb-pdf'],
action.report_type, //The 'root' report is considered the maine one, so we use its type for all the others.
];
- trigger_download(self.getSession(), response, c, current_action, options);
+ var success = trigger_download(self.getSession(), response, c, current_action, options);
+ if (!success) {
+ self.do_warn(_t('Warning'), _t('A popup window with your report was blocked. You may need to change your browser settings to allow popup windows for this page.'), true);
+ }
treated_actions.push(current_action);
current_action = current_action.next_report_to_generate;
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 6884442039e..1df118ecfd9 100644
--- a/addons/web/static/src/js/views/basic/basic_model.js
+++ b/addons/web/static/src/js/views/basic/basic_model.js
@@ -875,7 +875,10 @@ var BasicModel = AbstractModel.extend({
// id never changes, and should not be written
delete record._changes.id;
}
- var changes = self._generateChanges(record, {viewType: options.viewType});
+ var changes = self._generateChanges(record, {viewType: options.viewType, changesOnly: method !== 'create'});
+
+ // id field should never be written/changed
+ delete changes.id;
if (method === 'create') {
var fieldNames = record.getFieldNames();
@@ -1551,6 +1554,9 @@ var BasicModel = AbstractModel.extend({
removedDef = this._applyX2ManyChange(record, fieldName, {
operation: 'DELETE',
ids: _.map(removedIds, function (resID) {
+ if (resID in list._cache) {
+ return list._cache[resID];
+ }
return _.findWhere(listData, {res_id: resID}).id;
}),
});
@@ -2455,7 +2461,7 @@ var BasicModel = AbstractModel.extend({
changes = _.extend({}, record.data, record._changes);
} else {
changes = _.extend({}, record._changes);
- };
+ }
var withReadonly = options.withReadonly || false;
var commands = this._generateX2ManyCommands(record, {
changesOnly: 'changesOnly' in options ? options.changesOnly : true,
@@ -2492,6 +2498,7 @@ var BasicModel = AbstractModel.extend({
changes[fieldName] = false;
}
}
+
return changes;
},
/**
@@ -2608,6 +2615,7 @@ var BasicModel = AbstractModel.extend({
_.each(relRecordUpdated, function (relRecord) {
var changes = self._generateChanges(relRecord, options);
if (!_.isEmpty(changes)) {
+ delete changes.id;
var command = x2ManyCommands.update(relRecord.res_id, changes);
commands[fieldName].push(command);
}
@@ -2638,7 +2646,12 @@ var BasicModel = AbstractModel.extend({
// this is a new id
relRecord = _.findWhere(relRecordAdded, {res_id: list.res_ids[i]});
changes = this._generateChanges(relRecord, options);
- commands[fieldName].push(x2ManyCommands.create(relRecord.ref, changes));
+ if ('id' in changes) {
+ delete changes.id;
+ commands[fieldName].push(x2ManyCommands.update(relRecord.res_id, changes));
+ } else {
+ commands[fieldName].push(x2ManyCommands.create(relRecord.ref, changes));
+ }
}
}
if (options.changesOnly && !didChange && addedIds.length === 0 && removedIds.length === 0) {
@@ -3294,11 +3307,17 @@ var BasicModel = AbstractModel.extend({
* This method is quite important: it is supposed to perform the /onchange
* rpc and apply the result.
*
+ * The changes that triggered the onchange are assumed to have already been
+ * applied to the record.
+ *
* @param {Object} record
* @param {string[]} fields changed fields
* @param {string} [viewType] current viewType. If not set, we will assume
* main viewType from the record
- * @returns {Deferred}
+ * @returns {Deferred} Note: this deferred cannot fail. It is either in the
+ * 'success' or 'pending' state. If the onchange rpc fails, it will be
+ * resolved with an empty dictionary. Note that the initial change was
+ * already applied.
*/
_performOnChange: function (record, fields, viewType) {
var self = this;
@@ -3318,7 +3337,9 @@ var BasicModel = AbstractModel.extend({
var context = this._getContext(record, options);
var currentData = this._generateOnChangeData(record, {changesOnly: false});
- return self._rpc({
+ var def = $.Deferred();
+
+ self._rpc({
model: record.model,
method: 'onchange',
args: [idList, currentData, fields, onchangeSpec, context],
@@ -3343,9 +3364,12 @@ var BasicModel = AbstractModel.extend({
record._domains = _.extend(record._domains, result.domain);
}
return self._applyOnChange(result.value, record).then(function () {
- return result;
+ def.resolve(result);
});
+ }).fail(function () {
+ def.resolve({});
});
+ return def;
},
/**
* Once a record is created and some data has been fetched, we need to do
diff --git a/addons/web/static/src/js/views/calendar/calendar_controller.js b/addons/web/static/src/js/views/calendar/calendar_controller.js
index 4168d901065..08d03c037f0 100644
--- a/addons/web/static/src/js/views/calendar/calendar_controller.js
+++ b/addons/web/static/src/js/views/calendar/calendar_controller.js
@@ -257,13 +257,20 @@ var CalendarController = AbstractController.extend({
id = id && parseInt(id).toString() === id ? parseInt(id) : id;
if (!this.eventOpenPopup) {
- this.do_action({
- type: 'ir.actions.act_window',
- res_id: id,
- res_model: this.modelName,
- views: [[this.formViewId || false, 'form']],
- target: 'current',
- context: event.context || self.context,
+ this._rpc({
+ model: self.modelName,
+ method: 'get_formview_id',
+ //The event can be called by a view that can have another context than the default one.
+ args: [[id], event.context || self.context],
+ }).then(function (viewId) {
+ self.do_action({
+ type:'ir.actions.act_window',
+ res_id: id,
+ res_model: self.modelName,
+ views: [[viewId || false, 'form']],
+ target: 'current',
+ context: event.context || self.context,
+ });
});
return;
}
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 b9711d1d392..0ddf2f0a129 100644
--- a/addons/web/static/src/js/views/form/form_renderer.js
+++ b/addons/web/static/src/js/views/form/form_renderer.js
@@ -807,7 +807,15 @@ var FormRenderer = BasicRenderer.extend({
// Attach the tooltips on the fields' label
_.each(this.allFieldWidgets[this.state.id], function (widget) {
var idForLabel = self.idsForLabels[widget.name];
+ // We usually don't support multiple widgets for the same field on the
+ // same view but it is the case with the new settings view on V11.0.
+ // Therefore, we need to retrieve the correct label since it could be
+ // displayed multiple times on the view, otherwise, for example the
+ // enterprise label will be displayed as many times as the field
+ // exists on settings.
+ var $widgets = self.$('.o_field_widget[name=' + widget.name + ']');
var $label = idForLabel ? self.$('label[for=' + idForLabel + ']') : $();
+ $label = $label.eq($widgets.index(widget.$el));
if (config.debug || widget.attrs.help || widget.field.help) {
self._addFieldTooltip(widget, $label);
}
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 9027fc3c984..b0ddbdcd385 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
@@ -136,7 +136,9 @@ ListRenderer.include({
currentRowID = this.state.data[this.currentRow].id;
currentWidget = this.allFieldWidgets[currentRowID][this.currentFieldIndex];
focusedElement = currentWidget.getFocusableElement().get(0);
- selectionRange = dom.getSelectionRange(focusedElement);
+ if (currentWidget.formatType !== 'boolean') {
+ selectionRange = dom.getSelectionRange(focusedElement);
+ }
}
var oldData = this.state.data;
@@ -179,7 +181,9 @@ ListRenderer.include({
currentRowID = self.state.data[newRowIndex].id;
currentWidget = self.allFieldWidgets[currentRowID][self.currentFieldIndex];
focusedElement = currentWidget.getFocusableElement().get(0);
- dom.setSelectionRange(focusedElement, selectionRange);
+ if (selectionRange) {
+ dom.setSelectionRange(focusedElement, selectionRange);
+ }
});
}
});
@@ -585,12 +589,18 @@ ListRenderer.include({
if (fieldIndex >= (self.allFieldWidgets[record.id] || []).length) {
return $.Deferred().reject();
}
+ // _activateFieldWidget might trigger an onchange,
+ // which requires currentFieldIndex to be set
+ // so that the cursor can be restored
+ var oldFieldIndex = self.currentFieldIndex;
+ self.currentFieldIndex = fieldIndex;
fieldIndex = self._activateFieldWidget(record, fieldIndex, {
inc: 1,
wrap: wrap,
event: options && options.event,
});
if (fieldIndex < 0) {
+ self.currentFieldIndex = oldFieldIndex;
return $.Deferred().reject();
}
self.currentFieldIndex = fieldIndex;