flectra.define('web.ajax', function (require) { "use strict"; var core = require('web.core'); var utils = require('web.utils'); var time = require('web.time'); function genericJsonRpc (fct_name, params, settings, fct) { var shadow = settings.shadow || false; delete settings.shadow; if (! shadow) core.bus.trigger('rpc_request'); 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) { core.bus.trigger('rpc:result', data, result); if (result.error !== undefined) { if (result.error.data.arguments[0] !== "bus.Bus not available in test mode") { console.error("Server application error", JSON.stringify(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(); }; var p = result.then(function (result) { if (!shadow) { core.bus.trigger('rpc_response'); } return result; }, function (type, error, textStatus, errorThrown) { if (type === "server") { if (!shadow) { core.bus.trigger('rpc_response'); } if (error.code === 100) { core.bus.trigger('invalidate_session'); } return $.Deferred().reject(error, $.Event()); } else { if (!shadow) { core.bus.trigger('rpc_response_failed'); } var nerror = { code: -32098, message: "XmlHttpRequestError " + errorThrown, data: { type: "xhr"+textStatus, debug: error.responseText, objects: [error, errorThrown] }, }; return $.Deferred().reject(nerror, $.Event()); } }); return p.fail(function () { // Allow deferred user to disable rpc_error call in fail p.fail(function (error, event) { if (!event.isDefaultPrevented()) { core.bus.trigger('rpc_error', error, event); } }); }); } function jsonRpc(url, fct_name, params, settings) { settings = settings || {}; return genericJsonRpc(fct_name, params, settings, 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, settings, 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 = ""; var $iframe = $(html); var nurl = 'jsonp=1&' + $.param(args); nurl = url.indexOf("?") !== -1 ? url + "&" + nurl : url + "?" + nurl; var $form = $('
') .attr('method', 'POST') .attr('target', ifid) .attr('enctype', "multipart/form-data") .attr('action', nurl) .append($('').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 to make a rpc with a function name hardcoded to 'call' function rpc(url, params, settings) { return jsonRpc(url, 'call', params, settings); } // 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($('', { 'href': url, 'rel': 'stylesheet', 'type': 'text/css' })); } } var loadJS = (function () { var urls = []; var defs = []; var load = function loadJS(url) { // Check the DOM to see if a script with the specified url is already there var alreadyRequired = ($('script[src="' + url + '"]').length > 0); // If loadJS was already called with the same URL, it will have a registered deferred indicating if // the script has been fully loaded. If not, the deferred has to be initialized. This is initialized // as already resolved if the script was already there without the need of loadJS. var index = _.indexOf(urls, url); if (index < 0) { urls.push(url); index = defs.push(alreadyRequired ? $.when() : $.Deferred()) - 1; } // Get the script associated deferred and returns it after initializing the script if needed. The // deferred is marked to be resolved on script load and rejected on script error. var def = defs[index]; if (!alreadyRequired) { 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; }; return load; })(); /** * 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 * @returns {boolean} a false value means that a popup window was blocked. This * mean that we probably need to inform the user that something needs to be * changed to make it work. */ 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 = options.session.url(options.url, params); if (options.complete) { options.complete(); } var w = window.open(url); if (!w || w.closed || typeof w.closed === 'undefined') { // popup was blocked return false; } return true; } var $form, $form_data = $('
'); var complete = function () { if (options.complete) { options.complete(); } clearTimeout(timer); $form_data.remove(); $target.remove(); if (remove_form && $form) { $form.remove(); } }; var $target = $('