|
1 /* |
|
2 YUI 3.10.3 (build 2fb5187) |
|
3 Copyright 2013 Yahoo! Inc. All rights reserved. |
|
4 Licensed under the BSD License. |
|
5 http://yuilibrary.com/license/ |
|
6 */ |
|
7 |
|
8 YUI.add('cache-offline', function (Y, NAME) { |
|
9 |
|
10 /** |
|
11 * Provides a Cache subclass which uses HTML5 `localStorage` for persistence. |
|
12 * |
|
13 * @module cache |
|
14 * @submodule cache-offline |
|
15 */ |
|
16 |
|
17 /** |
|
18 * Extends Cache utility with offline functionality. |
|
19 * @class CacheOffline |
|
20 * @extends Cache |
|
21 * @constructor |
|
22 */ |
|
23 function CacheOffline() { |
|
24 CacheOffline.superclass.constructor.apply(this, arguments); |
|
25 } |
|
26 |
|
27 var localStorage = null, |
|
28 JSON = Y.JSON; |
|
29 |
|
30 // Bug 2529572 |
|
31 try { |
|
32 localStorage = Y.config.win.localStorage; |
|
33 } |
|
34 catch(e) { |
|
35 Y.log("Could not access localStorage.", "warn", "cache"); |
|
36 } |
|
37 |
|
38 ///////////////////////////////////////////////////////////////////////////// |
|
39 // |
|
40 // CacheOffline events |
|
41 // |
|
42 ///////////////////////////////////////////////////////////////////////////// |
|
43 |
|
44 /** |
|
45 * @event error |
|
46 * @description Fired when an entry could not be added, most likely due to |
|
47 * exceeded browser quota. |
|
48 * <dl> |
|
49 * <dt>error (Object)</dt> <dd>The error object.</dd> |
|
50 * </dl> |
|
51 */ |
|
52 |
|
53 ///////////////////////////////////////////////////////////////////////////// |
|
54 // |
|
55 // CacheOffline static |
|
56 // |
|
57 ///////////////////////////////////////////////////////////////////////////// |
|
58 Y.mix(CacheOffline, { |
|
59 /** |
|
60 * Class name. |
|
61 * |
|
62 * @property NAME |
|
63 * @type String |
|
64 * @static |
|
65 * @final |
|
66 * @value "cacheOffline" |
|
67 */ |
|
68 NAME: "cacheOffline", |
|
69 |
|
70 ATTRS: { |
|
71 ///////////////////////////////////////////////////////////////////////////// |
|
72 // |
|
73 // CacheOffline Attributes |
|
74 // |
|
75 ///////////////////////////////////////////////////////////////////////////// |
|
76 |
|
77 /** |
|
78 * @attribute sandbox |
|
79 * @description A string that must be passed in via the constructor. |
|
80 * This identifier is used to sandbox one cache instance's entries |
|
81 * from another. Calling the cache instance's flush and length methods |
|
82 * or get("entries") will apply to only these sandboxed entries. |
|
83 * @type String |
|
84 * @default "default" |
|
85 * @initOnly |
|
86 */ |
|
87 sandbox: { |
|
88 value: "default", |
|
89 writeOnce: "initOnly" |
|
90 }, |
|
91 |
|
92 /** |
|
93 * @attribute expires |
|
94 * @description Absolute Date when data expires or |
|
95 * relative number of milliseconds. Zero disables expiration. |
|
96 * @type Date | Number |
|
97 * @default 86400000 (one day) |
|
98 */ |
|
99 expires: { |
|
100 value: 86400000 |
|
101 }, |
|
102 |
|
103 /** |
|
104 * @attribute max |
|
105 * @description Disabled. |
|
106 * @readOnly |
|
107 * @default null |
|
108 */ |
|
109 max: { |
|
110 value: null, |
|
111 readOnly: true |
|
112 }, |
|
113 |
|
114 /** |
|
115 * @attribute uniqueKeys |
|
116 * @description Always true for CacheOffline. |
|
117 * @readOnly |
|
118 * @default true |
|
119 */ |
|
120 uniqueKeys: { |
|
121 value: true, |
|
122 readOnly: true, |
|
123 setter: function() { |
|
124 return true; |
|
125 } |
|
126 } |
|
127 }, |
|
128 |
|
129 /** |
|
130 * Removes all items from all sandboxes. Useful if localStorage has |
|
131 * exceeded quota. Only supported on browsers that implement HTML 5 |
|
132 * localStorage. |
|
133 * |
|
134 * @method flushAll |
|
135 * @static |
|
136 */ |
|
137 flushAll: function() { |
|
138 var store = localStorage, key; |
|
139 if(store) { |
|
140 if(store.clear) { |
|
141 store.clear(); |
|
142 } |
|
143 // FF2.x and FF3.0.x |
|
144 else { |
|
145 for (key in store) { |
|
146 if (store.hasOwnProperty(key)) { |
|
147 store.removeItem(key); |
|
148 delete store[key]; |
|
149 } |
|
150 } |
|
151 } |
|
152 Y.log("All sandboxes of OfflineCache flushed", "info", "cache"); |
|
153 } |
|
154 else { |
|
155 Y.log("Could not flush all OfflineCache sandboxes.", "warn", "cache"); |
|
156 } |
|
157 } |
|
158 }); |
|
159 |
|
160 Y.extend(CacheOffline, Y.Cache, localStorage ? { |
|
161 ///////////////////////////////////////////////////////////////////////////// |
|
162 // |
|
163 // Offline is supported |
|
164 // |
|
165 ///////////////////////////////////////////////////////////////////////////// |
|
166 |
|
167 ///////////////////////////////////////////////////////////////////////////// |
|
168 // |
|
169 // CacheOffline protected methods |
|
170 // |
|
171 ///////////////////////////////////////////////////////////////////////////// |
|
172 /** |
|
173 * Always return null. |
|
174 * |
|
175 * @method _setMax |
|
176 * @protected |
|
177 */ |
|
178 _setMax: function(value) { |
|
179 return null; |
|
180 }, |
|
181 |
|
182 /** |
|
183 * Gets size. |
|
184 * |
|
185 * @method _getSize |
|
186 * @protected |
|
187 */ |
|
188 _getSize: function() { |
|
189 var count = 0, |
|
190 i=0, |
|
191 l=localStorage.length; |
|
192 for(; i<l; ++i) { |
|
193 // Match sandbox id |
|
194 if(localStorage.key(i).indexOf(this.get("sandbox")) === 0) { |
|
195 count++; |
|
196 } |
|
197 } |
|
198 return count; |
|
199 }, |
|
200 |
|
201 /** |
|
202 * Gets all entries. |
|
203 * |
|
204 * @method _getEntries |
|
205 * @protected |
|
206 */ |
|
207 _getEntries: function() { |
|
208 var entries = [], |
|
209 i=0, |
|
210 l=localStorage.length, |
|
211 sandbox = this.get("sandbox"); |
|
212 for(; i<l; ++i) { |
|
213 // Match sandbox id |
|
214 if(localStorage.key(i).indexOf(sandbox) === 0) { |
|
215 entries[i] = JSON.parse(localStorage.key(i).substring(sandbox.length)); |
|
216 } |
|
217 } |
|
218 return entries; |
|
219 }, |
|
220 |
|
221 /** |
|
222 * Adds entry to cache. |
|
223 * |
|
224 * @method _defAddFn |
|
225 * @param e {Event.Facade} Event Facade with the following properties: |
|
226 * <dl> |
|
227 * <dt>entry (Object)</dt> <dd>The cached entry.</dd> |
|
228 * </dl> |
|
229 * @protected |
|
230 */ |
|
231 _defAddFn: function(e) { |
|
232 var entry = e.entry, |
|
233 request = entry.request, |
|
234 cached = entry.cached, |
|
235 expires = entry.expires; |
|
236 |
|
237 // Convert Dates to msecs on the way into localStorage |
|
238 entry.cached = cached.getTime(); |
|
239 entry.expires = expires ? expires.getTime() : expires; |
|
240 |
|
241 try { |
|
242 localStorage.setItem(this.get("sandbox")+JSON.stringify({"request":request}), JSON.stringify(entry)); |
|
243 Y.log("Cached offline entry: " + Y.dump(entry), "info", "cache"); |
|
244 } |
|
245 catch(error) { |
|
246 this.fire("error", {error:error}); |
|
247 Y.log("Could not cache offline entry: " + Y.dump(entry) + |
|
248 " due to error: " + Y.dump(error), "warn", "cache"); |
|
249 } |
|
250 }, |
|
251 |
|
252 /** |
|
253 * Flushes cache. |
|
254 * |
|
255 * @method _defFlushFn |
|
256 * @param e {Event.Facade} Event Facade object. |
|
257 * @protected |
|
258 */ |
|
259 _defFlushFn: function(e) { |
|
260 var key, |
|
261 i=localStorage.length-1; |
|
262 for(; i>-1; --i) { |
|
263 // Match sandbox id |
|
264 key = localStorage.key(i); |
|
265 if(key.indexOf(this.get("sandbox")) === 0) { |
|
266 localStorage.removeItem(key); |
|
267 } |
|
268 } |
|
269 }, |
|
270 |
|
271 ///////////////////////////////////////////////////////////////////////////// |
|
272 // |
|
273 // CacheOffline public methods |
|
274 // |
|
275 ///////////////////////////////////////////////////////////////////////////// |
|
276 /** |
|
277 * Adds a new entry to the cache of the format |
|
278 * {request:request, response:response, cached:cached, expires: expires}. |
|
279 * |
|
280 * @method add |
|
281 * @param request {Object} Request value must be a String or JSON. |
|
282 * @param response {Object} Response value must be a String or JSON. |
|
283 */ |
|
284 |
|
285 /** |
|
286 * Retrieves cached object for given request, if available. |
|
287 * Returns null if there is no cache match. |
|
288 * |
|
289 * @method retrieve |
|
290 * @param request {Object} Request object. |
|
291 * @return {Object} Cached object with the properties request, response, |
|
292 * and expires, or null. |
|
293 */ |
|
294 retrieve: function(request) { |
|
295 this.fire("request", {request: request}); |
|
296 |
|
297 var entry, expires, sandboxedrequest; |
|
298 |
|
299 try { |
|
300 sandboxedrequest = this.get("sandbox")+JSON.stringify({"request":request}); |
|
301 try { |
|
302 entry = JSON.parse(localStorage.getItem(sandboxedrequest)); |
|
303 } |
|
304 catch(e) { |
|
305 } |
|
306 } |
|
307 catch(e2) { |
|
308 } |
|
309 |
|
310 if(entry) { |
|
311 // Convert msecs to Dates on the way out of localStorage |
|
312 entry.cached = new Date(entry.cached); |
|
313 expires = entry.expires; |
|
314 expires = !expires ? null : new Date(expires); |
|
315 entry.expires = expires; |
|
316 |
|
317 if(this._isMatch(request, entry)) { |
|
318 this.fire("retrieve", {entry: entry}); |
|
319 Y.log("Retrieved offlinecached response: " + Y.dump(entry) + |
|
320 " for request: " + Y.dump(request), "info", "cache"); |
|
321 return entry; |
|
322 } |
|
323 } |
|
324 return null; |
|
325 } |
|
326 } : |
|
327 ///////////////////////////////////////////////////////////////////////////// |
|
328 // |
|
329 // Offline is not supported |
|
330 // |
|
331 ///////////////////////////////////////////////////////////////////////////// |
|
332 { |
|
333 /** |
|
334 * Always return null. |
|
335 * |
|
336 * @method _setMax |
|
337 * @protected |
|
338 */ |
|
339 _setMax: function(value) { |
|
340 return null; |
|
341 } |
|
342 }); |
|
343 |
|
344 |
|
345 Y.CacheOffline = CacheOffline; |
|
346 |
|
347 |
|
348 }, '3.10.3', {"requires": ["cache-base", "json"]}); |