Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 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/>.
15
 
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
 * @copyright  2015 Damyon Wiese <damyon@moodle.com>
23
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24
 * @since      2.9
25
 */
26
define(['jquery', 'core/config', 'core/log', 'core/url'], function($, config, Log, URL) {
27
 
28
/**
29
 * A request to be performed.
30
 *
31
 * @typedef {object} request
32
 * @property {string} methodname The remote method to be called
33
 * @property {object} args The arguments to pass when fetching the remote content
34
 */
35
 
36
    // Keeps track of when the user leaves the page so we know not to show an error.
37
    var unloading = false;
38
 
39
    /**
40
     * Success handler. Called when the ajax call succeeds. Checks each response and
41
     * resolves or rejects the deferred from that request.
42
     *
43
     * @method requestSuccess
44
     * @private
45
     * @param {Object[]} responses Array of responses containing error, exception and data attributes.
46
     */
47
    var requestSuccess = function(responses) {
48
        // Call each of the success handlers.
49
        var requests = this,
50
            exception = null,
51
            i = 0,
52
            request,
53
            response,
54
            nosessionupdate;
55
 
56
        if (responses.error) {
57
            // There was an error with the request as a whole.
58
            // We need to reject each promise.
59
            // Unfortunately this may lead to duplicate dialogues, but each Promise must be rejected.
60
            for (; i < requests.length; i++) {
61
                request = requests[i];
62
                request.deferred.reject(responses);
63
            }
64
 
65
            return;
66
        }
67
 
68
        for (i = 0; i < requests.length; i++) {
69
            request = requests[i];
70
 
71
            response = responses[i];
72
            // We may not have responses for all the requests.
73
            if (typeof response !== "undefined") {
74
                if (response.error === false) {
75
                    // Call the done handler if it was provided.
76
                    request.deferred.resolve(response.data);
77
                } else {
78
                    exception = response.exception;
79
                    nosessionupdate = requests[i].nosessionupdate;
80
                    break;
81
                }
82
            } else {
83
                // This is not an expected case.
84
                exception = new Error('missing response');
85
                break;
86
            }
87
        }
88
        // Something failed, reject the remaining promises.
89
        if (exception !== null) {
90
            // Redirect to the login page.
91
            if (exception.errorcode === "servicerequireslogin" && !nosessionupdate) {
92
                window.location = URL.relativeUrl("/login/index.php");
93
            } else {
94
                requests.forEach(function(request) {
95
                    request.deferred.reject(exception);
96
                });
97
            }
98
        }
99
    };
100
 
101
    /**
102
     * Fail handler. Called when the ajax call fails. Rejects all deferreds.
103
     *
104
     * @method requestFail
105
     * @private
106
     * @param {jqXHR} jqXHR The ajax object.
107
     * @param {string} textStatus The status string.
108
     * @param {Error|Object} exception The error thrown.
109
     */
110
    var requestFail = function(jqXHR, textStatus, exception) {
111
        // Reject all the promises.
112
        var requests = this;
113
 
114
        var i = 0;
115
        for (i = 0; i < requests.length; i++) {
116
            var request = requests[i];
117
 
118
            if (unloading) {
119
                // No need to trigger an error because we are already navigating.
120
                Log.error("Page unloaded.");
121
                Log.error(exception);
122
            } else {
123
                request.deferred.reject(exception);
124
            }
125
        }
126
    };
127
 
128
    return /** @alias module:core/ajax */ {
129
        // Public variables and functions.
130
        /**
131
         * Make a series of ajax requests and return all the responses.
132
         *
133
         * @method call
134
         * @param {request[]} requests Array of requests with each containing methodname and args properties.
135
         *                   done and fail callbacks can be set for each element in the array, or the
136
         *                   can be attached to the promises returned by this function.
137
         * @param {Boolean} [async=true] If false this function will not return until the promises are resolved.
138
         * @param {Boolean} [loginrequired=true] When false this function calls an endpoint which does not use the
139
         *                  session.
140
         *                  Note: This may only be used with external functions which have been marked as
141
         *                  `'loginrequired' => false`
142
         * @param {Boolean} [nosessionupdate=false] If true, the timemodified for the session will not be updated.
143
         * @param {Number}  [timeout] number of milliseconds to wait for a response. Defaults to no limit.
144
         * @param {Number}  [cachekey] A cache key used to improve browser-side caching.
145
         *                  Typically the same `cachekey` is used for all function calls.
146
         *                  When the key changes, this causes the URL used to perform the fetch to change, which
147
         *                  prevents the existing browser cache from being used.
148
         *                  Note: This option is only availbale when `loginrequired` is `false`.
149
         *                  See {@link https://tracker.moodle.org/browser/MDL-65794} for more information.
150
         * @return {Promise[]} The Promises for each of the supplied requests.
151
         *                  The order of the Promise matches the order of requests exactly.
152
         *
153
         * @example <caption>A simple example that you might find in a repository module</caption>
154
         *
155
         * import {call as fetchMany} from 'core/ajax';
156
         *
157
         * export const fetchMessages = timeSince => fetchMany([{methodname: 'core_message_get_messages', args: {timeSince}}])[0];
158
         *
159
         * export const fetchNotifications = timeSince => fetchMany([{
160
         *     methodname: 'core_message_get_notifications',
161
         *     args: {
162
         *         timeSince,
163
         *     }
164
         * }])[0];
165
         *
166
         * export const fetchSomethingElse = (some, params, here) => fetchMany([{
167
         *     methodname: 'core_get_something_else',
168
         *     args: {
169
         *         some,
170
         *         params,
171
         *         gohere: here,
172
         *     },
173
         * }])[0];
174
         *
175
         * @example <caption>An example of fetching a string using the cachekey parameter</caption>
176
         * import {call as fetchMany} from 'core/ajax';
177
         * import * as Notification from 'core/notification';
178
         *
179
         * export const performAction = (some, args) => {
180
         *     Promises.all(fetchMany([{methodname: 'core_get_string', args: {
181
         *         stringid: 'do_not_copy',
182
         *         component: 'core',
183
         *         lang: 'en',
184
         *         stringparams: [],
185
         *     }}], true, false, false, undefined, M.cfg.langrev))
186
         *     .then(([doNotCopyString]) => {
187
         *         window.console.log(doNotCopyString);
188
         *     })
189
         *     .catch(Notification.exception);
190
         * };
191
         *
192
         */
193
        call: function(requests, async, loginrequired, nosessionupdate, timeout, cachekey) {
194
            $(window).bind('beforeunload', function() {
195
                unloading = true;
196
            });
197
            var ajaxRequestData = [],
198
                i,
199
                promises = [],
200
                methodInfo = [],
201
                requestInfo = '';
202
 
203
            var maxUrlLength = 2000;
204
 
205
            if (typeof loginrequired === "undefined") {
206
                loginrequired = true;
207
            }
208
            if (typeof async === "undefined") {
209
                async = true;
210
            }
211
            if (typeof timeout === 'undefined') {
212
                timeout = 0;
213
            }
214
            if (typeof cachekey === 'undefined') {
215
                cachekey = null;
216
            } else {
217
                cachekey = parseInt(cachekey);
218
                if (cachekey <= 0) {
219
                    cachekey = null;
220
                } else if (!cachekey) {
221
                    cachekey = null;
222
                }
223
            }
224
 
225
            if (typeof nosessionupdate === "undefined") {
226
                nosessionupdate = false;
227
            }
228
            for (i = 0; i < requests.length; i++) {
229
                var request = requests[i];
230
                ajaxRequestData.push({
231
                    index: i,
232
                    methodname: request.methodname,
233
                    args: request.args
234
                });
235
                request.nosessionupdate = nosessionupdate;
236
                request.deferred = $.Deferred();
237
                promises.push(request.deferred.promise());
238
                // Allow setting done and fail handlers as arguments.
239
                // This is just a shortcut for the calling code.
240
                if (typeof request.done !== "undefined") {
241
                    request.deferred.done(request.done);
242
                }
243
                if (typeof request.fail !== "undefined") {
244
                    request.deferred.fail(request.fail);
245
                }
246
                request.index = i;
247
                methodInfo.push(request.methodname);
248
            }
249
 
250
            if (methodInfo.length <= 5) {
251
                requestInfo = methodInfo.sort().join();
252
            } else {
253
                requestInfo = methodInfo.length + '-method-calls';
254
            }
255
 
256
            ajaxRequestData = JSON.stringify(ajaxRequestData);
257
            var settings = {
258
                type: 'POST',
259
                context: requests,
260
                dataType: 'json',
261
                processData: false,
262
                async: async,
263
                contentType: "application/json",
264
                timeout: timeout
265
            };
266
 
267
            var script = 'service.php';
268
            var url = config.wwwroot + '/lib/ajax/';
269
            if (!loginrequired) {
270
                script = 'service-nologin.php';
271
                url += script + '?info=' + requestInfo;
272
                if (cachekey) {
273
                    url += '&cachekey=' + cachekey;
274
                    settings.type = 'GET';
275
                }
276
            } else {
277
                url += script + '?sesskey=' + config.sesskey + '&info=' + requestInfo;
278
            }
279
 
280
            if (nosessionupdate) {
281
                url += '&nosessionupdate=true';
282
            }
283
 
284
            if (settings.type === 'POST') {
285
                settings.data = ajaxRequestData;
286
            } else {
287
                var urlUseGet = url + '&args=' + encodeURIComponent(ajaxRequestData);
288
 
289
                if (urlUseGet.length > maxUrlLength) {
290
                    settings.type = 'POST';
291
                    settings.data = ajaxRequestData;
292
                } else {
293
                    url = urlUseGet;
294
                }
295
            }
296
 
297
            // Jquery deprecated done and fail with async=false so we need to do this 2 ways.
298
            if (async) {
299
                $.ajax(url, settings)
300
                    .done(requestSuccess)
301
                    .fail(requestFail);
302
            } else {
303
                settings.success = requestSuccess;
304
                settings.error = requestFail;
305
                $.ajax(url, settings);
306
            }
307
 
308
            return promises;
309
        }
310
    };
311
});