├── .gitdown
├── README.md
├── _badges.md
├── _browser-compatibility.md
├── _contributing.md
├── _development.md
├── _installation.md
└── _usage.md
├── .gitignore
├── .jscsrc
├── .jshintrc
├── .travis.yml
├── LICENSE
├── README.md
├── bower.json
├── changes.md
├── config.js
├── dist
├── angular-locker.js
├── angular-locker.min.js
└── angular-locker.min.js.map
├── gulpfile.babel.js
├── jsdoc.json
├── package.json
├── src
└── angular-locker.js
└── test
├── karma.conf.js
├── mock
└── storageMock.js
└── spec
└── angular-locker.spec.js
/.gitdown/README.md:
--------------------------------------------------------------------------------
1 | angular-locker
2 | ==============
3 |
4 | A simple & configurable abstraction for local/session storage in angular projects - providing a fluent api that is powerful and easy to use.
5 |
6 | {"gitdown": "include", "file": ".gitdown/_badges.md"}
7 |
8 | {"gitdown": "contents"}
9 |
10 | {"gitdown": "include", "file": ".gitdown/_installation.md"}
11 |
12 | {"gitdown": "include", "file": ".gitdown/_usage.md"}
13 |
14 | {"gitdown": "include", "file": ".gitdown/_browser-compatibility.md"}
15 |
16 | {"gitdown": "include", "file": ".gitdown/_contributing.md"}
17 |
18 | {"gitdown": "include", "file": ".gitdown/_development.md"}
19 |
20 | ## License
21 |
22 | {"gitdown": "include", "file": "LICENSE"}
23 |
--------------------------------------------------------------------------------
/.gitdown/_badges.md:
--------------------------------------------------------------------------------
1 | [](https://travis-ci.org/tymondesigns/angular-locker)
2 | [](https://codeclimate.com/github/tymondesigns/angular-locker)
3 | [](https://codeclimate.com/github/tymondesigns/angular-locker)
4 | [](http://www.opensource.org/licenses/MIT)
5 | [](https://www.npmjs.org/package/angular-locker)
6 | [](https://www.npmjs.org/package/angular-locker)
--------------------------------------------------------------------------------
/.gitdown/_browser-compatibility.md:
--------------------------------------------------------------------------------
1 | ## Browser Compatibility
2 |
3 | IE8 is not supported because I am utilising `Object.keys()`
4 |
5 | To check if the browser natively supports local and session storage, you can do the following:
6 |
7 | ```js
8 | if (! locker.supported()) {
9 | // load a polyfill?
10 | }
11 | ```
12 |
13 | I would recommend using [Remy's Storage polyfill](https://gist.github.com/remy/350433) if you want to support older browsers.
14 |
15 | For the latest browser compatibility chart see [HERE](http://caniuse.com/namevalue-storage)
--------------------------------------------------------------------------------
/.gitdown/_contributing.md:
--------------------------------------------------------------------------------
1 | ## Contributing
2 |
3 | Take care to maintain the existing coding style using the provided `.jscsrc` file. Add unit tests for any new or changed functionality. Lint and test your code using Gulp.
--------------------------------------------------------------------------------
/.gitdown/_development.md:
--------------------------------------------------------------------------------
1 | ## Development
2 |
3 | ```bash
4 | $ npm install
5 | $ gulp
6 | ```
--------------------------------------------------------------------------------
/.gitdown/_installation.md:
--------------------------------------------------------------------------------
1 | ## Installation
2 |
3 | #### via bower
4 |
5 | ```bash
6 | $ bower install angular-locker
7 | ```
8 |
9 | #### via npm
10 |
11 | ```bash
12 | $ npm install angular-locker
13 | ```
14 |
15 | #### via jsDelivr CDN
16 |
17 | http://www.jsdelivr.com/#!angular.locker
18 |
19 | #### manual
20 |
21 | Simply download the zip file [HERE](https://github.com/tymondesigns/angular-locker/archive/master.zip) and include `dist/angular-locker.min.js` in your project.
22 |
23 | {"gitdown": "filesize", "file": "dist/angular-locker.min.js", "gzip": true} Minified & gzipped.
--------------------------------------------------------------------------------
/.gitdown/_usage.md:
--------------------------------------------------------------------------------
1 | ## Usage
2 |
3 | ### Adding to your project
4 |
5 | Add `angular-locker` as a dependency
6 |
7 | ```js
8 | angular.module('myApp', ['angular-locker'])
9 | ```
10 |
11 | Configure via `lockerProvider` (*optional*)
12 |
13 | ```js
14 | .config(['lockerProvider', function config(lockerProvider) {
15 | lockerProvider.defaults({
16 | driver: 'session',
17 | namespace: 'myApp',
18 | separator: '.',
19 | eventsEnabled: true,
20 | extend: {}
21 | });
22 | }]);
23 | ```
24 |
25 | *Note*: You can also pass `false` to `namespace` if you prefer to not have a namespace in your keys.
26 |
27 | inject `locker` into your controller/service/directive etc
28 |
29 | ```js
30 | .factory('MyFactory', ['locker', function MyFactory(locker) {
31 | locker.put('someKey', 'someVal');
32 | }]);
33 | ```
34 |
35 | #### Extending Locker
36 |
37 | You can pass in an implementation of the [Storage Interface](https://developer.mozilla.org/en-US/docs/Web/API/Storage) to the `lockerProvider` as described above. e.g.
38 |
39 | ```js
40 | lockerProvider.defaults({
41 | extend: {
42 | myCustomStore: function () {
43 | // getItem
44 | // setItem
45 | // removeItem
46 | // etc
47 | }
48 | }
49 | });
50 |
51 | // then use as normal
52 | locker.driver('myCustomStore').put('foo', 'bar');
53 | ```
54 |
55 | See my [storageMock](https://github.com/tymondesigns/angular-locker/blob/master/test/mock/storageMock.js) for an example on how to define a custom implementation.
56 |
57 | ----------------------------
58 |
59 | ### Switching storage drivers
60 |
61 | There may be times where you will want to dynamically switch between using local and session storage.
62 | To achieve this, simply chain the `driver()` setter to specify what storage driver you want to use, as follows:
63 |
64 | ```js
65 | // put an item into session storage
66 | locker.driver('session').put('sessionKey', ['some', 'session', 'data']);
67 |
68 | // this time use local storage
69 | locker.driver('local').put('localKey', ['some', 'persistent', 'things']);
70 | ```
71 |
72 | ### Switching namespace
73 |
74 | ```js
75 | // add an item within a different namespace
76 | locker.namespace('otherNamespace').put('foo', 'bar');
77 | ```
78 |
79 | Omitting the driver or namespace setters will respect whatever default was specified via `lockerProvider`.
80 |
81 | ----------------------------
82 |
83 | ### Adding items to locker
84 |
85 | there are several ways to add something to locker:
86 |
87 | You can add Objects, Arrays, whatever :)
88 |
89 | locker will automatically serialize your objects/arrays in local/session storage
90 |
91 | ```js
92 | locker.put('someString', 'anyDataType');
93 | locker.put('someObject', { foo: 'I will be serialized', bar: 'pretty cool eh' });
94 | locker.put('someArray', ['foo', 'bar', 'baz']);
95 | // etc
96 | ```
97 |
98 | #### adding via value function param
99 |
100 | Inserts specified key and return value of function
101 |
102 | ```js
103 | locker.put('someKey', function() {
104 | var obj = { foo: 'bar', bar: 'baz' };
105 | // some other logic
106 | return obj;
107 | });
108 | ```
109 |
110 | The current value will be passed into the function so you can perform logic on the current value, before returning it. e.g.
111 |
112 | ```js
113 | locker.put('someKey', ['foo', 'bar']);
114 |
115 | locker.put('someKey', function(current) {
116 | current.push('baz');
117 |
118 | return current;
119 | });
120 |
121 | locker.get('someKey'); // = ['foo', 'bar', 'baz']
122 | ```
123 |
124 | If the current value is not already set then you can pass a third parameter as a default that will be returned instead. e.g.
125 |
126 | ```js
127 | // given locker.get('foo') is not defined
128 | locker.put('foo', function (current) {
129 | // current will equal 'bar'
130 | }, 'bar');
131 | ```
132 |
133 | #### adding multiple items at once by passing a single object
134 |
135 | This will add each key/value pair as a **separate** item in storage
136 |
137 | ```js
138 | locker.put({
139 | someKey: 'johndoe',
140 | anotherKey: ['some', 'random', 'array'],
141 | boolKey: true
142 | });
143 | ```
144 |
145 | #### adding via key function param
146 |
147 | Inserts each item from the returned Object, similar to above
148 |
149 | ```js
150 | locker.put(function() {
151 | // some logic
152 | return {
153 | foo: ['lorem', 'ipsum', 'dolor'],
154 | user: {
155 | username: 'johndoe',
156 | displayName: 'Johnny Doe',
157 | active: true,
158 | role: 'user'
159 | }
160 | };
161 | });
162 | ```
163 |
164 | #### conditionally adding an item if it doesn't already exist
165 |
166 | For this functionality you can use the `add()` method.
167 |
168 | If the key already exists then no action will be taken and `false` will be returned
169 |
170 | ```js
171 | locker.add('someKey', 'someVal'); // true or false - whether the item was added or not
172 | ```
173 |
174 | ----------------------------
175 |
176 | ### Retrieving items from locker
177 |
178 | ```js
179 | // locker.put('fooArray', ['bar', 'baz', 'bob']);
180 |
181 | locker.get('fooArray'); // ['bar', 'baz', 'bob']
182 | ```
183 |
184 | #### setting a default value
185 |
186 | if the key does not exist then, if specified the default will be returned
187 |
188 | ```js
189 | locker.get('keyDoesNotExist', 'a default value'); // 'a default value'
190 | ```
191 |
192 | #### retrieving multiple items at once
193 |
194 | You may pass an array to the `get()` method to return an Object containing the specified keys (if they exist)
195 |
196 | ```js
197 | locker.get(['someKey', 'anotherKey', 'foo']);
198 |
199 | // will return something like...
200 | {
201 | someKey: 'someValue',
202 | anotherKey: true,
203 | foo: 'bar'
204 | }
205 | ```
206 |
207 | #### deleting afterwards
208 |
209 | You can also retrieve an item and then delete it via the `pull()` method
210 |
211 | ```js
212 | // locker.put('someKey', { foo: 'bar', baz: 'bob' });
213 |
214 | locker.pull('someKey', 'defaultVal'); // { foo: 'bar', baz: 'bob' }
215 |
216 | // then...
217 |
218 | locker.get('someKey', 'defaultVal'); // 'defaultVal'
219 | ```
220 |
221 | #### all items
222 |
223 | You can retrieve all items within the current namespace
224 |
225 | This will return an object containing all the key/value pairs in storage
226 |
227 | ```js
228 | locker.all();
229 | // or
230 | locker.namespace('somethingElse').all();
231 | ```
232 |
233 | #### counting items
234 |
235 | To count the number of items within a given namespace:
236 |
237 | ```js
238 | locker.count();
239 | // or
240 | locker.namespace('somethingElse').count();
241 | ```
242 |
243 | ----------------------------
244 |
245 | ### Checking item exists in locker
246 |
247 | You can determine whether an item exists in the current namespace via
248 |
249 | ```js
250 | locker.has('someKey'); // true or false
251 | // or
252 | locker.namespace('foo').has('bar');
253 |
254 | // e.g.
255 | if (locker.has('user.authToken') ) {
256 | // we're logged in
257 | } else {
258 | // go to login page or something
259 | }
260 | ```
261 |
262 | ----------------------------
263 |
264 | ### Removing items from locker
265 |
266 | The simplest way to remove an item is to pass the key to the `forget()` method
267 |
268 | ```js
269 | locker.forget('keyToRemove');
270 | // or
271 | locker.driver('session').forget('sessionKey');
272 | // etc..
273 | ```
274 |
275 | #### removing multiple items at once
276 |
277 | You can also pass an array.
278 |
279 | ```js
280 | locker.forget(['keyToRemove', 'anotherKeyToRemove', 'something', 'else']);
281 | ```
282 |
283 | #### removing all within namespace
284 |
285 | you can remove all the items within the currently set namespace via the `clean()` method
286 |
287 | ```js
288 | locker.clean();
289 | // or
290 | locker.namespace('someOtherNamespace').clean();
291 | ```
292 | #### removing all items within the currently set storage driver
293 |
294 | ```js
295 | locker.empty();
296 | ```
297 |
298 | ----------------------------
299 |
300 | ### Events
301 |
302 | There are 3 events that can be fired during various operations, these are:
303 |
304 | ```js
305 | // fired when a new item is added to storage
306 | $rootScope.$on('locker.item.added', function (e, payload) {
307 | // payload is equal to:
308 | {
309 | driver: 'local', // the driver that was set when the event was fired
310 | namespace: 'locker', // the namespace that was set when the event was fired
311 | key: 'foo', // the key that was added
312 | value: 'bar' // the value that was added
313 | }
314 | });
315 | ```
316 |
317 | ```js
318 | // fired when an item is removed from storage
319 | $rootScope.$on('locker.item.forgotten', function (e, payload) {
320 | // payload is equal to:
321 | {
322 | driver: 'local', // the driver that was set when the event was fired
323 | namespace: 'locker', // the namespace that was set when the event was fired
324 | key: 'foo', // the key that was removed
325 | }
326 | });
327 | ```
328 |
329 | ```js
330 | // fired when an item's value changes to something new
331 | $rootScope.$on('locker.item.updated', function (e, payload) {
332 | // payload is equal to:
333 | {
334 | driver: 'local', // the driver that was set when the event was fired
335 | namespace: 'locker', // the namespace that was set when the event was fired
336 | key: 'foo', // the key that was updated
337 | oldValue: 'bar', // the value that was set before the item was updated
338 | newValue: 'baz' // the new value that the item was updated to
339 | }
340 | });
341 | ```
342 |
343 | ----------------------------
344 |
345 | ### Binding to a $scope property
346 |
347 | You can bind a scope property to a key in storage. Whenever the $scope value changes, it will automatically be persisted in storage. e.g.
348 |
349 | ```js
350 | app.controller('AppCtrl', ['$scope', function ($scope) {
351 |
352 | locker.bind($scope, 'foo');
353 |
354 | $scope.foo = ['bar', 'baz'];
355 |
356 | locker.get('foo'); // = ['bar', 'baz']
357 |
358 | }]);
359 | ```
360 |
361 | You can also set a default value via the third parameter:
362 |
363 | ```js
364 | app.controller('AppCtrl', ['$scope', function ($scope) {
365 |
366 | locker.bind($scope, 'foo', 'someDefault');
367 |
368 | $scope.foo; // = 'someDefault'
369 |
370 | locker.get('foo'); // = 'someDefault'
371 |
372 | }]);
373 | ```
374 |
375 | To unbind the $scope property, simply use the unbind method:
376 |
377 |
378 | ```js
379 | app.controller('AppCtrl', ['$scope', function ($scope) {
380 |
381 | locker.unbind($scope, 'foo');
382 |
383 | $scope.foo; // = undefined
384 |
385 | locker.get('foo'); // = undefined
386 |
387 | }]);
388 | ```
389 |
390 | ----------------------------
391 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | vendor
3 | test/coverage
--------------------------------------------------------------------------------
/.jscsrc:
--------------------------------------------------------------------------------
1 | {
2 | "plugins": [
3 | "jscs-jsdoc"
4 | ],
5 | "jsDoc": {
6 | "checkAnnotations": true,
7 | "checkParamNames": true,
8 | "checkRedundantParams": true,
9 | "checkReturnTypes": true,
10 | "checkRedundantReturns": true,
11 | "requireReturnTypes": true,
12 | "requireNewlineAfterDescription": true,
13 | "checkTypes": "capitalizedNativeCase",
14 | "enforceExistence": "exceptExports"
15 | },
16 |
17 | "requireCurlyBraces": [
18 | "else",
19 | "for",
20 | "while",
21 | "do",
22 | "try",
23 | "catch"
24 | ],
25 | "validateIndentation": 4,
26 | "validateQuoteMarks": "'",
27 |
28 | "disallowMultipleLineStrings": true,
29 | "disallowMixedSpacesAndTabs": true,
30 | "disallowTrailingWhitespace": true,
31 |
32 | "disallowKeywordsOnNewLine": ["else"],
33 |
34 | "requireSpaceAfterKeywords": [
35 | "if",
36 | "else",
37 | "for",
38 | "while",
39 | "do",
40 | "switch",
41 | "return",
42 | "try",
43 | "catch"
44 | ],
45 |
46 | "requireSpaceBeforeBinaryOperators": [
47 | "=", "+=", "-=", "*=", "/=", "%=", "<<=", ">>=", ">>>=",
48 | "&=", "|=", "^=", "+=",
49 |
50 | "+", "-", "*", "/", "%", "<<", ">>", ">>>", "&",
51 | "|", "^", "&&", "||", "===", "==", ">=",
52 | "<=", "<", ">", "!=", "!=="
53 | ],
54 |
55 | "requireSpaceAfterBinaryOperators": true,
56 | "requireSpacesInConditionalExpression": true,
57 | "requireSpaceBeforeBlockStatements": true,
58 | "requireSpacesInForStatement": true,
59 | "requireLineFeedAtFileEnd": true,
60 | "requireSpaceAfterLineComment": true,
61 | "requireCamelCaseOrUpperCaseIdentifiers": true,
62 | "requireSpacesInFunctionExpression": {
63 | "beforeOpeningRoundBrace": true,
64 | "beforeOpeningCurlyBrace": true
65 | },
66 |
67 | "requireSpaceAfterPrefixUnaryOperators": ["!"],
68 |
69 | "disallowSpacesInCallExpression": true,
70 | "disallowSpaceAfterObjectKeys": true,
71 | "requireSpaceBeforeObjectValues": true,
72 | "requireCapitalizedConstructors": true,
73 | "requireDotNotation": true,
74 | "validateParameterSeparator": ", ",
75 |
76 | "maximumLineLength": 120
77 | }
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "node": true,
3 | "browser": true,
4 | "esnext": true,
5 | "bitwise": true,
6 | "camelcase": true,
7 | "curly": false,
8 | "eqeqeq": true,
9 | "indent": 4,
10 | "immed": true,
11 | "latedef": true,
12 | "newcap": true,
13 | "noarg": true,
14 | "quotmark": "single",
15 | "regexp": true,
16 | "undef": true,
17 | "unused": false,
18 | "trailing": false,
19 | "globals" : {
20 | "angular": true,
21 | "define": true
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: false
2 | language: node_js
3 | node_js:
4 | - 4
5 | - 6
6 | - 8
7 | before_script:
8 | - npm install -g gulp codeclimate-test-reporter
9 | after_script:
10 | - codeclimate-test-reporter < test/coverage/lcov/lcov.info
11 |
12 | env:
13 | global:
14 | - secure: jGnioqs1TSYzv+IfKLf6weOn7b7+xkEkGPhnVJ/YngvGBiyu5G3aFdJRV5xZos4H2lhhsIHUej1ZocLM61bFLdgLp8p5khEXP8TSZPsO3OGyAj4ZhFRtNwK6vcY1y1Lez8CA/3Clikz8iSm5Twg83hwofLr7C57kQJ22gruISm4=
15 | - secure: cigU5ReZLTT2l75BFfEvSBGy+GZ68ivSfs561Kc22vSDidEBb/1x1Ydv7cjNLbyGoT43MJBYFHL5xeyyY9haBn03sUpXHu/PjNULJFfQUWpizqhfSmC7OZJNVuonESruq4k7yHWGgUEE9aHuJGi8cDw1cB0k6TMAtY7LExQNLaE=
16 | - secure: YHkhMjp5bOE8unA9zwpwLuPy0V9Ml3Sa5uQ8SFAnyDIDoo7YC5SZHXf5FBOOUlL9u0+buYNKT9YseiSUCpqMK09Q1JsN4xZXl660QjaZTYNki+1bleEAmGEYXr60XNOSs6c53b7nxg7FtS1ozRCgx43bEBKXCJKHnbPEVwQrcUI=
17 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 Sean Tymon
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | angular-locker
2 | ==============
3 |
4 | A simple & configurable abstraction for local/session storage in angular projects - providing a fluent api that is powerful and easy to use.
5 |
6 | [](https://travis-ci.org/tymondesigns/angular-locker)
7 | [](https://codeclimate.com/github/tymondesigns/angular-locker)
8 | [](http://www.opensource.org/licenses/MIT)
9 | [](https://www.npmjs.org/package/angular-locker)
10 | [](https://www.npmjs.org/package/angular-locker)
11 | [](https://www.npmjs.org/package/angular-locker)
12 |
13 | * [Installation](#installation)
14 | * [Usage](#usage)
15 | * [Adding to your project](#usage-adding-to-your-project)
16 | * [Switching storage drivers](#usage-switching-storage-drivers)
17 | * [Switching namespace](#usage-switching-namespace)
18 | * [Adding items to locker](#usage-adding-items-to-locker)
19 | * [Retrieving items from locker](#usage-retrieving-items-from-locker)
20 | * [Checking item exists in locker](#usage-checking-item-exists-in-locker)
21 | * [Removing items from locker](#usage-removing-items-from-locker)
22 | * [Events](#usage-events)
23 | * [Binding to a $scope property](#usage-binding-to-a-scope-property)
24 | * [Browser Compatibility](#browser-compatibility)
25 | * [Contributing](#contributing)
26 | * [Development](#development)
27 | * [License](#license)
28 |
29 |
30 |
Installation
31 |
32 | via bower
33 |
34 | ```bash
35 | $ bower install angular-locker
36 | ```
37 |
38 | via npm
39 |
40 | ```bash
41 | $ npm install angular-locker
42 | ```
43 |
44 | via jsDelivr CDN
45 |
46 | http://www.jsdelivr.com/#!angular.locker
47 |
48 | manual
49 |
50 | Simply download the zip file [HERE](https://github.com/tymondesigns/angular-locker/archive/master.zip) and include `dist/angular-locker.min.js` in your project.
51 |
52 | 1.66 KB Minified & gzipped.
53 |
54 | Usage
55 |
56 | Adding to your project
57 |
58 | Add `angular-locker` as a dependency
59 |
60 | ```js
61 | angular.module('myApp', ['angular-locker'])
62 | ```
63 |
64 | Configure via `lockerProvider` (*optional*)
65 |
66 | ```js
67 | .config(['lockerProvider', function config(lockerProvider) {
68 | lockerProvider.defaults({
69 | driver: 'session',
70 | namespace: 'myApp',
71 | separator: '.',
72 | eventsEnabled: true,
73 | extend: {}
74 | });
75 | }]);
76 | ```
77 |
78 | *Note*: You can also pass `false` to `namespace` if you prefer to not have a namespace in your keys.
79 |
80 | inject `locker` into your controller/service/directive etc
81 |
82 | ```js
83 | .factory('MyFactory', ['locker', function MyFactory(locker) {
84 | locker.put('someKey', 'someVal');
85 | }]);
86 | ```
87 |
88 | Extending Locker
89 |
90 | You can pass in an implementation of the [Storage Interface](https://developer.mozilla.org/en-US/docs/Web/API/Storage) to the `lockerProvider` as described above. e.g.
91 |
92 | ```js
93 | lockerProvider.defaults({
94 | extend: {
95 | myCustomStore: function () {
96 | // getItem
97 | // setItem
98 | // removeItem
99 | // etc
100 | }
101 | }
102 | });
103 |
104 | // then use as normal
105 | locker.driver('myCustomStore').put('foo', 'bar');
106 | ```
107 |
108 | See my [storageMock](https://github.com/tymondesigns/angular-locker/blob/master/test/mock/storageMock.js) for an example on how to define a custom implementation.
109 |
110 | ----------------------------
111 |
112 | Switching storage drivers
113 |
114 | There may be times where you will want to dynamically switch between using local and session storage.
115 | To achieve this, simply chain the `driver()` setter to specify what storage driver you want to use, as follows:
116 |
117 | ```js
118 | // put an item into session storage
119 | locker.driver('session').put('sessionKey', ['some', 'session', 'data']);
120 |
121 | // this time use local storage
122 | locker.driver('local').put('localKey', ['some', 'persistent', 'things']);
123 | ```
124 |
125 | Switching namespace
126 |
127 | ```js
128 | // add an item within a different namespace
129 | locker.namespace('otherNamespace').put('foo', 'bar');
130 | ```
131 |
132 | Omitting the driver or namespace setters will respect whatever default was specified via `lockerProvider`.
133 |
134 | ----------------------------
135 |
136 | Adding items to locker
137 |
138 | there are several ways to add something to locker:
139 |
140 | You can add Objects, Arrays, whatever :)
141 |
142 | locker will automatically serialize your objects/arrays in local/session storage
143 |
144 | ```js
145 | locker.put('someString', 'anyDataType');
146 | locker.put('someObject', { foo: 'I will be serialized', bar: 'pretty cool eh' });
147 | locker.put('someArray', ['foo', 'bar', 'baz']);
148 | // etc
149 | ```
150 |
151 | adding via value function param
152 |
153 | Inserts specified key and return value of function
154 |
155 | ```js
156 | locker.put('someKey', function() {
157 | var obj = { foo: 'bar', bar: 'baz' };
158 | // some other logic
159 | return obj;
160 | });
161 | ```
162 |
163 | The current value will be passed into the function so you can perform logic on the current value, before returning it. e.g.
164 |
165 | ```js
166 | locker.put('someKey', ['foo', 'bar']);
167 |
168 | locker.put('someKey', function(current) {
169 | current.push('baz');
170 |
171 | return current;
172 | });
173 |
174 | locker.get('someKey'); // = ['foo', 'bar', 'baz']
175 | ```
176 |
177 | If the current value is not already set then you can pass a third parameter as a default that will be returned instead. e.g.
178 |
179 | ```js
180 | // given locker.get('foo') is not defined
181 | locker.put('foo', function (current) {
182 | // current will equal 'bar'
183 | }, 'bar');
184 | ```
185 |
186 | adding multiple items at once by passing a single object
187 |
188 | This will add each key/value pair as a **separate** item in storage
189 |
190 | ```js
191 | locker.put({
192 | someKey: 'johndoe',
193 | anotherKey: ['some', 'random', 'array'],
194 | boolKey: true
195 | });
196 | ```
197 |
198 | adding via key function param
199 |
200 | Inserts each item from the returned Object, similar to above
201 |
202 | ```js
203 | locker.put(function() {
204 | // some logic
205 | return {
206 | foo: ['lorem', 'ipsum', 'dolor'],
207 | user: {
208 | username: 'johndoe',
209 | displayName: 'Johnny Doe',
210 | active: true,
211 | role: 'user'
212 | }
213 | };
214 | });
215 | ```
216 |
217 | conditionally adding an item if it doesn't already exist
218 |
219 | For this functionality you can use the `add()` method.
220 |
221 | If the key already exists then no action will be taken and `false` will be returned
222 |
223 | ```js
224 | locker.add('someKey', 'someVal'); // true or false - whether the item was added or not
225 | ```
226 |
227 | ----------------------------
228 |
229 | Retrieving items from locker
230 |
231 | ```js
232 | // locker.put('fooArray', ['bar', 'baz', 'bob']);
233 |
234 | locker.get('fooArray'); // ['bar', 'baz', 'bob']
235 | ```
236 |
237 | setting a default value
238 |
239 | if the key does not exist then, if specified the default will be returned
240 |
241 | ```js
242 | locker.get('keyDoesNotExist', 'a default value'); // 'a default value'
243 | ```
244 |
245 | retrieving multiple items at once
246 |
247 | You may pass an array to the `get()` method to return an Object containing the specified keys (if they exist)
248 |
249 | ```js
250 | locker.get(['someKey', 'anotherKey', 'foo']);
251 |
252 | // will return something like...
253 | {
254 | someKey: 'someValue',
255 | anotherKey: true,
256 | foo: 'bar'
257 | }
258 | ```
259 |
260 | deleting afterwards
261 |
262 | You can also retrieve an item and then delete it via the `pull()` method
263 |
264 | ```js
265 | // locker.put('someKey', { foo: 'bar', baz: 'bob' });
266 |
267 | locker.pull('someKey', 'defaultVal'); // { foo: 'bar', baz: 'bob' }
268 |
269 | // then...
270 |
271 | locker.get('someKey', 'defaultVal'); // 'defaultVal'
272 | ```
273 |
274 | all items
275 |
276 | You can retrieve all items within the current namespace
277 |
278 | This will return an object containing all the key/value pairs in storage
279 |
280 | ```js
281 | locker.all();
282 | // or
283 | locker.namespace('somethingElse').all();
284 | ```
285 |
286 | counting items
287 |
288 | To count the number of items within a given namespace:
289 |
290 | ```js
291 | locker.count();
292 | // or
293 | locker.namespace('somethingElse').count();
294 | ```
295 |
296 | ----------------------------
297 |
298 | Checking item exists in locker
299 |
300 | You can determine whether an item exists in the current namespace via
301 |
302 | ```js
303 | locker.has('someKey'); // true or false
304 | // or
305 | locker.namespace('foo').has('bar');
306 |
307 | // e.g.
308 | if (locker.has('user.authToken') ) {
309 | // we're logged in
310 | } else {
311 | // go to login page or something
312 | }
313 | ```
314 |
315 | ----------------------------
316 |
317 | Removing items from locker
318 |
319 | The simplest way to remove an item is to pass the key to the `forget()` method
320 |
321 | ```js
322 | locker.forget('keyToRemove');
323 | // or
324 | locker.driver('session').forget('sessionKey');
325 | // etc..
326 | ```
327 |
328 | removing multiple items at once
329 |
330 | You can also pass an array.
331 |
332 | ```js
333 | locker.forget(['keyToRemove', 'anotherKeyToRemove', 'something', 'else']);
334 | ```
335 |
336 | removing all within namespace
337 |
338 | you can remove all the items within the currently set namespace via the `clean()` method
339 |
340 | ```js
341 | locker.clean();
342 | // or
343 | locker.namespace('someOtherNamespace').clean();
344 | ```
345 | removing all items within the currently set storage driver
346 |
347 | ```js
348 | locker.empty();
349 | ```
350 |
351 | ----------------------------
352 |
353 | Events
354 |
355 | There are 3 events that can be fired during various operations, these are:
356 |
357 | ```js
358 | // fired when a new item is added to storage
359 | $rootScope.$on('locker.item.added', function (e, payload) {
360 | // payload is equal to:
361 | {
362 | driver: 'local', // the driver that was set when the event was fired
363 | namespace: 'locker', // the namespace that was set when the event was fired
364 | key: 'foo', // the key that was added
365 | value: 'bar' // the value that was added
366 | }
367 | });
368 | ```
369 |
370 | ```js
371 | // fired when an item is removed from storage
372 | $rootScope.$on('locker.item.forgotten', function (e, payload) {
373 | // payload is equal to:
374 | {
375 | driver: 'local', // the driver that was set when the event was fired
376 | namespace: 'locker', // the namespace that was set when the event was fired
377 | key: 'foo', // the key that was removed
378 | }
379 | });
380 | ```
381 |
382 | ```js
383 | // fired when an item's value changes to something new
384 | $rootScope.$on('locker.item.updated', function (e, payload) {
385 | // payload is equal to:
386 | {
387 | driver: 'local', // the driver that was set when the event was fired
388 | namespace: 'locker', // the namespace that was set when the event was fired
389 | key: 'foo', // the key that was updated
390 | oldValue: 'bar', // the value that was set before the item was updated
391 | newValue: 'baz' // the new value that the item was updated to
392 | }
393 | });
394 | ```
395 |
396 | ----------------------------
397 |
398 | Binding to a $scope property
399 |
400 | You can bind a scope property to a key in storage. Whenever the $scope value changes, it will automatically be persisted in storage. e.g.
401 |
402 | ```js
403 | app.controller('AppCtrl', ['$scope', function ($scope) {
404 |
405 | locker.bind($scope, 'foo');
406 |
407 | $scope.foo = ['bar', 'baz'];
408 |
409 | locker.get('foo'); // = ['bar', 'baz']
410 |
411 | }]);
412 | ```
413 |
414 | You can also set a default value via the third parameter:
415 |
416 | ```js
417 | app.controller('AppCtrl', ['$scope', function ($scope) {
418 |
419 | locker.bind($scope, 'foo', 'someDefault');
420 |
421 | $scope.foo; // = 'someDefault'
422 |
423 | locker.get('foo'); // = 'someDefault'
424 |
425 | }]);
426 | ```
427 |
428 | To unbind the $scope property, simply use the unbind method:
429 |
430 |
431 | ```js
432 | app.controller('AppCtrl', ['$scope', function ($scope) {
433 |
434 | locker.unbind($scope, 'foo');
435 |
436 | $scope.foo; // = undefined
437 |
438 | locker.get('foo'); // = undefined
439 |
440 | }]);
441 | ```
442 |
443 | ----------------------------
444 |
445 |
446 | Browser Compatibility
447 |
448 | IE8 is not supported because I am utilising `Object.keys()`
449 |
450 | To check if the browser natively supports local and session storage, you can do the following:
451 |
452 | ```js
453 | if (! locker.supported()) {
454 | // load a polyfill?
455 | }
456 | ```
457 |
458 | I would recommend using [Remy's Storage polyfill](https://gist.github.com/remy/350433) if you want to support older browsers.
459 |
460 | For the latest browser compatibility chart see [HERE](http://caniuse.com/namevalue-storage)
461 |
462 | Contributing
463 |
464 | Take care to maintain the existing coding style using the provided `.jscsrc` file. Add unit tests for any new or changed functionality. Lint and test your code using Gulp.
465 |
466 | Development
467 |
468 | ```bash
469 | $ npm install
470 | $ gulp
471 | ```
472 |
473 | License
474 |
475 | The MIT License (MIT)
476 |
477 | Copyright (c) 2014 Sean Tymon
478 |
479 | Permission is hereby granted, free of charge, to any person obtaining a copy
480 | of this software and associated documentation files (the "Software"), to deal
481 | in the Software without restriction, including without limitation the rights
482 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
483 | copies of the Software, and to permit persons to whom the Software is
484 | furnished to do so, subject to the following conditions:
485 |
486 | The above copyright notice and this permission notice shall be included in all
487 | copies or substantial portions of the Software.
488 |
489 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
490 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
491 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
492 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
493 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
494 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
495 | SOFTWARE.
496 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "angular-locker",
3 | "version": "2.0.5",
4 | "homepage": "https://github.com/tymondesigns/angular-locker",
5 | "authors": [
6 | "Sean Tymon "
7 | ],
8 | "description": "A simple & configurable abstraction for local/session storage in angular projects.",
9 | "main": "dist/angular-locker.js",
10 | "keywords": [
11 | "angular-locker",
12 | "angular",
13 | "locker",
14 | "storage",
15 | "localStorage",
16 | "sessionStorage",
17 | "session",
18 | "local"
19 | ],
20 | "dependencies": {
21 | "angular": ">=1.2.0 <2.0.0"
22 | },
23 | "devDependencies": {
24 | "angular-mocks": ">=1.2.0 <2.0.0"
25 | },
26 | "license": "MIT",
27 | "ignore": [
28 | "**/.*",
29 | "node_modules",
30 | "bower_components",
31 | "test"
32 | ]
33 | }
34 |
--------------------------------------------------------------------------------
/changes.md:
--------------------------------------------------------------------------------
1 | ### 2.0.5
2 |
3 | ##### Fixes
4 |
5 | - Fix compatibility with latest Chrome #38
6 |
7 | ### 2.0.4
8 |
9 | ##### Fixes
10 |
11 | - Fix Webpack support #30
12 |
13 | ### 2.0.3
14 |
15 | ##### General / Improvements
16 |
17 | - adding better Browserify & Webpack support
18 |
19 | ### 2.0.2
20 |
21 | ##### Fixes
22 |
23 | - Fixed issue with multiple sequential calls to same driver - see #22
24 |
25 | ##### General / Improvements
26 |
27 | - improving docblocks, to work with jsdoc
28 |
29 | ### 2.0.1
30 |
31 | ##### Fixes
32 |
33 | - Improving driver support checking - see #18
34 |
35 | ##### General / Improvements
36 |
37 | - Gulpfile now uses ES6 goodness via babel :)
38 | - Removing needless bower dev dependencies from project
39 |
40 | ### 2.0.0
41 |
42 | ##### Breaking Changes
43 |
44 | - Changed the way config is set via `lockerProvider` e.g.
45 | ```js
46 | lockerProvider.defaults({
47 | driver: 'session',
48 | namespace: 'myApp',
49 | separator: '.',
50 | eventsEnabled: true,
51 | extend: {}
52 | });
53 | ```
54 |
55 | ##### General
56 |
57 | - Added ability to extend locker at the config stage
58 | - Added `keys()` method to return an array of keys that exist within the current driver/namespace
59 | - Reduced size of minified file by removing *now* unnecessary functions
60 | - Adding third default parameter to `put()` method
61 | - Hugely refactored and simplified Gulp build process
62 | - Added [jscs](http://jscs.info/) to enforce coding style
63 | - Namespaces can now contain the separator without any issues
64 | - Lots of micro optimisations
65 |
--------------------------------------------------------------------------------
/config.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | export default {
4 | paths: {
5 | output: 'dist',
6 | vendor: [
7 | 'node_modules/angular/angular.js',
8 | 'node_modules/angular-mocks/angular-mocks.js'
9 | ],
10 | scripts: [
11 | 'src/angular-locker.js'
12 | ],
13 | test: [
14 | 'test/mock/storageMock.js',
15 | 'test/spec/**/*.js'
16 | ],
17 | versions: [
18 | './bower.json',
19 | './package.json'
20 | ],
21 | karma: 'test/karma.conf.js',
22 | gitdown: {
23 | src: '.gitdown/README.md',
24 | dest: 'README.md',
25 | glob: '.gitdown/**/*.md'
26 | }
27 | },
28 | banner: '/*! <%= pkg.name %> v<%= pkg.version %> | (c) ${new Date().getFullYear()} <%= pkg.author %> | <%= pkg.homepage %> */\n'
29 | };
30 |
--------------------------------------------------------------------------------
/dist/angular-locker.js:
--------------------------------------------------------------------------------
1 | /**
2 | * angular-locker
3 | *
4 | * A simple & configurable abstraction for local/session storage in angular projects.
5 | *
6 | * @link https://github.com/tymondesigns/angular-locker
7 | * @author Sean Tymon @tymondesigns
8 | * @license MIT License, http://www.opensource.org/licenses/MIT
9 | */
10 |
11 | (function (root, factory) {
12 | if (typeof define === 'function' && define.amd) {
13 | define(function () {
14 | return factory(root.angular || (window && window.angular));
15 | });
16 | } else if (typeof exports === 'object') {
17 | module.exports = factory(root.angular || (window && window.angular));
18 | } else {
19 | factory(root.angular);
20 | }
21 | })(this, function (angular) {
22 |
23 | 'use strict';
24 |
25 | return angular.module('angular-locker', [])
26 |
27 | .provider('locker', function () {
28 |
29 | /**
30 | * If value is a function then execute, otherwise return
31 | *
32 | * @private
33 | *
34 | * @param {Mixed} value The value to execute or return
35 | * @param {Mixed} param The parameter to pass to function if applicable
36 | *
37 | * @return {Mixed}
38 | */
39 | var _value = function (value, param) {
40 | return angular.isFunction(value) ? value(param) : value;
41 | };
42 |
43 | /**
44 | * Determine whether a value is defined and not null
45 | *
46 | * @private
47 | *
48 | * @param {Mixed} value The value to check
49 | *
50 | * @return {Boolean}
51 | */
52 | var _defined = function (value) {
53 | return angular.isDefined(value) && value !== null;
54 | };
55 |
56 | /**
57 | * Trigger an error
58 | *
59 | * @private
60 | * @throws {Error}
61 | *
62 | * @param {String} msg The error message
63 | */
64 | var _error = function (msg) {
65 | throw new Error('[angular-locker] ' + msg);
66 | };
67 |
68 | /**
69 | * Set the defaults
70 | *
71 | * @private
72 | *
73 | * @type {Object}
74 | */
75 | var defaults = {
76 | driver: 'local',
77 | namespace: 'locker',
78 | eventsEnabled: true,
79 | separator: '.',
80 | extend: {}
81 | };
82 |
83 | return {
84 |
85 | /**
86 | * Allow the defaults to be specified via the `lockerProvider`
87 | *
88 | * @param {Object} value The defaults to override
89 | */
90 | defaults: function (value) {
91 | if (! _defined(value)) return defaults;
92 |
93 | angular.forEach(value, function (val, key) {
94 | if (defaults.hasOwnProperty(key)) defaults[key] = val;
95 | });
96 | },
97 |
98 | /**
99 | * The locker service
100 | */
101 | $get: ['$window', '$rootScope', '$parse', function ($window, $rootScope, $parse) {
102 |
103 | /**
104 | * Define the Locker class
105 | *
106 | * @public
107 | * @constructor
108 | *
109 | * @param {Object} options The config options to initialize with
110 | */
111 | function Locker (options) {
112 |
113 | /**
114 | * The config options
115 | *
116 | * @private
117 | *
118 | * @type {Object}
119 | */
120 | this._options = options;
121 |
122 | /**
123 | * Out of the box drivers
124 | *
125 | * @private
126 | *
127 | * @type {Object}
128 | */
129 | this._registeredDrivers = angular.extend({
130 | local: $window.localStorage,
131 | session: $window.sessionStorage
132 | }, options.extend);
133 |
134 | /**
135 | * Get the Storage instance from the key
136 | *
137 | * @private
138 | *
139 | * @param {String} driver The storage driver identifier
140 | *
141 | * @return {Storage}
142 | */
143 | this._resolveDriver = function (driver) {
144 | if (! this._registeredDrivers.hasOwnProperty(driver)) {
145 | _error('The driver "' + driver + '" was not found.');
146 | }
147 |
148 | return this._registeredDrivers[driver];
149 | };
150 |
151 | /**
152 | * The driver instance
153 | *
154 | * @private
155 | *
156 | * @type {Storage}
157 | */
158 | this._driver = this._resolveDriver(options.driver);
159 |
160 | /**
161 | * The namespace value
162 | *
163 | * @private
164 | *
165 | * @type {String}
166 | */
167 | this._namespace = options.namespace;
168 |
169 | /**
170 | * Separates the namespace from the keys
171 | *
172 | * @private
173 | *
174 | * @type {String}
175 | */
176 | this._separator = options.separator;
177 |
178 | /**
179 | * Store the watchers here so we can un-register them later
180 | *
181 | * @private
182 | *
183 | * @type {Object}
184 | */
185 | this._watchers = {};
186 |
187 | /**
188 | * Check browser support
189 | *
190 | * @private
191 | * @see github.com/Modernizr/Modernizr/blob/master/feature-detects/storage/localstorage.js#L38-L47
192 | *
193 | * @param {String} driver The driver to check support with
194 | *
195 | * @return {Boolean}
196 | */
197 | this._checkSupport = function (driver) {
198 | if (! _defined(this._supported)) {
199 | var l = 'l';
200 | try {
201 | this._resolveDriver(driver || options.driver).setItem(l, l);
202 | this._resolveDriver(driver || options.driver).removeItem(l);
203 | this._supported = true;
204 | } catch (e) {
205 | this._supported = false;
206 | }
207 | }
208 |
209 | return this._supported;
210 | };
211 |
212 | /**
213 | * Build the storage key from the namspace
214 | *
215 | * @private
216 | *
217 | * @param {String} key The key to build the prefix with
218 | *
219 | * @return {String}
220 | */
221 | this._getPrefix = function (key) {
222 | if (! this._namespace) return key;
223 |
224 | return this._namespace + this._separator + key;
225 | };
226 |
227 | /**
228 | * Try to encode value as json, or just return the value upon failure
229 | *
230 | * @private
231 | *
232 | * @param {Mixed} value The value to serialize
233 | *
234 | * @return {Mixed}
235 | */
236 | this._serialize = function (value) {
237 | try {
238 | return angular.toJson(value);
239 | } catch (e) {
240 | return value;
241 | }
242 | };
243 |
244 | /**
245 | * Try to parse value as json, if it fails then it probably isn't json
246 | * so just return it
247 | *
248 | * @private
249 | *
250 | * @param {String} value The value to unserialize
251 | *
252 | * @return {Mixed}
253 | */
254 | this._unserialize = function (value) {
255 | try {
256 | return angular.fromJson(value);
257 | } catch (e) {
258 | return value;
259 | }
260 | };
261 |
262 | /**
263 | * Trigger an event
264 | *
265 | * @private
266 | *
267 | * @param {String} name The name of the event to trigger
268 | * @param {Object} payload The data to pass along with event
269 | */
270 | this._event = function (name, payload) {
271 | if (this._options.eventsEnabled) {
272 | $rootScope.$emit('locker.' + name, angular.extend(payload, {
273 | driver: this._options.driver,
274 | namespace: this._namespace,
275 | }));
276 | }
277 | };
278 |
279 | /**
280 | * Add to storage
281 | *
282 | * @private
283 | * @throws {Error} if browser support fails
284 | *
285 | * @param {String} key The key to add
286 | * @param {Mixed} value The value to add
287 | */
288 | this._setItem = function (key, value) {
289 | if (! this._checkSupport()) {
290 | _error('The browser does not support the "' + options.driver + '" driver');
291 | }
292 |
293 | try {
294 | var oldVal = this._getItem(key);
295 | this._driver.setItem(this._getPrefix(key), this._serialize(value));
296 | if (this._exists(key) && ! angular.equals(oldVal, value)) {
297 | this._event('item.updated', { key: key, oldValue: oldVal, newValue: value });
298 | } else {
299 | this._event('item.added', { key: key, value: value });
300 | }
301 | } catch (e) {
302 | if (['QUOTA_EXCEEDED_ERR',
303 | 'NS_ERROR_DOM_QUOTA_REACHED',
304 | 'QuotaExceededError'].indexOf(e.name) !== -1) {
305 | _error('The browser storage quota has been exceeded');
306 | } else {
307 | _error('Could not add item with key "' + key + '"');
308 | }
309 | }
310 | };
311 |
312 | /**
313 | * Get from storage
314 | *
315 | * @private
316 | * @throws {Error} if browser support fails
317 | *
318 | * @param {String} key The key to get
319 | *
320 | * @return {Mixed}
321 | */
322 | this._getItem = function (key) {
323 | if (! this._checkSupport()) {
324 | _error('The browser does not support the "' + options.driver + '" driver');
325 | }
326 |
327 | return this._unserialize(this._driver.getItem(this._getPrefix(key)));
328 | };
329 |
330 | /**
331 | * Exists in storage
332 | *
333 | * @private
334 | * @throws {Error} if browser support fails
335 | *
336 | * @param {String} key The key to check for existence
337 | *
338 | * @return {Boolean}
339 | */
340 | this._exists = function (key) {
341 | if (! this._checkSupport()) {
342 | _error('The browser does not support the "' + options.driver + '" driver');
343 | }
344 |
345 | return this._driver.hasOwnProperty(this._getPrefix(_value(key))) || !! this._getItem(key);
346 | };
347 |
348 | /**
349 | * Remove from storage
350 | *
351 | * @private
352 | * @throws {Error} if browser support fails
353 | *
354 | * @param {String} key The key to remove
355 | *
356 | * @return {Boolean}
357 | */
358 | this._removeItem = function (key) {
359 | if (! this._checkSupport()) {
360 | _error('The browser does not support the "' + options.driver + '" driver');
361 | }
362 |
363 | if (! this._exists(key)) return false;
364 |
365 | this._driver.removeItem(this._getPrefix(key));
366 | this._event('item.forgotten', { key: key });
367 |
368 | return true;
369 | };
370 | }
371 |
372 | /**
373 | * Define the public api
374 | *
375 | * @public
376 | *
377 | * @type {Object}
378 | */
379 | Locker.prototype = {
380 |
381 | /**
382 | * Add a new item to storage (even if it already exists)
383 | *
384 | * @public
385 | *
386 | * @param {Mixed} key The key to add
387 | * @param {Mixed} value The value to add
388 | * @param {Mixed} def The default to pass to function if doesn't already exist
389 | *
390 | * @return {Locker|Boolean}
391 | */
392 | put: function (key, value, def) {
393 | if (! _defined(key)) return false;
394 | key = _value(key);
395 |
396 | if (angular.isObject(key)) {
397 | angular.forEach(key, function (value, key) {
398 | this._setItem(key, _defined(value) ? value : def);
399 | }, this);
400 | } else {
401 | if (! _defined(value)) return false;
402 | var val = this._getItem(key);
403 | this._setItem(key, _value(value, _defined(val) ? val : def));
404 | }
405 |
406 | return this;
407 | },
408 |
409 | /**
410 | * Add an item to storage if it doesn't already exist
411 | *
412 | * @public
413 | *
414 | * @param {Mixed} key The key to add
415 | * @param {Mixed} value The value to add
416 | * @param {Mixed} def The default to pass to function if doesn't already exist
417 | *
418 | * @return {Boolean}
419 | */
420 | add: function (key, value, def) {
421 | if (! this.has(key)) {
422 | this.put(key, value, def);
423 | return true;
424 | }
425 |
426 | return false;
427 | },
428 |
429 | /**
430 | * Retrieve the specified item from storage
431 | *
432 | * @public
433 | *
434 | * @param {String|Array} key The key to get
435 | * @param {Mixed} def The default value if it does not exist
436 | *
437 | * @return {Mixed}
438 | */
439 | get: function (key, def) {
440 | if (angular.isArray(key)) {
441 | var items = {};
442 | angular.forEach(key, function (k) {
443 | if (this.has(k)) items[k] = this._getItem(k);
444 | }, this);
445 |
446 | return items;
447 | }
448 |
449 | if (! this.has(key)) return arguments.length === 2 ? def : void 0;
450 |
451 | return this._getItem(key);
452 | },
453 |
454 | /**
455 | * Determine whether the item exists in storage
456 | *
457 | * @public
458 | *
459 | * @param {String|Function} key - The key to remove
460 | *
461 | * @return {Boolean}
462 | */
463 | has: function (key) {
464 | return this._exists(key);
465 | },
466 |
467 | /**
468 | * Remove specified item(s) from storage
469 | *
470 | * @public
471 | *
472 | * @param {String|Array} key The key or array of keys to remove
473 | *
474 | * @return {Object}
475 | */
476 | forget: function (key) {
477 | key = _value(key);
478 |
479 | if (angular.isArray(key)) {
480 | key.map(this._removeItem, this);
481 | } else {
482 | this._removeItem(key);
483 | }
484 |
485 | return this;
486 | },
487 |
488 | /**
489 | * Retrieve the specified item from storage and then remove it
490 | *
491 | * @public
492 | *
493 | * @param {String|Array} key The key to pull from storage
494 | * @param {Mixed} def The default value if it does not exist
495 | *
496 | * @return {Mixed}
497 | */
498 | pull: function (key, def) {
499 | var value = this.get(key, def);
500 | this.forget(key);
501 |
502 | return value;
503 | },
504 |
505 | /**
506 | * Return all items in storage within the current namespace/driver
507 | *
508 | * @public
509 | *
510 | * @return {Object}
511 | */
512 | all: function () {
513 | var items = {};
514 | angular.forEach(this._driver, function (value, key) {
515 | if (this._namespace) {
516 | var prefix = this._namespace + this._separator;
517 | if (key.indexOf(prefix) === 0) key = key.substring(prefix.length);
518 | }
519 | if (this.has(key)) items[key] = this.get(key);
520 | }, this);
521 |
522 | return items;
523 | },
524 |
525 | /**
526 | * Get the storage keys as an array
527 | *
528 | * @public
529 | *
530 | * @return {Array}
531 | */
532 | keys: function () {
533 | return Object.keys(this.all());
534 | },
535 |
536 | /**
537 | * Remove all items set within the current namespace/driver
538 | *
539 | * @public
540 | *
541 | * @return {Locker}
542 | */
543 | clean: function () {
544 | return this.forget(this.keys());
545 | },
546 |
547 | /**
548 | * Empty the current storage driver completely. careful now.
549 | *
550 | * @public
551 | *
552 | * @return {Locker}
553 | */
554 | empty: function () {
555 | this._driver.clear();
556 |
557 | return this;
558 | },
559 |
560 | /**
561 | * Get the total number of items within the current namespace
562 | *
563 | * @public
564 | *
565 | * @return {Integer}
566 | */
567 | count: function () {
568 | return this.keys().length;
569 | },
570 |
571 | /**
572 | * Bind a storage key to a $scope property
573 | *
574 | * @public
575 | *
576 | * @param {Object} $scope The angular $scope object
577 | * @param {String} key The key in storage to bind to
578 | * @param {Mixed} def The default value to initially bind
579 | *
580 | * @return {Locker}
581 | */
582 | bind: function ($scope, key, def) {
583 | if (! _defined( $scope.$eval(key) )) {
584 | $parse(key).assign($scope, this.get(key, def));
585 | this.add(key, def);
586 | }
587 |
588 | var self = this;
589 | this._watchers[key + $scope.$id] = $scope.$watch(key, function (newVal) {
590 | self.put(key, newVal);
591 | }, angular.isObject($scope[key]));
592 |
593 | return this;
594 | },
595 |
596 | /**
597 | * Unbind a storage key from a $scope property
598 | *
599 | * @public
600 | *
601 | * @param {Object} $scope The angular $scope object
602 | * @param {String} key The key to remove from bindings
603 | *
604 | * @return {Locker}
605 | */
606 | unbind: function ($scope, key) {
607 | $parse(key).assign($scope, void 0);
608 | this.forget(key);
609 |
610 | var watchId = key + $scope.$id;
611 |
612 | if (this._watchers[watchId]) {
613 | // execute the de-registration function
614 | this._watchers[watchId]();
615 | delete this._watchers[watchId];
616 | }
617 |
618 | return this;
619 | },
620 |
621 | /**
622 | * Set the storage driver on a new instance to enable overriding defaults
623 | *
624 | * @public
625 | *
626 | * @param {String} driver The driver to switch to
627 | *
628 | * @return {Locker}
629 | */
630 | driver: function (driver) {
631 | return this.instance(angular.extend(this._options, { driver: driver }));
632 | },
633 |
634 | /**
635 | * Get the currently set driver
636 | *
637 | * @public
638 | *
639 | * @return {Storage}
640 | */
641 | getDriver: function () {
642 | return this._driver;
643 | },
644 |
645 | /**
646 | * Set the namespace on a new instance to enable overriding defaults
647 | *
648 | * @public
649 | *
650 | * @param {String} namespace The namespace to switch to
651 | *
652 | * @return {Locker}
653 | */
654 | namespace: function (namespace) {
655 | return this.instance(angular.extend(this._options, { namespace: namespace }));
656 | },
657 |
658 | /**
659 | * Get the currently set namespace
660 | *
661 | * @public
662 | *
663 | * @return {String}
664 | */
665 | getNamespace: function () {
666 | return this._namespace;
667 | },
668 |
669 | /**
670 | * Check browser support
671 | *
672 | * @public
673 | * @see github.com/Modernizr/Modernizr/blob/master/feature-detects/storage/localstorage.js#L38-L47
674 | *
675 | * @param {String} driver The driver to check support with
676 | *
677 | * @return {Boolean}
678 | */
679 | supported: function (driver) {
680 | return this._checkSupport(driver);
681 | },
682 |
683 | /**
684 | * Get a new instance of Locker
685 | *
686 | * @public
687 | *
688 | * @param {Object} options The config options to instantiate with
689 | *
690 | * @return {Locker}
691 | */
692 | instance: function (options) {
693 | return new Locker(options);
694 | }
695 | };
696 |
697 | // return the default instance
698 | return new Locker(defaults);
699 | }]
700 | };
701 |
702 | }).name; // export module name for the likes of Browserify and Webpack
703 |
704 | });
705 |
--------------------------------------------------------------------------------
/dist/angular-locker.min.js:
--------------------------------------------------------------------------------
1 | /*! angular-locker v2.0.5 | (c) 2017 @tymondesigns | https://github.com/tymondesigns/angular-locker */
2 | !function(t,e){"function"==typeof define&&define.amd?define(function(){return e(t.angular||window&&window.angular)}):"object"==typeof exports?module.exports=e(t.angular||window&&window.angular):e(t.angular)}(this,function(t){"use strict";return t.module("angular-locker",[]).provider("locker",function(){var e=function(e,r){return t.isFunction(e)?e(r):e},r=function(e){return t.isDefined(e)&&null!==e},i=function(t){throw new Error("[angular-locker] "+t)},s={driver:"local",namespace:"locker",eventsEnabled:!0,separator:".",extend:{}};return{defaults:function(e){return r(e)?void t.forEach(e,function(t,e){s.hasOwnProperty(e)&&(s[e]=t)}):s},$get:["$window","$rootScope","$parse",function(n,o,h){function u(s){this._options=s,this._registeredDrivers=t.extend({local:n.localStorage,session:n.sessionStorage},s.extend),this._resolveDriver=function(t){return this._registeredDrivers.hasOwnProperty(t)||i('The driver "'+t+'" was not found.'),this._registeredDrivers[t]},this._driver=this._resolveDriver(s.driver),this._namespace=s.namespace,this._separator=s.separator,this._watchers={},this._checkSupport=function(t){if(!r(this._supported)){var e="l";try{this._resolveDriver(t||s.driver).setItem(e,e),this._resolveDriver(t||s.driver).removeItem(e),this._supported=!0}catch(i){this._supported=!1}}return this._supported},this._getPrefix=function(t){return this._namespace?this._namespace+this._separator+t:t},this._serialize=function(e){try{return t.toJson(e)}catch(r){return e}},this._unserialize=function(e){try{return t.fromJson(e)}catch(r){return e}},this._event=function(e,r){this._options.eventsEnabled&&o.$emit("locker."+e,t.extend(r,{driver:this._options.driver,namespace:this._namespace}))},this._setItem=function(e,r){this._checkSupport()||i('The browser does not support the "'+s.driver+'" driver');try{var n=this._getItem(e);this._driver.setItem(this._getPrefix(e),this._serialize(r)),this._exists(e)&&!t.equals(n,r)?this._event("item.updated",{key:e,oldValue:n,newValue:r}):this._event("item.added",{key:e,value:r})}catch(o){i(-1!==["QUOTA_EXCEEDED_ERR","NS_ERROR_DOM_QUOTA_REACHED","QuotaExceededError"].indexOf(o.name)?"The browser storage quota has been exceeded":'Could not add item with key "'+e+'"')}},this._getItem=function(t){return this._checkSupport()||i('The browser does not support the "'+s.driver+'" driver'),this._unserialize(this._driver.getItem(this._getPrefix(t)))},this._exists=function(t){return this._checkSupport()||i('The browser does not support the "'+s.driver+'" driver'),this._driver.hasOwnProperty(this._getPrefix(e(t)))||!!this._getItem(t)},this._removeItem=function(t){return this._checkSupport()||i('The browser does not support the "'+s.driver+'" driver'),this._exists(t)?(this._driver.removeItem(this._getPrefix(t)),this._event("item.forgotten",{key:t}),!0):!1}}return u.prototype={put:function(i,s,n){if(!r(i))return!1;if(i=e(i),t.isObject(i))t.forEach(i,function(t,e){this._setItem(e,r(t)?t:n)},this);else{if(!r(s))return!1;var o=this._getItem(i);this._setItem(i,e(s,r(o)?o:n))}return this},add:function(t,e,r){return this.has(t)?!1:(this.put(t,e,r),!0)},get:function(e,r){if(t.isArray(e)){var i={};return t.forEach(e,function(t){this.has(t)&&(i[t]=this._getItem(t))},this),i}return this.has(e)?this._getItem(e):2===arguments.length?r:void 0},has:function(t){return this._exists(t)},forget:function(r){return r=e(r),t.isArray(r)?r.map(this._removeItem,this):this._removeItem(r),this},pull:function(t,e){var r=this.get(t,e);return this.forget(t),r},all:function(){var e={};return t.forEach(this._driver,function(t,r){if(this._namespace){var i=this._namespace+this._separator;0===r.indexOf(i)&&(r=r.substring(i.length))}this.has(r)&&(e[r]=this.get(r))},this),e},keys:function(){return Object.keys(this.all())},clean:function(){return this.forget(this.keys())},empty:function(){return this._driver.clear(),this},count:function(){return this.keys().length},bind:function(e,i,s){r(e.$eval(i))||(h(i).assign(e,this.get(i,s)),this.add(i,s));var n=this;return this._watchers[i+e.$id]=e.$watch(i,function(t){n.put(i,t)},t.isObject(e[i])),this},unbind:function(t,e){h(e).assign(t,void 0),this.forget(e);var r=e+t.$id;return this._watchers[r]&&(this._watchers[r](),delete this._watchers[r]),this},driver:function(e){return this.instance(t.extend(this._options,{driver:e}))},getDriver:function(){return this._driver},namespace:function(e){return this.instance(t.extend(this._options,{namespace:e}))},getNamespace:function(){return this._namespace},supported:function(t){return this._checkSupport(t)},instance:function(t){return new u(t)}},new u(s)}]}}).name});
3 | //# sourceMappingURL=angular-locker.min.js.map
4 |
--------------------------------------------------------------------------------
/dist/angular-locker.min.js.map:
--------------------------------------------------------------------------------
1 | /*! angular-locker v2.0.5 | (c) 2017 @tymondesigns | https://github.com/tymondesigns/angular-locker */
2 | {"version":3,"sources":["angular-locker.min.js"],"names":["root","factory","define","amd","angular","window","exports","module","this","provider","_value","value","param","isFunction","_defined","isDefined","_error","msg","Error","defaults","driver","namespace","eventsEnabled","separator","extend","forEach","val","key","hasOwnProperty","$get","$window","$rootScope","$parse","Locker","options","_options","_registeredDrivers","local","localStorage","session","sessionStorage","_resolveDriver","_driver","_namespace","_separator","_watchers","_checkSupport","_supported","l","setItem","removeItem","e","_getPrefix","_serialize","toJson","_unserialize","fromJson","_event","name","payload","$emit","_setItem","oldVal","_getItem","_exists","equals","oldValue","newValue","indexOf","getItem","_removeItem","prototype","put","def","isObject","add","has","get","isArray","items","k","arguments","length","forget","map","pull","all","prefix","substring","keys","Object","clean","empty","clear","count","bind","$scope","$eval","assign","self","$id","$watch","newVal","unbind","watchId","instance","getDriver","getNamespace","supported"],"mappings":"CAUA,SAAWA,EAAMC,GACS,kBAAXC,SAAyBA,OAAOC,IACvCD,OAAO,WACH,MAAOD,GAAQD,EAAKI,SAAYC,QAAUA,OAAOD,WAE3B,gBAAZE,SACdC,OAAOD,QAAUL,EAAQD,EAAKI,SAAYC,QAAUA,OAAOD,SAE3DH,EAAQD,EAAKI,UAElBI,KAAM,SAAUJ,GAEf,YAEA,OAAOA,GAAQG,OAAO,qBAErBE,SAAS,SAAU,WAYhB,GAAIC,GAAS,SAAUC,EAAOC,GAC1B,MAAOR,GAAQS,WAAWF,GAASA,EAAMC,GAASD,GAYlDG,EAAW,SAAUH,GACrB,MAAOP,GAAQW,UAAUJ,IAAoB,OAAVA,GAWnCK,EAAS,SAAUC,GACnB,KAAM,IAAIC,OAAM,oBAAsBD,IAUtCE,GACAC,OAAQ,QACRC,UAAW,SACXC,eAAe,EACfC,UAAW,IACXC,UAGJ,QAOIL,SAAU,SAAUR,GAChB,MAAMG,GAASH,OAEfP,GAAQqB,QAAQd,EAAO,SAAUe,EAAKC,GAC9BR,EAASS,eAAeD,KAAMR,EAASQ,GAAOD,KAHxBP,GAUlCU,MAAO,UAAW,aAAc,SAAU,SAAUC,EAASC,EAAYC,GAUrE,QAASC,GAAQC,GASb1B,KAAK2B,SAAWD,EAShB1B,KAAK4B,mBAAqBhC,EAAQoB,QAC9Ba,MAAOP,EAAQQ,aACfC,QAAST,EAAQU,gBAClBN,EAAQV,QAWXhB,KAAKiC,eAAiB,SAAUrB,GAK5B,MAJMZ,MAAK4B,mBAAmBR,eAAeR,IACzCJ,EAAO,eAAiBI,EAAS,oBAG9BZ,KAAK4B,mBAAmBhB,IAUnCZ,KAAKkC,QAAUlC,KAAKiC,eAAeP,EAAQd,QAS3CZ,KAAKmC,WAAaT,EAAQb,UAS1Bb,KAAKoC,WAAaV,EAAQX,UAS1Bf,KAAKqC,aAYLrC,KAAKsC,cAAgB,SAAU1B,GAC3B,IAAMN,EAASN,KAAKuC,YAAa,CAC7B,GAAIC,GAAI,GACR,KACIxC,KAAKiC,eAAerB,GAAUc,EAAQd,QAAQ6B,QAAQD,EAAGA,GACzDxC,KAAKiC,eAAerB,GAAUc,EAAQd,QAAQ8B,WAAWF,GACzDxC,KAAKuC,YAAa,EACpB,MAAOI,GACL3C,KAAKuC,YAAa,GAI1B,MAAOvC,MAAKuC,YAYhBvC,KAAK4C,WAAa,SAAUzB,GACxB,MAAMnB,MAAKmC,WAEJnC,KAAKmC,WAAanC,KAAKoC,WAAajB,EAFbA,GAclCnB,KAAK6C,WAAa,SAAU1C,GACxB,IACI,MAAOP,GAAQkD,OAAO3C,GACxB,MAAOwC,GACL,MAAOxC,KAcfH,KAAK+C,aAAe,SAAU5C,GAC1B,IACI,MAAOP,GAAQoD,SAAS7C,GAC1B,MAAOwC,GACL,MAAOxC,KAYfH,KAAKiD,OAAS,SAAUC,EAAMC,GACtBnD,KAAK2B,SAASb,eACdS,EAAW6B,MAAM,UAAYF,EAAMtD,EAAQoB,OAAOmC,GAC9CvC,OAAQZ,KAAK2B,SAASf,OACtBC,UAAWb,KAAKmC,eAc5BnC,KAAKqD,SAAW,SAAUlC,EAAKhB,GACrBH,KAAKsC,iBACP9B,EAAO,qCAAuCkB,EAAQd,OAAS,WAGnE,KACI,GAAI0C,GAAStD,KAAKuD,SAASpC,EAC3BnB,MAAKkC,QAAQO,QAAQzC,KAAK4C,WAAWzB,GAAMnB,KAAK6C,WAAW1C,IACvDH,KAAKwD,QAAQrC,KAAUvB,EAAQ6D,OAAOH,EAAQnD,GAC9CH,KAAKiD,OAAO,gBAAkB9B,IAAKA,EAAKuC,SAAUJ,EAAQK,SAAUxD,IAEpEH,KAAKiD,OAAO,cAAgB9B,IAAKA,EAAKhB,MAAOA,IAEnD,MAAOwC,GAIDnC,EAD0C,MAFzC,qBACD,6BACA,sBAAsBoD,QAAQjB,EAAEO,MACzB,8CAEA,gCAAkC/B,EAAM,OAe3DnB,KAAKuD,SAAW,SAAUpC,GAKtB,MAJMnB,MAAKsC,iBACP9B,EAAO,qCAAuCkB,EAAQd,OAAS,YAG5DZ,KAAK+C,aAAa/C,KAAKkC,QAAQ2B,QAAQ7D,KAAK4C,WAAWzB,MAalEnB,KAAKwD,QAAU,SAAUrC,GAKrB,MAJMnB,MAAKsC,iBACP9B,EAAO,qCAAuCkB,EAAQd,OAAS,YAG5DZ,KAAKkC,QAAQd,eAAepB,KAAK4C,WAAW1C,EAAOiB,QAAanB,KAAKuD,SAASpC,IAazFnB,KAAK8D,YAAc,SAAU3C,GAKzB,MAJMnB,MAAKsC,iBACP9B,EAAO,qCAAuCkB,EAAQd,OAAS,YAG7DZ,KAAKwD,QAAQrC,IAEnBnB,KAAKkC,QAAQQ,WAAW1C,KAAK4C,WAAWzB,IACxCnB,KAAKiD,OAAO,kBAAoB9B,IAAKA,KAE9B,IALyB,GA+UxC,MA/TAM,GAAOsC,WAaHC,IAAK,SAAU7C,EAAKhB,EAAO8D,GACvB,IAAM3D,EAASa,GAAM,OAAO,CAG5B,IAFAA,EAAMjB,EAAOiB,GAETvB,EAAQsE,SAAS/C,GACjBvB,EAAQqB,QAAQE,EAAK,SAAUhB,EAAOgB,GAClCnB,KAAKqD,SAASlC,EAAKb,EAASH,GAASA,EAAQ8D,IAC9CjE,UACA,CACH,IAAMM,EAASH,GAAQ,OAAO,CAC9B,IAAIe,GAAMlB,KAAKuD,SAASpC,EACxBnB,MAAKqD,SAASlC,EAAKjB,EAAOC,EAAOG,EAASY,GAAOA,EAAM+C,IAG3D,MAAOjE,OAcXmE,IAAK,SAAUhD,EAAKhB,EAAO8D,GACvB,MAAMjE,MAAKoE,IAAIjD,IAKR,GAJHnB,KAAKgE,IAAI7C,EAAKhB,EAAO8D,IACd,IAgBfI,IAAK,SAAUlD,EAAK8C,GAChB,GAAIrE,EAAQ0E,QAAQnD,GAAM,CACtB,GAAIoD,KAKJ,OAJA3E,GAAQqB,QAAQE,EAAK,SAAUqD,GACvBxE,KAAKoE,IAAII,KAAID,EAAMC,GAAKxE,KAAKuD,SAASiB,KAC3CxE,MAEIuE,EAGX,MAAMvE,MAAKoE,IAAIjD,GAERnB,KAAKuD,SAASpC,GAF4B,IAArBsD,UAAUC,OAAeT,EAAM,QAc/DG,IAAK,SAAUjD,GACX,MAAOnB,MAAKwD,QAAQrC,IAYxBwD,OAAQ,SAAUxD,GASd,MARAA,GAAMjB,EAAOiB,GAETvB,EAAQ0E,QAAQnD,GAChBA,EAAIyD,IAAI5E,KAAK8D,YAAa9D,MAE1BA,KAAK8D,YAAY3C,GAGdnB,MAaX6E,KAAM,SAAU1D,EAAK8C,GACjB,GAAI9D,GAAQH,KAAKqE,IAAIlD,EAAK8C,EAG1B,OAFAjE,MAAK2E,OAAOxD,GAELhB,GAUX2E,IAAK,WACD,GAAIP,KASJ,OARA3E,GAAQqB,QAAQjB,KAAKkC,QAAS,SAAU/B,EAAOgB,GAC3C,GAAInB,KAAKmC,WAAY,CACjB,GAAI4C,GAAS/E,KAAKmC,WAAanC,KAAKoC,UACR,KAAxBjB,EAAIyC,QAAQmB,KAAe5D,EAAMA,EAAI6D,UAAUD,EAAOL,SAE1D1E,KAAKoE,IAAIjD,KAAMoD,EAAMpD,GAAOnB,KAAKqE,IAAIlD,KAC1CnB,MAEIuE,GAUXU,KAAM,WACF,MAAOC,QAAOD,KAAKjF,KAAK8E,QAU5BK,MAAO,WACH,MAAOnF,MAAK2E,OAAO3E,KAAKiF,SAU5BG,MAAO,WAGH,MAFApF,MAAKkC,QAAQmD,QAENrF,MAUXsF,MAAO,WACH,MAAOtF,MAAKiF,OAAOP,QAcvBa,KAAM,SAAUC,EAAQrE,EAAK8C,GACnB3D,EAAUkF,EAAOC,MAAMtE,MACzBK,EAAOL,GAAKuE,OAAOF,EAAQxF,KAAKqE,IAAIlD,EAAK8C,IACzCjE,KAAKmE,IAAIhD,EAAK8C,GAGlB,IAAI0B,GAAO3F,IAKX,OAJAA,MAAKqC,UAAUlB,EAAMqE,EAAOI,KAAOJ,EAAOK,OAAO1E,EAAK,SAAU2E,GAC5DH,EAAK3B,IAAI7C,EAAK2E,IACflG,EAAQsE,SAASsB,EAAOrE,KAEpBnB,MAaX+F,OAAQ,SAAUP,EAAQrE,GACtBK,EAAOL,GAAKuE,OAAOF,EAAQ,QAC3BxF,KAAK2E,OAAOxD,EAEZ,IAAI6E,GAAU7E,EAAMqE,EAAOI,GAQ3B,OANI5F,MAAKqC,UAAU2D,KAEfhG,KAAKqC,UAAU2D,WACRhG,MAAKqC,UAAU2D,IAGnBhG,MAYXY,OAAQ,SAAUA,GACd,MAAOZ,MAAKiG,SAASrG,EAAQoB,OAAOhB,KAAK2B,UAAYf,OAAQA,MAUjEsF,UAAW,WACP,MAAOlG,MAAKkC,SAYhBrB,UAAW,SAAUA,GACjB,MAAOb,MAAKiG,SAASrG,EAAQoB,OAAOhB,KAAK2B,UAAYd,UAAWA,MAUpEsF,aAAc,WACV,MAAOnG,MAAKmC,YAahBiE,UAAW,SAAUxF,GACjB,MAAOZ,MAAKsC,cAAc1B,IAY9BqF,SAAU,SAAUvE,GAChB,MAAO,IAAID,GAAOC,KAKnB,GAAID,GAAOd,QAI3BuC","file":"angular-locker.min.js","sourcesContent":["/**\n * angular-locker\n *\n * A simple & configurable abstraction for local/session storage in angular projects.\n *\n * @link https://github.com/tymondesigns/angular-locker\n * @author Sean Tymon @tymondesigns\n * @license MIT License, http://www.opensource.org/licenses/MIT\n */\n\n(function (root, factory) {\n if (typeof define === 'function' && define.amd) {\n define(function () {\n return factory(root.angular || (window && window.angular));\n });\n } else if (typeof exports === 'object') {\n module.exports = factory(root.angular || (window && window.angular));\n } else {\n factory(root.angular);\n }\n})(this, function (angular) {\n\n 'use strict';\n\n return angular.module('angular-locker', [])\n\n .provider('locker', function () {\n\n /**\n * If value is a function then execute, otherwise return\n *\n * @private\n *\n * @param {Mixed} value The value to execute or return\n * @param {Mixed} param The parameter to pass to function if applicable\n *\n * @return {Mixed}\n */\n var _value = function (value, param) {\n return angular.isFunction(value) ? value(param) : value;\n };\n\n /**\n * Determine whether a value is defined and not null\n *\n * @private\n *\n * @param {Mixed} value The value to check\n *\n * @return {Boolean}\n */\n var _defined = function (value) {\n return angular.isDefined(value) && value !== null;\n };\n\n /**\n * Trigger an error\n *\n * @private\n * @throws {Error}\n *\n * @param {String} msg The error message\n */\n var _error = function (msg) {\n throw new Error('[angular-locker] ' + msg);\n };\n\n /**\n * Set the defaults\n *\n * @private\n *\n * @type {Object}\n */\n var defaults = {\n driver: 'local',\n namespace: 'locker',\n eventsEnabled: true,\n separator: '.',\n extend: {}\n };\n\n return {\n\n /**\n * Allow the defaults to be specified via the `lockerProvider`\n *\n * @param {Object} value The defaults to override\n */\n defaults: function (value) {\n if (! _defined(value)) return defaults;\n\n angular.forEach(value, function (val, key) {\n if (defaults.hasOwnProperty(key)) defaults[key] = val;\n });\n },\n\n /**\n * The locker service\n */\n $get: ['$window', '$rootScope', '$parse', function ($window, $rootScope, $parse) {\n\n /**\n * Define the Locker class\n *\n * @public\n * @constructor\n *\n * @param {Object} options The config options to initialize with\n */\n function Locker (options) {\n\n /**\n * The config options\n *\n * @private\n *\n * @type {Object}\n */\n this._options = options;\n\n /**\n * Out of the box drivers\n *\n * @private\n *\n * @type {Object}\n */\n this._registeredDrivers = angular.extend({\n local: $window.localStorage,\n session: $window.sessionStorage\n }, options.extend);\n\n /**\n * Get the Storage instance from the key\n *\n * @private\n *\n * @param {String} driver The storage driver identifier\n *\n * @return {Storage}\n */\n this._resolveDriver = function (driver) {\n if (! this._registeredDrivers.hasOwnProperty(driver)) {\n _error('The driver \"' + driver + '\" was not found.');\n }\n\n return this._registeredDrivers[driver];\n };\n\n /**\n * The driver instance\n *\n * @private\n *\n * @type {Storage}\n */\n this._driver = this._resolveDriver(options.driver);\n\n /**\n * The namespace value\n *\n * @private\n *\n * @type {String}\n */\n this._namespace = options.namespace;\n\n /**\n * Separates the namespace from the keys\n *\n * @private\n *\n * @type {String}\n */\n this._separator = options.separator;\n\n /**\n * Store the watchers here so we can un-register them later\n *\n * @private\n *\n * @type {Object}\n */\n this._watchers = {};\n\n /**\n * Check browser support\n *\n * @private\n * @see github.com/Modernizr/Modernizr/blob/master/feature-detects/storage/localstorage.js#L38-L47\n *\n * @param {String} driver The driver to check support with\n *\n * @return {Boolean}\n */\n this._checkSupport = function (driver) {\n if (! _defined(this._supported)) {\n var l = 'l';\n try {\n this._resolveDriver(driver || options.driver).setItem(l, l);\n this._resolveDriver(driver || options.driver).removeItem(l);\n this._supported = true;\n } catch (e) {\n this._supported = false;\n }\n }\n\n return this._supported;\n };\n\n /**\n * Build the storage key from the namspace\n *\n * @private\n *\n * @param {String} key The key to build the prefix with\n *\n * @return {String}\n */\n this._getPrefix = function (key) {\n if (! this._namespace) return key;\n\n return this._namespace + this._separator + key;\n };\n\n /**\n * Try to encode value as json, or just return the value upon failure\n *\n * @private\n *\n * @param {Mixed} value The value to serialize\n *\n * @return {Mixed}\n */\n this._serialize = function (value) {\n try {\n return angular.toJson(value);\n } catch (e) {\n return value;\n }\n };\n\n /**\n * Try to parse value as json, if it fails then it probably isn't json\n * so just return it\n *\n * @private\n *\n * @param {String} value The value to unserialize\n *\n * @return {Mixed}\n */\n this._unserialize = function (value) {\n try {\n return angular.fromJson(value);\n } catch (e) {\n return value;\n }\n };\n\n /**\n * Trigger an event\n *\n * @private\n *\n * @param {String} name The name of the event to trigger\n * @param {Object} payload The data to pass along with event\n */\n this._event = function (name, payload) {\n if (this._options.eventsEnabled) {\n $rootScope.$emit('locker.' + name, angular.extend(payload, {\n driver: this._options.driver,\n namespace: this._namespace,\n }));\n }\n };\n\n /**\n * Add to storage\n *\n * @private\n * @throws {Error} if browser support fails\n *\n * @param {String} key The key to add\n * @param {Mixed} value The value to add\n */\n this._setItem = function (key, value) {\n if (! this._checkSupport()) {\n _error('The browser does not support the \"' + options.driver + '\" driver');\n }\n\n try {\n var oldVal = this._getItem(key);\n this._driver.setItem(this._getPrefix(key), this._serialize(value));\n if (this._exists(key) && ! angular.equals(oldVal, value)) {\n this._event('item.updated', { key: key, oldValue: oldVal, newValue: value });\n } else {\n this._event('item.added', { key: key, value: value });\n }\n } catch (e) {\n if (['QUOTA_EXCEEDED_ERR',\n 'NS_ERROR_DOM_QUOTA_REACHED',\n 'QuotaExceededError'].indexOf(e.name) !== -1) {\n _error('The browser storage quota has been exceeded');\n } else {\n _error('Could not add item with key \"' + key + '\"');\n }\n }\n };\n\n /**\n * Get from storage\n *\n * @private\n * @throws {Error} if browser support fails\n *\n * @param {String} key The key to get\n *\n * @return {Mixed}\n */\n this._getItem = function (key) {\n if (! this._checkSupport()) {\n _error('The browser does not support the \"' + options.driver + '\" driver');\n }\n\n return this._unserialize(this._driver.getItem(this._getPrefix(key)));\n };\n\n /**\n * Exists in storage\n *\n * @private\n * @throws {Error} if browser support fails\n *\n * @param {String} key The key to check for existence\n *\n * @return {Boolean}\n */\n this._exists = function (key) {\n if (! this._checkSupport()) {\n _error('The browser does not support the \"' + options.driver + '\" driver');\n }\n\n return this._driver.hasOwnProperty(this._getPrefix(_value(key))) || !! this._getItem(key);\n };\n\n /**\n * Remove from storage\n *\n * @private\n * @throws {Error} if browser support fails\n *\n * @param {String} key The key to remove\n *\n * @return {Boolean}\n */\n this._removeItem = function (key) {\n if (! this._checkSupport()) {\n _error('The browser does not support the \"' + options.driver + '\" driver');\n }\n\n if (! this._exists(key)) return false;\n\n this._driver.removeItem(this._getPrefix(key));\n this._event('item.forgotten', { key: key });\n\n return true;\n };\n }\n\n /**\n * Define the public api\n *\n * @public\n *\n * @type {Object}\n */\n Locker.prototype = {\n\n /**\n * Add a new item to storage (even if it already exists)\n *\n * @public\n *\n * @param {Mixed} key The key to add\n * @param {Mixed} value The value to add\n * @param {Mixed} def The default to pass to function if doesn't already exist\n *\n * @return {Locker|Boolean}\n */\n put: function (key, value, def) {\n if (! _defined(key)) return false;\n key = _value(key);\n\n if (angular.isObject(key)) {\n angular.forEach(key, function (value, key) {\n this._setItem(key, _defined(value) ? value : def);\n }, this);\n } else {\n if (! _defined(value)) return false;\n var val = this._getItem(key);\n this._setItem(key, _value(value, _defined(val) ? val : def));\n }\n\n return this;\n },\n\n /**\n * Add an item to storage if it doesn't already exist\n *\n * @public\n *\n * @param {Mixed} key The key to add\n * @param {Mixed} value The value to add\n * @param {Mixed} def The default to pass to function if doesn't already exist\n *\n * @return {Boolean}\n */\n add: function (key, value, def) {\n if (! this.has(key)) {\n this.put(key, value, def);\n return true;\n }\n\n return false;\n },\n\n /**\n * Retrieve the specified item from storage\n *\n * @public\n *\n * @param {String|Array} key The key to get\n * @param {Mixed} def The default value if it does not exist\n *\n * @return {Mixed}\n */\n get: function (key, def) {\n if (angular.isArray(key)) {\n var items = {};\n angular.forEach(key, function (k) {\n if (this.has(k)) items[k] = this._getItem(k);\n }, this);\n\n return items;\n }\n\n if (! this.has(key)) return arguments.length === 2 ? def : void 0;\n\n return this._getItem(key);\n },\n\n /**\n * Determine whether the item exists in storage\n *\n * @public\n *\n * @param {String|Function} key - The key to remove\n *\n * @return {Boolean}\n */\n has: function (key) {\n return this._exists(key);\n },\n\n /**\n * Remove specified item(s) from storage\n *\n * @public\n *\n * @param {String|Array} key The key or array of keys to remove\n *\n * @return {Object}\n */\n forget: function (key) {\n key = _value(key);\n\n if (angular.isArray(key)) {\n key.map(this._removeItem, this);\n } else {\n this._removeItem(key);\n }\n\n return this;\n },\n\n /**\n * Retrieve the specified item from storage and then remove it\n *\n * @public\n *\n * @param {String|Array} key The key to pull from storage\n * @param {Mixed} def The default value if it does not exist\n *\n * @return {Mixed}\n */\n pull: function (key, def) {\n var value = this.get(key, def);\n this.forget(key);\n\n return value;\n },\n\n /**\n * Return all items in storage within the current namespace/driver\n *\n * @public\n *\n * @return {Object}\n */\n all: function () {\n var items = {};\n angular.forEach(this._driver, function (value, key) {\n if (this._namespace) {\n var prefix = this._namespace + this._separator;\n if (key.indexOf(prefix) === 0) key = key.substring(prefix.length);\n }\n if (this.has(key)) items[key] = this.get(key);\n }, this);\n\n return items;\n },\n\n /**\n * Get the storage keys as an array\n *\n * @public\n *\n * @return {Array}\n */\n keys: function () {\n return Object.keys(this.all());\n },\n\n /**\n * Remove all items set within the current namespace/driver\n *\n * @public\n *\n * @return {Locker}\n */\n clean: function () {\n return this.forget(this.keys());\n },\n\n /**\n * Empty the current storage driver completely. careful now.\n *\n * @public\n *\n * @return {Locker}\n */\n empty: function () {\n this._driver.clear();\n\n return this;\n },\n\n /**\n * Get the total number of items within the current namespace\n *\n * @public\n *\n * @return {Integer}\n */\n count: function () {\n return this.keys().length;\n },\n\n /**\n * Bind a storage key to a $scope property\n *\n * @public\n *\n * @param {Object} $scope The angular $scope object\n * @param {String} key The key in storage to bind to\n * @param {Mixed} def The default value to initially bind\n *\n * @return {Locker}\n */\n bind: function ($scope, key, def) {\n if (! _defined( $scope.$eval(key) )) {\n $parse(key).assign($scope, this.get(key, def));\n this.add(key, def);\n }\n\n var self = this;\n this._watchers[key + $scope.$id] = $scope.$watch(key, function (newVal) {\n self.put(key, newVal);\n }, angular.isObject($scope[key]));\n\n return this;\n },\n\n /**\n * Unbind a storage key from a $scope property\n *\n * @public\n *\n * @param {Object} $scope The angular $scope object\n * @param {String} key The key to remove from bindings\n *\n * @return {Locker}\n */\n unbind: function ($scope, key) {\n $parse(key).assign($scope, void 0);\n this.forget(key);\n\n var watchId = key + $scope.$id;\n\n if (this._watchers[watchId]) {\n // execute the de-registration function\n this._watchers[watchId]();\n delete this._watchers[watchId];\n }\n\n return this;\n },\n\n /**\n * Set the storage driver on a new instance to enable overriding defaults\n *\n * @public\n *\n * @param {String} driver The driver to switch to\n *\n * @return {Locker}\n */\n driver: function (driver) {\n return this.instance(angular.extend(this._options, { driver: driver }));\n },\n\n /**\n * Get the currently set driver\n *\n * @public\n *\n * @return {Storage}\n */\n getDriver: function () {\n return this._driver;\n },\n\n /**\n * Set the namespace on a new instance to enable overriding defaults\n *\n * @public\n *\n * @param {String} namespace The namespace to switch to\n *\n * @return {Locker}\n */\n namespace: function (namespace) {\n return this.instance(angular.extend(this._options, { namespace: namespace }));\n },\n\n /**\n * Get the currently set namespace\n *\n * @public\n *\n * @return {String}\n */\n getNamespace: function () {\n return this._namespace;\n },\n\n /**\n * Check browser support\n *\n * @public\n * @see github.com/Modernizr/Modernizr/blob/master/feature-detects/storage/localstorage.js#L38-L47\n *\n * @param {String} driver The driver to check support with\n *\n * @return {Boolean}\n */\n supported: function (driver) {\n return this._checkSupport(driver);\n },\n\n /**\n * Get a new instance of Locker\n *\n * @public\n *\n * @param {Object} options The config options to instantiate with\n *\n * @return {Locker}\n */\n instance: function (options) {\n return new Locker(options);\n }\n };\n\n // return the default instance\n return new Locker(defaults);\n }]\n };\n\n }).name; // export module name for the likes of Browserify and Webpack\n\n});\n"],"sourceRoot":"/source/"}
--------------------------------------------------------------------------------
/gulpfile.babel.js:
--------------------------------------------------------------------------------
1 | import gulp from 'gulp';
2 | import {task as fizzy} from 'fizzy';
3 | import pkg from './package.json';
4 | import {paths, banner} from './config';
5 | import runSequence from 'run-sequence';
6 |
7 | // Lint the JS
8 | gulp.task('lint', fizzy('lint', { src: paths.scripts }));
9 |
10 | // Check the coding style
11 | gulp.task('jscs', fizzy('jscs', { src: paths.scripts[0] }));
12 |
13 | // Remove the output folder
14 | gulp.task('clean', fizzy('clean', { src: paths.output }));
15 |
16 | // Build the output folder
17 | gulp.task('scripts', ['clean'], fizzy('scripts', {
18 | src: paths.scripts,
19 | dest: paths.output,
20 | header: [banner, { pkg }],
21 | size: false
22 | }));
23 |
24 | // Run the tests
25 | gulp.task('test', fizzy('test', {
26 | src: paths.vendor
27 | .concat(['./node_modules/phantomjs-polyfill/bind-polyfill.js'])
28 | .concat(paths.scripts, paths.test),
29 | karmaConfigFile: paths.karma
30 | }));
31 |
32 | // Build the readme
33 | gulp.task('gitdown', fizzy('gitdown', {
34 | src: paths.gitdown.src,
35 | dest: paths.gitdown.dest
36 | }));
37 |
38 | // Define the build tasks
39 | gulp.task('build', (cb) => {
40 | runSequence(['lint', 'jscs', 'scripts', 'test'], 'gitdown', cb);
41 | });
42 |
43 | // Increment versions
44 | gulp.task('version', fizzy('version', {
45 | src: paths.versions,
46 | currentVersion: pkg.version
47 | }));
48 |
49 | // release a new version
50 | gulp.task('release', (cb) => {
51 | runSequence('version', 'build', cb);
52 | });
53 |
54 | // Watch for changes
55 | gulp.task('watch', () => {
56 | gulp.watch(paths.scripts, ['lint', 'jscs']);
57 | gulp.watch(paths.gitdown.glob, ['gitdown']);
58 | });
59 |
60 | gulp.task('default', ['build']);
61 |
--------------------------------------------------------------------------------
/jsdoc.json:
--------------------------------------------------------------------------------
1 | {
2 | "opts": {
3 | "destination": "./docs/"
4 | },
5 | "templates": {
6 | "cleverLinks" : false,
7 | "monospaceLinks" : false,
8 | "outputSourceFiles" : false,
9 | "outputSourcePath" : false,
10 | "collapseSymbols" : false,
11 | "inverseNav" : true
12 | },
13 | "opts": {
14 | "encoding": "utf8",
15 | "private": false
16 | }
17 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "angular-locker",
3 | "version": "2.0.5",
4 | "description": "A simple & configurable abstraction for local/session storage in angular projects",
5 | "author": "@tymondesigns",
6 | "license": "MIT",
7 | "homepage": "https://github.com/tymondesigns/angular-locker",
8 | "keywords": [
9 | "angular-locker",
10 | "angular",
11 | "locker",
12 | "storage",
13 | "localStorage",
14 | "sessionStorage",
15 | "session",
16 | "local"
17 | ],
18 | "repository": {
19 | "type": "git",
20 | "url": "http://github.com/tymondesigns/angular-locker.git"
21 | },
22 | "dependencies": {
23 | "angular": ">=1.2.0 <2.0.0"
24 | },
25 | "devDependencies": {
26 | "angular-mocks": ">=1.2.0 <2.0.0",
27 | "babel-core": "^5.7.4",
28 | "fizzy": "^0.3.3",
29 | "gulp": "~3.9.0",
30 | "karma": "^0.12.16",
31 | "karma-babel-preprocessor": "^5.2.2",
32 | "karma-coverage": "~0.2.6",
33 | "karma-jasmine": "~0.2.0",
34 | "karma-notify-reporter": "^0.1.1",
35 | "karma-phantomjs-launcher": "^0.1.4",
36 | "karma-sauce-launcher": "^0.2.10",
37 | "karma-spec-reporter": "0.0.13",
38 | "phantomjs-polyfill": "0.0.2",
39 | "run-sequence": "^1.1.3"
40 | },
41 | "main": "src/angular-locker.js",
42 | "scripts": {
43 | "test": "gulp test"
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/angular-locker.js:
--------------------------------------------------------------------------------
1 | /**
2 | * angular-locker
3 | *
4 | * A simple & configurable abstraction for local/session storage in angular projects.
5 | *
6 | * @link https://github.com/tymondesigns/angular-locker
7 | * @author Sean Tymon @tymondesigns
8 | * @license MIT License, http://www.opensource.org/licenses/MIT
9 | */
10 |
11 | (function (root, factory) {
12 | if (typeof define === 'function' && define.amd) {
13 | define(function () {
14 | return factory(root.angular || (window && window.angular));
15 | });
16 | } else if (typeof exports === 'object') {
17 | module.exports = factory(root.angular || (window && window.angular));
18 | } else {
19 | factory(root.angular);
20 | }
21 | })(this, function (angular) {
22 |
23 | 'use strict';
24 |
25 | return angular.module('angular-locker', [])
26 |
27 | .provider('locker', function () {
28 |
29 | /**
30 | * If value is a function then execute, otherwise return
31 | *
32 | * @private
33 | *
34 | * @param {Mixed} value The value to execute or return
35 | * @param {Mixed} param The parameter to pass to function if applicable
36 | *
37 | * @return {Mixed}
38 | */
39 | var _value = function (value, param) {
40 | return angular.isFunction(value) ? value(param) : value;
41 | };
42 |
43 | /**
44 | * Determine whether a value is defined and not null
45 | *
46 | * @private
47 | *
48 | * @param {Mixed} value The value to check
49 | *
50 | * @return {Boolean}
51 | */
52 | var _defined = function (value) {
53 | return angular.isDefined(value) && value !== null;
54 | };
55 |
56 | /**
57 | * Trigger an error
58 | *
59 | * @private
60 | * @throws {Error}
61 | *
62 | * @param {String} msg The error message
63 | */
64 | var _error = function (msg) {
65 | throw new Error('[angular-locker] ' + msg);
66 | };
67 |
68 | /**
69 | * Set the defaults
70 | *
71 | * @private
72 | *
73 | * @type {Object}
74 | */
75 | var defaults = {
76 | driver: 'local',
77 | namespace: 'locker',
78 | eventsEnabled: true,
79 | separator: '.',
80 | extend: {}
81 | };
82 |
83 | return {
84 |
85 | /**
86 | * Allow the defaults to be specified via the `lockerProvider`
87 | *
88 | * @param {Object} value The defaults to override
89 | */
90 | defaults: function (value) {
91 | if (! _defined(value)) return defaults;
92 |
93 | angular.forEach(value, function (val, key) {
94 | if (defaults.hasOwnProperty(key)) defaults[key] = val;
95 | });
96 | },
97 |
98 | /**
99 | * The locker service
100 | */
101 | $get: ['$window', '$rootScope', '$parse', function ($window, $rootScope, $parse) {
102 |
103 | /**
104 | * Define the Locker class
105 | *
106 | * @public
107 | * @constructor
108 | *
109 | * @param {Object} options The config options to initialize with
110 | */
111 | function Locker (options) {
112 |
113 | /**
114 | * The config options
115 | *
116 | * @private
117 | *
118 | * @type {Object}
119 | */
120 | this._options = options;
121 |
122 | /**
123 | * Out of the box drivers
124 | *
125 | * @private
126 | *
127 | * @type {Object}
128 | */
129 | this._registeredDrivers = angular.extend({
130 | local: $window.localStorage,
131 | session: $window.sessionStorage
132 | }, options.extend);
133 |
134 | /**
135 | * Get the Storage instance from the key
136 | *
137 | * @private
138 | *
139 | * @param {String} driver The storage driver identifier
140 | *
141 | * @return {Storage}
142 | */
143 | this._resolveDriver = function (driver) {
144 | if (! this._registeredDrivers.hasOwnProperty(driver)) {
145 | _error('The driver "' + driver + '" was not found.');
146 | }
147 |
148 | return this._registeredDrivers[driver];
149 | };
150 |
151 | /**
152 | * The driver instance
153 | *
154 | * @private
155 | *
156 | * @type {Storage}
157 | */
158 | this._driver = this._resolveDriver(options.driver);
159 |
160 | /**
161 | * The namespace value
162 | *
163 | * @private
164 | *
165 | * @type {String}
166 | */
167 | this._namespace = options.namespace;
168 |
169 | /**
170 | * Separates the namespace from the keys
171 | *
172 | * @private
173 | *
174 | * @type {String}
175 | */
176 | this._separator = options.separator;
177 |
178 | /**
179 | * Store the watchers here so we can un-register them later
180 | *
181 | * @private
182 | *
183 | * @type {Object}
184 | */
185 | this._watchers = {};
186 |
187 | /**
188 | * Check browser support
189 | *
190 | * @private
191 | * @see github.com/Modernizr/Modernizr/blob/master/feature-detects/storage/localstorage.js#L38-L47
192 | *
193 | * @param {String} driver The driver to check support with
194 | *
195 | * @return {Boolean}
196 | */
197 | this._checkSupport = function (driver) {
198 | if (! _defined(this._supported)) {
199 | var l = 'l';
200 | try {
201 | this._resolveDriver(driver || options.driver).setItem(l, l);
202 | this._resolveDriver(driver || options.driver).removeItem(l);
203 | this._supported = true;
204 | } catch (e) {
205 | this._supported = false;
206 | }
207 | }
208 |
209 | return this._supported;
210 | };
211 |
212 | /**
213 | * Build the storage key from the namspace
214 | *
215 | * @private
216 | *
217 | * @param {String} key The key to build the prefix with
218 | *
219 | * @return {String}
220 | */
221 | this._getPrefix = function (key) {
222 | if (! this._namespace) return key;
223 |
224 | return this._namespace + this._separator + key;
225 | };
226 |
227 | /**
228 | * Try to encode value as json, or just return the value upon failure
229 | *
230 | * @private
231 | *
232 | * @param {Mixed} value The value to serialize
233 | *
234 | * @return {Mixed}
235 | */
236 | this._serialize = function (value) {
237 | try {
238 | return angular.toJson(value);
239 | } catch (e) {
240 | return value;
241 | }
242 | };
243 |
244 | /**
245 | * Try to parse value as json, if it fails then it probably isn't json
246 | * so just return it
247 | *
248 | * @private
249 | *
250 | * @param {String} value The value to unserialize
251 | *
252 | * @return {Mixed}
253 | */
254 | this._unserialize = function (value) {
255 | try {
256 | return angular.fromJson(value);
257 | } catch (e) {
258 | return value;
259 | }
260 | };
261 |
262 | /**
263 | * Trigger an event
264 | *
265 | * @private
266 | *
267 | * @param {String} name The name of the event to trigger
268 | * @param {Object} payload The data to pass along with event
269 | */
270 | this._event = function (name, payload) {
271 | if (this._options.eventsEnabled) {
272 | $rootScope.$emit('locker.' + name, angular.extend(payload, {
273 | driver: this._options.driver,
274 | namespace: this._namespace,
275 | }));
276 | }
277 | };
278 |
279 | /**
280 | * Add to storage
281 | *
282 | * @private
283 | * @throws {Error} if browser support fails
284 | *
285 | * @param {String} key The key to add
286 | * @param {Mixed} value The value to add
287 | */
288 | this._setItem = function (key, value) {
289 | if (! this._checkSupport()) {
290 | _error('The browser does not support the "' + options.driver + '" driver');
291 | }
292 |
293 | try {
294 | var oldVal = this._getItem(key);
295 | this._driver.setItem(this._getPrefix(key), this._serialize(value));
296 | if (this._exists(key) && ! angular.equals(oldVal, value)) {
297 | this._event('item.updated', { key: key, oldValue: oldVal, newValue: value });
298 | } else {
299 | this._event('item.added', { key: key, value: value });
300 | }
301 | } catch (e) {
302 | if (['QUOTA_EXCEEDED_ERR',
303 | 'NS_ERROR_DOM_QUOTA_REACHED',
304 | 'QuotaExceededError'].indexOf(e.name) !== -1) {
305 | _error('The browser storage quota has been exceeded');
306 | } else {
307 | _error('Could not add item with key "' + key + '"');
308 | }
309 | }
310 | };
311 |
312 | /**
313 | * Get from storage
314 | *
315 | * @private
316 | * @throws {Error} if browser support fails
317 | *
318 | * @param {String} key The key to get
319 | *
320 | * @return {Mixed}
321 | */
322 | this._getItem = function (key) {
323 | if (! this._checkSupport()) {
324 | _error('The browser does not support the "' + options.driver + '" driver');
325 | }
326 |
327 | return this._unserialize(this._driver.getItem(this._getPrefix(key)));
328 | };
329 |
330 | /**
331 | * Exists in storage
332 | *
333 | * @private
334 | * @throws {Error} if browser support fails
335 | *
336 | * @param {String} key The key to check for existence
337 | *
338 | * @return {Boolean}
339 | */
340 | this._exists = function (key) {
341 | if (! this._checkSupport()) {
342 | _error('The browser does not support the "' + options.driver + '" driver');
343 | }
344 |
345 | return this._driver.hasOwnProperty(this._getPrefix(_value(key))) || !! this._getItem(key);
346 | };
347 |
348 | /**
349 | * Remove from storage
350 | *
351 | * @private
352 | * @throws {Error} if browser support fails
353 | *
354 | * @param {String} key The key to remove
355 | *
356 | * @return {Boolean}
357 | */
358 | this._removeItem = function (key) {
359 | if (! this._checkSupport()) {
360 | _error('The browser does not support the "' + options.driver + '" driver');
361 | }
362 |
363 | if (! this._exists(key)) return false;
364 |
365 | this._driver.removeItem(this._getPrefix(key));
366 | this._event('item.forgotten', { key: key });
367 |
368 | return true;
369 | };
370 | }
371 |
372 | /**
373 | * Define the public api
374 | *
375 | * @public
376 | *
377 | * @type {Object}
378 | */
379 | Locker.prototype = {
380 |
381 | /**
382 | * Add a new item to storage (even if it already exists)
383 | *
384 | * @public
385 | *
386 | * @param {Mixed} key The key to add
387 | * @param {Mixed} value The value to add
388 | * @param {Mixed} def The default to pass to function if doesn't already exist
389 | *
390 | * @return {Locker|Boolean}
391 | */
392 | put: function (key, value, def) {
393 | if (! _defined(key)) return false;
394 | key = _value(key);
395 |
396 | if (angular.isObject(key)) {
397 | angular.forEach(key, function (value, key) {
398 | this._setItem(key, _defined(value) ? value : def);
399 | }, this);
400 | } else {
401 | if (! _defined(value)) return false;
402 | var val = this._getItem(key);
403 | this._setItem(key, _value(value, _defined(val) ? val : def));
404 | }
405 |
406 | return this;
407 | },
408 |
409 | /**
410 | * Add an item to storage if it doesn't already exist
411 | *
412 | * @public
413 | *
414 | * @param {Mixed} key The key to add
415 | * @param {Mixed} value The value to add
416 | * @param {Mixed} def The default to pass to function if doesn't already exist
417 | *
418 | * @return {Boolean}
419 | */
420 | add: function (key, value, def) {
421 | if (! this.has(key)) {
422 | this.put(key, value, def);
423 | return true;
424 | }
425 |
426 | return false;
427 | },
428 |
429 | /**
430 | * Retrieve the specified item from storage
431 | *
432 | * @public
433 | *
434 | * @param {String|Array} key The key to get
435 | * @param {Mixed} def The default value if it does not exist
436 | *
437 | * @return {Mixed}
438 | */
439 | get: function (key, def) {
440 | if (angular.isArray(key)) {
441 | var items = {};
442 | angular.forEach(key, function (k) {
443 | if (this.has(k)) items[k] = this._getItem(k);
444 | }, this);
445 |
446 | return items;
447 | }
448 |
449 | if (! this.has(key)) return arguments.length === 2 ? def : void 0;
450 |
451 | return this._getItem(key);
452 | },
453 |
454 | /**
455 | * Determine whether the item exists in storage
456 | *
457 | * @public
458 | *
459 | * @param {String|Function} key - The key to remove
460 | *
461 | * @return {Boolean}
462 | */
463 | has: function (key) {
464 | return this._exists(key);
465 | },
466 |
467 | /**
468 | * Remove specified item(s) from storage
469 | *
470 | * @public
471 | *
472 | * @param {String|Array} key The key or array of keys to remove
473 | *
474 | * @return {Object}
475 | */
476 | forget: function (key) {
477 | key = _value(key);
478 |
479 | if (angular.isArray(key)) {
480 | key.map(this._removeItem, this);
481 | } else {
482 | this._removeItem(key);
483 | }
484 |
485 | return this;
486 | },
487 |
488 | /**
489 | * Retrieve the specified item from storage and then remove it
490 | *
491 | * @public
492 | *
493 | * @param {String|Array} key The key to pull from storage
494 | * @param {Mixed} def The default value if it does not exist
495 | *
496 | * @return {Mixed}
497 | */
498 | pull: function (key, def) {
499 | var value = this.get(key, def);
500 | this.forget(key);
501 |
502 | return value;
503 | },
504 |
505 | /**
506 | * Return all items in storage within the current namespace/driver
507 | *
508 | * @public
509 | *
510 | * @return {Object}
511 | */
512 | all: function () {
513 | var items = {};
514 | angular.forEach(this._driver, function (value, key) {
515 | if (this._namespace) {
516 | var prefix = this._namespace + this._separator;
517 | if (key.indexOf(prefix) === 0) key = key.substring(prefix.length);
518 | }
519 | if (this.has(key)) items[key] = this.get(key);
520 | }, this);
521 |
522 | return items;
523 | },
524 |
525 | /**
526 | * Get the storage keys as an array
527 | *
528 | * @public
529 | *
530 | * @return {Array}
531 | */
532 | keys: function () {
533 | return Object.keys(this.all());
534 | },
535 |
536 | /**
537 | * Remove all items set within the current namespace/driver
538 | *
539 | * @public
540 | *
541 | * @return {Locker}
542 | */
543 | clean: function () {
544 | return this.forget(this.keys());
545 | },
546 |
547 | /**
548 | * Empty the current storage driver completely. careful now.
549 | *
550 | * @public
551 | *
552 | * @return {Locker}
553 | */
554 | empty: function () {
555 | this._driver.clear();
556 |
557 | return this;
558 | },
559 |
560 | /**
561 | * Get the total number of items within the current namespace
562 | *
563 | * @public
564 | *
565 | * @return {Integer}
566 | */
567 | count: function () {
568 | return this.keys().length;
569 | },
570 |
571 | /**
572 | * Bind a storage key to a $scope property
573 | *
574 | * @public
575 | *
576 | * @param {Object} $scope The angular $scope object
577 | * @param {String} key The key in storage to bind to
578 | * @param {Mixed} def The default value to initially bind
579 | *
580 | * @return {Locker}
581 | */
582 | bind: function ($scope, key, def) {
583 | if (! _defined( $scope.$eval(key) )) {
584 | $parse(key).assign($scope, this.get(key, def));
585 | this.add(key, def);
586 | }
587 |
588 | var self = this;
589 | this._watchers[key + $scope.$id] = $scope.$watch(key, function (newVal) {
590 | self.put(key, newVal);
591 | }, angular.isObject($scope[key]));
592 |
593 | return this;
594 | },
595 |
596 | /**
597 | * Unbind a storage key from a $scope property
598 | *
599 | * @public
600 | *
601 | * @param {Object} $scope The angular $scope object
602 | * @param {String} key The key to remove from bindings
603 | *
604 | * @return {Locker}
605 | */
606 | unbind: function ($scope, key) {
607 | $parse(key).assign($scope, void 0);
608 | this.forget(key);
609 |
610 | var watchId = key + $scope.$id;
611 |
612 | if (this._watchers[watchId]) {
613 | // execute the de-registration function
614 | this._watchers[watchId]();
615 | delete this._watchers[watchId];
616 | }
617 |
618 | return this;
619 | },
620 |
621 | /**
622 | * Set the storage driver on a new instance to enable overriding defaults
623 | *
624 | * @public
625 | *
626 | * @param {String} driver The driver to switch to
627 | *
628 | * @return {Locker}
629 | */
630 | driver: function (driver) {
631 | return this.instance(angular.extend(this._options, { driver: driver }));
632 | },
633 |
634 | /**
635 | * Get the currently set driver
636 | *
637 | * @public
638 | *
639 | * @return {Storage}
640 | */
641 | getDriver: function () {
642 | return this._driver;
643 | },
644 |
645 | /**
646 | * Set the namespace on a new instance to enable overriding defaults
647 | *
648 | * @public
649 | *
650 | * @param {String} namespace The namespace to switch to
651 | *
652 | * @return {Locker}
653 | */
654 | namespace: function (namespace) {
655 | return this.instance(angular.extend(this._options, { namespace: namespace }));
656 | },
657 |
658 | /**
659 | * Get the currently set namespace
660 | *
661 | * @public
662 | *
663 | * @return {String}
664 | */
665 | getNamespace: function () {
666 | return this._namespace;
667 | },
668 |
669 | /**
670 | * Check browser support
671 | *
672 | * @public
673 | * @see github.com/Modernizr/Modernizr/blob/master/feature-detects/storage/localstorage.js#L38-L47
674 | *
675 | * @param {String} driver The driver to check support with
676 | *
677 | * @return {Boolean}
678 | */
679 | supported: function (driver) {
680 | return this._checkSupport(driver);
681 | },
682 |
683 | /**
684 | * Get a new instance of Locker
685 | *
686 | * @public
687 | *
688 | * @param {Object} options The config options to instantiate with
689 | *
690 | * @return {Locker}
691 | */
692 | instance: function (options) {
693 | return new Locker(options);
694 | }
695 | };
696 |
697 | // return the default instance
698 | return new Locker(defaults);
699 | }]
700 | };
701 |
702 | }).name; // export module name for the likes of Browserify and Webpack
703 |
704 | });
705 |
--------------------------------------------------------------------------------
/test/karma.conf.js:
--------------------------------------------------------------------------------
1 | module.exports = function (config) {
2 |
3 | // if (!process.env.SAUCE_USERNAME || !process.env.SAUCE_ACCESS_KEY) {
4 | // console.log('Make sure the SAUCE_USERNAME and SAUCE_ACCESS_KEY environment variables are set.');
5 | // process.exit(1);
6 | // }
7 |
8 | var customLaunchers = {
9 | // sl_chrome: {
10 | // base: 'SauceLabs',
11 | // browserName: 'chrome',
12 | // platform: 'Windows 7',
13 | // version: '38'
14 | // },
15 | // sl_firefox: {
16 | // base: 'SauceLabs',
17 | // browserName: 'firefox',
18 | // version: '33',
19 | // platform: 'Windows 7'
20 | // },
21 | // sl_ios_safari: {
22 | // base: 'SauceLabs',
23 | // browserName: 'iphone',
24 | // platform: 'OS X 10.9',
25 | // version: '7.1'
26 | // },
27 | // sl_mac_safari: {
28 | // base: 'SauceLabs',
29 | // browserName: 'safari',
30 | // platform: 'OS X 10.10',
31 | // version: '8'
32 | // },
33 | // sl_ie_11: {
34 | // base: 'SauceLabs',
35 | // browserName: 'internet explorer',
36 | // platform: 'Windows 8.1',
37 | // version: '11'
38 | // },
39 | // sl_ie_9: {
40 | // base: 'SauceLabs',
41 | // browserName: 'internet explorer',
42 | // platform: 'Windows 7',
43 | // version: '9'
44 | // },
45 | // sl_opera_12: {
46 | // base: 'SauceLabs',
47 | // browserName: 'opera',
48 | // platform: 'Windows 7',
49 | // version: '12'
50 | // }
51 | };
52 |
53 | config.set({
54 | basePath: '',
55 | autoWatch: true,
56 | frameworks: ['jasmine'],
57 | // logLevel: config.LOG_DEBUG,
58 | preprocessors: {
59 | '../src/*.js': ['coverage'],
60 | '../test/**/*.js': ['babel']
61 | },
62 | sauceLabs: {
63 | testName: 'angular-locker',
64 | recordScreenshots: false,
65 | tunnelIdentifier: process.env.TRAVIS_JOB_NUMBER
66 | },
67 | customLaunchers: customLaunchers,
68 | browsers: ['PhantomJS'].concat(Object.keys(customLaunchers)),
69 | reporters: ['spec', 'coverage', 'notify', 'saucelabs'],
70 | singleRun: true,
71 | captureTimeout: 120000,
72 | coverageReporter: {
73 | type: 'lcov',
74 | dir: 'coverage/',
75 | subdir: function(browser) {
76 | return 'lcov';
77 | }
78 | }
79 | });
80 | };
81 |
--------------------------------------------------------------------------------
/test/mock/storageMock.js:
--------------------------------------------------------------------------------
1 |
2 | 'use strict';
3 |
4 | function storageMock() {
5 | let store = {};
6 |
7 | Object.defineProperties(store, {
8 | setItem: {
9 | value: (key, value) => {
10 | store[key] = value || '';
11 | },
12 | enumerable: false,
13 | writable: true
14 | },
15 | getItem: {
16 | value: (key) => store[key],
17 | enumerable: false,
18 | writable: true
19 | },
20 | removeItem: {
21 | value: (key) => {
22 | delete store[key];
23 | },
24 | enumerable: false,
25 | writable: true
26 | },
27 | length: {
28 | get: () => Object.keys(store).length,
29 | enumerable: false
30 | },
31 | clear: {
32 | value: () => {
33 | store = {};
34 | },
35 | enumerable: false,
36 | writable: true
37 | }
38 | });
39 |
40 | return store;
41 | }
--------------------------------------------------------------------------------
/test/spec/angular-locker.spec.js:
--------------------------------------------------------------------------------
1 | describe('angular-locker', () => {
2 |
3 | beforeEach(module('angular-locker', ($provide) => {
4 | $provide.value('$window', {
5 | localStorage: storageMock(),
6 | sessionStorage: storageMock()
7 | });
8 | }));
9 |
10 | describe('lockerProvider', () => {
11 |
12 | it('should be defined', () => {
13 | module((lockerProvider) => {
14 | expect(lockerProvider).toBeDefined();
15 | });
16 | });
17 |
18 | it('should set a default storage driver', () => {
19 | module((lockerProvider) => {
20 | expect(lockerProvider.defaults.driver).toEqual('local');
21 |
22 | lockerProvider.defaults({ driver: 'session'});
23 |
24 | expect(lockerProvider.defaults.driver).toEqual('session');
25 | expect(lockerProvider.defaults.namespace).toEqual('locker');
26 | });
27 | });
28 |
29 | it('should set a default storage driver via function', () => {
30 | module((lockerProvider) => {
31 | expect( lockerProvider.defaults.driver ).toEqual('local');
32 |
33 | lockerProvider.defaults({
34 | driver: () => {
35 | var shouldUseSession = true;
36 | if (shouldUseSession) return 'session';
37 | }
38 | });
39 |
40 | expect(lockerProvider.defaults.driver).toEqual('session');
41 | });
42 | });
43 |
44 | it('should throw an error if storage driver that does not exist is used', () => {
45 | module((lockerProvider) => {
46 | expect(() => {
47 | locker.driver('somethingNotExpected');
48 | }).toThrowError();
49 |
50 | expect( locker._driver ).toEqual($window.localStorage);
51 | });
52 | });
53 |
54 | it('should set a default namespace', () => {
55 | module((lockerProvider) => {
56 | expect( lockerProvider.defaults.namespace ).toEqual('locker');
57 |
58 | lockerProvider.defaults.namespace = 'myApp.foo';
59 | expect( lockerProvider.defaults.namespace ).toEqual('myApp');
60 |
61 | lockerProvider.defaults.namespace = '';
62 | expect( lockerProvider.defaults.namespace ).toEqual('');
63 |
64 | lockerProvider.defaults.namespace = false;
65 | expect( lockerProvider.defaults.namespace ).toEqual(false);
66 | });
67 | });
68 |
69 | it('should set a default namespace via function', () => {
70 | module((lockerProvider) => {
71 | expect( lockerProvider.defaults.namespace ).toEqual('locker');
72 | lockerProvider.defaults.namespace = () => {
73 | var arr = ['myApp', 'coolApp', 'somethingElse'];
74 | return arr[1];
75 | };
76 | expect( lockerProvider.defaults.namespace ).toEqual('coolApp');
77 | });
78 | });
79 |
80 | it('should set a default separator', () => {
81 | module((lockerProvider) => {
82 | expect( lockerProvider.defaults.separator ).toEqual('.');
83 | lockerProvider.defaults.separator = '-';
84 | expect( lockerProvider.defaults.separator ).toEqual('-');
85 | lockerProvider.defaults.separator = '';
86 | expect( lockerProvider.defaults.separator ).toEqual('');
87 |
88 | lockerProvider.defaults.separator = false;
89 | expect( lockerProvider.defaults.separator ).toEqual(false);
90 | });
91 | });
92 |
93 | it('should set a default separator via function', () => {
94 | module((lockerProvider) => {
95 | expect( lockerProvider.defaults.separator ).toEqual('.');
96 | lockerProvider.defaults.separator = () => {
97 | var arr = ['.', '-', '!'];
98 | return arr[1];
99 | };
100 | expect( lockerProvider.defaults.separator ).toEqual('-');
101 | });
102 | });
103 |
104 | it('should throw an error when setting a driver that is not registered', inject((locker) => {
105 | expect(() => {
106 | locker.driver('foo');
107 | }).toThrowError();
108 | }));
109 | });
110 |
111 | describe('lockerService', () => {
112 |
113 | describe('adding items to locker', () => {
114 |
115 | it('should put a string into the locker', inject((locker) => {
116 | var str = 'someVal';
117 | locker.put('someKey', str);
118 |
119 | expect( locker.get('someKey') ).toEqual(str);
120 |
121 | locker.driver('session').put('sessionKey1', 1);
122 | locker.driver('session').put('sessionKey2', 2);
123 | locker.driver('local').put('localKey1', 1);
124 | locker.driver('local').put('localKey2', 2);
125 |
126 | expect(locker.driver('session').get('sessionKey2')).toEqual(2);
127 |
128 | }));
129 |
130 | it('should put a boolean into the locker', inject((locker) => {
131 | locker.put('someKey', false);
132 | locker.put('someKey1', true);
133 |
134 | expect( locker.get('someKey') ).toEqual(false);
135 | expect( locker.get('someKey1') ).toEqual(true);
136 | }));
137 |
138 | it('should put an object into the locker', inject((locker) => {
139 | var obj = {
140 | foo: 'bar',
141 | bar: 'baz',
142 | baz: {
143 | foo: true,
144 | bar: false,
145 | baz: 12.34
146 | }
147 | };
148 |
149 | locker.put('objectKey', obj);
150 |
151 | var result = locker.get('objectKey');
152 |
153 | expect( result ).toEqual(obj);
154 | expect( result.baz.bar ).toBeFalsy();
155 | }));
156 |
157 | it('should put an array into the locker', inject((locker) => {
158 | var arr1 = ['foo', 123.456, true, { foo: 'bar' }];
159 | var arr2 = ['foo', 'bar', 'baz'];
160 |
161 | locker.put('arrayKey1', arr1);
162 | locker.put('arrayKey2', arr2);
163 |
164 | var result1 = locker.get('arrayKey1');
165 | var result2 = locker.get('arrayKey2');
166 |
167 | expect( result1 ).toEqual(arr1);
168 | expect( result2 ).toEqual(arr2);
169 |
170 | expect( result1[3].foo ).toEqual('bar');
171 | expect( result2[0] ).toEqual('foo');
172 | }));
173 |
174 | it('should put a key value object into the locker via first param', inject((locker) => {
175 | var obj = {
176 | foo: 'bar',
177 | bar: 'baz',
178 | baz: {
179 | foo: 'baz'
180 | },
181 | bob: {
182 | lorem: true
183 | }
184 | };
185 |
186 | locker.put(obj);
187 |
188 | expect( locker.get('foo') ).toEqual('bar');
189 | expect( locker.get('baz') ).toEqual({ foo: 'baz' });
190 | expect( locker.get('bob').lorem ).toBeTruthy();
191 | }));
192 |
193 | describe('when passing a function as the second param', () => {
194 |
195 | it('should put an item into the locker', inject((locker) => {
196 |
197 | locker.put('fnKey', () => 12 * 12);
198 |
199 | expect( locker.get('fnKey') ).toEqual(144);
200 | }));
201 |
202 | it('should pass the current value of the item to the function', inject((locker) => {
203 |
204 | locker.put('fnKey', ['foo', 'bar']);
205 |
206 | locker.put('fnKey', (param) => {
207 | param.push('baz');
208 | return param;
209 | });
210 |
211 | expect( locker.get('fnKey') ).toEqual(['foo', 'bar', 'baz']);
212 | }));
213 |
214 | it('should pass undefined to the function if the current item value does not exist', inject((locker) => {
215 | var value = null;
216 |
217 | expect(locker.get('fnKey')).not.toBeDefined();
218 |
219 | locker.put('fnKey', (param) => {
220 | value = param;
221 | return 2;
222 | });
223 |
224 | expect(locker.get('fnKey')).toEqual(2);
225 | expect(value).not.toBeDefined();
226 | }));
227 | });
228 |
229 | it('should pass the default value to the function if the current item value does not exist and a default was specified', inject((locker) => {
230 | var value = null;
231 |
232 | expect(locker.get('fnKey')).not.toBeDefined();
233 |
234 | locker.put('fnKey', (param) => {
235 | value = param;
236 | return 2;
237 | }, 1);
238 |
239 | expect(locker.get('fnKey')).toEqual(2);
240 | expect(value).toBe(1);
241 | }));
242 |
243 | it('should put an item into the locker when passing a function as first param', inject((locker) => {
244 |
245 | locker.put(() => {
246 | return {
247 | someKey: ['some', 'array'],
248 | anotherKey: { foo: 'bar', baz: true }
249 | };
250 | });
251 |
252 | expect( locker.get('someKey') ).toBeDefined();
253 | expect( locker.get('anotherKey') ).toBeDefined();
254 |
255 | expect( angular.isArray(locker.get('someKey')) ).toBeTruthy();
256 | expect( angular.isObject(locker.get('anotherKey')) ).toBeTruthy();
257 | }));
258 |
259 | it('should put an item into the locker if it doesn\'t already exist', inject((locker) => {
260 |
261 | locker.put('foo', 'loremipsumdolorsitamet');
262 | var added = locker.add('foo', ['foo', 'bar', 'baz']);
263 |
264 | locker.put('bar', 'foobarbazbob');
265 | var added2 = locker.add('bar1', 'foobazbob');
266 |
267 | expect( added ).toBeFalsy();
268 | expect( added2 ).toBeTruthy();
269 |
270 | expect( locker.get('foo') ).toEqual('loremipsumdolorsitamet');
271 | expect( locker.get('bar1') ).toEqual('foobazbob');
272 | }));
273 |
274 | it('should put an item into the locker in a different namespace', inject((locker) => {
275 | locker.put('foo', 'defaultNamespace');
276 | locker.namespace('someOtherNamespace').put('foo', 'newNamespace');
277 | locker.namespace(false).put('noNamespace', [true]);
278 |
279 | expect( locker.get('foo') ).toEqual('defaultNamespace');
280 | expect( locker.namespace('someOtherNamespace').get('foo') ).toEqual('newNamespace');
281 | expect( locker.namespace(false).get('noNamespace') ).toEqual([true]);
282 | }));
283 |
284 | it('should return false if key/value params are missing', inject((locker) => {
285 |
286 | var result1 = locker.put('aKey');
287 | var result2 = locker.put(null, 'aVal');
288 |
289 | expect( result1 && result2 ).toBeFalsy();
290 | }));
291 |
292 | it('should fail silently if value cannot be serialized and unserialized', inject((locker) => {
293 |
294 | spyOn(angular, 'toJson').and.throwError(new Error());
295 | spyOn(angular, 'fromJson').and.throwError(new Error());
296 |
297 | var result = locker.put('foo', ['bar', 'baz']).get('foo');
298 |
299 | expect( result ).toBeDefined();
300 | }));
301 |
302 | it('should catch the error when the browser reports storage is full', inject(($window, locker) => {
303 |
304 | spyOn(locker, '_checkSupport').and.returnValue(true);
305 |
306 | var error = new Error();
307 | error.name = 'QUOTA_EXCEEDED_ERR';
308 |
309 | spyOn($window.localStorage, 'setItem').and.throwError(error);
310 |
311 | expect(() => {
312 | locker.put('someKey', ['foo']);
313 | }).toThrowError();
314 | }));
315 |
316 | it('should catch the error when an item couldn\'t be added for some other reason', inject(($window, locker) => {
317 |
318 | spyOn(locker, '_checkSupport').and.returnValue(true);
319 | spyOn($window.localStorage, 'setItem').and.throwError(new Error());
320 |
321 | expect(() => {
322 | locker.put('someKey', ['foo']);
323 | }).toThrowError();
324 | }));
325 |
326 | it('should throw an error when adding item and no browser support detected', inject(($window, locker) => {
327 |
328 | spyOn(locker, '_checkSupport').and.returnValue(false);
329 |
330 | expect(() => {
331 | locker.put('someKey', ['foo']);
332 | }).toThrowError();
333 | }));
334 |
335 | it('should trigger added event when adding item to locker for the first time', () => {
336 |
337 | module(function(lockerProvider) {
338 | lockerProvider.defaults({ driver: 'session' });
339 | });
340 |
341 | inject((locker, $rootScope) => {
342 | spyOn($rootScope, '$emit');
343 | spyOn(locker, '_exists').and.returnValue(false);
344 |
345 | locker.put('foo', 'bar');
346 |
347 | expect($rootScope.$emit).toHaveBeenCalledWith('locker.item.added', {
348 | key: 'foo',
349 | value: 'bar',
350 | driver: 'session',
351 | namespace: 'locker'
352 | });
353 | });
354 | });
355 |
356 | it('should not trigger events when events are disabled', () => {
357 | module(function(lockerProvider) {
358 | lockerProvider.defaults({ eventsEnabled: false });
359 | });
360 |
361 | inject((locker, $rootScope) => {
362 | spyOn($rootScope, '$emit');
363 | locker.put('foo', 'bar');
364 |
365 | expect($rootScope.$emit).not.toHaveBeenCalled();
366 | });
367 | });
368 |
369 | it('should trigger updated event when updating item already in locker', inject((locker, $rootScope) => {
370 | spyOn($rootScope, '$emit');
371 |
372 | locker.put('foo', 'bar');
373 | locker.put('foo', 'baz');
374 |
375 | expect($rootScope.$emit).toHaveBeenCalledWith('locker.item.updated', {
376 | key: 'foo',
377 | oldValue: 'bar',
378 | newValue: 'baz',
379 | driver: 'local',
380 | namespace: 'locker'
381 | });
382 | }));
383 | });
384 |
385 | describe('switching drivers/namespaces', () => {
386 |
387 | it('should switch drivers when chained', () => {
388 | module(function(lockerProvider) {
389 | lockerProvider.defaults().driver = 'local';
390 | });
391 |
392 | inject((locker) => {
393 | locker.driver('session').put('foo', 'bar');
394 | expect( locker.get('foo') ).not.toBeDefined();
395 | });
396 | });
397 |
398 | it('should switch namespaces when chained', inject((locker) => {
399 |
400 | locker.namespace('fooBar').put('foo', 'bar');
401 |
402 | expect( locker.get('foo') ).not.toBeDefined();
403 | }));
404 |
405 | });
406 |
407 | describe('retrieving items from locker', () => {
408 |
409 | it('should return specified default value if item not in locker', inject((locker) => {
410 | var obj = { foo: 'bar', bar: 123, baz: true };
411 |
412 | locker.put('somethingThatDoesExist', 'exists');
413 |
414 | var result = locker.get('somethingThatDoesExist', 'defaultValue');
415 | var result2 = locker.get('somethingElseThatDoesntExist', { foo: 'bar', bar: 123, baz: true });
416 |
417 | var result3 = locker.get('somethingElseThatDoesntExist', false);
418 | var result4 = locker.get('somethingElseThatDoesntExist', '');
419 | var result5 = locker.get('somethingElseThatDoesntExist', 'NaN');
420 | var result6 = locker.get('somethingElseThatDoesntExist', null);
421 | var result7 = locker.get('somethingElseThatDoesntExist', 0);
422 |
423 | expect( result3 ).toEqual(false);
424 | expect( result4 ).toEqual('');
425 | expect( result5 ).toEqual('NaN');
426 | expect( result6 ).toEqual(null);
427 | expect( result7 ).toEqual(0);
428 |
429 | expect( result ).not.toEqual('defaultValue');
430 | expect( result2 ).toEqual(obj);
431 | }));
432 |
433 | it('should return an object containing the key/value pairs passed in via array', inject((locker) => {
434 |
435 | locker.put(() => {
436 | return {
437 | something: 'some value',
438 | anotherThing: ['foo', 'bar'],
439 | lorem: true,
440 | foo: null
441 | };
442 | });
443 |
444 | var result = locker.get(['something', 'anotherThing']);
445 |
446 | expect( angular.isObject(result) ).toBeTruthy();
447 | expect( result.something ).toEqual('some value');
448 | expect( result ).not.toEqual( jasmine.objectContaining({ lorem: true }) );
449 |
450 | }));
451 |
452 | it('should return a value and then delete the item', inject((locker) => {
453 | var str = 'someVal456';
454 | locker.put('someKey123', str);
455 |
456 | var value = locker.pull('someKey123');
457 |
458 | expect( value ).toEqual(str);
459 | expect( locker.get('someKey123') ).not.toBeDefined();
460 | }));
461 |
462 | it('should return all items within current namespace', inject((locker) => {
463 |
464 | for (var i=0; i<20; i++) {
465 | locker.namespace('foo.bar').put('aKey' + i, 'aVal' + i);
466 | }
467 |
468 | locker.namespace('foo.bar').put('something.foo.bar', ['someValue']);
469 |
470 | var all = locker.namespace('foo.bar').all();
471 | var none = locker.namespace('something').all();
472 |
473 | expect( angular.isObject(all) && angular.isObject(none) ).toBeTruthy();
474 | expect( Object.keys(none).length ).toEqual(0);
475 |
476 | expect( all ).toEqual(jasmine.objectContaining({ 'aKey12': 'aVal12' }));
477 |
478 | expect( Object.keys(all) ).toContain('aKey12');
479 | expect( Object.keys(all) ).toContain('something.foo.bar');
480 | expect( Object.keys(all).length ).toEqual(21);
481 | }));
482 |
483 | it('should count the items within current namespace', inject((locker) => {
484 | for (var i=0; i<20; i++) {
485 | locker.put('aKey' + i, 'aVal' + i);
486 | }
487 |
488 | locker.put('something.foo.bar', ['someValue']);
489 |
490 | expect( locker.count() ).toEqual(21);
491 | expect(locker.namespace('something').count()).toEqual(0);
492 | }));
493 |
494 | it('should throw an error when getting item and no browser support detected', inject(($window, locker) => {
495 |
496 | spyOn(locker, '_checkSupport').and.returnValue(false);
497 | spyOn(locker, 'has').and.returnValue(true);
498 |
499 | expect(() => {
500 | locker.get('someKey');
501 | }).toThrowError();
502 | }));
503 |
504 | });
505 |
506 | describe('removing items from locker', () => {
507 |
508 | it('should remove an item from locker', inject((locker) => {
509 | locker.put('someKey', 'someVal');
510 |
511 | locker.forget('someKey');
512 |
513 | expect( locker.get('someKey') ).not.toBeDefined();
514 | }));
515 |
516 | it('should remove an item from locker when passing a function', inject((locker) => {
517 | locker.put('someKey', 'someVal');
518 |
519 | locker.forget(() => 'someKey');
520 |
521 | expect( locker.get('someKey') ).not.toBeDefined();
522 | }));
523 |
524 | it('should remove multiple items from locker when passing a function', inject((locker) => {
525 | locker.put(() => {
526 | return {
527 | 'something': 'some value',
528 | 'anotherThing': ['foo', 'bar'],
529 | 'lorem': true
530 | };
531 | });
532 |
533 | locker.forget(() => ['something', 'anotherThing']);
534 |
535 | expect( locker.get('something') ).not.toBeDefined();
536 | expect( locker.get('anotherThing') ).not.toBeDefined();
537 | expect( locker.get('lorem') ).toBeTruthy();
538 | }));
539 |
540 | it('should remove multiple items from locker by passing an array', inject((locker) => {
541 |
542 | locker.put('objectKey', {foo: 'bar'});
543 | locker.put('arrayKey', ['foo', 'bar']);
544 | locker.put('foo', 'bar');
545 |
546 | locker.forget(['objectKey', 'arrayKey1', 'foo']);
547 |
548 | expect( locker.get('objectKey') ).not.toBeDefined();
549 | expect( locker.get('arrayKey1') ).not.toBeDefined();
550 | expect( locker.get('foo') ).not.toBeDefined();
551 | }));
552 |
553 | it('should remove all items within a namespace', inject((locker) => {
554 |
555 | locker.put('foo', 'bar');
556 |
557 | locker.namespace('otherNamespace').put('fooOther', 'barOther');
558 |
559 | locker.clean();
560 |
561 | expect( locker.namespace('otherNamespace').get('fooOther') ).toEqual('barOther');
562 | expect( locker.get('foo') ).not.toBeDefined();
563 | }));
564 |
565 | it('should empty the locker', inject((locker) => {
566 |
567 | locker.put('anotherKey', { someObj: true, foo: 'barbaz' });
568 |
569 | locker.empty();
570 |
571 | expect( locker.get('anotherKey') ).not.toBeDefined();
572 |
573 | }));
574 |
575 | it('should throw an error when removing item and no browser support detected', inject(($window, locker) => {
576 |
577 | spyOn(locker, '_checkSupport').and.returnValue(false);
578 |
579 | expect(() => {
580 | locker.forget('someKey');
581 | }).toThrowError();
582 | }));
583 |
584 | it('should trigger forgotten event when removing item from locker', inject((locker, $rootScope) => {
585 | spyOn($rootScope, '$emit');
586 |
587 | locker.put('foo', 'bar');
588 |
589 | locker.forget('foo');
590 |
591 | expect($rootScope.$emit).toHaveBeenCalledWith('locker.item.forgotten', {
592 | key: 'foo',
593 | driver: 'local',
594 | namespace: 'locker'
595 | });
596 | }));
597 |
598 | });
599 |
600 | describe('checking existence in locker', () => {
601 |
602 | it('should determine whether an item exists in locker', inject((locker) => {
603 | locker.put('randKey', Math.random());
604 |
605 | expect( locker.has('randKey') ).toBeTruthy();
606 | expect( locker.has('loremipsumdolorsitamet') ).toBeFalsy();
607 | }));
608 |
609 | it('should determine whether an item exists in locker when passing a function', inject((locker) => {
610 | locker.put('randKey', Math.random());
611 |
612 | var result = locker.has(() => 'randKey');
613 |
614 | expect(result).toBeTruthy();
615 | expect( locker.has('loremipsumdolorsitamet') ).toBeFalsy();
616 | }));
617 |
618 | it('should determine whether an item exists in locker within another namespace', inject((locker) => {
619 | locker.namespace('differentNs').put('randKeyNs', Math.random());
620 |
621 | expect( locker.namespace('differentNs').has('randKeyNs') ).toBeTruthy();
622 | expect( locker.namespace('loremipsumdolorsitamet').has('randKeyNs') ).toBeFalsy();
623 | }));
624 |
625 | it('should throw an error when checking has item and no browser support detected', inject(($window, locker) => {
626 |
627 | spyOn(locker, '_checkSupport').and.returnValue(false);
628 |
629 | expect(() => {
630 | locker.has('someKey');
631 | }).toThrowError();
632 | }));
633 |
634 | it('should check an item exists when hasOwnProperty does not work', inject((locker) => {
635 | locker.namespace('differentNs').put('randKeyNs', Math.random());
636 | spyOn(locker._driver, 'hasOwnProperty').and.returnValue(void 0);
637 |
638 | expect( locker.namespace('differentNs').has('randKeyNs') ).toBeTruthy();
639 | expect( locker.namespace('loremipsumdolorsitamet').has('randKeyNs') ).toBeFalsy();
640 | }));
641 |
642 | });
643 |
644 | describe('checking browser support', () => {
645 |
646 | it('should bind a variable to the scope', inject((locker, $rootScope) => {
647 | locker.bind($rootScope, 'foo');
648 |
649 | $rootScope.foo = ['bar', 'baz'];
650 | $rootScope.$apply();
651 |
652 | expect(locker.get('foo')).toEqual(['bar', 'baz']);
653 |
654 | $rootScope.foo = 123;
655 | $rootScope.$apply();
656 |
657 | expect(locker.get('foo')).toEqual(123);
658 | expect(Object.keys(locker._watchers).length).toEqual(1);
659 | }));
660 |
661 | it('should bind a variable to the scope with a default', inject((locker, $rootScope) => {
662 | locker.bind($rootScope, 'foo', 'defaultVal');
663 |
664 | expect($rootScope.foo).toEqual('defaultVal');
665 | expect(locker.get('foo')).toEqual('defaultVal');
666 | }));
667 |
668 | it('should unbind a variable from the scope', inject((locker, $rootScope) => {
669 | locker.bind($rootScope, 'foo');
670 | locker.bind($rootScope, 'bar');
671 |
672 | $rootScope.foo = ['bar', 'baz'];
673 | $rootScope.$apply();
674 |
675 | expect(locker.get('foo')).toEqual(['bar', 'baz']);
676 |
677 | locker.unbind($rootScope, 'foo');
678 |
679 | expect($rootScope.foo).toBeUndefined();
680 | expect(Object.keys(locker._watchers).length).toEqual(1);
681 | }));
682 |
683 | });
684 |
685 | describe('checking browser support', () => {
686 |
687 | it('should return true if storage is supported', inject(($window, locker) => {
688 |
689 | expect( locker.supported() ).toBeTruthy();
690 |
691 | }));
692 |
693 | it('should return false if storage is not supported', inject(($window, locker) => {
694 |
695 | spyOn($window.localStorage, 'setItem').and.throwError(new Error());
696 |
697 | expect( locker.supported() ).toBeFalsy();
698 |
699 | }));
700 |
701 | });
702 |
703 | describe('misc', () => {
704 |
705 | it('should get the currently set namespace', inject((locker) => {
706 | expect( locker.getNamespace() ).toEqual('locker');
707 | expect( locker.namespace('foo').getNamespace() ).toEqual('foo');
708 | }));
709 |
710 | it('should get the currently set driver', inject(($window, locker) => {
711 | expect( locker.getDriver() ).toEqual($window.localStorage);
712 | expect( locker.driver('session').getDriver() ).toEqual($window.sessionStorage);
713 | }));
714 |
715 | });
716 |
717 | });
718 | });
719 |
--------------------------------------------------------------------------------