├── .gitignore ├── src ├── outro.js ├── intro.js ├── query.js ├── util.js └── database.js ├── README.md ├── Makefile └── demo └── index.html /.gitignore: -------------------------------------------------------------------------------- 1 | /nbproject/ -------------------------------------------------------------------------------- /src/outro.js: -------------------------------------------------------------------------------- 1 | 2 | })(window); -------------------------------------------------------------------------------- /src/intro.js: -------------------------------------------------------------------------------- 1 | (function(window, undefined) { 2 | 3 | -------------------------------------------------------------------------------- /src/query.js: -------------------------------------------------------------------------------- 1 | jDal.Query = function(q) { 2 | //query class 3 | }; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | jDal - A JavaScript Database Abstraction Library 2 | ======================= 3 | This project's purpose is to abstract client-side database access across all major browsers. 4 | This current repo is a PoC for my Software Engineering II class, since the project is extremely time-boxed the "library" 5 | will be feature starved and not written in the best, most *extensible* way possible. 6 | 7 | Rewrite 8 | ------- 9 | More than likely this will be written with a better API and renamed Garage.js soon... -------------------------------------------------------------------------------- /src/util.js: -------------------------------------------------------------------------------- 1 | window.jDal = { 2 | //allows us to keep our 'this' reference 3 | _bind: function(scope, fn, remove) { 4 | return function() { 5 | var args = Array.prototype.slice.call(arguments), 6 | remove = (remove ? remove: 0); 7 | 8 | args.splice(0, remove, this); 9 | fn.apply(scope, args); 10 | }; 11 | }, 12 | _generateGuid: function() { 13 | var chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split(''), 14 | uuid = new Array(36), rnd=0, r; 15 | for (var i = 0; i < 36; i++) { 16 | if (i==8 || i==13 || i==18 || i==23) { 17 | uuid[i] = '-'; 18 | } else if (i==14) { 19 | uuid[i] = '4'; 20 | } else { 21 | if (rnd <= 0x02) rnd = 0x2000000 + (Math.random()*0x1000000)|0; 22 | r = rnd & 0xf; 23 | rnd = rnd >> 4; 24 | uuid[i] = chars[(i == 19) ? (r & 0x3) | 0x8 : r]; 25 | } 26 | } 27 | return uuid.join(''); 28 | }, 29 | //From jQuery Source 30 | _extend: function() { 31 | var options, name, src, copy, copyIsArray, clone, 32 | target = arguments[0] || {}, 33 | i = 1, 34 | length = arguments.length, 35 | deep = false; 36 | 37 | // Handle a deep copy situation 38 | if ( typeof target === "boolean" ) { 39 | deep = target; 40 | target = arguments[1] || {}; 41 | // skip the boolean and the target 42 | i = 2; 43 | } 44 | 45 | // Handle case when target is a string or something (possible in deep copy) 46 | if ( typeof target !== "object" && !jQuery.isFunction(target) ) { 47 | target = {}; 48 | } 49 | 50 | // extend jQuery itself if only one argument is passed 51 | if ( length === i ) { 52 | target = this; 53 | --i; 54 | } 55 | 56 | for ( ; i < length; i++ ) { 57 | // Only deal with non-null/undefined values 58 | if ( (options = arguments[ i ]) != null ) { 59 | // Extend the base object 60 | for ( name in options ) { 61 | src = target[ name ]; 62 | copy = options[ name ]; 63 | 64 | // Prevent never-ending loop 65 | if ( target === copy ) { 66 | continue; 67 | } 68 | 69 | // Recurse if we're merging plain objects or arrays 70 | if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) { 71 | if ( copyIsArray ) { 72 | copyIsArray = false; 73 | clone = src && jQuery.isArray(src) ? src : []; 74 | 75 | } else { 76 | clone = src && jQuery.isPlainObject(src) ? src : {}; 77 | } 78 | 79 | // Never move original objects, clone them 80 | target[ name ] = jQuery.extend( deep, clone, copy ); 81 | 82 | // Don't bring in undefined values 83 | } else if ( copy !== undefined ) { 84 | target[ name ] = copy; 85 | } 86 | } 87 | } 88 | } 89 | 90 | // Return the modified object 91 | return target; 92 | } 93 | }; -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SRC_DIR = src 2 | TEST_DIR = test 3 | BUILD_DIR = build 4 | 5 | PREFIX = . 6 | DIST_DIR = ${PREFIX}/dist 7 | 8 | BASE_FILES = ${SRC_DIR}/util.js\ 9 | ${SRC_DIR}/database.js\ 10 | ${SRC_DIR}/query.js 11 | 12 | MODULES = ${SRC_DIR}/intro.js\ 13 | ${BASE_FILES}\ 14 | ${SRC_DIR}/outro.js 15 | 16 | COMBINED = ${DIST_DIR}/jdal.js 17 | MINIFILE = jdal.min.js 18 | MINIFIED = ${DIST_DIR}/${MINIFILE} 19 | 20 | COMPILER_FILE = ${BUILD_DIR}/compiler.zip 21 | COMPILER_GET = wget -q http://closure-compiler.googlecode.com/files/compiler-latest.zip -O ${COMPILER_FILE} && unzip ${COMPILER_FILE} compiler.jar -d ${BUILD_DIR} 22 | COMPILER = ${BUILD_DIR}/compiler.jar 23 | COMPILE = java -jar ${COMPILER} --js ${COMBINED} --js_output_file ${MINIFIED} 24 | 25 | RHINO_FILE = ${BUILD_DIR}/rhino.zip 26 | RHINO_GET = wget -q ftp://ftp.mozilla.org/pub/mozilla.org/js/rhino1_7R3.zip -O ${RHINO_FILE} && unzip ${RHINO_FILE} rhino1_7R3/js.jar -d ${BUILD_DIR} && mv ${BUILD_DIR}/rhino1_7R3/js.jar ${BUILD_DIR}/rhino.jar && rm -rf ${BUILD_DIR}/rhino1_7R3/ 27 | RHINO = ${BUILD_DIR}/rhino.jar 28 | HINT = java -jar ${RHINO} ${BUILD_DIR}/jshint-rhino.js 29 | 30 | DEMO_DIR = demo/ 31 | PACK_DIR = jdal 32 | PACK_FILE = jdal.zip 33 | PACKAGE = rm -f ${DIST_DIR}/${PACK_FILE} && zip -rqb ${BUILD_DIR} ${DIST_DIR}/${PACK_FILE} ${PACK_DIR} 34 | 35 | REPLACE = src\/jquery.omnislide.js"><\/script>.+ 9 | 10 | 11 | 12 | 134 | 135 | 145 | 146 | 147 | Database: 148 | 149 | 150 | 151 |

Add Person To DB (CRUD)

152 | 153 | 154 |
155 | 156 |

Current People In DB (CRUD)

157 | 158 | 159 |
160 | 161 |

Delete People From DB (CRUD)

162 | 163 | 164 |


165 | 166 | 167 |


168 | 169 | 170 | 171 | 172 | -------------------------------------------------------------------------------- /src/database.js: -------------------------------------------------------------------------------- 1 | //This is the "Abstraction Layer" used to abstract off the browser 2 | /** 3 | * Supported Browsers: 4 | * - IndexedDB (prefered): 5 | * - Firefox 4+ 6 | * - IE 10+ 7 | * - Chrome 11+ 8 | * - Web SQL (fallback): 9 | * - Chrome 4+ 10 | * - Safari 4+ 11 | * - Opera 10.5+ 12 | * - iOS Safari 4+ 13 | * - Opera Mobile 11+ 14 | * - Android Browser 2.1+ 15 | * 16 | * TODO: 17 | * - Select filter uses OR logic now, needs to have AND logic as well 18 | * - Check if callbacks are defined 19 | * - Call callbacks with error code instead of throwing exceptions 20 | * - Handle Upgrade cases (specified version is greater than on disk version) 21 | * 22 | * NOTE: 23 | * - Schemas are passed as object of the form: 24 | * { 25 | * tableName: { 26 | * columns: { 27 | * //if {} not specific defaults to { false, false, false, 'TEXT' } 28 | * //type and required only matter on WebSQL databases 29 | * columnName: { unique: true/false, index: true/false, required: true/false, type: 'SQL_TYPE' }, 30 | * columnName: { unique: true/false, index: true/false, required: true/false, type: 'SQL_TYPE' }, 31 | * columnName: { unique: true/false, index: true/false, required: true/false, type: 'SQL_TYPE' }, 32 | * ... 33 | * } 34 | * }, 35 | * ... 36 | * } 37 | * - Open callback prototype: function(db) {} 38 | */ 39 | 40 | jDal.DB = { 41 | db: window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.msIndexedDB || window.openDatabase, 42 | version: 1, 43 | size: 100000, 44 | types: { 45 | IndexedDB: 'IDB', 46 | WebSQL: 'WSQL' 47 | }, 48 | open: function(dbName, schema, callback) { 49 | var db = jDal.DB.db; 50 | 51 | //No DB support 52 | if(!db) { 53 | return false; 54 | } 55 | 56 | //Otherwise this is a Indexed DB 57 | if(typeof(db) == 'object') { 58 | return new jDal.DB._handle(db.open(dbName, jDal.DB.version), jDal.DB.types.IndexedDB, schema, callback); 59 | } 60 | //If we are using WebSQL Database Type 61 | else { 62 | return new jDal.DB._handle(db(dbName, jDal.DB.version, dbName, jDal.DB.size), jDal.DB.types.WebSQL, schema, callback); 63 | } 64 | }, 65 | _handle: function(request, type, schema, openedCallback) { 66 | ////////////////////////////////////// 67 | // HANDLE INITIALIZATION 68 | ////////////////////////////////////// 69 | //privates 70 | this.db = null; 71 | this.type = type; 72 | this.schema = schema; 73 | this.defaultColumn = { 74 | unique: false, 75 | index: false, 76 | required: false, 77 | type: 'TEXT' 78 | }; 79 | 80 | //setup DB handle (parse request) 81 | if(type == jDal.DB.types.IndexedDB) { 82 | request.onerror = _onError; 83 | request.onsuccess = jDal._bind(this, function(req, e) { 84 | this.db = req.result; 85 | this.db.onerror = _onError; 86 | 87 | //Backwards compat with old IndexedDB draft (before FF10) 88 | if(this.db.version != jDal.DB.version) { 89 | var setupReq = this.db.setVersion(jDal.DB.version); 90 | setupReq.onsuccess = jDal._bind(this, function(req, e) { 91 | _createStructure.call(this, req, e); 92 | openedCallback.call(this, this); 93 | }); 94 | } else { 95 | //call user callback 96 | openedCallback.call(this, this); 97 | } 98 | 99 | }); 100 | request.onupgradeneeded = jDal._bind(this, _createStructure); 101 | } 102 | else { 103 | //get handle 104 | this.db = request; 105 | 106 | //setup schema 107 | _createStructure.call(this); 108 | } 109 | 110 | ////////////////////////////////////// 111 | // PUBLIC API 112 | ////////////////////////////////////// 113 | //table -> table to select from 114 | //filter -> key value pair to filter by (in { col: val1, ... } format) 115 | //callback ->callback to execute when finished 116 | this.select = function(table, filter, callback) { 117 | if(this.db === null) { 118 | throw new Error('DB not yet opened.'); 119 | } 120 | 121 | //if they skip a filter 122 | if(typeof(filter) == 'function') { 123 | callback = filter; 124 | filter = null; 125 | } 126 | 127 | if(this.type == jDal.DB.types.IndexedDB) { 128 | var objStore = this.db.transaction([table]).objectStore(table), 129 | results = [], 130 | selectCursor = function(req, e) { 131 | var cursor = req.result; 132 | if(cursor) { 133 | results.push(cursor.value); 134 | cursor['continue'](); 135 | } else { 136 | callback.call(this, results); 137 | } 138 | }; 139 | 140 | if(filter === null) { 141 | objStore.openCursor().onsuccess = jDal._bind(this, selectCursor); 142 | } else { 143 | for(var col in filter) { 144 | if(filter.hasOwnProperty(col)) { 145 | try { 146 | objStore.index(col).openCursor(IDBKeyRange.only(filter[col])).onsuccess = jDal._bind(this, selectCursor); 147 | } catch(e) { 148 | if(e.code == 3) { 149 | //index not found 150 | callback.call(this, 'You attempted to select on a column that is not indexed...') 151 | } 152 | } 153 | } 154 | } 155 | } 156 | } else { 157 | var sql = 'SELECT * FROM ' + table; 158 | 159 | if(filter) { 160 | sql += ' WHERE '; 161 | for(var col in filter) { 162 | if(filter.hasOwnProperty(col)) { 163 | sql += col + '=' + filter[col] + ' OR '; 164 | } 165 | } 166 | sql = sql.replace(/ OR $/, ''); 167 | } 168 | 169 | sql += ';'; 170 | 171 | this.db.transaction(function(trans) { 172 | trans.executeSql(sql, [], jDal._bind(this, callback, 1), _onError); 173 | }); 174 | } 175 | 176 | return this; 177 | }; 178 | 179 | //table -> table to insert into 180 | //data -> array of objects to store (in [{ col: value, ... }] format) 181 | //callback -> callback to execute when finished 182 | this.insert = function(table, data, callback) { 183 | if(this.db === null) { 184 | throw new Error('DB not yet opened.'); 185 | } 186 | 187 | if(this.type == jDal.DB.types.IndexedDB) { 188 | var trans = this.db.transaction([table], IDBTransaction.READ_WRITE); 189 | 190 | trans.oncomplete = jDal._bind(this, function(req, e) { 191 | callback.call(this); 192 | }); 193 | 194 | var objStore = trans.objectStore(table); 195 | for (var i in data) { 196 | if(data.hasOwnProperty(i)) { 197 | data[i]._id = jDal._generateGuid(); 198 | 199 | var reqst = objStore.add(data[i]); 200 | 201 | reqst.onerror = jDal._bind(this, function(req, e) { 202 | //guid collision, shouldnt happen but if it does, try again 203 | if(e.target.errorCode === 4) { 204 | data[i]._id = jDal._generateGuid(); 205 | objStore.add(data[i]); 206 | } 207 | e.stopPropogation(); 208 | }); 209 | } 210 | } 211 | } else { 212 | var sql = 'INSERT INTO ' + table, 213 | cols = [], 214 | vals = []; 215 | 216 | for(var i in data) { 217 | if(data.hasOwnProperty(i)) { 218 | for(var col in data[i]) { 219 | if(data[i].hasOwnProperty(col)) { 220 | cols.push(col); 221 | vals.push(data[i][col]); 222 | } 223 | } 224 | } 225 | } 226 | 227 | sql += '(' + cols.join(', ') + ') VALUES (' + vals.join(', '); 228 | 229 | this.db.transaction(function(trans) { 230 | trans.executeSql(sql, [], jDal._bind(this, callback), _onError); 231 | }); 232 | } 233 | 234 | return this; 235 | }; 236 | 237 | //table -> table to delete from 238 | //filter -> key value pair to filter by (in { col: val1, ... } format) 239 | //callback -> callback to execute when finished 240 | this.drop = function(table, filter, callback) { 241 | if(this.db === null) { 242 | throw new Error('DB not yet opened.'); 243 | } 244 | 245 | //if they skip a filter 246 | if(typeof(filter) == 'function') { 247 | callback = filter; 248 | filter = null; 249 | } 250 | 251 | if(this.type == jDal.DB.types.IndexedDB) { 252 | var trans = this.db.transaction([table], IDBTransaction.READ_WRITE); 253 | 254 | trans.oncomplete = jDal._bind(this, function(e) { 255 | callback.call(this, e); 256 | }); 257 | 258 | this.select(table, filter, function(results) { 259 | if(typeof(results) == 'string') { 260 | callback.call(this, results); 261 | return; 262 | } 263 | 264 | for(var i in results) { 265 | if(results.hasOwnProperty(i)) { 266 | trans.objectStore(table)['delete'](results[i]._id); 267 | } 268 | } 269 | }); 270 | } else { 271 | var sql = 'DELETE FROM ' + table, 272 | cols = [], 273 | vals = []; 274 | 275 | if(filter) { 276 | sql += ' WHERE '; 277 | for(var col in filter) { 278 | if(filter.hasOwnProperty(col)) { 279 | sql += col + '=' + filter[col] + ' OR '; 280 | } 281 | } 282 | sql = sql.replace(/ OR $/, ''); 283 | } 284 | 285 | sql += ';'; 286 | } 287 | 288 | return this; 289 | } 290 | 291 | this.isReady = function() { 292 | return (this.db !== null); 293 | } 294 | 295 | ////////////////////////////////////// 296 | // PRIVATE UTILITIES 297 | ////////////////////////////////////// 298 | //initializes structure of the database 299 | function _createStructure(req, e) { 300 | // iterate through each table in schema 301 | for(var tbl in this.schema) { 302 | if(this.schema.hasOwnProperty(tbl)) { 303 | var table = this.schema[tbl]; 304 | 305 | if(this.type == jDal.DB.types.IndexedDB) { 306 | var db = req.result; 307 | 308 | //if already created, move on 309 | if(db.objectStoreNames.contains(tbl)) 310 | continue; 311 | 312 | //store table obj, create object store 313 | var objStore = db.createObjectStore(tbl, { 314 | keyPath: '_id' 315 | }); 316 | 317 | //iterate through each column 318 | for(var col in table['columns']) { 319 | if(table['columns'].hasOwnProperty(col)) { 320 | //store column definition 321 | var column = jDal._extend(true, {}, this.defaultColumn, table['columns'][col]); 322 | 323 | if(column.index) { 324 | objStore.createIndex(col, col, { unique: column.unique }); 325 | } 326 | } 327 | } 328 | } else { 329 | var sql = 'CREATE TABLE IF NOT EXISTS ' + tbl + '(_id INTEGER NOT NULL PRIMARY KEY AUTO INCREMENT, '; 330 | 331 | //iterate through each column 332 | for(var col in table['columns']) { 333 | //store column definition 334 | var column = jDal.extend(true, {}, this.defaultColumn, table['columns'][col]); 335 | 336 | sql += col + ' ' + column.type + ' ' + (column.required ? 'NOT NULL ' : '') + 337 | (column.index ? 'INDEX ' : '') + (column.unique ? 'UNIQUE ' : '') + ');'; 338 | 339 | this.db.transaction(function(trans) { 340 | trans.executeSql(sql, [], function() { 341 | console.log('Win-Rar!'); 342 | }, _onError); 343 | }); 344 | } 345 | } 346 | } 347 | } 348 | } 349 | 350 | //global error callback 351 | function _onError(e) { 352 | if(window.console && console.error) 353 | console.error("[JDAL] Database error code: " + e.target.errorCode); 354 | } 355 | } 356 | }; --------------------------------------------------------------------------------