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