d739f540c39619b3fa9726fce7a4e3de88b042b4
[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         })
131         .fail(function(ex) {
132             // If we failed to retrieve the dates then reject the date's
133             // deferred objects to make sure they don't hang.
134             dates.forEach(function(date) {
135                 date.deferred.reject(ex);
136             });
137         });
138     };
140     /**
141      * Takes an array of request objects and returns a promise that
142      * is resolved with an array of formatted dates.
143      *
144      * The values in the returned array will be ordered the same as
145      * the request array.
146      *
147      * This function will check both the module's static promises cache
148      * and the browser's session storage to see if the user dates have
149      * already been loaded in order to avoid sending a network request
150      * if possible.
151      *
152      * Only dates not found in either cache will be sent to the server
153      * for transforming.
154      *
155      * A request object must have a timestamp key and a format key.
156      *
157      * E.g.
158      * var request = [
159      *     {
160      *         timestamp: 1293876000,
161      *         format: '%d %B %Y'
162      *     },
163      *     {
164      *         timestamp: 1293876000,
165      *         format: '%A, %d %B %Y, %I:%M %p'
166      *     }
167      * ];
168      *
169      * UserDate.get(request).done(function(dates) {
170      *     console.log(dates[0]); // prints "1 January 2011".
171      *     console.log(dates[1]); // prints "Saturday, 1 January 2011, 10:00 AM".
172      * });
173      *
174      * @param {array} requests
175      * @return {object} jQuery promise
176      */
177     var get = function(requests) {
178         var ajaxRequests = [];
179         var promises = [];
181         // Loop over each of the requested timestamp/format combos
182         // and add a promise to the promises array for them.
183         requests.forEach(function(request) {
184             var key = getKey(request);
186             // If we've already got a promise then use it.
187             if (inPromisesCache(key)) {
188                 promises.push(getFromPromisesCache(key));
189             } else {
190                 var deferred = $.Deferred();
191                 var cached = getFromLocalStorage(key);
193                 if (cached) {
194                     // If we were able to get the value from session storage
195                     // then we can resolve the deferred with that value. No
196                     // need to ask the server to transform it for us.
197                     deferred.resolve(cached);
198                 } else {
199                     // Add this request to the list of ones we need to load
200                     // from the server. Include the deferred so that it can
201                     // be resolved when the server has responded with the
202                     // transformed values.
203                     request.deferred = deferred;
204                     ajaxRequests.push(request);
205                 }
207                 // Remember this promise for next time so that we can
208                 // bail out early if it is requested again.
209                 addToPromisesCache(key, deferred.promise());
210                 promises.push(deferred.promise());
211             }
212         });
214         // If we have any requests that we couldn't resolve from the caches
215         // then let's ask the server to get them for us.
216         if (ajaxRequests.length) {
217             loadDatesFromServer(ajaxRequests);
218         }
220         // Wait for all of the promises to resolve. Some of them may be waiting
221         // for a response from the server.
222         return $.when.apply($, promises).then(function() {
223             // This looks complicated but it's just converting an unknown
224             // length of arguments into an array for the promise to resolve
225             // with.
226             return arguments.length === 1 ? [arguments[0]] : Array.apply(null, arguments);
227         });
228     };
230     return {
231         get: get
232     };
233 });