/* ***** html5sql.js ****** * Description: A helper javascript module for creating and working with * HTML5 Web Databases. * * License: MIT license * * Website: http://kencorbettjr.github.io/html5sql/ * * Authors: Ken Corbett Jr * * Version 0.9.7 */ var html5sql = (function () { var readTransactionAvailable = false; var doNothing = function () {}; var emptyArray = []; /** * This is the core object that gets returned by this script. It contains * certain config values which can be modified by users. * * @type {Object} */ var html5sql = { database: null, logInfo: false, logErrors: true, putSelectResultsInArray: true, defaultFailureCallback: doNothing }; // Utility Functions from Underscore.js function trim(string) { return string.replace(/^\s+/, "").replace(/\s+$/, ""); } function isArray(obj) { return Object.prototype.toString.call(obj) === '[object Array]'; } function isUndefined(obj) { return obj === void 0; } var isSelectStatementRegex = new RegExp('^select\\s', 'i'); function isSelectStmt(sqlstring) { return isSelectStatementRegex.test(sqlstring); }; // Returns true if all SQL statement objects are SELECT statements. function allStatementsAreSelectOnly(SQLObjects) { var i = 0; //Loop over SQL objects ensuring they are select statements do { //If the sql string is not a select statement return false if (!isSelectStmt(SQLObjects[i].sql)) { return false; } i++; } while (i < SQLObjects.length); //If all the statements happen to be select statements return true return true; }; /** * sqlProcessor is the core of html5sql and is responsible for executing all * sql statements. It is a private function and does all the heavy lifting. * * @param {SQLTransaction} transaction A sql transaction that the sql statements should be executed * within. * @param {[SQLObjects]} sqlObjects An array of one or more SqlObjects. * @param {[Function]} finalSuccess A final success callback to execute when all statements have * been executed. * @param {[Function]} failure A failure callback. Executed whenever any of the passed * statements encounter an error. */ function sqlProcessor(transaction, sqlObjects, finalSuccess, failure) { var sequenceNumber = 0, dataForNextTransaction = null, runTransaction = function () { transaction.executeSql(sqlObjects[sequenceNumber].sql, sqlObjects[sequenceNumber].data, successCallback, failureCallback); }, successCallback = function (transaction, results) { var i, max, rowsArray = []; if(html5sql.logInfo){ console.log("Success processing: " + sqlObjects[sequenceNumber].sql); } //Process the results of a select putting them in a much more manageable array form. if(html5sql.putSelectResultsInArray && isSelectStmt(sqlObjects[sequenceNumber].sql)){ for(i = 0, max = results.rows.length; i < max; i++){ rowsArray[i] = results.rows.item(i); } } else { rowsArray = null; } //Call the success callback provided with sql object //If an array of data is returned use that data as the //data attribute of the next transaction dataForNextTransaction = sqlObjects[sequenceNumber].success(transaction, results, rowsArray); sequenceNumber++; if (dataForNextTransaction && $.isArray(dataForNextTransaction)) { sqlObjects[sequenceNumber].data = dataForNextTransaction; dataForNextTransaction = null; } else { dataForNextTransaction = null; } if (sqlObjects.length > sequenceNumber) { runTransaction(); } else { finalSuccess(transaction, results, rowsArray); } }, failureCallback = function (transaction, error) { if(html5sql.logErrors){ console.error("Error: " + error.message + " while processing statment " + (sequenceNumber + 1)+": " + sqlObjects[sequenceNumber].sql); } failure(error, sqlObjects[sequenceNumber].sql); }; runTransaction(); }; var SQLObject = function(options){ if (typeof options === "string") { options = { sql: options } } this.sql = options.sql; this.data = options.data || emptyArray; this.success = options.success || doNothing; // Check to see that the sql object is formated correctly. if (typeof this.sql !== "string" || typeof this.success !== "function" || !$.isArray(this.data)) { throw new Error("Malformed sql object: " + this); } }; function sqlObjectCreator(sqlInput) { var i; if (typeof sqlInput === "string") { sqlInput = trim(sqlInput); //Separate sql statements by their ending semicolon sqlInput = sqlInput.split(';'); for(i = 1; i < sqlInput.length; i++){ //Ensure semicolons within quotes are put back in while(sqlInput[i].split(/["]/gm).length % 2 === 0 || sqlInput[i].split(/[']/gm).length % 2 === 0 || sqlInput[i].split(/[`]/gm).length % 2 === 0){ sqlInput.splice(i,2,sqlInput[i] + ";" + sqlInput[i+1]); } //Add back the semicolon at the end of the line sqlInput[i] = trim(sqlInput[i]) + ';'; //Get rid of any empty statements if(sqlInput[i] === ';'){ sqlInput.splice(i, 1); } } } if(isArray(sqlInput) === false){ // At this point the user has most likely passed in a sql object // We put it into an array so it will fit the normal format sqlInput = [new SQLObject(sqlInput)]; } for (i = 0; i < sqlInput.length; i++) { sqlInput[i] = new SQLObject(sqlInput[i]); } return sqlInput; }; html5sql.openDatabase = function (name, displayname, size, whenOpen) { html5sql.database = openDatabase(name, "", displayname, size); readTransactionAvailable = typeof html5sql.database.readTransaction === 'function'; if (whenOpen) { whenOpen(); } } /* * Arguments: * 1. sql = ~ there are four ways to pass sql * statements to html5sql.js * a. {object SQLStatementObject} ~ a single sql statement object * or * b. [array SQLStatementObject(s)] ~ a collection of SQL statement objects * or * c. "string SQL Statement(s)" ~ a SQL string to be split into individual * statements at the ';' character * or * d. [array SQl Statement(s)] ~ a collection of SQL strings * * 2. finalSuccessCallback = (function) ~ called after all sql statements have * been processed. Optional. * * 3. failureCallback = (function) ~ called if any of the sql statements * fails. A default one is used if none * is provided. Optional. * * SQLStatementObject: * { * sql: "string", !Required! ~ Your sql as a string * data: [array], Optional ~ The array of data to be sequentially * inserted into your sql at the ? * success: (function), Optional ~ A function to be called if this * individual sql statement succeeds * failure: (function), Optional ~ A function to be called if this * individual sql statement fails * } * * Usage: * html5sql.process( * { * sql: "UPDATE users SET ...", * data: [], * success: function(){}, * failure: function(){} * }, * function(){}, // finalSuccessCallback * function(){} // failureCallback * ); * */ html5sql.process = function (sqlInput, finalSuccessCallback, failureCallback) { if (html5sql.database) { var sqlObjects = sqlObjectCreator(sqlInput); if(isUndefined(finalSuccessCallback)){ finalSuccessCallback = doNothing; } if(isUndefined(failureCallback)){ failureCallback = html5sql.defaultFailureCallback; } if (allStatementsAreSelectOnly(sqlObjects) && readTransactionAvailable) { html5sql.database.readTransaction(function (transaction) { sqlProcessor(transaction, sqlObjects, finalSuccessCallback, failureCallback); }, failureCallback); } else { html5sql.database.transaction(function (transaction) { sqlProcessor(transaction, sqlObjects, finalSuccessCallback, failureCallback); }, failureCallback); } } else { // Database hasn't been opened. if(html5sql.logErrors){ console.error("Error: Database needs to be opened before sql can be processed."); } return false; } }; /* This is the same as html5sql.process but used when you want to change the * version of your database. If the database version matches the oldVersion * passed to the function the statements passed to the function are * processed and the version of the database is changed to the new version. * * Arguments: * 1. oldVersion = "String" ~ the old version to upgrade * 2. newVersion = "String" ~ the new version after the upgrade * 3. sql = ~ there are four ways to pass sql * statements to html5sql.js * a. {object SQLStatementObject} ~ a single sql statement object * or * b. [array SQLStatementObject(s)] ~ a collection of SQL statement objects * or * c. "string SQL Statement(s)" ~ a SQL string to be split into individual * statements at the ';' character * or * d. [array SQl Statement(s)] ~ a collection of SQL strings * * 4. finalSuccessCallback = (function) ~ called after all sql statements have * been processed. Optional. * * 5. failureCallback = (function) ~ called if any of the sql statements * fails. A default one is used if none * is provided. Optional. * * SQLStatementObject: * { * sql: "string", !Required! ~ Your sql as a string * data: [array], Optional ~ The array of data to be sequentially * inserted into your sql at the ? * success: (function), Optional ~ A function to be called if this * individual sql statement succeeds * failure: (function), Optional ~ A function to be called if this * individual sql statement fails * } * * Usage: * html5sql.changeVersion( * "1.0", // oldVersion * "2.0", // newVersion * [{ // sql * sql: "ALTER TABLE users ...", * data: [], * success: function(){}, * failure: function(){} * }, * { * sql: "UPDATE users ...", * data: [], * success: function(){}, * failure: function(){} * }], * function(){}, // finalSuccessCallback * function(){} // failureCallback * ); * */ html5sql.changeVersion = function (oldVersion, newVersion, sqlInput, finalSuccessCallback, failureCallback) { if (html5sql.database) { if(html5sql.database.version === oldVersion){ var sqlObjects = sqlObjectCreator(sqlInput); if(isUndefined(finalSuccessCallback)){ finalSuccessCallback = doNothing; } if(isUndefined(failureCallback)){ failureCallback = html5sql.defaultFailureCallback; } html5sql.database.changeVersion(oldVersion, newVersion, function (transaction) { sqlProcessor(transaction, sqlObjects, finalSuccessCallback, failureCallback); }, failureCallback); } } else { // Database hasn't been opened. if(html5sql.logErrors){ console.log("Error: Database needs to be opened before sql can be processed."); } return false; } }; return html5sql; })();