EmployeeDirectoryJQM task-timerBuilding an iPhone App using jQuery Mobile
원문보기
Introduction
For developers whose skills and experience centre around Windows or Web-based development, building an iPhone app can seem like a daunting proposition involving learning new libraries, languages and tools, and buying new hardware. However, for a large class of apps, it is possible to take advantage of your existing knowledge and skills to easily create powerful, full-featured utilities that have a native look and feel. In this article, I’ll explore the process of building an app using the standard web technologies of HTML, CSS, jQuery andjQuery Mobile, developed on a PC running Windows.
Introducing the Task Timer App
For this article, I created a Task Timer utility to illustrate the type of app that can be built.
This Task Timer app can be used to track how your time is spent throughout the day or week. For example, you could create a number of different tasks – Research, Development, Documentation, Marketing and Support – and choose the most suitable for each task you work on. I use the app for recording how long I spend on each of my client’s projects for billing purposes.
Here’s what it looks like:
The active task is identified with a pulsating orange/brown colour and its duration, on the right-hand side – inhours:minutes:seconds
format – increases with every passing second that the task remains active. Tapping a row will activate a different task, and a new task can be added by pressing the button in the top right corner.
You can access options for deleting, renaming, or resetting a task’s times by clicking the button in the top-left corner, which will animate the screen in the familiar iOS way as it switches into editing mode.
While in editing mode, clicking the “No Entry Sign” exposes the task’s delete button , and pressing the right-facing arrow reveals the Edit Task screen.
If you have an iPhone, you can see a live demo of the Task Timer app using Mobile Safari. Alternatively, open the link in Chrome on a PC for a similar experience.
Please note that since the app was designed to run only on iPhone, no effort has been invested designing for cross browser compatibility.
When you visit the link using Mobile Safari, the page will load as you’d expect for any website – within Safari’s frame – and the address and toolbar will be present. To make the web page look and behave like a real iOS app, it must first be added to the Home Screen.
Click the options button and choose Add to Home Screen. This adds the app’s icon to the iPhone home screen making it indistinguishable from other apps installed from the App Store:
Now that the app is installed, it can be launched by clicking its icon. A splash screen will be briefly shown then the app will fill the screen completely, showing no sign of Safari’s address bar or buttons.
Tools Needed to Build the App
The Task Timer app is built entirely using common web technologies. HTML is used for structure, jQuery Mobileand CSS for layout and styling, and JavaScript (and JQuery) to add behaviour.
The app was built using Visual Studio 2012 and the fantastic Resharper plugin. All images were created using the free image editor Paint.NET with the BoltBait plugin pack, which adds additional menu options to Paint.NET, such as Color Balance, Bevel Selection and Feather Selection.
The combination of Visual Studio and Resharper greatly assist the development process by providing useful tools such as syntax highlighting, autocomplete, easy project navigation and early error detection, but these tools aren’t essential and a simple text editor like notepad or notepad++ would suffice.
This article won’t assume any tools other than a basic text editor for writing the code, Chrome for testing and diagnostics, and access to an image editor, like Paint.NET.
Building the Main Page
To build the Task Timer, our first goal is to display a page containing a list of tasks, which should look and feel familiar to an iPhone user.
jQuery Mobile makes it trivially simple to produce the type of screen that is common on touch-capable mobile devices. The framework will effortlessly style native HTML elements, such as <button>
, <a href="">
and <ul>
, so they look and act like common mobile controls – buttons and links large enough to be targeted with a finger rather than a more precise mouse pointer and lists that scroll with a flick of a finger.
The framework will also convert standard <a>
links to use AJAX to fetch the target content, which removes the visible flicker associated with a normal HTTP GET page request. The target page can be configured to appear with a built-in transition, such as slideup, turn, fade or pop.
A jQuery Mobile page is built declaratively, using HTML data-
annotations which are used by the framework to influence how the elements are transformed into mobile-friendly controls.
Try building your own app by copying the HTML below into a file called index.html:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Task Timer</title>
<link href="http://code.jquery.com/mobile/1.3.0/jquery.mobile-1.3.0.css" rel="stylesheet"/>
<link href="http://code.jquery.com/mobile/1.3.0/jquery.mobile.structure-1.3.0.min.css" rel="stylesheet"/>
<script src="http://code.jquery.com/jquery-1.9.1.min.js"></script>
<script src="http://code.jquery.com/mobile/1.3.0/jquery.mobile-1.3.0.min.js"></script>
</head>
<body>
<div id="tasksPage" data-role="page">
<div data-role="header" data-position="fixed">
<a href="#" data-role="button">Edit</a>
<h1>Task Timer</h1>
<a href="#" data-role="button" data-icon="plus" data-iconpos="notext">Add</a>
</div>
<div data-role="content">
<ul id="taskList" data-role="listview">
<li>Task 1</li>
<li>Task 2</li>
</ul>
</div>
<div data-role="footer" data-position="fixed">
<a href="#" data-role="button">About</a>
</div>
</div>
</body>
</html>
Save and open this file in a browser and you’ll see a screen like this:
We now have the basics of an app, all from a few lines of markup with no custom CSS or JavaScript.
Notice how the <div data-role="header">
element has been automatically styled like an iOS app header using a subtle gradient, and the <ul data-role="listview">
markup has become a full-width, styled list that can be scrolled up or down with a flick of a finger.
jQuery Mobile performs its magic by manipulating the document object model (DOM) – produced from the raw HTML – before the page is displayed in the browser. Additional elements and CSS classes are automatically added to the DOM to give it an app-like look and feel.
You can see the transformation that takes place by comparing the bare document object model in the top image to the one resulting from the JQuery Mobile transformation, below:
Styling the App
jQuery Mobile has powerful support for re-skinning its controls with a well-designed suite of CSS classes. The best starting point for styling your app and applying your own branding, is the jQuery Mobile ThemeRoller. If you’ve ever used the Themeroller for JQuery UI, the point and click interface should be familiar.
You can either use the default theme as your starting point or import an existing one, as we will now do.
Copy the contents of the Task Timer Theme to the clipboard, then navigate tohttp://jquerymobile.com/themeroller/ and click the Import or Upgrade button at the top of the screen. This will load the existing styles into the editor allowing you to make changes.
JQuery Mobile supports multiple swatches, each can have a distinct style for a particular purpose.
The Task Timer app uses four swatches – A, B, C and D. I’ve chosen to use A as the primary style with the others being used to highlight particular elements. You can add as many swatches as needed and there are no constraints on which to use for a particular purpose.
After configuring the themes, click the Download button on the ThemeRoller to receive a ZIP file containing the updated CSS and an example HTML file.
Swatches are applied to individual screen elements by adding a data-theme
attribute.
Below, you can see the index.html file from earlier with the data-theme="a"
attribute applied to the page’s<div>
element and data-theme="c"
to each button:
Developing, Testing and Debugging
Before we go much further, let’s discuss the tools used to inspect the app as it is being built. Regularly feedback is essential during development and it is vital that, when unexpected behaviour is encountered, we have the means of digging deeper and diagnosing the underlying issue.
I find Chrome to be the best choice of browser for the large part of development and, by installing the usefulWindow Resizer extension, we can set the browser window’s dimensions for a good approximation of how the page will look on an iPhone.
Chrome’s built-in diagnostic tools are excellent for inspecting the document object model (DOM), examining element’s CSS styles, stepping through JavaScript, and for diagnosing performance issues using its built in profiler.
Although using Chrome is a quick way to get an accurate understanding of how the app will look and operate on iOS, there is no substitute for the real thing and it is useful to configure a website in IIS to allow the app to be accessed via Wi-Fi on the local network from an iPhone running Safari. The web rendering engine inside Chrome currently has the same core (i.e. WebKit) as Mobile Safari (although, this will change in the future), and although it is unusual to see differences in the way pages are rendered, it is still important to regularly test the app in its native environment to ensure performance is acceptable and behaviour is as designed.
Creating the Model
Now we’ve created the main page, the Task Timer is beginning to look like a real app, but this is in appearance only. jQuery Mobile handles the general page’s appearance but to bring the app to life, we must add behaviour using JavaScript with a little help from JQuery.
If you take a look at the finished app you’ll see that, in addition to jQuery and jQuery mobile, it refers to three JavaScript files: taskTimer.utils.js, taskTimer.model.js and taskTimer.ui.js (see below).
<html> <head> <!-- some tags removed --> <script src="http://code.jquery.com/jquery-1.9.1.min.js"></script> <script src="http://code.jquery.com/mobile/1.3.0/jquery.mobile-1.3.0.min.js"></script> <script type="text/javascript" src="scripts/taskTimer.utils.js"></script> <script type="text/javascript" src="scripts/taskTimer.model.js"></script> <script type="text/javascript" src="scripts/taskTimer.ui.js"></script>
These three scripts provide the interaction behaviour, input validation, model persistence and formatting logic that constitute the app. Their responsibilities and relationships are shown in the diagram below:
The UI
module is the only user-facing layer. It is responsible for responding to events generated by the user, such as screen taps and swipes, and it must present information held in the Model
to the user by manipulating the browser’s DOM. The UI
layer is also responsible for preventing inappropriate actions being performed, such as editing an empty task list, which, in this particular case, is achieved by hiding the Edit button when the last list item is removed.
The Model
is an in-memory, conceptual representation of the tasks, including the periods in which each task has been active. In addition to containing a set of structured data, the Model
is also responsible for controlling access to that data – fully encapsulating it – to prevent unnatural states from occurring, such as having a period of time with an end but no start. In other words, the model is a classic domain model.
The Utils
module is a collection of stateless methods (pure functions), generally containing logic for manipulating time and dates, which is used by both the UI
and the Model
layers.
Let’s take a look at an example Model
containing two tasks, the first of which is active:
Each task is uniquely identified by a GUID, which is generated using a simple algorithm. Using a GUID permits the task’s name to be changed without affecting any references to it.
Each task has a list of active periods with each having a start and end date, represented as the number of milliseconds that have elapsed since 1st January 1970. If a task is currently active, it’s end date will be null. The ID of the active task is held in activeTaskId
.
There are many alternative ways that could have been chosen to represent a task inside the Task Timer. A simpler model might have stored the total number of elapsed seconds as a single value, rather than having separate values for the start and end times. In one way this would be beneficial because we avoid having to calculate the difference between the two to produce the elapsed time. However, this simpler model would not work when we consider how iOS manages memory.
In iOS, when an app is on-screen and being interacted with, it is given the ability to execute instructions, update the screen and respond to events but, when another app is activated, or the screen becomes locked after a set period of time, our app will be frozen and will be evicted from memory if it runs low. iOS generally expects apps to be able to persist their state so they may be resumed at a later time. This gives the OS the freedom to reuse resources as deemed appropriate to conserve battery life and offer the best user experience.
The Model
shown above was chosen because it is ideal for persisting state that can be reloaded later, presenting the illusion that the app was constantly running throughout.
Persisting the Model’s State
Take a look at the Resources tab on Chrome’s Developer Window (press F12 to activate), and you’ll see a string ofJSON stored against the blackjetsoftware.com domain, under the tasks
key
Local Storage is a newish web technology that is now supported by all modern browsers, including Chrome and Mobile Safari. It is a key-value, client-side-only store that is isolated by domain. This is a remarkably useful technology, considering its simplicity and, as you can see from the image above, it is used by the Task Timer app to keep track of tasks and their times.
When the Task Timer is launched, an attempt is made to read the local storage value identified by the tasks
key and deserialise it to the in-memory model. If the key doesn’t exist, a model will be created containing no tasks.
if (localStorage.tasks) { var persistedModel = JSON.parse(localStorage.tasks); // ... }
Whenever, a task is created, edited, deleted, or when the active task is changed, the in-memory model is serialised to a string and written back to localstorage
:
localStorage.tasks = JSON.stringify(window.taskTimer.model);
This simple persistent store permits the Task Timer to present the illusion that it is continually running and tracking elapsed time against the active task. Because this data is stored in the browser on the client machine, it is completely private and cannot be accessed by other apps or devices. It is never sent to the server and the browser will enforce domain-level isolation so other websites cannot access it either.
Script Structure – the Module Pattern
Take a look at the JavaScript files, for example taskTimer.model.js
, and you’ll see they follow the same general structure:
This is the JavaScript Module Pattern. It provides all-important encapsulation for code modules to protect their internal data structures from being accessed accidentally, or deliberately, from outside code. This separation of interface and implementation is essential for building solid code whose behaviour can be reasoned through and is adaptable for future change. By using this pattern, only the module itself is visible from the global namespace (window.taskTimer.model
) and its ‘insides’ are selectively exposed, via the return structure, to be used by other code.
If your background is like mine – using languages that are strongly class based, like Java or C# – it can take a while to internalise this style of encapsulation to the point where it is second nature to use. However, I’d fully recommend persevering so you can create modules with crisply-defined responsibilities and low coupling that are easy to comprehend and reason through. This is key to maintainability and agility.
Unit Testing
A module like the taskTimer.model
, where the code doesn’t read from or update the user interface, is an ideal candidate for unit testing. Its public interface is likely to be fairly stable over time, since if it did change significantly the Task Timer has become something else. Also, models often contain complex logic that must be verified directly to cover all scenarios and edge cases and this can be tricky and time consuming to set up through the user interface alone. These two factors together suggest that the benefit of writing and maintaining an automated test suite for this module will be greater than its cost over the lifetime of the app.
All major languages and frameworks now have open source unit testing libraries available and JavaScript is no exception having many frameworks to choose from. For this project I chose to use QUnit, developed and maintained by the jQuery team.
Tests are defined in QUnit slightly differently to the usual xUnit pattern, where each test is a parameterless, non-returning method, and instead use a more idiomatic JavaScript approach of using anonymous functions.
Here is a sample of some of the tests in the Task Timer project to give you an idea how they’re constructed:
var model = window.taskTimer.model; test("adding task generates a non blank Guid", function () { var task = model.addTask('Task 1'); console.log('new id = ' + task.getId()); notEqual(task.getId(), ''); notEqual(task.getId(), null); }); test("getTaskById returns task if it exists", function () { var task1 = model.addTask('Task 1'); var task1Id = task1.getId(); deepEqual(model.getTaskById(task1Id), task1); }); test("getTaskById returns null if task doesn't exist", function () { deepEqual(model.getTaskById('0'), null); });
All standard assertion methods are available in QUnit, such as equal
, deepEqual
notEqual
, and being able to use a human-readable test description is a big advantage over tools like nUnit that require test names to be compiler friendly.
You can see the live test results for the Task Timer unit test suite by visiting the unit tests page
iPhone App Icon
There are several ways to specify which image should be used as the iOS app icon. The two main ones are:
- Place an image called
apple-touch-icon.png
in the website’s root directory. - Add a link tag:
<link rel="apple-touch-icon" href="/my-icon.png" />
to the page’s<head>
To Support Retina iPhone models (iPhone 4, 4S and 5), this icon should be 114×144 pixels with 24-bit colour.
Once an image has been specified, it will be used instead of the usual mini picture of the page when the app is added to the home screen.
Glassy Bevel
You may notice your image has a glassy bevel effect applied when it appears in iOS. This effect can improve your image’s appearance and make it feel more professional and three-dimensional, but some images will look worse, particularly those with their own lighting effects. For these, you can force iOS to use an unadulterated version by adding a -precomposed
suffix to your image name or the rel
attribute value.
Below is a pure black 114×114 icon with the standard iOS glossy bevel effect applied, produced by naming imageapple-touch-icon.png
or using the link tag: <link rel="apple-touch-icon" href="/my-icon.png" />
And here is the same pure black 114×114 icon without the effect, produced by naming image apple-touch-icon-precomposed.png
or using link tag: <link rel="apple-touch-icon-precomposed" href="/my-icon.png" />
If you study them closely, you’ll notice the built in iOS Apple apps use a mix of pre-composed and non-pre-composed icons. Contacts, Calculator, Calendar and Notes apps are all pre-composed (i.e. no glossy bevel) butMessages, App Store, Phone and Music apps use the standard beveled icon.
Although the Task Timer icon has a bevel, it is a manually-applied effect included in the image itself. This gives a more subtle effect than the one applied automatically by iOS, whilst still appearing like a classic iOS app. The icon was built in Paint.NET by taking the initial app image, of the checklist and stopwatch, and overlaying it with a 50% transparent layer containing the black glossy beveled image shown above. This was then blended using Screen mode to produce the finished icon:
Take a look at the pre-flattened Paint.NET icon file to see all layers used to construct the image.
Accessing Native Hardware and Services
The current version of the Task Timer app runs well within the confines of the browser but it’s conceivable that a future version might need to access the device’s hardware or services to provide new functionality. Perhaps, it would be useful to record the location at which a task was created using GPS, or support recording audio and video clips and attaching these to a Task for reference. Fortunately, for apps that need access to the iPhone’s native hardware and services, there are frameworks available that can bridge the gap from browser to OS. One popular framework is PhoneGap. This works by running your HTML, CSS and JavaScript within a special webview control that augments the DOM’s API with additional functions to expose the underlying device’s features.
This example shows how easy it is to retrieve the device’s current location using the PhoneGap API from JavaScript:
var onSuccess = function(position) {
alert('Latitude=' + position.coords.latitude + ', Longitude=' + position.coords.longitude);
};
var onError = function(error) {
alert(error.message);
}
navigator.geolocation.getCurrentPosition(onSuccess, onError);
As a bonus, an app built with PhoneGap can be submitted to the iOS App Store and installed on the user’s device, so it can be easily be discovered by users, monetised and run without needing a network connection.
Final Thoughts
Using jQuery Mobile together with common web technologies is a fantastic way to utilise existing skills to produce mobile applications that look and feel very similar to native apps. The availability of frameworks such asPhoneGap and Titanium offer a path for evolving your app so it may use native iPhone services and hardware without having to undergo an extensive rewrite.
It should be noted that using a web-based technology stack as described in this article is not suitable for all types of app. The additional layers of abstraction involved do reduce the potential for optimising the app’s performance and I wouldn’t recommend using this approach for any app that requires intensive dynamically rendered graphics, such as games or 3D modeling tools, but for many types of app, particularly server-based business apps and small utilities that don’t need extensive processing capability on the device, using jQuery Mobile, HTML, CSS and JavaScript is an excellent choice for efficiently producing native-looking apps using commonly available tools and skills.
I hope this article has offered a glimpse of what’s possible with browser-based iPhone apps and demonstrates the simplicity of building a basic iOS app.
Good luck building your first iPhone app and I look forward to hearing about your experiences!