MDL-57139 amd/user_date: always return
[moodle.git] / lib / amd / src / user_date.js
CommitLineData
b61015cf
RW
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 * 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 */
24define(['jquery', 'core/ajax', 'core/sessionstorage', 'core/config'],
25 function($, Ajax, Storage, Config) {
26
27 /** @var {object} promisesCache Store all promises we've seen so far. */
28 var promisesCache = {};
29
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) {
f3870588 38 var language = $('html').attr('lang').replace(/-/g, '_');
b61015cf
RW
39 return 'core_user_date/' +
40 language + '/' +
41 Config.usertimezone + '/' +
42 request.timestamp + '/' +
43 request.format;
44 };
45
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 };
55
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 };
65
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 };
75
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 };
85
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 };
95
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 });
113
114 var request = {
115 methodname: 'core_get_user_dates',
116 args: {
117 contextid: Config.contextid,
118 timestamps: args
119 }
120 };
121
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);
126
127 addToLocalStorage(key, value);
128 date.deferred.resolve(value);
129 });
4276f9c7 130 return;
b61015cf 131 })
4276f9c7 132 .catch(function(ex) {
b61015cf
RW
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 };
140
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 = [];
181
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);
186
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);
193
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 }
207
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 });
214
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 }
220
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 };
230
231 return {
232 get: get
233 };
234});