diff options
Diffstat (limited to 'addons/web/static/src/js/core')
| -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 |
5 files changed, 206 insertions, 129 deletions
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 |
