summaryrefslogtreecommitdiff
path: root/addons/web/static/src/js
diff options
context:
space:
mode:
authorChristophe Simonis <chs@odoo.com>2018-06-29 12:46:00 +0200
committerChristophe Simonis <chs@odoo.com>2018-06-29 12:46:00 +0200
commit49edee1f4bd74bc342863d8ef50eb86de0b4c55f (patch)
tree20f449f08ba1f4a548c9cb2d82f67f2545048419 /addons/web/static/src/js
parent4a08388492c6667b7f4a11f1397b8557c355b208 (diff)
parentd54a3726eca6ad48d969d5b4a6e559f783809832 (diff)
[MERGE] forward port branch saas-11.4 up to d54a3726eca
Diffstat (limited to 'addons/web/static/src/js')
-rw-r--r--addons/web/static/src/js/chrome/abstract_web_client.js2
-rw-r--r--addons/web/static/src/js/chrome/action_manager.js10
-rw-r--r--addons/web/static/src/js/chrome/loading.js6
-rw-r--r--addons/web/static/src/js/core/ajax.js62
-rw-r--r--addons/web/static/src/js/core/session.js67
-rw-r--r--addons/web/static/src/js/fields/basic_fields.js16
-rw-r--r--addons/web/static/src/js/services/crash_manager.js3
-rw-r--r--addons/web/static/src/js/views/basic/basic_model.js44
-rw-r--r--addons/web/static/src/js/views/form/form_controller.js6
-rw-r--r--addons/web/static/src/js/views/form/form_view.js1
10 files changed, 153 insertions, 64 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 7d3f19c1639..bce77a352ea 100644
--- a/addons/web/static/src/js/chrome/abstract_web_client.js
+++ b/addons/web/static/src/js/chrome/abstract_web_client.js
@@ -170,7 +170,7 @@ var AbstractWebClient = Widget.extend(ServiceProviderMixin, KeyboardNavigationMi
core.bus.on('connection_restored', this, this._onConnectionRestored);
// crash manager integration
- session.on('error', crash_manager, crash_manager.rpc_error);
+ core.bus.on('rpc_error', crash_manager, crash_manager.rpc_error);
window.onerror = function (message, file, line, col, error) {
// Scripts injected in DOM (eg: google API's js files) won't return a clean error on window.onerror.
// The browser will just give you a 'Script error.' as message and nothing else for security issue.
diff --git a/addons/web/static/src/js/chrome/action_manager.js b/addons/web/static/src/js/chrome/action_manager.js
index 0fe45f011e8..f98447c8983 100644
--- a/addons/web/static/src/js/chrome/action_manager.js
+++ b/addons/web/static/src/js/chrome/action_manager.js
@@ -391,10 +391,13 @@ var ActionManager = Widget.extend({
}
return this._startController(controller).then(function (controller) {
+ var prevDialogOnClose;
if (self.currentDialogController) {
+ prevDialogOnClose = self.currentDialogController.onClose;
self._closeDialog(true);
}
+ controller.onClose = prevDialogOnClose || options.on_close;
var dialog = new Dialog(self, _.defaults({}, options, {
buttons: [],
dialogClass: controller.className,
@@ -405,7 +408,7 @@ var ActionManager = Widget.extend({
self._removeAction(action.jsID);
self.currentDialogController = null;
if (silent !== true) {
- options.on_close();
+ controller.onClose();
}
});
controller.dialog = dialog;
@@ -488,8 +491,9 @@ var ActionManager = Widget.extend({
* @returns {Deferred} resolved immediately
*/
_executeCloseAction: function (action, options) {
+ var result;
if (!this.currentDialogController) {
- options.on_close();
+ result = options.on_close();
}
this._closeDialog();
@@ -499,7 +503,7 @@ var ActionManager = Widget.extend({
this.trigger_up('show_effect', action.effect);
}
- return $.when();
+ return $.when(result);
},
/**
* Executes actions of type 'ir.actions.server'.
diff --git a/addons/web/static/src/js/chrome/loading.js b/addons/web/static/src/js/chrome/loading.js
index 0c1b5e5025f..6c7f102231b 100644
--- a/addons/web/static/src/js/chrome/loading.js
+++ b/addons/web/static/src/js/chrome/loading.js
@@ -26,9 +26,9 @@ var Loading = Widget.extend({
this._super(parent);
this.count = 0;
this.blocked_ui = false;
- session.on("request", this, this.request_call);
- session.on("response", this, this.response_call);
- session.on("response_failed", this, this.response_call);
+ core.bus.on('rpc_request', this, this.request_call);
+ core.bus.on("rpc_response", this, this.response_call);
+ core.bus.on("rpc_response_failed", this, this.response_call);
},
destroy: function() {
this.on_rpc_event(-this.count);
diff --git a/addons/web/static/src/js/core/ajax.js b/addons/web/static/src/js/core/ajax.js
index d6c67eacaec..fa79c65ef45 100644
--- a/addons/web/static/src/js/core/ajax.js
+++ b/addons/web/static/src/js/core/ajax.js
@@ -5,7 +5,15 @@ var core = require('web.core');
var utils = require('web.utils');
var time = require('web.time');
-function genericJsonRpc (fct_name, params, fct) {
+function genericJsonRpc (fct_name, params, settings, fct) {
+ var shadow = settings.shadow || false;
+ delete settings.shadow;
+ if (!shadow) {
+ core.bus.trigger('rpc_request');
+ }
+
+ var deferred = $.Deferred();
+
var data = {
jsonrpc: "2.0",
method: fct_name,
@@ -29,12 +37,56 @@ function genericJsonRpc (fct_name, params, fct) {
return def.reject.apply(def, ["communication"].concat(_.toArray(arguments)));
});
// FIXME: jsonp?
- result.abort = function () { if (xhr.abort) xhr.abort(); };
- return result;
+ deferred.abort = function () {
+ deferred.reject('communication', $.Event(), 'abort', 'abort');
+ if (xhr.abort) {
+ xhr.abort();
+ }
+ };
+
+ result.then(function (result) {
+ if (!shadow) {
+ core.bus.trigger('rpc_response');
+ }
+ deferred.resolve(result);
+ }, function (type, error, textStatus, errorThrown) {
+ if (type === "server") {
+ if (!shadow) {
+ core.bus.trigger('rpc_response');
+ }
+ if (error.code === 100) {
+ core.bus.trigger('invalidate_session');
+ }
+ deferred.reject(error, $.Event());
+ } else {
+ if (!shadow) {
+ core.bus.trigger('rpc_response_failed');
+ }
+ var nerror = {
+ code: -32098,
+ message: "XmlHttpRequestError " + errorThrown,
+ data: {
+ type: "xhr"+textStatus,
+ debug: error.responseText,
+ objects: [error, errorThrown]
+ },
+ };
+ deferred.reject(nerror, $.Event());
+ }
+ });
+ deferred.fail(function () { // Allow deferred user to disable rpc_error call in fail
+ deferred.fail(function (error, event) {
+ if (!event.isDefaultPrevented()) {
+ core.bus.trigger('rpc_error', error, event);
+ }
+ });
+ });
+ return deferred;
}
function jsonRpc(url, fct_name, params, settings) {
- return genericJsonRpc(fct_name, params, function(data) {
+ settings = settings || {};
+ return genericJsonRpc(fct_name, params, settings, function(data) {
return $.ajax(url, _.extend({}, settings, {
url: url,
dataType: 'json',
@@ -47,7 +99,7 @@ function jsonRpc(url, fct_name, params, settings) {
function jsonpRpc(url, fct_name, params, settings) {
settings = settings || {};
- return genericJsonRpc(fct_name, params, function(data) {
+ return genericJsonRpc(fct_name, params, settings, function(data) {
var payload_str = JSON.stringify(data, time.date_to_utc);
var payload_url = $.param({r:payload_str});
var force2step = settings.force2step || false;
diff --git a/addons/web/static/src/js/core/session.js b/addons/web/static/src/js/core/session.js
index 11423eec909..09b65357404 100644
--- a/addons/web/static/src/js/core/session.js
+++ b/addons/web/static/src/js/core/session.js
@@ -49,6 +49,7 @@ var Session = core.Class.extend(mixins.EventDispatcherMixin, {
this.qweb_mutex = new concurrency.Mutex();
this.currencies = {};
this._groups_def = {};
+ core.bus.on('invalidate_session', this, this._onInvalidateSession);
},
setup: function (origin, options) {
// must be able to customize server
@@ -327,31 +328,31 @@ var Session = core.Class.extend(mixins.EventDispatcherMixin, {
rpc: function (url, params, options) {
var self = this;
options = _.clone(options || {});
- var shadow = options.shadow || false;
options.headers = _.extend({}, options.headers);
if (odoo.debug) {
options.headers["X-Debug-Mode"] = $.deparam($.param.querystring()).debug;
}
- delete options.shadow;
-
- var deferred = $.Deferred();
+ var deferred = self.check_session_id();
var aborted = false;
+ var xhrDef;
deferred.abort = function () {
- aborted = true;
+ if (xhrDef) {
+ xhrDef.abort();
+ } else {
+ aborted = true;
+ }
};
- self.check_session_id().then(function () {
+
+ return deferred.then(function () {
if (aborted) {
- return deferred.reject('communication', $.Event(), 'abort', 'abort');
+ return $.Deferred().reject('communication', $.Event(), 'abort', 'abort');
}
// TODO: remove
if (! _.isString(url)) {
_.extend(options, url);
url = url.url;
}
- // TODO correct handling of timeouts
- if (! shadow)
- self.trigger('request');
var fct;
if (self.origin_server) {
fct = ajax.jsonRpc;
@@ -370,40 +371,9 @@ var Session = core.Class.extend(mixins.EventDispatcherMixin, {
url = self.url(url, null);
options.session_id = self.session_id || '';
}
- var p = fct(url, "call", params, options);
- deferred.abort = p.abort.bind(p); // Allow to abort the rpc call
- p = p.then(function (result) {
- if (! shadow)
- self.trigger('response');
- return deferred.resolve(result);
- }, function (type, error, textStatus, errorThrown) {
- if (type === "server") {
- if (! shadow)
- self.trigger('response');
- if (error.code === 100) {
- self.uid = false;
- }
- return deferred.reject(error, $.Event());
- } else {
- if (! shadow)
- self.trigger('response_failed');
- var nerror = {
- code: -32098,
- message: "XmlHttpRequestError " + errorThrown,
- data: {type: "xhr"+textStatus, debug: error.responseText, objects: [error, errorThrown] }
- };
- return deferred.reject(nerror, $.Event());
- }
- });
- return deferred.fail(function () { // Allow deferred user to disable rpc_error call in fail
- deferred.fail(function (error, event) {
- if (!event.isDefaultPrevented()) {
- self.trigger('error', error, event);
- }
- });
- });
+ xhrDef = fct(url, "call", params, options);
+ return xhrDef;
});
- return deferred;
},
url: function (path, params) {
params = _.extend(params || {});
@@ -428,6 +398,17 @@ var Session = core.Class.extend(mixins.EventDispatcherMixin, {
getTZOffset: function (date) {
return -new Date(date).getTimezoneOffset();
},
+
+ //--------------------------------------------------------------------------
+ // Handlers
+ //--------------------------------------------------------------------------
+
+ /**
+ * @private
+ */
+ _onInvalidateSession: function () {
+ this.uid = false;
+ },
});
return Session;
diff --git a/addons/web/static/src/js/fields/basic_fields.js b/addons/web/static/src/js/fields/basic_fields.js
index aa66439143d..cb676871fc5 100644
--- a/addons/web/static/src/js/fields/basic_fields.js
+++ b/addons/web/static/src/js/fields/basic_fields.js
@@ -2597,6 +2597,21 @@ var AceEditor = DebouncedField.extend({
//--------------------------------------------------------------------------
/**
+ * Format value
+ *
+ * Note: We have to overwrite this method to always return a string.
+ * AceEditor works with string and not boolean value.
+ *
+ * @override
+ * @private
+ * @param {boolean|string} value
+ * @returns {string}
+ */
+ _formatValue: function (value) {
+ return this._super.apply(this, arguments) || '';
+ },
+
+ /**
* @override
* @private
*/
@@ -2618,6 +2633,7 @@ var AceEditor = DebouncedField.extend({
this.aceSession.setValue(newValue);
}
},
+
/**
* Starts the ace library on the given DOM element. This initializes the
* ace editor option according to the edit/readonly mode and binds ace
diff --git a/addons/web/static/src/js/services/crash_manager.js b/addons/web/static/src/js/services/crash_manager.js
index 8d15efda686..f3bfa87341c 100644
--- a/addons/web/static/src/js/services/crash_manager.js
+++ b/addons/web/static/src/js/services/crash_manager.js
@@ -41,7 +41,8 @@ var CrashManager = core.Class.extend({
core.bus.trigger('connection_lost');
this.connection_lost = true;
var timeinterval = setInterval(function() {
- ajax.jsonRpc('/web/webclient/version_info').then(function() {
+ var options = {shadow: true};
+ ajax.jsonRpc('/web/webclient/version_info', 'call', {}, options).then(function () {
clearInterval(timeinterval);
core.bus.trigger('connection_restored');
self.connection_lost = false;
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 d8ebddc101c..6ad520f9eb0 100644
--- a/addons/web/static/src/js/views/basic/basic_model.js
+++ b/addons/web/static/src/js/views/basic/basic_model.js
@@ -683,13 +683,16 @@ var BasicModel = AbstractModel.extend({
/**
* Returns true if a record can be abandoned.
*
- * A record can be abandonned if it is a new record, except if
- * this datapoint has been specifically tagged as "do not abandon".
+ * Case for not abandoning the record:
*
- * Example:
+ * 1. flagged as 'no abandon' (i.e. during a `default_get`, including any
+ * `onchange` from a `default_get`)
+ * 2. registered in a list on addition
+ * 2.1. registered as non-new addition
+ * 2.2. registered as new additon on update
+ * 3. record is not new
*
- * - Discard record from "Add an item" => "New" record => abandon
- * - Discard record from `default_get`/`onchange` => do not abandon
+ * Otherwise, the record can be abandoned.
*
* This is useful when discarding changes on this record, as it means that
* we must keep the record even if some fields are invalids (e.g. required
@@ -699,7 +702,29 @@ var BasicModel = AbstractModel.extend({
* @returns {boolean}
*/
canBeAbandoned: function (id) {
- return !this.localData[id]._noAbandon && this.isNew(id);
+ // 1. no drop if flagged
+ if (this.localData[id]._noAbandon) {
+ return false;
+ }
+ // 2. no drop in a list on "ADD in some cases
+ var record = this.localData[id];
+ var parent = this.localData[record.parentID];
+ if (parent) {
+ var entry = _.findWhere(parent._savePoint, {operation: 'ADD', id: id});
+ if (entry) {
+ // 2.1. no drop on non-new addition in list
+ if (!entry.isNew) {
+ return false;
+ }
+ // 2.2. no drop on new addition on "UPDATE"
+ var lastEntry = _.last(parent._savePoint);
+ if (lastEntry.operation === 'UPDATE' && lastEntry.id === id) {
+ return false;
+ }
+ }
+ }
+ // 3. drop new records
+ return this.isNew(id);
},
/**
* Returns true if a record is dirty. A record is considered dirty if it has
@@ -2673,7 +2698,7 @@ var BasicModel = AbstractModel.extend({
count: ids.length,
context: record.context,
fieldsInfo: fieldsInfo,
- fields: view ? view.viewFields : fieldInfo.relatedFields,
+ fields: view ? view.fields : fieldInfo.relatedFields,
limit: fieldInfo.limit,
modelName: field.relation,
res_ids: ids,
@@ -2685,6 +2710,11 @@ var BasicModel = AbstractModel.extend({
relationField: field.relation_field,
viewType: view ? view.type : fieldInfo.viewType,
});
+ // set existing changes to the list
+ if (record._changes && record._changes[fieldName]) {
+ list._changes = self.localData[record._changes[fieldName]]._changes;
+ record._changes[fieldName] = list.id;
+ }
record.data[fieldName] = list.id;
if (!fieldInfo.__no_fetch) {
var def = self._readUngroupedList(list).then(function () {
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 be49c12e768..d0ca5c3d09f 100644
--- a/addons/web/static/src/js/views/form/form_controller.js
+++ b/addons/web/static/src/js/views/form/form_controller.js
@@ -634,7 +634,11 @@ var FormController = BasicController.extend({
*/
_onSave: function (ev) {
ev.stopPropagation(); // Prevent x2m lines to be auto-saved
- this.saveRecord();
+ var self = this;
+ this._disableButtons();
+ this.saveRecord().always(function () {
+ self._enableButtons();
+ });
},
/**
* This method is called when someone tries to sort a column, most likely
diff --git a/addons/web/static/src/js/views/form/form_view.js b/addons/web/static/src/js/views/form/form_view.js
index f0ea360fed0..c688a997581 100644
--- a/addons/web/static/src/js/views/form/form_view.js
+++ b/addons/web/static/src/js/views/form/form_view.js
@@ -100,6 +100,7 @@ var FormView = BasicView.extend({
for (var viewName in views) {
// clone to make runbot green?
attrs.views[viewName] = self._processFieldsView(views[viewName], viewName);
+ attrs.views[viewName].fields = attrs.views[viewName].viewFields;
self._processSubViewAttrs(attrs.views[viewName], attrs);
}
self._setSubViewLimit(attrs);