├── DBi.js ├── index.html ├── readme.md ├── schema_1stRun └── schema.json ├── schema_v2 └── schema.json └── schema_v3 └── schema.json /DBi.js: -------------------------------------------------------------------------------- 1 | /* 2 | DBi.js 3 | 4 | Provides an interface to HTML 5 database objects 5 | 6 | This software is released under the MIT License. 7 | 8 | Copyright (c) 2011 Daniel J. Pinter, http://DataZombies.com/ 9 | 10 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 15 | 16 | */ 17 | /*jslint devel: true, bitwise: true, regexp: true, sloppy: true, white: true, nomen: true, plusplus: true, maxerr: 50, indent: 2 */ 18 | (function () { 19 | // Constructor 20 | var DBi = function (settings) { 21 | var i, that = this; 22 | 23 | that.db = {}; 24 | that.error = null; 25 | that.isInitialized = false; 26 | that.longName = null; 27 | that.maxSize = null; 28 | that.schema = {}; 29 | that.schemaReady = false; 30 | that.shortName = null; 31 | that.version = null; 32 | that.settings = { 33 | debug: false, 34 | jsonAsynchronous: false, 35 | reset: false, 36 | schemaFile: null, 37 | schemaObject: null 38 | }; 39 | 40 | // Apply user's settings 41 | for (i in settings) { 42 | if (settings.hasOwnProperty(i)){ 43 | that.settings[i] = settings[i]; 44 | } 45 | } 46 | that.initialization(); 47 | }, 48 | regexParseSQL = /(ALTER|CREATE|UPDATE)\s+(?:(?:UNIQUE\s+)?(INDEX|TABLE|TRIGGER|VIEW)\s+(?:IF\s+NOT\s+EXISTS\s+)?)?(?:(?:OR\s+(?:ABORT|FAIL|IGNORE|REPLACE|ROLLBACK)\s+))?(\w+)\s*(?:\((.*)\))?/i, 49 | regexTableConstraints = /,\s?((?:CHECK|FOREIGN\s+KEY|PRIMARY\s+KEY|UNIQUE).*)/i; 50 | 51 | // Prototype 52 | DBi.prototype = { 53 | // ***************************************************************************** 54 | // _debug () 55 | // If debug is true output a string to the console. 56 | // ***************************************************************************** 57 | _debug: function (a) { 58 | if (this.settings.debug) { 59 | console.log(a); 60 | } 61 | }, 62 | 63 | //**************************************************************************** 64 | // _errorHandler () 65 | // Sends error message to console regardless of value in debug. 66 | //**************************************************************************** 67 | _errorHandler: function (a, b, c) { 68 | if (a !== null && typeof (b) === 'undefined') { 69 | if (-1 !== a.message.indexOf('callback did not return false')) { 70 | return true; 71 | } 72 | c = 'Transaction Error ' + a.code + ' - ' + a.message; 73 | } else if (b !== null) { 74 | c = 'SQL Error ' + b.code + ' - ' + b.message; 75 | } 76 | this.error = c; 77 | console.log('**********\n* ' + c + '\n**********'); 78 | return true; 79 | }, 80 | 81 | 82 | //**************************************************************************** 83 | // initialization () 84 | // Initialize, create & populate and/or upgrade a database in one transaction 85 | // with a JSON file or object. 86 | //**************************************************************************** 87 | initialization: function () { 88 | var jDB, json, jStorage, that = this, 89 | 90 | // insert records 91 | populateTable = function (t, name, tbl) { 92 | var arrVal, i = tbl.records.length - 1, j, strCol, strVal; 93 | for (i; i >= 0; --i) { 94 | arrVal = []; 95 | strCol = ''; 96 | strVal = ''; 97 | for (j in tbl.records[i]) { 98 | if (tbl.records[i].hasOwnProperty(j)) { 99 | arrVal.push(tbl.records[i][j]); 100 | strCol += j + ', '; 101 | strVal += '?, '; 102 | } 103 | } 104 | t.executeSql( 105 | 'INSERT INTO ' + name + ' (' + strCol.substr(0, strCol.lastIndexOf(', ')) + ') VALUES (' + strVal.substr(0, strVal.lastIndexOf(', ')) + ');', 106 | arrVal, 107 | null, 108 | that._errorHandler 109 | ); 110 | } 111 | that._debug('Table ' + that.shortName + '.' + name + ' populated.'); 112 | }, 113 | 114 | // create indices, tables, triggers and views 115 | executeDDL = function (t, type, name, obj) { 116 | var action, i, sql; 117 | if (typeof (obj.sql) !== 'undefined') { 118 | sql = obj.sql; 119 | } else { 120 | sql = 'CREATE ' + type + ' IF NOT EXISTS ' + obj.name + ' ('; 121 | for (i in obj.columns) { 122 | if (obj.columns.hasOwnProperty(i)) { 123 | sql += i + ' ' + obj.columns[i] + ', '; 124 | } 125 | } 126 | sql = sql.substr(0, sql.lastIndexOf(', ')) + (typeof (obj.tableConstraint) !== 'undefined' ? ', ' + obj.tableConstraint : '') + ');'; 127 | } 128 | action = sql.match(regexParseSQL)[1]; 129 | t.executeSql( 130 | sql, 131 | [], 132 | function (t, r) { 133 | that._debug(action + ' ' + type + ' ' + that.shortName + '.' + name + '.'); 134 | if (typeof (obj.records) !== 'undefined' && obj.records.length > 0) { 135 | populateTable(t, name, obj); 136 | } 137 | }, 138 | that._errorHandler 139 | ); 140 | }, 141 | 142 | // remove objects from the database 143 | dropObject = function (t, type, name, tbl) { 144 | t.executeSql( 145 | 'DROP ' + type + ' IF EXISTS ' + name + ';', 146 | [], 147 | function (t, r) { 148 | that._debug('DROP ' + type + ' ' + that.shortName + '.' + name + '.'); 149 | if (typeof (tbl) !== 'undefined' && tbl !== null) { 150 | executeDDL(t, type, name, tbl); 151 | } 152 | }, 153 | that._errorHandler 154 | ); 155 | }, 156 | 157 | // create localStorage and sessionStorage objects if they don't exist 158 | createStorages = function (dbSN, jStorage) { 159 | var newS = function (m, s, j) { 160 | var i, n; 161 | for (i in j) { 162 | if (j.hasOwnProperty(i)) { 163 | n = m + '_' + i; 164 | if (!(n in s)) { 165 | s[n] = j[i]; 166 | that._debug('Storage ' + n + ' added with value "' + j[i] + '".'); 167 | } 168 | } 169 | } 170 | }; 171 | if (typeof (jStorage) !== 'undefined') { 172 | newS(dbSN, localStorage, jStorage.local); 173 | newS(dbSN, sessionStorage, jStorage.session); 174 | } 175 | }, 176 | 177 | // delete localStorage and sessionStorage objects 178 | deleteStorage = function (dbSN, jStorage) { 179 | var delS = function (n, s) { 180 | var i; 181 | for (i in s) { 182 | if (s.hasOwnProperty(i)) { 183 | if (i.substr(0, n.length + 1).toLowerCase() === n.toLowerCase() + '_') { 184 | delete s[i]; 185 | that._debug('Storage ' + i + ' deleted.'); 186 | } 187 | } 188 | } 189 | }; 190 | delS(dbSN, localStorage); 191 | delS(dbSN, sessionStorage); 192 | }, 193 | 194 | // open a database 195 | openDB = function (SN, V, LN, MS) { 196 | if (SN === null || SN === '' || LN === null || LN === '' || MS === null || MS === '') { 197 | that._errorHandler(null, null, 'Error - Database attributes error\n shortName: ' + SN + '\n longName: ' + LN + '\n maxSize: ' + MS + '.'); 198 | return false; 199 | } 200 | try { 201 | that.db = openDatabase(SN, '', LN, MS); 202 | } catch (err) { 203 | that._errorHandler(null, null, 'Error - Database failed to open. Unknown error ' + err + '.'); 204 | return false; 205 | } 206 | if (typeof (that.db) !== 'undefined') { 207 | that.shortName = SN; 208 | that.version = that.db.version; 209 | that.longName = LN; 210 | that.maxSize = MS; 211 | that._debug('Database ' + that.shortName + ' opened.\n version: ' + that.version + '\n longName: ' + that.longName + '\n maxSize: ' + that.maxSize + ' bytes (' + ~~ ((that.maxSize / 10488576 * 1000) + 0.5) / 1000 + ' MB)'); 212 | return true; 213 | } else { 214 | that._errorHandler(null, null, 'Error - Failed to open the database.'); 215 | return false; 216 | } 217 | }, 218 | 219 | // called on the app's 1st run or when reset = true 220 | resetOrNewSchema = function (jDB, jStorage) { 221 | var exclusions = '', i, j, type = ''; 222 | that._debug('reset or new.'); 223 | deleteStorage(jDB.shortName, jStorage); 224 | createStorages(jDB.shortName, jStorage); 225 | for (i in jDB) { 226 | if (jDB.hasOwnProperty(i) && i !== 'upgrades') { 227 | type = (i === 'indices' ? 'index' : i.substr(0, i.length - 1)); 228 | for (j in jDB[i]) { 229 | if (jDB[i].hasOwnProperty(j)) { 230 | exclusions += ',"' + type + jDB[i][j].name + '"'; 231 | } 232 | } 233 | } 234 | } 235 | that.db.transaction( 236 | function (t) { 237 | t.executeSql( 238 | 'SELECT upper(type) type, name FROM sqlite_master WHERE type||name Not In("table__WebKitDatabaseInfoTable__"' + exclusions + ') AND name NOT LIKE "sqlite_%"', 239 | [], 240 | function (t, r) { 241 | var i = r.rows.length - 1; 242 | if (r.rows.length !== -1) { 243 | for (i; i >= 0; --i) { 244 | dropObject(t, r.rows.item(i).type, r.rows.item(i).name, null); 245 | } 246 | } 247 | }, 248 | that._errorHandler 249 | ); 250 | }, 251 | that._errorHandler, 252 | function () { 253 | that.db.transaction( 254 | function (t) { 255 | var i, getName = function (obj) { 256 | var name, sql; 257 | if (typeof (obj.sql) !== 'undefined') { 258 | sql = obj.sql.replace(/\s{2,}/g, ' '); 259 | name = obj.sql.match(regexParseSQL)[3]; 260 | } else { 261 | name = obj.name; 262 | } 263 | return name; 264 | }; 265 | for (i in jDB.tables) { 266 | if (jDB.tables.hasOwnProperty(i)) { 267 | dropObject(t, 'Table', getName(jDB.tables[i]), jDB.tables[i]); 268 | } 269 | } 270 | for (i in jDB.indices) { 271 | if (jDB.indices.hasOwnProperty(i)) { 272 | dropObject(t, 'Index', getName(jDB.indices[i]), jDB.indices[i]); 273 | } 274 | } 275 | for (i in jDB.triggers) { 276 | if (jDB.triggers.hasOwnProperty(i)) { 277 | dropObject(t, 'Trigger', getName(jDB.triggers[i]), jDB.triggers[i]); 278 | } 279 | } 280 | for (i in jDB.views) { 281 | if (jDB.views.hasOwnProperty(i)) { 282 | dropObject(t, 'View', getName(jDB.views[i]), jDB.views[i]); 283 | } 284 | } 285 | }, 286 | that._errorHandler, 287 | function () { 288 | that.db.changeVersion( 289 | that.db.version, 290 | jDB.version, 291 | function (t) {}, // callback 292 | function (err) {}, // errorCallback 293 | function () { // successCallback 294 | that.version = that.db.version; 295 | that.isInitialized = true; 296 | that._debug(that.shortName + ', version ' + that.version +', is initialized.'); 297 | } 298 | ); 299 | } 300 | ); 301 | } 302 | ); 303 | }, 304 | 305 | // performs upgrades to database schema and records when the upgrades object is present in the JSON file 306 | upgradeSchema = function (jDB, jStorage) { 307 | // under construction 308 | }, 309 | 310 | // load a JSON object or a JSON data file asynchronously/non-asynchronously depending on the value in jsonAsynchronous 311 | AE_35 = function (jsonObj, jsonURL) { 312 | var jsonData = new XMLHttpRequest(), 313 | jsonStatus = false, 314 | processJSONdata = function () { 315 | if (jsonStatus) { 316 | if ('openDatabase' in window) { 317 | jDB = jsonData.database; 318 | jStorage = jsonData.storage; 319 | if (openDB(jDB.shortName, jDB.version, jDB.longName, jDB.maxSize)) { 320 | if (that.settings.reset || that.version === '') { 321 | that.settings.reset = true; 322 | resetOrNewSchema(jDB, jStorage); 323 | } else { 324 | if (jDB.version > that.version && that.version !== '') { 325 | //upgradeSchema(jDB, jStorage); 326 | // under construction 327 | } else { 328 | that.isInitialized = true; 329 | that._debug('same version, no reset - just open db.'); 330 | that._debug(that.shortName + ', version ' + that.version +', is initialized.'); 331 | } 332 | } 333 | } 334 | } else { 335 | that._errorHandler(null, null, 'Error - Databases are not supported on this platform.'); 336 | } 337 | } 338 | }, 339 | handleJSONresponse = function () { 340 | if (!jsonData || jsonData === null || jsonData.status !== 200) { 341 | that._errorHandler(null, null, 'JSON Error ' + jsonData.status + ' - file "' + jsonURL + '" ' + jsonData.statusText + '.'); 342 | } else { 343 | that._debug('JSON file "' + jsonURL + '" loaded.'); 344 | try { 345 | jsonData = JSON.parse(jsonData.responseText); 346 | jsonStatus = true; 347 | } catch (err) { 348 | that._errorHandler(null, null, err); 349 | jsonStatus = false; 350 | } 351 | } 352 | }; 353 | 354 | if(typeof (jsonURL) !== 'undefined' && jsonURL !== null) { 355 | jsonData.open("GET", jsonURL, that.settings.jsonAsynchronous); 356 | try { 357 | jsonData.send(null); 358 | } catch (err) { 359 | that._errorHandler(null, null, err); 360 | jsonData = null; 361 | } 362 | if (that.settings.jsonAsynchronous) { 363 | jsonData.onreadystatechange = function () { 364 | if (jsonData.readyState === 4) { 365 | handleJSONresponse(); 366 | processJSONdata(); 367 | } 368 | }; 369 | } else { 370 | handleJSONresponse(); 371 | processJSONdata(); 372 | } 373 | } else { 374 | jsonData = jsonObj; 375 | jsonStatus = true; 376 | processJSONdata(); 377 | } 378 | }; 379 | 380 | if((typeof (that.settings.schemaFile) !== 'undefined' && that.settings.schemaFile !== null) || 381 | (typeof (that.settings.schemaObject) !== 'undefined' && that.settings.schemaObject !== null)) { 382 | AE_35(that.settings.schemaObject, that.settings.schemaFile); 383 | } else { 384 | that._errorHandler(null, null, 'Error - schemaFile or schemaObject is not defined.'); 385 | } 386 | }, 387 | 388 | //**************************************************************************** 389 | // outputSchema () 390 | // Dump the localstorage, sessionStorage & database. 391 | // Based on "Abusing HTML 5 Structured Client-side Storage" by Alberto Trivero 392 | // (http://trivero.secdiscover.com/html5whitepaper.pdf) 393 | //**************************************************************************** 394 | outputSchema: function () { 395 | var indices = [], 396 | json = {}, 397 | ls, 398 | ss, 399 | tables = [], 400 | that = this, 401 | triggers = [], 402 | views = [], 403 | 404 | // gets localStorage and sessionStorage data 405 | getStorage = function (s) { 406 | var dbSN = that.shortName.length !== 0 ? that.shortName.toLowerCase() + '_' : '', 407 | i, 408 | storage = {isEmpty: true}; 409 | for (i in s) { 410 | if (s.hasOwnProperty(i)) { 411 | if (i.substr(0, dbSN.length).toLowerCase() === dbSN) { 412 | storage[i.substr(dbSN.length, i.length)] = s.getItem(i); 413 | storage.isEmpty = false; 414 | } 415 | } 416 | } 417 | return storage; 418 | }, 419 | 420 | // saves the database schema to schema 421 | outputJSON_Schema = function (json) { 422 | that.schema = json; 423 | that.schemaReady = true; 424 | }; 425 | 426 | that.schemaReady = false; 427 | json.generationTimeStamp = new Date(); 428 | json.userAgent = navigator.userAgent; 429 | 430 | // get sessionStorage and localStorage 431 | ls = getStorage(localStorage); 432 | ss = getStorage(sessionStorage); 433 | if (ls.isEmpty === false || ss.isEmpty === false) { 434 | json.storage = {}; 435 | if (ls.isEmpty === false) { 436 | delete ls.isEmpty; 437 | json.storage.local = ls; 438 | } 439 | if (ss.isEmpty === false) { 440 | delete ss.isEmpty; 441 | json.storage.session = ss; 442 | } 443 | } 444 | // start pulling the schema if the database is open 445 | if ('openDatabase' in window) { 446 | if (that.maxSize !== 0 && that.db.version !== '') { 447 | json.database = { 448 | shortName: that.shortName, 449 | version: that.version, 450 | longName: that.longName, 451 | maxSize: that.maxSize 452 | }; 453 | that.db.transaction( 454 | function (t) { 455 | t.executeSql( 456 | 'SELECT sql FROM sqlite_master WHERE name!="__WebKitDatabaseInfoTable__" AND name NOT LIKE "sqlite_%" ORDER BY 1 DESC;', 457 | [], 458 | function (t, r) { 459 | var i, sql, table = {}, 460 | dumpTable = function (t, table) { 461 | // get all data from the selected table 462 | t.executeSql( 463 | 'SELECT * FROM ' + table.name + ' ORDER BY 1 DESC;', 464 | [], 465 | function (t, r) { 466 | var a, b, record = [], 467 | records = []; 468 | if (r.rows.length !== 0) { 469 | for (a = r.rows.length - 1; a >= 0; --a) { 470 | record = {}; 471 | for (b in r.rows.item(a)) { 472 | if (r.rows.item(a).hasOwnProperty(b)) { 473 | record[b] = r.rows.item(a)[b]; 474 | } 475 | } 476 | records.push(record); 477 | } 478 | table.records = records; 479 | } 480 | }, 481 | that._errorHandler); 482 | }, 483 | getColumns = function (sql) { 484 | // extract columns from the sql 485 | var columns = {}, 486 | i, temp = []; 487 | for (i in sql) { 488 | if (sql.hasOwnProperty(i)) { 489 | temp = sql[i].match(/(\w+)\s+(.+)/); 490 | columns[temp[1]] = temp[2]; 491 | } 492 | } 493 | return columns; 494 | }; 495 | 496 | for (i = r.rows.length - 1; i >= 0; --i) { 497 | sql = r.rows.item(i).sql.replace(/\s{2,}/g, ' ') + ';'; 498 | sql = sql.match(regexParseSQL); 499 | sql[0] = r.rows.item(i).sql.replace(/\s{2,}/g, ' ') + ';'; 500 | if (typeof (sql[4]) !== 'undefined') { 501 | if (sql[4].match(regexTableConstraints) !== null) { 502 | sql[5] = sql[4].match(regexTableConstraints)[1]; 503 | sql[4] = sql[4].replace(sql[4].match(regexTableConstraints)[0], ''); 504 | } 505 | sql[4] = sql[4].split(/,/); 506 | } 507 | switch (sql[2].toLowerCase()) { 508 | case 'index': 509 | indices.push({ 510 | name: sql[3], 511 | sql: sql[0] 512 | }); 513 | break; 514 | case 'table': 515 | table = { 516 | name: sql[3], 517 | sql: sql[0], 518 | columns: '' 519 | }; 520 | if (typeof (sql[5]) !== 'undefined' && typeof (sql[5]) !== 'null') { 521 | table.tableConstraint = sql[5]; 522 | } 523 | table.columns = getColumns(sql[4]); 524 | dumpTable(t, table); 525 | tables.push(table); 526 | break; 527 | case 'trigger': 528 | triggers.push({ 529 | name: sql[3], 530 | sql: sql[0] 531 | }); 532 | break; 533 | case 'view': 534 | views.push({ 535 | name: sql[3], 536 | sql: sql[0] 537 | }); 538 | break; 539 | } 540 | } 541 | }, 542 | that._errorHandler 543 | ); 544 | }, 545 | that._errorHandler, 546 | function () { 547 | if (tables.length !== 0) { 548 | json.database.tables = tables; 549 | } 550 | if (indices.length !== 0) { 551 | json.database.indices = indices; 552 | } 553 | if (triggers.length !== 0) { 554 | json.database.triggers = triggers; 555 | } 556 | if (views.length !== 0) { 557 | json.database.views = views; 558 | } 559 | outputJSON_Schema(json); 560 | } 561 | ); 562 | } else { 563 | outputJSON_Schema(json); 564 | } 565 | } else { 566 | outputJSON_Schema(json); 567 | } 568 | } 569 | }; 570 | window.DBi = DBi; 571 | })(); 572 | /* 573 | 574 | Database openDatabase( 575 | DOMString name, 576 | DOMString version, 577 | DOMString displayName, 578 | unsigned long estimatedSize, 579 | function (Database database) { // optional DatabaseCallback creationCallback 580 | } 581 | ); 582 | 583 | database.transaction || database.readTransaction( 584 | function (SQLTransaction transaction) { // SQLTransactionCallback callback 585 | }, 586 | function (SQLError error) { // optional SQLTransactionErrorCallback errorCallback 587 | }, 588 | function () { // optional SQLVoidCallback successCallback 589 | } 590 | ); 591 | 592 | database.changeVersion( 593 | DOMString oldVersion, 594 | DOMString newVersion, 595 | function (SQLTransaction transaction) { // optional SQLTransactionCallback callback 596 | }, 597 | function (SQLError error) { // optional SQLTransactionErrorCallback errorCallback 598 | }, 599 | function () { // optional SQLVoidCallback successCallback 600 | } 601 | ); 602 | 603 | transaction.executeSql( 604 | DOMString sqlStatement, 605 | optional ObjectArray arguments, 606 | function (SQLTransaction transaction, SQLResultSet resultSet) { // optional SQLStatementCallback callback 607 | }, 608 | function (SQLTransaction transaction, SQLError error) { // optional SQLStatementErrorCallback errorCallback 609 | } 610 | ); 611 | 612 | */ -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |104 | | 105 | | |
108 | | 109 | | |
112 | | 113 | | |
116 | | 117 | | |
120 | 121 | 122 | 123 | 124 | |
125 |