diff --git a/addons/web/static/src/js/corelib.js b/addons/web/static/src/js/corelib.js
index 3070e1ed390..821d2a62d7e 100644
--- a/addons/web/static/src/js/corelib.js
+++ b/addons/web/static/src/js/corelib.js
@@ -172,6 +172,8 @@ openerp.web.corelib = function(openerp) {
};
})();
+// Mixins
+
/**
* Mixin to structure objects' life-cycles folowing a parent-children
* relationship. Each object can a have a parent and multiple children.
@@ -237,6 +239,8 @@ openerp.web.ParentedMixin = {
};
/**
+ * TODO al: move into the the mixin
+ *
* Backbone's events
*
* (c) 2010-2012 Jeremy Ashkenas, DocumentCloud Inc.
@@ -396,10 +400,10 @@ openerp.web.GetterSetterMixin = _.extend({}, openerp.web.EventDispatcherMixin, {
openerp.web.CallbackEnabledMixin = {
init: function() {
var self = this;
- var callback_maker = function(obj, method) {
+ var callback_maker = function(obj, name, method) {
var callback = function() {
var args = Array.prototype.slice.call(arguments);
- //self.trigger.apply(self, [name].concat(args));
+ self.trigger.apply(self, [name].concat(args));
var r;
for(var i = 0; i < callback.callback_chain.length; i++) {
var c = callback.callback_chain[i];
@@ -452,7 +456,7 @@ openerp.web.CallbackEnabledMixin = {
return callback.add({
callback: method,
self:obj,
- args:Array.prototype.slice.call(arguments, 2)
+ args:Array.prototype.slice.call(arguments, 3)
});
};
// Transform on_/do_* methods into callbacks
@@ -460,7 +464,7 @@ openerp.web.CallbackEnabledMixin = {
if(typeof(this[name]) == "function") {
this[name].debug_name = name;
if((/^on_|^do_/).test(name)) {
- this[name] = callback_maker(this, this[name]);
+ this[name] = callback_maker(this, name, this[name]);
}
}
}
@@ -731,6 +735,1034 @@ openerp.web.Registry = openerp.web.Class.extend({
}
});
+openerp.web.Connection = openerp.web.CallbackEnabled.extend( /** @lends openerp.web.Connection# */{
+ /**
+ * @constructs openerp.web.Connection
+ * @extends openerp.web.CallbackEnabled
+ *
+ * @param {String} [server] JSON-RPC endpoint hostname
+ * @param {String} [port] JSON-RPC endpoint port
+ */
+ init: function() {
+ this._super();
+ this.server = null;
+ this.debug = ($.deparam($.param.querystring()).debug != undefined);
+ // TODO: session store in cookie should be optional
+ this.name = openerp._session_id;
+ this.qweb_mutex = new $.Mutex();
+ },
+ session_bind: function(origin) {
+ var window_origin = location.protocol+"//"+location.host, self=this;
+ 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.openerp_entreprise = false;
+ this.module_list = openerp._modules.slice();
+ this.module_loaded = {};
+ _(this.module_list).each(function (mod) {
+ self.module_loaded[mod] = true;
+ });
+ this.context = {};
+ this.shortcuts = [];
+ this.active_id = null;
+ return this.session_init();
+ },
+ test_eval_get_context: function () {
+ var asJS = function (arg) {
+ if (arg instanceof py.object) {
+ return arg.toJSON();
+ }
+ return arg;
+ };
+
+ var datetime = new py.object();
+ datetime.datetime = new py.type(function datetime() {
+ throw new Error('datetime.datetime not implemented');
+ });
+ var date = datetime.date = new py.type(function date(y, m, d) {
+ if (y instanceof Array) {
+ d = y[2];
+ m = y[1];
+ y = y[0];
+ }
+ this.year = asJS(y);
+ this.month = asJS(m);
+ this.day = asJS(d);
+ }, py.object, {
+ strftime: function (args) {
+ var f = asJS(args[0]), self = this;
+ return new py.str(f.replace(/%([A-Za-z])/g, function (m, c) {
+ switch (c) {
+ case 'Y': return self.year;
+ case 'm': return _.str.sprintf('%02d', self.month);
+ case 'd': return _.str.sprintf('%02d', self.day);
+ }
+ throw new Error('ValueError: No known conversion for ' + m);
+ }));
+ }
+ });
+ date.__getattribute__ = function (name) {
+ if (name === 'today') {
+ return date.today;
+ }
+ throw new Error("AttributeError: object 'date' has no attribute '" + name +"'");
+ };
+ date.today = new py.def(function () {
+ var d = new Date();
+ return new date(d.getUTCFullYear(), d.getUTCMonth() + 1, d.getUTCDate());
+ });
+ datetime.time = new py.type(function time() {
+ throw new Error('datetime.time not implemented');
+ });
+
+ var time = new py.object();
+ time.strftime = new py.def(function (args) {
+ return date.today.__call__().strftime(args);
+ });
+
+ var relativedelta = new py.type(function relativedelta(args, kwargs) {
+ if (!_.isEmpty(args)) {
+ throw new Error('Extraction of relative deltas from existing datetimes not supported');
+ }
+ this.ops = kwargs;
+ }, py.object, {
+ __add__: function (other) {
+ if (!(other instanceof datetime.date)) {
+ return py.NotImplemented;
+ }
+ // TODO: test this whole mess
+ var year = asJS(this.ops.year) || asJS(other.year);
+ if (asJS(this.ops.years)) {
+ year += asJS(this.ops.years);
+ }
+
+ var month = asJS(this.ops.month) || asJS(other.month);
+ if (asJS(this.ops.months)) {
+ month += asJS(this.ops.months);
+ // FIXME: no divmod in JS?
+ while (month < 1) {
+ year -= 1;
+ month += 12;
+ }
+ while (month > 12) {
+ year += 1;
+ month -= 12;
+ }
+ }
+
+ var lastMonthDay = new Date(year, month, 0).getDate();
+ var day = asJS(this.ops.day) || asJS(other.day);
+ if (day > lastMonthDay) { day = lastMonthDay; }
+ var days_offset = ((asJS(this.ops.weeks) || 0) * 7) + (asJS(this.ops.days) || 0);
+ if (days_offset) {
+ day = new Date(year, month-1, day + days_offset).getDate();
+ }
+ // TODO: leapdays?
+ // TODO: hours, minutes, seconds? Not used in XML domains
+ // TODO: weekday?
+ return new datetime.date(year, month, day);
+ },
+ __radd__: function (other) {
+ return this.__add__(other);
+ },
+
+ __sub__: function (other) {
+ if (!(other instanceof datetime.date)) {
+ return py.NotImplemented;
+ }
+ // TODO: test this whole mess
+ var year = asJS(this.ops.year) || asJS(other.year);
+ if (asJS(this.ops.years)) {
+ year -= asJS(this.ops.years);
+ }
+
+ var month = asJS(this.ops.month) || asJS(other.month);
+ if (asJS(this.ops.months)) {
+ month -= asJS(this.ops.months);
+ // FIXME: no divmod in JS?
+ while (month < 1) {
+ year -= 1;
+ month += 12;
+ }
+ while (month > 12) {
+ year += 1;
+ month -= 12;
+ }
+ }
+
+ var lastMonthDay = new Date(year, month, 0).getDate();
+ var day = asJS(this.ops.day) || asJS(other.day);
+ if (day > lastMonthDay) { day = lastMonthDay; }
+ var days_offset = ((asJS(this.ops.weeks) || 0) * 7) + (asJS(this.ops.days) || 0);
+ if (days_offset) {
+ day = new Date(year, month-1, day - days_offset).getDate();
+ }
+ // TODO: leapdays?
+ // TODO: hours, minutes, seconds? Not used in XML domains
+ // TODO: weekday?
+ return new datetime.date(year, month, day);
+ },
+ __rsub__: function (other) {
+ return this.__sub__(other);
+ }
+ });
+
+ return {
+ uid: new py.float(this.uid),
+ datetime: datetime,
+ time: time,
+ relativedelta: relativedelta
+ };
+ },
+ /**
+ * FIXME: Huge testing hack, especially the evaluation context, rewrite + test for real before switching
+ */
+ test_eval: function (source, expected) {
+ var match_template = '
' +
+ '- Source: %(source)s
' +
+ '- Local: %(local)s
' +
+ '- Remote: %(remote)s
' +
+ '
',
+ fail_template = '' +
+ '- Error: %(error)s
' +
+ '- Source: %(source)s
' +
+ '
';
+ try {
+ var ctx = this.test_eval_contexts(source.contexts);
+ if (!_.isEqual(ctx, expected.context)) {
+ openerp.webclient.notification.warn('Context mismatch, report to xmo',
+ _.str.sprintf(match_template, {
+ source: JSON.stringify(source.contexts),
+ local: JSON.stringify(ctx),
+ remote: JSON.stringify(expected.context)
+ }), true);
+ }
+ } catch (e) {
+ openerp.webclient.notification.warn('Context fail, report to xmo',
+ _.str.sprintf(fail_template, {
+ error: e.message,
+ source: JSON.stringify(source.contexts)
+ }), true);
+ }
+
+ try {
+ var dom = this.test_eval_domains(source.domains, this.test_eval_get_context());
+ if (!_.isEqual(dom, expected.domain)) {
+ openerp.webclient.notification.warn('Domains mismatch, report to xmo',
+ _.str.sprintf(match_template, {
+ source: JSON.stringify(source.domains),
+ local: JSON.stringify(dom),
+ remote: JSON.stringify(expected.domain)
+ }), true);
+ }
+ } catch (e) {
+ openerp.webclient.notification.warn('Domain fail, report to xmo',
+ _.str.sprintf(fail_template, {
+ error: e.message,
+ source: JSON.stringify(source.domains)
+ }), true);
+ }
+
+ try {
+ var groups = this.test_eval_groupby(source.group_by_seq);
+ if (!_.isEqual(groups, expected.group_by)) {
+ openerp.webclient.notification.warn('GroupBy mismatch, report to xmo',
+ _.str.sprintf(match_template, {
+ source: JSON.stringify(source.group_by_seq),
+ local: JSON.stringify(groups),
+ remote: JSON.stringify(expected.group_by)
+ }), true);
+ }
+ } catch (e) {
+ openerp.webclient.notification.warn('GroupBy fail, report to xmo',
+ _.str.sprintf(fail_template, {
+ error: e.message,
+ source: JSON.stringify(source.group_by_seq)
+ }), true);
+ }
+ },
+ test_eval_contexts: function (contexts, evaluation_context) {
+ evaluation_context = evaluation_context || {};
+ var self = this;
+ return _(contexts).reduce(function (result_context, ctx) {
+ // __eval_context evaluations can lead to some of `contexts`'s
+ // values being null, skip them as well as empty contexts
+ if (_.isEmpty(ctx)) { return result_context; }
+ var evaluated = ctx;
+ switch(ctx.__ref) {
+ case 'context':
+ evaluated = py.eval(ctx.__debug, evaluation_context);
+ break;
+ case 'compound_context':
+ var eval_context = self.test_eval_contexts([ctx.__eval_context]);
+ evaluated = self.test_eval_contexts(
+ ctx.__contexts, _.extend({}, evaluation_context, eval_context));
+ break;
+ }
+ // add newly evaluated context to evaluation context for following
+ // siblings
+ _.extend(evaluation_context, evaluated);
+ return _.extend(result_context, evaluated);
+ }, _.extend({}, this.user_context));
+ },
+ test_eval_domains: function (domains, evaluation_context) {
+ var result_domain = [], self = this;
+ _(domains).each(function (dom) {
+ switch(dom.__ref) {
+ case 'domain':
+ result_domain.push.apply(
+ result_domain, py.eval(dom.__debug, evaluation_context));
+ break;
+ case 'compound_domain':
+ var eval_context = self.test_eval_contexts([dom.__eval_context]);
+ result_domain.push.apply(
+ result_domain, self.test_eval_domains(
+ dom.__domains, _.extend(
+ {}, evaluation_context, eval_context)));
+ break;
+ default:
+ result_domain.push.apply(
+ result_domain, dom);
+ }
+ });
+ return result_domain;
+ },
+ test_eval_groupby: function (contexts) {
+ var result_group = [], self = this;
+ _(contexts).each(function (ctx) {
+ var group;
+ switch(ctx.__ref) {
+ case 'context':
+ group = py.eval(ctx.__debug).group_by;
+ break;
+ case 'compound_context':
+ group = self.test_eval_contexts(
+ ctx.__contexts, ctx.__eval_context).group_by;
+ break;
+ default:
+ group = ctx.group_by
+ }
+ if (!group) { return; }
+ if (typeof group === 'string') {
+ result_group.push(group);
+ } else if (group instanceof Array) {
+ result_group.push.apply(result_group, group);
+ } else {
+ throw new Error('Got invalid groupby {{'
+ + JSON.stringify(group) + '}}');
+ }
+ });
+ return result_group;
+ },
+ /**
+ * Executes an RPC call, registering the provided callbacks.
+ *
+ * Registers a default error callback if none is provided, and handles
+ * setting the correct session id and session context in the parameter
+ * objects
+ *
+ * @param {String} url RPC endpoint
+ * @param {Object} params call parameters
+ * @param {Function} success_callback function to execute on RPC call success
+ * @param {Function} error_callback function to execute on RPC call failure
+ * @returns {jQuery.Deferred} jquery-provided ajax deferred
+ */
+ 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;
+ var payload = {
+ jsonrpc: '2.0',
+ method: 'call',
+ params: params,
+ id: _.uniqueId('r')
+ };
+ var deferred = $.Deferred();
+ this.on_rpc_request();
+ var aborter = params.aborter;
+ delete params.aborter;
+ var request = this.rpc_function(url, payload).then(
+ function (response, textStatus, jqXHR) {
+ self.on_rpc_response();
+ if (!response.error) {
+ if (url.url === '/web/session/eval_domain_and_context') {
+ self.test_eval(params, response.result);
+ }
+ 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());
+ });
+ if (aborter) {
+ aborter.abort_last = function () {
+ if (!(request.isResolved() || request.isRejected())) {
+ deferred.fail(function (error, event) {
+ event.preventDefault();
+ });
+ request.abort();
+ }
+ };
+ }
+ // 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_json: function(url, payload) {
+ var self = this;
+ var ajax = _.extend({
+ type: "POST",
+ dataType: 'json',
+ contentType: 'application/json',
+ data: JSON.stringify(payload),
+ processData: false
+ }, url);
+ 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 = $('