Skip to content

Commit aeccdfb

Browse files
committed
configurable copy and equality functions
1 parent 1b50182 commit aeccdfb

File tree

2 files changed

+185
-159
lines changed

2 files changed

+185
-159
lines changed

ngStorage.js

Lines changed: 171 additions & 158 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
(function (root, factory) {
2-
'use strict';
3-
4-
if (typeof define === 'function' && define.amd) {
5-
define(['angular'], factory);
6-
} else if (typeof exports === 'object') {
7-
module.exports = factory(require('angular'));
8-
} else {
9-
// Browser globals (root is window), we don't register it.
10-
factory(root.angular);
11-
}
12-
}(this , function (angular) {
2+
'use strict';
3+
4+
if (typeof define === 'function' && define.amd) {
5+
define(['angular'], factory);
6+
} else if (typeof exports === 'object') {
7+
module.exports = factory(require('angular'));
8+
} else {
9+
// Browser globals (root is window), we don't register it.
10+
factory(root.angular);
11+
}
12+
}(this, function (angular) {
1313
'use strict';
1414

1515
// In cases where Angular does not get passed or angular is a truthy value
@@ -30,7 +30,7 @@
3030
* @requires $window
3131
*/
3232

33-
.provider('$localStorage', _storageProvider('localStorage'))
33+
.provider('$localStorage', _storageProvider('localStorage'))
3434

3535
/**
3636
* @ngdoc object
@@ -39,183 +39,196 @@
3939
* @requires $window
4040
*/
4141

42-
.provider('$sessionStorage', _storageProvider('sessionStorage'));
42+
.provider('$sessionStorage', _storageProvider('sessionStorage'));
4343

4444
function _storageProvider(storageType) {
4545
return function () {
46-
var storageKeyPrefix = 'ngStorage-';
47-
48-
this.setKeyPrefix = function (prefix) {
49-
if (typeof prefix !== 'string') {
50-
throw new TypeError('[ngStorage] - ' + storageType + 'Provider.setKeyPrefix() expects a String.');
51-
}
52-
storageKeyPrefix = prefix;
53-
};
54-
55-
var serializer = angular.toJson;
56-
var deserializer = angular.fromJson;
57-
58-
this.setSerializer = function (s) {
59-
if (typeof s !== 'function') {
60-
throw new TypeError('[ngStorage] - ' + storageType + 'Provider.setSerializer expects a function.');
61-
}
62-
63-
serializer = s;
64-
};
65-
66-
this.setDeserializer = function (d) {
67-
if (typeof d !== 'function') {
68-
throw new TypeError('[ngStorage] - ' + storageType + 'Provider.setDeserializer expects a function.');
69-
}
70-
71-
deserializer = d;
72-
};
73-
74-
// Note: This is not very elegant at all.
75-
this.get = function (key) {
76-
return deserializer(window[storageType].getItem(storageKeyPrefix + key));
77-
};
78-
79-
// Note: This is not very elegant at all.
80-
this.set = function (key, value) {
81-
return window[storageType].setItem(storageKeyPrefix + key, serializer(value));
82-
};
83-
84-
this.$get = [
85-
'$rootScope',
86-
'$window',
87-
'$log',
88-
'$timeout',
89-
'$document',
90-
91-
function(
92-
$rootScope,
93-
$window,
94-
$log,
95-
$timeout,
96-
$document
97-
){
98-
function isStorageSupported(storageType) {
99-
100-
// Some installations of IE, for an unknown reason, throw "SCRIPT5: Error: Access is denied"
101-
// when accessing window.localStorage. This happens before you try to do anything with it. Catch
102-
// that error and allow execution to continue.
103-
104-
// fix 'SecurityError: DOM Exception 18' exception in Desktop Safari, Mobile Safari
105-
// when "Block cookies": "Always block" is turned on
106-
var supported;
107-
try {
108-
supported = $window[storageType];
109-
}
110-
catch (err) {
111-
supported = false;
112-
}
46+
var storageKeyPrefix = 'ngStorage-';
47+
48+
this.setKeyPrefix = function (prefix) {
49+
if (typeof prefix !== 'string') {
50+
throw new TypeError('[ngStorage] - ' + storageType + 'Provider.setKeyPrefix() expects a String.');
51+
}
52+
storageKeyPrefix = prefix;
53+
};
54+
55+
var serializer = angular.toJson;
56+
var deserializer = angular.fromJson;
57+
58+
this.setSerializer = function (s) {
59+
if (typeof s !== 'function') {
60+
throw new TypeError('[ngStorage] - ' + storageType + 'Provider.setSerializer expects a function.');
61+
}
62+
63+
serializer = s;
64+
};
65+
66+
this.setDeserializer = function (d) {
67+
if (typeof d !== 'function') {
68+
throw new TypeError('[ngStorage] - ' + storageType + 'Provider.setDeserializer expects a function.');
69+
}
70+
71+
deserializer = d;
72+
};
73+
74+
var objCopy = angular.copy;
75+
var objEquals = angular.equals;
76+
77+
this.setObjCopy = function (f) {
78+
if (typeof f !== 'function') {
79+
throw new TypeError('[ngStorage] - ' + storageType + 'Provider.setObjCopy expects a function.');
80+
}
81+
82+
objCopy = f;
83+
};
84+
85+
this.setObjEquals = function (f) {
86+
if (typeof f !== 'function') {
87+
throw new TypeError('[ngStorage] - ' + storageType + 'Provider.setObjEquals expects a function.');
88+
}
89+
90+
objEquals = f;
91+
};
92+
93+
// Note: This is not very elegant at all.
94+
this.get = function (key) {
95+
return deserializer(window[storageType].getItem(storageKeyPrefix + key));
96+
};
97+
98+
// Note: This is not very elegant at all.
99+
this.set = function (key, value) {
100+
return window[storageType].setItem(storageKeyPrefix + key, serializer(value));
101+
};
102+
103+
this.$get = [
104+
'$rootScope',
105+
'$window',
106+
'$log',
107+
'$timeout',
108+
'$document',
113109

114-
// When Safari (OS X or iOS) is in private browsing mode, it appears as though localStorage
115-
// is available, but trying to call .setItem throws an exception below:
116-
// "QUOTA_EXCEEDED_ERR: DOM Exception 22: An attempt was made to add something to storage that exceeded the quota."
117-
if (supported && storageType === 'localStorage') {
118-
var key = '__' + Math.round(Math.random() * 1e7);
110+
function ($rootScope, $window, $log, $timeout, $document) {
111+
function isStorageSupported(storageType) {
119112

113+
// Some installations of IE, for an unknown reason, throw "SCRIPT5: Error: Access is denied"
114+
// when accessing window.localStorage. This happens before you try to do anything with it. Catch
115+
// that error and allow execution to continue.
116+
117+
// fix 'SecurityError: DOM Exception 18' exception in Desktop Safari, Mobile Safari
118+
// when "Block cookies": "Always block" is turned on
119+
var supported;
120120
try {
121-
localStorage.setItem(key, key);
122-
localStorage.removeItem(key);
121+
supported = $window[storageType];
123122
}
124123
catch (err) {
125124
supported = false;
126125
}
127-
}
128-
129-
return supported;
130-
}
131126

132-
// The magic number 10 is used which only works for some keyPrefixes...
133-
// See https://github.com/gsklee/ngStorage/issues/137
134-
var prefixLength = storageKeyPrefix.length;
127+
// When Safari (OS X or iOS) is in private browsing mode, it appears as though localStorage
128+
// is available, but trying to call .setItem throws an exception below:
129+
// "QUOTA_EXCEEDED_ERR: DOM Exception 22: An attempt was made to add something to storage that exceeded the quota."
130+
if (supported && storageType === 'localStorage') {
131+
var key = '__' + Math.round(Math.random() * 1e7);
135132

136-
// #9: Assign a placeholder object if Web Storage is unavailable to prevent breaking the entire AngularJS app
137-
var webStorage = isStorageSupported(storageType) || ($log.warn('This browser does not support Web Storage!'), {setItem: angular.noop, getItem: angular.noop, removeItem: angular.noop}),
138-
$storage = {
139-
$default: function(items) {
140-
for (var k in items) {
141-
angular.isDefined($storage[k]) || ($storage[k] = angular.copy(items[k]) );
133+
try {
134+
localStorage.setItem(key, key);
135+
localStorage.removeItem(key);
142136
}
143-
144-
$storage.$sync();
145-
return $storage;
146-
},
147-
$reset: function(items) {
148-
for (var k in $storage) {
149-
'$' === k[0] || (delete $storage[k] && webStorage.removeItem(storageKeyPrefix + k));
137+
catch (err) {
138+
supported = false;
150139
}
140+
}
151141

152-
return $storage.$default(items);
153-
},
154-
$sync: function () {
155-
for (var i = 0, l = webStorage.length, k; i < l; i++) {
156-
// #8, #10: `webStorage.key(i)` may be an empty string (or throw an exception in IE9 if `webStorage` is empty)
157-
(k = webStorage.key(i)) && storageKeyPrefix === k.slice(0, prefixLength) && ($storage[k.slice(prefixLength)] = deserializer(webStorage.getItem(k)));
158-
}
159-
},
160-
$apply: function() {
161-
var temp$storage;
142+
return supported;
143+
}
162144

163-
_debounce = null;
145+
// The magic number 10 is used which only works for some keyPrefixes...
146+
// See https://github.com/gsklee/ngStorage/issues/137
147+
var prefixLength = storageKeyPrefix.length;
164148

165-
if (!angular.equals($storage, _last$storage)) {
166-
temp$storage = angular.copy(_last$storage);
167-
angular.forEach($storage, function(v, k) {
168-
if (angular.isDefined(v) && '$' !== k[0]) {
169-
webStorage.setItem(storageKeyPrefix + k, serializer(v));
170-
delete temp$storage[k];
171-
}
172-
});
149+
// #9: Assign a placeholder object if Web Storage is unavailable to prevent breaking the entire AngularJS app
150+
var webStorage = isStorageSupported(storageType) || ($log.warn('This browser does not support Web Storage!'), {setItem: angular.noop, getItem: angular.noop, removeItem: angular.noop}),
151+
$storage = {
152+
$default: function (items) {
153+
for (var k in items) {
154+
angular.isDefined($storage[k]) || ($storage[k] = objCopy(items[k]) );
155+
}
173156

174-
for (var k in temp$storage) {
175-
webStorage.removeItem(storageKeyPrefix + k);
157+
$storage.$sync();
158+
return $storage;
159+
},
160+
$reset: function (items) {
161+
for (var k in $storage) {
162+
'$' === k[0] || (delete $storage[k] && webStorage.removeItem(storageKeyPrefix + k));
176163
}
177164

178-
_last$storage = angular.copy($storage);
179-
}
165+
return $storage.$default(items);
166+
},
167+
$sync: function () {
168+
for (var i = 0, l = webStorage.length, k; i < l; i++) {
169+
// #8, #10: `webStorage.key(i)` may be an empty string (or throw an exception in IE9 if `webStorage` is empty)
170+
(k = webStorage.key(i)) && storageKeyPrefix === k.slice(0, prefixLength) && ($storage[k.slice(prefixLength)] = deserializer(webStorage.getItem(k)));
171+
}
172+
},
173+
$apply: function () {
174+
var temp$storage;
175+
176+
_debounce = null;
177+
178+
if (!objEquals($storage, _last$storage)) {
179+
temp$storage = objCopy(_last$storage);
180+
angular.forEach($storage, function (v, k) {
181+
if (angular.isDefined(v) && '$' !== k[0]) {
182+
webStorage.setItem(storageKeyPrefix + k, serializer(v));
183+
delete temp$storage[k];
184+
}
185+
});
186+
187+
for (var k in temp$storage) {
188+
webStorage.removeItem(storageKeyPrefix + k);
189+
}
190+
191+
_last$storage = objCopy($storage);
192+
}
193+
},
180194
},
181-
},
182-
_last$storage,
183-
_debounce;
195+
_last$storage,
196+
_debounce;
184197

185-
$storage.$sync();
198+
$storage.$sync();
186199

187-
_last$storage = angular.copy($storage);
200+
_last$storage = objCopy($storage);
188201

189-
$rootScope.$watch(function() {
190-
_debounce || (_debounce = $timeout($storage.$apply, 100, false));
191-
});
202+
$rootScope.$watch(function () {
203+
_debounce || (_debounce = $timeout($storage.$apply, 100, false));
204+
});
192205

193-
// #6: Use `$window.addEventListener` instead of `angular.element` to avoid the jQuery-specific `event.originalEvent`
194-
$window.addEventListener && $window.addEventListener('storage', function(event) {
195-
if (!event.key) {
196-
return;
197-
}
206+
// #6: Use `$window.addEventListener` instead of `angular.element` to avoid the jQuery-specific `event.originalEvent`
207+
$window.addEventListener && $window.addEventListener('storage', function (event) {
208+
if (!event.key) {
209+
return;
210+
}
198211

199-
// Reference doc.
200-
var doc = $document[0];
212+
// Reference doc.
213+
var doc = $document[0];
201214

202-
if ( (!doc.hasFocus || !doc.hasFocus()) && storageKeyPrefix === event.key.slice(0, prefixLength) ) {
203-
event.newValue ? $storage[event.key.slice(prefixLength)] = deserializer(event.newValue) : delete $storage[event.key.slice(prefixLength)];
215+
if ((!doc.hasFocus || !doc.hasFocus()) && storageKeyPrefix === event.key.slice(0, prefixLength)) {
216+
event.newValue ? $storage[event.key.slice(prefixLength)] = deserializer(event.newValue) : delete $storage[event.key.slice(prefixLength)];
204217

205-
_last$storage = angular.copy($storage);
218+
_last$storage = objCopy($storage);
206219

207-
$rootScope.$apply();
208-
}
209-
});
220+
$rootScope.$apply();
221+
}
222+
});
210223

211-
$window.addEventListener && $window.addEventListener('beforeunload', function() {
212-
$storage.$apply();
213-
});
224+
$window.addEventListener && $window.addEventListener('beforeunload', function () {
225+
$storage.$apply();
226+
});
214227

215-
return $storage;
216-
}
217-
];
218-
};
228+
return $storage;
229+
}
230+
];
231+
};
219232
}
220233

221234
}));

0 commit comments

Comments
 (0)