From 4ae44b3bd9022813e7b334e3fc19567b0c7c1e7a Mon Sep 17 00:00:00 2001 From: Martin Geubelle Date: Mon, 5 Feb 2018 15:33:17 +0100 Subject: [FIX] web: prevent crash when downloading pivot view A traceback was occuring when downloading the pivot view without data. The export function expects the attribute `main_row` set on data, which is defined in `_prepareData`. The latter wasn't called because of the `return`. Closes #22728 --- addons/web/static/src/js/views/pivot/pivot_model.js | 1 - 1 file changed, 1 deletion(-) (limited to 'addons/web/static/src/js') diff --git a/addons/web/static/src/js/views/pivot/pivot_model.js b/addons/web/static/src/js/views/pivot/pivot_model.js index 7eb2ba4bc6b..99a5e4ee334 100644 --- a/addons/web/static/src/js/views/pivot/pivot_model.js +++ b/addons/web/static/src/js/views/pivot/pivot_model.js @@ -545,7 +545,6 @@ var PivotModel = AbstractModel.extend({ var data = Array.prototype.slice.call(arguments); if (data[0][0].__count === 0) { self.data.has_data = false; - return; } self._prepareData(data); }); -- cgit v1.2.3 From 36d07bfc0f5b9841b17f4cfdf2ffb029ea1280e0 Mon Sep 17 00:00:00 2001 From: Martin Geubelle Date: Mon, 5 Feb 2018 16:56:54 +0100 Subject: [FIX] web: properly handles ESC in editable lists This is a small correction of odoo/odoo@3aad756 ; a traceback occured when ESC was pressed in a list view with modifiers. The `confirmChange` was done on the parent datapoint (of type list) which has no attribute `evalModifiers`. `confirmChange` is now called on the record datapoint. Note that for x2m, it means that all modifiers linked to the parent record won't be re-evaluated (but this wasn't probably intended as this is a bit overkill). Closes #22718 --- addons/web/static/src/js/views/basic/basic_controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'addons/web/static/src/js') 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 5fc68e3124e..c7345a22ccc 100644 --- a/addons/web/static/src/js/views/basic/basic_controller.js +++ b/addons/web/static/src/js/views/basic/basic_controller.js @@ -511,7 +511,7 @@ var BasicController = AbstractController.extend(FieldManagerMixin, { // TODO this will tell the renderer to rerender the widget that // asked for the discard but will unfortunately lose the click // made on another row if any - self._confirmChange(self.handle, [ev.data.fieldName], ev) + self._confirmChange(recordID, [ev.data.fieldName], ev) .always(ev.data.onSuccess); }) .fail(ev.data.onFailure); -- cgit v1.2.3 From b28ceec009e0e7fd7efa82177540ff7489112f5e Mon Sep 17 00:00:00 2001 From: Christophe Matthieu Date: Mon, 5 Feb 2018 16:55:15 +0100 Subject: [FIX] web: Views: always destroy BasicModel instances Before this rev., it might happen that the parent of BasicModel instances wasn't correctly set to the Controller instance. It was actually set to the ActionManager instance, which is never destroyed. So when this happened, the BasicModel instance wasn't destroyed when it's controller was. --- addons/web/static/src/js/views/abstract_view.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) (limited to 'addons/web/static/src/js') diff --git a/addons/web/static/src/js/views/abstract_view.js b/addons/web/static/src/js/views/abstract_view.js index 6cc8a3c8143..bb945d6fbb0 100644 --- a/addons/web/static/src/js/views/abstract_view.js +++ b/addons/web/static/src/js/views/abstract_view.js @@ -138,21 +138,23 @@ var AbstractView = Class.extend({ */ getController: function (parent) { var self = this; + // check if a model already exists, as if not, one will be created and + // we'll have to set the controller as its parent + var alreadyHasModel = !!this.model; return $.when(this._loadData(parent), ajax.loadLibs(this)).then(function () { - var model = self.getModel(); - var state = model.get(arguments[0]); + var state = self.model.get(arguments[0]); var renderer = self.getRenderer(parent, state); var Controller = self.Controller || self.config.Controller; var controllerParams = _.extend({ initialState: state, }, self.controllerParams); - var controller = new Controller(parent, model, renderer, controllerParams); + var controller = new Controller(parent, self.model, renderer, controllerParams); renderer.setParent(controller); - if (!self.model) { + if (!alreadyHasModel) { // if we have a model, it already has a parent. Otherwise, we // set the controller, so the rpcs from the model actually work - model.setParent(controller); + self.model.setParent(controller); } return controller; }); -- cgit v1.2.3 From 1d49d52b3a03f043d877447cae31c0768158c51c Mon Sep 17 00:00:00 2001 From: Christophe Matthieu Date: Tue, 6 Feb 2018 11:17:06 +0100 Subject: [FIX] web: properly set parent when using mixins The parentedMixin doesn't automatically set the parent, so when this mixin is used, the parent must be set manually. This wasn't done at several places, and thus those elements weren't destroyed with their actual parent. In master, we should probably make the ParentedMixin set the parent itself. --- addons/web/static/src/js/_deprecated/data.js | 1 + addons/web/static/src/js/chrome/control_panel.js | 2 +- addons/web/static/src/js/core/bus.js | 5 +++-- addons/web/static/src/js/core/session.js | 3 ++- 4 files changed, 7 insertions(+), 4 deletions(-) (limited to 'addons/web/static/src/js') diff --git a/addons/web/static/src/js/_deprecated/data.js b/addons/web/static/src/js/_deprecated/data.js index be5496c55c7..eb70f330076 100644 --- a/addons/web/static/src/js/_deprecated/data.js +++ b/addons/web/static/src/js/_deprecated/data.js @@ -314,6 +314,7 @@ var DataSet = Class.extend(mixins.PropertiesMixin, { */ init: function (parent, model, context) { mixins.PropertiesMixin.init.call(this); + this.setParent(parent); this.model = model; this.context = context || {}; this.index = null; diff --git a/addons/web/static/src/js/chrome/control_panel.js b/addons/web/static/src/js/chrome/control_panel.js index 98ce7814b7a..6aace69cf8d 100644 --- a/addons/web/static/src/js/chrome/control_panel.js +++ b/addons/web/static/src/js/chrome/control_panel.js @@ -78,7 +78,7 @@ var ControlPanel = Widget.extend({ this.template = template; } - this.bus = new Bus(); + this.bus = new Bus(this); this.bus.on("update", this, this.update); }, /** diff --git a/addons/web/static/src/js/core/bus.js b/addons/web/static/src/js/core/bus.js index 1f715da4df1..5257453edab 100644 --- a/addons/web/static/src/js/core/bus.js +++ b/addons/web/static/src/js/core/bus.js @@ -10,8 +10,9 @@ var mixins = require('web.mixins'); * @class Bus */ return Class.extend(mixins.EventDispatcherMixin, { - init: function() { - mixins.EventDispatcherMixin.init.call(this, parent); + init: function (parent) { + mixins.EventDispatcherMixin.init.call(this); + this.setParent(parent); }, }); diff --git a/addons/web/static/src/js/core/session.js b/addons/web/static/src/js/core/session.js index c3181a7abe6..2a41e7da44d 100644 --- a/addons/web/static/src/js/core/session.js +++ b/addons/web/static/src/js/core/session.js @@ -28,7 +28,8 @@ var Session = core.Class.extend(mixins.EventDispatcherMixin, { "override_session" is set to true. */ init: function (parent, origin, options) { - mixins.EventDispatcherMixin.init.call(this, parent); + mixins.EventDispatcherMixin.init.call(this); + this.setParent(parent); options = options || {}; this.module_list = (options.modules && options.modules.slice()) || (window.odoo._modules && window.odoo._modules.slice()) || []; this.server = null; -- cgit v1.2.3 From 3b9dda6106f9dd3eabd5dcb14f484a6072937dd6 Mon Sep 17 00:00:00 2001 From: Christophe Matthieu Date: Mon, 5 Feb 2018 16:55:15 +0100 Subject: [FIX] web: Views: always destroy BasicModel instances Before this rev., it might happen that the parent of BasicModel instances wasn't correctly set to the Controller instance. It was actually set to the ActionManager instance, which is never destroyed. So when this happened, the BasicModel instance wasn't destroyed when it's controller was. --- addons/web/static/src/js/views/abstract_view.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) (limited to 'addons/web/static/src/js') diff --git a/addons/web/static/src/js/views/abstract_view.js b/addons/web/static/src/js/views/abstract_view.js index 2a1ba16a33e..dea984adb64 100644 --- a/addons/web/static/src/js/views/abstract_view.js +++ b/addons/web/static/src/js/views/abstract_view.js @@ -138,21 +138,23 @@ var AbstractView = Class.extend({ */ getController: function (parent) { var self = this; + // check if a model already exists, as if not, one will be created and + // we'll have to set the controller as its parent + var alreadyHasModel = !!this.model; return $.when(this._loadData(parent), ajax.loadLibs(this)).then(function () { - var model = self.getModel(); - var state = model.get(arguments[0]); + var state = self.model.get(arguments[0]); var renderer = self.getRenderer(parent, state); var Controller = self.Controller || self.config.Controller; var controllerParams = _.extend({ initialState: state, }, self.controllerParams); - var controller = new Controller(parent, model, renderer, controllerParams); + var controller = new Controller(parent, self.model, renderer, controllerParams); renderer.setParent(controller); - if (!self.model) { + if (!alreadyHasModel) { // if we have a model, it already has a parent. Otherwise, we // set the controller, so the rpcs from the model actually work - model.setParent(controller); + self.model.setParent(controller); } return controller; }); -- cgit v1.2.3 From 3b2127c690959e1e43063b09472028a69a62fcb2 Mon Sep 17 00:00:00 2001 From: Christophe Matthieu Date: Tue, 6 Feb 2018 11:17:06 +0100 Subject: [FIX] web: properly set parent when using mixins The parentedMixin doesn't automatically set the parent, so when this mixin is used, the parent must be set manually. This wasn't done at several places, and thus those elements weren't destroyed with their actual parent. In master, we should probably make the ParentedMixin set the parent itself. --- addons/web/static/src/js/_deprecated/data.js | 1 + addons/web/static/src/js/chrome/control_panel.js | 2 +- addons/web/static/src/js/core/bus.js | 5 +++-- addons/web/static/src/js/core/session.js | 3 ++- 4 files changed, 7 insertions(+), 4 deletions(-) (limited to 'addons/web/static/src/js') diff --git a/addons/web/static/src/js/_deprecated/data.js b/addons/web/static/src/js/_deprecated/data.js index be5496c55c7..eb70f330076 100644 --- a/addons/web/static/src/js/_deprecated/data.js +++ b/addons/web/static/src/js/_deprecated/data.js @@ -314,6 +314,7 @@ var DataSet = Class.extend(mixins.PropertiesMixin, { */ init: function (parent, model, context) { mixins.PropertiesMixin.init.call(this); + this.setParent(parent); this.model = model; this.context = context || {}; this.index = null; diff --git a/addons/web/static/src/js/chrome/control_panel.js b/addons/web/static/src/js/chrome/control_panel.js index 98ce7814b7a..6aace69cf8d 100644 --- a/addons/web/static/src/js/chrome/control_panel.js +++ b/addons/web/static/src/js/chrome/control_panel.js @@ -78,7 +78,7 @@ var ControlPanel = Widget.extend({ this.template = template; } - this.bus = new Bus(); + this.bus = new Bus(this); this.bus.on("update", this, this.update); }, /** diff --git a/addons/web/static/src/js/core/bus.js b/addons/web/static/src/js/core/bus.js index 1f715da4df1..5257453edab 100644 --- a/addons/web/static/src/js/core/bus.js +++ b/addons/web/static/src/js/core/bus.js @@ -10,8 +10,9 @@ var mixins = require('web.mixins'); * @class Bus */ return Class.extend(mixins.EventDispatcherMixin, { - init: function() { - mixins.EventDispatcherMixin.init.call(this, parent); + init: function (parent) { + mixins.EventDispatcherMixin.init.call(this); + this.setParent(parent); }, }); diff --git a/addons/web/static/src/js/core/session.js b/addons/web/static/src/js/core/session.js index c3181a7abe6..2a41e7da44d 100644 --- a/addons/web/static/src/js/core/session.js +++ b/addons/web/static/src/js/core/session.js @@ -28,7 +28,8 @@ var Session = core.Class.extend(mixins.EventDispatcherMixin, { "override_session" is set to true. */ init: function (parent, origin, options) { - mixins.EventDispatcherMixin.init.call(this, parent); + mixins.EventDispatcherMixin.init.call(this); + this.setParent(parent); options = options || {}; this.module_list = (options.modules && options.modules.slice()) || (window.odoo._modules && window.odoo._modules.slice()) || []; this.server = null; -- cgit v1.2.3 From 20e4cd5bcd1816a478aedd441efa90a34cf63334 Mon Sep 17 00:00:00 2001 From: Aaron Bohy Date: Thu, 8 Feb 2018 11:28:45 +0100 Subject: [FIX] web: field widget with readonly modifier Rev. dbdd742 introduced the 'baseMode' parameter of field's nodes in the BasicRenderer. Its purpose is to encode the generic mode of the field, regardless of its readonly modifier (i.e. in form views, the baseMode of all fields is the mode of the renderer and in list views, the baseMode of all fields is 'readonly', except for fields in the row being edited, whose baseMode is 'edit'). Unfortunately, when the value of the readonly modifier of a field was 'readonly', its baseMode was changed from 'edit' to 'readonly', which shouldn't, as the baseMode's value should not be affected by the modifiers. This produced a bug when a field had a readonly modifier, as once its value became true, the field was rendered in 'readonly', and whatever the value of the modifier afterwards, it always remained in 'readonly'. The rev. ensures that the baseMode is no longer impacted by the value of the readonly modifier. Closes #22729 --- .../static/src/js/views/basic/basic_renderer.js | 51 +++++++++++++++------- 1 file changed, 35 insertions(+), 16 deletions(-) (limited to 'addons/web/static/src/js') diff --git a/addons/web/static/src/js/views/basic/basic_renderer.js b/addons/web/static/src/js/views/basic/basic_renderer.js index 64b90c33022..f5238a95b33 100644 --- a/addons/web/static/src/js/views/basic/basic_renderer.js +++ b/addons/web/static/src/js/views/basic/basic_renderer.js @@ -254,7 +254,10 @@ var BasicRenderer = AbstractRenderer.extend({ if ('readonly' in modifiers && element.widget) { var mode = modifiers.readonly ? 'readonly' : modifiersData.baseMode; if (mode !== element.widget.mode) { - self._rerenderFieldWidget(element.widget, record, mode); + self._rerenderFieldWidget(element.widget, record, { + keepBaseMode: true, + mode: mode, + }); return; // Rerendering already applied the modifiers, no need to go further } } @@ -406,12 +409,25 @@ var BasicRenderer = AbstractRenderer.extend({ * @param {Object} record * @param {jQuery|AbstractField} [element] * @param {Object} [options] - * @param {Object} [options.callback] - the callback to call on registration - * and on modifiers updates + * @param {Object} [options.callback] the callback to call on registration + * and on modifiers updates + * @param {boolean} [options.keepBaseMode=false] this function registers the + * 'baseMode' of the node; this is a field widget specific settings which + * represents the generic mode of the widget, regardless of its modifiers + * (the interesting case is the list view: all widgets are supposed to be + * in the baseMode 'readonly', except the ones that are in the line that + * is currently being edited). + * With option 'keepBaseMode' set to true, the current baseMode of the + * node isn't overridden (this is particularily useful when a field widget + * is re-rendered because its readonly modifier changed, as in this case, + * we don't want to change its base mode). + * @param {string} [options.mode] the 'baseMode' of the node is set to this + * value (if not given, it is set to this.mode, the mode of the renderer) * @returns {Object} for code efficiency, returns the last evaluated - * modifiers for the given node and record. + * modifiers for the given node and record. */ _registerModifiers: function (node, record, element, options) { + options = options || {}; // Check if we already registered the modifiers for the given node // If yes, this is simply an update of the related element // If not, check the modifiers to see if it needs registration @@ -428,12 +444,11 @@ var BasicRenderer = AbstractRenderer.extend({ this.allModifiersData.push(modifiersData); } } - // we register here the base mode of the node. This is a field widget - // specific settings which represents the generic mode for the widget, - // regardless of its modifiers. The interesting case is the list view: - // all widgets are supposed to be in the baseMode 'readonly', except the - // ones that are in the line that is currently being edited. - modifiersData.baseMode = (options && options.mode) || this.mode; + + // Compute node's base mode if necessary + if (!options.keepBaseMode) { + modifiersData.baseMode = options.mode || this.mode; + } // Evaluate if necessary if (!modifiersData.evaluatedModifiers[record.id]) { @@ -494,13 +509,16 @@ var BasicRenderer = AbstractRenderer.extend({ * @private * @param {Object} node * @param {Object} record - * @param {Object} [options] + * @param {Object} [options] passed to @_registerModifiers + * @param {string} [options.mode] either 'edit' or 'readonly' (defaults to + * this.mode, the mode of the renderer) * @returns {AbstractField} */ _renderFieldWidget: function (node, record, options) { + options = options || {}; var fieldName = node.attrs.name; // Register the node-associated modifiers - var mode = options && options.mode || this.mode; + var mode = options.mode || this.mode; var modifiers = this._registerModifiers(node, record, null, options); // Initialize and register the widget // Readonly status is known as the modifiers have just been registered @@ -537,7 +555,8 @@ var BasicRenderer = AbstractRenderer.extend({ && !element.widget.isSet() )); }, - mode: mode + keepBaseMode: !!options.keepBaseMode, + mode: mode, }); self._postProcessField(widget, node); }); @@ -606,12 +625,12 @@ var BasicRenderer = AbstractRenderer.extend({ * @private * @param {Widget} widget * @param {Object} record - * @param {string} mode either 'readonly' or 'edit' + * @param {Object} [options] options passed to @_renderFieldWidget * @returns {AbstractField} */ - _rerenderFieldWidget: function (widget, record, mode) { + _rerenderFieldWidget: function (widget, record, options) { // Render the new field widget - var newWidget = this._renderFieldWidget(widget.__node, record, {mode: mode}); + var newWidget = this._renderFieldWidget(widget.__node, record, options); widget.$el.replaceWith(newWidget.$el); // Destroy the old widget and position the new one at the old one's -- cgit v1.2.3 From e0cb522bf2bb1c12b2a3cce8c441a93a47685e0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9ry=20Debongnie?= Date: Fri, 9 Feb 2018 15:47:43 +0100 Subject: [FIX] web: update form widgets when state has changed Since the tag is not used anymore in any of our views, it was not really properly supported. In particular, the form widget was not notified when the state of the form view has changed, which makes it very difficult to actually do any useful work. With this commit, we make sure that the widget method 'updateState' (if it exists) is called with the new state whenever it changed. This should allow developers to do useful work without having to work around the issue. Note that this is not really a perfect solution, since the updateState methot may or may not have been implemented. I think that we need some kind of ViewWidget abstraction, which could be used to have a better and more complete API for widgets in a basic view. And AbstractField could inherit from ViewWidget. --- addons/web/static/src/js/views/basic/basic_renderer.js | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'addons/web/static/src/js') diff --git a/addons/web/static/src/js/views/basic/basic_renderer.js b/addons/web/static/src/js/views/basic/basic_renderer.js index f5238a95b33..204c35acaef 100644 --- a/addons/web/static/src/js/views/basic/basic_renderer.js +++ b/addons/web/static/src/js/views/basic/basic_renderer.js @@ -30,6 +30,7 @@ var BasicRenderer = AbstractRenderer.extend({ this.activeActions = params.activeActions; this.viewType = params.viewType; this.mode = params.mode || 'readonly'; + this.widgets = []; }, /** * This method has two responsabilities: find every invalid fields in the @@ -95,6 +96,9 @@ var BasicRenderer = AbstractRenderer.extend({ return this._render().then(_.constant([])); } + // reset all widgets (from the tag) if any: + _.invoke(this.widgets, 'updateState', state); + var defs = []; // Reset all the field widgets that are marked as changed and the ones @@ -605,6 +609,8 @@ var BasicRenderer = AbstractRenderer.extend({ var Widget = widgetRegistry.get(node.attrs.name); var widget = new Widget(this, record); + this.widgets.push(widget); + // Prepare widget rendering and save the related deferred var def = widget.__widgetRenderAndInsert(function () {}); if (def.state() === 'pending') { -- cgit v1.2.3 From 0982ceaa373c3da53c9d15abb0d946f8d45501a9 Mon Sep 17 00:00:00 2001 From: Aaron Bohy Date: Mon, 12 Feb 2018 08:53:22 +0100 Subject: [FIX] web: ListView: reset selection when reloading Before this rev., the list of selected records wasn't correctly reset when the list was reloaded (e.g. when coming back using the breadcrumbs). In the UI, the records weren't selected anymore, but in javascript, the list of selected records wasn't empty. As a consequence, the aggregates were wrongly computed, and always displayed 0. Closes #22725. --- addons/web/static/src/js/views/list/list_renderer.js | 1 + 1 file changed, 1 insertion(+) (limited to 'addons/web/static/src/js') diff --git a/addons/web/static/src/js/views/list/list_renderer.js b/addons/web/static/src/js/views/list/list_renderer.js index 858a8eaed8b..4c2eed53e18 100644 --- a/addons/web/static/src/js/views/list/list_renderer.js +++ b/addons/web/static/src/js/views/list/list_renderer.js @@ -72,6 +72,7 @@ var ListRenderer = BasicRenderer.extend({ */ updateState: function (state, params) { this._processColumns(params.columnInvisibleFields || {}); + this.selection = []; return this._super.apply(this, arguments); }, -- cgit v1.2.3 From 11dff2b05c72adb01f4094aa5077218a29315dec Mon Sep 17 00:00:00 2001 From: Aaron Bohy Date: Mon, 12 Feb 2018 09:21:55 +0100 Subject: [FIX] web: ListView: format footer values Before this rev., the aggregates values displayed in the footer of list views were formatted according to the field type, and the field's widget was ignored, whereas it should be used by default. For instance, if the 'float_time' widget is set on a float field, the sum should be formatted as FloatTime as well. Closes #22724. --- addons/web/static/src/js/views/list/list_renderer.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'addons/web/static/src/js') diff --git a/addons/web/static/src/js/views/list/list_renderer.js b/addons/web/static/src/js/views/list/list_renderer.js index 4c2eed53e18..32ff58acd02 100644 --- a/addons/web/static/src/js/views/list/list_renderer.js +++ b/addons/web/static/src/js/views/list/list_renderer.js @@ -204,9 +204,11 @@ var ListRenderer = BasicRenderer.extend({ var field = self.state.fields[column.attrs.name]; var value = aggregateValues[column.attrs.name].value; var help = aggregateValues[column.attrs.name].help; - var formattedValue = field_utils.format[field.type](value, field, { - escape: true, - }); + var formatFunc = field_utils.format[column.attrs.widget]; + if (!formatFunc) { + formatFunc = field_utils.format[field.type]; + } + var formattedValue = formatFunc(value, field, {escape: true}); $cell.addClass('o_list_number').attr('title', help).html(formattedValue); } return $cell; -- cgit v1.2.3 From 0de5c1f076d7e7d0e5361e08c810c727bd41d9e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexandre=20K=C3=BChn?= Date: Mon, 12 Feb 2018 00:03:22 +0100 Subject: [FIX] web{_site,_editor}: transparent root instance 'root widget' With this commit, any component that needs an instance of the root widget can simply require the module 'root.widget'. Based on the context (backend, frontend, iframe), the real root.widget will resolve to either the webclient, the website root instance, or the iframe root instance. This is desirable in order for the tour_manager to become a child of the root widget, as tours are enabled in any of the above contexts. This commit is a requirement in order to solve an issue with JS services, in which there should be only one service provider at any given time. --- addons/web/static/src/js/chrome/root_widget.js | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 addons/web/static/src/js/chrome/root_widget.js (limited to 'addons/web/static/src/js') diff --git a/addons/web/static/src/js/chrome/root_widget.js b/addons/web/static/src/js/chrome/root_widget.js new file mode 100644 index 00000000000..9cf0515b134 --- /dev/null +++ b/addons/web/static/src/js/chrome/root_widget.js @@ -0,0 +1,7 @@ +odoo.define('root.widget', function (require) { +"use strict"; + +var webClient = require('web.web_client'); + +return webClient; +}); -- cgit v1.2.3 From 066eaaeafad845a7b616a535e8827a5b3ae0164a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexandre=20K=C3=BChn?= Date: Mon, 12 Feb 2018 10:08:39 +0100 Subject: [FIX] mail,web: JS mail and services tracebacks Fix over this commit: https://github.com/odoo/odoo/commit/02ec09cb1c3e2d7bc7968f40c18f2208d7f3f498 In mobile, when discuss is installed, there is a traceback. This is a consequence on an issue with JS services, where some of them are not registered in the service provider. For instance, the webclient (the service provider) was instantiated before chat_manager (a service), so it was not aware of this service. To solve this issue, service providers now listen on newly registered services. Also improving deployment of JS services by not relying on topological sort. --- addons/web/static/src/js/core/abstract_service.js | 9 -- addons/web/static/src/js/core/registry.js | 14 ++++ addons/web/static/src/js/core/service_mixins.js | 98 ++++++++++++++-------- addons/web/static/src/js/core/utils.js | 31 ------- addons/web/static/src/js/services/ajax_service.js | 3 + addons/web/static/src/js/services/core.js | 1 + .../src/js/services/local_storage_service.js | 3 + .../web/static/src/js/services/report_service.js | 3 + 8 files changed, 87 insertions(+), 75 deletions(-) (limited to 'addons/web/static/src/js') diff --git a/addons/web/static/src/js/core/abstract_service.js b/addons/web/static/src/js/core/abstract_service.js index cbd6c1322ca..14efef09b39 100644 --- a/addons/web/static/src/js/core/abstract_service.js +++ b/addons/web/static/src/js/core/abstract_service.js @@ -6,7 +6,6 @@ var Mixins = require('web.mixins'); var ServicesMixin = require('web.ServicesMixin'); var AbstractService = Class.extend(Mixins.EventDispatcherMixin, ServicesMixin, { - Services: [], dependencies: [], name: null, init: function (parent) { @@ -15,13 +14,5 @@ var AbstractService = Class.extend(Mixins.EventDispatcherMixin, ServicesMixin, { }, }); -var realExtend = AbstractService.extend; - -AbstractService.extend = function () { - var Service = realExtend.apply(this, arguments); - AbstractService.prototype.Services.push(Service); - return Service; -}; - return AbstractService; }); diff --git a/addons/web/static/src/js/core/registry.js b/addons/web/static/src/js/core/registry.js index d70c09e4ae0..f925b0ea30a 100644 --- a/addons/web/static/src/js/core/registry.js +++ b/addons/web/static/src/js/core/registry.js @@ -25,6 +25,7 @@ var Registry = Class.extend({ */ init: function (mapping) { this.map = Object.create(mapping || null); + this.listeners = []; // listening callbacks on newly added items. }, //-------------------------------------------------------------------------- @@ -34,12 +35,17 @@ var Registry = Class.extend({ /** * Add a key (and a value) to the registry. * + * Notify the listeners on newly added item in the registry. + * * @param {string} key * @param {any} value * @returns {Registry} can be used to chain add calls. */ add: function (key, value) { this.map[key] = value; + _.each(this.listeners, function (callback) { + callback(key, value); + }); return this; }, /** @@ -89,6 +95,14 @@ var Registry = Class.extend({ } return null; }, + /** + * Register a callback to execute when items are added to the registry. + * + * @param {function} callback function with parameters (key, value). + */ + onAdd: function (callback) { + this.listeners.push(callback); + }, }); return Registry; diff --git a/addons/web/static/src/js/core/service_mixins.js b/addons/web/static/src/js/core/service_mixins.js index d5abf727704..052e2a6a3d9 100644 --- a/addons/web/static/src/js/core/service_mixins.js +++ b/addons/web/static/src/js/core/service_mixins.js @@ -1,58 +1,86 @@ odoo.define('web.ServiceProviderMixin', function (require) { "use strict"; -var AbstractService = require('web.AbstractService'); -var utils = require('web.utils'); +var core = require('web.core'); var ServiceProviderMixin = { - services: {}, - init: function () { + services: {}, // dict containing deployed service instances + UndeployedServices: {}, // dict containing classes of undeployed services + /** + * @override + */ + init: function (parent) { + var self = this; // to properly instantiate services with this as parent, this mixin // assumes that it is used along the EventDispatcherMixin, and that // EventDispatchedMixin's init is called first - var self = this; - // as EventDispatcherMixin's init is already called, this handler has to - // be bound manually + // as EventDispatcherMixin's init is already called, this handler has + // to be bound manually this.on('call_service', this, this._call_service.bind(this)); - var sortedServices = this._sortServices(AbstractService.prototype.Services); - _.each(sortedServices, function (Service) { - var service = new Service(self); - self.services[service.name] = service; + // add already registered services from the service registry + _.each(core.serviceRegistry.map, function (Service, serviceName) { + self.UndeployedServices[serviceName] = Service; }); + this._deployServices(); + + // listen on newly added services + core.serviceRegistry.onAdd(function (serviceName, Service) { + self.UndeployedServices[serviceName] = Service; + self._deployServices(); + }); + }, + + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + + /** + * @private + */ + _deployServices: function () { + var self = this; + var done = false; + while (!done) { + var Service = _.find(this.UndeployedServices, function (Service) { + // no missing dependency + return !_.some(Service.prototype.dependencies, function (depName) { + return !self.services[depName]; + }); + }); + if (Service) { + this.services[Service.prototype.name] = new Service(this); + delete this.UndeployedServices[Service.prototype.name]; + } else { + done = true; + } + } }, + + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- + + /** + * Call the 'service', using data from the 'event' that + * has triggered the service call. + * + * For the ajax service, the arguments are extended with + * the target so that it can call back the caller. + * + * @private + * @param {OdooEvent} event + */ _call_service: function (event) { - var service = this.services[event.data.service]; var args = event.data.args || []; - // ajax service uses an extra 'target' argument for rpc if (event.data.service === 'ajax' && event.data.method === 'rpc') { + // ajax service uses an extra 'target' argument for rpc args = args.concat(event.target); } + var service = this.services[event.data.service]; var result = service[event.data.method].apply(service, args); event.data.callback(result); }, - _sortServices: function (services) { - var nodes = {}; - // Create nodes (services) - _.each(services, function (Service) { - nodes[Service.prototype.name] = Service.prototype.dependencies; - }); - var sorted; - try { - sorted = utils.topologicalSort(nodes); - } catch (err) { - console.warn('topologicalSort Error:', err.message); - sorted = nodes; - } - // Sort services based on sorted - // Note: we convert sorted to an object key=>index for efficiency - var sortedObj = _.invert(_.object(_.pairs(sorted))); - sorted = _.sortBy(services, function (Service) { - return sortedObj[Service.prototype.name]; - }); - - return sorted; - }, }; return ServiceProviderMixin; diff --git a/addons/web/static/src/js/core/utils.js b/addons/web/static/src/js/core/utils.js index 0b4af722203..1bdd9e70a69 100644 --- a/addons/web/static/src/js/core/utils.js +++ b/addons/web/static/src/js/core/utils.js @@ -300,37 +300,6 @@ var utils = { } return fn(mod, Math.floor(x)); }, - /** - * Topological sort of nodes - * - * @param {Object} nodes where key is name of node, and value is list of - * names of dependent nodes. - * @param {string[]} nodes[name] list of named dependencies of node having - * 'name' as its name. - * @return {string[]} topological sort of nodes by their names. - * @throws {Error} if there is at least one circular - * dependency between nodes - */ - topologicalSort: function (nodes) { - var sorted = []; - var unresolved = nodes; - while (!_.isEmpty(unresolved)) { - // Get all nodes that have no dependency - var resolvable = _.pick(unresolved, function (dependencies, name) { - return _.isEmpty(_.difference(dependencies, sorted)); - }); - if (_.isEmpty(resolvable)) { - throw new Error("Circular dependency detected"); - } - // Append them to sorted - sorted = _.union(sorted, _.keys(resolvable)); - // Remove these nodes from unresolved - _.each(resolvable, function (dependencies, name) { - unresolved = _.omit(unresolved, name); - }); - } - return sorted; - }, /** * performs a half up rounding with a fixed amount of decimals, correcting for float loss of precision * See the corresponding float_round() in server/tools/float_utils.py for more info diff --git a/addons/web/static/src/js/services/ajax_service.js b/addons/web/static/src/js/services/ajax_service.js index 480549347d4..3446f651a59 100644 --- a/addons/web/static/src/js/services/ajax_service.js +++ b/addons/web/static/src/js/services/ajax_service.js @@ -2,6 +2,7 @@ odoo.define('web.AjaxService', function (require) { "use strict"; var AbstractService = require('web.AbstractService'); +var core = require('web.core'); var session = require('web.session'); var AjaxService = AbstractService.extend({ @@ -21,6 +22,8 @@ var AjaxService = AbstractService.extend({ }, }); +core.serviceRegistry.add('ajax', AjaxService); + return AjaxService; }); diff --git a/addons/web/static/src/js/services/core.js b/addons/web/static/src/js/services/core.js index 73071a2a65d..522d6b07302 100644 --- a/addons/web/static/src/js/services/core.js +++ b/addons/web/static/src/js/services/core.js @@ -41,6 +41,7 @@ return { crash_registry: new Registry(), search_filters_registry: new Registry(), search_widgets_registry: new Registry(), + serviceRegistry: new Registry(), /** * @type {String} */ diff --git a/addons/web/static/src/js/services/local_storage_service.js b/addons/web/static/src/js/services/local_storage_service.js index a4527026238..7a8c803a0f9 100644 --- a/addons/web/static/src/js/services/local_storage_service.js +++ b/addons/web/static/src/js/services/local_storage_service.js @@ -2,6 +2,7 @@ odoo.define('web.LocalStorageService', function (require) { 'use strict'; var AbstractService = require('web.AbstractService'); +var core = require('web.core'); var localStorage = require('web.local_storage'); var LocalStorageService = AbstractService.extend({ @@ -20,6 +21,8 @@ var LocalStorageService = AbstractService.extend({ } }); +core.serviceRegistry.add('local_storage', LocalStorageService); + return LocalStorageService; }); diff --git a/addons/web/static/src/js/services/report_service.js b/addons/web/static/src/js/services/report_service.js index a2bb8759a24..cfd62c0499f 100644 --- a/addons/web/static/src/js/services/report_service.js +++ b/addons/web/static/src/js/services/report_service.js @@ -6,6 +6,7 @@ odoo.define('web.ReportService', function (require) { */ var AbstractService = require('web.AbstractService'); +var core = require('web.core'); var ReportService = AbstractService.extend({ name: 'report', @@ -28,6 +29,8 @@ var ReportService = AbstractService.extend({ }, }); +core.serviceRegistry.add('report', ReportService); + return ReportService; }); -- cgit v1.2.3 From 7339ae978f5c3d374c43d59e9f75bc6d279d1c96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexandre=20K=C3=BChn?= Date: Mon, 12 Feb 2018 00:04:33 +0100 Subject: [FIX] mail,web,*: Cleaning some JS code - Whitespace trailing - Split long lines - Replace some 'self' to 'this' - Improved some JS docs - DRY in tests using services --- addons/web/static/src/js/chrome/abstract_web_client.js | 4 ++++ addons/web/static/src/js/core/service_mixins.js | 5 +++++ 2 files changed, 9 insertions(+) (limited to 'addons/web/static/src/js') 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 2dd69d7426c..b4b192c1ea9 100644 --- a/addons/web/static/src/js/chrome/abstract_web_client.js +++ b/addons/web/static/src/js/chrome/abstract_web_client.js @@ -245,6 +245,10 @@ var AbstractWebClient = Widget.extend(ServiceProviderMixin, { * identified by strings. Each time a title part is changed, all parts * are gathered, ordered by alphabetical order and displayed in the title * of the window separated by ``-``. + * + * @private + * @param {string} part + * @param {string} title */ set_title_part: function (part, title) { var tmp = _.clone(this.get("title_part")); diff --git a/addons/web/static/src/js/core/service_mixins.js b/addons/web/static/src/js/core/service_mixins.js index 052e2a6a3d9..4ab0a361988 100644 --- a/addons/web/static/src/js/core/service_mixins.js +++ b/addons/web/static/src/js/core/service_mixins.js @@ -97,6 +97,11 @@ var rpc = require('web.rpc'); * @name ServicesMixin */ var ServicesMixin = { + /** + * @param {string} service + * @param {string} method + * @return {any} result of the service called + */ call: function (service, method) { var args = Array.prototype.slice.call(arguments, 2); var result; -- cgit v1.2.3