This howto guide shows in a few quick and easy steps how to create an app that can run on multiple smartphone platforms.
We will use Cordova for cross platform support, Ratchet for Android and IOS native style and Handlebars for templating.
Cordova is a framework that lets you write web applications using HTML, CSS and javascript. It provides basically a webserver to run the same application natively on most smartphone operating systems.
Cordova was formerly known as Phonegap before it was donated to Apache and open sourced. A commercial version by Phonegap is available which includes some nice features that are not needed for this howto.
Cordova also supplies a set of plugins that you can use to utilise the phones camera, contacts, GPS etc.
We use Ratchet for the look and feel of the app. Think of Ratchet as a subset of Bootstrap (not entirely true), that are specificaly aimed at smartphone apps. It provides stylesheets that make make the app look like a native IOS, Android, etc app. It also provides useful javascript libraries such as push and toggle.
Cordova recommend for your app to use the single page application architecture style. Basically your entire app is only one html page and driven by a rich MVC-ish javascript application.
You could use full blown javascript frameworks such as Angular.js, Backbone, EmberJs or among many others.
However in this howto we keep it simple and do not use any application framework. We instead utilise the Handlebars javascript templating framework. It enables us to write little HTML snippets to compose our pages, and keep these logic free.
For detailed installation steps please read my Cordova (Phonegap) within Vagrant for Android howto. It is a step by step guide on installing and setting up Cordova for building Android applications. It optionally uses Vagrant as a means of keeping it separate from your local machine, but you can install everything locally if you prefer.
Lets check if you have installed Cordova correctly.
cordova --version
If okay lets create a basic scaffold application.
cordova create pizza com.example.pizza Pizzeria
You can manually find the javascript files for the libraries used in this application, but I prefer to keep things tidy and use Bower.
Bower can be installed via Node.
npm install -g bower;
We then initialise Bower. Set the name to Pizzeria and accept the default for everything else.
cd www;
bower init
We then add each library with Bower.
bower install -S -p ratchet
bower install -S -p handlebars
A couple of extra libraries will also make our lives easier. JQuery for simple page event handling. Fastclick to support touch click events better.
bower install -S -p fastclick;
bower install -S -p jquery
You can keep the application lighter by only keeping the required files inside bower_components. Ie. remove source files and other non dist files etc. But it is often hard to be certain which files are needed so for this howto we keep all of them.
Let's just knock up a quick hello world page as tracer bullet to check if everything installed ok.
vi index.html;
<!DOCTYPE html>
<html>
<head>
<title>Hello World</title>
<link rel="stylesheet" href="bower_components/ratchet/dist/css/ratchet.min.css" />
</head>
<body>
<div class="content">
<h1>Hello world</h1>
</div>
<script id="header-template" type="text/template">
<h1>Good bye</h1>
</script>
<script src="cordova.js"></script>
<script type="text/javascript"
src="bower_components/jquery/dist/jquery.min.js"></script>
<script type="text/javascript"
src="bower_components/fastclick/lib/fastclick.js"></script>
<script type="text/javascript"
src="bower_components/handlebars/handlebars.min.js"></script>
<script>
$(function(){
var headerTemplate = Handlebars.compile($("#header-template").html());
$('.content').html(headerTemplate);
});
</script>
</body>
</html>
If you installed the Android SDK detailed in my Cordova setup howto, then you can test this hello world on a emulator device. Otherwise I will assume you test it directly from a normal browser as the feedback loop is obviously much faster.
Ps: If you test it in Chrome you can open developer tools and click on the mobile icon to enable scaling to different phones. Quite useful.
open index.html
If the page says Good bye all is well. If not check the browser's developer console for errors. If opened in a browser then console 404 errors for cordova.js is expected as that only exists if on a device.
The source files for this step is available at github/flurdy/cordova-ratchet-handlebars/tree/master/www-helloworld.
Lets create a simple Pizzeria app as a proper example.
It will list a menu, let you look up some pizza information and let you place an order.
We will replace the hello world index.html with one that includes a header tab bar and a bottom navigation bar. We will also link it to the 3 javascript files we will add.
vi index.html;
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="format-detection" content="telephone=no" />
<meta name="msapplication-tap-highlight" content="no" />
<meta name="viewport"
content="initial-scale=1, maximum-scale=1, user-scalable=yes, minimal-ui">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<title>Pizzeria</title>
<link rel="stylesheet"
href="bower_components/ratchet/dist/css/ratchet.min.css" />
<link rel="stylesheet"
href="bower_components/ratchet/dist/ratchet-theme-ios.min.css" />
</head>
<body>
<header id="header" class="bar bar-nav">
<h1 class="title">Pizzeria</h1>
</header>
<nav id="footer" class="bar bar-tab">
<ul class="tab-inner">
<li class="tab-item options-link">
<span class="icon icon-list"></span>
</li>
<li class="tab-item options-link">
<span class="icon icon-gear"></span>
</li>
</ul>
</nav>
<div class="content">
Loading...
</div>
<script type="text/javascript" src="cordova.js"></script>
<script type="text/javascript"
src="bower_components/jquery/dist/jquery.min.js"></script>
<script type="text/javascript"
src="bower_components/fastclick/lib/fastclick.js"></script>
<script type="text/javascript"
src="bower_components/handlebars/handlebars.min.js"></script>
<script type="text/javascript" src="js/data.js"></script>
<script type="text/javascript" src="js/view.js"></script>
<script type="text/javascript" src="js/app.js"></script>
<script>
app.bindEvents();
$(function(){
app.initialize();
});
</script>
</body>
</html>
app.js is where we configure our app.
touch js/data.js;
vi js/app.js;
var app = {
isCordova: typeof cordova !== 'undefined',
messages: {
title: "Pizzeria",
order: "Order"
},
initialize: function(){
console.log('app initializing');
this.pizzeriaView.initialize();
},
bindEvents: function() {
if(this.isCordova){
document.addEventListener('deviceready', app.onDeviceReady, false);
}
},
onDeviceReady: function() {
console.log('deviceready');
FastClick.attach(document.body);
},
pizzeriaView: new PizzeriaView()
};
vi js/view.js;
var PizzeriaView = function(){
this.initialize = function(){
}
};
If you open this in your browser then you should see a plain loading page with no errors in the console (apart from 404 for the cordova.js file).
The source files for this step is available at github/flurdy/cordova-ratchet-handlebars/tree/master/www-scaffold.
Add some pizza as data.
vi js/data.js;
var pizzas = {
"1": {
id: "1",
title: "Hawaii",
ingredients: ["Pineapple","Ham"],
price: 9.99
},
"2": {
id: "2",
title: "Supreme",
ingredients: ["Pepperoni"],
price: 8.99
},
"3": {
id: "3",
title: "Margerita",
ingredients: ["Cheese"],
price: 7.99
}
};
We modify the PizzeriaView and add a MenuView.
vi js/view.js;
var MenuView = function(){
this.header = "<div/>";
this.content = "<div/>";
this.initialize = function(){
this.header = Handlebars.compile($('#menu-header-template').html());
this.content = Handlebars.compile($('#menu-content-template').html());
}
this.render = function(){
var context = {
messages: app.messages,
pizzas: pizzas
};
$('#header').html(this.header(context));
$('.content').html(this.content(context));
}
};
var PizzeriaView = function(){
this.menuView = new MenuView();
this.initialize = function(){
this.menuView.initialize();
this.menuView.render();
}
};
Add menu header and content template to the index.html file. Just above the cordova.js script line.
vi index.html;
<script id="menu-header-template" type="text/template">
<h1 class="title">{{messages.title}}</h1>
</script>
<script id="menu-content-template" type="text/template">
<div class="card">
<ul class="table-view pizzas">
{{#each pizzas}}
<li class="table-view-cell">
<a class="navigate-right" href="#" data-pizzaid="{{id}}">
{{title}}
</a>
</li>
{{/each}}
</ul>
</div>
</script>
This should show a menu list of pizzas on the front page.
The source files for this step is available at github/flurdy/cordova-ratchet-handlebars/tree/master/www-menu.
One way of interacting with the app is to enable people to click on a pizza to find out more details.
We add a PizzaView to the view.js.
vi js/view.js;
var PizzaView = function(){
this.header = "<div/>";
this.content = "<div/>";
this.initialize = function(){
this.header = Handlebars.compile($('#details-header-template').html());
this.content = Handlebars.compile($('#details-content-template').html());
}
this.render = function(pizzaId){
var pizza = pizzas[pizzaId];
var context = {
messages: app.messages,
pizza: pizza
};
$('#header').html(this.header(context));
$('.content').html(this.content(context));
$('.parent-link').click(function(){
app.pizzeriaView.menuView.render();
});
}
};
Include templates for pizza detail.
vi index.html;
<script id="details-header-template" type="text/template">
<a class="parent-link icon icon-left-nav pull-left"
href="#"></a>
<h1 class="title">{{messages.title}}</h1>
</script>
<script id="details-content-template" type="text/template">
<div class="card">
<ul class="table-view">
<li class="table-view-cell">
{{pizza.title}}
</li>
<li class="table-view-cell">
{{pizza.ingredients}}
</li>
<li class="table-view-cell">
{{pizza.price}}
</li>
</ul>
</div>
<div class="content-padded">
<form action="#" class="order-form">
<input type="hidden" name="pizzaId" value="{{pizza.id}}"/>
<button class="btn btn-positive btn-block" type="submit">
{{messages.order}}
</button>
</form>
</div>
</script>
And then modify MenuView to call it when the correct event is triggered.
vi js/view.js;
var MenuView = function(){
this.header = "<div/>";
this.content = "<div/>";
this.pizzaView = new PizzaView();
this.initialize = function(){
this.pizzaView.initialize();
this.header = Handlebars.compile($('#menu-header-template').html());
this.content = Handlebars.compile($('#menu-content-template').html());
}
this.render = function(){
var context = {
messages: app.messages,
pizzas: pizzas
};
$('#header').html(this.header(context));
$('.content').html(this.content(context));
$('.pizzas li a').click(function(){
var pizzaId = $(this).data("pizzaid");
app.pizzeriaView.menuView.pizzaView.render(pizzaId);
});
}
};
You should now able to list and view each pizza.
The source files for this step is available at github/flurdy/cordova-ratchet-handlebars/tree/master/www-details.
To order a pizza we need to intercept clicking the order button on the pizza details page. And then show a confirmation page.
The pizza details already contains the button and it is wrapped in a form with relevant information in it.
Lets add a form submission event handler to the PizzaView. And create a OrderConfirmView as well.
vi view.js;
var OrderConfirmView = function(){
this.header = "<div/>";
this.content = "<div/>";
this.initialize = function(){
this.header = Handlebars.compile($('#details-header-template').html());
this.content = Handlebars.compile($('#confirm-content-template').html());
};
this.render = function(pizzaId){
var pizza = pizzas[pizzaId];
var context = {
messages: app.messages,
pizza: pizza
};
$('#header').html(this.header(context));
$('.content').html(this.content(context));
$('.parent-link').click(function(){
app.pizzeriaView.menuView.pizzaView.render(pizzaId);
});
}
};
var PizzaView = function(){
this.header = "<div/>";
this.content = "<div/>";
this.orderView = new OrderConfirmView();
this.initialize = function(){
this.orderView.initialize();
this.header = Handlebars.compile($('#details-header-template').html());
this.content = Handlebars.compile($('#details-content-template').html());
}
this.render = function(pizzaId){
var pizza = pizzas[pizzaId];
var context = {
messages: app.messages,
pizza: pizza
};
$('#header').html(this.header(context));
$('.content').html(this.content(context));
$('.parent-link').click(function(){
app.pizzeriaView.menuView.render();
});
$('.order-form').submit(function(event){
event.preventDefault();
var values = {};
var form = $(this);
$.each(form.serializeArray(), function(i, field) {
values[field.name] = field.value;
});
app.pizzeriaView.menuView.pizzaView.orderView.render(values.pizzaId);
});
}
};
Create a confirm page template.
vi index.html
<script id="confirm-content-template" type="text/template">
<div class="card">
<div class="content-padded">
Pizza {{pizza.title}} ordered
</div>
</div>
</script>
You can now order a pizza!
The source files for this step is available at github/flurdy/cordova-ratchet-handlebars/tree/master/www-order.
Obviously this is just a rudementary example and does not actually order any real pizzas! But it does demonstrate the basics of iterating and displaying collections of data, passing parameters along, and submitting forms.
From this you can expand to include storage, synchronising offline data, external API calls, application resume etc. You can add Ratchet's or other javascript libraries to slide and fade in pages, use the history API etc. And also experiment with Cordova's plugins to use other device features available.
Please fork and send a pull request for to correct any typos, or useful additions.
Buy a t-shirt if you found this guide useful. Hire Ivar for short term advice or long term consultancy.
Otherwise contact flurdy. Especially for things factually incorrect. Apologies for procrastinated replies.