[imp] get rid of include() in novajs
[odoo/odoo.git] / addons / web / static / lib / novajs / src / nova.js
1 /*
2 Copyright (c) 2011, OpenERP S.A.
3 All rights reserved.
4
5 Redistribution and use in source and binary forms, with or without
6 modification, are permitted provided that the following conditions are met: 
7
8 1. Redistributions of source code must retain the above copyright notice, this
9    list of conditions and the following disclaimer. 
10 2. Redistributions in binary form must reproduce the above copyright notice,
11    this list of conditions and the following disclaimer in the documentation
12    and/or other materials provided with the distribution. 
13
14 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
15 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
16 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
18 ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
19 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
20 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
21 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
23 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 nova = (function() {
27     var lib = {};
28     lib.internal = {};
29
30     /*
31      * (Almost) unmodified John Resig's inheritance
32      */
33     /* Simple JavaScript Inheritance
34      * By John Resig http://ejohn.org/
35      * MIT Licensed.
36      */
37     // Inspired by base2 and Prototype
38     (function(){
39       var initializing = false, fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/;
40       // The base Class implementation (does nothing)
41       this.Class = function(){};
42       
43       // Create a new Class that inherits from this class
44       this.Class.extend = function(prop) {
45         var _super = this.prototype;
46         
47         // Instantiate a base class (but only create the instance,
48         // don't run the init constructor)
49         initializing = true;
50         var prototype = new this();
51         initializing = false;
52         
53         // Copy the properties over onto the new prototype
54         for (var name in prop) {
55           // Check if we're overwriting an existing function
56           prototype[name] = typeof prop[name] == "function" && 
57             typeof _super[name] == "function" && fnTest.test(prop[name]) ?
58             (function(name, fn){
59               return function() {
60                 var tmp = this._super;
61                 
62                 // Add a new ._super() method that is the same method
63                 // but on the super-class
64                 this._super = _super[name];
65                 
66                 // The method only need to be bound temporarily, so we
67                 // remove it when we're done executing
68                 var ret = fn.apply(this, arguments);        
69                 this._super = tmp;
70                 
71                 return ret;
72               };
73             })(name, prop[name]) :
74             prop[name];
75         }
76         
77         // The dummy class constructor
78         function Class() {
79           // All construction is actually done in the init method
80           if ( !initializing && this.init )
81             this.init.apply(this, arguments);
82         }
83         
84         // Populate our constructed prototype object
85         Class.prototype = prototype;
86         
87         // Enforce the constructor to be what we expect
88         Class.prototype.constructor = Class;
89     
90         // And make this class extendable
91         for(el in this) {
92             Class[el] = this[el];
93         }
94         
95         return Class;
96       };
97     }).call(lib);
98     // end of John Resig's code
99
100     lib.DestroyableMixin = {
101         init: function() {
102             this.__destroyableDestroyed = false;
103         },
104         isDestroyed : function() {
105             return this.__destroyableDestroyed;
106         },
107         destroy : function() {
108             this.__destroyableDestroyed = true;
109         }
110     };
111
112     lib.ParentedMixin = _.extend({}, lib.DestroyableMixin, {
113         __parentedMixin : true,
114         init: function() {
115             lib.DestroyableMixin.init.call(this);
116             this.__parentedChildren = [];
117             this.__parentedParent = null;
118         },
119         setParent : function(parent) {
120             if (this.getParent()) {
121                 if (this.getParent().__parentedMixin) {
122                     this.getParent().__parentedChildren = _.without(this
123                             .getParent().getChildren(), this);
124                 }
125             }
126             this.__parentedParent = parent;
127             if (parent && parent.__parentedMixin) {
128                 parent.__parentedChildren.push(this);
129             }
130         },
131         getParent : function() {
132             return this.__parentedParent;
133         },
134         getChildren : function() {
135             return _.clone(this.__parentedChildren);
136         },
137         destroy : function() {
138             _.each(this.getChildren(), function(el) {
139                 el.destroy();
140             });
141             this.setParent(undefined);
142             lib.DestroyableMixin.destroy.call(this);
143         }
144     });
145
146     /*
147      * Yes, we steal Backbone's events :)
148      * 
149      * This class just handle the dispatching of events, it is not meant to be extended,
150      * nor used directly. All integration with parenting and automatic unregistration of
151      * events is done in EventDispatcherMixin.
152      */
153     // (c) 2010-2012 Jeremy Ashkenas, DocumentCloud Inc.
154     // Backbone may be freely distributed under the MIT license.
155     // For all details and documentation:
156     // http://backbonejs.org
157     lib.internal.Events = lib.Class.extend({
158
159         on : function(events, callback, context) {
160             var ev;
161             events = events.split(/\s+/);
162             var calls = this._callbacks || (this._callbacks = {});
163             while (ev = events.shift()) {
164                 var list = calls[ev] || (calls[ev] = {});
165                 var tail = list.tail || (list.tail = list.next = {});
166                 tail.callback = callback;
167                 tail.context = context;
168                 list.tail = tail.next = {};
169             }
170             return this;
171         },
172
173         off : function(events, callback, context) {
174             var ev, calls, node;
175             if (!events) {
176                 delete this._callbacks;
177             } else if (calls = this._callbacks) {
178                 events = events.split(/\s+/);
179                 while (ev = events.shift()) {
180                     node = calls[ev];
181                     delete calls[ev];
182                     if (!callback || !node)
183                         continue;
184                     while ((node = node.next) && node.next) {
185                         if (node.callback === callback
186                                 && (!context || node.context === context))
187                             continue;
188                         this.on(ev, node.callback, node.context);
189                     }
190                 }
191             }
192             return this;
193         },
194
195         trigger : function(events) {
196             var event, node, calls, tail, args, all, rest;
197             if (!(calls = this._callbacks))
198                 return this;
199             all = calls['all'];
200             (events = events.split(/\s+/)).push(null);
201             // Save references to the current heads & tails.
202             while (event = events.shift()) {
203                 if (all)
204                     events.push({
205                         next : all.next,
206                         tail : all.tail,
207                         event : event
208                     });
209                 if (!(node = calls[event]))
210                     continue;
211                 events.push({
212                     next : node.next,
213                     tail : node.tail
214                 });
215             }
216             rest = Array.prototype.slice.call(arguments, 1);
217             while (node = events.pop()) {
218                 tail = node.tail;
219                 args = node.event ? [ node.event ].concat(rest) : rest;
220                 while ((node = node.next) !== tail) {
221                     node.callback.apply(node.context || this, args);
222                 }
223             }
224             return this;
225         }
226     });
227     // end of Backbone's events class
228     
229     lib.EventDispatcherMixin = _.extend({}, lib.ParentedMixin, {
230         __eventDispatcherMixin: true,
231         init: function() {
232             lib.ParentedMixin.init.call(this);
233             this.__edispatcherEvents = new lib.internal.Events();
234             this.__edispatcherRegisteredEvents = [];
235         },
236         on: function(events, dest, func) {
237             var self = this;
238             events = events.split(/\s+/);
239             _.each(events, function(eventName) {
240                 self.__edispatcherEvents.on(eventName, func, dest);
241                 if (dest && dest.__eventDispatcherMixin) {
242                     dest.__edispatcherRegisteredEvents.push({name: eventName, func: func, source: self});
243                 }
244             });
245             return this;
246         },
247         off: function(events, dest, func) {
248             var self = this;
249             events = events.split(/\s+/);
250             _.each(events, function(eventName) {
251                 self.__edispatcherEvents.off(eventName, func, dest);
252                 if (dest && dest.__eventDispatcherMixin) {
253                     dest.__edispatcherRegisteredEvents = _.filter(dest.__edispatcherRegisteredEvents, function(el) {
254                         return !(el.name === eventName && el.func === func && el.source === self);
255                     });
256                 }
257             });
258             return this;
259         },
260         trigger: function(events) {
261             this.__edispatcherEvents.trigger.apply(this.__edispatcherEvents, arguments);
262             return this;
263         },
264         destroy: function() {
265             var self = this;
266             _.each(this.__edispatcherRegisteredEvents, function(event) {
267                 event.source.__edispatcherEvents.off(event.name, event.func, self);
268             });
269             this.__edispatcherRegisteredEvents = [];
270             this.__edispatcherEvents.off();
271             lib.ParentedMixin.destroy.call(this);
272         }
273     });
274     
275     lib.GetterSetterMixin = _.extend({}, lib.EventDispatcherMixin, {
276         init: function() {
277             lib.EventDispatcherMixin.init.call(this);
278             this.__getterSetterInternalMap = {};
279         },
280         set: function(map) {
281             var self = this;
282             var changed = false;
283             _.each(map, function(val, key) {
284                 var tmp = self.__getterSetterInternalMap[key];
285                 if (tmp === val)
286                     return;
287                 changed = true;
288                 self.__getterSetterInternalMap[key] = val;
289                 self.trigger("change:" + key, self, {
290                     oldValue: tmp,
291                     newValue: val
292                 });
293             });
294             if (changed)
295                 self.trigger("change", self);
296         },
297         get: function(key) {
298             return this.__getterSetterInternalMap[key];
299         }
300     });
301     
302     lib.Widget = lib.Class.extend(_.extend({}, lib.GetterSetterMixin, {
303         /**
304          * Tag name when creating a default $element.
305          * @type string
306          */
307         tagName: 'div',
308         /**
309          * Constructs the widget and sets its parent if a parent is given.
310          *
311          * @constructs openerp.web.Widget
312          * @extends openerp.web.CallbackEnabled
313          *
314          * @param {openerp.web.Widget} parent Binds the current instance to the given Widget instance.
315          * When that widget is destroyed by calling destroy(), the current instance will be
316          * destroyed too. Can be null.
317          * @param {String} element_id Deprecated. Sets the element_id. Only useful when you want
318          * to bind the current Widget to an already existing part of the DOM, which is not compatible
319          * with the DOM insertion methods provided by the current implementation of Widget. So
320          * for new components this argument should not be provided any more.
321          */
322         init: function(parent) {
323             lib.GetterSetterMixin.init.call(this);
324             this.$element = $(document.createElement(this.tagName));
325     
326             this.setParent(parent);
327         },
328         /**
329          * Destroys the current widget, also destroys all its children before destroying itself.
330          */
331         destroy: function() {
332             _.each(this.getChildren(), function(el) {
333                 el.destroy();
334             });
335             if(this.$element != null) {
336                 this.$element.remove();
337             }
338             lib.GetterSetterMixin.destroy.call(this);
339         },
340         /**
341          * Renders the current widget and appends it to the given jQuery object or Widget.
342          *
343          * @param target A jQuery object or a Widget instance.
344          */
345         appendTo: function(target) {
346             var self = this;
347             return this.__widgetRenderAndInsert(function(t) {
348                 self.$element.appendTo(t);
349             }, target);
350         },
351         /**
352          * Renders the current widget and prepends it to the given jQuery object or Widget.
353          *
354          * @param target A jQuery object or a Widget instance.
355          */
356         prependTo: function(target) {
357             var self = this;
358             return this.__widgetRenderAndInsert(function(t) {
359                 self.$element.prependTo(t);
360             }, target);
361         },
362         /**
363          * Renders the current widget and inserts it after to the given jQuery object or Widget.
364          *
365          * @param target A jQuery object or a Widget instance.
366          */
367         insertAfter: function(target) {
368             var self = this;
369             return this.__widgetRenderAndInsert(function(t) {
370                 self.$element.insertAfter(t);
371             }, target);
372         },
373         /**
374          * Renders the current widget and inserts it before to the given jQuery object or Widget.
375          *
376          * @param target A jQuery object or a Widget instance.
377          */
378         insertBefore: function(target) {
379             var self = this;
380             return this.__widgetRenderAndInsert(function(t) {
381                 self.$element.insertBefore(t);
382             }, target);
383         },
384         /**
385          * Renders the current widget and replaces the given jQuery object.
386          *
387          * @param target A jQuery object or a Widget instance.
388          */
389         replace: function(target) {
390             return this.__widgetRenderAndInsert(_.bind(function(t) {
391                 this.$element.replaceAll(t);
392             }, this), target);
393         },
394         __widgetRenderAndInsert: function(insertion, target) {
395             this.renderElement();
396             insertion(target);
397             return this.start();
398         },
399         /**
400          * This is the method to implement to render the Widget.
401          */
402         renderElement: function() {},
403         /**
404          * Method called after rendering. Mostly used to bind actions, perform asynchronous
405          * calls, etc...
406          *
407          * By convention, the method should return a promise to inform the caller when
408          * this widget has been initialized.
409          *
410          * @returns {jQuery.Deferred}
411          */
412         start: function() {}
413     }));
414
415     return lib;
416 })();