3130fdc85eba28e3dbebbdf4e4bde3253d55170f
[moodle.git] / lib / amd / src / ajax.js
1 // This file is part of Moodle - http://moodle.org/
2 //
3 // Moodle is free software: you can redistribute it and/or modify
4 // it under the terms of the GNU General Public License as published by
5 // the Free Software Foundation, either version 3 of the License, or
6 // (at your option) any later version.
7 //
8 // Moodle is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 // GNU General Public License for more details.
12 //
13 // You should have received a copy of the GNU General Public License
14 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
16 /**
17  * Standard Ajax wrapper for Moodle. It calls the central Ajax script,
18  * which can call any existing webservice using the current session.
19  * In addition, it can batch multiple requests and return multiple responses.
20  *
21  * @module     core/ajax
22  * @class      ajax
23  * @package    core
24  * @copyright  2015 Damyon Wiese <damyon@moodle.com>
25  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
26  * @since      2.9
27  */
28 define(['jquery', 'core/config', 'core/log', 'core/yui', 'core/url'], function($, config, Log, Y, URL) {
30     // Keeps track of when the user leaves the page so we know not to show an error.
31     var unloading = false;
33     /**
34      * Success handler. Called when the ajax call succeeds. Checks each response and
35      * resolves or rejects the deferred from that request.
36      *
37      * @method requestSuccess
38      * @private
39      * @param {Object[]} responses Array of responses containing error, exception and data attributes.
40      */
41     var requestSuccess = function(responses) {
42         // Call each of the success handlers.
43         var requests = this;
44         var exception = null;
45         var i = 0;
46         var request;
47         var response;
49         if (responses.error) {
50             // There was an error with the request as a whole.
51             // We need to reject each promise.
52             // Unfortunately this may lead to duplicate dialogues, but each Promise must be rejected.
53             for (; i < requests.length; i++) {
54                 request = requests[i];
55                 request.deferred.reject(responses);
56             }
58             return;
59         }
61         for (i = 0; i < requests.length; i++) {
62             request = requests[i];
64             response = responses[i];
65             // We may not have responses for all the requests.
66             if (typeof response !== "undefined") {
67                 if (response.error === false) {
68                     // Call the done handler if it was provided.
69                     request.deferred.resolve(response.data);
70                 } else {
71                     exception = response.exception;
72                     break;
73                 }
74             } else {
75                 // This is not an expected case.
76                 exception = new Error('missing response');
77                 break;
78             }
79         }
80         // Something failed, reject the remaining promises.
81         if (exception !== null) {
82             // If the user isn't doing anything too important, redirect to the login page.
83             if (exception.errorcode === "servicerequireslogin") {
84                 Y.use('moodle-core-formchangechecker', function() {
85                     if (!M.core_formchangechecker.get_form_dirty_state()) {
86                         // If we reach here, the user isn't editing anything on the page.
87                         var loginUrl = URL.relativeUrl("/login/index.php");
88                         window.location.replace(loginUrl);
89                     }
90                 });
91             } else {
92                 for (; i < requests.length; i++) {
93                     request = requests[i];
94                     request.deferred.reject(exception);
95                 }
96             }
97         }
98     };
100     /**
101      * Fail handler. Called when the ajax call fails. Rejects all deferreds.
102      *
103      * @method requestFail
104      * @private
105      * @param {jqXHR} jqXHR The ajax object.
106      * @param {string} textStatus The status string.
107      * @param {Error|Object} exception The error thrown.
108      */
109     var requestFail = function(jqXHR, textStatus, exception) {
110         // Reject all the promises.
111         var requests = this;
113         var i = 0;
114         for (i = 0; i < requests.length; i++) {
115             var request = requests[i];
117             if (unloading) {
118                 // No need to trigger an error because we are already navigating.
119                 Log.error("Page unloaded.");
120                 Log.error(exception);
121             } else {
122                 request.deferred.reject(exception);
123             }
124         }
125     };
127     return /** @alias module:core/ajax */ {
128         // Public variables and functions.
129         /**
130          * Make a series of ajax requests and return all the responses.
131          *
132          * @method call
133          * @param {Object[]} requests Array of requests with each containing methodname and args properties.
134          *                   done and fail callbacks can be set for each element in the array, or the
135          *                   can be attached to the promises returned by this function.
136          * @param {Boolean} async Optional, defaults to true.
137          *                  If false - this function will not return until the promises are resolved.
138          * @param {Boolean} loginrequired Optional, defaults to true.
139          *                  If false - this function will call the faster nologin ajax script - but
140          *                  will fail unless all functions have been marked as 'loginrequired' => false
141          *                  in services.php
142          * @return {Promise[]} Array of promises that will be resolved when the ajax call returns.
143          */
144         call: function(requests, async, loginrequired) {
145             $(window).bind('beforeunload', function() {
146                 unloading = true;
147             });
148             var ajaxRequestData = [],
149                 i,
150                 promises = [],
151                 methodInfo = [],
152                 requestInfo = '';
154             if (typeof loginrequired === "undefined") {
155                 loginrequired = true;
156             }
157             if (typeof async === "undefined") {
158                 async = true;
159             }
160             for (i = 0; i < requests.length; i++) {
161                 var request = requests[i];
162                 ajaxRequestData.push({
163                     index: i,
164                     methodname: request.methodname,
165                     args: request.args
166                 });
167                 request.deferred = $.Deferred();
168                 promises.push(request.deferred.promise());
169                 // Allow setting done and fail handlers as arguments.
170                 // This is just a shortcut for the calling code.
171                 if (typeof request.done !== "undefined") {
172                     request.deferred.done(request.done);
173                 }
174                 if (typeof request.fail !== "undefined") {
175                     request.deferred.fail(request.fail);
176                 }
177                 request.index = i;
178                 methodInfo.push(request.methodname);
179             }
181             if (methodInfo.length <= 5) {
182                 requestInfo = methodInfo.sort().join();
183             } else {
184                 requestInfo = methodInfo.length + '-method-calls';
185             }
187             ajaxRequestData = JSON.stringify(ajaxRequestData);
188             var settings = {
189                 type: 'POST',
190                 data: ajaxRequestData,
191                 context: requests,
192                 dataType: 'json',
193                 processData: false,
194                 async: async,
195                 contentType: "application/json"
196             };
198             var script = 'service.php';
199             if (!loginrequired) {
200                 script = 'service-nologin.php';
201             }
202             var url = config.wwwroot + '/lib/ajax/' + script +
203                     '?sesskey=' + config.sesskey + '&info=' + requestInfo;
205             // Jquery deprecated done and fail with async=false so we need to do this 2 ways.
206             if (async) {
207                 $.ajax(url, settings)
208                     .done(requestSuccess)
209                     .fail(requestFail);
210             } else {
211                 settings.success = requestSuccess;
212                 settings.error = requestFail;
213                 $.ajax(url, settings);
214             }
216             return promises;
217         }
218     };
219 });