├── .gitignore ├── LICENSE ├── README.md ├── build ├── storage.js └── storage.min.js ├── gulpfile.js ├── package-lock.json ├── package.json ├── qunit ├── qunit.css ├── qunit.js └── storage.test.js ├── storage.js └── test.html /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .idea 3 | .DS_STORE 4 | node_modules 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Luís Serralheiro 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. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Browser Storage JS 2 | 3 | JavaScript Library for Cross Browser Persistence using WebStorage (LocalStorage, SessionStorage, WebSQL and IndexedDB) for all browsers. 4 | 5 | Basically it allows us to use any of the storage technologies in a standard way. 6 | 7 | ## Usage 8 | 9 | Note: All objects are expected to have a unique ID in a property called ```id```. 10 | 11 | ### Get the instance 12 | The simplest way to get an instance is to call the function 13 | 14 | ``` 15 | storage(readyCallback [, type]) 16 | ``` 17 | 18 | * readyCallback - Callback that is called when the storage is ready. The passed function receives the instance of the common API and should be used to retrieve it. 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 | -------------------------------------------------------------------------------- /build/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 | -------------------------------------------------------------------------------- /build/storage.min.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 | var storage=function(e,o){var n={sequencialActionCallbackWrapper:function(e,o,n){var t=0,r=e.length,c=function(){t0?JSON.parse(o.rows.item(0).value):void 0)},function(e,o){5===o.code?t():(window.console.log("WebSQL Error: "+o.message+" (Code "+o.code+")",o),t())})})},s=function(e,o){n.transaction(function(n){n.executeSql("select value from "+e,[],function(e,n){for(var t=[],r=n.rows.length,c=0;c 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 | -------------------------------------------------------------------------------- /test.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |

StorageJS QUnit tests

18 | 19 |

20 | 21 |

22 |
    23 |
24 | 25 | 26 | --------------------------------------------------------------------------------