Home Application-development PhoneGap 2.x Mobile Application Development HOTSHOT

PhoneGap 2.x Mobile Application Development HOTSHOT

By Kerri Shotts
books-svg-icon Book
Subscription
$10 p/m for first 3 months. $15.99 p/m after that. Cancel Anytime!
What do you get with a Packt Subscription?
This book & 7000+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with a Packt Subscription?
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with eBook + Subscription?
Download this book in EPUB and PDF formats, plus a monthly download credit
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with a Packt Subscription?
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with eBook?
Download this book in EPUB and PDF formats
Access this title in our online reader
DRM FREE - Read whenever, wherever and however you want
Online reader with customised display settings for better reading experience
What do you get with video?
Download this video in MP4 format
Access this title in our online reader
DRM FREE - Watch whenever, wherever and however you want
Online reader with customised display settings for better learning experience
What do you get with video?
Stream this video
Access this title in our online reader
DRM FREE - Watch whenever, wherever and however you want
Online reader with customised display settings for better learning experience
What do you get with Audiobook?
Download a zip folder consisting of audio files (in MP3 Format) along with supplementary PDF
What do you get with Exam Trainer?
Flashcards, Mock exams, Exam Tips, Practice Questions
Access these resources with our interactive certification platform
Mobile compatible-Practice whenever, wherever, however you want
BUY NOW $10 p/m for first 3 months. $15.99 p/m after that. Cancel Anytime!
Subscription
What do you get with a Packt Subscription?
This book & 7000+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with a Packt Subscription?
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with eBook + Subscription?
Download this book in EPUB and PDF formats, plus a monthly download credit
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with a Packt Subscription?
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with eBook?
Download this book in EPUB and PDF formats
Access this title in our online reader
DRM FREE - Read whenever, wherever and however you want
Online reader with customised display settings for better reading experience
What do you get with video?
Download this video in MP4 format
Access this title in our online reader
DRM FREE - Watch whenever, wherever and however you want
Online reader with customised display settings for better learning experience
What do you get with video?
Stream this video
Access this title in our online reader
DRM FREE - Watch whenever, wherever and however you want
Online reader with customised display settings for better learning experience
What do you get with Audiobook?
Download a zip folder consisting of audio files (in MP3 Format) along with supplementary PDF
What do you get with Exam Trainer?
Flashcards, Mock exams, Exam Tips, Practice Questions
Access these resources with our interactive certification platform
Mobile compatible-Practice whenever, wherever, however you want
  1. Free Chapter
    Let's Get Local!
About this book

Do you want to create mobile apps that run on multiple mobile platforms? With PhoneGap (Apache Cordova), you can put your existing development skills and HTML, CSS, and JavaScript knowledge to great use by creating mobile apps for cross-platform devices.

"PhoneGap 2.x Mobile Application Development Hotshot" covers the concepts necessary to let you create great apps for mobile devices. The book includes ten apps varying in difficulty that cover the gamut – productivity apps, games, and more - that are designed to help you learn how to use PhoneGap to create a great experience.

"PhoneGap 2.x Mobile Application Development Hotshot" covers the creation of ten apps, from their design to their completion, using the PhoneGap APIs. The book begins with the importance of localization and how HTML, CSS, and JavaScript interact to create the mobile app experience. The book then proceeds through mobile apps of various genres, including productivity apps, entertainment apps, and games. Each app covers specific items provided by PhoneGap that help make the mobile app experience better. This book covers the camera, geolocation, audio and video, and much more in order to help you create feature-rich mobile apps.

Publication date:
February 2013
Publisher
Packt
Pages
388
ISBN
9781849519403

 

Chapter 1. Let's Get Local!

There are a lot of languages in the world, and chances are good that you want your app to have the widest possible use and distribution, which means that you will need to provide your app with the ability to be multilingual. This can be a thorny task; a lot goes in to localization, translations, currency formats, date formats, and so on. But thankfully, there are some very smart people out there who have already worked through a lot of the pain involved. Now it's up to us to put that work to good use.

 

What do we build?


The project that we will create is a simple game entitled Quiz Time! The game will essentially ask the player ten random questions in their native language and then tally and present their score when the game is finished. At the end, the app will ask the user if they want to try again as well.

The app itself will serve to introduce you to creating mobile apps using a simple framework named YASMF (Yet Another Simple Mobile Framework). There are a multitude of fantastic frameworks out there (jQuery Mobile, jQuery Touch, iUI, Sencha Touch, and so on.), but the point of this book isn't to show you how to use a particular framework; rather, the point is to show you how to use PhoneGap to do some amazing things. The framework you choose to use ultimately doesn't really matter that much—they all do what they advertise—and our using a custom framework isn't intended to throw you off-kilter in any fashion. The main reason for using this particular custom framework is that it's very lightweight and simple, which means the concepts it uses will be easy to transfer to any framework. For more information regarding the framework, please visit https://github.com/photokandyStudios/YASMF/wiki.

The app itself will also serve as a foundation to creating localized apps in the future. Localization is absolutely critical to get right, even in the beginning stages of development, which is why we start with it here, and why we assign it such importance. In essence, this first project is intended to make the rest of your app development career easier.

What does it do?

As an app, Quiz Time! is pretty simple. There are only three screens, and only one of them is remotely complex. The game has ten built-in questions that it will randomly ask of the player. If the question is correct, the player is notified, and their score is increased by an arbitrarily large number. This is to show that we correctly handle the display of numbers in the player's locale. If the question is incorrect, we also notify the user, and then decrement their score. If they get enough questions wrong, they'll end up in negative territory, which is another great test for our localization skills.

Once the game is over, we'll display the score and the date to the player, along with the opportunity to try again. If the player does elect to try again, we'll reset everything and start the game over.

Why is it great?

You'll be primarily learning two things: building a simple game in PhoneGap, and localizing that app from the very beginning. A lot of projects forget about localization until near the end of the project, and then the poor developers find out that it is very difficult to shoehorn localization in after most of the project has already been developed. For example, the space assigned to some text might turn out to be too small for certain languages, or the images used as buttons or other widgets might not be large enough to hold the localized text. The app itself might crash in a certain language because it didn't expect to receive any non-English characters. By implementing localization at the start of your app development, you'll be saving yourself a lot of effort down the road, even if the first release of your app is only localized to one locale.

Note

You'll often see the word Cordova in our code examples in this book. PhoneGap was recently acquired by Adobe and the underlying code was given to the Apache Incubator project. This project is named Cordova, and PhoneGap utilizes it to provide its various services. So if you see Cordova, it really means the same thing for now.

How are we going to do it?

We're going to follow the typical development cycle: design, implement, and test the app. Our design phase won't just include the user interface, but also the data model, that is, how our questions are stored and retrieved. The implementation will focus on our three stages of the app: the start view, the game view, and the end view. After implementation, we'll test the app not only to make sure it properly handles localization but also to make sure that the game works correctly.

Here's the general outline:

  • Designing the app, UI/interactions

  • Designing the data model

  • Implementing the data model

  • Implementing the start view

  • Implementing the game view

  • Implementing the end view

  • Putting it all together

What do I need to get started?

First, be sure to download the latest version of PhoneGap from http://phonegap.com/download, currently 2.2.0 (as this was being written), and extract it to the appropriate directory. (For example, I use /Applications/phonegap/phonegap220.) Make sure that you have also installed the appropriate IDEs (Xcode for iOS development and Eclipse for Android development).

Next, download the latest version of the YASMF framework from https://github.com/photokandyStudios/YASMF/downloads, and extract it anywhere. (For example, I used my Downloads folder.)

If you want a copy of the projects for this book in order to look at, or to avoid the following project-creation steps, you can download them at https://github.com/photokandyStudios/phonegap-hotshot.

Next, you need to create a project for the various platforms you intend to support. Here's how we create both projects at once on Mac OS X. The commands should translate to Linux and Android-only projects with a little modification, and the same should apply to creating Android projects on Windows with some additional modification. For the following steps, consider $PROJECT_HOME to be the location of your project, $PHONEGAP_HOME to be the location where you installed PhoneGap, and $YASMF_DOWNLOAD to be the location where you extracted the YASMF framework.

Note

The following steps are just the steps that I use when setting up a project. You can, of course, structure it however you would like, but you'll need to make any modifications with regards to the file references and the likes on your own.

The following steps assume that you have downloaded PhoneGap (Cordova) 2.2.0. If you download a more recent version, the following steps should work with minimal modification:

Tip

Downloading the example code

You can download the example code files for all Packt books you have purchased from your account at http://www.packtpub.com. If you purchased this book elsewhere, you can visit http://www.packtpub.com/support and register to have the files e-mailed directly to you.

  1. Use the following code snippet:

    mkdir $PROJECT_HOME
    cd $PROJECT_HOME
    mkdir Android iOS www
    cd $PHONEGAP_HOME/lib/android/bin
    ./create $PROJECT_HOME/Android/QuizTime com.phonegaphotshot.QuizTime QuizTime
    cd $PHONEGAP_HOME/lib/ios/bin
    ./create $PROJECT_HOME/iOS com.phonegaphotshot.QuizTime QuizTime
    cd $PROJECT_HOME
    mkdir www/cordova
    cp Android/QuizTime/assets/www/cordova-2.2.0.js www/cordova/cordova-2.2.0-android.js
    cp iOS/www/cordova-2.2.0.js www/cordova/cordova-2.2.0-ios.js
    cd Android/QuizTime/assets
    rm –rf www
    ln –s ../../../www
    cd ../../../iOS
    rm –rf www
    ln  -s ../www
    cd ..
    cd www
    cp –r $YASMF_DOWNLOAD/framework .
    mkdir images models views style
    cd ..
    cd Android/QuizTime/src/com/phonegaphotshot/QuizTime
    edit QuizTime.java
    Change "index.html" to "index_android.html"
    Save the file.
    cd $PROJECT_HOME/iOS/QuizTime
  2. Edit Cordova.plist.

  3. Search for UIWebViewBounce.

  4. Change the <true/> tag just below it to <false/>.

  5. Search for ShowSplashScreenSpinner.

  6. Change the <true/> tag just below it to <false/>.

  7. Search for ExternalHosts.

  8. Remove the <array/> line and replace it with "<array>", "<string>*</string>" and "</array>". This isn't always something you'd want to do for a production app, but as it allows for our apps to access the Internet with no restrictions, it's good for testing purposes.

  9. Save the file.

  10. Start Eclipse.

  11. Navigate to File | New | Project….

  12. Select Android Project.

  13. Click on Next >.

  14. Select the Create project from existing source option.

  15. Click on Browse.

  16. Navigate to $PROJECT_HOME/Android/QuizTime/.

  17. Click on Open.

  18. Click on Next >.

  19. Uncheck and re-check the highest Google APIs entry. (For some reason, Eclipse doesn't always keep the correct SDK version when doing this, so you may have to go back after the project is created and reset it. Just right-click any directory, Configure Build Paths… and go to the Android section. Then you can re-select the highest SDK.)

  20. Click on Next >.

  21. Change the Minimum SDK value to 8.

  22. Click on Finish.

  23. Start Xcode.

  24. Navigate to File | Open….

  25. Navigate to the project in $PROJECT_HOME/iOS.

  26. Click on Open.

  27. At this point you should have Xcode and Eclipse open with the project. Close both; we'll be using our favorite editor for now.

When the project is created, the following directory structure results:

  • /Android: The Android project

  • /iOS: The iOS project

  • /www

    • /cordova: We'll place the PhoneGap support libraries here.

    • /framework: Our framework will be in this directory.

    • /cultures: Any localization configuration will be placed here. The framework comes with en-US.

    • /images: All of our images will be in this directory.

    • /views: All of our views will be here.

    • /models: All of our data models will be here.

    • /style: Any custom CSS we need to use will live here.

Once you've created the project, you also need to download the jQuery/Globalize repository from https://github.com/jquery/globalize. There's a lot of content there, but we're most interested in the lib directory. Copy the globalize.culture.en-US.js and globalize.culture.es-ES.js files to the www/framework/cultures directory. (If you want, feel free to copy other culture files as well, if you want to try your hand at localizing in a language you know.)

Note

If you are using Eclipse, you must make absolutely certain that all the files you use in the www directory are set to the proper encoding. The easiest way to do this is to right-click on the assets directory, click on Properties, and then click on Other. Select the UTF-8 option from the drop-down list and click on Apply. If you don't do this, it is entirely possible that some of your localized content will not be displayed correctly.

 

Designing the app – UI/interactions


In this first task we'll be designing the look and feel of the application as well as specifying the interactions between the various elements in the user interface and the player. For most of this task you can use pencil and paper or a graphics editor, though at some point you'll need a graphics editor such as Adobe Photoshop or GIMP in order to create some of the resources you'll need for the app.

Getting on with it

The difficulty when it comes to designing an app that can run on more than one platform is that each platform has many different ideas when it comes to how things should look on the screen. There are several ways to approach this; they are discussed as follows:

  • You can build the user interface for the majority of your market, and use the exact interface on all the other devices (but be careful; this will often lead to poor reviews).

  • You could decide to customize the app's user interface for each device. This often requires a significant amount of work to accomplish and get it just right, but it can be very rewarding, especially when the end user has no idea the app wasn't written just for their own platform.

  • Or you could create a platform-agnostic look and feel. This is the direction we'll take in this app. The interface would be reasonably at home on iOS and Android devices. That's not to say that the appearance will be identical on both devices; it won't, but it will be similar while incorporating some of the platform-specific notions as well.

Before we go too much further, we need to get out our pencil and paper and sketch out an idea of how we want our app to look. It should be similar to the following screenshot:

Our start view is a fairly simple view. At the top of the view we will have a navigation bar containing our app's title. In other views, this bar would often have other buttons in it, including one to go back to the previous view. At the bottom of the view, we will have a toolbar which will contain buttons relevant to the current view.

The app's title will be an image containing the title of the application. This image should be made using a font that's fun and stylistic. The image will be appropriately localized.

We'll have one button in the toolbar: a Start button. The text needs to be localized.

Below the navigation bar is the content area. Here we describe what the app will do. We won't have anything terribly fancy here; our space is limited, especially since we are restricted to the phone's screen size. In the future, we'll talk about how to allow content to scroll, but for now we'll keep it short and simple.

We do want to add a little bit of pizazz to the view, so we'll add a color splash to the background. You could make this anything you want, we'll go with rays of color shooting up from the bottom.

Our game view looks like the following screenshot:

Our game view is the most complex view we have in this app. Let's start outward and work in.

At the top, our navigation bar will indicate the current question number. This lets the player know how many questions they've answered. To the left of the text is a Back button. If clicked, it should take the player back to the start view.

At the bottom, our toolbar contains a single button: Skip. This button will allow the player to skip any question they don't want to answer. For now, we won't assign any penalty to skipping a question, but you could always add a score deduction or something worse if you wanted to do so. If you removed the button entirely, it would be wise to remove the toolbar as well.

In the middle is our content area, the most complex portion of the view; at the top we have the player's score, which needs to be localized. Below it is the question being asked, again, properly localized.

Below the question, we have several buttons; these need to be generated dynamically based on the question being asked. Not every question will have three answers; there may be some with two answers or some with four or more. The answers themselves also need to be properly localized.

Tapping a button will check to see if the button is labeled with the correct answer. If it is, we'll display a nice notice and increment the score. If it isn't, we'll indicate such and decrement the score.

After a question is answered or skipped, we'll get a new question and display it on the screen. Then, after ten questions have been answered, we'll end the game. The end view looks like the following screenshot:

The end view is similar to the start view in that it isn't terribly complex, but it does have a little more going on. It needs to properly display the final score and permit the player to play the game again.

The navigation bar contains the text Results and also a Back button. If tapped, it is the same thing as starting the game all over again.

The toolbar contains the Try Again? button. If tapped, it also starts the game again.

In the content area, we display a message containing the final score, and the date when it was achieved.

Of course, all of the content on the view needs to be properly localized. Numbers are hard enough; dates are even worse. It's a good thing that we have jQuery/Globalize to fall back on, or we'd have to do the hard work of localizing the date ourselves.

Now that we've sketched the user interface, it's time to start building some of the resources we'll need in our app. Open up your graphics editor and build a template of what any one of the views would look like. What we're doing here is determining what parts of the display will need to have images generated, and what parts will be able to be text or CSS-generated.

It's not super critical that you have the exact dimensions of any specific device. After all, the app can run on many different devices, each of which has a different screen size. We'll use 640 x 920 px, which just happens to be the available area on the screen for an iPhone 4 with a Retina display.

Note

You do need to design using a high-enough resolution to get Retina-quality assets out of the design. That is, if you expect an icon to be 32 x 32 px, you will actually want it to be 64 x 64 px. Now whether you build on an exact size is up to you, but it's best to target the device you think will get the most use.

Here's the final template we're using:

There's a little bit of a texture there. While it's possible to do this in CSS, it's easiest to use images instead. The texture itself is tile-able and so it can adapt to any screen size. The navigation bar should be placed in the images directory and named NavigationBar.png.

Notice the title? While this could also be handled by CSS and adding the font to your app, that gets into a lot of sticky licensing issues. Instead, we'll use an image of it, which means the font itself will never get distributed. The title should be placed in the images directory and named AppTitle-enus.png. The Spanish version (which should read ¡Examen Tiempo!) should be named AppTitle-eses.png.

The background will also be an image, though you could likely approximate it with CSS (though getting the texture there would be a bit painful). Since we're supporting many platforms and screen sizes, the image approach is the best. This image should be saved in the images directory and named Background.jpg.

We'll build the app so that the image stretches to fill the screen. There will be some minor distortion, of course, but since this image is just a color splash, it doesn't really matter. (Other options include creating the background at various resolutions, or to create a tile-able background that fills easily to any resolution.)

The button, on the other hand, is easy to build in CSS, and it's easy enough to get right on many platforms. In the worst case, the button won't be quite as shiny or rounded, but it'll still convey that it should be touched.

The middle area is where everything else will go, the player's score, the current question, the answers to the question, and so on. Since all of that is easily achievable with HTML, CSS, and JavaScript, we're not going to worry about putting those elements into the template.

What did we do?

In this task we designed our user interface and also spelled out the interaction between the various views and widgets. We indicated what parts we knew would need to be localized (everything!) and then drew up a pretty version of it in our favorite graphics editor. From this version we can splice the various elements that need to be saved as images while also identifying what portions can be rendered with HTML, CSS, and JavaScript.

 

Designing the data model


The data model is very important to get right: this is how we'll store our questions, the answers to those questions, and which answer is the correct answer for each of the questions. We'll also define how we should interact with the model, that is, how do we get a question, ask it if the answer is correct, and so forth.

Getting ready

Let's get out our pencil and paper again, or if you'd prefer, a diagramming tool that you're comfortable with. What we're really trying to do in this step is to come up with the properties the model needs in order to store the questions, and the interactions it will need in order to properly do everything we're asking of it.

Getting on with it

We'll essentially have two data models: a single question, and a collection of questions. Let's start with what the question model should do:

  • Store the actual question

  • Have a list of all the possible answers

  • Know the correct answer

  • Set the question when created

  • Return the question when asked

  • Add an answer to its list of answers

  • Return the list of answers when asked (in a random order)

  • Set the correct answer

  • Give the correct answer when asked

  • Return a specific answer in the list when asked

  • Check if a given answer is correct

  • Return the number of answers

We can indicate this by creating a simple diagram as follows:

Our collection of questions should:

  • Have a list of all the questions

  • Be able to add a question to that list

  • Return the total number of questions in the list

  • Return a random question from the list

The diagram covering these points would look like the following screenshot:

Having both of the models defined, let's come up with the questions we're going to ask, as well as the answers that will go along with them (for the full list of questions, see chapter1/www/models/quizQuestions.js in the download for this book):

#

English

Spanish

1

What is the color of the Sun?

¿Cuál es el color del Sol?

 

Green

Verde

 

White

Blanco

 

Yellow (correct)

Amarillo (correct)

2

What is the name of the fourth planet?

¿Cuál es el nombre del cuarto planeta?

 

Mars (correct)

Marzo (correct)

 

Venus

Venus

 

Mercury

Mercurio

With the design of our model complete, and the questions we're going to ask, this task is complete. Next we'll write the code to implement the model.

 

What did we do?


In this task we designed two data models: a single question and a collection of questions. We also determined the questions we were going to ask, along with their localized variants.

 

Implementing the data model


We'll be creating two JavaScript files in the www/models directory named quizQuestion.js and quizQuestions.js. The file quizQuestion.js will be the actual model: it will specify how the data should be formatted and how we can interact with it. quizQuestions.js will contain our actual question data.

Getting on with it

Before we define our model, let's define a namespace where it will live. This is an important habit to establish since it relieves us of having to worry about whether or not we'll collide with another function, object, or variable of the same name.

While there are various methods used to create a namespace, we're going to do it simply using the following code snippet:

// quizQuestion.js

var QQ = QQ || {};

Now that our namespace is defined, we can create our question object as follows:

QQ.Question = function ( theQuestion )
{
    var self = this;

Note

Note the use of self: this will allow us to refer to the object using self rather than using this. (Javascript's this is a bit nuts, so it's always better to refer to a variable that we know will always refer to the object.)

Next, we'll set up the properties based on the diagram we created from step two using the following code snippet:

    self.question = theQuestion;
    self.answers = Array();
    self.correctAnswer = -1;

We've set the self.correctAnswer value to -1 to indicate that, at the moment, any answer provided by the player is considered correct. This means you can ask questions where all of the answers are right.

Our next step is to define the methods or interactions the object will have. Let's start with determining if an answer is correct. In the following code, we will take an incoming answer and compare it to the self.correctAnswer value. If it matches, or if the self.correctAnswer value is -1, we'll indicate that the answer is correct:

    self.testAnswer = function( theAnswerGiven )
    {
        if ((theAnswerGiven == self.correctAnswer) 
            || (self.correctAnswer == -1))
        {
            return true;
        }    
        else
        {
            return false;
        }
    }

We're going to need a way to access a specific answer, so we'll define the answerAtIndex function as follows:

    self.answerAtIndex = function ( theIndex )
    {
        return self.answers[ theIndex ];
    }

To be a well-defined model, we should always have a way of determining the number of items in the model as shown in the following code snippet:

    self.answerCount = function ()
    {
        return self.answers.length;
    }

Next, we need to define a method that allows an answer to be added to our object. Note that with the help of the return value, we return ourselves to permitting daisy-chaining in our code:

    self.addAnswer = function( theAnswer )
    {
        self.answers.push ( theAnswer );
        return self;
    }

In theory we could display the answers to a question in the order they were given to the object. In practice, that would turn out to be a pretty boring game: the answers would always be in the same order, and chances would be pretty good that the first answer would be the correct answer. So let's give ourselves a randomized list using the following code snippet:

    self.getRandomizedAnswers = function ()
    {
        var randomizedArray = Array();
        var theRandomNumber;
        var theNumberExists;
        
        // go through each item in the answers array
        for (var i=0; i<self.answers.length; i++)
        {
        
            // always do this at least once
            do 
            {
                // generate a random number less than the 
                // count of answers
                theRandomNumber = Math.floor ( Math.random() * 
                                  self.answers.length );
                theNumberExists = false;
                
                // check to see if it is already in the array
                for (var j=0; j<randomizedArray.length; j++)
                {
                    if (randomizedArray[j] == theRandomNumber)
                    {
                        theNumberExists = true;
                    }
                }
                
                // If it exists, we repeat the loop.
            } while ( theNumberExists );
            
            // We have a random number that is unique in the   
            // array; add it to it.
            randomizedArray.push ( theRandomNumber );
        }
        
        return randomizedArray;
    }

The randomized list is just an array of numbers that indexes into the answers[] array. To get the actual answer, we'll have to use the answerAtIndex() method.

Our model still needs a way to set the correct answer. Again, notice the return value in the following code snippet permitting us to daisy-chain later on:

    self.setCorrectAnswer = function ( theIndex )
    {
        self.correctAnswer = theIndex;
        return self;
    }

Now that we've properly set the correct answer, what if we need to ask the object what the correct answer is? For this let's define a getCorrectAnswer function using the following code snippet:

    self.getCorrectAnswer = function ()
    {
        return self.correctAnswer;
    }

Of course, our object also needs to return the question given to it whenever it was created; this can be done using the following code snippet:

    self.getQuestion = function()
    {
        return self.question;
    }
}

That's it for the question object. Next we'll create the container that will hold all of our questions using the following code line:

QQ.questions = Array();

We could go the regular object-oriented approach and make the container an object as well, but in this game we have only one list of questions, so it's easier to do it this way.

Next, we need to have the ability to add a question to the container, this can be done using the following code snippet:

QQ.addQuestion = function (theQuestion)
{
    QQ.questions.push ( theQuestion );
}

Like any good data model, we need to know how many questions we have; we can know this using the following code snippet:

QQ.count = function ()
{
    return QQ.questions.length;
}

Finally, we need to be able to get a random question out of the list so that we can show it to the player; this can be done using the following code snippet:

QQ.getRandomQuestion = function ()
{
    var theQuestion = Math.floor (Math.random() * QQ.count());
    return QQ.questions[theQuestion];
}

Our data model is officially complete. Let's define some questions using the following code snippet:

// quizQuestions.js
//
// QUESTION 1
//
QQ.addQuestion ( new QQ.Question ( "WHAT_IS_THE_COLOR_OF_THE_SUN?" )
                       .addAnswer( "YELLOW" )
                       .addAnswer( "WHITE" )
                       .addAnswer( "GREEN" )
                       .setCorrectAnswer ( 0 ) );

Notice how we attach the addAnswer and setCorrectAnswer methods to the new question object. This is what is meant by daisy-chaining: it helps us write just a little bit less code.

You may be wondering why we're using upper-case text for the questions and answers. This is due to how we'll localize the text, which is next:

PKLOC.addTranslation ( "en", "WHAT_IS_THE_COLOR_OF_THE_SUN?", "What is the color of the Sun?" );
PKLOC.addTranslation ( "en", "YELLOW", "Yellow" );
PKLOC.addTranslation ( "en", "WHITE",  "White" );
PKLOC.addTranslation ( "en", "GREEN",  "Green" );

PKLOC.addTranslation ( "es", "WHAT_IS_THE_COLOR_OF_THE_SUN?", "¿Cuál es el color del Sol?" );
PKLOC.addTranslation ( "es", "YELLOW", "Amarillo" );
PKLOC.addTranslation ( "es", "WHITE",  "Blanco" );
PKLOC.addTranslation ( "es", "GREEN",  "Verde" );                        

The questions and answers themselves serve as keys to the actual translation. This serves two purposes: it makes the keys obvious in our code, so we know that the text will be replaced later on, and should we forget to include a translation for one of the keys, it'll show up in uppercase letters.

PKLOC as used in the earlier code snippet is the namespace we're using for our localization library. It's defined in www/framework/localization.js. The addTranslation method is a method that adds a translation to a specific locale. The first parameter is the locale for which we're defining the translation, the second parameter is the key, and the third parameter is the translated text.

The PKLOC.addTranslation function looks like the following code snippet:

PKLOC.addTranslation = function (locale, key, value)
{
  if (PKLOC.localizedText[locale])
  {
    PKLOC.localizedText[locale][key] = value;
  }
  else
  {
    PKLOC.localizedText[locale] = {};
    PKLOC.localizedText[locale][key] = value;
  }
}

The addTranslation method first checks to see if an array is defined under the PKLOC.localizedText array for the desired locale. If it is there, it just adds the key/value pair. If it isn't, it creates the array first and then adds the key/value pair. You may be wondering how the PKLOC.localizedText array gets defined in the first place. The answer is that it is defined when the script is loaded, a little higher in the file:

PKLOC.localizedText = {};

Continue adding questions in this fashion until you've created all the questions you want. The quizQuestions.js file contains ten questions. You could, of course, add as many as you want.

What did we do?

In this task, we created our data model and created some data for the model. We also showed how translations are added to each locale.

What else do I need to know?

Before we move on to the next task, let's cover a little more of the localization library we'll be using. Our localization efforts are split into two parts: translation and data formatting.

For the translation effort, we're using our own simple translation framework, literally just an array of keys and values based on locale. Whenever code asks for the translation for a key, we'll look it up in the array and return whatever translation we find, if any. But first, we need to determine the actual locale of the player, using the following code snippet:

// www/framework/localization.js

PKLOC.currentUserLocale = "";

PKLOC.getUserLocale = function()
{

Determining the locale isn't hard, but neither is it as easy as you would initially think. There is a property (navigator.language) under WebKit browsers that is technically supposed to return the locale, but it has a bug under Android, so we have to use the userAgent. For WP7, we have to use one of three properties to determine the value.

Because that takes some work, we'll check to see if we've defined it before; if we have, we'll return that value instead:

  if (PKLOC.currentUserLocale)
  {
    return PKLOC.currentUserLocale;
  }

Next, we determine the current device we're on by using the device object provided by Cordova. We'll check for it first, and if it doesn't exist, we'll assume we can access it using one of the four properties attached to the navigator object using the following code snippet:

  var currentPlatform = "unknown";
  if (typeof device != 'undefined')
  {
    currentPlatform = device.platform;
  }

We'll also provide a suitable default locale if we can't determine the user's locale at all as seen in the following code snippet:

  var userLocale = "en-US";

Next, we handle parsing the user agent if we're on an Android platform. The following code is heavily inspired by an answer given online at http://stackoverflow.com/a/7728507/741043.

  if (currentPlatform == "Android")
  {
    var userAgent = navigator.userAgent;

    var tempLocale = userAgent.match(/Android.*([a-zA-Z]{2}-[a-zA-Z]{2})/);
    if (tempLocale)
    {
        userLocale = tempLocale[1];
    }
  }

If we're on any other platform, we'll use the navigator object to retrieve the locale as follows:

  else
  {
    userLocale = navigator.language ||
                 navigator.browserLanguage ||
                 navigator.systemLanguage ||
                 navigator.userLanguage;
}

Once we have the locale, we return it as follows:

  PKLOC.currentUserLocale = userLocale;
  return PKLOC.currentUserLocale;
}

This method is called over and over by all of our translation codes, which means it needs to be efficient. This is why we've defined the PKLOC.currentUserLocale property. Once it is set, the preceding code won't try to calculate it out again. This also introduces another benefit: we can easily test our translation code by overwriting this property. While it is always important to test that the code properly localizes when the device is set to a specific language and region, it often takes considerable time to switch between these settings. Having the ability to set the specific locale helps us save time in the initial testing by bypassing the time it takes to switch device settings. It also permits us to focus on a specific locale, especially when testing.

Translation of text is accomplished by a convenience function named __T(). The convenience functions are going to be our only functions outside of any specific namespace simply because we are aiming for easy-to-type and easy-to-remember names that aren't arduous to add to our code. This is especially important since they'll wrap every string, number, date, or percentage in our code.

The __T() function depends on two functions: substituteVariables and lookupTranslation. The first function is defined as follows:

PKLOC.substituteVariables = function ( theString, theParms )
{
  var currentValue = theString;

  // handle replacement variables
  if (theParms)
  {
    for (var i=1; i<=theParms.length; i++)
    {
      currentValue = currentValue.replace("%" + i, theParms[i-1]);
    }
  }
  
  return currentValue;
}

All this function does is handle the substitution variables. This means we can define a translation with %1 in the text and we will be able to replace %1 with some value passed into the function.

The next function, lookupTranslation, is defined as follows:

PKLOC.lookupTranslation = function ( key, theLocale )
{
  var userLocale = theLocale || PKLOC.getUserLocale();
  
  if ( PKLOC.localizedText[userLocale] )
  {
    if ( PKLOC.localizedText[userLocale][key.toUpperCase()] )
    {
       return PKLOC.localizedText[userLocale][key.toUpperCase()];
    }
  }
  
  return null;
}

Essentially, we're checking to see if a specific translation exists for the given key and locale. If it does, we'll return the translation, but if it doesn't, we'll return null. Note that the key is always converted to uppercase, so case doesn't matter when looking up a translation.

Our __T() function looks as follows:

function __T(key, parms, locale)
{
  var userLocale = locale || PKLOC.getUserLocale();
  var currentValue = "";

First, we determine if the translation requested can be found in the locale, whatever that may be. Note that it can be passed in, therefore overriding the current locale. This can be done using the following code snippet:

  if (! (currentValue=PKLOC.lookupTranslation(key,  
                                       userLocale)) )
  {

Locales are often of the form xx-YY, where xx is a two-character language code and YY is a two-character character code. My locale is defined as en-US. Another player's might be defined as es-ES.

If you recall, we defined our translations only for the language. This presents a problem: the preceding code will not return any translation unless we defined the translation for the language and the country.

Note

Sometimes it is critical to define a translation specific to a language and a country. While various regions may speak the same language from a technical perspective, idioms often differ. If you use an idiom in your translation, you'll need to localize them to the specific region that uses them, or you could generate potential confusion.

Therefore, we chop off the country code, and try again as follows:

    userLocale = userLocale.substr(0,2);

    if (! (currentValue=PKLOC.lookupTranslation(key, userLocale)) )
    {

But we've only defined translations for English (en) and Spanish(es)! What if the player's locale is fr-FR (French)? The preceding code will fail, because we've not defined any translation for the fr language (French). Therefore, we'll check for a suitable default, which we've defined to be en-US, American English:

      userLocale = "en-US";      
      if (! (currentValue=PKLOC.lookupTranslation(key, userLocale)) )
      {

Of course, we are now in the same boat as before: there are no translations defined for en-US in our game. So we need to fall back to en as follows:

        userLocale = "en";      
        if (! (currentValue=PKLOC.lookupTranslation(key, userLocale)) )
        {

But what happens if we can't find a translation at all? We could be mean and throw a nasty error, and perhaps you might want to do exactly that, but in our example, we're just returning the incoming key. If the convention of capitalizing the key is always followed, we'll still be able to see that something hasn't been translated.

          currentValue = key;
        }
      }
    }
  }

Finally, we pass the currentValue parameter to the substituteVariables property in order to process any substitutions that we might need as follows:

  return PKLOC.substituteVariables( currentValue, parms 
         );
}
 

Implementing the start view


To create our view, we need to create the file for it first. The files should be called startView.html, and should live under the www/views directory. The view we're creating will end up looking like the following screenshot for iOS:

For Android (localized to Spanish), the view will be as follows:

Before we actually create the view though, let's define the structure of our view. Depending upon the framework in use, the structure of a view can be vastly different. For the YASMF framework, our view will consist of some HTML that will depend on some pre-defined CSS, and some JavaScript defined below that same HTML. You could easily make the case that the JavaScript and inline styles should be separated out as well, and if you wish, you can do so.

The HTML portion for all our views will be of the following form:

<div class="viewBackground">
 <div class="navigationBar">
  <div id="theView_AppTitle"></div>
  <button class="barButton backButton" 
   id="theView_backButton" style="left:10px" ></button>
 </div>
 <div class="content avoidNavigationBar avoidToolBar" 
  id="theView_anId">
 </div>
 <div class="toolBar">
  <button class="barButton" id="theView_aButton" 
   style="right:10px"></button>
 </div>
</div>

As you can see, there's no visible text anywhere in this code. Since everything must be localized, we'll be inserting the text programmatically via JavaScript.

The viewBackground class will be our view's container: everything related to the view's structure is defined within. The style is defined in www/framework/base.css and www/style/style.css; the latter is for our app's custom styles.

The navigationBar class indicates that the div class is just a navigation bar. For iOS users, this has instant meaning, but it should be pretty clear to everyone else: this bar holds the title of the view, as well as any buttons that serve for navigation (such as a back button). Notice that the title and back button both have id values. This value makes it easy for us to access them in our JavaScript later on. Notice also that we are namespacing these id values with the view name and an underscore; this is to prevent any issues with using the same id twice.

The next div class is given the class of content avoidNavigationBar avoidToolBar; this is where all the content will go. The latter two classes specify that it should be offset from the top of the screen and short enough to avoid both the navigation bar (already defined) and the toolbar (coming up).

Finally, the toolbar is defined. This is a bar much like the navigation bar, but is intended to hold buttons that are related to the view. For Android this would be commonly shown near or at the top of the screen, while for iPhone and WP7 display this bar is at the bottom. (iPad, on the other hand, would display this just below the navigation bar or on the navigation bar. We'll worry about that in Project 10, Scaling Up.)

Below this HTML block, we'll define any templates we may need for localization, and then finally, any JavaScript we need.

Getting on with it

With these pointers in mind, let's create our start view, which should be named startView.html in the www/views directory as follows:

<div class="viewBackground">
 <div class="navigationBar">
  <div id="startView_AppTitle"></div>
 </div>
 <div class="content avoidNavigationBar avoidToolBar"   
  id="startView_welcome">
 </div>
 <div class="toolBar">
  <button class="barButton" id="startView_startButton" 
   style="right:10px"></button>
 </div>
</div>

The preceding code looks almost exactly like our view template defined earlier except that we're missing a back button. This is due to the fact that the first view we display to the user doesn't have anything to go back to, so we omit that button. The id values have also changed to include the name of our view.

None of these define what our view will look like, though. To determine that, we need to override our framework styles in www/framework/base.css by setting them in www/style/style.css.

First, to define the look of navigationBar, we use the glossy black bar from our template defined earlier in this project as follows:

.navigationBar
{
  background-image: url(../images/NavigationBar.png);
  color: #FFF;
  background-color: transparent;
}

The toolbar is defined similarly as follows:

.toolBar
{
  background-image: url(../images/ToolBar.png);
}

The view's background is defined as follows:

.viewBackground
{
  background-image: url(../images/Background.jpg);
  background-size: cover;
}

That's everything needed to make our start view start to look like a real app. Of course, there's a lot of pre-built stuff in www/framework/base.css, which you're welcome to analyze and reuse in your own projects.

Now that we've defined the view and the appearance, we need to define some of the view's content. We're going to do this by using a couple of hidden div elements that have the locale attached to their id values, as follows:

<div id="startView_welcome_en" class="hidden">
 <h2>PhoneGap-Hotshot Sample Application</h2>
 <h3>Chapter 1: Let's Get Local!</h3>
 <p>This application demonstrates localization
    between two languages, based on your device's
    language settings. The two languages implemented
    are English and Spanish.</p>
</div>

<div id="startView_welcome_es" class="hidden">
 <h2>Ejemplo de aplicación de PhoneGap-Hotshot</h2>
 <h3>Capítulo 1: Let's Get Local!</h3>
 <p>Esta aplicación muestra la localización
     entre los dos idiomas, sobre la base de su dispositivo de
     la configuración de idioma. Las dos lenguas aplicadas
     son Inglés y Español.</p>
</div>

These two div elements are classed hidden so that they won't be visible to the player. We'll then use some JavaScript to copy their content to the content area inside the view. Easier than using the __T() and PKLOC.addTranslation() functions for all that text, isn't it?

Next comes the JavaScript as follows:

<script>
  var startView = $ge("startView") || {};  // properly namespace

Our first act is to put all our script into a namespace. Unlike most of our other namespace definitions, we're actually going to piggyback onto the "startView" element (which the astute reader will notice has not been defined yet; that'll be near the end of this project). While the element is a proper DOM element, it also serves as a perfect place for us to attach to, as long as we avoid any of the cardinal sins of using the DOM method names as our own, which, I promise, we won't do.

You might be wondering what $ge does. Since we're not including any JavaScript framework like jQuery, we don't have a convenience method to get an element by its ID. jQuery does this with the $() method, and because you might actually be using jQuery along with the framework we're using, I chose to use the $ge() method, short for get element. It's defined in www/framework/utility.js like the following code snippet and all it does is act as a shortened version of document.getElementById:

function $ge ( elementId )
{
  return document.getElementById ( elementId );
}

Getting back to our start view script, we define what needs to happen when the view is initialized. Here we hook into the various buttons and other interface elements that are in the view, as well as localize all the text and content as follows:

startView.initializeView = function ()
  {
    PKLOC.addTranslation ("en", "APP_TITLE_IMAGE", 
         "AppTitle-enus.png");
    PKLOC.addTranslation ("es", "APP_TITLE_IMAGE", 
         "AppTitle-eses.png");

    startView.applicationTitleImage = 
         $ge("startView_AppTitle");
    startView.applicationTitleImage.style.backgroundImage = 
        "url('./images/" + __T("APP_TITLE_IMAGE") + "')";

This is our first use of the __T() function. This is how we can properly localize an image. The APP_TITLE_IMAGE key is set to point at either the English version or the Spanish version of the title image, and the __T() function returns the correct one based on our locale.

    PKLOC.addTranslation ("en", "START", "Start");
    PKLOC.addTranslation ("es", "START", "Comenzar");

    startView.startButton = $ge("startView_startButton");
    startView.startButton.innerHTML = __T("START");

Now we've properly localized our start button, but how do we make it do anything? We use a little function defined in www/framework/ui-core.js called PKUI.CORE.addTouchListener() as follows:

    PKUI.CORE.addTouchListener( startView.startButton, 
        "touchend", startView.startGame );

Finally, we need to display the correct welcome text in the content area using the following code snippet:

    var theWelcomeContent = $geLocale("startView_welcome");
    $ge("startView_welcome").innerHTML = 
        theWelcomeContent.innerHTML;
  }

We now introduce another convenience function: the $geLocale() function. This function acts like the $ge() function, except that it assumes there will be a locale appended to the ID of the element we're asking for. It's defined in the same file (utility.js) and looks like the following:

function $geLocale ( elementId )
{
  var currentLocale = PKLOC.getUserLocale();
  var theLocalizedElementId = elementId + "_" + currentLocale;
  if ($ge(theLocalizedElementId)) { return 
      $ge(theLocalizedElementId); }

  theLocalizedElementId = elementId + "_" + 
      currentLocale.substr(0,2);
  if ($ge(theLocalizedElementId)) { return 
      $ge(theLocalizedElementId); }

  theLocalizedElementId = elementId + "_en-US";
  if ($ge(theLocalizedElementId)) { return 
      $ge(theLocalizedElementId); }

  theLocalizedElementId = elementId + "_en";
  if ($ge(theLocalizedElementId)) { return 
      $ge(theLocalizedElementId); }
  return $ge( elementId );
}

Much like our __T() function, it attempts to find an element with our full locale attached (that is, _xx-YY). If it can't find it, it tries _xx, and here it should succeed if our locale is English- or Spanish-speaking. If it isn't, we'll then look for _en-US, and if that isn't found, we'll look for _en. If no suitable element is found, we'll return the original element –which in our case doesn't exist, which means we'll return "undefined".

Next up in our start view script, we have the function that is called whenever the start button is tapped as shown in the following code snippet:

startView.startGame = function()
  {
    PKUI.CORE.pushView ( gameView );
  }
  
</script>

Real short, but packs a punch. This displays our game view to the player, which actually starts the game. For devices that support it (as this was being written, iOS and Android), the player also sees a nice animation between this view (start) and the next one (game).

If you want to know more about how the pushView() method works, visit https://github.com/photokandyStudios/YASMF/wiki/PKUI.CORE.pushView.

Whew! That was a lot of work for a pretty simple view. Thankfully, most of the work is actually done by the framework, so our actual startView.html file is pretty small.

What did we do?

We implemented our start view, which is presented to the player when they first launch the app. We properly localized the view's title image based on the player's locale, and we also properly localized HTML content based on the locale.

We defined the various hooks and text for the widgets on the view such as the Start button, and attached touch listeners to the them to make them function correctly.

We covered a portion of the framework that provides support for pushing views onto the screen as well.

What else do I need to know?

It probably doesn't take much to guess, but there are several complementary functions to the pushView method: popView, showView, and hideView.

The popView function does the exact opposite of pushView, that is, it moves the views right (instead of left) by popping them off the view stack.

The showView and hideView functions do essentially the same thing, but simpler. They don't do any animation at all. Furthermore, since they don't involve any other view on the stack, they are most useful at the beginning of an app when we have to figure out how to display our very first view with no previous view to animate.

If you want to know more about view management, you might want to visit https://github.com/photokandyStudios/YASMF/wiki/Understanding-the-View-Stack-and-View-Management and explore https://github.com/photokandyStudios/YASMF/wiki/PKUI.CORE.

 

Implementing our game view


To get started, create a file named gameView.html under the www/views directory. When we're done, we'll have a view that looks like the following screenshot for iOS:

For Android, the view will look like the following screenshot:

Now, before we get too deep into the view itself, let's go over the view stack and how it helps us deal with navigation. The view stack is shown in the following screenshot:

The view stack is really just a stack that maintains the list of previously visible views and the currently visible view. When our app first starts, the stack will be empty as identified in the first step in the preceding screenshot. Then, the startView view is pushed onto the stack using the showView method, and you have the stack in (2). When the player taps the Start button, the gameView view is pushed onto the stack, which results in the stack as seen in (3). Then, when the game is over, we'll push the endView view on the stack, resulting in (4).

Because we're tracking all these views, including the ones that are no longer visible (especially at the end of the game), it makes it easy to go back to a previous view. For iOS, this is done via the back button. For Android, the device often has a physical back button that is used instead. Regardless of how a back event is triggered, we need to be able to go backwards in the stack.

Let's say that the user now decides to go back in the stack; we would have the stack in (5). If they decide to go back another step, (6) would result. At this point, iOS would permit no further backtracking, but for Android, another back event should exit the user out of the app.

Getting on with it

The game view will be very similar to our start view, except that it is a little more complicated. After all, it plays an entire game. Thankfully, there's really nothing terribly new here, so it should be smooth going.

Let's start with the HTML portion of the view given as follows:

<div class="viewBackground">
 <div class="navigationBar">
  <div id="gameView_title"></div>
  <button class="barButton backButton" 
   id="gameView_backButton" style="left:10px"></button>
 </div>
 <div class="content avoidNavigationBar avoidToolBar" 
  id="gameView_gameArea">
  <div id="gameView_scoreArea" style="height:1em; text-  
   align: center;"></div>
  <div id="gameView_questionArea" style="text-align: 
   center"></div>
 </div>
 <div class="toolBar">
  <button class="barButton" id="gameView_nextButton" 
   style="right:10px" ></button>
 </div>
</div>

I've highlighted what's new in the earlier code, but there is not much as you can see. First we've defined a back button that lives in the navigation bar, and in the content area we've defined two new areas: one for the player's score, and another for the actual question (and answers).

Up next, while similar to the localized content in the start view, we have templates that specify how a question and its answers are displayed; this is given as follows:

<div id="gameView_questionTemplate" class="hidden">
 <h2>%QUESTION%</h2>
 <div style="text-align:center;">%ANSWERS%</div>
</div>

First, we define the question template, which consists of a second-level heading that will have the question's text, and a div element that will contain all the answers. But what will the answers look like? That's next:

<div id="gameView_answerTemplate" class="hidden">
 <button class="barButton answerButton" 
  onclick="gameView.selectAnswer(%ANSWER_INDEX%);">%ANSWER%
 </button><br/>
</div>

Each answer will be presented as a button with the answer text inside, and an onclick event attached to call the gameView.selectAnswer() method with the selected answer.

Of course, as these are templates, they don't appear to the player, and so they are given the hidden class. But we'll definitely make use of them in our JavaScript when we construct an actual random question to display to the player. Let's go over the script now:

<script>

  var gameView = $ge("gameView") || {};

  gameView.questionNumber = -1;
  gameView.score = 0;
  gameView.theCurrentQuestion;

By now you should be familiar with our namespacing technique, which comes first in our code. After that, though, we define the properties in our view. The question number, which will act as our counter so that when it reaches ten, we know the game is over; the score; and the current question. The latter isn't obvious, but it will be an actual question object, not an index to the object.

After that, we have the initializeView function, which will wire up all the widgets and do the localization of the text, as seen in the following code snippet:

gameView.initializeView = function ()
  {
  PKUTIL.include ( ["./models/quizQuestions.js", 
      "./models/quizQuestion.js"] );
  gameView.viewTitle = $ge("gameView_title");
  gameView.viewTitle.innerHTML = __T("APP_TITLE");

  gameView.backButton = $ge("gameView_backButton");
  gameView.backButton.innerHTML = __T("BACK");
  PKUI.CORE.addTouchListener(gameView.backButton, "touchend", 
      function () { PKUI.CORE.popView(); });

  gameView.nextButton = $ge("gameView_nextButton");
  gameView.nextButton.innerHTML = __T("SKIP");

  PKUI.CORE.addTouchListener(gameView.nextButton, "touchend", 
      gameView.nextQuestion);

  gameView.scoreArea = $ge("gameView_scoreArea");
  gameView.questionArea = $ge("gameView_questionArea");
}

I've highlighted a few areas in the preceding code block. The last ones are more or less the same, as we're storing the gameView_scoreArea and gameView_questionArea elements into properties for later use, so that's not really anything new. What is new about it is that we aren't loading any content into them yet.

The second highlight is not something you'd really ever add to a production game. You may ask, so why is it here? The idea is that this button lets us skip the current question without a penalty. Why? The answer is testing. I don't want to have to tap through an answer, tap through the alert saying if I got it right or wrong a million times to see if the localization is working for all the questions. Hence, skip was born.

The first highlight though, is more interesting. It's a JavaScript include. "Wait," I hear you saying, "JavaScript doesn't do includes." And you'd be right.

But, it is possible to simulate an include by using XmlHttpRequest, which is often referred to as AJAX. With this short include statement, we're asking the browser to load the two referenced JavaScript files (quizQuestions.js and quizQuestion.js) on our behalf. It's important that this happens too; otherwise, our game would have no questions!

The PKUTIL.include() function is defined in www/framework/utility.js. We'll worry about the full implementation details a little later in this project, but it would suffice to say, it does what it says. The scripts are loaded and waiting for us when we need to use the questions. (At this point the reader with a gazillion questions is asking this key question, "Does the order matter?", the answer is, "Yes." And you'll see why in a short bit.)

So now that we have the initialization for gameView down, let's look at another key method: viewWillAppear. It is shown in the following code snippet:

gameView.viewWillAppear = function ()
{
  gameView.questionNumber =1;
  gameView.score = 0;
  gameView.nextQuestion();
}

The latter part of this code is fairly innocuous. We set the question number to 1, the score to zero, and call the nextQuestion() method, which, as it is turns out, renders the next question and displays it to the player.

The viewWillAppear() function, as you may remember, is called by PKUI.CORE.pushView() and PKUI.CORE.showView() methods just prior to the actual animation that renders the view onscreen. Therefore, the act of the Start button on the start view pushing the game view on the stack will call this function, and start the game.

It also works when we're coming back to the view by popping the end view off the stack. We'll receive a viewWillAppear notification, reset the game, and it's as if the user gets a whole new game. It's almost magic!

Note

To those who have done any amount of Objective-C programming for iOS using Apple's frameworks, I'll apologize right now for using the concepts in the framework. It's just that, well, they fit the view model so well! If you prefer Android's methodology, or Microsoft's, feel free to substitute. I just happen to like the framework Apple has built up for their platform.

Of course, we need to actually do something when the back button is pressed, the code for it is as follows:

gameView.backButtonPressed = function ()
{
  PKUI.CORE.popView();
}

The popView() method is literally the reverse of pushView. It takes the currently visible view (gameView), pops it off the stack, and displays the underlying view, in this case, startView. The best thing to do here would be to prompt the player if they really wanted to do this; it will end their game, perhaps prematurely. For now, as an example, we'll leave it at this.

Next, we need to define how a question is displayed on the screen. We do that in nextQuestion() , as seen in the following code snippet:

gameView.nextQuestion = function ()
{

First, we'll get a random question from the QQ namespace:

  // load the next question into the view
  gameView.theCurrentQuestion = QQ.getRandomQuestion();

Next, we get our templates:

  var theQuestionTemplate = 
    $ge("gameView_questionTemplate").innerHTML;
  var theAnswerTemplate   = 
    $ge("gameView_answerTemplate").innerHTML;

Now that we have our templates, we'll replace all occurrences of "%QUESTION%" with the translated question, as shown in the following code snippet:

  theQuestionTemplate = theQuestionTemplate.replace( 
    "%QUESTION%", 
    __T(gameView.theCurrentQuestion.getQuestion()) );

Generating the answers is a little more tricky. There may be two, three, or more answers for any one question, so we'll ask the question for a list of randomized answers first, and then loop through that list while building up an HTML string, as shown in the following code snippet:

  var theAnswers = 
    gameView.theCurrentQuestion.getRandomizedAnswers();

  var theAnswersHTML = "";

  for (var i=0; i<theAnswers.length; i++)
    {

For each answer, we'll replace the %ANSWER% text with the translated text of the answer, and "%ANSWER_INDEX%" with the current index (i), as shown in the following screenshot:

        theAnswersHTML += theAnswerTemplate.replace( 
          "%ANSWER%", 
          __T(gameView.theCurrentQuestion.answerAtIndex( 
          theAnswers[i] ) )).replace ( "%ANSWER_INDEX%", 
          theAnswers[i] );
    }

Now that we've got the HTML for our answers, we can replace %ANSWERS% in the question template with it as follows:

    theQuestionTemplate = theQuestionTemplate.replace ( 
      "%ANSWERS%", theAnswersHTML );

At this point, we can display the question to the player:

    gameView.questionArea.innerHTML = theQuestionTemplate;

We also want to update the player's score. We're going to have an artificially absurd scoring system to highlight whether or not our localization is working correctly. Note that the 2 in the following code snippet specifies we want two decimal places in the score.

    gameView.scoreArea.innerHTML = __T("SCORE_%1", 
      [ __N(gameView.score, "2") ]);

We'll also update the view's title with the current question number. This time the "0" following code snippet indicates no decimal points:

    gameView.viewTitle.innerHTML = __T("QUESTION_%1", 
      [ __N(gameView.questionNumber, "0") ]);
    
}

All of this is well and good, but it does nothing without the user being able to select an answer, which is where the next function comes in:

  gameView.selectAnswer = function ( theAnswer )
  {

First, we'll ask the current question if the answer selected is correct using the following code snippet:

    if (gameView.theCurrentQuestion.testAnswer ( theAnswer ))
    {

If it is, we'll tell the user they got it right, and increment their score as follows:

        alert (__T("CORRECT"));
        gameView.score += 483.07;
    }
    else
    {

But if it is wrong, we'll indicate that it is incorrect, and decrement their score (We're mean, I guess. Not really though-we want to test that negative numbers work too.), using the following code snippet:

        alert (__T("INCORRECT"));
        gameView.score -= 192.19;
    }

Next, we check to see if we've asked the last question in the set as follows:

    if (gameView.questionNumber >= 10)
    {

If we have, we'll communicate the score to the end view and push it onto the stack. This ends the game, using the following code snippet:

        endView.setScore ( gameView.score );
        PKUI.CORE.pushView ( endView );
    }
    else
    {

In this case, we've got more questions to answer, so we load the next question as follows:

        gameView.questionNumber++;
        gameView.nextQuestion();
    }
  }
  
</script>

With that, we're done with the game view. Tell me, that wasn't too difficult, was it?

What did we do?

We implemented the actual game in one view. We also learned how to handle the back button on Android, and back navigation on iOS. We also gained an understanding of how to use HTML blocks that are hidden as templates for dynamic content.

What else do I need to know?

If you remember, I mentioned that we'd talk about that wonderful little include function a little more. Let's look at it a bit closer:

PKUTIL.include = function ( theScripts, completion )
{

First off, let me clue you into something: we're using recursion here to load the scripts. So, as you'll see in the following code, we're testing the length of the incoming array, and if it is zero, we call the completion method passed to us. This allows us—if we like—to have code called after all the scripts are loaded. This code block is as follows:

var theNewScripts = theScripts;
  if (theNewScripts.length == 0)
  {
    if (completion)
    {
       completion();
    }
    return;
  }

In the next section, we'll pop off the next script to load. This also explains that the array must contain the scripts in reverse order of their dependencies. Yes, you could reverse the array yourself and you should, but I wanted to make the point. To pop off the script the following code instruction is used:

  var theScriptName = theNewScripts.pop();

Then we call another previously unknown function, PKUTIL.load() . This method takes the script filename, and then calls the completion function we've given it. It will call it regardless of success or failure. Notice that it is an incoming parameter to the completion function. This function is shown in the following screenshot:

  PKUTIL.load ( theScriptName, true, function ( success, data )
  {

If the script was successfully loaded, we create a SCRIPT DOM element and add the data to it. It is important to note that nothing happens with the script until we actually attach it to the DOM. We do this by appending the child to the BODY. It is at this point that whatever is in the script will be executed. This conditional if block is shown in the following code snippet:

    if (success)
    {
      var theScriptElement = document.createElement("script");
      theScriptElement.type = "text/javascript";
      theScriptElement.charset = "utf-8";
      theScriptElement.text = data;
      document.body.appendChild ( theScriptElement ); // add it as a script tag
    }

If we fail to load the script, we'll generate a log message on the console. You could make a case that something worse should happen, like a fatal error that stops everything, but this also permits loading libraries that may or may not be there and taking advantage of them if they happen to exist. Perhaps not a feature one would use frequently, but useful at times nonetheless. The conditional else block is as follows:

    else
    {
      console.log ("WARNING: Failed to load " + theScriptName );
    }

And say hello to our little friend, recursion. We call ourselves with the array of script names (minus the one we just popped), with the completion function, and sooner or later, we'll end up with no items in the array. Then, the completion function will be called as seen in the following code block:

    PKUTIL.include ( theNewScripts, completion );
  }
  );
}

The PKUTIL.load() function is another interesting beast, which must work correctly for our includes to work. It's defined something like the following (for full implementation details, visit https://github.com/photokandyStudios/YASMF/blob/master/framework/utility.js#L126):

PKUTIL.load = function ( theFileName, aSync, completion )
{

First, we'll check to see if the browser understands XMLHttpRequest. If it doesn't, we'll call completion with a failure notice and a message describing that we couldn't load anything, as shown in the following code block:

  if (!window.XMLHttpRequest) 
  { 
    if (completion) 
    {
      completion ( PKUTIL.COMPLETION_FAILURE,
                   "This browser does not support 
                    XMLHttpRequest." );
      return;
    }
  }

Next we set up the XMLHttpRequest , and assign the onreadystatechange function as follows:

  var r = new XMLHttpRequest();
  r.onreadystatechange = function()
  {

This function can be called many different times during the loading process, so we check for a specific value. In this case, 4 means that the content has been loaded:

    if (r.readyState == 4)
    {

Of course, just because we got data doesn't mean that it is useable data. We need to verify the status of the load, and here we get into a little bit of murky territory. iOS defines success with a zero value, while Android defines it with a 200:

      if ( r.status==200 || r.status == 0)
      {

If we've successfully loaded the data, we'll call the completion function with a success notification, and the data, as follows:

        if (completion)
        {
          completion ( PKUTIL.COMPLETION_SUCCESS,
                       r.responseText );
        } 
      }

But if we've failed to load the data, we call the completion function with a failure notification and the status value of the load, as follows:

      else
      {
        if (completion)
        {
          completion ( PKUTIL.COMPLETION_FAILURE,
                       r.status );
        }
      }
    }
  }

Keep in mind that we're still just setting up the XMLHttpRequest object and that we've not actually triggered the load yet.

The next step is to specify the path to the file, and here we run into a problem on WP7 versus Android and iOS. On both Android and iOS we can load files relative to the index.html file, but on WP7, we have to load them relative to the /app/www directory. Subtle to track down, but critically important. Even though we aren't supporting WP7 in this book, the framework does, and so it needs to handle cases like this using the following code snippet:

  if (device.platform=="WinCE")
  {
    r.open ('GET', "/app/www/" + theFileName, aSync); 
  }
  else
  {
    r.open ('GET', theFileName, aSync); 
  }

Now that we've set the filename, we fire off the load:

  r.send ( null );
      
}

Note

Should you ever decide to support WP7, it is critical that even though the framework supports passing false for aSync, which should result in a synchronous load, you shouldn't actually ever do so. WP7's browser does funny things when it can't load data asynchronously. For one thing, it loads it asynchronously anyway (not your intended behavior), and for another thing, it has a tendency to think the file simply doesn't exist. So instead of loading scripts, you'll get errors in the console indicating that a 404 error occurred. And you'll scratch your head (I did!) wondering why in the world that could be when the file is right there. Then you'll remember this long note, change the value back to true, and things will suddenly start working. (You seriously do not want to know the hours it took me to debug on WP7 to finally figure this out. I want those hours back!)

 

Implementing the end view


We'll be creating the file name endView.html in the www/views directory. When we're done, we'll end up with this view for iOS:

The view for Android will be as follows:

Getting on with it

As with our previous views, the first step is to define the HTML representation:

<div class="viewBackground">
 <div class="navigationBar">
  <div id="endView_title"></div>
  <button class="barButton backButton" id="endView_backButton" style="left:10px" ></button>
 </div>
 <div class="content avoidNavigationBar avoidToolBar" id="endView_gameArea">
  <div id="endView_resultsArea"></div>
 </div>
 <div class="toolBar">
  <button class="barButton" id="endView_tryAgainButton" style="right:10px" ></button>
 </div>
</div>

I've highlighted two areas in this code: resultsArea where we'll tell the player how they scored, and the button in the toolbar, which this time is a Try Again? button. It acts just like a back button, though.

Next, we need localized content. In this case, it's both localized content and a template, as shown in the following code snippet:

<div id="endView_template_en" class="hidden">
  <h2>Congratulations!</h2>
  <p>You finished that round with a score of %SCORE%!</p>
  <p>Dated: %DATE%</p>
</div>

<div id="endView_template_es" class="hidden">
  <h2>¡Felicitaciones!</h2>
  <p>¡Se terminó la ronda con una puntuación de %SCORE%!</p>
  <p>Fecha: %DATE%</p>
</div>

Again, these div elements are hidden so that the player can't see them, but we'll take their content, replace %SCORE% and %DATE%, and then show the resulting content to the player.

Let's look at our script:

<script>
  
  var endView = $ge("endView") || {};  // properly namespace
  
  endView.score = 0;

First, we set the score to zero, mainly for initialization purposes. We'll provide a utility function next that sets the score to any value. As you should remember, this is called when the game is ending in the game view. This initialization is shown in the following code snippet:

  endView.setScore = function( theScore )
  {
    endView.score = theScore;
  }

As has been typical of all our previous views, we have an initializeView() method. What's a little different is that it doesn't localize the content area; that's because we don't know the score at this point. The initializeView() function is called well in advance of the game even starting, let alone being finished. This function is given as follows:

  endView.initializeView = function ()
  {
    
    endView.viewTitle = $ge("endView_title");
    endView.viewTitle.innerHTML = __T("RESULTS"); 
    
    endView.backButton = $ge("endView_backButton");
    endView.backButton.innerHTML = __T("BACK");
    PKUI.CORE.addTouchListener(endView.backButton, "touchend", 
      function () { PKUI.CORE.popView(); });
    
    endView.nextButton = $ge("endView_tryAgainButton");
    endView.nextButton.innerHTML = __T("TRY_AGAIN?");
    
    PKUI.CORE.addTouchListener(endView.nextButton, "touchend", 
      function () { PKUI.CORE.popView(); });
  
    endView.questionArea = $ge("endView_resultsArea");
  }

Notice that both buttons, the back button and the try again button do the same thing, they pop the view. This works because when we pop off the view, gameView will get the viewWillAppear notification, which resets the game.

This view also needs such a notification to set up the content area, since we'll know the score by the time endView appears on screen:

  endView.viewWillAppear = function ()
  {
    var theTemplate = $geLocale("endView_template").innerHTML;
    
    theTemplate = theTemplate.replace ( "%SCORE%", 
        __N(endView.score, "2") );

    theTemplate = theTemplate.replace ( "%DATE%",  
        __D(new Date(), "D") );
    
    endView.questionArea.innerHTML = theTemplate;
  }

We get the properly localized template and replace %SCORE% with the actual score, and %DATE% with the current date (the D here means long format date). We then show it to the end user. All of this happens just prior to the view animating on screen.

We need to have code that will handle the back button should it be pressed, which is a pop back to the gameView:

endView.backButtonPressed = function ()
  {
    PKUI.CORE.popView();
  }

Astonishingly, that's it. There's really no new ground to cover here, no new methods in the framework, no new utility methods, no new localization concepts. The only thing that looks new is the __D() function, which, as you can probably guess, is what localizes dates. In fact, there are two more functions that are similar: __C(), which localizes currency and __PCT(), which localizes percentages. We'll be dealing with these in later apps.

What did we do?

We created the End view. We properly localized a content template, and localized both numbers and dates.

 

Putting it all together


We've almost got a fully functional app on our hands, but we're missing a couple of critical components: the part that loads it all and starts it off. For this, we'll be creating an app.js file and two HTML files under the www directory.

Getting on with it

The index.html and index_android.html files are what kicks everything off by loading the necessary scripts and calling app.js. These are typically pretty standard for each app, so they aren't going to change much throughout the rest of the book.

First, index.html, which is intended for iOS, is as follows:

<!DOCTYPE html>
<html>
  <head>
    <title>Chapter 1 App: Quiz Time</title>
    <meta name="apple-mobile-web-app-capable" content="yes" />
    <meta name="viewport" content="width=device-width, maximum-scale=1.0" />
    <meta name="format-detection" content="telephone=no" />
    <link rel="stylesheet" href="./framework/base.css" type="text/css" />
    <link rel="stylesheet" href="./style/style.css" type="text/css" />
    <script type="application/javascript" charset="utf-8" src="./cordova/cordova-2.2.0-ios.js"></script>
    <script type="application/javascript" charset="utf-8" src="./framework/utility.js"></script>
    <script type="application/javascript" charset="utf-8" src="./app.js"></script>
  </head>
  <body>
    <div class="container" id="rootContainer">
    </div>
    <div id="preventClicks"></div>
  </body>
</html>

Next, index_android.html, which is for Android is as follows:

<!DOCTYPE html>
<html>
  <head>
    <title>Chapter 1 App: Quiz Time</title>
    <meta name="apple-mobile-web-app-capable" content="yes" />
    <meta name="viewport" content="width=device-width, maximum-scale=1.0, target-densityDpi=160" />
    <meta name="format-detection" content="telephone=no" />
    <link rel="stylesheet" href="./framework/base.css" type="text/css" />
    <link rel="stylesheet" href="./style/style.css" type="text/css" />
    <script type="application/javascript" charset="utf-8" src="./cordova/cordova-2.2.0-android.js"></script>
    <script type="application/javascript" charset="utf-8" src="./framework/utility.js"></script>
    <script type="application/javascript" charset="utf-8" src="./app.js"></script>
  </head>
  <body>
    <div class="container" id="rootContainer">
    </div>
    <div id="preventClicks"></div>
  </body>
</html>

The app.js file is what actually starts our app. It is also what initializes our localization, sets our current locale, loads various libraries (such as ui-core.js), and finally, starts our app. Let's look at the code now:

var APP = APP || {};    

As usual, we set up our namespace, this time as APP. Next, we'll attach an event listener to the deviceready event; this fires whenever Cordova has finished loading its libraries. We must wait for this event before we can do much of anything, especially anything that relies on Cordova. If we don't, we'll get errors. We'll set our namespace as follows:

document.addEventListener("deviceready", onDeviceReady, false);

function onDeviceReady()
{
  APP.start();
}

All that the preceding function does is call the APP.start() function, which is defined as follows:

APP.start = function ()
{
  PKUTIL.include ( [ "./framework/ui-core.js", 
                     "./framework/device.js", 
                     "./framework/localization.js" ], 
           function () { APP.initLocalization(); } );
}

You've already seen PKUTIL.include, so it isn't anything new to you, but here we're loading three libraries, and including a completion function to call APP.initLocalization. Because the include is asynchronous, we cannot continue writing the code after this call that relies on those libraries, or there's a good chance the library wouldn't be loaded in time. Therefore, we call the initLocalization function when all three libraries are fully loaded.

The next function, initLocalization, initializes our jQuery/Globalize by loading its libraries and when it is complete, we load any locales we might need. When those locales are finished loading, then we call APP.init and this is where the real work begins. The APP.init function is given as follows:

APP.initLocalization = function ()
{
  PKLOC.initializeGlobalization( 
   function ()
   {
      PKLOC.loadLocales ( ["en-US","en-AU","en-GB",
        "es-ES","es-MX","es-US","es"], 
      function ()
     {
       APP.init();
     } );
   }
  );
}

The APP.init() function defines our app's basic translation matrix (you may see translations you've seen before; that's because they originated from here), and we also proceed to load the three views we have created into the document:

APP.init = function ()
{ 

First, we fake our locale by setting it to Spanish language and the country of Spain. If you want the app to determine the locale by querying the system, comment the line out.

  PKLOC.currentUserLocale = "es-ES";

Next, we have our basic translation matrix, application titles, the translations for correct and incorrect, start, back and skip, and more:

  PKLOC.addTranslation ("en", "APP_TITLE",     "Quiz Time");
  PKLOC.addTranslation ("en", "APP_TITLE_IMAGE", "AppTitle-enus.png");
  PKLOC.addTranslation ("en", "CORRECT",       "Correct!");
  PKLOC.addTranslation ("en", "INCORRECT",     "Incorrect.");
  PKLOC.addTranslation ("en", "START",         "Start");
  PKLOC.addTranslation ("en", "BACK",          "Back");
  PKLOC.addTranslation ("en", "SKIP",          "Skip");
  PKLOC.addTranslation ("en", "QUESTION_%1",   "Question %1");
  PKLOC.addTranslation ("en", "SCORE_%1",   "Score: %1");
  PKLOC.addTranslation ("en", "RESULTS",    "Results");
  PKLOC.addTranslation ("en", "TRY_AGAIN?",    "Try Again?");
     
  PKLOC.addTranslation ("es", "APP_TITLE",     "Examen Tiempo");
  PKLOC.addTranslation ("es", "APP_TITLE_IMAGE", "AppTitle-eses.png");
  PKLOC.addTranslation ("es", "CORRECT",       "¡Correcto!");
  PKLOC.addTranslation ("es", "INCORRECT",     "Incorrecto.");
  PKLOC.addTranslation ("es", "START",         "Comenzar");
  PKLOC.addTranslation ("es", "BACK",          "Volver");
  PKLOC.addTranslation ("es", "SKIP",          "Omitir"); 
  PKLOC.addTranslation ("es", "QUESTION_%1",   "Pregunta %1");
  PKLOC.addTranslation ("es", "SCORE_%1",   "Puntuación: %1");
  PKLOC.addTranslation ("es", "RESULTS",    "Resultados");
  PKLOC.addTranslation ("es", "TRY_AGAIN?",    "¿Intentar du nuevo?");

Next, we call a function in PKUI.CORE called initializeApplication. All this function does is attach a special event handler that tracks the orientation of the device. But by doing so, it also attaches the device, the form factor, and the orientation to the BODY element, which is what permits us to target various platforms with CSS. This function is given as follows:

  PKUI.CORE.initializeApplication ( );

Next, we load a view, gameView in this case (order doesn't really matter here):

  PKUTIL.loadHTML ( "./views/gameView.html", 
                    { id : "gameView", 
                      className: "container", 
                      attachTo: $ge("rootContainer"), 
                      aSync: true
                    },
                    function (success)
                    {
                      if (success)
                      {
                        gameView.initializeView();
                      }
                    });

We call PKUTIL.loadHTML to accomplish this, and if you're thinking it would be a lot like PKUTIL.include, you'd be right. We'll look at the definition a little later, but it should suffice to say, we're loading the content inside gameView.html, wrapping it with another div with an id value of gameView and a class of container, attaching it to the rootContainer, and indicating that it can be loaded asynchronously.

Once it finishes loading, we'll call initializeView() on it.

We load the end view the same way as follows:

  PKUTIL.loadHTML ( "./views/endView.html", 
                    { id : "endView", 
                      className: "container", 
                      attachTo: $ge("rootContainer"), 
                      aSync: true
                    },
                    function (success)
                    {
                      if (success)
                      {
                        endView.initializeView();
                      }
                    });

We load the start view almost exactly the same way as all the others. I'll highlight the difference in the following code block:

  PKUTIL.loadHTML ( "./views/startView.html", 
                    { id : "startView", 
                      className: "container", 
                      attachTo: $ge("rootContainer"), 
                      aSync: true
                    },
                    function (success)
                    {
                      if (success)
                      {
                        startView.initializeView();
                        PKUI.CORE.showView (startView);
                      }
                    });

}

The only thing we do differently is to show startView after we initialize it. At this point the game is fully loaded and running, and waiting for the player to tap Start.

What did we do?

We tied everything together by creating the app.js file. We learned how to initialize the jQuery/Globalize library, how to fake a locale, and how to set up our translation matrix. We learned how to load views, and how to show the first one.

What else do I need to know?

Let's look at PKUTIL.loadHTML a little closer:

PKUTIL.loadHTML = function( theFileName, options, completion )
{
  var aSync = options["aSync"];

The first thing we do is pull out the aSync option, we need it to call PKUTIL.load. Again, the warning about WP7 and loading synchronously still applies. It is best to assume you'll always be using true unless you can rule WP7 out of your supported platforms. We use the aSync option as follows:

  PKUTIL.load ( theFileName, aSync, function ( success, data )
  {
    if (success)
    {

At this point, we've successfully loaded the HTML file, as seen in the following code snippet, and now we have to figure out what to do with it:

      var theId = options["id"];
      var theClass = options["className"];
      var attachTo = options["attachTo"];

First, we extract out the other parameters we need, namely, id, className, and attachTo:

      var theElement = document.createElement ("DIV");
      theElement.setAttribute ("id", theId);
      theElement.setAttribute ("class", theClass);
      theElement.style.display = "none";
      theElement.innerHTML = data;

Next, we create a div element, and give it id and class. We also load the data into the element as shown in the following code block:

      if (attachTo)
      {
        attachTo.appendChild (theElement);
      }
      else
      {
        document.body.appendChild (theElement);
      }

If possible, we'll attach to the element specified in attachTo, but if it isn't defined, we'll attach to the BODY element. It is at this point that we become a real DOM element in the display hierarchy.

Unfortunately this isn't all. Remember that our HTML files have SCRIPT tags in them. For whatever reason, these scripts don't execute automatically when loaded in this fashion. We have to create SCRIPT tags for them again, as shown in the following code snippet:

      var theScriptTags = theElement.getElementsByTagName 
          ("script");

First, we get all the SCRIPT tags in our newly created element. Then we'll iterate through each one, like this:

      for (var i=0;i<theScriptTags.length;i++)
      {
        try 
        {
          // inspired by 
          http://bytes.com/topic/javascript/answers/513633-innerhtml-script-tag
          var theScriptElement = 
              document.createElement("script");
          theScriptElement.type = "text/javascript";
          theScriptElement.charset = "utf-8";
          if (theScriptTags[i].src)
          {
            theScriptElement.src = theScriptTags[i].src;
          }
          else
          {
            theScriptElement.text = theScriptTags[i].text;
          }
          document.body.appendChild (theScriptElement);

If this code looks somewhat familiar, it's because PKUTIL.include has a variant of it. The important distinction is that it was only concerned about the data of the script; here we have to worry if the script is defined as an external script. That is why we check to see if the SRC attribute is defined.

We also have surrounded this in a try/catch block, just in case the scripts have errors in them:

        }
        catch ( err )
        {
          console.log ( "When loading " + theFileName + 
                        ", error: " + err );
        }
      }

We've finished loading the HTML and the scripts, so we call the completion function:

      if (completion)
      {
        completion (PKUTIL.COMPLETION_SUCCESS);
      }
    }

If, for whatever reason, we couldn't load the view, we generate a log message and call the completion function with a failure notification as follows:

    else
    {
      console.log ("WARNING: Failed to load " + theFileName );
      if (completion)
      {
        completion (PKUTIL.COMPLETION_FAILURE);
      }
    }
  }
  );
}

Next, we should review the new localization functions we encountered. The first was PKLOC.initializeGlobalization():

PKLOC.initializeGlobalization = function ( completion )
{
    PKUTIL.include ( [ "./framework/globalize.js" ], 
        completion );
}

As you can see, all it does is load the jQuery/Globalize framework, and then call its completion handler.

The next function is PKLOC.loadLocales. It is designed to make it easy to load jQuery/Globalize culture files. These files live in the www/framework/cultures directory, and you can have and load as many as you like. Just remember that the more you have, the larger your app will be, and the longer it will take to start:

PKLOC.loadLocales = function ( theLocales, completion )
{
   for (var i=0; i<theLocales.length; i++)
  {
     theLocales[i] =        
        "./framework/cultures/globalize.culture." + 
        theLocales[i] + ".js";
  }
   PKUTIL.include ( theLocales, completion );
}

Here we take advantage of the fact that PKUTIL.include takes an array of script files. The incoming locales (in no real particular order; jQuery/Globalize culture files only depend upon the jQuery/Globalize library being loaded) are already in an array, and so we alter the array to include the full path and name of the culture file. When we're done, we include them, and the completion function will be called when they are all loaded.

 

Game Over..... Wrapping it up


Wow, we've been through a lot together in this first project. We've learned a lot too, including:

  • How to properly localize text

  • How to properly localize numbers

  • How to properly localize dates

  • How to properly localize images

  • How to properly localize HTML

  • How to implement simple HTML templates

  • How to create a new view

  • How to display the view

  • How to push a new view onscreen, and pop a view offscreen

  • How to handle the Android/WP7 back button

  • How to include files within our JavaScript

  • How to determine the user's locale

  • How to initialize jQuery/Globalize and load the locales we might need

There are some resources that you might find interesting. You might want to look through the YASMF documentation to learn more about the framework we're using. Some of these resources are mentioned as follows:

 

Can you take the HEAT? The Hotshot Challenge


There are quite a few ways that this project could be enhanced. Why don't you try your hand at one or more of them?

  • The game currently supports English and Spanish. Try adding another language.

  • If you play the game for any length of time, you'll find that the same question is often asked again within the same set. Make it so that a question can only be asked once per set.

  • Add categories to the questions, and then allow the user to select which category they'd like to play through.

  • Add difficulty levels to the questions. These could affect the score awarded as well. Allow the user to select the difficulty level they want to play at.

  • Come up with an alternative look and feel for the game and implement it. Perhaps even allow the user to decide which look and feel they want to use.

About the Author
  • Kerri Shotts

    Contacted on 14 apr '16

    __________

    Kerri Shotts has worked with computers for nearly twenty-five years. Her love for technology and programming started when she was introduced to her first computer: a Commodore 64. She obtained a degree in Computer Science while at college, and moved on to become a Software Test Engineer. Afterwards, she became an Oracle Database Administrator for several years. Now she works as a Technology Consultant creating, implementing, and maintaining custom applications (both desktop and mobile), websites, graphics and logos, and more for her clients. You can find her blog posts at her website (www.photokandy.com) and she is active on the Google Group for PhoneGap. When she isn't working, she enjoys photography, music, and fish keeping. She is the author of several books published by Packt Publishing.

    Thanks first to my family for their incredible support, even with the late night clacking of the keyboard. Thanks also to those who have made PhoneGap and Cordova such an awesome tool and also to those who support it on the forums. Thanks especially to the technical reviewers for this book – you're all amazing! Finally, thanks should go to Packt Publishing and their editorial staff for making sure everything came out just right.

    Browse publications by this author
PhoneGap 2.x Mobile Application Development HOTSHOT
Unlock this book and the full library FREE for 7 days
Start now