diff options
| author | Aaron Bohy <aab@odoo.com> | 2017-12-21 12:29:50 +0100 |
|---|---|---|
| committer | Alexandre Kühn <alex.e.kuhn@gmail.com> | 2018-02-01 14:51:58 +0100 |
| commit | 02ec09cb1c3e2d7bc7968f40c18f2208d7f3f498 (patch) | |
| tree | 10d91136786b13666e78cfb94b12f1609d6f883d /addons/web/static/src/js | |
| parent | 0cd656b5af38051df3f7d6319e99705150bb1cd8 (diff) | |
[REF][IMP] *: mail js refactoring
This commit improves the JS code of the mail module,
which comes maily from the new coding guidelines
and the addition of "JS services".
JS services are important objects that do not fit
well in the component tree, such as chat_manager
or ajax.
The benefits of JS services are improved readability
of the code, reduced coupling, and more testable
modules. In particular, discuss was hard to
Summary of the changes:
- ClientAction has been renamed into Discuss
- Clear instantiation of chatManager
- New coding guidelines in most mail modules
- JS Services can interact with each other
- Chat Manager and Window Manager are services
- Window Manager renamed to Chat Window Manager
- bus.bus is now encapsulated in Bus Service (a service)
- The test infrastructure has been tweaked with JS Services
- Chat Mixin has been removed
A future improvement would be to translate some 'trigger' into 'trigger_up'.
Diffstat (limited to 'addons/web/static/src/js')
| -rw-r--r-- | addons/web/static/src/js/chrome/abstract_web_client.js | 6 | ||||
| -rw-r--r-- | addons/web/static/src/js/chrome/action_manager_report.js | 1 | ||||
| -rw-r--r-- | addons/web/static/src/js/core/abstract_service.js | 11 | ||||
| -rw-r--r-- | addons/web/static/src/js/core/bus.js | 4 | ||||
| -rw-r--r-- | addons/web/static/src/js/core/mixins.js | 125 | ||||
| -rw-r--r-- | addons/web/static/src/js/core/service_mixins.js | 164 | ||||
| -rw-r--r-- | addons/web/static/src/js/core/utils.js | 31 | ||||
| -rw-r--r-- | addons/web/static/src/js/services/report_service.js | 14 |
8 files changed, 216 insertions, 140 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 58c068fecb1..2dd69d7426c 100644 --- a/addons/web/static/src/js/chrome/abstract_web_client.js +++ b/addons/web/static/src/js/chrome/abstract_web_client.js @@ -20,16 +20,16 @@ var data_manager = require('web.data_manager'); var Dialog = require('web.Dialog'); var dom = require('web.dom'); var Loading = require('web.Loading'); -var mixins = require('web.mixins'); var NotificationManager = require('web.NotificationManager'); var RainbowMan = require('web.RainbowMan'); +var ServiceProviderMixin = require('web.ServiceProviderMixin'); var session = require('web.session'); var Widget = require('web.Widget'); var _t = core._t; var qweb = core.qweb; -var AbstractWebClient = Widget.extend(mixins.ServiceProvider, { +var AbstractWebClient = Widget.extend(ServiceProviderMixin, { custom_events: { clear_uncommitted_changes: function (e) { this.clear_uncommitted_changes().then(e.data.callback); @@ -90,8 +90,8 @@ var AbstractWebClient = Widget.extend(mixins.ServiceProvider, { }, init: function (parent) { this.client_options = {}; - mixins.ServiceProvider.init.call(this); this._super(parent); + ServiceProviderMixin.init.call(this); this.origin = undefined; this._current_state = null; this.menu_dm = new concurrency.DropMisordered(); diff --git a/addons/web/static/src/js/chrome/action_manager_report.js b/addons/web/static/src/js/chrome/action_manager_report.js index bfdfa15f8b6..8975fea2628 100644 --- a/addons/web/static/src/js/chrome/action_manager_report.js +++ b/addons/web/static/src/js/chrome/action_manager_report.js @@ -12,6 +12,7 @@ var crash_manager = require('web.crash_manager'); var framework = require('web.framework'); var session = require('web.session'); + var _t = core._t; var _lt = core._lt; diff --git a/addons/web/static/src/js/core/abstract_service.js b/addons/web/static/src/js/core/abstract_service.js index 6ae0c3183da..cbd6c1322ca 100644 --- a/addons/web/static/src/js/core/abstract_service.js +++ b/addons/web/static/src/js/core/abstract_service.js @@ -2,15 +2,22 @@ odoo.define('web.AbstractService', function (require) { "use strict"; var Class = require('web.Class'); +var Mixins = require('web.mixins'); +var ServicesMixin = require('web.ServicesMixin'); -var AbstractService = Class.extend({ +var AbstractService = Class.extend(Mixins.EventDispatcherMixin, ServicesMixin, { Services: [], + dependencies: [], name: null, + init: function (parent) { + Mixins.EventDispatcherMixin.init.call(this, arguments); + this.setParent(parent); + }, }); var realExtend = AbstractService.extend; -AbstractService.extend = function() { +AbstractService.extend = function () { var Service = realExtend.apply(this, arguments); AbstractService.prototype.Services.push(Service); return Service; diff --git a/addons/web/static/src/js/core/bus.js b/addons/web/static/src/js/core/bus.js index 1f715da4df1..92278e0f1d6 100644 --- a/addons/web/static/src/js/core/bus.js +++ b/addons/web/static/src/js/core/bus.js @@ -10,8 +10,8 @@ var mixins = require('web.mixins'); * @class Bus */ return Class.extend(mixins.EventDispatcherMixin, { - init: function() { - mixins.EventDispatcherMixin.init.call(this, parent); + init: function () { + mixins.EventDispatcherMixin.init.call(this, arguments); }, }); diff --git a/addons/web/static/src/js/core/mixins.js b/addons/web/static/src/js/core/mixins.js index 23958d9e0f2..e4738fc2f43 100644 --- a/addons/web/static/src/js/core/mixins.js +++ b/addons/web/static/src/js/core/mixins.js @@ -3,7 +3,6 @@ odoo.define('web.mixins', function (require) { var Class = require('web.Class'); var utils = require('web.utils'); -var AbstractService = require('web.AbstractService'); /** * Mixin to structure objects' life-cycles folowing a parent-children @@ -409,134 +408,10 @@ var PropertiesMixin = _.extend({}, EventDispatcherMixin, { } }); -var ServiceProvider = { - services: {}, - init: function () { - var self = this; - _.each(AbstractService.prototype.Services, function (Service) { - var service = new Service(); - self.services[service.name] = service; - }); - this.custom_events = _.clone(this.custom_events); - this.custom_events.call_service = this._call_service.bind(this); - }, - _call_service: function (event) { - var service = this.services[event.data.service]; - var args = (event.data.args || []).concat(event.target); - var result = service[event.data.method].apply(service, args); - event.data.callback(result); - }, -}; - return { ParentedMixin: ParentedMixin, EventDispatcherMixin: EventDispatcherMixin, PropertiesMixin: PropertiesMixin, - ServiceProvider: ServiceProvider, }; }); - -odoo.define('web.ServicesMixin', function (require) { -"use strict"; - -var rpc = require('web.rpc'); - -/** - * @mixin - * @name ServicesMixin - */ -var ServicesMixin = { - call: function (service, method) { - var args = Array.prototype.slice.call(arguments, 2); - var result; - this.trigger_up('call_service', { - service: service, - method: method, - args: args, - callback: function (r) { - result = r; - }, - }); - return result; - }, - /** - * Builds and executes RPC query. Returns a deferred's promise resolved with - * the RPC result. - * - * @param {string} params either a route or a model - * @param {string} options if a model is given, this argument is a method - * @returns {Promise} - */ - _rpc: function (params, options) { - var query = rpc.buildQuery(params); - var def = this.call('ajax', 'rpc', query.route, query.params, options); - return def ? def.promise() : $.Deferred().promise(); - }, - loadFieldView: function (dataset, view_id, view_type, options) { - return this.loadViews(dataset.model, dataset.get_context().eval(), [[view_id, view_type]], options).then(function (result) { - return result[view_type]; - }); - }, - loadViews: function (modelName, context, views, options) { - var def = $.Deferred(); - this.trigger_up('load_views', { - modelName: modelName, - context: context, - views: views, - options: options, - on_success: def.resolve.bind(def), - }); - return def; - }, - loadFilters: function (dataset, action_id) { - var def = $.Deferred(); - this.trigger_up('load_filters', { - dataset: dataset, - action_id: action_id, - on_success: def.resolve.bind(def), - }); - return def; - }, - // Session stuff - getSession: function () { - var session; - this.trigger_up('get_session', { - callback: function (result) { - session = result; - } - }); - return session; - }, - /** - * Informs the action manager to do an action. This supposes that the action - * manager can be found amongst the ancestors of the current widget. - * If that's not the case this method will simply return an unresolved - * deferred. - * - * @param {any} action - * @param {any} options - * @returns {Deferred} - */ - do_action: function (action, options) { - var def = $.Deferred(); - - this.trigger_up('do_action', { - action: action, - options: options, - on_success: function (result) { def.resolve(result); }, - on_fail: function (result) { def.reject(result); }, - }); - return def; - }, - do_notify: function (title, message, sticky, className) { - this.trigger_up('notification', {title: title, message: message, sticky: sticky, className: className}); - }, - do_warn: function (title, message, sticky, className) { - this.trigger_up('warning', {title: title, message: message, sticky: sticky, className: className}); - }, -}; - -return ServicesMixin; - -}); diff --git a/addons/web/static/src/js/core/service_mixins.js b/addons/web/static/src/js/core/service_mixins.js new file mode 100644 index 00000000000..d5abf727704 --- /dev/null +++ b/addons/web/static/src/js/core/service_mixins.js @@ -0,0 +1,164 @@ +odoo.define('web.ServiceProviderMixin', function (require) { +"use strict"; + +var AbstractService = require('web.AbstractService'); +var utils = require('web.utils'); + +var ServiceProviderMixin = { + services: {}, + init: function () { + // 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 + 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; + }); + }, + _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') { + args = args.concat(event.target); + } + 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; + +}); + +odoo.define('web.ServicesMixin', function (require) { +"use strict"; + +var rpc = require('web.rpc'); + +/** + * @mixin + * @name ServicesMixin + */ +var ServicesMixin = { + call: function (service, method) { + var args = Array.prototype.slice.call(arguments, 2); + var result; + this.trigger_up('call_service', { + service: service, + method: method, + args: args, + callback: function (r) { + result = r; + }, + }); + return result; + }, + /** + * Builds and executes RPC query. Returns a deferred's promise resolved with + * the RPC result. + * + * @param {string} params either a route or a model + * @param {string} options if a model is given, this argument is a method + * @returns {Promise} + */ + _rpc: function (params, options) { + var query = rpc.buildQuery(params); + var def = this.call('ajax', 'rpc', query.route, query.params, options); + return def ? def.promise() : $.Deferred().promise(); + }, + loadFieldView: function (dataset, view_id, view_type, options) { + return this.loadViews(dataset.model, dataset.get_context().eval(), [[view_id, view_type]], options).then(function (result) { + return result[view_type]; + }); + }, + loadViews: function (modelName, context, views, options) { + var def = $.Deferred(); + this.trigger_up('load_views', { + modelName: modelName, + context: context, + views: views, + options: options, + on_success: def.resolve.bind(def), + }); + return def; + }, + loadFilters: function (dataset, action_id) { + var def = $.Deferred(); + this.trigger_up('load_filters', { + dataset: dataset, + action_id: action_id, + on_success: def.resolve.bind(def), + }); + return def; + }, + // Session stuff + getSession: function () { + var session; + this.trigger_up('get_session', { + callback: function (result) { + session = result; + } + }); + return session; + }, + /** + * Informs the action manager to do an action. This supposes that the action + * manager can be found amongst the ancestors of the current widget. + * If that's not the case this method will simply return an unresolved + * deferred. + * + * @param {any} action + * @param {any} options + * @returns {Deferred} + */ + do_action: function (action, options) { + var def = $.Deferred(); + + this.trigger_up('do_action', { + action: action, + options: options, + on_success: function (result) { def.resolve(result); }, + on_fail: function (result) { def.reject(result); }, + }); + return def; + }, + do_notify: function (title, message, sticky, className) { + this.trigger_up('notification', {title: title, message: message, sticky: sticky, className: className}); + }, + do_warn: function (title, message, sticky, className) { + this.trigger_up('warning', {title: title, message: message, sticky: sticky, className: className}); + }, +}; + +return ServicesMixin; + +}); diff --git a/addons/web/static/src/js/core/utils.js b/addons/web/static/src/js/core/utils.js index 1bdd9e70a69..0b4af722203 100644 --- a/addons/web/static/src/js/core/utils.js +++ b/addons/web/static/src/js/core/utils.js @@ -301,6 +301,37 @@ 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 * @param {Number} value the value to be rounded diff --git a/addons/web/static/src/js/services/report_service.js b/addons/web/static/src/js/services/report_service.js index 3272eef55bf..a2bb8759a24 100644 --- a/addons/web/static/src/js/services/report_service.js +++ b/addons/web/static/src/js/services/report_service.js @@ -6,12 +6,10 @@ odoo.define('web.ReportService', function (require) { */ var AbstractService = require('web.AbstractService'); -var session = require('web.session'); - -var wkhtmltopdfState; var ReportService = AbstractService.extend({ name: 'report', + dependencies: ['ajax'], /** * Checks the state of the installation of wkhtmltopdf on the server. @@ -21,12 +19,12 @@ var ReportService = AbstractService.extend({ * (possible values are 'ok', 'broken', 'install', 'upgrade', 'workers'). */ checkWkhtmltopdf: function () { - if (!wkhtmltopdfState) { - // AAB: for now, services aren't available for each other, so we - // can't use this._rpc (the ajax service) - wkhtmltopdfState = session.rpc('/report/check_wkhtmltopdf'); + if (!this.wkhtmltopdfState) { + this.wkhtmltopdfState = this._rpc({ + route:'/report/check_wkhtmltopdf' + }); } - return wkhtmltopdfState; + return this.wkhtmltopdfState; }, }); |
