summaryrefslogtreecommitdiff
path: root/addons/web/static/src/js/corelib.js
diff options
context:
space:
mode:
authorChristophe Matthieu <chm@openerp.com>2013-08-01 16:42:29 +0200
committerChristophe Matthieu <chm@openerp.com>2013-08-01 16:42:29 +0200
commit55e3a0dedd98b512ec6f649d5ff645add53c38ab (patch)
treeeb560d79393be4f6329bc3630bce12b11177c456 /addons/web/static/src/js/corelib.js
parent25f117a0561cef866f21c5360cd09ab32066b96b (diff)
parentbe7b1239c33c24f2727216eebf1d5f9907c54046 (diff)
[MERGE] from trunk-website-public_uid-chm
bzr revid: chm@openerp.com-20130801144229-tibuoa574kn86kyy
Diffstat (limited to 'addons/web/static/src/js/corelib.js')
-rw-r--r--addons/web/static/src/js/corelib.js781
1 files changed, 18 insertions, 763 deletions
diff --git a/addons/web/static/src/js/corelib.js b/addons/web/static/src/js/corelib.js
index 6ab7a30c642..c419685c222 100644
--- a/addons/web/static/src/js/corelib.js
+++ b/addons/web/static/src/js/corelib.js
@@ -25,486 +25,7 @@
openerp.web.corelib = function(instance) {
-/**
- * Improved John Resig's inheritance, based on:
- *
- * Simple JavaScript Inheritance
- * By John Resig http://ejohn.org/
- * MIT Licensed.
- *
- * Adds "include()"
- *
- * Defines The Class object. That object can be used to define and inherit classes using
- * the extend() method.
- *
- * Example:
- *
- * var Person = instance.web.Class.extend({
- * init: function(isDancing){
- * this.dancing = isDancing;
- * },
- * dance: function(){
- * return this.dancing;
- * }
- * });
- *
- * The init() method act as a constructor. This class can be instancied this way:
- *
- * var person = new Person(true);
- * person.dance();
- *
- * The Person class can also be extended again:
- *
- * var Ninja = Person.extend({
- * init: function(){
- * this._super( false );
- * },
- * dance: function(){
- * // Call the inherited version of dance()
- * return this._super();
- * },
- * swingSword: function(){
- * return true;
- * }
- * });
- *
- * When extending a class, each re-defined method can use this._super() to call the previous
- * implementation of that method.
- */
-(function() {
- var initializing = false,
- fnTest = /xyz/.test(function(){xyz();}) ? /\b_super\b/ : /.*/;
- // The web Class implementation (does nothing)
- instance.web.Class = function(){};
-
- /**
- * Subclass an existing class
- *
- * @param {Object} prop class-level properties (class attributes and instance methods) to set on the new class
- */
- instance.web.Class.extend = function() {
- var _super = this.prototype;
- // Support mixins arguments
- var args = _.toArray(arguments);
- args.unshift({});
- var prop = _.extend.apply(_,args);
-
- // Instantiate a web class (but only create the instance,
- // don't run the init constructor)
- initializing = true;
- var prototype = new this();
- initializing = false;
-
- // Copy the properties over onto the new prototype
- _.each(prop, function(val, name) {
- // Check if we're overwriting an existing function
- prototype[name] = typeof prop[name] == "function" &&
- fnTest.test(prop[name]) ?
- (function(name, fn) {
- return function() {
- var tmp = this._super;
-
- // Add a new ._super() method that is the same
- // method but on the super-class
- this._super = _super[name];
-
- // The method only need to be bound temporarily, so
- // we remove it when we're done executing
- var ret = fn.apply(this, arguments);
- this._super = tmp;
-
- return ret;
- };
- })(name, prop[name]) :
- prop[name];
- });
-
- // The dummy class constructor
- function Class() {
- if(this.constructor !== instance.web.Class){
- throw new Error("You can only instanciate objects with the 'new' operator");
- }
- // All construction is actually done in the init method
- if (!initializing && this.init) {
- var ret = this.init.apply(this, arguments);
- if (ret) { return ret; }
- }
- return this;
- }
- Class.include = function (properties) {
- _.each(properties, function(val, name) {
- if (typeof properties[name] !== 'function'
- || !fnTest.test(properties[name])) {
- prototype[name] = properties[name];
- } else if (typeof prototype[name] === 'function'
- && prototype.hasOwnProperty(name)) {
- prototype[name] = (function (name, fn, previous) {
- return function () {
- var tmp = this._super;
- this._super = previous;
- var ret = fn.apply(this, arguments);
- this._super = tmp;
- return ret;
- };
- })(name, properties[name], prototype[name]);
- } else if (typeof _super[name] === 'function') {
- prototype[name] = (function (name, fn) {
- return function () {
- var tmp = this._super;
- this._super = _super[name];
- var ret = fn.apply(this, arguments);
- this._super = tmp;
- return ret;
- };
- })(name, properties[name]);
- }
- });
- };
-
- // Populate our constructed prototype object
- Class.prototype = prototype;
-
- // Enforce the constructor to be what we expect
- Class.constructor = Class;
-
- // And make this class extendable
- Class.extend = arguments.callee;
-
- return Class;
- };
-})();
-
-// Mixins
-
-/**
- * Mixin to structure objects' life-cycles folowing a parent-children
- * relationship. Each object can a have a parent and multiple children.
- * When an object is destroyed, all its children are destroyed too releasing
- * any resource they could have reserved before.
- */
-instance.web.ParentedMixin = {
- __parentedMixin : true,
- init: function() {
- this.__parentedDestroyed = false;
- this.__parentedChildren = [];
- this.__parentedParent = null;
- },
- /**
- * Set the parent of the current object. When calling this method, the
- * parent will also be informed and will return the current object
- * when its getChildren() method is called. If the current object did
- * already have a parent, it is unregistered before, which means the
- * previous parent will not return the current object anymore when its
- * getChildren() method is called.
- */
- setParent : function(parent) {
- if (this.getParent()) {
- if (this.getParent().__parentedMixin) {
- this.getParent().__parentedChildren = _.without(this
- .getParent().getChildren(), this);
- }
- }
- this.__parentedParent = parent;
- if (parent && parent.__parentedMixin) {
- parent.__parentedChildren.push(this);
- }
- },
- /**
- * Return the current parent of the object (or null).
- */
- getParent : function() {
- return this.__parentedParent;
- },
- /**
- * Return a list of the children of the current object.
- */
- getChildren : function() {
- return _.clone(this.__parentedChildren);
- },
- /**
- * Returns true if destroy() was called on the current object.
- */
- isDestroyed : function() {
- return this.__parentedDestroyed;
- },
- /**
- Utility method to only execute asynchronous actions if the current
- object has not been destroyed.
-
- @param {$.Deferred} promise The promise representing the asynchronous
- action.
- @param {bool} [reject=false] If true, the returned promise will be
- rejected with no arguments if the current
- object is destroyed. If false, the
- returned promise will never be resolved
- or rejected.
- @returns {$.Deferred} A promise that will mirror the given promise if
- everything goes fine but will either be rejected
- with no arguments or never resolved if the
- current object is destroyed.
- */
- alive: function(promise, reject) {
- var def = $.Deferred();
- var self = this;
- promise.done(function() {
- if (! self.isDestroyed()) {
- if (! reject)
- def.resolve.apply(def, arguments);
- else
- def.reject();
- }
- }).fail(function() {
- if (! self.isDestroyed()) {
- if (! reject)
- def.reject.apply(def, arguments);
- else
- def.reject();
- }
- });
- return def.promise();
- },
- /**
- * Inform the object it should destroy itself, releasing any
- * resource it could have reserved.
- */
- destroy : function() {
- _.each(this.getChildren(), function(el) {
- el.destroy();
- });
- this.setParent(undefined);
- this.__parentedDestroyed = true;
- }
-};
-
-/**
- * Backbone's events. Do not ever use it directly, use EventDispatcherMixin instead.
- *
- * This class just handle the dispatching of events, it is not meant to be extended,
- * nor used directly. All integration with parenting and automatic unregistration of
- * events is done in EventDispatcherMixin.
- *
- * Copyright notice for the following Class:
- *
- * (c) 2010-2012 Jeremy Ashkenas, DocumentCloud Inc.
- * Backbone may be freely distributed under the MIT license.
- * For all details and documentation:
- * http://backbonejs.org
- *
- */
-var Events = instance.web.Class.extend({
- on : function(events, callback, context) {
- var ev;
- events = events.split(/\s+/);
- var calls = this._callbacks || (this._callbacks = {});
- while ((ev = events.shift())) {
- var list = calls[ev] || (calls[ev] = {});
- var tail = list.tail || (list.tail = list.next = {});
- tail.callback = callback;
- tail.context = context;
- list.tail = tail.next = {};
- }
- return this;
- },
-
- off : function(events, callback, context) {
- var ev, calls, node;
- if (!events) {
- delete this._callbacks;
- } else if ((calls = this._callbacks)) {
- events = events.split(/\s+/);
- while ((ev = events.shift())) {
- node = calls[ev];
- delete calls[ev];
- if (!callback || !node)
- continue;
- while ((node = node.next) && node.next) {
- if (node.callback === callback
- && (!context || node.context === context))
- continue;
- this.on(ev, node.callback, node.context);
- }
- }
- }
- return this;
- },
-
- callbackList: function() {
- var lst = [];
- _.each(this._callbacks || {}, function(el, eventName) {
- var node = el;
- while ((node = node.next) && node.next) {
- lst.push([eventName, node.callback, node.context]);
- }
- });
- return lst;
- },
-
- trigger : function(events) {
- var event, node, calls, tail, args, all, rest;
- if (!(calls = this._callbacks))
- return this;
- all = calls['all'];
- (events = events.split(/\s+/)).push(null);
- // Save references to the current heads & tails.
- while ((event = events.shift())) {
- if (all)
- events.push({
- next : all.next,
- tail : all.tail,
- event : event
- });
- if (!(node = calls[event]))
- continue;
- events.push({
- next : node.next,
- tail : node.tail
- });
- }
- rest = Array.prototype.slice.call(arguments, 1);
- while ((node = events.pop())) {
- tail = node.tail;
- args = node.event ? [ node.event ].concat(rest) : rest;
- while ((node = node.next) !== tail) {
- node.callback.apply(node.context || this, args);
- }
- }
- return this;
- }
-});
-
-instance.web.EventDispatcherMixin = _.extend({}, instance.web.ParentedMixin, {
- __eventDispatcherMixin: true,
- init: function() {
- instance.web.ParentedMixin.init.call(this);
- this.__edispatcherEvents = new Events();
- this.__edispatcherRegisteredEvents = [];
- },
- on: function(events, dest, func) {
- var self = this;
- if (!(func instanceof Function)) {
- throw new Error("Event handler must be a function.");
- }
- events = events.split(/\s+/);
- _.each(events, function(eventName) {
- self.__edispatcherEvents.on(eventName, func, dest);
- if (dest && dest.__eventDispatcherMixin) {
- dest.__edispatcherRegisteredEvents.push({name: eventName, func: func, source: self});
- }
- });
- return this;
- },
- off: function(events, dest, func) {
- var self = this;
- events = events.split(/\s+/);
- _.each(events, function(eventName) {
- self.__edispatcherEvents.off(eventName, func, dest);
- if (dest && dest.__eventDispatcherMixin) {
- dest.__edispatcherRegisteredEvents = _.filter(dest.__edispatcherRegisteredEvents, function(el) {
- return !(el.name === eventName && el.func === func && el.source === self);
- });
- }
- });
- return this;
- },
- trigger: function(events) {
- this.__edispatcherEvents.trigger.apply(this.__edispatcherEvents, arguments);
- return this;
- },
- destroy: function() {
- var self = this;
- _.each(this.__edispatcherRegisteredEvents, function(event) {
- event.source.__edispatcherEvents.off(event.name, event.func, self);
- });
- this.__edispatcherRegisteredEvents = [];
- _.each(this.__edispatcherEvents.callbackList(), function(cal) {
- this.off(cal[0], cal[2], cal[1]);
- }, this);
- this.__edispatcherEvents.off();
- instance.web.ParentedMixin.destroy.call(this);
- }
-});
-
-instance.web.PropertiesMixin = _.extend({}, instance.web.EventDispatcherMixin, {
- init: function() {
- instance.web.EventDispatcherMixin.init.call(this);
- this.__getterSetterInternalMap = {};
- },
- set: function(arg1, arg2, arg3) {
- var map;
- var options;
- if (typeof arg1 === "string") {
- map = {};
- map[arg1] = arg2;
- options = arg3 || {};
- } else {
- map = arg1;
- options = arg2 || {};
- }
- var self = this;
- var changed = false;
- _.each(map, function(val, key) {
- var tmp = self.__getterSetterInternalMap[key];
- if (tmp === val)
- return;
- changed = true;
- self.__getterSetterInternalMap[key] = val;
- if (! options.silent)
- self.trigger("change:" + key, self, {
- oldValue: tmp,
- newValue: val
- });
- });
- if (changed)
- self.trigger("change", self);
- },
- get: function(key) {
- return this.__getterSetterInternalMap[key];
- }
-});
-
-// Classes
-
-/**
- A class containing common utility methods useful when working with OpenERP as well as the PropertiesMixin.
-*/
-instance.web.Controller = instance.web.Class.extend(instance.web.PropertiesMixin, {
- /**
- * Constructs the object and sets its parent if a parent is given.
- *
- * @param {instance.web.Controller} parent Binds the current instance to the given Controller instance.
- * When that controller is destroyed by calling destroy(), the current instance will be
- * destroyed too. Can be null.
- */
- init: function(parent) {
- instance.web.PropertiesMixin.init.call(this);
- this.setParent(parent);
- },
- /**
- * Proxies a method of the object, in order to keep the right ``this`` on
- * method invocations.
- *
- * This method is similar to ``Function.prototype.bind`` or ``_.bind``, and
- * even more so to ``jQuery.proxy`` with a fundamental difference: its
- * resolution of the method being called is lazy, meaning it will use the
- * method as it is when the proxy is called, not when the proxy is created.
- *
- * Other methods will fix the bound method to what it is when creating the
- * binding/proxy, which is fine in most javascript code but problematic in
- * OpenERP Web where developers may want to replace existing callbacks with
- * theirs.
- *
- * The semantics of this precisely replace closing over the method call.
- *
- * @param {String|Function} method function or name of the method to invoke
- * @returns {Function} proxied method
- */
- proxy: function (method) {
- var self = this;
- return function () {
- var fn = (typeof method === 'string') ? self[method] : method;
- return fn.apply(self, arguments);
- };
- },
+var ControllerMixin = {
/**
* Informs the action manager to do an action. This supposes that
* the action manager can be found amongst the ancestors of the current widget.
@@ -530,300 +51,34 @@ instance.web.Controller = instance.web.Class.extend(instance.web.PropertiesMixin
return false;
},
rpc: function(url, data, options) {
- return this.alive(instance.session.rpc(url, data, options));
+ return this.alive(openerp.session.rpc(url, data, options));
}
-});
+};
/**
- * Base class for all visual components. Provides a lot of functionalities helpful
- * for the management of a part of the DOM.
- *
- * Widget handles:
- * - Rendering with QWeb.
- * - Life-cycle management and parenting (when a parent is destroyed, all its children are
- * destroyed too).
- * - Insertion in DOM.
- *
- * Guide to create implementations of the Widget class:
- * ==============================================
- *
- * Here is a sample child class:
- *
- * MyWidget = instance.base.Widget.extend({
- * // the name of the QWeb template to use for rendering
- * template: "MyQWebTemplate",
- *
- * init: function(parent) {
- * this._super(parent);
- * // stuff that you want to init before the rendering
- * },
- * start: function() {
- * // stuff you want to make after the rendering, `this.$el` holds a correct value
- * this.$el.find(".my_button").click(/* an example of event binding * /);
- *
- * // if you have some asynchronous operations, it's a good idea to return
- * // a promise in start()
- * var promise = this.rpc(...);
- * return promise;
- * }
- * });
- *
- * Now this class can simply be used with the following syntax:
- *
- * var my_widget = new MyWidget(this);
- * my_widget.appendTo($(".some-div"));
- *
- * With these two lines, the MyWidget instance was inited, rendered, it was inserted into the
- * DOM inside the ".some-div" div and its events were binded.
- *
- * And of course, when you don't need that widget anymore, just do:
- *
- * my_widget.destroy();
- *
- * That will kill the widget in a clean way and erase its content from the dom.
- */
-instance.web.Widget = instance.web.Controller.extend({
- // Backbone-ish API
- tagName: 'div',
- id: null,
- className: null,
- attributes: {},
- events: {},
- /**
- * The name of the QWeb template that will be used for rendering. Must be
- * redefined in subclasses or the default render() method can not be used.
- *
- * @type string
- */
- template: null,
+ A class containing common utility methods useful when working with OpenERP as well as the PropertiesMixin.
+*/
+openerp.web.Controller = openerp.web.Class.extend(openerp.web.PropertiesMixin, ControllerMixin, {
/**
- * Constructs the widget and sets its parent if a parent is given.
- *
- * @constructs instance.web.Widget
+ * Constructs the object and sets its parent if a parent is given.
*
- * @param {instance.web.Widget} parent Binds the current instance to the given Widget instance.
- * When that widget is destroyed by calling destroy(), the current instance will be
+ * @param {openerp.web.Controller} parent Binds the current instance to the given Controller instance.
+ * When that controller is destroyed by calling destroy(), the current instance will be
* destroyed too. Can be null.
*/
init: function(parent) {
- this._super(parent);
- // Bind on_/do_* methods to this
- // We might remove this automatic binding in the future
- for (var name in this) {
- if(typeof(this[name]) == "function") {
- if((/^on_|^do_/).test(name)) {
- this[name] = this[name].bind(this);
- }
- }
- }
- // FIXME: this should not be
- this.setElement(this._make_descriptive());
- this.session = instance.session;
- },
- /**
- * Destroys the current widget, also destroys all its children before destroying itself.
- */
- destroy: function() {
- _.each(this.getChildren(), function(el) {
- el.destroy();
- });
- if(this.$el) {
- this.$el.remove();
- }
- instance.web.PropertiesMixin.destroy.call(this);
- },
- /**
- * Renders the current widget and appends it to the given jQuery object or Widget.
- *
- * @param target A jQuery object or a Widget instance.
- */
- appendTo: function(target) {
- var self = this;
- return this.__widgetRenderAndInsert(function(t) {
- self.$el.appendTo(t);
- }, target);
- },
- /**
- * Renders the current widget and prepends it to the given jQuery object or Widget.
- *
- * @param target A jQuery object or a Widget instance.
- */
- prependTo: function(target) {
- var self = this;
- return this.__widgetRenderAndInsert(function(t) {
- self.$el.prependTo(t);
- }, target);
- },
- /**
- * Renders the current widget and inserts it after to the given jQuery object or Widget.
- *
- * @param target A jQuery object or a Widget instance.
- */
- insertAfter: function(target) {
- var self = this;
- return this.__widgetRenderAndInsert(function(t) {
- self.$el.insertAfter(t);
- }, target);
- },
- /**
- * Renders the current widget and inserts it before to the given jQuery object or Widget.
- *
- * @param target A jQuery object or a Widget instance.
- */
- insertBefore: function(target) {
- var self = this;
- return this.__widgetRenderAndInsert(function(t) {
- self.$el.insertBefore(t);
- }, target);
- },
- /**
- * Renders the current widget and replaces the given jQuery object.
- *
- * @param target A jQuery object or a Widget instance.
- */
- replace: function(target) {
- return this.__widgetRenderAndInsert(_.bind(function(t) {
- this.$el.replaceAll(t);
- }, this), target);
- },
- __widgetRenderAndInsert: function(insertion, target) {
- this.renderElement();
- insertion(target);
- return this.start();
- },
- /**
- * Method called after rendering. Mostly used to bind actions, perform asynchronous
- * calls, etc...
- *
- * By convention, this method should return an object that can be passed to $.when()
- * to inform the caller when this widget has been initialized.
- *
- * @returns {jQuery.Deferred or any}
- */
- start: function() {
- return $.when();
- },
- /**
- * Renders the element. The default implementation renders the widget using QWeb,
- * `this.template` must be defined. The context given to QWeb contains the "widget"
- * key that references `this`.
- */
- renderElement: function() {
- var $el;
- if (this.template) {
- $el = $(_.str.trim(instance.web.qweb.render(
- this.template, {widget: this})));
- } else {
- $el = this._make_descriptive();
- }
- this.replaceElement($el);
- },
- /**
- * Re-sets the widget's root element and replaces the old root element
- * (if any) by the new one in the DOM.
- *
- * @param {HTMLElement | jQuery} $el
- * @returns {*} this
- */
- replaceElement: function ($el) {
- var $oldel = this.$el;
- this.setElement($el);
- if ($oldel && !$oldel.is(this.$el)) {
- $oldel.replaceWith(this.$el);
- }
- return this;
- },
- /**
- * Re-sets the widget's root element (el/$el/$el).
- *
- * Includes:
- * * re-delegating events
- * * re-binding sub-elements
- * * if the widget already had a root element, replacing the pre-existing
- * element in the DOM
- *
- * @param {HTMLElement | jQuery} element new root element for the widget
- * @return {*} this
- */
- setElement: function (element) {
- // NB: completely useless, as WidgetMixin#init creates a $el
- // always
- if (this.$el) {
- this.undelegateEvents();
- }
-
- this.$el = (element instanceof $) ? element : $(element);
- this.el = this.$el[0];
-
- this.delegateEvents();
-
- return this;
- },
- /**
- * Utility function to build small DOM elements.
- *
- * @param {String} tagName name of the DOM element to create
- * @param {Object} [attributes] map of DOM attributes to set on the element
- * @param {String} [content] HTML content to set on the element
- * @return {Element}
- */
- make: function (tagName, attributes, content) {
- var el = document.createElement(tagName);
- if (!_.isEmpty(attributes)) {
- $(el).attr(attributes);
- }
- if (content) {
- $(el).html(content);
- }
- return el;
- },
- /**
- * Makes a potential root element from the declarative builder of the
- * widget
- *
- * @return {jQuery}
- * @private
- */
- _make_descriptive: function () {
- var attrs = _.extend({}, this.attributes || {});
- if (this.id) { attrs.id = this.id; }
- if (this.className) { attrs['class'] = this.className; }
- return $(this.make(this.tagName, attrs));
+ openerp.web.PropertiesMixin.init.call(this);
+ this.setParent(parent);
+ this.session = openerp.session;
},
- delegateEvents: function () {
- var events = this.events;
- if (_.isEmpty(events)) { return; }
-
- for(var key in events) {
- if (!events.hasOwnProperty(key)) { continue; }
-
- var method = this.proxy(events[key]);
-
- var match = /^(\S+)(\s+(.*))?$/.exec(key);
- var event = match[1];
- var selector = match[3];
+});
- event += '.widget_events';
- if (!selector) {
- this.$el.on(event, method);
- } else {
- this.$el.on(event, selector, method);
- }
- }
- },
- undelegateEvents: function () {
- this.$el.off('.widget_events');
+openerp.web.Widget.include(_.extend({}, ControllerMixin, {
+ init: function() {
+ this._super.apply(this, arguments);
+ this.session = openerp.session;
},
- /**
- * Shortcut for ``this.$el.find(selector)``
- *
- * @param {String} selector CSS selector, rooted in $el
- * @returns {jQuery} selector match
- */
- $: function(selector) {
- return this.$el.find(selector);
- }
-});
+}));
instance.web.Registry = instance.web.Class.extend({
/**