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");
128 // All construction is actually done in the init method
129 if (!initializing && this.init) {
130 var ret = this.init.apply(this, arguments);
131 if (ret) { return ret; }
135 Class.include = function (properties) {
136 for (var name in properties) {
137 if (typeof properties[name] !== 'function'
138 || !fnTest.test(properties[name])) {
139 prototype[name] = properties[name];
140 } else if (typeof prototype[name] === 'function'
141 && prototype.hasOwnProperty(name)) {
142 prototype[name] = (function (name, fn, previous) {
144 var tmp = this._super;
145 this._super = previous;
146 var ret = fn.apply(this, arguments);
150 })(name, properties[name], prototype[name]);
151 } else if (typeof _super[name] === 'function') {
152 prototype[name] = (function (name, fn) {
154 var tmp = this._super;
155 this._super = _super[name];
156 var ret = fn.apply(this, arguments);
160 })(name, properties[name]);
165 // Populate our constructed prototype object
166 Class.prototype = prototype;
168 // Enforce the constructor to be what we expect
169 Class.constructor = Class;
171 // And make this class extendable
172 Class.extend = arguments.callee;
181 * Mixin to structure objects' life-cycles folowing a parent-children
182 * relationship. Each object can a have a parent and multiple children.
183 * When an object is destroyed, all its children are destroyed too releasing
184 * any resource they could have reserved before.
186 instance.web.ParentedMixin = {
187 __parentedMixin : true,
189 this.__parentedDestroyed = false;
190 this.__parentedChildren = [];
191 this.__parentedParent = null;
194 * Set the parent of the current object. When calling this method, the
195 * parent will also be informed and will return the current object
196 * when its getChildren() method is called. If the current object did
197 * already have a parent, it is unregistered before, which means the
198 * previous parent will not return the current object anymore when its
199 * getChildren() method is called.
201 setParent : function(parent) {
202 if (this.getParent()) {
203 if (this.getParent().__parentedMixin) {
204 this.getParent().__parentedChildren = _.without(this
205 .getParent().getChildren(), this);
208 this.__parentedParent = parent;
209 if (parent && parent.__parentedMixin) {
210 parent.__parentedChildren.push(this);
214 * Return the current parent of the object (or null).
216 getParent : function() {
217 return this.__parentedParent;
220 * Return a list of the children of the current object.
222 getChildren : function() {
223 return _.clone(this.__parentedChildren);
226 * Returns true if destroy() was called on the current object.
228 isDestroyed : function() {
229 return this.__parentedDestroyed;
232 * Inform the object it should destroy itself, releasing any
233 * resource it could have reserved.
235 destroy : function() {
236 _.each(this.getChildren(), function(el) {
239 this.setParent(undefined);
240 this.__parentedDestroyed = true;
245 * Backbone's events. Do not ever use it directly, use EventDispatcherMixin instead.
247 * This class just handle the dispatching of events, it is not meant to be extended,
248 * nor used directly. All integration with parenting and automatic unregistration of
249 * events is done in EventDispatcherMixin.
251 * Copyright notice for the following Class:
253 * (c) 2010-2012 Jeremy Ashkenas, DocumentCloud Inc.
254 * Backbone may be freely distributed under the MIT license.
255 * For all details and documentation:
256 * http://backbonejs.org
259 var Events = instance.web.Class.extend({
260 on : function(events, callback, context) {
262 events = events.split(/\s+/);
263 var calls = this._callbacks || (this._callbacks = {});
264 while (ev = events.shift()) {
265 var list = calls[ev] || (calls[ev] = {});
266 var tail = list.tail || (list.tail = list.next = {});
267 tail.callback = callback;
268 tail.context = context;
269 list.tail = tail.next = {};
274 off : function(events, callback, context) {
277 delete this._callbacks;
278 } else if (calls = this._callbacks) {
279 events = events.split(/\s+/);
280 while (ev = events.shift()) {
283 if (!callback || !node)
285 while ((node = node.next) && node.next) {
286 if (node.callback === callback
287 && (!context || node.context === context))
289 this.on(ev, node.callback, node.context);
296 callbackList: function() {
298 _.each(this._callbacks || {}, function(el, eventName) {
300 while ((node = node.next) && node.next) {
301 lst.push([eventName, node.callback, node.context]);
307 trigger : function(events) {
308 var event, node, calls, tail, args, all, rest;
309 if (!(calls = this._callbacks))
312 (events = events.split(/\s+/)).push(null);
313 // Save references to the current heads & tails.
314 while (event = events.shift()) {
321 if (!(node = calls[event]))
328 rest = Array.prototype.slice.call(arguments, 1);
329 while (node = events.pop()) {
331 args = node.event ? [ node.event ].concat(rest) : rest;
332 while ((node = node.next) !== tail) {
333 node.callback.apply(node.context || this, args);
340 instance.web.EventDispatcherMixin = _.extend({}, instance.web.ParentedMixin, {
341 __eventDispatcherMixin: true,
343 instance.web.ParentedMixin.init.call(this);
344 this.__edispatcherEvents = new Events();
345 this.__edispatcherRegisteredEvents = [];
347 on: function(events, dest, func) {
349 if (!(func instanceof Function)) {
350 throw new Error("Event handler must be a function.");
352 events = events.split(/\s+/);
353 _.each(events, function(eventName) {
354 self.__edispatcherEvents.on(eventName, func, dest);
355 if (dest && dest.__eventDispatcherMixin) {
356 dest.__edispatcherRegisteredEvents.push({name: eventName, func: func, source: self});
361 off: function(events, dest, func) {
363 events = events.split(/\s+/);
364 _.each(events, function(eventName) {
365 self.__edispatcherEvents.off(eventName, func, dest);
366 if (dest && dest.__eventDispatcherMixin) {
367 dest.__edispatcherRegisteredEvents = _.filter(dest.__edispatcherRegisteredEvents, function(el) {
368 return !(el.name === eventName && el.func === func && el.source === self);
374 trigger: function(events) {
375 this.__edispatcherEvents.trigger.apply(this.__edispatcherEvents, arguments);
378 destroy: function() {
380 _.each(this.__edispatcherRegisteredEvents, function(event) {
381 event.source.__edispatcherEvents.off(event.name, event.func, self);
383 this.__edispatcherRegisteredEvents = [];
384 _.each(this.__edispatcherEvents.callbackList(), function(cal) {
385 this.off(cal[0], cal[2], cal[1]);
387 this.__edispatcherEvents.off();
388 instance.web.ParentedMixin.destroy.call(this);
392 instance.web.PropertiesMixin = _.extend({}, instance.web.EventDispatcherMixin, {
394 instance.web.EventDispatcherMixin.init.call(this);
395 this.__getterSetterInternalMap = {};
397 set: function(arg1, arg2, arg3) {
400 if (typeof arg1 === "string") {
403 options = arg3 || {};
406 options = arg2 || {};
410 _.each(map, function(val, key) {
411 var tmp = self.__getterSetterInternalMap[key];
415 self.__getterSetterInternalMap[key] = val;
416 if (! options.silent)
417 self.trigger("change:" + key, self, {
423 self.trigger("change", self);
426 return this.__getterSetterInternalMap[key];
433 * Base class for all visual components. Provides a lot of functionalities helpful
434 * for the management of a part of the DOM.
437 * - Rendering with QWeb.
438 * - Life-cycle management and parenting (when a parent is destroyed, all its children are
440 * - Insertion in DOM.
442 * Guide to create implementations of the Widget class:
443 * ==============================================
445 * Here is a sample child class:
447 * MyWidget = instance.base.Widget.extend({
448 * // the name of the QWeb template to use for rendering
449 * template: "MyQWebTemplate",
451 * init: function(parent) {
452 * this._super(parent);
453 * // stuff that you want to init before the rendering
455 * start: function() {
456 * // stuff you want to make after the rendering, `this.$el` holds a correct value
457 * this.$el.find(".my_button").click(/* an example of event binding * /);
459 * // if you have some asynchronous operations, it's a good idea to return
460 * // a promise in start()
461 * var promise = this.rpc(...);
466 * Now this class can simply be used with the following syntax:
468 * var my_widget = new MyWidget(this);
469 * my_widget.appendTo($(".some-div"));
471 * With these two lines, the MyWidget instance was inited, rendered, it was inserted into the
472 * DOM inside the ".some-div" div and its events were binded.
474 * And of course, when you don't need that widget anymore, just do:
476 * my_widget.destroy();
478 * That will kill the widget in a clean way and erase its content from the dom.
480 instance.web.Widget = instance.web.Class.extend(instance.web.PropertiesMixin, {
488 * The name of the QWeb template that will be used for rendering. Must be
489 * redefined in subclasses or the default render() method can not be used.
495 * Constructs the widget and sets its parent if a parent is given.
497 * @constructs instance.web.Widget
499 * @param {instance.web.Widget} parent Binds the current instance to the given Widget instance.
500 * When that widget is destroyed by calling destroy(), the current instance will be
501 * destroyed too. Can be null.
503 init: function(parent) {
504 instance.web.PropertiesMixin.init.call(this);
505 this.setParent(parent);
506 // Bind on_/do_* methods to this
507 // We might remove this automatic binding in the future
508 for (var name in this) {
509 if(typeof(this[name]) == "function") {
510 if((/^on_|^do_/).test(name)) {
511 this[name] = this[name].bind(this);
515 // FIXME: this should not be
516 this.setElement(this._make_descriptive());
517 this.session = instance.session;
520 * Destroys the current widget, also destroys all its children before destroying itself.
522 destroy: function() {
523 _.each(this.getChildren(), function(el) {
529 instance.web.PropertiesMixin.destroy.call(this);
532 * Renders the current widget and appends it to the given jQuery object or Widget.
534 * @param target A jQuery object or a Widget instance.
536 appendTo: function(target) {
538 return this.__widgetRenderAndInsert(function(t) {
539 self.$el.appendTo(t);
543 * Renders the current widget and prepends it to the given jQuery object or Widget.
545 * @param target A jQuery object or a Widget instance.
547 prependTo: function(target) {
549 return this.__widgetRenderAndInsert(function(t) {
550 self.$el.prependTo(t);
554 * Renders the current widget and inserts it after to the given jQuery object or Widget.
556 * @param target A jQuery object or a Widget instance.
558 insertAfter: function(target) {
560 return this.__widgetRenderAndInsert(function(t) {
561 self.$el.insertAfter(t);
565 * Renders the current widget and inserts it before to the given jQuery object or Widget.
567 * @param target A jQuery object or a Widget instance.
569 insertBefore: function(target) {
571 return this.__widgetRenderAndInsert(function(t) {
572 self.$el.insertBefore(t);
576 * Renders the current widget and replaces the given jQuery object.
578 * @param target A jQuery object or a Widget instance.
580 replace: function(target) {
581 return this.__widgetRenderAndInsert(_.bind(function(t) {
582 this.$el.replaceAll(t);
585 __widgetRenderAndInsert: function(insertion, target) {
586 this.renderElement();
591 * This is the method to implement to render the Widget.
593 renderElement: function() {
596 * Method called after rendering. Mostly used to bind actions, perform asynchronous
599 * By convention, the method should return a promise to inform the caller when
600 * this widget has been initialized.
602 * @returns {jQuery.Deferred}
608 * Proxies a method of the object, in order to keep the right ``this`` on
609 * method invocations.
611 * This method is similar to ``Function.prototype.bind`` or ``_.bind``, and
612 * even more so to ``jQuery.proxy`` with a fundamental difference: its
613 * resolution of the method being called is lazy, meaning it will use the
614 * method as it is when the proxy is called, not when the proxy is created.
616 * Other methods will fix the bound method to what it is when creating the
617 * binding/proxy, which is fine in most javascript code but problematic in
618 * OpenERP Web where developers may want to replace existing callbacks with
621 * The semantics of this precisely replace closing over the method call.
623 * @param {String|Function} method function or name of the method to invoke
624 * @returns {Function} proxied method
626 proxy: function (method) {
629 var fn = (typeof method === 'string') ? self[method] : method;
630 return fn.apply(self, arguments);
634 * Renders the element. The default implementation renders the widget using QWeb,
635 * `this.template` must be defined. The context given to QWeb contains the "widget"
636 * key that references `this`.
638 renderElement: function() {
641 $el = $(_.str.trim(instance.web.qweb.render(
642 this.template, {widget: this})));
644 $el = this._make_descriptive();
646 this.replaceElement($el);
649 * Re-sets the widget's root element and replaces the old root element
650 * (if any) by the new one in the DOM.
652 * @param {HTMLElement | jQuery} $el
655 replaceElement: function ($el) {
656 var $oldel = this.$el;
657 this.setElement($el);
658 if ($oldel && !$oldel.is(this.$el)) {
659 $oldel.replaceWith(this.$el);
664 * Re-sets the widget's root element (el/$el/$el).
667 * * re-delegating events
668 * * re-binding sub-elements
669 * * if the widget already had a root element, replacing the pre-existing
672 * @param {HTMLElement | jQuery} element new root element for the widget
675 setElement: function (element) {
676 // NB: completely useless, as WidgetMixin#init creates a $el
679 this.undelegateEvents();
682 this.$el = (element instanceof $) ? element : $(element);
683 this.el = this.$el[0];
685 this.delegateEvents();
690 * Utility function to build small DOM elements.
692 * @param {String} tagName name of the DOM element to create
693 * @param {Object} [attributes] map of DOM attributes to set on the element
694 * @param {String} [content] HTML content to set on the element
697 make: function (tagName, attributes, content) {
698 var el = document.createElement(tagName);
699 if (!_.isEmpty(attributes)) {
700 $(el).attr(attributes);
708 * Makes a potential root element from the declarative builder of the
714 _make_descriptive: function () {
715 var attrs = _.extend({}, this.attributes || {});
716 if (this.id) { attrs.id = this.id; }
717 if (this.className) { attrs['class'] = this.className; }
718 return $(this.make(this.tagName, attrs));
720 delegateEvents: function () {
721 var events = this.events;
722 if (_.isEmpty(events)) { return; }
724 for(var key in events) {
725 if (!events.hasOwnProperty(key)) { continue; }
727 var method = this.proxy(events[key]);
729 var match = /^(\S+)(\s+(.*))?$/.exec(key);
730 var event = match[1];
731 var selector = match[3];
733 event += '.widget_events';
735 this.$el.on(event, method);
737 this.$el.on(event, selector, method);
741 undelegateEvents: function () {
742 this.$el.off('.widget_events');
745 * Shortcut for ``this.$el.find(selector)``
747 * @param {String} selector CSS selector, rooted in $el
748 * @returns {jQuery} selector match
750 $: function(selector) {
751 return this.$el.find(selector);
754 * Informs the action manager to do an action. This supposes that
755 * the action manager can be found amongst the ancestors of the current widget.
756 * If that's not the case this method will simply return `false`.
758 do_action: function() {
759 var parent = this.getParent();
761 return parent.do_action.apply(parent, arguments);
765 do_notify: function() {
766 if (this.getParent()) {
767 return this.getParent().do_notify.apply(this,arguments);
771 do_warn: function() {
772 if (this.getParent()) {
773 return this.getParent().do_warn.apply(this,arguments);
777 rpc: function(url, data, options) {
778 var def = $.Deferred();
780 instance.session.rpc(url, data, options).done(function() {
781 if (!self.isDestroyed())
782 def.resolve.apply(def, arguments);
784 if (!self.isDestroyed())
785 def.reject.apply(def, arguments);
787 return def.promise();
791 instance.web.Registry = instance.web.Class.extend({
793 * Stores a mapping of arbitrary key (strings) to object paths (as strings
796 * Resolves those paths at query time in order to always fetch the correct
797 * object, even if those objects have been overloaded/replaced after the
798 * registry was created.
800 * An object path is simply a dotted name from the instance root to the
801 * object pointed to (e.g. ``"instance.web.Session"`` for an OpenERP
804 * @constructs instance.web.Registry
805 * @param {Object} mapping a mapping of keys to object-paths
807 init: function (mapping) {
809 this.map = mapping || {};
812 * Retrieves the object matching the provided key string.
814 * @param {String} key the key to fetch the object for
815 * @param {Boolean} [silent_error=false] returns undefined if the key or object is not found, rather than throwing an exception
816 * @returns {Class} the stored class, to initialize or null if not found
818 get_object: function (key, silent_error) {
819 var path_string = this.map[key];
820 if (path_string === undefined) {
822 return this.parent.get_object(key, silent_error);
824 if (silent_error) { return void 'nooo'; }
828 var object_match = instance;
829 var path = path_string.split('.');
830 // ignore first section
831 for(var i=1; i<path.length; ++i) {
832 object_match = object_match[path[i]];
834 if (object_match === undefined) {
835 if (silent_error) { return void 'noooooo'; }
842 * Checks if the registry contains an object mapping for this key.
844 * @param {String} key key to look for
846 contains: function (key) {
847 if (key === undefined) { return false; }
848 if (key in this.map) {
852 return this.parent.contains(key);
857 * Tries a number of keys, and returns the first object matching one of
860 * @param {Array} keys a sequence of keys to fetch the object for
861 * @returns {Class} the first class found matching an object
863 get_any: function (keys) {
864 for (var i=0; i<keys.length; ++i) {
866 if (!this.contains(key)) {
870 return this.get_object(key);
875 * Adds a new key and value to the registry.
877 * This method can be chained.
879 * @param {String} key
880 * @param {String} object_path fully qualified dotted object path
881 * @returns {instance.web.Registry} itself
883 add: function (key, object_path) {
884 this.map[key] = object_path;
888 * Creates and returns a copy of the current mapping, with the provided
889 * mapping argument added in (replacing existing keys if needed)
891 * Parent and child remain linked, a new key in the parent (which is not
892 * overwritten by the child) will appear in the child.
894 * @param {Object} [mapping={}] a mapping of keys to object-paths
896 extend: function (mapping) {
897 var child = new instance.web.Registry(mapping);
902 * @deprecated use Registry#extend
904 clone: function (mapping) {
905 console.warn('Registry#clone is deprecated, use Registry#extend');
906 return this.extend(mapping);
910 instance.web.JsonRPC = instance.web.Class.extend(instance.web.PropertiesMixin, {
912 'request': 'Request sent',
913 'response': 'Response received',
914 'response_failed': 'HTTP Error response or timeout received',
915 'error': 'The received response is an JSON-RPC error',
918 * @constructs instance.web.JsonRPC
920 * @param {String} [server] JSON-RPC endpoint hostname
921 * @param {String} [port] JSON-RPC endpoint port
924 instance.web.PropertiesMixin.init.call(this);
926 this.debug = ($.deparam($.param.querystring()).debug != undefined);
928 setup: function(origin) {
929 var window_origin = location.protocol+"//"+location.host, self=this;
930 this.origin = origin ? _.str.rtrim(origin,'/') : window_origin;
931 this.prefix = this.origin;
932 this.server = this.origin; // keep chs happy
933 this.rpc_function = (this.origin == window_origin) ? this.rpc_json : this.rpc_jsonp;
936 * Executes an RPC call, registering the provided callbacks.
938 * Registers a default error callback if none is provided, and handles
939 * setting the correct session id and session context in the parameter
942 * @param {String} url RPC endpoint
943 * @param {Object} params call parameters
944 * @param {Object} options additional options for rpc call
945 * @param {Function} success_callback function to execute on RPC call success
946 * @param {Function} error_callback function to execute on RPC call failure
947 * @returns {jQuery.Deferred} jquery-provided ajax deferred
949 rpc: function(url, params, options) {
951 options = options || {};
952 // url can be an $.ajax option object
953 if (_.isString(url)) {
956 // Construct a JSON-RPC2 request, method is currently unused
965 var deferred = $.Deferred();
966 if (! options.shadow)
967 this.trigger('request', url, payload);
969 if (url.url === '/web/session/eval_domain_and_context') {
970 // intercept eval_domain_and_context
971 request = instance.web.pyeval.eval_domains_and_contexts(
974 request = this.rpc_function(url, payload);
977 function (response, textStatus, jqXHR) {
978 if (! options.shadow)
979 self.trigger('response', response);
980 if (!response.error) {
981 deferred.resolve(response["result"], textStatus, jqXHR);
982 } else if (response.error.data.type === "session_invalid") {
985 deferred.reject(response.error, $.Event());
988 function(jqXHR, textStatus, errorThrown) {
989 if (! options.shadow)
990 self.trigger('response_failed', jqXHR);
993 message: "XmlHttpRequestError " + errorThrown,
994 data: {type: "xhr"+textStatus, debug: jqXHR.responseText, objects: [jqXHR, errorThrown] }
996 deferred.reject(error, $.Event());
998 // Allow deferred user to disable rpc_error call in fail
999 deferred.fail(function() {
1000 deferred.fail(function(error, event) {
1001 if (!event.isDefaultPrevented()) {
1002 self.trigger('error', error, event);
1011 * @returns {jQuery.Deferred} ajax-webd deferred object
1013 rpc_json: function(url, payload) {
1015 var ajax = _.extend({
1018 contentType: 'application/json',
1019 data: JSON.stringify(payload),
1024 return $.ajax(ajax);
1026 rpc_jsonp: function(url, payload) {
1028 // extracted from payload to set on the url
1030 session_id: this.session_id,
1032 sid: this.httpsessionid,
1035 var set_sid = function (response, textStatus, jqXHR) {
1036 // If response give us the http session id, we store it for next requests...
1037 if (response.httpsessionid) {
1038 self.httpsessionid = response.httpsessionid;
1042 url.url = this.url(url.url, null);
1043 var ajax = _.extend({
1052 var payload_str = JSON.stringify(payload);
1053 var payload_url = $.param({r:payload_str});
1054 if(payload_url.length < 2000) {
1055 // Direct jsonp request
1056 ajax.data.r = payload_str;
1057 return $.ajax(ajax).done(set_sid);
1059 // Indirect jsonp request
1060 var ifid = _.uniqueId('oe_rpc_iframe');
1061 var display = self.debug ? 'block' : 'none';
1062 var $iframe = $(_.str.sprintf("<iframe src='javascript:false;' name='%s' id='%s' style='display:%s'></iframe>", ifid, ifid, display));
1063 var $form = $('<form>')
1064 .attr('method', 'POST')
1065 .attr('target', ifid)
1066 .attr('enctype', "multipart/form-data")
1067 .attr('action', ajax.url + '?jsonp=1&' + $.param(data))
1068 .append($('<input type="hidden" name="r" />').attr('value', payload_str))
1070 .appendTo($('body'));
1071 var cleanUp = function() {
1073 $iframe.unbind("load").remove();
1077 var deferred = $.Deferred();
1078 // the first bind is fired up when the iframe is added to the DOM
1079 $iframe.bind('load', function() {
1080 // the second bind is fired up when the result of the form submission is received
1081 $iframe.unbind('load').bind('load', function() {
1082 $.ajax(ajax).always(function() {
1084 }).done(function() {
1085 deferred.resolve.apply(deferred, arguments);
1086 }).fail(function() {
1087 deferred.reject.apply(deferred, arguments);
1090 // now that the iframe can receive data, we fill and submit the form
1093 // append the iframe to the DOM (will trigger the first load)
1094 $form.after($iframe);
1095 return deferred.done(set_sid);
1099 url: function(path, params) {
1101 if (!_.isNull(params)) {
1102 params = _.extend(params || {}, {session_id: this.session_id});
1103 if (this.httpsessionid) {
1104 params.sid = this.httpsessionid;
1106 qs = '?' + $.param(params);
1108 return this.prefix + path + qs;
1112 instance.web.py_eval = function(expr, context) {
1113 return py.eval(expr, _.extend({}, context || {}, {"true": true, "false": false, "null": null}));
1118 // vim:et fdc=0 fdl=0 foldnestmax=3 fdm=syntax: