PhoneGap Tutorial: A Cross-Platform Zombie App

Dani Arnaout Dani Arnaout
Learn how to create a to-do app to survive the zombie apocalypse in this PhoneGap tutorial!

Learn how to create a to-do app to survive the zombie apocalypse in this PhoneGap tutorial!

PhoneGap is a mobile development framework that allows developers to build applications for a variety of mobile platforms, using familiar web technologies such as HTML, CSS, and JavaScript.

The concept is that you can develop one application using the PhoneGap architecture, and then deploy to multiple mobile platforms — without the need to re-write any code!

In this tutorial, you’ll learn the basics of using PhoneGap to make a simple to-do app, with a twist – it’s an app to help you survive the Zombie Apocalypse!

In the process, you’ll learn a ton about PhoneGap – such as installing PhoneGap, writing Javascript code, using local storage, and much more.

This tutorial assumes you have some basic familiarity with HTML, CSS, and Javascript. If you are new to these technologies you can still follow along with the tutorial, but you’ll be missing some background knowledge, so I recommend you read a book on those when you get a chance.

So if you think you have the brains, keep reading to get started – the zombies are waiting!

Getting Started

As with any new project, it’s always a good idea to plan ahead for what you’ll be building.

Here’s a sketch of what the app you’re developing will look like:

Basically this is an app to help you keep track of all the things you need to do in order to survive the Zombie Apocalypse.

The app’s logo sits at the top of the screen. Two buttons sit below the logo; one for adding a new task, and another for removing completed ones. When the user adds an item to their to-do list, a new table row will appear with four individual elements.

First, you have a checkbox which indicates a task’s status. Next is the content of the to-do item. Then, finally, there are two more buttons: one that will displays the text of the to-do item in a popup dialog, and another that will delete the to-do item.

The app will store the to-do list using the Local Storage API.

And, thanks to the graphic design talents of Vicki Wenderlich, this is how the final app will look:

Aww yeah. You’d never forget your zombie repellent spray if you had an app like that! :]

That’s it for the look and feel — time to take a look at how the app should behave.

  • Adding a task: When tapping the Add To-Do button, the user will be prompted to enter some text for their to-do list item.
  • Marking tasks as completed: The user will be able to mark tasks as completed by tapping the check box to the left of the task.
  • Removing completed tasks: Tapping the Remove Completed Tasks button will remove all completed tasks.
  • Viewing a task: When tapping the View button, the full text of the task will be displayed.
  • Deleting a task Tapping the Delete button will remove the corresponding task.

Altogether, it’s a relatively simple app. The purpose behind this PhoneGap tutorial is to demonstrate the development of a fully functional app without using any native code, and make it available on a variety of mobile platforms with nothing more than a single click!

Sound good? All right — time to get PhoneGap installed!

Installing PhoneGap

Note: If you already have PhoneGap and want to get started immediately, skip this section and download the starter project. If you choose to skip ahead, I encourage you to have a read through the official PhoneGap getting started guide and command line usage documentation, once you’ve completed the PhoneGap tutorial.

Head over to the PhoneGap website, and choose Download PhoneGap from the home page.

Click the big blue Download button on the next page to initiate the download, as shown in the screenshot below:

Unarchive the folder, and you might want to save it somewhere safe on your hard drive. And that’s all there is to installing PhoneGap!

It’s now time to create your project. Open up the Terminal app. It’s usually found in the Applications folder, within the Utilities folder. You’ll be presented with the Terminal interface, as below:

Type the following command in Terminal and hit enter (but replace the path with wherever your copy of PhoneGap is):

cd ~/Downloads/phoneGap-2.3.0/lib/ios/bin

Note: At the time of this writing, the most recent version of PhoneGap was 2.3.0. If the version you’ve downloaded is newer, then please replace the version number in the command above with the version you have.

To generate a new project, you need to use the command line script create, and pass in the project location, the bundle identifier and finally the project name as arguments to the call.

Type the following command into Terminal and hit Return to generate the project:

./create ~/Desktop/Zombie com.raywenderlich.zombie ZombieApocalypseToDoList

You’ll now find a Zombie folder on your desktop. Open it up, dive into the www folder and take a look at the files inside, as shown below:

The three key files and folders here are index.html, which is where you’ll be writing the app’s code; index.css, found in the css folder, which is used to style the look and feel of the app; and finally the img folder, which is where all the images for the app will be stored.

Go back to the top level folder and open up the ZombieApocalypseToDoList.xcodeproj file in Xcode.

In Xcode’s project navigator, open index.html. Then build and run your app. “Already?” you ask. Yes! Already! :]

You should see the following:

If your display matches the screenshot above, then everything is set up properly for your PhoneGap development environment.

Installation was a breeze – even the undead could have handled that one! :]

You’ll find that the coding of your PhoneGap app in the next section is almost as easy!

Structuring Your App

When your app first launches it displays index.html, so in this section you’ll edit that to give your app some bones to build upon.

Replace all of the existing template code in index.html with the following:

<!DOCTYPE html>
<html>
    <head>
        <title>Zombie Apocalypse To-Do List</title>
        <link rel="stylesheet" type="text/css" href="css/index.css"/>
        <script type="text/javascript" language="JavaScript">
 
        </script>
    </head>
    <body>
 
    </body>
</html>

What you see above is the main structure of any HTML code that leverages JavaScript; it contains html, head, body, and script tags. On a standard web page, JavaScript is usually found between the head tags, while the HTML lives between the body tags.

Now you’ll need to structure the app as designed in the mockup above. Recall that there were two main buttons at the top, followed by a table.

Add the following code to index.html between the body tags:

<input type="button" value="Add To-Do"/>
<input type="button" value="Remove Completed Tasks"/>
<br/><br/>
<table id="dataTable" width="100%" border="1">
 
</table>

Build and run your app! If all goes according to plan, it should look like this:

With a few simple lines of code, you now have some functional buttons displaying on the screen.

Now you need to add some logic behind the buttons to really bring your app to life!

Bring on the Javscript: An Overview

In order to support your app’s functionality, you’ll need to implement a number of JavaScript functions.

Before you start implementing them, let’s start with an overview of the functions you’ll need to create for this app.

createNewToDo()

Prompts the user to enter a new to-do item. Once the user presses the OK button on the resulting dialog, a new row is added to the table.

addTableRow(todoDictionary, appIsLoading)

Adds a row to the to-do list table. It accepts several arguments: a dictionary containing both the text of the to-do along with the checkbox state, as well as with a boolean value which indicates if the app is loading or not. This will be explained a little later.

checkBoxClicked()

Called whenever the state of a checkbox is changed. It loops through all checkboxes and either applies or removes a strike-through style to the text of the corresponding to-do.

viewSelectedRow(todoTextField)

Displays the value of the passed-in text field in a popup dialog.

deleteSelectedRow(deleteButton)

Deletes the row that corresponds to the row of the button that was passed into the function.

removeCompletedTasks()

Loops through all the table rows and removes the rows where the to-do item is marked as complete.

saveToDoList()

Saves the to-do list using the Local Storage API.

loadToDoList()

Loads the to-do list when the app is first launched using the Local Storage API.

deleteAllRows()

Removes all rows from the table.

And that’s it. You can refer back to this section if you have any questions about how each part fits in as you continue working through this PhoneGap tutorial.

Bring on the Javascript: Implementation

Now that the basic structure of the functions has been covered, it’s time to implement them in your app.

Note: Each code block that you encounter below, unless indicated, should be placed between the script tags in index.html.

Add the code below:

// create a new to-do
function createNewToDo()
{
    var todoDictionary = {};
 
    // prompt the user to enter to-do
    var todo = prompt("To-Do","");
    if (todo != null)
    {
        if (todo == "")
        {
            alert("To-Do can't be empty!");
        }
        else
        {
            // append the new to-do with the table
            todoDictionary = { check : 0 , text : todo};
            addTableRow(todoDictionary, false);
        }
    }
 
}

createNewToDo() is relatively straightforward; it simply prompts the user to enter a new to-do item.

If the text of the to-do is empty, this function alerts the user, otherwise it calls addTableRow(), passing a dictionary containing the text of the to-do along with the initial checkbox state. In the case of a new to-do item, the checkbox state will be set as false.

Add the following code:

// add a row to the table
var rowID = 0;
function addTableRow(todoDictionary, appIsLoading)
{
    rowID +=1;
    var table = document.getElementById("dataTable");
    var rowCount = table.rows.length;
    var row = table.insertRow(rowCount);
 
    // create the checkbox
    var cell1 = row.insertCell(0);
    var element1 = document.createElement("input");
    element1.type = "checkbox";
    element1.name = "chkbox[]";
    element1.checked = todoDictionary["check"];
    element1.setAttribute("onclick", "checkboxClicked()");
    cell1.appendChild(element1);
 
    // create the textbox
    var cell2 = row.insertCell(1);
    var element2 = document.createElement("input");
    element2.type = "text";
    element2.name = "txtbox[]";
    element2.size = 16;
    element2.id = "text" + rowID;
    element2.value = todoDictionary["text"];
    element2.setAttribute("onchange", "saveToDoList()");
    cell2.appendChild(element2);
 
    // create the view button
    var cell3 = row.insertCell(2);
    var element3 = document.createElement("input");
    element3.type = "button";
    element3.id = rowID;
    element3.value = "View";
    element3.setAttribute("onclick", "viewSelectedRow(document.getElementById('text' + this.id))");
    cell3.appendChild(element3);
 
    // create the delete button
    var cell4 = row.insertCell(3);
    var element4 = document.createElement("input");
    element4.type = "button";
    element4.value = "Delete";
    element4.setAttribute("onclick", "deleteSelectedRow(this)");
    cell4.appendChild(element4);
 
    // update the UI and save the to-do list
    checkboxClicked();
    saveToDoList();
 
    if (!appIsLoading) alert("Task Added Successfully.");
}

When addTableRow() is called it adds a new row to the table with four separate cells. Then it adds an HTML element to each cell, namely a checkbox, a text box, a View button, and a Delete button. This also sets the necessary attributes on these four elements.

Finally, it calls checkboxClicked() and saveToDoList(), which update the UI and save the to-do list respectively.

addTableRow() is called not only when the user adds a new to-do item, but it’s also called when the app is first launched and the to-do list is being loaded. Hence, that’s why you pass the appIsLoading boolean value to this function.

If you didn’t check this condition, then the user would be alerted for every to-do that’s loaded on startup — which would get annoying REALLY fast! :]

Add the following code:

// add the strike-through styling to completed tasks
function checkboxClicked()
{
    var table = document.getElementById("dataTable");
    var rowCount = table.rows.length;
 
    // loop through all rows of the table
    for(var i = 0; i < rowCount; i++)
    {
        var row = table.rows[i];
        var chkbox = row.cells[0].childNodes[0];
        var textbox = row.cells[1].childNodes[0];
 
        // if the checkbox is checked, add the strike-through styling
        if(null != chkbox && true == chkbox.checked)
        {
            if(null != textbox)
            {		
                textbox.style.setProperty("text-decoration", "line-through");
            }
        }
 
        // if the checkbox isn't checked, remove the strike-through styling
        else
        {
            textbox.style.setProperty("text-decoration", "none");
        }
 
    }
 
    // save the to-do list
    saveToDoList();
}

checkboxClicked() performs only a single task. It loops through the all the rows of the table, determines the state of each checkbox, and applies the strike-through styling to the to-do text if the checkbox is set. Once complete, checkboxClicked() then calls saveToDoList().

Add the following code:

// view the content of the selected row
function viewSelectedRow(todoTextField)
{
    alert(todoTextField.value);
}

There’s not much to viewSelectedRow(); it’s just a simple function that displays an alert containing the full text of the to-do item.

Add the following code:

// delete the selected row
function deleteSelectedRow(deleteButton)
{
    var p = deleteButton.parentNode.parentNode;
    p.parentNode.removeChild(p);
    saveToDoList();
}

deleteSelectedRow() is another relatively simple function. It takes the button reference that was passed in and deletes the corresponding to-do item from the list.

Add the following code:

// remove completed tasks
function removeCompletedTasks()
{
    var table = document.getElementById("dataTable");
    var rowCount = table.rows.length;
 
    // loop through all rows of the table
    for(var i = 0; i < rowCount; i++)
    {
        // if the checkbox is checked, delete the row
        var row = table.rows[i];
        var chkbox = row.cells[0].childNodes[0];
        if(null != chkbox && true == chkbox.checked)
        {
            table.deleteRow(i);
            rowCount--;
            i--;
        }
    }
 
    // save the to-do list
    saveToDoList();
 
    alert("Completed Tasks Were Removed Successfully.");
}

removeCompletedTasks() loops through all table rows and determines the state of each row’s checkbox. If the checkbox is checked, then the table row is removed. Then the updated to-do list is saved and the user is alerted that the action is complete.

Add the following code:

// save the to-do list
function saveToDoList()
{
    var todoArray = {};
    var checkBoxState = 0;
    var textValue = "";
 
    var table = document.getElementById("dataTable");
    var rowCount = table.rows.length;
 
    if (rowCount != 0)
    {
        // loop through all rows of the table
        for(var i=0; i<rowCount; i++)
        {
            var row = table.rows[i];
 
            // determine the state of the checkbox
            var chkbox = row.cells[0].childNodes[0];
            if(null != chkbox && true == chkbox.checked)
            {
                checkBoxState = 1;
            }
            else
            {
                checkBoxState= 0;
            }
 
            // retrieve the content of the to-do
            var textbox = row.cells[1].childNodes[0];
            textValue = textbox.value;
 
            // populate the array
            todoArray["row" + i] =
            {
                check : checkBoxState,
                text : textValue
            };
        }
    }
    else
    {
        todoArray = null;
    }
 
    // use the local storage API to persist the data as JSON
    window.localStorage.setItem("todoList", JSON.stringify(todoArray));
}

saveToDoList() does what it says on the tin. It loops through all table rows and populates a dictionary for each row with the checkbox state and to-do text. This collection of dictionaries are then added to an array, which is then saved using the local storage API.

Now that you have your to-do list saved, you’ll need a way to load it back up, won’t you?

Add the following code:

// load the to-do list
function loadToDoList()
{
    // use the local storage API load the JSON formatted to-do list, and decode it
    var theList = JSON.parse(window.localStorage.getItem("todoList"));
 
    if (null == theList || theList == "null")
    {
        deleteAllRows();
    }
    else
    {
        var count = 0;
        for (var obj in theList)
        {
            count++;
        }
 
        // remove any existing rows from the table
        deleteAllRows();
 
        // loop through the to-dos
        for(var i = 0; i < count; i++)
        {
            // adding a row to the table for each one
            addTableRow(theList["row" + i], true);
        }
    }
}

loadToDoList() provides a mechanism to load up the to-do list that you’ve persisted with saveToDoList().

The Local Storage API is leveraged to the load the JSON formatted to-do list and then decode it. Any existing rows are removed from the current table, then a new row is added for each to-do item. As before, you pass true to addTableRow() to prevent the user from being alerted as each row is loaded at startup.

Add the following code:

// delete all the rows
function deleteAllRows()
{
    var table = document.getElementById("dataTable");
    var rowCount = table.rows.length;
 
    // loop through all rows of the table
    for(var i = 0; i < rowCount; i++)
    {
        // delete the row
        table.deleteRow(i);
        rowCount--;
        i--;
    }
 
    // save the to-do list
    saveToDoList();
}

deleteAllRows() simply removes all rows from the table; it’s used to clean up the UI in an efficent fashion.

Okay, that’s it for the JavaScript functions! Now you need to update the HTML in your app to make use of these functions.

Bringing it All Together

Phew – finally done – just one final bit left to see this working!

Calling the functions you created above is quite simple. You’re just adding the relevant function call to the onClick property of the UI controls.

Add the following code between the body tags of index.html:

<body onload="loadToDoList()">
    <input type="button" value="Add To-Do" onclick="createNewToDo()"/>
    <input type="button" value="Remove Completed Tasks()" onclick="removeCompletedTasks()"/>
    <br/><br/>
    <table id="dataTable" width="100%" border="1">
 
    </table>
</body>

In the code above, loadToDoList() is called when the page is first loaded. As well, createNewToDo() and removeCompletedTasks() are called when their respective buttons are tapped.

Build and run your app! It should look similar to the screenshot below:

One of the disadvantages of JavaScript is that it’s an interpreted language. Unlike a compiled language such as Objective-C, JavaScript will execute the code line by line until an error occurs. If an error does occur and it’s not handled properly, the app will likely crash.

Feel free to play around with the app; add some tasks and then delete them.

Well, the app seems to work okay – but it’s pretty bland! You can’t save the world with a corporate-looking app like that!

Don’t worry — the next part of this PhoneGap tutorial will walk you through polishing the app to a standard that any zombie warrior would be proud of! :]

Oppa Zombie Style – Reworking Your App’s Graphics

Now that the app fully functional, it’s time to make it look great.

Download the necessary images here, and copy them to the img folder of the project.

Blood? Boards? Nails? Yup, sounds like an awesome header image for your app! :]

Add the following line to index.html, just below the opening body tag:

<img src="img/header.png" width="100%" />

This inserts the header image into your app via the HTML img tag.

Since you’re using web technologies at the heart of your application, you’ll be using CSS to influence how the rest of your app looks. Buttons, backgrounds, and other elements in your app can all be controlled by the definitions in your CSS files.

Each UI element that you want to style requires a class element — this indicates which style(s) in your CSS should apply to the element in question.

You’ll begin your foray into CSS by styling the buttons in your app.

Update the existing button related code in index.html as in the code block below:

<button type="button" class="addToDo" onclick="createNewToDo()"><img src="img/button_addtodo.png" /></button>
<button type="button" class="removeTasks" onclick="removeCompletedTasks()"><img src="img/button_removetasks.png" /></button>

Notice that you added a class attribute to each button; this will allow you add a CSS style to it later. As well, each button now contains a reference to an image file that will be displayed, instead of the default buttons of the host UI.

You now need to add class attributes to the checkboxes, the textboxes, as well as the other buttons in your app so that they can also be styled using CSS.

Update the JavaScript section of index.html, adding the lines below:

...
element1.setAttribute("onclick","checkboxClicked()");
element1.className = "checkbox"; // ADD ME - Add the class name 'checkbox' to element1
cell1.appendChild(element1);
...
element2.setAttribute("onchange", "saveToDoList()");
element2.className = "textbox"; // ADD ME - Add the class name 'textbox' to element2
cell2.appendChild(element2);
...
element3.setAttribute("onclick", "viewSelectedRow(document.getElementById('text' + this.id))");
element3.className = "viewButton"; // ADD ME - Add the class name 'viewButton' to element3
cell3.appendChild(element3);
...
element4.setAttribute("onclick", "deleteSelectedRow(this)");
element4.className = "deleteButton"; // ADD ME - Add the class name 'deleteButton' to element4
cell4.appendChild(element4);
...

Now it’s time to define all of the styles you’ve been referencing in your HTML above.

Open index.css (found in the css folder of your project), and find the following lines at the top:

background-color:#E4E4E4;
background-image:linear-gradient(top, #A7A7A7 0%, #E4E4E4 51%);
background-image:-webkit-linear-gradient(top, #A7A7A7 0%, #E4E4E4 51%);
background-image:-ms-linear-gradient(top, #A7A7A7 0%, #E4E4E4 51%);
background-image:-webkit-gradient(
    linear,
    left top,
    left bottom,
    color-stop(0, #A7A7A7),
    color-stop(0.51, #E4E4E4)
);
background-attachment:fixed;

Those lines were providing the background gradient effect, which you won’t need in your app. You’ll be replacing it with something more fitting to your post-apocalyptic masterpiece!

Replace all of those lines with the following one line of code:

background-image: url('../img/bg_pattern.png');

That indicates that the background of the app is now just represented by a single image resource.

Scroll to the very end of index.css and add the following code:

.addToDo {
background-color: Transparent;
border-color: Transparent;
float: left;
margin-left: 0;
position: absolute;
left: 0px
}
 
.removeTasks {
background-color: Transparent;
border-color: Transparent;
float: right;
margin-right: 0;
position: absolute;
right: 0px;
}
 
.checkbox {
background-color: Transparent;
background-image: url('../img/check_box.png');
width: 34px;
height: 32px;
}
 
.textbox {
background-color: grey;
}
 
.viewButton { 
background-color: Transparent;
background-image: url('../img/button_view.png');
width: 64px;
height: 32px;
}
 
.deleteButton { 
background-color: Transparent;
background-image: url('../img/button_delete.png');
width: 64px;
height: 32px;
}

Each style class above — .deleteButton, .viewButton, and so on — defines the appearance of each UI element by way of setting such properties as width, height, background color, and positioning, among other things.

For example, every checkbox in your app does not need to be individually styled when it’s defined; it just needs to reference the .checkbox class and it will take on the characteristics defined in the CSS.

Ready to see how it all works together?

Build and run your app! It should look like the image below:

Wow! Quite a difference, isn’t it?

The changes that you can effect with simple CSS are quite stunning. Just by adjusting the dimensions of the buttons and adding some graphical elements to your app, you’ve really made your app “pop”.

Okay, so your app is complete! But you need to get it out to all the other zombie hunters in the world – and you can bet that they own a mix of mobile devices. What to do?

One of PhoneGap’s great features is that you can publish your application to various platforms without changing a bit of code. There’s a few steps that you need to follow in order to get your app published — but it sure beats rewriting code! :]

Putting the “Gory” in App Store Category – Publishing Your App

First of all, you need to compress your project prior uploading it to Adobe Build.

Head to your desktop, right click on your Zombie folder and choose Compress “Zombie”.

Launch the browser of your choice and head on over to http://build.phonegap.com to get started. Just click the big blue Get started! button to initiate the publishing process, as such:

You can investigate the paid plan when you have a need for more private apps, but for the purposes of this PhoneGap tutorial, choose the free package, as below:

If you have an existing Adobe ID (or wish to create a new Adobe ID), you can use that option to login. If you don’t have or want an Adobe ID, you can also use your GitHub account to log in, as seen below:

If you choose the GitHub option, you’ll need to authorize the login attempt.

Once you’ve authorized your GitHub account login, or chosen to use an Adobe ID, simply choose your country, agree to the Terms of Use and click Complete my registration.

Now you’re ready to upload the compressed archive you created earlier.

Click Upload a .zip file and choose the Zombie.zip archive located on your desktop. Once the archive has finished uploading, click the Ready to build button, like so:

Once the build is done, you’ll see a screen showing the compile state of the multiple mobile OS that PhoneGap supports. Notice that both the iOS and Blackberry indicators are red, while the others are blue, as shown in the screenshot below:

This indicates that there were issues when building the application for that particular platform.

In order to build your app for iOS, there are a few more things that you need to take care of.

Click the iOS button. You’ll end up at the screen below:

Aha! It turns out you need to provide two more pieces of information to compile your app under iOS: your developer certificate and a provisioning profile.

If you’ve been doing iOS development in the past, you already have a certificate, so let’s export it. If you don’t already have a certificate, check out Apple’s App Store Submission Tutorial.

Using Spotlight, search for KeyChain Access. If you previously installed your developer certificate issued by Apple via the provisioning portal, this is where you’ll find it.

You now need to export the certificate.

Choose the My Certificates category, right-click the iPhone Distribution… certificate and choose Export “iPhone Distribution…” from the popup menu, like so:

Save the exported certificate to your desktop, making sure it’s in the Personal Information Export (.p12) format. Choose a password for this certificate and enter it. You’ll need this password along with your certificate later, so choose something that’s easy for you to remember (but hard for the zombies to guess!).

Finally, click OK, and your desktop will now contain your exported certificate.

Now that you have your certificate, you need to generate a provisioning profile. To do this, head to developer.apple.com and sign in with your iOS developer account.

To get started with your provisioning profile, you’ll first need to create an App ID for your Zombie Apocalypse app.

Choose App IDs from the options on the left. On the next page, click New App ID. Complete the form as necessary, making sure to enter com.raywenderlich.zombie for the Bundle Identifier, since that’s what you used earlier when first generating the initial project using PhoneGap.

Your form should look similar to the screenshot below:

Finally, click Submit.

Now that you’ve created an App ID for your new app, choose Provisioning from the options on the left. On the next page, select the Distribution tab and click New Profile. Fill out the form as necessary, selecting the App ID you just created, and select the devices on which you plan to install the app.

Finally, click Submit. The page will now generate your provisioning profile.

Once the portal has finished generating the profile, download it to your desktop. You may need to refresh the page a couple of times to see when it’s complete.

Now that you have both your certificate and provisioning profile, head back over to the Adobe Build site, enter a name for your app, and upload your certificate and your profile as shown below:

Click on the yellow lock icon and enter the certificate’s password that you created previously, as such:

Finally, Click Submit key. After a short wait, you will be presented with a button on the top of the screen to download the .ipa file as shown here:

Download the .ipa file, and install it to your iOS device by simply dragging the file into iTunes and then syncing your device.

w00t – you are now prepared for the worst – fill in your to-do list and get your shotgun ready!

As you’ve seen, iOS requires a few additional steps in order to build the app using Adobe Build. However, it is very much a one-click process for other platforms such as Android, where you can directly download your builds without any extra steps required!

Where to Go Next?

You can download the completed project here.

You’ve only seen a small portion of the features that PhoneGap has to offer for your cross-platform development needs. I recommend you take a quick look at the the PhoneGap API reference to find out about the huge list of APIs you can leverage in your apps.

I should mention that although PhoneGap makes it nice and easy to create cross-platform apps, if you’re looking for a nice and polished experience, nothing matches what you can do with native APIs. Some people even say that you can create a native iOS app and native Android app in less time than making a polished cross-platform app with something like PhoneGap.

On the other hand, there’s a ton of apps that have been successfully made with PhoneGap already – check out the full list to see for yourself. It kinda depends on what you’re going for.

Either way, I hope this tutorial showed you a little bit about PhoneGap and how it works, so you can make the decision that is best for your app. If you have any questions or comments, please join the forum discussion below!

Dani Arnaout
Dani Arnaout

Dani Arnaout is an iOS developer working at Lextech Global Services.
He has a YouTube channel full of iOS & programming related tutorials.
You can also find him on Twitter.

User Comments

28 Comments

[ 1 , 2 ]
  • Great tutorial ! Submit the project to PhoneGap build (to get the IPA) wouldn't be the same if i compile it using XCODE ? I mean using the same process as is done for an usual native OBJC app.
    jlamimoso
  • Great tutorial ! Submit the project to PhoneGap build (to get the IPA) wouldn't be the same if i compile it using XCODE ? I mean using the same process as is done for an usual native OBJC app.
    jlamimoso
  • question about deleteSelectedRow function.
    why did you write the following line in your code?
    var p =deleteButton.parentNode.parentNode;
    Aren't you getting the reference to a grand parent, so to speak? Wouldn't this line suffice?
    var p =deleteButton.parentNode;
    gked
  • Hello, I cant download the starter project, it looks like link to zip file is broke.
    jtorrescr
  • When I compress my project folder to a zip file I end up with a 26.7MB zip file which is too large to update to the Adobe site (it says must be less than 15MB). Is this because I've compiled it already for ios? 15MB seems kind of small. What am I doing wrong?
    dnassler
  • Hi Dani
    Thank you for this great tutorial!
    I've been using it as a base for a sort of "favourite assignment list" for my photography app.

    For each assignment page there are two variables: the title (text) and the link (text2). The is a "checkbox" variable, but I don't actually have a checkbox (i replaced it with a delete box-which does work)
    When the user taps a FAV button on each page, it adds the variables in the createTODO action.

    It works great in a browser, and on first launch in the app. But on refresh or relaunch, I dont think it's saving the link variable to the dictionary as it comes up "undefined"

    I'm a stupid noob with all this, I would appreciate any guidance with the code and would be happy to make a nice paypal donation for the help:
    Thank you very much

    Noel Chenier
    CODE:




    Code: Select all
    //Create a new To-Do
        function createNewToDo()
        {
            var todoDictionary = {};
            //Prompt the user to enter To-Do
            var todo="FAST SHUTTER SPEEDS";
            var todolink="#fastshutter";
            if (todo!=null)
            {
                if (todo == "")
                {
                    alert("To-Do can't be empty!");
                }
                else
                {
                    //Append the new To-Do with the table
                    todoDictionary = { check : 0 , text : todo , text2 : todolink};
                    addTableRow(todoDictionary,false);
                }
            }

        }


        //Add a row to the table
        var rowID = 0;
        function addTableRow(todoDictionary, appIsLoading)
        {

            rowID +=1;
            var table = document.getElementById("dataTable");
            var rowCount = table.rows.length;
            var row = table.insertRow(rowCount);

            //Set up the CheckBox
            var cell1 = row.insertCell(0);
            var element1 = document.createElement("input");
            element1.type = "deleteButton";
            element1.value = "X";

            element1.setAttribute("onclick","deleteSelectedRow(this)");
            element1.className = "deleteButton";
            cell1.appendChild(element1);


            //Set up the View Button
            var cell2 = row.insertCell(1);
            var element2 = document.createElement("input");
            element2.type = "viewButton";
            element2.value = todoDictionary["text"];
            var link = todoDictionary["text2"];
            element2.id = rowID;
            element2.onclick=function(){ window.location.hash = link; };
            element2.className = "viewButton";
            cell2.appendChild(element2);

            //Save & Update UI
            saveToDoList();

            if (!appIsLoading)
            alert("Assignment Added To Favourite List!");
        }




        //Deletes current row
        function deleteSelectedRow(deleteButton)
        {
            var p=deleteButton.parentNode.parentNode;
            p.parentNode.removeChild(p);
            saveToDoList();
        }




        function saveToDoList()
        {
            //Create a todoArray
            var todoArray = {};
            var checkBoxState = 0;
            var textValue = "";
            var text2Value = "";

            //Get current table
            var table = document.getElementById("dataTable");
            var rowCount = table.rows.length;

            if (rowCount != 0)
            {
                //Loop through all rows
                for(var i=0; i<rowCount; i++)
                {
                    var row = table.rows[i];

                    //Add checkbox state
                    var chkbox = row.cells[0].childNodes[0];
                    if(null != chkbox && true == chkbox.checked)
                    {
                        checkBoxState = 1;
                    }
                    else
                    {
                        checkBoxState= 0;
                    }


                    //Add text data
                    var textbox = row.cells[1].childNodes[0];
                    textValue = textbox.value;


                    //Fill the array with check & text data
                    todoArray["row"+i] =
                    {
                        check : checkBoxState,
                        text : textValue
                    };

                }
            }
            else
            {
                todoArray = null;
            }

            //Use local storage to persist data
            //We use JSON to preserve objects

            window.localStorage.setItem("todoList", JSON.stringify(todoArray));
        }



        function loadToDoList()
        {

            //Get the saved To-Do list array by JSON parsing localStorage
            var theList = JSON.parse(window.localStorage.getItem("todoList"));


            if (null == theList || theList=="null")
            {
                deleteAllRows();
            }
            else
            {
                var count = 0;
                for (var obj in theList)
                {
                    count++;
                }

                //Clear table
                deleteAllRows();
                //Loop through all rows
                for(var i=0; i<count; i++)
                {
                    //Add row
                    addTableRow(theList["row"+i],true);
                }

            }

        }
    nchenier
  • Hey @nchenier,

    Thanks for your interest in this tutorial.
    All questions will be answered for FREE :)

    I took some time to read your code, and it turns out the your are not saving the value of the links in your saveToDoList() function.
    I see that you have created a new variable called text2Value, but it was kept unhandled through out the function.

    Code: Select all
    function saveToDoList()
        {
            //Create a todoArray
            var todoArray = {};
            var checkBoxState = 0;
            var textValue = "";
            var text2Value = "";


    You should assign a value to this variable just like the way it was done with textValue.

    Code: Select all
                    //Add text data
                    var textbox = row.cells[1].childNodes[0];
                    textValue = textbox.value;


    So I suggest doing something like:

    Code: Select all
                    //Add text data
                    var text2box = row.cells[1].childNodes[1];
                    text2Value = textbox.value;


    Pay attention to childNodes index, depending on how you placed it in your code.
    Let me know if this works well for you.

    Thanks.
    Dani Arnaoutdani
  • Hi Dani
    When I added the code, it stopped working totally.

    I dont think I'm adding the "text2" variable as an element to the table at all... should i be added it as element3 maybe? even though it wont be displayed?

    Noel
    nchenier
  • After

    ./create ~/Desktop/Zombie com.raywenderlich.zombie ZombieApocalypseToDoList

    I had to do something like

    phonegap platform add ios

    to get
    thunderrabbit
  • Hi dani
    Sorry for the delay, I dropped working on this and picked it up again just last week.
    I was able to get it to work using this:

    //Add text data
    var textbox = row.cells[1].childNodes[0];
    textValue = textbox.value;

    var textbox2 = row.cells[2].childNodes[0];
    text2Value = textbox2.value;

    Thanks for the help! I've now been able to adapt this script for a variety of different uses in my app.

    One more question....is there a way to take the data from the table and put it in the body of an email?

    Noel
    nchenier
  • Thanks so much! I was really struggling to get my head around saving stuff to arrays and objects and then reloading. Your clear explanations may have just saved my project! THANKS!!
    HeatherREllis
  • Hi Dani

    hey i just run this code on an android platform. But it is not working. Only add-todo and Remove completed task is displaying but neither of it on clicking is able to make the table!! apart from that the image of hearder.png when i changed it is unable to load..
    Padmini.V.K
  • Great Article, I learns a lot from you article.you need to update this tutorial since phonegap latest version is named as a cordova.

    cordova docs:
    http://cordova.apache.org/docs/en/edge/ ... 0Interface
    rkj
[ 1 , 2 ]

Other Items of Interest

Ray's Monthly Newsletter

Sign up to receive a monthly newsletter with my favorite dev links, and receive a free epic-length tutorial as a bonus!

Advertise with Us!

Vote for Our Next Tutorial!

Every week, we alternate between Gaming and Non-Gaming tutorial votes. This week: Non-Gaming!

    Loading ... Loading ...

Last week's winner: How To Make a Tower Defense Game with Swift.

Suggest a Tutorial - Past Results

Hang Out With Us!

Every month, we have a free live Tech Talk - come hang out with us!


Coming up in December: The Great CALayer Tour

Sign Up - December

Our Books

Our Team

Tutorial Team

  • Ron Kliffer
  • Toby Stephens

... 59 total!

Update Team

... 14 total!

Editorial Team

  • Matt Galloway

... 22 total!

Code Team

  • Orta Therox

... 3 total!

Subject Matter Experts

  • Richard Casey

... 4 total!