2013-08-06 12:26:23 +00:00
|
|
|
|
|
|
|
(function() {
|
|
|
|
|
2013-08-06 12:50:22 +00:00
|
|
|
if (typeof(console) === "undefined") {
|
|
|
|
// Even IE9 only exposes console object if debug window opened
|
|
|
|
window.console = {};
|
|
|
|
('log error debug info warn assert clear dir dirxml trace group'
|
|
|
|
+ ' groupCollapsed groupEnd time timeEnd profile profileEnd count'
|
|
|
|
+ ' exception').split(/\s+/).forEach(function(property) {
|
|
|
|
console[property] = _.identity;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
var instance = openerp;
|
|
|
|
openerp.web.core = {};
|
2013-08-06 12:26:23 +00:00
|
|
|
|
|
|
|
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.
|
|
|
|
* If that's not the case this method will simply return `false`.
|
|
|
|
*/
|
|
|
|
do_action: function() {
|
|
|
|
var parent = this.getParent();
|
|
|
|
if (parent) {
|
|
|
|
return parent.do_action.apply(parent, arguments);
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
},
|
|
|
|
do_notify: function() {
|
|
|
|
if (this.getParent()) {
|
|
|
|
return this.getParent().do_notify.apply(this,arguments);
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
},
|
|
|
|
do_warn: function() {
|
|
|
|
if (this.getParent()) {
|
|
|
|
return this.getParent().do_warn.apply(this,arguments);
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
},
|
|
|
|
rpc: function(url, data, options) {
|
|
|
|
return this.alive(openerp.session.rpc(url, data, options));
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
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 object and sets its parent if a parent is given.
|
|
|
|
*
|
|
|
|
* @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) {
|
|
|
|
openerp.web.PropertiesMixin.init.call(this);
|
|
|
|
this.setParent(parent);
|
|
|
|
this.session = openerp.session;
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
openerp.web.Widget.include(_.extend({}, ControllerMixin, {
|
|
|
|
init: function() {
|
|
|
|
this._super.apply(this, arguments);
|
|
|
|
this.session = openerp.session;
|
|
|
|
},
|
|
|
|
}));
|
|
|
|
|
|
|
|
instance.web.Registry = instance.web.Class.extend({
|
|
|
|
/**
|
|
|
|
* Stores a mapping of arbitrary key (strings) to object paths (as strings
|
|
|
|
* as well).
|
|
|
|
*
|
|
|
|
* Resolves those paths at query time in order to always fetch the correct
|
|
|
|
* object, even if those objects have been overloaded/replaced after the
|
|
|
|
* registry was created.
|
|
|
|
*
|
|
|
|
* An object path is simply a dotted name from the instance root to the
|
|
|
|
* object pointed to (e.g. ``"instance.web.Session"`` for an OpenERP
|
|
|
|
* session object).
|
|
|
|
*
|
|
|
|
* @constructs instance.web.Registry
|
|
|
|
* @param {Object} mapping a mapping of keys to object-paths
|
|
|
|
*/
|
|
|
|
init: function (mapping) {
|
|
|
|
this.parent = null;
|
|
|
|
this.map = mapping || {};
|
|
|
|
},
|
|
|
|
/**
|
|
|
|
* 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 or null if not found
|
|
|
|
*/
|
|
|
|
get_object: function (key, silent_error) {
|
|
|
|
var path_string = this.map[key];
|
|
|
|
if (path_string === undefined) {
|
|
|
|
if (this.parent) {
|
|
|
|
return this.parent.get_object(key, silent_error);
|
|
|
|
}
|
|
|
|
if (silent_error) { return void 'nooo'; }
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
var object_match = instance;
|
|
|
|
var path = path_string.split('.');
|
|
|
|
// ignore first section
|
|
|
|
for(var i=1; i<path.length; ++i) {
|
|
|
|
object_match = object_match[path[i]];
|
|
|
|
|
|
|
|
if (object_match === undefined) {
|
|
|
|
if (silent_error) { return void 'noooooo'; }
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return object_match;
|
|
|
|
},
|
|
|
|
/**
|
|
|
|
* Checks if the registry contains an object mapping for this key.
|
|
|
|
*
|
|
|
|
* @param {String} key key to look for
|
|
|
|
*/
|
|
|
|
contains: function (key) {
|
|
|
|
if (key === undefined) { return false; }
|
|
|
|
if (key in this.map) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
if (this.parent) {
|
|
|
|
return this.parent.contains(key);
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
},
|
|
|
|
/**
|
|
|
|
* Tries a number of keys, and returns the first object matching one of
|
|
|
|
* the keys.
|
|
|
|
*
|
|
|
|
* @param {Array} keys a sequence of keys to fetch the object for
|
|
|
|
* @returns {Class} the first class found matching an object
|
|
|
|
*/
|
|
|
|
get_any: function (keys) {
|
|
|
|
for (var i=0; i<keys.length; ++i) {
|
|
|
|
var key = keys[i];
|
|
|
|
if (!this.contains(key)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
return this.get_object(key);
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
},
|
|
|
|
/**
|
|
|
|
* Adds a new key and value to the registry.
|
|
|
|
*
|
|
|
|
* This method can be chained.
|
|
|
|
*
|
|
|
|
* @param {String} key
|
|
|
|
* @param {String} object_path fully qualified dotted object path
|
|
|
|
* @returns {instance.web.Registry} itself
|
|
|
|
*/
|
|
|
|
add: function (key, object_path) {
|
|
|
|
this.map[key] = object_path;
|
|
|
|
return this;
|
|
|
|
},
|
|
|
|
/**
|
|
|
|
* Creates and returns a copy of the current mapping, with the provided
|
|
|
|
* mapping argument added in (replacing existing keys if needed)
|
|
|
|
*
|
|
|
|
* Parent and child remain linked, a new key in the parent (which is not
|
|
|
|
* overwritten by the child) will appear in the child.
|
|
|
|
*
|
|
|
|
* @param {Object} [mapping={}] a mapping of keys to object-paths
|
|
|
|
*/
|
|
|
|
extend: function (mapping) {
|
|
|
|
var child = new instance.web.Registry(mapping);
|
|
|
|
child.parent = this;
|
|
|
|
return child;
|
|
|
|
},
|
|
|
|
/**
|
|
|
|
* @deprecated use Registry#extend
|
|
|
|
*/
|
|
|
|
clone: function (mapping) {
|
|
|
|
console.warn('Registry#clone is deprecated, use Registry#extend');
|
|
|
|
return this.extend(mapping);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
instance.web.py_eval = function(expr, context) {
|
|
|
|
return py.eval(expr, _.extend({}, context || {}, {"true": true, "false": false, "null": null}));
|
|
|
|
};
|
2011-08-12 10:19:52 +00:00
|
|
|
|
2013-08-05 11:12:50 +00:00
|
|
|
/*
|
|
|
|
Some retro-compatibility.
|
|
|
|
*/
|
|
|
|
instance.web.JsonRPC = instance.web.Session;
|
|
|
|
|
2012-04-30 15:02:44 +00:00
|
|
|
/** Session openerp specific RPC class */
|
2013-08-05 11:12:50 +00:00
|
|
|
instance.web.Session.include( /** @lends instance.web.Session# */{
|
2012-04-30 15:02:44 +00:00
|
|
|
init: function() {
|
|
|
|
this._super.apply(this, arguments);
|
2013-08-07 09:22:32 +00:00
|
|
|
this.debug = ($.deparam($.param.querystring()).debug !== undefined);
|
2012-04-30 15:02:44 +00:00
|
|
|
// TODO: session store in cookie should be optional
|
|
|
|
this.name = instance._session_id;
|
|
|
|
this.qweb_mutex = new $.Mutex();
|
|
|
|
},
|
|
|
|
/**
|
|
|
|
* Setup a sessionm
|
|
|
|
*/
|
|
|
|
session_bind: function(origin) {
|
|
|
|
var self = this;
|
|
|
|
this.setup(origin);
|
|
|
|
instance.web.qweb.default_dict['_s'] = this.origin;
|
2013-07-12 09:53:09 +00:00
|
|
|
this.uid = null;
|
|
|
|
this.username = null;
|
2012-04-30 15:02:44 +00:00
|
|
|
this.user_context= {};
|
2013-07-12 09:53:09 +00:00
|
|
|
this.db = null;
|
2012-04-30 15:02:44 +00:00
|
|
|
this.module_list = instance._modules.slice();
|
|
|
|
this.module_loaded = {};
|
|
|
|
_(this.module_list).each(function (mod) {
|
|
|
|
self.module_loaded[mod] = true;
|
|
|
|
});
|
|
|
|
this.active_id = null;
|
|
|
|
return this.session_init();
|
|
|
|
},
|
|
|
|
/**
|
|
|
|
* Init a session, reloads from cookie, if it exists
|
|
|
|
*/
|
|
|
|
session_init: function () {
|
|
|
|
var self = this;
|
2012-10-30 14:06:30 +00:00
|
|
|
return this.session_reload().then(function(result) {
|
2012-04-30 15:02:44 +00:00
|
|
|
var modules = instance._modules.join(',');
|
2012-11-10 19:34:40 +00:00
|
|
|
var deferred = self.rpc('/web/webclient/qweblist', {mods: modules}).then(self.load_qweb.bind(self));
|
2012-04-30 15:02:44 +00:00
|
|
|
if(self.session_is_valid()) {
|
2012-10-30 14:06:30 +00:00
|
|
|
return deferred.then(function() { return self.load_modules(); });
|
2012-04-30 15:02:44 +00:00
|
|
|
}
|
2012-09-18 07:28:02 +00:00
|
|
|
return $.when(
|
2012-12-26 18:18:33 +00:00
|
|
|
deferred,
|
2012-10-30 14:06:30 +00:00
|
|
|
self.rpc('/web/webclient/bootstrap_translations', {mods: instance._modules}).then(function(trans) {
|
2012-09-18 07:28:02 +00:00
|
|
|
instance.web._t.database.set_bundle(trans);
|
|
|
|
})
|
|
|
|
);
|
2012-04-30 15:02:44 +00:00
|
|
|
});
|
|
|
|
},
|
|
|
|
session_is_valid: function() {
|
2013-02-28 16:19:01 +00:00
|
|
|
var db = $.deparam.querystring().db;
|
|
|
|
if (db && this.db !== db) {
|
|
|
|
return false;
|
|
|
|
}
|
2012-04-30 15:02:44 +00:00
|
|
|
return !!this.uid;
|
|
|
|
},
|
|
|
|
/**
|
2014-01-13 17:38:08 +00:00
|
|
|
* The session is validated by restoration of a previous session
|
2012-04-30 15:02:44 +00:00
|
|
|
*/
|
2013-08-05 11:12:50 +00:00
|
|
|
session_authenticate: function() {
|
2012-04-30 15:02:44 +00:00
|
|
|
var self = this;
|
2013-08-05 11:12:50 +00:00
|
|
|
return $.when(this._super.apply(this, arguments)).then(function() {
|
2012-04-30 15:02:44 +00:00
|
|
|
return self.load_modules();
|
|
|
|
});
|
|
|
|
},
|
|
|
|
session_logout: function() {
|
2012-12-11 14:56:32 +00:00
|
|
|
$.bbq.removeState();
|
2012-04-30 15:02:44 +00:00
|
|
|
return this.rpc("/web/session/destroy", {});
|
|
|
|
},
|
|
|
|
get_cookie: function (name) {
|
|
|
|
if (!this.name) { return null; }
|
|
|
|
var nameEQ = this.name + '|' + name + '=';
|
|
|
|
var cookies = document.cookie.split(';');
|
|
|
|
for(var i=0; i<cookies.length; ++i) {
|
|
|
|
var cookie = cookies[i].replace(/^\s*/, '');
|
|
|
|
if(cookie.indexOf(nameEQ) === 0) {
|
2014-03-27 11:34:01 +00:00
|
|
|
try {
|
|
|
|
return JSON.parse(decodeURIComponent(cookie.substring(nameEQ.length)));
|
2014-04-03 08:48:08 +00:00
|
|
|
} catch(err) {
|
2014-03-27 12:08:26 +00:00
|
|
|
// wrong cookie, delete it
|
2014-03-27 11:34:01 +00:00
|
|
|
this.set_cookie(name, '', -1);
|
|
|
|
}
|
2012-04-30 15:02:44 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
},
|
|
|
|
/**
|
|
|
|
* Create a new cookie with the provided name and value
|
|
|
|
*
|
|
|
|
* @private
|
|
|
|
* @param name the cookie's name
|
|
|
|
* @param value the cookie's value
|
|
|
|
* @param ttl the cookie's time to live, 1 year by default, set to -1 to delete
|
|
|
|
*/
|
|
|
|
set_cookie: function (name, value, ttl) {
|
|
|
|
if (!this.name) { return; }
|
|
|
|
ttl = ttl || 24*60*60*365;
|
|
|
|
document.cookie = [
|
|
|
|
this.name + '|' + name + '=' + encodeURIComponent(JSON.stringify(value)),
|
|
|
|
'path=/',
|
|
|
|
'max-age=' + ttl,
|
|
|
|
'expires=' + new Date(new Date().getTime() + ttl*1000).toGMTString()
|
|
|
|
].join(';');
|
|
|
|
},
|
|
|
|
/**
|
|
|
|
* Load additional web addons of that instance and init them
|
|
|
|
*
|
|
|
|
*/
|
2012-09-03 11:30:59 +00:00
|
|
|
load_modules: function() {
|
2012-04-30 15:02:44 +00:00
|
|
|
var self = this;
|
2012-10-30 14:06:30 +00:00
|
|
|
return this.rpc('/web/session/modules', {}).then(function(result) {
|
2012-11-10 21:13:43 +00:00
|
|
|
var all_modules = _.uniq(self.module_list.concat(result));
|
2012-04-30 15:02:44 +00:00
|
|
|
var to_load = _.difference(result, self.module_list).join(',');
|
|
|
|
self.module_list = all_modules;
|
|
|
|
|
2012-11-10 21:13:43 +00:00
|
|
|
var loaded = self.load_translations();
|
|
|
|
var datejs_locale = "/web/static/lib/datejs/globalization/" + self.user_context.lang.replace("_", "-") + ".js";
|
|
|
|
|
|
|
|
var file_list = [ datejs_locale ];
|
2012-09-14 14:28:48 +00:00
|
|
|
if(to_load.length) {
|
2012-04-30 15:02:44 +00:00
|
|
|
loaded = $.when(
|
2012-09-14 14:28:48 +00:00
|
|
|
loaded,
|
2012-11-10 19:34:40 +00:00
|
|
|
self.rpc('/web/webclient/csslist', {mods: to_load}).done(self.load_css.bind(self)),
|
|
|
|
self.rpc('/web/webclient/qweblist', {mods: to_load}).then(self.load_qweb.bind(self)),
|
2012-10-30 14:06:30 +00:00
|
|
|
self.rpc('/web/webclient/jslist', {mods: to_load}).done(function(files) {
|
2012-09-14 14:28:48 +00:00
|
|
|
file_list = file_list.concat(files);
|
|
|
|
})
|
|
|
|
);
|
2012-04-30 15:02:44 +00:00
|
|
|
}
|
2012-10-30 14:06:30 +00:00
|
|
|
return loaded.then(function () {
|
2012-11-10 19:34:40 +00:00
|
|
|
return self.load_js(file_list);
|
2012-10-30 14:06:30 +00:00
|
|
|
}).done(function() {
|
2012-04-30 15:02:44 +00:00
|
|
|
self.on_modules_loaded();
|
2012-06-21 22:49:28 +00:00
|
|
|
self.trigger('module_loaded');
|
2012-09-14 14:28:48 +00:00
|
|
|
if (!Date.CultureInfo.pmDesignator) {
|
|
|
|
// If no am/pm designator is specified but the openerp
|
|
|
|
// datetime format uses %i, date.js won't be able to
|
|
|
|
// correctly format a date. See bug#938497.
|
|
|
|
Date.CultureInfo.amDesignator = 'AM';
|
|
|
|
Date.CultureInfo.pmDesignator = 'PM';
|
|
|
|
}
|
2012-04-30 15:02:44 +00:00
|
|
|
});
|
|
|
|
});
|
|
|
|
},
|
2012-11-10 21:13:43 +00:00
|
|
|
load_translations: function() {
|
2013-08-05 15:10:12 +00:00
|
|
|
return instance.web._t.database.load_translations(this, this.module_list, this.user_context.lang);
|
2012-11-10 21:13:43 +00:00
|
|
|
},
|
2012-11-10 19:34:40 +00:00
|
|
|
load_css: function (files) {
|
2012-04-30 15:02:44 +00:00
|
|
|
var self = this;
|
|
|
|
_.each(files, function (file) {
|
|
|
|
$('head').append($('<link>', {
|
2012-11-14 17:41:50 +00:00
|
|
|
'href': self.url(file, null),
|
2012-04-30 15:02:44 +00:00
|
|
|
'rel': 'stylesheet',
|
|
|
|
'type': 'text/css'
|
|
|
|
}));
|
|
|
|
});
|
|
|
|
},
|
2012-11-10 19:34:40 +00:00
|
|
|
load_js: function(files) {
|
2012-04-30 15:02:44 +00:00
|
|
|
var self = this;
|
|
|
|
var d = $.Deferred();
|
2012-11-14 17:41:50 +00:00
|
|
|
if(files.length !== 0) {
|
2012-04-30 15:02:44 +00:00
|
|
|
var file = files.shift();
|
|
|
|
var tag = document.createElement('script');
|
|
|
|
tag.type = 'text/javascript';
|
2012-11-14 17:41:50 +00:00
|
|
|
tag.src = self.url(file, null);
|
2012-04-30 15:02:44 +00:00
|
|
|
tag.onload = tag.onreadystatechange = function() {
|
|
|
|
if ( (tag.readyState && tag.readyState != "loaded" && tag.readyState != "complete") || tag.onload_done )
|
|
|
|
return;
|
|
|
|
tag.onload_done = true;
|
2012-11-10 19:34:40 +00:00
|
|
|
self.load_js(files).done(function () {
|
2012-04-30 15:02:44 +00:00
|
|
|
d.resolve();
|
|
|
|
});
|
|
|
|
};
|
|
|
|
var head = document.head || document.getElementsByTagName('head')[0];
|
|
|
|
head.appendChild(tag);
|
|
|
|
} else {
|
|
|
|
d.resolve();
|
|
|
|
}
|
|
|
|
return d;
|
|
|
|
},
|
2012-11-10 19:34:40 +00:00
|
|
|
load_qweb: function(files) {
|
2012-04-30 15:02:44 +00:00
|
|
|
var self = this;
|
|
|
|
_.each(files, function(file) {
|
|
|
|
self.qweb_mutex.exec(function() {
|
2012-10-30 14:06:30 +00:00
|
|
|
return self.rpc('/web/proxy/load', {path: file}).then(function(xml) {
|
2012-04-30 15:02:44 +00:00
|
|
|
if (!xml) { return; }
|
|
|
|
instance.web.qweb.add_template(_.str.trim(xml));
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
return self.qweb_mutex.def;
|
|
|
|
},
|
|
|
|
on_modules_loaded: function() {
|
|
|
|
for(var j=0; j<this.module_list.length; j++) {
|
|
|
|
var mod = this.module_list[j];
|
|
|
|
if(this.module_loaded[mod])
|
|
|
|
continue;
|
|
|
|
instance[mod] = {};
|
|
|
|
// init module mod
|
2013-07-26 12:15:14 +00:00
|
|
|
var fct = instance._openerp[mod];
|
|
|
|
if(typeof(fct) === "function") {
|
|
|
|
instance._openerp[mod] = {};
|
2013-07-29 09:25:12 +00:00
|
|
|
for (var k in fct) {
|
2013-07-26 12:15:14 +00:00
|
|
|
instance._openerp[mod][k] = fct[k];
|
|
|
|
}
|
|
|
|
fct(instance, instance._openerp[mod]);
|
2012-04-30 15:02:44 +00:00
|
|
|
}
|
2013-07-26 12:15:14 +00:00
|
|
|
this.module_loaded[mod] = true;
|
2012-04-30 15:02:44 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
/**
|
|
|
|
* Cooperative file download implementation, for ajaxy APIs.
|
|
|
|
*
|
|
|
|
* Requires that the server side implements an httprequest correctly
|
|
|
|
* setting the `fileToken` cookie to the value provided as the `token`
|
|
|
|
* parameter. The cookie *must* be set on the `/` path and *must not* be
|
|
|
|
* `httpOnly`.
|
|
|
|
*
|
|
|
|
* It would probably also be a good idea for the response to use a
|
|
|
|
* `Content-Disposition: attachment` header, especially if the MIME is a
|
|
|
|
* "known" type (e.g. text/plain, or for some browsers application/json
|
|
|
|
*
|
|
|
|
* @param {Object} options
|
|
|
|
* @param {String} [options.url] used to dynamically create a form
|
|
|
|
* @param {Object} [options.data] data to add to the form submission. If can be used without a form, in which case a form is created from scratch. Otherwise, added to form data
|
|
|
|
* @param {HTMLFormElement} [options.form] the form to submit in order to fetch the file
|
|
|
|
* @param {Function} [options.success] callback in case of download success
|
|
|
|
* @param {Function} [options.error] callback in case of request error, provided with the error body
|
|
|
|
* @param {Function} [options.complete] called after both ``success`` and ``error` callbacks have executed
|
|
|
|
*/
|
|
|
|
get_file: function (options) {
|
|
|
|
// need to detect when the file is done downloading (not used
|
|
|
|
// yet, but we'll need it to fix the UI e.g. with a throbber
|
|
|
|
// while dump is being generated), iframe load event only fires
|
|
|
|
// when the iframe content loads, so we need to go smarter:
|
|
|
|
// http://geekswithblogs.net/GruffCode/archive/2010/10/28/detecting-the-file-download-dialog-in-the-browser.aspx
|
|
|
|
var timer, token = new Date().getTime(),
|
|
|
|
cookie_name = 'fileToken', cookie_length = cookie_name.length,
|
|
|
|
CHECK_INTERVAL = 1000, id = _.uniqueId('get_file_frame'),
|
|
|
|
remove_form = false;
|
|
|
|
|
2013-06-21 12:44:49 +00:00
|
|
|
|
|
|
|
// iOS devices doesn't allow iframe use the way we do it,
|
|
|
|
// opening a new window seems the best way to workaround
|
2013-07-02 09:39:32 +00:00
|
|
|
if (navigator.userAgent.match(/(iPod|iPhone|iPad)/)) {
|
2013-06-21 12:44:49 +00:00
|
|
|
var params = _.extend({}, options.data || {}, {token: token});
|
|
|
|
var url = this.url(options.url, params);
|
|
|
|
instance.web.unblockUI();
|
|
|
|
return window.open(url);
|
|
|
|
}
|
|
|
|
|
2012-04-30 15:02:44 +00:00
|
|
|
var $form, $form_data = $('<div>');
|
|
|
|
|
|
|
|
var complete = function () {
|
|
|
|
if (options.complete) { options.complete(); }
|
|
|
|
clearTimeout(timer);
|
|
|
|
$form_data.remove();
|
|
|
|
$target.remove();
|
|
|
|
if (remove_form && $form) { $form.remove(); }
|
|
|
|
};
|
|
|
|
var $target = $('<iframe style="display: none;">')
|
|
|
|
.attr({id: id, name: id})
|
|
|
|
.appendTo(document.body)
|
|
|
|
.load(function () {
|
|
|
|
try {
|
2014-04-14 10:53:33 +00:00
|
|
|
if (options.error) {
|
|
|
|
var body = this.contentDocument.body;
|
|
|
|
var node = body.childNodes[1] || body.childNodes[0];
|
|
|
|
options.error(JSON.parse(node.textContent));
|
|
|
|
}
|
2012-04-30 15:02:44 +00:00
|
|
|
} finally {
|
|
|
|
complete();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
if (options.form) {
|
|
|
|
$form = $(options.form);
|
|
|
|
} else {
|
|
|
|
remove_form = true;
|
|
|
|
$form = $('<form>', {
|
|
|
|
action: options.url,
|
|
|
|
method: 'POST'
|
|
|
|
}).appendTo(document.body);
|
|
|
|
}
|
|
|
|
|
2013-07-15 12:16:11 +00:00
|
|
|
var hparams = _.extend({}, options.data || {}, {token: token});
|
|
|
|
if (this.override_session)
|
|
|
|
hparams.session_id = this.session_id;
|
|
|
|
_.each(hparams, function (value, key) {
|
2012-04-30 15:02:44 +00:00
|
|
|
var $input = $form.find('[name=' + key +']');
|
|
|
|
if (!$input.length) {
|
|
|
|
$input = $('<input type="hidden" name="' + key + '">')
|
|
|
|
.appendTo($form_data);
|
|
|
|
}
|
2013-07-25 10:33:01 +00:00
|
|
|
$input.val(value);
|
2012-04-30 15:02:44 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
$form
|
|
|
|
.append($form_data)
|
|
|
|
.attr('target', id)
|
|
|
|
.get(0).submit();
|
|
|
|
|
|
|
|
var waitLoop = function () {
|
|
|
|
var cookies = document.cookie.split(';');
|
|
|
|
// setup next check
|
|
|
|
timer = setTimeout(waitLoop, CHECK_INTERVAL);
|
|
|
|
for (var i=0; i<cookies.length; ++i) {
|
|
|
|
var cookie = cookies[i].replace(/^\s*/, '');
|
|
|
|
if (!cookie.indexOf(cookie_name === 0)) { continue; }
|
|
|
|
var cookie_val = cookie.substring(cookie_length + 1);
|
|
|
|
if (parseInt(cookie_val, 10) !== token) { continue; }
|
|
|
|
|
|
|
|
// clear cookie
|
|
|
|
document.cookie = _.str.sprintf("%s=;expires=%s;path=/",
|
|
|
|
cookie_name, new Date().toGMTString());
|
|
|
|
if (options.success) { options.success(); }
|
|
|
|
complete();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
timer = setTimeout(waitLoop, CHECK_INTERVAL);
|
|
|
|
},
|
|
|
|
synchronized_mode: function(to_execute) {
|
2012-08-02 14:46:23 +00:00
|
|
|
var synch = this.synch;
|
|
|
|
this.synch = true;
|
|
|
|
try {
|
|
|
|
return to_execute();
|
|
|
|
} finally {
|
|
|
|
this.synch = synch;
|
|
|
|
}
|
2012-04-30 15:02:44 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2012-07-13 16:36:21 +00:00
|
|
|
|
2012-06-14 12:17:19 +00:00
|
|
|
/**
|
|
|
|
* Event Bus used to bind events scoped in the current instance
|
|
|
|
*/
|
2012-07-13 16:36:21 +00:00
|
|
|
instance.web.Bus = instance.web.Class.extend(instance.web.EventDispatcherMixin, {
|
2012-06-14 12:17:19 +00:00
|
|
|
init: function() {
|
|
|
|
instance.web.EventDispatcherMixin.init.call(this, parent);
|
|
|
|
var self = this;
|
|
|
|
// TODO fme: allow user to bind keys for some global actions.
|
|
|
|
// check gtk bindings
|
|
|
|
// http://unixpapa.com/js/key.html
|
|
|
|
_.each('click,dblclick,keydown,keypress,keyup'.split(','), function(evtype) {
|
2012-08-01 13:40:18 +00:00
|
|
|
$('html').on(evtype, function(ev) {
|
2012-06-14 13:48:19 +00:00
|
|
|
self.trigger(evtype, ev);
|
2012-06-14 12:17:19 +00:00
|
|
|
});
|
|
|
|
});
|
|
|
|
_.each('resize,scroll'.split(','), function(evtype) {
|
2012-08-01 13:40:18 +00:00
|
|
|
$(window).on(evtype, function(ev) {
|
2012-06-14 13:48:19 +00:00
|
|
|
self.trigger(evtype, ev);
|
2012-06-14 12:17:19 +00:00
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
2013-07-25 10:33:01 +00:00
|
|
|
});
|
2012-07-13 16:36:21 +00:00
|
|
|
instance.web.bus = new instance.web.Bus();
|
2012-06-14 12:17:19 +00:00
|
|
|
|
2013-08-05 14:55:14 +00:00
|
|
|
instance.web.TranslationDataBase.include({
|
2012-04-30 15:02:44 +00:00
|
|
|
set_bundle: function(translation_bundle) {
|
2013-08-05 14:55:14 +00:00
|
|
|
this._super(translation_bundle);
|
2012-04-30 15:02:44 +00:00
|
|
|
if (translation_bundle.lang_parameters) {
|
2013-08-05 14:55:14 +00:00
|
|
|
this.parameters.grouping = py.eval(this.parameters.grouping);
|
2012-04-30 15:02:44 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
2012-04-01 21:55:07 +00:00
|
|
|
/** Custom jQuery plugins */
|
2013-12-02 14:22:40 +00:00
|
|
|
$.browser = $.browser || {};
|
2013-11-18 18:06:42 +00:00
|
|
|
if(navigator.appVersion.indexOf("MSIE") !== -1) {
|
|
|
|
$.browser.msie = 1;
|
|
|
|
}
|
2012-04-01 21:55:07 +00:00
|
|
|
$.fn.getAttributes = function() {
|
|
|
|
var o = {};
|
|
|
|
if (this.length) {
|
|
|
|
for (var attr, i = 0, attrs = this[0].attributes, l = attrs.length; i < l; i++) {
|
2013-07-25 10:33:01 +00:00
|
|
|
attr = attrs.item(i);
|
2012-04-01 21:55:07 +00:00
|
|
|
o[attr.nodeName] = attr.nodeValue;
|
2012-03-13 16:37:19 +00:00
|
|
|
}
|
|
|
|
}
|
2012-04-01 21:55:07 +00:00
|
|
|
return o;
|
2013-07-25 10:33:01 +00:00
|
|
|
};
|
2012-11-15 16:41:32 +00:00
|
|
|
$.fn.openerpClass = function(additionalClass) {
|
|
|
|
// This plugin should be applied on top level elements
|
|
|
|
additionalClass = additionalClass || '';
|
|
|
|
if (!!$.browser.msie) {
|
|
|
|
additionalClass += ' openerp_ie';
|
|
|
|
}
|
|
|
|
return this.each(function() {
|
|
|
|
$(this).addClass('openerp ' + additionalClass);
|
|
|
|
});
|
|
|
|
};
|
2012-12-19 15:12:17 +00:00
|
|
|
$.fn.openerpBounce = function() {
|
|
|
|
return this.each(function() {
|
|
|
|
$(this).css('box-sizing', 'content-box').effect('bounce', {distance: 18, times: 5}, 250);
|
|
|
|
});
|
|
|
|
};
|
2012-03-13 16:37:19 +00:00
|
|
|
|
2011-12-15 12:07:32 +00:00
|
|
|
/** Jquery extentions */
|
2013-08-06 13:17:46 +00:00
|
|
|
$.Mutex = openerp.Mutex;
|
2011-12-12 15:56:35 +00:00
|
|
|
|
|
|
|
$.async_when = function() {
|
|
|
|
var async = false;
|
|
|
|
var def = $.Deferred();
|
2012-10-30 14:06:30 +00:00
|
|
|
$.when.apply($, arguments).done(function() {
|
2011-12-12 15:56:35 +00:00
|
|
|
var args = arguments;
|
|
|
|
var action = function() {
|
|
|
|
def.resolve.apply(def, args);
|
|
|
|
};
|
|
|
|
if (async)
|
|
|
|
action();
|
|
|
|
else
|
|
|
|
setTimeout(action, 0);
|
2012-10-30 14:06:30 +00:00
|
|
|
}).fail(function() {
|
2011-12-12 15:56:35 +00:00
|
|
|
var args = arguments;
|
|
|
|
var action = function() {
|
|
|
|
def.reject.apply(def, args);
|
|
|
|
};
|
|
|
|
if (async)
|
|
|
|
action();
|
|
|
|
else
|
|
|
|
setTimeout(action, 0);
|
|
|
|
});
|
|
|
|
async = true;
|
|
|
|
return def;
|
|
|
|
};
|
|
|
|
|
2011-12-19 16:14:41 +00:00
|
|
|
// special tweak for the web client
|
|
|
|
var old_async_when = $.async_when;
|
|
|
|
$.async_when = function() {
|
2012-08-14 15:29:00 +00:00
|
|
|
if (instance.session.synch)
|
2012-08-02 14:46:23 +00:00
|
|
|
return $.when.apply(this, arguments);
|
|
|
|
else
|
|
|
|
return old_async_when.apply(this, arguments);
|
2011-12-19 16:14:41 +00:00
|
|
|
};
|
|
|
|
|
2012-06-21 22:49:28 +00:00
|
|
|
/** Setup default session */
|
2012-08-14 15:29:00 +00:00
|
|
|
instance.session = new instance.web.Session();
|
2012-06-21 22:49:28 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Lazy translation function, only performs the translation when actually
|
|
|
|
* printed (e.g. inserted into a template)
|
|
|
|
*
|
|
|
|
* Useful when defining translatable strings in code evaluated before the
|
|
|
|
* translation database is loaded, as class attributes or at the top-level of
|
|
|
|
* an OpenERP Web module
|
|
|
|
*
|
|
|
|
* @param {String} s string to translate
|
|
|
|
* @returns {Object} lazy translation object
|
|
|
|
*/
|
|
|
|
instance.web._lt = function (s) {
|
2013-07-25 10:33:01 +00:00
|
|
|
return {toString: function () { return instance.web._t(s); }};
|
2012-06-21 22:49:28 +00:00
|
|
|
};
|
2012-08-14 15:29:00 +00:00
|
|
|
instance.web.qweb.debug = instance.session.debug;
|
2013-07-26 12:44:46 +00:00
|
|
|
_.extend(instance.web.qweb.default_dict, {
|
2012-11-21 14:43:20 +00:00
|
|
|
'__debug__': instance.session.debug,
|
2013-07-26 12:44:46 +00:00
|
|
|
});
|
2012-06-21 22:49:28 +00:00
|
|
|
instance.web.qweb.preprocess_node = function() {
|
|
|
|
// Note that 'this' is the Qweb Node
|
|
|
|
switch (this.node.nodeType) {
|
2013-05-16 11:03:35 +00:00
|
|
|
case Node.TEXT_NODE:
|
|
|
|
case Node.CDATA_SECTION_NODE:
|
2012-06-21 22:49:28 +00:00
|
|
|
// Text and CDATAs
|
|
|
|
var translation = this.node.parentNode.attributes['t-translation'];
|
|
|
|
if (translation && translation.value === 'off') {
|
|
|
|
return;
|
|
|
|
}
|
2013-04-11 16:20:35 +00:00
|
|
|
var match = /^(\s*)([\s\S]+?)(\s*)$/.exec(this.node.data);
|
2012-10-03 16:35:04 +00:00
|
|
|
if (match) {
|
|
|
|
this.node.data = match[1] + instance.web._t(match[2]) + match[3];
|
2012-06-21 22:49:28 +00:00
|
|
|
}
|
|
|
|
break;
|
2013-05-16 11:03:35 +00:00
|
|
|
case Node.ELEMENT_NODE:
|
2012-06-21 22:49:28 +00:00
|
|
|
// Element
|
2012-06-22 09:06:14 +00:00
|
|
|
var attr, attrs = ['label', 'title', 'alt', 'placeholder'];
|
2013-07-25 10:07:49 +00:00
|
|
|
while ((attr = attrs.pop())) {
|
2012-06-21 22:49:28 +00:00
|
|
|
if (this.attributes[attr]) {
|
|
|
|
this.attributes[attr] = instance.web._t(this.attributes[attr]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/** Setup jQuery timeago */
|
2012-07-02 07:14:28 +00:00
|
|
|
var _t = instance.web._t;
|
|
|
|
/*
|
|
|
|
* Strings in timeago are "composed" with prefixes, words and suffixes. This
|
|
|
|
* makes their detection by our translating system impossible. Use all literal
|
|
|
|
* strings we're using with a translation mark here so the extractor can do its
|
|
|
|
* job.
|
|
|
|
*/
|
|
|
|
{
|
|
|
|
_t('less than a minute ago');
|
|
|
|
_t('about a minute ago');
|
|
|
|
_t('%d minutes ago');
|
|
|
|
_t('about an hour ago');
|
|
|
|
_t('%d hours ago');
|
|
|
|
_t('a day ago');
|
|
|
|
_t('%d days ago');
|
|
|
|
_t('about a month ago');
|
|
|
|
_t('%d months ago');
|
|
|
|
_t('about a year ago');
|
|
|
|
_t('%d years ago');
|
2012-06-21 22:49:28 +00:00
|
|
|
}
|
2012-07-02 07:14:28 +00:00
|
|
|
|
2012-08-14 15:29:00 +00:00
|
|
|
instance.session.on('module_loaded', this, function () {
|
2012-06-28 13:33:53 +00:00
|
|
|
// provide timeago.js with our own translator method
|
2012-06-25 15:35:45 +00:00
|
|
|
$.timeago.settings.translator = instance.web._t;
|
2012-07-02 07:14:28 +00:00
|
|
|
});
|
2012-06-21 22:49:28 +00:00
|
|
|
|
2012-08-24 16:54:49 +00:00
|
|
|
/** Setup blockui */
|
|
|
|
if ($.blockUI) {
|
|
|
|
$.blockUI.defaults.baseZ = 1100;
|
2012-09-06 16:10:16 +00:00
|
|
|
$.blockUI.defaults.message = '<div class="openerp oe_blockui_spin_container" style="background-color: transparent;">';
|
2012-08-24 16:54:49 +00:00
|
|
|
$.blockUI.defaults.css.border = '0';
|
|
|
|
$.blockUI.defaults.css["background-color"] = '';
|
|
|
|
}
|
|
|
|
|
|
|
|
var messages_by_seconds = function() {
|
|
|
|
return [
|
|
|
|
[0, _t("Loading...")],
|
2012-09-05 21:19:20 +00:00
|
|
|
[20, _t("Still loading...")],
|
2012-08-24 16:54:49 +00:00
|
|
|
[60, _t("Still loading...<br />Please be patient.")],
|
|
|
|
[120, _t("Don't leave yet,<br />it's still loading...")],
|
|
|
|
[300, _t("You may not believe it,<br />but the application is actually loading...")],
|
2012-09-06 14:41:40 +00:00
|
|
|
[420, _t("Take a minute to get a coffee,<br />because it's loading...")],
|
2012-11-30 13:30:21 +00:00
|
|
|
[3600, _t("Maybe you should consider reloading the application by pressing F5...")]
|
2012-08-24 16:54:49 +00:00
|
|
|
];
|
|
|
|
};
|
|
|
|
|
|
|
|
instance.web.Throbber = instance.web.Widget.extend({
|
|
|
|
template: "Throbber",
|
|
|
|
start: function() {
|
|
|
|
var opts = {
|
|
|
|
lines: 13, // The number of lines to draw
|
|
|
|
length: 7, // The length of each line
|
|
|
|
width: 4, // The line thickness
|
|
|
|
radius: 10, // The radius of the inner circle
|
|
|
|
rotate: 0, // The rotation offset
|
|
|
|
color: '#FFF', // #rgb or #rrggbb
|
|
|
|
speed: 1, // Rounds per second
|
|
|
|
trail: 60, // Afterglow percentage
|
|
|
|
shadow: false, // Whether to render a shadow
|
|
|
|
hwaccel: false, // Whether to use hardware acceleration
|
|
|
|
className: 'spinner', // The CSS class to assign to the spinner
|
|
|
|
zIndex: 2e9, // The z-index (defaults to 2000000000)
|
|
|
|
top: 'auto', // Top position relative to parent in px
|
|
|
|
left: 'auto' // Left position relative to parent in px
|
|
|
|
};
|
2012-08-24 18:27:07 +00:00
|
|
|
this.spin = new Spinner(opts).spin(this.$el[0]);
|
2012-08-24 16:54:49 +00:00
|
|
|
this.start_time = new Date().getTime();
|
|
|
|
this.act_message();
|
|
|
|
},
|
|
|
|
act_message: function() {
|
|
|
|
var self = this;
|
|
|
|
setTimeout(function() {
|
|
|
|
if (self.isDestroyed())
|
|
|
|
return;
|
|
|
|
var seconds = (new Date().getTime() - self.start_time) / 1000;
|
|
|
|
var mes;
|
|
|
|
_.each(messages_by_seconds(), function(el) {
|
|
|
|
if (seconds >= el[0])
|
|
|
|
mes = el[1];
|
|
|
|
});
|
|
|
|
self.$(".oe_throbber_message").html(mes);
|
|
|
|
self.act_message();
|
|
|
|
}, 1000);
|
|
|
|
},
|
|
|
|
destroy: function() {
|
|
|
|
if (this.spin)
|
|
|
|
this.spin.stop();
|
|
|
|
this._super();
|
|
|
|
},
|
|
|
|
});
|
|
|
|
instance.web.Throbber.throbbers = [];
|
|
|
|
|
|
|
|
instance.web.blockUI = function() {
|
|
|
|
var tmp = $.blockUI.apply($, arguments);
|
|
|
|
var throbber = new instance.web.Throbber();
|
|
|
|
instance.web.Throbber.throbbers.push(throbber);
|
|
|
|
throbber.appendTo($(".oe_blockui_spin_container"));
|
|
|
|
return tmp;
|
2013-07-25 10:33:01 +00:00
|
|
|
};
|
2012-08-24 16:54:49 +00:00
|
|
|
instance.web.unblockUI = function() {
|
|
|
|
_.each(instance.web.Throbber.throbbers, function(el) {
|
|
|
|
el.destroy();
|
|
|
|
});
|
|
|
|
return $.unblockUI.apply($, arguments);
|
2013-07-25 10:33:01 +00:00
|
|
|
};
|
2012-08-24 16:54:49 +00:00
|
|
|
|
2014-04-17 08:59:54 +00:00
|
|
|
|
|
|
|
/* Bootstrap defaults overwrite */
|
2014-04-18 15:25:11 +00:00
|
|
|
$.fn.tooltip.Constructor.DEFAULTS.placement = 'auto top';
|
2014-04-17 08:59:54 +00:00
|
|
|
$.fn.tooltip.Constructor.DEFAULTS.html = true;
|
2014-04-18 10:12:35 +00:00
|
|
|
$.fn.tooltip.Constructor.DEFAULTS.container = 'body';
|
2014-04-18 15:25:11 +00:00
|
|
|
//overwrite bootstrap tooltip method to fix bug when using placement
|
|
|
|
//auto and the parent element does not exist anymore resulting in
|
|
|
|
//an error. This should be remove once bootstrap fix the bug
|
|
|
|
var bootstrap_show_function = $.fn.tooltip.Constructor.prototype.show;
|
|
|
|
$.fn.tooltip.Constructor.prototype.show = function (e) {
|
|
|
|
if (this.$element.parent().length === 0){
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
return bootstrap_show_function.call(this, e);
|
|
|
|
};
|
2014-04-17 08:59:54 +00:00
|
|
|
|
2012-05-15 15:34:23 +00:00
|
|
|
/**
|
|
|
|
* Registry for all the client actions key: tag value: widget
|
|
|
|
*/
|
|
|
|
instance.web.client_actions = new instance.web.Registry();
|
|
|
|
|
2013-08-06 12:50:22 +00:00
|
|
|
})();
|
2011-08-11 03:10:00 +00:00
|
|
|
|
2011-07-15 14:16:00 +00:00
|
|
|
// vim:et fdc=0 fdl=0 foldnestmax=3 fdm=syntax:
|