7cdaf94446a17bd590871a3f6ecae6392a2bf2ad
[odoo/odoo.git] / addons / base / static / src / js / core.js
1 /*---------------------------------------------------------
2  * OpenERP controller framework
3  *--------------------------------------------------------*/
4
5 openerp.base.core = function(openerp) {
6 /**
7  * John Resig Class with factory improvement
8  */
9 (function() {
10     var initializing = false,
11         fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/;
12     // The base Class implementation (does nothing)
13     openerp.base.Class = function(){};
14
15     // Create a new Class that inherits from this class
16     openerp.base.Class.extend = function(prop) {
17         var _super = this.prototype;
18
19         // Instantiate a base class (but only create the instance,
20         // don't run the init constructor)
21         initializing = true;
22         var prototype = new this();
23         initializing = false;
24
25         // Copy the properties over onto the new prototype
26         for (var name in prop) {
27             // Check if we're overwriting an existing function
28             prototype[name] = typeof prop[name] == "function" &&
29                               typeof _super[name] == "function" &&
30                               fnTest.test(prop[name]) ?
31                     (function(name, fn) {
32                         return function() {
33                             var tmp = this._super;
34
35                             // Add a new ._super() method that is the same
36                             // method but on the super-class
37                             this._super = _super[name];
38
39                             // The method only need to be bound temporarily, so
40                             // we remove it when we're done executing
41                             var ret = fn.apply(this, arguments);
42                             this._super = tmp;
43
44                             return ret;
45                         };
46                     })(name, prop[name]) :
47                     prop[name];
48         }
49
50         // The dummy class constructor
51         function Class() {
52             // All construction is actually done in the init method
53             if (!initializing && this.init) {
54                 var ret = this.init.apply(this, arguments);
55                 if (ret) { return ret; }
56             }
57             return this;
58         }
59         Class.include = function (properties) {
60             for (var name in properties) {
61                 if (typeof properties[name] !== 'function'
62                         || !fnTest.test(properties[name])) {
63                     prototype[name] = properties[name];
64                 } else if (typeof prototype[name] === 'function'
65                            && prototype.hasOwnProperty(name)) {
66                     prototype[name] = (function (name, fn, previous) {
67                         return function () {
68                             var tmp = this._super;
69                             this._super = previous;
70                             var ret = fn.apply(this, arguments);
71                             this._super = tmp;
72                             return ret;
73                         }
74                     })(name, properties[name], prototype[name]);
75                 } else if (typeof _super[name] === 'function') {
76                     prototype[name] = (function (name, fn) {
77                         return function () {
78                             var tmp = this._super;
79                             this._super = _super[name];
80                             var ret = fn.apply(this, arguments);
81                             this._super = tmp;
82                             return ret;
83                         }
84                     })(name, properties[name]);
85                 }
86             }
87         };
88
89         // Populate our constructed prototype object
90         Class.prototype = prototype;
91
92         // Enforce the constructor to be what we expect
93         Class.constructor = Class;
94
95         // And make this class extendable
96         Class.extend = arguments.callee;
97
98         return Class;
99     };
100 })();
101
102 openerp.base.callback = function(obj, method) {
103     var callback = function() {
104         var args = Array.prototype.slice.call(arguments);
105         var r;
106         for(var i = 0; i < callback.callback_chain.length; i++)  {
107             var c = callback.callback_chain[i];
108             if(c.unique) {
109                 callback.callback_chain.splice(i, 1);
110                 i -= 1;
111             }
112             r = c.callback.apply(c.self, c.args.concat(args));
113             // TODO special value to stop the chain
114             // openerp.base.callback_stop
115         }
116         return r;
117     };
118     callback.callback_chain = [];
119     callback.add = function(f) {
120         if(typeof(f) == 'function') {
121             f = { callback: f, args: Array.prototype.slice.call(arguments, 1) };
122         }
123         f.self = f.self || null;
124         f.args = f.args || [];
125         f.unique = !!f.unique;
126         if(f.position == 'last') {
127             callback.callback_chain.push(f);
128         } else {
129             callback.callback_chain.unshift(f);
130         }
131         return callback;
132     };
133     callback.add_first = function(f) {
134         return callback.add.apply(null,arguments);
135     };
136     callback.add_last = function(f) {
137         return callback.add({
138             callback: f,
139             args: Array.prototype.slice.call(arguments, 1),
140             position: "last"
141         });
142     };
143
144     return callback.add({
145         callback: method,
146         self:obj,
147         args:Array.prototype.slice.call(arguments, 2)
148     });
149 };
150
151 /**
152  * Generates an inherited class that replaces all the methods by null methods (methods
153  * that does nothing and always return undefined).
154  *
155  * @param {Class} claz
156  * @param {dict} add Additional functions to override.
157  * @return {Class}
158  */
159 openerp.base.generate_null_object_class = function(claz, add) {
160     var newer = {};
161     var copy_proto = function(prototype) {
162         for (var name in prototype) {
163             if(typeof prototype[name] == "function") {
164                 newer[name] = function() {};
165             }
166         }
167         if (prototype.prototype)
168             copy_proto(prototype.prototype);
169     };
170     copy_proto(claz.prototype);
171     newer.init = openerp.base.Widget.prototype.init;
172     var tmpclass = claz.extend(newer);
173     return tmpclass.extend(add || {});
174 };
175
176 /**
177  * Base error for lookup failure
178  *
179  * @class
180  */
181 openerp.base.NotFound = openerp.base.Class.extend( /** @lends openerp.base.NotFound# */ {
182 });
183 openerp.base.KeyNotFound = openerp.base.NotFound.extend( /** @lends openerp.base.KeyNotFound# */ {
184     /**
185      * Thrown when a key could not be found in a mapping
186      *
187      * @constructs
188      * @extends openerp.base.NotFound
189      * @param {String} key the key which could not be found
190      */
191     init: function (key) {
192         this.key = key;
193     },
194     toString: function () {
195         return "The key " + this.key + " was not found";
196     }
197 });
198 openerp.base.ObjectNotFound = openerp.base.NotFound.extend( /** @lends openerp.base.ObjectNotFound# */ {
199     /**
200      * Thrown when an object path does not designate a valid class or object
201      * in the openerp hierarchy.
202      *
203      * @constructs
204      * @extends openerp.base.NotFound
205      * @param {String} path the invalid object path
206      */
207     init: function (path) {
208         this.path = path;
209     },
210     toString: function () {
211         return "Could not find any object of path " + this.path;
212     }
213 });
214 openerp.base.Registry = openerp.base.Class.extend( /** @lends openerp.base.Registry# */ {
215     /**
216      * Stores a mapping of arbitrary key (strings) to object paths (as strings
217      * as well).
218      *
219      * Resolves those paths at query time in order to always fetch the correct
220      * object, even if those objects have been overloaded/replaced after the
221      * registry was created.
222      *
223      * An object path is simply a dotted name from the openerp root to the
224      * object pointed to (e.g. ``"openerp.base.Session"`` for an OpenERP
225      * session object).
226      *
227      * @constructs
228      * @param {Object} mapping a mapping of keys to object-paths
229      */
230     init: function (mapping) {
231         this.map = mapping || {};
232     },
233     /**
234      * Retrieves the object matching the provided key string.
235      *
236      * @param {String} key the key to fetch the object for
237      * @returns {Class} the stored class, to initialize
238      *
239      * @throws {openerp.base.KeyNotFound} if the object was not in the mapping
240      * @throws {openerp.base.ObjectNotFound} if the object path was invalid
241      */
242     get_object: function (key) {
243         var path_string = this.map[key];
244         if (path_string === undefined) {
245             throw new openerp.base.KeyNotFound(key);
246         }
247
248         var object_match = openerp;
249         var path = path_string.split('.');
250         // ignore first section
251         for(var i=1; i<path.length; ++i) {
252             object_match = object_match[path[i]];
253
254             if (object_match === undefined) {
255                 throw new openerp.base.ObjectNotFound(path_string);
256             }
257         }
258         return object_match;
259     },
260     /**
261      * Tries a number of keys, and returns the first object matching one of
262      * the keys.
263      *
264      * @param {Array} keys a sequence of keys to fetch the object for
265      * @returns {Class} the first class found matching an object
266      *
267      * @throws {openerp.base.KeyNotFound} if none of the keys was in the mapping
268      * @trows {openerp.base.ObjectNotFound} if a found object path was invalid
269      */
270     get_any: function (keys) {
271         for (var i=0; i<keys.length; ++i) {
272             try {
273                 return this.get_object(keys[i]);
274             } catch (e) {
275                 if (e instanceof openerp.base.KeyNotFound) {
276                     continue;
277                 }
278                 throw e;
279             }
280         }
281         throw new openerp.base.KeyNotFound(keys.join(','));
282     },
283     /**
284      * Adds a new key and value to the registry.
285      *
286      * This method can be chained.
287      *
288      * @param {String} key
289      * @param {String} object_path fully qualified dotted object path
290      * @returns {openerp.base.Registry} itself
291      */
292     add: function (key, object_path) {
293         this.map[key] = object_path;
294         return this;
295     },
296     /**
297      * Creates and returns a copy of the current mapping, with the provided
298      * mapping argument added in (replacing existing keys if needed)
299      *
300      * @param {Object} [mapping={}] a mapping of keys to object-paths
301      */
302     clone: function (mapping) {
303         return new openerp.base.Registry(
304             _.extend({}, this.map, mapping || {}));
305     }
306 });
307
308 /**
309  * Utility class that any class is allowed to extend to easy common manipulations.
310  *
311  * It provides rpc calls, callback on all methods preceded by "on_" or "do_" and a
312  * logging facility.
313  */
314 openerp.base.SessionAware = openerp.base.Class.extend({
315     init: function(session) {
316         this.session = session;
317
318         // Transform on_* method into openerp.base.callbacks
319         for (var name in this) {
320             if(typeof(this[name]) == "function") {
321                 this[name].debug_name = name;
322                 // bind ALL function to this not only on_and _do ?
323                 if((/^on_|^do_/).test(name)) {
324                     this[name] = openerp.base.callback(this, this[name]);
325                 }
326             }
327         }
328     },
329     /**
330      * Performs a JSON-RPC call
331      *
332      * @param {String} url endpoint url
333      * @param {Object} data RPC parameters
334      * @param {Function} success RPC call success callback
335      * @param {Function} error RPC call error callback
336      * @returns {jQuery.Deferred} deferred object for the RPC call
337      */
338     rpc: function(url, data, success, error) {
339         return this.session.rpc(url, data, success, error);
340     },
341     log: function() {
342         var args = Array.prototype.slice.call(arguments);
343         var caller = arguments.callee.caller;
344         // TODO add support for line number using
345         // https://github.com/emwendelin/javascript-stacktrace/blob/master/stacktrace.js
346         // args.unshift("" + caller.debug_name);
347         this.on_log.apply(this,args);
348     },
349     on_log: function() {
350         if(window.openerp.debug || (window.location.search.indexOf('?debug') !== -1)) {
351             var notify = false;
352             var body = false;
353             if(window.console) {
354                 console.log(arguments);
355             } else {
356                 body = true;
357             }
358             var a = Array.prototype.slice.call(arguments, 0);
359             for(var i = 0; i < a.length; i++) {
360                 var v = a[i]==null ? "null" : a[i].toString();
361                 if(i==0) {
362                     notify = v.match(/^not/);
363                     body = v.match(/^bod/);
364                 }
365                 if(body) {
366                     $('<pre></pre>').text(v).appendTo($('body'));
367                 }
368                 if(notify && this.notification) {
369                     this.notification.notify("Logging:",v);
370                 }
371             }
372         }
373     }
374 });
375
376 /**
377  * Base class for all visual components. Provides a lot of functionalities helpful
378  * for the management of a part of the DOM.
379  *
380  * Widget handles:
381  * - Rendering with QWeb.
382  * - Life-cycle management and parenting (when a parent is destroyed, all its children are
383  *     destroyed too).
384  * - Insertion in DOM.
385  *
386  * Widget also extends SessionAware for ease of use.
387  *
388  * Guide to create implementations of the Widget class:
389  * ==============================================
390  *
391  * Here is a sample child class:
392  *
393  * MyWidget = openerp.base.Widget.extend({
394  *     // the name of the QWeb template to use for rendering
395  *     template: "MyQWebTemplate",
396  *     // identifier prefix, it is useful to put an obvious one for debugging
397  *     identifier_prefix: 'my-id-prefix-',
398  *
399  *     init: function(parent) {
400  *         this._super(parent);
401  *         // stuff that you want to init before the rendering
402  *     },
403  *     start: function() {
404  *         this._super();
405  *         // stuff you want to make after the rendering, `this.$element` holds a correct value
406  *         this.$element.find(".my_button").click(/* an example of event binding * /);
407  *
408  *         // if you have some asynchronous operations, it's a good idea to return
409  *         // a promise in start()
410  *         var promise = this.rpc(...);
411  *         return promise;
412  *     }
413  * });
414  *
415  * Now this class can simply be used with the following syntax:
416  *
417  * var my_widget = new MyWidget(this);
418  * my_widget.appendTo($(".some-div"));
419  *
420  * With these two lines, the MyWidget instance was inited, rendered, it was inserted into the
421  * DOM inside the ".some-div" div and its events were binded.
422  *
423  * And of course, when you don't need that widget anymore, just do:
424  *
425  * my_widget.stop();
426  *
427  * That will kill the widget in a clean way and erase its content from the dom.
428  */
429 openerp.base.Widget = openerp.base.SessionAware.extend({
430     /**
431      * The name of the QWeb template that will be used for rendering. Must be
432      * redefined in subclasses or the default render() method can not be used.
433      *
434      * @type string
435      */
436     template: null,
437     /**
438      * The prefix used to generate an id automatically. Should be redefined in
439      * subclasses. If it is not defined, a generic identifier will be used.
440      *
441      * @type string
442      */
443     identifier_prefix: 'generic-identifier-',
444     /**
445      * Construct the widget and set its parent if a parent is given.
446      *
447      * @constructs
448      * @param {openerp.base.Widget} parent Binds the current instance to the given Widget instance.
449      * When that widget is destroyed by calling stop(), the current instance will be
450      * destroyed too. Can be null.
451      * @param {String} element_id Deprecated. Sets the element_id. Only useful when you want
452      * to bind the current Widget to an already existing part of the DOM, which is not compatible
453      * with the DOM insertion methods provided by the current implementation of Widget. So
454      * for new components this argument should not be provided any more.
455      */
456     init: function(parent, /** @deprecated */ element_id) {
457         this._super((parent || {}).session);
458         // if given an element_id, try to get the associated DOM element and save
459         // a reference in this.$element. Else just generate a unique identifier.
460         this.element_id = element_id;
461         this.element_id = this.element_id || _.uniqueId(this.identifier_prefix);
462         var tmp = document.getElementById(this.element_id);
463         this.$element = tmp ? $(tmp) : undefined;
464
465         this.widget_parent = parent;
466         this.widget_children = [];
467         if(parent && parent.widget_children) {
468             parent.widget_children.push(this);
469         }
470         // useful to know if the widget was destroyed and should not be used anymore
471         this.widget_is_stopped = false;
472     },
473     /**
474      * Render the current widget and appends it to the given jQuery object or Widget.
475      *
476      * @param target A jQuery object or a Widget instance.
477      */
478     appendTo: function(target) {
479         var self = this;
480         return this._render_and_insert(function(t) {
481             self.$element.appendTo(t);
482         }, target);
483     },
484     /**
485      * Render the current widget and prepends it to the given jQuery object or Widget.
486      *
487      * @param target A jQuery object or a Widget instance.
488      */
489     prependTo: function(target) {
490         var self = this;
491         return this._render_and_insert(function(t) {
492             self.$element.prependTo(t);
493         }, target);
494     },
495     /**
496      * Render the current widget and inserts it after to the given jQuery object or Widget.
497      *
498      * @param target A jQuery object or a Widget instance.
499      */
500     insertAfter: function(target) {
501         var self = this;
502         return this._render_and_insert(function(t) {
503             self.$element.insertAfter(t);
504         }, target);
505     },
506     /**
507      * Render the current widget and inserts it before to the given jQuery object or Widget.
508      *
509      * @param target A jQuery object or a Widget instance.
510      */
511     insertBefore: function(target) {
512         var self = this;
513         return this._render_and_insert(function(t) {
514             self.$element.insertBefore(t);
515         }, target);
516     },
517     _render_and_insert: function(insertion, target) {
518         var rendered = this.render();
519         this.$element = $(rendered);
520         if (target instanceof openerp.base.Widget)
521             target = target.$element;
522         insertion(target);
523         return this.start();
524     },
525     /**
526      * Renders the widget using QWeb, `this.template` must be defined.
527      * The context given to QWeb contains the "widget" key that references `this`.
528      *
529      * @param {Object} additional Additional context arguments to pass to the template.
530      */
531     render: function (additional) {
532         return QWeb.render(this.template, _.extend({widget: this}, additional || {}));
533     },
534     /**
535      * Method called after rendering. Mostly used to bind actions, perform asynchronous
536      * calls, etc...
537      *
538      * By convention, the method should return a promise to inform the caller when
539      * this widget has been initialized.
540      *
541      * @returns {jQuery.Deferred}
542      */
543     start: function() {
544         if (!this.$element) {
545             var tmp = document.getElementById(this.element_id);
546             this.$element = tmp ? $(tmp) : undefined;
547         }
548         return $.Deferred().done().promise();
549     },
550     /**
551      * Destroys the current widget, also destory all its children before destroying itself.
552      */
553     stop: function() {
554         _.each(_.clone(this.widget_children), function(el) {
555             el.stop();
556         });
557         if(this.$element != null) {
558             this.$element.remove();
559         }
560         if (this.widget_parent && this.widget_parent.widget_children) {
561             this.widget_parent.widget_children = _.without(this.widget_parent.widget_children, this);
562         }
563         this.widget_parent = null;
564         this.widget_is_stopped = true;
565     },
566     /**
567      * Inform the action manager to do an action. Of course, this suppose that
568      * the action manager can be found amongst the ancestors of the current widget.
569      * If that's not the case this method will simply return `false`.
570      */
571     do_action: function(action, on_finished) {
572         if (this.widget_parent) {
573             return this.widget_parent.do_action(action, on_finished);
574         }
575         return false;
576     },
577     do_notify: function() {
578         if (this.widget_parent) {
579             return this.widget_parent.do_notify.apply(this,arguments);
580         }
581         return false;
582     },
583     do_warn: function() {
584         if (this.widget_parent) {
585             return this.widget_parent.do_warn.apply(this,arguments);
586         }
587         return false;
588     },
589     rpc: function(url, data, success, error) {
590         var def = $.Deferred().then(success, error);
591         var self = this;
592         this._super(url, data). then(function() {
593             if (!self.widget_is_stopped)
594                 def.resolve.apply(def, arguments);
595         }, function() {
596             if (!self.widget_is_stopped)
597                 def.reject.apply(def, arguments);
598         });
599         return def.promise();
600     }
601 });
602
603 /**
604  * @deprecated
605  * For retro compatibility only, the only difference with is that render() uses
606  * directly `this` instead of context with a "widget" key.
607  */
608 openerp.base.OldWidget = openerp.base.Widget.extend({
609     render: function (additional) {
610         return QWeb.render(this.template, _.extend(_.extend({}, this), additional || {}));
611     }
612 });
613
614 openerp.base.Session = openerp.base.Widget.extend( /** @lends openerp.base.Session# */{
615     /**
616      * @constructs
617      * @param element_id to use for exception reporting
618      * @param server
619      * @param port
620      */
621     init: function(parent, element_id, server, port) {
622         this._super(parent, element_id);
623         this.server = (server == undefined) ? location.hostname : server;
624         this.port = (port == undefined) ? location.port : port;
625         this.rpc_mode = (server == location.hostname) ? "ajax" : "jsonp";
626         this.debug = true;
627         this.db = "";
628         this.login = "";
629         this.password = "";
630         this.uid = false;
631         this.session_id = false;
632         this.module_list = [];
633         this.module_loaded = {"base": true};
634         this.context = {};
635     },
636     start: function() {
637         this.session_restore();
638     },
639     /**
640      * Executes an RPC call, registering the provided callbacks.
641      *
642      * Registers a default error callback if none is provided, and handles
643      * setting the correct session id and session context in the parameter
644      * objects
645      *
646      * @param {String} url RPC endpoint
647      * @param {Object} params call parameters
648      * @param {Function} success_callback function to execute on RPC call success
649      * @param {Function} error_callback function to execute on RPC call failure
650      * @returns {jQuery.Deferred} jquery-provided ajax deferred
651      */
652     rpc: function(url, params, success_callback, error_callback) {
653         var self = this;
654         // Construct a JSON-RPC2 request, method is currently unused
655         params.session_id = this.session_id;
656
657         // Call using the rpc_mode
658         var deferred = $.Deferred();
659         this.rpc_ajax(url, {
660             jsonrpc: "2.0",
661             method: "call",
662             params: params,
663             id:null
664         }).then(function () {deferred.resolve.apply(deferred, arguments);},
665                 function(error) {deferred.reject(error, $.Event());});
666         return deferred.fail(function() {
667             deferred.fail(function(error, event) {
668                 if (!event.isDefaultPrevented()) {
669                     self.on_rpc_error(error, event);
670                 }
671             });
672         }).then(success_callback, error_callback).promise();
673     },
674     /**
675      * Raw JSON-RPC call
676      *
677      * @returns {jQuery.Deferred} ajax-based deferred object
678      */
679     rpc_ajax: function(url, payload) {
680         var self = this;
681         this.on_rpc_request();
682         // url can be an $.ajax option object
683         if (_.isString(url)) {
684             url = {
685                 url: url
686             }
687         }
688         var ajax = _.extend({
689             type: "POST",
690             url: url,
691             dataType: 'json',
692             contentType: 'application/json',
693             data: JSON.stringify(payload),
694             processData: false
695         }, url);
696         var deferred = $.Deferred();
697         $.ajax(ajax).done(function(response, textStatus, jqXHR) {
698             self.on_rpc_response();
699             if (!response.error) {
700                 deferred.resolve(response["result"], textStatus, jqXHR);
701                 return;
702             }
703             if (response.error.data.type !== "session_invalid") {
704                 deferred.reject(response.error);
705                 return;
706             }
707             self.uid = false;
708             self.on_session_invalid(function() {
709                 self.rpc(url, payload.params,
710                     function() {
711                         deferred.resolve.apply(deferred, arguments);
712                     },
713                     function(error, event) {
714                         event.preventDefault();
715                         deferred.reject.apply(deferred, arguments);
716                     });
717             });
718         }).fail(function(jqXHR, textStatus, errorThrown) {
719             self.on_rpc_response();
720             var error = {
721                 code: -32098,
722                 message: "XmlHttpRequestError " + errorThrown,
723                 data: {type: "xhr"+textStatus, debug: jqXHR.responseText, objects: [jqXHR, errorThrown] }
724             };
725             deferred.reject(error);
726         });
727         return deferred.promise();
728     },
729     on_rpc_request: function() {
730     },
731     on_rpc_response: function() {
732     },
733     on_rpc_error: function(error) {
734     },
735     /**
736      * The session is validated either by login or by restoration of a previous session
737      */
738     on_session_valid: function() {
739         if(!openerp._modules_loaded)
740             this.load_modules();
741     },
742     on_session_invalid: function(contination) {
743     },
744     session_is_valid: function() {
745         return this.uid;
746     },
747     session_login: function(db, login, password, success_callback) {
748         var self = this;
749         this.db = db;
750         this.login = login;
751         this.password = password;
752         var params = { db: this.db, login: this.login, password: this.password };
753         this.rpc("/base/session/login", params, function(result) {
754             self.session_id = result.session_id;
755             self.uid = result.uid;
756             self.session_save();
757             self.on_session_valid();
758             if (success_callback)
759                 success_callback();
760         });
761     },
762     session_logout: function() {
763         this.uid = false;
764     },
765     /**
766      * Reloads uid and session_id from local storage, if they exist
767      */
768     session_restore: function () {
769         this.uid = this.get_cookie('uid');
770         this.session_id = this.get_cookie('session_id');
771         this.db = this.get_cookie('db');
772         this.login = this.get_cookie('login');
773         // we should do an rpc to confirm that this session_id is valid and if it is retrieve the information about db and login
774         // then call on_session_valid
775         this.on_session_valid();
776     },
777     /**
778      * Saves the session id and uid locally
779      */
780     session_save: function () {
781         this.set_cookie('uid', this.uid);
782         this.set_cookie('session_id', this.session_id);
783         this.set_cookie('db', this.db);
784         this.set_cookie('login', this.login);
785     },
786     logout: function() {
787         delete this.uid;
788         delete this.session_id;
789         delete this.db;
790         delete this.login;
791         this.set_cookie('uid', '');
792         this.set_cookie('session_id', '');
793         this.set_cookie('db', '');
794         this.set_cookie('login', '');
795         this.on_session_invalid(function() {});
796     },
797     /**
798      * Fetches a cookie stored by an openerp session
799      *
800      * @private
801      * @param name the cookie's name
802      */
803     get_cookie: function (name) {
804         var nameEQ = this.element_id + '|' + name + '=';
805         var cookies = document.cookie.split(';');
806         for(var i=0; i<cookies.length; ++i) {
807             var cookie = cookies[i].replace(/^\s*/, '');
808             if(cookie.indexOf(nameEQ) === 0) {
809                 return JSON.parse(decodeURIComponent(cookie.substring(nameEQ.length)));
810             }
811         }
812         return null;
813     },
814     /**
815      * Create a new cookie with the provided name and value
816      *
817      * @private
818      * @param name the cookie's name
819      * @param value the cookie's value
820      * @param ttl the cookie's time to live, 1 year by default, set to -1 to delete
821      */
822     set_cookie: function (name, value, ttl) {
823         ttl = ttl || 24*60*60*365;
824         document.cookie = [
825             this.element_id + '|' + name + '=' + encodeURIComponent(JSON.stringify(value)),
826             'max-age=' + ttl,
827             'expires=' + new Date(new Date().getTime() + ttl*1000).toGMTString()
828         ].join(';');
829     },
830     /**
831      * Load additional web addons of that instance and init them
832      */
833     load_modules: function() {
834         var self = this;
835         this.rpc('/base/session/modules', {}, function(result) {
836             self.module_list = result;
837             var modules = self.module_list.join(',');
838             if(self.debug || true) {
839                 self.rpc('/base/webclient/csslist', {"mods": modules}, self.do_load_css);
840                 self.rpc('/base/webclient/jslist', {"mods": modules}, self.do_load_js);
841             } else {
842                 self.do_load_css(["/base/webclient/css?mods="+modules]);
843                 self.do_load_js(["/base/webclient/js?mods="+modules]);
844             }
845             openerp._modules_loaded = true;
846         });
847     },
848     do_load_css: function (files) {
849         _.each(files, function (file) {
850             $('head').append($('<link>', {
851                 'href': file,
852                 'rel': 'stylesheet',
853                 'type': 'text/css'
854             }));
855         });
856     },
857     do_load_js: function(files) {
858         var self = this;
859         if(files.length != 0) {
860             var file = files.shift();
861             var tag = document.createElement('script');
862             tag.type = 'text/javascript';
863             tag.src = file;
864             tag.onload = tag.onreadystatechange = function() {
865                 if ( (tag.readyState && tag.readyState != "loaded" && tag.readyState != "complete") || tag.onload_done )
866                     return;
867                 tag.onload_done = true;
868                 self.do_load_js(files);
869             };
870             document.head.appendChild(tag);
871         } else {
872             this.on_modules_loaded();
873         }
874     },
875     on_modules_loaded: function() {
876         for(var j=0; j<this.module_list.length; j++) {
877             var mod = this.module_list[j];
878             if(this.module_loaded[mod])
879                 continue;
880             openerp[mod] = {};
881             // init module mod
882             if(openerp._openerp[mod] != undefined) {
883                 openerp._openerp[mod](openerp);
884                 this.module_loaded[mod] = true;
885             }
886         }
887     }
888 });
889
890 };
891 // vim:et fdc=0 fdl=0 foldnestmax=3 fdm=syntax: