[MERGE] Merge with trunk upto revision no 1218.
[odoo/odoo.git] / addons / web / static / src / js / core.js
index fe08e53..b1708bb 100644 (file)
@@ -1,6 +1,13 @@
 /*---------------------------------------------------------
  * OpenERP Web core
  *--------------------------------------------------------*/
+var console;
+if (!console) {
+    console = {log: function () {}};
+}
+if (!console.debug) {
+    console.debug = console.log;
+}
 
 openerp.web.core = function(openerp) {
 openerp.web.qweb = new QWeb2.Engine();
@@ -13,11 +20,17 @@ openerp.web.qweb.debug = (window.location.search.indexOf('?debug') !== -1);
         fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/;
     // The web Class implementation (does nothing)
     /**
+     * Extended version of John Resig's Class pattern
+     *
      * @class
      */
     openerp.web.Class = function(){};
 
-    // Create a new Class that inherits from this class
+    /**
+     * Subclass an existing class
+     *
+     * @param {Object} prop class-level properties (class attributes and instance methods) to set on the new class
+     */
     openerp.web.Class.extend = function(prop) {
         var _super = this.prototype;
 
@@ -61,6 +74,7 @@ openerp.web.qweb.debug = (window.location.search.indexOf('?debug') !== -1);
             }
             return this;
         }
+        // This should NOT be used, like callbackenable it's too hackish not enough javasish
         Class.include = function (properties) {
             for (var name in properties) {
                 if (typeof properties[name] !== 'function'
@@ -158,7 +172,7 @@ openerp.web.callback = function(obj, method) {
  * that does nothing and always return undefined).
  *
  * @param {Class} claz
- * @param {dict} add Additional functions to override.
+ * @param {Object} add Additional functions to override.
  * @return {Class}
  */
 openerp.web.generate_null_object_class = function(claz, add) {
@@ -189,7 +203,7 @@ openerp.web.KeyNotFound = openerp.web.NotFound.extend( /** @lends openerp.web.Ke
     /**
      * Thrown when a key could not be found in a mapping
      *
-     * @constructs
+     * @constructs openerp.web.KeyNotFound
      * @extends openerp.web.NotFound
      * @param {String} key the key which could not be found
      */
@@ -205,7 +219,7 @@ openerp.web.ObjectNotFound = openerp.web.NotFound.extend( /** @lends openerp.web
      * Thrown when an object path does not designate a valid class or object
      * in the openerp hierarchy.
      *
-     * @constructs
+     * @constructs openerp.web.ObjectNotFound
      * @extends openerp.web.NotFound
      * @param {String} path the invalid object path
      */
@@ -229,7 +243,7 @@ openerp.web.Registry = openerp.web.Class.extend( /** @lends openerp.web.Registry
      * object pointed to (e.g. ``"openerp.web.Session"`` for an OpenERP
      * session object).
      *
-     * @constructs
+     * @constructs openerp.web.Registry
      * @param {Object} mapping a mapping of keys to object-paths
      */
     init: function (mapping) {
@@ -310,7 +324,11 @@ openerp.web.Registry = openerp.web.Class.extend( /** @lends openerp.web.Registry
     }
 });
 
-openerp.web.CallbackEnabled = openerp.web.Class.extend({
+openerp.web.CallbackEnabled = openerp.web.Class.extend(/** @lends openerp.web.CallbackEnabled# */{
+    /**
+     * @constructs openerp.web.CallbackEnabled
+     * @extends openerp.web.Class
+     */
     init: function() {
         // Transform on_* method into openerp.web.callbacks
         for (var name in this) {
@@ -327,9 +345,11 @@ openerp.web.CallbackEnabled = openerp.web.Class.extend({
 
 openerp.web.Session = openerp.web.CallbackEnabled.extend( /** @lends openerp.web.Session# */{
     /**
-     * @constructs
-     * @param server
-     * @param port
+     * @constructs openerp.web.Session
+     * @extends openerp.web.CallbackEnabled
+     *
+     * @param {String} [server] JSON-RPC endpoint hostname
+     * @param {String} [port] JSON-RPC endpoint port
      */
     init: function(server, port) {
         this._super();
@@ -337,12 +357,10 @@ openerp.web.Session = openerp.web.CallbackEnabled.extend( /** @lends openerp.web
         this.port = (port == undefined) ? location.port : port;
         this.rpc_mode = (server == location.hostname) ? "ajax" : "jsonp";
         this.debug = (window.location.search.indexOf('?debug') !== -1);
-        this.db = "";
-        this.login = "";
-        this.password = "";
-        this.user_context= {};
-        this.uid = false;
         this.session_id = false;
+        this.uid = false;
+        this.user_context= {};
+        this.db = false;
         this.module_list = [];
         this.module_loaded = {"web": true};
         this.context = {};
@@ -462,58 +480,46 @@ openerp.web.Session = openerp.web.CallbackEnabled.extend( /** @lends openerp.web
     },
     session_login: function(db, login, password, success_callback) {
         var self = this;
-        this.db = db;
-        this.login = login;
-        this.password = password;
-        var params = { db: this.db, login: this.login, password: this.password };
-        this.rpc("/web/session/login", params, function(result) {
+        var params = { db: db, login: login, password: password };
+        return this.rpc("/web/session/login", params, function(result) {
             self.session_id = result.session_id;
             self.uid = result.uid;
             self.user_context = result.context;
+            self.db = result.db;
             self.session_save();
-            self.on_session_valid();
             return true;
         }).then(success_callback);
     },
-    session_logout: function() {
-        this.uid = false;
-    },
     /**
      * Reloads uid and session_id from local storage, if they exist
      */
     session_restore: function () {
-        this.uid = this.get_cookie('uid');
+        var self = this;
         this.session_id = this.get_cookie('session_id');
-        this.db = this.get_cookie('db');
-        this.login = this.get_cookie('login');
-        this.user_context = this.get_cookie("user_context");
-        // we should do an rpc to confirm that this session_id is valid and if it is retrieve the information about db and login
-        // then call on_session_valid
-        if (this.uid)
-            this.on_session_valid();
-        else
-            this.on_session_invalid();
+        return this.rpc("/web/session/get_session_info", {}).then(function(result) {
+            self.uid = result.uid;
+            self.user_context = result.context;
+            self.db = result.db;
+            if (self.uid)
+                self.on_session_valid();
+            else
+                self.on_session_invalid();
+        }, function() {
+            self.on_session_invalid();
+        });
     },
     /**
      * Saves the session id and uid locally
      */
     session_save: function () {
-        this.set_cookie('uid', this.uid);
         this.set_cookie('session_id', this.session_id);
-        this.set_cookie('db', this.db);
-        this.set_cookie('login', this.login);
-        this.set_cookie('user_context', this.user_context);
     },
     logout: function() {
-        delete this.uid;
-        delete this.session_id;
-        delete this.db;
-        delete this.login;
-        this.set_cookie('uid', '');
         this.set_cookie('session_id', '');
-        this.set_cookie('db', '');
-        this.set_cookie('login', '');
-        this.on_session_invalid(function() {});
+        this.reload_client();
+    },
+    reload_client: function() {
+        window.location.reload();
     },
     /**
      * Fetches a cookie stored by an openerp session
@@ -577,9 +583,10 @@ openerp.web.Session = openerp.web.CallbackEnabled.extend( /** @lends openerp.web
         });
     },
     do_load_css: function (files) {
+        var self = this;
         _.each(files, function (file) {
             $('head').append($('<link>', {
-                'href': file + (this.debug ? '?debug=' + (new Date().getTime()) : ''),
+                'href': file + (self.debug ? '?debug=' + (new Date().getTime()) : ''),
                 'rel': 'stylesheet',
                 'type': 'text/css'
             }));
@@ -598,7 +605,8 @@ openerp.web.Session = openerp.web.CallbackEnabled.extend( /** @lends openerp.web
                 tag.onload_done = true;
                 self.do_load_js(files);
             };
-            document.head.appendChild(tag);
+            $('head').append(tag);
+            self.do_load_js(files);
         } else {
             this.on_modules_loaded();
         }
@@ -709,13 +717,18 @@ openerp.web.Session = openerp.web.CallbackEnabled.extend( /** @lends openerp.web
     }
 });
 
-/**
- * Utility class that any class is allowed to extend to easy common manipulations.
- *
- * It provides rpc calls, callback on all methods preceded by "on_" or "do_" and a
- * logging facility.
- */
-openerp.web.SessionAware = openerp.web.CallbackEnabled.extend({
+openerp.web.SessionAware = openerp.web.CallbackEnabled.extend(/** @lends openerp.web.SessionAware# */{
+    /**
+     * Utility class that any class is allowed to extend to easy common manipulations.
+     *
+     * It provides rpc calls, callback on all methods preceded by "on_" or "do_" and a
+     * logging facility.
+     *
+     * @constructs openerp.web.SessionAware
+     * @extends openerp.web.CallbackEnabled
+     *
+     * @param {openerp.web.Session} session
+     */
     init: function(session) {
         this._super();
         this.session = session;
@@ -731,43 +744,62 @@ openerp.web.SessionAware = openerp.web.CallbackEnabled.extend({
      */
     rpc: function(url, data, success, error) {
         return this.session.rpc(url, data, success, error);
-    },
-    log: function() {
-        var args = Array.prototype.slice.call(arguments);
-        var caller = arguments.callee.caller;
-        // TODO add support for line number using
-        // https://github.com/emwendelin/javascript-stacktrace/blob/master/stacktrace.js
-        // args.unshift("" + caller.debug_name);
-        this.on_log.apply(this,args);
-    },
-    on_log: function() {
-        if(this.session.debug) {
-            var notify = false;
-            var body = false;
-            if(window.console) {
-                console.log(arguments);
-            } else {
-                body = true;
-            }
-            var a = Array.prototype.slice.call(arguments, 0);
-            for(var i = 0; i < a.length; i++) {
-                var v = a[i]==null ? "null" : a[i].toString();
-                if(i==0) {
-                    notify = v.match(/^not/);
-                    body = v.match(/^bod/);
-                }
-                if(body) {
-                    $('<pre></pre>').text(v).appendTo($('body'));
-                }
-                if(notify && this.notification) {
-                    this.notification.notify("Logging:",v);
-                }
-            }
-        }
     }
 });
 
-openerp.web.Widget = openerp.web.SessionAware.extend({
+/**
+ * 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.
+ *
+ * Widget also extends SessionAware for ease of use.
+ *
+ * Guide to create implementations of the Widget class:
+ * ==============================================
+ *
+ * Here is a sample child class:
+ *
+ * MyWidget = openerp.base.Widget.extend({
+ *     // the name of the QWeb template to use for rendering
+ *     template: "MyQWebTemplate",
+ *     // identifier prefix, it is useful to put an obvious one for debugging
+ *     identifier_prefix: 'my-id-prefix-',
+ *
+ *     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.$element` holds a correct value
+ *         this.$element.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.stop();
+ *
+ * That will kill the widget in a clean way and erase its content from the dom.
+ */
+openerp.web.Widget = openerp.web.SessionAware.extend(/** @lends openerp.web.Widget# */{
     /**
      * 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.
@@ -785,7 +817,9 @@ openerp.web.Widget = openerp.web.SessionAware.extend({
     /**
      * Construct the widget and set its parent if a parent is given.
      *
-     * @constructs
+     * @constructs openerp.web.Widget
+     * @extends openerp.web.SessionAware
+     *
      * @param {openerp.web.Widget} parent Binds the current instance to the given Widget instance.
      * When that widget is destroyed by calling stop(), the current instance will be
      * destroyed too. Can be null.
@@ -884,6 +918,8 @@ openerp.web.Widget = openerp.web.SessionAware.extend({
      * @returns {jQuery.Deferred}
      */
     start: function() {
+        /* The default implementation is only useful for retro-compatibility, it is
+        not necessary to call it using _super() when using Widget for new components. */
         if (!this.$element) {
             var tmp = document.getElementById(this.element_id);
             this.$element = tmp ? $(tmp) : undefined;
@@ -891,7 +927,7 @@ openerp.web.Widget = openerp.web.SessionAware.extend({
         return $.Deferred().done().promise();
     },
     /**
-     * Destroys the current widget, also destory all its children before destroying itself.
+     * Destroys the current widget, also destroy all its children before destroying itself.
      */
     stop: function() {
         _.each(_.clone(this.widget_children), function(el) {
@@ -944,17 +980,23 @@ openerp.web.Widget = openerp.web.SessionAware.extend({
 });
 
 /**
+ * @class
+ * @extends openerp.web.Widget
  * @deprecated
  * For retro compatibility only, the only difference with is that render() uses
- * directly `this` instead of context with a "widget" key.
+ * directly ``this`` instead of context with a ``widget`` key.
  */
-openerp.web.OldWidget = openerp.web.Widget.extend({
+openerp.web.OldWidget = openerp.web.Widget.extend(/** @lends openerp.web.OldWidget# */{
     render: function (additional) {
         return openerp.web.qweb.render(this.template, _.extend(_.extend({}, this), additional || {}));
     }
 });
 
-openerp.web.TranslationDataBase = openerp.web.Class.extend({
+openerp.web.TranslationDataBase = openerp.web.Class.extend(/** @lends openerp.web.TranslationDataBase# */{
+    /**
+     * @constructs openerp.web.TranslationDataBase
+     * @extends openerp.web.Class
+     */
     init: function() {
         this.db = {};
         this.parameters = {"direction": 'ltr',