MDL-57139 amd/user_date: always return
[moodle.git] / lib / amd / src / user_date.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  * Fetch and render dates from timestamps.
18  *
19  * @module     core/user_date
20  * @package    core
21  * @copyright  2017 Ryan Wyllie <ryan@moodle.com>
22  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23  */
24 define(['jquery', 'core/ajax', 'core/sessionstorage', 'core/config'],
25         function($, Ajax, Storage, Config) {
27     /** @var {object} promisesCache Store all promises we've seen so far. */
28     var promisesCache = {};
30     /**
31      * Generate a cache key for the given request. The request should
32      * have a timestamp and format key.
33      *
34      * @param {object} request
35      * @return {string}
36      */
37     var getKey = function(request) {
38         var language = $('html').attr('lang').replace(/-/g, '_');
39         return 'core_user_date/' +
40                language + '/' +
41                Config.usertimezone + '/' +
42                request.timestamp + '/' +
43                request.format;
44     };
46     /**
47      * Retrieve a transformed date from the browser's storage.
48      *
49      * @param {string} key
50      * @return {string}
51      */
52     var getFromLocalStorage = function(key) {
53         return Storage.get(key);
54     };
56     /**
57      * Save the transformed date in the browser's storage.
58      *
59      * @param {string} key
60      * @param {string} value
61      */
62     var addToLocalStorage = function(key, value) {
63         Storage.set(key, value);
64     };
66     /**
67      * Check if a key is in the module's cache.
68      *
69      * @param {string} key
70      * @return {bool}
71      */
72     var inPromisesCache = function(key) {
73         return (typeof promisesCache[key] !== 'undefined');
74     };
76     /**
77      * Retrieve a promise from the module's cache.
78      *
79      * @param {string} key
80      * @return {object} jQuery promise
81      */
82     var getFromPromisesCache = function(key) {
83         return promisesCache[key];
84     };
86     /**
87      * Save the given promise in the module's cache.
88      *
89      * @param {string} key
90      * @param {object} promise
91      */
92     var addToPromisesCache = function(key, promise) {
93         promisesCache[key] = promise;
94     };
96     /**
97      * Send a request to the server for each of the required timestamp
98      * and format combinations.
99      *
100      * Resolves the date's deferred with the values returned from the
101      * server and saves the value in local storage.
102      *
103      * @param {array} dates
104      * @return {object} jQuery promise
105      */
106     var loadDatesFromServer = function(dates) {
107         var args = dates.map(function(data) {
108             return {
109                 timestamp: data.timestamp,
110                 format: data.format
111             };
112         });
114         var request = {
115             methodname: 'core_get_user_dates',
116             args: {
117                 contextid: Config.contextid,
118                 timestamps: args
119             }
120         };
122         return Ajax.call([request], true, true)[0].then(function(results) {
123             results.dates.forEach(function(value, index) {
124                 var date = dates[index];
125                 var key = getKey(date);
127                 addToLocalStorage(key, value);
128                 date.deferred.resolve(value);
129             });
130             return;
131         })
132         .catch(function(ex) {
133             // If we failed to retrieve the dates then reject the date's
134             // deferred objects to make sure they don't hang.
135             dates.forEach(function(date) {
136                 date.deferred.reject(ex);
137             });
138         });
139     };
141     /**
142      * Takes an array of request objects and returns a promise that
143      * is resolved with an array of formatted dates.
144      *
145      * The values in the returned array will be ordered the same as
146      * the request array.
147      *
148      * This function will check both the module's static promises cache
149      * and the browser's session storage to see if the user dates have
150      * already been loaded in order to avoid sending a network request
151      * if possible.
152      *
153      * Only dates not found in either cache will be sent to the server
154      * for transforming.
155      *
156      * A request object must have a timestamp key and a format key.
157      *
158      * E.g.
159      * var request = [
160      *     {
161      *         timestamp: 1293876000,
162      *         format: '%d %B %Y'
163      *     },
164      *     {
165      *         timestamp: 1293876000,
166      *         format: '%A, %d %B %Y, %I:%M %p'
167      *     }
168      * ];
169      *
170      * UserDate.get(request).done(function(dates) {
171      *     console.log(dates[0]); // prints "1 January 2011".
172      *     console.log(dates[1]); // prints "Saturday, 1 January 2011, 10:00 AM".
173      * });
174      *
175      * @param {array} requests
176      * @return {object} jQuery promise
177      */
178     var get = function(requests) {
179         var ajaxRequests = [];
180         var promises = [];
182         // Loop over each of the requested timestamp/format combos
183         // and add a promise to the promises array for them.
184         requests.forEach(function(request) {
185             var key = getKey(request);
187             // If we've already got a promise then use it.
188             if (inPromisesCache(key)) {
189                 promises.push(getFromPromisesCache(key));
190             } else {
191                 var deferred = $.Deferred();
192                 var cached = getFromLocalStorage(key);
194                 if (cached) {
195                     // If we were able to get the value from session storage
196                     // then we can resolve the deferred with that value. No
197                     // need to ask the server to transform it for us.
198                     deferred.resolve(cached);
199                 } else {
200                     // Add this request to the list of ones we need to load
201                     // from the server. Include the deferred so that it can
202                     // be resolved when the server has responded with the
203                     // transformed values.
204                     request.deferred = deferred;
205                     ajaxRequests.push(request);
206                 }
208                 // Remember this promise for next time so that we can
209                 // bail out early if it is requested again.
210                 addToPromisesCache(key, deferred.promise());
211                 promises.push(deferred.promise());
212             }
213         });
215         // If we have any requests that we couldn't resolve from the caches
216         // then let's ask the server to get them for us.
217         if (ajaxRequests.length) {
218             loadDatesFromServer(ajaxRequests);
219         }
221         // Wait for all of the promises to resolve. Some of them may be waiting
222         // for a response from the server.
223         return $.when.apply($, promises).then(function() {
224             // This looks complicated but it's just converting an unknown
225             // length of arguments into an array for the promise to resolve
226             // with.
227             return arguments.length === 1 ? [arguments[0]] : Array.apply(null, arguments);
228         });
229     };
231     return {
232         get: get
233     };
234 });