├── .gitignore ├── qunit ├── qunit.css ├── qunit.js └── storage.test.js ├── gulpfile.js ├── test.html ├── package.json ├── LICENSE ├── README.md └── storage.js /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .idea 3 | .DS_STORE 4 | node_modules 5 | -------------------------------------------------------------------------------- /qunit/qunit.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lcavadas/browser-storage-js/HEAD/qunit/qunit.css -------------------------------------------------------------------------------- /qunit/qunit.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lcavadas/browser-storage-js/HEAD/qunit/qunit.js -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | /*global require:true */ 2 | (function () { 3 | 'use strict'; 4 | 5 | var gulp = require('gulp'); 6 | var rename = require('gulp-rename'); 7 | var uglify = require('gulp-uglify'); 8 | var jshint = require('gulp-jshint'); 9 | var concat = require('gulp-concat'); 10 | var shell = require('gulp-shell'); 11 | 12 | var DEST = 'build/'; 13 | 14 | gulp.task('default', function () { 15 | return gulp.src(['storage.js']) 16 | .pipe(jshint()) 17 | .pipe(jshint.reporter('default')) 18 | .pipe(gulp.dest(DEST)) 19 | .pipe(uglify({preserveComments: 'some'})) 20 | .pipe(rename({extname: '.min.js'})) 21 | .pipe(gulp.dest(DEST)); 22 | }); 23 | 24 | gulp.task('watch', function () { 25 | gulp.watch('*.js', ['default']); 26 | }); 27 | }()); -------------------------------------------------------------------------------- /test.html: -------------------------------------------------------------------------------- 1 | 3 | 4 |
5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |function(SJS sjs){}
19 | * type - This is an optional parameter that specifies which implementation of WebStorage you wish to use. I not specified the library will try to use IndexedDB falling back to WebSQL and LocalStorage if none of the previous two are available. Valid values are:
20 | * 'LocalStorage'
21 | * 'SessionStorage'
22 | * 'WebSQL'
23 | * 'IndexedDB'
24 |
25 | ### The SJS object
26 | The SJS object is the central part of the library. This is the object that exposes, in a common way, the storage and retrieval operations.
27 |
28 | The possible operations are set, setAll, get, getAll, remove, removeAll and close.
29 |
30 | #### set
31 | Stores an object of a given entity. If an object with the same id exists it updates the stored object.
32 |
33 | ```
34 | set(entity, value, callback)
35 | ```
36 |
37 | * entity - Name of the entity to which this object should be associated.
38 | * value - The Object to be stored.
39 | * callback - Function called when the operation is complete. This function does not pass parameters.
40 |
41 | #### setAll
42 | Stores all objects of a given entity. If objects with the same ids exist it updates the stored objects.
43 |
44 | ```
45 | setAll(entity, values, callback)
46 | ```
47 |
48 | * entity - Name of the entity to which these objects should be associated.
49 | * value - The Objects to be stored, in an array.
50 | * callback - Function called when the operation is complete. This function does not pass parameters.
51 |
52 | #### get
53 | Retrieves a specified object of a given entity. This function has no return. The results are passed through the callback.
54 |
55 | ```
56 | get(entity, id, callback)
57 | ```
58 |
59 | * entity - Name of the entity from which you want to retrieve the object.
60 | * id - The id of the object to be retrieved.
61 | * callback - Function called when the operation is complete. The callback should be ```callback(sv){...}``` where sv is the retrieved object.
62 |
63 | #### getAll
64 | Retrieves all objects of a given entity. This function has no return. The results are passed through the callback.
65 |
66 | ```
67 | getAll(entity, callback)
68 | ```
69 |
70 | * entity - Name of the entity from which you wish to retrieve all entries.
71 | * id - The id of the object to be retrieved.
72 | * callback - Function called when the operation is complete. The callback should be ```callback(svs){...}``` where svs is an array of the retrieved objects.
73 |
74 | #### remove
75 |
76 | Removes a specific entry for a given entity.
77 |
78 | ```
79 | remove(entity, id, callback)
80 | ```
81 |
82 | * entity - Name of the entity from which you wish to remove the identified entry.
83 | * id - The id of the object to be removed.
84 | * callback - Function called when the operation is complete. This function does not pass parameters.
85 |
86 | #### removeAll
87 | Removes all the extries for a given entity.
88 |
89 | ```
90 | removeAll(entity, callback)
91 | ```
92 |
93 | * entity - Name of the entity from which you wish to remove all entries.
94 | * id - The id of the object to be retrieved.
95 | * callback - Function called when the operation is complete. This function does not pass parameters.
96 |
97 | #### close
98 | Closes the database connection. While this is not important for all implementations, it is good practice to close it if you no longer need it.
99 |
100 | ```
101 | close()
102 | ```
103 |
104 | ## Practical examples
105 | For practical example consult the unit tests. The unit tests can be run here. The source code for the tests can be viewed here.
106 |
107 | ## Known Limitations
108 | If IndexedDB is used only one connection can be established at a given time as external upgrades are not yet implemented.
109 |
110 | ## Future Versions
111 | * Implement the capability of external upgrades under IndexedDB.
112 | * Allow the indexing of fields other than the object's id.
113 | * Ability to query by something more than the object's id.
114 |
115 | ## Version History
116 | ver 1.1.1:
117 | Fixing issues arising from the non-existance of the underlying collection.
118 | 119 | ver 1.1.0: 120 |Improved the IndexedDB implementation and made it compatible with latest changes.
121 | 122 | ver 1.0.0: 123 |Added a minified version.
124 | 125 | ver 0.1.0: 126 |First working version of the API that is able to run the four implementations of WebStorage.
127 | -------------------------------------------------------------------------------- /qunit/storage.test.js: -------------------------------------------------------------------------------- 1 | /*globals storage:false, module:false, test:false, asyncTest:false, start:false, expect:false, ok:false, equal:false, notEqual:false, notEqual:false, cob:false */ 2 | 3 | $(document).ready(function () { 4 | 5 | module("LocalStorage"); 6 | asyncTest("set unexistant adds new", function () { 7 | storage(function (storage) { 8 | _cleanup(storage, unexistantAddsNew); 9 | }, 'LocalStorage'); 10 | }); 11 | asyncTest("set existant updates", function () { 12 | storage(function (storage) { 13 | _cleanup(storage, existantUpdates); 14 | }, 'LocalStorage'); 15 | }); 16 | asyncTest("setAll adds/updates all values", function () { 17 | storage(function (storage) { 18 | _cleanup(storage, setAllAddsAndUpdatesAllValues); 19 | }, 'LocalStorage'); 20 | }); 21 | asyncTest("remove", function () { 22 | storage(function (storage) { 23 | _cleanup(storage, remove); 24 | }, 'LocalStorage'); 25 | }); 26 | asyncTest("getAll", function () { 27 | storage(function (storage) { 28 | _cleanup(storage, getAll); 29 | }, 'LocalStorage'); 30 | }); 31 | asyncTest("removeAll", function () { 32 | storage(function (storage) { 33 | _cleanup(storage, removeAll); 34 | }, 'LocalStorage'); 35 | }); 36 | asyncTest("getNoCollection", function () { 37 | storage(function (storage) { 38 | _cleanup(storage, getNoCollection); 39 | }, 'LocalStorage'); 40 | }); 41 | asyncTest("getAllNoCollection", function () { 42 | storage(function (storage) { 43 | _cleanup(storage, getAllNoCollection); 44 | }, 'LocalStorage'); 45 | }); 46 | asyncTest("removeNoCollection", function () { 47 | storage(function (storage) { 48 | _cleanup(storage, removeNoCollection); 49 | }, 'LocalStorage'); 50 | }); 51 | asyncTest("removeAllNoCollection", function () { 52 | storage(function (storage) { 53 | _cleanup(storage, removeAllNoCollection); 54 | }, 'LocalStorage'); 55 | }); 56 | 57 | module("SessionStorage"); 58 | asyncTest("set unexistant adds new", function () { 59 | storage(function (storage) { 60 | _cleanup(storage, unexistantAddsNew); 61 | }, 'SessionStorage'); 62 | }); 63 | asyncTest("set existant updates", function () { 64 | storage(function (storage) { 65 | _cleanup(storage, existantUpdates); 66 | }, 'SessionStorage'); 67 | }); 68 | asyncTest("setAll adds/updates all values", function () { 69 | storage(function (storage) { 70 | _cleanup(storage, setAllAddsAndUpdatesAllValues); 71 | }, 'SessionStorage'); 72 | }); 73 | asyncTest("remove", function () { 74 | storage(function (storage) { 75 | _cleanup(storage, remove); 76 | }, 'SessionStorage'); 77 | }); 78 | asyncTest("getAll", function () { 79 | storage(function (storage) { 80 | _cleanup(storage, getAll); 81 | }, 'SessionStorage'); 82 | }); 83 | asyncTest("removeAll", function () { 84 | storage(function (storage) { 85 | _cleanup(storage, removeAll); 86 | }, 'SessionStorage'); 87 | }); 88 | asyncTest("getNoCollection", function () { 89 | storage(function (storage) { 90 | _cleanup(storage, getNoCollection); 91 | }, 'SessionStorage'); 92 | }); 93 | asyncTest("getAllNoCollection", function () { 94 | storage(function (storage) { 95 | _cleanup(storage, getAllNoCollection); 96 | }, 'SessionStorage'); 97 | }); 98 | asyncTest("removeNoCollection", function () { 99 | storage(function (storage) { 100 | _cleanup(storage, removeNoCollection); 101 | }, 'SessionStorage'); 102 | }); 103 | asyncTest("removeAllNoCollection", function () { 104 | storage(function (storage) { 105 | _cleanup(storage, removeAllNoCollection); 106 | }, 'SessionStorage'); 107 | }); 108 | 109 | module("WebSQL"); 110 | asyncTest("set unexistant adds new", function () { 111 | storage(function (storage) { 112 | _cleanup(storage, unexistantAddsNew); 113 | }, 'WebSQL'); 114 | }); 115 | asyncTest("set existant updates", function () { 116 | storage(function (storage) { 117 | _cleanup(storage, existantUpdates); 118 | }, 'WebSQL'); 119 | }); 120 | asyncTest("setAll adds/updates all values", function () { 121 | storage(function (storage) { 122 | _cleanup(storage, setAllAddsAndUpdatesAllValues); 123 | }, 'WebSQL'); 124 | }); 125 | asyncTest("remove", function () { 126 | storage(function (storage) { 127 | _cleanup(storage, remove); 128 | }, 'WebSQL'); 129 | }); 130 | asyncTest("getAll", function () { 131 | storage(function (storage) { 132 | _cleanup(storage, getAll); 133 | }, 'WebSQL'); 134 | }); 135 | asyncTest("removeAll", function () { 136 | storage(function (storage) { 137 | _cleanup(storage, removeAll); 138 | }, 'WebSQL'); 139 | }); 140 | asyncTest("getNoCollection", function () { 141 | storage(function (storage) { 142 | _cleanup(storage, getNoCollection); 143 | }, 'WebSQL'); 144 | }); 145 | asyncTest("getAllNoCollection", function () { 146 | storage(function (storage) { 147 | _cleanup(storage, getAllNoCollection); 148 | }, 'WebSQL'); 149 | }); 150 | asyncTest("removeNoCollection", function () { 151 | storage(function (storage) { 152 | _cleanup(storage, removeNoCollection); 153 | }, 'WebSQL'); 154 | }); 155 | asyncTest("removeAllNoCollection", function () { 156 | storage(function (storage) { 157 | _cleanup(storage, removeAllNoCollection); 158 | }, 'WebSQL'); 159 | }); 160 | 161 | module("IndexedDB"); 162 | asyncTest("set unexistant adds new", function () { 163 | storage(function (storage) { 164 | _cleanup(storage, unexistantAddsNew); 165 | }, 'IndexedDB'); 166 | }); 167 | asyncTest("set existant updates", function () { 168 | storage(function (storage) { 169 | _cleanup(storage, existantUpdates); 170 | }, 'IndexedDB'); 171 | }); 172 | asyncTest("setAll adds/updates all values", function () { 173 | storage(function (storage) { 174 | _cleanup(storage, setAllAddsAndUpdatesAllValues); 175 | }, 'IndexedDB'); 176 | }); 177 | asyncTest("remove", function () { 178 | storage(function (storage) { 179 | _cleanup(storage, remove); 180 | }, 'IndexedDB'); 181 | }); 182 | asyncTest("getAll", function () { 183 | storage(function (storage) { 184 | _cleanup(storage, getAll); 185 | }, 'IndexedDB'); 186 | }); 187 | asyncTest("removeAll", function () { 188 | storage(function (storage) { 189 | _cleanup(storage, removeAll); 190 | }, 'IndexedDB'); 191 | }); 192 | asyncTest("getNoCollection", function () { 193 | storage(function (storage) { 194 | _cleanup(storage, getNoCollection); 195 | }, 'IndexedDB'); 196 | }); 197 | asyncTest("getAllNoCollection", function () { 198 | storage(function (storage) { 199 | _cleanup(storage, getAllNoCollection); 200 | }, 'IndexedDB'); 201 | }); 202 | asyncTest("removeNoCollection", function () { 203 | storage(function (storage) { 204 | _cleanup(storage, removeNoCollection); 205 | }, 'IndexedDB'); 206 | }); 207 | asyncTest("removeAllNoCollection", function () { 208 | storage(function (storage) { 209 | _cleanup(storage, removeAllNoCollection); 210 | }, 'IndexedDB'); 211 | }); 212 | 213 | var _cleanup = function (storage, test) { 214 | if (!storage) { 215 | ok(true, "Database is not supported, Skipping test"); 216 | start(); 217 | return; 218 | } 219 | storage.removeAll("unexistantAddsNew", function () { 220 | storage.removeAll("existantUpdates", function () { 221 | storage.removeAll("setAllAddsAndUpdatesAllValues", function () { 222 | storage.removeAll("getAll", function () { 223 | storage.removeAll("remove", function () { 224 | storage.removeAll("removeAll", function () { 225 | test(storage); 226 | }); 227 | }); 228 | }); 229 | }); 230 | }); 231 | }); 232 | }; 233 | 234 | var unexistantAddsNew = function (storage) { 235 | var testValue = {id: 1, name: "sample 1"}; 236 | storage.set("unexistantAddsNew", testValue, function () { 237 | storage.get("unexistantAddsNew", 1, function (value) { 238 | equal(value.id, testValue.id, "retrieved value matches sent value id"); 239 | equal(value.name, testValue.name, "retrieved value matches sent value name"); 240 | storage.close(); 241 | start(); 242 | }); 243 | }); 244 | }; 245 | 246 | var existantUpdates = function (storage) { 247 | var testValue = {id: 1, name: "sample 1"}; 248 | storage.set("existantUpdates", testValue, function () { 249 | storage.get("existantUpdates", 1, function (value) { 250 | equal(value.id, testValue.id, "original retrieved value matches sent value id"); 251 | equal(value.name, testValue.name, "original retrieved value matches sent value name"); 252 | storage.set("existantUpdates", {id: 1, name: "ahaha"}, function () { 253 | storage.get("existantUpdates", 1, function (value) { 254 | equal(value.id, testValue.id, "set retrieved value matches sent value id"); 255 | equal(value.name, "ahaha", "set retrieved value matches new value name"); 256 | storage.close(); 257 | start(); 258 | }); 259 | }); 260 | }); 261 | }); 262 | }; 263 | 264 | var setAllAddsAndUpdatesAllValues = function (storage) { 265 | var testValue = {id: 1, name: "sample 1"}; 266 | var otherValue = {id: 2, name: "sample 2"}; 267 | 268 | storage.set("setAllAddsAndUpdatesAllValues", testValue, function () { 269 | storage.get("setAllAddsAndUpdatesAllValues", 1, function (value) { 270 | equal(value.id, testValue.id, "retrieved value matches sent value id"); 271 | equal(value.name, testValue.name, "retrieved value matches sent value name"); 272 | storage.setAll("setAllAddsAndUpdatesAllValues", [ 273 | {id: 1, name: "ahaha"}, 274 | otherValue 275 | ], function () { 276 | storage.getAll("setAllAddsAndUpdatesAllValues", function (values) { 277 | equal(values[0].id, testValue.id, "retrieved value matches sent value id"); 278 | equal(values[0].name, "ahaha", "retrieved value matches new value name"); 279 | equal(values[1].id, otherValue.id, "retrieved value matches sent value id"); 280 | equal(values[1].name, otherValue.name, "retrieved value matches new value name"); 281 | storage.close(); 282 | start(); 283 | }); 284 | }); 285 | }); 286 | }); 287 | }; 288 | 289 | var getAll = function (storage) { 290 | var testValue = {id: 1, name: "sample 1"}; 291 | var otherValue = {id: 2, name: "sample 2"}; 292 | storage.setAll("getAll", [testValue, otherValue], function () { 293 | storage.getAll("getAll", function (values) { 294 | equal(values.length, 2, "has two results"); 295 | equal(values[0].id, 1, "id of first element"); 296 | equal(values[0].name, "sample 1", "name of first element"); 297 | equal(values[1].id, 2, "id of second element"); 298 | equal(values[1].name, "sample 2", "name of second element"); 299 | storage.close(); 300 | start(); 301 | }); 302 | }); 303 | }; 304 | 305 | var remove = function (storage) { 306 | var testValue = {id: 1, name: "sample 1"}; 307 | var otherValue = {id: 2, name: "sample 2"}; 308 | storage.setAll("remove", [testValue, otherValue], function () { 309 | storage.remove("remove", 1, function () { 310 | storage.getAll("remove", function (values) { 311 | equal(values.length, 1, "has one result"); 312 | equal(values[0].id, 2, "id of element"); 313 | equal(values[0].name, "sample 2", "name of element"); 314 | storage.close(); 315 | start(); 316 | }); 317 | }); 318 | }); 319 | }; 320 | 321 | var removeAll = function (storage) { 322 | var testValue = {id: 1, name: "sample 1"}; 323 | var otherValue = {id: 2, name: "sample 2"}; 324 | storage.setAll("removeAll", [testValue, otherValue], function () { 325 | storage.removeAll("removeAll", function () { 326 | storage.getAll("removeAll", function (values) { 327 | equal(values.length, 0, "has no results"); 328 | storage.close(); 329 | start(); 330 | }); 331 | }); 332 | }); 333 | }; 334 | 335 | var getNoCollection = function (storage) { 336 | storage.get("thisIsACollectionThatWillNotBePresent", 1, function (value) { 337 | equal(value, undefined, "has null result"); 338 | storage.close(); 339 | start(); 340 | }); 341 | }; 342 | 343 | var getAllNoCollection = function (storage) { 344 | storage.getAll("thisIsACollectionThatWillNotBePresent", function (values) { 345 | equal(values.length, 0, "has no results"); 346 | storage.close(); 347 | start(); 348 | }); 349 | }; 350 | 351 | var removeNoCollection = function (storage) { 352 | storage.remove("thisIsACollectionThatWillNotBePresent", 1, function () { 353 | expect(0); 354 | storage.close(); 355 | start(); 356 | }); 357 | }; 358 | 359 | var removeAllNoCollection = function (storage) { 360 | storage.removeAll("thisIsACollectionThatWillNotBePresent", function () { 361 | expect(0); 362 | storage.close(); 363 | start(); 364 | }); 365 | }; 366 | }); 367 | -------------------------------------------------------------------------------- /storage.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Storage.js JavaScript Library v1.1.1 3 | * https://github.com/lcavadas/Storage.js 4 | * 5 | * Copyright 2012-2017, Luís Serralheiro 6 | */ 7 | 8 | var storage = function (readyCallback, type) { 9 | 10 | var commons = { 11 | sequencialActionCallbackWrapper: function (values, callback, finalCallback) { 12 | var index = 0; 13 | var length = values.length; 14 | 15 | var _next = function () { 16 | if (index < length) { 17 | callback(values[index++]); 18 | } else { 19 | finalCallback(); 20 | } 21 | }; 22 | 23 | return { 24 | next: _next 25 | }; 26 | }, 27 | multipleActionCallbackWrapper: function (times, callback) { 28 | var values = []; 29 | 30 | return { 31 | countDown: function (value) { 32 | values.push(value); 33 | if (values.length === (times)) { 34 | callback(values); 35 | } 36 | } 37 | }; 38 | } 39 | }; 40 | 41 | var invokeReadyCallBack = function (database) { 42 | if (!database) { 43 | readyCallback(); 44 | } else { 45 | readyCallback({ 46 | set: function (entity, value, callback) { 47 | database.set(entity, value, callback); 48 | }, 49 | setAll: function (entity, values, callback) { 50 | database.setAll(entity, values, callback); 51 | }, 52 | get: function (entity, id, callback) { 53 | database.get(entity, id, callback); 54 | }, 55 | getAll: function (entity, callback) { 56 | database.getAll(entity, callback); 57 | }, 58 | remove: function (entity, id, callback) { 59 | database.remove(entity, id, callback); 60 | }, 61 | removeAll: function (entity, callback) { 62 | database.removeAll(entity, callback); 63 | }, 64 | ready: function (callback) { 65 | database.ready(callback); 66 | }, 67 | close: database.close, 68 | type: database.type 69 | }); 70 | } 71 | }; 72 | 73 | switch (type) { 74 | case 'LocalStorage': 75 | storage.KeyValue(invokeReadyCallBack, commons); 76 | break; 77 | case 'SessionStorage': 78 | storage.KeyValue(invokeReadyCallBack, commons, true); 79 | break; 80 | case 'WebSQL': 81 | storage.WebSQL(invokeReadyCallBack, commons); 82 | break; 83 | case 'IndexedDB': 84 | storage.IndexedDB(invokeReadyCallBack, commons); 85 | break; 86 | default : 87 | //WebSQL 88 | if (window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.msIndexedDB) { 89 | window.console.log("Using IndexedDB"); 90 | storage.IndexedDB(invokeReadyCallBack, commons); 91 | } else if (window.openDatabase) { 92 | window.console.log("Using WebSQL"); 93 | storage.WebSQL(invokeReadyCallBack, commons); 94 | } else { 95 | window.console.log("Using LocalStorage"); 96 | //Fallback to localStorage 97 | storage.KeyValue(invokeReadyCallBack, commons); 98 | } 99 | break; 100 | } 101 | }; 102 | 103 | // localStorage and sessionStorage wrapper 104 | // useSession: Indicates if sessionStorage is to be used (default is localStorage) 105 | storage.KeyValue = function (ready, commons, useSession) { 106 | 107 | try { 108 | var kv = useSession ? sessionStorage : localStorage; 109 | } catch (e) { 110 | ready(); 111 | return; 112 | } 113 | 114 | var _get = function (entity, id, callback) { 115 | var jsonString = kv.getItem(entity); 116 | if(jsonString){ 117 | var stored = JSON.parse(jsonString); 118 | var length = stored ? stored.length : 0; 119 | 120 | for (var i = 0; i < length; i++) { 121 | if (stored[i].id === id) { 122 | callback(stored[i]); 123 | return; 124 | } 125 | } 126 | callback(); 127 | } else { 128 | callback(); 129 | } 130 | }; 131 | 132 | var _set = function (entity, value, callback) { 133 | var stored = JSON.parse(kv.getItem(entity)) || []; 134 | var updated = false; 135 | var length = stored.length; 136 | 137 | for (var i = 0; i < length; i++) { 138 | if (stored[i].id === value.id) { 139 | updated = true; 140 | stored[i] = value; 141 | } 142 | } 143 | if (!updated) { 144 | stored.push(value); 145 | } 146 | 147 | kv.setItem(entity, JSON.stringify(stored)); 148 | 149 | callback(); 150 | }; 151 | 152 | var _remove = function (entity, id) { 153 | var jsonString = kv.getItem(entity); 154 | if(jsonString){ 155 | var stored = JSON.parse(jsonString); 156 | var length = stored.length; 157 | 158 | for (var i = 0; i < length; i++) { 159 | if (stored[i].id === id) { 160 | stored.splice(i, 1); 161 | kv.setItem(entity, JSON.stringify(stored)); 162 | return; 163 | } 164 | } 165 | } 166 | }; 167 | 168 | ready({ 169 | set: _set, 170 | setAll: function (entity, values, callback) { 171 | var responseCallback = commons.multipleActionCallbackWrapper(values.length, callback); 172 | values.forEach(function (value) { 173 | _set(entity, value, responseCallback.countDown); 174 | }); 175 | }, 176 | get: _get, 177 | getAll: function (entity, callback) { 178 | var value = kv.getItem(entity); 179 | callback(value ? JSON.parse(value) : []); 180 | }, 181 | remove: function (entity, id, callback) { 182 | _remove(entity, id); 183 | callback(); 184 | }, 185 | removeAll: function (entity, callback) { 186 | kv.removeItem(entity); 187 | callback(); 188 | }, 189 | close: function () { 190 | //There is nothing to do 191 | }, 192 | type: 'KeyValue' 193 | }); 194 | }; 195 | 196 | storage.WebSQL = function (ready, commons) { 197 | var db; 198 | 199 | var _createTable = function (name, callback) { 200 | db.transaction( 201 | function (transaction) { 202 | transaction.executeSql( 203 | 'CREATE TABLE if not exists ' + name + '(id TEXT NOT NULL, value TEXT, PRIMARY KEY(id));', 204 | [], 205 | callback, 206 | function (transaction, error) { 207 | window.console.log('Oops. Error was ' + error.message + ' (Code ' + error.code + ')', error); 208 | } 209 | ); 210 | } 211 | ); 212 | }; 213 | 214 | var _set = function (entity, value, callback) { 215 | db.transaction( 216 | function (transaction) { 217 | transaction.executeSql( 218 | 'INSERT OR REPLACE into ' + entity + '(id, value) VALUES ( ?, ? );', 219 | [value.id, JSON.stringify(value)], 220 | function () { 221 | callback(); 222 | }, 223 | function (transaction, error) { 224 | //No such table 225 | if (error.code === 5) { 226 | window.console.log("WebSQL: going to create table " + entity); 227 | //create the table and try again 228 | _createTable(entity, function () { 229 | window.console.log("WebSQL: created table " + entity); 230 | _set(entity, value, callback); 231 | }); 232 | } else { 233 | window.console.log('WebSQL Error: ' + error.message + ' (Code ' + error.code + ')', error); 234 | callback(); 235 | } 236 | } 237 | ); 238 | }); 239 | }; 240 | 241 | var _get = function (entity, id, callback) { 242 | db.transaction( 243 | function (transaction) { 244 | transaction.executeSql( 245 | "select value from " + entity + " where id=?;", 246 | [id], 247 | function (transaction, results) { 248 | callback(results.rows.length > 0 ? JSON.parse(results.rows.item(0).value) : undefined); 249 | }, 250 | function (transaction, error) { 251 | //No such table 252 | if (error.code === 5) { 253 | callback(); 254 | } else { 255 | window.console.log('WebSQL Error: ' + error.message + ' (Code ' + error.code + ')', error); 256 | callback(); 257 | } 258 | } 259 | ); 260 | } 261 | ); 262 | }; 263 | 264 | var _getAll = function (entity, callback) { 265 | db.transaction( 266 | function (transaction) { 267 | transaction.executeSql( 268 | "select value from " + entity, 269 | [], 270 | function (transaction, results) { 271 | var objectArray = []; 272 | var length = results.rows.length; 273 | for (var i = 0; i < length; i++) { 274 | objectArray.push(JSON.parse(results.rows.item(i).value)); 275 | } 276 | callback(objectArray); 277 | }, 278 | function (transaction, error) { 279 | //No such table 280 | if (error.code === 5) { 281 | callback([]); 282 | } else { 283 | window.console.log('WebSQL Error: ' + error.message + ' (Code ' + error.code + ')', error); 284 | callback(); 285 | } 286 | } 287 | ); 288 | } 289 | ); 290 | }; 291 | 292 | var _remove = function (entity, id, callback) { 293 | db.transaction( 294 | function (transaction) { 295 | transaction.executeSql( 296 | "delete from " + entity + " where id=?", 297 | [id], 298 | callback, 299 | function (transaction, error) { 300 | window.console.log('WebSQL Error: ' + error.message + ' (Code ' + error.code + ')', error); 301 | callback(); 302 | } 303 | ); 304 | } 305 | ); 306 | }; 307 | 308 | var _removeAll = function (entity, callback) { 309 | db.transaction( 310 | function (transaction) { 311 | transaction.executeSql( 312 | "drop table " + entity + "", 313 | [], 314 | callback, 315 | //table doesnt exist 316 | callback 317 | ); 318 | } 319 | ); 320 | }; 321 | 322 | try { 323 | if (!window.openDatabase) { 324 | window.console.log('SQL Database not supported'); 325 | ready(); 326 | } else { 327 | var shortName = 'storage.js'; 328 | var version = '1.0'; 329 | var displayName = 'storage.js database'; 330 | var maxSize = 65536; // in bytes 331 | db = openDatabase(shortName, version, displayName, maxSize); 332 | 333 | ready({ 334 | set: _set, 335 | setAll: function (entity, values, callback) { 336 | var responseCallback = commons.multipleActionCallbackWrapper(values.length, callback); 337 | values.forEach(function (value) { 338 | _set(entity, value, responseCallback.countDown); 339 | }); 340 | }, 341 | get: _get, 342 | getAll: _getAll, 343 | remove: _remove, 344 | removeAll: _removeAll, 345 | close: function () { 346 | //There is nothing to do 347 | }, 348 | type: 'WebSQL' 349 | }); 350 | } 351 | } catch (e) { 352 | // Error handling code goes here. 353 | if (e === 2) { 354 | // Version number mismatch. 355 | window.console.log("Invalid database version."); 356 | } else { 357 | window.console.log("Unknown error " + e + "."); 358 | } 359 | } 360 | }; 361 | 362 | storage.IndexedDB = function (ready, commons) { 363 | var indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.msIndexedDB; 364 | var db; 365 | 366 | var _createObjectStore = function (entity, callback) { 367 | db.close(); 368 | var version = db.version + 1; 369 | var versionRequest = indexedDB.open("storage_js", version); 370 | versionRequest.onupgradeneeded = function () { 371 | db = versionRequest.result; 372 | db.createObjectStore(entity, {keyPath: "id"}); 373 | }; 374 | versionRequest.onsuccess = callback; 375 | }; 376 | 377 | var _set = function (entity, value, callback) { 378 | try { 379 | if (!db.objectStoreNames.contains(entity)) { 380 | window.console.log("IndexedDB: going to create objectStore " + entity); 381 | _createObjectStore(entity, function () { 382 | window.console.log("IndexedDB: created objectStore " + entity); 383 | _set(entity, value, callback); 384 | }); 385 | return; 386 | } 387 | 388 | var transaction = db.transaction([entity], "readwrite"); 389 | var objectStore = transaction.objectStore(entity); 390 | var request = objectStore.put(value); 391 | transaction.onerror = function (error) { 392 | window.console.trace('IndexedDB Error: ' + error.message + ' (Code ' + error.code + ')', error); 393 | }; 394 | request.onsuccess = function () { 395 | callback(); 396 | }; 397 | request.onerror = function (error) { 398 | window.console.trace('IndexedDB Error: ' + error.message + ' (Code ' + error.code + ')', error); 399 | }; 400 | } catch (error) { 401 | //error code 3 and 8 are not found on chrome and canary respectively 402 | if (error.code !== 3 && error.code !== 8) { 403 | window.console.trace('IndexedDB Error: ' + error.message + ' (Code ' + error.code + ')', error); 404 | callback(); 405 | } else { 406 | window.console.log("IndexedDB: going to create objectStore " + entity); 407 | _createObjectStore(entity, function () { 408 | _set(entity, value, callback); 409 | }); 410 | } 411 | } 412 | }; 413 | 414 | var _get = function (entity, id, callback) { 415 | try { 416 | if (!db.objectStoreNames.contains(entity)) { 417 | window.console.log("IndexedDB: missing objectStore " + entity); 418 | callback(); 419 | } else { 420 | var transaction = db.transaction([entity], "readwrite"); 421 | transaction.onerror = function (error) { 422 | window.console.trace('IndexedDB Error: ' + error.message + ' (Code ' + error.code + ')', error); 423 | }; 424 | var objectStore = transaction.objectStore(entity); 425 | objectStore.get(id).onsuccess = function (event) { 426 | callback(event.target.result); 427 | }; 428 | } 429 | } catch (error) { 430 | window.console.trace('IndexedDB Error: ' + error.message + ' (Code ' + error.code + ')', error); 431 | callback(); 432 | } 433 | }; 434 | 435 | var _getAll = function (entity, callback) { 436 | try { 437 | var objectArray = []; 438 | if (!db.objectStoreNames.contains(entity)) { 439 | window.console.log("IndexedDB: missing objectStore " + entity); 440 | callback(objectArray); 441 | } else { 442 | var transaction = db.transaction([entity], "readwrite"); 443 | transaction.onerror = function (error) { 444 | window.console.trace('IndexedDB Error: ' + error.message + ' (Code ' + error.code + ')', error); 445 | }; 446 | var objectStore = transaction.objectStore(entity); 447 | objectStore.openCursor().onsuccess = function (event) { 448 | var cursor = event.target.result; 449 | if (cursor) { 450 | objectArray.push(cursor.value); 451 | cursor.continue(); 452 | } 453 | else { 454 | callback(objectArray); 455 | } 456 | }; 457 | } 458 | } catch (error) { 459 | callback([]); 460 | } 461 | }; 462 | 463 | var _remove = function (entity, id, callback) { 464 | if (!db.objectStoreNames.contains(entity)) { 465 | window.console.log("IndexedDB: missing objectStore " + entity); 466 | callback(); 467 | } else { 468 | var transaction = db.transaction([entity], "readwrite"); 469 | var objectStore = transaction.objectStore(entity); 470 | objectStore.delete(id).onsuccess = callback; 471 | } 472 | }; 473 | 474 | var _removeAll = function (entity, callback) { 475 | db.close(); 476 | var version = db.version + 1; 477 | var request = indexedDB.open("storage_js", version); 478 | request.onupgradeneeded = function () { 479 | try { 480 | db = request.result; 481 | if (db.objectStoreNames.contains(entity)) { 482 | db.deleteObjectStore(entity); 483 | } 484 | } catch (error) { 485 | //error code 3 and 8 are not found on chrome and canary respectively 486 | if (error.code !== 3 && error.code !== 8) { 487 | window.console.trace('IndexedDB Error: ' + error.message + ' (Code ' + error.code + ')', error); 488 | } 489 | } 490 | }; 491 | request.onsuccess = callback; 492 | }; 493 | 494 | var _close = function () { 495 | db.close(); 496 | }; 497 | 498 | if (indexedDB) { 499 | // Now we can open our database 500 | var request = indexedDB.open("storage_js"); 501 | request.onsuccess = function () { 502 | db = request.result; 503 | ready({ 504 | set: _set, 505 | setAll: function (entity, values, callback) { 506 | var seqWrapper = commons.sequencialActionCallbackWrapper(values, function (value) { 507 | _set(entity, value, seqWrapper.next); 508 | }, callback); 509 | seqWrapper.next(); 510 | }, 511 | get: _get, 512 | getAll: _getAll, 513 | remove: _remove, 514 | removeAll: _removeAll, 515 | close: _close, 516 | type: 'IndexedDB' 517 | }); 518 | }; 519 | request.onerror = function (event) { 520 | window.console.log("An error ocurred", event); 521 | ready(); 522 | }; 523 | } else { 524 | ready(); 525 | } 526 | }; 527 | 528 | window.storage = storage; 529 | --------------------------------------------------------------------------------