Friday, October 11, 2013

Phonegap Diaries: Technologies

Introduction

Here I’m going to review the programming technologies used in the Twentyfour Challenge.  These include:
  •         Html5
  •          CSS3
  •          Javascript
  •          JQuery Mobile
  •          PhoneGap

Html5

There’s not too much to say about Html5 as I only used a couple of features that are specific to it:  local storage and the <audio> tag.  I also used a specific tag that is crucial to all PhoneGap projects.
Local storage is incredibly easy to use.  I use it to store the game options for Twentyfour and to keep track of the high scores.  The code below saves the game options:

function storeOptions () {
 // must use JSON stringify here!
 localStorage["tfc.options"] = JSON.stringify(options);
}

Here’s the code that reads the stored options:

function loadOptions () {     
 // are options in localStorage?
 if (localStorage["tfc.options"]) {
  options = JSON.parse(localStorage["tfc.options"]);
  // Options that are not strings need to be converted
  options.difficulty = Number(options.difficulty);
  options.count      = Number(options.count);      
  options.soundEnabled = Number(options.soundEnabled);
 }
 game.numSystem = options.numSystem;
 game.difficultyLevel = options.difficulty;
 game.countProblems = options.count;
 game.soundEnabled = options.soundEnabled;
};

The tag is complemented by a simple DOM-oriented API to play the sound file that is referenced.  It is also very easy to use.  The sound file (applause) worked in the Chrome browser, the iOS simulator, and an iPhone 4 device running iOS6.  It did not work in my own Android device (Samsung Galaxy Stellar I200), my iPad2, and another iPhone4 running iOS7.  I know I did everything right so I decided not to advertise the applause function.

A crucial <meta> tag needed by PhoneGap (really just the html5 compliant browser) is:
<meta name="viewport" content="width=device-width, height=device-height, initial-scale=1, minimum-scale=1, maximum-scale=1" >

There are many stackoverflow questions regarding the application’s web page being shown in a teensy-weensy font.  The solution is to add the above meta tag.

There’s one more thing that almost drove me crazy regarding a brower-based application – the back button.  The application runs as a “WebView” (Android terminology) where the browser does not display any “chrome”  (not Google Chrome, this is chrome like the back button, forward button, refresh, etc.).  This works fine on iOS but on Android there is a hardware back button.

I found that while playing a game of Twentyfour you must not use the back button at all as it destroys the state tracking that the app must perform.  I will discuss the solution later in this article.

CSS3

Most of the styling for Twentyfour comes from JQuery Mobile.  My hat is off to the professionals that developed CSS/html code that is portable across Safari, Chrome, and Internet Explorer, especially since the vendors of these browsers compete against one another.

I did, however, have to define a few styles for specific situations.  Here’s the problem “goal” which is really a button with some style changes.


The style changes are:

.goal-style   {
  text-align: center;
  background: chartreuse;
  ui-disabled: true;
}

 The trick to achieving this style is to add it dynamically when the page is rendered.  

            $('#problem-goal').addClass('goal-style');

The most important reason for learning CSS is due to the fact that JQuery uses its notion of a selector extensively.

Javascript

Ah, Javascript, how do I hate thee?  I speak these words as a Java programmer who trusts the statically typed language completely.  Remember that Twentyfour started as a native Android application that at its heart contained six classes with 775 lines of code (carriage returns counted here).  This includes a simple algebraic expression parser and evaluator.

Since I was skeptical of translating my Java code to Javascript by hand, I first sought an automated solution in the Google Web Toolkit (GWT).  Google created the GWT so they could create sophisticated browser-based applications (think Google Maps) in Java and then convert the client part to html/javascript.  I did get my 775 lines of java code translated by GWT, but then realized that there was no way to incorporate the generated javascript into a PhoneGap application.

So I hand translated to Javascript using techniques documented by David Flanagan in “Javascript: The Definitive Guide.”  It resulted in six classes and 588 lines of code.

Yes, there were type conversion surprises during development, but thorough testing flushed them out.  I developed unit test cases for the important code and gained the desired degree of confidence in the reincarnated algebraic expression parser and evaluator.

On every page of the application I included all of the Javascript files of the application (including JQuery Mobile files).  This was recommended in various books and Internet articles that I read.  It also benefits the use of a Javascript debugger.  Chrome, for example, will not allow you to set breakpoints on a page if it doesn’t see Javascript included on the page.

There are a couple of takeaways from this:
  1.        You must order your Javascript includes avoiding forward references.
  2.        You have to live with an enormous number of global variables.

JQuery Mobile

The technology I had the most difficulty with was JQuery Mobile (JQM).  The technology (including JQuery itself) has been going through many rapid revisions.  And there is the fundamental fact that JQM loads a document into the browser’s DOM and manipulates that one DOM thereafter.  This means that you get one chance to set up your event handlers.

Let me backtrack and try to re-express myself.  For the longest time I could not get my JQM page events to fire properly.  My stackoverflow searches resulted in conflicting information:  “use ‘bind’ on the pageinit event.”  “No use ‘on’ event.”  “Sorry you’re both wrong use ‘delegate’.”  No matter what I tried the JQM event never fired off my handler code.

Then I realized that the DOM is created exactly once by JQM and just updated when new pages are loaded via AJAX (really HJAX here).  The “binding” code that worked for me is shown below.

$(document).delegate('#index', 'pageinit', function() {
  console.log('#index: pageinit');
  // local event handlers
  $('#play').on('click', function() {
    console.log('play clicked');
    startGame();
  });
});

This breakthrough got me off the dime so I could learn the JQM widgets and events.  The most confusing event is one of the most important – the ‘click’ event.  Due to the nature of clicking using a mouse versus tapping on a touch sensitive screen, JQM differentiates separate events: ‘tap,’ ‘click,’ and ‘vclick.’  I started with ‘click’ but found it introduces an annoying 300 ms delay which isn’t good for rapid play.  So I switched to ‘tap’ which has no delay by occasionally has its own unwanted side-effects (ghost clicks).  ‘vclick’ wasn’t any improvement so I left the implementation with ‘tap.’  I finally learned that 'tap' works fine as long as you immediately stopped event propagation.

// event handlers
  
$("#score-ok-btn").tap (function(e) {
  e.preventDefault();
  gamePauseAudio();
  $.mobile.changePage('index.html');
});

Another surprise from JQM is that when you manipulate one of its widgets you must explicitly tell JQM to refresh its display of the widget.

$('#num-probs').val(game.countProblems).slider('refresh');

There were tons of stackoverflow questions asking, “Why doesn’t my updated widget show the update?”  Answer: “you didn’t refresh it.”

PhoneGap

As I mentioned in a previous blog PhoneGap gives you free packaging of your browser application and some level of Javascript access to native device APIs.

I thought I could live without any need for device-specific API but that Android hardware “back” button was a source of much aggravation.  If the user clicked in the middle of a game the entire game state would be invalid and many Javascript errors would occur.

So the back button became my nemesis as it has been forever for web developers.  I thought of several approaches:
  1. Disable the back button
  2. Manipulate the browser’s stack of “back” pages
  3. Catch every page transition and only allow the transitions for a “normal” game
  4. Detect when the hardware back button was pressed
Well #1 is prohibited.  #2 is difficult given the DOM APIs available.  #3 requires a lot of careful programming and is very brittle.  #4 is supported by PhoneGap.  After exploring #1 through #3, I found A PhoneGap API (see the Events API)  that solved the problem completely.  When the hardware back button is pressed on Android (iOS doesn’t have this) then just jump to the application homepage.  Thank you, PhoneGap!

PhoneGap also does the packaging and in this area it encourages you to use Adobe’s PhoneGap build service.  I did not use this approach for a couple of reasons:
  1. It costs money (there is a limited free plan)
  2. You need to package, deploy, and test your app on real devices before a release build.
The build service drives off the application’s config.xml file.  I discovered more documentation on config.xml at Adobe’s build site than I did at W3 Consortium (there’s a minor standard for widget packaging) and at the PhoneGap site.

It is not clear to me if the PhoneGap CLI tools process config.xml the same as the PhoneGap build service.

Summary

There’s a lot of technologies involve here.  JQuery Mobile keeps its promise for great looking portable UIs across mobile clients (Javascript too).  And PhoneGap delivers in its packaging for specific platforms.

Other postings:



No comments: