2 * Copyright (c) 2012, OpenERP S.A.
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
8 * 1. Redistributions of source code must retain the above copyright notice, this
9 * list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright notice,
11 * this list of conditions and the following disclaimer in the documentation
12 * and/or other materials provided with the distribution.
14 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
16 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
18 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
19 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
20 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
21 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
23 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 openerp.web.corelib = function(instance) {
29 * Improved John Resig's inheritance, based on:
31 * Simple JavaScript Inheritance
32 * By John Resig http://ejohn.org/
37 * Defines The Class object. That object can be used to define and inherit classes using
38 * the extend() method.
42 * var Person = instance.web.Class.extend({
43 * init: function(isDancing){
44 * this.dancing = isDancing;
47 * return this.dancing;
51 * The init() method act as a constructor. This class can be instancied this way:
53 * var person = new Person(true);
56 * The Person class can also be extended again:
58 * var Ninja = Person.extend({
60 * this._super( false );
63 * // Call the inherited version of dance()
64 * return this._super();
66 * swingSword: function(){
71 * When extending a class, each re-defined method can use this._super() to call the previous
72 * implementation of that method.
75 var initializing = false,
76 fnTest = /xyz/.test(function(){xyz();}) ? /\b_super\b/ : /.*/;
77 // The web Class implementation (does nothing)
78 instance.web.Class = function(){};
81 * Subclass an existing class
83 * @param {Object} prop class-level properties (class attributes and instance methods) to set on the new class
85 instance.web.Class.extend = function() {
86 var _super = this.prototype;
87 // Support mixins arguments
88 var args = _.toArray(arguments);
90 var prop = _.extend.apply(_,args);
92 // Instantiate a web class (but only create the instance,
93 // don't run the init constructor)
95 var prototype = new this();
98 // Copy the properties over onto the new prototype
99 for (var name in prop) {
100 // Check if we're overwriting an existing function
101 prototype[name] = typeof prop[name] == "function" &&
102 fnTest.test(prop[name]) ?
103 (function(name, fn) {
105 var tmp = this._super;
107 // Add a new ._super() method that is the same
108 // method but on the super-class
109 this._super = _super[name];
111 // The method only need to be bound temporarily, so
112 // we remove it when we're done executing
113 var ret = fn.apply(this, arguments);
118 })(name, prop[name]) :
122 // The dummy class constructor
124 if(this.constructor !== instance.web.Class){
125 throw new Error("You can only instanciate objects with the 'new' operator");
127 // All construction is actually done in the init method
128 if (!initializing && this.init) {
129 var ret = this.init.apply(this, arguments);
130 if (ret) { return ret; }
134 Class.include = function (properties) {
135 for (var name in properties) {
136 if (typeof properties[name] !== 'function'
137 || !fnTest.test(properties[name])) {
138 prototype[name] = properties[name];
139 } else if (typeof prototype[name] === 'function'
140 && prototype.hasOwnProperty(name)) {
141 prototype[name] = (function (name, fn, previous) {
143 var tmp = this._super;
144 this._super = previous;
145 var ret = fn.apply(this, arguments);
149 })(name, properties[name], prototype[name]);
150 } else if (typeof _super[name] === 'function') {
151 prototype[name] = (function (name, fn) {
153 var tmp = this._super;
154 this._super = _super[name];
155 var ret = fn.apply(this, arguments);
159 })(name, properties[name]);
164 // Populate our constructed prototype object
165 Class.prototype = prototype;
167 // Enforce the constructor to be what we expect
168 Class.constructor = Class;
170 // And make this class extendable
171 Class.extend = arguments.callee;
180 * Mixin to structure objects' life-cycles folowing a parent-children
181 * relationship. Each object can a have a parent and multiple children.
182 * When an object is destroyed, all its children are destroyed too releasing
183 * any resource they could have reserved before.
185 instance.web.ParentedMixin = {
186 __parentedMixin : true,
188 this.__parentedDestroyed = false;
189 this.__parentedChildren = [];
190 this.__parentedParent = null;
193 * Set the parent of the current object. When calling this method, the
194 * parent will also be informed and will return the current object
195 * when its getChildren() method is called. If the current object did
196 * already have a parent, it is unregistered before, which means the
197 * previous parent will not return the current object anymore when its
198 * getChildren() method is called.
200 setParent : function(parent) {
201 if (this.getParent()) {
202 if (this.getParent().__parentedMixin) {
203 this.getParent().__parentedChildren = _.without(this
204 .getParent().getChildren(), this);
207 this.__parentedParent = parent;
208 if (parent && parent.__parentedMixin) {
209 parent.__parentedChildren.push(this);
213 * Return the current parent of the object (or null).
215 getParent : function() {
216 return this.__parentedParent;
219 * Return a list of the children of the current object.
221 getChildren : function() {
222 return _.clone(this.__parentedChildren);
225 * Returns true if destroy() was called on the current object.
227 isDestroyed : function() {
228 return this.__parentedDestroyed;
231 Utility method to only execute asynchronous actions if the current
232 object has not been destroyed.
234 @param {$.Deferred} promise The promise representing the asynchronous
236 @param {bool} [reject=false] If true, the returned promise will be
237 rejected with no arguments if the current
238 object is destroyed. If false, the
239 returned promise will never be resolved
241 @returns {$.Deferred} A promise that will mirror the given promise if
242 everything goes fine but will either be rejected
243 with no arguments or never resolved if the
244 current object is destroyed.
246 alive: function(promise, reject) {
247 var def = $.Deferred();
249 promise.done(function() {
250 if (! self.isDestroyed()) {
252 def.resolve.apply(def, arguments);
257 if (! self.isDestroyed()) {
259 def.reject.apply(def, arguments);
264 return def.promise();
267 * Inform the object it should destroy itself, releasing any
268 * resource it could have reserved.
270 destroy : function() {
271 _.each(this.getChildren(), function(el) {
274 this.setParent(undefined);
275 this.__parentedDestroyed = true;
280 * Backbone's events. Do not ever use it directly, use EventDispatcherMixin instead.
282 * This class just handle the dispatching of events, it is not meant to be extended,
283 * nor used directly. All integration with parenting and automatic unregistration of
284 * events is done in EventDispatcherMixin.
286 * Copyright notice for the following Class:
288 * (c) 2010-2012 Jeremy Ashkenas, DocumentCloud Inc.
289 * Backbone may be freely distributed under the MIT license.
290 * For all details and documentation:
291 * http://backbonejs.org
294 var Events = instance.web.Class.extend({
295 on : function(events, callback, context) {
297 events = events.split(/\s+/);
298 var calls = this._callbacks || (this._callbacks = {});
299 while ((ev = events.shift())) {
300 var list = calls[ev] || (calls[ev] = {});
301 var tail = list.tail || (list.tail = list.next = {});
302 tail.callback = callback;
303 tail.context = context;
304 list.tail = tail.next = {};
309 off : function(events, callback, context) {
312 delete this._callbacks;
313 } else if ((calls = this._callbacks)) {
314 events = events.split(/\s+/);
315 while ((ev = events.shift())) {
318 if (!callback || !node)
320 while ((node = node.next) && node.next) {
321 if (node.callback === callback
322 && (!context || node.context === context))
324 this.on(ev, node.callback, node.context);
331 callbackList: function() {
333 _.each(this._callbacks || {}, function(el, eventName) {
335 while ((node = node.next) && node.next) {
336 lst.push([eventName, node.callback, node.context]);
342 trigger : function(events) {
343 var event, node, calls, tail, args, all, rest;
344 if (!(calls = this._callbacks))
347 (events = events.split(/\s+/)).push(null);
348 // Save references to the current heads & tails.
349 while ((event = events.shift())) {
356 if (!(node = calls[event]))
363 rest = Array.prototype.slice.call(arguments, 1);
364 while ((node = events.pop())) {
366 args = node.event ? [ node.event ].concat(rest) : rest;
367 while ((node = node.next) !== tail) {
368 node.callback.apply(node.context || this, args);
375 instance.web.EventDispatcherMixin = _.extend({}, instance.web.ParentedMixin, {
376 __eventDispatcherMixin: true,
378 instance.web.ParentedMixin.init.call(this);
379 this.__edispatcherEvents = new Events();
380 this.__edispatcherRegisteredEvents = [];
382 on: function(events, dest, func) {
384 if (!(func instanceof Function)) {
385 throw new Error("Event handler must be a function.");
387 events = events.split(/\s+/);
388 _.each(events, function(eventName) {
389 self.__edispatcherEvents.on(eventName, func, dest);
390 if (dest && dest.__eventDispatcherMixin) {
391 dest.__edispatcherRegisteredEvents.push({name: eventName, func: func, source: self});
396 off: function(events, dest, func) {
398 events = events.split(/\s+/);
399 _.each(events, function(eventName) {
400 self.__edispatcherEvents.off(eventName, func, dest);
401 if (dest && dest.__eventDispatcherMixin) {
402 dest.__edispatcherRegisteredEvents = _.filter(dest.__edispatcherRegisteredEvents, function(el) {
403 return !(el.name === eventName && el.func === func && el.source === self);
409 trigger: function(events) {
410 this.__edispatcherEvents.trigger.apply(this.__edispatcherEvents, arguments);
413 destroy: function() {
415 _.each(this.__edispatcherRegisteredEvents, function(event) {
416 event.source.__edispatcherEvents.off(event.name, event.func, self);
418 this.__edispatcherRegisteredEvents = [];
419 _.each(this.__edispatcherEvents.callbackList(), function(cal) {
420 this.off(cal[0], cal[2], cal[1]);
422 this.__edispatcherEvents.off();
423 instance.web.ParentedMixin.destroy.call(this);
427 instance.web.PropertiesMixin = _.extend({}, instance.web.EventDispatcherMixin, {
429 instance.web.EventDispatcherMixin.init.call(this);
430 this.__getterSetterInternalMap = {};
432 set: function(arg1, arg2, arg3) {
435 if (typeof arg1 === "string") {
438 options = arg3 || {};
441 options = arg2 || {};
445 _.each(map, function(val, key) {
446 var tmp = self.__getterSetterInternalMap[key];
450 self.__getterSetterInternalMap[key] = val;
451 if (! options.silent)
452 self.trigger("change:" + key, self, {
458 self.trigger("change", self);
461 return this.__getterSetterInternalMap[key];
468 A class containing common utility methods useful when working with OpenERP as well as the PropertiesMixin.
470 instance.web.Controller = instance.web.Class.extend(instance.web.PropertiesMixin, {
472 * Constructs the object and sets its parent if a parent is given.
474 * @param {instance.web.Controller} parent Binds the current instance to the given Controller instance.
475 * When that controller is destroyed by calling destroy(), the current instance will be
476 * destroyed too. Can be null.
478 init: function(parent) {
479 instance.web.PropertiesMixin.init.call(this);
480 this.setParent(parent);
483 * Proxies a method of the object, in order to keep the right ``this`` on
484 * method invocations.
486 * This method is similar to ``Function.prototype.bind`` or ``_.bind``, and
487 * even more so to ``jQuery.proxy`` with a fundamental difference: its
488 * resolution of the method being called is lazy, meaning it will use the
489 * method as it is when the proxy is called, not when the proxy is created.
491 * Other methods will fix the bound method to what it is when creating the
492 * binding/proxy, which is fine in most javascript code but problematic in
493 * OpenERP Web where developers may want to replace existing callbacks with
496 * The semantics of this precisely replace closing over the method call.
498 * @param {String|Function} method function or name of the method to invoke
499 * @returns {Function} proxied method
501 proxy: function (method) {
504 var fn = (typeof method === 'string') ? self[method] : method;
505 return fn.apply(self, arguments);
509 * Informs the action manager to do an action. This supposes that
510 * the action manager can be found amongst the ancestors of the current widget.
511 * If that's not the case this method will simply return `false`.
513 do_action: function() {
514 var parent = this.getParent();
516 return parent.do_action.apply(parent, arguments);
520 do_notify: function() {
521 if (this.getParent()) {
522 return this.getParent().do_notify.apply(this,arguments);
526 do_warn: function() {
527 if (this.getParent()) {
528 return this.getParent().do_warn.apply(this,arguments);
532 rpc: function(url, data, options) {
533 return this.alive(instance.session.rpc(url, data, options));
538 * Base class for all visual components. Provides a lot of functionalities helpful
539 * for the management of a part of the DOM.
542 * - Rendering with QWeb.
543 * - Life-cycle management and parenting (when a parent is destroyed, all its children are
545 * - Insertion in DOM.
547 * Guide to create implementations of the Widget class:
548 * ==============================================
550 * Here is a sample child class:
552 * MyWidget = instance.base.Widget.extend({
553 * // the name of the QWeb template to use for rendering
554 * template: "MyQWebTemplate",
556 * init: function(parent) {
557 * this._super(parent);
558 * // stuff that you want to init before the rendering
560 * start: function() {
561 * // stuff you want to make after the rendering, `this.$el` holds a correct value
562 * this.$el.find(".my_button").click(/* an example of event binding * /);
564 * // if you have some asynchronous operations, it's a good idea to return
565 * // a promise in start()
566 * var promise = this.rpc(...);
571 * Now this class can simply be used with the following syntax:
573 * var my_widget = new MyWidget(this);
574 * my_widget.appendTo($(".some-div"));
576 * With these two lines, the MyWidget instance was inited, rendered, it was inserted into the
577 * DOM inside the ".some-div" div and its events were binded.
579 * And of course, when you don't need that widget anymore, just do:
581 * my_widget.destroy();
583 * That will kill the widget in a clean way and erase its content from the dom.
585 instance.web.Widget = instance.web.Controller.extend({
593 * The name of the QWeb template that will be used for rendering. Must be
594 * redefined in subclasses or the default render() method can not be used.
600 * Constructs the widget and sets its parent if a parent is given.
602 * @constructs instance.web.Widget
604 * @param {instance.web.Widget} parent Binds the current instance to the given Widget instance.
605 * When that widget is destroyed by calling destroy(), the current instance will be
606 * destroyed too. Can be null.
608 init: function(parent) {
610 // Bind on_/do_* methods to this
611 // We might remove this automatic binding in the future
612 for (var name in this) {
613 if(typeof(this[name]) == "function") {
614 if((/^on_|^do_/).test(name)) {
615 this[name] = this[name].bind(this);
619 // FIXME: this should not be
620 this.setElement(this._make_descriptive());
621 this.session = instance.session;
624 * Destroys the current widget, also destroys all its children before destroying itself.
626 destroy: function() {
627 _.each(this.getChildren(), function(el) {
633 instance.web.PropertiesMixin.destroy.call(this);
636 * Renders the current widget and appends it to the given jQuery object or Widget.
638 * @param target A jQuery object or a Widget instance.
640 appendTo: function(target) {
642 return this.__widgetRenderAndInsert(function(t) {
643 self.$el.appendTo(t);
647 * Renders the current widget and prepends it to the given jQuery object or Widget.
649 * @param target A jQuery object or a Widget instance.
651 prependTo: function(target) {
653 return this.__widgetRenderAndInsert(function(t) {
654 self.$el.prependTo(t);
658 * Renders the current widget and inserts it after to the given jQuery object or Widget.
660 * @param target A jQuery object or a Widget instance.
662 insertAfter: function(target) {
664 return this.__widgetRenderAndInsert(function(t) {
665 self.$el.insertAfter(t);
669 * Renders the current widget and inserts it before to the given jQuery object or Widget.
671 * @param target A jQuery object or a Widget instance.
673 insertBefore: function(target) {
675 return this.__widgetRenderAndInsert(function(t) {
676 self.$el.insertBefore(t);
680 * Renders the current widget and replaces the given jQuery object.
682 * @param target A jQuery object or a Widget instance.
684 replace: function(target) {
685 return this.__widgetRenderAndInsert(_.bind(function(t) {
686 this.$el.replaceAll(t);
689 __widgetRenderAndInsert: function(insertion, target) {
690 this.renderElement();
695 * Method called after rendering. Mostly used to bind actions, perform asynchronous
698 * By convention, this method should return an object that can be passed to $.when()
699 * to inform the caller when this widget has been initialized.
701 * @returns {jQuery.Deferred or any}
707 * Renders the element. The default implementation renders the widget using QWeb,
708 * `this.template` must be defined. The context given to QWeb contains the "widget"
709 * key that references `this`.
711 renderElement: function() {
714 $el = $(_.str.trim(instance.web.qweb.render(
715 this.template, {widget: this})));
717 $el = this._make_descriptive();
719 this.replaceElement($el);
722 * Re-sets the widget's root element and replaces the old root element
723 * (if any) by the new one in the DOM.
725 * @param {HTMLElement | jQuery} $el
728 replaceElement: function ($el) {
729 var $oldel = this.$el;
730 this.setElement($el);
731 if ($oldel && !$oldel.is(this.$el)) {
732 $oldel.replaceWith(this.$el);
737 * Re-sets the widget's root element (el/$el/$el).
740 * * re-delegating events
741 * * re-binding sub-elements
742 * * if the widget already had a root element, replacing the pre-existing
745 * @param {HTMLElement | jQuery} element new root element for the widget
748 setElement: function (element) {
749 // NB: completely useless, as WidgetMixin#init creates a $el
752 this.undelegateEvents();
755 this.$el = (element instanceof $) ? element : $(element);
756 this.el = this.$el[0];
758 this.delegateEvents();
763 * Utility function to build small DOM elements.
765 * @param {String} tagName name of the DOM element to create
766 * @param {Object} [attributes] map of DOM attributes to set on the element
767 * @param {String} [content] HTML content to set on the element
770 make: function (tagName, attributes, content) {
771 var el = document.createElement(tagName);
772 if (!_.isEmpty(attributes)) {
773 $(el).attr(attributes);
781 * Makes a potential root element from the declarative builder of the
787 _make_descriptive: function () {
788 var attrs = _.extend({}, this.attributes || {});
789 if (this.id) { attrs.id = this.id; }
790 if (this.className) { attrs['class'] = this.className; }
791 return $(this.make(this.tagName, attrs));
793 delegateEvents: function () {
794 var events = this.events;
795 if (_.isEmpty(events)) { return; }
797 for(var key in events) {
798 if (!events.hasOwnProperty(key)) { continue; }
800 var method = this.proxy(events[key]);
802 var match = /^(\S+)(\s+(.*))?$/.exec(key);
803 var event = match[1];
804 var selector = match[3];
806 event += '.widget_events';
808 this.$el.on(event, method);
810 this.$el.on(event, selector, method);
814 undelegateEvents: function () {
815 this.$el.off('.widget_events');
818 * Shortcut for ``this.$el.find(selector)``
820 * @param {String} selector CSS selector, rooted in $el
821 * @returns {jQuery} selector match
823 $: function(selector) {
824 return this.$el.find(selector);
828 instance.web.Registry = instance.web.Class.extend({
830 * Stores a mapping of arbitrary key (strings) to object paths (as strings
833 * Resolves those paths at query time in order to always fetch the correct
834 * object, even if those objects have been overloaded/replaced after the
835 * registry was created.
837 * An object path is simply a dotted name from the instance root to the
838 * object pointed to (e.g. ``"instance.web.Session"`` for an OpenERP
841 * @constructs instance.web.Registry
842 * @param {Object} mapping a mapping of keys to object-paths
844 init: function (mapping) {
846 this.map = mapping || {};
849 * Retrieves the object matching the provided key string.
851 * @param {String} key the key to fetch the object for
852 * @param {Boolean} [silent_error=false] returns undefined if the key or object is not found, rather than throwing an exception
853 * @returns {Class} the stored class, to initialize or null if not found
855 get_object: function (key, silent_error) {
856 var path_string = this.map[key];
857 if (path_string === undefined) {
859 return this.parent.get_object(key, silent_error);
861 if (silent_error) { return void 'nooo'; }
865 var object_match = instance;
866 var path = path_string.split('.');
867 // ignore first section
868 for(var i=1; i<path.length; ++i) {
869 object_match = object_match[path[i]];
871 if (object_match === undefined) {
872 if (silent_error) { return void 'noooooo'; }
879 * Checks if the registry contains an object mapping for this key.
881 * @param {String} key key to look for
883 contains: function (key) {
884 if (key === undefined) { return false; }
885 if (key in this.map) {
889 return this.parent.contains(key);
894 * Tries a number of keys, and returns the first object matching one of
897 * @param {Array} keys a sequence of keys to fetch the object for
898 * @returns {Class} the first class found matching an object
900 get_any: function (keys) {
901 for (var i=0; i<keys.length; ++i) {
903 if (!this.contains(key)) {
907 return this.get_object(key);
912 * Adds a new key and value to the registry.
914 * This method can be chained.
916 * @param {String} key
917 * @param {String} object_path fully qualified dotted object path
918 * @returns {instance.web.Registry} itself
920 add: function (key, object_path) {
921 this.map[key] = object_path;
925 * Creates and returns a copy of the current mapping, with the provided
926 * mapping argument added in (replacing existing keys if needed)
928 * Parent and child remain linked, a new key in the parent (which is not
929 * overwritten by the child) will appear in the child.
931 * @param {Object} [mapping={}] a mapping of keys to object-paths
933 extend: function (mapping) {
934 var child = new instance.web.Registry(mapping);
939 * @deprecated use Registry#extend
941 clone: function (mapping) {
942 console.warn('Registry#clone is deprecated, use Registry#extend');
943 return this.extend(mapping);
947 instance.web.JsonRPC = instance.web.Class.extend(instance.web.PropertiesMixin, {
949 'request': 'Request sent',
950 'response': 'Response received',
951 'response_failed': 'HTTP Error response or timeout received',
952 'error': 'The received response is an JSON-RPC error',
955 * @constructs instance.web.JsonRPC
957 * @param {String} [server] JSON-RPC endpoint hostname
958 * @param {String} [port] JSON-RPC endpoint port
961 instance.web.PropertiesMixin.init.call(this);
963 this.debug = ($.deparam($.param.querystring()).debug !== undefined);
964 this.override_session = false;
965 this.session_id = undefined;
967 setup: function(origin) {
968 var window_origin = location.protocol+"//"+location.host, self=this;
969 this.origin = origin ? _.str.rtrim(origin,'/') : window_origin;
970 this.prefix = this.origin;
971 this.server = this.origin; // keep chs happy
972 this.rpc_function = (this.origin == window_origin) ? this.rpc_json : this.rpc_jsonp;
975 * Executes an RPC call, registering the provided callbacks.
977 * Registers a default error callback if none is provided, and handles
978 * setting the correct session id and session context in the parameter
981 * @param {String} url RPC endpoint
982 * @param {Object} params call parameters
983 * @param {Object} options additional options for rpc call
984 * @param {Function} success_callback function to execute on RPC call success
985 * @param {Function} error_callback function to execute on RPC call failure
986 * @returns {jQuery.Deferred} jquery-provided ajax deferred
988 rpc: function(url, params, options) {
990 options = options || {};
991 // url can be an $.ajax option object
992 if (_.isString(url)) {
996 context: this.user_context || {}
998 // Construct a JSON-RPC2 request, method is currently unused
1005 var deferred = $.Deferred();
1006 if (! options.shadow)
1007 this.trigger('request', url, payload);
1009 if (options.timeout)
1010 url.timeout = options.timeout;
1012 this.rpc_function(url, payload).then(
1013 function (response, textStatus, jqXHR) {
1014 if (! options.shadow)
1015 self.trigger('response', response);
1016 if (!response.error) {
1017 deferred.resolve(response["result"], textStatus, jqXHR);
1018 } else if (response.error.code === 100) {
1021 deferred.reject(response.error, $.Event());
1024 function(jqXHR, textStatus, errorThrown) {
1025 if (! options.shadow)
1026 self.trigger('response_failed', jqXHR);
1029 message: "XmlHttpRequestError " + errorThrown,
1030 data: {type: "xhr"+textStatus, debug: jqXHR.responseText, objects: [jqXHR, errorThrown] }
1032 deferred.reject(error, $.Event());
1034 // Allow deferred user to disable rpc_error call in fail
1035 deferred.fail(function() {
1036 deferred.fail(function(error, event) {
1037 if (!event.isDefaultPrevented()) {
1038 self.trigger('error', error, event);
1047 * @returns {jQuery.Deferred} ajax-webd deferred object
1049 rpc_json: function(url, payload) {
1051 var ajax = _.extend({
1054 contentType: 'application/json',
1055 data: JSON.stringify(payload),
1058 "X-Openerp-Session-Id": this.override_session ? this.session_id : undefined,
1063 return $.ajax(ajax);
1065 rpc_jsonp: function(url, payload) {
1067 // extracted from payload to set on the url
1069 session_id: this.session_id,
1073 var set_sid = function (response, textStatus, jqXHR) {
1074 // If response give us the http session id, we store it for next requests...
1075 if (response.session_id) {
1076 self.session_id = response.session_id;
1080 url.url = this.url(url.url, null);
1081 var ajax = _.extend({
1090 var payload_str = JSON.stringify(payload);
1091 var payload_url = $.param({r:payload_str});
1092 if (payload_url.length < 2000) {
1093 // Direct jsonp request
1094 ajax.data.r = payload_str;
1095 return $.ajax(ajax).done(set_sid);
1097 // Indirect jsonp request
1098 var ifid = _.uniqueId('oe_rpc_iframe');
1099 var display = self.debug ? 'block' : 'none';
1100 var $iframe = $(_.str.sprintf("<iframe src='javascript:false;' name='%s' id='%s' style='display:%s'></iframe>", ifid, ifid, display));
1101 var $form = $('<form>')
1102 .attr('method', 'POST')
1103 .attr('target', ifid)
1104 .attr('enctype', "multipart/form-data")
1105 .attr('action', ajax.url + '?jsonp=1&' + $.param(data))
1106 .append($('<input type="hidden" name="r" />').attr('value', payload_str))
1108 .appendTo($('body'));
1109 var cleanUp = function() {
1111 $iframe.unbind("load").remove();
1115 var deferred = $.Deferred();
1116 // the first bind is fired up when the iframe is added to the DOM
1117 $iframe.bind('load', function() {
1118 // the second bind is fired up when the result of the form submission is received
1119 $iframe.unbind('load').bind('load', function() {
1120 $.ajax(ajax).always(function() {
1122 }).done(function() {
1123 deferred.resolve.apply(deferred, arguments);
1124 }).fail(function() {
1125 deferred.reject.apply(deferred, arguments);
1128 // now that the iframe can receive data, we fill and submit the form
1131 // append the iframe to the DOM (will trigger the first load)
1132 $form.after($iframe);
1133 return deferred.done(set_sid);
1137 url: function(path, params) {
1138 params = _.extend(params || {});
1139 if (this.override_session)
1140 params.session_id = this.session_id;
1141 var qs = '?' + $.param(params);
1142 var prefix = _.any(['http://', 'https://', '//'], _.bind(_.str.startsWith, null, path)) ? '' : this.prefix;
1143 return prefix + path + qs;
1147 instance.web.py_eval = function(expr, context) {
1148 return py.eval(expr, _.extend({}, context || {}, {"true": true, "false": false, "null": null}));
1153 // vim:et fdc=0 fdl=0 foldnestmax=3 fdm=syntax: