1 /*---------------------------------------------------------
3 *--------------------------------------------------------*/
5 openerp.web.core = function(openerp) {
6 openerp.web.qweb = new QWeb2.Engine();
7 openerp.web.qweb.debug = (window.location.search.indexOf('?debug') !== -1);
9 * John Resig Class with factory improvement
12 var initializing = false,
13 fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/;
14 // The web Class implementation (does nothing)
16 * Extended version of John Resig's Class pattern
20 openerp.web.Class = function(){};
23 * Subclass an existing class
25 * @param {Object} prop class-level properties (class attributes and instance methods) to set on the new class
27 openerp.web.Class.extend = function(prop) {
28 var _super = this.prototype;
30 // Instantiate a web class (but only create the instance,
31 // don't run the init constructor)
33 var prototype = new this();
36 // Copy the properties over onto the new prototype
37 for (var name in prop) {
38 // Check if we're overwriting an existing function
39 prototype[name] = typeof prop[name] == "function" &&
40 typeof _super[name] == "function" &&
41 fnTest.test(prop[name]) ?
44 var tmp = this._super;
46 // Add a new ._super() method that is the same
47 // method but on the super-class
48 this._super = _super[name];
50 // The method only need to be bound temporarily, so
51 // we remove it when we're done executing
52 var ret = fn.apply(this, arguments);
57 })(name, prop[name]) :
61 // The dummy class constructor
63 // All construction is actually done in the init method
64 if (!initializing && this.init) {
65 var ret = this.init.apply(this, arguments);
66 if (ret) { return ret; }
70 Class.include = function (properties) {
71 for (var name in properties) {
72 if (typeof properties[name] !== 'function'
73 || !fnTest.test(properties[name])) {
74 prototype[name] = properties[name];
75 } else if (typeof prototype[name] === 'function'
76 && prototype.hasOwnProperty(name)) {
77 prototype[name] = (function (name, fn, previous) {
79 var tmp = this._super;
80 this._super = previous;
81 var ret = fn.apply(this, arguments);
85 })(name, properties[name], prototype[name]);
86 } else if (typeof _super[name] === 'function') {
87 prototype[name] = (function (name, fn) {
89 var tmp = this._super;
90 this._super = _super[name];
91 var ret = fn.apply(this, arguments);
95 })(name, properties[name]);
100 // Populate our constructed prototype object
101 Class.prototype = prototype;
103 // Enforce the constructor to be what we expect
104 Class.constructor = Class;
106 // And make this class extendable
107 Class.extend = arguments.callee;
113 openerp.web.callback = function(obj, method) {
114 var callback = function() {
115 var args = Array.prototype.slice.call(arguments);
117 for(var i = 0; i < callback.callback_chain.length; i++) {
118 var c = callback.callback_chain[i];
120 callback.callback_chain.splice(i, 1);
123 r = c.callback.apply(c.self, c.args.concat(args));
124 // TODO special value to stop the chain
125 // openerp.web.callback_stop
129 callback.callback_chain = [];
130 callback.add = function(f) {
131 if(typeof(f) == 'function') {
132 f = { callback: f, args: Array.prototype.slice.call(arguments, 1) };
134 f.self = f.self || null;
135 f.args = f.args || [];
136 f.unique = !!f.unique;
137 if(f.position == 'last') {
138 callback.callback_chain.push(f);
140 callback.callback_chain.unshift(f);
144 callback.add_first = function(f) {
145 return callback.add.apply(null,arguments);
147 callback.add_last = function(f) {
148 return callback.add({
150 args: Array.prototype.slice.call(arguments, 1),
155 return callback.add({
158 args:Array.prototype.slice.call(arguments, 2)
163 * Generates an inherited class that replaces all the methods by null methods (methods
164 * that does nothing and always return undefined).
166 * @param {Class} claz
167 * @param {Object} add Additional functions to override.
170 openerp.web.generate_null_object_class = function(claz, add) {
172 var copy_proto = function(prototype) {
173 for (var name in prototype) {
174 if(typeof prototype[name] == "function") {
175 newer[name] = function() {};
178 if (prototype.prototype)
179 copy_proto(prototype.prototype);
181 copy_proto(claz.prototype);
182 newer.init = openerp.web.Widget.prototype.init;
183 var tmpclass = claz.extend(newer);
184 return tmpclass.extend(add || {});
188 * web error for lookup failure
192 openerp.web.NotFound = openerp.web.Class.extend( /** @lends openerp.web.NotFound# */ {
194 openerp.web.KeyNotFound = openerp.web.NotFound.extend( /** @lends openerp.web.KeyNotFound# */ {
196 * Thrown when a key could not be found in a mapping
198 * @constructs openerp.web.KeyNotFound
199 * @extends openerp.web.NotFound
200 * @param {String} key the key which could not be found
202 init: function (key) {
205 toString: function () {
206 return "The key " + this.key + " was not found";
209 openerp.web.ObjectNotFound = openerp.web.NotFound.extend( /** @lends openerp.web.ObjectNotFound# */ {
211 * Thrown when an object path does not designate a valid class or object
212 * in the openerp hierarchy.
214 * @constructs openerp.web.ObjectNotFound
215 * @extends openerp.web.NotFound
216 * @param {String} path the invalid object path
218 init: function (path) {
221 toString: function () {
222 return "Could not find any object of path " + this.path;
225 openerp.web.Registry = openerp.web.Class.extend( /** @lends openerp.web.Registry# */ {
227 * Stores a mapping of arbitrary key (strings) to object paths (as strings
230 * Resolves those paths at query time in order to always fetch the correct
231 * object, even if those objects have been overloaded/replaced after the
232 * registry was created.
234 * An object path is simply a dotted name from the openerp root to the
235 * object pointed to (e.g. ``"openerp.web.Session"`` for an OpenERP
238 * @constructs openerp.web.Registry
239 * @param {Object} mapping a mapping of keys to object-paths
241 init: function (mapping) {
242 this.map = mapping || {};
245 * Retrieves the object matching the provided key string.
247 * @param {String} key the key to fetch the object for
248 * @returns {Class} the stored class, to initialize
250 * @throws {openerp.web.KeyNotFound} if the object was not in the mapping
251 * @throws {openerp.web.ObjectNotFound} if the object path was invalid
253 get_object: function (key) {
254 var path_string = this.map[key];
255 if (path_string === undefined) {
256 throw new openerp.web.KeyNotFound(key);
259 var object_match = openerp;
260 var path = path_string.split('.');
261 // ignore first section
262 for(var i=1; i<path.length; ++i) {
263 object_match = object_match[path[i]];
265 if (object_match === undefined) {
266 throw new openerp.web.ObjectNotFound(path_string);
272 * Tries a number of keys, and returns the first object matching one of
275 * @param {Array} keys a sequence of keys to fetch the object for
276 * @returns {Class} the first class found matching an object
278 * @throws {openerp.web.KeyNotFound} if none of the keys was in the mapping
279 * @trows {openerp.web.ObjectNotFound} if a found object path was invalid
281 get_any: function (keys) {
282 for (var i=0; i<keys.length; ++i) {
284 return this.get_object(keys[i]);
286 if (e instanceof openerp.web.KeyNotFound) {
292 throw new openerp.web.KeyNotFound(keys.join(','));
295 * Adds a new key and value to the registry.
297 * This method can be chained.
299 * @param {String} key
300 * @param {String} object_path fully qualified dotted object path
301 * @returns {openerp.web.Registry} itself
303 add: function (key, object_path) {
304 this.map[key] = object_path;
308 * Creates and returns a copy of the current mapping, with the provided
309 * mapping argument added in (replacing existing keys if needed)
311 * @param {Object} [mapping={}] a mapping of keys to object-paths
313 clone: function (mapping) {
314 return new openerp.web.Registry(
315 _.extend({}, this.map, mapping || {}));
319 openerp.web.CallbackEnabled = openerp.web.Class.extend(/** @lends openerp.web.CallbackEnabled# */{
321 * @constructs openerp.web.CallbackEnabled
322 * @extends openerp.web.Class
325 // Transform on_* method into openerp.web.callbacks
326 for (var name in this) {
327 if(typeof(this[name]) == "function") {
328 this[name].debug_name = name;
329 // bind ALL function to this not only on_and _do ?
330 if((/^on_|^do_/).test(name)) {
331 this[name] = openerp.web.callback(this, this[name]);
338 openerp.web.Session = openerp.web.CallbackEnabled.extend( /** @lends openerp.web.Session# */{
340 * @constructs openerp.web.Session
341 * @extends openerp.web.CallbackEnabled
343 * @param {String} [server] JSON-RPC endpoint hostname
344 * @param {String} [port] JSON-RPC endpoint port
346 init: function(server, port) {
348 this.server = (server == undefined) ? location.hostname : server;
349 this.port = (port == undefined) ? location.port : port;
350 this.rpc_mode = (server == location.hostname) ? "ajax" : "jsonp";
351 this.debug = (window.location.search.indexOf('?debug') !== -1);
355 this.user_context= {};
357 this.session_id = false;
358 this.module_list = [];
359 this.module_loaded = {"web": true};
362 this.active_id = null;
365 this.session_restore();
368 * Executes an RPC call, registering the provided callbacks.
370 * Registers a default error callback if none is provided, and handles
371 * setting the correct session id and session context in the parameter
374 * @param {String} url RPC endpoint
375 * @param {Object} params call parameters
376 * @param {Function} success_callback function to execute on RPC call success
377 * @param {Function} error_callback function to execute on RPC call failure
378 * @returns {jQuery.Deferred} jquery-provided ajax deferred
380 rpc: function(url, params, success_callback, error_callback) {
382 // Construct a JSON-RPC2 request, method is currently unused
383 params.session_id = this.session_id;
385 // Call using the rpc_mode
386 var deferred = $.Deferred();
391 id: _.uniqueId('browser-client-')
392 }).then(function () {deferred.resolve.apply(deferred, arguments);},
393 function(error) {deferred.reject(error, $.Event());});
394 return deferred.fail(function() {
395 deferred.fail(function(error, event) {
396 if (!event.isDefaultPrevented()) {
397 self.on_rpc_error(error, event);
400 }).then(success_callback, error_callback).promise();
405 * @returns {jQuery.Deferred} ajax-webd deferred object
407 rpc_ajax: function(url, payload) {
409 this.on_rpc_request();
410 // url can be an $.ajax option object
411 if (_.isString(url)) {
416 var ajax = _.extend({
420 contentType: 'application/json',
421 data: JSON.stringify(payload),
424 var deferred = $.Deferred();
425 $.ajax(ajax).done(function(response, textStatus, jqXHR) {
426 self.on_rpc_response();
427 if (!response.error) {
428 deferred.resolve(response["result"], textStatus, jqXHR);
431 if (response.error.data.type !== "session_invalid") {
432 deferred.reject(response.error);
436 self.on_session_invalid(function() {
437 self.rpc(url, payload.params,
439 deferred.resolve.apply(deferred, arguments);
441 function(error, event) {
442 event.preventDefault();
443 deferred.reject.apply(deferred, arguments);
446 }).fail(function(jqXHR, textStatus, errorThrown) {
447 self.on_rpc_response();
450 message: "XmlHttpRequestError " + errorThrown,
451 data: {type: "xhr"+textStatus, debug: jqXHR.responseText, objects: [jqXHR, errorThrown] }
453 deferred.reject(error);
455 return deferred.promise();
457 on_rpc_request: function() {
459 on_rpc_response: function() {
461 on_rpc_error: function(error) {
464 * The session is validated either by login or by restoration of a previous session
466 on_session_valid: function() {
467 if(!openerp._modules_loaded)
470 on_session_invalid: function(contination) {
472 session_is_valid: function() {
475 session_login: function(db, login, password, success_callback) {
479 this.password = password;
480 var params = { db: this.db, login: this.login, password: this.password };
481 this.rpc("/web/session/login", params, function(result) {
482 self.session_id = result.session_id;
483 self.uid = result.uid;
484 self.user_context = result.context;
486 self.on_session_valid();
488 }).then(success_callback);
490 session_logout: function() {
494 * Reloads uid and session_id from local storage, if they exist
496 session_restore: function () {
497 this.uid = this.get_cookie('uid');
498 this.session_id = this.get_cookie('session_id');
499 this.db = this.get_cookie('db');
500 this.login = this.get_cookie('login');
501 this.user_context = this.get_cookie("user_context");
502 // we should do an rpc to confirm that this session_id is valid and if it is retrieve the information about db and login
503 // then call on_session_valid
505 this.on_session_valid();
507 this.on_session_invalid();
510 * Saves the session id and uid locally
512 session_save: function () {
513 this.set_cookie('uid', this.uid);
514 this.set_cookie('session_id', this.session_id);
515 this.set_cookie('db', this.db);
516 this.set_cookie('login', this.login);
517 this.set_cookie('user_context', this.user_context);
521 delete this.session_id;
524 this.set_cookie('uid', '');
525 this.set_cookie('session_id', '');
526 this.set_cookie('db', '');
527 this.set_cookie('login', '');
528 this.on_session_invalid(function() {});
531 * Fetches a cookie stored by an openerp session
534 * @param name the cookie's name
536 get_cookie: function (name) {
537 var nameEQ = this.element_id + '|' + name + '=';
538 var cookies = document.cookie.split(';');
539 for(var i=0; i<cookies.length; ++i) {
540 var cookie = cookies[i].replace(/^\s*/, '');
541 if(cookie.indexOf(nameEQ) === 0) {
542 return JSON.parse(decodeURIComponent(cookie.substring(nameEQ.length)));
548 * Create a new cookie with the provided name and value
551 * @param name the cookie's name
552 * @param value the cookie's value
553 * @param ttl the cookie's time to live, 1 year by default, set to -1 to delete
555 set_cookie: function (name, value, ttl) {
556 ttl = ttl || 24*60*60*365;
558 this.element_id + '|' + name + '=' + encodeURIComponent(JSON.stringify(value)),
560 'expires=' + new Date(new Date().getTime() + ttl*1000).toGMTString()
564 * Load additional web addons of that instance and init them
566 load_modules: function() {
568 this.rpc('/web/session/modules', {}, function(result) {
569 self.module_list = result;
570 var lang = self.user_context.lang;
571 var params = { mods: ["web"].concat(result), lang: lang};
572 self.rpc('/web/webclient/translations',params).then(function(transs) {
573 openerp.web._t.database.set_bundle(transs);
574 var modules = self.module_list.join(',');
575 var file_list = ["/web/static/lib/datejs/globalization/" +
576 self.user_context.lang.replace("_", "-") + ".js"
579 self.rpc('/web/webclient/csslist', {"mods": modules}, self.do_load_css);
580 self.rpc('/web/webclient/jslist', {"mods": modules}, function(files) {
581 self.do_load_js(file_list.concat(files));
584 self.do_load_css(["/web/webclient/css?mods="+modules]);
585 self.do_load_js(file_list.concat(["/web/webclient/js?mods="+modules]));
587 openerp._modules_loaded = true;
591 do_load_css: function (files) {
593 _.each(files, function (file) {
594 $('head').append($('<link>', {
595 'href': file + (self.debug ? '?debug=' + (new Date().getTime()) : ''),
601 do_load_js: function(files) {
603 if(files.length != 0) {
604 var file = files.shift();
605 var tag = document.createElement('script');
606 tag.type = 'text/javascript';
607 tag.src = file + (this.debug ? '?debug=' + (new Date().getTime()) : '');
608 tag.onload = tag.onreadystatechange = function() {
609 if ( (tag.readyState && tag.readyState != "loaded" && tag.readyState != "complete") || tag.onload_done )
611 tag.onload_done = true;
612 self.do_load_js(files);
614 document.head.appendChild(tag);
616 this.on_modules_loaded();
619 on_modules_loaded: function() {
620 for(var j=0; j<this.module_list.length; j++) {
621 var mod = this.module_list[j];
622 if(this.module_loaded[mod])
626 if(openerp._openerp[mod] != undefined) {
627 openerp._openerp[mod](openerp);
628 this.module_loaded[mod] = true;
633 * Cooperative file download implementation, for ajaxy APIs.
635 * Requires that the server side implements an httprequest correctly
636 * setting the `fileToken` cookie to the value provided as the `token`
637 * parameter. The cookie *must* be set on the `/` path and *must not* be
640 * It would probably also be a good idea for the response to use a
641 * `Content-Disposition: attachment` header, especially if the MIME is a
642 * "known" type (e.g. text/plain, or for some browsers application/json
644 * @param {Object} options
645 * @param {String} [options.url] used to dynamically create a form
646 * @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
647 * @param {HTMLFormElement} [options.form] the form to submit in order to fetch the file
648 * @param {Function} [options.success] callback in case of download success
649 * @param {Function} [options.error] callback in case of request error, provided with the error body
650 * @param {Function} [options.complete] called after both ``success`` and ``error` callbacks have executed
652 get_file: function (options) {
653 // need to detect when the file is done downloading (not used
654 // yet, but we'll need it to fix the UI e.g. with a throbber
655 // while dump is being generated), iframe load event only fires
656 // when the iframe content loads, so we need to go smarter:
657 // http://geekswithblogs.net/GruffCode/archive/2010/10/28/detecting-the-file-download-dialog-in-the-browser.aspx
658 var timer, token = new Date().getTime(),
659 cookie_name = 'fileToken', cookie_length = cookie_name.length,
660 CHECK_INTERVAL = 1000, id = _.uniqueId('get_file_frame'),
663 var $form, $form_data = $('<div>');
665 var complete = function () {
666 if (options.complete) { options.complete(); }
670 if (remove_form && $form) { $form.remove(); }
672 var $target = $('<iframe style="display: none;">')
673 .attr({id: id, name: id})
674 .appendTo(document.body)
676 if (options.error) { options.error(this.contentDocument.body); }
681 $form = $(options.form);
684 $form = $('<form>', {
687 }).appendTo(document.body);
690 _(_.extend({}, options.data || {},
691 {session_id: this.session_id, token: token}))
692 .each(function (value, key) {
693 $('<input type="hidden" name="' + key + '">')
695 .appendTo($form_data);
703 var waitLoop = function () {
704 var cookies = document.cookie.split(';');
706 timer = setTimeout(waitLoop, CHECK_INTERVAL);
707 for (var i=0; i<cookies.length; ++i) {
708 var cookie = cookies[i].replace(/^\s*/, '');
709 if (!cookie.indexOf(cookie_name === 0)) { continue; }
710 var cookie_val = cookie.substring(cookie_length + 1);
711 if (parseInt(cookie_val, 10) !== token) { continue; }
714 document.cookie = _.sprintf("%s=;expires=%s;path=/",
715 cookie_name, new Date().toGMTString());
716 if (options.success) { options.success(); }
721 timer = setTimeout(waitLoop, CHECK_INTERVAL);
725 openerp.web.SessionAware = openerp.web.CallbackEnabled.extend(/** @lends openerp.web.SessionAware# */{
727 * Utility class that any class is allowed to extend to easy common manipulations.
729 * It provides rpc calls, callback on all methods preceded by "on_" or "do_" and a
732 * @constructs openerp.web.SessionAware
733 * @extends openerp.web.CallbackEnabled
735 * @param {openerp.web.Session} session
737 init: function(session) {
739 this.session = session;
742 * Performs a JSON-RPC call
744 * @param {String} url endpoint url
745 * @param {Object} data RPC parameters
746 * @param {Function} success RPC call success callback
747 * @param {Function} error RPC call error callback
748 * @returns {jQuery.Deferred} deferred object for the RPC call
750 rpc: function(url, data, success, error) {
751 return this.session.rpc(url, data, success, error);
754 var args = Array.prototype.slice.call(arguments);
755 var caller = arguments.callee.caller;
756 // TODO add support for line number using
757 // https://github.com/emwendelin/javascript-stacktrace/blob/master/stacktrace.js
758 // args.unshift("" + caller.debug_name);
759 this.on_log.apply(this,args);
762 if(this.session.debug) {
766 console.log(arguments);
770 var a = Array.prototype.slice.call(arguments, 0);
771 for(var i = 0; i < a.length; i++) {
772 var v = a[i]==null ? "null" : a[i].toString();
774 notify = v.match(/^not/);
775 body = v.match(/^bod/);
778 $('<pre></pre>').text(v).appendTo($('body'));
785 openerp.web.Widget = openerp.web.SessionAware.extend(/** @lends openerp.web.Widget# */{
787 * The name of the QWeb template that will be used for rendering. Must be
788 * redefined in subclasses or the default render() method can not be used.
794 * The prefix used to generate an id automatically. Should be redefined in
795 * subclasses. If it is not defined, a generic identifier will be used.
799 identifier_prefix: 'generic-identifier-',
801 * Construct the widget and set its parent if a parent is given.
803 * @constructs openerp.web.Widget
804 * @extends openerp.web.SessionAware
806 * @param {openerp.web.Widget} parent Binds the current instance to the given Widget instance.
807 * When that widget is destroyed by calling stop(), the current instance will be
808 * destroyed too. Can be null.
809 * @param {String} element_id Deprecated. Sets the element_id. Only useful when you want
810 * to bind the current Widget to an already existing part of the DOM, which is not compatible
811 * with the DOM insertion methods provided by the current implementation of Widget. So
812 * for new components this argument should not be provided any more.
814 init: function(parent, /** @deprecated */ element_id) {
815 this._super((parent || {}).session);
816 // if given an element_id, try to get the associated DOM element and save
817 // a reference in this.$element. Else just generate a unique identifier.
818 this.element_id = element_id;
819 this.element_id = this.element_id || _.uniqueId(this.identifier_prefix);
820 var tmp = document.getElementById(this.element_id);
821 this.$element = tmp ? $(tmp) : undefined;
823 this.widget_parent = parent;
824 this.widget_children = [];
825 if(parent && parent.widget_children) {
826 parent.widget_children.push(this);
828 // useful to know if the widget was destroyed and should not be used anymore
829 this.widget_is_stopped = false;
832 * Render the current widget and appends it to the given jQuery object or Widget.
834 * @param target A jQuery object or a Widget instance.
836 appendTo: function(target) {
838 return this._render_and_insert(function(t) {
839 self.$element.appendTo(t);
843 * Render the current widget and prepends it to the given jQuery object or Widget.
845 * @param target A jQuery object or a Widget instance.
847 prependTo: function(target) {
849 return this._render_and_insert(function(t) {
850 self.$element.prependTo(t);
854 * Render the current widget and inserts it after to the given jQuery object or Widget.
856 * @param target A jQuery object or a Widget instance.
858 insertAfter: function(target) {
860 return this._render_and_insert(function(t) {
861 self.$element.insertAfter(t);
865 * Render the current widget and inserts it before to the given jQuery object or Widget.
867 * @param target A jQuery object or a Widget instance.
869 insertBefore: function(target) {
871 return this._render_and_insert(function(t) {
872 self.$element.insertBefore(t);
875 _render_and_insert: function(insertion, target) {
876 var rendered = this.render();
877 this.$element = $(rendered);
878 if (target instanceof openerp.web.Widget)
879 target = target.$element;
881 this.on_inserted(this.$element, this);
884 on_inserted: function(element, widget) {},
886 * Renders the widget using QWeb, `this.template` must be defined.
887 * The context given to QWeb contains the "widget" key that references `this`.
889 * @param {Object} additional Additional context arguments to pass to the template.
891 render: function (additional) {
892 return openerp.web.qweb.render(this.template, _.extend({widget: this}, additional || {}));
895 * Method called after rendering. Mostly used to bind actions, perform asynchronous
898 * By convention, the method should return a promise to inform the caller when
899 * this widget has been initialized.
901 * @returns {jQuery.Deferred}
904 if (!this.$element) {
905 var tmp = document.getElementById(this.element_id);
906 this.$element = tmp ? $(tmp) : undefined;
908 return $.Deferred().done().promise();
911 * Destroys the current widget, also destory all its children before destroying itself.
914 _.each(_.clone(this.widget_children), function(el) {
917 if(this.$element != null) {
918 this.$element.remove();
920 if (this.widget_parent && this.widget_parent.widget_children) {
921 this.widget_parent.widget_children = _.without(this.widget_parent.widget_children, this);
923 this.widget_parent = null;
924 this.widget_is_stopped = true;
927 * Inform the action manager to do an action. Of course, this suppose that
928 * the action manager can be found amongst the ancestors of the current widget.
929 * If that's not the case this method will simply return `false`.
931 do_action: function(action, on_finished) {
932 if (this.widget_parent) {
933 return this.widget_parent.do_action(action, on_finished);
937 do_notify: function() {
938 if (this.widget_parent) {
939 return this.widget_parent.do_notify.apply(this,arguments);
943 do_warn: function() {
944 if (this.widget_parent) {
945 return this.widget_parent.do_warn.apply(this,arguments);
949 rpc: function(url, data, success, error) {
950 var def = $.Deferred().then(success, error);
952 this._super(url, data). then(function() {
953 if (!self.widget_is_stopped)
954 def.resolve.apply(def, arguments);
956 if (!self.widget_is_stopped)
957 def.reject.apply(def, arguments);
959 return def.promise();
965 * @extends openerp.web.Widget
967 * For retro compatibility only, the only difference with is that render() uses
968 * directly ``this`` instead of context with a ``widget`` key.
970 openerp.web.OldWidget = openerp.web.Widget.extend(/** @lends openerp.web.OldWidget# */{
971 render: function (additional) {
972 return openerp.web.qweb.render(this.template, _.extend(_.extend({}, this), additional || {}));
976 openerp.web.TranslationDataBase = openerp.web.Class.extend(/** @lends openerp.web.TranslationDataBase# */{
978 * @constructs openerp.web.TranslationDataBase
979 * @extends openerp.web.Class
983 this.parameters = {"direction": 'ltr',
984 "date_format": '%m/%d/%Y',
985 "time_format": '%H:%M:%S',
987 "decimal_point": ".",
988 "thousands_sep": ","};
990 set_bundle: function(translation_bundle) {
993 var modules = _.keys(translation_bundle.modules).sort();
994 if (_.include(modules, "web")) {
995 modules = ["web"].concat(_.without(modules, "web"));
997 _.each(modules, function(name) {
998 self.add_module_translation(translation_bundle.modules[name]);
1000 if (translation_bundle.lang_parameters) {
1001 this.parameters = translation_bundle.lang_parameters;
1004 add_module_translation: function(mod) {
1006 _.each(mod.messages, function(message) {
1007 if (self.db[message.id] === undefined) {
1008 self.db[message.id] = message.string;
1012 build_translation_function: function() {
1014 var fcnt = function(str) {
1015 var tmp = self.get(str);
1016 return tmp === undefined ? str : tmp;
1018 fcnt.database = this;
1021 get: function(key) {
1023 return this.db[key];
1028 openerp.web._t = new openerp.web.TranslationDataBase().build_translation_function();
1032 // vim:et fdc=0 fdl=0 foldnestmax=3 fdm=syntax: