Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

initial commit

  • Loading branch information...
commit 769fa31c2fd5048c2e874f7a41264704dfd80a2f 0 parents
alunny authored
74 assets/css/master.css
@@ -0,0 +1,74 @@
+body {
+ background: blue;
+ margin: 0;
+ font: 18px Helvetica;
+ text-align: center;
+}
+
+#title_bar {
+ background: black;
+ color: white;
+ height: 25px;
+ padding: 10px 5%;
+ width: 90%;
+}
+
+.app_button {
+ background: gold;
+ border: khaki;
+ margin: 15px auto;
+ padding: 5px;
+ width: 200px;
+ -webkit-border-radius: 5px;
+}
+
+#back_button {
+ background: grey;
+}
+
+/**
+ * Since all of the views are the same, we'll set them to static widths and heights to match our device.
+ **/
+
+.view {
+ width: 320px;
+ /* on the iPhone, we have to account for the 20px status bar at the top */
+ /* 480 - 20 = 460 */
+ /* then 460 - 40 to account for our title bar */
+ height: 420px;
+
+ /* only one view is displayed at a time
+ so we'll set the views to default to be un-displayed
+ and then override this based on the element id */
+ display: none;
+}
+/* our three views */
+#welcome {
+ display: block;
+}
+#settings {
+ background: white;
+}
+#map {
+ background: black;
+ color: white;
+}
+
+.map_image {
+ margin: auto;
+ height: 250px;
+ padding-top: 35px;
+ width: 250px;
+}
+
+#settings_form {
+ display: block;
+ padding: 20px;
+}
+
+button {
+ font: 18px Helvetica;
+ height: 30px;
+ width: 100px;
+ -webkit-appearance:push-button;
+}
BIN  assets/img/staticmap.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
57 assets/js/app.js
@@ -0,0 +1,57 @@
+//
+// --- our app behavior logic ---
+//
+run(function () {
+ // immediately invoked on first run
+ var init = (function () {
+ navigator.network.isReachable("google.com", function(status) {
+ var connectivity = (status.internetConnectionStatus || status.code || status);
+ if (connectivity === NetworkStatus.NOT_REACHABLE) {
+ alert("No internet connection - we won't be able to show you any maps");
+ } else {
+ alert("We can reach Google - get ready for some awesome maps!");
+ }
+ });
+ })();
+
+ // a little inline controller
+ when('#welcome');
+ when('#settings', function() {
+ // load settings from store and make sure we persist radio buttons.
+ store.get('config', function(saved) {
+ if (saved) {
+ if (saved.map) {
+ x$('input[value=' + saved.map + ']').attr('checked',true);
+ }
+ if (saved.zoom) {
+ x$('input[name=zoom][value="' + saved.zoom + '"]').attr('checked',true);
+ }
+ }
+ });
+ });
+ when('#map', function () {
+ store.get('config', function (saved) {
+ // construct a gmap str
+ var map = saved ? saved.map || ui('map') : ui('map')
+ , zoom = saved ? saved.zoom || ui('zoom') : ui('zoom')
+ , path = "http://maps.google.com/maps/api/staticmap?center=";
+
+ navigator.geolocation.getCurrentPosition(function (position) {
+ var location = "" + position.coords.latitude + "," + position.coords.longitude;
+ path += location + "&zoom=" + zoom;
+ path += "&size=250x250&maptype=" + map + "&markers=color:red|label:P|";
+ path += location + "&sensor=false";
+
+ x$('img#static_map').attr('src', path);
+ });
+ });
+ });
+ when('#save', function () {
+ store.save({
+ key:'config',
+ map:ui('map'),
+ zoom:ui('zoom')
+ });
+ display('#welcome');
+ });
+});
41 assets/js/dsl.js
@@ -0,0 +1,41 @@
+// the app method accepts a fn to invoke on init unobtrusively
+var run = function(application) {
+ if (navigator.userAgent.indexOf('Browzr') > -1) {
+ // blackberry
+ setTimeout(application, 250)
+ } else {
+ // attach to deviceready event, which is fired when phonegap is all good to go.
+ x$(document).on('deviceready', application, false);
+ }
+}
+
+// throw our settings into a lawnchair
+, store = new Lawnchair({adaptor:'dom'})
+
+// shows id passed
+, display = function(id) {
+ x$(["#welcome", "#map", "#settings"]).each(function(e, i) {
+ var display = '#' + x$(e)[0].id === id ? 'block' : 'none';
+ x$(e).css({ 'display':display })
+ });
+}
+
+// reg a click to [id]_button, displays id (if it exists) and executes callback (if it exists)
+, when = function(id, callback) {
+ x$(id + '_button').on('touchstart', function () {
+ if (x$(id).length > 0)
+ display(id);
+ if (callback)
+ callback.call(this);
+ return false;
+ });
+}
+
+// gets the value of the setting from the ui
+, ui = function(setting) {
+ var radio = x$('#settings_form')[0][setting];
+ for (var i = 0, l = radio.length; i < l; i++) {
+ if (radio[i].checked)
+ return radio[i].value;
+ }
+};
79 assets/js/lawnchair/Lawnchair.js
@@ -0,0 +1,79 @@
+/**
+ * Lawnchair
+ * =========
+ * A lightweight JSON document store.
+ *
+ */
+var Lawnchair = function(opts) {
+ this.init(opts);
+}
+
+Lawnchair.prototype = {
+
+ init:function(opts) {
+ var adaptors = {
+ 'webkit':window.WebkitSQLiteAdaptor,
+ 'gears':window.GearsSQLiteAdaptor,
+ 'dom':window.DOMStorageAdaptor,
+ 'cookie':window.CookieAdaptor,
+ 'air':window.AIRSQLiteAdaptor,
+ 'userdata':window.UserDataAdaptor,
+ 'air-async':window.AIRSQLiteAsyncAdaptor,
+ 'blackberry':window.BlackBerryPersistentStorageAdaptor
+ };
+ this.adaptor = opts.adaptor ? new adaptors[opts.adaptor](opts) : new WebkitSQLiteAdaptor(opts);
+ // Check for native JSON functions.
+ if (!JSON || !JSON.stringify) throw "Native JSON functions unavailable - please include http://www.json.org/json2.js or run on a decent browser :P";
+ },
+
+ // Save an object to the store. If a key is present then update. Otherwise create a new record.
+ save:function(obj, callback) {this.adaptor.save(obj, callback)},
+
+ // Invokes a callback on an object with the matching key.
+ get:function(key, callback) {this.adaptor.get(key, callback)},
+
+ // Returns whether a key exists to a callback.
+ exists:function(callback) {this.adaptor.exists(callback)},
+
+ // Returns all rows to a callback.
+ all:function(callback) {this.adaptor.all(callback)},
+
+ // Removes a json object from the store.
+ remove:function(keyOrObj, callback) {this.adaptor.remove(keyOrObj, callback)},
+
+ // Removes all documents from a store and returns self.
+ nuke:function(callback) {this.adaptor.nuke(callback);return this},
+
+ /**
+ * Iterator that accepts two paramters (methods or eval strings):
+ *
+ * - conditional test for a record
+ * - callback to invoke on matches
+ *
+ */
+ find:function(condition, callback) {
+ var is = (typeof condition == 'string') ? function(r){return eval(condition)} : condition;
+ var cb = this.adaptor.terseToVerboseCallback(callback);
+
+ this.each(function(record, index) {
+ if (is(record)) cb(record, index); // thats hot
+ });
+ },
+
+
+ /**
+ * Classic iterator.
+ * - Passes the record and the index as the second parameter to the callback.
+ * - Accepts a string for eval or a method to be invoked for each document in the collection.
+ */
+ each:function(callback) {
+ var cb = this.adaptor.terseToVerboseCallback(callback);
+ this.all(function(results) {
+ var l = results.length;
+ for (var i = 0; i < l; i++) {
+ cb(results[i], i);
+ }
+ });
+ }
+// --
+};
271 assets/js/lawnchair/adaptors/AIRSqliteAdaptor.js
@@ -0,0 +1,271 @@
+// inline the AIR aliases file, edited to include only what we need
+
+/* AIRAliases.js - Revision: 2.0beta */
+
+/*
+ADOBE SYSTEMS INCORPORATED
+Copyright 2007-2008 Adobe Systems Incorporated. All Rights Reserved.
+
+NOTICE: Adobe permits you to modify and distribute this file only in accordance with
+the terms of Adobe AIR SDK license agreement. You may have received this file from a
+source other than Adobe. Nonetheless, you may modify or
+distribute this file only in accordance with such agreement.
+
+http://www.adobe.com/products/air/tools/sdk/eula/
+*/
+
+var air;
+if (window.runtime)
+{
+ if (!air) air = {};
+ // functions
+ air.trace = window.runtime.trace;
+
+ // file
+ air.File = window.runtime.flash.filesystem.File;
+ air.FileStream = window.runtime.flash.filesystem.FileStream;
+ air.FileMode = window.runtime.flash.filesystem.FileMode;
+
+ // data
+ air.EncryptedLocalStore = window.runtime.flash.data.EncryptedLocalStore;
+ air.SQLCollationType = window.runtime.flash.data.SQLCollationType;
+ air.SQLColumnNameStyle = window.runtime.flash.data.SQLColumnNameStyle;
+ air.SQLColumnSchema = window.runtime.flash.data.SQLColumnSchema;
+ air.SQLConnection = window.runtime.flash.data.SQLConnection;
+ air.SQLError = window.runtime.flash.errors.SQLError;
+ air.SQLErrorEvent = window.runtime.flash.events.SQLErrorEvent;
+ air.SQLErrorOperation = window.runtime.flash.errors.SQLErrorOperation;
+ air.SQLEvent = window.runtime.flash.events.SQLEvent;
+ air.SQLIndexSchema = window.runtime.flash.data.SQLIndexSchema;
+ air.SQLMode = window.runtime.flash.data.SQLMode;
+ air.SQLResult = window.runtime.flash.data.SQLResult;
+ air.SQLSchema = window.runtime.flash.data.SQLSchema;
+ air.SQLSchemaResult = window.runtime.flash.data.SQLSchemaResult;
+ air.SQLStatement = window.runtime.flash.data.SQLStatement;
+ air.SQLTableSchema = window.runtime.flash.data.SQLTableSchema;
+ air.SQLTransactionLockType = window.runtime.flash.data.SQLTransactionLockType;
+ air.SQLTriggerSchema = window.runtime.flash.data.SQLTriggerSchema;
+ air.SQLUpdateEvent = window.runtime.flash.events.SQLUpdateEvent;
+ air.SQLViewSchema = window.runtime.flash.data.SQLViewSchema;
+
+}
+
+
+
+
+/**
+ * AIRSQLiteAdaptor
+ * ===================
+ * AIR flavored SQLite implementation for Lawnchair.
+ *
+ * This uses synchronous connections to the DB. If this is available,
+ * I think this is the better option, but in single-threaded apps it
+ * may cause blocking. It might be reasonable to implement an alternative
+ * that uses async connections.
+ *
+ */
+var AIRSQLiteAdaptor = function(options) {
+ for (var i in LawnchairAdaptorHelpers) {
+ this[i] = LawnchairAdaptorHelpers[i];
+ }
+ this.init(options);
+};
+
+
+AIRSQLiteAdaptor.prototype = {
+ init:function(options) {
+
+ var that = this;
+ var merge = that.merge;
+ var opts = (typeof arguments[0] == 'string') ? {table:options} : options;
+
+ this.name = merge('Lawnchair', opts.name);
+ this.table = merge('field', opts.table);
+
+ this.conn = new air.SQLConnection();
+ var appstoredir = air.File.applicationStorageDirectory;
+ this.dbFile = appstoredir.resolvePath(this.name + ".sqlite.db");
+
+ try {
+ this.conn.open(this.dbFile);
+ } catch(err) {
+ air.trace('Error msg:'+err.message);
+ air.trace('Error details:'+err.details);
+ }
+
+ this._execSql('create table if not exists ' + this.table + ' (id NVARCHAR(32) UNIQUE PRIMARY KEY, value TEXT, timestamp REAL)');
+ },
+
+ /*
+
+ */
+ save:function(obj, callback) {
+ var that = this;
+
+ var insert = function(obj, callback) {
+ var id;
+
+ if (obj.key == undefined) {
+ id = that.uuid();
+ } else {
+ id = obj.key;
+ }
+
+ delete(obj.key);
+
+ var rs = that._execSql("INSERT INTO " + that.table + " (id, value, timestamp) VALUES (:id,:value,:timestamp)",
+ {
+ ':id':id,
+ ':value':that.serialize(obj),
+ ':timestamp':that.now()
+ }
+ );
+
+ if (callback != undefined) {
+ obj.key = id;
+ callback(obj);
+ }
+ };
+
+ var update = function(id, obj, callback) {
+ var rs = that._execSql("UPDATE " + that.table + " SET value=:value, timestamp=:timestamp WHERE id=:id",
+ {
+ ':id':id,
+ ':value':that.serialize(obj),
+ ':timestamp':that.now()
+ }
+ );
+
+ if (callback != undefined) {
+ obj.key = id;
+ callback(obj);
+ }
+ };
+
+
+ if (obj.key == undefined) {
+
+ insert(obj, callback);
+ } else {
+
+ this.get(obj.key, function(r) {
+ var isUpdate = (r != null);
+
+ if (isUpdate) {
+ var id = obj.key;
+ delete(obj.key);
+ update(id, obj, callback);
+ } else {
+ insert(obj, callback);
+ }
+ });
+ }
+
+ },
+
+ /*
+
+ */
+ get:function(key, callback) {
+ var rs = this._execSql("SELECT * FROM " + this.table + " WHERE id = :id",
+ {
+ ':id':key
+ }
+ );
+
+ if (rs.data && rs.data.length> 0) {
+ var o = this.deserialize(rs.data[0].value);
+ o.key = key;
+ callback(o);
+ } else {
+ callback(null);
+ }
+ },
+
+ all:function(callback) {
+
+ if (typeof callback === 'string') {
+ throw new Error("Callback was a string; strings are not supported for callback shorthand under AIR");
+ }
+
+ var cb = this.terseToVerboseCallback(callback);
+ var rs = this._execSql("SELECT * FROM " + this.table);
+ var r = [];
+ var o;
+
+
+ if (rs.data && rs.data.length > 0) {
+ var k = 0;
+ var numrows = rs.data.length;
+
+ while (k < numrows) {
+ var thisdata = rs.data[k];
+ o = this.deserialize(thisdata.value);
+ o.key = thisdata.id;
+ r.push(o);
+ k++;
+ }
+ } else {
+ r = [];
+ }
+
+ cb(r);
+
+
+ },
+
+ /*
+
+ */
+ remove:function(keyOrObj, callback) {
+
+ var key = (typeof keyOrObj == 'string') ? keyOrObj : keyOrObj.key;
+ var rs = this._execSql("DELETE FROM " + this.table + " WHERE id = :id",
+ {
+ ':id':key
+ },
+ callback
+ );
+ },
+
+ /*
+
+ */
+ nuke:function(callback) {
+ var rs = this._execSql("DELETE FROM " + this.table, {}, callback);
+ },
+
+ /*
+ this is a wrapper for the overly complex AIR SQL API method of executing
+ SQL statements
+ */
+ _execSql:function(sql, params, onSuccess, onError) {
+
+ var stmt = new air.SQLStatement();
+ stmt.sqlConnection = this.conn;
+ stmt.text = sql;
+ if (params) {
+ for (var key in params) {
+ stmt.parameters[key] = params[key];
+ }
+ }
+
+ try {
+ stmt.execute();
+
+ var rs = stmt.getResult();
+ if (onSuccess) {
+ onSuccess(rs.data);
+ }
+
+ return rs;
+ } catch(err) {
+ air.trace('Error:' + err.message);
+ air.trace('Error details:' + err.details);
+ if (onError) {
+ onError(err);
+ }
+ return false;
+ }
+ }
+};
280 assets/js/lawnchair/adaptors/AIRSqliteAsyncAdaptor.js
@@ -0,0 +1,280 @@
+// inline the AIR aliases file, edited to include only what we need
+
+/* AIRAliases.js - Revision: 2.0beta */
+
+/*
+ADOBE SYSTEMS INCORPORATED
+Copyright 2007-2008 Adobe Systems Incorporated. All Rights Reserved.
+
+NOTICE: Adobe permits you to modify and distribute this file only in accordance with
+the terms of Adobe AIR SDK license agreement. You may have received this file from a
+source other than Adobe. Nonetheless, you may modify or
+distribute this file only in accordance with such agreement.
+
+http://www.adobe.com/products/air/tools/sdk/eula/
+*/
+
+var air;
+if (window.runtime)
+{
+ if (!air) air = {};
+ // functions
+ air.trace = window.runtime.trace;
+
+ // file
+ air.File = window.runtime.flash.filesystem.File;
+ air.FileStream = window.runtime.flash.filesystem.FileStream;
+ air.FileMode = window.runtime.flash.filesystem.FileMode;
+
+ // data
+ air.EncryptedLocalStore = window.runtime.flash.data.EncryptedLocalStore;
+ air.SQLCollationType = window.runtime.flash.data.SQLCollationType;
+ air.SQLColumnNameStyle = window.runtime.flash.data.SQLColumnNameStyle;
+ air.SQLColumnSchema = window.runtime.flash.data.SQLColumnSchema;
+ air.SQLConnection = window.runtime.flash.data.SQLConnection;
+ air.SQLError = window.runtime.flash.errors.SQLError;
+ air.SQLErrorEvent = window.runtime.flash.events.SQLErrorEvent;
+ air.SQLErrorOperation = window.runtime.flash.errors.SQLErrorOperation;
+ air.SQLEvent = window.runtime.flash.events.SQLEvent;
+ air.SQLIndexSchema = window.runtime.flash.data.SQLIndexSchema;
+ air.SQLMode = window.runtime.flash.data.SQLMode;
+ air.SQLResult = window.runtime.flash.data.SQLResult;
+ air.SQLSchema = window.runtime.flash.data.SQLSchema;
+ air.SQLSchemaResult = window.runtime.flash.data.SQLSchemaResult;
+ air.SQLStatement = window.runtime.flash.data.SQLStatement;
+ air.SQLTableSchema = window.runtime.flash.data.SQLTableSchema;
+ air.SQLTransactionLockType = window.runtime.flash.data.SQLTransactionLockType;
+ air.SQLTriggerSchema = window.runtime.flash.data.SQLTriggerSchema;
+ air.SQLUpdateEvent = window.runtime.flash.events.SQLUpdateEvent;
+ air.SQLViewSchema = window.runtime.flash.data.SQLViewSchema;
+
+}
+
+
+
+
+/**
+ * AIRSQLiteAsyncAdaptor
+ * ===================
+ * AIR flavored SQLite implementation for Lawnchair.
+ *
+ * This uses asynchronous connections to the DB.
+ */
+var AIRSQLiteAsyncAdaptor = function(options) {
+ for (var i in LawnchairAdaptorHelpers) {
+ this[i] = LawnchairAdaptorHelpers[i];
+ }
+ this.init(options);
+};
+
+
+AIRSQLiteAsyncAdaptor.prototype = {
+ init:function(options) {
+
+ var that = this;
+ var merge = that.merge;
+ var opts = (typeof arguments[0] == 'string') ? {table:options} : options;
+
+ this.name = merge('Lawnchair', opts.name);
+ this.table = merge('field', opts.table);
+
+ this.conn = new air.SQLConnection();
+ var appstoredir = air.File.applicationStorageDirectory;
+ this.dbFile = appstoredir.resolvePath(this.name + ".sqlite.db");
+
+ try {
+ this.conn.openAsync(this.dbFile);
+ } catch(err) {
+ air.trace('Error msg:'+err.message);
+ air.trace('Error details:'+err.details);
+ }
+
+ this._execSql('create table if not exists ' + this.table + ' (id NVARCHAR(32) UNIQUE PRIMARY KEY, value TEXT, timestamp REAL)');
+ },
+
+ /*
+
+ */
+ save:function(obj, callback) {
+ var that = this;
+
+ var insert = function(obj, callback) {
+ var id;
+
+ if (obj.key == undefined) {
+ id = that.uuid();
+ } else {
+ id = obj.key;
+ }
+
+ delete(obj.key);
+
+ that._execSql("INSERT INTO " + that.table + " (id, value, timestamp) VALUES (:id,:value,:timestamp)",
+ {
+ ':id':id,
+ ':value':that.serialize(obj),
+ ':timestamp':that.now()
+ },
+ function(rs) {
+ if (callback != undefined) {
+ obj.key = id;
+ callback(obj);
+ }
+ }
+ );
+ };
+
+ var update = function(id, obj, callback) {
+ that._execSql("UPDATE " + that.table + " SET value=:value, timestamp=:timestamp WHERE id=:id",
+ {
+ ':id':id,
+ ':value':that.serialize(obj),
+ ':timestamp':that.now()
+ },
+ function(rs) {
+ if (callback != undefined) {
+ obj.key = id;
+ callback(obj);
+ }
+ }
+ );
+ };
+
+
+ if (obj.key == undefined) {
+
+ insert(obj, callback);
+ } else {
+
+ this.get(obj.key, function(r) {
+ var isUpdate = (r != null);
+
+ if (isUpdate) {
+ var id = obj.key;
+ delete(obj.key);
+ update(id, obj, callback);
+ } else {
+ insert(obj, callback);
+ }
+ });
+ }
+
+ },
+
+ /*
+
+ */
+ get:function(key, callback) {
+ var that = this;
+ this._execSql("SELECT * FROM " + this.table + " WHERE id = :id",
+ {
+ ':id':key
+ },
+ function(rs) {
+ if (rs.data && rs.data.length> 0) {
+ var o = that.deserialize(rs.data[0].value);
+ o.key = key;
+ callback(o);
+ } else {
+ callback(null);
+ }
+ }
+ );
+ },
+
+ all:function(callback) {
+ var that = this;
+
+ if (typeof callback === 'string') {
+ throw new Error("Callback was a string; strings are not supported for callback shorthand under AIR");
+ }
+
+ var cb = this.terseToVerboseCallback(callback);
+ this._execSql("SELECT * FROM " + this.table, null, function(rs) {
+ var r = [];
+ var o;
+ if (rs.data && rs.data.length > 0) {
+ var k = 0;
+ var numrows = rs.data.length;
+
+ while (k < numrows) {
+ var thisdata = rs.data[k];
+ o = that.deserialize(thisdata.value);
+ o.key = thisdata.id;
+ r.push(o);
+ k++;
+ }
+ } else {
+ r = [];
+ }
+
+ cb(r);
+ });
+
+ },
+
+ /*
+
+ */
+ remove:function(keyOrObj) {
+
+ var key = (typeof keyOrObj == 'string') ? keyOrObj : keyOrObj.key;
+ this._execSql("DELETE FROM " + this.table + " WHERE id = :id",
+ {
+ ':id':key
+ }
+ );
+ },
+
+ /*
+
+ */
+ nuke:function() {
+ this._execSql("DELETE FROM " + this.table);
+ },
+
+ /*
+ this is a wrapper for the overly complex AIR SQL API method of executing
+ SQL statements
+ */
+ _execSql:function(sql, params, onSuccess, onError) {
+
+ var stmt = new air.SQLStatement();
+ stmt.sqlConnection = this.conn;
+ stmt.text = sql;
+ if (params) {
+ for (var key in params) {
+ stmt.parameters[key] = params[key];
+ }
+ }
+
+
+ function resultHandler(event) {
+ var rs = stmt.getResult();
+ if (onSuccess) {
+ onSuccess(rs);
+ }
+ }
+
+ function errorHandler(event) {
+ air.trace('Error:' + event.error.message);
+ air.trace('Error details:' + event.error.details);
+ if (onError) {
+ onError(event.error);
+ }
+ }
+
+ stmt.addEventListener(air.SQLEvent.RESULT, resultHandler);
+ stmt.addEventListener(air.SQLErrorEvent.ERROR, errorHandler);
+
+ try {
+ stmt.execute();
+ } catch(err) {
+ air.trace('Error:' + err.message);
+ air.trace('Error details:' + err.details);
+ if (onError) {
+ onError(err);
+ }
+ }
+ }
+};
82 assets/js/lawnchair/adaptors/BlackBerryPersistentStorageAdaptor.js
@@ -0,0 +1,82 @@
+/**
+ * BlackBerryPersistentStorageAdaptor
+ * ===================
+ * Implementation that uses the BlackBerry Persistent Storage mechanism. This is only available in PhoneGap BlackBerry projects
+ * See http://www.github.com/phonegap/phonegap-blackberry
+ *
+ */
+var BlackBerryPersistentStorageAdaptor = function(options) {
+ for (var i in LawnchairAdaptorHelpers) {
+ this[i] = LawnchairAdaptorHelpers[i];
+ }
+ this.init(options);
+};
+
+BlackBerryPersistentStorageAdaptor.prototype = {
+ init:function() {
+ // Check for the existence of the phonegap blackberry persistent store API
+ if (!navigator.store)
+ throw('Lawnchair, "This browser does not support BlackBerry Persistent Storage; it is a PhoneGap-only implementation."');
+ },
+ get:function(key, callback) {
+ var that = this;
+ navigator.store.get(function(value) { // success cb
+ if (callback) {
+ // Check if BBPS returned a serialized JSON obj, if so eval it.
+ if (that.isObjectAsString(value)) {
+ eval('value = ' + value.substr(0,value.length-1) + ',key:\'' + key + '\'};');
+ }
+ that.terseToVerboseCallback(callback)(value);
+ }
+ }, function() {}, // empty error cb
+ key);
+ },
+ save:function(obj, callback) {
+ var id = obj.key || this.uuid();
+ delete obj.key;
+ var that = this;
+ navigator.store.put(function(){
+ if (callback) {
+ var cbObj = obj;
+ cbObj['key'] = id;
+ that.terseToVerboseCallback(callback)(cbObj);
+ }
+ }, function(){}, id, this.serialize(obj));
+ },
+ all:function(callback) {
+ var that = this;
+ navigator.store.getAll(function(json) { // success cb
+ if (callback) {
+ // BlackBerry store returns straight strings, so eval as necessary for values we deem as objects.
+ var arr = [];
+ for (var prop in json) {
+ if (that.isObjectAsString(json[prop])) {
+ eval('arr.push(' + json[prop].substr(0,json[prop].length-1) + ',key:\'' + prop + '\'});');
+ } else {
+ eval('arr.push({\'' + prop + '\':\'' + json[prop] + '\'});');
+ }
+ }
+ that.terseToVerboseCallback(callback)(arr);
+ }
+ }, function() {}); // empty error cb
+ },
+ remove:function(keyOrObj, callback) {
+ var key = (typeof keyOrObj == 'string') ? keyOrObj : keyOrObj.key;
+ var that = this;
+ navigator.store.remove(function() {
+ if (callback)
+ that.terseToVerboseCallback(callback)();
+ }, function() {}, key);
+ },
+ nuke:function(callback) {
+ var that = this;
+ navigator.store.nuke(function(){
+ if (callback)
+ that.terseToVerboseCallback(callback)();
+ },function(){});
+ },
+ // Private helper.
+ isObjectAsString:function(value) {
+ return (value != null && value[0] == '{' && value[value.length-1] == '}');
+ }
+};
93 assets/js/lawnchair/adaptors/CookieAdaptor.js
@@ -0,0 +1,93 @@
+/**
+ * CookieAdaptor
+ * ===================
+ * Cookie implementation for Lawnchair for older browsers.
+ *
+ * Based on ppk's http://www.quirksmode.org/js/cookies.html
+ *
+ */
+var CookieAdaptor = function(options) {
+ for (var i in LawnchairAdaptorHelpers) {
+ this[i] = LawnchairAdaptorHelpers[i];
+ }
+ this.init(options);
+};
+
+CookieAdaptor.prototype = {
+ init:function(){
+ this.createCookie = function(name, value, days) {
+ if (days) {
+ var date = new Date();
+ date.setTime(date.getTime()+(days*24*60*60*1000));
+ var expires = "; expires="+date.toGMTString();
+ }
+ else var expires = "";
+ document.cookie = name+"="+value+expires+"; path=/";
+ };
+ },
+ get:function(key, callback){
+ var readCookie = function(name) {
+ var nameEQ = name + "=";
+ var ca = document.cookie.split(';');
+ var len = ca.length;
+ for (var i=0; i < len; i++) {
+ var c = ca[i];
+ while (c.charAt(0)==' ') c = c.substring(1,c.length);
+ if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);
+ }
+ return null;
+ };
+ var obj = this.deserialize(readCookie(key)) || null;
+ if (obj) {
+ obj.key = key;
+ }
+ if (callback)
+ this.terseToVerboseCallback(callback)(obj);
+ },
+ save:function(obj, callback){
+ var id = obj.key || this.uuid();
+ delete obj.key;
+ this.createCookie(id, this.serialize(obj), 365);
+ obj.key = id;
+ if (callback)
+ this.terseToVerboseCallback(callback)(obj);
+ },
+ all:function(callback){
+ var cb = this.terseToVerboseCallback(callback);
+ var ca = document.cookie.split(';');
+ var yar = [];
+ var c,k,v,o;
+ // yo ho yo ho a pirates life for me
+ for (var i = 0, l = ca.length; i < l; i++) {
+ c = ca[i].split('=');
+ k = c[0];
+ v = c[1];
+ o = this.deserialize(v);
+ if (o) {
+ o.key = k;
+ yar.push(o);
+ }
+ }
+ if (cb)
+ cb(yar);
+ },
+ remove:function(keyOrObj, callback) {
+ var key = (typeof keyOrObj == 'string') ? keyOrObj : keyOrObj.key;
+ this.createCookie(key, '', -1);
+ if (callback)
+ this.terseToVerboseCallback(callback)();
+ },
+ nuke:function(callback) {
+ var that = this;
+ this.all(function(r){
+ for (var i = 0, l = r.length; i < l; i++) {
+ if (r[i].key)
+ that.remove(r[i].key);
+ }
+ if (callback) {
+ callback = that.terseToVerboseCallback(callback);
+ callback(r);
+ }
+ });
+ }
+};
105 assets/js/lawnchair/adaptors/DOMStorageAdaptor.js
@@ -0,0 +1,105 @@
+/**
+ * DOMStorageAdaptor
+ * ===================
+ * DOM Storage implementation for Lawnchair.
+ *
+ * - originally authored by Joseph Pecoraro
+ * - window.name code courtesy Remy Sharp: http://24ways.org/2009/breaking-out-the-edges-of-the-browser
+ *
+ */
+var DOMStorageAdaptor = function(options) {
+ for (var i in LawnchairAdaptorHelpers) {
+ this[i] = LawnchairAdaptorHelpers[i];
+ }
+ this.init(options);
+};
+
+
+DOMStorageAdaptor.prototype = {
+ init:function(options) {
+ var self = this;
+ this.storage = this.merge(window.localStorage, options.storage);
+ this.table = this.merge('field', options.table);
+
+ if (!window.Storage) {
+ this.storage = (function () {
+ // window.top.name ensures top level, and supports around 2Mb
+ var data = window.top.name ? self.deserialize(window.top.name) : {};
+ return {
+ setItem: function (key, value) {
+ data[key] = value+""; // force to string
+ window.top.name = self.serialize(data);
+ },
+ removeItem: function (key) {
+ delete data[key];
+ window.top.name = self.serialize(data);
+ },
+ getItem: function (key) {
+ return data[key] || null;
+ },
+ clear: function () {
+ data = {};
+ window.top.name = '';
+ }
+ };
+ })();
+ };
+ },
+
+ save:function(obj, callback) {
+ var id = this.table + '::' + (obj.key || this.uuid());
+ delete obj.key;
+ this.storage.setItem(id, this.serialize(obj));
+ if (callback) {
+ obj.key = id.split('::')[1];
+ callback(obj);
+ }
+ },
+
+ get:function(key, callback) {
+ var obj = this.deserialize(this.storage.getItem(this.table + '::' + key));
+ var cb = this.terseToVerboseCallback(callback);
+
+ if (obj) {
+ obj.key = key;
+ if (callback) cb(obj);
+ } else {
+ if (callback) cb(null);
+ }
+ },
+
+ all:function(callback) {
+ var cb = this.terseToVerboseCallback(callback);
+ var results = [];
+ for (var i = 0, l = this.storage.length; i < l; ++i) {
+ var id = this.storage.key(i);
+ var tbl = id.split('::')[0]
+ var key = id.split('::').slice(1).join("::");
+ if (tbl == this.table) {
+ var obj = this.deserialize(this.storage.getItem(id));
+ obj.key = key;
+ results.push(obj);
+ }
+ }
+ if (cb)
+ cb(results);
+ },
+
+ remove:function(keyOrObj, callback) {
+ var key = this.table + '::' + (typeof keyOrObj === 'string' ? keyOrObj : keyOrObj.key);
+ this.storage.removeItem(key);
+ if(callback)
+ callback();
+ },
+
+ nuke:function(callback) {
+ var self = this;
+ this.all(function(r) {
+ for (var i = 0, l = r.length; i < l; i++) {
+ self.remove(r[i]);
+ }
+ if(callback)
+ callback();
+ });
+ }
+};
210 assets/js/lawnchair/adaptors/GearsSQLiteAdaptor.js
@@ -0,0 +1,210 @@
+// init.js directly included to save on include traffic
+//
+// Copyright 2007, Google Inc.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+// 3. Neither the name of Google Inc. nor the names of its contributors may be
+// used to endorse or promote products derived from this software without
+// specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+// Sets up google.gears.*, which is *the only* supported way to access Gears.
+//
+// Circumvent this file at your own risk!
+//
+// In the future, Gears may automatically define google.gears.* without this
+// file. Gears may use these objects to transparently fix bugs and compatibility
+// issues. Applications that use the code below will continue to work seamlessly
+// when that happens.
+
+(function() {
+ // We are already defined. Hooray!
+ if (window.google && google.gears) {
+ return;
+ }
+
+ var factory = null;
+
+ // Firefox
+ if (typeof GearsFactory != 'undefined') {
+ factory = new GearsFactory();
+ } else {
+ // IE
+ try {
+ factory = new ActiveXObject('Gears.Factory');
+ // privateSetGlobalObject is only required and supported on IE Mobile on
+ // WinCE.
+ if (factory.getBuildInfo().indexOf('ie_mobile') != -1) {
+ factory.privateSetGlobalObject(this);
+ }
+ } catch (e) {
+ // Safari
+ if ((typeof navigator.mimeTypes != 'undefined')
+ && navigator.mimeTypes["application/x-googlegears"]) {
+ factory = document.createElement("object");
+ factory.style.display = "none";
+ factory.width = 0;
+ factory.height = 0;
+ factory.type = "application/x-googlegears";
+ document.documentElement.appendChild(factory);
+ }
+ }
+ }
+
+ // *Do not* define any objects if Gears is not installed. This mimics the
+ // behavior of Gears defining the objects in the future.
+ if (!factory) {
+ return;
+ }
+
+ // Now set up the objects, being careful not to overwrite anything.
+ //
+ // Note: In Internet Explorer for Windows Mobile, you can't add properties to
+ // the window object. However, global objects are automatically added as
+ // properties of the window object in all browsers.
+ if (!window.google) {
+ google = {};
+ }
+
+ if (!google.gears) {
+ google.gears = {factory: factory};
+ }
+})();
+
+/**
+ * GearsSQLiteAdaptor
+ * ===================
+ * Gears flavored SQLite implementation for Lawnchair.
+ *
+ */
+var GearsSQLiteAdaptor = function(options) {
+ for (var i in LawnchairAdaptorHelpers) {
+ this[i] = LawnchairAdaptorHelpers[i];
+ }
+ this.init(options);
+};
+
+
+GearsSQLiteAdaptor.prototype = {
+ init:function(options) {
+
+ var that = this;
+ var merge = that.merge;
+ var opts = (typeof arguments[0] == 'string') ? {table:options} : options;
+
+ this.name = merge('Lawnchair', opts.name);
+ this.table = merge('field', opts.table);
+
+ this.db = google.gears.factory.create('beta.database');
+ this.db.open(this.name);
+ this.db.execute('create table if not exists ' + this.table + ' (id NVARCHAR(32) UNIQUE PRIMARY KEY, value TEXT, timestamp REAL)');
+ },
+ save:function(obj, callback) {
+ var that = this;
+
+ var insert = function(obj, callback) {
+ var id = (obj.key == undefined) ? that.uuid() : obj.key;
+ delete(obj.key);
+
+ var rs = that.db.execute(
+ "INSERT INTO " + that.table + " (id, value, timestamp) VALUES (?,?,?)",
+ [id, that.serialize(obj), that.now()]
+ );
+ if (callback != undefined) {
+ obj.key = id;
+ callback(obj);
+ }
+ };
+
+ var update = function(id, obj, callback) {
+ that.db.execute(
+ "UPDATE " + that.table + " SET value=?, timestamp=? WHERE id=?",
+ [that.serialize(obj), that.now(), id]
+ );
+ if (callback != undefined) {
+ obj.key = id;
+ callback(obj);
+ }
+ };
+
+ if (obj.key == undefined) {
+ insert(obj, callback);
+ } else {
+ this.get(obj.key, function(r) {
+ var isUpdate = (r != null);
+
+ if (isUpdate) {
+ var id = obj.key;
+ delete(obj.key);
+ update(id, obj, callback);
+ } else {
+ insert(obj, callback);
+ }
+ });
+ }
+
+ },
+ get:function(key, callback) {
+ var rs = this.db.execute("SELECT * FROM " + this.table + " WHERE id = ?", [key]);
+
+ if (rs.isValidRow()) {
+ // FIXME need to test null return / empty recordset
+ var o = this.deserialize(rs.field(1));
+ o.key = key;
+ callback(o);
+ } else {
+ callback(null);
+ }
+ rs.close();
+ },
+ all:function(callback) {
+ var cb = this.terseToVerboseCallback(callback);
+ var rs = this.db.execute("SELECT * FROM " + this.table);
+ var r = [];
+ var o;
+
+ // FIXME need to add 0 len support
+ //if (results.rows.length == 0 ) {
+ // cb([]);
+
+ while (rs.isValidRow()) {
+ o = this.deserialize(rs.field(1));
+ o.key = rs.field(0);
+ r.push(o);
+ rs.next();
+ }
+ rs.close();
+ cb(r);
+ },
+ remove:function(keyOrObj, callback) {
+ this.db.execute(
+ "DELETE FROM " + this.table + " WHERE id = ?",
+ [(typeof keyOrObj == 'string') ? keyOrObj : keyOrObj.key]
+ );
+ if(callback)
+ callback();
+ },
+ nuke:function(callback) {
+ this.db.execute("DELETE FROM " + this.table);
+ if(callback)
+ callback();
+ return this;
+ }
+};
66 assets/js/lawnchair/adaptors/LawnchairAdaptorHelpers.js
@@ -0,0 +1,66 @@
+/**
+ * LawnchairAdaptorHelpers
+ * =======================
+ * Useful helpers for creating Lawnchair stores. Used as a mixin.
+ *
+ */
+var LawnchairAdaptorHelpers = {
+ // merging default properties with user defined args
+ merge: function(defaultOption, userOption) {
+ return (userOption == undefined || userOption == null) ? defaultOption: userOption;
+ },
+
+ // awesome shorthand callbacks as strings. this is shameless theft from dojo.
+ terseToVerboseCallback: function(callback) {
+ return (typeof arguments[0] == 'string') ?
+ function(r, i) {
+ eval(callback);
+ }: callback;
+ },
+
+ // Returns current datetime for timestamps.
+ now: function() {
+ return new Date().getTime();
+ },
+
+ // Returns a unique identifier
+ uuid: function(len, radix) {
+ // based on Robert Kieffer's randomUUID.js at http://www.broofa.com
+ var chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split('');
+ var uuid = [];
+ radix = radix || chars.length;
+
+ if (len) {
+ for (var i = 0; i < len; i++) uuid[i] = chars[0 | Math.random() * radix];
+ } else {
+ // rfc4122, version 4 form
+ var r;
+
+ // rfc4122 requires these characters
+ uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-';
+ uuid[14] = '4';
+
+ // Fill in random data. At i==19 set the high bits of clock sequence as
+ // per rfc4122, sec. 4.1.5
+ for (var i = 0; i < 36; i++) {
+ if (!uuid[i]) {
+ r = 0 | Math.random() * 16;
+ uuid[i] = chars[(i == 19) ? (r & 0x3) | 0x8: r];
+ }
+ }
+ }
+ return uuid.join('');
+ },
+
+ // Serialize a JSON object as a string.
+ serialize: function(obj) {
+ var r = '';
+ r = JSON.stringify(obj);
+ return r;
+ },
+
+ // Deserialize JSON.
+ deserialize: function(json) {
+ return eval('(' + json + ')');
+ }
+};
79 assets/js/lawnchair/adaptors/UserDataStorage.js
@@ -0,0 +1,79 @@
+/**
+ * UserDataAdaptor
+ * ===================
+ * UserData implementation for Lawnchair for older IE browsers.
+ *
+ */
+var UserDataAdaptor = function(options) {
+ for (var i in LawnchairAdaptorHelpers) {
+ this[i] = LawnchairAdaptorHelpers[i];
+ }
+ this.init(options);
+};
+
+UserDataAdaptor.prototype = {
+ init:function(){
+ var s = document.createElement('span');
+ s.style.behavior = 'url(\'#default#userData\')';
+ s.style.position = 'absolute';
+ s.style.left = 10000;
+ document.body.appendChild(s);
+ this.storage = s;
+ this.storage.load('lawnchair');
+ },
+ get:function(key, callback){
+
+ var obj = this.deserialize(this.storage.getAttribute(key));
+ if (obj) {
+ obj.key = key;
+
+ }
+ if (callback)
+ callback(obj);
+ },
+ save:function(obj, callback){
+ var id = obj.key || 'lc' + this.uuid();
+ delete obj.key;
+ this.storage.setAttribute(id, this.serialize(obj));
+ this.storage.save('lawnchair');
+ if (callback){
+ obj.key = id;
+ callback(obj);
+ }
+ },
+ all:function(callback){
+ var cb = this.terseToVerboseCallback(callback);
+ var ca = this.storage.XMLDocument.firstChild.attributes;
+ var yar = [];
+ var v,o;
+ // yo ho yo ho a pirates life for me
+ for (var i = 0, l = ca.length; i < l; i++) {
+ v = ca[i];
+ o = this.deserialize(v.nodeValue);
+ if (o) {
+ o.key = v.nodeName;
+ yar.push(o);
+ }
+ }
+ if (cb)
+ cb(yar);
+ },
+ remove:function(keyOrObj,callback) {
+ var key = (typeof keyOrObj == 'string') ? keyOrObj : keyOrObj.key;
+ this.storage.removeAttribute(key);
+ this.storage.save('lawnchair');
+ if(callback)
+ callback();
+ },
+ nuke:function(callback) {
+ var that = this;
+ this.all(function(r){
+ for (var i = 0, l = r.length; i < l; i++) {
+ if (r[i].key)
+ that.remove(r[i].key);
+ }
+ if(callback)
+ callback();
+ });
+ }
+};
170 assets/js/lawnchair/adaptors/WebkitSQLiteAdaptor.js
@@ -0,0 +1,170 @@
+/**
+ * WebkitSQLiteAdaptor
+ * ===================
+ * Sqlite implementation for Lawnchair.
+ *
+ */
+var WebkitSQLiteAdaptor = function(options) {
+ for (var i in LawnchairAdaptorHelpers) {
+ this[i] = LawnchairAdaptorHelpers[i];
+ }
+ this.init(options);
+};
+
+
+WebkitSQLiteAdaptor.prototype = {
+ init:function(options) {
+ var that = this;
+ var merge = that.merge;
+ var opts = (typeof arguments[0] == 'string') ? {table:options} : options;
+
+ // default properties
+ this.name = merge('Lawnchair', opts.name );
+ this.version = merge('1.0', opts.version );
+ this.table = merge('field', opts.table );
+ this.display = merge('shed', opts.display );
+ this.max = merge(65536, opts.max );
+ this.db = merge(null, opts.db );
+
+ // default sqlite callbacks
+ this.onError = function(){};
+ this.onData = function(){};
+
+ if("onError" in opts) {
+ this.onError = opts.onError;
+ }
+
+ // error out on shit browsers
+ if (!window.openDatabase)
+ throw('Lawnchair, "This browser does not support sqlite storage."');
+
+ // instantiate the store
+ this.db = openDatabase(this.name, this.version, this.display, this.max);
+
+ // create a default database and table if one does not exist
+ this.db.transaction(function(tx) {
+ tx.executeSql("SELECT COUNT(*) FROM " + that.table, [], function(){}, function(tx, error) {
+ that.db.transaction(function(tx) {
+ tx.executeSql("CREATE TABLE "+ that.table + " (id NVARCHAR(32) UNIQUE PRIMARY KEY, value TEXT, timestamp REAL)", [], function(){}, that.onError);
+ });
+ });
+ });
+ },
+ save:function(obj, callback) {
+ var that = this;
+
+ var update = function(id, obj, callback) {
+ that.db.transaction(function(t) {
+ t.executeSql(
+ "UPDATE " + that.table + " SET value=?, timestamp=? WHERE id=?",
+ [that.serialize(obj), that.now(), id],
+ function() {
+ if (callback != undefined) {
+ obj.key = id;
+ that.terseToVerboseCallback(callback)(obj);
+ }
+ },
+ that.onError
+ );
+ });
+ };
+ var insert = function(obj, callback) {
+ that.db.transaction(function(t) {
+ var id = (obj.key == undefined) ? that.uuid() : obj.key;
+ delete(obj.key);
+ t.executeSql(
+ "INSERT INTO " + that.table + " (id, value,timestamp) VALUES (?,?,?)",
+ [id, that.serialize(obj), that.now()],
+ function() {
+ if (callback != undefined) {
+ obj.key = id;
+ that.terseToVerboseCallback(callback)(obj);
+ }
+ },
+ that.onError
+ );
+ });
+ };
+ if (obj.key == undefined) {
+ insert(obj, callback);
+ } else {
+ this.get(obj.key, function(r) {
+ var isUpdate = (r != null);
+
+ if (isUpdate) {
+ var id = obj.key;
+ delete(obj.key);
+ update(id, obj, callback);
+ } else {
+ insert(obj, callback);
+ }
+ });
+ }
+ },
+ get:function(key, callback) {
+ var that = this;
+ this.db.transaction(function(t) {
+ t.executeSql(
+ "SELECT value FROM " + that.table + " WHERE id = ?",
+ [key],
+ function(tx, results) {
+ if (results.rows.length == 0) {
+ that.terseToVerboseCallback(callback)(null);
+ } else {
+ var o = that.deserialize(results.rows.item(0).value);
+ o.key = key;
+ that.terseToVerboseCallback(callback)(o);
+ }
+ },
+ this.onError
+ );
+ });
+ },
+ all:function(callback) {
+ var cb = this.terseToVerboseCallback(callback);
+ var that = this;
+ this.db.transaction(function(t) {
+ t.executeSql("SELECT * FROM " + that.table, [], function(tx, results) {
+ if (results.rows.length == 0 ) {
+ cb([]);
+ } else {
+ var r = [];
+ for (var i = 0, l = results.rows.length; i < l; i++) {
+ var raw = results.rows.item(i).value;
+ var obj = that.deserialize(raw);
+ obj.key = results.rows.item(i).id;
+ r.push(obj);
+ }
+ cb(r);
+ }
+ },
+ that.onError);
+ });
+ },
+ remove:function(keyOrObj, callback) {
+ var that = this;
+ if (callback)
+ callback = that.terseToVerboseCallback(callback);
+ this.db.transaction(function(t) {
+ t.executeSql(
+ "DELETE FROM " + that.table + " WHERE id = ?",
+ [(typeof keyOrObj == 'string') ? keyOrObj : keyOrObj.key],
+ callback || that.onData,
+ that.onError
+ );
+ });
+ },
+ nuke:function(callback) {
+ var that = this;
+ if (callback)
+ callback = that.terseToVerboseCallback(callback);
+ this.db.transaction(function(tx) {
+ tx.executeSql(
+ "DELETE FROM " + that.table,
+ [],
+ callback || that.onData,
+ that.onError
+ );
+ });
+ }
+};
1,321 assets/js/xui.js
@@ -0,0 +1,1321 @@
+/*
+ * XUI JavaScript Library v1.0.0
+ * http://xuijs.com
+ *
+ * Copyright (c) 2009 Brian LeRoux, Rob Ellis, Brock Whitten
+ * Licensed under the MIT license.
+ *
+ * Date: 2010-05-28T10:05:06-07:00
+ */
+(function() {
+
+ var undefined,
+ xui,
+ window = this,
+ string = ('string'), // prevents Goog compiler from removing primative and subsidising out allowing us to compress further
+ document = window.document, // obvious really
+ simpleExpr = /^#?([\w-]+)$/, // for situations of dire need. Symbian and the such
+ idExpr = /^#/,
+ tagExpr = /<([\w:]+)/, // so you can create elements on the fly a la x$('<img href="/foo" /><strong>yay</strong>')
+ slice = function (e) { return [].slice.call(e, 0); };
+ try { var a = slice(document.documentElement.childNodes)[0].nodeType; }
+ catch(e){ slice = function (e) { var ret=[]; for (var i=0; e[i]; i++) ret.push(e[i]); return ret; }; }
+
+ window.x$ = window.xui = xui = function(q, context) {
+ return new xui.fn.find(q, context);
+ };
+
+ // patch in forEach to help get the size down a little and avoid over the top currying on event.js and dom.js (shortcuts)
+ if (! [].forEach) {
+ Array.prototype.forEach = function(fn) {
+ var len = this.length || 0,
+ i = 0;
+ that = arguments[1]; // wait, what's that!? awwww rem. here I thought I knew ya!
+ // @rem - that that is a hat tip to your thats :)
+
+ if (typeof fn == 'function') {
+ for (; i < len; i++) {
+ fn.call(that, this[i], i, this);
+ }
+ }
+ };
+ }
+ /**
+ * Array Remove - By John Resig (MIT Licensed)
+ */
+ function removex(array, from, to) {
+ var rest = array.slice((to || from) + 1 || array.length);
+ array.length = from < 0 ? array.length + from: from;
+ return array.push.apply(array, rest);
+ }
+
+ xui.fn = xui.prototype = {
+
+ extend: function(o) {
+ for (var i in o) {
+ xui.fn[i] = o[i];
+ }
+ },
+
+ find: function(q, context) {
+ var ele = [], tempNode;
+
+ if (!q) {
+ return this;
+ } else if (context == undefined && this.length) {
+ ele = this.each(function(el) {
+ ele = ele.concat(slice(xui(q, el)));
+ }).reduce(ele);
+ } else {
+ context = context || document;
+ // fast matching for pure ID selectors and simple element based selectors
+ if (typeof q == string) {
+ if (simpleExpr.test(q)) {
+ ele = idExpr.test(q) ? [context.getElementById(q.substr(1))] : context.getElementsByTagName(q);
+ // match for full html tags to create elements on the go
+ } else if (tagExpr.test(q)) {
+ tempNode = document.createElement('i');
+ tempNode.innerHTML = q;
+ slice(tempNode.childNodes).forEach(function (el) {
+ ele.push(el);
+ });
+ } else {
+ // one selector, check if Sizzle is available and use it instead of querySelectorAll.
+ if (window.Sizzle !== undefined) {
+ ele = Sizzle(q);
+ } else {
+ ele = context.querySelectorAll(q);
+ }
+ }
+ // blanket slice
+ ele = slice(ele);
+ } else if (q instanceof Array) {
+ ele = q;
+ } else if (q.toString() == '[object NodeList]') {
+ ele = slice(q);
+ } else {
+ // an element was passed in
+ ele = [q];
+ }
+ }
+ // disabling the append style, could be a plugin (found in more/base):
+ // xui.fn.add = function (q) { this.elements = this.elements.concat(this.reduce(xui(q).elements)); return this; }
+ return this.set(ele);
+ },
+
+ /**
+ * Resets the body of elements contained in XUI
+ * Note that due to the way this.length = 0 works
+ * if you do console.dir() you can still see the
+ * old elements, but you can't access them. Confused?
+ */
+ set: function(elements) {
+ var ret = xui();
+ ret.cache = slice(this.length ? this : []);
+ ret.length = 0;
+ [].push.apply(ret, elements);
+ return ret;
+ },
+
+ /**
+ * Array Unique
+ */
+ reduce: function(elements, b) {
+ var a = [],
+ elements = elements || slice(this);
+ elements.forEach(function(el) {
+ // question the support of [].indexOf in older mobiles (RS will bring up 5800 to test)
+ if (a.indexOf(el, 0, b) < 0)
+ a.push(el);
+ });
+
+ return a;
+ },
+
+ /**
+ * Has modifies the elements array and reurns all the elements that match (has) a CSS Query
+ */
+ has: function(q) {
+ var list = xui(q);
+ return this.filter(function () {
+ var that = this;
+ var found = null;
+ list.each(function (el) {
+ found = (found || el == that);
+ });
+ return found;
+ });
+ },
+
+ /**
+ * Both an internal utility function, but also allows developers to extend xui using custom filters
+ */
+ filter: function(fn) {
+ var elements = [];
+ return this.each(function(el, i) {
+ if (fn.call(el, i)) elements.push(el);
+ }).set(elements);
+ },
+
+ /**
+ * Not modifies the elements array and reurns all the elements that DO NOT match a CSS Query
+ */
+ not: function(q) {
+ var list = slice(this);
+ return this.filter(function(i) {
+ var found;
+ xui(q).each(function(el) {
+ return found = list[i] != el;
+ });
+ return found;
+ });
+ },
+
+
+ /**
+ * Element iterator.
+ *
+ * @return {XUI} Returns the XUI object.
+ */
+ each: function(fn) {
+ // we could compress this by using [].forEach.call - but we wouldn't be able to support
+ // fn return false breaking the loop, a feature I quite like.
+ for (var i = 0, len = this.length; i < len; ++i) {
+ if (fn.call(this[i], this[i], i, this) === false)
+ break;
+ }
+ return this;
+ }
+ };
+
+ xui.fn.find.prototype = xui.fn;
+ xui.extend = xui.fn.extend;
+
+ // ---
+ /**
+ *
+ * @namespace {Dom}
+ * @example
+ *
+ * Dom
+ * ---
+ *
+ * Manipulating the Document Object Model aka the DOM.
+ *
+ */
+ xui.extend({
+
+ /**
+ * For manipulating HTML markup in the DOM.
+ *
+ * syntax:
+ *
+ * x$(window).html( location, html );
+ *
+ * or this method will accept just an html fragment with a default behavior of inner..
+ *
+ * x$(window).html( htmlFragment );
+ *
+ * arguments:
+ *
+ * - location:string can be one of inner, outer, top, bottom
+ * - html:string any string of html markup or HTMLElement
+ *
+ * example:
+ *
+ * x$('#foo').html( 'inner', '<strong>rock and roll</strong>' );
+ * x$('#foo').html( 'outer', '<p>lock and load</p>' );
+ * x$('#foo').html( 'top', '<div>bangers and mash</div>');
+ * x$('#foo').html( 'bottom', '<em>mean and clean</em>');
+ * x$('#foo').html( 'remove');
+ * x$('#foo').html( 'before', '<p>some warmup html</p>');
+ * x$('#foo').html( 'after', '<p>more html!</p>');
+ *
+ * or
+ *
+ * x$('#foo').html('<p>sweet as honey</p>');
+ *
+ */
+ html: function(location, html) {
+ clean(this);
+
+ if (arguments.length == 0) {
+ return this[0].innerHTML;
+ }
+ if (arguments.length == 1 && arguments[0] != 'remove') {
+ html = location;
+ location = 'inner';
+ }
+
+ return this.each(function(el) {
+ var parent,
+ list,
+ len,
+ i = 0;
+ if (location == "inner") {
+ if (typeof html == string) {
+ el.innerHTML = html;
+ list = el.getElementsByTagName('SCRIPT');
+ len = list.length;
+ for (; i < len; i++) {
+ eval(list[i].text);
+ }
+ } else {
+ el.innerHTML = '';
+ el.appendChild(html);
+ }
+ } else if (location == "outer") {
+ el.parentNode.replaceChild(wrapHelper(html, el), el);
+ } else if (location == "top") {
+ el.insertBefore(wrapHelper(html, el), el.firstChild);
+ } else if (location == "bottom") {
+ el.insertBefore(wrapHelper(html, el), null);
+ } else if (location == "remove") {
+ el.parentNode.removeChild(el);
+ } else if (location == "before") {
+ el.parentNode.insertBefore(wrapHelper(html, el.parentNode), el);
+ } else if (location == "after") {
+ el.parentNode.insertBefore(wrapHelper(html, el.parentNode), el.nextSibling);
+ }
+ });
+ },
+
+ append: function (html) {
+ return this.html(html, 'bottom');
+ },
+
+ prepend: function (html) {
+ return this.html(html, 'top');
+ },
+
+ /**
+ * Attribute getter/setter
+ *
+ */
+ attr: function(attribute, val) {
+ if (arguments.length == 2) {
+ return this.each(function(el) {
+ el.setAttribute(attribute, val);
+ });
+ } else {
+ var attrs = [];
+ this.each(function(el) {
+ var val = el.getAttribute(attribute);
+ if (val != null)
+ attrs.push(val);
+ });
+ return attrs;
+ }
+ }
+ // --
+ });
+
+ // private method for finding a dom element
+ function getTag(el) {
+ return (el.firstChild === null) ? {'UL':'LI','DL':'DT','TR':'TD'}[el.tagName] || el.tagName : el.firstChild.tagName;
+ }
+
+ function wrapHelper(html, el) {
+ return (typeof html == string) ? wrap(html, getTag(el)) : html;
+ }
+
+ // private method
+ // Wraps the HTML in a TAG, Tag is optional
+ // If the html starts with a Tag, it will wrap the context in that tag.
+ function wrap(xhtml, tag) {
+
+ var attributes = {},
+ re = /^<([A-Z][A-Z0-9]*)([^>]*)>(.*)<\/\1>/i,
+ element,
+ x,
+ a,
+ i = 0,
+ attr,
+ node,
+ attrList;
+
+ if (re.test(xhtml)) {
+ result = re.exec(xhtml);
+ tag = result[1];
+
+ // if the node has any attributes, convert to object
+ if (result[2] !== "") {
+ attrList = result[2].split(/([A-Z]*\s*=\s*['|"][A-Z0-9:;#\s]*['|"])/i);
+
+ for (; i < attrList.length; i++) {
+ attr = attrList[i].replace(/^\s*|\s*$/g, "");
+ if (attr !== "" && attr !== " ") {
+ node = attr.split('=');
+ attributes[node[0]] = node[1].replace(/(["']?)/g, '');
+ }
+ }
+ }
+ xhtml = result[3];
+ }
+
+ element = document.createElement(tag);
+
+ for (x in attributes) {
+ a = document.createAttribute(x);
+ a.nodeValue = attributes[x];
+ element.setAttributeNode(a);
+ }
+
+ element.innerHTML = xhtml;
+ return element;
+ }
+
+
+ /**
+ * Removes all erronious nodes from the DOM.
+ *
+ */
+ function clean(collection) {
+ var ns = /\S/;
+ collection.each(function(el) {
+ var d = el,
+ n = d.firstChild,
+ ni = -1,
+ nx;
+ while (n) {
+ nx = n.nextSibling;
+ if (n.nodeType == 3 && !ns.test(n.nodeValue)) {
+ d.removeChild(n);
+ } else {
+ n.nodeIndex = ++ni; // FIXME not sure what this is for, and causes IE to bomb (the setter) - @rem
+ }
+ n = nx;
+ }
+ });
+ }/**
+ *
+ * @namespace {Event}
+ * @example
+ *
+ * Event
+ * ---
+ *
+ * A good old fashioned event handling system.
+ *
+ */
+ xui.extend({
+
+
+ /**
+ *
+ * Register callbacks to DOM events.
+ *
+ * @param {Event} type The event identifier as a string.
+ * @param {Function} fn The callback function to invoke when the event is raised.
+ * @return self
+ * @example
+ *
+ * ### on
+ *
+ * Registers a callback function to a DOM event on the element collection.
+ *
+ * For more information see:
+ *
+ * - http://developer.apple.com/webapps/docs/documentation/AppleApplications/Reference/SafariWebContent/HandlingEvents/chapter_7_section_1.html#//apple_ref/doc/uid/TP40006511-SW1
+ *
+ * syntax:
+ *
+ * x$('button').on( 'click', function(e){ alert('hey that tickles!') });
+ *
+ * or...
+ *
+ * x$('a.save').click(function(e){ alert('tee hee!') });
+ *
+ * arguments:
+ *
+ * - type:string the event to subscribe to click|load|etc
+ * - fn:function a callback function to execute when the event is fired
+ *
+ * example:
+ *
+ * x$(window).load(function(e){
+ * x$('.save').touchstart( function(evt){ alert('tee hee!') }).css(background:'grey');
+ * });
+ *
+ */
+ /*on: function(type, fn) {
+ return this.each(function(el) {
+ if (window.addEventListener) {
+ el.addEventListener(type, fn, false);
+ }
+ });
+ },*/
+
+ touch: eventSupported('ontouchstart'),
+
+
+
+ on: function(type, fn) {
+ return this.each(function (el) {
+ el.addEventListener(type, _createResponder(el, type, fn), false);
+ });
+ },
+
+ un: function(type) {
+ var that = this;
+ return this.each(function (el) {
+ var id = _getEventID(el), responders = _getRespondersForEvent(id, type), i = responders.length;
+
+ while (i--) {
+ el.removeEventListener(type, responders[i], false);
+ }
+
+ delete cache[id];
+ });
+ },
+
+ fire: function (type, data) {
+ return this.each(function (el) {
+ if (el == document && !el.dispatchEvent)
+ el = document.documentElement;
+
+ var event = document.createEvent('HTMLEvents');
+ event.initEvent(type, true, true);
+ event.data = data || {};
+ event.eventName = type;
+
+ el.dispatchEvent(event);
+ });
+ }
+
+ // --
+ });
+
+ function eventSupported(event) {
+ var element = document.createElement('i');
+ return event in element || element.setAttribute && element.setAttribute(event, "return;") || false;
+ }
+
+ // lifted from Prototype's (big P) event model
+ function _getEventID(element) {
+ if (element._xuiEventID) return element._xuiEventID[0];
+ return element._xuiEventID = [++_getEventID.id];
+ }
+
+ _getEventID.id = 1;
+
+ function _getRespondersForEvent(id, eventName) {
+ var c = cache[id] = cache[id] || {};
+ return c[eventName] = c[eventName] || [];
+ }
+
+ function _createResponder(element, eventName, handler) {
+ var id = _getEventID(element), r = _getRespondersForEvent(id, eventName);
+
+ var responder = function(event) {
+ if (handler.call(element, event) === false) {
+ event.preventDefault();
+ event.stopPropagation();
+ }
+ };
+ responder.handler = handler;
+ r.push(responder);
+ return responder;
+ }/**
+ *
+ * @namespace {Fx}
+ * @example
+ *
+ * Fx
+ * ---
+ *
+ * Animations, transforms and transitions for getting the most out of hardware accelerated CSS.
+ *
+ */
+ xui.extend({
+
+ /**
+ *
+ * Tween is a method for transforming a css property to a new value.
+ *
+ * @param {Object} options [Array|Object]
+ * @param {Function} callback
+ * @return self
+ * @example
+ *
+ * ### tween
+ *
+ * syntax:
+ *
+ * x$(selector).tween(obj, callback);
+ *
+ * arguments:
+ *
+ * - properties: object an object literal of element css properties to tween or an array containing object literals of css properties to tween sequentially.
+ * - callback (optional): function to run when the animation is complete
+ *
+ * example:
+ *
+ * x$('#box').tween({ left:100px, backgroundColor:'blue' });
+ * x$('#box').tween({ left:100px, backgroundColor:'blue' }, function() { alert('done!'); });
+ * x$('#box').tween([{ left:100px, backgroundColor:'green', duration:.2 }, { right:'100px' }]);
+ *
+ */
+ // options: duration, after, easing
+ tween: function( props, callback ) {
+
+ // creates an options obj for emile
+ var emileOpts = function(o) {
+ var options = {};
+ "duration after easing".split(' ').forEach( function(p) {
+ if (props[p]) {
+ options[p] = props[p];
+ delete props[p];
+ }
+ });
+ return options;
+ }
+
+ // serialize the properties into a string for emile
+ var serialize = function(props) {
+ var serialisedProps = [], key;
+ if (typeof props != string) {
+ for (key in props) {
+ serialisedProps.push(key + ':' + props[key]);
+ }
+ serialisedProps = serialisedProps.join(';');
+ } else {
+ serialisedProps = props;
+ }
+ return serialisedProps;
+ };
+
+
+ // queued animations
+ if (props instanceof Array) {
+ // animate each passing the next to the last callback to enqueue
+ props.forEach(function(a){
+
+ });
+ }
+
+
+
+
+
+ // this branch means we're dealing with a single tween
+ var opts = emileOpts(props);
+ var prop = serialize(props);
+
+ if (typeof callback == 'function') options.after = callback;
+
+ return this.each(function(e){
+ emile(e, prop, opts, callback);
+ });
+ }
+ //---
+ });/**
+ *
+ * @namespace {Style}
+ * @example
+ *
+ * Style
+ * ---
+ *
+ * Anything related to how things look. Usually, this is CSS.
+ *
+ */
+ function hasClass(el, className) {
+ return getClassRegEx(className).test(el.className);
+ }
+
+ // Via jQuery - used to avoid el.className = ' foo';
+ // Used for trimming whitespace
+ var rtrim = /^(\s|\u00A0)+|(\s|\u00A0)+$/g;
+
+ function trim(text) {
+ return (text || "").replace( rtrim, "" );
+ }
+
+ xui.extend({
+
+ /**
+ *
+ * Sets a single CSS property to a new value.
+ *
+ * @param {String} prop The property to set.
+ * @param {String} val The value to set the property.
+ * @return self
+ * @example
+ *
+ * ### setStyle
+ *
+ * syntax:
+ *
+ * x$(selector).setStyle(property, value);
+ *
+ * arguments:
+ *
+ * - property:string the property to modify
+ * - value:string the property value to set
+ *
+ * example:
+ *
+ * x$('.txt').setStyle('color', '#000');
+ *
+ */
+ setStyle: function(prop, val) {
+ return this.each(function(el) {
+ el.style[prop] = val;
+ });
+ },
+
+ /**
+ *
+ * Retuns a single CSS property. Can also invoke a callback to perform more specific processing tasks related to the property value.
+ *
+ * @param {String} prop The property to retrieve.
+ * @param {Function} callback A callback function to invoke with the property value.
+ * @return self if a callback is passed, otherwise the individual property requested
+ * @example
+ *
+ * ### getStyle
+ *
+ * syntax:
+ *
+ * x$(selector).getStyle(property, callback);
+ *
+ * arguments:
+ *
+ * - property:string a css key (for example, border-color NOT borderColor)
+ * - callback:function (optional) a method to call on each element in the collection
+ *
+ * example:
+ *
+ * x$('ul#nav li.trunk').getStyle('font-size');
+ *
+ * x$('a.globalnav').getStyle( 'background', function(prop){ prop == 'blue' ? 'green' : 'blue' });
+ *
+ */
+ getStyle: function(prop, callback) {
+ return (callback === undefined) ?
+
+ getStyle(this[0], prop) :
+
+ this.each(function(el) {
+ callback(getStyle(el, prop));
+ });
+ },
+
+ /**
+ *
+ * Adds the classname to all the elements in the collection.
+ *
+ * @param {String} className The class name.
+ * @return self
+ * @example
+ *
+ * ### addClass
+ *
+ * syntax:
+ *
+ * $(selector).addClass(className);
+ *
+ * arguments:
+ *
+ * - className:string the name of the CSS class to apply
+ *
+ * example:
+ *
+ * $('.foo').addClass('awesome');