0
votes

I have written an application in Sencha Touch 2.1, of which I embed a package build into Cordova/PhoneGap 2.5.0 and compile in xCode to run on iOS Simulator / iOS. I have added the PGSQLite plugin to PhoneGap, and built my own PhoneGap/SQLite Proxy for Sencha, which I used on a few of my Stores.*

Problem: When I embed a package build into PhoneGap and run in iOS Simulator, I see that Cordova does not load before Sencha initializes. I see this because my calls in my Sencha app to Cordova.exec that I make in my Proxy initialization result in an error telling me that the Cordova object cannot be found.

I do successfully use Cordova.exec later in my application to run things like the Childbrowser plugin for PhoneGap, and it works. But using Cordova.exec at an early stage in the app's execution, i.e., initialization, is too soon to guarantee that the Cordova object will have been instantiated.

Already tried: I already tried the following approaches:

  1. I tried simply embedding the developer build of my Sencha app into PhoneGap. Although this worked, I don't want to deploy my development build as my released app because it is inefficient and takes up a lot of space. I have learned from this experiment, however, that the way the Sencha Touch microloader works on package and production builds loads PhoneGap after Sencha. This can be clearly seen when inspecting the DOM after Sencha loads in a package build.

  2. I have already configured my app.json file to include PhoneGap and my plugins before app.js and the Sencha Touch framework. Playing with the order of my JS file references in my app.json did not seem to affect the load order.

  3. I also tried creating a script loader, as described here (StackOverflow). I then ran the script loader for Cordova, and in the callback for that, ran the script loader for my plugin, and then, finally, in the callback for that, ran the Sencha Touch microloader. This resulted in an error. Additionally, I had to manually set that up in my index.html file after Sencha built my package. This seems unacceptable.

What I am looking for: I am looking for answers to the following:

  1. Is there a way to configure Sencha's microloader or my Sencha app in general so that Cordova is ensured to have loaded before Sencha's microloader runs?

  2. Is there a way to set this up so that using Sencha Cmd still works, and I don't have to hack around in my index.html file after I build the app?

Note: *Please don't suggest I use the existing, so-called, SQLite Proxy for Sencha. I specifically chose my approach because, though I appreciated the existing work on a SQLite proxy for Sencha Touch 2 (namely, this), it is actually a WebSQL proxy that does not store natively in SQLite on iOS. My proxy uses the PGSQLite plugin for PhoneGap to natively store data in SQLite on iOS. I plan to open-source it when I have an opportunity to clean it up and untangle it from my code.

1

1 Answers

0
votes

I ended up solving this myself by building a custom loader. I am not sure if there is a more Sencha-ish way to do it, but here are the details of what I did, which does work, in case anyone else wants to ensure that PhoneGap is completely loaded in package and production builds before running anything in Sencha. (That would probably be the case in all scenarios in which PhoneGap is packaging a Sencha app).

My index.html file:

<!DOCTYPE HTML>
<html manifest="" lang="en-US">
<head>
    <!-- Load Cordova first. Replace with whatever version you are using -->
    <script type="text/javascript" src="cordova.js"></script>    
    <script type="text/javascript" charset="utf-8">
        function onBodyLoad() {
            // Check for whatever mobile you will run your PhoneGap app
            // on. Below is a list of iOS devices. If you have a ton of
            // devices, you can probably do this more elegantly.
            // The goal here is to only listen to the onDeviceReady event
            // to continue the load process on devices. Otherwise you will
            // be waiting forever (literally) on Desktops.
            if ((navigator.platform == 'iPad') ||
                (navigator.platform == 'iPhone') ||
                (navigator.platform == 'iPod') ||
                (navigator.platform == 'iPhone Simulator') ||
                (navigator.platform == 'iPadSimulator')
               ) {
              // Listening for this event to continue the load process ensures
              // that Cordova is loaded.
              document.addEventListener("deviceready", onDeviceReady, false);
            } else {
                // If we're on Desktops, just proceed with loading Sencha.
                loadScript('loader.js', function() {
                    console.log('Finished loading scripts.');
                });
            }
        };

        // This function is a modified version of the one found on
        // StackOverflow, here: http://stackoverflow.com/questions/756382/bookmarklet-wait-until-javascript-is-loaded#answer-756526
        // Using this allows you to wait to load another script by
        // putting the call to load it in a callback, which is
        // executed only when the script that loadScript is loading has
        // been loaded.
        function loadScript(url, callback)
        {
            var head = document.getElementsByTagName("head")[0];
            var script = document.createElement("script");
            script.src = url;

            // Attach handlers for all browsers
            var done = false;
            script.onload = script.onreadystatechange = function()
            {
                if( !done && ( !this.readyState 
                            || this.readyState == "loaded" 
                            || this.readyState == "complete") )
                {
                    done = true;

                    // Continue your code
                    callback();
                }
            };

            head.appendChild(script);


        }

        function onDeviceReady() {
            console.log("[PhoneGap] Device initialized.");
            console.log("[PhoneGap] Loading plugins.");

            // You can load whatever PhoneGap plugins you want by daisy-chaining
            // callbacks together like I did with pgsqlite and Sencha.
            loadScript('pgsqlite_plugin.js', function() {
                console.log("[Sencha] Adding loader.");

                // The last one to load is the custom Sencha loader.
                loadScript('loader.js', function() {
                    console.log('Finished loading scripts.');
                });
            });

        };
    </script>

    <meta charset="UTF-8">
    <title>Sencha App</title>   
</head>
<!-- Don't forget to call onBodyLoad() in onLoad -->
<body onLoad="onBodyLoad();">
</body>
</html>

Next, create a custom loader in loader.js in your document root, alongside your index.html. This one is heavily based on the development microloader that comes with Sencha. Much props to them:

console.log("Loader included.");

(function() {
    function write(content) {
        document.write(content);
    }

    function meta(name, content) {
        write('<meta name="' + name + '" content="' + content + '">');
    }

    var global = this;

    if (typeof Ext === 'undefined') {
        var Ext = global.Ext = {};
    }

    var head = document.getElementsByTagName("head")[0];

    var xhr = new XMLHttpRequest();
    xhr.open('GET', 'app.json', false);
    xhr.send(null);

    var options = eval("(" + xhr.responseText + ")"),
        scripts = options.js || [],
        styleSheets = options.css || [],
        i, ln, path;

    meta('viewport', 'width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no');
    meta('apple-mobile-web-app-capable', 'yes');
    meta('apple-touch-fullscreen', 'yes');

    console.log("Loading stylesheets");
    for (i = 0,ln = styleSheets.length; i < ln; i++) {
        path = styleSheets[i];

        if (typeof path != 'string') {
            path = path.path;
        }

        var stylesheet = document.createElement("link");
        stylesheet.rel = "stylesheet";
        stylesheet.href = path;

        head.appendChild(stylesheet);
    }

    for (i = 0,ln = scripts.length; i < ln; i++) {
        path = scripts[i];

        if (typeof path != 'string') {
            path = path.path;
        }

        var script = document.createElement("script");
        script.src = path;
        head.appendChild(script);
    }

})();

Notice that your index.html file does not contain a #microloader script element. That's because you should take it out and use your custom loader.

With all that in place, you will be able to sail smoothly, knowing that the whole PhoneGap environment is in place before your Sencha javascript starts doing things.