LocalStorageDB Script
This script should not be not be installed directly. It is a library for other scripts to include with the meta directive // @require https://update.greasyfork.org/scripts/14976/94706/LocalStorageDB.js
/* Kailash Nadh (http://nadh.in) localStorageDB v 2.3.1 A simple database layer for localStorage v 2.3.1 Mar 2015 v 2.3 Feb 2014 Contribution: Christian Kellner (http://orange-coding.net) v 2.2 Jan 2014 Contribution: Andy Hawkins (http://a904guy.com) v 2.1 Nov 2013 v 2.0 June 2013 v 1.9 Nov 2012 License : MIT License */ !(function (_global, undefined) { function localStorageDB(db_name, engine) { var db_prefix = 'db_', db_id = db_prefix + db_name, db_new = false, // this flag determines whether a new database was created during an object initialisation db = null; try { var storage = (engine == sessionStorage ? sessionStorage: localStorage); } catch(e) { // ie8 hack var storage = engine; } // if the database doesn't exist, create it db = storage[ db_id ]; if( !( db && (db = JSON.parse(db)) && db.tables && db.data ) ) { if(!validateName(db_name)) { error("The name '" + db_name + "' contains invalid characters"); } else { db = {tables: {}, data: {}}; commit(); db_new = true; } } // ______________________ private methods // _________ database functions // drop the database function drop() { if(storage.hasOwnProperty(db_id)) { delete storage[db_id]; } db = null; } // number of tables in the database function tableCount() { var count = 0; for(var table in db.tables) { if( db.tables.hasOwnProperty(table) ) { count++; } } return count; } // _________ table functions // returns all fields in a table. function tableFields(table_name) { return db.tables[table_name].fields; } // check whether a table exists function tableExists(table_name) { return db.tables[table_name] ? true : false; } // check whether a table exists, and if not, throw an error function tableExistsWarn(table_name) { if(!tableExists(table_name)) { error("The table '" + table_name + "' does not exist"); } } // check whether a table column exists function columnExists(table_name, field_name) { var exists = false; var table_fields = db.tables[table_name].fields; for(var field in table_fields){ if(table_fields[field] == field_name) { exists = true; break; } } return exists; } // create a table function createTable(table_name, fields) { db.tables[table_name] = {fields: fields, auto_increment: 1}; db.data[table_name] = {}; } // drop a table function dropTable(table_name) { delete db.tables[table_name]; delete db.data[table_name]; } // empty a table function truncate(table_name) { db.tables[table_name].auto_increment = 1; db.data[table_name] = {}; } //alter a table function alterTable(table_name, new_fields, default_values){ db.tables[table_name].fields = db.tables[table_name].fields.concat(new_fields); // insert default values in existing table if(typeof default_values != "undefined") { // loop through all the records in the table for(var ID in db.data[table_name]) { if( !db.data[table_name].hasOwnProperty(ID) ) { continue; } for(var field in new_fields) { if(typeof default_values == "object") { db.data[table_name][ID][new_fields[field]] = default_values[new_fields[field]]; } else { db.data[table_name][ID][new_fields[field]] = default_values; } } } } } // number of rows in a table function rowCount(table_name) { var count = 0; for(var ID in db.data[table_name]) { if( db.data[table_name].hasOwnProperty(ID) ) { count++; } } return count; } // insert a new row function insert(table_name, data) { data.ID = db.tables[table_name].auto_increment; db.data[table_name][ db.tables[table_name].auto_increment ] = data; db.tables[table_name].auto_increment++; return data.ID; } // select rows, given a list of IDs of rows in a table function select(table_name, ids, start, limit, sort, distinct) { var ID = null, results = [], row = null; for(var i=0; i<ids.length; i++) { ID = ids[i]; row = db.data[table_name][ID]; results.push( clone(row) ); } // there are sorting params if(sort && sort instanceof Array) { for(var i=0; i<sort.length; i++) { results.sort(sort_results(sort[i][0], sort[i].length > 1 ? sort[i][1] : null)); } } // distinct params if(distinct && distinct instanceof Array) { for(var j=0; j<distinct.length; j++) { var seen = {}, d = distinct[j]; for(var i=0; i<results.length; i++) { if(results[i] === undefined) { continue; } if(results[i].hasOwnProperty(d) && seen.hasOwnProperty(results[i][d])) { delete(results[i]); } else { seen[results[i][d]] = 1; } } } // can't use .filter(ie8) var new_results = []; for(var i=0; i<results.length; i++) { if(results[i] !== undefined) { new_results.push(results[i]); } } results = new_results; } // limit and offset start = start && typeof start === "number" ? start : null; limit = limit && typeof limit === "number" ? limit : null; if(start && limit) { results = results.slice(start, start+limit); } else if(start) { results = results.slice(start); } else if(limit) { results = results.slice(start, limit); } return results; } // sort a result set function sort_results(field, order) { return function(x, y) { // case insensitive comparison for string values var v1 = typeof(x[field]) === "string" ? x[field].toLowerCase() : x[field], v2 = typeof(y[field]) === "string" ? y[field].toLowerCase() : y[field]; if(order === "DESC") { return v1 == v2 ? 0 : (v1 < v2 ? 1 : -1); } else { return v1 == v2 ? 0 : (v1 > v2 ? 1 : -1); } }; } // select rows in a table by field-value pairs, returns the IDs of matches function queryByValues(table_name, data) { var result_ids = [], exists = false, row = null; // loop through all the records in the table, looking for matches for(var ID in db.data[table_name]) { if( !db.data[table_name].hasOwnProperty(ID) ) { continue; } row = db.data[table_name][ID]; exists = true; for(var field in data) { if( !data.hasOwnProperty(field) ) { continue; } if(typeof data[field] == 'string') { // if the field is a string, do a case insensitive comparison if( row[field] === null || row[field].toString().toLowerCase() != data[field].toString().toLowerCase() ) { exists = false; break; } } else { if(row[field] != data[field]) { exists = false; break; } } } if(exists) { result_ids.push(ID); } } return result_ids; } // select rows in a table by a function, returns the IDs of matches function queryByFunction(table_name, query_function) { var result_ids = [], exists = false, row = null; // loop through all the records in the table, looking for matches for(var ID in db.data[table_name]) { if( !db.data[table_name].hasOwnProperty(ID) ) { continue; } row = db.data[table_name][ID]; if( query_function( clone(row) ) == true ) { // it's a match if the supplied conditional function is satisfied result_ids.push(ID); } } return result_ids; } // return all the IDs in a table function getIDs(table_name) { var result_ids = []; for(var ID in db.data[table_name]) { if( db.data[table_name].hasOwnProperty(ID) ) { result_ids.push(ID); } } return result_ids; } // delete rows, given a list of their IDs in a table function deleteRows(table_name, ids) { for(var i=0; i<ids.length; i++) { if( db.data[table_name].hasOwnProperty(ids[i]) ) { delete db.data[table_name][ ids[i] ]; } } return ids.length; } // update rows function update(table_name, ids, update_function) { var ID = '', num = 0; for(var i=0; i<ids.length; i++) { ID = ids[i]; var updated_data = update_function( clone(db.data[table_name][ID]) ); if(updated_data) { delete updated_data['ID']; // no updates possible to ID var new_data = db.data[table_name][ID]; // merge updated data with existing data for(var field in updated_data) { if( updated_data.hasOwnProperty(field) ) { new_data[field] = updated_data[field]; } } db.data[table_name][ID] = validFields(table_name, new_data); num++; } } return num; } // commit the database to localStorage function commit() { try { storage.setItem(db_id, JSON.stringify(db)); return true; } catch(e) { return false; } } // serialize the database function serialize() { return JSON.stringify(db); } // throw an error function error(msg) { throw new Error(msg); } // clone an object function clone(obj) { var new_obj = {}; for(var key in obj) { if( obj.hasOwnProperty(key) ) { new_obj[key] = obj[key]; } } return new_obj; } // validate db, table, field names (alpha-numeric only) function validateName(name) { return name.toString().match(/[^a-z_0-9]/ig) ? false : true; } // given a data list, only retain valid fields in a table function validFields(table_name, data) { var field = '', new_data = {}; for(var i=0; i<db.tables[table_name].fields.length; i++) { field = db.tables[table_name].fields[i]; if (data[field] !== undefined) { new_data[field] = data[field]; } } return new_data; } // given a data list, populate with valid field names of a table function validateData(table_name, data) { var field = '', new_data = {}; for(var i=0; i<db.tables[table_name].fields.length; i++) { field = db.tables[table_name].fields[i]; new_data[field] = (data[field] === null || data[field] === undefined) ? null : data[field]; } return new_data; } // ______________________ public methods return { // commit the database to localStorage commit: function() { return commit(); }, // is this instance a newly created database? isNew: function() { return db_new; }, // delete the database drop: function() { drop(); }, // serialize the database serialize: function() { return serialize(); }, // check whether a table exists tableExists: function(table_name) { return tableExists(table_name); }, // list of keys in a table tableFields: function(table_name) { return tableFields(table_name); }, // number of tables in the database tableCount: function() { return tableCount(); }, columnExists: function(table_name, field_name){ return columnExists(table_name, field_name); }, // create a table createTable: function(table_name, fields) { var result = false; if(!validateName(table_name)) { error("The database name '" + table_name + "' contains invalid characters."); } else if(this.tableExists(table_name)) { error("The table name '" + table_name + "' already exists."); } else { // make sure field names are valid var is_valid = true; for(var i=0; i<fields.length; i++) { if(!validateName(fields[i])) { is_valid = false; break; } } if(is_valid) { // cannot use indexOf due to <IE9 incompatibility // de-duplicate the field list var fields_literal = {}; for(var i=0; i<fields.length; i++) { fields_literal[ fields[i] ] = true; } delete fields_literal['ID']; // ID is a reserved field name fields = ['ID']; for(var field in fields_literal) { if( fields_literal.hasOwnProperty(field) ) { fields.push(field); } } createTable(table_name, fields); result = true; } else { error("One or more field names in the table definition contains invalid characters"); } } return result; }, // Create a table using array of Objects @ [{k:v,k:v},{k:v,k:v},etc] createTableWithData: function(table_name, data) { if(typeof data !== 'object' || !data.length || data.length < 1) { error("Data supplied isn't in object form. Example: [{k:v,k:v},{k:v,k:v} ..]"); } var fields = Object.keys(data[0]); // create the table if( this.createTable(table_name, fields) ) { this.commit(); // populate for (var i=0; i<data.length; i++) { if( !insert(table_name, data[i]) ) { error("Failed to insert record: [" + JSON.stringify(data[i]) + "]"); } } this.commit(); } return true; }, // drop a table dropTable: function(table_name) { tableExistsWarn(table_name); dropTable(table_name); }, // empty a table truncate: function(table_name) { tableExistsWarn(table_name); truncate(table_name); }, // alter a table alterTable: function(table_name, new_fields, default_values) { var result = false; if(!validateName(table_name)) { error("The database name '" + table_name + "' contains invalid characters"); } else { if(typeof new_fields == "object") { // make sure field names are valid var is_valid = true; for(var i=0; i<new_fields.length; i++) { if(!validateName(new_fields[i])) { is_valid = false; break; } } if(is_valid) { // cannot use indexOf due to <IE9 incompatibility // de-duplicate the field list var fields_literal = {}; for(var i=0; i<new_fields.length; i++) { fields_literal[ new_fields[i] ] = true; } delete fields_literal['ID']; // ID is a reserved field name new_fields = []; for(var field in fields_literal) { if( fields_literal.hasOwnProperty(field) ) { new_fields.push(field); } } alterTable(table_name, new_fields, default_values); result = true; } else { error("One or more field names in the table definition contains invalid characters"); } } else if(typeof new_fields == "string") { if(validateName(new_fields)) { var new_fields_array = []; new_fields_array.push(new_fields); alterTable(table_name, new_fields_array, default_values); result = true; } else { error("One or more field names in the table definition contains invalid characters"); } } } return result; }, // number of rows in a table rowCount: function(table_name) { tableExistsWarn(table_name); return rowCount(table_name); }, // insert a row insert: function(table_name, data) { tableExistsWarn(table_name); return insert(table_name, validateData(table_name, data) ); }, // insert or update based on a given condition insertOrUpdate: function(table_name, query, data) { tableExistsWarn(table_name); var result_ids = []; if(!query) { result_ids = getIDs(table_name); // there is no query. applies to all records } else if(typeof query == 'object') { // the query has key-value pairs provided result_ids = queryByValues(table_name, validFields(table_name, query)); } else if(typeof query == 'function') { // the query has a conditional map function provided result_ids = queryByFunction(table_name, query); } // no existing records matched, so insert a new row if(result_ids.length == 0) { return insert(table_name, validateData(table_name, data) ); } else { var ids = []; for(var n=0; n<result_ids.length; n++) { update(table_name, result_ids, function(o) { ids.push(o.ID); return data; }); } return ids; } }, // update rows update: function(table_name, query, update_function) { tableExistsWarn(table_name); var result_ids = []; if(!query) { result_ids = getIDs(table_name); // there is no query. applies to all records } else if(typeof query == 'object') { // the query has key-value pairs provided result_ids = queryByValues(table_name, validFields(table_name, query)); } else if(typeof query == 'function') { // the query has a conditional map function provided result_ids = queryByFunction(table_name, query); } return update(table_name, result_ids, update_function); }, // select rows query: function(table_name, query, limit, start, sort, distinct) { tableExistsWarn(table_name); var result_ids = []; if(!query) { result_ids = getIDs(table_name, limit, start); // no conditions given, return all records } else if(typeof query == 'object') { // the query has key-value pairs provided result_ids = queryByValues(table_name, validFields(table_name, query), limit, start); } else if(typeof query == 'function') { // the query has a conditional map function provided result_ids = queryByFunction(table_name, query, limit, start); } return select(table_name, result_ids, start, limit, sort, distinct); }, // alias for query() that takes a dict of params instead of positional arrguments queryAll: function(table_name, params) { if(!params) { return this.query(table_name) } else { return this.query(table_name, params.hasOwnProperty('query') ? params.query : null, params.hasOwnProperty('limit') ? params.limit : null, params.hasOwnProperty('start') ? params.start : null, params.hasOwnProperty('sort') ? params.sort : null, params.hasOwnProperty('distinct') ? params.distinct : null ); } }, // delete rows deleteRows: function(table_name, query) { tableExistsWarn(table_name); var result_ids = []; if(!query) { result_ids = getIDs(table_name); } else if(typeof query == 'object') { result_ids = queryByValues(table_name, validFields(table_name, query)); } else if(typeof query == 'function') { result_ids = queryByFunction(table_name, query); } return deleteRows(table_name, result_ids); } } } // make amd compatible if(typeof define === 'function' && define.amd) { define(function() { return localStorageDB; }); } else { _global['localStorageDB'] = localStorageDB; } }(window));