LocalStorageDB

LocalStorageDB Script

Dieses Skript sollte nicht direkt installiert werden. Es handelt sich hier um eine Bibliothek für andere Skripte, welche über folgenden Befehl in den Metadaten eines Skriptes eingebunden wird // @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));