Saturday, December 1, 2012

Relocation of Cordova SQLitePlugin and blogs

I have been blogging since last March on several topics including the PhoneGap SQLitePlugin, CoffeeScript, and GWT, and am now making some preparations to take my blogging to another level. I have come up with a personal brand name "brodyspark" which I plan to use for my online presence accounts such as my blog name, my personal dot-com, and my github account. I am now transitioning from 7 blogs, most of which have no more than 4 postings, to 2 blogs: brodyspark for apps, online businesses, and anything else that is non-religious, and Nazarenespark for my faith-based writings.

There are now two major consequences. The first is that I expect retire this mobile app help blog and put new postings into my brodyspark blog. I do expect to use the mobileapphelp.org domain which I had reserved last Spring to set up some sort of a Q&A forum help site sometime in the future.

The second consequence is that the Cordova-SQLitePlugin project has now been relocated into my new brodyspark github account, with the iOS and Android versions split into different projects:
I add redirection links in the old project location with the hope that nobody will be lost by this transition.

I will put some more information about brodyspark / PhoneGap-sqlitePlugin-iOS and brodyspark / PhoneGap-sqlitePlugin-Android in a future posting.

Monday, October 22, 2012

Cordova/PhoneGap SQLitePlugin continues to show excellent reliability

UPDATE: Please see my new posting: Cordova/PhoneGap sqlite plugins offer large db size, excellent reliability

Back in March 2012 I blogged about the Phonegap-SQLitePlugin project for providing reliable offline storage for iOS applications built using Cordova/PhoneGap.

UPDATED: I worked on a fork in brodyspark / PhoneGap-SQLitePlugin-iOS to provide a more HTML5 SQL API compliant interface and also made a version for Android: brodyspark / PhoneGap-SQLitePlugin-Android.

Some improvements were made in the iOS version of Cordova/PhoneGap to use certain attributes to make sure the data would be backed up the iCloud. However there seems to be more issues between CB-1561 Using Storage API - rejected by Apple and a new thread about the possibility of data loss.

I originally started using and improving the SQLitePlugin to provide reliable configuration storage from the very beginning, and it has now proven its value compared to the ongoing possibility of data loss from the built-in storage API. A number of GitHub stargazers have indicated this project to be of value for both its reliability and close conformance to the HTML5 SQL API.

Update December 2012: a posting about data not persisted on the Android version using the built-in Web SQL database.

Wednesday, August 8, 2012

Trying SQLCipher with Cordova iOS SQLitePlugin

NOTICE (June 2015): These instructions are completely out-of-date, the following Cordova plugin supports sqlcipher out-of-the-box: https://github.com/litehelpers/Cordova-sqlcipher-adapter

UPDATE (OLD): please see the updated directions posted on my new blog.

I have already documented running the Cordova SQLitePlugin PhoneGap/Cordova SQLitePlugin for Android with SQLCipher for Android here and here for rebuilding SQLite for Android from source. Here I document the steps I am taking to run SQLCipher with the Cordova-SQLitePlugin PhoneGap-SQLitePlugin for iOS.

The first step is to create the (test) app project. I created a Cordova 2.0 project using the command line tool create as documented here and here.

Next I follow the directions from SQLCipher for iOS integration to build the project with SQLCipher (hold off on the integration code for now). For this case I am using OpenSSL 1.0.1c. Download from http://www.openssl.org/source/ and extract.

Under the project folder, git clone git://github.com/sqlcipher/sqlcipher.git and git://github.com/sqlcipher/openssl-xcode.git

Now open the app project, open Xcode --> Preferences menu, Locations, Source Trees, then add the setting OPENSSL_SRC that points to the location of the extracted OpenSSL source tree.

Add the subproject references for the sqlcipher xcodeproject and openssl-xcode/openssl.xcodeproj. I selected the top-level project item and alt-command-a to add each subproject reference.

Configure build dependencies: select the app target, click the build phases tab, and add the target dependencies both openssl/crypto and sqlcipher. Under link binary with libraries section add libcrypto.a and libsqlcipher.a but make sure there is no libsqlite3 library.

Holding off on setting build architectures and CFLAGS for now. Under other C flags add
-DSQLITE_HAS_CODEC for both debug and release projects. It should now be possible to build the project.

Building with SQLitePlugin: add the SQLitePlugin.[hm] to the Xcode project Plugins folder and the project should still build. Make the following patch to SQLitePlugin.m:


$ git diff Plugins/SQLitePlugin.m
diff --git a/iOS/Plugins/SQLitePlugin.m b/iOS/Plugins/SQLitePlugin.m
index 444a78b..65b5b61 100644
--- a/iOS/Plugins/SQLitePlugin.m
+++ b/iOS/Plugins/SQLitePlugin.m
@@ -70,7 +70,10 @@
         [self respond:callback withString:@"{ message: 'Unable to open DB' }" withType:@"error"];
         return;
     }
-    
+
+    const char* key = [@"BIGSecret" UTF8String];
+    sqlite3_key(db, key, strlen(key));
+
     NSValue *dbPointer = [NSValue valueWithPointer:db];
     [openDBs setObject:dbPointer forKey: dbPath];
     [self respond:callback withString: @"{ message: 'Database opened' }" withType:@"success"];


and build should still be working. Add SQLitePlugin to Cordova.plist resources, Install SQLitePlugin.js into the www directory, add a small test program using the SQLitePlugin, and try running.

To be honest, I do not really like the number of external projects that have to be assembled and cobbled together to make SQLCipher work with the Cordova-SQLitePlugin for a Cordova project. I would like to find a solution that allows someone to just setup a simple project, add some files as necessary, and run.

Tuesday, August 7, 2012

Rebuilding SQLite/SQLCipher for Android on OSX

NOTICE (June 2015): These instructions are completely out-of-date, the following Cordova plugin supports sqlcipher out-of-the-box: https://github.com/litehelpers/Cordova-sqlcipher-adapter

UPDATE (OLD): please see the updated directions for both OSX and Linux on my new blog.

From a previous post I have already built and run the Cordova SQLitePlugin with SQLCipher. The next task is to rebuild SQLCipher for Android from source. Note that the android-database-sqlcipher project repository includes the sub-repository and tasks to rebuild a special version of the sqlite C library itself.

From SQLCipher for Android the first task is to install the Android NDK (assuming that Android SDK and Java have been installed). Their directions tell you to install the Android ndk-build in a special directory and add it to your path. However, for OSX I believe there is an easy way if you are using Homebrew.

The assumption is that you have already installed the Android SDK using Homebrew. If not, make sure Homebrew is installed then install the Android SDK using:
$ brew install android (see my Notes on Running Cordova/PhoneGap on Android without Eclipse posting).

To install the Android NDK, simply: $ brew install android-ndk

The next step is to follow the directions for SQLCipher for Android to rebuild SQLCipher for Android from source. I made a special subdirectory and cloned the android-database-sqlcipher project like:
$ git clone git://github.com/sqlcipher/android-database-sqlcipher.git

Inside the android-database-sqlcipher directory the next step is to do
$ make init to update/init the git submodules and run an android update project command.

To start the build process: $ make

There seems to be an issue with the Makefile that tries to copy libstlport_shared.so from the wrong place. To fix:

$ git diff Makefile
diff --git a/Makefile b/Makefile
index b8067f2..d8b4883 100644
--- a/Makefile
+++ b/Makefile
@@ -42,7 +42,7 @@ copy-libs:
        cp ${JNI_DIR}/libs/armeabi/libdatabase_sqlcipher.so \
                ${LIBRARY_ROOT}/armeabi && \
        cp ${CURDIR}/bin/classes/sqlcipher.jar ${LIBRARY_ROOT} && \
-       cp ${ANDROID_NDK_ROOT}/sources/cxx-stl/stlport/libs/armeabi/libstlport_shared.so \
+       cp ${EXTERNAL_DIR}/libs/armeabi/libstlport_shared.so \
                 ${LIBRARY_ROOT}/armeabi

 copy-libs-dist:

Then to start the build process: $make

The libraries should show up under the libs subdirectory.

To test with a Cordova/PhoneGap project with Cordova-SQLitePlugin: follow the previous post for trying SQLCipher with Cordova-SQLitePlugin but copy the contents of the project libs directory from the newly-built libs subdirectory (and keep cordova-x.x.x.jar but remove commons-codec.jar). The test should now continue be working OK.

Sunday, August 5, 2012

Trying SQLCipher with Cordova SQLitePlugin for Android

NOTICE (June 2015): These instructions are completely out-of-date, the following Cordova plugin supports sqlcipher out-of-the-box: https://github.com/litehelpers/Cordova-sqlcipher-adapter

UPDATE (OLD): Please see the updated instructions on my new blog.

The project chbrody/Cordova-SQLitePlugin brodyspark / PhoneGap-SQLitePlugin-Android provides a native interface to the built-in sqlite library for Cordova/PhoneGap applications on Android and iOS. However, I think a better future is to provide a plugin to use a custom-built version of the sqlite library, and this can provide several benefits including native compression and encryption. Another benefit for the Android is to support all result parameters including rowsAffected for UPDATE and DELETE for all versions of the Android SDK.

In this test, the idea is to use the Java classes for SQLite from SQLCipher for Android instead of the built-in SQLiteDatabase API. The other part is to recompile the sqlite C library itself UPDATE: the next step is to rebuild SQLCipher for Android, including the special version of the C library. This post was written as I tried the steps myself, using Cordova/PhoneGap version 2.0 (with some trial-and-error).

The first step is to make a sample project with the PhoneGap-SQLitePlugin-Android with the quick test, but do not compile yet (see the Android section of Installing in PhoneGap-sqlitePlugin-Android/README.md). This includes installing the Plugin Javascript and Java files, updating res/xml/config.xml, and adding the test program to index.html. (The android update project command can wait for now.)

This test is done only with the binaries of SQLCipher for Android, rebuilding from source will be saved for another posting. In this test, version 2.0.8 is used from https://github.com/sqlcipher/android-database-sqlcipher/downloads though any recent version should be OK. I make a separate directory before opening the ZIP file to make it easier to cleanup afterwards. (When you uncompress the ZIP file you get an extra __MACOSX directory, which should be safe to ignore, in addition to the SQLCipher\ for\ Android\ 2.0.8.)

To install the SQLCipher libraries:
$ cp -Rv path-to-sqlcipher-for-android/libs/* libs
but remove libs/commons-codec.jar since it is already part of Cordova/PhoneGap. Also you have to install ICU file icudt46l.zip for lower Android distro/SDK versions:
$ cp path-to-sqlcipher-for-android/assets/icudt46l.zip assets

The next step is to modify the Plugin Java code to use the classes from SQLCipher instead of the built-in SQLiteDatabase classes. Make sure the android update project is done to compile with the desired Android API.

Remove the lines to import classes from android.database.sqlite but keep the import of android.database.Cursor and import the SQLCipher version of the database classes along with java.io.File like:

//import android.database.sqlite.*;
import net.sqlcipher.database.*;

import java.io.File;

(maybe better to import the database classes by name in the future)

In the SQLitePlugin() constructor beginning of openDatabase() add code to initialize the SQLCipher like:
SQLiteDatabase.loadLibs(this.cordova.getActivity());

and fix the following lines in openDatabase():

//this.myDb = this.cordova.getActivity().getApplicationContext().openOrCreateDatabase(db + ".db", Context.MODE_PRIVATE, null);

String dbfile = db + ".db";

File databaseFile = this.cordova.getActivity().getDatabasePath(db + ".db");
databaseFile.mkdirs();

databaseFile.delete();

this.myDb = SQLiteDatabase.openOrCreateDatabase(databaseFile, "test123", null);

In processResults() please comment out the lines for android.os.Build.VERSION.SDK_INT >= 11 since abstract function Cursor.getType() is not implemented by the SQLCipher for Android.

The quick-test from the Android subsection of Installing should now be working.

Next steps include rebuilding SQLCipher for Android from source, re-compiling sqlite C library for Android using NDK and updating the using SQLCipher for iOS.

Saturday, April 21, 2012

Preparing for round-trip conversions between CoffeeScript and Javascript

I have been working quite a bit on a Cordova/PhoneGap plugin Cordova/PhoneGap SQLite plugin (introduced here) and have encountered a need for round-trip conversions between CoffeeScript and JavaScript. The Javascript portion of the iOS version was originally authored in CoffeeScript and compiled to Javascript. For the Android version, I had originally made an adaptation from a version in the Cordova/PhoneGap repository as of October 2011 and then the Javascript portion was largely rebuilt by someone else, based on generated code from the iOS version, to provide some improvements for batch processing. I would like to refactor the code to a common base that can be easily enhanced and maintained in both CoffeeScript and Javascript.


The problem


I already posted several reasons why I find CoffeeScript so much better to work with and I continue to feel the same way for this project, however there are still people in the community who try to stay away from CoffeeScript. Yes there are tools like js2coffee to convert Javascript back to CoffeeScript but there are quite a few problems right now including:

  • By default, the coffee compiler puts the entire code into a special top-level block with the extra indentation but js2coffee does not take the code back out of the block. The only workaround is to add a -b flag to coffee to skip the extra block.
  • Every CoffeeScript class is converted to a block that is achieving the effect in Javascript using constructor functions and prototype members. The conversion back to CoffeeScript will make a small tree of constructor and prototype functions. This becomes worse when class members and/or class functions are used.
  • When var declarations are generated in Javascript, the conversion back to CoffeeScript adds extra statements assigning the variables to undefined.
  • Of course, comments and perhaps extra line spacing are normally ignored. I believe there is a version of js2coffee that can preserve the comment lines but have not taken an opportunity to try it out.

Approach


Of course, the biggest challenge programming-wise is to convert the classes to using constructor and prototype functions. Fortunately I do not have to deal with inheritance for this case so basically each member function is converted to a prototype member declaration, static members get special treatment, and the class declarations are replaced with constructor functions.


The other major change is to enclose the entire CoffeeScript code into a do block, so that only 2 functions will have to be exported.


Changing a member function into a prototype function would indent the function code one block less, while putting the code into a do block would indent the code one block more. So I was able to let the function code remain with some extra indentation, which will be perfect when the code will be put into a do block.


Sample class


So here is a CoffeeScript class, to handle a sqlite transaction:



class SQLitePluginTransaction


  constructor: (@dbPath) ->
    @executes = []


  executeSql: (sql, values, success, error) ->
    txself = @
    successcb = null
    if success
      successcb = (execres) ->
        saveres = execres
        res =
          rows:
            item: (i) ->
              saveres.rows[i]
            length: saveres.rows.length
          rowsAffected: saveres.rowsAffected
          insertId: saveres.insertId || null
        success(txself, res)
    errorcb = null
    if error
      errorcb = (res) ->
        error(txself, res)
    @executes.push getOptions({ query: [sql].concat(values || []), path: @dbPath }, successcb, errorcb)
    return


  complete: (success, error) ->
    throw new Error "Transaction already run" if @__completed
    @__completed = true
    txself = @
    successcb = (res) ->
      success(txself, res)
    errorcb = (res) ->
      error(txself, res)
    begin_opts = getOptions({ query: [ "BEGIN;" ], path: @dbPath })
    commit_opts = getOptions({ query: [ "COMMIT;" ], path: @dbPath }, successcb, errorcb)
    executes = [ begin_opts ].concat(@executes).concat([ commit_opts ])
    opts = { executes: executes }
    Cordova.exec("SQLitePlugin.backgroundExecuteSqlBatch", opts)
    @executes = []
    return

and the generated Javascript (with no extra block indentation, small font to save space):




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
    };
    Cordova.exec("SQLitePlugin.backgroundExecuteSqlBatch", opts);
    this.executes = [];
  };

  return SQLitePluginTransaction;

})();

and when converted back to CoffeeScript:

SQLitePluginTransaction = (->
  SQLitePluginTransaction = (dbPath) ->
    @dbPath = dbPath
    @executes = []
  SQLitePluginTransaction::executeSql = (sql, values, success, error) ->
    errorcb = undefined
    successcb = undefined
    txself = undefined
    txself = this
    successcb = null
    if success
      successcb = (execres) ->
        res = undefined
        saveres = undefined
        saveres = execres
        res =
          rows:
            item: (i) ->
              saveres.rows[i]

            length: saveres.rows.length

          rowsAffected: saveres.rowsAffected
          insertId: saveres.insertId or null

        success txself, res
    errorcb = null
    if error
      errorcb = (res) ->
        error txself, res
    @executes.push getOptions(
      query: [ sql ].concat(values or [])
      path: @dbPath
    , successcb, errorcb)

  SQLitePluginTransaction::complete = (success, error) ->
    begin_opts = undefined
    commit_opts = undefined
    errorcb = undefined
    executes = undefined
    opts = undefined
    successcb = undefined
    txself = undefined
    throw new Error("Transaction already run")  if @__completed
    @__completed = true
    txself = this
    successcb = (res) ->
      success txself, res

    errorcb = (res) ->
      error txself, res

    begin_opts = getOptions(
      query: [ "BEGIN;" ]
      path: @dbPath
    )
    commit_opts = getOptions(
      query: [ "COMMIT;" ]
      path: @dbPath
    , successcb, errorcb)
    executes = [ begin_opts ].concat(@executes).concat([ commit_opts ])
    opts = executes: executes
    Cordova.exec "SQLitePlugin.backgroundExecuteSqlBatch", opts
    @executes = []

  SQLitePluginTransaction
)()

This code is not so great to work with, considering the extra block with indentation that is added in the round trip from a class declaration.

Prototype members

So the first task is to convert the member functions to prototype members, the diffsS are shown here:

@@ -111,7 +112,7 @@ class SQLitePluginTransaction
   constructor: (@dbPath) ->
     @executes = []
 
-  executeSql: (sql, values, success, error) ->
+SQLitePluginTransaction::executeSql = (sql, values, success, error) ->
     txself = @
     successcb = null
     if success
@@ -132,7 +133,7 @@ class SQLitePluginTransaction
     @executes.push getOptions({ query: [sql].concat(values || []), path: @dbPath }, successcb, errorcb)
     return
 
-  complete: (success, error) ->
+SQLitePluginTransaction::complete = (success, error) ->
     throw new Error "Transaction already run" if @__completed
     @__completed = true
     txself = @

So all that remains in the original class block is the constructor function. The generated Javascript is omitted in order to save some space. Here is the CoffeeScript that comes out of the round trip:

SQLitePluginTransaction = (->
  SQLitePluginTransaction = (dbPath) ->
    @dbPath = dbPath
    @executes = []
  SQLitePluginTransaction
)()
SQLitePluginTransaction::executeSql = (sql, values, success, error) ->
  errorcb = undefined
  successcb = undefined
  txself = undefined
  txself = this
  successcb = null
  if success
    successcb = (execres) ->
      res = undefined
      saveres = undefined
      saveres = execres
      res =
        rows:
          item: (i) ->
            saveres.rows[i]

          length: saveres.rows.length

        rowsAffected: saveres.rowsAffected
        insertId: saveres.insertId or null

      success txself, res
  errorcb = null
  if error
    errorcb = (res) ->
      error txself, res
  @executes.push getOptions(
    query: [ sql ].concat(values or [])
    path: @dbPath
  , successcb, errorcb)

SQLitePluginTransaction::complete = (success, error) ->
  begin_opts = undefined
  commit_opts = undefined
  errorcb = undefined
  executes = undefined
  opts = undefined
  successcb = undefined
  txself = undefined
  throw new Error("Transaction already run")  if @__completed
  @__completed = true
  txself = this
  successcb = (res) ->
    success txself, res

  errorcb = (res) ->
    error txself, res

  begin_opts = getOptions(
    query: [ "BEGIN;" ]
    path: @dbPath
  )
  commit_opts = getOptions(
    query: [ "COMMIT;" ]
    path: @dbPath
  , successcb, errorcb)
  executes = [ begin_opts ].concat(@executes).concat([ commit_opts ])
  opts = executes: executes
  Cordova.exec "SQLitePlugin.backgroundExecuteSqlBatch", opts
  @executes = []

So the member functions are much better to work with after going through the round trip and now the issue remains with the constructor.

Constructor function

So all that remains of the class declaration:ss

class SQLitePluginTransaction


  constructor: (@dbPath) ->
    @executes = []

So to replace this with the constructor function, we can extract the constructor function from the generated code above leaving out the extra declaration block:
  SQLitePluginTransaction = (dbPath) ->
    @dbPath = dbPath
    @executes = []

But there is something that js2coffee does not tell us: we need to add an empty return statement otherwise the constructor function will return the last expression evaluated and the user application code will fail. So here is the desired function constructor code:

SQLitePluginTransaction = (dbPath) ->
    @dbPath = dbPath
    @executes = []
    return

and after a round trip:

SQLitePluginTransaction = (dbPath) ->
  @dbPath = dbPath
  @executes = []

SQLitePluginTransaction::executeSql = (sql, values, success, error) ->
  errorcb = undefined
  successcb = undefined
  txself = undefined
  txself = this
  successcb = null
  if success
    successcb = (execres) ->
      res = undefined
      saveres = undefined
      saveres = execres
      res =
        rows:
          item: (i) ->
            saveres.rows[i]

          length: saveres.rows.length

        rowsAffected: saveres.rowsAffected
        insertId: saveres.insertId or null

      success txself, res
  errorcb = null
  if error
    errorcb = (res) ->
      error txself, res
  @executes.push getOptions(
    query: [ sql ].concat(values or [])
    path: @dbPath
  , successcb, errorcb)

SQLitePluginTransaction::complete = (success, error) ->
  begin_opts = undefined
  commit_opts = undefined
  errorcb = undefined
  executes = undefined
  opts = undefined
  successcb = undefined
  txself = undefined
  throw new Error("Transaction already run")  if @__completed
  @__completed = true
  txself = this
  successcb = (res) ->
    success txself, res

  errorcb = (res) ->
    error txself, res

  begin_opts = getOptions(
    query: [ "BEGIN;" ]
    path: @dbPath
  )
  commit_opts = getOptions(
    query: [ "COMMIT;" ]
    path: @dbPath
  , successcb, errorcb)
  executes = [ begin_opts ].concat(@executes).concat([ commit_opts ])
  opts = executes: executes
  Cordova.exec "SQLitePlugin.backgroundExecuteSqlBatch", opts
  @executes = []

Much better, hey? The only major problem I see is that the empty return statements were forgotten by js2coffee. Yeah some extra undefined declarations can also be removed. But at least this is usable, in case I receive patches to the Javascript version.

Class members and methods

I have actually just learned something about how class members work in CoffeeScript, which will lose their meaning in the translation to Javascript. So for one of the classes, I had first factored the regular member functions into prototype members and the following remains:

class SQLitePlugin

  constructor: (@dbPath, @openSuccess, @openError) ->
    throw new Error "Cannot create a SQLitePlugin instance without a dbPath" unless dbPath
    @openSuccess ||= () ->
      console.log "DB opened: #{dbPath}"
      return
    @openError ||= (e) ->
      console.log e.message
      return
    @open(@openSuccess, @openError)

  # Note: Class member
  # All instances will interact directly on the prototype openDBs object.
  # One instance that closes a db path will remove it from any other instance's perspective as well.
  openDBs: {}

  # Note: Class method
  @handleCallback: (ref, type, obj) ->
    callbacks[ref]?[type]?(obj)
    callbacks[ref] = null
    delete callbacks[ref]
    return

Which becomes after a round-trip:

SQLitePlugin = (->
  SQLitePlugin = (dbPath, openSuccess, openError) ->
    @dbPath = dbPath
    @openSuccess = openSuccess
    @openError = openError
    throw new Error("Cannot create a SQLitePlugin instance without a dbPath")  unless dbPath
    @openSuccess or (@openSuccess = ->
      console.log "DB opened: " + dbPath
    )
    @openError or (@openError = (e) ->
      console.log e.message
    )
    @open @openSuccess, @openError
  SQLitePlugin::openDBs = {}
  SQLitePlugin.handleCallback = (ref, type, obj) ->
    _ref = undefined
    _ref[type] obj  if typeof _ref[type] is "function"  if (_ref = callbacks[ref])?
    callbacks[ref] = null
    delete callbacks[ref]

  SQLitePlugin
)()

and the hints show up again: class member like SQLitePlugin::openDBs = ... and class method like SQLitePlugin.handleCallback = ...

So to fix the class member and method:
@@ -62,13 +61,13 @@ class SQLitePlugin
       return
     @open(@openSuccess, @openError)
 
-  # Note: Class member
-  # All instances will interact directly on the prototype openDBs object.
-  # One instance that closes a db path will remove it from any other instance's perspective as well.
-  openDBs: {}
+# Note: Class member
+# All instances will interact directly on the prototype openDBs object.
+# One instance that closes a db path will remove it from any other instance's perspective as well.
+SQLitePlugin::openDBs = {}
 
-  # Note: Class method
-  @handleCallback: (ref, type, obj) ->
+# Note: Class method (will be exported by a member of root.sqlitePlugin)
+SQLitePlugin.handleCallback = (ref, type, obj) ->
     callbacks[ref]?[type]?(obj)
     callbacks[ref] = null
     delete callbacks[ref]

So to fix the constructor:

@@ -50,8 +50,11 @@ getOptions = (opts, success, error) ->
     opts.callback = cbref(cb) if has_cbs
     opts
 
-class SQLitePlugin
-  constructor: (@dbPath, @openSuccess, @openError) ->
+# Prototype constructor function
+SQLitePlugin = (dbPath, openSuccess, openError) ->
+    @dbPath = dbPath
+    @openSuccess = openSuccess
+    @openError = openError
     throw new Error "Cannot create a SQLitePlugin instance without a dbPath" unless dbPath
     @openSuccess ||= () ->
       console.log "DB opened: #{dbPath}"
@@ -60,6 +63,7 @@ class SQLitePlugin
       console.log e.message
       return
     @open(@openSuccess, @openError)
+    return

Further

The one thing I have not really gone through here is to put the contents into a do -> block and then use the -b flag to tell the coffee compiler not to add an extra block. Then the results of round-trip transformations between CoffeeScript and Javascript should start to become less painful.

Wednesday, April 18, 2012

Cordova/PhoneGap iOS Plugin Return Result

Someone raised an excellent question on how to get the return result from a Cordova/PhoneGap iOS plugin. The articles from phonegap.com and from adobe.com do give an answer but you have to look very carefully and there is a much easier way which I will document here for the iOS and in another post for the Android.

Basically, a Cordova/PhoneGap plugin returns a result asynchronously by sending some Javascript code to be executed in the app's browser. Here is an example from the DatePicker plugin, with small adaptions to make it easier to read (extra spacing, using single quote in Javascript string):

- (void)actionSheet:(UIActionSheet *)actionSheet willDismissWithButtonIndex:(NSInteger)buttonIndex
{
    NSString* jsCallback = [NSString stringWithFormat:
        @"window.plugins.datePicker._dateSelected('%i');",
        (int)[self.datePicker.date timeIntervalSince1970]];
    [super writeJavascript:jsCallback];
}


So this code actually wants to execute a piece of code on the Javascript side to report a result that a date had been selected. Here is the code on the Javascript side that will be called back with the date that has been selected:

    DatePicker.prototype._dateSelected = function(date) {
        var d = new Date(parseFloat(date)*1000);
        if (this._callback)
            this._callback(d);
    }


Assuming you remember how prototype works you can see that this function will parse the value that is returned and make a callback to the Javascript application code, if it exists.


Update to answer a comment: the _callback member is set according to a parameter from the original function call, for example:
    DatePicker.prototype.show = function(options, cb) {
        // [options code omitted, TBD describe in another post]
        this._callback = cb;
        Cordova.exec("DatePicker.show", options);
    } 

The Cordova/PhoneGap documents describe how to use a CDVPluginResult object to return a plugin result object with data that will be sent to either a success callback or an error callback that was passed into cordova.exec() function but I find the method described here much easier to understand and debug.