From 15ccdb6c23013372d49a691fee020e69e4a4fecf Mon Sep 17 00:00:00 2001 From: Davide Bertola Date: Thu, 23 Jun 2011 07:34:43 +0200 Subject: [PATCH 01/44] Initial import From defa3a21178dbc3a2f5aa0a87c78cb879187efe2 Mon Sep 17 00:00:00 2001 From: Davide Bertola Date: Thu, 23 Jun 2011 16:03:47 +0200 Subject: [PATCH 02/44] Add readme and copyright headers --- README.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..a3769e9 --- /dev/null +++ b/README.md @@ -0,0 +1,16 @@ +Phonegap SQLitePlugin +===================== + +I have been developing Phonegap applications that required big databases. +WebKitSQLite HTML5 database has hardcoded quota limitations so I decided to write +this plugin to open my own sqlite files from javascript. + +Currently the plugin supports basic open, close, executeSQL and has no support +for transactions. + +I also started writing a js wrapper that exposes an HTML5-like interface but +it is unfinished yet (see sqlite-html5.js) + +Later I found out that I could actually use this plugin to open the same database +file WebKit uses to store HTML5 dbs and change their quota limits (see sqlite.js). +This way I was able to store up to 1Gb using HTML5 WebKitSQLite. From 79bb1160eccf34b219975bdf913c269a7f835fff Mon Sep 17 00:00:00 2001 From: Davide Bertola Date: Sun, 14 Aug 2011 09:22:11 +0200 Subject: [PATCH 03/44] Add usage section to README --- README.md | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index a3769e9..88fc692 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,8 @@ Phonegap SQLitePlugin ===================== I have been developing Phonegap applications that required big databases. -WebKitSQLite HTML5 database has hardcoded quota limitations so I decided to write -this plugin to open my own sqlite files from javascript. +WebKitSQLite HTML5 database has hardcoded quota limitations so I decided to +write this plugin to open my own sqlite files from javascript. Currently the plugin supports basic open, close, executeSQL and has no support for transactions. @@ -11,6 +11,16 @@ for transactions. I also started writing a js wrapper that exposes an HTML5-like interface but it is unfinished yet (see sqlite-html5.js) -Later I found out that I could actually use this plugin to open the same database -file WebKit uses to store HTML5 dbs and change their quota limits (see sqlite.js). -This way I was able to store up to 1Gb using HTML5 WebKitSQLite. +Later I found out that I could actually use this plugin to open the same +database file WebKit uses to store HTML5 dbs and change their quota limits +(see sqlite.js). This way I was able to store up to 1Gb using standard +WebKitSQLite. + + +Usage +===== + +Like any other PGPlugin drag it into your project, include sqlite.js file in +your application, modify PhoneGap.plist to bind PGSQLitePlugin to +it.dadeb.sqliteplugin namespace. Look at sqlite.js to get a sense of how it +works. From 4d795d8425498040bec969c4ad72eb634bf284ed Mon Sep 17 00:00:00 2001 From: Joe Noon Date: Mon, 26 Sep 2011 16:58:05 -0700 Subject: [PATCH 04/44] removing incompatible files, updating readme --- README.md | 108 +++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 91 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 88fc692..3cd3fc5 100644 --- a/README.md +++ b/README.md @@ -1,26 +1,100 @@ Phonegap SQLitePlugin ===================== -I have been developing Phonegap applications that required big databases. -WebKitSQLite HTML5 database has hardcoded quota limitations so I decided to -write this plugin to open my own sqlite files from javascript. +This is Joe Noon's fork of Phonegap-SQLitePlugin -Currently the plugin supports basic open, close, executeSQL and has no support -for transactions. +This fork lives at: https://github.com/joenoon/Phonegap-SQLitePlugin -I also started writing a js wrapper that exposes an HTML5-like interface but -it is unfinished yet (see sqlite-html5.js) +The original lives at: https://github.com/davibe/Phonegap-SQLitePlugin -Later I found out that I could actually use this plugin to open the same -database file WebKit uses to store HTML5 dbs and change their quota limits -(see sqlite.js). This way I was able to store up to 1Gb using standard -WebKitSQLite. +This fork has largely diverged from the original, and is not a drop-in +replacement. +DISCLAIMER: + + I'm brand new to objective-c, so there could be problems with my code! + Please tell me. joenoon@gmail.com -Usage -===== +Added: -Like any other PGPlugin drag it into your project, include sqlite.js file in -your application, modify PhoneGap.plist to bind PGSQLitePlugin to -it.dadeb.sqliteplugin namespace. Look at sqlite.js to get a sense of how it -works. + obj-c: + batch execution support + query parameter binding + perform after delay so js-objc call doesn't need to wait for response + callbacks moved out of instance and into options of each method call + path just takes filename, and path is put in Documents folder + added rowsAffected, insertId + success callback response is { insertId: x, rowsAffected: y, rows: z } + error callback response is { message: x } + + js (coffeescript): + new implementation + first cut transaction support + callbacks per-statement, even within transaction + somewhat similar api to the webkit/phonegap default + +Removed: + + quota limit webkit html5 db patching + exit from app + (I don't think either of these would make it through the approval process) + +Other notes: + + I played with the idea of batching responses into larger sets of + writeJavascript on a timer, however there was only a barely noticeable + performance gain. So I took it out, not worth it. However there is a + massive performance gain by batching on the client-side to minimize + PhoneGap.exec calls using the transaction support. + +Installing +========== + +Drag .h and .m files into your project's Plugins folder (in xcode) -- I always +just have "Create references" as the option selected. + +Compile the coffeescript file to javascript WITH the top-level function wrapper +option (default). + +Use the resulting javascript file in your HTML. + +Look for the following to your project's PhoneGap.plist: + +Plugins + + ... + + +Insert this in there: + +PGSQLitePlugin +PGSQLitePlugin + +General Usage +============= + +(You're using Coffeescript right? Of course you are.) + +db = new PGSQLitePlugin("test_native.sqlite3") +db.executeSql('DROP TABLE IF EXISTS test_table') +db.executeSql('CREATE TABLE IF NOT EXISTS test_table (id integer primary key, data text, data_num integer)') + +db.transaction (tx) -> + + tx.executeSql [ "INSERT INTO test_table (data, data_num) VALUES (?,?)", "test", 100], (res) -> + + # success callback + + console.log "insertId: #{res.insertId} -- probably 1" + console.log "rowsAffected: #{res.rowsAffected} -- should be 1" + + # check the count (not a part of the transaction) + db.executeSql "select count(id) as cnt from test_table;", (res) -> + console.log "rows.length: #{res.rows.length} -- should be 1" + console.log "rows[0].cnt: #{res.rows[0].cnt} -- should be 1" + + , (e) -> + + # error callback + + console.log "ERROR: #{e.message}" From 0f546fd088717f2db38e76a62f607a868f353583 Mon Sep 17 00:00:00 2001 From: Joe Noon Date: Mon, 26 Sep 2011 17:05:16 -0700 Subject: [PATCH 05/44] markdown --- README.md | 90 +++++++++++++++++++++++++++---------------------------- 1 file changed, 45 insertions(+), 45 deletions(-) diff --git a/README.md b/README.md index 3cd3fc5..e20824a 100644 --- a/README.md +++ b/README.md @@ -12,40 +12,40 @@ replacement. DISCLAIMER: - I'm brand new to objective-c, so there could be problems with my code! - Please tell me. joenoon@gmail.com +I'm brand new to objective-c, so there could be problems with my code! +Please tell me. joenoon@gmail.com Added: - obj-c: - batch execution support - query parameter binding - perform after delay so js-objc call doesn't need to wait for response - callbacks moved out of instance and into options of each method call - path just takes filename, and path is put in Documents folder - added rowsAffected, insertId - success callback response is { insertId: x, rowsAffected: y, rows: z } - error callback response is { message: x } +- obj-c: + - batch execution support + - query parameter binding + - perform after delay so js-objc call doesn't need to wait for response + - callbacks moved out of instance and into options of each method call + - path just takes filename, and path is put in Documents folder + - added rowsAffected, insertId + - success callback response is { insertId: x, rowsAffected: y, rows: z } + - error callback response is { message: x } - js (coffeescript): - new implementation - first cut transaction support - callbacks per-statement, even within transaction - somewhat similar api to the webkit/phonegap default +- js (coffeescript): + - new implementation + - first cut transaction support + - callbacks per-statement, even within transaction + - somewhat similar api to the webkit/phonegap default Removed: - quota limit webkit html5 db patching - exit from app - (I don't think either of these would make it through the approval process) +- quota limit webkit html5 db patching +- exit from app +- (I don't think either of these would make it through the approval process) Other notes: - I played with the idea of batching responses into larger sets of - writeJavascript on a timer, however there was only a barely noticeable - performance gain. So I took it out, not worth it. However there is a - massive performance gain by batching on the client-side to minimize - PhoneGap.exec calls using the transaction support. +I played with the idea of batching responses into larger sets of +writeJavascript on a timer, however there was only a barely noticeable +performance gain. So I took it out, not worth it. However there is a +massive performance gain by batching on the client-side to minimize +PhoneGap.exec calls using the transaction support. Installing ========== @@ -60,41 +60,41 @@ Use the resulting javascript file in your HTML. Look for the following to your project's PhoneGap.plist: -Plugins - - ... - + Plugins + + ... + Insert this in there: -PGSQLitePlugin -PGSQLitePlugin + PGSQLitePlugin + PGSQLitePlugin General Usage ============= (You're using Coffeescript right? Of course you are.) -db = new PGSQLitePlugin("test_native.sqlite3") -db.executeSql('DROP TABLE IF EXISTS test_table') -db.executeSql('CREATE TABLE IF NOT EXISTS test_table (id integer primary key, data text, data_num integer)') + db = new PGSQLitePlugin("test_native.sqlite3") + db.executeSql('DROP TABLE IF EXISTS test_table') + db.executeSql('CREATE TABLE IF NOT EXISTS test_table (id integer primary key, data text, data_num integer)') -db.transaction (tx) -> + db.transaction (tx) -> - tx.executeSql [ "INSERT INTO test_table (data, data_num) VALUES (?,?)", "test", 100], (res) -> + tx.executeSql [ "INSERT INTO test_table (data, data_num) VALUES (?,?)", "test", 100], (res) -> - # success callback + # success callback - console.log "insertId: #{res.insertId} -- probably 1" - console.log "rowsAffected: #{res.rowsAffected} -- should be 1" + console.log "insertId: #{res.insertId} -- probably 1" + console.log "rowsAffected: #{res.rowsAffected} -- should be 1" - # check the count (not a part of the transaction) - db.executeSql "select count(id) as cnt from test_table;", (res) -> - console.log "rows.length: #{res.rows.length} -- should be 1" - console.log "rows[0].cnt: #{res.rows[0].cnt} -- should be 1" + # check the count (not a part of the transaction) + db.executeSql "select count(id) as cnt from test_table;", (res) -> + console.log "rows.length: #{res.rows.length} -- should be 1" + console.log "rows[0].cnt: #{res.rows[0].cnt} -- should be 1" - , (e) -> + , (e) -> - # error callback + # error callback - console.log "ERROR: #{e.message}" + console.log "ERROR: #{e.message}" From 09fb70f38d2c5277c0ae8fa3d859ffe2f10b40c8 Mon Sep 17 00:00:00 2001 From: Joe Noon Date: Mon, 26 Sep 2011 17:44:37 -0700 Subject: [PATCH 06/44] adding a Makefile and caching the latest coffee output for people who wont like just a coffee src file --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e20824a..20a6993 100644 --- a/README.md +++ b/README.md @@ -53,8 +53,8 @@ Installing Drag .h and .m files into your project's Plugins folder (in xcode) -- I always just have "Create references" as the option selected. -Compile the coffeescript file to javascript WITH the top-level function wrapper -option (default). +Take the precompiled javascript file from build/, or compile the coffeescript +file in src/ to javascript WITH the top-level function wrapper option (default). Use the resulting javascript file in your HTML. From b60211ee8785dcc43c2823739bcaf3c9b59d261c Mon Sep 17 00:00:00 2001 From: Joe Noon Date: Tue, 27 Sep 2011 05:39:37 -0700 Subject: [PATCH 07/44] adding lawnchair adapter, passes lawnchair spec --- README.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/README.md b/README.md index 20a6993..2a93433 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,8 @@ Added: - callbacks per-statement, even within transaction - somewhat similar api to the webkit/phonegap default +- lawnchair adapter + Removed: - quota limit webkit html5 db patching @@ -98,3 +100,18 @@ General Usage # error callback console.log "ERROR: #{e.message}" + +Lawnchair Adapter Usage +======================= + +Include the following js files in your html: + +- lawnchair.js (you provide) +- pgsqlite_plugin.js +- lawnchair_pgsqlite_plugin_adapter.js (must come after pgsqlite_plugin.js) + +The `name` option will determine the sqlite filename. In this example, you would be using/creating +the database at: *Documents/kvstore.sqlite3* (all db's in PGSQLitePlugin are in the Documents folder) + + kvstore = new Lawnchair { name: "kvstore", adapter: PGSQLitePlugin.lawnchair_adapter }, () -> + # do stuff From 6ca8109102faf66a2c2afc176a6bef3100fb65f4 Mon Sep 17 00:00:00 2001 From: Davide Bertola Date: Wed, 28 Sep 2011 19:42:22 +0200 Subject: [PATCH 08/44] Merge contributions form @Joenoon, adapt README.md --- README.md | 78 +++++++++++++++++++++++-------------------------------- 1 file changed, 32 insertions(+), 46 deletions(-) diff --git a/README.md b/README.md index 2a93433..21f0a4e 100644 --- a/README.md +++ b/README.md @@ -1,53 +1,9 @@ Phonegap SQLitePlugin ===================== -This is Joe Noon's fork of Phonegap-SQLitePlugin - -This fork lives at: https://github.com/joenoon/Phonegap-SQLitePlugin - -The original lives at: https://github.com/davibe/Phonegap-SQLitePlugin - -This fork has largely diverged from the original, and is not a drop-in -replacement. - DISCLAIMER: -I'm brand new to objective-c, so there could be problems with my code! -Please tell me. joenoon@gmail.com - -Added: - -- obj-c: - - batch execution support - - query parameter binding - - perform after delay so js-objc call doesn't need to wait for response - - callbacks moved out of instance and into options of each method call - - path just takes filename, and path is put in Documents folder - - added rowsAffected, insertId - - success callback response is { insertId: x, rowsAffected: y, rows: z } - - error callback response is { message: x } - -- js (coffeescript): - - new implementation - - first cut transaction support - - callbacks per-statement, even within transaction - - somewhat similar api to the webkit/phonegap default - -- lawnchair adapter - -Removed: - -- quota limit webkit html5 db patching -- exit from app -- (I don't think either of these would make it through the approval process) - -Other notes: - -I played with the idea of batching responses into larger sets of -writeJavascript on a timer, however there was only a barely noticeable -performance gain. So I took it out, not worth it. However there is a -massive performance gain by batching on the client-side to minimize -PhoneGap.exec calls using the transaction support. +We are brand new to objective-c, so there could be problems with our code! Installing ========== @@ -75,7 +31,7 @@ Insert this in there: General Usage ============= -(You're using Coffeescript right? Of course you are.) +## Coffee Script db = new PGSQLitePlugin("test_native.sqlite3") db.executeSql('DROP TABLE IF EXISTS test_table') @@ -101,6 +57,26 @@ General Usage console.log "ERROR: #{e.message}" +## Plain Javascript + + var db; + db = new PGSQLitePlugin("test_native.sqlite3"); + db.executeSql('DROP TABLE IF EXISTS test_table'); + db.executeSql('CREATE TABLE IF NOT EXISTS test_table (id integer primary key, data text, data_num integer)'); + db.transaction(function(tx) { + return tx.executeSql(["INSERT INTO test_table (data, data_num) VALUES (?,?)", "test", 100], function(res) { + console.log("insertId: " + res.insertId + " -- probably 1"); + console.log("rowsAffected: " + res.rowsAffected + " -- should be 1"); + return db.executeSql("select count(id) as cnt from test_table;", function(res) { + console.log("rows.length: " + res.rows.length + " -- should be 1"); + return console.log("rows[0].cnt: " + res.rows[0].cnt + " -- should be 1"); + }); + }, function(e) { + return console.log("ERROR: " + e.message); + }); + }); + + Lawnchair Adapter Usage ======================= @@ -115,3 +91,13 @@ the database at: *Documents/kvstore.sqlite3* (all db's in PGSQLitePlugin are in kvstore = new Lawnchair { name: "kvstore", adapter: PGSQLitePlugin.lawnchair_adapter }, () -> # do stuff + + +Other notes from @Joenoon: + +I played with the idea of batching responses into larger sets of +writeJavascript on a timer, however there was only a barely noticeable +performance gain. So I took it out, not worth it. However there is a +massive performance gain by batching on the client-side to minimize +PhoneGap.exec calls using the transaction support. + From 6badbe15e44930b7e9d55fedf0806642bf5cb786 Mon Sep 17 00:00:00 2001 From: Davide Date: Wed, 28 Sep 2011 21:20:54 +0300 Subject: [PATCH 09/44] Edited README.md via GitHub --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 21f0a4e..a25287d 100644 --- a/README.md +++ b/README.md @@ -93,7 +93,7 @@ the database at: *Documents/kvstore.sqlite3* (all db's in PGSQLitePlugin are in # do stuff -Other notes from @Joenoon: +### Other notes from @Joenoon: I played with the idea of batching responses into larger sets of writeJavascript on a timer, however there was only a barely noticeable From a6798cb0420eff6ab199f7628d04a731d91ad3e7 Mon Sep 17 00:00:00 2001 From: Vojto Rinik Date: Fri, 2 Dec 2011 16:45:09 +0100 Subject: [PATCH 10/44] Adding db option to change the sqlite filename --- README.md | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index a25287d..a005384 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ Phonegap SQLitePlugin ===================== -DISCLAIMER: - +DISCLAIMER: + We are brand new to objective-c, so there could be problems with our code! Installing @@ -38,23 +38,23 @@ General Usage db.executeSql('CREATE TABLE IF NOT EXISTS test_table (id integer primary key, data text, data_num integer)') db.transaction (tx) -> - + tx.executeSql [ "INSERT INTO test_table (data, data_num) VALUES (?,?)", "test", 100], (res) -> - + # success callback - + console.log "insertId: #{res.insertId} -- probably 1" console.log "rowsAffected: #{res.rowsAffected} -- should be 1" - + # check the count (not a part of the transaction) db.executeSql "select count(id) as cnt from test_table;", (res) -> console.log "rows.length: #{res.rows.length} -- should be 1" console.log "rows[0].cnt: #{res.rows[0].cnt} -- should be 1" - + , (e) -> - + # error callback - + console.log "ERROR: #{e.message}" ## Plain Javascript @@ -86,12 +86,19 @@ Include the following js files in your html: - pgsqlite_plugin.js - lawnchair_pgsqlite_plugin_adapter.js (must come after pgsqlite_plugin.js) -The `name` option will determine the sqlite filename. In this example, you would be using/creating -the database at: *Documents/kvstore.sqlite3* (all db's in PGSQLitePlugin are in the Documents folder) + + +The `name` option will determine the sqlite filename. Optionally, you can change it using the `db` option. + +In this example, you would be using/creating the database at: *Documents/kvstore.sqlite3* (all db's in PGSQLitePlugin are in the Documents folder) kvstore = new Lawnchair { name: "kvstore", adapter: PGSQLitePlugin.lawnchair_adapter }, () -> # do stuff +Using the `db` option you can create multiple stores in one sqlite file. (There will be one table per store.) + + recipes = new Lawnchair {db: "cookbook", name: "recipes", ...} + ingredients = new Lawnchair {db: "cookbook", name: "ingredients", ...} ### Other notes from @Joenoon: From ba625d60360a152f20e7538cfd0e62416113e369 Mon Sep 17 00:00:00 2001 From: felixactv8 Date: Tue, 31 Jan 2012 18:54:54 -0800 Subject: [PATCH 11/44] PhoneGap 1.3.0 Included file changes in .h file for PhoneGap 1.3.0 implementation as well as needed libraries in order for xcode to compile. --- README.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/README.md b/README.md index a005384..7406c44 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,30 @@ We are brand new to objective-c, so there could be problems with our code! Installing ========== +/** +For installing with PhoneGap 1.3.0: +in PGSQLitePlugin.h file change for PhoneGaps JSONKit.h implementation. +[code] +#ifdef PHONEGAP_FRAMEWORK + #import + #import + #import + #import + #import +#else + #import "PGPlugin.h" + #import "JSON.h" + #import "PhoneGapDelegate.h" + #import "File.h" +#endif +[/code] + +In the Project Build Phases tab, select the "Link Binary with Libraries" dropdown menu and add 2 libraries: +1. libsqlite3.0.dylib +2. libsqlite3.dylib + +**/ Drag .h and .m files into your project's Plugins folder (in xcode) -- I always just have "Create references" as the option selected. From 1c1a9f01600a6c03b315dfd95484a09c810d94e4 Mon Sep 17 00:00:00 2001 From: felixactv8 Date: Tue, 31 Jan 2012 18:56:03 -0800 Subject: [PATCH 12/44] text changes text changes to update --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 7406c44..7f6d24a 100644 --- a/README.md +++ b/README.md @@ -27,10 +27,11 @@ in PGSQLitePlugin.h file change for PhoneGaps JSONKit.h implementation. [/code] In the Project Build Phases tab, select the "Link Binary with Libraries" dropdown menu and add 2 libraries: -1. libsqlite3.0.dylib -2. libsqlite3.dylib -**/ +libsqlite3.0.dylib +libsqlite3.dylib + + Drag .h and .m files into your project's Plugins folder (in xcode) -- I always just have "Create references" as the option selected. From 98076ebacd883052b30cbb1b2c8b13a660ee69a5 Mon Sep 17 00:00:00 2001 From: Davide Bertola Date: Wed, 7 Mar 2012 16:50:27 +0100 Subject: [PATCH 13/44] Fix readme markdown formatting --- README.md | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 7f6d24a..a59ba21 100644 --- a/README.md +++ b/README.md @@ -7,24 +7,22 @@ We are brand new to objective-c, so there could be problems with our code! Installing ========== -/** + For installing with PhoneGap 1.3.0: in PGSQLitePlugin.h file change for PhoneGaps JSONKit.h implementation. -[code] -#ifdef PHONEGAP_FRAMEWORK - #import - #import - #import - #import - #import -#else - #import "PGPlugin.h" - #import "JSON.h" - #import "PhoneGapDelegate.h" - #import "File.h" -#endif -[/code] + #ifdef PHONEGAP_FRAMEWORK + #import + #import + #import + #import + #import + #else + #import "PGPlugin.h" + #import "JSON.h" + #import "PhoneGapDelegate.h" + #import "File.h" + #endif In the Project Build Phases tab, select the "Link Binary with Libraries" dropdown menu and add 2 libraries: From 4fe8acc3804bc33f1ee35a9341be1ec849fee41c Mon Sep 17 00:00:00 2001 From: Chris Brody Date: Fri, 23 Mar 2012 08:55:19 +0100 Subject: [PATCH 14/44] In README.md: document change to Plugins/PGSQLitePlugin.m for PG1.3, only 1 sqlite library is required, add

subsections to Installing, and warning to use the _first_ "Link Binary with Libraries" dropdown menu. --- README.md | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index a59ba21..b0941b9 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,9 @@ We are brand new to objective-c, so there could be problems with our code! Installing ========== +PhoneGap 1.3.0 +-------------- + For installing with PhoneGap 1.3.0: in PGSQLitePlugin.h file change for PhoneGaps JSONKit.h implementation. @@ -24,11 +27,28 @@ in PGSQLitePlugin.h file change for PhoneGaps JSONKit.h implementation. #import "File.h" #endif -In the Project Build Phases tab, select the "Link Binary with Libraries" dropdown menu and add 2 libraries: +and in PGSQLitePlugin.m JSONRepresentation must be changed to JSONString: -libsqlite3.0.dylib -libsqlite3.dylib + --- a/Plugins/PGSQLitePlugin.m + +++ b/Plugins/PGSQLitePlugin.m + @@ -219,7 +219,7 @@ + if (hasInsertId) { + [resultSet setObject:insertId forKey:@"insertId"]; + } + - [self respond:callback withString:[resultSet JSONRepresentation] withType:@"success"]; + + [self respond:callback withString:[resultSet JSONString] withType:@"success"]; + } + } +SQLite library +-------------- + +In the Project "Build Phases" tab, select the _first_ "Link Binary with Libraries" dropdown menu and add the library `libsqlite3.dylib` or `libsqlite3.0.dylib`. + +**NOTE:** In the "Build Phases" there can be multiple "Link Binary with Libraries" dropdown menus. Please select the first one otherwise it will not work. + +PGSQLite Plugin +--------------- Drag .h and .m files into your project's Plugins folder (in xcode) -- I always just have "Create references" as the option selected. From 7428cd738495fdcaf11b8336dc15eb85fd8c4794 Mon Sep 17 00:00:00 2001 From: Iain Campion Date: Tue, 27 Mar 2012 19:31:55 +1300 Subject: [PATCH 15/44] Initial Upload of 1.5 Cordova changes. --- README.md | 59 +++++++++++++++++-------------------------------------- 1 file changed, 18 insertions(+), 41 deletions(-) diff --git a/README.md b/README.md index b0941b9..7a3c3ce 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,13 @@ -Phonegap SQLitePlugin +Cordova SQLitePlugin ===================== +DISCLAIMER: + +Created by @Joenoon: + +Adapted to 1.5 by coomsie + + DISCLAIMER: We are brand new to objective-c, so there could be problems with our code! @@ -8,38 +15,9 @@ We are brand new to objective-c, so there could be problems with our code! Installing ========== -PhoneGap 1.3.0 +Cordova (PhoneGap) 1.5.0 -------------- -For installing with PhoneGap 1.3.0: -in PGSQLitePlugin.h file change for PhoneGaps JSONKit.h implementation. - - #ifdef PHONEGAP_FRAMEWORK - #import - #import - #import - #import - #import - #else - #import "PGPlugin.h" - #import "JSON.h" - #import "PhoneGapDelegate.h" - #import "File.h" - #endif - -and in PGSQLitePlugin.m JSONRepresentation must be changed to JSONString: - - --- a/Plugins/PGSQLitePlugin.m - +++ b/Plugins/PGSQLitePlugin.m - @@ -219,7 +219,7 @@ - if (hasInsertId) { - [resultSet setObject:insertId forKey:@"insertId"]; - } - - [self respond:callback withString:[resultSet JSONRepresentation] withType:@"success"]; - + [self respond:callback withString:[resultSet JSONString] withType:@"success"]; - } - } - SQLite library -------------- @@ -47,7 +25,7 @@ In the Project "Build Phases" tab, select the _first_ "Link Binary with Librarie **NOTE:** In the "Build Phases" there can be multiple "Link Binary with Libraries" dropdown menus. Please select the first one otherwise it will not work. -PGSQLite Plugin +SQLite Plugin --------------- Drag .h and .m files into your project's Plugins folder (in xcode) -- I always @@ -67,15 +45,15 @@ Look for the following to your project's PhoneGap.plist: Insert this in there: - PGSQLitePlugin - PGSQLitePlugin + SQLitePlugin + SQLitePlugin General Usage ============= ## Coffee Script - db = new PGSQLitePlugin("test_native.sqlite3") + db = new SQLitePlugin("test_native.sqlite3") db.executeSql('DROP TABLE IF EXISTS test_table') db.executeSql('CREATE TABLE IF NOT EXISTS test_table (id integer primary key, data text, data_num integer)') @@ -102,7 +80,7 @@ General Usage ## Plain Javascript var db; - db = new PGSQLitePlugin("test_native.sqlite3"); + db = new SQLitePlugin("test_native.sqlite3"); db.executeSql('DROP TABLE IF EXISTS test_table'); db.executeSql('CREATE TABLE IF NOT EXISTS test_table (id integer primary key, data text, data_num integer)'); db.transaction(function(tx) { @@ -125,16 +103,16 @@ Lawnchair Adapter Usage Include the following js files in your html: - lawnchair.js (you provide) -- pgsqlite_plugin.js -- lawnchair_pgsqlite_plugin_adapter.js (must come after pgsqlite_plugin.js) +- sqlite_plugin.js +- lawnchair_sqlite_plugin_adapter.js (must come after sqlite_plugin.js) The `name` option will determine the sqlite filename. Optionally, you can change it using the `db` option. -In this example, you would be using/creating the database at: *Documents/kvstore.sqlite3* (all db's in PGSQLitePlugin are in the Documents folder) +In this example, you would be using/creating the database at: *Documents/kvstore.sqlite3* (all db's in SQLitePlugin are in the Documents folder) - kvstore = new Lawnchair { name: "kvstore", adapter: PGSQLitePlugin.lawnchair_adapter }, () -> + kvstore = new Lawnchair { name: "kvstore", adapter: SQLitePlugin.lawnchair_adapter }, () -> # do stuff Using the `db` option you can create multiple stores in one sqlite file. (There will be one table per store.) @@ -149,4 +127,3 @@ writeJavascript on a timer, however there was only a barely noticeable performance gain. So I took it out, not worth it. However there is a massive performance gain by batching on the client-side to minimize PhoneGap.exec calls using the transaction support. - From 971a859f6890bb81ab07e515222bc311439b7292 Mon Sep 17 00:00:00 2001 From: Iain Campion Date: Wed, 28 Mar 2012 21:02:55 +1300 Subject: [PATCH 16/44] Added changes as per @chbrody pull 24 for Cordova https://github.com/davibe/Phonegap-SQLitePlugin/pull/24 Added changes as per @chbrody --- README.md | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 7a3c3ce..d16dc9f 100644 --- a/README.md +++ b/README.md @@ -79,22 +79,23 @@ General Usage ## Plain Javascript - var db; - db = new SQLitePlugin("test_native.sqlite3"); - db.executeSql('DROP TABLE IF EXISTS test_table'); - db.executeSql('CREATE TABLE IF NOT EXISTS test_table (id integer primary key, data text, data_num integer)'); - db.transaction(function(tx) { - return tx.executeSql(["INSERT INTO test_table (data, data_num) VALUES (?,?)", "test", 100], function(res) { - console.log("insertId: " + res.insertId + " -- probably 1"); - console.log("rowsAffected: " + res.rowsAffected + " -- should be 1"); - return db.executeSql("select count(id) as cnt from test_table;", function(res) { - console.log("rows.length: " + res.rows.length + " -- should be 1"); - return console.log("rows[0].cnt: " + res.rows[0].cnt + " -- should be 1"); - }); - }, function(e) { - return console.log("ERROR: " + e.message); - }); - }); + var db; + db = new SQLitePlugin("my_sqlite_database.sqlite3"); + + db.executeSql('DROP TABLE IF EXISTS test_table'); + db.executeSql('CREATE TABLE IF NOT EXISTS test_table (id integer primary key, data text, data_num integer)'); + db.transaction(function(tx) { + return tx.executeSql("INSERT INTO test_table (data, data_num) VALUES (?,?)",[ "test", 100], function(res) { + console.log("insertId: " + res.insertId + " -- probably 1"); + console.log("rowsAffected: " + res.rowsAffected + " -- should be 1"); + return db.executeSql("select count(id) as cnt from test_table;", [], function(res) { + console.log("rows.length: " + res.rows.length + " -- should be 1"); + return console.log("rows[0].cnt: " + res.rows[0].cnt + " -- should be 1"); + }); + }, function(e) { + return console.log("ERROR: " + e.message); + }); + }); Lawnchair Adapter Usage From 08b113a95932f74dd87d6883eba3da95e2d2879e Mon Sep 17 00:00:00 2001 From: Iain Campion Date: Wed, 28 Mar 2012 21:45:36 +1300 Subject: [PATCH 17/44] =?UTF-8?q?coffeescript=20overlooked=20=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d16dc9f..9f8d0a5 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ General Usage db.transaction (tx) -> - tx.executeSql [ "INSERT INTO test_table (data, data_num) VALUES (?,?)", "test", 100], (res) -> + tx.executeSql "INSERT INTO test_table (data, data_num) VALUES (?,?)", ["test", 100], (res) -> # success callback @@ -67,7 +67,7 @@ General Usage console.log "rowsAffected: #{res.rowsAffected} -- should be 1" # check the count (not a part of the transaction) - db.executeSql "select count(id) as cnt from test_table;", (res) -> + db.executeSql "select count(id) as cnt from test_table;", [], (res) -> console.log "rows.length: #{res.rows.length} -- should be 1" console.log "rows[0].cnt: #{res.rows[0].cnt} -- should be 1" From ae073173f4d38dca32b634ab2e9e8bbc52c32e79 Mon Sep 17 00:00:00 2001 From: Chris Brody Date: Sun, 1 Apr 2012 22:10:22 +0200 Subject: [PATCH 18/44] Cordova-iOS version in subdirectory, keep legacy PhoneGap (iPhone) version in its own subdirectory --- README.md | 100 ++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 94 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 9f8d0a5..416dcbf 100644 --- a/README.md +++ b/README.md @@ -3,9 +3,9 @@ Cordova SQLitePlugin DISCLAIMER: -Created by @Joenoon: +Created by @Joenoon: -Adapted to 1.5 by coomsie +Adapted to 1.5 by @coomsie DISCLAIMER: @@ -15,9 +15,41 @@ We are brand new to objective-c, so there could be problems with our code! Installing ========== -Cordova (PhoneGap) 1.5.0 +**NOTE:** There are now 2 trees: `Cordova-iOS` for Cordova 1.5(+) and `Legacy-PhoneGap-iPhone` for PhoneGap (tested 1.3 and earlier). I am planning to add an Android version in another tree, hopefully in the near future. + + +PhoneGap 1.3.0 -------------- +For installing with PhoneGap 1.3.0: +in PGSQLitePlugin.h file change for PhoneGaps JSONKit.h implementation. + + #ifdef PHONEGAP_FRAMEWORK + #import + #import + #import + #import + #import + #else + #import "PGPlugin.h" + #import "JSON.h" + #import "PhoneGapDelegate.h" + #import "File.h" + #endif + +and in PGSQLitePlugin.m JSONRepresentation must be changed to JSONString: + + --- a/Plugins/PGSQLitePlugin.m + +++ b/Plugins/PGSQLitePlugin.m + @@ -219,7 +219,7 @@ + if (hasInsertId) { + [resultSet setObject:insertId forKey:@"insertId"]; + } + - [self respond:callback withString:[resultSet JSONRepresentation] withType:@"success"]; + + [self respond:callback withString:[resultSet JSONString] withType:@"success"]; + } + } + SQLite library -------------- @@ -36,7 +68,7 @@ file in src/ to javascript WITH the top-level function wrapper option (default). Use the resulting javascript file in your HTML. -Look for the following to your project's PhoneGap.plist: +Look for the following to your project's Cordova.plist or PhoneGap.plist: Plugins @@ -48,9 +80,16 @@ Insert this in there: SQLitePlugin SQLitePlugin +**NOTE:** for `Legacy-PhoneGap-iPhone` the plugin name is still `PGSQLitePlugin`, expected to be fixed in the near future. + General Usage ============= +**NOTE:** in this fork the API is undergoing changes to be closer to the HTML5 Web SQL API. + +Cordova iOS +----------- + ## Coffee Script db = new SQLitePlugin("test_native.sqlite3") @@ -98,14 +137,63 @@ General Usage }); +Legacy PhoneGap (old version) +----------------------------- + +## Coffee Script + + db = new PGSQLitePlugin("test_native.sqlite3") + db.executeSql('DROP TABLE IF EXISTS test_table') + db.executeSql('CREATE TABLE IF NOT EXISTS test_table (id integer primary key, data text, data_num integer)') + + db.transaction (tx) -> + + tx.executeSql [ "INSERT INTO test_table (data, data_num) VALUES (?,?)", "test", 100], (res) -> + + # success callback + + console.log "insertId: #{res.insertId} -- probably 1" + console.log "rowsAffected: #{res.rowsAffected} -- should be 1" + + # check the count (not a part of the transaction) + db.executeSql "select count(id) as cnt from test_table;", (res) -> + console.log "rows.length: #{res.rows.length} -- should be 1" + console.log "rows[0].cnt: #{res.rows[0].cnt} -- should be 1" + + , (e) -> + + # error callback + + console.log "ERROR: #{e.message}" + +## Plain Javascript + + var db; + db = new PGSQLitePlugin("test_native.sqlite3"); + db.executeSql('DROP TABLE IF EXISTS test_table'); + db.executeSql('CREATE TABLE IF NOT EXISTS test_table (id integer primary key, data text, data_num integer)'); + db.transaction(function(tx) { + return tx.executeSql(["INSERT INTO test_table (data, data_num) VALUES (?,?)", "test", 100], function(res) { + console.log("insertId: " + res.insertId + " -- probably 1"); + console.log("rowsAffected: " + res.rowsAffected + " -- should be 1"); + return db.executeSql("select count(id) as cnt from test_table;", function(res) { + console.log("rows.length: " + res.rows.length + " -- should be 1"); + return console.log("rows[0].cnt: " + res.rows[0].cnt + " -- should be 1"); + }); + }, function(e) { + return console.log("ERROR: " + e.message); + }); + }); + + Lawnchair Adapter Usage ======================= Include the following js files in your html: - lawnchair.js (you provide) -- sqlite_plugin.js -- lawnchair_sqlite_plugin_adapter.js (must come after sqlite_plugin.js) +- sqlite_plugin.js [pgsqlite_plugin.js in Legacy-PhoneGap-iPhone] +- lawnchair_sqlite_plugin_adapter.js [lawnchair_pgsqlite_plugin_adapter.js] (must come after sqlite_plugin.js [pgsqlite_plugin.js in Legacy-PhoneGap-iPhone]) From 7691027d758e870a1762ad476c8984f7cf4118c8 Mon Sep 17 00:00:00 2001 From: Chris Brody Date: Sun, 1 Apr 2012 22:50:16 +0200 Subject: [PATCH 19/44] Add basic lawnchair test suite from the lawnchair repository --- README.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 416dcbf..0f94a59 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ -Cordova SQLitePlugin -===================== +Cordova/PhoneGap SQLiteNative plugin +==================================== + +Native interface to sqlite in a Cordova/PhoneGap plugin, working to follow the HTML5 Web SQL API as close as possible. DISCLAIMER: @@ -209,6 +211,12 @@ Using the `db` option you can create multiple stores in one sqlite file. (There recipes = new Lawnchair {db: "cookbook", name: "recipes", ...} ingredients = new Lawnchair {db: "cookbook", name: "ingredients", ...} + +Lawnchair test +-------------- + +In the lawnchair-test subdirectory of Cordova-iOS or Legacy-PhoneGap-iPhone you can copy the contents of the www subdirectory into a Cordova/PhoneGap project and see the behavior of the Lawnchair test suite. + ### Other notes from @Joenoon: I played with the idea of batching responses into larger sets of From fb86044011f998a06ff6f6ca91118c99f56df42a Mon Sep 17 00:00:00 2001 From: Chris Brody Date: Mon, 2 Apr 2012 01:51:39 +0200 Subject: [PATCH 20/44] Missed change to API in README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0f94a59..890506d 100644 --- a/README.md +++ b/README.md @@ -175,10 +175,10 @@ Legacy PhoneGap (old version) db.executeSql('DROP TABLE IF EXISTS test_table'); db.executeSql('CREATE TABLE IF NOT EXISTS test_table (id integer primary key, data text, data_num integer)'); db.transaction(function(tx) { - return tx.executeSql(["INSERT INTO test_table (data, data_num) VALUES (?,?)", "test", 100], function(res) { + return tx.executeSql("INSERT INTO test_table (data, data_num) VALUES (?,?)", ["test", 100], function(res) { console.log("insertId: " + res.insertId + " -- probably 1"); console.log("rowsAffected: " + res.rowsAffected + " -- should be 1"); - return db.executeSql("select count(id) as cnt from test_table;", function(res) { + return db.executeSql("select count(id) as cnt from test_table;", [] function(res) { console.log("rows.length: " + res.rows.length + " -- should be 1"); return console.log("rows[0].cnt: " + res.rows[0].cnt + " -- should be 1"); }); From 6e334728c896ba820ef40d172c3b364ec23a5b84 Mon Sep 17 00:00:00 2001 From: Chris Brody Date: Mon, 2 Apr 2012 02:47:12 +0200 Subject: [PATCH 21/44] Fix typo in legacy PhoneGap code example --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 890506d..59c31af 100644 --- a/README.md +++ b/README.md @@ -178,7 +178,7 @@ Legacy PhoneGap (old version) return tx.executeSql("INSERT INTO test_table (data, data_num) VALUES (?,?)", ["test", 100], function(res) { console.log("insertId: " + res.insertId + " -- probably 1"); console.log("rowsAffected: " + res.rowsAffected + " -- should be 1"); - return db.executeSql("select count(id) as cnt from test_table;", [] function(res) { + return db.executeSql("select count(id) as cnt from test_table;", [], function(res) { console.log("rows.length: " + res.rows.length + " -- should be 1"); return console.log("rows[0].cnt: " + res.rows[0].cnt + " -- should be 1"); }); From c729656c2973a8a569f08ac346edf40e802ce616 Mon Sep 17 00:00:00 2001 From: Chris Brody Date: Mon, 2 Apr 2012 03:17:39 +0200 Subject: [PATCH 22/44] Changes in db.transaction(), tx.executeSql success callback in Legacy PhoneGap version (for peer review) --- README.md | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 59c31af..39e96a0 100644 --- a/README.md +++ b/README.md @@ -175,7 +175,7 @@ Legacy PhoneGap (old version) db.executeSql('DROP TABLE IF EXISTS test_table'); db.executeSql('CREATE TABLE IF NOT EXISTS test_table (id integer primary key, data text, data_num integer)'); db.transaction(function(tx) { - return tx.executeSql("INSERT INTO test_table (data, data_num) VALUES (?,?)", ["test", 100], function(res) { + return tx.executeSql("INSERT INTO test_table (data, data_num) VALUES (?,?)", ["test", 100], function(tx, res) { console.log("insertId: " + res.insertId + " -- probably 1"); console.log("rowsAffected: " + res.rowsAffected + " -- should be 1"); return db.executeSql("select count(id) as cnt from test_table;", [], function(res) { @@ -187,6 +187,23 @@ Legacy PhoneGap (old version) }); }); +## Changes in tx.executeSql success callback + + var db; + db = new PGSQLitePlugin("test_native.sqlite3"); + db.executeSql('DROP TABLE IF EXISTS test_table'); + db.executeSql('CREATE TABLE IF NOT EXISTS test_table (id integer primary key, data text, data_num integer)'); + db.executeSql("INSERT INTO test_table (data, data_num) VALUES (?,?)", ["test", 100], function(res) { + console.log("insertId: " + res.insertId + " -- probably 1"); + console.log("rowsAffected: " + res.rowsAffected + " -- should be 1"); + db.transaction(function(tx) { + return tx.executeSql("select count(id) as cnt from test_table;", [], function(tx, res) { + console.log("rows.length: " + res.rows.length + " -- should be 1"); + return console.log("rows[0].cnt: " + res.rows.item[0].cnt + " -- should be 1"); + }); + }); + }); + Lawnchair Adapter Usage ======================= From 44bd4ae17cb232e41dde40d7add09b69cff57917 Mon Sep 17 00:00:00 2001 From: Chris Brody Date: Mon, 2 Apr 2012 19:38:18 +0200 Subject: [PATCH 23/44] Cleanup API changes, etc. in README.md --- README.md | 53 ++++++++++++++++++++++++++++------------------------- 1 file changed, 28 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 39e96a0..a202c69 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ Cordova/PhoneGap SQLiteNative plugin ==================================== -Native interface to sqlite in a Cordova/PhoneGap plugin, working to follow the HTML5 Web SQL API as close as possible. +Native interface to sqlite in a Cordova/PhoneGap plugin, working to follow the HTML5 Web SQL API as close as possible. **NOTE** that the API is now different from https://github.com/davibe/Phonegap-SQLitePlugin and is still undergoing some changes. DISCLAIMER: @@ -9,6 +9,8 @@ Created by @Joenoon: Adapted to 1.5 by @coomsie +API changes by @chbrody + DISCLAIMER: @@ -87,14 +89,14 @@ Insert this in there: General Usage ============= -**NOTE:** in this fork the API is undergoing changes to be closer to the HTML5 Web SQL API. +**NOTE:** in this fork the API is undergoing changes to be closer to the HTML5 Web SQL API and is expected to change in the near future. Cordova iOS ----------- ## Coffee Script - db = new SQLitePlugin("test_native.sqlite3") + db = new SQLitePlugin("my_sqlite_database.sqlite3") db.executeSql('DROP TABLE IF EXISTS test_table') db.executeSql('CREATE TABLE IF NOT EXISTS test_table (id integer primary key, data text, data_num integer)') @@ -120,23 +122,24 @@ Cordova iOS ## Plain Javascript - var db; - db = new SQLitePlugin("my_sqlite_database.sqlite3"); - - db.executeSql('DROP TABLE IF EXISTS test_table'); - db.executeSql('CREATE TABLE IF NOT EXISTS test_table (id integer primary key, data text, data_num integer)'); - db.transaction(function(tx) { - return tx.executeSql("INSERT INTO test_table (data, data_num) VALUES (?,?)",[ "test", 100], function(res) { - console.log("insertId: " + res.insertId + " -- probably 1"); - console.log("rowsAffected: " + res.rowsAffected + " -- should be 1"); - return db.executeSql("select count(id) as cnt from test_table;", [], function(res) { - console.log("rows.length: " + res.rows.length + " -- should be 1"); - return console.log("rows[0].cnt: " + res.rows[0].cnt + " -- should be 1"); - }); - }, function(e) { - return console.log("ERROR: " + e.message); - }); - }); + var db = new SQLitePlugin("my_sqlite_database.sqlite3"); + + db.executeSql('DROP TABLE IF EXISTS test_table'); + db.executeSql('CREATE TABLE IF NOT EXISTS test_table (id integer primary key, data text, data_num integer)'); + db.transaction(function(tx) { + return tx.executeSql("INSERT INTO test_table (data, data_num) VALUES (?,?)", ["test", 100], function(res) { + console.log("insertId: " + res.insertId + " -- probably 1"); + console.log("rowsAffected: " + res.rowsAffected + " -- should be 1"); + return db.executeSql("select count(id) as cnt from test_table;", [], function(res) { + console.log("rows.length: " + res.rows.length + " -- should be 1"); + return console.log("rows[0].cnt: " + res.rows[0].cnt + " -- should be 1"); + }); + }, function(e) { + return console.log("ERROR: " + e.message); + }); + }); + +**NOTE:** changes to tx.executeSql() success callback and possibly db.executeSql() callback are expected very soon. Legacy PhoneGap (old version) @@ -144,13 +147,13 @@ Legacy PhoneGap (old version) ## Coffee Script - db = new PGSQLitePlugin("test_native.sqlite3") + db = new PGSQLitePlugin("my_sqlite_database.sqlite3") db.executeSql('DROP TABLE IF EXISTS test_table') db.executeSql('CREATE TABLE IF NOT EXISTS test_table (id integer primary key, data text, data_num integer)') db.transaction (tx) -> - tx.executeSql [ "INSERT INTO test_table (data, data_num) VALUES (?,?)", "test", 100], (res) -> + tx.executeSql "INSERT INTO test_table (data, data_num) VALUES (?,?)", ["test", 100], (res) -> # success callback @@ -171,7 +174,7 @@ Legacy PhoneGap (old version) ## Plain Javascript var db; - db = new PGSQLitePlugin("test_native.sqlite3"); + db = new PGSQLitePlugin("my_sqlite_database.sqlite3"); db.executeSql('DROP TABLE IF EXISTS test_table'); db.executeSql('CREATE TABLE IF NOT EXISTS test_table (id integer primary key, data text, data_num integer)'); db.transaction(function(tx) { @@ -187,10 +190,10 @@ Legacy PhoneGap (old version) }); }); -## Changes in tx.executeSql success callback +## Changes in tx.executeSql() success callback var db; - db = new PGSQLitePlugin("test_native.sqlite3"); + db = new PGSQLitePlugin("my_sqlite_database.sqlite3"); db.executeSql('DROP TABLE IF EXISTS test_table'); db.executeSql('CREATE TABLE IF NOT EXISTS test_table (id integer primary key, data text, data_num integer)'); db.executeSql("INSERT INTO test_table (data, data_num) VALUES (?,?)", ["test", 100], function(res) { From 4df3314693d5a38dd5d2a2674f657cf8dc9a9644 Mon Sep 17 00:00:00 2001 From: Chris Brody Date: Tue, 3 Apr 2012 12:44:21 +0200 Subject: [PATCH 24/44] Update db.transaction() and tx.executeSql() callback in Cordova-iOS version --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a202c69..3299cd5 100644 --- a/README.md +++ b/README.md @@ -202,7 +202,7 @@ Legacy PhoneGap (old version) db.transaction(function(tx) { return tx.executeSql("select count(id) as cnt from test_table;", [], function(tx, res) { console.log("rows.length: " + res.rows.length + " -- should be 1"); - return console.log("rows[0].cnt: " + res.rows.item[0].cnt + " -- should be 1"); + return console.log("rows[0].cnt: " + res.rows.item(0).cnt + " -- should be 1"); }); }); }); From a9e99c12dca89262f11c462ac41c6eaae062ea3c Mon Sep 17 00:00:00 2001 From: Chris Brody Date: Tue, 3 Apr 2012 12:46:17 +0200 Subject: [PATCH 25/44] Update README.md to changes in tx.executeSql() success callback for Cordova-iOS --- README.md | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 3299cd5..5f1f5d0 100644 --- a/README.md +++ b/README.md @@ -139,7 +139,21 @@ Cordova iOS }); }); -**NOTE:** changes to tx.executeSql() success callback and possibly db.executeSql() callback are expected very soon. +## Changes in tx.executeSql() success callback + + var db = new SQLitePlugin("my_sqlite_database.sqlite3"); + db.executeSql('DROP TABLE IF EXISTS test_table'); + db.executeSql('CREATE TABLE IF NOT EXISTS test_table (id integer primary key, data text, data_num integer)'); + db.executeSql("INSERT INTO test_table (data, data_num) VALUES (?,?)", ["test", 100], function(res) { + console.log("insertId: " + res.insertId + " -- probably 1"); + console.log("rowsAffected: " + res.rowsAffected + " -- should be 1"); + db.transaction(function(tx) { + return tx.executeSql("select count(id) as cnt from test_table;", [], function(tx, res) { + console.log("rows.length: " + res.rows.length + " -- should be 1"); + return console.log("rows.item(0).cnt: " + res.rows.item(0).cnt + " -- should be 1"); + }); + }); + }); Legacy PhoneGap (old version) From a9b94c219026f772de9c6bcf8c64d4a3ff84053f Mon Sep 17 00:00:00 2001 From: Chris Brody Date: Tue, 3 Apr 2012 14:38:02 +0200 Subject: [PATCH 26/44] Replace SQLitePlugin constructor with sqliteNative.openDatabase() --- README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 5f1f5d0..47a7ac1 100644 --- a/README.md +++ b/README.md @@ -61,8 +61,8 @@ In the Project "Build Phases" tab, select the _first_ "Link Binary with Librarie **NOTE:** In the "Build Phases" there can be multiple "Link Binary with Libraries" dropdown menus. Please select the first one otherwise it will not work. -SQLite Plugin ---------------- +Native SQLite Plugin +-------------------- Drag .h and .m files into your project's Plugins folder (in xcode) -- I always just have "Create references" as the option selected. @@ -84,19 +84,19 @@ Insert this in there: SQLitePlugin SQLitePlugin -**NOTE:** for `Legacy-PhoneGap-iPhone` the plugin name is still `PGSQLitePlugin`, expected to be fixed in the near future. +**NOTE:** this plugin name is expected to be changed in the near future. For `Legacy-PhoneGap-iPhone` the plugin name is still `PGSQLitePlugin`, also expected to be fixed in the near future. General Usage ============= -**NOTE:** in this fork the API is undergoing changes to be closer to the HTML5 Web SQL API and is expected to change in the near future. - Cordova iOS ----------- +**NOTE:** Please use sqliteNative.openDatabase() to open a database, the parameters are different from the old SQLitePlugin() constructor which is now gone. This should a closer resemblance to the HTML5/W3 SQL API. + ## Coffee Script - db = new SQLitePlugin("my_sqlite_database.sqlite3") + db = sqliteNative.openDatabase("my_sqlite_database.sqlite3") db.executeSql('DROP TABLE IF EXISTS test_table') db.executeSql('CREATE TABLE IF NOT EXISTS test_table (id integer primary key, data text, data_num integer)') @@ -122,7 +122,7 @@ Cordova iOS ## Plain Javascript - var db = new SQLitePlugin("my_sqlite_database.sqlite3"); + var db = sqliteNative.openDatabase("my_sqlite_database.sqlite3"); db.executeSql('DROP TABLE IF EXISTS test_table'); db.executeSql('CREATE TABLE IF NOT EXISTS test_table (id integer primary key, data text, data_num integer)'); @@ -141,7 +141,7 @@ Cordova iOS ## Changes in tx.executeSql() success callback - var db = new SQLitePlugin("my_sqlite_database.sqlite3"); + var db = sqliteNative.openDatabase("my_sqlite_database.sqlite3"); db.executeSql('DROP TABLE IF EXISTS test_table'); db.executeSql('CREATE TABLE IF NOT EXISTS test_table (id integer primary key, data text, data_num integer)'); db.executeSql("INSERT INTO test_table (data, data_num) VALUES (?,?)", ["test", 100], function(res) { From 641b19daa38161d059ff5ece55a9bdba3dd80c3b Mon Sep 17 00:00:00 2001 From: Chris Brody Date: Thu, 5 Apr 2012 01:11:25 +0200 Subject: [PATCH 27/44] First DroidGap (1.1) version, use at your own risk --- DroidGap/assets/www/SQLitePlugin.js | 316 ++++++++++++++++++ DroidGap/assets/www/index.html | 111 ++++++ .../phonegap/example/plugin/SQLitePlugin.java | 248 ++++++++++++++ README.md | 9 +- 4 files changed, 682 insertions(+), 2 deletions(-) create mode 100644 DroidGap/assets/www/SQLitePlugin.js create mode 100644 DroidGap/assets/www/index.html create mode 100644 DroidGap/src/com/phonegap/example/plugin/SQLitePlugin.java diff --git a/DroidGap/assets/www/SQLitePlugin.js b/DroidGap/assets/www/SQLitePlugin.js new file mode 100644 index 0000000..d377dcb --- /dev/null +++ b/DroidGap/assets/www/SQLitePlugin.js @@ -0,0 +1,316 @@ +/* + * PhoneGap is available under *either* the terms of the modified BSD license *or* the + * MIT License (2008). See http://opensource.org/licenses/alphabetical for full text. + * + * Copyright (c) 2005-2010, Nitobi Software Inc. + * Copyright (c) 2010-2011, IBM Corporation + */ + +/* + * This is purely for the Android 1.5/1.6 HTML 5 Storage + * I was hoping that Android 2.0 would deprecate this, but given the fact that + * most manufacturers ship with Android 1.5 and do not do OTA Updates, this is required + */ + + +/** + * SQL result set object + * PRIVATE METHOD + * @constructor + */ +var DDB_Rows = function() { + this.resultSet = []; // results array + this.length = 0; // number of rows +}; + +/** + * Get item from SQL result set + * + * @param row The row number to return + * @return The row object + */ +DDB_Rows.prototype.item = function(row) { + return this.resultSet[row]; +}; + +/** + * SQL result set that is returned to user. + * PRIVATE METHOD + * @constructor + */ +var DDB_Result = function() { + this.rows = new DDB_Rows(); +}; + +/** + * Storage object that is called by native code when performing queries. + * PRIVATE METHOD + * @constructor + */ +var DDB = function() { + this.queryQueue = {}; +}; + +/** + * Callback from native code when query is complete. + * PRIVATE METHOD + * + * @param id Query id + */ +DDB.prototype.completeQuery = function(id, data) { + var query = this.queryQueue[id]; + if (query) { + try { + delete this.queryQueue[id]; + + // Get transaction + var tx = query.tx; + + // If transaction hasn't failed + // Note: We ignore all query results if previous query + // in the same transaction failed. + if (tx && tx.queryList[id]) { + + // Save query results + var r = new DDB_Result(); + r.rows.resultSet = data; + r.rows.length = data.length; + try { + if (typeof query.successCallback === 'function') { + query.successCallback(query.tx, r); + } + } catch (ex) { + console.log("executeSql error calling user success callback: "+ex); + } + + tx.queryComplete(id); + } + } catch (e) { + console.log("executeSql error: "+e); + } + } +}; + +/** + * Callback from native code when query fails + * PRIVATE METHOD + * + * @param reason Error message + * @param id Query id + */ +DDB.prototype.fail = function(reason, id) { + var query = this.queryQueue[id]; + if (query) { + try { + delete this.queryQueue[id]; + + // Get transaction + var tx = query.tx; + + // If transaction hasn't failed + // Note: We ignore all query results if previous query + // in the same transaction failed. + if (tx && tx.queryList[id]) { + tx.queryList = {}; + + try { + if (typeof query.errorCallback === 'function') { + query.errorCallback(query.tx, reason); + } + } catch (ex) { + console.log("executeSql error calling user error callback: "+ex); + } + + tx.queryFailed(id, reason); + } + + } catch (e) { + console.log("executeSql error: "+e); + } + } +}; + +/** + * SQL query object + * PRIVATE METHOD + * + * @constructor + * @param tx The transaction object that this query belongs to + */ +var DDB_Query = function(tx) { + + // Set the id of the query + this.id = PhoneGap.createUUID(); + + // Add this query to the queue + dddb.queryQueue[this.id] = this; + + // Init result + this.resultSet = []; + + // Set transaction that this query belongs to + this.tx = tx; + + // Add this query to transaction list + this.tx.queryList[this.id] = this; + + // Callbacks + this.successCallback = null; + this.errorCallback = null; + +}; + +/** + * Transaction object + * PRIVATE METHOD + * @constructor + */ +var DDB_Tx = function() { + + // Set the id of the transaction + this.id = PhoneGap.createUUID(); + + // Callbacks + this.successCallback = null; + this.errorCallback = null; + + // Query list + this.queryList = {}; +}; + +/** + * Mark query in transaction as complete. + * If all queries are complete, call the user's transaction success callback. + * + * @param id Query id + */ +DDB_Tx.prototype.queryComplete = function(id) { + delete this.queryList[id]; + + // If no more outstanding queries, then fire transaction success + if (this.successCallback) { + var count = 0; + var i; + for (i in this.queryList) { + if (this.queryList.hasOwnProperty(i)) { + count++; + } + } + if (count === 0) { + try { + this.successCallback(); + } catch(e) { + console.log("Transaction error calling user success callback: " + e); + } + } + } +}; + +/** + * Mark query in transaction as failed. + * + * @param id Query id + * @param reason Error message + */ +DDB_Tx.prototype.queryFailed = function(id, reason) { + + // The sql queries in this transaction have already been run, since + // we really don't have a real transaction implemented in native code. + // However, the user callbacks for the remaining sql queries in transaction + // will not be called. + this.queryList = {}; + + if (this.errorCallback) { + try { + this.errorCallback(reason); + } catch(e) { + console.log("Transaction error calling user error callback: " + e); + } + } +}; + +/** + * Execute SQL statement + * + * @param sql SQL statement to execute + * @param params Statement parameters + * @param successCallback Success callback + * @param errorCallback Error callback + */ +DDB_Tx.prototype.executeSql = function(sql, params, successCallback, errorCallback) { + + // Init params array + if (typeof params === 'undefined') { + params = []; + } + + // Create query and add to queue + var query = new DDB_Query(this); + dddb.queryQueue[query.id] = query; + + // Save callbacks + query.successCallback = successCallback; + query.errorCallback = errorCallback; + + // Call native code + PhoneGap.exec(null, null, "SQLitePlugin", "executeSql", [sql, params, query.id]); +}; + +var DatabaseShell = function() { +}; + +/** + * Start a transaction. + * Does not support rollback in event of failure. + * + * @param process {Function} The transaction function + * @param successCallback {Function} + * @param errorCallback {Function} + */ +DatabaseShell.prototype.transaction = function(process, errorCallback, successCallback) { + var tx = new DDB_Tx(); + tx.successCallback = successCallback; + tx.errorCallback = errorCallback; + try { + process(tx); + } catch (e) { + console.log("Transaction error: "+e); + if (tx.errorCallback) { + try { + tx.errorCallback(e); + } catch (ex) { + console.log("Transaction error calling user error callback: "+e); + } + } + } +}; + +/** + * Open database + * + * @param name Database name + * @param version Database version + * @param display_name Database display name + * @param size Database size in bytes + * @return Database object + */ +var DDB_openDatabase = function(name, version, display_name, size) { + PhoneGap.exec(null, null, "SQLitePlugin", "openDatabase", [name, version, display_name, size]); + var db = new DatabaseShell(); + return db; +}; + +/** + * For browsers with no localStorage we emulate it with SQLite. Follows the w3c api. + * TODO: Do similar for sessionStorage. + */ + +/** + * @constructor + */ + +window.my_openDatabase = function(name, version, desc, size){ + window.dddb = new DDB(); + return DDB_openDatabase(name, version, desc, size); + } + diff --git a/DroidGap/assets/www/index.html b/DroidGap/assets/www/index.html new file mode 100644 index 0000000..555768d --- /dev/null +++ b/DroidGap/assets/www/index.html @@ -0,0 +1,111 @@ + + + + + + PhoneGap + + + + + + + + + + + + +

Welcome to PhoneGap!

+

this file is located at assets/www/index.html

+
+

Platform:  , Version:  

+

UUID:  , Name:  

+

Width:  , Height:   + , Color Depth:

+
+
+
X:
 
+
Y:
 
+
Z:
 
+
+ Toggle Accelerometer + Get Location + Call 411 + Beep + Vibrate + Get a Picture + Get Phone's Contacts + Check Network + + + diff --git a/DroidGap/src/com/phonegap/example/plugin/SQLitePlugin.java b/DroidGap/src/com/phonegap/example/plugin/SQLitePlugin.java new file mode 100644 index 0000000..895b632 --- /dev/null +++ b/DroidGap/src/com/phonegap/example/plugin/SQLitePlugin.java @@ -0,0 +1,248 @@ +/* + * PhoneGap is available under *either* the terms of the modified BSD license *or* the + * MIT License (2008). See http://opensource.org/licenses/alphabetical for full text. + * + * Copyright (c) 2005-2010, Nitobi Software Inc. + * Copyright (c) 2010, IBM Corporation + */ +package com.phonegap.plugin; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +import com.phonegap.api.Plugin; +import com.phonegap.api.PluginResult; +import android.database.Cursor; +import android.database.sqlite.*; + +import android.util.Log; + +/** + * This class implements the HTML5 database support for Android 1.X devices. It + * is not used for Android 2.X, since HTML5 database is built in to the browser. + */ +public class SQLitePlugin extends Plugin { + + // Data Definition Language + private static final String ALTER = "alter"; + private static final String CREATE = "create"; + private static final String DROP = "drop"; + private static final String TRUNCATE = "truncate"; + + SQLiteDatabase myDb = null; // Database object + String path = null; // Database path + String dbName = null; // Database name + + /** + * Constructor. + */ + public SQLitePlugin() { + } + + /** + * Executes the request and returns PluginResult. + * + * @param action + * The action to execute. + * @param args + * JSONArry of arguments for the plugin. + * @param callbackId + * The callback id used when calling back into JavaScript. + * @return A PluginResult object with a status and message. + */ + public PluginResult execute(String action, JSONArray args, String callbackId) { + PluginResult.Status status = PluginResult.Status.OK; + String result = ""; + + try { + // TODO: Do we want to allow a user to do this, since they could get + // to other app databases? + if (action.equals("setStorage")) { + this.setStorage(args.getString(0)); + } else if (action.equals("openDatabase")) { + this.openDatabase(args.getString(0), args.getString(1), + args.getString(2), args.getLong(3)); + } else if (action.equals("executeSql")) { + String[] s = null; + if (args.isNull(1)) { + s = new String[0]; + } else { + JSONArray a = args.getJSONArray(1); + int len = a.length(); + s = new String[len]; + for (int i = 0; i < len; i++) { + s[i] = a.getString(i); + } + } + this.executeSql(args.getString(0), s, args.getString(2)); + } + return new PluginResult(status, result); + } catch (JSONException e) { + return new PluginResult(PluginResult.Status.JSON_EXCEPTION); + } + } + + /** + * Identifies if action to be executed returns a value and should be run + * synchronously. + * + * @param action + * The action to execute + * @return T=returns value + */ + public boolean isSynch(String action) { + return true; + } + + /** + * Clean up and close database. + */ + @Override + public void onDestroy() { + if (this.myDb != null) { + this.myDb.close(); + this.myDb = null; + } + } + + // -------------------------------------------------------------------------- + // LOCAL METHODS + // -------------------------------------------------------------------------- + + /** + * Set the application package for the database. Each application saves its + * database files in a directory with the application package as part of the + * file name. + * + * For example, application "com.phonegap.demo.Demo" would save its database + * files in "/data/data/com.phonegap.demo/databases/" directory. + * + * @param appPackage + * The application package. + */ + public void setStorage(String appPackage) { + this.path = "/data/data/" + appPackage + "/databases/"; + } + + /** + * Open database. + * + * @param db + * The name of the database + * @param version + * The version + * @param display_name + * The display name + * @param size + * The size in bytes + */ + public void openDatabase(String db, String version, String display_name, + long size) { + + // If database is open, then close it + if (this.myDb != null) { + this.myDb.close(); + } + + // If no database path, generate from application package + if (this.path == null) { + Package pack = this.ctx.getClass().getPackage(); + String appPackage = pack.getName(); + this.setStorage(appPackage); + } + + this.dbName = this.path + db + ".db"; + this.myDb = SQLiteDatabase.openOrCreateDatabase(this.dbName, null); + } + + /** + * Execute SQL statement. + * + * @param query + * The SQL query + * @param params + * Parameters for the query + * @param tx_id + * Transaction id + */ + public void executeSql(String query, String[] params, String tx_id) { + try { + if (isDDL(query)) { + this.myDb.execSQL(query); + this.sendJavascript("dddb.completeQuery('" + tx_id + "', '');"); + } + else { + Cursor myCursor = this.myDb.rawQuery(query, params); + this.processResults(myCursor, tx_id); + myCursor.close(); + } + } + catch (SQLiteException ex) { + ex.printStackTrace(); + System.out.println("SQLitePlugin.executeSql(): Error=" + ex.getMessage()); + + // Send error message back to JavaScript + this.sendJavascript("dddb.fail('" + ex.getMessage() + "','" + tx_id + "');"); + } + } + + /** + * Checks to see the the query is a Data Definintion command + * + * @param query to be executed + * @return true if it is a DDL command, false otherwise + */ + private boolean isDDL(String query) { + String cmd = query.toLowerCase(); + if (cmd.startsWith(DROP) || cmd.startsWith(CREATE) || cmd.startsWith(ALTER) || cmd.startsWith(TRUNCATE)) { + return true; + } + return false; + } + + /** + * Process query results. + * + * @param cur + * Cursor into query results + * @param tx_id + * Transaction id + */ + public void processResults(Cursor cur, String tx_id) { + + String result = "[]"; + // If query result has rows + + if (cur.moveToFirst()) { + JSONArray fullresult = new JSONArray(); + String key = ""; + String value = ""; + int colCount = cur.getColumnCount(); + + // Build up JSON result object for each row + do { + JSONObject row = new JSONObject(); + try { + for (int i = 0; i < colCount; ++i) { + key = cur.getColumnName(i); + value = cur.getString(i); + row.put(key, value); + } + fullresult.put(row); + + } catch (JSONException e) { + e.printStackTrace(); + } + + } while (cur.moveToNext()); + + result = fullresult.toString(); + } + + // Let JavaScript know that there are no more rows + this.sendJavascript("dddb.completeQuery('" + tx_id + "', " + result + + ");"); + + } + +} diff --git a/README.md b/README.md index 47a7ac1..df5b5aa 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ Created by @Joenoon: Adapted to 1.5 by @coomsie -API changes by @chbrody +API changes and Android version by @chbrody DISCLAIMER: @@ -89,10 +89,15 @@ Insert this in there: General Usage ============= +Android (DroidGap) +------------------ + +I put the information here for the sake of completeness. I have tested the DroidGap SQLitePlugin version on a simulator on PhoneGap 1.1 *ONLY* and do not guarantee what will happen in any other situation. Basically, I copied and adapted the code from storage.js and Storage.java to make a plugin version. I got the versions that were there on October 2011, so it should be OK to use them under the MIT or Apache licenses. Hereby you can take SQLitePlugin.java (it is in the wrong place but it still worked), SQLitePlugin.js, and look at my index.html, register in plugins.xml, and give it your best shot! Fork it and take it over! + Cordova iOS ----------- -**NOTE:** Please use sqliteNative.openDatabase() to open a database, the parameters are different from the old SQLitePlugin() constructor which is now gone. This should a closer resemblance to the HTML5/W3 SQL API. +**NOTE:** Please use sqliteNative.openDatabase() to open a database, this will be changed to sqlitePlugin.openDatabase(), the parameters are different from the old SQLitePlugin() constructor which is now gone. This should a closer resemblance to the HTML5/W3 SQL API. ## Coffee Script From 105ff6dbb1c0b55785e7c8193c84c747c6bcd660 Mon Sep 17 00:00:00 2001 From: Chris Brody Date: Thu, 5 Apr 2012 18:55:33 +0200 Subject: [PATCH 28/44] Rename back to SQLitePlugin for Cordova-iOS --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index df5b5aa..70f5321 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -Cordova/PhoneGap SQLiteNative plugin +Cordova/PhoneGap Native SQLitePlugin ==================================== Native interface to sqlite in a Cordova/PhoneGap plugin, working to follow the HTML5 Web SQL API as close as possible. **NOTE** that the API is now different from https://github.com/davibe/Phonegap-SQLitePlugin and is still undergoing some changes. @@ -84,7 +84,7 @@ Insert this in there: SQLitePlugin SQLitePlugin -**NOTE:** this plugin name is expected to be changed in the near future. For `Legacy-PhoneGap-iPhone` the plugin name is still `PGSQLitePlugin`, also expected to be fixed in the near future. +**NOTE:** For `Legacy-PhoneGap-iPhone` the plugin name is `PGSQLitePlugin`, no fix is expected in this project. General Usage ============= @@ -97,11 +97,11 @@ I put the information here for the sake of completeness. I have tested the Droid Cordova iOS ----------- -**NOTE:** Please use sqliteNative.openDatabase() to open a database, this will be changed to sqlitePlugin.openDatabase(), the parameters are different from the old SQLitePlugin() constructor which is now gone. This should a closer resemblance to the HTML5/W3 SQL API. +**NOTE:** Please use sqlitePlugin.openDatabase() to open a database, this will be changed to sqlitePlugin.openDatabase(), the parameters are different from the old SQLitePlugin() constructor which is now gone. This should a closer resemblance to the HTML5/W3 SQL API. ## Coffee Script - db = sqliteNative.openDatabase("my_sqlite_database.sqlite3") + db = sqlitePlugin.openDatabase("my_sqlite_database.sqlite3") db.executeSql('DROP TABLE IF EXISTS test_table') db.executeSql('CREATE TABLE IF NOT EXISTS test_table (id integer primary key, data text, data_num integer)') @@ -127,7 +127,7 @@ Cordova iOS ## Plain Javascript - var db = sqliteNative.openDatabase("my_sqlite_database.sqlite3"); + var db = sqlitePlugin.openDatabase("my_sqlite_database.sqlite3"); db.executeSql('DROP TABLE IF EXISTS test_table'); db.executeSql('CREATE TABLE IF NOT EXISTS test_table (id integer primary key, data text, data_num integer)'); @@ -146,7 +146,7 @@ Cordova iOS ## Changes in tx.executeSql() success callback - var db = sqliteNative.openDatabase("my_sqlite_database.sqlite3"); + var db = sqlitePlugin.openDatabase("my_sqlite_database.sqlite3"); db.executeSql('DROP TABLE IF EXISTS test_table'); db.executeSql('CREATE TABLE IF NOT EXISTS test_table (id integer primary key, data text, data_num integer)'); db.executeSql("INSERT INTO test_table (data, data_num) VALUES (?,?)", ["test", 100], function(res) { From 5b528368580ad8631fc3a6ddb5dafa42a9c21ab4 Mon Sep 17 00:00:00 2001 From: Chris Brody Date: Fri, 6 Apr 2012 06:36:18 +0200 Subject: [PATCH 29/44] Move SQLitePlugin.java into com.phonegap.plugin.sqlitePlugin package --- .../{example/plugin => plugin/sqlitePlugin}/SQLitePlugin.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename DroidGap/src/com/phonegap/{example/plugin => plugin/sqlitePlugin}/SQLitePlugin.java (99%) diff --git a/DroidGap/src/com/phonegap/example/plugin/SQLitePlugin.java b/DroidGap/src/com/phonegap/plugin/sqlitePlugin/SQLitePlugin.java similarity index 99% rename from DroidGap/src/com/phonegap/example/plugin/SQLitePlugin.java rename to DroidGap/src/com/phonegap/plugin/sqlitePlugin/SQLitePlugin.java index 895b632..74a50f3 100644 --- a/DroidGap/src/com/phonegap/example/plugin/SQLitePlugin.java +++ b/DroidGap/src/com/phonegap/plugin/sqlitePlugin/SQLitePlugin.java @@ -5,7 +5,7 @@ * Copyright (c) 2005-2010, Nitobi Software Inc. * Copyright (c) 2010, IBM Corporation */ -package com.phonegap.plugin; +package com.phonegap.plugin.sqlitePlugin; import org.json.JSONArray; import org.json.JSONException; From 068d23bbc3b97283abd5bf8dc1557675450110d3 Mon Sep 17 00:00:00 2001 From: Chris Brody Date: Fri, 6 Apr 2012 07:01:34 +0200 Subject: [PATCH 30/44] Fix Android plugin for Cordova 1.5+ --- DroidGap/assets/www/SQLitePlugin.js | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/DroidGap/assets/www/SQLitePlugin.js b/DroidGap/assets/www/SQLitePlugin.js index d377dcb..9c7f6e5 100644 --- a/DroidGap/assets/www/SQLitePlugin.js +++ b/DroidGap/assets/www/SQLitePlugin.js @@ -130,6 +130,28 @@ DDB.prototype.fail = function(reason, id) { } }; +var mycreateUUID = function() { + return myUUIDcreatePart(4) + '-' + + myUUIDcreatePart(2) + '-' + + myUUIDcreatePart(2) + '-' + + myUUIDcreatePart(2) + '-' + + myUUIDcreatePart(6); +}; + +myUUIDcreatePart = function(length) { + var uuidpart = ""; + var i, uuidchar; + for (i=0; i Date: Mon, 9 Apr 2012 17:04:04 +0200 Subject: [PATCH 31/44] Fix README note about API change --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 70f5321..866881a 100644 --- a/README.md +++ b/README.md @@ -97,7 +97,7 @@ I put the information here for the sake of completeness. I have tested the Droid Cordova iOS ----------- -**NOTE:** Please use sqlitePlugin.openDatabase() to open a database, this will be changed to sqlitePlugin.openDatabase(), the parameters are different from the old SQLitePlugin() constructor which is now gone. This should a closer resemblance to the HTML5/W3 SQL API. +**NOTE:** Please use sqlitePlugin.openDatabase() to open a database, the parameters are different from the old SQLitePlugin() constructor which is now gone. This should a closer resemblance to the HTML5/W3 SQL API. ## Coffee Script From 813ba2b8b775854c1cb1584c11228090b18761c5 Mon Sep 17 00:00:00 2001 From: Chris Brody Date: Mon, 9 Apr 2012 17:43:14 +0200 Subject: [PATCH 32/44] Initial adaptation of Lawnchair adapter from original WebKit version --- .../www/SQLitePlugin.js | 340 ++ .../www/cordova-1.5.0.js | 4841 +++++++++++++++++ .../lawnchair-adapter-test/www/index.html | 181 + .../www/lawnchair-spec.js | 429 ++ .../lawnchair-adapter-test/www/lib/json2.js | 482 ++ .../www/lib/lawnchair.js | 151 + .../lawnchair-adapter-test/www/lib/qunit.css | 225 + .../lawnchair-adapter-test/www/lib/qunit.js | 1448 +++++ .../lawnchair-adapter-test/www/master.css | 96 + .../www/webkit-sqlite.js | 201 + README.md | 2 + 11 files changed, 8396 insertions(+) create mode 100644 DroidGap/lawnchair-adapter-test/www/SQLitePlugin.js create mode 100644 DroidGap/lawnchair-adapter-test/www/cordova-1.5.0.js create mode 100644 DroidGap/lawnchair-adapter-test/www/index.html create mode 100755 DroidGap/lawnchair-adapter-test/www/lawnchair-spec.js create mode 100644 DroidGap/lawnchair-adapter-test/www/lib/json2.js create mode 100644 DroidGap/lawnchair-adapter-test/www/lib/lawnchair.js create mode 100644 DroidGap/lawnchair-adapter-test/www/lib/qunit.css create mode 100644 DroidGap/lawnchair-adapter-test/www/lib/qunit.js create mode 100644 DroidGap/lawnchair-adapter-test/www/master.css create mode 100644 DroidGap/lawnchair-adapter-test/www/webkit-sqlite.js diff --git a/DroidGap/lawnchair-adapter-test/www/SQLitePlugin.js b/DroidGap/lawnchair-adapter-test/www/SQLitePlugin.js new file mode 100644 index 0000000..068def9 --- /dev/null +++ b/DroidGap/lawnchair-adapter-test/www/SQLitePlugin.js @@ -0,0 +1,340 @@ +/* + * PhoneGap is available under *either* the terms of the modified BSD license *or* the + * MIT License (2008). See http://opensource.org/licenses/alphabetical for full text. + * + * Copyright (c) 2005-2010, Nitobi Software Inc. + * Copyright (c) 2010-2011, IBM Corporation + */ + +/* + * This is purely for the Android 1.5/1.6 HTML 5 Storage + * I was hoping that Android 2.0 would deprecate this, but given the fact that + * most manufacturers ship with Android 1.5 and do not do OTA Updates, this is required + */ + + +/** + * SQL result set object + * PRIVATE METHOD + * @constructor + */ +var DDB_Rows = function() { + this.resultSet = []; // results array + this.length = 0; // number of rows +}; + +/** + * Get item from SQL result set + * + * @param row The row number to return + * @return The row object + */ +DDB_Rows.prototype.item = function(row) { + return this.resultSet[row]; +}; + +/** + * SQL result set that is returned to user. + * PRIVATE METHOD + * @constructor + */ +var DDB_Result = function() { + this.rows = new DDB_Rows(); +}; + +/** + * Storage object that is called by native code when performing queries. + * PRIVATE METHOD + * @constructor + */ +var DDB = function() { + this.queryQueue = {}; +}; + +/** + * Callback from native code when query is complete. + * PRIVATE METHOD + * + * @param id Query id + */ +DDB.prototype.completeQuery = function(id, data) { + var query = this.queryQueue[id]; + if (query) { + try { + delete this.queryQueue[id]; + + // Get transaction + var tx = query.tx; + + // If transaction hasn't failed + // Note: We ignore all query results if previous query + // in the same transaction failed. + if (tx && tx.queryList[id]) { + + // Save query results + var r = new DDB_Result(); + r.rows.resultSet = data; + r.rows.length = data.length; + try { + if (typeof query.successCallback === 'function') { + query.successCallback(query.tx, r); + } + } catch (ex) { + console.log("executeSql error calling user success callback: "+ex); + } + + tx.queryComplete(id); + } + } catch (e) { + console.log("executeSql error: "+e); + } + } +}; + +/** + * Callback from native code when query fails + * PRIVATE METHOD + * + * @param reason Error message + * @param id Query id + */ +DDB.prototype.fail = function(reason, id) { + var query = this.queryQueue[id]; + if (query) { + try { + delete this.queryQueue[id]; + + // Get transaction + var tx = query.tx; + + // If transaction hasn't failed + // Note: We ignore all query results if previous query + // in the same transaction failed. + if (tx && tx.queryList[id]) { + tx.queryList = {}; + + try { + if (typeof query.errorCallback === 'function') { + query.errorCallback(query.tx, reason); + } + } catch (ex) { + console.log("executeSql error calling user error callback: "+ex); + } + + tx.queryFailed(id, reason); + } + + } catch (e) { + console.log("executeSql error: "+e); + } + } +}; + +var mycreateUUID = function() { + return myUUIDcreatePart(4) + '-' + + myUUIDcreatePart(2) + '-' + + myUUIDcreatePart(2) + '-' + + myUUIDcreatePart(2) + '-' + + myUUIDcreatePart(6); +}; + +myUUIDcreatePart = function(length) { + var uuidpart = ""; + var i, uuidchar; + for (i=0; i 0) { + eval("var v="+r+";"); + + // If status is OK, then return value back to caller + if (v.status === cordova.callbackStatus.OK) { + + // If there is a success callback, then call it now with + // returned value + if (success) { + try { + success(v.message); + } catch (e) { + console.log("Error in success callback: " + callbackId + " = " + e); + } + + // Clear callback if not expecting any more results + if (!v.keepCallback) { + delete cordova.callbacks[callbackId]; + } + } + return v.message; + } + + // If no result + else if (v.status === cordova.callbackStatus.NO_RESULT) { + // Clear callback if not expecting any more results + if (!v.keepCallback) { + delete cordova.callbacks[callbackId]; + } + } + + // If error, then display error + else { + console.log("Error: Status="+v.status+" Message="+v.message); + + // If there is a fail callback, then call it now with returned value + if (fail) { + try { + fail(v.message); + } + catch (e1) { + console.log("Error in error callback: "+callbackId+" = "+e1); + } + + // Clear callback if not expecting any more results + if (!v.keepCallback) { + delete cordova.callbacks[callbackId]; + } + } + return null; + } + } + } catch (e2) { + console.log("Error: "+e2); + } +}; + +}); +define('cordova/common', function(require, exports, module) { +module.exports = { + objects: { + cordova: { + path: 'cordova', + children: { + exec: { + path: 'cordova/exec' + } + } + }, + navigator: { + children: { + notification: { + path: 'cordova/plugin/notification' + }, + accelerometer: { + path: 'cordova/plugin/accelerometer' + }, + battery: { + path: 'cordova/plugin/battery' + }, + camera:{ + path: 'cordova/plugin/Camera' + }, + compass:{ + path: 'cordova/plugin/compass' + }, + contacts: { + path: 'cordova/plugin/contacts' + }, + device:{ + children:{ + capture: { + path: 'cordova/plugin/capture' + } + } + }, + geolocation: { + path: 'cordova/plugin/geolocation' + }, + network: { + children: { + connection: { + path: 'cordova/plugin/network' + } + } + } + } + }, + Acceleration: { + path: 'cordova/plugin/Acceleration' + }, + Camera:{ + path: 'cordova/plugin/CameraConstants' + }, + CaptureError: { + path: 'cordova/plugin/CaptureError' + }, + CaptureAudioOptions:{ + path: 'cordova/plugin/CaptureAudioOptions' + }, + CaptureImageOptions: { + path: 'cordova/plugin/CaptureImageOptions' + }, + CaptureVideoOptions: { + path: 'cordova/plugin/CaptureVideoOptions' + }, + CompassHeading:{ + path: 'cordova/plugin/CompassHeading' + }, + CompassError:{ + path: 'cordova/plugin/CompassError' + }, + ConfigurationData: { + path: 'cordova/plugin/ConfigurationData' + }, + Connection: { + path: 'cordova/plugin/Connection' + }, + Contact: { + path: 'cordova/plugin/Contact' + }, + ContactAddress: { + path: 'cordova/plugin/ContactAddress' + }, + ContactError: { + path: 'cordova/plugin/ContactError' + }, + ContactField: { + path: 'cordova/plugin/ContactField' + }, + ContactFindOptions: { + path: 'cordova/plugin/ContactFindOptions' + }, + ContactName: { + path: 'cordova/plugin/ContactName' + }, + ContactOrganization: { + path: 'cordova/plugin/ContactOrganization' + }, + Coordinates: { + path: 'cordova/plugin/Coordinates' + }, + DirectoryEntry: { + path: 'cordova/plugin/DirectoryEntry' + }, + DirectoryReader: { + path: 'cordova/plugin/DirectoryReader' + }, + Entry: { + path: 'cordova/plugin/Entry' + }, + File: { + path: 'cordova/plugin/File' + }, + FileEntry: { + path: 'cordova/plugin/FileEntry' + }, + FileError: { + path: 'cordova/plugin/FileError' + }, + FileReader: { + path: 'cordova/plugin/FileReader' + }, + FileSystem: { + path: 'cordova/plugin/FileSystem' + }, + FileTransfer: { + path: 'cordova/plugin/FileTransfer' + }, + FileTransferError: { + path: 'cordova/plugin/FileTransferError' + }, + FileUploadOptions: { + path: 'cordova/plugin/FileUploadOptions' + }, + FileUploadResult: { + path: 'cordova/plugin/FileUploadResult' + }, + FileWriter: { + path: 'cordova/plugin/FileWriter' + }, + Flags: { + path: 'cordova/plugin/Flags' + }, + LocalFileSystem: { + path: 'cordova/plugin/LocalFileSystem' + }, + Media: { + path: 'cordova/plugin/Media' + }, + MediaError: { + path: 'cordova/plugin/MediaError' + }, + MediaFile: { + path: 'cordova/plugin/MediaFile' + }, + MediaFileData:{ + path: 'cordova/plugin/MediaFileData' + }, + Metadata:{ + path: 'cordova/plugin/Metadata' + }, + Position: { + path: 'cordova/plugin/Position' + }, + PositionError: { + path: 'cordova/plugin/PositionError' + }, + ProgressEvent: { + path: 'cordova/plugin/ProgressEvent' + }, + requestFileSystem:{ + path: 'cordova/plugin/requestFileSystem' + }, + resolveLocalFileSystemURI:{ + path: 'cordova/plugin/resolveLocalFileSystemURI' + } + } +}; + +}); +define('cordova/platform', function(require, exports, module) { +module.exports = { + id: "android", + initialize:function() { + var channel = require("cordova/channel"), + cordova = require('cordova'), + callback = require('cordova/plugin/android/callback'), + polling = require('cordova/plugin/android/polling'), + exec = require('cordova/exec'); + + channel.onDestroy.subscribe(function() { + cordova.shuttingDown = true; + }); + + // Start listening for XHR callbacks + // Figure out which bridge approach will work on this Android + // device: polling or XHR-based callbacks + setTimeout(function() { + if (cordova.UsePolling) { + polling(); + } + else { + var isPolling = prompt("usePolling", "gap_callbackServer:"); + cordova.UsePolling = isPolling; + if (isPolling == "true") { + cordova.UsePolling = true; + polling(); + } else { + cordova.UsePolling = false; + callback(); + } + } + }, 1); + + // Inject a listener for the backbutton on the document. + var backButtonChannel = cordova.addDocumentEventHandler('backbutton', { + onSubscribe:function() { + // If we just attached the first handler, let native know we need to override the back button. + if (this.numHandlers === 1) { + exec(null, null, "App", "overrideBackbutton", [true]); + } + }, + onUnsubscribe:function() { + // If we just detached the last handler, let native know we no longer override the back button. + if (this.numHandlers === 0) { + exec(null, null, "App", "overrideBackbutton", [false]); + } + } + }); + + // Add hardware MENU and SEARCH button handlers + cordova.addDocumentEventHandler('menubutton'); + cordova.addDocumentEventHandler('searchbutton'); + + // Let native code know we are all done on the JS side. + // Native code will then un-hide the WebView. + channel.join(function() { + prompt("", "gap_init:"); + }, [channel.onCordovaReady]); + + // Figure out if we need to shim-in localStorage and WebSQL + // support from the native side. + var storage = require('cordova/plugin/android/storage'); + + // First patch WebSQL if necessary + if (typeof window.openDatabase == 'undefined') { + // Not defined, create an openDatabase function for all to use! + window.openDatabase = storage.openDatabase; + } else { + // Defined, but some Android devices will throw a SECURITY_ERR - + // so we wrap the whole thing in a try-catch and shim in our own + // if the device has Android bug 16175. + var originalOpenDatabase = window.openDatabase; + window.openDatabase = function(name, version, desc, size) { + var db = null; + try { + db = originalOpenDatabase(name, version, desc, size); + } + catch (ex) { + db = null; + } + + if (db === null) { + return storage.openDatabase(name, version, desc, size); + } + else { + return db; + } + + }; + } + + // Patch localStorage if necessary + if (typeof window.localStorage == 'undefined' || window.localStorage === null) { + window.localStorage = new storage.CupCakeLocalStorage(); + } + }, + objects: { + cordova: { + children: { + JSCallback:{ + path:"cordova/plugin/android/callback" + }, + JSCallbackPolling:{ + path:"cordova/plugin/android/polling" + } + } + }, + navigator: { + children: { + app:{ + path: "cordova/plugin/android/app" + } + } + }, + device:{ + path: "cordova/plugin/android/device" + }, + File: { // exists natively on Android WebView, override + path: "cordova/plugin/File" + }, + FileReader: { // exists natively on Android WebView, override + path: "cordova/plugin/FileReader" + }, + FileError: { //exists natively on Android WebView on Android 4.x + path: "cordova/plugin/FileError" + } + } +}; + +}); +define('cordova/utils', function(require, exports, module) { +function UUIDcreatePart(length) { + var uuidpart = ""; + for (var i=0; i frequency + 10 sec + exec( + function(timeout) { + if (timeout < (frequency + 10000)) { + exec(null, null, "Accelerometer", "setTimeout", [frequency + 10000]); + } + }, + function(e) { }, "Accelerometer", "getTimeout", []); + + // Start watch timer + var id = utils.createUUID(); + timers[id] = window.setInterval(function() { + exec(successCallback, errorCallback, "Accelerometer", "getAcceleration", []); + }, (frequency ? frequency : 1)); + + return id; + }, + + /** + * Clears the specified accelerometer watch. + * + * @param {String} id The id of the watch returned from #watchAcceleration. + */ + clearWatch: function(id) { + + // Stop javascript timer & remove from timer list + if (id && timers[id] !== undefined) { + window.clearInterval(timers[id]); + delete timers[id]; + } + } +}; + +module.exports = accelerometer; + +}); + + +define('cordova/plugin/battery', function(require, exports, module) { +/** + * This class contains information about the current battery status. + * @constructor + */ +var cordova = require('cordova'), + exec = require('cordova/exec'); + +function handlers() { + return battery.channels.batterystatus.numHandlers + + battery.channels.batterylow.numHandlers + + battery.channels.batterycritical.numHandlers; +} + +var Battery = function() { + this._level = null; + this._isPlugged = null; + // Create new event handlers on the window (returns a channel instance) + var subscriptionEvents = { + onSubscribe:this.onSubscribe, + onUnsubscribe:this.onUnsubscribe + }; + this.channels = { + batterystatus:cordova.addWindowEventHandler("batterystatus", subscriptionEvents), + batterylow:cordova.addWindowEventHandler("batterylow", subscriptionEvents), + batterycritical:cordova.addWindowEventHandler("batterycritical", subscriptionEvents) + }; +}; +/** + * Event handlers for when callbacks get registered for the battery. + * Keep track of how many handlers we have so we can start and stop the native battery listener + * appropriately (and hopefully save on battery life!). + */ +Battery.prototype.onSubscribe = function() { + var me = battery; + // If we just registered the first handler, make sure native listener is started. + if (handlers() === 1) { + exec(me._status, me._error, "Battery", "start", []); + } +}; + +Battery.prototype.onUnsubscribe = function() { + var me = battery; + + // If we just unregistered the last handler, make sure native listener is stopped. + if (handlers() === 0) { + exec(null, null, "Battery", "stop", []); + } +}; + +/** + * Callback for battery status + * + * @param {Object} info keys: level, isPlugged + */ +Battery.prototype._status = function(info) { + if (info) { + var me = battery; + var level = info.level; + if (me._level !== level || me._isPlugged !== info.isPlugged) { + // Fire batterystatus event + cordova.fireWindowEvent("batterystatus", info); + + // Fire low battery event + if (level === 20 || level === 5) { + if (level === 20) { + cordova.fireWindowEvent("batterylow", info); + } + else { + cordova.fireWindowEvent("batterycritical", info); + } + } + } + me._level = level; + me._isPlugged = info.isPlugged; + } +}; + +/** + * Error callback for battery start + */ +Battery.prototype._error = function(e) { + console.log("Error initializing Battery: " + e); +}; + +var battery = new Battery(); + +module.exports = battery; + +}); + + +define('cordova/plugin/Camera', function(require, exports, module) { +var exec = require('cordova/exec'), + Camera = require('cordova/plugin/CameraConstants'); + +var cameraExport = {}; + +// Tack on the Camera Constants to the base camera plugin. +for (var key in Camera) { + cameraExport[key] = Camera[key]; +} + +/** + * Gets a picture from source defined by "options.sourceType", and returns the + * image as defined by the "options.destinationType" option. + + * The defaults are sourceType=CAMERA and destinationType=FILE_URL. + * + * @param {Function} successCallback + * @param {Function} errorCallback + * @param {Object} options + */ +cameraExport.getPicture = function(successCallback, errorCallback, options) { + // successCallback required + if (typeof successCallback != "function") { + console.log("Camera Error: successCallback is not a function"); + return; + } + + // errorCallback optional + if (errorCallback && (typeof errorCallback != "function")) { + console.log("Camera Error: errorCallback is not a function"); + return; + } + + var quality = 50; + if (options && typeof options.quality == "number") { + quality = options.quality; + } else if (options && typeof options.quality == "string") { + var qlity = parseInt(options.quality, 10); + if (isNaN(qlity) === false) { + quality = qlity.valueOf(); + } + } + + var destinationType = Camera.DestinationType.FILE_URL; + if (typeof options.destinationType == "number") { + destinationType = options.destinationType; + } + + var sourceType = Camera.PictureSourceType.CAMERA; + if (typeof options.sourceType == "number") { + sourceType = options.sourceType; + } + + var targetWidth = -1; + if (typeof options.targetWidth == "number") { + targetWidth = options.targetWidth; + } else if (typeof options.targetWidth == "string") { + var width = parseInt(options.targetWidth, 10); + if (isNaN(width) === false) { + targetWidth = width.valueOf(); + } + } + + var targetHeight = -1; + if (typeof options.targetHeight == "number") { + targetHeight = options.targetHeight; + } else if (typeof options.targetHeight == "string") { + var height = parseInt(options.targetHeight, 10); + if (isNaN(height) === false) { + targetHeight = height.valueOf(); + } + } + + var encodingType = Camera.EncodingType.JPEG; + if (typeof options.encodingType == "number") { + encodingType = options.encodingType; + } + // TODO: parse MediaType + // TODO: enable allow edit? + + exec(successCallback, errorCallback, "Camera", "takePicture", [quality, destinationType, sourceType, targetWidth, targetHeight, encodingType]); +} + +module.exports = cameraExport; + +}); + +define('cordova/plugin/CameraConstants', function(require, exports, module) { +module.exports = { + DestinationType:{ + DATA_URL: 0, // Return base64 encoded string + FILE_URI: 1 // Return file uri (content://media/external/images/media/2 for Android) + }, + EncodingType:{ + JPEG: 0, // Return JPEG encoded image + PNG: 1 // Return PNG encoded image + }, + MediaType:{ + PICTURE: 0, // allow selection of still pictures only. DEFAULT. Will return format specified via DestinationType + VIDEO: 1, // allow selection of video only, ONLY RETURNS URL + ALLMEDIA : 2 // allow selection from all media types + }, + PictureSourceType:{ + PHOTOLIBRARY : 0, // Choose image from picture library (same as SAVEDPHOTOALBUM for Android) + CAMERA : 1, // Take picture from camera + SAVEDPHOTOALBUM : 2 // Choose image from picture library (same as PHOTOLIBRARY for Android) + } +}; + +}); + +define('cordova/plugin/capture', function(require, exports, module) { +var exec = require('cordova/exec'), + MediaFile = require('cordova/plugin/MediaFile'); + +/** + * Launches a capture of different types. + * + * @param (DOMString} type + * @param {Function} successCB + * @param {Function} errorCB + * @param {CaptureVideoOptions} options + */ +function _capture(type, successCallback, errorCallback, options) { + var win = function(pluginResult) { + var mediaFiles = []; + var i; + for (i = 0; i < pluginResult.length; i++) { + var mediaFile = new MediaFile(); + mediaFile.name = pluginResult[i].name; + mediaFile.fullPath = pluginResult[i].fullPath; + mediaFile.type = pluginResult[i].type; + mediaFile.lastModifiedDate = pluginResult[i].lastModifiedDate; + mediaFile.size = pluginResult[i].size; + mediaFiles.push(mediaFile); + } + successCallback(mediaFiles); + }; + exec(win, errorCallback, "Capture", type, [options]); +} +/** + * The Capture interface exposes an interface to the camera and microphone of the hosting device. + */ +function Capture() { + this.supportedAudioModes = []; + this.supportedImageModes = []; + this.supportedVideoModes = []; +} + +/** + * Launch audio recorder application for recording audio clip(s). + * + * @param {Function} successCB + * @param {Function} errorCB + * @param {CaptureAudioOptions} options + */ +Capture.prototype.captureAudio = function(successCallback, errorCallback, options){ + _capture("captureAudio", successCallback, errorCallback, options); +}; + +/** + * Launch camera application for taking image(s). + * + * @param {Function} successCB + * @param {Function} errorCB + * @param {CaptureImageOptions} options + */ +Capture.prototype.captureImage = function(successCallback, errorCallback, options){ + _capture("captureImage", successCallback, errorCallback, options); +}; + +/** + * Launch device camera application for recording video(s). + * + * @param {Function} successCB + * @param {Function} errorCB + * @param {CaptureVideoOptions} options + */ +Capture.prototype.captureVideo = function(successCallback, errorCallback, options){ + _capture("captureVideo", successCallback, errorCallback, options); +}; + + +module.exports = new Capture(); + +}); + +define('cordova/plugin/CaptureAudioOptions', function(require, exports, module) { +/** + * Encapsulates all audio capture operation configuration options. + */ +var CaptureAudioOptions = function(){ + // Upper limit of sound clips user can record. Value must be equal or greater than 1. + this.limit = 1; + // Maximum duration of a single sound clip in seconds. + this.duration = 0; + // The selected audio mode. Must match with one of the elements in supportedAudioModes array. + this.mode = null; +}; + +module.exports = CaptureAudioOptions; + +}); + +define('cordova/plugin/CaptureError', function(require, exports, module) { +/** + * The CaptureError interface encapsulates all errors in the Capture API. + */ +var CaptureError = function(c) { + this.code = c || null; +}; + +// Camera or microphone failed to capture image or sound. +CaptureError.CAPTURE_INTERNAL_ERR = 0; +// Camera application or audio capture application is currently serving other capture request. +CaptureError.CAPTURE_APPLICATION_BUSY = 1; +// Invalid use of the API (e.g. limit parameter has value less than one). +CaptureError.CAPTURE_INVALID_ARGUMENT = 2; +// User exited camera application or audio capture application before capturing anything. +CaptureError.CAPTURE_NO_MEDIA_FILES = 3; +// The requested capture operation is not supported. +CaptureError.CAPTURE_NOT_SUPPORTED = 20; + +module.exports = CaptureError; + +}); + +define('cordova/plugin/CaptureImageOptions', function(require, exports, module) { +/** + * Encapsulates all image capture operation configuration options. + */ +var CaptureImageOptions = function(){ + // Upper limit of images user can take. Value must be equal or greater than 1. + this.limit = 1; + // The selected image mode. Must match with one of the elements in supportedImageModes array. + this.mode = null; +}; + +module.exports = CaptureImageOptions; + +}); + +define('cordova/plugin/CaptureVideoOptions', function(require, exports, module) { +/** + * Encapsulates all video capture operation configuration options. + */ +var CaptureVideoOptions = function(){ + // Upper limit of videos user can record. Value must be equal or greater than 1. + this.limit = 1; + // Maximum duration of a single video clip in seconds. + this.duration = 0; + // The selected video mode. Must match with one of the elements in supportedVideoModes array. + this.mode = null; +}; + +module.exports = CaptureVideoOptions; + +}); + +define('cordova/plugin/compass', function(require, exports, module) { +var exec = require('cordova/exec'), + utils = require('cordova/utils'), + CompassHeading = require('cordova/plugin/CompassHeading'), + CompassError = require('cordova/plugin/CompassError'), + timers = {}, + compass = { + /** + * Asynchronously acquires the current heading. + * @param {Function} successCallback The function to call when the heading + * data is available + * @param {Function} errorCallback The function to call when there is an error + * getting the heading data. + * @param {CompassOptions} options The options for getting the heading data (not used). + */ + getCurrentHeading:function(successCallback, errorCallback, options) { + // successCallback required + if (typeof successCallback !== "function") { + console.log("Compass Error: successCallback is not a function"); + return; + } + + // errorCallback optional + if (errorCallback && (typeof errorCallback !== "function")) { + console.log("Compass Error: errorCallback is not a function"); + return; + } + + var win = function(result) { + var ch = new CompassHeading(result.magneticHeading, result.trueHeading, result.headingAccuracy, result.timestamp); + successCallback(ch); + }; + var fail = function(code) { + var ce = new CompassError(code); + errorCallback(ce); + } + + // Get heading + exec(win, fail, "Compass", "getHeading", []); + }, + + /** + * Asynchronously acquires the heading repeatedly at a given interval. + * @param {Function} successCallback The function to call each time the heading + * data is available + * @param {Function} errorCallback The function to call when there is an error + * getting the heading data. + * @param {HeadingOptions} options The options for getting the heading data + * such as timeout and the frequency of the watch. + */ + watchHeading:function(successCallback, errorCallback, options) { + // Default interval (100 msec) + var frequency = (options !== undefined && options.frequency !== undefined) ? options.frequency : 100; + + // successCallback required + if (typeof successCallback !== "function") { + console.log("Compass Error: successCallback is not a function"); + return; + } + + // errorCallback optional + if (errorCallback && (typeof errorCallback !== "function")) { + console.log("Compass Error: errorCallback is not a function"); + return; + } + + // Start watch timer to get headings + var id = utils.createUUID(); + var win = function(result) { + var ch = new CompassHeading(result.magneticHeading, result.trueHeading, result.headingAccuracy, result.timestamp); + successCallback(ch); + }; + var fail = function(code) { + var ce = new CompassError(code); + errorCallback(ce); + }; + + timers[id] = window.setInterval(function() { + exec(win, fail, "Compass", "getHeading", []); + }, frequency); + + return id; + }, + + /** + * Clears the specified heading watch. + * @param {String} watchId The ID of the watch returned from #watchHeading. + */ + clearWatch:function(id) { + // Stop javascript timer & remove from timer list + if (id && timers[id]) { + clearInterval(timers[id]); + delete timers[id]; + } + } + // TODO: add the filter-based iOS-only methods + }; + +module.exports = compass; + +}); + +define('cordova/plugin/CompassError', function(require, exports, module) { +/** + * CompassError. + * An error code assigned by an implementation when an error has occured + * @constructor + */ +var CompassError = function(err) { + this.code = (typeof err != 'undefined' ? err : null); +}; + +/** + * Error codes + */ +CompassError.COMPASS_INTERNAL_ERR = 0; +CompassError.COMPASS_NOT_SUPPORTED = 20; + +module.exports = CompassError; + + +}); + +define('cordova/plugin/CompassHeading', function(require, exports, module) { +var CompassHeading = function(magneticHeading, trueHeading, headingAccuracy, timestamp) { + this.magneticHeading = magneticHeading !== undefined ? magneticHeading : null; + this.trueHeading = trueHeading !== undefined ? trueHeading : null; + this.headingAccuracy = headingAccuracy !== undefined ? headingAccuracy : null; + this.timestamp = timestamp !== undefined ? new Date(timestamp) : new Date(); +}; + +module.exports = CompassHeading; + +}); + +define('cordova/plugin/ConfigurationData', function(require, exports, module) { +/** + * Encapsulates a set of parameters that the capture device supports. + */ +function ConfigurationData() { + // The ASCII-encoded string in lower case representing the media type. + this.type = null; + // The height attribute represents height of the image or video in pixels. + // In the case of a sound clip this attribute has value 0. + this.height = 0; + // The width attribute represents width of the image or video in pixels. + // In the case of a sound clip this attribute has value 0 + this.width = 0; +} + +module.exports = ConfigurationData; + +}); + +define('cordova/plugin/Connection', function(require, exports, module) { +/** + * Network status + */ +module.exports = { + UNKNOWN: "unknown", + ETHERNET: "ethernet", + WIFI: "wifi", + CELL_2G: "2g", + CELL_3G: "3g", + CELL_4G: "4g", + NONE: "none" +}; + +}); + +define('cordova/plugin/Contact', function(require, exports, module) { +var exec = require('cordova/exec'), + ContactError = require('cordova/plugin/ContactError'), + utils = require('cordova/utils'); + +/** +* Contains information about a single contact. +* @constructor +* @param {DOMString} id unique identifier +* @param {DOMString} displayName +* @param {ContactName} name +* @param {DOMString} nickname +* @param {Array.} phoneNumbers array of phone numbers +* @param {Array.} emails array of email addresses +* @param {Array.} addresses array of addresses +* @param {Array.} ims instant messaging user ids +* @param {Array.} organizations +* @param {DOMString} birthday contact's birthday +* @param {DOMString} note user notes about contact +* @param {Array.} photos +* @param {Array.} categories +* @param {Array.} urls contact's web sites +*/ +var Contact = function (id, displayName, name, nickname, phoneNumbers, emails, addresses, + ims, organizations, birthday, note, photos, categories, urls) { + this.id = id || null; + this.rawId = null; + this.displayName = displayName || null; + this.name = name || null; // ContactName + this.nickname = nickname || null; + this.phoneNumbers = phoneNumbers || []; // ContactField[] + this.emails = emails || []; // ContactField[] + this.addresses = addresses || []; // ContactAddress[] + this.ims = ims || []; // ContactField[] + this.organizations = organizations || []; // ContactOrganization[] + this.birthday = birthday || null; + this.note = note || null; + this.photos = photos || []; // ContactField[] + this.categories = categories || []; // ContactField[] + this.urls = urls || []; // ContactField[] +}; + +/** +* Removes contact from device storage. +* @param successCB success callback +* @param errorCB error callback +*/ +Contact.prototype.remove = function(successCB, errorCB) { + if (this.id === null) { + var errorObj = new ContactError(ContactError.UNKNOWN_ERROR); + errorCB(errorObj); + } + else { + exec(successCB, errorCB, "Contacts", "remove", [this.id]); + } +}; + +/** +* Creates a deep copy of this Contact. +* With the contact ID set to null. +* @return copy of this Contact +*/ +Contact.prototype.clone = function() { + var clonedContact = utils.clone(this); + var i; + clonedContact.id = null; + clonedContact.rawId = null; + // Loop through and clear out any id's in phones, emails, etc. + if (clonedContact.phoneNumbers) { + for (i = 0; i < clonedContact.phoneNumbers.length; i++) { + clonedContact.phoneNumbers[i].id = null; + } + } + if (clonedContact.emails) { + for (i = 0; i < clonedContact.emails.length; i++) { + clonedContact.emails[i].id = null; + } + } + if (clonedContact.addresses) { + for (i = 0; i < clonedContact.addresses.length; i++) { + clonedContact.addresses[i].id = null; + } + } + if (clonedContact.ims) { + for (i = 0; i < clonedContact.ims.length; i++) { + clonedContact.ims[i].id = null; + } + } + if (clonedContact.organizations) { + for (i = 0; i < clonedContact.organizations.length; i++) { + clonedContact.organizations[i].id = null; + } + } + if (clonedContact.categories) { + for (i = 0; i < clonedContact.categories.length; i++) { + clonedContact.categories[i].id = null; + } + } + if (clonedContact.photos) { + for (i = 0; i < clonedContact.photos.length; i++) { + clonedContact.photos[i].id = null; + } + } + if (clonedContact.urls) { + for (i = 0; i < clonedContact.urls.length; i++) { + clonedContact.urls[i].id = null; + } + } + return clonedContact; +}; + +/** +* Persists contact to device storage. +* @param successCB success callback +* @param errorCB error callback +*/ +Contact.prototype.save = function(successCB, errorCB) { + exec(successCB, errorCB, "Contacts", "save", [this]); +}; + + +module.exports = Contact; + +}); + +define('cordova/plugin/ContactAddress', function(require, exports, module) { +/** +* Contact address. +* @constructor +* @param {DOMString} id unique identifier, should only be set by native code +* @param formatted // NOTE: not a W3C standard +* @param streetAddress +* @param locality +* @param region +* @param postalCode +* @param country +*/ + +var ContactAddress = function(pref, type, formatted, streetAddress, locality, region, postalCode, country) { + this.id = null; + this.pref = (typeof pref != 'undefined' ? pref : false); + this.type = type || null; + this.formatted = formatted || null; + this.streetAddress = streetAddress || null; + this.locality = locality || null; + this.region = region || null; + this.postalCode = postalCode || null; + this.country = country || null; +}; + +module.exports = ContactAddress; + +}); + +define('cordova/plugin/ContactError', function(require, exports, module) { +/** + * ContactError. + * An error code assigned by an implementation when an error has occured + * @constructor + */ +var ContactError = function(err) { + this.code = (typeof err != 'undefined' ? err : null); +}; + +/** + * Error codes + */ +ContactError.UNKNOWN_ERROR = 0; +ContactError.INVALID_ARGUMENT_ERROR = 1; +ContactError.TIMEOUT_ERROR = 2; +ContactError.PENDING_OPERATION_ERROR = 3; +ContactError.IO_ERROR = 4; +ContactError.NOT_SUPPORTED_ERROR = 5; +ContactError.PERMISSION_DENIED_ERROR = 20; + +module.exports = ContactError; + +}); + +define('cordova/plugin/ContactField', function(require, exports, module) { +/** +* Generic contact field. +* @constructor +* @param {DOMString} id unique identifier, should only be set by native code // NOTE: not a W3C standard +* @param type +* @param value +* @param pref +*/ +var ContactField = function(type, value, pref) { + this.id = null; + this.type = type || null; + this.value = value || null; + this.pref = (typeof pref != 'undefined' ? pref : false); +}; + +module.exports = ContactField; + +}); + +define('cordova/plugin/ContactFindOptions', function(require, exports, module) { +/** + * ContactFindOptions. + * @constructor + * @param filter used to match contacts against + * @param multiple boolean used to determine if more than one contact should be returned + */ + +var ContactFindOptions = function(filter, multiple) { + this.filter = filter || ''; + this.multiple = (typeof multiple != 'undefined' ? multiple : false); +}; + +module.exports = ContactFindOptions; + +}); + +define('cordova/plugin/ContactName', function(require, exports, module) { +/** +* Contact name. +* @constructor +* @param formatted // NOTE: not part of W3C standard +* @param familyName +* @param givenName +* @param middle +* @param prefix +* @param suffix +*/ +var ContactName = function(formatted, familyName, givenName, middle, prefix, suffix) { + this.formatted = formatted || null; + this.familyName = familyName || null; + this.givenName = givenName || null; + this.middleName = middle || null; + this.honorificPrefix = prefix || null; + this.honorificSuffix = suffix || null; +}; + +module.exports = ContactName; + +}); + +define('cordova/plugin/ContactOrganization', function(require, exports, module) { +/** +* Contact organization. +* @constructor +* @param {DOMString} id unique identifier, should only be set by native code // NOTE: not a W3C standard +* @param name +* @param dept +* @param title +* @param startDate +* @param endDate +* @param location +* @param desc +*/ + +var ContactOrganization = function(pref, type, name, dept, title) { + this.id = null; + this.pref = (typeof pref != 'undefined' ? pref : false); + this.type = type || null; + this.name = name || null; + this.department = dept || null; + this.title = title || null; +}; + +module.exports = ContactOrganization; + +}); + +define('cordova/plugin/contacts', function(require, exports, module) { +var exec = require('cordova/exec'), + ContactError = require('cordova/plugin/ContactError'), + Contact = require('cordova/plugin/Contact'); + +/** +* Represents a group of Contacts. +* @constructor +*/ +var contacts = { + /** + * Returns an array of Contacts matching the search criteria. + * @param fields that should be searched + * @param successCB success callback + * @param errorCB error callback + * @param {ContactFindOptions} options that can be applied to contact searching + * @return array of Contacts matching search criteria + */ + find:function(fields, successCB, errorCB, options) { + if (!successCB) { + throw new TypeError("You must specify a success callback for the find command."); + } + if (!fields || (fields instanceof Array && fields.length === 0)) { + if (typeof errorCB === "function") { + errorCB(new ContactError(ContactError.INVALID_ARGUMENT_ERROR)); + } + } else { + var win = function(result) { + var cs = []; + for (var i = 0, l = result.length; i < l; i++) { + cs.push(contacts.create(result[i])); + } + successCB(cs); + }; + exec(win, errorCB, "Contacts", "search", [fields, options]); + } + }, + + /** + * This function creates a new contact, but it does not persist the contact + * to device storage. To persist the contact to device storage, invoke + * contact.save(). + * @param properties an object who's properties will be examined to create a new Contact + * @returns new Contact object + */ + create:function(properties) { + var i; + var contact = new Contact(); + for (i in properties) { + if (typeof contact[i] !== 'undefined' && properties.hasOwnProperty(i)) { + contact[i] = properties[i]; + } + } + return contact; + } +}; + +module.exports = contacts; + +}); + +define('cordova/plugin/Coordinates', function(require, exports, module) { +/** + * This class contains position information. + * @param {Object} lat + * @param {Object} lng + * @param {Object} alt + * @param {Object} acc + * @param {Object} head + * @param {Object} vel + * @param {Object} altacc + * @constructor + */ +var Coordinates = function(lat, lng, alt, acc, head, vel, altacc) { + /** + * The latitude of the position. + */ + this.latitude = lat; + /** + * The longitude of the position, + */ + this.longitude = lng; + /** + * The accuracy of the position. + */ + this.accuracy = acc; + /** + * The altitude of the position. + */ + this.altitude = alt; + /** + * The direction the device is moving at the position. + */ + this.heading = head; + /** + * The velocity with which the device is moving at the position. + */ + this.speed = vel; + /** + * The altitude accuracy of the position. + */ + this.altitudeAccuracy = (altacc !== undefined) ? altacc : null; +}; + +module.exports = Coordinates; + +}); + +define('cordova/plugin/DirectoryEntry', function(require, exports, module) { +var utils = require('cordova/utils'), + exec = require('cordova/exec'), + Entry = require('cordova/plugin/Entry'), + DirectoryReader = require('cordova/plugin/DirectoryReader'); + +/** + * An interface representing a directory on the file system. + * + * {boolean} isFile always false (readonly) + * {boolean} isDirectory always true (readonly) + * {DOMString} name of the directory, excluding the path leading to it (readonly) + * {DOMString} fullPath the absolute full path to the directory (readonly) + * {FileSystem} filesystem on which the directory resides (readonly) + */ +var DirectoryEntry = function(name, fullPath) { + DirectoryEntry.__super__.constructor.apply(this, [false, true, name, fullPath]); +}; + +utils.extend(DirectoryEntry, Entry); + +/** + * Creates a new DirectoryReader to read entries from this directory + */ +DirectoryEntry.prototype.createReader = function() { + return new DirectoryReader(this.fullPath); +}; + +/** + * Creates or looks up a directory + * + * @param {DOMString} path either a relative or absolute path from this directory in which to look up or create a directory + * @param {Flags} options to create or excluively create the directory + * @param {Function} successCallback is called with the new entry + * @param {Function} errorCallback is called with a FileError + */ +DirectoryEntry.prototype.getDirectory = function(path, options, successCallback, errorCallback) { + var win = typeof successCallback !== 'function' ? null : function(result) { + var entry = new DirectoryEntry(result.name, result.fullPath); + successCallback(entry); + }; + var fail = typeof errorCallback !== 'function' ? null : function(code) { + errorCallback(new FileError(code)); + }; + exec(win, fail, "File", "getDirectory", [this.fullPath, path, options]); +}; + +/** + * Deletes a directory and all of it's contents + * + * @param {Function} successCallback is called with no parameters + * @param {Function} errorCallback is called with a FileError + */ +DirectoryEntry.prototype.removeRecursively = function(successCallback, errorCallback) { + var fail = typeof errorCallback !== 'function' ? null : function(code) { + errorCallback(new FileError(code)); + }; + exec(successCallback, fail, "File", "removeRecursively", [this.fullPath]); +}; + +/** + * Creates or looks up a file + * + * @param {DOMString} path either a relative or absolute path from this directory in which to look up or create a file + * @param {Flags} options to create or excluively create the file + * @param {Function} successCallback is called with the new entry + * @param {Function} errorCallback is called with a FileError + */ +DirectoryEntry.prototype.getFile = function(path, options, successCallback, errorCallback) { + var win = typeof successCallback !== 'function' ? null : function(result) { + var FileEntry = require('cordova/plugin/FileEntry'); + var entry = new FileEntry(result.name, result.fullPath); + successCallback(entry); + }; + var fail = typeof errorCallback !== 'function' ? null : function(code) { + errorCallback(new FileError(code)); + }; + exec(win, fail, "File", "getFile", [this.fullPath, path, options]); +}; + +module.exports = DirectoryEntry; + +}); + +define('cordova/plugin/DirectoryReader', function(require, exports, module) { +var exec = require('cordova/exec'); + +/** + * An interface that lists the files and directories in a directory. + */ +function DirectoryReader(path) { + this.path = path || null; +} + +/** + * Returns a list of entries from a directory. + * + * @param {Function} successCallback is called with a list of entries + * @param {Function} errorCallback is called with a FileError + */ +DirectoryReader.prototype.readEntries = function(successCallback, errorCallback) { + var win = typeof successCallback !== 'function' ? null : function(result) { + var retVal = []; + for (var i=0; i][;base64], + * + * @param file {File} File object containing file properties + */ +FileReader.prototype.readAsDataURL = function(file) { + this.fileName = ""; + if (typeof file.fullPath === "undefined") { + this.fileName = file; + } else { + this.fileName = file.fullPath; + } + + // Already loading something + if (this.readyState == FileReader.LOADING) { + throw new FileError(FileError.INVALID_STATE_ERR); + } + + // LOADING state + this.readyState = FileReader.LOADING; + + // If loadstart callback + if (typeof this.onloadstart === "function") { + this.onloadstart(new ProgressEvent("loadstart", {target:this})); + } + + var me = this; + + // Read file + exec( + // Success callback + function(r) { + // If DONE (cancelled), then don't do anything + if (me.readyState === FileReader.DONE) { + return; + } + + // DONE state + me.readyState = FileReader.DONE; + + // Save result + me.result = r; + + // If onload callback + if (typeof me.onload === "function") { + me.onload(new ProgressEvent("load", {target:me})); + } + + // If onloadend callback + if (typeof me.onloadend === "function") { + me.onloadend(new ProgressEvent("loadend", {target:me})); + } + }, + // Error callback + function(e) { + // If DONE (cancelled), then don't do anything + if (me.readyState === FileReader.DONE) { + return; + } + + // DONE state + me.readyState = FileReader.DONE; + + me.result = null; + + // Save error + me.error = new FileError(e); + + // If onerror callback + if (typeof me.onerror === "function") { + me.onerror(new ProgressEvent("error", {target:me})); + } + + // If onloadend callback + if (typeof me.onloadend === "function") { + me.onloadend(new ProgressEvent("loadend", {target:me})); + } + }, "File", "readAsDataURL", [this.fileName]); +}; + +/** + * Read file and return data as a binary data. + * + * @param file {File} File object containing file properties + */ +FileReader.prototype.readAsBinaryString = function(file) { + // TODO - Can't return binary data to browser. + console.log('This method is not supported at this time.'); +}; + +/** + * Read file and return data as a binary data. + * + * @param file {File} File object containing file properties + */ +FileReader.prototype.readAsArrayBuffer = function(file) { + // TODO - Can't return binary data to browser. + console.log('This method is not supported at this time.'); +}; + +module.exports = FileReader; + +}); + +define('cordova/plugin/FileSystem', function(require, exports, module) { +var DirectoryEntry = require('cordova/plugin/DirectoryEntry'); + +/** + * An interface representing a file system + * + * @constructor + * {DOMString} name the unique name of the file system (readonly) + * {DirectoryEntry} root directory of the file system (readonly) + */ +var FileSystem = function(name, root) { + this.name = name || null; + if (root) { + this.root = new DirectoryEntry(root.name, root.fullPath); + } +}; + +module.exports = FileSystem; + +}); + +define('cordova/plugin/FileTransfer', function(require, exports, module) { +var exec = require('cordova/exec'); + +/** + * FileTransfer uploads a file to a remote server. + * @constructor + */ +var FileTransfer = function() {}; + +/** +* Given an absolute file path, uploads a file on the device to a remote server +* using a multipart HTTP request. +* @param filePath {String} Full path of the file on the device +* @param server {String} URL of the server to receive the file +* @param successCallback (Function} Callback to be invoked when upload has completed +* @param errorCallback {Function} Callback to be invoked upon error +* @param options {FileUploadOptions} Optional parameters such as file name and mimetype +*/ +FileTransfer.prototype.upload = function(filePath, server, successCallback, errorCallback, options, debug) { + // check for options + var fileKey = null; + var fileName = null; + var mimeType = null; + var params = null; + var chunkedMode = true; + if (options) { + fileKey = options.fileKey; + fileName = options.fileName; + mimeType = options.mimeType; + if (options.chunkedMode !== null || typeof options.chunkedMode !== "undefined") { + chunkedMode = options.chunkedMode; + } + if (options.params) { + params = options.params; + } + else { + params = {}; + } + } + + exec(successCallback, errorCallback, 'FileTransfer', 'upload', [filePath, server, fileKey, fileName, mimeType, params, debug, chunkedMode]); +}; + +/** + * Downloads a file form a given URL and saves it to the specified directory. + * @param source {String} URL of the server to receive the file + * @param target {String} Full path of the file on the device + * @param successCallback (Function} Callback to be invoked when upload has completed + * @param errorCallback {Function} Callback to be invoked upon error + */ +FileTransfer.prototype.download = function(source, target, successCallback, errorCallback) { + var win = function(result) { + var entry = null; + if (result.isDirectory) { + entry = new DirectoryEntry(); + } + else if (result.isFile) { + entry = new FileEntry(); + } + entry.isDirectory = result.isDirectory; + entry.isFile = result.isFile; + entry.name = result.name; + entry.fullPath = result.fullPath; + successCallback(entry); + }; + exec(win, errorCallback, 'FileTransfer', 'download', [source, target]); +}; + +module.exports = FileTransfer; + +}); + +define('cordova/plugin/FileTransferError', function(require, exports, module) { +/** + * FileTransferError + * @constructor + */ +var FileTransferError = function(code) { + this.code = code || null; +}; + +FileTransferError.FILE_NOT_FOUND_ERR = 1; +FileTransferError.INVALID_URL_ERR = 2; +FileTransferError.CONNECTION_ERR = 3; + +module.exports = FileTransferError; + +}); + +define('cordova/plugin/FileUploadOptions', function(require, exports, module) { +/** + * Options to customize the HTTP request used to upload files. + * @constructor + * @param fileKey {String} Name of file request parameter. + * @param fileName {String} Filename to be used by the server. Defaults to image.jpg. + * @param mimeType {String} Mimetype of the uploaded file. Defaults to image/jpeg. + * @param params {Object} Object with key: value params to send to the server. + */ +var FileUploadOptions = function(fileKey, fileName, mimeType, params) { + this.fileKey = fileKey || null; + this.fileName = fileName || null; + this.mimeType = mimeType || null; + this.params = params || null; +}; + +module.exports = FileUploadOptions; + +}); + +define('cordova/plugin/FileUploadResult', function(require, exports, module) { +/** + * FileUploadResult + * @constructor + */ +var FileUploadResult = function() { + this.bytesSent = 0; + this.responseCode = null; + this.response = null; +}; + +module.exports = FileUploadResult; + +}); + +define('cordova/plugin/FileWriter', function(require, exports, module) { +var exec = require('cordova/exec'), + FileError = require('cordova/plugin/FileError'); + ProgressEvent = require('cordova/plugin/ProgressEvent'); + +/** + * This class writes to the mobile device file system. + * + * For Android: + * The root directory is the root of the file system. + * To write to the SD card, the file name is "sdcard/my_file.txt" + * + * @constructor + * @param file {File} File object containing file properties + * @param append if true write to the end of the file, otherwise overwrite the file + */ +var FileWriter = function(file) { + this.fileName = ""; + this.length = 0; + if (file) { + this.fileName = file.fullPath || file; + this.length = file.size || 0; + } + // default is to write at the beginning of the file + this.position = 0; + + this.readyState = 0; // EMPTY + + this.result = null; + + // Error + this.error = null; + + // Event handlers + this.onwritestart = null; // When writing starts + this.onprogress = null; // While writing the file, and reporting partial file data + this.onwrite = null; // When the write has successfully completed. + this.onwriteend = null; // When the request has completed (either in success or failure). + this.onabort = null; // When the write has been aborted. For instance, by invoking the abort() method. + this.onerror = null; // When the write has failed (see errors). +}; + +// States +FileWriter.INIT = 0; +FileWriter.WRITING = 1; +FileWriter.DONE = 2; + +/** + * Abort writing file. + */ +FileWriter.prototype.abort = function() { + // check for invalid state + if (this.readyState === FileWriter.DONE || this.readyState === FileWriter.INIT) { + throw new FileError(FileError.INVALID_STATE_ERR); + } + + // set error + this.error = new FileError(FileError.ABORT_ERR); + + this.readyState = FileWriter.DONE; + + // If abort callback + if (typeof this.onabort === "function") { + this.onabort(new ProgressEvent("abort", {"target":this})); + } + + // If write end callback + if (typeof this.onwriteend === "function") { + this.onwriteend(new ProgressEvent("writeend", {"target":this})); + } +}; + +/** + * Writes data to the file + * + * @param text to be written + */ +FileWriter.prototype.write = function(text) { + // Throw an exception if we are already writing a file + if (this.readyState === FileWriter.WRITING) { + throw new FileError(FileError.INVALID_STATE_ERR); + } + + // WRITING state + this.readyState = FileWriter.WRITING; + + var me = this; + + // If onwritestart callback + if (typeof me.onwritestart === "function") { + me.onwritestart(new ProgressEvent("writestart", {"target":me})); + } + + // Write file + exec( + // Success callback + function(r) { + // If DONE (cancelled), then don't do anything + if (me.readyState === FileWriter.DONE) { + return; + } + + // position always increases by bytes written because file would be extended + me.position += r; + // The length of the file is now where we are done writing. + + me.length = me.position; + + // DONE state + me.readyState = FileWriter.DONE; + + // If onwrite callback + if (typeof me.onwrite === "function") { + me.onwrite(new ProgressEvent("write", {"target":me})); + } + + // If onwriteend callback + if (typeof me.onwriteend === "function") { + me.onwriteend(new ProgressEvent("writeend", {"target":me})); + } + }, + // Error callback + function(e) { + // If DONE (cancelled), then don't do anything + if (me.readyState === FileWriter.DONE) { + return; + } + + // DONE state + me.readyState = FileWriter.DONE; + + // Save error + me.error = new FileError(e); + + // If onerror callback + if (typeof me.onerror === "function") { + me.onerror(new ProgressEvent("error", {"target":me})); + } + + // If onwriteend callback + if (typeof me.onwriteend === "function") { + me.onwriteend(new ProgressEvent("writeend", {"target":me})); + } + }, "File", "write", [this.fileName, text, this.position]); +}; + +/** + * Moves the file pointer to the location specified. + * + * If the offset is a negative number the position of the file + * pointer is rewound. If the offset is greater than the file + * size the position is set to the end of the file. + * + * @param offset is the location to move the file pointer to. + */ +FileWriter.prototype.seek = function(offset) { + // Throw an exception if we are already writing a file + if (this.readyState === FileWriter.WRITING) { + throw new FileError(FileError.INVALID_STATE_ERR); + } + + if (!offset) { + return; + } + + // See back from end of file. + if (offset < 0) { + this.position = Math.max(offset + this.length, 0); + } + // Offset is bigger then file size so set position + // to the end of the file. + else if (offset > this.length) { + this.position = this.length; + } + // Offset is between 0 and file size so set the position + // to start writing. + else { + this.position = offset; + } +}; + +/** + * Truncates the file to the size specified. + * + * @param size to chop the file at. + */ +FileWriter.prototype.truncate = function(size) { + // Throw an exception if we are already writing a file + if (this.readyState === FileWriter.WRITING) { + throw new FileError(FileError.INVALID_STATE_ERR); + } + + // WRITING state + this.readyState = FileWriter.WRITING; + + var me = this; + + // If onwritestart callback + if (typeof me.onwritestart === "function") { + me.onwritestart(new ProgressEvent("writestart", {"target":this})); + } + + // Write file + exec( + // Success callback + function(r) { + // If DONE (cancelled), then don't do anything + if (me.readyState === FileWriter.DONE) { + return; + } + + // DONE state + me.readyState = FileWriter.DONE; + + // Update the length of the file + me.length = r; + me.position = Math.min(me.position, r); + + // If onwrite callback + if (typeof me.onwrite === "function") { + me.onwrite(new ProgressEvent("write", {"target":me})); + } + + // If onwriteend callback + if (typeof me.onwriteend === "function") { + me.onwriteend(new ProgressEvent("writeend", {"target":me})); + } + }, + // Error callback + function(e) { + // If DONE (cancelled), then don't do anything + if (me.readyState === FileWriter.DONE) { + return; + } + + // DONE state + me.readyState = FileWriter.DONE; + + // Save error + me.error = new FileError(e); + + // If onerror callback + if (typeof me.onerror === "function") { + me.onerror(new ProgressEvent("error", {"target":me})); + } + + // If onwriteend callback + if (typeof me.onwriteend === "function") { + me.onwriteend(new ProgressEvent("writeend", {"target":me})); + } + }, "File", "truncate", [this.fileName, size]); +}; + +module.exports = FileWriter; + +}); + +define('cordova/plugin/Flags', function(require, exports, module) { +/** + * Supplies arguments to methods that lookup or create files and directories. + * + * @param create + * {boolean} file or directory if it doesn't exist + * @param exclusive + * {boolean} used with create; if true the command will fail if + * target path exists + */ +function Flags(create, exclusive) { + this.create = create || false; + this.exclusive = exclusive || false; +} + +module.exports = Flags; + +}); + +define('cordova/plugin/geolocation', function(require, exports, module) { +var utils = require('cordova/utils'), + exec = require('cordova/exec'), + PositionError = require('cordova/plugin/PositionError'); + +var timers = {}; // list of timers in use + +// Returns default params, overrides if provided with values +function parseParameters(options) { + var opt = { + maximumAge: 10000, + enableHighAccuracy: false, + timeout: 10000 + }; + + if (options) { + if (typeof options.maximumAge !== "undefined") { + opt.maximumAge = options.maximumAge; + } + if (typeof options.enableHighAccuracy !== "undefined") { + opt.enableHighAccuracy = options.enableHighAccuracy; + } + if (typeof options.timeout !== "undefined") { + opt.timeout = options.timeout; + } + } + + return opt; +} + +var geolocation = { + /** + * Asynchronously aquires the current position. + * + * @param {Function} successCallback The function to call when the position data is available + * @param {Function} errorCallback The function to call when there is an error getting the heading position. (OPTIONAL) + * @param {PositionOptions} options The options for getting the position data. (OPTIONAL) + */ + getCurrentPosition:function(successCallback, errorCallback, options) { + options = parseParameters(options); + exec(successCallback, errorCallback, "Geolocation", "getLocation", [options.enableHighAccuracy, options.timeout, options.maximumAge]); + }, + /** + * Asynchronously watches the geolocation for changes to geolocation. When a change occurs, + * the successCallback is called with the new location. + * + * @param {Function} successCallback The function to call each time the location data is available + * @param {Function} errorCallback The function to call when there is an error getting the location data. (OPTIONAL) + * @param {PositionOptions} options The options for getting the location data such as frequency. (OPTIONAL) + * @return String The watch id that must be passed to #clearWatch to stop watching. + */ + watchPosition:function(successCallback, errorCallback, options) { + options = parseParameters(options); + var id = utils.createUUID(); + timers[id] = window.setInterval(function() { + exec(successCallback, errorCallback, "Geolocation", "getLocation", [options.enableHighAccuracy, options.timeout, options.maximumAge]); + }, options.timeout); + + return id; + }, + /** + * Clears the specified heading watch. + * + * @param {String} id The ID of the watch returned from #watchPosition + */ + clearWatch:function(id) { + if (id && timers[id] !== undefined) { + window.clearInterval(timers[id]); + delete timers[id]; + } + } +}; + +module.exports = geolocation; + +}); + + +define('cordova/plugin/LocalFileSystem', function(require, exports, module) { +var exec = require('cordova/exec'); + +/** + * Represents a local file system. + */ +var LocalFileSystem = function() { + +}; + +LocalFileSystem.TEMPORARY = 0; //temporary, with no guarantee of persistence +LocalFileSystem.PERSISTENT = 1; //persistent + +module.exports = LocalFileSystem; + +}); + +define('cordova/plugin/Media', function(require, exports, module) { +var utils = require('cordova/utils'), + exec = require('cordova/exec'); + +var mediaObjects = {}; + +/** + * This class provides access to the device media, interfaces to both sound and video + * + * @constructor + * @param src The file name or url to play + * @param successCallback The callback to be called when the file is done playing or recording. + * successCallback() + * @param errorCallback The callback to be called if there is an error. + * errorCallback(int errorCode) - OPTIONAL + * @param statusCallback The callback to be called when media status has changed. + * statusCallback(int statusCode) - OPTIONAL + */ +var Media = function(src, successCallback, errorCallback, statusCallback) { + + // successCallback optional + if (successCallback && (typeof successCallback !== "function")) { + console.log("Media Error: successCallback is not a function"); + return; + } + + // errorCallback optional + if (errorCallback && (typeof errorCallback !== "function")) { + console.log("Media Error: errorCallback is not a function"); + return; + } + + // statusCallback optional + if (statusCallback && (typeof statusCallback !== "function")) { + console.log("Media Error: statusCallback is not a function"); + return; + } + + this.id = utils.createUUID(); + mediaObjects[this.id] = this; + this.src = src; + this.successCallback = successCallback; + this.errorCallback = errorCallback; + this.statusCallback = statusCallback; + this._duration = -1; + this._position = -1; + exec(null, this.errorCallback, "Media", "create", [this.id, this.src]); +}; + +// Media messages +Media.MEDIA_STATE = 1; +Media.MEDIA_DURATION = 2; +Media.MEDIA_POSITION = 3; +Media.MEDIA_ERROR = 9; + +// Media states +Media.MEDIA_NONE = 0; +Media.MEDIA_STARTING = 1; +Media.MEDIA_RUNNING = 2; +Media.MEDIA_PAUSED = 3; +Media.MEDIA_STOPPED = 4; +Media.MEDIA_MSG = ["None", "Starting", "Running", "Paused", "Stopped"]; + +// "static" function to return existing objs. +Media.get = function(id) { + return mediaObjects[id]; +}; + +/** + * Start or resume playing audio file. + */ +Media.prototype.play = function() { + exec(this.successCallback, this.errorCallback, "Media", "startPlayingAudio", [this.id, this.src]); +}; + +/** + * Stop playing audio file. + */ +Media.prototype.stop = function() { + var me = this; + exec(function() { + me._position = 0; + me.successCallback(); + }, this.errorCallback, "Media", "stopPlayingAudio", [this.id]); +}; + +/** + * Seek or jump to a new time in the track.. + */ +Media.prototype.seekTo = function(milliseconds) { + var me = this; + exec(function(p) { + me._position = p; + }, this.errorCallback, "Media", "seekToAudio", [this.id, milliseconds]); +}; + +/** + * Pause playing audio file. + */ +Media.prototype.pause = function() { + exec(null, this.errorCallback, "Media", "pausePlayingAudio", [this.id]); +}; + +/** + * Get duration of an audio file. + * The duration is only set for audio that is playing, paused or stopped. + * + * @return duration or -1 if not known. + */ +Media.prototype.getDuration = function() { + return this._duration; +}; + +/** + * Get position of audio. + */ +Media.prototype.getCurrentPosition = function(success, fail) { + var me = this; + exec(function(p) { + me._position = p; + success(p); + }, fail, "Media", "getCurrentPositionAudio", [this.id]); +}; + +/** + * Start recording audio file. + */ +Media.prototype.startRecord = function() { + exec(this.successCallback, this.errorCallback, "Media", "startRecordingAudio", [this.id, this.src]); +}; + +/** + * Stop recording audio file. + */ +Media.prototype.stopRecord = function() { + exec(this.successCallback, this.errorCallback, "Media", "stopRecordingAudio", [this.id]); +}; + +/** + * Release the resources. + */ +Media.prototype.release = function() { + exec(null, this.errorCallback, "Media", "release", [this.id]); +}; + +/** + * Adjust the volume. + */ +Media.prototype.setVolume = function(volume) { + exec(null, null, "Media", "setVolume", [this.id, volume]); +}; + +/** + * Audio has status update. + * PRIVATE + * + * @param id The media object id (string) + * @param status The status code (int) + * @param msg The status message (string) + */ +Media.onStatus = function(id, msg, value) { + var media = mediaObjects[id]; + // If state update + if (msg === Media.MEDIA_STATE) { + if (value === Media.MEDIA_STOPPED) { + if (media.successCallback) { + media.successCallback(); + } + } + if (media.statusCallback) { + media.statusCallback(value); + } + } + else if (msg === Media.MEDIA_DURATION) { + media._duration = value; + } + else if (msg === Media.MEDIA_ERROR) { + if (media.errorCallback) { + media.errorCallback({"code":value}); + } + } + else if (msg === Media.MEDIA_POSITION) { + media._position = value; + } +}; + +module.exports = Media; + +}); + +define('cordova/plugin/MediaError', function(require, exports, module) { +/** + * This class contains information about any Media errors. + * @constructor + */ +var MediaError = function(code, msg) { + this.code = code || null; + this.message = msg || ""; +}; + +MediaError.MEDIA_ERR_NONE_ACTIVE = 0; +MediaError.MEDIA_ERR_ABORTED = 1; +MediaError.MEDIA_ERR_NETWORK = 2; +MediaError.MEDIA_ERR_DECODE = 3; +MediaError.MEDIA_ERR_NONE_SUPPORTED = 4; + +module.exports = MediaError; + +}); + +define('cordova/plugin/MediaFile', function(require, exports, module) { +var utils = require('cordova/utils'), + exec = require('cordova/exec'), + File = require('cordova/plugin/File'), + CaptureError = require('cordova/plugin/CaptureError'); +/** + * Represents a single file. + * + * name {DOMString} name of the file, without path information + * fullPath {DOMString} the full path of the file, including the name + * type {DOMString} mime type + * lastModifiedDate {Date} last modified date + * size {Number} size of the file in bytes + */ +var MediaFile = function(name, fullPath, type, lastModifiedDate, size){ + MediaFile.__super__.constructor.apply(this, arguments); +}; + +utils.extend(MediaFile, File); + +/** + * Request capture format data for a specific file and type + * + * @param {Function} successCB + * @param {Function} errorCB + */ +MediaFile.prototype.getFormatData = function(successCallback, errorCallback) { + if (typeof this.fullPath === "undefined" || this.fullPath === null) { + errorCallback(new CaptureError(CaptureError.CAPTURE_INVALID_ARGUMENT)); + } else { + exec(successCallback, errorCallback, "Capture", "getFormatData", [this.fullPath, this.type]); + } +}; + +/** + * Casts a PluginResult message property (array of objects) to an array of MediaFile objects + * (used in Objective-C and Android) + * + * @param {PluginResult} pluginResult + */ +MediaFile.cast = function(pluginResult) { + var mediaFiles = []; + var i; + for (i=0; i.dispatchEvent + // need to first figure out how to implement EventTarget + } + } + return event; + }; + try { + var ev = createEvent({type:"abort",target:document}); + return function ProgressEvent(type, data) { + data.type = type; + return createEvent(data); + }; + } catch(e){ + */ + return function ProgressEvent(type, dict) { + this.type = type; + this.bubbles = false; + this.cancelBubble = false; + this.cancelable = false; + this.lengthComputable = false; + this.loaded = dict && dict.loaded ? dict.loaded : 0; + this.total = dict && dict.total ? dict.total : 0; + this.target = dict && dict.target ? dict.target : null; + }; + //} +})(); + +module.exports = ProgressEvent; + +}); + +define('cordova/plugin/requestFileSystem', function(require, exports, module) { +var FileError = require('cordova/plugin/FileError'), + FileSystem = require('cordova/plugin/FileSystem'), + exec = require('cordova/exec'); + +/** + * Request a file system in which to store application data. + * @param type local file system type + * @param size indicates how much storage space, in bytes, the application expects to need + * @param successCallback invoked with a FileSystem object + * @param errorCallback invoked if error occurs retrieving file system + */ +var requestFileSystem = function(type, size, successCallback, errorCallback) { + var fail = function(code) { + if (typeof errorCallback === 'function') { + errorCallback(new FileError(code)); + } + }; + + if (type < 0 || type > 3) { + fail(FileError.SYNTAX_ERR); + } else { + // if successful, return a FileSystem object + var success = function(file_system) { + if (file_system) { + if (typeof successCallback === 'function') { + // grab the name and root from the file system object + var result = new FileSystem(file_system.name, file_system.root); + successCallback(result); + } + } + else { + // no FileSystem object returned + fail(FileError.NOT_FOUND_ERR); + } + }; + exec(success, fail, "File", "requestFileSystem", [type, size]); + } +}; + +module.exports = requestFileSystem; + +}); + +define('cordova/plugin/resolveLocalFileSystemURI', function(require, exports, module) { +var DirectoryEntry = require('cordova/plugin/DirectoryEntry'), + FileEntry = require('cordova/plugin/FileEntry'), + exec = require('cordova/exec'); + +/** + * Look up file system Entry referred to by local URI. + * @param {DOMString} uri URI referring to a local file or directory + * @param successCallback invoked with Entry object corresponding to URI + * @param errorCallback invoked if error occurs retrieving file system entry + */ +module.exports = function(uri, successCallback, errorCallback) { + // error callback + var fail = function(error) { + if (typeof errorCallback === 'function') { + errorCallback(new FileError(error)); + } + }; + // if successful, return either a file or directory entry + var success = function(entry) { + var result; + + if (entry) { + if (typeof successCallback === 'function') { + // create appropriate Entry object + result = (entry.isDirectory) ? new DirectoryEntry(entry.name, entry.fullPath) : new FileEntry(entry.name, entry.fullPath); + try { + successCallback(result); + } + catch (e) { + console.log('Error invoking callback: ' + e); + } + } + } + else { + // no Entry object returned + fail(FileError.NOT_FOUND_ERR); + } + }; + + exec(success, fail, "File", "resolveLocalFileSystemURI", [uri]); +}; + +}); + + +define('cordova/plugin/android/app', function(require, exports, module) { +var exec = require('cordova/exec'); + +module.exports = { + /** + * Clear the resource cache. + */ + clearCache:function() { + exec(null, null, "App", "clearCache", []); + }, + + /** + * Load the url into the webview or into new browser instance. + * + * @param url The URL to load + * @param props Properties that can be passed in to the activity: + * wait: int => wait msec before loading URL + * loadingDialog: "Title,Message" => display a native loading dialog + * loadUrlTimeoutValue: int => time in msec to wait before triggering a timeout error + * clearHistory: boolean => clear webview history (default=false) + * openExternal: boolean => open in a new browser (default=false) + * + * Example: + * navigator.app.loadUrl("http://server/myapp/index.html", {wait:2000, loadingDialog:"Wait,Loading App", loadUrlTimeoutValue: 60000}); + */ + loadUrl:function(url, props) { + exec(null, null, "App", "loadUrl", [url, props]); + }, + + /** + * Cancel loadUrl that is waiting to be loaded. + */ + cancelLoadUrl:function() { + exec(null, null, "App", "cancelLoadUrl", []); + }, + + /** + * Clear web history in this web view. + * Instead of BACK button loading the previous web page, it will exit the app. + */ + clearHistory:function() { + exec(null, null, "App", "clearHistory", []); + }, + + /** + * Go to previous page displayed. + * This is the same as pressing the backbutton on Android device. + */ + backHistory:function() { + exec(null, null, "App", "backHistory", []); + }, + + /** + * Override the default behavior of the Android back button. + * If overridden, when the back button is pressed, the "backKeyDown" JavaScript event will be fired. + * + * Note: The user should not have to call this method. Instead, when the user + * registers for the "backbutton" event, this is automatically done. + * + * @param override T=override, F=cancel override + */ + overrideBackbutton:function(override) { + exec(null, null, "App", "overrideBackbutton", [override]); + }, + + /** + * Exit and terminate the application. + */ + exitApp:function() { + return exec(null, null, "App", "exitApp", []); + } +}; + +}); + +define('cordova/plugin/android/callback', function(require, exports, module) { +var port = null, + token = null, + cordova = require('cordova'), + polling = require('cordova/plugin/android/polling'), + callback = function() { + // Exit if shutting down app + if (cordova.shuttingDown) { + return; + } + + // If polling flag was changed, start using polling from now on + if (cordova.UsePolling) { + polling(); + return; + } + + var xmlhttp = new XMLHttpRequest(); + + // Callback function when XMLHttpRequest is ready + xmlhttp.onreadystatechange=function(){ + if(xmlhttp.readyState === 4){ + + // Exit if shutting down app + if (cordova.shuttingDown) { + return; + } + + // If callback has JavaScript statement to execute + if (xmlhttp.status === 200) { + + // Need to url decode the response + var msg = decodeURIComponent(xmlhttp.responseText); + setTimeout(function() { + try { + var t = eval(msg); + } + catch (e) { + // If we're getting an error here, seeing the message will help in debugging + console.log("JSCallback: Message from Server: " + msg); + console.log("JSCallback Error: "+e); + } + }, 1); + setTimeout(callback, 1); + } + + // If callback ping (used to keep XHR request from timing out) + else if (xmlhttp.status === 404) { + setTimeout(callback, 10); + } + + // If security error + else if (xmlhttp.status === 403) { + console.log("JSCallback Error: Invalid token. Stopping callbacks."); + } + + // If server is stopping + else if (xmlhttp.status === 503) { + console.log("JSCallback Server Closed: Stopping callbacks."); + } + + // If request wasn't GET + else if (xmlhttp.status === 400) { + console.log("JSCallback Error: Bad request. Stopping callbacks."); + } + + // If error, revert to polling + else { + console.log("JSCallback Error: Request failed."); + cordova.UsePolling = true; + polling(); + } + } + }; + + if (port === null) { + port = prompt("getPort", "gap_callbackServer:"); + } + if (token === null) { + token = prompt("getToken", "gap_callbackServer:"); + } + xmlhttp.open("GET", "http://127.0.0.1:"+port+"/"+token , true); + xmlhttp.send(); +}; + +module.exports = callback; + +}); + +define('cordova/plugin/android/device', function(require, exports, module) { +var channel = require('cordova/channel'), + exec = require('cordova/exec'); + +/** + * This represents the mobile device, and provides properties for inspecting the model, version, UUID of the + * phone, etc. + * @constructor + */ +function Device() { + this.available = false; + this.platform = null; + this.version = null; + this.name = null; + this.uuid = null; + this.cordova = null; + + var me = this; + this.getInfo( + function(info) { + me.available = true; + me.platform = info.platform; + me.version = info.version; + me.name = info.name; + me.uuid = info.uuid; + me.cordova = info.cordova; + channel.onCordovaInfoReady.fire(); + }, + function(e) { + me.available = false; + console.log("Error initializing Cordova: " + e); + alert("Error initializing Cordova: "+e); + }); +} + +/** + * Get device info + * + * @param {Function} successCallback The function to call when the heading data is available + * @param {Function} errorCallback The function to call when there is an error getting the heading data. (OPTIONAL) + */ +Device.prototype.getInfo = function(successCallback, errorCallback) { + + // successCallback required + if (typeof successCallback !== "function") { + console.log("Device Error: successCallback is not a function"); + return; + } + + // errorCallback optional + if (errorCallback && (typeof errorCallback !== "function")) { + console.log("Device Error: errorCallback is not a function"); + return; + } + + // Get info + exec(successCallback, errorCallback, "Device", "getDeviceInfo", []); +}; + +/* + * DEPRECATED + * This is only for Android. + * + * You must explicitly override the back button. + */ +Device.prototype.overrideBackButton = function() { + console.log("Device.overrideBackButton() is deprecated. Use App.overrideBackbutton(true)."); + navigator.app.overrideBackbutton(true); +}; + +/* + * DEPRECATED + * This is only for Android. + * + * This resets the back button to the default behaviour + */ +Device.prototype.resetBackButton = function() { + console.log("Device.resetBackButton() is deprecated. Use App.overrideBackbutton(false)."); + navigator.app.overrideBackbutton(false); +}; + +/* + * DEPRECATED + * This is only for Android. + * + * This terminates the activity! + */ +Device.prototype.exitApp = function() { + console.log("Device.exitApp() is deprecated. Use App.exitApp()."); + navigator.app.exitApp(); +}; + +module.exports = new Device(); + +}); + +define('cordova/plugin/android/polling', function(require, exports, module) { +var cordova = require('cordova'), + period = 50, + polling = function() { + // Exit if shutting down app + if (cordova.shuttingDown) { + return; + } + + // If polling flag was changed, stop using polling from now on and switch to XHR server / callback + if (!cordova.UsePolling) { + require('cordova/plugin/android/callback')(); + return; + } + + var msg = prompt("", "gap_poll:"); + if (msg) { + setTimeout(function() { + try { + var t = eval(""+msg); + } + catch (e) { + console.log("JSCallbackPolling: Message from Server: " + msg); + console.log("JSCallbackPolling Error: "+e); + } + }, 1); + setTimeout(polling, 1); + } + else { + setTimeout(polling, period); + } +}; + +module.exports = polling; + +}); + +define('cordova/plugin/android/storage', function(require, exports, module) { +var utils = require('cordova/utils'), + exec = require('cordova/exec'); + +var queryQueue = {}; + +/** + * SQL result set object + * PRIVATE METHOD + * @constructor + */ +var DroidDB_Rows = function() { + this.resultSet = []; // results array + this.length = 0; // number of rows +}; + +/** + * Get item from SQL result set + * + * @param row The row number to return + * @return The row object + */ +DroidDB_Rows.prototype.item = function(row) { + return this.resultSet[row]; +}; + +/** + * SQL result set that is returned to user. + * PRIVATE METHOD + * @constructor + */ +var DroidDB_Result = function() { + this.rows = new DroidDB_Rows(); +}; + +/** + * Callback from native code when query is complete. + * PRIVATE METHOD + * + * @param id Query id + */ +function completeQuery(id, data) { + var query = queryQueue[id]; + if (query) { + try { + delete queryQueue[id]; + + // Get transaction + var tx = query.tx; + + // If transaction hasn't failed + // Note: We ignore all query results if previous query + // in the same transaction failed. + if (tx && tx.queryList[id]) { + + // Save query results + var r = new DroidDB_Result(); + r.rows.resultSet = data; + r.rows.length = data.length; + try { + if (typeof query.successCallback === 'function') { + query.successCallback(query.tx, r); + } + } catch (ex) { + console.log("executeSql error calling user success callback: "+ex); + } + + tx.queryComplete(id); + } + } catch (e) { + console.log("executeSql error: "+e); + } + } +} + +/** + * Callback from native code when query fails + * PRIVATE METHOD + * + * @param reason Error message + * @param id Query id + */ +function failQuery(reason, id) { + var query = queryQueue[id]; + if (query) { + try { + delete queryQueue[id]; + + // Get transaction + var tx = query.tx; + + // If transaction hasn't failed + // Note: We ignore all query results if previous query + // in the same transaction failed. + if (tx && tx.queryList[id]) { + tx.queryList = {}; + + try { + if (typeof query.errorCallback === 'function') { + query.errorCallback(query.tx, reason); + } + } catch (ex) { + console.log("executeSql error calling user error callback: "+ex); + } + + tx.queryFailed(id, reason); + } + + } catch (e) { + console.log("executeSql error: "+e); + } + } +} + +/** + * SQL query object + * PRIVATE METHOD + * + * @constructor + * @param tx The transaction object that this query belongs to + */ +var DroidDB_Query = function(tx) { + + // Set the id of the query + this.id = utils.createUUID(); + + // Add this query to the queue + queryQueue[this.id] = this; + + // Init result + this.resultSet = []; + + // Set transaction that this query belongs to + this.tx = tx; + + // Add this query to transaction list + this.tx.queryList[this.id] = this; + + // Callbacks + this.successCallback = null; + this.errorCallback = null; + +}; + +/** + * Transaction object + * PRIVATE METHOD + * @constructor + */ +var DroidDB_Tx = function() { + + // Set the id of the transaction + this.id = utils.createUUID(); + + // Callbacks + this.successCallback = null; + this.errorCallback = null; + + // Query list + this.queryList = {}; +}; + +/** + * Mark query in transaction as complete. + * If all queries are complete, call the user's transaction success callback. + * + * @param id Query id + */ +DroidDB_Tx.prototype.queryComplete = function(id) { + delete this.queryList[id]; + + // If no more outstanding queries, then fire transaction success + if (this.successCallback) { + var count = 0; + var i; + for (i in this.queryList) { + if (this.queryList.hasOwnProperty(i)) { + count++; + } + } + if (count === 0) { + try { + this.successCallback(); + } catch(e) { + console.log("Transaction error calling user success callback: " + e); + } + } + } +}; + +/** + * Mark query in transaction as failed. + * + * @param id Query id + * @param reason Error message + */ +DroidDB_Tx.prototype.queryFailed = function(id, reason) { + + // The sql queries in this transaction have already been run, since + // we really don't have a real transaction implemented in native code. + // However, the user callbacks for the remaining sql queries in transaction + // will not be called. + this.queryList = {}; + + if (this.errorCallback) { + try { + this.errorCallback(reason); + } catch(e) { + console.log("Transaction error calling user error callback: " + e); + } + } +}; + +/** + * Execute SQL statement + * + * @param sql SQL statement to execute + * @param params Statement parameters + * @param successCallback Success callback + * @param errorCallback Error callback + */ +DroidDB_Tx.prototype.executeSql = function(sql, params, successCallback, errorCallback) { + + // Init params array + if (typeof params === 'undefined') { + params = []; + } + + // Create query and add to queue + var query = new DroidDB_Query(this); + queryQueue[query.id] = query; + + // Save callbacks + query.successCallback = successCallback; + query.errorCallback = errorCallback; + + // Call native code + exec(null, null, "Storage", "executeSql", [sql, params, query.id]); +}; + +var DatabaseShell = function() { +}; + +/** + * Start a transaction. + * Does not support rollback in event of failure. + * + * @param process {Function} The transaction function + * @param successCallback {Function} + * @param errorCallback {Function} + */ +DatabaseShell.prototype.transaction = function(process, errorCallback, successCallback) { + var tx = new DroidDB_Tx(); + tx.successCallback = successCallback; + tx.errorCallback = errorCallback; + try { + process(tx); + } catch (e) { + console.log("Transaction error: "+e); + if (tx.errorCallback) { + try { + tx.errorCallback(e); + } catch (ex) { + console.log("Transaction error calling user error callback: "+e); + } + } + } +}; + +/** + * Open database + * + * @param name Database name + * @param version Database version + * @param display_name Database display name + * @param size Database size in bytes + * @return Database object + */ +var DroidDB_openDatabase = function(name, version, display_name, size) { + exec(null, null, "Storage", "openDatabase", [name, version, display_name, size]); + var db = new DatabaseShell(); + return db; +}; + +/** + * For browsers with no localStorage we emulate it with SQLite. Follows the w3c api. + * TODO: Do similar for sessionStorage. + * @constructor + */ +var CupcakeLocalStorage = function() { + try { + + this.db = openDatabase('localStorage', '1.0', 'localStorage', 2621440); + var storage = {}; + this.length = 0; + function setLength (length) { + this.length = length; + localStorage.length = length; + } + this.db.transaction( + function (transaction) { + var i; + transaction.executeSql('CREATE TABLE IF NOT EXISTS storage (id NVARCHAR(40) PRIMARY KEY, body NVARCHAR(255))'); + transaction.executeSql('SELECT * FROM storage', [], function(tx, result) { + for(var i = 0; i < result.rows.length; i++) { + storage[result.rows.item(i)['id']] = result.rows.item(i)['body']; + } + setLength(result.rows.length); + }); + + }, + function (err) { + alert(err.message); + } + ); + this.setItem = function(key, val) { + if (typeof(storage[key])=='undefined') { + this.length++; + } + storage[key] = val; + this.db.transaction( + function (transaction) { + transaction.executeSql('CREATE TABLE IF NOT EXISTS storage (id NVARCHAR(40) PRIMARY KEY, body NVARCHAR(255))'); + transaction.executeSql('REPLACE INTO storage (id, body) values(?,?)', [key,val]); + } + ); + }; + this.getItem = function(key) { + return storage[key]; + }; + this.removeItem = function(key) { + delete storage[key]; + this.length--; + this.db.transaction( + function (transaction) { + transaction.executeSql('CREATE TABLE IF NOT EXISTS storage (id NVARCHAR(40) PRIMARY KEY, body NVARCHAR(255))'); + transaction.executeSql('DELETE FROM storage where id=?', [key]); + } + ); + }; + this.clear = function() { + storage = {}; + this.length = 0; + this.db.transaction( + function (transaction) { + transaction.executeSql('CREATE TABLE IF NOT EXISTS storage (id NVARCHAR(40) PRIMARY KEY, body NVARCHAR(255))'); + transaction.executeSql('DELETE FROM storage', []); + } + ); + }; + this.key = function(index) { + var i = 0; + for (var j in storage) { + if (i==index) { + return j; + } else { + i++; + } + } + return null; + }; + + } catch(e) { + alert("Database error "+e+"."); + return; + } +}; + +module.exports = { + openDatabase:DroidDB_openDatabase, + CupcakeLocalStorage:CupcakeLocalStorage, + failQuery:failQuery, + completeQuery:completeQuery +}; + +}); +window.cordova = require('cordova'); +(function (context) { + var channel = require("cordova/channel"), + /** + * cordova Channels that must fire before "deviceready" is fired. + */ + deviceReadyChannelsArray = [channel.onCordovaReady, channel.onCordovaInfoReady, channel.onCordovaConnectionReady], + deviceReadyChannelsMap = {}, + _self = { + boot: function () { + //--------------- + // Event handling + //--------------- + + /** + * Listen for DOMContentLoaded and notify our channel subscribers. + */ + document.addEventListener('DOMContentLoaded', function() { + channel.onDOMContentLoaded.fire(); + }, false); + if (document.readyState == 'complete') { + channel.onDOMContentLoaded.fire(); + } + + /** + * Create all cordova objects once page has fully loaded and native side is ready. + */ + channel.join(function() { + var builder = require('cordova/builder'), + base = require('cordova/common'), + platform = require('cordova/platform'); + + // Drop the common globals into the window object, but be nice and don't overwrite anything. + builder.build(base.objects).intoButDontClobber(window); + + // Drop the platform-specific globals into the window object and do it like a honey badger does it. + builder.build(platform.objects).intoAndClobberTheFOutOf(window); + + // Call the platform-specific initialization + platform.initialize(); + + // Fire event to notify that all objects are created + channel.onCordovaReady.fire(); + + // Fire onDeviceReady event once all constructors have run and + // cordova info has been received from native side. + channel.join(function() { + channel.onDeviceReady.fire(); + + // Fire the onresume event, since first one happens before JavaScript is loaded + channel.onResume.fire(); + }, deviceReadyChannelsArray); + + }, [ channel.onDOMContentLoaded, channel.onNativeReady ]); + } + }; + // boot up once native side is ready + channel.onNativeReady.subscribe(_self.boot); + + // _nativeReady is global variable that the native side can set + // to signify that the native code is ready. It is a global since + // it may be called before any cordova JS is ready. + if (window._nativeReady) { + channel.onNativeReady.fire(); + } + +}(window)); + diff --git a/DroidGap/lawnchair-adapter-test/www/index.html b/DroidGap/lawnchair-adapter-test/www/index.html new file mode 100644 index 0000000..dc44255 --- /dev/null +++ b/DroidGap/lawnchair-adapter-test/www/index.html @@ -0,0 +1,181 @@ + + + + + + PhoneGap + + + + + + + + + + + + + + + + + + + + + +

Lawnchair Spec

+

+

+
    + + + + + diff --git a/DroidGap/lawnchair-adapter-test/www/lawnchair-spec.js b/DroidGap/lawnchair-adapter-test/www/lawnchair-spec.js new file mode 100755 index 0000000..39515b1 --- /dev/null +++ b/DroidGap/lawnchair-adapter-test/www/lawnchair-spec.js @@ -0,0 +1,429 @@ +module('Lawnchair construction/destruction', { + setup:function() { + }, + teardown:function() { + } +}); + +test('ctor requires callbacks in each form', function() { + QUnit.stop(); + QUnit.expect(6); + + // raise exception if no ctor callback is supplied + try { + var lc2 = new Lawnchair(); + } catch(e) { + ok(true, 'exception raised if no callback supplied to init'); + } + try { + var lc3 = new Lawnchair({}, {}); + } catch(e) { + ok(true, 'exception raised if no callback supplied to init, but two args are present'); + } + try { + var lc3 = new Lawnchair({}); + } catch(e) { + ok(true, 'exception raised if no callback supplied to init, but one arg is present'); + } + + var lc = new Lawnchair({name:store.name}, function(ref) { + ok(true, 'should call passed in callback when using obj+function ctor form') + equals(this, ref, "lawnchair callback scoped to lawnchair instance") + equals(ref, this, "lawnchair passes self into callback too") + QUnit.start() + }); +}); + +/** +test('independent data stores', function() { + + var store1 = new Lawnchair({name: "store1"}, function() {}); + + store1 .save({key: 'apple', quantity: 3}, function() { + + var store2 = new Lawnchair({name: "store2"}, function() {}); + + store1.all(function(r) { + equals(r.length, 1); + }); + + store2.all(function(r) { + equals(r.length, 0); + }); + + }) + + +}) +**/ + +module('all()', { + setup:function() { + QUnit.stop(); + + // I like to make all my variables globals. Starting a new trend. + me = {name:'brian', age:30}; + store.nuke(function() { QUnit.start(); }); + }, + teardown:function() { + me = null; + } +}) + +test('chainable', function() { + QUnit.stop(); + QUnit.expect(1); + + same(store.all(function(r) { QUnit.start(); }), store, 'should be chainable (return itself)'); +}) + +test('full callback syntax', function() { + QUnit.stop(); + QUnit.expect(4); + + store.all(function(r) { + ok(true, 'calls callback'); + ok(r instanceof Array, 'should provide array as parameter'); + equals(r.length, 0, 'parameter should initially have zero length'); + same(this, store, '"this" should be scoped to the lawnchair object inside callback'); + QUnit.start(); + }); +}) + +test('adding, nuking and size tests', function() { + QUnit.stop(); + QUnit.expect(2); + + store.save(me, function() { + store.all(function(r) { + equals(r.length, 1, 'parameter should have length 1 after saving a single record'); + store.nuke(function() { + store.all(function(r) { + equals(r.length, 0, 'parameter should have length 0 after nuking'); + QUnit.start(); + }); + }); + }); + }); +}) + +test( 'shorthand callback syntax', function() { + QUnit.stop(); + QUnit.expect(2); + + store.all('ok(true, "shorthand syntax callback gets evaled"); same(this, store, "`this` should be scoped to the Lawnchair instance"); QUnit.start();'); + + // Is this test block necessary? + // + // var tmp = new Lawnchair({name:'temps', record:'tmp'}, function(){ + // QUnit.start() + // var Temps = this; + // equals(this, Temps, 'this is bound to Lawnchair') + // QUnit.stop() + // Temps.all('ok(temps, "this.name is passed to all callback"); QUnit.start()') + // }) +}) + +test('scoped variable in shorthand callback', function() { + QUnit.expect(1); + QUnit.stop(); + + // FIXME fkn qunit being weird here... expect(1) + var tmp = new Lawnchair({name:'temps', record:'tmp'}, function() { + this.nuke(function() { + this.save({a:1}, function() { + this.each('ok(tmp, "this.record is passed to each callback"); QUnit.start()') + }) + }) + }) +}) + +module('nuke()', { + setup:function() { + QUnit.stop(); + store.nuke(function() { + QUnit.start() + }); + }, + teardown:function() { + } +}) + +test( 'chainable', function() { + QUnit.expect(1); + QUnit.stop() + + same(store.nuke(function() { QUnit.start() }), store, 'should be chainable'); +}) + +test( 'full callback syntax', function() { + QUnit.stop(); + QUnit.expect(2); + + store.nuke(function() { + ok(true, "should call callback in nuke"); + same(this, store, '"this" should be scoped to the Lawnchair instance'); + QUnit.start(); + }); +}) + +test( 'shorthand callback syntax', function() { + QUnit.stop(); + QUnit.expect(2); + + store.nuke('ok(true, "shorthand syntax callback gets evaled"); same(this, store, "`this` should be scoped to the Lawnchair instance"); QUnit.start();'); +}) + +module('save()', { + setup:function() { + QUnit.stop(); + + // I like to make all my variables globals. Starting a new trend. + me = {name:'brian', age:30}; + store.nuke(function() { QUnit.start(); }); + }, + teardown:function() { + me = null; + } +}) + +test( 'chainable', function() { + QUnit.stop(); + QUnit.expect(1); + + same(store.save(me, function() { QUnit.start(); }), store, 'should be chainable'); +}) + +test( 'full callback syntax', function() { + QUnit.stop(); + QUnit.expect(2); + + store.save(me, function(it) { + ok(true, 'should call passed in callback'); + same(it, me, 'should pass in original saved object in callback'); + QUnit.start(); + }); +}) + +test( 'shorthand callback syntax', function() { + QUnit.stop(); + QUnit.expect(2); + + store.save(me, 'ok(true, "shorthand syntax callback gets evaled"); same(this, store, "`this` should be scoped to the Lawnchair instance"); QUnit.start();'); +}) + +test( 'saving objects', function() { + QUnit.stop(); + QUnit.expect(1); + + store.save(me, function() { + store.save({key:"something", value:"else"}, function(r) { + store.all(function(r) { + equals(r.length, 2, 'after saving two keys, num. records should equal to 2'); + QUnit.start(); + }); + }); + }) +}) + +test( 'save without callback', function() { + + QUnit.stop(); + QUnit.expect(1); + + store.save(me, function(obj) { + var key = obj.key; + store.save(obj); + equals(obj.key, key, "save without callback retains key"); + QUnit.start(); + }) + +}); + +module('batch()', { + setup:function() { + QUnit.stop(); + + // I like to make all my variables globals. Starting a new trend. + me = {name:'brian', age:30}; + store.nuke(function() { QUnit.start(); }); + }, + teardown:function() { + me = null; + } +}) + +test('batch insertion', function(){ + QUnit.expect(3); + QUnit.stop(); + + ok(store.batch, 'batch implemented'); + equals(store.batch([]), store, 'chainable') + + store.batch([{i:1},{i:2}], function() { + store.all(function(r){ + equals(r.length, 2, 'should be two records from batch insert with array of two objects'); + QUnit.start(); + }); + }); +}) + +test( 'full callback syntax', function() { + QUnit.stop(500); + QUnit.expect(2); + + store.batch([{j:'k'}], function() { + ok(true, 'callback called with full syntax'); + same(this, store, '"this" should be the LAwnchair instance'); + QUnit.start(); + }) +}) + +test( 'shorthand callback syntax', function() { + QUnit.stop(500); + QUnit.expect(2); + + store.batch([{o:'k'}], 'ok(true, "shorthand syntax callback gets evaled"); same(this, store, "`this` should be scoped to the Lawnchair instance"); QUnit.start();') +}) + +module('get()', { + setup:function() { + QUnit.stop(); + + // I like to make all my variables globals. Starting a new trend. + me = {name:'brian', age:30}; + store.nuke(function() { QUnit.start(); }); + }, + teardown:function() { + me = null; + } +}); + +test( 'should it be chainable?', function() { + QUnit.expect(1); + QUnit.stop(); + + equals(store.get('foo', function() { QUnit.start(); }), store, 'get chainable'); +}); + +test('get functionality', function() { + QUnit.expect(4); + QUnit.stop(); + + store.save({key:'xyz', name:'tim'}, function() { + store.get('xyz', function(r) { + equals(r.key, 'xyz', 'should return key in loaded object'); + equals(r.name, 'tim', 'should return proper object when calling get with a key'); + store.get('doesntexist', function(s) { + ok(true, 'should call callback even for non-existent key'); + equals(s, null, 'should return null for non-existent key'); + QUnit.start(); + }); + }); + }); +}); + +test('get batch functionality', function() { + QUnit.expect(3); + QUnit.stop(500); + + var t = [{key:'test-get'},{key:'test-get-1'}] + store.batch(t, function() { + this.get(['test-get','test-get-1'], function(r) { + equals(r[0].key, 'test-get', "get first object"); + equals(r[1].key, 'test-get-1', "get second object"); + equals(r.length, t.length, "should batch get") + QUnit.start() + }) + }) +}); + +test( 'full callback syntax', function() { + QUnit.stop(); + QUnit.expect(2); + + store.get('somekey', function(r){ + ok(true, 'callback got called'); + same(this, store, '"this" should be teh Lawnchair instance'); + QUnit.start(); + }); +}); + +test('short callback syntax', function() { + QUnit.stop(); + QUnit.expect(2); + + store.get('somekey', 'ok(true, "shorthand syntax callback gets evaled"); same(this, store, "`this` should be scoped to the Lawnchair instance"); QUnit.start();'); +}); + +module('remove()', { + setup:function() { + QUnit.stop(); + + // I like to make all my variables globals. Starting a new trend. + me = {name:'brian', age:30}; + store.nuke(function() { QUnit.start(); }); + }, + teardown:function() { + me = null; + } +}); + + +test( 'chainable', function() { + QUnit.expect(1); + QUnit.stop(); + + store.save({key:'me', name:'brian'}, function() { + same(store.remove('me', function() { + QUnit.start(); + }), store, 'should be chainable'); + + }); +}); + +test( 'full callback syntax', function() { + QUnit.stop(); + QUnit.expect(2); + + store.save({key:'somekey', name:'something'}, function() { + store.remove('somekey', function(r){ + ok(true, 'callback got called'); + same(this, store, '"this" should be teh Lawnchair instance'); + QUnit.start(); + }); + }); +}); + +test('short callback syntax', function() { + QUnit.stop(); + QUnit.expect(2); + + store.save({key:'somekey', name:'something'}, function() { + store.remove('somekey', 'ok(true, "shorthand syntax callback gets evaled"); same(this, store, "`this` should be scoped to the Lawnchair instance"); QUnit.start();'); + }); +}); + +// FIXME need to add tests for batch deletion +test( 'remove functionality', function() { + QUnit.stop(); + QUnit.expect(2); + + store.save({name:'joni'}, function(r) { + //store.find("r.name == 'joni'", function(r){ + store.remove(r, function(r) { + store.all(function(all) { + equals(all.length, 0, "should have length 0 after saving, finding, and removing a record using entire object"); + store.save({key:'die', name:'dudeman'}, function(r) { + store.remove('die', function(r){ + store.all(function(rec) { + equals(rec.length, 0, "should have length 0 after saving and removing by string key"); + QUnit.start(); + }); + }); + }); + }); + }); + //}); + }); +}); diff --git a/DroidGap/lawnchair-adapter-test/www/lib/json2.js b/DroidGap/lawnchair-adapter-test/www/lib/json2.js new file mode 100644 index 0000000..a1a3b17 --- /dev/null +++ b/DroidGap/lawnchair-adapter-test/www/lib/json2.js @@ -0,0 +1,482 @@ +/* + http://www.JSON.org/json2.js + 2010-03-20 + + Public Domain. + + NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. + + See http://www.JSON.org/js.html + + + This code should be minified before deployment. + See http://javascript.crockford.com/jsmin.html + + USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO + NOT CONTROL. + + + This file creates a global JSON object containing two methods: stringify + and parse. + + JSON.stringify(value, replacer, space) + value any JavaScript value, usually an object or array. + + replacer an optional parameter that determines how object + values are stringified for objects. It can be a + function or an array of strings. + + space an optional parameter that specifies the indentation + of nested structures. If it is omitted, the text will + be packed without extra whitespace. If it is a number, + it will specify the number of spaces to indent at each + level. If it is a string (such as '\t' or ' '), + it contains the characters used to indent at each level. + + This method produces a JSON text from a JavaScript value. + + When an object value is found, if the object contains a toJSON + method, its toJSON method will be called and the result will be + stringified. A toJSON method does not serialize: it returns the + value represented by the name/value pair that should be serialized, + or undefined if nothing should be serialized. The toJSON method + will be passed the key associated with the value, and this will be + bound to the value + + For example, this would serialize Dates as ISO strings. + + Date.prototype.toJSON = function (key) { + function f(n) { + // Format integers to have at least two digits. + return n < 10 ? '0' + n : n; + } + + return this.getUTCFullYear() + '-' + + f(this.getUTCMonth() + 1) + '-' + + f(this.getUTCDate()) + 'T' + + f(this.getUTCHours()) + ':' + + f(this.getUTCMinutes()) + ':' + + f(this.getUTCSeconds()) + 'Z'; + }; + + You can provide an optional replacer method. It will be passed the + key and value of each member, with this bound to the containing + object. The value that is returned from your method will be + serialized. If your method returns undefined, then the member will + be excluded from the serialization. + + If the replacer parameter is an array of strings, then it will be + used to select the members to be serialized. It filters the results + such that only members with keys listed in the replacer array are + stringified. + + Values that do not have JSON representations, such as undefined or + functions, will not be serialized. Such values in objects will be + dropped; in arrays they will be replaced with null. You can use + a replacer function to replace those with JSON values. + JSON.stringify(undefined) returns undefined. + + The optional space parameter produces a stringification of the + value that is filled with line breaks and indentation to make it + easier to read. + + If the space parameter is a non-empty string, then that string will + be used for indentation. If the space parameter is a number, then + the indentation will be that many spaces. + + Example: + + text = JSON.stringify(['e', {pluribus: 'unum'}]); + // text is '["e",{"pluribus":"unum"}]' + + + text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t'); + // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]' + + text = JSON.stringify([new Date()], function (key, value) { + return this[key] instanceof Date ? + 'Date(' + this[key] + ')' : value; + }); + // text is '["Date(---current time---)"]' + + + JSON.parse(text, reviver) + This method parses a JSON text to produce an object or array. + It can throw a SyntaxError exception. + + The optional reviver parameter is a function that can filter and + transform the results. It receives each of the keys and values, + and its return value is used instead of the original value. + If it returns what it received, then the structure is not modified. + If it returns undefined then the member is deleted. + + Example: + + // Parse the text. Values that look like ISO date strings will + // be converted to Date objects. + + myData = JSON.parse(text, function (key, value) { + var a; + if (typeof value === 'string') { + a = +/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value); + if (a) { + return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], + +a[5], +a[6])); + } + } + return value; + }); + + myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) { + var d; + if (typeof value === 'string' && + value.slice(0, 5) === 'Date(' && + value.slice(-1) === ')') { + d = new Date(value.slice(5, -1)); + if (d) { + return d; + } + } + return value; + }); + + + This is a reference implementation. You are free to copy, modify, or + redistribute. +*/ + +/*jslint evil: true, strict: false */ + +/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply, + call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours, + getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join, + lastIndex, length, parse, prototype, push, replace, slice, stringify, + test, toJSON, toString, valueOf +*/ + + +// Create a JSON object only if one does not already exist. We create the +// methods in a closure to avoid creating global variables. + +if (!this.JSON) { + this.JSON = {}; +} + +(function () { + + function f(n) { + // Format integers to have at least two digits. + return n < 10 ? '0' + n : n; + } + + if (typeof Date.prototype.toJSON !== 'function') { + + Date.prototype.toJSON = function (key) { + + return isFinite(this.valueOf()) ? + this.getUTCFullYear() + '-' + + f(this.getUTCMonth() + 1) + '-' + + f(this.getUTCDate()) + 'T' + + f(this.getUTCHours()) + ':' + + f(this.getUTCMinutes()) + ':' + + f(this.getUTCSeconds()) + 'Z' : null; + }; + + String.prototype.toJSON = + Number.prototype.toJSON = + Boolean.prototype.toJSON = function (key) { + return this.valueOf(); + }; + } + + var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, + escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, + gap, + indent, + meta = { // table of character substitutions + '\b': '\\b', + '\t': '\\t', + '\n': '\\n', + '\f': '\\f', + '\r': '\\r', + '"' : '\\"', + '\\': '\\\\' + }, + rep; + + + function quote(string) { + +// If the string contains no control characters, no quote characters, and no +// backslash characters, then we can safely slap some quotes around it. +// Otherwise we must also replace the offending characters with safe escape +// sequences. + + escapable.lastIndex = 0; + return escapable.test(string) ? + '"' + string.replace(escapable, function (a) { + var c = meta[a]; + return typeof c === 'string' ? c : + '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); + }) + '"' : + '"' + string + '"'; + } + + + function str(key, holder) { + +// Produce a string from holder[key]. + + var i, // The loop counter. + k, // The member key. + v, // The member value. + length, + mind = gap, + partial, + value = holder[key]; + +// If the value has a toJSON method, call it to obtain a replacement value. + + if (value && typeof value === 'object' && + typeof value.toJSON === 'function') { + value = value.toJSON(key); + } + +// If we were called with a replacer function, then call the replacer to +// obtain a replacement value. + + if (typeof rep === 'function') { + value = rep.call(holder, key, value); + } + +// What happens next depends on the value's type. + + switch (typeof value) { + case 'string': + return quote(value); + + case 'number': + +// JSON numbers must be finite. Encode non-finite numbers as null. + + return isFinite(value) ? String(value) : 'null'; + + case 'boolean': + case 'null': + +// If the value is a boolean or null, convert it to a string. Note: +// typeof null does not produce 'null'. The case is included here in +// the remote chance that this gets fixed someday. + + return String(value); + +// If the type is 'object', we might be dealing with an object or an array or +// null. + + case 'object': + +// Due to a specification blunder in ECMAScript, typeof null is 'object', +// so watch out for that case. + + if (!value) { + return 'null'; + } + +// Make an array to hold the partial results of stringifying this object value. + + gap += indent; + partial = []; + +// Is the value an array? + + if (Object.prototype.toString.apply(value) === '[object Array]') { + +// The value is an array. Stringify every element. Use null as a placeholder +// for non-JSON values. + + length = value.length; + for (i = 0; i < length; i += 1) { + partial[i] = str(i, value) || 'null'; + } + +// Join all of the elements together, separated with commas, and wrap them in +// brackets. + + v = partial.length === 0 ? '[]' : + gap ? '[\n' + gap + + partial.join(',\n' + gap) + '\n' + + mind + ']' : + '[' + partial.join(',') + ']'; + gap = mind; + return v; + } + +// If the replacer is an array, use it to select the members to be stringified. + + if (rep && typeof rep === 'object') { + length = rep.length; + for (i = 0; i < length; i += 1) { + k = rep[i]; + if (typeof k === 'string') { + v = str(k, value); + if (v) { + partial.push(quote(k) + (gap ? ': ' : ':') + v); + } + } + } + } else { + +// Otherwise, iterate through all of the keys in the object. + + for (k in value) { + if (Object.hasOwnProperty.call(value, k)) { + v = str(k, value); + if (v) { + partial.push(quote(k) + (gap ? ': ' : ':') + v); + } + } + } + } + +// Join all of the member texts together, separated with commas, +// and wrap them in braces. + + v = partial.length === 0 ? '{}' : + gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + + mind + '}' : '{' + partial.join(',') + '}'; + gap = mind; + return v; + } + } + +// If the JSON object does not yet have a stringify method, give it one. + + if (typeof JSON.stringify !== 'function') { + JSON.stringify = function (value, replacer, space) { + +// The stringify method takes a value and an optional replacer, and an optional +// space parameter, and returns a JSON text. The replacer can be a function +// that can replace values, or an array of strings that will select the keys. +// A default replacer method can be provided. Use of the space parameter can +// produce text that is more easily readable. + + var i; + gap = ''; + indent = ''; + +// If the space parameter is a number, make an indent string containing that +// many spaces. + + if (typeof space === 'number') { + for (i = 0; i < space; i += 1) { + indent += ' '; + } + +// If the space parameter is a string, it will be used as the indent string. + + } else if (typeof space === 'string') { + indent = space; + } + +// If there is a replacer, it must be a function or an array. +// Otherwise, throw an error. + + rep = replacer; + if (replacer && typeof replacer !== 'function' && + (typeof replacer !== 'object' || + typeof replacer.length !== 'number')) { + throw new Error('JSON.stringify'); + } + +// Make a fake root object containing our value under the key of ''. +// Return the result of stringifying the value. + + return str('', {'': value}); + }; + } + + +// If the JSON object does not yet have a parse method, give it one. + + if (typeof JSON.parse !== 'function') { + JSON.parse = function (text, reviver) { + +// The parse method takes a text and an optional reviver function, and returns +// a JavaScript value if the text is a valid JSON text. + + var j; + + function walk(holder, key) { + +// The walk method is used to recursively walk the resulting structure so +// that modifications can be made. + + var k, v, value = holder[key]; + if (value && typeof value === 'object') { + for (k in value) { + if (Object.hasOwnProperty.call(value, k)) { + v = walk(value, k); + if (v !== undefined) { + value[k] = v; + } else { + delete value[k]; + } + } + } + } + return reviver.call(holder, key, value); + } + + +// Parsing happens in four stages. In the first stage, we replace certain +// Unicode characters with escape sequences. JavaScript handles many characters +// incorrectly, either silently deleting them, or treating them as line endings. + + text = String(text); + cx.lastIndex = 0; + if (cx.test(text)) { + text = text.replace(cx, function (a) { + return '\\u' + + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); + }); + } + +// In the second stage, we run the text against regular expressions that look +// for non-JSON patterns. We are especially concerned with '()' and 'new' +// because they can cause invocation, and '=' because it can cause mutation. +// But just to be safe, we want to reject all unexpected forms. + +// We split the second stage into 4 regexp operations in order to work around +// crippling inefficiencies in IE's and Safari's regexp engines. First we +// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we +// replace all simple value tokens with ']' characters. Third, we delete all +// open brackets that follow a colon or comma or that begin the text. Finally, +// we look to see that the remaining characters are only whitespace or ']' or +// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. + + if (/^[\],:{}\s]*$/. +test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@'). +replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']'). +replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { + +// In the third stage we use the eval function to compile the text into a +// JavaScript structure. The '{' operator is subject to a syntactic ambiguity +// in JavaScript: it can begin a block or an object literal. We wrap the text +// in parens to eliminate the ambiguity. + + j = eval('(' + text + ')'); + +// In the optional fourth stage, we recursively walk the new structure, passing +// each name/value pair to a reviver function for possible transformation. + + return typeof reviver === 'function' ? + walk({'': j}, '') : j; + } + +// If the text is not JSON parseable, then a SyntaxError is thrown. + + throw new SyntaxError('JSON.parse'); + }; + } +}()); diff --git a/DroidGap/lawnchair-adapter-test/www/lib/lawnchair.js b/DroidGap/lawnchair-adapter-test/www/lib/lawnchair.js new file mode 100644 index 0000000..3f67835 --- /dev/null +++ b/DroidGap/lawnchair-adapter-test/www/lib/lawnchair.js @@ -0,0 +1,151 @@ +/** + * Lawnchair! + * --- + * clientside json store + * + */ +var Lawnchair = function (options, callback) { + // ensure Lawnchair was called as a constructor + if (!(this instanceof Lawnchair)) return new Lawnchair(options, callback); + + // lawnchair requires json + if (!JSON) throw 'JSON unavailable! Include http://www.json.org/json2.js to fix.' + // options are optional; callback is not + if (arguments.length <= 2 && arguments.length > 0) { + callback = (typeof arguments[0] === 'function') ? arguments[0] : arguments[1]; + options = (typeof arguments[0] === 'function') ? {} : arguments[0]; + } else { + throw 'Incorrect # of ctor args!' + } + // TODO perhaps allow for pub/sub instead? + if (typeof callback !== 'function') throw 'No callback was provided'; + + // default configuration + this.record = options.record || 'record' // default for records + this.name = options.name || 'records' // default name for underlying store + + // mixin first valid adapter + var adapter + // if the adapter is passed in we try to load that only + if (options.adapter) { + for (var i = 0, l = Lawnchair.adapters.length; i < l; i++) { + if (Lawnchair.adapters[i].adapter === options.adapter) { + adapter = Lawnchair.adapters[i].valid() ? Lawnchair.adapters[i] : undefined; + break; + } + } + // otherwise find the first valid adapter for this env + } + else { + for (var i = 0, l = Lawnchair.adapters.length; i < l; i++) { + adapter = Lawnchair.adapters[i].valid() ? Lawnchair.adapters[i] : undefined + if (adapter) break + } + } + + // we have failed + if (!adapter) throw 'No valid adapter.' + + // yay! mixin the adapter + for (var j in adapter) + this[j] = adapter[j] + + // call init for each mixed in plugin + for (var i = 0, l = Lawnchair.plugins.length; i < l; i++) + Lawnchair.plugins[i].call(this) + + // init the adapter + this.init(options, callback) +} + +Lawnchair.adapters = [] + +/** + * queues an adapter for mixin + * === + * - ensures an adapter conforms to a specific interface + * + */ +Lawnchair.adapter = function (id, obj) { + // add the adapter id to the adapter obj + // ugly here for a cleaner dsl for implementing adapters + obj['adapter'] = id + // methods required to implement a lawnchair adapter + var implementing = 'adapter valid init keys save batch get exists all remove nuke'.split(' ') + , indexOf = this.prototype.indexOf + // mix in the adapter + for (var i in obj) { + if (indexOf(implementing, i) === -1) throw 'Invalid adapter! Nonstandard method: ' + i + } + // if we made it this far the adapter interface is valid + // insert the new adapter as the preferred adapter + Lawnchair.adapters.splice(0,0,obj) +} + +Lawnchair.plugins = [] + +/** + * generic shallow extension for plugins + * === + * - if an init method is found it registers it to be called when the lawnchair is inited + * - yes we could use hasOwnProp but nobody here is an asshole + */ +Lawnchair.plugin = function (obj) { + for (var i in obj) + i === 'init' ? Lawnchair.plugins.push(obj[i]) : this.prototype[i] = obj[i] +} + +/** + * helpers + * + */ +Lawnchair.prototype = { + + isArray: Array.isArray || function(o) { return Object.prototype.toString.call(o) === '[object Array]' }, + + /** + * this code exists for ie8... for more background see: + * http://www.flickr.com/photos/westcoastlogic/5955365742/in/photostream + */ + indexOf: function(ary, item, i, l) { + if (ary.indexOf) return ary.indexOf(item) + for (i = 0, l = ary.length; i < l; i++) if (ary[i] === item) return i + return -1 + }, + + // awesome shorthand callbacks as strings. this is shameless theft from dojo. + lambda: function (callback) { + return this.fn(this.record, callback) + }, + + // first stab at named parameters for terse callbacks; dojo: first != best // ;D + fn: function (name, callback) { + return typeof callback == 'string' ? new Function(name, callback) : callback + }, + + // returns a unique identifier (by way of Backbone.localStorage.js) + // TODO investigate smaller UUIDs to cut on storage cost + uuid: function () { + var S4 = function () { + return (((1+Math.random())*0x10000)|0).toString(16).substring(1); + } + return (S4()+S4()+"-"+S4()+"-"+S4()+"-"+S4()+"-"+S4()+S4()+S4()); + }, + + // a classic iterator + each: function (callback) { + var cb = this.lambda(callback) + // iterate from chain + if (this.__results) { + for (var i = 0, l = this.__results.length; i < l; i++) cb.call(this, this.__results[i], i) + } + // otherwise iterate the entire collection + else { + this.all(function(r) { + for (var i = 0, l = r.length; i < l; i++) cb.call(this, r[i], i) + }) + } + return this + } +// -- +}; diff --git a/DroidGap/lawnchair-adapter-test/www/lib/qunit.css b/DroidGap/lawnchair-adapter-test/www/lib/qunit.css new file mode 100644 index 0000000..b3c6db5 --- /dev/null +++ b/DroidGap/lawnchair-adapter-test/www/lib/qunit.css @@ -0,0 +1,225 @@ +/** + * QUnit - A JavaScript Unit Testing Framework + * + * http://docs.jquery.com/QUnit + * + * Copyright (c) 2011 John Resig, Jörn Zaefferer + * Dual licensed under the MIT (MIT-LICENSE.txt) + * or GPL (GPL-LICENSE.txt) licenses. + */ + +/** Font Family and Sizes */ + +#qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult { + font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif; +} + +#qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; } +#qunit-tests { font-size: smaller; } + + +/** Resets */ + +#qunit-tests, #qunit-tests ol, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult { + margin: 0; + padding: 0; +} + + +/** Header */ + +#qunit-header { + padding: 0.5em 0 0.5em 1em; + + color: #8699a4; + background-color: #0d3349; + + font-size: 1.5em; + line-height: 1em; + font-weight: normal; + + border-radius: 15px 15px 0 0; + -moz-border-radius: 15px 15px 0 0; + -webkit-border-top-right-radius: 15px; + -webkit-border-top-left-radius: 15px; +} + +#qunit-header a { + text-decoration: none; + color: #c2ccd1; +} + +#qunit-header a:hover, +#qunit-header a:focus { + color: #fff; +} + +#qunit-banner { + height: 5px; +} + +#qunit-testrunner-toolbar { + padding: 0.5em 0 0.5em 2em; + color: #5E740B; + background-color: #eee; +} + +#qunit-userAgent { + padding: 0.5em 0 0.5em 2.5em; + background-color: #2b81af; + color: #fff; + text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px; +} + + +/** Tests: Pass/Fail */ + +#qunit-tests { + list-style-position: inside; +} + +#qunit-tests li { + padding: 0.4em 0.5em 0.4em 2.5em; + border-bottom: 1px solid #fff; + list-style-position: inside; +} + +#qunit-tests.hidepass li.pass, #qunit-tests.hidepass li.running { + display: none; +} + +#qunit-tests li strong { + cursor: pointer; +} + +#qunit-tests li a { + padding: 0.5em; + color: #c2ccd1; + text-decoration: none; +} +#qunit-tests li a:hover, +#qunit-tests li a:focus { + color: #000; +} + +#qunit-tests ol { + margin-top: 0.5em; + padding: 0.5em; + + background-color: #fff; + + border-radius: 15px; + -moz-border-radius: 15px; + -webkit-border-radius: 15px; + + box-shadow: inset 0px 2px 13px #999; + -moz-box-shadow: inset 0px 2px 13px #999; + -webkit-box-shadow: inset 0px 2px 13px #999; +} + +#qunit-tests table { + border-collapse: collapse; + margin-top: .2em; +} + +#qunit-tests th { + text-align: right; + vertical-align: top; + padding: 0 .5em 0 0; +} + +#qunit-tests td { + vertical-align: top; +} + +#qunit-tests pre { + margin: 0; + white-space: pre-wrap; + word-wrap: break-word; +} + +#qunit-tests del { + background-color: #e0f2be; + color: #374e0c; + text-decoration: none; +} + +#qunit-tests ins { + background-color: #ffcaca; + color: #500; + text-decoration: none; +} + +/*** Test Counts */ + +#qunit-tests b.counts { color: black; } +#qunit-tests b.passed { color: #5E740B; } +#qunit-tests b.failed { color: #710909; } + +#qunit-tests li li { + margin: 0.5em; + padding: 0.4em 0.5em 0.4em 0.5em; + background-color: #fff; + border-bottom: none; + list-style-position: inside; +} + +/*** Passing Styles */ + +#qunit-tests li li.pass { + color: #5E740B; + background-color: #fff; + border-left: 26px solid #C6E746; +} + +#qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; } +#qunit-tests .pass .test-name { color: #366097; } + +#qunit-tests .pass .test-actual, +#qunit-tests .pass .test-expected { color: #999999; } + +#qunit-banner.qunit-pass { background-color: #C6E746; } + +/*** Failing Styles */ + +#qunit-tests li li.fail { + color: #710909; + background-color: #fff; + border-left: 26px solid #EE5757; +} + +#qunit-tests > li:last-child { + border-radius: 0 0 15px 15px; + -moz-border-radius: 0 0 15px 15px; + -webkit-border-bottom-right-radius: 15px; + -webkit-border-bottom-left-radius: 15px; +} + +#qunit-tests .fail { color: #000000; background-color: #EE5757; } +#qunit-tests .fail .test-name, +#qunit-tests .fail .module-name { color: #000000; } + +#qunit-tests .fail .test-actual { color: #EE5757; } +#qunit-tests .fail .test-expected { color: green; } + +#qunit-banner.qunit-fail { background-color: #EE5757; } + + +/** Result */ + +#qunit-testresult { + padding: 0.5em 0.5em 0.5em 2.5em; + + color: #2b81af; + background-color: #D2E0E6; + + border-bottom: 1px solid white; +} + +/** Fixture */ + +#qunit-fixture { + position: absolute; + top: -10000px; + left: -10000px; +} diff --git a/DroidGap/lawnchair-adapter-test/www/lib/qunit.js b/DroidGap/lawnchair-adapter-test/www/lib/qunit.js new file mode 100644 index 0000000..3d640c7 --- /dev/null +++ b/DroidGap/lawnchair-adapter-test/www/lib/qunit.js @@ -0,0 +1,1448 @@ +/** + * QUnit - A JavaScript Unit Testing Framework + * + * http://docs.jquery.com/QUnit + * + * Copyright (c) 2011 John Resig, Jörn Zaefferer + * Dual licensed under the MIT (MIT-LICENSE.txt) + * or GPL (GPL-LICENSE.txt) licenses. + */ + +(function(window) { + +var defined = { + setTimeout: typeof window.setTimeout !== "undefined", + sessionStorage: (function() { + try { + return !!sessionStorage.getItem; + } catch(e){ + return false; + } + })() +}; + +var testId = 0; + +var Test = function(name, testName, expected, testEnvironmentArg, async, callback) { + this.name = name; + this.testName = testName; + this.expected = expected; + this.testEnvironmentArg = testEnvironmentArg; + this.async = async; + this.callback = callback; + this.assertions = []; +}; +Test.prototype = { + init: function() { + var tests = id("qunit-tests"); + if (tests) { + var b = document.createElement("strong"); + b.innerHTML = "Running " + this.name; + var li = document.createElement("li"); + li.appendChild( b ); + li.className = "running"; + li.id = this.id = "test-output" + testId++; + tests.appendChild( li ); + } + }, + setup: function() { + if (this.module != config.previousModule) { + if ( config.previousModule ) { + QUnit.moduleDone( { + name: config.previousModule, + failed: config.moduleStats.bad, + passed: config.moduleStats.all - config.moduleStats.bad, + total: config.moduleStats.all + } ); + } + config.previousModule = this.module; + config.moduleStats = { all: 0, bad: 0 }; + QUnit.moduleStart( { + name: this.module + } ); + } + + config.current = this; + this.testEnvironment = extend({ + setup: function() {}, + teardown: function() {} + }, this.moduleTestEnvironment); + if (this.testEnvironmentArg) { + extend(this.testEnvironment, this.testEnvironmentArg); + } + + QUnit.testStart( { + name: this.testName + } ); + + // allow utility functions to access the current test environment + // TODO why?? + QUnit.current_testEnvironment = this.testEnvironment; + + try { + if ( !config.pollution ) { + saveGlobal(); + } + + this.testEnvironment.setup.call(this.testEnvironment); + } catch(e) { + QUnit.ok( false, "Setup failed on " + this.testName + ": " + e.message ); + } + }, + run: function() { + if ( this.async ) { + QUnit.stop(); + } + + if ( config.notrycatch ) { + this.callback.call(this.testEnvironment); + return; + } + try { + this.callback.call(this.testEnvironment); + } catch(e) { + fail("Test " + this.testName + " died, exception and test follows", e, this.callback); + QUnit.ok( false, "Died on test #" + (this.assertions.length + 1) + ": " + e.message + " - " + QUnit.jsDump.parse(e) ); + // else next test will carry the responsibility + saveGlobal(); + + // Restart the tests if they're blocking + if ( config.blocking ) { + start(); + } + } + }, + teardown: function() { + try { + this.testEnvironment.teardown.call(this.testEnvironment); + checkPollution(); + } catch(e) { + QUnit.ok( false, "Teardown failed on " + this.testName + ": " + e.message ); + } + }, + finish: function() { + if ( this.expected && this.expected != this.assertions.length ) { + QUnit.ok( false, "Expected " + this.expected + " assertions, but " + this.assertions.length + " were run" ); + } + + var good = 0, bad = 0, + tests = id("qunit-tests"); + + config.stats.all += this.assertions.length; + config.moduleStats.all += this.assertions.length; + + if ( tests ) { + var ol = document.createElement("ol"); + + for ( var i = 0; i < this.assertions.length; i++ ) { + var assertion = this.assertions[i]; + + var li = document.createElement("li"); + li.className = assertion.result ? "pass" : "fail"; + li.innerHTML = assertion.message || (assertion.result ? "okay" : "failed"); + ol.appendChild( li ); + + if ( assertion.result ) { + good++; + } else { + bad++; + config.stats.bad++; + config.moduleStats.bad++; + } + } + + // store result when possible + if ( QUnit.config.reorder && defined.sessionStorage ) { + if (bad) { + sessionStorage.setItem("qunit-" + this.module + "-" + this.testName, bad); + } else { + sessionStorage.removeItem("qunit-" + this.module + "-" + this.testName); + } + } + + if (bad == 0) { + ol.style.display = "none"; + } + + var b = document.createElement("strong"); + b.innerHTML = this.name + " (" + bad + ", " + good + ", " + this.assertions.length + ")"; + + var a = document.createElement("a"); + a.innerHTML = "Rerun"; + a.href = QUnit.url({ filter: getText([b]).replace(/\([^)]+\)$/, "").replace(/(^\s*|\s*$)/g, "") }); + + addEvent(b, "click", function() { + var next = b.nextSibling.nextSibling, + display = next.style.display; + next.style.display = display === "none" ? "block" : "none"; + }); + + addEvent(b, "dblclick", function(e) { + var target = e && e.target ? e.target : window.event.srcElement; + if ( target.nodeName.toLowerCase() == "span" || target.nodeName.toLowerCase() == "b" ) { + target = target.parentNode; + } + if ( window.location && target.nodeName.toLowerCase() === "strong" ) { + window.location = QUnit.url({ filter: getText([target]).replace(/\([^)]+\)$/, "").replace(/(^\s*|\s*$)/g, "") }); + } + }); + + var li = id(this.id); + li.className = bad ? "fail" : "pass"; + li.removeChild( li.firstChild ); + li.appendChild( b ); + li.appendChild( a ); + li.appendChild( ol ); + + } else { + for ( var i = 0; i < this.assertions.length; i++ ) { + if ( !this.assertions[i].result ) { + bad++; + config.stats.bad++; + config.moduleStats.bad++; + } + } + } + + try { + QUnit.reset(); + } catch(e) { + fail("reset() failed, following Test " + this.testName + ", exception and reset fn follows", e, QUnit.reset); + } + + QUnit.testDone( { + name: this.testName, + failed: bad, + passed: this.assertions.length - bad, + total: this.assertions.length + } ); + }, + + queue: function() { + var test = this; + synchronize(function() { + test.init(); + }); + function run() { + // each of these can by async + synchronize(function() { + test.setup(); + }); + synchronize(function() { + test.run(); + }); + synchronize(function() { + test.teardown(); + }); + synchronize(function() { + test.finish(); + }); + } + // defer when previous test run passed, if storage is available + var bad = QUnit.config.reorder && defined.sessionStorage && +sessionStorage.getItem("qunit-" + this.module + "-" + this.testName); + if (bad) { + run(); + } else { + synchronize(run); + }; + } + +}; + +var QUnit = { + + // call on start of module test to prepend name to all tests + module: function(name, testEnvironment) { + config.currentModule = name; + config.currentModuleTestEnviroment = testEnvironment; + }, + + asyncTest: function(testName, expected, callback) { + if ( arguments.length === 2 ) { + callback = expected; + expected = 0; + } + + QUnit.test(testName, expected, callback, true); + }, + + test: function(testName, expected, callback, async) { + var name = '' + testName + '', testEnvironmentArg; + + if ( arguments.length === 2 ) { + callback = expected; + expected = null; + } + // is 2nd argument a testEnvironment? + if ( expected && typeof expected === 'object') { + testEnvironmentArg = expected; + expected = null; + } + + if ( config.currentModule ) { + name = '' + config.currentModule + ": " + name; + } + + if ( !validTest(config.currentModule + ": " + testName) ) { + return; + } + + var test = new Test(name, testName, expected, testEnvironmentArg, async, callback); + test.module = config.currentModule; + test.moduleTestEnvironment = config.currentModuleTestEnviroment; + test.queue(); + }, + + /** + * Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through. + */ + expect: function(asserts) { + config.current.expected = asserts; + }, + + /** + * Asserts true. + * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" ); + */ + ok: function(a, msg) { + a = !!a; + var details = { + result: a, + message: msg + }; + msg = escapeHtml(msg); + QUnit.log(details); + config.current.assertions.push({ + result: a, + message: msg + }); + }, + + /** + * Checks that the first two arguments are equal, with an optional message. + * Prints out both actual and expected values. + * + * Prefered to ok( actual == expected, message ) + * + * @example equal( format("Received {0} bytes.", 2), "Received 2 bytes." ); + * + * @param Object actual + * @param Object expected + * @param String message (optional) + */ + equal: function(actual, expected, message) { + QUnit.push(expected == actual, actual, expected, message); + }, + + notEqual: function(actual, expected, message) { + QUnit.push(expected != actual, actual, expected, message); + }, + + deepEqual: function(actual, expected, message) { + QUnit.push(QUnit.equiv(actual, expected), actual, expected, message); + }, + + notDeepEqual: function(actual, expected, message) { + QUnit.push(!QUnit.equiv(actual, expected), actual, expected, message); + }, + + strictEqual: function(actual, expected, message) { + QUnit.push(expected === actual, actual, expected, message); + }, + + notStrictEqual: function(actual, expected, message) { + QUnit.push(expected !== actual, actual, expected, message); + }, + + raises: function(block, expected, message) { + var actual, ok = false; + + if (typeof expected === 'string') { + message = expected; + expected = null; + } + + try { + block(); + } catch (e) { + actual = e; + } + + if (actual) { + // we don't want to validate thrown error + if (!expected) { + ok = true; + // expected is a regexp + } else if (QUnit.objectType(expected) === "regexp") { + ok = expected.test(actual); + // expected is a constructor + } else if (actual instanceof expected) { + ok = true; + // expected is a validation function which returns true is validation passed + } else if (expected.call({}, actual) === true) { + ok = true; + } + } + + QUnit.ok(ok, message); + }, + + start: function() { + config.semaphore--; + if (config.semaphore > 0) { + // don't start until equal number of stop-calls + return; + } + if (config.semaphore < 0) { + // ignore if start is called more often then stop + config.semaphore = 0; + } + // A slight delay, to avoid any current callbacks + if ( defined.setTimeout ) { + window.setTimeout(function() { + if ( config.timeout ) { + clearTimeout(config.timeout); + } + + config.blocking = false; + process(); + }, 13); + } else { + config.blocking = false; + process(); + } + }, + + stop: function(timeout) { + config.semaphore++; + config.blocking = true; + + if ( timeout && defined.setTimeout ) { + clearTimeout(config.timeout); + config.timeout = window.setTimeout(function() { + QUnit.ok( false, "Test timed out" ); + QUnit.start(); + }, timeout); + } + } +}; + +// Backwards compatibility, deprecated +QUnit.equals = QUnit.equal; +QUnit.same = QUnit.deepEqual; + +// Maintain internal state +var config = { + // The queue of tests to run + queue: [], + + // block until document ready + blocking: true, + + // by default, run previously failed tests first + // very useful in combination with "Hide passed tests" checked + reorder: true, + + noglobals: false, + notrycatch: false +}; + +// Load paramaters +(function() { + var location = window.location || { search: "", protocol: "file:" }, + params = location.search.slice( 1 ).split( "&" ), + length = params.length, + urlParams = {}, + current; + + if ( params[ 0 ] ) { + for ( var i = 0; i < length; i++ ) { + current = params[ i ].split( "=" ); + current[ 0 ] = decodeURIComponent( current[ 0 ] ); + // allow just a key to turn on a flag, e.g., test.html?noglobals + current[ 1 ] = current[ 1 ] ? decodeURIComponent( current[ 1 ] ) : true; + urlParams[ current[ 0 ] ] = current[ 1 ]; + if ( current[ 0 ] in config ) { + config[ current[ 0 ] ] = current[ 1 ]; + } + } + } + + QUnit.urlParams = urlParams; + config.filter = urlParams.filter; + + // Figure out if we're running the tests from a server or not + QUnit.isLocal = !!(location.protocol === 'file:'); +})(); + +// Expose the API as global variables, unless an 'exports' +// object exists, in that case we assume we're in CommonJS +if ( typeof exports === "undefined" || typeof require === "undefined" ) { + extend(window, QUnit); + window.QUnit = QUnit; +} else { + extend(exports, QUnit); + exports.QUnit = QUnit; +} + +// define these after exposing globals to keep them in these QUnit namespace only +extend(QUnit, { + config: config, + + // Initialize the configuration options + init: function() { + extend(config, { + stats: { all: 0, bad: 0 }, + moduleStats: { all: 0, bad: 0 }, + started: +new Date, + updateRate: 1000, + blocking: false, + autostart: true, + autorun: false, + filter: "", + queue: [], + semaphore: 0 + }); + + var tests = id( "qunit-tests" ), + banner = id( "qunit-banner" ), + result = id( "qunit-testresult" ); + + if ( tests ) { + tests.innerHTML = ""; + } + + if ( banner ) { + banner.className = ""; + } + + if ( result ) { + result.parentNode.removeChild( result ); + } + + if ( tests ) { + result = document.createElement( "p" ); + result.id = "qunit-testresult"; + result.className = "result"; + tests.parentNode.insertBefore( result, tests ); + result.innerHTML = 'Running...
     '; + } + }, + + /** + * Resets the test setup. Useful for tests that modify the DOM. + * + * If jQuery is available, uses jQuery's html(), otherwise just innerHTML. + */ + reset: function() { + if ( window.jQuery ) { + jQuery( "#qunit-fixture" ).html( config.fixture ); + } else { + var main = id( 'qunit-fixture' ); + if ( main ) { + main.innerHTML = config.fixture; + } + } + }, + + /** + * Trigger an event on an element. + * + * @example triggerEvent( document.body, "click" ); + * + * @param DOMElement elem + * @param String type + */ + triggerEvent: function( elem, type, event ) { + if ( document.createEvent ) { + event = document.createEvent("MouseEvents"); + event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView, + 0, 0, 0, 0, 0, false, false, false, false, 0, null); + elem.dispatchEvent( event ); + + } else if ( elem.fireEvent ) { + elem.fireEvent("on"+type); + } + }, + + // Safe object type checking + is: function( type, obj ) { + return QUnit.objectType( obj ) == type; + }, + + objectType: function( obj ) { + if (typeof obj === "undefined") { + return "undefined"; + + // consider: typeof null === object + } + if (obj === null) { + return "null"; + } + + var type = Object.prototype.toString.call( obj ) + .match(/^\[object\s(.*)\]$/)[1] || ''; + + switch (type) { + case 'Number': + if (isNaN(obj)) { + return "nan"; + } else { + return "number"; + } + case 'String': + case 'Boolean': + case 'Array': + case 'Date': + case 'RegExp': + case 'Function': + return type.toLowerCase(); + } + if (typeof obj === "object") { + return "object"; + } + return undefined; + }, + + push: function(result, actual, expected, message) { + var details = { + result: result, + message: message, + actual: actual, + expected: expected + }; + + message = escapeHtml(message) || (result ? "okay" : "failed"); + message = '' + message + ""; + expected = escapeHtml(QUnit.jsDump.parse(expected)); + actual = escapeHtml(QUnit.jsDump.parse(actual)); + var output = message + ''; + if (actual != expected) { + output += ''; + output += ''; + } + if (!result) { + var source = sourceFromStacktrace(); + if (source) { + details.source = source; + output += ''; + } + } + output += "
    Expected:
    ' + expected + '
    Result:
    ' + actual + '
    Diff:
    ' + QUnit.diff(expected, actual) +'
    Source:
    ' + source +'
    "; + + QUnit.log(details); + + config.current.assertions.push({ + result: !!result, + message: output + }); + }, + + url: function( params ) { + params = extend( extend( {}, QUnit.urlParams ), params ); + var querystring = "?", + key; + for ( key in params ) { + querystring += encodeURIComponent( key ) + "=" + + encodeURIComponent( params[ key ] ) + "&"; + } + return window.location.pathname + querystring.slice( 0, -1 ); + }, + + // Logging callbacks; all receive a single argument with the listed properties + // run test/logs.html for any related changes + begin: function() {}, + // done: { failed, passed, total, runtime } + done: function() {}, + // log: { result, actual, expected, message } + log: function() {}, + // testStart: { name } + testStart: function() {}, + // testDone: { name, failed, passed, total } + testDone: function() {}, + // moduleStart: { name } + moduleStart: function() {}, + // moduleDone: { name, failed, passed, total } + moduleDone: function() {} +}); + +if ( typeof document === "undefined" || document.readyState === "complete" ) { + config.autorun = true; +} + +addEvent(window, "load", function() { + QUnit.begin({}); + + // Initialize the config, saving the execution queue + var oldconfig = extend({}, config); + QUnit.init(); + extend(config, oldconfig); + + config.blocking = false; + + var userAgent = id("qunit-userAgent"); + if ( userAgent ) { + userAgent.innerHTML = navigator.userAgent; + } + var banner = id("qunit-header"); + if ( banner ) { + banner.innerHTML = ' ' + banner.innerHTML + ' ' + + '' + + ''; + addEvent( banner, "change", function( event ) { + var params = {}; + params[ event.target.name ] = event.target.checked ? true : undefined; + window.location = QUnit.url( params ); + }); + } + + var toolbar = id("qunit-testrunner-toolbar"); + if ( toolbar ) { + var filter = document.createElement("input"); + filter.type = "checkbox"; + filter.id = "qunit-filter-pass"; + addEvent( filter, "click", function() { + var ol = document.getElementById("qunit-tests"); + if ( filter.checked ) { + ol.className = ol.className + " hidepass"; + } else { + var tmp = " " + ol.className.replace( /[\n\t\r]/g, " " ) + " "; + ol.className = tmp.replace(/ hidepass /, " "); + } + if ( defined.sessionStorage ) { + if (filter.checked) { + sessionStorage.setItem("qunit-filter-passed-tests", "true"); + } else { + sessionStorage.removeItem("qunit-filter-passed-tests"); + } + } + }); + if ( defined.sessionStorage && sessionStorage.getItem("qunit-filter-passed-tests") ) { + filter.checked = true; + var ol = document.getElementById("qunit-tests"); + ol.className = ol.className + " hidepass"; + } + toolbar.appendChild( filter ); + + var label = document.createElement("label"); + label.setAttribute("for", "qunit-filter-pass"); + label.innerHTML = "Hide passed tests"; + toolbar.appendChild( label ); + } + + var main = id('qunit-fixture'); + if ( main ) { + config.fixture = main.innerHTML; + } + + if (config.autostart) { + QUnit.start(); + } +}); + +function done() { + config.autorun = true; + + // Log the last module results + if ( config.currentModule ) { + QUnit.moduleDone( { + name: config.currentModule, + failed: config.moduleStats.bad, + passed: config.moduleStats.all - config.moduleStats.bad, + total: config.moduleStats.all + } ); + } + + var banner = id("qunit-banner"), + tests = id("qunit-tests"), + runtime = +new Date - config.started, + passed = config.stats.all - config.stats.bad, + html = [ + 'Tests completed in ', + runtime, + ' milliseconds.
    ', + '', + passed, + ' tests of ', + config.stats.all, + ' passed, ', + config.stats.bad, + ' failed.' + ].join(''); + + if ( banner ) { + banner.className = (config.stats.bad ? "qunit-fail" : "qunit-pass"); + } + + if ( tests ) { + id( "qunit-testresult" ).innerHTML = html; + } + + if ( typeof document !== "undefined" && document.title ) { + // show ✖ for good, ✔ for bad suite result in title + // use escape sequences in case file gets loaded with non-utf-8-charset + document.title = (config.stats.bad ? "\u2716" : "\u2714") + " " + document.title; + } + + QUnit.done( { + failed: config.stats.bad, + passed: passed, + total: config.stats.all, + runtime: runtime + } ); +} + +function validTest( name ) { + var filter = config.filter, + run = false; + + if ( !filter ) { + return true; + } + + var not = filter.charAt( 0 ) === "!"; + if ( not ) { + filter = filter.slice( 1 ); + } + + if ( name.indexOf( filter ) !== -1 ) { + return !not; + } + + if ( not ) { + run = true; + } + + return run; +} + +// so far supports only Firefox, Chrome and Opera (buggy) +// could be extended in the future to use something like https://github.com/csnover/TraceKit +function sourceFromStacktrace() { + try { + throw new Error(); + } catch ( e ) { + if (e.stacktrace) { + // Opera + return e.stacktrace.split("\n")[6]; + } else if (e.stack) { + // Firefox, Chrome + return e.stack.split("\n")[4]; + } + } +} + +function escapeHtml(s) { + if (!s) { + return ""; + } + s = s + ""; + return s.replace(/[\&"<>\\]/g, function(s) { + switch(s) { + case "&": return "&"; + case "\\": return "\\\\"; + case '"': return '\"'; + case "<": return "<"; + case ">": return ">"; + default: return s; + } + }); +} + +function synchronize( callback ) { + config.queue.push( callback ); + + if ( config.autorun && !config.blocking ) { + process(); + } +} + +function process() { + var start = (new Date()).getTime(); + + while ( config.queue.length && !config.blocking ) { + if ( config.updateRate <= 0 || (((new Date()).getTime() - start) < config.updateRate) ) { + config.queue.shift()(); + } else { + window.setTimeout( process, 13 ); + break; + } + } + if (!config.blocking && !config.queue.length) { + done(); + } +} + +function saveGlobal() { + config.pollution = []; + + if ( config.noglobals ) { + for ( var key in window ) { + config.pollution.push( key ); + } + } +} + +function checkPollution( name ) { + var old = config.pollution; + saveGlobal(); + + var newGlobals = diff( config.pollution, old ); + if ( newGlobals.length > 0 ) { + ok( false, "Introduced global variable(s): " + newGlobals.join(", ") ); + } + + var deletedGlobals = diff( old, config.pollution ); + if ( deletedGlobals.length > 0 ) { + ok( false, "Deleted global variable(s): " + deletedGlobals.join(", ") ); + } +} + +// returns a new Array with the elements that are in a but not in b +function diff( a, b ) { + var result = a.slice(); + for ( var i = 0; i < result.length; i++ ) { + for ( var j = 0; j < b.length; j++ ) { + if ( result[i] === b[j] ) { + result.splice(i, 1); + i--; + break; + } + } + } + return result; +} + +function fail(message, exception, callback) { + if ( typeof console !== "undefined" && console.error && console.warn ) { + console.error(message); + console.error(exception); + console.warn(callback.toString()); + + } else if ( window.opera && opera.postError ) { + opera.postError(message, exception, callback.toString); + } +} + +function extend(a, b) { + for ( var prop in b ) { + if ( b[prop] === undefined ) { + delete a[prop]; + } else { + a[prop] = b[prop]; + } + } + + return a; +} + +function addEvent(elem, type, fn) { + if ( elem.addEventListener ) { + elem.addEventListener( type, fn, false ); + } else if ( elem.attachEvent ) { + elem.attachEvent( "on" + type, fn ); + } else { + fn(); + } +} + +function id(name) { + return !!(typeof document !== "undefined" && document && document.getElementById) && + document.getElementById( name ); +} + +// Test for equality any JavaScript type. +// Discussions and reference: http://philrathe.com/articles/equiv +// Test suites: http://philrathe.com/tests/equiv +// Author: Philippe Rathé +QUnit.equiv = function () { + + var innerEquiv; // the real equiv function + var callers = []; // stack to decide between skip/abort functions + var parents = []; // stack to avoiding loops from circular referencing + + // Call the o related callback with the given arguments. + function bindCallbacks(o, callbacks, args) { + var prop = QUnit.objectType(o); + if (prop) { + if (QUnit.objectType(callbacks[prop]) === "function") { + return callbacks[prop].apply(callbacks, args); + } else { + return callbacks[prop]; // or undefined + } + } + } + + var callbacks = function () { + + // for string, boolean, number and null + function useStrictEquality(b, a) { + if (b instanceof a.constructor || a instanceof b.constructor) { + // to catch short annotaion VS 'new' annotation of a declaration + // e.g. var i = 1; + // var j = new Number(1); + return a == b; + } else { + return a === b; + } + } + + return { + "string": useStrictEquality, + "boolean": useStrictEquality, + "number": useStrictEquality, + "null": useStrictEquality, + "undefined": useStrictEquality, + + "nan": function (b) { + return isNaN(b); + }, + + "date": function (b, a) { + return QUnit.objectType(b) === "date" && a.valueOf() === b.valueOf(); + }, + + "regexp": function (b, a) { + return QUnit.objectType(b) === "regexp" && + a.source === b.source && // the regex itself + a.global === b.global && // and its modifers (gmi) ... + a.ignoreCase === b.ignoreCase && + a.multiline === b.multiline; + }, + + // - skip when the property is a method of an instance (OOP) + // - abort otherwise, + // initial === would have catch identical references anyway + "function": function () { + var caller = callers[callers.length - 1]; + return caller !== Object && + typeof caller !== "undefined"; + }, + + "array": function (b, a) { + var i, j, loop; + var len; + + // b could be an object literal here + if ( ! (QUnit.objectType(b) === "array")) { + return false; + } + + len = a.length; + if (len !== b.length) { // safe and faster + return false; + } + + //track reference to avoid circular references + parents.push(a); + for (i = 0; i < len; i++) { + loop = false; + for(j=0;j= 0) { + type = "array"; + } else { + type = typeof obj; + } + return type; + }, + separator:function() { + return this.multiline ? this.HTML ? '
    ' : '\n' : this.HTML ? ' ' : ' '; + }, + indent:function( extra ) {// extra can be a number, shortcut for increasing-calling-decreasing + if ( !this.multiline ) + return ''; + var chr = this.indentChar; + if ( this.HTML ) + chr = chr.replace(/\t/g,' ').replace(/ /g,' '); + return Array( this._depth_ + (extra||0) ).join(chr); + }, + up:function( a ) { + this._depth_ += a || 1; + }, + down:function( a ) { + this._depth_ -= a || 1; + }, + setParser:function( name, parser ) { + this.parsers[name] = parser; + }, + // The next 3 are exposed so you can use them + quote:quote, + literal:literal, + join:join, + // + _depth_: 1, + // This is the list of parsers, to modify them, use jsDump.setParser + parsers:{ + window: '[Window]', + document: '[Document]', + error:'[ERROR]', //when no parser is found, shouldn't happen + unknown: '[Unknown]', + 'null':'null', + 'undefined':'undefined', + 'function':function( fn ) { + var ret = 'function', + name = 'name' in fn ? fn.name : (reName.exec(fn)||[])[1];//functions never have name in IE + if ( name ) + ret += ' ' + name; + ret += '('; + + ret = [ ret, QUnit.jsDump.parse( fn, 'functionArgs' ), '){'].join(''); + return join( ret, QUnit.jsDump.parse(fn,'functionCode'), '}' ); + }, + array: array, + nodelist: array, + arguments: array, + object:function( map ) { + var ret = [ ]; + QUnit.jsDump.up(); + for ( var key in map ) + ret.push( QUnit.jsDump.parse(key,'key') + ': ' + QUnit.jsDump.parse(map[key]) ); + QUnit.jsDump.down(); + return join( '{', ret, '}' ); + }, + node:function( node ) { + var open = QUnit.jsDump.HTML ? '<' : '<', + close = QUnit.jsDump.HTML ? '>' : '>'; + + var tag = node.nodeName.toLowerCase(), + ret = open + tag; + + for ( var a in QUnit.jsDump.DOMAttrs ) { + var val = node[QUnit.jsDump.DOMAttrs[a]]; + if ( val ) + ret += ' ' + a + '=' + QUnit.jsDump.parse( val, 'attribute' ); + } + return ret + close + open + '/' + tag + close; + }, + functionArgs:function( fn ) {//function calls it internally, it's the arguments part of the function + var l = fn.length; + if ( !l ) return ''; + + var args = Array(l); + while ( l-- ) + args[l] = String.fromCharCode(97+l);//97 is 'a' + return ' ' + args.join(', ') + ' '; + }, + key:quote, //object calls it internally, the key part of an item in a map + functionCode:'[code]', //function calls it internally, it's the content of the function + attribute:quote, //node calls it internally, it's an html attribute value + string:quote, + date:quote, + regexp:literal, //regex + number:literal, + 'boolean':literal + }, + DOMAttrs:{//attributes to dump from nodes, name=>realName + id:'id', + name:'name', + 'class':'className' + }, + HTML:false,//if true, entities are escaped ( <, >, \t, space and \n ) + indentChar:' ',//indentation unit + multiline:true //if true, items in a collection, are separated by a \n, else just a space. + }; + + return jsDump; +})(); + +// from Sizzle.js +function getText( elems ) { + var ret = "", elem; + + for ( var i = 0; elems[i]; i++ ) { + elem = elems[i]; + + // Get the text from text nodes and CDATA nodes + if ( elem.nodeType === 3 || elem.nodeType === 4 ) { + ret += elem.nodeValue; + + // Traverse everything else, except comment nodes + } else if ( elem.nodeType !== 8 ) { + ret += getText( elem.childNodes ); + } + } + + return ret; +}; + +/* + * Javascript Diff Algorithm + * By John Resig (http://ejohn.org/) + * Modified by Chu Alan "sprite" + * + * Released under the MIT license. + * + * More Info: + * http://ejohn.org/projects/javascript-diff-algorithm/ + * + * Usage: QUnit.diff(expected, actual) + * + * QUnit.diff("the quick brown fox jumped over", "the quick fox jumps over") == "the quick brown fox jumped jumps over" + */ +QUnit.diff = (function() { + function diff(o, n){ + var ns = new Object(); + var os = new Object(); + + for (var i = 0; i < n.length; i++) { + if (ns[n[i]] == null) + ns[n[i]] = { + rows: new Array(), + o: null + }; + ns[n[i]].rows.push(i); + } + + for (var i = 0; i < o.length; i++) { + if (os[o[i]] == null) + os[o[i]] = { + rows: new Array(), + n: null + }; + os[o[i]].rows.push(i); + } + + for (var i in ns) { + if (ns[i].rows.length == 1 && typeof(os[i]) != "undefined" && os[i].rows.length == 1) { + n[ns[i].rows[0]] = { + text: n[ns[i].rows[0]], + row: os[i].rows[0] + }; + o[os[i].rows[0]] = { + text: o[os[i].rows[0]], + row: ns[i].rows[0] + }; + } + } + + for (var i = 0; i < n.length - 1; i++) { + if (n[i].text != null && n[i + 1].text == null && n[i].row + 1 < o.length && o[n[i].row + 1].text == null && + n[i + 1] == o[n[i].row + 1]) { + n[i + 1] = { + text: n[i + 1], + row: n[i].row + 1 + }; + o[n[i].row + 1] = { + text: o[n[i].row + 1], + row: i + 1 + }; + } + } + + for (var i = n.length - 1; i > 0; i--) { + if (n[i].text != null && n[i - 1].text == null && n[i].row > 0 && o[n[i].row - 1].text == null && + n[i - 1] == o[n[i].row - 1]) { + n[i - 1] = { + text: n[i - 1], + row: n[i].row - 1 + }; + o[n[i].row - 1] = { + text: o[n[i].row - 1], + row: i - 1 + }; + } + } + + return { + o: o, + n: n + }; + } + + return function(o, n){ + o = o.replace(/\s+$/, ''); + n = n.replace(/\s+$/, ''); + var out = diff(o == "" ? [] : o.split(/\s+/), n == "" ? [] : n.split(/\s+/)); + + var str = ""; + + var oSpace = o.match(/\s+/g); + if (oSpace == null) { + oSpace = [" "]; + } + else { + oSpace.push(" "); + } + var nSpace = n.match(/\s+/g); + if (nSpace == null) { + nSpace = [" "]; + } + else { + nSpace.push(" "); + } + + if (out.n.length == 0) { + for (var i = 0; i < out.o.length; i++) { + str += '' + out.o[i] + oSpace[i] + ""; + } + } + else { + if (out.n[0].text == null) { + for (n = 0; n < out.o.length && out.o[n].text == null; n++) { + str += '' + out.o[n] + oSpace[n] + ""; + } + } + + for (var i = 0; i < out.n.length; i++) { + if (out.n[i].text == null) { + str += '' + out.n[i] + nSpace[i] + ""; + } + else { + var pre = ""; + + for (n = out.n[i].row + 1; n < out.o.length && out.o[n].text == null; n++) { + pre += '' + out.o[n] + oSpace[n] + ""; + } + str += " " + out.n[i].text + nSpace[i] + pre; + } + } + } + + return str; + }; +})(); + +})(this); \ No newline at end of file diff --git a/DroidGap/lawnchair-adapter-test/www/master.css b/DroidGap/lawnchair-adapter-test/www/master.css new file mode 100644 index 0000000..d4401f5 --- /dev/null +++ b/DroidGap/lawnchair-adapter-test/www/master.css @@ -0,0 +1,96 @@ + body { + background:#222 none repeat scroll 0 0; + color:#666; + font-family:Helvetica; + font-size:72%; + line-height:1.5em; + margin:0; + border-top:1px solid #393939; + } + + #info{ + background:#ffa; + border: 1px solid #ffd324; + -webkit-border-radius: 5px; + border-radius: 5px; + clear:both; + margin:15px 6px 0; + width:295px; + padding:4px 0px 2px 10px; + } + + #info > h4{ + font-size:.95em; + margin:5px 0; + } + + #stage.theme{ + padding-top:3px; + } + + /* Definition List */ + #stage.theme > dl{ + padding-top:10px; + clear:both; + margin:0; + list-style-type:none; + padding-left:10px; + overflow:auto; + } + + #stage.theme > dl > dt{ + font-weight:bold; + float:left; + margin-left:5px; + } + + #stage.theme > dl > dd{ + width:45px; + float:left; + color:#a87; + font-weight:bold; + } + + /* Content Styling */ + #stage.theme > h1, #stage.theme > h2, #stage.theme > p{ + margin:1em 0 .5em 13px; + } + + #stage.theme > h1{ + color:#eee; + font-size:1.6em; + text-align:center; + margin:0; + margin-top:15px; + padding:0; + } + + #stage.theme > h2{ + clear:both; + margin:0; + padding:3px; + font-size:1em; + text-align:center; + } + + /* Stage Buttons */ + #stage.theme a.btn{ + border: 1px solid #555; + -webkit-border-radius: 5px; + border-radius: 5px; + text-align:center; + display:block; + float:left; + background:#444; + width:150px; + color:#9ab; + font-size:1.1em; + text-decoration:none; + padding:1.2em 0; + margin:3px 0px 3px 5px; + } + #stage.theme a.btn.large{ + width:308px; + padding:1.2em 0; + } + diff --git a/DroidGap/lawnchair-adapter-test/www/webkit-sqlite.js b/DroidGap/lawnchair-adapter-test/www/webkit-sqlite.js new file mode 100644 index 0000000..6e1fadc --- /dev/null +++ b/DroidGap/lawnchair-adapter-test/www/webkit-sqlite.js @@ -0,0 +1,201 @@ +Lawnchair.adapter('webkit-sqlite', (function () { + // private methods + var fail = function (e, i) { console.log('error in sqlite adaptor!', e, i) } + , now = function () { return new Date() } // FIXME need to use better date fn + // not entirely sure if this is needed... + if (!Function.prototype.bind) { + Function.prototype.bind = function( obj ) { + var slice = [].slice + , args = slice.call(arguments, 1) + , self = this + , nop = function () {} + , bound = function () { + return self.apply(this instanceof nop ? this : (obj || {}), args.concat(slice.call(arguments))) + } + nop.prototype = self.prototype + bound.prototype = new nop() + return bound + } + } + + // public methods + return { + + //valid: function() { return !!(window.openDatabase) }, + valid: function() { return !!(window.my_openDatabase) }, + //valid: function() { return !!(sqlitePlugin.openDatabase) }, + + init: function (options, callback) { + var that = this + , cb = that.fn(that.name, callback) + , create = "CREATE TABLE IF NOT EXISTS " + this.name + " (id NVARCHAR(32) UNIQUE PRIMARY KEY, value TEXT, timestamp REAL)" + , win = function(){ return cb.call(that, that); } + // open a connection and create the db if it doesn't exist + //this.db = openDatabase(this.name, '1.0.0', this.name, 65536) + //this.db = my_openDatabase(this.name, '1.0.0', this.name, 65536) + this.db = window.my_openDatabase("Database", "1.0", "PhoneGap Demo", 200000); + //this.db = sqlitePlugin.openDatabase(this.name, '1.0.0', this.name, 65536) + this.db.transaction(function (t) { + t.executeSql(create, [], win, fail) + }) + }, + + keys: function (callback) { + var cb = this.lambda(callback) + , that = this + , keys = "SELECT id FROM " + this.name + " ORDER BY timestamp DESC" + + this.db.transaction(function(t) { + var win = function (xxx, results) { + if (results.rows.length == 0 ) { + cb.call(that, []) + } else { + var r = []; + for (var i = 0, l = results.rows.length; i < l; i++) { + r.push(results.rows.item(i).id); + } + cb.call(that, r) + } + } + t.executeSql(keys, [], win, fail) + }) + return this + }, + // you think thats air you're breathing now? + save: function (obj, callback) { + var that = this + , id = obj.key || that.uuid() + , ins = "INSERT INTO " + this.name + " (value, timestamp, id) VALUES (?,?,?)" + , up = "UPDATE " + this.name + " SET value=?, timestamp=? WHERE id=?" + , win = function () { if (callback) { obj.key = id; that.lambda(callback).call(that, obj) }} + , val = [now(), id] + // existential + that.exists(obj.key, function(exists) { + // transactions are like condoms + that.db.transaction(function(t) { + // TODO move timestamp to a plugin + var insert = function (obj) { + val.unshift(JSON.stringify(obj)) + t.executeSql(ins, val, win, fail) + } + // TODO move timestamp to a plugin + var update = function (obj) { + delete(obj.key) + val.unshift(JSON.stringify(obj)) + t.executeSql(up, val, win, fail) + } + // pretty + exists ? update(obj) : insert(obj) + }) + }); + return this + }, + + // FIXME this should be a batch insert / just getting the test to pass... + batch: function (objs, cb) { + + var results = [] + , done = false + , that = this + + var updateProgress = function(obj) { + results.push(obj) + done = results.length === objs.length + } + + var checkProgress = setInterval(function() { + if (done) { + if (cb) that.lambda(cb).call(that, results) + clearInterval(checkProgress) + } + }, 200) + + for (var i = 0, l = objs.length; i < l; i++) + this.save(objs[i], updateProgress) + + return this + }, + + get: function (keyOrArray, cb) { + var that = this + , sql = '' + // batch selects support + if (this.isArray(keyOrArray)) { + sql = 'SELECT id, value FROM ' + this.name + " WHERE id IN ('" + keyOrArray.join("','") + "')" + } else { + sql = 'SELECT id, value FROM ' + this.name + " WHERE id = '" + keyOrArray + "'" + } + // FIXME + // will always loop the results but cleans it up if not a batch return at the end.. + // in other words, this could be faster + var win = function (xxx, results) { + var o = null + , r = [] + if (results.rows.length) { + for (var i = 0, l = results.rows.length; i < l; i++) { + o = JSON.parse(results.rows.item(i).value) + o.key = results.rows.item(i).id + r.push(o) + } + } + if (!that.isArray(keyOrArray)) r = r.length ? r[0] : null + if (cb) that.lambda(cb).call(that, r) + } + this.db.transaction(function(t){ t.executeSql(sql, [], win, fail) }) + return this + }, + + exists: function (key, cb) { + var is = "SELECT * FROM " + this.name + " WHERE id = ?" + , that = this + , win = function(xxx, results) { if (cb) that.fn('exists', cb).call(that, (results.rows.length > 0)) } + this.db.transaction(function(t){ t.executeSql(is, [key], win, fail) }) + return this + }, + + all: function (callback) { + var that = this + , all = "SELECT * FROM " + this.name + , r = [] + , cb = this.fn(this.name, callback) || undefined + , win = function (xxx, results) { + if (results.rows.length != 0) { + for (var i = 0, l = results.rows.length; i < l; i++) { + var obj = JSON.parse(results.rows.item(i).value) + obj.key = results.rows.item(i).id + r.push(obj) + } + } + if (cb) cb.call(that, r) + } + + this.db.transaction(function (t) { + t.executeSql(all, [], win, fail) + }) + return this + }, + + remove: function (keyOrObj, cb) { + var that = this + , key = typeof keyOrObj === 'string' ? keyOrObj : keyOrObj.key + , del = "DELETE FROM " + this.name + " WHERE id = ?" + , win = function () { if (cb) that.lambda(cb).call(that) } + + this.db.transaction( function (t) { + t.executeSql(del, [key], win, fail); + }); + + return this; + }, + + nuke: function (cb) { + var nuke = "DELETE FROM " + this.name + , that = this + , win = cb ? function() { that.lambda(cb).call(that) } : function(){} + this.db.transaction(function (t) { + t.executeSql(nuke, [], win, fail) + }) + return this + } +////// +}})()) diff --git a/README.md b/README.md index 866881a..c25db44 100644 --- a/README.md +++ b/README.md @@ -230,6 +230,8 @@ Legacy PhoneGap (old version) Lawnchair Adapter Usage ======================= +**NOTE:** For the Android version see DroidGap/lawnchair-adapter-test, which is using a Lawnchair adapter based on the original WebKit version. The plan is to make this one work for both iOS and Android versions. + Include the following js files in your html: - lawnchair.js (you provide) From 0aff2f23f7ed4d6b377e283d230f870be21ab7ca Mon Sep 17 00:00:00 2001 From: Chris Brody Date: Mon, 9 Apr 2012 18:21:52 +0200 Subject: [PATCH 33/44] Fix DroidGap/lawnchair-adapter-test/www/index.html to omit extra test --- DroidGap/lawnchair-adapter-test/www/index.html | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/DroidGap/lawnchair-adapter-test/www/index.html b/DroidGap/lawnchair-adapter-test/www/index.html index dc44255..ac9fbf4 100644 --- a/DroidGap/lawnchair-adapter-test/www/index.html +++ b/DroidGap/lawnchair-adapter-test/www/index.html @@ -71,6 +71,8 @@ function start1() { document.addEventListener("deviceready", startTests, false); } +/** + function init1test() { //alert("alert 0"); } @@ -82,6 +84,7 @@ function init1test() { // PhoneGap is ready // function onDeviceReady() { + //var db = window.sqlitePlugin.openDatabase("Database", "1.0", "PhoneGap Demo", 200000); var db = window.my_openDatabase("Database", "1.0", "PhoneGap Demo", 200000); db.transaction(function(tx) { @@ -135,6 +138,7 @@ alert("a 7"); function successCB() { alert("success!"); } +**/ From 7402f0d3addba2ecda71a43987edfb1b5eebcd07 Mon Sep 17 00:00:00 2001 From: Chris Brody Date: Mon, 9 Apr 2012 18:40:51 +0200 Subject: [PATCH 34/44] sqlitePlugin.openDatabase() instead of window.my_openDatabase() in Android (DroidGap) version, fix Droid lawnchair adapter --- DroidGap/assets/www/SQLitePlugin.js | 6 +++++- DroidGap/assets/www/index.html | 4 ++-- DroidGap/lawnchair-adapter-test/www/SQLitePlugin.js | 8 +++++--- DroidGap/lawnchair-adapter-test/www/webkit-sqlite.js | 7 ++++--- 4 files changed, 16 insertions(+), 9 deletions(-) diff --git a/DroidGap/assets/www/SQLitePlugin.js b/DroidGap/assets/www/SQLitePlugin.js index 9c7f6e5..4ca0f06 100644 --- a/DroidGap/assets/www/SQLitePlugin.js +++ b/DroidGap/assets/www/SQLitePlugin.js @@ -13,6 +13,8 @@ */ +// XXX TODO: use function() { ... } () to encapsulate these declarations (except for Java callback) + /** * SQL result set object * PRIVATE METHOD @@ -331,8 +333,10 @@ var DDB_openDatabase = function(name, version, display_name, size) { * @constructor */ -window.my_openDatabase = function(name, version, desc, size){ +window.sqlitePlugin = { + openDatabase: function(name, version, desc, size) { window.dddb = new DDB(); return DDB_openDatabase(name, version, desc, size); } +}; diff --git a/DroidGap/assets/www/index.html b/DroidGap/assets/www/index.html index 555768d..6928c2a 100644 --- a/DroidGap/assets/www/index.html +++ b/DroidGap/assets/www/index.html @@ -5,7 +5,7 @@ PhoneGap - + @@ -23,7 +23,7 @@ function init1test() { // PhoneGap is ready // function onDeviceReady() { - var db = window.my_openDatabase("Database", "1.0", "PhoneGap Demo", 200000); + var db = window.sqlitePlugin.openDatabase("Database", "1.0", "PhoneGap Demo", 200000); db.transaction(function(tx) { diff --git a/DroidGap/lawnchair-adapter-test/www/SQLitePlugin.js b/DroidGap/lawnchair-adapter-test/www/SQLitePlugin.js index 068def9..4ca0f06 100644 --- a/DroidGap/lawnchair-adapter-test/www/SQLitePlugin.js +++ b/DroidGap/lawnchair-adapter-test/www/SQLitePlugin.js @@ -13,6 +13,8 @@ */ +// XXX TODO: use function() { ... } () to encapsulate these declarations (except for Java callback) + /** * SQL result set object * PRIVATE METHOD @@ -317,9 +319,7 @@ DatabaseShell.prototype.transaction = function(process, errorCallback, successCa * @return Database object */ var DDB_openDatabase = function(name, version, display_name, size) { -console.log("open database " + name + " ... typeof " + typeof(PhoneGap.exec)); PhoneGap.exec(null, null, "SQLitePlugin", "openDatabase", [name, version, display_name, size]); - //cordova.exec(null, null, "SQLitePlugin", "openDatabase", [name, version, display_name, size]); var db = new DatabaseShell(); return db; }; @@ -333,8 +333,10 @@ console.log("open database " + name + " ... typeof " + typeof(PhoneGap.exec)); * @constructor */ -window.my_openDatabase = function(name, version, desc, size){ +window.sqlitePlugin = { + openDatabase: function(name, version, desc, size) { window.dddb = new DDB(); return DDB_openDatabase(name, version, desc, size); } +}; diff --git a/DroidGap/lawnchair-adapter-test/www/webkit-sqlite.js b/DroidGap/lawnchair-adapter-test/www/webkit-sqlite.js index 6e1fadc..7fafb74 100644 --- a/DroidGap/lawnchair-adapter-test/www/webkit-sqlite.js +++ b/DroidGap/lawnchair-adapter-test/www/webkit-sqlite.js @@ -22,8 +22,8 @@ Lawnchair.adapter('webkit-sqlite', (function () { return { //valid: function() { return !!(window.openDatabase) }, - valid: function() { return !!(window.my_openDatabase) }, - //valid: function() { return !!(sqlitePlugin.openDatabase) }, + //valid: function() { return !!(window.my_openDatabase) }, + valid: function() { return !!(sqlitePlugin.openDatabase) }, init: function (options, callback) { var that = this @@ -33,8 +33,9 @@ Lawnchair.adapter('webkit-sqlite', (function () { // open a connection and create the db if it doesn't exist //this.db = openDatabase(this.name, '1.0.0', this.name, 65536) //this.db = my_openDatabase(this.name, '1.0.0', this.name, 65536) - this.db = window.my_openDatabase("Database", "1.0", "PhoneGap Demo", 200000); + //this.db = window.my_openDatabase("Database", "1.0", "PhoneGap Demo", 200000); //this.db = sqlitePlugin.openDatabase(this.name, '1.0.0', this.name, 65536) + this.db = sqlitePlugin.openDatabase("Database", "1.0", "PhoneGap Demo", 200000); this.db.transaction(function (t) { t.executeSql(create, [], win, fail) }) From 81745981e06d0b2b51fe7cab36e18800e6e396b0 Mon Sep 17 00:00:00 2001 From: Chris Brody Date: Mon, 9 Apr 2012 20:41:07 +0200 Subject: [PATCH 35/44] Update README.md for DroidGap changes, common Lawnchair adapter --- README.md | 45 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c25db44..c5051de 100644 --- a/README.md +++ b/README.md @@ -94,6 +94,38 @@ Android (DroidGap) I put the information here for the sake of completeness. I have tested the DroidGap SQLitePlugin version on a simulator on PhoneGap 1.1 *ONLY* and do not guarantee what will happen in any other situation. Basically, I copied and adapted the code from storage.js and Storage.java to make a plugin version. I got the versions that were there on October 2011, so it should be OK to use them under the MIT or Apache licenses. Hereby you can take SQLitePlugin.java (it is in the wrong place but it still worked), SQLitePlugin.js, and look at my index.html, register in plugins.xml, and give it your best shot! Fork it and take it over! +**Update:** I have now tested the Android (DroidGap) SQLitePlugin on Cordova 1.5 on both a simulator and a test mobile, using the Lawnchair testsuite. In addition, I have also adapted a Lawnchair plugin to work for both the iOS and the Android. Here is the sample (in Javascript): + + // Wait for PhoneGap to load + // + document.addEventListener("deviceready", onDeviceReady, false); + + // PhoneGap is ready + // + function onDeviceReady() { + var db = window.sqlitePlugin.openDatabase("Database", "1.0", "PhoneGap Demo", 200000); + + db.transaction(function(tx) { + + tx.executeSql('DROP TABLE IF EXISTS test_table'); + tx.executeSql('CREATE TABLE IF NOT EXISTS test_table (id integer primary key, data text, data_num integer)'); + + + return tx.executeSql("INSERT INTO test_table (data, data_num) VALUES (?,?)", ["test", 100], function(tx, res) { + //console.log("insertId: " + res.insertId + " -- probably 1"); + //console.log("rowsAffected: " + res.rowsAffected + " -- should be 1"); + + tx.executeSql("select count(id) as cnt from test_table;", [], function(tx, res) { + console.log("rows.length: " + res.rows.length + " -- should be 1"); + return console.log("rows[0].cnt: " + res.rows.item(0).cnt + " -- should be 1"); + }); + + }, function(e) { + return console.log("ERROR: " + e.message); + }); + }); + + Cordova iOS ----------- @@ -232,6 +264,15 @@ Lawnchair Adapter Usage **NOTE:** For the Android version see DroidGap/lawnchair-adapter-test, which is using a Lawnchair adapter based on the original WebKit version. The plan is to make this one work for both iOS and Android versions. +Common adapter +-------------- + +Please look at the `Lawnchair-adapter` tree that contains a common adapter, working for both Android (DroidGap) and iOS, along with a test-www directory. + + +Legacy: iOS/iPhone only +----------------------- + Include the following js files in your html: - lawnchair.js (you provide) @@ -253,8 +294,8 @@ Using the `db` option you can create multiple stores in one sqlite file. (There ingredients = new Lawnchair {db: "cookbook", name: "ingredients", ...} -Lawnchair test --------------- +Legacy Lawnchair test +--------------------- In the lawnchair-test subdirectory of Cordova-iOS or Legacy-PhoneGap-iPhone you can copy the contents of the www subdirectory into a Cordova/PhoneGap project and see the behavior of the Lawnchair test suite. From d24062b58ea506f97746c6851b14b6d371672469 Mon Sep 17 00:00:00 2001 From: Chris Brody Date: Wed, 11 Apr 2012 08:00:50 +0200 Subject: [PATCH 36/44] From @mineshaftgap: patch to work with iOS Cordova 1.6 --- README.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/README.md b/README.md index c5051de..7240b73 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,25 @@ Installing **NOTE:** There are now 2 trees: `Cordova-iOS` for Cordova 1.5(+) and `Legacy-PhoneGap-iPhone` for PhoneGap (tested 1.3 and earlier). I am planning to add an Android version in another tree, hopefully in the near future. +Cordova 1.6 (RC) +---------------- + +From @mineshaftgap: you have to make the following change to Cordova-iOS/build/SQLitePlugin.js: + + diff --git a/Cordova-iOS/build/SQLitePlugin.js b/Cordova-iOS/build/SQLitePlugin.js + index 65cf62f..85649e4 100644 + --- a/Cordova-iOS/build/SQLitePlugin.js + +++ b/Cordova-iOS/build/SQLitePlugin.js + @@ -1,6 +1,8 @@ + (function() { + var SQLiteNative, SQLitePluginTransaction, callbacks, cbref, counter, getOptions, root; + + + window.Cordova = window.cordova; + + + root = this; + + callbacks = {}; + PhoneGap 1.3.0 -------------- From 410dd0d35f09dfc634fa20108e0d78ada73c4424 Mon Sep 17 00:00:00 2001 From: Chris Brody Date: Wed, 11 Apr 2012 20:34:39 +0200 Subject: [PATCH 37/44] New Android version from @marcucio with major improvements in batch processing --- Android/assets/www/SQLitePlugin.js | 259 +++++++++++++++++ .../plugin/sqlitePlugin/SQLitePlugin.java | 271 ++++++++++++++++++ 2 files changed, 530 insertions(+) create mode 100755 Android/assets/www/SQLitePlugin.js create mode 100755 Android/src/com/phonegap/plugin/sqlitePlugin/SQLitePlugin.java diff --git a/Android/assets/www/SQLitePlugin.js b/Android/assets/www/SQLitePlugin.js new file mode 100755 index 0000000..6044969 --- /dev/null +++ b/Android/assets/www/SQLitePlugin.js @@ -0,0 +1,259 @@ +(function() { + var root; + root = this; + root.SQLitePlugin = (function() { + console.log("root.SQLitePlugin"); + SQLitePlugin.prototype.openDBs = {}; + + function SQLitePlugin(dbPath, openSuccess, openError) + { + console.log("SQLitePlugin"); + this.dbPath = dbPath; + this.openSuccess = openSuccess; + this.openError = openError; + if (!dbPath) { + throw new Error("Cannot create a SQLitePlugin instance without a dbPath"); + } + this.openSuccess || (this.openSuccess = function() { + console.log("DB opened: " + dbPath); + }); + this.openError || (this.openError = function(e) { + console.log(e.message); + }); + this.open(this.openSuccess, this.openError); + } + + SQLitePlugin.prototype.transaction = function(fn, error, success) + { + console.log("SQLitePlugin.prototype.transaction"); + var t; + t = new root.SQLitePluginTransaction(this.dbPath); + fn(t); + return t.complete(success, error); + }; + + SQLitePlugin.prototype.open = function(success, error) + { + console.log("SQLitePlugin.prototype.open"); + var opts; + if (!(this.dbPath in this.openDBs)) { + this.openDBs[this.dbPath] = true; + PhoneGap.exec(success, error, "SQLitePlugin", "open", [this.dbPath]); + } + }; + + SQLitePlugin.prototype.close = function(success, error) + { + console.log("SQLitePlugin.prototype.close"); + var opts; + if (this.dbPath in this.openDBs) { + delete this.openDBs[this.dbPath]; + PhoneGap.exec(null, null, "SQLitePlugin", "close", [this.dbPath]); + } + }; + return SQLitePlugin; + })(); + get_unique_id = function() + { + var id = new Date().getTime(); + var id2 = new Date().getTime(); + while(id === id2) + { + id2 = new Date().getTime(); + } + return id2+'000'; + } + transaction_queue = []; + transaction_callback_queue = new Object(); + root.SQLitePluginTransaction = (function() { + console.log("root.SQLitePluginTransaction"); + function SQLitePluginTransaction(dbPath) + { + console.log("root.SQLitePluginTransaction.SQLitePluginTransaction"); + this.dbPath = dbPath; + this.executes = []; + this.trans_id = get_unique_id(); + this.__completed = false; + this.__submitted = false; + this.optimization_no_nested_callbacks = true;//if set to true large batches of queries within a transaction will be much faster but you lose the ability to do muiti level nesting of executeSQL callbacks + console.log("root.SQLitePluginTransaction - this.trans_id:"+this.trans_id); + transaction_queue[this.trans_id] = []; + transaction_callback_queue[this.trans_id] = new Object(); + } + SQLitePluginTransaction.queryCompleteCallback = function(transId, queryId, result) + { + console.log("SQLitePluginTransaction.queryCompleteCallback"); + var query = null; + for (var x in transaction_queue[transId]) + { + if(transaction_queue[transId][x]['query_id'] == queryId) + { + query = transaction_queue[transId][x]; + if(transaction_queue[transId].length == 1) + transaction_queue[transId] = []; + else + transaction_queue[transId].splice(x, 1); + break; + } + } + +// if(query) +// console.log("SQLitePluginTransaction.completeCallback---query:"+query['query']); + if(query && query['callback']) + { + query['callback'](result) + } + } + SQLitePluginTransaction.queryErrorCallback = function(transId, queryId, result) + { + var query = null; + for (var x in transaction_queue[transId]) + { + if(transaction_queue[transId][x]['query_id'] == queryId) + { + query = transaction_queue[transId][x]; + if(transaction_queue[transId].length == 1) + transaction_queue[transId] = []; + else + transaction_queue[transId].splice(x, 1); + break; + } + } + //if(query) + // console.log("SQLitePluginTransaction.queryErrorCallback---query:"+query['query']); + if(query && query['err_callback']) + query['err_callback'](result) + } + SQLitePluginTransaction.txCompleteCallback = function(transId) + { + if(typeof transId != 'undefined') + { + if(transId && transaction_callback_queue[transId] && transaction_callback_queue[transId]['success']) + { + transaction_callback_queue[transId]['success'](); + } + + + // delete transaction_queue[transId]; + // delete transaction_callback_queue[transId]; + } + else + console.log("SQLitePluginTransaction.txCompleteCallback---transId = NULL"); + } + SQLitePluginTransaction.txErrorCallback = function(transId, error) + { + if(typeof transId != 'undefined') + { + console.log("SQLitePluginTransaction.txErrorCallback---transId:"+transId); + if(transId && transaction_callback_queue[transId]['error']) + transaction_callback_queue[transId]['error'](error); + delete transaction_queue[transId]; + delete transaction_callback_queue[transId]; + } + else + console.log("SQLitePluginTransaction.txErrorCallback---transId = NULL"); + } + SQLitePluginTransaction.prototype.add_to_transaction = function(trans_id, query, params, callback, err_callback) + { + var new_query = new Object();; + new_query['trans_id'] = trans_id; + if(callback || !this.optimization_no_nested_callbacks) + new_query['query_id'] = get_unique_id(); + else if(this.optimization_no_nested_callbacks) + new_query['query_id'] = ""; + new_query['query'] = query; + if(params) + new_query['params'] = params; + else + new_query['params'] = []; + new_query['callback'] = callback; + new_query['err_callback'] = err_callback; + if(!transaction_queue[trans_id]) + transaction_queue[trans_id] = []; + transaction_queue[trans_id].push(new_query); + } + + SQLitePluginTransaction.prototype.executeSql = function(sql, values, success, error) { + console.log("SQLitePluginTransaction.prototype.executeSql"); + var errorcb, successcb, txself; + txself = this; + successcb = null; + if (success) + { + console.log("success not null:"+sql); + successcb = function(execres) + { + console.log("executeSql callback:"+JSON.stringify(execres)); + var res, saveres; + saveres = execres; + res = { + rows: { + item: function(i) { + return saveres[i]; + }, + length: saveres.length + }, + rowsAffected: saveres.rowsAffected, + insertId: saveres.insertId || null + }; + return success(txself, res); + }; + } + else + console.log("success NULL:"+sql); + + errorcb = null; + if (error) { + errorcb = function(res) { + return error(txself, res); + }; + } + this.add_to_transaction(this.trans_id, sql, values, successcb, errorcb); + console.log("executeSql - add_to_transaction"+sql); + }; + + SQLitePluginTransaction.prototype.complete = function(success, error) { + console.log("SQLitePluginTransaction.prototype.complete"); + var begin_opts, commit_opts, errorcb, executes, opts, successcb, txself; + if (this.__completed) throw new Error("Transaction already run"); + if (this.__submitted) throw new Error("Transaction already submitted"); + this.__submitted = true; + txself = this; + successcb = function() + { + if(transaction_queue[txself.trans_id].length > 0 && !txself.optimization_no_nested_callbacks) + { + txself.__submitted = false; + txself.complete(success, error); + } + else + { + this.__completed = true; + if(success) + return success(txself); + } + }; + errorcb = function(res) {}; + if (error) { + errorcb = function(res) { + return error(txself, res); + }; + } + transaction_callback_queue[this.trans_id]['success'] = successcb; + transaction_callback_queue[this.trans_id]['error'] = errorcb; + PhoneGap.exec(null, null, "SQLitePlugin", "executeSqlBatch", transaction_queue[this.trans_id]); + }; + return SQLitePluginTransaction; + })(); + + root.sqlitePlugin = { + openDatabase: function(dbPath, version, displayName, estimatedSize, creationCallback, errorCallback) { + if (version == null) version = null; + if (displayName == null) displayName = null; + if (estimatedSize == null) estimatedSize = 0; + if (creationCallback == null) creationCallback = null; + if (errorCallback == null) errorCallback = null; + return new SQLitePlugin(dbPath, creationCallback, errorCallback); + } + }; +}).call(this); \ No newline at end of file diff --git a/Android/src/com/phonegap/plugin/sqlitePlugin/SQLitePlugin.java b/Android/src/com/phonegap/plugin/sqlitePlugin/SQLitePlugin.java new file mode 100755 index 0000000..f0f7469 --- /dev/null +++ b/Android/src/com/phonegap/plugin/sqlitePlugin/SQLitePlugin.java @@ -0,0 +1,271 @@ +/* + * PhoneGap is available under *either* the terms of the modified BSD license *or* the + * MIT License (2008). See http://opensource.org/licenses/alphabetical for full text. + * + * Copyright (c) 2005-2010, Nitobi Software Inc. + * Copyright (c) 2010, IBM Corporation + */ +package com.phonegap.plugins.sqlitePlugin; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +import com.phonegap.api.Plugin; +import com.phonegap.api.PluginResult; +import android.database.Cursor; +import android.database.sqlite.*; + +import android.util.Log; + +public class SQLitePlugin extends Plugin { + + // Data Definition Language + SQLiteDatabase myDb = null; // Database object + String path = null; // Database path + String dbName = null; // Database name + + /** + * Constructor. + */ + public SQLitePlugin() { + } + + /** + * Executes the request and returns PluginResult. + * + * @param action + * The action to execute. + * @param args + * JSONArry of arguments for the plugin. + * @param callbackId + * The callback id used when calling back into JavaScript. + * @return A PluginResult object with a status and message. + */ + public PluginResult execute(String action, JSONArray args, String callbackId) { + PluginResult.Status status = PluginResult.Status.OK; + String result = ""; + + try { + // TODO: Do we want to allow a user to do this, since they could get + // to other app databases? + if (action.equals("setStorage")) { + this.setStorage(args.getString(0)); + } else if (action.equals("open")) { + this.openDatabase(args.getString(0), "1", + "database", 5000000); + //this.openDatabase(args.getString(0), args.getString(1), + // args.getString(2), args.getLong(3)); + } + else if (action.equals("executeSqlBatch")) + { + String[] queries = null; + String[] queryIDs = null; + String[][] params = null; + String trans_id = null; + JSONObject a = null; + JSONArray jsonArr = null; + int paramLen = 0; + + if (args.isNull(0)) { + queries = new String[0]; + } else { + int len = args.length(); + queries = new String[len]; + queryIDs = new String[len]; + params = new String[len][1]; + for (int i = 0; i < len; i++) + { + a = args.getJSONObject(i); + queries[i] = a.getString("query"); + queryIDs[i] = a.getString("query_id"); + trans_id = a.getString("trans_id"); + jsonArr = a.getJSONArray("params"); + paramLen = jsonArr.length(); + params[i] = new String[paramLen]; + + for (int j = 0; j < paramLen; j++) { + params[i][j] = jsonArr.getString(j); + if(params[i][j] == "null") + params[i][j] = ""; + } + } + } + if(trans_id != null) + this.executeSqlBatch(queries, params, queryIDs, trans_id); + else + Log.v("error", "null trans_id"); + } + return new PluginResult(status, result); + } catch (JSONException e) { + return new PluginResult(PluginResult.Status.JSON_EXCEPTION); + } + } + + /** + * Identifies if action to be executed returns a value and should be run + * synchronously. + * + * @param action + * The action to execute + * @return T=returns value + */ + public boolean isSynch(String action) { + return true; + } + + /** + * Clean up and close database. + */ + @Override + public void onDestroy() { + if (this.myDb != null) { + this.myDb.close(); + this.myDb = null; + } + } + + // -------------------------------------------------------------------------- + // LOCAL METHODS + // -------------------------------------------------------------------------- + + /** + * Set the application package for the database. Each application saves its + * database files in a directory with the application package as part of the + * file name. + * + * For example, application "com.phonegap.demo.Demo" would save its database + * files in "/data/data/com.phonegap.demo/databases/" directory. + * + * @param appPackage + * The application package. + */ + public void setStorage(String appPackage) { + this.path = "/data/data/" + appPackage + "/databases/"; + } + + /** + * Open database. + * + * @param db + * The name of the database + * @param version + * The version + * @param display_name + * The display name + * @param size + * The size in bytes + */ + public void openDatabase(String db, String version, String display_name, + long size) { + + // If database is open, then close it + if (this.myDb != null) { + this.myDb.close(); + } + + // If no database path, generate from application package + if (this.path == null) { + Package pack = this.ctx.getClass().getPackage(); + String appPackage = pack.getName(); + this.setStorage(appPackage); + } + + this.dbName = this.path + db + ".db"; + this.myDb = SQLiteDatabase.openOrCreateDatabase(this.dbName, null); + } + + public void executeSqlBatch(String[] queryarr, String[][] paramsarr, String[] queryIDs, String tx_id) { + try { + this.myDb.beginTransaction(); + String query = ""; + String query_id = ""; + String[] params; + int len = queryarr.length; + for (int i = 0; i < len; i++) { + query = queryarr[i]; + params = paramsarr[i]; + query_id = queryIDs[i]; + Cursor myCursor = this.myDb.rawQuery(query, params); + + this.processResults(myCursor, query_id, tx_id); + myCursor.close(); + } + this.myDb.setTransactionSuccessful(); + } + catch (SQLiteException ex) { + ex.printStackTrace(); + Log.v("executeSqlBatch", "SQLitePlugin.executeSql(): Error=" + ex.getMessage()); + this.sendJavascript("SQLitePluginTransaction.txErrorCallback('" + tx_id + "', '"+ex.getMessage()+"');"); + } + finally { + this.myDb.endTransaction(); + Log.v("executeSqlBatch", tx_id); + this.sendJavascript("SQLitePluginTransaction.txCompleteCallback('" + tx_id + "');"); + } + } + + /** + * Process query results. + * + * @param cur + * Cursor into query results + * @param tx_id + * Transaction id + */ + public void processResults(Cursor cur, String query_id, String tx_id) { + + String result = "[]"; + // If query result has rows + + if (cur.moveToFirst()) { + JSONArray fullresult = new JSONArray(); + String key = ""; + int colCount = cur.getColumnCount(); + + // Build up JSON result object for each row + do { + JSONObject row = new JSONObject(); + try { + for (int i = 0; i < colCount; ++i) { + key = cur.getColumnName(i); + if(android.os.Build.VERSION.SDK_INT >= 11) + { + switch(cur.getType (i)) + { + case Cursor.FIELD_TYPE_NULL: + row.put(key, null); + break; + case Cursor.FIELD_TYPE_INTEGER: + row.put(key, cur.getInt(i)); + break; + case Cursor.FIELD_TYPE_FLOAT: + row.put(key, cur.getFloat(i)); + break; + case Cursor.FIELD_TYPE_STRING: + row.put(key, cur.getString(i)); + break; + case Cursor.FIELD_TYPE_BLOB: + row.put(key, cur.getBlob(i)); + break; + } + } + else + { + row.put(key, cur.getString(i)); + } + } + fullresult.put(row); + + } catch (JSONException e) { + e.printStackTrace(); + } + + } while (cur.moveToNext()); + + result = fullresult.toString(); + } + if(query_id.length() > 0) + this.sendJavascript(" SQLitePluginTransaction.queryCompleteCallback('" + tx_id + "','" + query_id + "', " + result + ");"); + + } +} From 645d1f37f3330d0a4a4dbb691aed76e8290efc48 Mon Sep 17 00:00:00 2001 From: Chris Brody Date: Wed, 11 Apr 2012 20:41:00 +0200 Subject: [PATCH 38/44] Updates for Android version to match working @chbrody test environment --- Android/assets/www/SQLitePlugin.js | 4 ++-- .../src/com/phonegap/plugin/sqlitePlugin/SQLitePlugin.java | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Android/assets/www/SQLitePlugin.js b/Android/assets/www/SQLitePlugin.js index 6044969..33e7276 100755 --- a/Android/assets/www/SQLitePlugin.js +++ b/Android/assets/www/SQLitePlugin.js @@ -75,7 +75,7 @@ this.trans_id = get_unique_id(); this.__completed = false; this.__submitted = false; - this.optimization_no_nested_callbacks = true;//if set to true large batches of queries within a transaction will be much faster but you lose the ability to do muiti level nesting of executeSQL callbacks + this.optimization_no_nested_callbacks = true;//if set to true large batches of queries within a transaction will be much faster but you _MAY_ lose the ability to do deep multi level nesting of executeSQL callbacks console.log("root.SQLitePluginTransaction - this.trans_id:"+this.trans_id); transaction_queue[this.trans_id] = []; transaction_callback_queue[this.trans_id] = new Object(); @@ -256,4 +256,4 @@ return new SQLitePlugin(dbPath, creationCallback, errorCallback); } }; -}).call(this); \ No newline at end of file +}).call(this); diff --git a/Android/src/com/phonegap/plugin/sqlitePlugin/SQLitePlugin.java b/Android/src/com/phonegap/plugin/sqlitePlugin/SQLitePlugin.java index f0f7469..fc2e536 100755 --- a/Android/src/com/phonegap/plugin/sqlitePlugin/SQLitePlugin.java +++ b/Android/src/com/phonegap/plugin/sqlitePlugin/SQLitePlugin.java @@ -5,7 +5,7 @@ * Copyright (c) 2005-2010, Nitobi Software Inc. * Copyright (c) 2010, IBM Corporation */ -package com.phonegap.plugins.sqlitePlugin; +package com.phonegap.plugin.sqlitePlugin; import org.json.JSONArray; import org.json.JSONException; @@ -228,6 +228,7 @@ public class SQLitePlugin extends Plugin { try { for (int i = 0; i < colCount; ++i) { key = cur.getColumnName(i); + // for old Android SDK remove lines from HERE: if(android.os.Build.VERSION.SDK_INT >= 11) { switch(cur.getType (i)) @@ -249,7 +250,7 @@ public class SQLitePlugin extends Plugin { break; } } - else + else // to HERE. { row.put(key, cur.getString(i)); } From 5a1bf38dd808a874fed857c17633b3bf97bbe1c9 Mon Sep 17 00:00:00 2001 From: Chris Brody Date: Wed, 11 Apr 2012 20:51:14 +0200 Subject: [PATCH 39/44] Fix README.md, rename DroidGap to DroidGap-old --- .../assets/www/SQLitePlugin.js | 0 .../assets/www/index.html | 0 .../plugin/sqlitePlugin/SQLitePlugin.java | 0 .../www/SQLitePlugin.js | 342 -- .../www/cordova-1.5.0.js | 4841 ----------------- .../lawnchair-adapter-test/www/index.html | 185 - .../www/lawnchair-spec.js | 429 -- .../lawnchair-adapter-test/www/lib/json2.js | 482 -- .../www/lib/lawnchair.js | 151 - .../lawnchair-adapter-test/www/lib/qunit.css | 225 - .../lawnchair-adapter-test/www/lib/qunit.js | 1448 ----- .../lawnchair-adapter-test/www/master.css | 96 - .../www/webkit-sqlite.js | 202 - README.md | 30 +- 14 files changed, 20 insertions(+), 8411 deletions(-) rename {DroidGap => DroidGap-old}/assets/www/SQLitePlugin.js (100%) rename {DroidGap => DroidGap-old}/assets/www/index.html (100%) rename {DroidGap => DroidGap-old}/src/com/phonegap/plugin/sqlitePlugin/SQLitePlugin.java (100%) delete mode 100644 DroidGap/lawnchair-adapter-test/www/SQLitePlugin.js delete mode 100644 DroidGap/lawnchair-adapter-test/www/cordova-1.5.0.js delete mode 100644 DroidGap/lawnchair-adapter-test/www/index.html delete mode 100755 DroidGap/lawnchair-adapter-test/www/lawnchair-spec.js delete mode 100644 DroidGap/lawnchair-adapter-test/www/lib/json2.js delete mode 100644 DroidGap/lawnchair-adapter-test/www/lib/lawnchair.js delete mode 100644 DroidGap/lawnchair-adapter-test/www/lib/qunit.css delete mode 100644 DroidGap/lawnchair-adapter-test/www/lib/qunit.js delete mode 100644 DroidGap/lawnchair-adapter-test/www/master.css delete mode 100644 DroidGap/lawnchair-adapter-test/www/webkit-sqlite.js diff --git a/DroidGap/assets/www/SQLitePlugin.js b/DroidGap-old/assets/www/SQLitePlugin.js similarity index 100% rename from DroidGap/assets/www/SQLitePlugin.js rename to DroidGap-old/assets/www/SQLitePlugin.js diff --git a/DroidGap/assets/www/index.html b/DroidGap-old/assets/www/index.html similarity index 100% rename from DroidGap/assets/www/index.html rename to DroidGap-old/assets/www/index.html diff --git a/DroidGap/src/com/phonegap/plugin/sqlitePlugin/SQLitePlugin.java b/DroidGap-old/src/com/phonegap/plugin/sqlitePlugin/SQLitePlugin.java similarity index 100% rename from DroidGap/src/com/phonegap/plugin/sqlitePlugin/SQLitePlugin.java rename to DroidGap-old/src/com/phonegap/plugin/sqlitePlugin/SQLitePlugin.java diff --git a/DroidGap/lawnchair-adapter-test/www/SQLitePlugin.js b/DroidGap/lawnchair-adapter-test/www/SQLitePlugin.js deleted file mode 100644 index 4ca0f06..0000000 --- a/DroidGap/lawnchair-adapter-test/www/SQLitePlugin.js +++ /dev/null @@ -1,342 +0,0 @@ -/* - * PhoneGap is available under *either* the terms of the modified BSD license *or* the - * MIT License (2008). See http://opensource.org/licenses/alphabetical for full text. - * - * Copyright (c) 2005-2010, Nitobi Software Inc. - * Copyright (c) 2010-2011, IBM Corporation - */ - -/* - * This is purely for the Android 1.5/1.6 HTML 5 Storage - * I was hoping that Android 2.0 would deprecate this, but given the fact that - * most manufacturers ship with Android 1.5 and do not do OTA Updates, this is required - */ - - -// XXX TODO: use function() { ... } () to encapsulate these declarations (except for Java callback) - -/** - * SQL result set object - * PRIVATE METHOD - * @constructor - */ -var DDB_Rows = function() { - this.resultSet = []; // results array - this.length = 0; // number of rows -}; - -/** - * Get item from SQL result set - * - * @param row The row number to return - * @return The row object - */ -DDB_Rows.prototype.item = function(row) { - return this.resultSet[row]; -}; - -/** - * SQL result set that is returned to user. - * PRIVATE METHOD - * @constructor - */ -var DDB_Result = function() { - this.rows = new DDB_Rows(); -}; - -/** - * Storage object that is called by native code when performing queries. - * PRIVATE METHOD - * @constructor - */ -var DDB = function() { - this.queryQueue = {}; -}; - -/** - * Callback from native code when query is complete. - * PRIVATE METHOD - * - * @param id Query id - */ -DDB.prototype.completeQuery = function(id, data) { - var query = this.queryQueue[id]; - if (query) { - try { - delete this.queryQueue[id]; - - // Get transaction - var tx = query.tx; - - // If transaction hasn't failed - // Note: We ignore all query results if previous query - // in the same transaction failed. - if (tx && tx.queryList[id]) { - - // Save query results - var r = new DDB_Result(); - r.rows.resultSet = data; - r.rows.length = data.length; - try { - if (typeof query.successCallback === 'function') { - query.successCallback(query.tx, r); - } - } catch (ex) { - console.log("executeSql error calling user success callback: "+ex); - } - - tx.queryComplete(id); - } - } catch (e) { - console.log("executeSql error: "+e); - } - } -}; - -/** - * Callback from native code when query fails - * PRIVATE METHOD - * - * @param reason Error message - * @param id Query id - */ -DDB.prototype.fail = function(reason, id) { - var query = this.queryQueue[id]; - if (query) { - try { - delete this.queryQueue[id]; - - // Get transaction - var tx = query.tx; - - // If transaction hasn't failed - // Note: We ignore all query results if previous query - // in the same transaction failed. - if (tx && tx.queryList[id]) { - tx.queryList = {}; - - try { - if (typeof query.errorCallback === 'function') { - query.errorCallback(query.tx, reason); - } - } catch (ex) { - console.log("executeSql error calling user error callback: "+ex); - } - - tx.queryFailed(id, reason); - } - - } catch (e) { - console.log("executeSql error: "+e); - } - } -}; - -var mycreateUUID = function() { - return myUUIDcreatePart(4) + '-' + - myUUIDcreatePart(2) + '-' + - myUUIDcreatePart(2) + '-' + - myUUIDcreatePart(2) + '-' + - myUUIDcreatePart(6); -}; - -myUUIDcreatePart = function(length) { - var uuidpart = ""; - var i, uuidchar; - for (i=0; i 0) { - eval("var v="+r+";"); - - // If status is OK, then return value back to caller - if (v.status === cordova.callbackStatus.OK) { - - // If there is a success callback, then call it now with - // returned value - if (success) { - try { - success(v.message); - } catch (e) { - console.log("Error in success callback: " + callbackId + " = " + e); - } - - // Clear callback if not expecting any more results - if (!v.keepCallback) { - delete cordova.callbacks[callbackId]; - } - } - return v.message; - } - - // If no result - else if (v.status === cordova.callbackStatus.NO_RESULT) { - // Clear callback if not expecting any more results - if (!v.keepCallback) { - delete cordova.callbacks[callbackId]; - } - } - - // If error, then display error - else { - console.log("Error: Status="+v.status+" Message="+v.message); - - // If there is a fail callback, then call it now with returned value - if (fail) { - try { - fail(v.message); - } - catch (e1) { - console.log("Error in error callback: "+callbackId+" = "+e1); - } - - // Clear callback if not expecting any more results - if (!v.keepCallback) { - delete cordova.callbacks[callbackId]; - } - } - return null; - } - } - } catch (e2) { - console.log("Error: "+e2); - } -}; - -}); -define('cordova/common', function(require, exports, module) { -module.exports = { - objects: { - cordova: { - path: 'cordova', - children: { - exec: { - path: 'cordova/exec' - } - } - }, - navigator: { - children: { - notification: { - path: 'cordova/plugin/notification' - }, - accelerometer: { - path: 'cordova/plugin/accelerometer' - }, - battery: { - path: 'cordova/plugin/battery' - }, - camera:{ - path: 'cordova/plugin/Camera' - }, - compass:{ - path: 'cordova/plugin/compass' - }, - contacts: { - path: 'cordova/plugin/contacts' - }, - device:{ - children:{ - capture: { - path: 'cordova/plugin/capture' - } - } - }, - geolocation: { - path: 'cordova/plugin/geolocation' - }, - network: { - children: { - connection: { - path: 'cordova/plugin/network' - } - } - } - } - }, - Acceleration: { - path: 'cordova/plugin/Acceleration' - }, - Camera:{ - path: 'cordova/plugin/CameraConstants' - }, - CaptureError: { - path: 'cordova/plugin/CaptureError' - }, - CaptureAudioOptions:{ - path: 'cordova/plugin/CaptureAudioOptions' - }, - CaptureImageOptions: { - path: 'cordova/plugin/CaptureImageOptions' - }, - CaptureVideoOptions: { - path: 'cordova/plugin/CaptureVideoOptions' - }, - CompassHeading:{ - path: 'cordova/plugin/CompassHeading' - }, - CompassError:{ - path: 'cordova/plugin/CompassError' - }, - ConfigurationData: { - path: 'cordova/plugin/ConfigurationData' - }, - Connection: { - path: 'cordova/plugin/Connection' - }, - Contact: { - path: 'cordova/plugin/Contact' - }, - ContactAddress: { - path: 'cordova/plugin/ContactAddress' - }, - ContactError: { - path: 'cordova/plugin/ContactError' - }, - ContactField: { - path: 'cordova/plugin/ContactField' - }, - ContactFindOptions: { - path: 'cordova/plugin/ContactFindOptions' - }, - ContactName: { - path: 'cordova/plugin/ContactName' - }, - ContactOrganization: { - path: 'cordova/plugin/ContactOrganization' - }, - Coordinates: { - path: 'cordova/plugin/Coordinates' - }, - DirectoryEntry: { - path: 'cordova/plugin/DirectoryEntry' - }, - DirectoryReader: { - path: 'cordova/plugin/DirectoryReader' - }, - Entry: { - path: 'cordova/plugin/Entry' - }, - File: { - path: 'cordova/plugin/File' - }, - FileEntry: { - path: 'cordova/plugin/FileEntry' - }, - FileError: { - path: 'cordova/plugin/FileError' - }, - FileReader: { - path: 'cordova/plugin/FileReader' - }, - FileSystem: { - path: 'cordova/plugin/FileSystem' - }, - FileTransfer: { - path: 'cordova/plugin/FileTransfer' - }, - FileTransferError: { - path: 'cordova/plugin/FileTransferError' - }, - FileUploadOptions: { - path: 'cordova/plugin/FileUploadOptions' - }, - FileUploadResult: { - path: 'cordova/plugin/FileUploadResult' - }, - FileWriter: { - path: 'cordova/plugin/FileWriter' - }, - Flags: { - path: 'cordova/plugin/Flags' - }, - LocalFileSystem: { - path: 'cordova/plugin/LocalFileSystem' - }, - Media: { - path: 'cordova/plugin/Media' - }, - MediaError: { - path: 'cordova/plugin/MediaError' - }, - MediaFile: { - path: 'cordova/plugin/MediaFile' - }, - MediaFileData:{ - path: 'cordova/plugin/MediaFileData' - }, - Metadata:{ - path: 'cordova/plugin/Metadata' - }, - Position: { - path: 'cordova/plugin/Position' - }, - PositionError: { - path: 'cordova/plugin/PositionError' - }, - ProgressEvent: { - path: 'cordova/plugin/ProgressEvent' - }, - requestFileSystem:{ - path: 'cordova/plugin/requestFileSystem' - }, - resolveLocalFileSystemURI:{ - path: 'cordova/plugin/resolveLocalFileSystemURI' - } - } -}; - -}); -define('cordova/platform', function(require, exports, module) { -module.exports = { - id: "android", - initialize:function() { - var channel = require("cordova/channel"), - cordova = require('cordova'), - callback = require('cordova/plugin/android/callback'), - polling = require('cordova/plugin/android/polling'), - exec = require('cordova/exec'); - - channel.onDestroy.subscribe(function() { - cordova.shuttingDown = true; - }); - - // Start listening for XHR callbacks - // Figure out which bridge approach will work on this Android - // device: polling or XHR-based callbacks - setTimeout(function() { - if (cordova.UsePolling) { - polling(); - } - else { - var isPolling = prompt("usePolling", "gap_callbackServer:"); - cordova.UsePolling = isPolling; - if (isPolling == "true") { - cordova.UsePolling = true; - polling(); - } else { - cordova.UsePolling = false; - callback(); - } - } - }, 1); - - // Inject a listener for the backbutton on the document. - var backButtonChannel = cordova.addDocumentEventHandler('backbutton', { - onSubscribe:function() { - // If we just attached the first handler, let native know we need to override the back button. - if (this.numHandlers === 1) { - exec(null, null, "App", "overrideBackbutton", [true]); - } - }, - onUnsubscribe:function() { - // If we just detached the last handler, let native know we no longer override the back button. - if (this.numHandlers === 0) { - exec(null, null, "App", "overrideBackbutton", [false]); - } - } - }); - - // Add hardware MENU and SEARCH button handlers - cordova.addDocumentEventHandler('menubutton'); - cordova.addDocumentEventHandler('searchbutton'); - - // Let native code know we are all done on the JS side. - // Native code will then un-hide the WebView. - channel.join(function() { - prompt("", "gap_init:"); - }, [channel.onCordovaReady]); - - // Figure out if we need to shim-in localStorage and WebSQL - // support from the native side. - var storage = require('cordova/plugin/android/storage'); - - // First patch WebSQL if necessary - if (typeof window.openDatabase == 'undefined') { - // Not defined, create an openDatabase function for all to use! - window.openDatabase = storage.openDatabase; - } else { - // Defined, but some Android devices will throw a SECURITY_ERR - - // so we wrap the whole thing in a try-catch and shim in our own - // if the device has Android bug 16175. - var originalOpenDatabase = window.openDatabase; - window.openDatabase = function(name, version, desc, size) { - var db = null; - try { - db = originalOpenDatabase(name, version, desc, size); - } - catch (ex) { - db = null; - } - - if (db === null) { - return storage.openDatabase(name, version, desc, size); - } - else { - return db; - } - - }; - } - - // Patch localStorage if necessary - if (typeof window.localStorage == 'undefined' || window.localStorage === null) { - window.localStorage = new storage.CupCakeLocalStorage(); - } - }, - objects: { - cordova: { - children: { - JSCallback:{ - path:"cordova/plugin/android/callback" - }, - JSCallbackPolling:{ - path:"cordova/plugin/android/polling" - } - } - }, - navigator: { - children: { - app:{ - path: "cordova/plugin/android/app" - } - } - }, - device:{ - path: "cordova/plugin/android/device" - }, - File: { // exists natively on Android WebView, override - path: "cordova/plugin/File" - }, - FileReader: { // exists natively on Android WebView, override - path: "cordova/plugin/FileReader" - }, - FileError: { //exists natively on Android WebView on Android 4.x - path: "cordova/plugin/FileError" - } - } -}; - -}); -define('cordova/utils', function(require, exports, module) { -function UUIDcreatePart(length) { - var uuidpart = ""; - for (var i=0; i frequency + 10 sec - exec( - function(timeout) { - if (timeout < (frequency + 10000)) { - exec(null, null, "Accelerometer", "setTimeout", [frequency + 10000]); - } - }, - function(e) { }, "Accelerometer", "getTimeout", []); - - // Start watch timer - var id = utils.createUUID(); - timers[id] = window.setInterval(function() { - exec(successCallback, errorCallback, "Accelerometer", "getAcceleration", []); - }, (frequency ? frequency : 1)); - - return id; - }, - - /** - * Clears the specified accelerometer watch. - * - * @param {String} id The id of the watch returned from #watchAcceleration. - */ - clearWatch: function(id) { - - // Stop javascript timer & remove from timer list - if (id && timers[id] !== undefined) { - window.clearInterval(timers[id]); - delete timers[id]; - } - } -}; - -module.exports = accelerometer; - -}); - - -define('cordova/plugin/battery', function(require, exports, module) { -/** - * This class contains information about the current battery status. - * @constructor - */ -var cordova = require('cordova'), - exec = require('cordova/exec'); - -function handlers() { - return battery.channels.batterystatus.numHandlers + - battery.channels.batterylow.numHandlers + - battery.channels.batterycritical.numHandlers; -} - -var Battery = function() { - this._level = null; - this._isPlugged = null; - // Create new event handlers on the window (returns a channel instance) - var subscriptionEvents = { - onSubscribe:this.onSubscribe, - onUnsubscribe:this.onUnsubscribe - }; - this.channels = { - batterystatus:cordova.addWindowEventHandler("batterystatus", subscriptionEvents), - batterylow:cordova.addWindowEventHandler("batterylow", subscriptionEvents), - batterycritical:cordova.addWindowEventHandler("batterycritical", subscriptionEvents) - }; -}; -/** - * Event handlers for when callbacks get registered for the battery. - * Keep track of how many handlers we have so we can start and stop the native battery listener - * appropriately (and hopefully save on battery life!). - */ -Battery.prototype.onSubscribe = function() { - var me = battery; - // If we just registered the first handler, make sure native listener is started. - if (handlers() === 1) { - exec(me._status, me._error, "Battery", "start", []); - } -}; - -Battery.prototype.onUnsubscribe = function() { - var me = battery; - - // If we just unregistered the last handler, make sure native listener is stopped. - if (handlers() === 0) { - exec(null, null, "Battery", "stop", []); - } -}; - -/** - * Callback for battery status - * - * @param {Object} info keys: level, isPlugged - */ -Battery.prototype._status = function(info) { - if (info) { - var me = battery; - var level = info.level; - if (me._level !== level || me._isPlugged !== info.isPlugged) { - // Fire batterystatus event - cordova.fireWindowEvent("batterystatus", info); - - // Fire low battery event - if (level === 20 || level === 5) { - if (level === 20) { - cordova.fireWindowEvent("batterylow", info); - } - else { - cordova.fireWindowEvent("batterycritical", info); - } - } - } - me._level = level; - me._isPlugged = info.isPlugged; - } -}; - -/** - * Error callback for battery start - */ -Battery.prototype._error = function(e) { - console.log("Error initializing Battery: " + e); -}; - -var battery = new Battery(); - -module.exports = battery; - -}); - - -define('cordova/plugin/Camera', function(require, exports, module) { -var exec = require('cordova/exec'), - Camera = require('cordova/plugin/CameraConstants'); - -var cameraExport = {}; - -// Tack on the Camera Constants to the base camera plugin. -for (var key in Camera) { - cameraExport[key] = Camera[key]; -} - -/** - * Gets a picture from source defined by "options.sourceType", and returns the - * image as defined by the "options.destinationType" option. - - * The defaults are sourceType=CAMERA and destinationType=FILE_URL. - * - * @param {Function} successCallback - * @param {Function} errorCallback - * @param {Object} options - */ -cameraExport.getPicture = function(successCallback, errorCallback, options) { - // successCallback required - if (typeof successCallback != "function") { - console.log("Camera Error: successCallback is not a function"); - return; - } - - // errorCallback optional - if (errorCallback && (typeof errorCallback != "function")) { - console.log("Camera Error: errorCallback is not a function"); - return; - } - - var quality = 50; - if (options && typeof options.quality == "number") { - quality = options.quality; - } else if (options && typeof options.quality == "string") { - var qlity = parseInt(options.quality, 10); - if (isNaN(qlity) === false) { - quality = qlity.valueOf(); - } - } - - var destinationType = Camera.DestinationType.FILE_URL; - if (typeof options.destinationType == "number") { - destinationType = options.destinationType; - } - - var sourceType = Camera.PictureSourceType.CAMERA; - if (typeof options.sourceType == "number") { - sourceType = options.sourceType; - } - - var targetWidth = -1; - if (typeof options.targetWidth == "number") { - targetWidth = options.targetWidth; - } else if (typeof options.targetWidth == "string") { - var width = parseInt(options.targetWidth, 10); - if (isNaN(width) === false) { - targetWidth = width.valueOf(); - } - } - - var targetHeight = -1; - if (typeof options.targetHeight == "number") { - targetHeight = options.targetHeight; - } else if (typeof options.targetHeight == "string") { - var height = parseInt(options.targetHeight, 10); - if (isNaN(height) === false) { - targetHeight = height.valueOf(); - } - } - - var encodingType = Camera.EncodingType.JPEG; - if (typeof options.encodingType == "number") { - encodingType = options.encodingType; - } - // TODO: parse MediaType - // TODO: enable allow edit? - - exec(successCallback, errorCallback, "Camera", "takePicture", [quality, destinationType, sourceType, targetWidth, targetHeight, encodingType]); -} - -module.exports = cameraExport; - -}); - -define('cordova/plugin/CameraConstants', function(require, exports, module) { -module.exports = { - DestinationType:{ - DATA_URL: 0, // Return base64 encoded string - FILE_URI: 1 // Return file uri (content://media/external/images/media/2 for Android) - }, - EncodingType:{ - JPEG: 0, // Return JPEG encoded image - PNG: 1 // Return PNG encoded image - }, - MediaType:{ - PICTURE: 0, // allow selection of still pictures only. DEFAULT. Will return format specified via DestinationType - VIDEO: 1, // allow selection of video only, ONLY RETURNS URL - ALLMEDIA : 2 // allow selection from all media types - }, - PictureSourceType:{ - PHOTOLIBRARY : 0, // Choose image from picture library (same as SAVEDPHOTOALBUM for Android) - CAMERA : 1, // Take picture from camera - SAVEDPHOTOALBUM : 2 // Choose image from picture library (same as PHOTOLIBRARY for Android) - } -}; - -}); - -define('cordova/plugin/capture', function(require, exports, module) { -var exec = require('cordova/exec'), - MediaFile = require('cordova/plugin/MediaFile'); - -/** - * Launches a capture of different types. - * - * @param (DOMString} type - * @param {Function} successCB - * @param {Function} errorCB - * @param {CaptureVideoOptions} options - */ -function _capture(type, successCallback, errorCallback, options) { - var win = function(pluginResult) { - var mediaFiles = []; - var i; - for (i = 0; i < pluginResult.length; i++) { - var mediaFile = new MediaFile(); - mediaFile.name = pluginResult[i].name; - mediaFile.fullPath = pluginResult[i].fullPath; - mediaFile.type = pluginResult[i].type; - mediaFile.lastModifiedDate = pluginResult[i].lastModifiedDate; - mediaFile.size = pluginResult[i].size; - mediaFiles.push(mediaFile); - } - successCallback(mediaFiles); - }; - exec(win, errorCallback, "Capture", type, [options]); -} -/** - * The Capture interface exposes an interface to the camera and microphone of the hosting device. - */ -function Capture() { - this.supportedAudioModes = []; - this.supportedImageModes = []; - this.supportedVideoModes = []; -} - -/** - * Launch audio recorder application for recording audio clip(s). - * - * @param {Function} successCB - * @param {Function} errorCB - * @param {CaptureAudioOptions} options - */ -Capture.prototype.captureAudio = function(successCallback, errorCallback, options){ - _capture("captureAudio", successCallback, errorCallback, options); -}; - -/** - * Launch camera application for taking image(s). - * - * @param {Function} successCB - * @param {Function} errorCB - * @param {CaptureImageOptions} options - */ -Capture.prototype.captureImage = function(successCallback, errorCallback, options){ - _capture("captureImage", successCallback, errorCallback, options); -}; - -/** - * Launch device camera application for recording video(s). - * - * @param {Function} successCB - * @param {Function} errorCB - * @param {CaptureVideoOptions} options - */ -Capture.prototype.captureVideo = function(successCallback, errorCallback, options){ - _capture("captureVideo", successCallback, errorCallback, options); -}; - - -module.exports = new Capture(); - -}); - -define('cordova/plugin/CaptureAudioOptions', function(require, exports, module) { -/** - * Encapsulates all audio capture operation configuration options. - */ -var CaptureAudioOptions = function(){ - // Upper limit of sound clips user can record. Value must be equal or greater than 1. - this.limit = 1; - // Maximum duration of a single sound clip in seconds. - this.duration = 0; - // The selected audio mode. Must match with one of the elements in supportedAudioModes array. - this.mode = null; -}; - -module.exports = CaptureAudioOptions; - -}); - -define('cordova/plugin/CaptureError', function(require, exports, module) { -/** - * The CaptureError interface encapsulates all errors in the Capture API. - */ -var CaptureError = function(c) { - this.code = c || null; -}; - -// Camera or microphone failed to capture image or sound. -CaptureError.CAPTURE_INTERNAL_ERR = 0; -// Camera application or audio capture application is currently serving other capture request. -CaptureError.CAPTURE_APPLICATION_BUSY = 1; -// Invalid use of the API (e.g. limit parameter has value less than one). -CaptureError.CAPTURE_INVALID_ARGUMENT = 2; -// User exited camera application or audio capture application before capturing anything. -CaptureError.CAPTURE_NO_MEDIA_FILES = 3; -// The requested capture operation is not supported. -CaptureError.CAPTURE_NOT_SUPPORTED = 20; - -module.exports = CaptureError; - -}); - -define('cordova/plugin/CaptureImageOptions', function(require, exports, module) { -/** - * Encapsulates all image capture operation configuration options. - */ -var CaptureImageOptions = function(){ - // Upper limit of images user can take. Value must be equal or greater than 1. - this.limit = 1; - // The selected image mode. Must match with one of the elements in supportedImageModes array. - this.mode = null; -}; - -module.exports = CaptureImageOptions; - -}); - -define('cordova/plugin/CaptureVideoOptions', function(require, exports, module) { -/** - * Encapsulates all video capture operation configuration options. - */ -var CaptureVideoOptions = function(){ - // Upper limit of videos user can record. Value must be equal or greater than 1. - this.limit = 1; - // Maximum duration of a single video clip in seconds. - this.duration = 0; - // The selected video mode. Must match with one of the elements in supportedVideoModes array. - this.mode = null; -}; - -module.exports = CaptureVideoOptions; - -}); - -define('cordova/plugin/compass', function(require, exports, module) { -var exec = require('cordova/exec'), - utils = require('cordova/utils'), - CompassHeading = require('cordova/plugin/CompassHeading'), - CompassError = require('cordova/plugin/CompassError'), - timers = {}, - compass = { - /** - * Asynchronously acquires the current heading. - * @param {Function} successCallback The function to call when the heading - * data is available - * @param {Function} errorCallback The function to call when there is an error - * getting the heading data. - * @param {CompassOptions} options The options for getting the heading data (not used). - */ - getCurrentHeading:function(successCallback, errorCallback, options) { - // successCallback required - if (typeof successCallback !== "function") { - console.log("Compass Error: successCallback is not a function"); - return; - } - - // errorCallback optional - if (errorCallback && (typeof errorCallback !== "function")) { - console.log("Compass Error: errorCallback is not a function"); - return; - } - - var win = function(result) { - var ch = new CompassHeading(result.magneticHeading, result.trueHeading, result.headingAccuracy, result.timestamp); - successCallback(ch); - }; - var fail = function(code) { - var ce = new CompassError(code); - errorCallback(ce); - } - - // Get heading - exec(win, fail, "Compass", "getHeading", []); - }, - - /** - * Asynchronously acquires the heading repeatedly at a given interval. - * @param {Function} successCallback The function to call each time the heading - * data is available - * @param {Function} errorCallback The function to call when there is an error - * getting the heading data. - * @param {HeadingOptions} options The options for getting the heading data - * such as timeout and the frequency of the watch. - */ - watchHeading:function(successCallback, errorCallback, options) { - // Default interval (100 msec) - var frequency = (options !== undefined && options.frequency !== undefined) ? options.frequency : 100; - - // successCallback required - if (typeof successCallback !== "function") { - console.log("Compass Error: successCallback is not a function"); - return; - } - - // errorCallback optional - if (errorCallback && (typeof errorCallback !== "function")) { - console.log("Compass Error: errorCallback is not a function"); - return; - } - - // Start watch timer to get headings - var id = utils.createUUID(); - var win = function(result) { - var ch = new CompassHeading(result.magneticHeading, result.trueHeading, result.headingAccuracy, result.timestamp); - successCallback(ch); - }; - var fail = function(code) { - var ce = new CompassError(code); - errorCallback(ce); - }; - - timers[id] = window.setInterval(function() { - exec(win, fail, "Compass", "getHeading", []); - }, frequency); - - return id; - }, - - /** - * Clears the specified heading watch. - * @param {String} watchId The ID of the watch returned from #watchHeading. - */ - clearWatch:function(id) { - // Stop javascript timer & remove from timer list - if (id && timers[id]) { - clearInterval(timers[id]); - delete timers[id]; - } - } - // TODO: add the filter-based iOS-only methods - }; - -module.exports = compass; - -}); - -define('cordova/plugin/CompassError', function(require, exports, module) { -/** - * CompassError. - * An error code assigned by an implementation when an error has occured - * @constructor - */ -var CompassError = function(err) { - this.code = (typeof err != 'undefined' ? err : null); -}; - -/** - * Error codes - */ -CompassError.COMPASS_INTERNAL_ERR = 0; -CompassError.COMPASS_NOT_SUPPORTED = 20; - -module.exports = CompassError; - - -}); - -define('cordova/plugin/CompassHeading', function(require, exports, module) { -var CompassHeading = function(magneticHeading, trueHeading, headingAccuracy, timestamp) { - this.magneticHeading = magneticHeading !== undefined ? magneticHeading : null; - this.trueHeading = trueHeading !== undefined ? trueHeading : null; - this.headingAccuracy = headingAccuracy !== undefined ? headingAccuracy : null; - this.timestamp = timestamp !== undefined ? new Date(timestamp) : new Date(); -}; - -module.exports = CompassHeading; - -}); - -define('cordova/plugin/ConfigurationData', function(require, exports, module) { -/** - * Encapsulates a set of parameters that the capture device supports. - */ -function ConfigurationData() { - // The ASCII-encoded string in lower case representing the media type. - this.type = null; - // The height attribute represents height of the image or video in pixels. - // In the case of a sound clip this attribute has value 0. - this.height = 0; - // The width attribute represents width of the image or video in pixels. - // In the case of a sound clip this attribute has value 0 - this.width = 0; -} - -module.exports = ConfigurationData; - -}); - -define('cordova/plugin/Connection', function(require, exports, module) { -/** - * Network status - */ -module.exports = { - UNKNOWN: "unknown", - ETHERNET: "ethernet", - WIFI: "wifi", - CELL_2G: "2g", - CELL_3G: "3g", - CELL_4G: "4g", - NONE: "none" -}; - -}); - -define('cordova/plugin/Contact', function(require, exports, module) { -var exec = require('cordova/exec'), - ContactError = require('cordova/plugin/ContactError'), - utils = require('cordova/utils'); - -/** -* Contains information about a single contact. -* @constructor -* @param {DOMString} id unique identifier -* @param {DOMString} displayName -* @param {ContactName} name -* @param {DOMString} nickname -* @param {Array.} phoneNumbers array of phone numbers -* @param {Array.} emails array of email addresses -* @param {Array.} addresses array of addresses -* @param {Array.} ims instant messaging user ids -* @param {Array.} organizations -* @param {DOMString} birthday contact's birthday -* @param {DOMString} note user notes about contact -* @param {Array.} photos -* @param {Array.} categories -* @param {Array.} urls contact's web sites -*/ -var Contact = function (id, displayName, name, nickname, phoneNumbers, emails, addresses, - ims, organizations, birthday, note, photos, categories, urls) { - this.id = id || null; - this.rawId = null; - this.displayName = displayName || null; - this.name = name || null; // ContactName - this.nickname = nickname || null; - this.phoneNumbers = phoneNumbers || []; // ContactField[] - this.emails = emails || []; // ContactField[] - this.addresses = addresses || []; // ContactAddress[] - this.ims = ims || []; // ContactField[] - this.organizations = organizations || []; // ContactOrganization[] - this.birthday = birthday || null; - this.note = note || null; - this.photos = photos || []; // ContactField[] - this.categories = categories || []; // ContactField[] - this.urls = urls || []; // ContactField[] -}; - -/** -* Removes contact from device storage. -* @param successCB success callback -* @param errorCB error callback -*/ -Contact.prototype.remove = function(successCB, errorCB) { - if (this.id === null) { - var errorObj = new ContactError(ContactError.UNKNOWN_ERROR); - errorCB(errorObj); - } - else { - exec(successCB, errorCB, "Contacts", "remove", [this.id]); - } -}; - -/** -* Creates a deep copy of this Contact. -* With the contact ID set to null. -* @return copy of this Contact -*/ -Contact.prototype.clone = function() { - var clonedContact = utils.clone(this); - var i; - clonedContact.id = null; - clonedContact.rawId = null; - // Loop through and clear out any id's in phones, emails, etc. - if (clonedContact.phoneNumbers) { - for (i = 0; i < clonedContact.phoneNumbers.length; i++) { - clonedContact.phoneNumbers[i].id = null; - } - } - if (clonedContact.emails) { - for (i = 0; i < clonedContact.emails.length; i++) { - clonedContact.emails[i].id = null; - } - } - if (clonedContact.addresses) { - for (i = 0; i < clonedContact.addresses.length; i++) { - clonedContact.addresses[i].id = null; - } - } - if (clonedContact.ims) { - for (i = 0; i < clonedContact.ims.length; i++) { - clonedContact.ims[i].id = null; - } - } - if (clonedContact.organizations) { - for (i = 0; i < clonedContact.organizations.length; i++) { - clonedContact.organizations[i].id = null; - } - } - if (clonedContact.categories) { - for (i = 0; i < clonedContact.categories.length; i++) { - clonedContact.categories[i].id = null; - } - } - if (clonedContact.photos) { - for (i = 0; i < clonedContact.photos.length; i++) { - clonedContact.photos[i].id = null; - } - } - if (clonedContact.urls) { - for (i = 0; i < clonedContact.urls.length; i++) { - clonedContact.urls[i].id = null; - } - } - return clonedContact; -}; - -/** -* Persists contact to device storage. -* @param successCB success callback -* @param errorCB error callback -*/ -Contact.prototype.save = function(successCB, errorCB) { - exec(successCB, errorCB, "Contacts", "save", [this]); -}; - - -module.exports = Contact; - -}); - -define('cordova/plugin/ContactAddress', function(require, exports, module) { -/** -* Contact address. -* @constructor -* @param {DOMString} id unique identifier, should only be set by native code -* @param formatted // NOTE: not a W3C standard -* @param streetAddress -* @param locality -* @param region -* @param postalCode -* @param country -*/ - -var ContactAddress = function(pref, type, formatted, streetAddress, locality, region, postalCode, country) { - this.id = null; - this.pref = (typeof pref != 'undefined' ? pref : false); - this.type = type || null; - this.formatted = formatted || null; - this.streetAddress = streetAddress || null; - this.locality = locality || null; - this.region = region || null; - this.postalCode = postalCode || null; - this.country = country || null; -}; - -module.exports = ContactAddress; - -}); - -define('cordova/plugin/ContactError', function(require, exports, module) { -/** - * ContactError. - * An error code assigned by an implementation when an error has occured - * @constructor - */ -var ContactError = function(err) { - this.code = (typeof err != 'undefined' ? err : null); -}; - -/** - * Error codes - */ -ContactError.UNKNOWN_ERROR = 0; -ContactError.INVALID_ARGUMENT_ERROR = 1; -ContactError.TIMEOUT_ERROR = 2; -ContactError.PENDING_OPERATION_ERROR = 3; -ContactError.IO_ERROR = 4; -ContactError.NOT_SUPPORTED_ERROR = 5; -ContactError.PERMISSION_DENIED_ERROR = 20; - -module.exports = ContactError; - -}); - -define('cordova/plugin/ContactField', function(require, exports, module) { -/** -* Generic contact field. -* @constructor -* @param {DOMString} id unique identifier, should only be set by native code // NOTE: not a W3C standard -* @param type -* @param value -* @param pref -*/ -var ContactField = function(type, value, pref) { - this.id = null; - this.type = type || null; - this.value = value || null; - this.pref = (typeof pref != 'undefined' ? pref : false); -}; - -module.exports = ContactField; - -}); - -define('cordova/plugin/ContactFindOptions', function(require, exports, module) { -/** - * ContactFindOptions. - * @constructor - * @param filter used to match contacts against - * @param multiple boolean used to determine if more than one contact should be returned - */ - -var ContactFindOptions = function(filter, multiple) { - this.filter = filter || ''; - this.multiple = (typeof multiple != 'undefined' ? multiple : false); -}; - -module.exports = ContactFindOptions; - -}); - -define('cordova/plugin/ContactName', function(require, exports, module) { -/** -* Contact name. -* @constructor -* @param formatted // NOTE: not part of W3C standard -* @param familyName -* @param givenName -* @param middle -* @param prefix -* @param suffix -*/ -var ContactName = function(formatted, familyName, givenName, middle, prefix, suffix) { - this.formatted = formatted || null; - this.familyName = familyName || null; - this.givenName = givenName || null; - this.middleName = middle || null; - this.honorificPrefix = prefix || null; - this.honorificSuffix = suffix || null; -}; - -module.exports = ContactName; - -}); - -define('cordova/plugin/ContactOrganization', function(require, exports, module) { -/** -* Contact organization. -* @constructor -* @param {DOMString} id unique identifier, should only be set by native code // NOTE: not a W3C standard -* @param name -* @param dept -* @param title -* @param startDate -* @param endDate -* @param location -* @param desc -*/ - -var ContactOrganization = function(pref, type, name, dept, title) { - this.id = null; - this.pref = (typeof pref != 'undefined' ? pref : false); - this.type = type || null; - this.name = name || null; - this.department = dept || null; - this.title = title || null; -}; - -module.exports = ContactOrganization; - -}); - -define('cordova/plugin/contacts', function(require, exports, module) { -var exec = require('cordova/exec'), - ContactError = require('cordova/plugin/ContactError'), - Contact = require('cordova/plugin/Contact'); - -/** -* Represents a group of Contacts. -* @constructor -*/ -var contacts = { - /** - * Returns an array of Contacts matching the search criteria. - * @param fields that should be searched - * @param successCB success callback - * @param errorCB error callback - * @param {ContactFindOptions} options that can be applied to contact searching - * @return array of Contacts matching search criteria - */ - find:function(fields, successCB, errorCB, options) { - if (!successCB) { - throw new TypeError("You must specify a success callback for the find command."); - } - if (!fields || (fields instanceof Array && fields.length === 0)) { - if (typeof errorCB === "function") { - errorCB(new ContactError(ContactError.INVALID_ARGUMENT_ERROR)); - } - } else { - var win = function(result) { - var cs = []; - for (var i = 0, l = result.length; i < l; i++) { - cs.push(contacts.create(result[i])); - } - successCB(cs); - }; - exec(win, errorCB, "Contacts", "search", [fields, options]); - } - }, - - /** - * This function creates a new contact, but it does not persist the contact - * to device storage. To persist the contact to device storage, invoke - * contact.save(). - * @param properties an object who's properties will be examined to create a new Contact - * @returns new Contact object - */ - create:function(properties) { - var i; - var contact = new Contact(); - for (i in properties) { - if (typeof contact[i] !== 'undefined' && properties.hasOwnProperty(i)) { - contact[i] = properties[i]; - } - } - return contact; - } -}; - -module.exports = contacts; - -}); - -define('cordova/plugin/Coordinates', function(require, exports, module) { -/** - * This class contains position information. - * @param {Object} lat - * @param {Object} lng - * @param {Object} alt - * @param {Object} acc - * @param {Object} head - * @param {Object} vel - * @param {Object} altacc - * @constructor - */ -var Coordinates = function(lat, lng, alt, acc, head, vel, altacc) { - /** - * The latitude of the position. - */ - this.latitude = lat; - /** - * The longitude of the position, - */ - this.longitude = lng; - /** - * The accuracy of the position. - */ - this.accuracy = acc; - /** - * The altitude of the position. - */ - this.altitude = alt; - /** - * The direction the device is moving at the position. - */ - this.heading = head; - /** - * The velocity with which the device is moving at the position. - */ - this.speed = vel; - /** - * The altitude accuracy of the position. - */ - this.altitudeAccuracy = (altacc !== undefined) ? altacc : null; -}; - -module.exports = Coordinates; - -}); - -define('cordova/plugin/DirectoryEntry', function(require, exports, module) { -var utils = require('cordova/utils'), - exec = require('cordova/exec'), - Entry = require('cordova/plugin/Entry'), - DirectoryReader = require('cordova/plugin/DirectoryReader'); - -/** - * An interface representing a directory on the file system. - * - * {boolean} isFile always false (readonly) - * {boolean} isDirectory always true (readonly) - * {DOMString} name of the directory, excluding the path leading to it (readonly) - * {DOMString} fullPath the absolute full path to the directory (readonly) - * {FileSystem} filesystem on which the directory resides (readonly) - */ -var DirectoryEntry = function(name, fullPath) { - DirectoryEntry.__super__.constructor.apply(this, [false, true, name, fullPath]); -}; - -utils.extend(DirectoryEntry, Entry); - -/** - * Creates a new DirectoryReader to read entries from this directory - */ -DirectoryEntry.prototype.createReader = function() { - return new DirectoryReader(this.fullPath); -}; - -/** - * Creates or looks up a directory - * - * @param {DOMString} path either a relative or absolute path from this directory in which to look up or create a directory - * @param {Flags} options to create or excluively create the directory - * @param {Function} successCallback is called with the new entry - * @param {Function} errorCallback is called with a FileError - */ -DirectoryEntry.prototype.getDirectory = function(path, options, successCallback, errorCallback) { - var win = typeof successCallback !== 'function' ? null : function(result) { - var entry = new DirectoryEntry(result.name, result.fullPath); - successCallback(entry); - }; - var fail = typeof errorCallback !== 'function' ? null : function(code) { - errorCallback(new FileError(code)); - }; - exec(win, fail, "File", "getDirectory", [this.fullPath, path, options]); -}; - -/** - * Deletes a directory and all of it's contents - * - * @param {Function} successCallback is called with no parameters - * @param {Function} errorCallback is called with a FileError - */ -DirectoryEntry.prototype.removeRecursively = function(successCallback, errorCallback) { - var fail = typeof errorCallback !== 'function' ? null : function(code) { - errorCallback(new FileError(code)); - }; - exec(successCallback, fail, "File", "removeRecursively", [this.fullPath]); -}; - -/** - * Creates or looks up a file - * - * @param {DOMString} path either a relative or absolute path from this directory in which to look up or create a file - * @param {Flags} options to create or excluively create the file - * @param {Function} successCallback is called with the new entry - * @param {Function} errorCallback is called with a FileError - */ -DirectoryEntry.prototype.getFile = function(path, options, successCallback, errorCallback) { - var win = typeof successCallback !== 'function' ? null : function(result) { - var FileEntry = require('cordova/plugin/FileEntry'); - var entry = new FileEntry(result.name, result.fullPath); - successCallback(entry); - }; - var fail = typeof errorCallback !== 'function' ? null : function(code) { - errorCallback(new FileError(code)); - }; - exec(win, fail, "File", "getFile", [this.fullPath, path, options]); -}; - -module.exports = DirectoryEntry; - -}); - -define('cordova/plugin/DirectoryReader', function(require, exports, module) { -var exec = require('cordova/exec'); - -/** - * An interface that lists the files and directories in a directory. - */ -function DirectoryReader(path) { - this.path = path || null; -} - -/** - * Returns a list of entries from a directory. - * - * @param {Function} successCallback is called with a list of entries - * @param {Function} errorCallback is called with a FileError - */ -DirectoryReader.prototype.readEntries = function(successCallback, errorCallback) { - var win = typeof successCallback !== 'function' ? null : function(result) { - var retVal = []; - for (var i=0; i][;base64], - * - * @param file {File} File object containing file properties - */ -FileReader.prototype.readAsDataURL = function(file) { - this.fileName = ""; - if (typeof file.fullPath === "undefined") { - this.fileName = file; - } else { - this.fileName = file.fullPath; - } - - // Already loading something - if (this.readyState == FileReader.LOADING) { - throw new FileError(FileError.INVALID_STATE_ERR); - } - - // LOADING state - this.readyState = FileReader.LOADING; - - // If loadstart callback - if (typeof this.onloadstart === "function") { - this.onloadstart(new ProgressEvent("loadstart", {target:this})); - } - - var me = this; - - // Read file - exec( - // Success callback - function(r) { - // If DONE (cancelled), then don't do anything - if (me.readyState === FileReader.DONE) { - return; - } - - // DONE state - me.readyState = FileReader.DONE; - - // Save result - me.result = r; - - // If onload callback - if (typeof me.onload === "function") { - me.onload(new ProgressEvent("load", {target:me})); - } - - // If onloadend callback - if (typeof me.onloadend === "function") { - me.onloadend(new ProgressEvent("loadend", {target:me})); - } - }, - // Error callback - function(e) { - // If DONE (cancelled), then don't do anything - if (me.readyState === FileReader.DONE) { - return; - } - - // DONE state - me.readyState = FileReader.DONE; - - me.result = null; - - // Save error - me.error = new FileError(e); - - // If onerror callback - if (typeof me.onerror === "function") { - me.onerror(new ProgressEvent("error", {target:me})); - } - - // If onloadend callback - if (typeof me.onloadend === "function") { - me.onloadend(new ProgressEvent("loadend", {target:me})); - } - }, "File", "readAsDataURL", [this.fileName]); -}; - -/** - * Read file and return data as a binary data. - * - * @param file {File} File object containing file properties - */ -FileReader.prototype.readAsBinaryString = function(file) { - // TODO - Can't return binary data to browser. - console.log('This method is not supported at this time.'); -}; - -/** - * Read file and return data as a binary data. - * - * @param file {File} File object containing file properties - */ -FileReader.prototype.readAsArrayBuffer = function(file) { - // TODO - Can't return binary data to browser. - console.log('This method is not supported at this time.'); -}; - -module.exports = FileReader; - -}); - -define('cordova/plugin/FileSystem', function(require, exports, module) { -var DirectoryEntry = require('cordova/plugin/DirectoryEntry'); - -/** - * An interface representing a file system - * - * @constructor - * {DOMString} name the unique name of the file system (readonly) - * {DirectoryEntry} root directory of the file system (readonly) - */ -var FileSystem = function(name, root) { - this.name = name || null; - if (root) { - this.root = new DirectoryEntry(root.name, root.fullPath); - } -}; - -module.exports = FileSystem; - -}); - -define('cordova/plugin/FileTransfer', function(require, exports, module) { -var exec = require('cordova/exec'); - -/** - * FileTransfer uploads a file to a remote server. - * @constructor - */ -var FileTransfer = function() {}; - -/** -* Given an absolute file path, uploads a file on the device to a remote server -* using a multipart HTTP request. -* @param filePath {String} Full path of the file on the device -* @param server {String} URL of the server to receive the file -* @param successCallback (Function} Callback to be invoked when upload has completed -* @param errorCallback {Function} Callback to be invoked upon error -* @param options {FileUploadOptions} Optional parameters such as file name and mimetype -*/ -FileTransfer.prototype.upload = function(filePath, server, successCallback, errorCallback, options, debug) { - // check for options - var fileKey = null; - var fileName = null; - var mimeType = null; - var params = null; - var chunkedMode = true; - if (options) { - fileKey = options.fileKey; - fileName = options.fileName; - mimeType = options.mimeType; - if (options.chunkedMode !== null || typeof options.chunkedMode !== "undefined") { - chunkedMode = options.chunkedMode; - } - if (options.params) { - params = options.params; - } - else { - params = {}; - } - } - - exec(successCallback, errorCallback, 'FileTransfer', 'upload', [filePath, server, fileKey, fileName, mimeType, params, debug, chunkedMode]); -}; - -/** - * Downloads a file form a given URL and saves it to the specified directory. - * @param source {String} URL of the server to receive the file - * @param target {String} Full path of the file on the device - * @param successCallback (Function} Callback to be invoked when upload has completed - * @param errorCallback {Function} Callback to be invoked upon error - */ -FileTransfer.prototype.download = function(source, target, successCallback, errorCallback) { - var win = function(result) { - var entry = null; - if (result.isDirectory) { - entry = new DirectoryEntry(); - } - else if (result.isFile) { - entry = new FileEntry(); - } - entry.isDirectory = result.isDirectory; - entry.isFile = result.isFile; - entry.name = result.name; - entry.fullPath = result.fullPath; - successCallback(entry); - }; - exec(win, errorCallback, 'FileTransfer', 'download', [source, target]); -}; - -module.exports = FileTransfer; - -}); - -define('cordova/plugin/FileTransferError', function(require, exports, module) { -/** - * FileTransferError - * @constructor - */ -var FileTransferError = function(code) { - this.code = code || null; -}; - -FileTransferError.FILE_NOT_FOUND_ERR = 1; -FileTransferError.INVALID_URL_ERR = 2; -FileTransferError.CONNECTION_ERR = 3; - -module.exports = FileTransferError; - -}); - -define('cordova/plugin/FileUploadOptions', function(require, exports, module) { -/** - * Options to customize the HTTP request used to upload files. - * @constructor - * @param fileKey {String} Name of file request parameter. - * @param fileName {String} Filename to be used by the server. Defaults to image.jpg. - * @param mimeType {String} Mimetype of the uploaded file. Defaults to image/jpeg. - * @param params {Object} Object with key: value params to send to the server. - */ -var FileUploadOptions = function(fileKey, fileName, mimeType, params) { - this.fileKey = fileKey || null; - this.fileName = fileName || null; - this.mimeType = mimeType || null; - this.params = params || null; -}; - -module.exports = FileUploadOptions; - -}); - -define('cordova/plugin/FileUploadResult', function(require, exports, module) { -/** - * FileUploadResult - * @constructor - */ -var FileUploadResult = function() { - this.bytesSent = 0; - this.responseCode = null; - this.response = null; -}; - -module.exports = FileUploadResult; - -}); - -define('cordova/plugin/FileWriter', function(require, exports, module) { -var exec = require('cordova/exec'), - FileError = require('cordova/plugin/FileError'); - ProgressEvent = require('cordova/plugin/ProgressEvent'); - -/** - * This class writes to the mobile device file system. - * - * For Android: - * The root directory is the root of the file system. - * To write to the SD card, the file name is "sdcard/my_file.txt" - * - * @constructor - * @param file {File} File object containing file properties - * @param append if true write to the end of the file, otherwise overwrite the file - */ -var FileWriter = function(file) { - this.fileName = ""; - this.length = 0; - if (file) { - this.fileName = file.fullPath || file; - this.length = file.size || 0; - } - // default is to write at the beginning of the file - this.position = 0; - - this.readyState = 0; // EMPTY - - this.result = null; - - // Error - this.error = null; - - // Event handlers - this.onwritestart = null; // When writing starts - this.onprogress = null; // While writing the file, and reporting partial file data - this.onwrite = null; // When the write has successfully completed. - this.onwriteend = null; // When the request has completed (either in success or failure). - this.onabort = null; // When the write has been aborted. For instance, by invoking the abort() method. - this.onerror = null; // When the write has failed (see errors). -}; - -// States -FileWriter.INIT = 0; -FileWriter.WRITING = 1; -FileWriter.DONE = 2; - -/** - * Abort writing file. - */ -FileWriter.prototype.abort = function() { - // check for invalid state - if (this.readyState === FileWriter.DONE || this.readyState === FileWriter.INIT) { - throw new FileError(FileError.INVALID_STATE_ERR); - } - - // set error - this.error = new FileError(FileError.ABORT_ERR); - - this.readyState = FileWriter.DONE; - - // If abort callback - if (typeof this.onabort === "function") { - this.onabort(new ProgressEvent("abort", {"target":this})); - } - - // If write end callback - if (typeof this.onwriteend === "function") { - this.onwriteend(new ProgressEvent("writeend", {"target":this})); - } -}; - -/** - * Writes data to the file - * - * @param text to be written - */ -FileWriter.prototype.write = function(text) { - // Throw an exception if we are already writing a file - if (this.readyState === FileWriter.WRITING) { - throw new FileError(FileError.INVALID_STATE_ERR); - } - - // WRITING state - this.readyState = FileWriter.WRITING; - - var me = this; - - // If onwritestart callback - if (typeof me.onwritestart === "function") { - me.onwritestart(new ProgressEvent("writestart", {"target":me})); - } - - // Write file - exec( - // Success callback - function(r) { - // If DONE (cancelled), then don't do anything - if (me.readyState === FileWriter.DONE) { - return; - } - - // position always increases by bytes written because file would be extended - me.position += r; - // The length of the file is now where we are done writing. - - me.length = me.position; - - // DONE state - me.readyState = FileWriter.DONE; - - // If onwrite callback - if (typeof me.onwrite === "function") { - me.onwrite(new ProgressEvent("write", {"target":me})); - } - - // If onwriteend callback - if (typeof me.onwriteend === "function") { - me.onwriteend(new ProgressEvent("writeend", {"target":me})); - } - }, - // Error callback - function(e) { - // If DONE (cancelled), then don't do anything - if (me.readyState === FileWriter.DONE) { - return; - } - - // DONE state - me.readyState = FileWriter.DONE; - - // Save error - me.error = new FileError(e); - - // If onerror callback - if (typeof me.onerror === "function") { - me.onerror(new ProgressEvent("error", {"target":me})); - } - - // If onwriteend callback - if (typeof me.onwriteend === "function") { - me.onwriteend(new ProgressEvent("writeend", {"target":me})); - } - }, "File", "write", [this.fileName, text, this.position]); -}; - -/** - * Moves the file pointer to the location specified. - * - * If the offset is a negative number the position of the file - * pointer is rewound. If the offset is greater than the file - * size the position is set to the end of the file. - * - * @param offset is the location to move the file pointer to. - */ -FileWriter.prototype.seek = function(offset) { - // Throw an exception if we are already writing a file - if (this.readyState === FileWriter.WRITING) { - throw new FileError(FileError.INVALID_STATE_ERR); - } - - if (!offset) { - return; - } - - // See back from end of file. - if (offset < 0) { - this.position = Math.max(offset + this.length, 0); - } - // Offset is bigger then file size so set position - // to the end of the file. - else if (offset > this.length) { - this.position = this.length; - } - // Offset is between 0 and file size so set the position - // to start writing. - else { - this.position = offset; - } -}; - -/** - * Truncates the file to the size specified. - * - * @param size to chop the file at. - */ -FileWriter.prototype.truncate = function(size) { - // Throw an exception if we are already writing a file - if (this.readyState === FileWriter.WRITING) { - throw new FileError(FileError.INVALID_STATE_ERR); - } - - // WRITING state - this.readyState = FileWriter.WRITING; - - var me = this; - - // If onwritestart callback - if (typeof me.onwritestart === "function") { - me.onwritestart(new ProgressEvent("writestart", {"target":this})); - } - - // Write file - exec( - // Success callback - function(r) { - // If DONE (cancelled), then don't do anything - if (me.readyState === FileWriter.DONE) { - return; - } - - // DONE state - me.readyState = FileWriter.DONE; - - // Update the length of the file - me.length = r; - me.position = Math.min(me.position, r); - - // If onwrite callback - if (typeof me.onwrite === "function") { - me.onwrite(new ProgressEvent("write", {"target":me})); - } - - // If onwriteend callback - if (typeof me.onwriteend === "function") { - me.onwriteend(new ProgressEvent("writeend", {"target":me})); - } - }, - // Error callback - function(e) { - // If DONE (cancelled), then don't do anything - if (me.readyState === FileWriter.DONE) { - return; - } - - // DONE state - me.readyState = FileWriter.DONE; - - // Save error - me.error = new FileError(e); - - // If onerror callback - if (typeof me.onerror === "function") { - me.onerror(new ProgressEvent("error", {"target":me})); - } - - // If onwriteend callback - if (typeof me.onwriteend === "function") { - me.onwriteend(new ProgressEvent("writeend", {"target":me})); - } - }, "File", "truncate", [this.fileName, size]); -}; - -module.exports = FileWriter; - -}); - -define('cordova/plugin/Flags', function(require, exports, module) { -/** - * Supplies arguments to methods that lookup or create files and directories. - * - * @param create - * {boolean} file or directory if it doesn't exist - * @param exclusive - * {boolean} used with create; if true the command will fail if - * target path exists - */ -function Flags(create, exclusive) { - this.create = create || false; - this.exclusive = exclusive || false; -} - -module.exports = Flags; - -}); - -define('cordova/plugin/geolocation', function(require, exports, module) { -var utils = require('cordova/utils'), - exec = require('cordova/exec'), - PositionError = require('cordova/plugin/PositionError'); - -var timers = {}; // list of timers in use - -// Returns default params, overrides if provided with values -function parseParameters(options) { - var opt = { - maximumAge: 10000, - enableHighAccuracy: false, - timeout: 10000 - }; - - if (options) { - if (typeof options.maximumAge !== "undefined") { - opt.maximumAge = options.maximumAge; - } - if (typeof options.enableHighAccuracy !== "undefined") { - opt.enableHighAccuracy = options.enableHighAccuracy; - } - if (typeof options.timeout !== "undefined") { - opt.timeout = options.timeout; - } - } - - return opt; -} - -var geolocation = { - /** - * Asynchronously aquires the current position. - * - * @param {Function} successCallback The function to call when the position data is available - * @param {Function} errorCallback The function to call when there is an error getting the heading position. (OPTIONAL) - * @param {PositionOptions} options The options for getting the position data. (OPTIONAL) - */ - getCurrentPosition:function(successCallback, errorCallback, options) { - options = parseParameters(options); - exec(successCallback, errorCallback, "Geolocation", "getLocation", [options.enableHighAccuracy, options.timeout, options.maximumAge]); - }, - /** - * Asynchronously watches the geolocation for changes to geolocation. When a change occurs, - * the successCallback is called with the new location. - * - * @param {Function} successCallback The function to call each time the location data is available - * @param {Function} errorCallback The function to call when there is an error getting the location data. (OPTIONAL) - * @param {PositionOptions} options The options for getting the location data such as frequency. (OPTIONAL) - * @return String The watch id that must be passed to #clearWatch to stop watching. - */ - watchPosition:function(successCallback, errorCallback, options) { - options = parseParameters(options); - var id = utils.createUUID(); - timers[id] = window.setInterval(function() { - exec(successCallback, errorCallback, "Geolocation", "getLocation", [options.enableHighAccuracy, options.timeout, options.maximumAge]); - }, options.timeout); - - return id; - }, - /** - * Clears the specified heading watch. - * - * @param {String} id The ID of the watch returned from #watchPosition - */ - clearWatch:function(id) { - if (id && timers[id] !== undefined) { - window.clearInterval(timers[id]); - delete timers[id]; - } - } -}; - -module.exports = geolocation; - -}); - - -define('cordova/plugin/LocalFileSystem', function(require, exports, module) { -var exec = require('cordova/exec'); - -/** - * Represents a local file system. - */ -var LocalFileSystem = function() { - -}; - -LocalFileSystem.TEMPORARY = 0; //temporary, with no guarantee of persistence -LocalFileSystem.PERSISTENT = 1; //persistent - -module.exports = LocalFileSystem; - -}); - -define('cordova/plugin/Media', function(require, exports, module) { -var utils = require('cordova/utils'), - exec = require('cordova/exec'); - -var mediaObjects = {}; - -/** - * This class provides access to the device media, interfaces to both sound and video - * - * @constructor - * @param src The file name or url to play - * @param successCallback The callback to be called when the file is done playing or recording. - * successCallback() - * @param errorCallback The callback to be called if there is an error. - * errorCallback(int errorCode) - OPTIONAL - * @param statusCallback The callback to be called when media status has changed. - * statusCallback(int statusCode) - OPTIONAL - */ -var Media = function(src, successCallback, errorCallback, statusCallback) { - - // successCallback optional - if (successCallback && (typeof successCallback !== "function")) { - console.log("Media Error: successCallback is not a function"); - return; - } - - // errorCallback optional - if (errorCallback && (typeof errorCallback !== "function")) { - console.log("Media Error: errorCallback is not a function"); - return; - } - - // statusCallback optional - if (statusCallback && (typeof statusCallback !== "function")) { - console.log("Media Error: statusCallback is not a function"); - return; - } - - this.id = utils.createUUID(); - mediaObjects[this.id] = this; - this.src = src; - this.successCallback = successCallback; - this.errorCallback = errorCallback; - this.statusCallback = statusCallback; - this._duration = -1; - this._position = -1; - exec(null, this.errorCallback, "Media", "create", [this.id, this.src]); -}; - -// Media messages -Media.MEDIA_STATE = 1; -Media.MEDIA_DURATION = 2; -Media.MEDIA_POSITION = 3; -Media.MEDIA_ERROR = 9; - -// Media states -Media.MEDIA_NONE = 0; -Media.MEDIA_STARTING = 1; -Media.MEDIA_RUNNING = 2; -Media.MEDIA_PAUSED = 3; -Media.MEDIA_STOPPED = 4; -Media.MEDIA_MSG = ["None", "Starting", "Running", "Paused", "Stopped"]; - -// "static" function to return existing objs. -Media.get = function(id) { - return mediaObjects[id]; -}; - -/** - * Start or resume playing audio file. - */ -Media.prototype.play = function() { - exec(this.successCallback, this.errorCallback, "Media", "startPlayingAudio", [this.id, this.src]); -}; - -/** - * Stop playing audio file. - */ -Media.prototype.stop = function() { - var me = this; - exec(function() { - me._position = 0; - me.successCallback(); - }, this.errorCallback, "Media", "stopPlayingAudio", [this.id]); -}; - -/** - * Seek or jump to a new time in the track.. - */ -Media.prototype.seekTo = function(milliseconds) { - var me = this; - exec(function(p) { - me._position = p; - }, this.errorCallback, "Media", "seekToAudio", [this.id, milliseconds]); -}; - -/** - * Pause playing audio file. - */ -Media.prototype.pause = function() { - exec(null, this.errorCallback, "Media", "pausePlayingAudio", [this.id]); -}; - -/** - * Get duration of an audio file. - * The duration is only set for audio that is playing, paused or stopped. - * - * @return duration or -1 if not known. - */ -Media.prototype.getDuration = function() { - return this._duration; -}; - -/** - * Get position of audio. - */ -Media.prototype.getCurrentPosition = function(success, fail) { - var me = this; - exec(function(p) { - me._position = p; - success(p); - }, fail, "Media", "getCurrentPositionAudio", [this.id]); -}; - -/** - * Start recording audio file. - */ -Media.prototype.startRecord = function() { - exec(this.successCallback, this.errorCallback, "Media", "startRecordingAudio", [this.id, this.src]); -}; - -/** - * Stop recording audio file. - */ -Media.prototype.stopRecord = function() { - exec(this.successCallback, this.errorCallback, "Media", "stopRecordingAudio", [this.id]); -}; - -/** - * Release the resources. - */ -Media.prototype.release = function() { - exec(null, this.errorCallback, "Media", "release", [this.id]); -}; - -/** - * Adjust the volume. - */ -Media.prototype.setVolume = function(volume) { - exec(null, null, "Media", "setVolume", [this.id, volume]); -}; - -/** - * Audio has status update. - * PRIVATE - * - * @param id The media object id (string) - * @param status The status code (int) - * @param msg The status message (string) - */ -Media.onStatus = function(id, msg, value) { - var media = mediaObjects[id]; - // If state update - if (msg === Media.MEDIA_STATE) { - if (value === Media.MEDIA_STOPPED) { - if (media.successCallback) { - media.successCallback(); - } - } - if (media.statusCallback) { - media.statusCallback(value); - } - } - else if (msg === Media.MEDIA_DURATION) { - media._duration = value; - } - else if (msg === Media.MEDIA_ERROR) { - if (media.errorCallback) { - media.errorCallback({"code":value}); - } - } - else if (msg === Media.MEDIA_POSITION) { - media._position = value; - } -}; - -module.exports = Media; - -}); - -define('cordova/plugin/MediaError', function(require, exports, module) { -/** - * This class contains information about any Media errors. - * @constructor - */ -var MediaError = function(code, msg) { - this.code = code || null; - this.message = msg || ""; -}; - -MediaError.MEDIA_ERR_NONE_ACTIVE = 0; -MediaError.MEDIA_ERR_ABORTED = 1; -MediaError.MEDIA_ERR_NETWORK = 2; -MediaError.MEDIA_ERR_DECODE = 3; -MediaError.MEDIA_ERR_NONE_SUPPORTED = 4; - -module.exports = MediaError; - -}); - -define('cordova/plugin/MediaFile', function(require, exports, module) { -var utils = require('cordova/utils'), - exec = require('cordova/exec'), - File = require('cordova/plugin/File'), - CaptureError = require('cordova/plugin/CaptureError'); -/** - * Represents a single file. - * - * name {DOMString} name of the file, without path information - * fullPath {DOMString} the full path of the file, including the name - * type {DOMString} mime type - * lastModifiedDate {Date} last modified date - * size {Number} size of the file in bytes - */ -var MediaFile = function(name, fullPath, type, lastModifiedDate, size){ - MediaFile.__super__.constructor.apply(this, arguments); -}; - -utils.extend(MediaFile, File); - -/** - * Request capture format data for a specific file and type - * - * @param {Function} successCB - * @param {Function} errorCB - */ -MediaFile.prototype.getFormatData = function(successCallback, errorCallback) { - if (typeof this.fullPath === "undefined" || this.fullPath === null) { - errorCallback(new CaptureError(CaptureError.CAPTURE_INVALID_ARGUMENT)); - } else { - exec(successCallback, errorCallback, "Capture", "getFormatData", [this.fullPath, this.type]); - } -}; - -/** - * Casts a PluginResult message property (array of objects) to an array of MediaFile objects - * (used in Objective-C and Android) - * - * @param {PluginResult} pluginResult - */ -MediaFile.cast = function(pluginResult) { - var mediaFiles = []; - var i; - for (i=0; i.dispatchEvent - // need to first figure out how to implement EventTarget - } - } - return event; - }; - try { - var ev = createEvent({type:"abort",target:document}); - return function ProgressEvent(type, data) { - data.type = type; - return createEvent(data); - }; - } catch(e){ - */ - return function ProgressEvent(type, dict) { - this.type = type; - this.bubbles = false; - this.cancelBubble = false; - this.cancelable = false; - this.lengthComputable = false; - this.loaded = dict && dict.loaded ? dict.loaded : 0; - this.total = dict && dict.total ? dict.total : 0; - this.target = dict && dict.target ? dict.target : null; - }; - //} -})(); - -module.exports = ProgressEvent; - -}); - -define('cordova/plugin/requestFileSystem', function(require, exports, module) { -var FileError = require('cordova/plugin/FileError'), - FileSystem = require('cordova/plugin/FileSystem'), - exec = require('cordova/exec'); - -/** - * Request a file system in which to store application data. - * @param type local file system type - * @param size indicates how much storage space, in bytes, the application expects to need - * @param successCallback invoked with a FileSystem object - * @param errorCallback invoked if error occurs retrieving file system - */ -var requestFileSystem = function(type, size, successCallback, errorCallback) { - var fail = function(code) { - if (typeof errorCallback === 'function') { - errorCallback(new FileError(code)); - } - }; - - if (type < 0 || type > 3) { - fail(FileError.SYNTAX_ERR); - } else { - // if successful, return a FileSystem object - var success = function(file_system) { - if (file_system) { - if (typeof successCallback === 'function') { - // grab the name and root from the file system object - var result = new FileSystem(file_system.name, file_system.root); - successCallback(result); - } - } - else { - // no FileSystem object returned - fail(FileError.NOT_FOUND_ERR); - } - }; - exec(success, fail, "File", "requestFileSystem", [type, size]); - } -}; - -module.exports = requestFileSystem; - -}); - -define('cordova/plugin/resolveLocalFileSystemURI', function(require, exports, module) { -var DirectoryEntry = require('cordova/plugin/DirectoryEntry'), - FileEntry = require('cordova/plugin/FileEntry'), - exec = require('cordova/exec'); - -/** - * Look up file system Entry referred to by local URI. - * @param {DOMString} uri URI referring to a local file or directory - * @param successCallback invoked with Entry object corresponding to URI - * @param errorCallback invoked if error occurs retrieving file system entry - */ -module.exports = function(uri, successCallback, errorCallback) { - // error callback - var fail = function(error) { - if (typeof errorCallback === 'function') { - errorCallback(new FileError(error)); - } - }; - // if successful, return either a file or directory entry - var success = function(entry) { - var result; - - if (entry) { - if (typeof successCallback === 'function') { - // create appropriate Entry object - result = (entry.isDirectory) ? new DirectoryEntry(entry.name, entry.fullPath) : new FileEntry(entry.name, entry.fullPath); - try { - successCallback(result); - } - catch (e) { - console.log('Error invoking callback: ' + e); - } - } - } - else { - // no Entry object returned - fail(FileError.NOT_FOUND_ERR); - } - }; - - exec(success, fail, "File", "resolveLocalFileSystemURI", [uri]); -}; - -}); - - -define('cordova/plugin/android/app', function(require, exports, module) { -var exec = require('cordova/exec'); - -module.exports = { - /** - * Clear the resource cache. - */ - clearCache:function() { - exec(null, null, "App", "clearCache", []); - }, - - /** - * Load the url into the webview or into new browser instance. - * - * @param url The URL to load - * @param props Properties that can be passed in to the activity: - * wait: int => wait msec before loading URL - * loadingDialog: "Title,Message" => display a native loading dialog - * loadUrlTimeoutValue: int => time in msec to wait before triggering a timeout error - * clearHistory: boolean => clear webview history (default=false) - * openExternal: boolean => open in a new browser (default=false) - * - * Example: - * navigator.app.loadUrl("http://server/myapp/index.html", {wait:2000, loadingDialog:"Wait,Loading App", loadUrlTimeoutValue: 60000}); - */ - loadUrl:function(url, props) { - exec(null, null, "App", "loadUrl", [url, props]); - }, - - /** - * Cancel loadUrl that is waiting to be loaded. - */ - cancelLoadUrl:function() { - exec(null, null, "App", "cancelLoadUrl", []); - }, - - /** - * Clear web history in this web view. - * Instead of BACK button loading the previous web page, it will exit the app. - */ - clearHistory:function() { - exec(null, null, "App", "clearHistory", []); - }, - - /** - * Go to previous page displayed. - * This is the same as pressing the backbutton on Android device. - */ - backHistory:function() { - exec(null, null, "App", "backHistory", []); - }, - - /** - * Override the default behavior of the Android back button. - * If overridden, when the back button is pressed, the "backKeyDown" JavaScript event will be fired. - * - * Note: The user should not have to call this method. Instead, when the user - * registers for the "backbutton" event, this is automatically done. - * - * @param override T=override, F=cancel override - */ - overrideBackbutton:function(override) { - exec(null, null, "App", "overrideBackbutton", [override]); - }, - - /** - * Exit and terminate the application. - */ - exitApp:function() { - return exec(null, null, "App", "exitApp", []); - } -}; - -}); - -define('cordova/plugin/android/callback', function(require, exports, module) { -var port = null, - token = null, - cordova = require('cordova'), - polling = require('cordova/plugin/android/polling'), - callback = function() { - // Exit if shutting down app - if (cordova.shuttingDown) { - return; - } - - // If polling flag was changed, start using polling from now on - if (cordova.UsePolling) { - polling(); - return; - } - - var xmlhttp = new XMLHttpRequest(); - - // Callback function when XMLHttpRequest is ready - xmlhttp.onreadystatechange=function(){ - if(xmlhttp.readyState === 4){ - - // Exit if shutting down app - if (cordova.shuttingDown) { - return; - } - - // If callback has JavaScript statement to execute - if (xmlhttp.status === 200) { - - // Need to url decode the response - var msg = decodeURIComponent(xmlhttp.responseText); - setTimeout(function() { - try { - var t = eval(msg); - } - catch (e) { - // If we're getting an error here, seeing the message will help in debugging - console.log("JSCallback: Message from Server: " + msg); - console.log("JSCallback Error: "+e); - } - }, 1); - setTimeout(callback, 1); - } - - // If callback ping (used to keep XHR request from timing out) - else if (xmlhttp.status === 404) { - setTimeout(callback, 10); - } - - // If security error - else if (xmlhttp.status === 403) { - console.log("JSCallback Error: Invalid token. Stopping callbacks."); - } - - // If server is stopping - else if (xmlhttp.status === 503) { - console.log("JSCallback Server Closed: Stopping callbacks."); - } - - // If request wasn't GET - else if (xmlhttp.status === 400) { - console.log("JSCallback Error: Bad request. Stopping callbacks."); - } - - // If error, revert to polling - else { - console.log("JSCallback Error: Request failed."); - cordova.UsePolling = true; - polling(); - } - } - }; - - if (port === null) { - port = prompt("getPort", "gap_callbackServer:"); - } - if (token === null) { - token = prompt("getToken", "gap_callbackServer:"); - } - xmlhttp.open("GET", "http://127.0.0.1:"+port+"/"+token , true); - xmlhttp.send(); -}; - -module.exports = callback; - -}); - -define('cordova/plugin/android/device', function(require, exports, module) { -var channel = require('cordova/channel'), - exec = require('cordova/exec'); - -/** - * This represents the mobile device, and provides properties for inspecting the model, version, UUID of the - * phone, etc. - * @constructor - */ -function Device() { - this.available = false; - this.platform = null; - this.version = null; - this.name = null; - this.uuid = null; - this.cordova = null; - - var me = this; - this.getInfo( - function(info) { - me.available = true; - me.platform = info.platform; - me.version = info.version; - me.name = info.name; - me.uuid = info.uuid; - me.cordova = info.cordova; - channel.onCordovaInfoReady.fire(); - }, - function(e) { - me.available = false; - console.log("Error initializing Cordova: " + e); - alert("Error initializing Cordova: "+e); - }); -} - -/** - * Get device info - * - * @param {Function} successCallback The function to call when the heading data is available - * @param {Function} errorCallback The function to call when there is an error getting the heading data. (OPTIONAL) - */ -Device.prototype.getInfo = function(successCallback, errorCallback) { - - // successCallback required - if (typeof successCallback !== "function") { - console.log("Device Error: successCallback is not a function"); - return; - } - - // errorCallback optional - if (errorCallback && (typeof errorCallback !== "function")) { - console.log("Device Error: errorCallback is not a function"); - return; - } - - // Get info - exec(successCallback, errorCallback, "Device", "getDeviceInfo", []); -}; - -/* - * DEPRECATED - * This is only for Android. - * - * You must explicitly override the back button. - */ -Device.prototype.overrideBackButton = function() { - console.log("Device.overrideBackButton() is deprecated. Use App.overrideBackbutton(true)."); - navigator.app.overrideBackbutton(true); -}; - -/* - * DEPRECATED - * This is only for Android. - * - * This resets the back button to the default behaviour - */ -Device.prototype.resetBackButton = function() { - console.log("Device.resetBackButton() is deprecated. Use App.overrideBackbutton(false)."); - navigator.app.overrideBackbutton(false); -}; - -/* - * DEPRECATED - * This is only for Android. - * - * This terminates the activity! - */ -Device.prototype.exitApp = function() { - console.log("Device.exitApp() is deprecated. Use App.exitApp()."); - navigator.app.exitApp(); -}; - -module.exports = new Device(); - -}); - -define('cordova/plugin/android/polling', function(require, exports, module) { -var cordova = require('cordova'), - period = 50, - polling = function() { - // Exit if shutting down app - if (cordova.shuttingDown) { - return; - } - - // If polling flag was changed, stop using polling from now on and switch to XHR server / callback - if (!cordova.UsePolling) { - require('cordova/plugin/android/callback')(); - return; - } - - var msg = prompt("", "gap_poll:"); - if (msg) { - setTimeout(function() { - try { - var t = eval(""+msg); - } - catch (e) { - console.log("JSCallbackPolling: Message from Server: " + msg); - console.log("JSCallbackPolling Error: "+e); - } - }, 1); - setTimeout(polling, 1); - } - else { - setTimeout(polling, period); - } -}; - -module.exports = polling; - -}); - -define('cordova/plugin/android/storage', function(require, exports, module) { -var utils = require('cordova/utils'), - exec = require('cordova/exec'); - -var queryQueue = {}; - -/** - * SQL result set object - * PRIVATE METHOD - * @constructor - */ -var DroidDB_Rows = function() { - this.resultSet = []; // results array - this.length = 0; // number of rows -}; - -/** - * Get item from SQL result set - * - * @param row The row number to return - * @return The row object - */ -DroidDB_Rows.prototype.item = function(row) { - return this.resultSet[row]; -}; - -/** - * SQL result set that is returned to user. - * PRIVATE METHOD - * @constructor - */ -var DroidDB_Result = function() { - this.rows = new DroidDB_Rows(); -}; - -/** - * Callback from native code when query is complete. - * PRIVATE METHOD - * - * @param id Query id - */ -function completeQuery(id, data) { - var query = queryQueue[id]; - if (query) { - try { - delete queryQueue[id]; - - // Get transaction - var tx = query.tx; - - // If transaction hasn't failed - // Note: We ignore all query results if previous query - // in the same transaction failed. - if (tx && tx.queryList[id]) { - - // Save query results - var r = new DroidDB_Result(); - r.rows.resultSet = data; - r.rows.length = data.length; - try { - if (typeof query.successCallback === 'function') { - query.successCallback(query.tx, r); - } - } catch (ex) { - console.log("executeSql error calling user success callback: "+ex); - } - - tx.queryComplete(id); - } - } catch (e) { - console.log("executeSql error: "+e); - } - } -} - -/** - * Callback from native code when query fails - * PRIVATE METHOD - * - * @param reason Error message - * @param id Query id - */ -function failQuery(reason, id) { - var query = queryQueue[id]; - if (query) { - try { - delete queryQueue[id]; - - // Get transaction - var tx = query.tx; - - // If transaction hasn't failed - // Note: We ignore all query results if previous query - // in the same transaction failed. - if (tx && tx.queryList[id]) { - tx.queryList = {}; - - try { - if (typeof query.errorCallback === 'function') { - query.errorCallback(query.tx, reason); - } - } catch (ex) { - console.log("executeSql error calling user error callback: "+ex); - } - - tx.queryFailed(id, reason); - } - - } catch (e) { - console.log("executeSql error: "+e); - } - } -} - -/** - * SQL query object - * PRIVATE METHOD - * - * @constructor - * @param tx The transaction object that this query belongs to - */ -var DroidDB_Query = function(tx) { - - // Set the id of the query - this.id = utils.createUUID(); - - // Add this query to the queue - queryQueue[this.id] = this; - - // Init result - this.resultSet = []; - - // Set transaction that this query belongs to - this.tx = tx; - - // Add this query to transaction list - this.tx.queryList[this.id] = this; - - // Callbacks - this.successCallback = null; - this.errorCallback = null; - -}; - -/** - * Transaction object - * PRIVATE METHOD - * @constructor - */ -var DroidDB_Tx = function() { - - // Set the id of the transaction - this.id = utils.createUUID(); - - // Callbacks - this.successCallback = null; - this.errorCallback = null; - - // Query list - this.queryList = {}; -}; - -/** - * Mark query in transaction as complete. - * If all queries are complete, call the user's transaction success callback. - * - * @param id Query id - */ -DroidDB_Tx.prototype.queryComplete = function(id) { - delete this.queryList[id]; - - // If no more outstanding queries, then fire transaction success - if (this.successCallback) { - var count = 0; - var i; - for (i in this.queryList) { - if (this.queryList.hasOwnProperty(i)) { - count++; - } - } - if (count === 0) { - try { - this.successCallback(); - } catch(e) { - console.log("Transaction error calling user success callback: " + e); - } - } - } -}; - -/** - * Mark query in transaction as failed. - * - * @param id Query id - * @param reason Error message - */ -DroidDB_Tx.prototype.queryFailed = function(id, reason) { - - // The sql queries in this transaction have already been run, since - // we really don't have a real transaction implemented in native code. - // However, the user callbacks for the remaining sql queries in transaction - // will not be called. - this.queryList = {}; - - if (this.errorCallback) { - try { - this.errorCallback(reason); - } catch(e) { - console.log("Transaction error calling user error callback: " + e); - } - } -}; - -/** - * Execute SQL statement - * - * @param sql SQL statement to execute - * @param params Statement parameters - * @param successCallback Success callback - * @param errorCallback Error callback - */ -DroidDB_Tx.prototype.executeSql = function(sql, params, successCallback, errorCallback) { - - // Init params array - if (typeof params === 'undefined') { - params = []; - } - - // Create query and add to queue - var query = new DroidDB_Query(this); - queryQueue[query.id] = query; - - // Save callbacks - query.successCallback = successCallback; - query.errorCallback = errorCallback; - - // Call native code - exec(null, null, "Storage", "executeSql", [sql, params, query.id]); -}; - -var DatabaseShell = function() { -}; - -/** - * Start a transaction. - * Does not support rollback in event of failure. - * - * @param process {Function} The transaction function - * @param successCallback {Function} - * @param errorCallback {Function} - */ -DatabaseShell.prototype.transaction = function(process, errorCallback, successCallback) { - var tx = new DroidDB_Tx(); - tx.successCallback = successCallback; - tx.errorCallback = errorCallback; - try { - process(tx); - } catch (e) { - console.log("Transaction error: "+e); - if (tx.errorCallback) { - try { - tx.errorCallback(e); - } catch (ex) { - console.log("Transaction error calling user error callback: "+e); - } - } - } -}; - -/** - * Open database - * - * @param name Database name - * @param version Database version - * @param display_name Database display name - * @param size Database size in bytes - * @return Database object - */ -var DroidDB_openDatabase = function(name, version, display_name, size) { - exec(null, null, "Storage", "openDatabase", [name, version, display_name, size]); - var db = new DatabaseShell(); - return db; -}; - -/** - * For browsers with no localStorage we emulate it with SQLite. Follows the w3c api. - * TODO: Do similar for sessionStorage. - * @constructor - */ -var CupcakeLocalStorage = function() { - try { - - this.db = openDatabase('localStorage', '1.0', 'localStorage', 2621440); - var storage = {}; - this.length = 0; - function setLength (length) { - this.length = length; - localStorage.length = length; - } - this.db.transaction( - function (transaction) { - var i; - transaction.executeSql('CREATE TABLE IF NOT EXISTS storage (id NVARCHAR(40) PRIMARY KEY, body NVARCHAR(255))'); - transaction.executeSql('SELECT * FROM storage', [], function(tx, result) { - for(var i = 0; i < result.rows.length; i++) { - storage[result.rows.item(i)['id']] = result.rows.item(i)['body']; - } - setLength(result.rows.length); - }); - - }, - function (err) { - alert(err.message); - } - ); - this.setItem = function(key, val) { - if (typeof(storage[key])=='undefined') { - this.length++; - } - storage[key] = val; - this.db.transaction( - function (transaction) { - transaction.executeSql('CREATE TABLE IF NOT EXISTS storage (id NVARCHAR(40) PRIMARY KEY, body NVARCHAR(255))'); - transaction.executeSql('REPLACE INTO storage (id, body) values(?,?)', [key,val]); - } - ); - }; - this.getItem = function(key) { - return storage[key]; - }; - this.removeItem = function(key) { - delete storage[key]; - this.length--; - this.db.transaction( - function (transaction) { - transaction.executeSql('CREATE TABLE IF NOT EXISTS storage (id NVARCHAR(40) PRIMARY KEY, body NVARCHAR(255))'); - transaction.executeSql('DELETE FROM storage where id=?', [key]); - } - ); - }; - this.clear = function() { - storage = {}; - this.length = 0; - this.db.transaction( - function (transaction) { - transaction.executeSql('CREATE TABLE IF NOT EXISTS storage (id NVARCHAR(40) PRIMARY KEY, body NVARCHAR(255))'); - transaction.executeSql('DELETE FROM storage', []); - } - ); - }; - this.key = function(index) { - var i = 0; - for (var j in storage) { - if (i==index) { - return j; - } else { - i++; - } - } - return null; - }; - - } catch(e) { - alert("Database error "+e+"."); - return; - } -}; - -module.exports = { - openDatabase:DroidDB_openDatabase, - CupcakeLocalStorage:CupcakeLocalStorage, - failQuery:failQuery, - completeQuery:completeQuery -}; - -}); -window.cordova = require('cordova'); -(function (context) { - var channel = require("cordova/channel"), - /** - * cordova Channels that must fire before "deviceready" is fired. - */ - deviceReadyChannelsArray = [channel.onCordovaReady, channel.onCordovaInfoReady, channel.onCordovaConnectionReady], - deviceReadyChannelsMap = {}, - _self = { - boot: function () { - //--------------- - // Event handling - //--------------- - - /** - * Listen for DOMContentLoaded and notify our channel subscribers. - */ - document.addEventListener('DOMContentLoaded', function() { - channel.onDOMContentLoaded.fire(); - }, false); - if (document.readyState == 'complete') { - channel.onDOMContentLoaded.fire(); - } - - /** - * Create all cordova objects once page has fully loaded and native side is ready. - */ - channel.join(function() { - var builder = require('cordova/builder'), - base = require('cordova/common'), - platform = require('cordova/platform'); - - // Drop the common globals into the window object, but be nice and don't overwrite anything. - builder.build(base.objects).intoButDontClobber(window); - - // Drop the platform-specific globals into the window object and do it like a honey badger does it. - builder.build(platform.objects).intoAndClobberTheFOutOf(window); - - // Call the platform-specific initialization - platform.initialize(); - - // Fire event to notify that all objects are created - channel.onCordovaReady.fire(); - - // Fire onDeviceReady event once all constructors have run and - // cordova info has been received from native side. - channel.join(function() { - channel.onDeviceReady.fire(); - - // Fire the onresume event, since first one happens before JavaScript is loaded - channel.onResume.fire(); - }, deviceReadyChannelsArray); - - }, [ channel.onDOMContentLoaded, channel.onNativeReady ]); - } - }; - // boot up once native side is ready - channel.onNativeReady.subscribe(_self.boot); - - // _nativeReady is global variable that the native side can set - // to signify that the native code is ready. It is a global since - // it may be called before any cordova JS is ready. - if (window._nativeReady) { - channel.onNativeReady.fire(); - } - -}(window)); - diff --git a/DroidGap/lawnchair-adapter-test/www/index.html b/DroidGap/lawnchair-adapter-test/www/index.html deleted file mode 100644 index ac9fbf4..0000000 --- a/DroidGap/lawnchair-adapter-test/www/index.html +++ /dev/null @@ -1,185 +0,0 @@ - - - - - - PhoneGap - - - - - - - - - - - - - - - - - - - - - -

    Lawnchair Spec

    -

    -

    -
      - - - - - diff --git a/DroidGap/lawnchair-adapter-test/www/lawnchair-spec.js b/DroidGap/lawnchair-adapter-test/www/lawnchair-spec.js deleted file mode 100755 index 39515b1..0000000 --- a/DroidGap/lawnchair-adapter-test/www/lawnchair-spec.js +++ /dev/null @@ -1,429 +0,0 @@ -module('Lawnchair construction/destruction', { - setup:function() { - }, - teardown:function() { - } -}); - -test('ctor requires callbacks in each form', function() { - QUnit.stop(); - QUnit.expect(6); - - // raise exception if no ctor callback is supplied - try { - var lc2 = new Lawnchair(); - } catch(e) { - ok(true, 'exception raised if no callback supplied to init'); - } - try { - var lc3 = new Lawnchair({}, {}); - } catch(e) { - ok(true, 'exception raised if no callback supplied to init, but two args are present'); - } - try { - var lc3 = new Lawnchair({}); - } catch(e) { - ok(true, 'exception raised if no callback supplied to init, but one arg is present'); - } - - var lc = new Lawnchair({name:store.name}, function(ref) { - ok(true, 'should call passed in callback when using obj+function ctor form') - equals(this, ref, "lawnchair callback scoped to lawnchair instance") - equals(ref, this, "lawnchair passes self into callback too") - QUnit.start() - }); -}); - -/** -test('independent data stores', function() { - - var store1 = new Lawnchair({name: "store1"}, function() {}); - - store1 .save({key: 'apple', quantity: 3}, function() { - - var store2 = new Lawnchair({name: "store2"}, function() {}); - - store1.all(function(r) { - equals(r.length, 1); - }); - - store2.all(function(r) { - equals(r.length, 0); - }); - - }) - - -}) -**/ - -module('all()', { - setup:function() { - QUnit.stop(); - - // I like to make all my variables globals. Starting a new trend. - me = {name:'brian', age:30}; - store.nuke(function() { QUnit.start(); }); - }, - teardown:function() { - me = null; - } -}) - -test('chainable', function() { - QUnit.stop(); - QUnit.expect(1); - - same(store.all(function(r) { QUnit.start(); }), store, 'should be chainable (return itself)'); -}) - -test('full callback syntax', function() { - QUnit.stop(); - QUnit.expect(4); - - store.all(function(r) { - ok(true, 'calls callback'); - ok(r instanceof Array, 'should provide array as parameter'); - equals(r.length, 0, 'parameter should initially have zero length'); - same(this, store, '"this" should be scoped to the lawnchair object inside callback'); - QUnit.start(); - }); -}) - -test('adding, nuking and size tests', function() { - QUnit.stop(); - QUnit.expect(2); - - store.save(me, function() { - store.all(function(r) { - equals(r.length, 1, 'parameter should have length 1 after saving a single record'); - store.nuke(function() { - store.all(function(r) { - equals(r.length, 0, 'parameter should have length 0 after nuking'); - QUnit.start(); - }); - }); - }); - }); -}) - -test( 'shorthand callback syntax', function() { - QUnit.stop(); - QUnit.expect(2); - - store.all('ok(true, "shorthand syntax callback gets evaled"); same(this, store, "`this` should be scoped to the Lawnchair instance"); QUnit.start();'); - - // Is this test block necessary? - // - // var tmp = new Lawnchair({name:'temps', record:'tmp'}, function(){ - // QUnit.start() - // var Temps = this; - // equals(this, Temps, 'this is bound to Lawnchair') - // QUnit.stop() - // Temps.all('ok(temps, "this.name is passed to all callback"); QUnit.start()') - // }) -}) - -test('scoped variable in shorthand callback', function() { - QUnit.expect(1); - QUnit.stop(); - - // FIXME fkn qunit being weird here... expect(1) - var tmp = new Lawnchair({name:'temps', record:'tmp'}, function() { - this.nuke(function() { - this.save({a:1}, function() { - this.each('ok(tmp, "this.record is passed to each callback"); QUnit.start()') - }) - }) - }) -}) - -module('nuke()', { - setup:function() { - QUnit.stop(); - store.nuke(function() { - QUnit.start() - }); - }, - teardown:function() { - } -}) - -test( 'chainable', function() { - QUnit.expect(1); - QUnit.stop() - - same(store.nuke(function() { QUnit.start() }), store, 'should be chainable'); -}) - -test( 'full callback syntax', function() { - QUnit.stop(); - QUnit.expect(2); - - store.nuke(function() { - ok(true, "should call callback in nuke"); - same(this, store, '"this" should be scoped to the Lawnchair instance'); - QUnit.start(); - }); -}) - -test( 'shorthand callback syntax', function() { - QUnit.stop(); - QUnit.expect(2); - - store.nuke('ok(true, "shorthand syntax callback gets evaled"); same(this, store, "`this` should be scoped to the Lawnchair instance"); QUnit.start();'); -}) - -module('save()', { - setup:function() { - QUnit.stop(); - - // I like to make all my variables globals. Starting a new trend. - me = {name:'brian', age:30}; - store.nuke(function() { QUnit.start(); }); - }, - teardown:function() { - me = null; - } -}) - -test( 'chainable', function() { - QUnit.stop(); - QUnit.expect(1); - - same(store.save(me, function() { QUnit.start(); }), store, 'should be chainable'); -}) - -test( 'full callback syntax', function() { - QUnit.stop(); - QUnit.expect(2); - - store.save(me, function(it) { - ok(true, 'should call passed in callback'); - same(it, me, 'should pass in original saved object in callback'); - QUnit.start(); - }); -}) - -test( 'shorthand callback syntax', function() { - QUnit.stop(); - QUnit.expect(2); - - store.save(me, 'ok(true, "shorthand syntax callback gets evaled"); same(this, store, "`this` should be scoped to the Lawnchair instance"); QUnit.start();'); -}) - -test( 'saving objects', function() { - QUnit.stop(); - QUnit.expect(1); - - store.save(me, function() { - store.save({key:"something", value:"else"}, function(r) { - store.all(function(r) { - equals(r.length, 2, 'after saving two keys, num. records should equal to 2'); - QUnit.start(); - }); - }); - }) -}) - -test( 'save without callback', function() { - - QUnit.stop(); - QUnit.expect(1); - - store.save(me, function(obj) { - var key = obj.key; - store.save(obj); - equals(obj.key, key, "save without callback retains key"); - QUnit.start(); - }) - -}); - -module('batch()', { - setup:function() { - QUnit.stop(); - - // I like to make all my variables globals. Starting a new trend. - me = {name:'brian', age:30}; - store.nuke(function() { QUnit.start(); }); - }, - teardown:function() { - me = null; - } -}) - -test('batch insertion', function(){ - QUnit.expect(3); - QUnit.stop(); - - ok(store.batch, 'batch implemented'); - equals(store.batch([]), store, 'chainable') - - store.batch([{i:1},{i:2}], function() { - store.all(function(r){ - equals(r.length, 2, 'should be two records from batch insert with array of two objects'); - QUnit.start(); - }); - }); -}) - -test( 'full callback syntax', function() { - QUnit.stop(500); - QUnit.expect(2); - - store.batch([{j:'k'}], function() { - ok(true, 'callback called with full syntax'); - same(this, store, '"this" should be the LAwnchair instance'); - QUnit.start(); - }) -}) - -test( 'shorthand callback syntax', function() { - QUnit.stop(500); - QUnit.expect(2); - - store.batch([{o:'k'}], 'ok(true, "shorthand syntax callback gets evaled"); same(this, store, "`this` should be scoped to the Lawnchair instance"); QUnit.start();') -}) - -module('get()', { - setup:function() { - QUnit.stop(); - - // I like to make all my variables globals. Starting a new trend. - me = {name:'brian', age:30}; - store.nuke(function() { QUnit.start(); }); - }, - teardown:function() { - me = null; - } -}); - -test( 'should it be chainable?', function() { - QUnit.expect(1); - QUnit.stop(); - - equals(store.get('foo', function() { QUnit.start(); }), store, 'get chainable'); -}); - -test('get functionality', function() { - QUnit.expect(4); - QUnit.stop(); - - store.save({key:'xyz', name:'tim'}, function() { - store.get('xyz', function(r) { - equals(r.key, 'xyz', 'should return key in loaded object'); - equals(r.name, 'tim', 'should return proper object when calling get with a key'); - store.get('doesntexist', function(s) { - ok(true, 'should call callback even for non-existent key'); - equals(s, null, 'should return null for non-existent key'); - QUnit.start(); - }); - }); - }); -}); - -test('get batch functionality', function() { - QUnit.expect(3); - QUnit.stop(500); - - var t = [{key:'test-get'},{key:'test-get-1'}] - store.batch(t, function() { - this.get(['test-get','test-get-1'], function(r) { - equals(r[0].key, 'test-get', "get first object"); - equals(r[1].key, 'test-get-1', "get second object"); - equals(r.length, t.length, "should batch get") - QUnit.start() - }) - }) -}); - -test( 'full callback syntax', function() { - QUnit.stop(); - QUnit.expect(2); - - store.get('somekey', function(r){ - ok(true, 'callback got called'); - same(this, store, '"this" should be teh Lawnchair instance'); - QUnit.start(); - }); -}); - -test('short callback syntax', function() { - QUnit.stop(); - QUnit.expect(2); - - store.get('somekey', 'ok(true, "shorthand syntax callback gets evaled"); same(this, store, "`this` should be scoped to the Lawnchair instance"); QUnit.start();'); -}); - -module('remove()', { - setup:function() { - QUnit.stop(); - - // I like to make all my variables globals. Starting a new trend. - me = {name:'brian', age:30}; - store.nuke(function() { QUnit.start(); }); - }, - teardown:function() { - me = null; - } -}); - - -test( 'chainable', function() { - QUnit.expect(1); - QUnit.stop(); - - store.save({key:'me', name:'brian'}, function() { - same(store.remove('me', function() { - QUnit.start(); - }), store, 'should be chainable'); - - }); -}); - -test( 'full callback syntax', function() { - QUnit.stop(); - QUnit.expect(2); - - store.save({key:'somekey', name:'something'}, function() { - store.remove('somekey', function(r){ - ok(true, 'callback got called'); - same(this, store, '"this" should be teh Lawnchair instance'); - QUnit.start(); - }); - }); -}); - -test('short callback syntax', function() { - QUnit.stop(); - QUnit.expect(2); - - store.save({key:'somekey', name:'something'}, function() { - store.remove('somekey', 'ok(true, "shorthand syntax callback gets evaled"); same(this, store, "`this` should be scoped to the Lawnchair instance"); QUnit.start();'); - }); -}); - -// FIXME need to add tests for batch deletion -test( 'remove functionality', function() { - QUnit.stop(); - QUnit.expect(2); - - store.save({name:'joni'}, function(r) { - //store.find("r.name == 'joni'", function(r){ - store.remove(r, function(r) { - store.all(function(all) { - equals(all.length, 0, "should have length 0 after saving, finding, and removing a record using entire object"); - store.save({key:'die', name:'dudeman'}, function(r) { - store.remove('die', function(r){ - store.all(function(rec) { - equals(rec.length, 0, "should have length 0 after saving and removing by string key"); - QUnit.start(); - }); - }); - }); - }); - }); - //}); - }); -}); diff --git a/DroidGap/lawnchair-adapter-test/www/lib/json2.js b/DroidGap/lawnchair-adapter-test/www/lib/json2.js deleted file mode 100644 index a1a3b17..0000000 --- a/DroidGap/lawnchair-adapter-test/www/lib/json2.js +++ /dev/null @@ -1,482 +0,0 @@ -/* - http://www.JSON.org/json2.js - 2010-03-20 - - Public Domain. - - NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. - - See http://www.JSON.org/js.html - - - This code should be minified before deployment. - See http://javascript.crockford.com/jsmin.html - - USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO - NOT CONTROL. - - - This file creates a global JSON object containing two methods: stringify - and parse. - - JSON.stringify(value, replacer, space) - value any JavaScript value, usually an object or array. - - replacer an optional parameter that determines how object - values are stringified for objects. It can be a - function or an array of strings. - - space an optional parameter that specifies the indentation - of nested structures. If it is omitted, the text will - be packed without extra whitespace. If it is a number, - it will specify the number of spaces to indent at each - level. If it is a string (such as '\t' or ' '), - it contains the characters used to indent at each level. - - This method produces a JSON text from a JavaScript value. - - When an object value is found, if the object contains a toJSON - method, its toJSON method will be called and the result will be - stringified. A toJSON method does not serialize: it returns the - value represented by the name/value pair that should be serialized, - or undefined if nothing should be serialized. The toJSON method - will be passed the key associated with the value, and this will be - bound to the value - - For example, this would serialize Dates as ISO strings. - - Date.prototype.toJSON = function (key) { - function f(n) { - // Format integers to have at least two digits. - return n < 10 ? '0' + n : n; - } - - return this.getUTCFullYear() + '-' + - f(this.getUTCMonth() + 1) + '-' + - f(this.getUTCDate()) + 'T' + - f(this.getUTCHours()) + ':' + - f(this.getUTCMinutes()) + ':' + - f(this.getUTCSeconds()) + 'Z'; - }; - - You can provide an optional replacer method. It will be passed the - key and value of each member, with this bound to the containing - object. The value that is returned from your method will be - serialized. If your method returns undefined, then the member will - be excluded from the serialization. - - If the replacer parameter is an array of strings, then it will be - used to select the members to be serialized. It filters the results - such that only members with keys listed in the replacer array are - stringified. - - Values that do not have JSON representations, such as undefined or - functions, will not be serialized. Such values in objects will be - dropped; in arrays they will be replaced with null. You can use - a replacer function to replace those with JSON values. - JSON.stringify(undefined) returns undefined. - - The optional space parameter produces a stringification of the - value that is filled with line breaks and indentation to make it - easier to read. - - If the space parameter is a non-empty string, then that string will - be used for indentation. If the space parameter is a number, then - the indentation will be that many spaces. - - Example: - - text = JSON.stringify(['e', {pluribus: 'unum'}]); - // text is '["e",{"pluribus":"unum"}]' - - - text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t'); - // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]' - - text = JSON.stringify([new Date()], function (key, value) { - return this[key] instanceof Date ? - 'Date(' + this[key] + ')' : value; - }); - // text is '["Date(---current time---)"]' - - - JSON.parse(text, reviver) - This method parses a JSON text to produce an object or array. - It can throw a SyntaxError exception. - - The optional reviver parameter is a function that can filter and - transform the results. It receives each of the keys and values, - and its return value is used instead of the original value. - If it returns what it received, then the structure is not modified. - If it returns undefined then the member is deleted. - - Example: - - // Parse the text. Values that look like ISO date strings will - // be converted to Date objects. - - myData = JSON.parse(text, function (key, value) { - var a; - if (typeof value === 'string') { - a = -/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value); - if (a) { - return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], - +a[5], +a[6])); - } - } - return value; - }); - - myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) { - var d; - if (typeof value === 'string' && - value.slice(0, 5) === 'Date(' && - value.slice(-1) === ')') { - d = new Date(value.slice(5, -1)); - if (d) { - return d; - } - } - return value; - }); - - - This is a reference implementation. You are free to copy, modify, or - redistribute. -*/ - -/*jslint evil: true, strict: false */ - -/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply, - call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours, - getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join, - lastIndex, length, parse, prototype, push, replace, slice, stringify, - test, toJSON, toString, valueOf -*/ - - -// Create a JSON object only if one does not already exist. We create the -// methods in a closure to avoid creating global variables. - -if (!this.JSON) { - this.JSON = {}; -} - -(function () { - - function f(n) { - // Format integers to have at least two digits. - return n < 10 ? '0' + n : n; - } - - if (typeof Date.prototype.toJSON !== 'function') { - - Date.prototype.toJSON = function (key) { - - return isFinite(this.valueOf()) ? - this.getUTCFullYear() + '-' + - f(this.getUTCMonth() + 1) + '-' + - f(this.getUTCDate()) + 'T' + - f(this.getUTCHours()) + ':' + - f(this.getUTCMinutes()) + ':' + - f(this.getUTCSeconds()) + 'Z' : null; - }; - - String.prototype.toJSON = - Number.prototype.toJSON = - Boolean.prototype.toJSON = function (key) { - return this.valueOf(); - }; - } - - var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, - escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, - gap, - indent, - meta = { // table of character substitutions - '\b': '\\b', - '\t': '\\t', - '\n': '\\n', - '\f': '\\f', - '\r': '\\r', - '"' : '\\"', - '\\': '\\\\' - }, - rep; - - - function quote(string) { - -// If the string contains no control characters, no quote characters, and no -// backslash characters, then we can safely slap some quotes around it. -// Otherwise we must also replace the offending characters with safe escape -// sequences. - - escapable.lastIndex = 0; - return escapable.test(string) ? - '"' + string.replace(escapable, function (a) { - var c = meta[a]; - return typeof c === 'string' ? c : - '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); - }) + '"' : - '"' + string + '"'; - } - - - function str(key, holder) { - -// Produce a string from holder[key]. - - var i, // The loop counter. - k, // The member key. - v, // The member value. - length, - mind = gap, - partial, - value = holder[key]; - -// If the value has a toJSON method, call it to obtain a replacement value. - - if (value && typeof value === 'object' && - typeof value.toJSON === 'function') { - value = value.toJSON(key); - } - -// If we were called with a replacer function, then call the replacer to -// obtain a replacement value. - - if (typeof rep === 'function') { - value = rep.call(holder, key, value); - } - -// What happens next depends on the value's type. - - switch (typeof value) { - case 'string': - return quote(value); - - case 'number': - -// JSON numbers must be finite. Encode non-finite numbers as null. - - return isFinite(value) ? String(value) : 'null'; - - case 'boolean': - case 'null': - -// If the value is a boolean or null, convert it to a string. Note: -// typeof null does not produce 'null'. The case is included here in -// the remote chance that this gets fixed someday. - - return String(value); - -// If the type is 'object', we might be dealing with an object or an array or -// null. - - case 'object': - -// Due to a specification blunder in ECMAScript, typeof null is 'object', -// so watch out for that case. - - if (!value) { - return 'null'; - } - -// Make an array to hold the partial results of stringifying this object value. - - gap += indent; - partial = []; - -// Is the value an array? - - if (Object.prototype.toString.apply(value) === '[object Array]') { - -// The value is an array. Stringify every element. Use null as a placeholder -// for non-JSON values. - - length = value.length; - for (i = 0; i < length; i += 1) { - partial[i] = str(i, value) || 'null'; - } - -// Join all of the elements together, separated with commas, and wrap them in -// brackets. - - v = partial.length === 0 ? '[]' : - gap ? '[\n' + gap + - partial.join(',\n' + gap) + '\n' + - mind + ']' : - '[' + partial.join(',') + ']'; - gap = mind; - return v; - } - -// If the replacer is an array, use it to select the members to be stringified. - - if (rep && typeof rep === 'object') { - length = rep.length; - for (i = 0; i < length; i += 1) { - k = rep[i]; - if (typeof k === 'string') { - v = str(k, value); - if (v) { - partial.push(quote(k) + (gap ? ': ' : ':') + v); - } - } - } - } else { - -// Otherwise, iterate through all of the keys in the object. - - for (k in value) { - if (Object.hasOwnProperty.call(value, k)) { - v = str(k, value); - if (v) { - partial.push(quote(k) + (gap ? ': ' : ':') + v); - } - } - } - } - -// Join all of the member texts together, separated with commas, -// and wrap them in braces. - - v = partial.length === 0 ? '{}' : - gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + - mind + '}' : '{' + partial.join(',') + '}'; - gap = mind; - return v; - } - } - -// If the JSON object does not yet have a stringify method, give it one. - - if (typeof JSON.stringify !== 'function') { - JSON.stringify = function (value, replacer, space) { - -// The stringify method takes a value and an optional replacer, and an optional -// space parameter, and returns a JSON text. The replacer can be a function -// that can replace values, or an array of strings that will select the keys. -// A default replacer method can be provided. Use of the space parameter can -// produce text that is more easily readable. - - var i; - gap = ''; - indent = ''; - -// If the space parameter is a number, make an indent string containing that -// many spaces. - - if (typeof space === 'number') { - for (i = 0; i < space; i += 1) { - indent += ' '; - } - -// If the space parameter is a string, it will be used as the indent string. - - } else if (typeof space === 'string') { - indent = space; - } - -// If there is a replacer, it must be a function or an array. -// Otherwise, throw an error. - - rep = replacer; - if (replacer && typeof replacer !== 'function' && - (typeof replacer !== 'object' || - typeof replacer.length !== 'number')) { - throw new Error('JSON.stringify'); - } - -// Make a fake root object containing our value under the key of ''. -// Return the result of stringifying the value. - - return str('', {'': value}); - }; - } - - -// If the JSON object does not yet have a parse method, give it one. - - if (typeof JSON.parse !== 'function') { - JSON.parse = function (text, reviver) { - -// The parse method takes a text and an optional reviver function, and returns -// a JavaScript value if the text is a valid JSON text. - - var j; - - function walk(holder, key) { - -// The walk method is used to recursively walk the resulting structure so -// that modifications can be made. - - var k, v, value = holder[key]; - if (value && typeof value === 'object') { - for (k in value) { - if (Object.hasOwnProperty.call(value, k)) { - v = walk(value, k); - if (v !== undefined) { - value[k] = v; - } else { - delete value[k]; - } - } - } - } - return reviver.call(holder, key, value); - } - - -// Parsing happens in four stages. In the first stage, we replace certain -// Unicode characters with escape sequences. JavaScript handles many characters -// incorrectly, either silently deleting them, or treating them as line endings. - - text = String(text); - cx.lastIndex = 0; - if (cx.test(text)) { - text = text.replace(cx, function (a) { - return '\\u' + - ('0000' + a.charCodeAt(0).toString(16)).slice(-4); - }); - } - -// In the second stage, we run the text against regular expressions that look -// for non-JSON patterns. We are especially concerned with '()' and 'new' -// because they can cause invocation, and '=' because it can cause mutation. -// But just to be safe, we want to reject all unexpected forms. - -// We split the second stage into 4 regexp operations in order to work around -// crippling inefficiencies in IE's and Safari's regexp engines. First we -// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we -// replace all simple value tokens with ']' characters. Third, we delete all -// open brackets that follow a colon or comma or that begin the text. Finally, -// we look to see that the remaining characters are only whitespace or ']' or -// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. - - if (/^[\],:{}\s]*$/. -test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@'). -replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']'). -replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { - -// In the third stage we use the eval function to compile the text into a -// JavaScript structure. The '{' operator is subject to a syntactic ambiguity -// in JavaScript: it can begin a block or an object literal. We wrap the text -// in parens to eliminate the ambiguity. - - j = eval('(' + text + ')'); - -// In the optional fourth stage, we recursively walk the new structure, passing -// each name/value pair to a reviver function for possible transformation. - - return typeof reviver === 'function' ? - walk({'': j}, '') : j; - } - -// If the text is not JSON parseable, then a SyntaxError is thrown. - - throw new SyntaxError('JSON.parse'); - }; - } -}()); diff --git a/DroidGap/lawnchair-adapter-test/www/lib/lawnchair.js b/DroidGap/lawnchair-adapter-test/www/lib/lawnchair.js deleted file mode 100644 index 3f67835..0000000 --- a/DroidGap/lawnchair-adapter-test/www/lib/lawnchair.js +++ /dev/null @@ -1,151 +0,0 @@ -/** - * Lawnchair! - * --- - * clientside json store - * - */ -var Lawnchair = function (options, callback) { - // ensure Lawnchair was called as a constructor - if (!(this instanceof Lawnchair)) return new Lawnchair(options, callback); - - // lawnchair requires json - if (!JSON) throw 'JSON unavailable! Include http://www.json.org/json2.js to fix.' - // options are optional; callback is not - if (arguments.length <= 2 && arguments.length > 0) { - callback = (typeof arguments[0] === 'function') ? arguments[0] : arguments[1]; - options = (typeof arguments[0] === 'function') ? {} : arguments[0]; - } else { - throw 'Incorrect # of ctor args!' - } - // TODO perhaps allow for pub/sub instead? - if (typeof callback !== 'function') throw 'No callback was provided'; - - // default configuration - this.record = options.record || 'record' // default for records - this.name = options.name || 'records' // default name for underlying store - - // mixin first valid adapter - var adapter - // if the adapter is passed in we try to load that only - if (options.adapter) { - for (var i = 0, l = Lawnchair.adapters.length; i < l; i++) { - if (Lawnchair.adapters[i].adapter === options.adapter) { - adapter = Lawnchair.adapters[i].valid() ? Lawnchair.adapters[i] : undefined; - break; - } - } - // otherwise find the first valid adapter for this env - } - else { - for (var i = 0, l = Lawnchair.adapters.length; i < l; i++) { - adapter = Lawnchair.adapters[i].valid() ? Lawnchair.adapters[i] : undefined - if (adapter) break - } - } - - // we have failed - if (!adapter) throw 'No valid adapter.' - - // yay! mixin the adapter - for (var j in adapter) - this[j] = adapter[j] - - // call init for each mixed in plugin - for (var i = 0, l = Lawnchair.plugins.length; i < l; i++) - Lawnchair.plugins[i].call(this) - - // init the adapter - this.init(options, callback) -} - -Lawnchair.adapters = [] - -/** - * queues an adapter for mixin - * === - * - ensures an adapter conforms to a specific interface - * - */ -Lawnchair.adapter = function (id, obj) { - // add the adapter id to the adapter obj - // ugly here for a cleaner dsl for implementing adapters - obj['adapter'] = id - // methods required to implement a lawnchair adapter - var implementing = 'adapter valid init keys save batch get exists all remove nuke'.split(' ') - , indexOf = this.prototype.indexOf - // mix in the adapter - for (var i in obj) { - if (indexOf(implementing, i) === -1) throw 'Invalid adapter! Nonstandard method: ' + i - } - // if we made it this far the adapter interface is valid - // insert the new adapter as the preferred adapter - Lawnchair.adapters.splice(0,0,obj) -} - -Lawnchair.plugins = [] - -/** - * generic shallow extension for plugins - * === - * - if an init method is found it registers it to be called when the lawnchair is inited - * - yes we could use hasOwnProp but nobody here is an asshole - */ -Lawnchair.plugin = function (obj) { - for (var i in obj) - i === 'init' ? Lawnchair.plugins.push(obj[i]) : this.prototype[i] = obj[i] -} - -/** - * helpers - * - */ -Lawnchair.prototype = { - - isArray: Array.isArray || function(o) { return Object.prototype.toString.call(o) === '[object Array]' }, - - /** - * this code exists for ie8... for more background see: - * http://www.flickr.com/photos/westcoastlogic/5955365742/in/photostream - */ - indexOf: function(ary, item, i, l) { - if (ary.indexOf) return ary.indexOf(item) - for (i = 0, l = ary.length; i < l; i++) if (ary[i] === item) return i - return -1 - }, - - // awesome shorthand callbacks as strings. this is shameless theft from dojo. - lambda: function (callback) { - return this.fn(this.record, callback) - }, - - // first stab at named parameters for terse callbacks; dojo: first != best // ;D - fn: function (name, callback) { - return typeof callback == 'string' ? new Function(name, callback) : callback - }, - - // returns a unique identifier (by way of Backbone.localStorage.js) - // TODO investigate smaller UUIDs to cut on storage cost - uuid: function () { - var S4 = function () { - return (((1+Math.random())*0x10000)|0).toString(16).substring(1); - } - return (S4()+S4()+"-"+S4()+"-"+S4()+"-"+S4()+"-"+S4()+S4()+S4()); - }, - - // a classic iterator - each: function (callback) { - var cb = this.lambda(callback) - // iterate from chain - if (this.__results) { - for (var i = 0, l = this.__results.length; i < l; i++) cb.call(this, this.__results[i], i) - } - // otherwise iterate the entire collection - else { - this.all(function(r) { - for (var i = 0, l = r.length; i < l; i++) cb.call(this, r[i], i) - }) - } - return this - } -// -- -}; diff --git a/DroidGap/lawnchair-adapter-test/www/lib/qunit.css b/DroidGap/lawnchair-adapter-test/www/lib/qunit.css deleted file mode 100644 index b3c6db5..0000000 --- a/DroidGap/lawnchair-adapter-test/www/lib/qunit.css +++ /dev/null @@ -1,225 +0,0 @@ -/** - * QUnit - A JavaScript Unit Testing Framework - * - * http://docs.jquery.com/QUnit - * - * Copyright (c) 2011 John Resig, Jörn Zaefferer - * Dual licensed under the MIT (MIT-LICENSE.txt) - * or GPL (GPL-LICENSE.txt) licenses. - */ - -/** Font Family and Sizes */ - -#qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult { - font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif; -} - -#qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; } -#qunit-tests { font-size: smaller; } - - -/** Resets */ - -#qunit-tests, #qunit-tests ol, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult { - margin: 0; - padding: 0; -} - - -/** Header */ - -#qunit-header { - padding: 0.5em 0 0.5em 1em; - - color: #8699a4; - background-color: #0d3349; - - font-size: 1.5em; - line-height: 1em; - font-weight: normal; - - border-radius: 15px 15px 0 0; - -moz-border-radius: 15px 15px 0 0; - -webkit-border-top-right-radius: 15px; - -webkit-border-top-left-radius: 15px; -} - -#qunit-header a { - text-decoration: none; - color: #c2ccd1; -} - -#qunit-header a:hover, -#qunit-header a:focus { - color: #fff; -} - -#qunit-banner { - height: 5px; -} - -#qunit-testrunner-toolbar { - padding: 0.5em 0 0.5em 2em; - color: #5E740B; - background-color: #eee; -} - -#qunit-userAgent { - padding: 0.5em 0 0.5em 2.5em; - background-color: #2b81af; - color: #fff; - text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px; -} - - -/** Tests: Pass/Fail */ - -#qunit-tests { - list-style-position: inside; -} - -#qunit-tests li { - padding: 0.4em 0.5em 0.4em 2.5em; - border-bottom: 1px solid #fff; - list-style-position: inside; -} - -#qunit-tests.hidepass li.pass, #qunit-tests.hidepass li.running { - display: none; -} - -#qunit-tests li strong { - cursor: pointer; -} - -#qunit-tests li a { - padding: 0.5em; - color: #c2ccd1; - text-decoration: none; -} -#qunit-tests li a:hover, -#qunit-tests li a:focus { - color: #000; -} - -#qunit-tests ol { - margin-top: 0.5em; - padding: 0.5em; - - background-color: #fff; - - border-radius: 15px; - -moz-border-radius: 15px; - -webkit-border-radius: 15px; - - box-shadow: inset 0px 2px 13px #999; - -moz-box-shadow: inset 0px 2px 13px #999; - -webkit-box-shadow: inset 0px 2px 13px #999; -} - -#qunit-tests table { - border-collapse: collapse; - margin-top: .2em; -} - -#qunit-tests th { - text-align: right; - vertical-align: top; - padding: 0 .5em 0 0; -} - -#qunit-tests td { - vertical-align: top; -} - -#qunit-tests pre { - margin: 0; - white-space: pre-wrap; - word-wrap: break-word; -} - -#qunit-tests del { - background-color: #e0f2be; - color: #374e0c; - text-decoration: none; -} - -#qunit-tests ins { - background-color: #ffcaca; - color: #500; - text-decoration: none; -} - -/*** Test Counts */ - -#qunit-tests b.counts { color: black; } -#qunit-tests b.passed { color: #5E740B; } -#qunit-tests b.failed { color: #710909; } - -#qunit-tests li li { - margin: 0.5em; - padding: 0.4em 0.5em 0.4em 0.5em; - background-color: #fff; - border-bottom: none; - list-style-position: inside; -} - -/*** Passing Styles */ - -#qunit-tests li li.pass { - color: #5E740B; - background-color: #fff; - border-left: 26px solid #C6E746; -} - -#qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; } -#qunit-tests .pass .test-name { color: #366097; } - -#qunit-tests .pass .test-actual, -#qunit-tests .pass .test-expected { color: #999999; } - -#qunit-banner.qunit-pass { background-color: #C6E746; } - -/*** Failing Styles */ - -#qunit-tests li li.fail { - color: #710909; - background-color: #fff; - border-left: 26px solid #EE5757; -} - -#qunit-tests > li:last-child { - border-radius: 0 0 15px 15px; - -moz-border-radius: 0 0 15px 15px; - -webkit-border-bottom-right-radius: 15px; - -webkit-border-bottom-left-radius: 15px; -} - -#qunit-tests .fail { color: #000000; background-color: #EE5757; } -#qunit-tests .fail .test-name, -#qunit-tests .fail .module-name { color: #000000; } - -#qunit-tests .fail .test-actual { color: #EE5757; } -#qunit-tests .fail .test-expected { color: green; } - -#qunit-banner.qunit-fail { background-color: #EE5757; } - - -/** Result */ - -#qunit-testresult { - padding: 0.5em 0.5em 0.5em 2.5em; - - color: #2b81af; - background-color: #D2E0E6; - - border-bottom: 1px solid white; -} - -/** Fixture */ - -#qunit-fixture { - position: absolute; - top: -10000px; - left: -10000px; -} diff --git a/DroidGap/lawnchair-adapter-test/www/lib/qunit.js b/DroidGap/lawnchair-adapter-test/www/lib/qunit.js deleted file mode 100644 index 3d640c7..0000000 --- a/DroidGap/lawnchair-adapter-test/www/lib/qunit.js +++ /dev/null @@ -1,1448 +0,0 @@ -/** - * QUnit - A JavaScript Unit Testing Framework - * - * http://docs.jquery.com/QUnit - * - * Copyright (c) 2011 John Resig, Jörn Zaefferer - * Dual licensed under the MIT (MIT-LICENSE.txt) - * or GPL (GPL-LICENSE.txt) licenses. - */ - -(function(window) { - -var defined = { - setTimeout: typeof window.setTimeout !== "undefined", - sessionStorage: (function() { - try { - return !!sessionStorage.getItem; - } catch(e){ - return false; - } - })() -}; - -var testId = 0; - -var Test = function(name, testName, expected, testEnvironmentArg, async, callback) { - this.name = name; - this.testName = testName; - this.expected = expected; - this.testEnvironmentArg = testEnvironmentArg; - this.async = async; - this.callback = callback; - this.assertions = []; -}; -Test.prototype = { - init: function() { - var tests = id("qunit-tests"); - if (tests) { - var b = document.createElement("strong"); - b.innerHTML = "Running " + this.name; - var li = document.createElement("li"); - li.appendChild( b ); - li.className = "running"; - li.id = this.id = "test-output" + testId++; - tests.appendChild( li ); - } - }, - setup: function() { - if (this.module != config.previousModule) { - if ( config.previousModule ) { - QUnit.moduleDone( { - name: config.previousModule, - failed: config.moduleStats.bad, - passed: config.moduleStats.all - config.moduleStats.bad, - total: config.moduleStats.all - } ); - } - config.previousModule = this.module; - config.moduleStats = { all: 0, bad: 0 }; - QUnit.moduleStart( { - name: this.module - } ); - } - - config.current = this; - this.testEnvironment = extend({ - setup: function() {}, - teardown: function() {} - }, this.moduleTestEnvironment); - if (this.testEnvironmentArg) { - extend(this.testEnvironment, this.testEnvironmentArg); - } - - QUnit.testStart( { - name: this.testName - } ); - - // allow utility functions to access the current test environment - // TODO why?? - QUnit.current_testEnvironment = this.testEnvironment; - - try { - if ( !config.pollution ) { - saveGlobal(); - } - - this.testEnvironment.setup.call(this.testEnvironment); - } catch(e) { - QUnit.ok( false, "Setup failed on " + this.testName + ": " + e.message ); - } - }, - run: function() { - if ( this.async ) { - QUnit.stop(); - } - - if ( config.notrycatch ) { - this.callback.call(this.testEnvironment); - return; - } - try { - this.callback.call(this.testEnvironment); - } catch(e) { - fail("Test " + this.testName + " died, exception and test follows", e, this.callback); - QUnit.ok( false, "Died on test #" + (this.assertions.length + 1) + ": " + e.message + " - " + QUnit.jsDump.parse(e) ); - // else next test will carry the responsibility - saveGlobal(); - - // Restart the tests if they're blocking - if ( config.blocking ) { - start(); - } - } - }, - teardown: function() { - try { - this.testEnvironment.teardown.call(this.testEnvironment); - checkPollution(); - } catch(e) { - QUnit.ok( false, "Teardown failed on " + this.testName + ": " + e.message ); - } - }, - finish: function() { - if ( this.expected && this.expected != this.assertions.length ) { - QUnit.ok( false, "Expected " + this.expected + " assertions, but " + this.assertions.length + " were run" ); - } - - var good = 0, bad = 0, - tests = id("qunit-tests"); - - config.stats.all += this.assertions.length; - config.moduleStats.all += this.assertions.length; - - if ( tests ) { - var ol = document.createElement("ol"); - - for ( var i = 0; i < this.assertions.length; i++ ) { - var assertion = this.assertions[i]; - - var li = document.createElement("li"); - li.className = assertion.result ? "pass" : "fail"; - li.innerHTML = assertion.message || (assertion.result ? "okay" : "failed"); - ol.appendChild( li ); - - if ( assertion.result ) { - good++; - } else { - bad++; - config.stats.bad++; - config.moduleStats.bad++; - } - } - - // store result when possible - if ( QUnit.config.reorder && defined.sessionStorage ) { - if (bad) { - sessionStorage.setItem("qunit-" + this.module + "-" + this.testName, bad); - } else { - sessionStorage.removeItem("qunit-" + this.module + "-" + this.testName); - } - } - - if (bad == 0) { - ol.style.display = "none"; - } - - var b = document.createElement("strong"); - b.innerHTML = this.name + " (" + bad + ", " + good + ", " + this.assertions.length + ")"; - - var a = document.createElement("a"); - a.innerHTML = "Rerun"; - a.href = QUnit.url({ filter: getText([b]).replace(/\([^)]+\)$/, "").replace(/(^\s*|\s*$)/g, "") }); - - addEvent(b, "click", function() { - var next = b.nextSibling.nextSibling, - display = next.style.display; - next.style.display = display === "none" ? "block" : "none"; - }); - - addEvent(b, "dblclick", function(e) { - var target = e && e.target ? e.target : window.event.srcElement; - if ( target.nodeName.toLowerCase() == "span" || target.nodeName.toLowerCase() == "b" ) { - target = target.parentNode; - } - if ( window.location && target.nodeName.toLowerCase() === "strong" ) { - window.location = QUnit.url({ filter: getText([target]).replace(/\([^)]+\)$/, "").replace(/(^\s*|\s*$)/g, "") }); - } - }); - - var li = id(this.id); - li.className = bad ? "fail" : "pass"; - li.removeChild( li.firstChild ); - li.appendChild( b ); - li.appendChild( a ); - li.appendChild( ol ); - - } else { - for ( var i = 0; i < this.assertions.length; i++ ) { - if ( !this.assertions[i].result ) { - bad++; - config.stats.bad++; - config.moduleStats.bad++; - } - } - } - - try { - QUnit.reset(); - } catch(e) { - fail("reset() failed, following Test " + this.testName + ", exception and reset fn follows", e, QUnit.reset); - } - - QUnit.testDone( { - name: this.testName, - failed: bad, - passed: this.assertions.length - bad, - total: this.assertions.length - } ); - }, - - queue: function() { - var test = this; - synchronize(function() { - test.init(); - }); - function run() { - // each of these can by async - synchronize(function() { - test.setup(); - }); - synchronize(function() { - test.run(); - }); - synchronize(function() { - test.teardown(); - }); - synchronize(function() { - test.finish(); - }); - } - // defer when previous test run passed, if storage is available - var bad = QUnit.config.reorder && defined.sessionStorage && +sessionStorage.getItem("qunit-" + this.module + "-" + this.testName); - if (bad) { - run(); - } else { - synchronize(run); - }; - } - -}; - -var QUnit = { - - // call on start of module test to prepend name to all tests - module: function(name, testEnvironment) { - config.currentModule = name; - config.currentModuleTestEnviroment = testEnvironment; - }, - - asyncTest: function(testName, expected, callback) { - if ( arguments.length === 2 ) { - callback = expected; - expected = 0; - } - - QUnit.test(testName, expected, callback, true); - }, - - test: function(testName, expected, callback, async) { - var name = '' + testName + '', testEnvironmentArg; - - if ( arguments.length === 2 ) { - callback = expected; - expected = null; - } - // is 2nd argument a testEnvironment? - if ( expected && typeof expected === 'object') { - testEnvironmentArg = expected; - expected = null; - } - - if ( config.currentModule ) { - name = '' + config.currentModule + ": " + name; - } - - if ( !validTest(config.currentModule + ": " + testName) ) { - return; - } - - var test = new Test(name, testName, expected, testEnvironmentArg, async, callback); - test.module = config.currentModule; - test.moduleTestEnvironment = config.currentModuleTestEnviroment; - test.queue(); - }, - - /** - * Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through. - */ - expect: function(asserts) { - config.current.expected = asserts; - }, - - /** - * Asserts true. - * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" ); - */ - ok: function(a, msg) { - a = !!a; - var details = { - result: a, - message: msg - }; - msg = escapeHtml(msg); - QUnit.log(details); - config.current.assertions.push({ - result: a, - message: msg - }); - }, - - /** - * Checks that the first two arguments are equal, with an optional message. - * Prints out both actual and expected values. - * - * Prefered to ok( actual == expected, message ) - * - * @example equal( format("Received {0} bytes.", 2), "Received 2 bytes." ); - * - * @param Object actual - * @param Object expected - * @param String message (optional) - */ - equal: function(actual, expected, message) { - QUnit.push(expected == actual, actual, expected, message); - }, - - notEqual: function(actual, expected, message) { - QUnit.push(expected != actual, actual, expected, message); - }, - - deepEqual: function(actual, expected, message) { - QUnit.push(QUnit.equiv(actual, expected), actual, expected, message); - }, - - notDeepEqual: function(actual, expected, message) { - QUnit.push(!QUnit.equiv(actual, expected), actual, expected, message); - }, - - strictEqual: function(actual, expected, message) { - QUnit.push(expected === actual, actual, expected, message); - }, - - notStrictEqual: function(actual, expected, message) { - QUnit.push(expected !== actual, actual, expected, message); - }, - - raises: function(block, expected, message) { - var actual, ok = false; - - if (typeof expected === 'string') { - message = expected; - expected = null; - } - - try { - block(); - } catch (e) { - actual = e; - } - - if (actual) { - // we don't want to validate thrown error - if (!expected) { - ok = true; - // expected is a regexp - } else if (QUnit.objectType(expected) === "regexp") { - ok = expected.test(actual); - // expected is a constructor - } else if (actual instanceof expected) { - ok = true; - // expected is a validation function which returns true is validation passed - } else if (expected.call({}, actual) === true) { - ok = true; - } - } - - QUnit.ok(ok, message); - }, - - start: function() { - config.semaphore--; - if (config.semaphore > 0) { - // don't start until equal number of stop-calls - return; - } - if (config.semaphore < 0) { - // ignore if start is called more often then stop - config.semaphore = 0; - } - // A slight delay, to avoid any current callbacks - if ( defined.setTimeout ) { - window.setTimeout(function() { - if ( config.timeout ) { - clearTimeout(config.timeout); - } - - config.blocking = false; - process(); - }, 13); - } else { - config.blocking = false; - process(); - } - }, - - stop: function(timeout) { - config.semaphore++; - config.blocking = true; - - if ( timeout && defined.setTimeout ) { - clearTimeout(config.timeout); - config.timeout = window.setTimeout(function() { - QUnit.ok( false, "Test timed out" ); - QUnit.start(); - }, timeout); - } - } -}; - -// Backwards compatibility, deprecated -QUnit.equals = QUnit.equal; -QUnit.same = QUnit.deepEqual; - -// Maintain internal state -var config = { - // The queue of tests to run - queue: [], - - // block until document ready - blocking: true, - - // by default, run previously failed tests first - // very useful in combination with "Hide passed tests" checked - reorder: true, - - noglobals: false, - notrycatch: false -}; - -// Load paramaters -(function() { - var location = window.location || { search: "", protocol: "file:" }, - params = location.search.slice( 1 ).split( "&" ), - length = params.length, - urlParams = {}, - current; - - if ( params[ 0 ] ) { - for ( var i = 0; i < length; i++ ) { - current = params[ i ].split( "=" ); - current[ 0 ] = decodeURIComponent( current[ 0 ] ); - // allow just a key to turn on a flag, e.g., test.html?noglobals - current[ 1 ] = current[ 1 ] ? decodeURIComponent( current[ 1 ] ) : true; - urlParams[ current[ 0 ] ] = current[ 1 ]; - if ( current[ 0 ] in config ) { - config[ current[ 0 ] ] = current[ 1 ]; - } - } - } - - QUnit.urlParams = urlParams; - config.filter = urlParams.filter; - - // Figure out if we're running the tests from a server or not - QUnit.isLocal = !!(location.protocol === 'file:'); -})(); - -// Expose the API as global variables, unless an 'exports' -// object exists, in that case we assume we're in CommonJS -if ( typeof exports === "undefined" || typeof require === "undefined" ) { - extend(window, QUnit); - window.QUnit = QUnit; -} else { - extend(exports, QUnit); - exports.QUnit = QUnit; -} - -// define these after exposing globals to keep them in these QUnit namespace only -extend(QUnit, { - config: config, - - // Initialize the configuration options - init: function() { - extend(config, { - stats: { all: 0, bad: 0 }, - moduleStats: { all: 0, bad: 0 }, - started: +new Date, - updateRate: 1000, - blocking: false, - autostart: true, - autorun: false, - filter: "", - queue: [], - semaphore: 0 - }); - - var tests = id( "qunit-tests" ), - banner = id( "qunit-banner" ), - result = id( "qunit-testresult" ); - - if ( tests ) { - tests.innerHTML = ""; - } - - if ( banner ) { - banner.className = ""; - } - - if ( result ) { - result.parentNode.removeChild( result ); - } - - if ( tests ) { - result = document.createElement( "p" ); - result.id = "qunit-testresult"; - result.className = "result"; - tests.parentNode.insertBefore( result, tests ); - result.innerHTML = 'Running...
       '; - } - }, - - /** - * Resets the test setup. Useful for tests that modify the DOM. - * - * If jQuery is available, uses jQuery's html(), otherwise just innerHTML. - */ - reset: function() { - if ( window.jQuery ) { - jQuery( "#qunit-fixture" ).html( config.fixture ); - } else { - var main = id( 'qunit-fixture' ); - if ( main ) { - main.innerHTML = config.fixture; - } - } - }, - - /** - * Trigger an event on an element. - * - * @example triggerEvent( document.body, "click" ); - * - * @param DOMElement elem - * @param String type - */ - triggerEvent: function( elem, type, event ) { - if ( document.createEvent ) { - event = document.createEvent("MouseEvents"); - event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView, - 0, 0, 0, 0, 0, false, false, false, false, 0, null); - elem.dispatchEvent( event ); - - } else if ( elem.fireEvent ) { - elem.fireEvent("on"+type); - } - }, - - // Safe object type checking - is: function( type, obj ) { - return QUnit.objectType( obj ) == type; - }, - - objectType: function( obj ) { - if (typeof obj === "undefined") { - return "undefined"; - - // consider: typeof null === object - } - if (obj === null) { - return "null"; - } - - var type = Object.prototype.toString.call( obj ) - .match(/^\[object\s(.*)\]$/)[1] || ''; - - switch (type) { - case 'Number': - if (isNaN(obj)) { - return "nan"; - } else { - return "number"; - } - case 'String': - case 'Boolean': - case 'Array': - case 'Date': - case 'RegExp': - case 'Function': - return type.toLowerCase(); - } - if (typeof obj === "object") { - return "object"; - } - return undefined; - }, - - push: function(result, actual, expected, message) { - var details = { - result: result, - message: message, - actual: actual, - expected: expected - }; - - message = escapeHtml(message) || (result ? "okay" : "failed"); - message = '' + message + ""; - expected = escapeHtml(QUnit.jsDump.parse(expected)); - actual = escapeHtml(QUnit.jsDump.parse(actual)); - var output = message + ''; - if (actual != expected) { - output += ''; - output += ''; - } - if (!result) { - var source = sourceFromStacktrace(); - if (source) { - details.source = source; - output += ''; - } - } - output += "
      Expected:
      ' + expected + '
      Result:
      ' + actual + '
      Diff:
      ' + QUnit.diff(expected, actual) +'
      Source:
      ' + source +'
      "; - - QUnit.log(details); - - config.current.assertions.push({ - result: !!result, - message: output - }); - }, - - url: function( params ) { - params = extend( extend( {}, QUnit.urlParams ), params ); - var querystring = "?", - key; - for ( key in params ) { - querystring += encodeURIComponent( key ) + "=" + - encodeURIComponent( params[ key ] ) + "&"; - } - return window.location.pathname + querystring.slice( 0, -1 ); - }, - - // Logging callbacks; all receive a single argument with the listed properties - // run test/logs.html for any related changes - begin: function() {}, - // done: { failed, passed, total, runtime } - done: function() {}, - // log: { result, actual, expected, message } - log: function() {}, - // testStart: { name } - testStart: function() {}, - // testDone: { name, failed, passed, total } - testDone: function() {}, - // moduleStart: { name } - moduleStart: function() {}, - // moduleDone: { name, failed, passed, total } - moduleDone: function() {} -}); - -if ( typeof document === "undefined" || document.readyState === "complete" ) { - config.autorun = true; -} - -addEvent(window, "load", function() { - QUnit.begin({}); - - // Initialize the config, saving the execution queue - var oldconfig = extend({}, config); - QUnit.init(); - extend(config, oldconfig); - - config.blocking = false; - - var userAgent = id("qunit-userAgent"); - if ( userAgent ) { - userAgent.innerHTML = navigator.userAgent; - } - var banner = id("qunit-header"); - if ( banner ) { - banner.innerHTML = ' ' + banner.innerHTML + ' ' + - '' + - ''; - addEvent( banner, "change", function( event ) { - var params = {}; - params[ event.target.name ] = event.target.checked ? true : undefined; - window.location = QUnit.url( params ); - }); - } - - var toolbar = id("qunit-testrunner-toolbar"); - if ( toolbar ) { - var filter = document.createElement("input"); - filter.type = "checkbox"; - filter.id = "qunit-filter-pass"; - addEvent( filter, "click", function() { - var ol = document.getElementById("qunit-tests"); - if ( filter.checked ) { - ol.className = ol.className + " hidepass"; - } else { - var tmp = " " + ol.className.replace( /[\n\t\r]/g, " " ) + " "; - ol.className = tmp.replace(/ hidepass /, " "); - } - if ( defined.sessionStorage ) { - if (filter.checked) { - sessionStorage.setItem("qunit-filter-passed-tests", "true"); - } else { - sessionStorage.removeItem("qunit-filter-passed-tests"); - } - } - }); - if ( defined.sessionStorage && sessionStorage.getItem("qunit-filter-passed-tests") ) { - filter.checked = true; - var ol = document.getElementById("qunit-tests"); - ol.className = ol.className + " hidepass"; - } - toolbar.appendChild( filter ); - - var label = document.createElement("label"); - label.setAttribute("for", "qunit-filter-pass"); - label.innerHTML = "Hide passed tests"; - toolbar.appendChild( label ); - } - - var main = id('qunit-fixture'); - if ( main ) { - config.fixture = main.innerHTML; - } - - if (config.autostart) { - QUnit.start(); - } -}); - -function done() { - config.autorun = true; - - // Log the last module results - if ( config.currentModule ) { - QUnit.moduleDone( { - name: config.currentModule, - failed: config.moduleStats.bad, - passed: config.moduleStats.all - config.moduleStats.bad, - total: config.moduleStats.all - } ); - } - - var banner = id("qunit-banner"), - tests = id("qunit-tests"), - runtime = +new Date - config.started, - passed = config.stats.all - config.stats.bad, - html = [ - 'Tests completed in ', - runtime, - ' milliseconds.
      ', - '', - passed, - ' tests of ', - config.stats.all, - ' passed, ', - config.stats.bad, - ' failed.' - ].join(''); - - if ( banner ) { - banner.className = (config.stats.bad ? "qunit-fail" : "qunit-pass"); - } - - if ( tests ) { - id( "qunit-testresult" ).innerHTML = html; - } - - if ( typeof document !== "undefined" && document.title ) { - // show ✖ for good, ✔ for bad suite result in title - // use escape sequences in case file gets loaded with non-utf-8-charset - document.title = (config.stats.bad ? "\u2716" : "\u2714") + " " + document.title; - } - - QUnit.done( { - failed: config.stats.bad, - passed: passed, - total: config.stats.all, - runtime: runtime - } ); -} - -function validTest( name ) { - var filter = config.filter, - run = false; - - if ( !filter ) { - return true; - } - - var not = filter.charAt( 0 ) === "!"; - if ( not ) { - filter = filter.slice( 1 ); - } - - if ( name.indexOf( filter ) !== -1 ) { - return !not; - } - - if ( not ) { - run = true; - } - - return run; -} - -// so far supports only Firefox, Chrome and Opera (buggy) -// could be extended in the future to use something like https://github.com/csnover/TraceKit -function sourceFromStacktrace() { - try { - throw new Error(); - } catch ( e ) { - if (e.stacktrace) { - // Opera - return e.stacktrace.split("\n")[6]; - } else if (e.stack) { - // Firefox, Chrome - return e.stack.split("\n")[4]; - } - } -} - -function escapeHtml(s) { - if (!s) { - return ""; - } - s = s + ""; - return s.replace(/[\&"<>\\]/g, function(s) { - switch(s) { - case "&": return "&"; - case "\\": return "\\\\"; - case '"': return '\"'; - case "<": return "<"; - case ">": return ">"; - default: return s; - } - }); -} - -function synchronize( callback ) { - config.queue.push( callback ); - - if ( config.autorun && !config.blocking ) { - process(); - } -} - -function process() { - var start = (new Date()).getTime(); - - while ( config.queue.length && !config.blocking ) { - if ( config.updateRate <= 0 || (((new Date()).getTime() - start) < config.updateRate) ) { - config.queue.shift()(); - } else { - window.setTimeout( process, 13 ); - break; - } - } - if (!config.blocking && !config.queue.length) { - done(); - } -} - -function saveGlobal() { - config.pollution = []; - - if ( config.noglobals ) { - for ( var key in window ) { - config.pollution.push( key ); - } - } -} - -function checkPollution( name ) { - var old = config.pollution; - saveGlobal(); - - var newGlobals = diff( config.pollution, old ); - if ( newGlobals.length > 0 ) { - ok( false, "Introduced global variable(s): " + newGlobals.join(", ") ); - } - - var deletedGlobals = diff( old, config.pollution ); - if ( deletedGlobals.length > 0 ) { - ok( false, "Deleted global variable(s): " + deletedGlobals.join(", ") ); - } -} - -// returns a new Array with the elements that are in a but not in b -function diff( a, b ) { - var result = a.slice(); - for ( var i = 0; i < result.length; i++ ) { - for ( var j = 0; j < b.length; j++ ) { - if ( result[i] === b[j] ) { - result.splice(i, 1); - i--; - break; - } - } - } - return result; -} - -function fail(message, exception, callback) { - if ( typeof console !== "undefined" && console.error && console.warn ) { - console.error(message); - console.error(exception); - console.warn(callback.toString()); - - } else if ( window.opera && opera.postError ) { - opera.postError(message, exception, callback.toString); - } -} - -function extend(a, b) { - for ( var prop in b ) { - if ( b[prop] === undefined ) { - delete a[prop]; - } else { - a[prop] = b[prop]; - } - } - - return a; -} - -function addEvent(elem, type, fn) { - if ( elem.addEventListener ) { - elem.addEventListener( type, fn, false ); - } else if ( elem.attachEvent ) { - elem.attachEvent( "on" + type, fn ); - } else { - fn(); - } -} - -function id(name) { - return !!(typeof document !== "undefined" && document && document.getElementById) && - document.getElementById( name ); -} - -// Test for equality any JavaScript type. -// Discussions and reference: http://philrathe.com/articles/equiv -// Test suites: http://philrathe.com/tests/equiv -// Author: Philippe Rathé -QUnit.equiv = function () { - - var innerEquiv; // the real equiv function - var callers = []; // stack to decide between skip/abort functions - var parents = []; // stack to avoiding loops from circular referencing - - // Call the o related callback with the given arguments. - function bindCallbacks(o, callbacks, args) { - var prop = QUnit.objectType(o); - if (prop) { - if (QUnit.objectType(callbacks[prop]) === "function") { - return callbacks[prop].apply(callbacks, args); - } else { - return callbacks[prop]; // or undefined - } - } - } - - var callbacks = function () { - - // for string, boolean, number and null - function useStrictEquality(b, a) { - if (b instanceof a.constructor || a instanceof b.constructor) { - // to catch short annotaion VS 'new' annotation of a declaration - // e.g. var i = 1; - // var j = new Number(1); - return a == b; - } else { - return a === b; - } - } - - return { - "string": useStrictEquality, - "boolean": useStrictEquality, - "number": useStrictEquality, - "null": useStrictEquality, - "undefined": useStrictEquality, - - "nan": function (b) { - return isNaN(b); - }, - - "date": function (b, a) { - return QUnit.objectType(b) === "date" && a.valueOf() === b.valueOf(); - }, - - "regexp": function (b, a) { - return QUnit.objectType(b) === "regexp" && - a.source === b.source && // the regex itself - a.global === b.global && // and its modifers (gmi) ... - a.ignoreCase === b.ignoreCase && - a.multiline === b.multiline; - }, - - // - skip when the property is a method of an instance (OOP) - // - abort otherwise, - // initial === would have catch identical references anyway - "function": function () { - var caller = callers[callers.length - 1]; - return caller !== Object && - typeof caller !== "undefined"; - }, - - "array": function (b, a) { - var i, j, loop; - var len; - - // b could be an object literal here - if ( ! (QUnit.objectType(b) === "array")) { - return false; - } - - len = a.length; - if (len !== b.length) { // safe and faster - return false; - } - - //track reference to avoid circular references - parents.push(a); - for (i = 0; i < len; i++) { - loop = false; - for(j=0;j= 0) { - type = "array"; - } else { - type = typeof obj; - } - return type; - }, - separator:function() { - return this.multiline ? this.HTML ? '
      ' : '\n' : this.HTML ? ' ' : ' '; - }, - indent:function( extra ) {// extra can be a number, shortcut for increasing-calling-decreasing - if ( !this.multiline ) - return ''; - var chr = this.indentChar; - if ( this.HTML ) - chr = chr.replace(/\t/g,' ').replace(/ /g,' '); - return Array( this._depth_ + (extra||0) ).join(chr); - }, - up:function( a ) { - this._depth_ += a || 1; - }, - down:function( a ) { - this._depth_ -= a || 1; - }, - setParser:function( name, parser ) { - this.parsers[name] = parser; - }, - // The next 3 are exposed so you can use them - quote:quote, - literal:literal, - join:join, - // - _depth_: 1, - // This is the list of parsers, to modify them, use jsDump.setParser - parsers:{ - window: '[Window]', - document: '[Document]', - error:'[ERROR]', //when no parser is found, shouldn't happen - unknown: '[Unknown]', - 'null':'null', - 'undefined':'undefined', - 'function':function( fn ) { - var ret = 'function', - name = 'name' in fn ? fn.name : (reName.exec(fn)||[])[1];//functions never have name in IE - if ( name ) - ret += ' ' + name; - ret += '('; - - ret = [ ret, QUnit.jsDump.parse( fn, 'functionArgs' ), '){'].join(''); - return join( ret, QUnit.jsDump.parse(fn,'functionCode'), '}' ); - }, - array: array, - nodelist: array, - arguments: array, - object:function( map ) { - var ret = [ ]; - QUnit.jsDump.up(); - for ( var key in map ) - ret.push( QUnit.jsDump.parse(key,'key') + ': ' + QUnit.jsDump.parse(map[key]) ); - QUnit.jsDump.down(); - return join( '{', ret, '}' ); - }, - node:function( node ) { - var open = QUnit.jsDump.HTML ? '<' : '<', - close = QUnit.jsDump.HTML ? '>' : '>'; - - var tag = node.nodeName.toLowerCase(), - ret = open + tag; - - for ( var a in QUnit.jsDump.DOMAttrs ) { - var val = node[QUnit.jsDump.DOMAttrs[a]]; - if ( val ) - ret += ' ' + a + '=' + QUnit.jsDump.parse( val, 'attribute' ); - } - return ret + close + open + '/' + tag + close; - }, - functionArgs:function( fn ) {//function calls it internally, it's the arguments part of the function - var l = fn.length; - if ( !l ) return ''; - - var args = Array(l); - while ( l-- ) - args[l] = String.fromCharCode(97+l);//97 is 'a' - return ' ' + args.join(', ') + ' '; - }, - key:quote, //object calls it internally, the key part of an item in a map - functionCode:'[code]', //function calls it internally, it's the content of the function - attribute:quote, //node calls it internally, it's an html attribute value - string:quote, - date:quote, - regexp:literal, //regex - number:literal, - 'boolean':literal - }, - DOMAttrs:{//attributes to dump from nodes, name=>realName - id:'id', - name:'name', - 'class':'className' - }, - HTML:false,//if true, entities are escaped ( <, >, \t, space and \n ) - indentChar:' ',//indentation unit - multiline:true //if true, items in a collection, are separated by a \n, else just a space. - }; - - return jsDump; -})(); - -// from Sizzle.js -function getText( elems ) { - var ret = "", elem; - - for ( var i = 0; elems[i]; i++ ) { - elem = elems[i]; - - // Get the text from text nodes and CDATA nodes - if ( elem.nodeType === 3 || elem.nodeType === 4 ) { - ret += elem.nodeValue; - - // Traverse everything else, except comment nodes - } else if ( elem.nodeType !== 8 ) { - ret += getText( elem.childNodes ); - } - } - - return ret; -}; - -/* - * Javascript Diff Algorithm - * By John Resig (http://ejohn.org/) - * Modified by Chu Alan "sprite" - * - * Released under the MIT license. - * - * More Info: - * http://ejohn.org/projects/javascript-diff-algorithm/ - * - * Usage: QUnit.diff(expected, actual) - * - * QUnit.diff("the quick brown fox jumped over", "the quick fox jumps over") == "the quick brown fox jumped jumps over" - */ -QUnit.diff = (function() { - function diff(o, n){ - var ns = new Object(); - var os = new Object(); - - for (var i = 0; i < n.length; i++) { - if (ns[n[i]] == null) - ns[n[i]] = { - rows: new Array(), - o: null - }; - ns[n[i]].rows.push(i); - } - - for (var i = 0; i < o.length; i++) { - if (os[o[i]] == null) - os[o[i]] = { - rows: new Array(), - n: null - }; - os[o[i]].rows.push(i); - } - - for (var i in ns) { - if (ns[i].rows.length == 1 && typeof(os[i]) != "undefined" && os[i].rows.length == 1) { - n[ns[i].rows[0]] = { - text: n[ns[i].rows[0]], - row: os[i].rows[0] - }; - o[os[i].rows[0]] = { - text: o[os[i].rows[0]], - row: ns[i].rows[0] - }; - } - } - - for (var i = 0; i < n.length - 1; i++) { - if (n[i].text != null && n[i + 1].text == null && n[i].row + 1 < o.length && o[n[i].row + 1].text == null && - n[i + 1] == o[n[i].row + 1]) { - n[i + 1] = { - text: n[i + 1], - row: n[i].row + 1 - }; - o[n[i].row + 1] = { - text: o[n[i].row + 1], - row: i + 1 - }; - } - } - - for (var i = n.length - 1; i > 0; i--) { - if (n[i].text != null && n[i - 1].text == null && n[i].row > 0 && o[n[i].row - 1].text == null && - n[i - 1] == o[n[i].row - 1]) { - n[i - 1] = { - text: n[i - 1], - row: n[i].row - 1 - }; - o[n[i].row - 1] = { - text: o[n[i].row - 1], - row: i - 1 - }; - } - } - - return { - o: o, - n: n - }; - } - - return function(o, n){ - o = o.replace(/\s+$/, ''); - n = n.replace(/\s+$/, ''); - var out = diff(o == "" ? [] : o.split(/\s+/), n == "" ? [] : n.split(/\s+/)); - - var str = ""; - - var oSpace = o.match(/\s+/g); - if (oSpace == null) { - oSpace = [" "]; - } - else { - oSpace.push(" "); - } - var nSpace = n.match(/\s+/g); - if (nSpace == null) { - nSpace = [" "]; - } - else { - nSpace.push(" "); - } - - if (out.n.length == 0) { - for (var i = 0; i < out.o.length; i++) { - str += '' + out.o[i] + oSpace[i] + ""; - } - } - else { - if (out.n[0].text == null) { - for (n = 0; n < out.o.length && out.o[n].text == null; n++) { - str += '' + out.o[n] + oSpace[n] + ""; - } - } - - for (var i = 0; i < out.n.length; i++) { - if (out.n[i].text == null) { - str += '' + out.n[i] + nSpace[i] + ""; - } - else { - var pre = ""; - - for (n = out.n[i].row + 1; n < out.o.length && out.o[n].text == null; n++) { - pre += '' + out.o[n] + oSpace[n] + ""; - } - str += " " + out.n[i].text + nSpace[i] + pre; - } - } - } - - return str; - }; -})(); - -})(this); \ No newline at end of file diff --git a/DroidGap/lawnchair-adapter-test/www/master.css b/DroidGap/lawnchair-adapter-test/www/master.css deleted file mode 100644 index d4401f5..0000000 --- a/DroidGap/lawnchair-adapter-test/www/master.css +++ /dev/null @@ -1,96 +0,0 @@ - body { - background:#222 none repeat scroll 0 0; - color:#666; - font-family:Helvetica; - font-size:72%; - line-height:1.5em; - margin:0; - border-top:1px solid #393939; - } - - #info{ - background:#ffa; - border: 1px solid #ffd324; - -webkit-border-radius: 5px; - border-radius: 5px; - clear:both; - margin:15px 6px 0; - width:295px; - padding:4px 0px 2px 10px; - } - - #info > h4{ - font-size:.95em; - margin:5px 0; - } - - #stage.theme{ - padding-top:3px; - } - - /* Definition List */ - #stage.theme > dl{ - padding-top:10px; - clear:both; - margin:0; - list-style-type:none; - padding-left:10px; - overflow:auto; - } - - #stage.theme > dl > dt{ - font-weight:bold; - float:left; - margin-left:5px; - } - - #stage.theme > dl > dd{ - width:45px; - float:left; - color:#a87; - font-weight:bold; - } - - /* Content Styling */ - #stage.theme > h1, #stage.theme > h2, #stage.theme > p{ - margin:1em 0 .5em 13px; - } - - #stage.theme > h1{ - color:#eee; - font-size:1.6em; - text-align:center; - margin:0; - margin-top:15px; - padding:0; - } - - #stage.theme > h2{ - clear:both; - margin:0; - padding:3px; - font-size:1em; - text-align:center; - } - - /* Stage Buttons */ - #stage.theme a.btn{ - border: 1px solid #555; - -webkit-border-radius: 5px; - border-radius: 5px; - text-align:center; - display:block; - float:left; - background:#444; - width:150px; - color:#9ab; - font-size:1.1em; - text-decoration:none; - padding:1.2em 0; - margin:3px 0px 3px 5px; - } - #stage.theme a.btn.large{ - width:308px; - padding:1.2em 0; - } - diff --git a/DroidGap/lawnchair-adapter-test/www/webkit-sqlite.js b/DroidGap/lawnchair-adapter-test/www/webkit-sqlite.js deleted file mode 100644 index 7fafb74..0000000 --- a/DroidGap/lawnchair-adapter-test/www/webkit-sqlite.js +++ /dev/null @@ -1,202 +0,0 @@ -Lawnchair.adapter('webkit-sqlite', (function () { - // private methods - var fail = function (e, i) { console.log('error in sqlite adaptor!', e, i) } - , now = function () { return new Date() } // FIXME need to use better date fn - // not entirely sure if this is needed... - if (!Function.prototype.bind) { - Function.prototype.bind = function( obj ) { - var slice = [].slice - , args = slice.call(arguments, 1) - , self = this - , nop = function () {} - , bound = function () { - return self.apply(this instanceof nop ? this : (obj || {}), args.concat(slice.call(arguments))) - } - nop.prototype = self.prototype - bound.prototype = new nop() - return bound - } - } - - // public methods - return { - - //valid: function() { return !!(window.openDatabase) }, - //valid: function() { return !!(window.my_openDatabase) }, - valid: function() { return !!(sqlitePlugin.openDatabase) }, - - init: function (options, callback) { - var that = this - , cb = that.fn(that.name, callback) - , create = "CREATE TABLE IF NOT EXISTS " + this.name + " (id NVARCHAR(32) UNIQUE PRIMARY KEY, value TEXT, timestamp REAL)" - , win = function(){ return cb.call(that, that); } - // open a connection and create the db if it doesn't exist - //this.db = openDatabase(this.name, '1.0.0', this.name, 65536) - //this.db = my_openDatabase(this.name, '1.0.0', this.name, 65536) - //this.db = window.my_openDatabase("Database", "1.0", "PhoneGap Demo", 200000); - //this.db = sqlitePlugin.openDatabase(this.name, '1.0.0', this.name, 65536) - this.db = sqlitePlugin.openDatabase("Database", "1.0", "PhoneGap Demo", 200000); - this.db.transaction(function (t) { - t.executeSql(create, [], win, fail) - }) - }, - - keys: function (callback) { - var cb = this.lambda(callback) - , that = this - , keys = "SELECT id FROM " + this.name + " ORDER BY timestamp DESC" - - this.db.transaction(function(t) { - var win = function (xxx, results) { - if (results.rows.length == 0 ) { - cb.call(that, []) - } else { - var r = []; - for (var i = 0, l = results.rows.length; i < l; i++) { - r.push(results.rows.item(i).id); - } - cb.call(that, r) - } - } - t.executeSql(keys, [], win, fail) - }) - return this - }, - // you think thats air you're breathing now? - save: function (obj, callback) { - var that = this - , id = obj.key || that.uuid() - , ins = "INSERT INTO " + this.name + " (value, timestamp, id) VALUES (?,?,?)" - , up = "UPDATE " + this.name + " SET value=?, timestamp=? WHERE id=?" - , win = function () { if (callback) { obj.key = id; that.lambda(callback).call(that, obj) }} - , val = [now(), id] - // existential - that.exists(obj.key, function(exists) { - // transactions are like condoms - that.db.transaction(function(t) { - // TODO move timestamp to a plugin - var insert = function (obj) { - val.unshift(JSON.stringify(obj)) - t.executeSql(ins, val, win, fail) - } - // TODO move timestamp to a plugin - var update = function (obj) { - delete(obj.key) - val.unshift(JSON.stringify(obj)) - t.executeSql(up, val, win, fail) - } - // pretty - exists ? update(obj) : insert(obj) - }) - }); - return this - }, - - // FIXME this should be a batch insert / just getting the test to pass... - batch: function (objs, cb) { - - var results = [] - , done = false - , that = this - - var updateProgress = function(obj) { - results.push(obj) - done = results.length === objs.length - } - - var checkProgress = setInterval(function() { - if (done) { - if (cb) that.lambda(cb).call(that, results) - clearInterval(checkProgress) - } - }, 200) - - for (var i = 0, l = objs.length; i < l; i++) - this.save(objs[i], updateProgress) - - return this - }, - - get: function (keyOrArray, cb) { - var that = this - , sql = '' - // batch selects support - if (this.isArray(keyOrArray)) { - sql = 'SELECT id, value FROM ' + this.name + " WHERE id IN ('" + keyOrArray.join("','") + "')" - } else { - sql = 'SELECT id, value FROM ' + this.name + " WHERE id = '" + keyOrArray + "'" - } - // FIXME - // will always loop the results but cleans it up if not a batch return at the end.. - // in other words, this could be faster - var win = function (xxx, results) { - var o = null - , r = [] - if (results.rows.length) { - for (var i = 0, l = results.rows.length; i < l; i++) { - o = JSON.parse(results.rows.item(i).value) - o.key = results.rows.item(i).id - r.push(o) - } - } - if (!that.isArray(keyOrArray)) r = r.length ? r[0] : null - if (cb) that.lambda(cb).call(that, r) - } - this.db.transaction(function(t){ t.executeSql(sql, [], win, fail) }) - return this - }, - - exists: function (key, cb) { - var is = "SELECT * FROM " + this.name + " WHERE id = ?" - , that = this - , win = function(xxx, results) { if (cb) that.fn('exists', cb).call(that, (results.rows.length > 0)) } - this.db.transaction(function(t){ t.executeSql(is, [key], win, fail) }) - return this - }, - - all: function (callback) { - var that = this - , all = "SELECT * FROM " + this.name - , r = [] - , cb = this.fn(this.name, callback) || undefined - , win = function (xxx, results) { - if (results.rows.length != 0) { - for (var i = 0, l = results.rows.length; i < l; i++) { - var obj = JSON.parse(results.rows.item(i).value) - obj.key = results.rows.item(i).id - r.push(obj) - } - } - if (cb) cb.call(that, r) - } - - this.db.transaction(function (t) { - t.executeSql(all, [], win, fail) - }) - return this - }, - - remove: function (keyOrObj, cb) { - var that = this - , key = typeof keyOrObj === 'string' ? keyOrObj : keyOrObj.key - , del = "DELETE FROM " + this.name + " WHERE id = ?" - , win = function () { if (cb) that.lambda(cb).call(that) } - - this.db.transaction( function (t) { - t.executeSql(del, [key], win, fail); - }); - - return this; - }, - - nuke: function (cb) { - var nuke = "DELETE FROM " + this.name - , that = this - , win = cb ? function() { that.lambda(cb).call(that) } : function(){} - this.db.transaction(function (t) { - t.executeSql(nuke, [], win, fail) - }) - return this - } -////// -}})()) diff --git a/README.md b/README.md index 7240b73..f62fc64 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,8 @@ Created by @Joenoon: Adapted to 1.5 by @coomsie -API changes and Android version by @chbrody +API changes by @chbrody +Android version by @marcucio and @chbrody DISCLAIMER: @@ -19,7 +20,12 @@ We are brand new to objective-c, so there could be problems with our code! Installing ========== -**NOTE:** There are now 2 trees: `Cordova-iOS` for Cordova 1.5(+) and `Legacy-PhoneGap-iPhone` for PhoneGap (tested 1.3 and earlier). I am planning to add an Android version in another tree, hopefully in the near future. +**NOTE:** There are now the following trees: + + - `Cordova-iOS` for Cordova 1.5(+) iOS + - `Legacy-PhoneGap-iPhone` for PhoneGap (tested 1.3 and earlier). + - `Android`: new version by @marcucio, with improvements for batch transaction processing, testing seems OK + - `DroidGap-old`: initial adaptation for Android, expected to go away pretty soon. Cordova 1.6 (RC) ---------------- @@ -108,12 +114,10 @@ Insert this in there: General Usage ============= -Android (DroidGap) ------------------- +Android +------- -I put the information here for the sake of completeness. I have tested the DroidGap SQLitePlugin version on a simulator on PhoneGap 1.1 *ONLY* and do not guarantee what will happen in any other situation. Basically, I copied and adapted the code from storage.js and Storage.java to make a plugin version. I got the versions that were there on October 2011, so it should be OK to use them under the MIT or Apache licenses. Hereby you can take SQLitePlugin.java (it is in the wrong place but it still worked), SQLitePlugin.js, and look at my index.html, register in plugins.xml, and give it your best shot! Fork it and take it over! - -**Update:** I have now tested the Android (DroidGap) SQLitePlugin on Cordova 1.5 on both a simulator and a test mobile, using the Lawnchair testsuite. In addition, I have also adapted a Lawnchair plugin to work for both the iOS and the Android. Here is the sample (in Javascript): +Sample in Javascript: // Wait for PhoneGap to load // @@ -144,6 +148,7 @@ I put the information here for the sake of completeness. I have tested the Droid }); }); +Tested OK using the Lawnchair test suite. Old DroidGap version is expected to go away very soon, yes I did repeat myself! Cordova iOS ----------- @@ -281,12 +286,10 @@ Legacy PhoneGap (old version) Lawnchair Adapter Usage ======================= -**NOTE:** For the Android version see DroidGap/lawnchair-adapter-test, which is using a Lawnchair adapter based on the original WebKit version. The plan is to make this one work for both iOS and Android versions. - Common adapter -------------- -Please look at the `Lawnchair-adapter` tree that contains a common adapter, working for both Android (DroidGap) and iOS, along with a test-www directory. +Please look at the `Lawnchair-adapter` tree that contains a common adapter, working for both Android and iOS, along with a test-www directory. Legacy: iOS/iPhone only @@ -318,6 +321,13 @@ Legacy Lawnchair test In the lawnchair-test subdirectory of Cordova-iOS or Legacy-PhoneGap-iPhone you can copy the contents of the www subdirectory into a Cordova/PhoneGap project and see the behavior of the Lawnchair test suite. +Extra notes +----------- + +@marcucio has also made some improvements for batching in the iOS version in https://github.com/marcucio/Cordova-Plugins but I do not want to take these until I know it is working OK with multi-level transaction API. + +Old baching notes, will go away: + ### Other notes from @Joenoon: I played with the idea of batching responses into larger sets of From 09018aafa0519f8afc73f1a02616a8239cb00da6 Mon Sep 17 00:00:00 2001 From: Chris Brody Date: Thu, 12 Apr 2012 14:07:02 +0200 Subject: [PATCH 40/44] Fix README.md to reflect new iOS version by @marcucio --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index f62fc64..fdbac4e 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,8 @@ Installing **NOTE:** There are now the following trees: - - `Cordova-iOS` for Cordova 1.5(+) iOS + - `iOS`: new version by @marcucio, with improvements for batch transaction processing, testing seems OK, **TBD** needs to be ported to Cordova 1.5/1.6 + - `Cordova-iOS` for Cordova 1.5/1.6 iOS - `Legacy-PhoneGap-iPhone` for PhoneGap (tested 1.3 and earlier). - `Android`: new version by @marcucio, with improvements for batch transaction processing, testing seems OK - `DroidGap-old`: initial adaptation for Android, expected to go away pretty soon. From fd3eaad9c24fa4b8d01794563a54c6e3a572f62e Mon Sep 17 00:00:00 2001 From: Chris Brody Date: Thu, 12 Apr 2012 14:17:59 +0200 Subject: [PATCH 41/44] Cleanup README.md for current state, moved sample and added missing brace --- README.md | 85 ++++++++++++++++++++++++++++++++----------------------- 1 file changed, 49 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index fdbac4e..5061b7d 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ -Cordova/PhoneGap Native SQLitePlugin -==================================== +Cordova/PhoneGap SQLitePlugin +============================= Native interface to sqlite in a Cordova/PhoneGap plugin, working to follow the HTML5 Web SQL API as close as possible. **NOTE** that the API is now different from https://github.com/davibe/Phonegap-SQLitePlugin and is still undergoing some changes. @@ -9,14 +9,53 @@ Created by @Joenoon: Adapted to 1.5 by @coomsie -API changes by @chbrody +Major improvements for batch processing by @marcucio + Android version by @marcucio and @chbrody +API changes by @chbrody DISCLAIMER: We are brand new to objective-c, so there could be problems with our code! +Usage +===== + +The idea is to emulate the HTML5 SQL API as closely as possible. The only major change is to use window.sqlitePlugin.openDatabase() (or sqlitePlugin.openDatabase()) instead of window.openDatabase(). If you see any other major change please report it, it is probably a bug. + +Sample in Javascript: + + // Wait for Cordova/PhoneGap to load + // + document.addEventListener("deviceready", onDeviceReady, false); + + // Cordova/PhoneGap is ready + // + function onDeviceReady() { + var db = window.sqlitePlugin.openDatabase("Database", "1.0", "PhoneGap Demo", 200000); + + db.transaction(function(tx) { + + tx.executeSql('DROP TABLE IF EXISTS test_table'); + tx.executeSql('CREATE TABLE IF NOT EXISTS test_table (id integer primary key, data text, data_num integer)'); + + + return tx.executeSql("INSERT INTO test_table (data, data_num) VALUES (?,?)", ["test", 100], function(tx, res) { + //console.log("insertId: " + res.insertId + " -- probably 1"); + //console.log("rowsAffected: " + res.rowsAffected + " -- should be 1"); + + tx.executeSql("select count(id) as cnt from test_table;", [], function(tx, res) { + console.log("rows.length: " + res.rows.length + " -- should be 1"); + return console.log("rows[0].cnt: " + res.rows.item(0).cnt + " -- should be 1"); + }); + + }, function(e) { + return console.log("ERROR: " + e.message); + }); + }); + } + Installing ========== @@ -118,41 +157,15 @@ General Usage Android ------- -Sample in Javascript: - - // Wait for PhoneGap to load - // - document.addEventListener("deviceready", onDeviceReady, false); - - // PhoneGap is ready - // - function onDeviceReady() { - var db = window.sqlitePlugin.openDatabase("Database", "1.0", "PhoneGap Demo", 200000); - - db.transaction(function(tx) { - - tx.executeSql('DROP TABLE IF EXISTS test_table'); - tx.executeSql('CREATE TABLE IF NOT EXISTS test_table (id integer primary key, data text, data_num integer)'); - - - return tx.executeSql("INSERT INTO test_table (data, data_num) VALUES (?,?)", ["test", 100], function(tx, res) { - //console.log("insertId: " + res.insertId + " -- probably 1"); - //console.log("rowsAffected: " + res.rowsAffected + " -- should be 1"); - - tx.executeSql("select count(id) as cnt from test_table;", [], function(tx, res) { - console.log("rows.length: " + res.rows.length + " -- should be 1"); - return console.log("rows[0].cnt: " + res.rows.item(0).cnt + " -- should be 1"); - }); - - }, function(e) { - return console.log("ERROR: " + e.message); - }); - }); - Tested OK using the Lawnchair test suite. Old DroidGap version is expected to go away very soon, yes I did repeat myself! -Cordova iOS ------------ +New iOS version +--------------- + +Tested OK using the sample above and the Lawnchair test suite. Needs to be ported for Cordova 1.5/1.6+. Old version will go away if there are no major issues. + +Cordova iOS (stable version) +---------------------------- **NOTE:** Please use sqlitePlugin.openDatabase() to open a database, the parameters are different from the old SQLitePlugin() constructor which is now gone. This should a closer resemblance to the HTML5/W3 SQL API. From be092c1d3faba0c7454828b82decd8f8ecec21dd Mon Sep 17 00:00:00 2001 From: Chris Brody Date: Thu, 12 Apr 2012 14:46:56 +0200 Subject: [PATCH 42/44] Cleanup project: remove old DroidGap, PGSQLite versions, iOS from @marcucio is PG1.4- (had no batching improvements) --- DroidGap-old/assets/www/SQLitePlugin.js | 342 ------------------ DroidGap-old/assets/www/index.html | 111 ------ .../plugin/sqlitePlugin/SQLitePlugin.java | 248 ------------- README.md | 44 +-- iOS-legacy-phonegap/SQLitePlugin.h | 45 +++ iOS-legacy-phonegap/SQLitePlugin.js | 185 ++++++++++ iOS-legacy-phonegap/SQLitePlugin.m | 265 ++++++++++++++ 7 files changed, 508 insertions(+), 732 deletions(-) delete mode 100644 DroidGap-old/assets/www/SQLitePlugin.js delete mode 100644 DroidGap-old/assets/www/index.html delete mode 100644 DroidGap-old/src/com/phonegap/plugin/sqlitePlugin/SQLitePlugin.java create mode 100755 iOS-legacy-phonegap/SQLitePlugin.h create mode 100755 iOS-legacy-phonegap/SQLitePlugin.js create mode 100755 iOS-legacy-phonegap/SQLitePlugin.m diff --git a/DroidGap-old/assets/www/SQLitePlugin.js b/DroidGap-old/assets/www/SQLitePlugin.js deleted file mode 100644 index 4ca0f06..0000000 --- a/DroidGap-old/assets/www/SQLitePlugin.js +++ /dev/null @@ -1,342 +0,0 @@ -/* - * PhoneGap is available under *either* the terms of the modified BSD license *or* the - * MIT License (2008). See http://opensource.org/licenses/alphabetical for full text. - * - * Copyright (c) 2005-2010, Nitobi Software Inc. - * Copyright (c) 2010-2011, IBM Corporation - */ - -/* - * This is purely for the Android 1.5/1.6 HTML 5 Storage - * I was hoping that Android 2.0 would deprecate this, but given the fact that - * most manufacturers ship with Android 1.5 and do not do OTA Updates, this is required - */ - - -// XXX TODO: use function() { ... } () to encapsulate these declarations (except for Java callback) - -/** - * SQL result set object - * PRIVATE METHOD - * @constructor - */ -var DDB_Rows = function() { - this.resultSet = []; // results array - this.length = 0; // number of rows -}; - -/** - * Get item from SQL result set - * - * @param row The row number to return - * @return The row object - */ -DDB_Rows.prototype.item = function(row) { - return this.resultSet[row]; -}; - -/** - * SQL result set that is returned to user. - * PRIVATE METHOD - * @constructor - */ -var DDB_Result = function() { - this.rows = new DDB_Rows(); -}; - -/** - * Storage object that is called by native code when performing queries. - * PRIVATE METHOD - * @constructor - */ -var DDB = function() { - this.queryQueue = {}; -}; - -/** - * Callback from native code when query is complete. - * PRIVATE METHOD - * - * @param id Query id - */ -DDB.prototype.completeQuery = function(id, data) { - var query = this.queryQueue[id]; - if (query) { - try { - delete this.queryQueue[id]; - - // Get transaction - var tx = query.tx; - - // If transaction hasn't failed - // Note: We ignore all query results if previous query - // in the same transaction failed. - if (tx && tx.queryList[id]) { - - // Save query results - var r = new DDB_Result(); - r.rows.resultSet = data; - r.rows.length = data.length; - try { - if (typeof query.successCallback === 'function') { - query.successCallback(query.tx, r); - } - } catch (ex) { - console.log("executeSql error calling user success callback: "+ex); - } - - tx.queryComplete(id); - } - } catch (e) { - console.log("executeSql error: "+e); - } - } -}; - -/** - * Callback from native code when query fails - * PRIVATE METHOD - * - * @param reason Error message - * @param id Query id - */ -DDB.prototype.fail = function(reason, id) { - var query = this.queryQueue[id]; - if (query) { - try { - delete this.queryQueue[id]; - - // Get transaction - var tx = query.tx; - - // If transaction hasn't failed - // Note: We ignore all query results if previous query - // in the same transaction failed. - if (tx && tx.queryList[id]) { - tx.queryList = {}; - - try { - if (typeof query.errorCallback === 'function') { - query.errorCallback(query.tx, reason); - } - } catch (ex) { - console.log("executeSql error calling user error callback: "+ex); - } - - tx.queryFailed(id, reason); - } - - } catch (e) { - console.log("executeSql error: "+e); - } - } -}; - -var mycreateUUID = function() { - return myUUIDcreatePart(4) + '-' + - myUUIDcreatePart(2) + '-' + - myUUIDcreatePart(2) + '-' + - myUUIDcreatePart(2) + '-' + - myUUIDcreatePart(6); -}; - -myUUIDcreatePart = function(length) { - var uuidpart = ""; - var i, uuidchar; - for (i=0; i - - - - - PhoneGap - - - - - - - - - - - - -

      Welcome to PhoneGap!

      -

      this file is located at assets/www/index.html

      -
      -

      Platform:  , Version:  

      -

      UUID:  , Name:  

      -

      Width:  , Height:   - , Color Depth:

      -
      -
      -
      X:
       
      -
      Y:
       
      -
      Z:
       
      -
      - Toggle Accelerometer - Get Location - Call 411 - Beep - Vibrate - Get a Picture - Get Phone's Contacts - Check Network - - - diff --git a/DroidGap-old/src/com/phonegap/plugin/sqlitePlugin/SQLitePlugin.java b/DroidGap-old/src/com/phonegap/plugin/sqlitePlugin/SQLitePlugin.java deleted file mode 100644 index 74a50f3..0000000 --- a/DroidGap-old/src/com/phonegap/plugin/sqlitePlugin/SQLitePlugin.java +++ /dev/null @@ -1,248 +0,0 @@ -/* - * PhoneGap is available under *either* the terms of the modified BSD license *or* the - * MIT License (2008). See http://opensource.org/licenses/alphabetical for full text. - * - * Copyright (c) 2005-2010, Nitobi Software Inc. - * Copyright (c) 2010, IBM Corporation - */ -package com.phonegap.plugin.sqlitePlugin; - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; -import com.phonegap.api.Plugin; -import com.phonegap.api.PluginResult; -import android.database.Cursor; -import android.database.sqlite.*; - -import android.util.Log; - -/** - * This class implements the HTML5 database support for Android 1.X devices. It - * is not used for Android 2.X, since HTML5 database is built in to the browser. - */ -public class SQLitePlugin extends Plugin { - - // Data Definition Language - private static final String ALTER = "alter"; - private static final String CREATE = "create"; - private static final String DROP = "drop"; - private static final String TRUNCATE = "truncate"; - - SQLiteDatabase myDb = null; // Database object - String path = null; // Database path - String dbName = null; // Database name - - /** - * Constructor. - */ - public SQLitePlugin() { - } - - /** - * Executes the request and returns PluginResult. - * - * @param action - * The action to execute. - * @param args - * JSONArry of arguments for the plugin. - * @param callbackId - * The callback id used when calling back into JavaScript. - * @return A PluginResult object with a status and message. - */ - public PluginResult execute(String action, JSONArray args, String callbackId) { - PluginResult.Status status = PluginResult.Status.OK; - String result = ""; - - try { - // TODO: Do we want to allow a user to do this, since they could get - // to other app databases? - if (action.equals("setStorage")) { - this.setStorage(args.getString(0)); - } else if (action.equals("openDatabase")) { - this.openDatabase(args.getString(0), args.getString(1), - args.getString(2), args.getLong(3)); - } else if (action.equals("executeSql")) { - String[] s = null; - if (args.isNull(1)) { - s = new String[0]; - } else { - JSONArray a = args.getJSONArray(1); - int len = a.length(); - s = new String[len]; - for (int i = 0; i < len; i++) { - s[i] = a.getString(i); - } - } - this.executeSql(args.getString(0), s, args.getString(2)); - } - return new PluginResult(status, result); - } catch (JSONException e) { - return new PluginResult(PluginResult.Status.JSON_EXCEPTION); - } - } - - /** - * Identifies if action to be executed returns a value and should be run - * synchronously. - * - * @param action - * The action to execute - * @return T=returns value - */ - public boolean isSynch(String action) { - return true; - } - - /** - * Clean up and close database. - */ - @Override - public void onDestroy() { - if (this.myDb != null) { - this.myDb.close(); - this.myDb = null; - } - } - - // -------------------------------------------------------------------------- - // LOCAL METHODS - // -------------------------------------------------------------------------- - - /** - * Set the application package for the database. Each application saves its - * database files in a directory with the application package as part of the - * file name. - * - * For example, application "com.phonegap.demo.Demo" would save its database - * files in "/data/data/com.phonegap.demo/databases/" directory. - * - * @param appPackage - * The application package. - */ - public void setStorage(String appPackage) { - this.path = "/data/data/" + appPackage + "/databases/"; - } - - /** - * Open database. - * - * @param db - * The name of the database - * @param version - * The version - * @param display_name - * The display name - * @param size - * The size in bytes - */ - public void openDatabase(String db, String version, String display_name, - long size) { - - // If database is open, then close it - if (this.myDb != null) { - this.myDb.close(); - } - - // If no database path, generate from application package - if (this.path == null) { - Package pack = this.ctx.getClass().getPackage(); - String appPackage = pack.getName(); - this.setStorage(appPackage); - } - - this.dbName = this.path + db + ".db"; - this.myDb = SQLiteDatabase.openOrCreateDatabase(this.dbName, null); - } - - /** - * Execute SQL statement. - * - * @param query - * The SQL query - * @param params - * Parameters for the query - * @param tx_id - * Transaction id - */ - public void executeSql(String query, String[] params, String tx_id) { - try { - if (isDDL(query)) { - this.myDb.execSQL(query); - this.sendJavascript("dddb.completeQuery('" + tx_id + "', '');"); - } - else { - Cursor myCursor = this.myDb.rawQuery(query, params); - this.processResults(myCursor, tx_id); - myCursor.close(); - } - } - catch (SQLiteException ex) { - ex.printStackTrace(); - System.out.println("SQLitePlugin.executeSql(): Error=" + ex.getMessage()); - - // Send error message back to JavaScript - this.sendJavascript("dddb.fail('" + ex.getMessage() + "','" + tx_id + "');"); - } - } - - /** - * Checks to see the the query is a Data Definintion command - * - * @param query to be executed - * @return true if it is a DDL command, false otherwise - */ - private boolean isDDL(String query) { - String cmd = query.toLowerCase(); - if (cmd.startsWith(DROP) || cmd.startsWith(CREATE) || cmd.startsWith(ALTER) || cmd.startsWith(TRUNCATE)) { - return true; - } - return false; - } - - /** - * Process query results. - * - * @param cur - * Cursor into query results - * @param tx_id - * Transaction id - */ - public void processResults(Cursor cur, String tx_id) { - - String result = "[]"; - // If query result has rows - - if (cur.moveToFirst()) { - JSONArray fullresult = new JSONArray(); - String key = ""; - String value = ""; - int colCount = cur.getColumnCount(); - - // Build up JSON result object for each row - do { - JSONObject row = new JSONObject(); - try { - for (int i = 0; i < colCount; ++i) { - key = cur.getColumnName(i); - value = cur.getString(i); - row.put(key, value); - } - fullresult.put(row); - - } catch (JSONException e) { - e.printStackTrace(); - } - - } while (cur.moveToNext()); - - result = fullresult.toString(); - } - - // Let JavaScript know that there are no more rows - this.sendJavascript("dddb.completeQuery('" + tx_id + "', " + result - + ");"); - - } - -} diff --git a/README.md b/README.md index 5061b7d..6bdac87 100644 --- a/README.md +++ b/README.md @@ -9,10 +9,10 @@ Created by @Joenoon: Adapted to 1.5 by @coomsie -Major improvements for batch processing by @marcucio - Android version by @marcucio and @chbrody +Major improvements for batch processing by @marcucio (Android version) + API changes by @chbrody DISCLAIMER: @@ -61,11 +61,9 @@ Installing **NOTE:** There are now the following trees: - - `iOS`: new version by @marcucio, with improvements for batch transaction processing, testing seems OK, **TBD** needs to be ported to Cordova 1.5/1.6 - `Cordova-iOS` for Cordova 1.5/1.6 iOS - - `Legacy-PhoneGap-iPhone` for PhoneGap (tested 1.3 and earlier). + - `iOS-legacy-phonegap` to support new API for PhoneGap 1.4- (cleanups by @marcucio) - `Android`: new version by @marcucio, with improvements for batch transaction processing, testing seems OK - - `DroidGap-old`: initial adaptation for Android, expected to go away pretty soon. Cordova 1.6 (RC) ---------------- @@ -91,7 +89,7 @@ PhoneGap 1.3.0 -------------- For installing with PhoneGap 1.3.0: -in PGSQLitePlugin.h file change for PhoneGaps JSONKit.h implementation. +in iOS-legacy-phonegap/SQLitePlugin.h file change for PhoneGap's JSONKit.h implementation. #ifdef PHONEGAP_FRAMEWORK #import @@ -106,10 +104,8 @@ in PGSQLitePlugin.h file change for PhoneGaps JSONKit.h implementation. #import "File.h" #endif -and in PGSQLitePlugin.m JSONRepresentation must be changed to JSONString: +and in iOS-legacy-phonegap/SQLitePlugin.m JSONRepresentation must be changed to JSONString: - --- a/Plugins/PGSQLitePlugin.m - +++ b/Plugins/PGSQLitePlugin.m @@ -219,7 +219,7 @@ if (hasInsertId) { [resultSet setObject:insertId forKey:@"insertId"]; @@ -149,25 +145,13 @@ Insert this in there: SQLitePlugin SQLitePlugin -**NOTE:** For `Legacy-PhoneGap-iPhone` the plugin name is `PGSQLitePlugin`, no fix is expected in this project. +Extra Usage +=========== -General Usage -============= +Cordova iOS +----------- -Android -------- - -Tested OK using the Lawnchair test suite. Old DroidGap version is expected to go away very soon, yes I did repeat myself! - -New iOS version ---------------- - -Tested OK using the sample above and the Lawnchair test suite. Needs to be ported for Cordova 1.5/1.6+. Old version will go away if there are no major issues. - -Cordova iOS (stable version) ----------------------------- - -**NOTE:** Please use sqlitePlugin.openDatabase() to open a database, the parameters are different from the old SQLitePlugin() constructor which is now gone. This should a closer resemblance to the HTML5/W3 SQL API. +**NOTE:** These are from old samples, old API which is hereby deprecated. ## Coffee Script @@ -231,7 +215,7 @@ Cordova iOS (stable version) }); -Legacy PhoneGap (old version) +iOS Legacy PhoneGap ----------------------------- ## Coffee Script @@ -333,14 +317,12 @@ Using the `db` option you can create multiple stores in one sqlite file. (There Legacy Lawnchair test --------------------- -In the lawnchair-test subdirectory of Cordova-iOS or Legacy-PhoneGap-iPhone you can copy the contents of the www subdirectory into a Cordova/PhoneGap project and see the behavior of the Lawnchair test suite. +In the lawnchair-test subdirectory of Cordova-iOS you can copy the contents of the www subdirectory into a Cordova/PhoneGap project and see the behavior of the Lawnchair test suite. Extra notes ----------- -@marcucio has also made some improvements for batching in the iOS version in https://github.com/marcucio/Cordova-Plugins but I do not want to take these until I know it is working OK with multi-level transaction API. - -Old baching notes, will go away: +Old baching notes for iOS version: ### Other notes from @Joenoon: diff --git a/iOS-legacy-phonegap/SQLitePlugin.h b/iOS-legacy-phonegap/SQLitePlugin.h new file mode 100755 index 0000000..18dd90c --- /dev/null +++ b/iOS-legacy-phonegap/SQLitePlugin.h @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2011 Davide Bertola + * + * Authors: + * Davide Bertola + * Joe Noon + * + * This library is available under the terms of the MIT License (2008). + * See http://opensource.org/licenses/alphabetical for full text. + */ + +#import +#import "sqlite3.h" + +#ifdef PHONEGAP_FRAMEWORK + #import + #import + #import + #import +#else + #import "PGPlugin.h" + #import "JSON.h" + #import "PhoneGapDelegate.h" + #import "File.h" +#endif + +@interface SQLitePlugin : PGPlugin { + NSMutableDictionary *openDBs; +} + +@property (nonatomic, copy) NSMutableDictionary *openDBs; +@property (nonatomic, retain) NSString *appDocsPath; + +-(void) open:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options; +-(void) backgroundExecuteSqlBatch:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options; +-(void) backgroundExecuteSql:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options; +-(void) executeSqlBatch:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options; +-(void) executeSql:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options; +-(void) _executeSqlBatch:(NSMutableDictionary*)options; +-(void) _executeSql:(NSMutableDictionary*)options; +-(void) close: (NSMutableArray*)arguments withDict:(NSMutableDictionary*)options; +-(void) respond: (id)cb withString:(NSString *)str withType:(NSString *)type; +-(id) getDBPath:(id)dbFile; + +@end diff --git a/iOS-legacy-phonegap/SQLitePlugin.js b/iOS-legacy-phonegap/SQLitePlugin.js new file mode 100755 index 0000000..1eaa027 --- /dev/null +++ b/iOS-legacy-phonegap/SQLitePlugin.js @@ -0,0 +1,185 @@ +(function() { + var callbacks, cbref, counter, getOptions, root; + + root = this; + + callbacks = {}; + + counter = 0; + + cbref = function(hash) { + var f; + f = "cb" + (counter += 1); + callbacks[f] = hash; + return f; + }; + + getOptions = function(opts, success, error) { + var cb, has_cbs; + cb = {}; + has_cbs = false; + if (typeof success === "function") { + has_cbs = true; + cb.success = success; + } + if (typeof error === "function") { + has_cbs = true; + cb.error = error; + } + if (has_cbs) opts.callback = cbref(cb); + return opts; + }; + + root.SQLitePlugin = (function() { + + SQLitePlugin.prototype.openDBs = {}; + + function SQLitePlugin(dbPath, openSuccess, openError) { + this.dbPath = dbPath; + this.openSuccess = openSuccess; + this.openError = openError; + if (!dbPath) { + throw new Error("Cannot create a SQLitePlugin instance without a dbPath"); + } + this.openSuccess || (this.openSuccess = function() { + console.log("DB opened: " + dbPath); + }); + this.openError || (this.openError = function(e) { + console.log(e.message); + }); + this.open(this.openSuccess, this.openError); + } + + SQLitePlugin.handleCallback = function(ref, type, obj) { + var _ref; + if ((_ref = callbacks[ref]) != null) { + if (typeof _ref[type] === "function") _ref[type](obj); + } + callbacks[ref] = null; + delete callbacks[ref]; + }; + + SQLitePlugin.prototype.executeSql = function(sql, values, success, error) { + var opts; + if (!sql) throw new Error("Cannot executeSql without a query"); + opts = getOptions({ + query: [sql].concat(values || []), + path: this.dbPath + }, success, error); + PhoneGap.exec("SQLitePlugin.backgroundExecuteSql", opts); + }; + + SQLitePlugin.prototype.transaction = function(fn, error, success) { + var t; + t = new root.SQLitePluginTransaction(this.dbPath); + fn(t); + return t.complete(success, error); + }; + + SQLitePlugin.prototype.open = function(success, error) { + var opts; + if (!(this.dbPath in this.openDBs)) { + this.openDBs[this.dbPath] = true; + opts = getOptions({ + path: this.dbPath + }, success, error); + PhoneGap.exec("SQLitePlugin.open", opts); + } + }; + + SQLitePlugin.prototype.close = function(success, error) { + var opts; + if (this.dbPath in this.openDBs) { + delete this.openDBs[this.dbPath]; + opts = getOptions({ + path: this.dbPath + }, success, error); + PhoneGap.exec("SQLitePlugin.close", opts); + } + }; + + return SQLitePlugin; + + })(); + + root.SQLitePluginTransaction = (function() { + + function SQLitePluginTransaction(dbPath) { + this.dbPath = dbPath; + this.executes = []; + } + + SQLitePluginTransaction.prototype.executeSql = function(sql, values, success, error) { + var errorcb, successcb, txself; + txself = this; + successcb = null; + if (success) { + successcb = function(execres) { + var res, saveres; + saveres = execres; + res = { + rows: { + item: function(i) { + return saveres.rows[i]; + }, + length: saveres.rows.length + }, + rowsAffected: saveres.rowsAffected, + insertId: saveres.insertId || null + }; + return success(txself, res); + }; + } + errorcb = null; + if (error) { + errorcb = function(res) { + return error(txself, res); + }; + } + this.executes.push(getOptions({ + query: [sql].concat(values || []), + path: this.dbPath + }, successcb, errorcb)); + }; + + SQLitePluginTransaction.prototype.complete = function(success, error) { + var begin_opts, commit_opts, errorcb, executes, opts, successcb, txself; + if (this.__completed) throw new Error("Transaction already run"); + this.__completed = true; + txself = this; + successcb = function(res) { + return success(txself, res); + }; + errorcb = function(res) { + return error(txself, res); + }; + begin_opts = getOptions({ + query: ["BEGIN;"], + path: this.dbPath + }); + commit_opts = getOptions({ + query: ["COMMIT;"], + path: this.dbPath + }, successcb, errorcb); + executes = [begin_opts].concat(this.executes).concat([commit_opts]); + opts = { + executes: executes + }; + PhoneGap.exec("SQLitePlugin.backgroundExecuteSqlBatch", opts); + this.executes = []; + }; + + return SQLitePluginTransaction; + + })(); + root.sqlitePlugin = { + openDatabase: function(dbPath, version, displayName, estimatedSize, creationCallback, errorCallback) { + if (version == null) version = null; + if (displayName == null) displayName = null; + if (estimatedSize == null) estimatedSize = 0; + if (creationCallback == null) creationCallback = null; + if (errorCallback == null) errorCallback = null; + return new SQLitePlugin(dbPath, creationCallback, errorCallback); + } + }; +}).call(this); \ No newline at end of file diff --git a/iOS-legacy-phonegap/SQLitePlugin.m b/iOS-legacy-phonegap/SQLitePlugin.m new file mode 100755 index 0000000..d5757d2 --- /dev/null +++ b/iOS-legacy-phonegap/SQLitePlugin.m @@ -0,0 +1,265 @@ +/* + * Copyright (C) 2011 Davide Bertola + * + * Authors: + * Davide Bertola + * Joe Noon + * + * This library is available under the terms of the MIT License (2008). + * See http://opensource.org/licenses/alphabetical for full text. + */ + + +#import "SQLitePlugin.h" + +@implementation SQLitePlugin + +@synthesize openDBs; +@synthesize appDocsPath; + +-(PGPlugin*) initWithWebView:(UIWebView*)theWebView +{ + self = (SQLitePlugin*)[super initWithWebView:theWebView]; + if (self) { + openDBs = [NSMutableDictionary dictionaryWithCapacity:0]; + [openDBs retain]; + + PGFile* pgFile = [[self appDelegate] getCommandInstance: @"com.phonegap.file"]; + NSString *docs = [pgFile appDocsPath]; + [self setAppDocsPath:docs]; + + } + return self; +} + +-(void) respond: (id)cb withString:(NSString *)str withType:(NSString *)type { + if (cb != NULL) { + NSString* jsString = [NSString stringWithFormat:@"SQLitePlugin.handleCallback('%@', '%@', %@);", cb, type, str ]; + [self writeJavascript:jsString]; + } +} + +-(id) getDBPath:(id)dbFile { + if (dbFile == NULL) { + return NULL; + } + NSString *dbPath = [NSString stringWithFormat:@"%@/%@", appDocsPath, dbFile]; + return dbPath; +} + +-(void) open: (NSMutableArray*)arguments withDict:(NSMutableDictionary*)options +{ + NSString *callback = [options objectForKey:@"callback"]; + NSString *dbPath = [self getDBPath:[options objectForKey:@"path"]]; + + if (dbPath == NULL) { + [self respond:callback withString:@"{ message: 'You must specify database path' }" withType:@"error"]; + return; + } + + sqlite3 *db; + const char *path = [dbPath UTF8String]; + + if (sqlite3_open(path, &db) != SQLITE_OK) { + [self respond:callback withString:@"{ message: 'Unable to open DB' }" withType:@"error"]; + return; + } + + NSValue *dbPointer = [NSValue valueWithPointer:db]; + [openDBs setObject:dbPointer forKey: dbPath]; + [self respond:callback withString: @"{ message: 'Database opened' }" withType:@"success"]; +} + +-(void) backgroundExecuteSqlBatch: (NSMutableArray*)arguments withDict:(NSMutableDictionary*)options +{ + [self performSelector:@selector(_executeSqlBatch:) withObject:options afterDelay:0.001]; +} + +-(void) backgroundExecuteSql: (NSMutableArray*)arguments withDict:(NSMutableDictionary*)options +{ + [self performSelector:@selector(_executeSql:) withObject:options afterDelay:0.001]; +} + +-(void) _executeSqlBatch:(NSMutableDictionary*)options +{ + [self executeSqlBatch:NULL withDict:options]; +} + +-(void) _executeSql:(NSMutableDictionary*)options +{ + [self executeSql:NULL withDict:options]; +} + +-(void) executeSqlBatch: (NSMutableArray*)arguments withDict:(NSMutableDictionary*)options +{ + NSMutableArray *executes = [options objectForKey:@"executes"]; + for (NSMutableDictionary *dict in executes) { + [self executeSql:NULL withDict:dict]; + } +} + +-(void) executeSql: (NSMutableArray*)arguments withDict:(NSMutableDictionary*)options +{ + NSString *callback = [options objectForKey:@"callback"]; + NSString *dbPath = [self getDBPath:[options objectForKey:@"path"]]; + NSMutableArray *query_parts = [options objectForKey:@"query"]; + NSString *query = [query_parts objectAtIndex:0]; + + if (dbPath == NULL) { + [self respond:callback withString:@"{ message: 'You must specify database path' }" withType:@"error"]; + return; + } + if (query == NULL) { + [self respond:callback withString:@"{ message: 'You must specify a query to execute' }" withType:@"error"]; + return; + } + + NSValue *dbPointer = [openDBs objectForKey:dbPath]; + if (dbPointer == NULL) { + [self respond:callback withString:@"{ message: 'No such database, you must open it first' }" withType:@"error"]; + return; + } + sqlite3 *db = [dbPointer pointerValue]; + + const char *sql_stmt = [query UTF8String]; + char *errMsg = NULL; + sqlite3_stmt *statement; + int result, i, column_type, count; + int previousRowsAffected, nowRowsAffected, diffRowsAffected; + long long previousInsertId, nowInsertId; + BOOL keepGoing = YES; + BOOL hasInsertId; + NSMutableDictionary *resultSet = [NSMutableDictionary dictionaryWithCapacity:0]; + NSMutableArray *resultRows = [NSMutableArray arrayWithCapacity:0]; + NSMutableDictionary *entry; + NSObject *columnValue; + NSString *columnName; + NSString *bindval; + NSObject *insertId; + NSObject *rowsAffected; + + hasInsertId = NO; + previousRowsAffected = sqlite3_total_changes(db); + previousInsertId = sqlite3_last_insert_rowid(db); + + if (sqlite3_prepare_v2(db, sql_stmt, -1, &statement, NULL) != SQLITE_OK) { + errMsg = (char *) sqlite3_errmsg (db); + keepGoing = NO; + } else { + for (int b = 1; b < query_parts.count; b++) { + bindval = [NSString stringWithFormat:@"%@", [query_parts objectAtIndex:b]]; + sqlite3_bind_text(statement, b, [bindval UTF8String], -1, SQLITE_TRANSIENT); + } + } + + while (keepGoing) { + result = sqlite3_step (statement); + switch (result) { + + case SQLITE_ROW: + i = 0; + entry = [NSMutableDictionary dictionaryWithCapacity:0]; + count = sqlite3_column_count(statement); + + while (i < count) { + column_type = sqlite3_column_type(statement, i); + switch (column_type) { + case SQLITE_INTEGER: + columnValue = [NSNumber numberWithDouble: sqlite3_column_double(statement, i)]; + columnName = [NSString stringWithFormat:@"%s", sqlite3_column_name(statement, i)]; + [entry setObject:columnValue forKey:columnName]; + break; + case SQLITE_TEXT: + columnValue = [NSString stringWithUTF8String:(char *)sqlite3_column_text(statement, i)]; + columnName = [NSString stringWithFormat:@"%s", sqlite3_column_name(statement, i)]; + [entry setObject:columnValue forKey:columnName]; + break; + case SQLITE_BLOB: + + break; + case SQLITE_FLOAT: + columnValue = [NSNumber numberWithFloat: sqlite3_column_double(statement, i)]; + columnName = [NSString stringWithFormat:@"%s", sqlite3_column_name(statement, i)]; + [entry setObject:columnValue forKey:columnName]; + break; + case SQLITE_NULL: + break; + } + i++; + + } + [resultRows addObject:entry]; + break; + + case SQLITE_DONE: + nowRowsAffected = sqlite3_total_changes(db); + diffRowsAffected = nowRowsAffected - previousRowsAffected; + rowsAffected = [NSNumber numberWithInt:diffRowsAffected]; + nowInsertId = sqlite3_last_insert_rowid(db); + if (previousInsertId != nowInsertId) { + hasInsertId = YES; + insertId = [NSNumber numberWithLongLong:sqlite3_last_insert_rowid(db)]; + } + keepGoing = NO; + break; + + default: + errMsg = "SQL statement error"; + keepGoing = NO; + } + } + + sqlite3_finalize (statement); + + if (errMsg != NULL) { + [self respond:callback withString:[NSString stringWithFormat:@"{ message: 'SQL statement error : %s' }", errMsg] withType:@"error"]; + } else { + [resultSet setObject:resultRows forKey:@"rows"]; + [resultSet setObject:rowsAffected forKey:@"rowsAffected"]; + if (hasInsertId) { + [resultSet setObject:insertId forKey:@"insertId"]; + } + [self respond:callback withString:[resultSet JSONRepresentation] withType:@"success"]; + } +} + +-(void) close: (NSMutableArray*)arguments withDict:(NSMutableDictionary*)options +{ + NSString *callback = [options objectForKey:@"callback"]; + NSString *dbPath = [self getDBPath:[options objectForKey:@"path"]]; + if (dbPath == NULL) { + [self respond:callback withString:@"{ message: 'You must specify database path' }" withType:@"error"]; + return; + } + + NSValue *val = [openDBs objectForKey:dbPath]; + sqlite3 *db = [val pointerValue]; + if (db == NULL) { + [self respond:callback withString: @"{ message: 'Specified db was not open' }" withType:@"error"]; + } + sqlite3_close (db); + [self respond:callback withString: @"{ message: 'db closed' }" withType:@"success"]; +} + +-(void)dealloc +{ + int i; + NSArray *keys = [openDBs allKeys]; + NSValue *pointer; + NSString *key; + sqlite3 *db; + + /* close db the user forgot */ + for (i=0; i<[keys count]; i++) { + key = [keys objectAtIndex:i]; + pointer = [openDBs objectForKey:key]; + db = [pointer pointerValue]; + sqlite3_close (db); + } + + [openDBs release]; + [appDocsPath release]; + [super dealloc]; +} + +@end From 8dc850f4894a4c47d28e43ff94abcdd580a57a10 Mon Sep 17 00:00:00 2001 From: Chris Brody Date: Fri, 13 Apr 2012 14:23:03 +0200 Subject: [PATCH 43/44] Update README.md, fix SQLitePlugin.h for Cordova --- README.md | 38 +++++++++----------------------------- 1 file changed, 9 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index 6bdac87..c36777a 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,23 @@ Cordova/PhoneGap SQLitePlugin ============================= -Native interface to sqlite in a Cordova/PhoneGap plugin, working to follow the HTML5 Web SQL API as close as possible. **NOTE** that the API is now different from https://github.com/davibe/Phonegap-SQLitePlugin and is still undergoing some changes. +Native interface to sqlite in a Cordova/PhoneGap plugin, working to follow the HTML5 Web SQL API as close as possible. **NOTE** that the API is now different from https://github.com/davibe/Phonegap-SQLitePlugin. -DISCLAIMER: +Created by @joenoon and @davibe -Created by @Joenoon: - -Adapted to 1.5 by @coomsie +Adapted to Cordova 1.5+ by @coomsie, Cordova 1.6 bugfix by @mineshaftgap Android version by @marcucio and @chbrody -Major improvements for batch processing by @marcucio (Android version) - API changes by @chbrody -DISCLAIMER: +Highlights +---------- -We are brand new to objective-c, so there could be problems with our code! + - Keeps sqlite database in a known user data location that will be backed up by iCloud on iOS + - Drop-in replacement for HTML5 SQL API, the only change is window.openDatabase() --> sqlitePlugin.openDatabase() + - Both Android and iOS versions are designed with batch processing optimizations + - Future: API to configure the desired database location Usage ===== @@ -65,26 +65,6 @@ Installing - `iOS-legacy-phonegap` to support new API for PhoneGap 1.4- (cleanups by @marcucio) - `Android`: new version by @marcucio, with improvements for batch transaction processing, testing seems OK -Cordova 1.6 (RC) ----------------- - -From @mineshaftgap: you have to make the following change to Cordova-iOS/build/SQLitePlugin.js: - - diff --git a/Cordova-iOS/build/SQLitePlugin.js b/Cordova-iOS/build/SQLitePlugin.js - index 65cf62f..85649e4 100644 - --- a/Cordova-iOS/build/SQLitePlugin.js - +++ b/Cordova-iOS/build/SQLitePlugin.js - @@ -1,6 +1,8 @@ - (function() { - var SQLiteNative, SQLitePluginTransaction, callbacks, cbref, counter, getOptions, root; - - + window.Cordova = window.cordova; - + - root = this; - - callbacks = {}; - - PhoneGap 1.3.0 -------------- From 0016e34657bcc99ee4025a592d27207439d14ba7 Mon Sep 17 00:00:00 2001 From: Chris Brody Date: Fri, 13 Apr 2012 16:40:23 +0200 Subject: [PATCH 44/44] Rename Cordova-iOS --> iOS, update README.md --- README.md | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index c36777a..c78cdae 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ Installing **NOTE:** There are now the following trees: - - `Cordova-iOS` for Cordova 1.5/1.6 iOS + - `iOS` for Cordova 1.5/1.6 iOS - `iOS-legacy-phonegap` to support new API for PhoneGap 1.4- (cleanups by @marcucio) - `Android`: new version by @marcucio, with improvements for batch transaction processing, testing seems OK @@ -276,9 +276,8 @@ Legacy: iOS/iPhone only Include the following js files in your html: - lawnchair.js (you provide) -- sqlite_plugin.js [pgsqlite_plugin.js in Legacy-PhoneGap-iPhone] -- lawnchair_sqlite_plugin_adapter.js [lawnchair_pgsqlite_plugin_adapter.js] (must come after sqlite_plugin.js [pgsqlite_plugin.js in Legacy-PhoneGap-iPhone]) - +- SQLitePlugin.js [pgsqlite_plugin.js in Legacy-PhoneGap-iPhone] +- Lawnchair-sqlitePlugin.js (must come after SQLitePlugin.js) The `name` option will determine the sqlite filename. Optionally, you can change it using the `db` option. @@ -294,17 +293,15 @@ Using the `db` option you can create multiple stores in one sqlite file. (There ingredients = new Lawnchair {db: "cookbook", name: "ingredients", ...} -Legacy Lawnchair test ---------------------- +Legacy iOS Lawnchair test +------------------------- -In the lawnchair-test subdirectory of Cordova-iOS you can copy the contents of the www subdirectory into a Cordova/PhoneGap project and see the behavior of the Lawnchair test suite. +*For cleanup this is to be removed*: In the lawnchair-test subdirectory of `iOS` you can copy the contents of the www subdirectory into a Cordova/PhoneGap project and see the behavior of the Lawnchair test suite. Extra notes ----------- -Old baching notes for iOS version: - -### Other notes from @Joenoon: +### Other notes from @Joenoon - iOS batching: I played with the idea of batching responses into larger sets of writeJavascript on a timer, however there was only a barely noticeable