aTabSplitter – the web standards edition

I thought that it would be helpful to document the experience I had while building a web application using jQuery Mobile. And this is what this article is all about. But before going into details let me set the context.

I started building mobile applications a while ago using Adobe AIR and the Flex framework. My first “production ready” application was called aTabSplitter and it is available for download in Android and BlackBerry markets. It was a fun and eye-opening experience. I mean it is one thing “to play” with technology and an entirely different thing to build something that will be actually useful and people will love to use.

Recently, as I was playing with jQuery mobile I had an idea: what if I tried to rebuild the application using HTML, CSS, and JavaScript? To make things more complicated I wanted to retain the same UI and user experience. In other words I treated the mobile application as the design and user workflow diagrams you’d get from a UX.

If you haven’t tried the application yet let me briefly describe it: it allows you to split a restaurant bill. The important workflows are the first screen, where you add new persons, delete them, move them around the table, or adjust the tip, and the calculator screen where you add the items for a single person. Actually, I am pretty proud because I think I found a good/intuitive user interface.

And by the way, here is the link for this web app.

Here are screenshots with the application running on desktop and mobile devices.

 

Android 4 Phone:

 

On iPad:

 

The Plan

Here is what I was set to achieve: a web application that will work on mobile devices and the desktop. Although I could have gone the PhoneGap route and pack the whole thing into a native app, I wanted to have a single base code base for the first pass that will work in browsers on all these different devices. As a framework I decided to go with jQuery Mobile. This, at least in theory, was a perfect match for my application given the fact that all I needed was a nice way to keep the two screens separated from a functional point of view.

I didn’t see any need for other frameworks. So for the rest of functionality I wrote JavaScript and used HTML/CSS and images to create all the UI I needed. Because I went for a web application I chose to implement only the main features of the original AIR app. Nevertheless, I wanted to use all the new features available in modern browsers that made sense for my app so I could make a responsive and usable web app. For the rest of the article, I will highlight some of the things that were either trickier than I anticipated or altogeher unexpected and the general approach. I hope you will find it useful.

jQuery Mobile and architectural considerations

jQuery mobile was a pretty obvious choice because of the features it offers. The way it lets you organize your application using “pages” and keep them in the same HTML document is perfect for what I needed. Remember that I wanted to build a small application that has only two “screens”.  Also I like the fact that I can define most of the UI using pure HTML without the need for JavaScript.

Once I had the UI in place, I started to write the JavaScript code that powers the user interface. Normally, I would wrap the different functionalities in a number of objects. However for this project I decided to go against the best practices and write mainly functions. The only way I organized them was by splitting them in three different files: home.js, calculator.js, and utils.js. The first two files hold the functions used for the home screen and calculator screen and the third one holds utility functions. Why did I choose to go against best practices? One main reason: the application is too small to be worth spending time on architecting the code.

Although conceptually, there are two pages in my application as far as the browser is concerned there is only one page. This means that the JavaScript code has visibility over the whole application DOM.

In index.html there is a little bit of JavaScript code used for boot strapping the application. It is the usual jQuery business, executing the bootstrap code on the jQuery ready event. This is where I attached various event listeners on UI components and I set the height for main UI components.

The part with setting up the height manually was a bit of a surprised. I was expecting that jQuery Mobile will take care of this for me and when the content in the page didn’t overflow it would set the size of the div to be equal to the viewport minus the header and footer. This is not the case.

Another interesting thing in index.html is the array that holds references to all the plates on the screen, as well as a reference to the trash bin image – so I can quickly check if the user wants to delete a plate (if you drag a plate on top of the trash bin it will be deleted).

One of the issues I had was how can I make sure that the user doesn’t land directly on the second page. It doesn’t make sense to support this and if it happens it is confusing for the user. After I spent some time reading the documentation and talking to my fellow evangelist Ray Camden I decided to go for an extreme solution. Inside the function executed for the jQuery ready event I checked the current page value. If it is calculator (the second page) then I reload the page:


//forcing the app to load the first screen if the user lands directly on the second page
if ($.mobile.activePage[0].id === 'calculator') {
   l = window.location;
   l.hash = '';
   window.location = l;
}

Finally, I tried to keep the dependencies on jQuery to a minimum. Why? Because I plan to rewrite the application using other frameworks and if I decide to go without jQuery then I don’t want to have lots of code to clean up.

Custom Data Attributes

Obviously this application must have a data model, right? There are different approaches I could have used to store the data model. The one that made most sense to me is using custom data- attributes.

Each plate that you add has the data stored in two attributes:

  • data-i : stores the items you add. For example, if you added 5 + 12, then data-i = [5, 12]
  • data-t: stores the total. Using the example above, data-t = 17
This is convenient because if I want to delete a plate I just remove that element from the DOM and both the visual part and the data model go away.

Touchstart event vs mouse click event

If you’ve worked with mobile web apps then chances are that you already know this. If not then you should pay attention. If you use click events for your UI components (buttons for example) when you run the application on a mobile device (including most powerful tablets) you will notice a huge lag between the time you touch a button and the execution of the click event listener. It is so big that it renders the app almost unusable. The solution is pretty simple: use touchstart events instead of clickevents. On touch devices whenever you touch the screen you get events in the following order:

  1. touchstart
  2. touchmove
  3. touchend
A click event is basically an event that triggers once you’ve closed the whole loop – actually if you’d listen for touchend you should get this event long before the click one. So this is the reason for getting improved speed when using touchstart instead of click – you use the very first event triggered.

Dealing with multiple touch events

When you use touchstart and click events in your application a lot, you will end up in a situation where you need to add logic to handle some use cases. The first screen of my application is pretty heavy on touch events usage: you can touch the screen to add a new plate, you touch a plate to go the second screen and edit, you can move around a plate, or you can touch the slider to adjust the tip. I have all my events registered for the bubbling phase (if you need a refresher on the event phases check this resource).

Much to my surprise, I couldn’t use the slider on mobile devices. Every time I touched the slider a new plate was added to the screen. My solution to this issue was a quick hack to check for the target object in the event listener that adds a new plate – if the target is part of the slider then I don’t add a new plate.

Another area where I had to pay attention was the plate itself. You can touch/click a plate to move around or to edit. I decided that if you move a plate around it doesn’t necessarily mean you want to edit. I change the app to the edit plate screen if the plate has moved more than 10 pixels in any direction.

Custom components

Most of the UI components I needed for my app were covered by the browser or jQuery Mobile. However there was one I needed to create – the component that represents one person on the first screen.

It is pretty simple component: an image that represents a plate and a label that displays the total. At least it seems simple until you try to make it work. Because I wanted the text to be centered both vertically and horizontally. Also, if the text grows bigger than the image, I wanted it to be centered (see the picture below).

After some trial and error I managed to come up with the following code for this:

<div class="plate">
    <img src="assets/images/plate-big.png" alt="" />
    <div>0</div>
</div>

Here is the CSS for this component:


.plate {
      position: absolute;
      color: #000000;
      font-size: 20px;
      overflow: visible;
      text-align: center;
      -moz-user-select: -moz-none;
      -khtml-user-select: none;
      -webkit-user-select: none;
      user-select: none;
}
.plate > div {
      text-align: center;
      margin-top: -65px;
      width: 100%;
}

The second custom component was the keyboard I use on the second screen. All I needed was a basic calculator keyboard – actually even simpler as I didn’t need all the operations. Why did I go for a custom keyboard? Because it makes my app more usable by giving the user only the keys he needs.

I used the old school tables approach. And it worked beautifully on the desktop but not so good on mobile phones – the keyboard was falling out the screen. After some debugging using weinreI realized what was happening. jQuery Mobile augments your code. Typically, for any button you have in your app it will generate a number of divs/spans that are positioned on top of the button and these elements actually provides the visual appearance of the button.

Because it does a generous use of padding and margins there was no way I could manage to fit 5 buttons on the same row on an iPhone or Android phone. How did I fix this? Well my solution is certainly not the most elegant but it works. I used Media Queries, CSS selectors, and the infamous !important to overwrite the jQuery styles for my keyboard when the app is running on mobile phones. If anyone knows a better solution, please tell me!

Saving application state – Web Storage

Suppose you add some items and persons to the app and you accidentally hit the back button. Of course, the whole state of the application will be lost. And this is something I didn’t want to happen.

I save the application state using the Web Storage localStorage feature. This feature is supported by all modern browsers and it offers a simple API to store key/value pairs (think of a hash map or associative array in PHP). The only catch is that the value is saved as a string. The application state is saved in the form of an array of objects where each object maps one of the plates: it holds the top and left position, the total, and the items. This information is enough to restore the application state. The relevant code is in the scripts/home.jsfile. Here is the code for saving and reading:


//
function persistApplicationState(arr) {
    var i, l, toSave, el;
    toSave = [];
    for (i = 0, l = arr.length; i 0 && localStorage) {
       localStorage.setItem('plates', JSON.stringify(toSave));
   }
}

function getPersistedPlates() {
    var ret;
    if (!localStorage)
       return false;
    ret = JSON.parse(localStorage.getItem('plates'));
    if (!ret || ret.length === 0)
       return false;
    return ret;
}

Note that I am using JSON.stringify() and JSON.parse(). This is related to the fact that Web Storage saves only strings. In terms of size, most browsers offer by default 5MB (Firefox warns the users and asks for permission).

Reduce Loading Time – Application Cache

You might think of Application Cache like browser caching on steroids. By using Application Cache you can dramatically improve the loading speed of your application and the app can be used while users are offline.

This feature is pretty simple to use: you create a manifest file (which is a simple text file with an appcache extension and the first line must have this text CACHE MANIFEST). In this file you add entries for all the things you want to cache – could be CSS files, JS files, images, HTML. Then you enable the Application cache by setting the manifest attribute on the <html> tag:

<html manifest="app.appcache">

Now, every time you want to invalidate the cache and make the browser reload the resources you change something in the application cache file. Most people add a commented line with a timestamp and version number and they just change this. Here is my cache file:


CACHE MANIFEST
# 2012-02-21:v9.5

CACHE:
index.html
styles/styles.css
styles/jquery.mobile-1.0.1.min.css
scripts/jquery-1.6.4.min.js
scripts/jquery.mobile-1.0.1.min.js
scripts/utils.js
scripts/home.js
scripts/calculator.js
assets/images/plate-big.png
assets/images/table_01.jpg
assets/images/bill.jpg
assets/images/trash.png
assets/favicon.ico
styles/images/ajax-loader.png
styles/images/icons-18-black.png
styles/images/icons-18-white.png
styles/images/icons-36-black.png
styles/images/icons-36-white.png

Once the application has loaded for the first time, you will notice that subsequent loads are extremely fast. This is because all the resources used in the app are cached locally. And remember that each time you change something in your application you have to invalidate the cache.

iOS Offline Web Application

iOS lets you save a web site/application to the home screen. This is the closest you can get to a native app with HTML without using something like PhoneGap. Also this feature plays nicely with Application Cache.

You have control over a number of things:  the icon to be used for your application, an image to be used during the loading time (startup image), and if you want to hide the Safari Mobile chrome or not. All these are set or controlled by using <meta> or <link> tags added to the <head> section of your webpage.

In the index.html you will find these tags:

<!-- Hiding the browser chrome -->
<meta name="apple-mobile-web-app-capable" content="yes" />
<!-- Setting a startup image for the app -->
<link rel="apple-touch-startup-image" href="assets/images/table_01.jpg">
<!-- Providing Icons for iOS and iPAd -->
<link rel="apple-touch-icon" href="apple-touch-icon-57x57-precomposed.png" />
<link rel="apple-touch-icon" sizes="72x72" href="apple-touch-icon-72x72-precomposed.png" />
<link rel="apple-touch-icon" sizes="114x114" href="apple-touch-icon-114x114-precomposed.png" />

Here are screenshots with the app saved to the home screen and running without the browser chrome.

 

You can read more about this topic here.

Preparing the app for mobile browsers

By default, on most mobile platforms you can scroll the page in the browsers a few pixels up and down even when the page content fits to the screen. Man, I hated this feature so much. Why? Because the main paradigm for using the first screen is by touch and dragging. Now every time you tried to move a plate up or down, the whole page was moving in the same direction. It was disturbing and annoying at the same time. To fix this I added a listener on the body for the touchmove event to prevent the default behavior:


// fix for canceling the scrolling of the body on mobile devices
 document.body.addEventListener('touchmove', function(event) {
           event.preventDefault();
      }, false);

The second thing I had to do was to set the layout viewport width to the device width and make sure scaling was disabled – I didn’t feel that the user could benefit in any way by zooming the app in or out. So I placed the following meta tag in the head section:

<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, user-scalable=no">

This is a good read on understanding the mobile layouts viewports and screen sizes and the difference between screen resolution and CSS pixels.

Handling different screen sizes/devices – Media Queries

The Media Queries feature was quite handy especially for getting the second screen of the application right. Because that screen has a keyboard, I had to make sure that the keyboard size was big enough to be usable on the phones and, at the same time, stays within the screen width.

Due to how the layout viewport works on mobiles and how jQuery Mobile styles buttons, the code that was working perfectly on the desktop even when minimizing the browser window was entirely unusable on iPhone or Android phones. The keyboard (on the second screen) didn’t fit so almost half of it was invisible. By setting up two additional sections for devices with maximum width 720px (Samsung Nexus) and 640px (most Android phones and iPhone/iPod Touches) I managed to fix the problems.


//styles/styles.css

@media only screen and (max-device-width: 720px) {

...

}

@media only screen and (max-device-width: 640px) {

...

}

Debugging the code

Nothing new here. For the desktop I used Web Inspector and for mobile, weinre. In case you have never used weinre then you can read my post on this topic here. I was so happy to have weinre because it helped me to understand and fix the issues I had with the layout on the calculator screen.

Google released this month a special build of Chrome for Android that enables remote debugging. Basically you get the web inspector on your desktop hooked up to the app running on your mobile device. Unfortunately, the application is not available in all the countries. I don’t exactly understand why and I don’t know when it will be available world wide.

The Code

You can download the source code from GitHub or you can play with the application. I tested it on desktop (Firefox, Chrome, and Safari) and on Android Phones/iPad/iPhone4.

Conclusions

As I said at the beginning, I had lots of fun while building this. The fact that I was able to test some of the frameworks and HTML features I’ve been playing with in a real app was a bonus.

In terms of development effort, I spent most of the time on making the application look the same (or as close as possible) on different browsers/devices. I used Chrome during my development. And once everything was in place I was sure that I was done. Poor me, because I kid you not the app looked differently in Firefox, Safari, and Chrome – I mean each browser rendered something different than the other two :D

In terms of performance and quality there is a big difference between how the app performs on the desktop and how it works on mobile. On mobile there is some screen flickering, the transitions are not always smooth, and the UI is not always perfectly aligned. You might argue that it is jQuery Mobile’s fault or my fault. My opinion is this: it is what it is. I mean mobile browsers have come a long way in recent years but there is still a gap between desktop and mobile. This is what is possible today. Tomorrow it will be better, no doubts.

Now, that I have the V.1 I am ready to experiment with different approaches. First on my list is to use PhoneGap to package my web application as a native application. This will give me a chance to add another feature from the original app: taking a photo with the device camera and replacing the picture from the first screen. Second, I will try frameworks other than jQuery Mobile.

9 thoughts on “aTabSplitter – the web standards edition

  1. Yeah, I know :D. But even with the current state of economy we still have few good years ahead of us until we reach that point :D

  2. Dear mr. Corlan, I know you like to taste new beverages but this is pretty interesting bill. May I ask what were you drinking :D ?

    Regarding your experience during process of creating this code (not the bill) – I share your thoughts. jQuery Mobile still has room to improve transitions but I believe every HTML mobile UI framework has same issues.

    XUI has it, Sencha has these annoying transition blinks too. Maybe Sencha has faster page transitions but there is one thing that is much better in jQM – back button. jQM handles android hardware back button like it should and Sencha gets you out of the app :)

    Just my 2 cents ;)

    I hope I’ll be around next time you decide to pay this kind of bill ;)

    Cheers,
    Ivan

  3. @Ivan
    My friend,

    It is not important how big the bill is. It is important who’s gonna pay for it :D
    So are you up for this?

    cheers,
    Mihai

  4. Pretty cool Mihai…

    I have a few comments.

    1) The transitions between table and calculator are a little jarring.

    2) I know this is a demo but you should consider key mappings for the calculator so that people on desktop can try it out with the 10-key.

    3) The table background image is a little much. Might be better with a simple top down illustration of a table rather than an isometric photo of one. You could even draw it in canvas and set it as a background image.

    4) The image of the plate could be a radial gradient rather than an image.

    Otherwise good job.

  5. Pingback: Using PhoneGap Build and Debug : Mihai Corlan

  6. @andy matthews

    Good observations :)

    1) I don;t know how I can make this faster. Maybe when jQuery Mobile will roll up new/faster transitions?

    2)Too lazy to do it :)

    3) & 4) I wanted to see if I can implement what I had in the AIR app. Second, I am afraid that using CSS3 for drawing lots of gradients might slow down the app on some platforms.

    Thanks for the feedback,
    Mihai

Leave a Reply

Your email address will not be published. Required fields are marked *