81 | var YourCtrl = function($scope, localStorageService, ...) {
82 | // To add to local storage
83 | localStorageService.set('localStorageKey','Add this!');
84 | // Read that value back
85 | var value = localStorageService.get('localStorageKey');
86 | // To remove a local storage
87 | localStorageService.remove('localStorageKey');
88 | // Removes all local storage
89 | localStorageService.clearAll();
90 | // You can also play with cookies the same way
91 | localStorageService.cookie.set('localStorageKey','I am a cookie value now');
92 | }
93 |
94 |
API Access
95 |
96 |
97 |
98 |
99 |
Call
100 |
Arguments
101 |
Action
102 |
Returns
103 |
104 |
105 |
106 |
107 |
isSupported
108 |
n/a
109 |
Checks the browsers support for local storage
110 |
Boolean for success
111 |
112 |
113 |
set
114 |
key, value
115 |
Adds a key-value pair to the browser local storage
116 |
Boolean for success
117 |
118 |
119 |
get
120 |
key
121 |
Gets a value from local storage
122 |
Stored value
123 |
124 |
125 |
remove
126 |
key, ...
127 |
Removes a key-value pairs from the browser local storage
128 |
n/a
129 |
130 |
131 |
clearAll
132 |
n/a
133 |
Warning Removes all local storage key-value pairs for this app. It will optionally take a string, which is converted to a regular expression, and then clears keys based on that regular expression.
134 |
Boolean for success
135 |
136 |
137 |
cookie
138 |
set | get | remove | clearAll
139 |
Each function within cookie uses the same arguments as the coresponding local storage functions
140 |
n/a
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
--------------------------------------------------------------------------------
/dist/angular-local-storage.js:
--------------------------------------------------------------------------------
1 | /**
2 | * An Angular module that gives you access to the browsers local storage
3 | * @version v0.7.1 - 2017-06-21
4 | * @link https://github.com/grevory/angular-local-storage
5 | * @author grevory
6 | * @license MIT License, http://www.opensource.org/licenses/MIT
7 | */
8 | (function (window, angular) {
9 | var isDefined = angular.isDefined,
10 | isUndefined = angular.isUndefined,
11 | isNumber = angular.isNumber,
12 | isObject = angular.isObject,
13 | isArray = angular.isArray,
14 | isString = angular.isString,
15 | extend = angular.extend,
16 | toJson = angular.toJson;
17 |
18 | angular
19 | .module('LocalStorageModule', [])
20 | .provider('localStorageService', function() {
21 | // You should set a prefix to avoid overwriting any local storage variables from the rest of your app
22 | // e.g. localStorageServiceProvider.setPrefix('yourAppName');
23 | // With provider you can use config as this:
24 | // myApp.config(function (localStorageServiceProvider) {
25 | // localStorageServiceProvider.prefix = 'yourAppName';
26 | // });
27 | this.prefix = 'ls';
28 |
29 | // You could change web storage type localstorage or sessionStorage
30 | this.storageType = 'localStorage';
31 |
32 | // Cookie options (usually in case of fallback)
33 | // expiry = Number of days before cookies expire // 0 = Does not expire
34 | // path = The web path the cookie represents
35 | // secure = Wether the cookies should be secure (i.e only sent on HTTPS requests)
36 | this.cookie = {
37 | expiry: 30,
38 | path: '/',
39 | secure: false
40 | };
41 |
42 | // Decides wether we should default to cookies if localstorage is not supported.
43 | this.defaultToCookie = true;
44 |
45 | // Send signals for each of the following actions?
46 | this.notify = {
47 | setItem: true,
48 | removeItem: false
49 | };
50 |
51 | // Setter for the prefix
52 | this.setPrefix = function(prefix) {
53 | this.prefix = prefix;
54 | return this;
55 | };
56 |
57 | // Setter for the storageType
58 | this.setStorageType = function(storageType) {
59 | this.storageType = storageType;
60 | return this;
61 | };
62 | // Setter for defaultToCookie value, default is true.
63 | this.setDefaultToCookie = function (shouldDefault) {
64 | this.defaultToCookie = !!shouldDefault; // Double-not to make sure it's a bool value.
65 | return this;
66 | };
67 | // Setter for cookie config
68 | this.setStorageCookie = function(exp, path, secure) {
69 | this.cookie.expiry = exp;
70 | this.cookie.path = path;
71 | this.cookie.secure = secure;
72 | return this;
73 | };
74 |
75 | // Setter for cookie domain
76 | this.setStorageCookieDomain = function(domain) {
77 | this.cookie.domain = domain;
78 | return this;
79 | };
80 |
81 | // Setter for notification config
82 | // itemSet & itemRemove should be booleans
83 | this.setNotify = function(itemSet, itemRemove) {
84 | this.notify = {
85 | setItem: itemSet,
86 | removeItem: itemRemove
87 | };
88 | return this;
89 | };
90 |
91 | this.$get = ['$rootScope', '$window', '$document', '$parse','$timeout', function($rootScope, $window, $document, $parse, $timeout) {
92 | var self = this;
93 | var prefix = self.prefix;
94 | var cookie = self.cookie;
95 | var notify = self.notify;
96 | var storageType = self.storageType;
97 | var webStorage;
98 |
99 | // When Angular's $document is not available
100 | if (!$document) {
101 | $document = document;
102 | } else if ($document[0]) {
103 | $document = $document[0];
104 | }
105 |
106 | // If there is a prefix set in the config lets use that with an appended period for readability
107 | if (prefix.substr(-1) !== '.') {
108 | prefix = !!prefix ? prefix + '.' : '';
109 | }
110 | var deriveQualifiedKey = function(key) {
111 | return prefix + key;
112 | };
113 |
114 | // Removes prefix from the key.
115 | var underiveQualifiedKey = function (key) {
116 | return key.replace(new RegExp('^' + prefix, 'g'), '');
117 | };
118 |
119 | // Check if the key is within our prefix namespace.
120 | var isKeyPrefixOurs = function (key) {
121 | return key.indexOf(prefix) === 0;
122 | };
123 |
124 | // Checks the browser to see if local storage is supported
125 | var checkSupport = function () {
126 | try {
127 | var supported = (storageType in $window && $window[storageType] !== null);
128 |
129 | // When Safari (OS X or iOS) is in private browsing mode, it appears as though localStorage
130 | // is available, but trying to call .setItem throws an exception.
131 | //
132 | // "QUOTA_EXCEEDED_ERR: DOM Exception 22: An attempt was made to add something to storage
133 | // that exceeded the quota."
134 | var key = deriveQualifiedKey('__' + Math.round(Math.random() * 1e7));
135 | if (supported) {
136 | webStorage = $window[storageType];
137 | webStorage.setItem(key, '');
138 | webStorage.removeItem(key);
139 | }
140 |
141 | return supported;
142 | } catch (e) {
143 | // Only change storageType to cookies if defaulting is enabled.
144 | if (self.defaultToCookie)
145 | storageType = 'cookie';
146 | $rootScope.$broadcast('LocalStorageModule.notification.error', e.message);
147 | return false;
148 | }
149 | };
150 | var browserSupportsLocalStorage = checkSupport();
151 |
152 | // Directly adds a value to local storage
153 | // If local storage is not available in the browser use cookies
154 | // Example use: localStorageService.add('library','angular');
155 | var addToLocalStorage = function (key, value, type) {
156 | var previousType = getStorageType();
157 |
158 | try {
159 | setStorageType(type);
160 |
161 | // Let's convert undefined values to null to get the value consistent
162 | if (isUndefined(value)) {
163 | value = null;
164 | } else {
165 | value = toJson(value);
166 | }
167 |
168 | // If this browser does not support local storage use cookies
169 | if (!browserSupportsLocalStorage && self.defaultToCookie || self.storageType === 'cookie') {
170 | if (!browserSupportsLocalStorage) {
171 | $rootScope.$broadcast('LocalStorageModule.notification.warning', 'LOCAL_STORAGE_NOT_SUPPORTED');
172 | }
173 |
174 | if (notify.setItem) {
175 | $rootScope.$broadcast('LocalStorageModule.notification.setitem', {key: key, newvalue: value, storageType: 'cookie'});
176 | }
177 | return addToCookies(key, value);
178 | }
179 |
180 | try {
181 | if (webStorage) {
182 | webStorage.setItem(deriveQualifiedKey(key), value);
183 | }
184 | if (notify.setItem) {
185 | $rootScope.$broadcast('LocalStorageModule.notification.setitem', {key: key, newvalue: value, storageType: self.storageType});
186 | }
187 | } catch (e) {
188 | $rootScope.$broadcast('LocalStorageModule.notification.error', e.message);
189 | return addToCookies(key, value);
190 | }
191 | return true;
192 | } finally {
193 | setStorageType(previousType);
194 | }
195 | };
196 |
197 | // Directly get a value from local storage
198 | // Example use: localStorageService.get('library'); // returns 'angular'
199 | var getFromLocalStorage = function (key, type) {
200 | var previousType = getStorageType();
201 |
202 | try {
203 | setStorageType(type);
204 |
205 | if (!browserSupportsLocalStorage && self.defaultToCookie || self.storageType === 'cookie') {
206 | if (!browserSupportsLocalStorage) {
207 | $rootScope.$broadcast('LocalStorageModule.notification.warning', 'LOCAL_STORAGE_NOT_SUPPORTED');
208 | }
209 |
210 | return getFromCookies(key);
211 | }
212 |
213 | var item = webStorage ? webStorage.getItem(deriveQualifiedKey(key)) : null;
214 | // angular.toJson will convert null to 'null', so a proper conversion is needed
215 | // FIXME not a perfect solution, since a valid 'null' string can't be stored
216 | if (!item || item === 'null') {
217 | return null;
218 | }
219 |
220 | try {
221 | return JSON.parse(item);
222 | } catch (e) {
223 | return item;
224 | }
225 | } finally {
226 | setStorageType(previousType);
227 | }
228 | };
229 |
230 | // Remove an item from local storage
231 | // Example use: localStorageService.remove('library'); // removes the key/value pair of library='angular'
232 | //
233 | // This is var-arg removal, check the last argument to see if it is a storageType
234 | // and set type accordingly before removing.
235 | //
236 | var removeFromLocalStorage = function () {
237 | var previousType = getStorageType();
238 |
239 | try {
240 | // can't pop on arguments, so we do this
241 | var consumed = 0;
242 | if (arguments.length >= 1 &&
243 | (arguments[arguments.length - 1] === 'localStorage' ||
244 | arguments[arguments.length - 1] === 'sessionStorage')) {
245 | consumed = 1;
246 | setStorageType(arguments[arguments.length - 1]);
247 | }
248 |
249 | var i, key;
250 | for (i = 0; i < arguments.length - consumed; i++) {
251 | key = arguments[i];
252 | if (!browserSupportsLocalStorage && self.defaultToCookie || self.storageType === 'cookie') {
253 | if (!browserSupportsLocalStorage) {
254 | $rootScope.$broadcast('LocalStorageModule.notification.warning', 'LOCAL_STORAGE_NOT_SUPPORTED');
255 | }
256 |
257 | if (notify.removeItem) {
258 | $rootScope.$broadcast('LocalStorageModule.notification.removeitem', {key: key, storageType: 'cookie'});
259 | }
260 | removeFromCookies(key);
261 | }
262 | else {
263 | try {
264 | webStorage.removeItem(deriveQualifiedKey(key));
265 | if (notify.removeItem) {
266 | $rootScope.$broadcast('LocalStorageModule.notification.removeitem', {
267 | key: key,
268 | storageType: self.storageType
269 | });
270 | }
271 | } catch (e) {
272 | $rootScope.$broadcast('LocalStorageModule.notification.error', e.message);
273 | removeFromCookies(key);
274 | }
275 | }
276 | }
277 | } finally {
278 | setStorageType(previousType);
279 | }
280 | };
281 |
282 | // Return array of keys for local storage
283 | // Example use: var keys = localStorageService.keys()
284 | var getKeysForLocalStorage = function (type) {
285 | var previousType = getStorageType();
286 |
287 | try {
288 | setStorageType(type);
289 |
290 | if (!browserSupportsLocalStorage) {
291 | $rootScope.$broadcast('LocalStorageModule.notification.warning', 'LOCAL_STORAGE_NOT_SUPPORTED');
292 | return [];
293 | }
294 |
295 | var prefixLength = prefix.length;
296 | var keys = [];
297 | for (var key in webStorage) {
298 | // Only return keys that are for this app
299 | if (key.substr(0, prefixLength) === prefix) {
300 | try {
301 | keys.push(key.substr(prefixLength));
302 | } catch (e) {
303 | $rootScope.$broadcast('LocalStorageModule.notification.error', e.Description);
304 | return [];
305 | }
306 | }
307 | }
308 |
309 | return keys;
310 | } finally {
311 | setStorageType(previousType);
312 | }
313 | };
314 |
315 | // Remove all data for this app from local storage
316 | // Also optionally takes a regular expression string and removes the matching key-value pairs
317 | // Example use: localStorageService.clearAll();
318 | // Should be used mostly for development purposes
319 | var clearAllFromLocalStorage = function (regularExpression, type) {
320 | var previousType = getStorageType();
321 |
322 | try {
323 | setStorageType(type);
324 |
325 | // Setting both regular expressions independently
326 | // Empty strings result in catchall RegExp
327 | var prefixRegex = !!prefix ? new RegExp('^' + prefix) : new RegExp();
328 | var testRegex = !!regularExpression ? new RegExp(regularExpression) : new RegExp();
329 |
330 | if (!browserSupportsLocalStorage && self.defaultToCookie || self.storageType === 'cookie') {
331 | if (!browserSupportsLocalStorage) {
332 | $rootScope.$broadcast('LocalStorageModule.notification.warning', 'LOCAL_STORAGE_NOT_SUPPORTED');
333 | }
334 | return clearAllFromCookies();
335 | }
336 | if (!browserSupportsLocalStorage && !self.defaultToCookie)
337 | return false;
338 | var prefixLength = prefix.length;
339 |
340 | for (var key in webStorage) {
341 | // Only remove items that are for this app and match the regular expression
342 | if (prefixRegex.test(key) && testRegex.test(key.substr(prefixLength))) {
343 | try {
344 | removeFromLocalStorage(key.substr(prefixLength));
345 | } catch (e) {
346 | $rootScope.$broadcast('LocalStorageModule.notification.error', e.message);
347 | return clearAllFromCookies();
348 | }
349 | }
350 | }
351 |
352 | return true;
353 | } finally {
354 | setStorageType(previousType);
355 | }
356 | };
357 |
358 | // Checks the browser to see if cookies are supported
359 | var browserSupportsCookies = (function() {
360 | try {
361 | return $window.navigator.cookieEnabled ||
362 | ("cookie" in $document && ($document.cookie.length > 0 ||
363 | ($document.cookie = "test").indexOf.call($document.cookie, "test") > -1));
364 | } catch (e) {
365 | $rootScope.$broadcast('LocalStorageModule.notification.error', e.message);
366 | return false;
367 | }
368 | }());
369 |
370 | // Directly adds a value to cookies
371 | // Typically used as a fallback if local storage is not available in the browser
372 | // Example use: localStorageService.cookie.add('library','angular');
373 | var addToCookies = function (key, value, daysToExpiry, secure) {
374 |
375 | if (isUndefined(value)) {
376 | return false;
377 | } else if(isArray(value) || isObject(value)) {
378 | value = toJson(value);
379 | }
380 |
381 | if (!browserSupportsCookies) {
382 | $rootScope.$broadcast('LocalStorageModule.notification.error', 'COOKIES_NOT_SUPPORTED');
383 | return false;
384 | }
385 |
386 | try {
387 | var expiry = '',
388 | expiryDate = new Date(),
389 | cookieDomain = '';
390 |
391 | if (value === null) {
392 | // Mark that the cookie has expired one day ago
393 | expiryDate.setTime(expiryDate.getTime() + (-1 * 24 * 60 * 60 * 1000));
394 | expiry = "; expires=" + expiryDate.toGMTString();
395 | value = '';
396 | } else if (isNumber(daysToExpiry) && daysToExpiry !== 0) {
397 | expiryDate.setTime(expiryDate.getTime() + (daysToExpiry * 24 * 60 * 60 * 1000));
398 | expiry = "; expires=" + expiryDate.toGMTString();
399 | } else if (cookie.expiry !== 0) {
400 | expiryDate.setTime(expiryDate.getTime() + (cookie.expiry * 24 * 60 * 60 * 1000));
401 | expiry = "; expires=" + expiryDate.toGMTString();
402 | }
403 | if (!!key) {
404 | var cookiePath = "; path=" + cookie.path;
405 | if (cookie.domain) {
406 | cookieDomain = "; domain=" + cookie.domain;
407 | }
408 | /* Providing the secure parameter always takes precedence over config
409 | * (allows developer to mix and match secure + non-secure) */
410 | if (typeof secure === 'boolean') {
411 | if (secure === true) {
412 | /* We've explicitly specified secure,
413 | * add the secure attribute to the cookie (after domain) */
414 | cookieDomain += "; secure";
415 | }
416 | // else - secure has been supplied but isn't true - so don't set secure flag, regardless of what config says
417 | }
418 | else if (cookie.secure === true) {
419 | // secure parameter wasn't specified, get default from config
420 | cookieDomain += "; secure";
421 | }
422 | $document.cookie = deriveQualifiedKey(key) + "=" + encodeURIComponent(value) + expiry + cookiePath + cookieDomain;
423 | }
424 | } catch (e) {
425 | $rootScope.$broadcast('LocalStorageModule.notification.error', e.message);
426 | return false;
427 | }
428 | return true;
429 | };
430 |
431 | // Directly get a value from a cookie
432 | // Example use: localStorageService.cookie.get('library'); // returns 'angular'
433 | var getFromCookies = function (key) {
434 | if (!browserSupportsCookies) {
435 | $rootScope.$broadcast('LocalStorageModule.notification.error', 'COOKIES_NOT_SUPPORTED');
436 | return false;
437 | }
438 |
439 | var cookies = $document.cookie && $document.cookie.split(';') || [];
440 | for(var i=0; i < cookies.length; i++) {
441 | var thisCookie = cookies[i];
442 | while (thisCookie.charAt(0) === ' ') {
443 | thisCookie = thisCookie.substring(1,thisCookie.length);
444 | }
445 | if (thisCookie.indexOf(deriveQualifiedKey(key) + '=') === 0) {
446 | var storedValues = decodeURIComponent(thisCookie.substring(prefix.length + key.length + 1, thisCookie.length));
447 | try {
448 | var parsedValue = JSON.parse(storedValues);
449 | return typeof(parsedValue) === 'number' ? storedValues : parsedValue;
450 | } catch(e) {
451 | return storedValues;
452 | }
453 | }
454 | }
455 | return null;
456 | };
457 |
458 | var removeFromCookies = function (key) {
459 | addToCookies(key,null);
460 | };
461 |
462 | var clearAllFromCookies = function () {
463 | var thisCookie = null;
464 | var prefixLength = prefix.length;
465 | var cookies = $document.cookie.split(';');
466 | for(var i = 0; i < cookies.length; i++) {
467 | thisCookie = cookies[i];
468 |
469 | while (thisCookie.charAt(0) === ' ') {
470 | thisCookie = thisCookie.substring(1, thisCookie.length);
471 | }
472 |
473 | var key = thisCookie.substring(prefixLength, thisCookie.indexOf('='));
474 | removeFromCookies(key);
475 | }
476 | };
477 |
478 | var getStorageType = function() {
479 | return storageType;
480 | };
481 |
482 | var setStorageType = function(type) {
483 | if (type && storageType !== type) {
484 | storageType = type;
485 | browserSupportsLocalStorage = checkSupport();
486 | }
487 | return browserSupportsLocalStorage;
488 | };
489 |
490 | // Add a listener on scope variable to save its changes to local storage
491 | // Return a function which when called cancels binding
492 | var bindToScope = function(scope, key, def, lsKey, type) {
493 | lsKey = lsKey || key;
494 | var value = getFromLocalStorage(lsKey, type);
495 |
496 | if (value === null && isDefined(def)) {
497 | value = def;
498 | } else if (isObject(value) && isObject(def)) {
499 | value = extend(value, def);
500 | }
501 |
502 | $parse(key).assign(scope, value);
503 |
504 | return scope.$watch(key, function(newVal) {
505 | addToLocalStorage(lsKey, newVal, type);
506 | }, isObject(scope[key]));
507 | };
508 |
509 | // Add listener to local storage, for update callbacks.
510 | if (browserSupportsLocalStorage) {
511 | if ($window.addEventListener) {
512 | $window.addEventListener("storage", handleStorageChangeCallback, false);
513 | $rootScope.$on('$destroy', function() {
514 | $window.removeEventListener("storage", handleStorageChangeCallback);
515 | });
516 | } else if($window.attachEvent){
517 | // attachEvent and detachEvent are proprietary to IE v6-10
518 | $window.attachEvent("onstorage", handleStorageChangeCallback);
519 | $rootScope.$on('$destroy', function() {
520 | $window.detachEvent("onstorage", handleStorageChangeCallback);
521 | });
522 | }
523 | }
524 |
525 | // Callback handler for storage changed.
526 | function handleStorageChangeCallback(e) {
527 | if (!e) { e = $window.event; }
528 | if (notify.setItem) {
529 | if (isString(e.key) && isKeyPrefixOurs(e.key)) {
530 | var key = underiveQualifiedKey(e.key);
531 | // Use timeout, to avoid using $rootScope.$apply.
532 | $timeout(function () {
533 | $rootScope.$broadcast('LocalStorageModule.notification.changed', { key: key, newvalue: e.newValue, storageType: self.storageType });
534 | });
535 | }
536 | }
537 | }
538 |
539 | // Return localStorageService.length
540 | // ignore keys that not owned
541 | var lengthOfLocalStorage = function(type) {
542 | var previousType = getStorageType();
543 |
544 | try {
545 | setStorageType(type);
546 |
547 | var count = 0;
548 | var storage = $window[storageType];
549 | for(var i = 0; i < storage.length; i++) {
550 | if(storage.key(i).indexOf(prefix) === 0 ) {
551 | count++;
552 | }
553 | }
554 |
555 | return count;
556 | } finally {
557 | setStorageType(previousType);
558 | }
559 | };
560 |
561 | var changePrefix = function(localStoragePrefix) {
562 | prefix = localStoragePrefix;
563 | };
564 |
565 | return {
566 | isSupported: browserSupportsLocalStorage,
567 | getStorageType: getStorageType,
568 | setStorageType: setStorageType,
569 | setPrefix: changePrefix,
570 | set: addToLocalStorage,
571 | add: addToLocalStorage, //DEPRECATED
572 | get: getFromLocalStorage,
573 | keys: getKeysForLocalStorage,
574 | remove: removeFromLocalStorage,
575 | clearAll: clearAllFromLocalStorage,
576 | bind: bindToScope,
577 | deriveKey: deriveQualifiedKey,
578 | underiveKey: underiveQualifiedKey,
579 | length: lengthOfLocalStorage,
580 | defaultToCookie: this.defaultToCookie,
581 | cookie: {
582 | isSupported: browserSupportsCookies,
583 | set: addToCookies,
584 | add: addToCookies, //DEPRECATED
585 | get: getFromCookies,
586 | remove: removeFromCookies,
587 | clearAll: clearAllFromCookies
588 | }
589 | };
590 | }];
591 | });
592 | })(window, window.angular);
--------------------------------------------------------------------------------
/dist/angular-local-storage.min.js:
--------------------------------------------------------------------------------
1 | /**
2 | * An Angular module that gives you access to the browsers local storage
3 | * @version v0.7.1 - 2017-06-21
4 | * @link https://github.com/grevory/angular-local-storage
5 | * @author grevory
6 | * @license MIT License, http://www.opensource.org/licenses/MIT
7 | */
8 | !function(a,b){var c=b.isDefined,d=b.isUndefined,e=b.isNumber,f=b.isObject,g=b.isArray,h=b.isString,i=b.extend,j=b.toJson;b.module("LocalStorageModule",[]).provider("localStorageService",function(){this.prefix="ls",this.storageType="localStorage",this.cookie={expiry:30,path:"/",secure:!1},this.defaultToCookie=!0,this.notify={setItem:!0,removeItem:!1},this.setPrefix=function(a){return this.prefix=a,this},this.setStorageType=function(a){return this.storageType=a,this},this.setDefaultToCookie=function(a){return this.defaultToCookie=!!a,this},this.setStorageCookie=function(a,b,c){return this.cookie.expiry=a,this.cookie.path=b,this.cookie.secure=c,this},this.setStorageCookieDomain=function(a){return this.cookie.domain=a,this},this.setNotify=function(a,b){return this.notify={setItem:a,removeItem:b},this},this.$get=["$rootScope","$window","$document","$parse","$timeout",function(a,b,k,l,m){function n(c){if(c||(c=b.event),s.setItem&&h(c.key)&&w(c.key)){var d=v(c.key);m(function(){a.$broadcast("LocalStorageModule.notification.changed",{key:d,newvalue:c.newValue,storageType:p.storageType})})}}var o,p=this,q=p.prefix,r=p.cookie,s=p.notify,t=p.storageType;k?k[0]&&(k=k[0]):k=document,"."!==q.substr(-1)&&(q=q?q+".":"");var u=function(a){return q+a},v=function(a){return a.replace(new RegExp("^"+q,"g"),"")},w=function(a){return 0===a.indexOf(q)},x=function(){try{var c=t in b&&null!==b[t],d=u("__"+Math.round(1e7*Math.random()));return c&&(o=b[t],o.setItem(d,""),o.removeItem(d)),c}catch(b){return p.defaultToCookie&&(t="cookie"),a.$broadcast("LocalStorageModule.notification.error",b.message),!1}},y=x(),z=function(b,c,e){var f=J();try{if(K(e),c=d(c)?null:j(c),!y&&p.defaultToCookie||"cookie"===p.storageType)return y||a.$broadcast("LocalStorageModule.notification.warning","LOCAL_STORAGE_NOT_SUPPORTED"),s.setItem&&a.$broadcast("LocalStorageModule.notification.setitem",{key:b,newvalue:c,storageType:"cookie"}),F(b,c);try{o&&o.setItem(u(b),c),s.setItem&&a.$broadcast("LocalStorageModule.notification.setitem",{key:b,newvalue:c,storageType:p.storageType})}catch(d){return a.$broadcast("LocalStorageModule.notification.error",d.message),F(b,c)}return!0}finally{K(f)}},A=function(b,c){var d=J();try{if(K(c),!y&&p.defaultToCookie||"cookie"===p.storageType)return y||a.$broadcast("LocalStorageModule.notification.warning","LOCAL_STORAGE_NOT_SUPPORTED"),G(b);var e=o?o.getItem(u(b)):null;if(!e||"null"===e)return null;try{return JSON.parse(e)}catch(a){return e}}finally{K(d)}},B=function(){var b=J();try{var c=0;arguments.length>=1&&("localStorage"===arguments[arguments.length-1]||"sessionStorage"===arguments[arguments.length-1])&&(c=1,K(arguments[arguments.length-1]));var d,e;for(d=0;d0||(k.cookie="test").indexOf.call(k.cookie,"test")>-1)}catch(b){return a.$broadcast("LocalStorageModule.notification.error",b.message),!1}}(),F=function(b,c,h,i){if(d(c))return!1;if((g(c)||f(c))&&(c=j(c)),!E)return a.$broadcast("LocalStorageModule.notification.error","COOKIES_NOT_SUPPORTED"),!1;try{var l="",m=new Date,n="";if(null===c?(m.setTime(m.getTime()+-864e5),l="; expires="+m.toGMTString(),c=""):e(h)&&0!==h?(m.setTime(m.getTime()+24*h*60*60*1e3),l="; expires="+m.toGMTString()):0!==r.expiry&&(m.setTime(m.getTime()+24*r.expiry*60*60*1e3),l="; expires="+m.toGMTString()),b){var o="; path="+r.path;r.domain&&(n="; domain="+r.domain),"boolean"==typeof i?i===!0&&(n+="; secure"):r.secure===!0&&(n+="; secure"),k.cookie=u(b)+"="+encodeURIComponent(c)+l+o+n}}catch(b){return a.$broadcast("LocalStorageModule.notification.error",b.message),!1}return!0},G=function(b){if(!E)return a.$broadcast("LocalStorageModule.notification.error","COOKIES_NOT_SUPPORTED"),!1;for(var c=k.cookie&&k.cookie.split(";")||[],d=0;d",
20 | "contributors": [
21 | "Ariel Mashraki "
22 | ],
23 | "license": "MIT",
24 | "bugs": {
25 | "url": "https://github.com/grevory/angular-local-storage/issues"
26 | },
27 | "devDependencies": {
28 | "grunt": "^0.4.5",
29 | "grunt-cli": "~0.1.9",
30 | "grunt-contrib-concat": "*",
31 | "grunt-contrib-jshint": "~0.12.0",
32 | "grunt-contrib-uglify": "*",
33 | "grunt-karma": "latest",
34 | "jasmine-core": "^2.4.1",
35 | "karma": "~0.13.19",
36 | "karma-coverage": "^0.5.3",
37 | "karma-jasmine": "~0.3.7",
38 | "karma-phantomjs-launcher": "~1.0.0",
39 | "load-grunt-tasks": "~3.4.0",
40 | "phantomjs-prebuilt": "^2.1.4",
41 | "time-grunt": "~1.3.0"
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/angular-local-storage.js:
--------------------------------------------------------------------------------
1 | var isDefined = angular.isDefined,
2 | isUndefined = angular.isUndefined,
3 | isNumber = angular.isNumber,
4 | isObject = angular.isObject,
5 | isArray = angular.isArray,
6 | isString = angular.isString,
7 | extend = angular.extend,
8 | toJson = angular.toJson;
9 |
10 | angular
11 | .module('LocalStorageModule', [])
12 | .provider('localStorageService', function() {
13 | // You should set a prefix to avoid overwriting any local storage variables from the rest of your app
14 | // e.g. localStorageServiceProvider.setPrefix('yourAppName');
15 | // With provider you can use config as this:
16 | // myApp.config(function (localStorageServiceProvider) {
17 | // localStorageServiceProvider.prefix = 'yourAppName';
18 | // });
19 | this.prefix = 'ls';
20 |
21 | // You could change web storage type localstorage or sessionStorage
22 | this.storageType = 'localStorage';
23 |
24 | // Cookie options (usually in case of fallback)
25 | // expiry = Number of days before cookies expire // 0 = Does not expire
26 | // path = The web path the cookie represents
27 | // secure = Wether the cookies should be secure (i.e only sent on HTTPS requests)
28 | this.cookie = {
29 | expiry: 30,
30 | path: '/',
31 | secure: false
32 | };
33 |
34 | // Decides wether we should default to cookies if localstorage is not supported.
35 | this.defaultToCookie = true;
36 |
37 | // Send signals for each of the following actions?
38 | this.notify = {
39 | setItem: true,
40 | removeItem: false
41 | };
42 |
43 | // Setter for the prefix
44 | this.setPrefix = function(prefix) {
45 | this.prefix = prefix;
46 | return this;
47 | };
48 |
49 | // Setter for the storageType
50 | this.setStorageType = function(storageType) {
51 | this.storageType = storageType;
52 | return this;
53 | };
54 | // Setter for defaultToCookie value, default is true.
55 | this.setDefaultToCookie = function (shouldDefault) {
56 | this.defaultToCookie = !!shouldDefault; // Double-not to make sure it's a bool value.
57 | return this;
58 | };
59 | // Setter for cookie config
60 | this.setStorageCookie = function(exp, path, secure) {
61 | this.cookie.expiry = exp;
62 | this.cookie.path = path;
63 | this.cookie.secure = secure;
64 | return this;
65 | };
66 |
67 | // Setter for cookie domain
68 | this.setStorageCookieDomain = function(domain) {
69 | this.cookie.domain = domain;
70 | return this;
71 | };
72 |
73 | // Setter for notification config
74 | // itemSet & itemRemove should be booleans
75 | this.setNotify = function(itemSet, itemRemove) {
76 | this.notify = {
77 | setItem: itemSet,
78 | removeItem: itemRemove
79 | };
80 | return this;
81 | };
82 |
83 | this.$get = ['$rootScope', '$window', '$document', '$parse','$timeout', function($rootScope, $window, $document, $parse, $timeout) {
84 | var self = this;
85 | var prefix = self.prefix;
86 | var cookie = self.cookie;
87 | var notify = self.notify;
88 | var storageType = self.storageType;
89 | var webStorage;
90 |
91 | // When Angular's $document is not available
92 | if (!$document) {
93 | $document = document;
94 | } else if ($document[0]) {
95 | $document = $document[0];
96 | }
97 |
98 | // If there is a prefix set in the config lets use that with an appended period for readability
99 | if (prefix.substr(-1) !== '.') {
100 | prefix = !!prefix ? prefix + '.' : '';
101 | }
102 | var deriveQualifiedKey = function(key) {
103 | return prefix + key;
104 | };
105 |
106 | // Removes prefix from the key.
107 | var underiveQualifiedKey = function (key) {
108 | return key.replace(new RegExp('^' + prefix, 'g'), '');
109 | };
110 |
111 | // Check if the key is within our prefix namespace.
112 | var isKeyPrefixOurs = function (key) {
113 | return key.indexOf(prefix) === 0;
114 | };
115 |
116 | // Checks the browser to see if local storage is supported
117 | var checkSupport = function () {
118 | try {
119 | var supported = (storageType in $window && $window[storageType] !== null);
120 |
121 | // When Safari (OS X or iOS) is in private browsing mode, it appears as though localStorage
122 | // is available, but trying to call .setItem throws an exception.
123 | //
124 | // "QUOTA_EXCEEDED_ERR: DOM Exception 22: An attempt was made to add something to storage
125 | // that exceeded the quota."
126 | var key = deriveQualifiedKey('__' + Math.round(Math.random() * 1e7));
127 | if (supported) {
128 | webStorage = $window[storageType];
129 | webStorage.setItem(key, '');
130 | webStorage.removeItem(key);
131 | }
132 |
133 | return supported;
134 | } catch (e) {
135 | // Only change storageType to cookies if defaulting is enabled.
136 | if (self.defaultToCookie)
137 | storageType = 'cookie';
138 | $rootScope.$broadcast('LocalStorageModule.notification.error', e.message);
139 | return false;
140 | }
141 | };
142 | var browserSupportsLocalStorage = checkSupport();
143 |
144 | // Directly adds a value to local storage
145 | // If local storage is not available in the browser use cookies
146 | // Example use: localStorageService.add('library','angular');
147 | var addToLocalStorage = function (key, value, type) {
148 | var previousType = getStorageType();
149 |
150 | try {
151 | setStorageType(type);
152 |
153 | // Let's convert undefined values to null to get the value consistent
154 | if (isUndefined(value)) {
155 | value = null;
156 | } else {
157 | value = toJson(value);
158 | }
159 |
160 | // If this browser does not support local storage use cookies
161 | if (!browserSupportsLocalStorage && self.defaultToCookie || self.storageType === 'cookie') {
162 | if (!browserSupportsLocalStorage) {
163 | $rootScope.$broadcast('LocalStorageModule.notification.warning', 'LOCAL_STORAGE_NOT_SUPPORTED');
164 | }
165 |
166 | if (notify.setItem) {
167 | $rootScope.$broadcast('LocalStorageModule.notification.setitem', {key: key, newvalue: value, storageType: 'cookie'});
168 | }
169 | return addToCookies(key, value);
170 | }
171 |
172 | try {
173 | if (webStorage) {
174 | webStorage.setItem(deriveQualifiedKey(key), value);
175 | }
176 | if (notify.setItem) {
177 | $rootScope.$broadcast('LocalStorageModule.notification.setitem', {key: key, newvalue: value, storageType: self.storageType});
178 | }
179 | } catch (e) {
180 | $rootScope.$broadcast('LocalStorageModule.notification.error', e.message);
181 | return addToCookies(key, value);
182 | }
183 | return true;
184 | } finally {
185 | setStorageType(previousType);
186 | }
187 | };
188 |
189 | // Directly get a value from local storage
190 | // Example use: localStorageService.get('library'); // returns 'angular'
191 | var getFromLocalStorage = function (key, type) {
192 | var previousType = getStorageType();
193 |
194 | try {
195 | setStorageType(type);
196 |
197 | if (!browserSupportsLocalStorage && self.defaultToCookie || self.storageType === 'cookie') {
198 | if (!browserSupportsLocalStorage) {
199 | $rootScope.$broadcast('LocalStorageModule.notification.warning', 'LOCAL_STORAGE_NOT_SUPPORTED');
200 | }
201 |
202 | return getFromCookies(key);
203 | }
204 |
205 | var item = webStorage ? webStorage.getItem(deriveQualifiedKey(key)) : null;
206 | // angular.toJson will convert null to 'null', so a proper conversion is needed
207 | // FIXME not a perfect solution, since a valid 'null' string can't be stored
208 | if (!item || item === 'null') {
209 | return null;
210 | }
211 |
212 | try {
213 | return JSON.parse(item);
214 | } catch (e) {
215 | return item;
216 | }
217 | } finally {
218 | setStorageType(previousType);
219 | }
220 | };
221 |
222 | // Remove an item from local storage
223 | // Example use: localStorageService.remove('library'); // removes the key/value pair of library='angular'
224 | //
225 | // This is var-arg removal, check the last argument to see if it is a storageType
226 | // and set type accordingly before removing.
227 | //
228 | var removeFromLocalStorage = function () {
229 | var previousType = getStorageType();
230 |
231 | try {
232 | // can't pop on arguments, so we do this
233 | var consumed = 0;
234 | if (arguments.length >= 1 &&
235 | (arguments[arguments.length - 1] === 'localStorage' ||
236 | arguments[arguments.length - 1] === 'sessionStorage')) {
237 | consumed = 1;
238 | setStorageType(arguments[arguments.length - 1]);
239 | }
240 |
241 | var i, key;
242 | for (i = 0; i < arguments.length - consumed; i++) {
243 | key = arguments[i];
244 | if (!browserSupportsLocalStorage && self.defaultToCookie || self.storageType === 'cookie') {
245 | if (!browserSupportsLocalStorage) {
246 | $rootScope.$broadcast('LocalStorageModule.notification.warning', 'LOCAL_STORAGE_NOT_SUPPORTED');
247 | }
248 |
249 | if (notify.removeItem) {
250 | $rootScope.$broadcast('LocalStorageModule.notification.removeitem', {key: key, storageType: 'cookie'});
251 | }
252 | removeFromCookies(key);
253 | }
254 | else {
255 | try {
256 | webStorage.removeItem(deriveQualifiedKey(key));
257 | if (notify.removeItem) {
258 | $rootScope.$broadcast('LocalStorageModule.notification.removeitem', {
259 | key: key,
260 | storageType: self.storageType
261 | });
262 | }
263 | } catch (e) {
264 | $rootScope.$broadcast('LocalStorageModule.notification.error', e.message);
265 | removeFromCookies(key);
266 | }
267 | }
268 | }
269 | } finally {
270 | setStorageType(previousType);
271 | }
272 | };
273 |
274 | // Return array of keys for local storage
275 | // Example use: var keys = localStorageService.keys()
276 | var getKeysForLocalStorage = function (type) {
277 | var previousType = getStorageType();
278 |
279 | try {
280 | setStorageType(type);
281 |
282 | if (!browserSupportsLocalStorage) {
283 | $rootScope.$broadcast('LocalStorageModule.notification.warning', 'LOCAL_STORAGE_NOT_SUPPORTED');
284 | return [];
285 | }
286 |
287 | var prefixLength = prefix.length;
288 | var keys = [];
289 | for (var key in webStorage) {
290 | // Only return keys that are for this app
291 | if (key.substr(0, prefixLength) === prefix) {
292 | try {
293 | keys.push(key.substr(prefixLength));
294 | } catch (e) {
295 | $rootScope.$broadcast('LocalStorageModule.notification.error', e.Description);
296 | return [];
297 | }
298 | }
299 | }
300 |
301 | return keys;
302 | } finally {
303 | setStorageType(previousType);
304 | }
305 | };
306 |
307 | // Remove all data for this app from local storage
308 | // Also optionally takes a regular expression string and removes the matching key-value pairs
309 | // Example use: localStorageService.clearAll();
310 | // Should be used mostly for development purposes
311 | var clearAllFromLocalStorage = function (regularExpression, type) {
312 | var previousType = getStorageType();
313 |
314 | try {
315 | setStorageType(type);
316 |
317 | // Setting both regular expressions independently
318 | // Empty strings result in catchall RegExp
319 | var prefixRegex = !!prefix ? new RegExp('^' + prefix) : new RegExp();
320 | var testRegex = !!regularExpression ? new RegExp(regularExpression) : new RegExp();
321 |
322 | if (!browserSupportsLocalStorage && self.defaultToCookie || self.storageType === 'cookie') {
323 | if (!browserSupportsLocalStorage) {
324 | $rootScope.$broadcast('LocalStorageModule.notification.warning', 'LOCAL_STORAGE_NOT_SUPPORTED');
325 | }
326 | return clearAllFromCookies();
327 | }
328 | if (!browserSupportsLocalStorage && !self.defaultToCookie)
329 | return false;
330 | var prefixLength = prefix.length;
331 |
332 | for (var key in webStorage) {
333 | // Only remove items that are for this app and match the regular expression
334 | if (prefixRegex.test(key) && testRegex.test(key.substr(prefixLength))) {
335 | try {
336 | removeFromLocalStorage(key.substr(prefixLength));
337 | } catch (e) {
338 | $rootScope.$broadcast('LocalStorageModule.notification.error', e.message);
339 | return clearAllFromCookies();
340 | }
341 | }
342 | }
343 |
344 | return true;
345 | } finally {
346 | setStorageType(previousType);
347 | }
348 | };
349 |
350 | // Checks the browser to see if cookies are supported
351 | var browserSupportsCookies = (function() {
352 | try {
353 | return $window.navigator.cookieEnabled ||
354 | ("cookie" in $document && ($document.cookie.length > 0 ||
355 | ($document.cookie = "test").indexOf.call($document.cookie, "test") > -1));
356 | } catch (e) {
357 | $rootScope.$broadcast('LocalStorageModule.notification.error', e.message);
358 | return false;
359 | }
360 | }());
361 |
362 | // Directly adds a value to cookies
363 | // Typically used as a fallback if local storage is not available in the browser
364 | // Example use: localStorageService.cookie.add('library','angular');
365 | var addToCookies = function (key, value, daysToExpiry, secure) {
366 |
367 | if (isUndefined(value)) {
368 | return false;
369 | } else if(isArray(value) || isObject(value)) {
370 | value = toJson(value);
371 | }
372 |
373 | if (!browserSupportsCookies) {
374 | $rootScope.$broadcast('LocalStorageModule.notification.error', 'COOKIES_NOT_SUPPORTED');
375 | return false;
376 | }
377 |
378 | try {
379 | var expiry = '',
380 | expiryDate = new Date(),
381 | cookieDomain = '';
382 |
383 | if (value === null) {
384 | // Mark that the cookie has expired one day ago
385 | expiryDate.setTime(expiryDate.getTime() + (-1 * 24 * 60 * 60 * 1000));
386 | expiry = "; expires=" + expiryDate.toGMTString();
387 | value = '';
388 | } else if (isNumber(daysToExpiry) && daysToExpiry !== 0) {
389 | expiryDate.setTime(expiryDate.getTime() + (daysToExpiry * 24 * 60 * 60 * 1000));
390 | expiry = "; expires=" + expiryDate.toGMTString();
391 | } else if (cookie.expiry !== 0) {
392 | expiryDate.setTime(expiryDate.getTime() + (cookie.expiry * 24 * 60 * 60 * 1000));
393 | expiry = "; expires=" + expiryDate.toGMTString();
394 | }
395 | if (!!key) {
396 | var cookiePath = "; path=" + cookie.path;
397 | if (cookie.domain) {
398 | cookieDomain = "; domain=" + cookie.domain;
399 | }
400 | /* Providing the secure parameter always takes precedence over config
401 | * (allows developer to mix and match secure + non-secure) */
402 | if (typeof secure === 'boolean') {
403 | if (secure === true) {
404 | /* We've explicitly specified secure,
405 | * add the secure attribute to the cookie (after domain) */
406 | cookieDomain += "; secure";
407 | }
408 | // else - secure has been supplied but isn't true - so don't set secure flag, regardless of what config says
409 | }
410 | else if (cookie.secure === true) {
411 | // secure parameter wasn't specified, get default from config
412 | cookieDomain += "; secure";
413 | }
414 | $document.cookie = deriveQualifiedKey(key) + "=" + encodeURIComponent(value) + expiry + cookiePath + cookieDomain;
415 | }
416 | } catch (e) {
417 | $rootScope.$broadcast('LocalStorageModule.notification.error', e.message);
418 | return false;
419 | }
420 | return true;
421 | };
422 |
423 | // Directly get a value from a cookie
424 | // Example use: localStorageService.cookie.get('library'); // returns 'angular'
425 | var getFromCookies = function (key) {
426 | if (!browserSupportsCookies) {
427 | $rootScope.$broadcast('LocalStorageModule.notification.error', 'COOKIES_NOT_SUPPORTED');
428 | return false;
429 | }
430 |
431 | var cookies = $document.cookie && $document.cookie.split(';') || [];
432 | for(var i=0; i < cookies.length; i++) {
433 | var thisCookie = cookies[i];
434 | while (thisCookie.charAt(0) === ' ') {
435 | thisCookie = thisCookie.substring(1,thisCookie.length);
436 | }
437 | if (thisCookie.indexOf(deriveQualifiedKey(key) + '=') === 0) {
438 | var storedValues = decodeURIComponent(thisCookie.substring(prefix.length + key.length + 1, thisCookie.length));
439 | try {
440 | var parsedValue = JSON.parse(storedValues);
441 | return typeof(parsedValue) === 'number' ? storedValues : parsedValue;
442 | } catch(e) {
443 | return storedValues;
444 | }
445 | }
446 | }
447 | return null;
448 | };
449 |
450 | var removeFromCookies = function (key) {
451 | addToCookies(key,null);
452 | };
453 |
454 | var clearAllFromCookies = function () {
455 | var thisCookie = null;
456 | var prefixLength = prefix.length;
457 | var cookies = $document.cookie.split(';');
458 | for(var i = 0; i < cookies.length; i++) {
459 | thisCookie = cookies[i];
460 |
461 | while (thisCookie.charAt(0) === ' ') {
462 | thisCookie = thisCookie.substring(1, thisCookie.length);
463 | }
464 |
465 | var key = thisCookie.substring(prefixLength, thisCookie.indexOf('='));
466 | removeFromCookies(key);
467 | }
468 | };
469 |
470 | var getStorageType = function() {
471 | return storageType;
472 | };
473 |
474 | var setStorageType = function(type) {
475 | if (type && storageType !== type) {
476 | storageType = type;
477 | browserSupportsLocalStorage = checkSupport();
478 | }
479 | return browserSupportsLocalStorage;
480 | };
481 |
482 | // Add a listener on scope variable to save its changes to local storage
483 | // Return a function which when called cancels binding
484 | var bindToScope = function(scope, key, def, lsKey, type) {
485 | lsKey = lsKey || key;
486 | var value = getFromLocalStorage(lsKey, type);
487 |
488 | if (value === null && isDefined(def)) {
489 | value = def;
490 | } else if (isObject(value) && isObject(def)) {
491 | value = extend(value, def);
492 | }
493 |
494 | $parse(key).assign(scope, value);
495 |
496 | return scope.$watch(key, function(newVal) {
497 | addToLocalStorage(lsKey, newVal, type);
498 | }, isObject(scope[key]));
499 | };
500 |
501 | // Add listener to local storage, for update callbacks.
502 | if (browserSupportsLocalStorage) {
503 | if ($window.addEventListener) {
504 | $window.addEventListener("storage", handleStorageChangeCallback, false);
505 | $rootScope.$on('$destroy', function() {
506 | $window.removeEventListener("storage", handleStorageChangeCallback);
507 | });
508 | } else if($window.attachEvent){
509 | // attachEvent and detachEvent are proprietary to IE v6-10
510 | $window.attachEvent("onstorage", handleStorageChangeCallback);
511 | $rootScope.$on('$destroy', function() {
512 | $window.detachEvent("onstorage", handleStorageChangeCallback);
513 | });
514 | }
515 | }
516 |
517 | // Callback handler for storage changed.
518 | function handleStorageChangeCallback(e) {
519 | if (!e) { e = $window.event; }
520 | if (notify.setItem) {
521 | if (isString(e.key) && isKeyPrefixOurs(e.key)) {
522 | var key = underiveQualifiedKey(e.key);
523 | // Use timeout, to avoid using $rootScope.$apply.
524 | $timeout(function () {
525 | $rootScope.$broadcast('LocalStorageModule.notification.changed', { key: key, newvalue: e.newValue, storageType: self.storageType });
526 | });
527 | }
528 | }
529 | }
530 |
531 | // Return localStorageService.length
532 | // ignore keys that not owned
533 | var lengthOfLocalStorage = function(type) {
534 | var previousType = getStorageType();
535 |
536 | try {
537 | setStorageType(type);
538 |
539 | var count = 0;
540 | var storage = $window[storageType];
541 | for(var i = 0; i < storage.length; i++) {
542 | if(storage.key(i).indexOf(prefix) === 0 ) {
543 | count++;
544 | }
545 | }
546 |
547 | return count;
548 | } finally {
549 | setStorageType(previousType);
550 | }
551 | };
552 |
553 | var changePrefix = function(localStoragePrefix) {
554 | prefix = localStoragePrefix;
555 | };
556 |
557 | return {
558 | isSupported: browserSupportsLocalStorage,
559 | getStorageType: getStorageType,
560 | setStorageType: setStorageType,
561 | setPrefix: changePrefix,
562 | set: addToLocalStorage,
563 | add: addToLocalStorage, //DEPRECATED
564 | get: getFromLocalStorage,
565 | keys: getKeysForLocalStorage,
566 | remove: removeFromLocalStorage,
567 | clearAll: clearAllFromLocalStorage,
568 | bind: bindToScope,
569 | deriveKey: deriveQualifiedKey,
570 | underiveKey: underiveQualifiedKey,
571 | length: lengthOfLocalStorage,
572 | defaultToCookie: this.defaultToCookie,
573 | cookie: {
574 | isSupported: browserSupportsCookies,
575 | set: addToCookies,
576 | add: addToCookies, //DEPRECATED
577 | get: getFromCookies,
578 | remove: removeFromCookies,
579 | clearAll: clearAllFromCookies
580 | }
581 | };
582 | }];
583 | });
584 |
--------------------------------------------------------------------------------
/test/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "node": true,
3 | "browser": true,
4 | "esnext": true,
5 | "bitwise": true,
6 | "camelcase": true,
7 | "curly": true,
8 | "eqeqeq": true,
9 | "immed": true,
10 | "indent": 2,
11 | "latedef": true,
12 | "newcap": true,
13 | "noarg": true,
14 | "quotmark": "single",
15 | "regexp": true,
16 | "undef": true,
17 | "unused": true,
18 | "strict": true,
19 | "trailing": true,
20 | "smarttabs": true,
21 | "globals": {
22 | "after": false,
23 | "afterEach": false,
24 | "angular": false,
25 | "before": false,
26 | "beforeEach": false,
27 | "browser": false,
28 | "describe": false,
29 | "expect": false,
30 | "inject": false,
31 | "it": false,
32 | "xit": false,
33 | "spyOn": false,
34 | "jasmine": false,
35 | "localStorageMock": false
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/test/karma.conf.js:
--------------------------------------------------------------------------------
1 | // Karma configuration
2 | // http://karma-runner.github.io/0.10/config/configuration-file.html
3 |
4 | module.exports = function(config) {
5 | 'use strict';
6 |
7 | var bower = 'test/lib/bower_components/';
8 |
9 | config.set({
10 |
11 | // enable / disable watching file and executing tests whenever any file changes
12 | autoWatch: true,
13 |
14 | // base path, that will be used to resolve files and exclude
15 | basePath: '../',
16 |
17 | // Start these browsers, currently available:
18 | // - Chrome
19 | // - ChromeCanary
20 | // - Firefox
21 | // - Opera
22 | // - Safari (only Mac)
23 | // - PhantomJS
24 | // - IE (only Windows)
25 | browsers: ['PhantomJS'],
26 |
27 | // list of files / patterns to load in the browser
28 | files: [
29 | bower + 'angular/angular.js',
30 | bower + 'angular-mocks/angular-mocks.js',
31 | 'dist/angular-local-storage.js',
32 | 'test/mock/*.js',
33 | 'test/spec/**/*.js'
34 | ],
35 |
36 | // testing framework to use (jasmine/mocha/qunit/...)
37 | frameworks: ['jasmine'],
38 |
39 | // Which plugins to enable
40 | plugins: [
41 | 'karma-phantomjs-launcher',
42 | 'karma-jasmine',
43 | 'karma-coverage'
44 | ],
45 |
46 | // level of logging
47 | // possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG
48 | logLevel: config.LOG_INFO,
49 |
50 | // web server port
51 | port: 8999,
52 |
53 | // Continuous Integration mode
54 | // if true, it capture browsers, run tests and exit
55 | singleRun: true,
56 |
57 | reporters: ['progress', 'coverage'],
58 |
59 | // preprocessors
60 | preprocessors: {
61 | 'src/*.js': ['coverage']
62 | },
63 |
64 | // configure the reporter
65 | coverageReporter: {
66 | type : 'lcov',
67 | dir : 'coverage/'
68 | }
69 | });
70 | };
71 |
--------------------------------------------------------------------------------
/test/mock/localStorageMock.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | //Mock localStorage
3 | function localStorageMock() {
4 | var storage = {};
5 | Object.defineProperties(storage, {
6 | setItem: {
7 | value: function(key, value) {
8 | storage[key] = value || '';
9 | },
10 | enumerable: false,
11 | writable: true
12 | },
13 | getItem: {
14 | value: function(key) {
15 | return storage[key] ? storage[key] : null;
16 | },
17 | enumerable: false,
18 | writable: true
19 | },
20 | removeItem: {
21 | value: function(key) {
22 | delete storage[key];
23 | },
24 | enumerable: false,
25 | writable: true
26 | },
27 | length: {
28 | get: function() {
29 | return Object.keys(storage).length;
30 | },
31 | enumerable: false
32 | },
33 | key: {
34 | value: function(i) {
35 | var aKeys = Object.keys(storage);
36 | return aKeys[i] || null;
37 | },
38 | enumerable: false
39 | }
40 | });
41 | return storage;
42 | }
43 |
--------------------------------------------------------------------------------
/test/spec/localStorageSpec.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | describe('localStorageService', function () {
4 | var elmSpy;
5 |
6 | //Actions
7 | function getItem(key, type) {
8 | return function ($window, localStorageService) {
9 | elmSpy = spyOn($window.localStorage, 'getItem').and.callThrough();
10 | localStorageService.get(key, type);
11 | };
12 | }
13 |
14 | function addItem(key, value, type) {
15 | return function ($window, localStorageService) {
16 | elmSpy = spyOn($window.localStorage, 'setItem').and.callThrough();
17 | localStorageService.set(key, value, type);
18 | };
19 | }
20 |
21 | function removeItem(key, type) {
22 | return function ($window, localStorageService) {
23 | elmSpy = spyOn($window.localStorage, 'removeItem').and.callThrough();
24 | localStorageService.remove(key, type);
25 | };
26 | }
27 |
28 | //Expectations
29 | function expectGetting(key) {
30 | return function () {
31 | expect(elmSpy).toHaveBeenCalledWith(key);
32 | };
33 | }
34 |
35 | function expectAdding(key, value) {
36 | return function () {
37 | expect(elmSpy).toHaveBeenCalledWith(key, value);
38 | };
39 | }
40 |
41 | function expectRemoving(key) {
42 | return function () {
43 | expect(elmSpy).toHaveBeenCalledWith(key);
44 | };
45 | }
46 |
47 | function expectMatching(key, expected) {
48 | return function (localStorageService) {
49 | expect(localStorageService.get(key)).toEqual(expected);
50 | };
51 | }
52 |
53 | function expectStorageTyping(type) {
54 | return function (localStorageService) {
55 | expect(localStorageService.getStorageType()).toEqual(type);
56 | };
57 | }
58 |
59 | function expectSupporting(expected) {
60 | return function (localStorageService) {
61 | expect(localStorageService.isSupported).toEqual(expected);
62 | };
63 | }
64 |
65 | function expectCookieSupporting(expected) {
66 | return function (localStorageService) {
67 | expect(localStorageService.cookie.isSupported).toEqual(expected);
68 | };
69 | }
70 |
71 | function expectDefaultToCookieSupporting(expected) {
72 | return function (localStorageService) {
73 | expect(localStorageService.defaultToCookie).toEqual(expected);
74 | };
75 | }
76 |
77 | function expectDomain(domain) {
78 | return function ($document, localStorageService) {
79 | localStorageService.set('foo', 'bar'); //Should trigger first time
80 | expect($document.cookie.indexOf('domain=' + domain)).not.toEqual(-1);
81 | };
82 | }
83 |
84 | function expectCookieConfig(exp, path) {
85 | return function ($document, localStorageService) {
86 | localStorageService.set('foo', 'bar'); //Should trigger first time
87 | // Just compare the expiry date, not the time, because of daylight savings
88 | var expiryStringPartial = exp.substr(0, exp.indexOf(new Date().getFullYear()));
89 | expect($document.cookie.indexOf('expires=' + expiryStringPartial)).not.toEqual(-1);
90 | expect($document.cookie.indexOf('path=' + path)).not.toEqual(-1);
91 | expect($document.cookie.indexOf('secure')).toEqual(-1);
92 | };
93 | }
94 |
95 | function expectCookieExpiry(exp) {
96 | return function ($document, localStorageService) {
97 | localStorageService.cookie.set('foo', 'bar', 10); //Should trigger first time
98 | // Just compare the expiry date, not the time, because of daylight savings
99 | var expiryStringPartial = exp.substr(0, exp.indexOf(new Date().getFullYear()));
100 | expect($document.cookie.indexOf('expires=' + expiryStringPartial)).not.toEqual(-1);
101 | };
102 | }
103 |
104 | function expectCookieSecure() {
105 | return function ($document, localStorageService) {
106 | localStorageService.cookie.set('foo', 'bar', null, true);
107 | expect($document.cookie.indexOf('secure')).not.toEqual(-1);
108 | };
109 | }
110 |
111 | //Provider
112 | function setPrefix(prefix) {
113 | return function (localStorageServiceProvider) {
114 | localStorageServiceProvider.setPrefix(prefix);
115 | };
116 | }
117 |
118 | function setDefaultToCookie(shouldDefault) {
119 | return function (localStorageServiceProvider) {
120 | localStorageServiceProvider.setDefaultToCookie(shouldDefault);
121 | };
122 | }
123 |
124 | function setNotify(itemSet, itemRemove) {
125 | return function (localStorageServiceProvider) {
126 | localStorageServiceProvider.setNotify(itemSet, itemRemove);
127 | };
128 | }
129 |
130 | function setStorage(type) {
131 | return function (localStorageServiceProvider) {
132 | localStorageServiceProvider.setStorageType(type);
133 | };
134 | }
135 |
136 | function setCookieDomain(domain) {
137 | return function (localStorageServiceProvider) {
138 | localStorageServiceProvider.setStorageCookieDomain(domain);
139 | };
140 | }
141 |
142 | function setStorageCookie(exp, path, secure) {
143 | return function (localStorageServiceProvider) {
144 | localStorageServiceProvider.setStorageCookie(exp, path, secure);
145 | };
146 | }
147 |
148 | beforeEach(module('LocalStorageModule', function ($provide) {
149 |
150 | $provide.value('$window', {
151 | localStorage: localStorageMock()
152 | });
153 |
154 | }));
155 |
156 | it('isSupported should be true', inject(
157 | expectSupporting(true)
158 | ));
159 |
160 | it('typing should be "localStorage" by default, if supported', inject(
161 | expectStorageTyping('localStorage')
162 | ));
163 |
164 | it('should add key to localeStorage with initial prefix(ls)', inject(
165 | addItem('foo', 'bar'),
166 | expectAdding('ls.foo', '"bar"')
167 | ));
168 |
169 | it('should add key to localeStorage null if value not provided', inject(
170 | addItem('foo'),
171 | expectAdding('ls.foo', null)
172 | ));
173 |
174 | it('should support to set custom prefix', function () {
175 | module(setPrefix('myApp'));
176 | inject(
177 | addItem('foo', 'bar'),
178 | expectAdding('myApp.foo', '"bar"')
179 | );
180 | });
181 |
182 | it('should support to set empty prefix', function () {
183 | module(setPrefix(''));
184 | inject(
185 | addItem('foo', 'bar'),
186 | expectAdding('foo', '"bar"')
187 | );
188 | });
189 |
190 | it('should support changing prefix on the fly', function() {
191 | module(function(localStorageServiceProvider) {
192 | localStorageServiceProvider.setPrefix('startPrefix');
193 | });
194 |
195 | inject(function(localStorageService) {
196 | localStorageService.setPrefix('newPrefix');
197 | localStorageService.add('foo', 'bar');
198 | expectAdding('newPrefix.foo', 'bar');
199 | });
200 | });
201 |
202 | it('should be able to chain functions in the config phase', function () {
203 | module(function (localStorageServiceProvider) {
204 | localStorageServiceProvider
205 | .setPrefix('chain')
206 | .setNotify(false, true)
207 | .setStorageType('session');
208 | });
209 | inject(function (localStorageService) {
210 | expect(localStorageService.deriveKey('foo')).toEqual('chain.foo');
211 | expect(localStorageService.getStorageType()).toEqual('session');
212 | });
213 | });
214 |
215 | it('should be able to return the derive key', function () {
216 | module(setPrefix('myApp'));
217 | inject(function (localStorageService) {
218 | expect(localStorageService.deriveKey('foo')).toEqual('myApp.foo');
219 | });
220 | });
221 |
222 | it('should be able to return the underivedkey', function () {
223 | module(setPrefix('myApp'));
224 | inject(function (localStorageService) {
225 | expect(localStorageService.underiveKey('myApp.foo')).toEqual('foo');
226 | });
227 | });
228 |
229 | it('should be able to set and get arrays', function () {
230 | var values = ['foo', 'bar', 'baz'];
231 | inject(
232 | addItem('appKey', values),
233 | expectAdding('ls.appKey', angular.toJson(values)),
234 | expectMatching('appKey', values)
235 | );
236 | });
237 |
238 | it('should be able to set and get objects', function () {
239 | var values = { 0: 'foo', 1: 'bar', 2: 'baz' };
240 | inject(
241 | addItem('appKey', values),
242 | expectAdding('ls.appKey', angular.toJson(values)),
243 | expectMatching('appKey', values)
244 | );
245 | });
246 |
247 | it('should be able to set and get objects contains boolean-like strings - issue #225', function () {
248 | var t = { x: 'true', y: 'false' };
249 | inject(
250 | addItem('appKey', t),
251 | expectAdding('ls.appKey', angular.toJson(t)),
252 | expectMatching('appKey', t)
253 | );
254 | });
255 |
256 | it('should be able to set and get integers', function () {
257 | inject(
258 | addItem('appKey', 777),
259 | expectAdding('ls.appKey', angular.toJson(777)),
260 | expectMatching('appKey', 777)
261 | );
262 | });
263 |
264 | it('should be able to set and get float numbers', function () {
265 | inject(
266 | addItem('appKey', 123.123),
267 | expectAdding('ls.appKey', angular.toJson(123.123)),
268 | expectMatching('appKey', 123.123)
269 | );
270 | });
271 |
272 | it('should be able to set and get booleans', function () {
273 | inject(
274 | addItem('appKey', true),
275 | expectAdding('ls.appKey', angular.toJson(true)),
276 | expectMatching('appKey', true)
277 | );
278 | });
279 |
280 | it('should be able to set and get boolean-like strings', function () {
281 | inject(
282 | addItem('appKey', 'true'),
283 | expectAdding('ls.appKey', angular.toJson('true')),
284 | expectMatching('appKey', 'true')
285 | );
286 | });
287 |
288 | it('should be able to set and get null-like strings', function () {
289 | inject(
290 | addItem('appKey', 'null'),
291 | expectAdding('ls.appKey', angular.toJson('null')),
292 | expectMatching('appKey', 'null')
293 | );
294 | });
295 |
296 | it('should be able to set and get strings', function () {
297 | inject(
298 | addItem('appKey', 'string'),
299 | expectAdding('ls.appKey', '"string"'),
300 | expectMatching('appKey', 'string')
301 | );
302 | });
303 |
304 | it('should be able to set and get numbers as a strings', function () {
305 | inject(
306 | addItem('appKey', '777'),
307 | expectAdding('ls.appKey', angular.toJson('777')),
308 | expectMatching('appKey', '777')
309 | );
310 | });
311 |
312 | it('should be able to get items', inject(
313 | getItem('appKey'),
314 | expectGetting('ls.appKey')
315 | ));
316 |
317 | it('should be able to remove items', inject(
318 | removeItem('lorem.ipsum'),
319 | expectRemoving('ls.lorem.ipsum')
320 | ));
321 |
322 | it('should be able to remove multiple items', inject(function ($window, localStorageService) {
323 | elmSpy = spyOn($window.localStorage, 'removeItem').and.callThrough();
324 | localStorageService.remove('lorem.ipsum1', 'lorem.ipsum2', 'lorem.ipsum3');
325 |
326 | expect(elmSpy.calls.count()).toEqual(3);
327 | expect(elmSpy).toHaveBeenCalledWith('ls.lorem.ipsum1');
328 | expect(elmSpy).toHaveBeenCalledWith('ls.lorem.ipsum2');
329 | expect(elmSpy).toHaveBeenCalledWith('ls.lorem.ipsum3');
330 | }));
331 |
332 | it('should be able only to remove owned keys', inject(function ($window, localStorageService) {
333 | localStorageService.set('appKey', 'appValue');
334 | $window.localStorage.setItem('appKey', 'appValue');
335 |
336 | expect($window.localStorage.getItem('ls.appKey')).toBeDefined();
337 | expect($window.localStorage.getItem('appKey')).toBeDefined();
338 |
339 | localStorageService.remove('appKey');
340 |
341 | expect($window.localStorage.getItem('ls.appKey')).toBeNull();
342 | expect($window.localStorage.getItem('appKey')).toBeDefined();
343 | }));
344 |
345 | it('should be able only to remove keys with empty prefix', function () {
346 | module(setPrefix(''));
347 | inject(function ($window, localStorageService) {
348 | localStorageService.set('appKey', 'appValue');
349 |
350 | expect($window.localStorage.getItem('appKey')).toBeDefined();
351 |
352 | localStorageService.remove('appKey');
353 |
354 | expect($window.localStorage.getItem('appKey')).toBeNull();
355 | });
356 | });
357 |
358 | it('should broadcast event on settingItem', inject(function ($rootScope, localStorageService) {
359 | var setSpy = spyOn($rootScope, '$broadcast');
360 | localStorageService.set('Ariel', 'Mashraki');
361 | expect(setSpy).toHaveBeenCalled();
362 | }));
363 |
364 | it('should not broadcast event on removingItem', inject(function ($rootScope, localStorageService) {
365 | var removeSpy = spyOn($rootScope, '$broadcast');
366 | localStorageService.remove('Ariel', 'Mashraki');
367 | expect(removeSpy).not.toHaveBeenCalled();
368 | }));
369 |
370 | it('should be able to change notify/broadcasting settings', function () {
371 | module(setNotify(false, false));
372 | inject(function ($rootScope, localStorageService) {
373 | var spy = spyOn($rootScope, '$broadcast');
374 | localStorageService.set('a8m', 'foobar');
375 | localStorageService.remove('a8m', 'foobar');
376 |
377 | expect(spy).not.toHaveBeenCalled();
378 | });
379 | });
380 |
381 | it('should be able to notify/broadcasting if set', function () {
382 | module(setNotify(true, true));
383 | inject(function ($rootScope, localStorageService) {
384 | var spy = spyOn($rootScope, '$broadcast');
385 |
386 | localStorageService.set('a8m', 'foobar');
387 | localStorageService.remove('a8m');
388 | expect(spy.calls.count()).toEqual(2);
389 | });
390 | });
391 |
392 | it('should be able to bind to scope', inject(function ($rootScope, localStorageService) {
393 |
394 | localStorageService.set('property', 'oldValue');
395 | localStorageService.bind($rootScope, 'property');
396 |
397 | $rootScope.property = 'newValue';
398 | $rootScope.$digest();
399 |
400 | expect($rootScope.property).toEqual(localStorageService.get('property'));
401 | }));
402 |
403 | it('should be able to unbind from scope variable', inject(function ($rootScope, localStorageService) {
404 |
405 | localStorageService.set('property', 'oldValue');
406 | var lsUnbind = localStorageService.bind($rootScope, 'property');
407 |
408 | $rootScope.property = 'newValue';
409 | $rootScope.$digest();
410 |
411 | expect($rootScope.property).toEqual(localStorageService.get('property'));
412 |
413 | lsUnbind();
414 | $rootScope.property = 'anotherValue';
415 | $rootScope.$digest();
416 |
417 | expect($rootScope.property).not.toEqual(localStorageService.get('property'));
418 | }));
419 |
420 | it('should be able to bind to properties of objects', inject(function ($rootScope, localStorageService) {
421 |
422 | localStorageService.set('obj.property', 'oldValue');
423 | localStorageService.bind($rootScope, 'obj.property');
424 |
425 | expect($rootScope.obj.property).toEqual(localStorageService.get('obj.property'));
426 |
427 | $rootScope.obj.property = 'newValue';
428 | $rootScope.$digest();
429 |
430 | expect($rootScope.obj.property).toEqual(localStorageService.get('obj.property'));
431 | }));
432 |
433 | it('should be able to bind to scope using different key', inject(function ($rootScope, localStorageService) {
434 |
435 | localStorageService.set('lsProperty', 'oldValue');
436 | localStorageService.bind($rootScope, 'property', undefined, 'lsProperty');
437 |
438 | expect($rootScope.property).toEqual(localStorageService.get('lsProperty'));
439 |
440 | $rootScope.property = 'newValue';
441 | $rootScope.$digest();
442 |
443 | expect($rootScope.property).toEqual(localStorageService.get('lsProperty'));
444 | }));
445 |
446 | it('should $watch with deep comparison only for objects', inject(function ($rootScope, localStorageService) {
447 | var mocks = [{}, [], 'string', 90, false];
448 | var expectation = [true, true, false, false, false];
449 | var results = [];
450 |
451 | spyOn($rootScope, '$watch').and.callFake(function (key, func, eq) {
452 | results.push(eq);
453 | });
454 |
455 | mocks.forEach(function (elm, i) {
456 | localStorageService.set('mock' + i, elm);
457 | localStorageService.bind($rootScope, 'mock' + i);
458 | });
459 |
460 | expect(results).toEqual(expectation);
461 | }));
462 |
463 | it('should be able to return it\'s owned keys amount', inject(
464 | function (localStorageService, $window) {
465 |
466 | for (var i = 0; i < 10; i++) {
467 | localStorageService.set('key' + i, 'val' + i);
468 | $window.localStorage.setItem('key' + i, 'val' + i);
469 | }
470 | expect(localStorageService.length()).toEqual(10);
471 | expect($window.localStorage.length).toEqual(20);
472 | })
473 | );
474 |
475 | it('should be able to clear all owned keys from storage', inject(function ($window, localStorageService) {
476 | for (var i = 0; i < 10; i++) {
477 | localStorageService.set('key' + i, 'val' + i);
478 | $window.localStorage.setItem('key' + i, 'val' + i);
479 | }
480 |
481 | localStorageService.clearAll();
482 | //remove only owned keys
483 | for (var l = 0; l < 10; l++) {
484 | expect(localStorageService.get('key' + l)).toBeNull();
485 | expect($window.localStorage.getItem('key' + l)).toEqual('val' + l);
486 | }
487 | }));
488 |
489 | it('should be able to clear owned keys from storage, using RegExp', inject(function ($window, localStorageService) {
490 | for (var i = 0; i < 10; i++) {
491 | localStorageService.set('key' + i, 'val' + i);
492 | localStorageService.set('otherKey' + i, 'val' + i);
493 | $window.localStorage.setItem('key' + i, 'val' + i);
494 | $window.localStorage.setItem('otherKey' + i, 'val' + i);
495 | }
496 | localStorageService.set('keyAlpha', 'val');
497 |
498 | localStorageService.clearAll(/^key/);
499 |
500 | expect(localStorageService.get('keyAlpha')).toBeNull();
501 |
502 | //remove only owned keys that follow RegExp
503 | for (var l = 0; l < 10; l++) {
504 | expect(localStorageService.get('key' + l)).toBeNull();
505 | expect($window.localStorage.getItem('key' + l)).toEqual('val' + l);
506 | expect(localStorageService.get('otherKey' + l)).toEqual('val' + l);
507 | expect($window.localStorage.getItem('otherKey' + l)).toEqual('val' + l);
508 | }
509 | }));
510 |
511 | it('should be able to clear owned keys from storage, using RegExp when prefix is empty string', function () {
512 | module(setPrefix(''));
513 | inject(function ($window, localStorageService) {
514 | for (var i = 0; i < 10; i++) {
515 | localStorageService.set('key' + i, 'val' + i);
516 | localStorageService.set('otherKey' + i, 'val' + i);
517 | }
518 | localStorageService.set('keyAlpha', 'val');
519 |
520 | localStorageService.clearAll(/^key/);
521 |
522 | expect(localStorageService.get('keyAlpha')).toBeNull();
523 | expect($window.localStorage.getItem('keyAlpha')).toBeNull();
524 |
525 | for (var l = 0; l < 10; l++) {
526 | expect(localStorageService.get('key' + l)).toBeNull();
527 | expect($window.localStorage.getItem('key' + l)).toBeNull();
528 | expect(localStorageService.get('otherKey' + l)).toEqual('val' + l);
529 | expect($window.localStorage.getItem('otherKey' + l)).toEqual('"val' + l + '"');
530 | }
531 | });
532 | });
533 |
534 | it('should return array of all owned keys', inject(function ($window, localStorageService) {
535 | //set keys
536 | for (var i = 0; i < 10; i++) {
537 | //localStorageService
538 | localStorageService.set('ownKey' + i, 'val' + i);
539 | //window.localStorage
540 | $window.localStorage.setItem('windowKey' + i, 'val' + i);
541 | }
542 | localStorageService.keys().forEach(function (el, i) {
543 | expect(el).toEqual('ownKey' + i);
544 | });
545 | }));
546 |
547 | // Backward compatibility issue-#230
548 | it('should return the item as-is if the parsing fail', inject(function ($window, localStorageService) {
549 | var items = ['{', '[', 'foo'];
550 | //set keys
551 | items.forEach(function (item, i) {
552 | $window.localStorage.setItem('ls.' + i, item);
553 | });
554 |
555 | items.forEach(function (item, i) {
556 | expect(localStorageService.get(i)).toEqual(item);
557 | });
558 | }));
559 |
560 | //sessionStorage
561 | describe('SessionStorage', function () {
562 |
563 | beforeEach(module('LocalStorageModule', function ($provide) {
564 | $provide.value('$window', {
565 | sessionStorage: localStorageMock()
566 | });
567 | }));
568 |
569 | it('should be able to change storage to SessionStorage', function () {
570 | module(setStorage('sessionStorage'));
571 |
572 | inject(function ($window, localStorageService) {
573 | var setSpy = spyOn($window.sessionStorage, 'setItem'),
574 | getSpy = spyOn($window.sessionStorage, 'getItem'),
575 | removeSpy = spyOn($window.sessionStorage, 'removeItem');
576 |
577 | localStorageService.set('foo', 'bar');
578 | localStorageService.get('foo');
579 | localStorageService.remove('foo');
580 |
581 | expect(setSpy).toHaveBeenCalledWith('ls.foo', '"bar"');
582 | expect(getSpy).toHaveBeenCalledWith('ls.foo');
583 | expect(removeSpy).toHaveBeenCalledWith('ls.foo');
584 |
585 | });
586 | });
587 |
588 | it('type should be sessionStorage', function () {
589 | module(setStorage('sessionStorage'));
590 | inject(
591 | expectStorageTyping('sessionStorage')
592 | );
593 | });
594 |
595 | it('isSupported should be true on sessionStorage mode', function () {
596 | module(setStorage('sessionStorage'));
597 | inject(
598 | expectSupporting(true)
599 | );
600 | });
601 |
602 | });
603 |
604 | //cookie
605 | describe('Cookie', function () {
606 |
607 | beforeEach(module('LocalStorageModule', function ($provide) {
608 | $provide.value('$window', {
609 | localStorage: false,
610 | sessionStorage: false,
611 | navigator: {
612 | cookieEnabled: true
613 | }
614 | });
615 | $provide.value('$document', {
616 | cookie: ''
617 | });
618 | }));
619 |
620 | it('isSupported should be false on fallback mode', inject(
621 | expectSupporting(false)
622 | ));
623 |
624 | it('cookie.isSupported should be true if cookies are enabled', inject(
625 | expectCookieSupporting(true)
626 | ));
627 |
628 | it('fallback storage type should be cookie', inject(
629 | expectStorageTyping('cookie')
630 | ));
631 |
632 | it('should be able to add to cookie domain', function () {
633 | module(setCookieDomain('.example.org'));
634 | inject(expectDomain('.example.org'));
635 | });
636 |
637 | it('should be able to config expiry and path', function () {
638 | module(setStorageCookie(60, '/path'));
639 | inject(expectCookieConfig(new Date().addDays(60), '/path'));
640 | });
641 |
642 | it('should be able to set and get cookie', inject(function (localStorageService) {
643 | localStorageService.set('cookieKey', 'cookieValue');
644 | expect(localStorageService.get('cookieKey')).toEqual('cookieValue');
645 | }));
646 |
647 | it('should be able to set individual cookie with expiry', function () {
648 | inject(expectCookieExpiry(new Date().addDays(10)));
649 | });
650 |
651 | it('should be able to set individual cookie with secure attribute', function () {
652 | inject(expectCookieSecure());
653 | });
654 |
655 | it('should be able to remove from cookie', inject(function (localStorageService) {
656 | localStorageService.set('cookieKey', 'cookieValue');
657 | localStorageService.remove('cookieKey');
658 | expect(localStorageService.get('cookieKey')).toEqual('');
659 | }));
660 |
661 | it('should be able to set and get objects from cookie', inject(function (localStorageService) {
662 | //use as a fallback
663 | localStorageService.set('cookieKey', { a: { b: 1 } });
664 | expect(localStorageService.get('cookieKey')).toEqual({ a: { b: 1 } });
665 | //use directly
666 | localStorageService.cookie.set('cookieKey', { a: 2 });
667 | expect(localStorageService.cookie.get('cookieKey')).toEqual({ a: 2 });
668 | }));
669 |
670 | it('should be able to set and get arrays from cookie', inject(function (localStorageService) {
671 | //use as a fallback
672 | localStorageService.set('cookieKey', [1, 2, 3, [1, 2, 3]]);
673 | expect(localStorageService.get('cookieKey')).toEqual([1, 2, 3, [1, 2, 3]]);
674 | //use directly
675 | localStorageService.cookie.set('cookieKey', ['foo', 'bar']);
676 | expect(localStorageService.cookie.get('cookieKey')).toEqual(['foo', 'bar']);
677 | }));
678 |
679 | it('should be able to clear all owned keys from cookie', inject(function (localStorageService, $document) {
680 | localStorageService.set('ownKey1', 1);
681 | $document.cookie = 'username=John Doe';
682 | localStorageService.clearAll();
683 | expect(localStorageService.get('ownKey1')).toBeNull();
684 | expect($document.cookie).not.toEqual('');
685 | }));
686 |
687 | it('should be able to return a string when a cookie exceeds max integer', inject(function (localStorageService, $document) {
688 | $document.cookie = 'ls.token=90071992547409919';
689 | var token = localStorageService.cookie.get('token');
690 | expect(token).toEqual('90071992547409919');
691 | expect(token).not.toEqual('Infinity');
692 | }));
693 |
694 | it('should be broadcast on adding item', function () {
695 | module(setNotify(true, false));
696 | inject(function ($rootScope, localStorageService) {
697 | var spy = spyOn($rootScope, '$broadcast');
698 | localStorageService.set('a8m', 'foobar');
699 | expect(spy).toHaveBeenCalled();
700 | });
701 | });
702 |
703 | it('should be broadcast on removing item', function () {
704 | module(setNotify(false, true));
705 | inject(function ($rootScope, localStorageService) {
706 | var spy = spyOn($rootScope, '$broadcast');
707 | localStorageService.remove('a8m', 'foobar');
708 | expect(spy).toHaveBeenCalled();
709 | });
710 | });
711 |
712 | Date.prototype.addDays = function (days) {
713 | var date = new Date(this.getTime());
714 | date.setDate(date.getDate() + days);
715 | return date.toUTCString();
716 | };
717 | });
718 |
719 | describe('secure cookies', function(){
720 | beforeEach(module('LocalStorageModule', function ($provide) {
721 | $provide.value('$window', {
722 | localStorage: false,
723 | sessionStorage: false,
724 | navigator: {
725 | cookieEnabled: true
726 | }
727 | });
728 | $provide.value('$document', {
729 | cookie: ''
730 | });
731 | }));
732 |
733 | it('should be able to set all cookies as secure via config', function () {
734 | module(setStorageCookie(60, '/path', true));
735 | inject(function(localStorageService, $document){
736 | localStorageService.set('cookieKey', 'cookieValue');
737 | expect($document.cookie.indexOf('secure')).not.toEqual(-1);
738 | });
739 | });
740 |
741 | it('should be able to override secure option in config by specifying a secure flag', inject(function (localStorageService, $document) {
742 | localStorageService.cookie.set('foo', 'bar', null, true);
743 | expect($document.cookie.indexOf('secure')).not.toEqual(-1);
744 | }));
745 |
746 | it('should default to non-secure cookies', inject(function (localStorageService, $document) {
747 | localStorageService.set('foo', 'bar');
748 | expect($document.cookie.indexOf('secure')).toEqual(-1);
749 | }));
750 | });
751 |
752 | //cookie disabled
753 | describe('No Cookie', function () {
754 |
755 | beforeEach(module('LocalStorageModule', function ($provide) {
756 | $provide.value('$window', {
757 | navigator: {
758 | cookieEnabled: false
759 | }
760 | });
761 | $provide.value('$document', {
762 |
763 | });
764 | }));
765 |
766 | it('cookie.isSupported should be false if cookies are disabled', inject(
767 | expectCookieSupporting(false)
768 | ));
769 | });
770 |
771 | //setDefaultToCookie
772 | describe('setDefaultToCookie', function () {
773 |
774 | beforeEach(module('LocalStorageModule', function ($provide) {
775 | $provide.value('$window', {
776 | localStorage: false,
777 | sessionStorage: false,
778 | });
779 | }));
780 |
781 | it('should by default be enabled', inject(
782 | expectDefaultToCookieSupporting(true)
783 | ));
784 |
785 | it('should be possible to disable', function () {
786 | module(setDefaultToCookie(false));
787 | inject(expectDefaultToCookieSupporting(false));
788 | });
789 |
790 | it('should not default to cookies', function () {
791 | module(setDefaultToCookie(false));
792 | inject(expectSupporting(false));
793 | inject(expectStorageTyping('localStorage'));
794 | });
795 | });
796 |
797 | //localStorageChanged
798 | describe('localStorageChanged', function () {
799 | var listeners = {};
800 | beforeEach(module('LocalStorageModule', function ($provide) {
801 | var window = jasmine.createSpyObj('$window', ['addEventListener','removeEventListener']);
802 | window.localStorage = localStorageMock();
803 | window.addEventListener.and.callFake(function (event, listener) {
804 | listeners[event] = listener;
805 | });
806 | window.removeEventListener.and.callFake(function (event) {
807 | listeners[event] = null;
808 | });
809 | $provide.value('$window', window);
810 | $provide.value('$timeout', function (fn) { fn(); });
811 | module(function (localStorageServiceProvider) {
812 | localStorageServiceProvider
813 | .setPrefix('test')
814 | .setNotify(true, true);
815 | });
816 | }));
817 |
818 | it('should call $window.addEventListener if storage is supported and notify.setitem is true', inject(function ($window, localStorageService) { // jshint ignore:line
819 | expect($window.addEventListener).toHaveBeenCalled();
820 | expect($window.addEventListener.calls.mostRecent().args[0] === 'storage').toBeTruthy();
821 | expect($window.addEventListener.calls.mostRecent().args[1] instanceof Function).toBeTruthy();
822 | expect($window.addEventListener.calls.mostRecent().args[2]).toEqual(false);
823 | }));
824 |
825 | it('localStorageChanged should call $broadcast', inject(function ($window, localStorageService, $rootScope) {
826 | var spy = spyOn($rootScope, '$broadcast');
827 | var event = {
828 | key: localStorageService.deriveKey('foo'),
829 | newValue: 'bar'
830 | };
831 | listeners.storage(event);
832 |
833 | expect(spy).toHaveBeenCalled();
834 | expect(spy.calls.mostRecent().args[0] === 'LocalStorageModule.notification.changed').toBeTruthy();
835 | expect(spy.calls.mostRecent().args[1].key === 'foo').toBeTruthy();
836 | expect(spy.calls.mostRecent().args[1].newvalue === 'bar').toBeTruthy();
837 | }));
838 |
839 | it('localStorageChanged should not $broadcast on non-string keys', inject(function ($window, localStorageService, $rootScope) {
840 | var spy = spyOn($rootScope, '$broadcast');
841 | var event = {
842 | key: null,
843 | newValue: 'bar'
844 | };
845 | listeners.storage(event);
846 |
847 | expect(spy).not.toHaveBeenCalled();
848 | }));
849 | });
850 |
851 | describe('overriding the default storage type for a specific transaction', function() {
852 |
853 | var localStorageService, sessionStorageSetSpy, sessionStorageGetSpy, sessionStorageRemoveSpy;
854 |
855 | beforeEach(module('LocalStorageModule', function($provide) {
856 | $provide.value('$window', {
857 | localStorage: localStorageMock(),
858 | sessionStorage: localStorageMock()
859 | });
860 | }));
861 |
862 | beforeEach(inject(function($window, _localStorageService_) {
863 | localStorageService = _localStorageService_;
864 | sessionStorageSetSpy = spyOn($window.sessionStorage, 'setItem');
865 | sessionStorageGetSpy = spyOn($window.sessionStorage, 'getItem');
866 | sessionStorageRemoveSpy = spyOn($window.sessionStorage, 'removeItem');
867 | }));
868 |
869 | describe('when setting a value', function() {
870 |
871 | it('should use the specified storage type, not affecting the default storage type', function() {
872 | localStorageService.set('appKey', 777, 'sessionStorage');
873 |
874 | expect(sessionStorageSetSpy).toHaveBeenCalledWith('ls.appKey', angular.toJson(777));
875 |
876 | inject(
877 | addItem('appKey', '777'),
878 | expectAdding('ls.appKey', angular.toJson('777')),
879 | expectMatching('appKey', '777')
880 | );
881 | });
882 | });
883 |
884 | describe('when getting a value', function() {
885 | it('should use the specified storage type, not affecting the default storage type', function() {
886 | localStorageService.get('appKey', 'sessionStorage');
887 |
888 | expect(sessionStorageGetSpy).toHaveBeenCalledWith('ls.appKey');
889 |
890 | inject(
891 | getItem('appKey'),
892 | expectGetting('ls.appKey')
893 | );
894 | });
895 | });
896 |
897 | describe('when removing a value', function() {
898 | it('should use the specified storage type, not affecting the default storage type', function() {
899 | localStorageService.remove('appKey', 'sessionStorage');
900 |
901 | expect(sessionStorageRemoveSpy).toHaveBeenCalledWith('ls.appKey');
902 |
903 | inject(
904 | removeItem('appKey'),
905 | expectRemoving('ls.appKey')
906 | );
907 | });
908 | });
909 | });
910 | });
911 |
--------------------------------------------------------------------------------