diff options
Diffstat (limited to 'addons/web/static/src/js/framework/ajax.js')
| -rw-r--r-- | addons/web/static/src/js/framework/ajax.js | 286 |
1 files changed, 286 insertions, 0 deletions
diff --git a/addons/web/static/src/js/framework/ajax.js b/addons/web/static/src/js/framework/ajax.js new file mode 100644 index 00000000000..00da4610f9c --- /dev/null +++ b/addons/web/static/src/js/framework/ajax.js @@ -0,0 +1,286 @@ +odoo.define('web.ajax', ['web.time'], function (require) { +"use strict"; + +var time = require('web.time'); + +function genericJsonRpc (fct_name, params, fct) { + var data = { + jsonrpc: "2.0", + method: fct_name, + params: params, + id: Math.floor(Math.random() * 1000 * 1000 * 1000) + }; + var xhr = fct(data); + var result = xhr.pipe(function(result) { + if (result.error !== undefined) { + console.error("Server application error", result.error); + return $.Deferred().reject("server", result.error); + } else { + return result.result; + } + }, function() { + //console.error("JsonRPC communication error", _.toArray(arguments)); + var def = $.Deferred(); + return def.reject.apply(def, ["communication"].concat(_.toArray(arguments))); + }); + // FIXME: jsonp? + result.abort = function () { if (xhr.abort) xhr.abort(); }; + return result; +} + +function jsonRpc(url, fct_name, params, settings) { + return genericJsonRpc(fct_name, params, function(data) { + return $.ajax(url, _.extend({}, settings, { + url: url, + dataType: 'json', + type: 'POST', + data: JSON.stringify(data, time.date_to_utc), + contentType: 'application/json' + })); + }); +}; + +function jsonpRpc(url, fct_name, params, settings) { + settings = settings || {}; + return genericJsonRpc(fct_name, params, function(data) { + var payload_str = JSON.stringify(data, time.date_to_utc); + var payload_url = $.param({r:payload_str}); + var force2step = settings.force2step || false; + delete settings.force2step; + var session_id = settings.session_id || null; + delete settings.session_id; + if (payload_url.length < 2000 && ! force2step) { + return $.ajax(url, _.extend({}, settings, { + url: url, + dataType: 'jsonp', + jsonp: 'jsonp', + type: 'GET', + cache: false, + data: {r: payload_str, session_id: session_id} + })); + } else { + var args = {session_id: session_id, id: data.id}; + var ifid = _.uniqueId('oe_rpc_iframe'); + var html = "<iframe src='javascript:false;' name='" + ifid + "' id='" + ifid + "' style='display:none'></iframe>"; + var $iframe = $(html); + var nurl = 'jsonp=1&' + $.param(args); + nurl = url.indexOf("?") !== -1 ? url + "&" + nurl : url + "?" + nurl; + var $form = $('<form>') + .attr('method', 'POST') + .attr('target', ifid) + .attr('enctype', "multipart/form-data") + .attr('action', nurl) + .append($('<input type="hidden" name="r" />').attr('value', payload_str)) + .hide() + .appendTo($('body')); + var cleanUp = function() { + if ($iframe) { + $iframe.unbind("load").remove(); + } + $form.remove(); + }; + var deferred = $.Deferred(); + // the first bind is fired up when the iframe is added to the DOM + $iframe.bind('load', function() { + // the second bind is fired up when the result of the form submission is received + $iframe.unbind('load').bind('load', function() { + $.ajax({ + url: url, + dataType: 'jsonp', + jsonp: 'jsonp', + type: 'GET', + cache: false, + data: {session_id: session_id, id: data.id} + }).always(function() { + cleanUp(); + }).done(function() { + deferred.resolve.apply(deferred, arguments); + }).fail(function() { + deferred.reject.apply(deferred, arguments); + }); + }); + // now that the iframe can receive data, we fill and submit the form + $form.submit(); + }); + // append the iframe to the DOM (will trigger the first load) + $form.after($iframe); + if (settings.timeout) { + realSetTimeout(function() { + deferred.reject({}); + }, settings.timeout); + } + return deferred; + } + }); +}; + +// helper +function realSetTimeout (fct, millis) { + var finished = new Date().getTime() + millis; + var wait = function() { + var current = new Date().getTime(); + if (current < finished) { + setTimeout(wait, finished - current); + } else { + fct(); + } + }; + setTimeout(wait, millis); +} + +function loadCSS(url) { + if (!$('link[href="' + url + '"]').length) { + $('head').append($('<link>', { + 'href': url, + 'rel': 'stylesheet', + 'type': 'text/css' + })); + } +}; + +function loadJS(url) { + var def = $.Deferred(); + if ($('script[src="' + url + '"]').length) { + def.resolve(); + } else { + var script = document.createElement('script'); + script.type = 'text/javascript'; + script.src = url; + script.onload = script.onreadystatechange = function() { + if ((script.readyState && script.readyState != "loaded" && script.readyState != "complete") || script.onload_done) { + return; + } + script.onload_done = true; + def.resolve(url); + }; + script.onerror = function () { + console.error("Error loading file", script.src); + def.reject(url); + }; + var head = document.head || document.getElementsByTagName('head')[0]; + head.appendChild(script); + } + return def; +}; + +/** + * Cooperative file download implementation, for ajaxy APIs. + * + * Requires that the server side implements an httprequest correctly + * setting the `fileToken` cookie to the value provided as the `token` + * parameter. The cookie *must* be set on the `/` path and *must not* be + * `httpOnly`. + * + * It would probably also be a good idea for the response to use a + * `Content-Disposition: attachment` header, especially if the MIME is a + * "known" type (e.g. text/plain, or for some browsers application/json + * + * @param {Object} options + * @param {String} [options.url] used to dynamically create a form + * @param {Object} [options.data] data to add to the form submission. If can be used without a form, in which case a form is created from scratch. Otherwise, added to form data + * @param {HTMLFormElement} [options.form] the form to submit in order to fetch the file + * @param {Function} [options.success] callback in case of download success + * @param {Function} [options.error] callback in case of request error, provided with the error body + * @param {Function} [options.complete] called after both ``success`` and ``error` callbacks have executed + */ +function get_file(options) { + // need to detect when the file is done downloading (not used + // yet, but we'll need it to fix the UI e.g. with a throbber + // while dump is being generated), iframe load event only fires + // when the iframe content loads, so we need to go smarter: + // http://geekswithblogs.net/GruffCode/archive/2010/10/28/detecting-the-file-download-dialog-in-the-browser.aspx + var timer, token = new Date().getTime(), + cookie_name = 'fileToken', cookie_length = cookie_name.length, + CHECK_INTERVAL = 1000, id = _.uniqueId('get_file_frame'), + remove_form = false; + + + // iOS devices doesn't allow iframe use the way we do it, + // opening a new window seems the best way to workaround + if (navigator.userAgent.match(/(iPod|iPhone|iPad)/)) { + var params = _.extend({}, options.data || {}, {token: token}); + var url = this.url(options.url, params); + return window.open(url); + } + + var $form, $form_data = $('<div>'); + + var complete = function () { + if (options.complete) { options.complete(); } + clearTimeout(timer); + $form_data.remove(); + $target.remove(); + if (remove_form && $form) { $form.remove(); } + }; + var $target = $('<iframe style="display: none;">') + .attr({id: id, name: id}) + .appendTo(document.body) + .load(function () { + try { + if (options.error) { + var body = this.contentDocument.body; + var nodes = body.children.length === 0 ? body.childNodes : body.children; + var node = nodes[1] || nodes[0]; + options.error(JSON.parse(node.textContent)); + } + } finally { + complete(); + } + }); + + if (options.form) { + $form = $(options.form); + } else { + remove_form = true; + $form = $('<form>', { + action: options.url, + method: 'POST' + }).appendTo(document.body); + } + + var hparams = _.extend({}, options.data || {}, {token: token}); + _.each(hparams, function (value, key) { + var $input = $form.find('[name=' + key +']'); + if (!$input.length) { + $input = $('<input type="hidden" name="' + key + '">') + .appendTo($form_data); + } + $input.val(value); + }); + + $form + .append($form_data) + .attr('target', id) + .get(0).submit(); + + var waitLoop = function () { + var cookies = document.cookie.split(';'); + // setup next check + timer = setTimeout(waitLoop, CHECK_INTERVAL); + for (var i=0; i<cookies.length; ++i) { + var cookie = cookies[i].replace(/^\s*/, ''); + if (!cookie.indexOf(cookie_name === 0)) { continue; } + var cookie_val = cookie.substring(cookie_length + 1); + if (parseInt(cookie_val, 10) !== token) { continue; } + + // clear cookie + document.cookie = _.str.sprintf("%s=;expires=%s;path=/", + cookie_name, new Date().toGMTString()); + if (options.success) { options.success(); } + complete(); + return; + } + }; + timer = setTimeout(waitLoop, CHECK_INTERVAL); +}; + + +return { + jsonRpc: jsonRpc, + jsonpRpc: jsonpRpc, + loadCSS: loadCSS, + loadJS: loadJS, + get_file: get_file, +}; + +}); |
