X-Git-Url: http://git.inspyration.org/?a=blobdiff_plain;f=addons%2Fweb%2Fstatic%2Fsrc%2Fjs%2Fcore.js;h=066e904a8dfefbe44faa83faf51a6f47d5a9a6f5;hb=f87fe3c08a2fb0b39852efcaefa341b0d3e4a0a2;hp=0c3eaff56ed6a967e0f757fde61c53139af3d777;hpb=c5bcea713d360a71ca1d94d385d6e5110d1ff142;p=odoo%2Fodoo.git diff --git a/addons/web/static/src/js/core.js b/addons/web/static/src/js/core.js index 0c3eaff..066e904 100644 --- a/addons/web/static/src/js/core.js +++ b/addons/web/static/src/js/core.js @@ -254,14 +254,16 @@ openerp.web.Registry = openerp.web.Class.extend( /** @lends openerp.web.Registry * Retrieves the object matching the provided key string. * * @param {String} key the key to fetch the object for + * @param {Boolean} [silent_error=false] returns undefined if the key or object is not found, rather than throwing an exception * @returns {Class} the stored class, to initialize * * @throws {openerp.web.KeyNotFound} if the object was not in the mapping * @throws {openerp.web.ObjectNotFound} if the object path was invalid */ - get_object: function (key) { + get_object: function (key, silent_error) { var path_string = this.map[key]; if (path_string === undefined) { + if (silent_error) { return void 'nooo'; } throw new openerp.web.KeyNotFound(key); } @@ -272,6 +274,7 @@ openerp.web.Registry = openerp.web.Class.extend( /** @lends openerp.web.Registry object_match = object_match[path[i]]; if (object_match === undefined) { + if (silent_error) { return void 'noooooo'; } throw new openerp.web.ObjectNotFound(path_string); } } @@ -351,74 +354,33 @@ openerp.web.Connection = openerp.web.CallbackEnabled.extend( /** @lends openerp. * @param {String} [port] JSON-RPC endpoint port */ init: function() { - - // FIXME need to be a real singleton. - // create more than one Connection on the same instance will not works - this._super(); this.server = null; this.debug = ($.deparam($.param.querystring()).debug != undefined); - // TODO: session should have an optional name indicating that they'll - // be saved to (and revived from) cookies + // TODO: session store in cookie should be optional this.name = openerp._session_id; + this.qweb_mutex = new $.Mutex(); }, - - bind: function(server, continuation) { - - if(_.isFunction(server)) { - continuation = server; - server = null; - } - - if (this.server) { - throw new Error("Connection already bound to " + this.server); - } - var hostname = _.str.sprintf('%s//%s', location.protocol, location.host); - this.server = _.str.rtrim((!server) ? hostname : server, '/'); - this.rpc_mode = (this.server == hostname) ? "oe-json" : "oe-jsonp"; + bind: function(origin) { + var window_origin = location.protocol+"//"+location.host; + this.origin = origin ? _.str.rtrim(origin,'/') : window_origin; + this.prefix = this.origin; + this.server = this.origin; // keep chs happy + openerp.web.qweb.default_dict['_s'] = this.origin; + this.rpc_function = (this.origin == window_origin) ? this.rpc_json : this.rpc_jsonp; this.session_id = false; this.uid = false; this.username = false; this.user_context= {}; this.db = false; - this.module_loading = $.Deferred(); this.module_list = []; this.module_loaded = {"web": true}; this.context = {}; this.shortcuts = []; this.active_id = null; - this.do_load_qweb(['/web/webclient/qweb'], continuation); - }, - - connect: function(server, db, login, password, continuation) { - var self = this; - this.bind(server, function() { - self.login(db, login, password, continuation); - }); - }, - - get_absolute_url: function(path) { - var r_has_protocol = /^https?:\/\//, - r_absolute_internal = /^\/[^\/]/; // starts with / (but not //) - - - if (r_has_protocol.test(path)) { - if (!_.str.startsWith(path, this.server + '/')) { - $.error('can only contact openerp.server'); - } - absolute_url = path; - } else if (r_absolute_internal.test(path)) { - absolute_url = this.server + path; - } else { // relative url - // XXX is it correct to mix document.location and this.server ?? - var parts = document.location.pathname.split('/'); - parts.pop(); - parts.push(path); - absolute_url = this.server + parts.join('/'); - } - return absolute_url + this.ready = $.Deferred(); + return this.session_init(); }, - /** * Executes an RPC call, registering the provided callbacks. * @@ -434,80 +396,137 @@ openerp.web.Connection = openerp.web.CallbackEnabled.extend( /** @lends openerp. */ rpc: function(url, params, success_callback, error_callback) { var self = this; + // url can be an $.ajax option object + if (_.isString(url)) { + url = { url: url }; + } // Construct a JSON-RPC2 request, method is currently unused params.session_id = this.session_id; if (this.debug) params.debug = 1; - - // Call using the rpc_mode + var payload = { + jsonrpc: '2.0', + method: 'call', + params: params, + id: _.uniqueId('r') + }; var deferred = $.Deferred(); - this.rpc_ajax(url, params) - .then(function () {deferred.resolve.apply(deferred, arguments);}, - function(error) {deferred.reject(error, $.Event());}); - return deferred.fail(function() { + this.on_rpc_request(); + this.rpc_function(url, payload).then( + function (response, textStatus, jqXHR) { + self.on_rpc_response(); + if (!response.error) { + deferred.resolve(response["result"], textStatus, jqXHR); + } else if (response.error.data.type === "session_invalid") { + self.uid = false; + // TODO deprecate or use a deferred on login.do_ask_login() + self.on_session_invalid(function() { + self.rpc(url, payload.params, + function() { deferred.resolve.apply(deferred, arguments); }, + function() { deferred.reject.apply(deferred, arguments); }); + }); + } else { + deferred.reject(response.error, $.Event()); + } + }, + function(jqXHR, textStatus, errorThrown) { + self.on_rpc_response(); + var error = { + code: -32098, + message: "XmlHttpRequestError " + errorThrown, + data: {type: "xhr"+textStatus, debug: jqXHR.responseText, objects: [jqXHR, errorThrown] } + }; + deferred.reject(error, $.Event()); + }); + // Allow deferred user to disable on_rpc_error in fail + deferred.fail(function() { deferred.fail(function(error, event) { if (!event.isDefaultPrevented()) { self.on_rpc_error(error, event); } }); }).then(success_callback, error_callback).promise(); + return deferred; }, - /** * Raw JSON-RPC call * * @returns {jQuery.Deferred} ajax-webd deferred object */ - rpc_ajax: function(url, payload) { + rpc_json: function(url, payload) { var self = this; - this.on_rpc_request(); - // url can be an $.ajax option object - if (_.isString(url)) { - url = { - url: url - } - } var ajax = _.extend({ type: "POST", - url: url, - dataType: this.rpc_mode, + dataType: 'json', contentType: 'application/json', - data: payload, + data: JSON.stringify(payload), processData: false, - openerp: _.extend({}, this), // need a plainObject }, url); - var deferred = $.Deferred(); - $.ajax(ajax).done(function(response, textStatus, jqXHR) { - self.on_rpc_response(); - if (!response.error) { - deferred.resolve(response["result"], textStatus, jqXHR); - return; - } - if (response.error.data.type !== "session_invalid") { - deferred.reject(response.error); - return; - } - self.uid = false; - self.on_session_invalid(function() { - self.rpc(url, payload, - function() { - deferred.resolve.apply(deferred, arguments); - }, - function(error, event) { - event.preventDefault(); - deferred.reject.apply(deferred, arguments); - }); - }); - }).fail(function(jqXHR, textStatus, errorThrown) { - self.on_rpc_response(); - var error = { - code: -32098, - message: "XmlHttpRequestError " + errorThrown, - data: {type: "xhr"+textStatus, debug: jqXHR.responseText, objects: [jqXHR, errorThrown] } + if (this.synch) + ajax.async = false; + return $.ajax(ajax); + }, + rpc_jsonp: function(url, payload) { + var self = this; + // extracted from payload to set on the url + var data = { + session_id: this.session_id, + id: payload.id, + }; + url.url = this.get_url(url.url); + var ajax = _.extend({ + type: "GET", + dataType: 'jsonp', + jsonp: 'jsonp', + cache: false, + data: data + }, url); + if (this.synch) + ajax.async = false; + var payload_str = JSON.stringify(payload); + var payload_url = $.param({r:payload_str}); + if(payload_url.length < 2000) { + // Direct jsonp request + ajax.data.r = payload_str; + return $.ajax(ajax); + } else { + // Indirect jsonp request + var ifid = _.uniqueId('oe_rpc_iframe'); + var display = options.openerp.debug ? 'block' : 'none'; + var $iframe = $(_.str.sprintf("", ifid, ifid, display)); + var $form = $('
') + .attr('method', 'POST') + .attr('target', ifid) + .attr('enctype', "multipart/form-data") + .attr('action', ajax.url + '?' + $.param(data)) + .append($('').attr('value', payload_str)) + .hide() + .appendTo($('body')); + var cleanUp = function() { + if ($iframe) { + $iframe.unbind("load").attr("src", "javascript:false;").remove(); + } + $form.remove(); }; - deferred.reject(error); - }); - return deferred.promise(); + var deferred = $.Deferred(); + // the first bind is fired up when the iframe is added to the DOM + $iframe.bind('load', function() { + // the second bind is fired up when the result of the form submission is received + $iframe.unbind('load').bind('load', function() { + $.ajax(ajax).always(function() { + cleanUp(); + }).then( + function() { deferred.resolve.apply(deferred, arguments); }, + function() { deferred.reject.apply(deferred, arguments); } + ); + }); + // now that the iframe can receive data, we fill and submit the form + $form.submit(); + }); + // append the iframe to the DOM (will trigger the first load) + $form.after($iframe); + return deferred; + } }, on_rpc_request: function() { }, @@ -516,70 +535,64 @@ openerp.web.Connection = openerp.web.CallbackEnabled.extend( /** @lends openerp. on_rpc_error: function(error) { }, /** - * The session is validated either by login or by restoration of a previous session + * Init a session, reloads from cookie, if it exists */ - on_session_valid: function(continuation) { - this.load_modules(continuation); - }, - on_session_invalid: function(contination) { - }, - session_is_valid: function() { - return this.uid; - }, - session_login: function(db, login, password, success_callback) { + session_init: function () { var self = this; - var params = { db: db, login: login, password: password }; - return this.rpc("/web/session/login", params, function(result) { + // TODO: session store in cookie should be optional + this.session_id = this.get_cookie('session_id'); + return this.rpc("/web/session/get_session_info", {}).pipe(function(result) { + // If immediately follows a login (triggered by trying to restore + // an invalid session or no session at all), refresh session data + // (should not change, but just in case...) _.extend(self, { - session_id: result.session_id, - uid: result.uid, - user_context: result.context, db: result.db, - username: result.login + username: result.login, + uid: result.uid, + user_context: result.context }); - self.session_save(); - - self.on_session_valid(success_callback); - return true; - //}).done(success_callback); + var deferred = self.do_load_qweb(['/web/webclient/qweb']); + if(self.session_is_valid()) { + return deferred.pipe(_.bind(function() { this.load_modules(); }, self)); + } + return deferred; }); }, - login: function() { this.session_login.apply(this, arguments); }, + session_is_valid: function() { + return !!this.uid; + }, /** - * Reloads uid and session_id from local storage, if they exist + * The session is validated either by login or by restoration of a previous session */ - session_restore: function (continuation) { + session_authenticate: function(db, login, password, volatile) { var self = this; - this.session_id = this.get_cookie('session_id'); - return this.rpc("/web/session/get_session_info", {}).then(function(result) { + var base_location = document.location.protocol + '//' + document.location.host; + var params = { db: db, login: login, password: password, base_location: base_location }; + return this.rpc("/web/session/authenticate", params).pipe(function(result) { _.extend(self, { - uid: result.uid, - user_context: result.context, + session_id: result.session_id, db: result.db, - username: result.login + username: result.login, + uid: result.uid, + user_context: result.context }); - if (self.uid) - self.on_session_valid(continuation); - else - self.on_session_invalid(continuation); - }, function() { - self.on_session_invalid(continuation); + if (!volatile) { + self.set_cookie('session_id', self.session_id); + } + return self.load_modules(); }); }, - /** - * Saves the session id and uid locally - */ - session_save: function () { - this.set_cookie('session_id', this.session_id); - }, - logout: function() { + session_logout: function() { this.set_cookie('session_id', ''); - this.reload_client(); - }, - reload_client: function() { window.location.reload(); }, /** + * Called when a rpc call fail due to an invalid session. + * By default, it's a noop + */ + on_session_invalid: function(retry_callback) { + }, + /** * Fetches a cookie stored by an openerp session * * @private @@ -617,99 +630,76 @@ openerp.web.Connection = openerp.web.CallbackEnabled.extend( /** @lends openerp. }, /** * Load additional web addons of that instance and init them - * FIXME do all actions in sync... */ - load_modules: function(continuation) { + load_modules: function() { var self = this; - - this.module_loading.done(continuation); - //if (this.module_loading.state() !== "pending") { - if (this.module_loading.isResolved() || this.module_loading.isRejected()) { - return; + if(openerp._modules_loaded) { + return $.when(); } - - this.rpc('/web/session/modules', {}, function(result) { + return this.rpc('/web/session/modules', {}).pipe(function(result) { self.module_list = result; var lang = self.user_context.lang; var params = { mods: ["web"].concat(result), lang: lang}; - self.rpc('/web/webclient/translations',params).then(function(transs) { - openerp.web._t.database.set_bundle(transs); - var modules = self.module_list.join(','); - var file_list = ["/web/static/lib/datejs/globalization/" + - self.user_context.lang.replace("_", "-") + ".js" - ]; - - /* - self.rpc('/web/webclient/qweblist', {mods: modules}, self.do_load_qweb); - self.rpc('/web/webclient/csslist', {mods: modules}, self.do_load_css); - self.rpc('/web/webclient/jslist', {mods: modules}, function(files) { - self.do_load_js(file_list.concat(files)); - }); - openerp._modules_loaded = true; - // */ - - self.rpc('/web/webclient/qweblist', {mods: modules}, function(files) { - self.do_load_qweb(files, function() { - // once qweb files are loaded... - self.rpc('/web/webclient/csslist', {mods: modules}, function(files) { - self.do_load_css(files, function() { - // once css files are loaded - self.rpc('/web/webclient/jslist', {mods: modules}, function(files) { - self.do_load_js(file_list.concat(files), function() { - self.on_modules_loaded(); - self.module_loading.resolve(); - }); - }); - }); - }); + var modules = self.module_list.join(','); + return $.when( + self.rpc('/web/webclient/csslist', {mods: modules}, self.do_load_css), + self.rpc('/web/webclient/qweblist', {mods: modules}).pipe(self.do_load_qweb), + self.rpc('/web/webclient/translations', params).pipe(function(trans) { + openerp.web._t.database.set_bundle(trans); + var file_list = ["/web/static/lib/datejs/globalization/" + lang.replace("_", "-") + ".js"]; + return self.rpc('/web/webclient/jslist', {mods: modules}).pipe(function(files) { + return self.do_load_js(file_list.concat(files)); }); - }); - + }) + ).then(function() { + self.ready.resolve(); }); }); }, - do_load_css: function (files, callback) { + do_load_css: function (files) { var self = this; _.each(files, function (file) { $('head').append($('', { - 'href': self.get_absolute_url(file), + 'href': self.get_url(file), 'rel': 'stylesheet', 'type': 'text/css' })); }); - if (callback) { callback(); } }, - do_load_js: function(files, callback) { + do_load_js: function(files) { var self = this; + var d = $.Deferred(); if(files.length != 0) { var file = files.shift(); var tag = document.createElement('script'); tag.type = 'text/javascript'; - tag.src = self.get_absolute_url(file); + tag.src = self.get_url(file); tag.onload = tag.onreadystatechange = function() { if ( (tag.readyState && tag.readyState != "loaded" && tag.readyState != "complete") || tag.onload_done ) return; tag.onload_done = true; - self.do_load_js(files, callback); + self.do_load_js(files).then(function () { + d.resolve(); + }); }; var head = document.head || document.getElementsByTagName('head')[0]; head.appendChild(tag); } else { - if (callback) { callback(); } + self.on_modules_loaded(); + d.resolve(); } + return d; }, - do_load_qweb: function(files, callback) { + do_load_qweb: function(files) { var self = this; - - if (files.length != 0) { - var file = files.shift(); - self.rpc('/web/jsonp/static_proxy', {path: file}, function(xml) { - openerp.web.qweb.add_template(_.str.trim(xml)); - self.do_load_qweb(files, callback); + _.each(files, function(file) { + self.qweb_mutex.exec(function() { + return self.rpc('/web/proxy/load', {path: file}).pipe(function(xml) { + openerp.web.qweb.add_template(_.str.trim(xml)); + }); }); - } else { - if (callback) { callback(); } - } + }); + return self.qweb_mutex.def; }, on_modules_loaded: function() { for(var j=0; j