Chuck McQuilkin

my writings, design experiements & code

Simply-List: iOS Web App with Local Storage

Posted by on Nov 15, 2013 in Code, Snippets, Web Design | No Comments

As Steve Jobs famously said, “Design is how it works.” The role of a Web designer increasingly requires some ability to write code because good design goes beyond appearances.

I built my own mobile web app to challenge myself to design a web app that would look and feel like a native iOS app. A todo list app makes a good beginner project. A warning, because my aim is targeting iOS it has not been designed to work on Firefox or IE:

Try Out Simply-List


Local storage is relatively new to the web, and a part of the HTML5 specification. It allows web applications to store more data on the client’s device than will fit in a cookie and retain it even if the user reloads the page. Local storage is currently supported by all major browsers except Opera Mini.

Apple’s HTML5 Offline Application Cache spec provides guidelines for creating offline web apps. Incorporating these guidelines into the web app mean that the app will not only store the user’s to-dos when they load the app in their browser but it will work when the user has no Internet connection. There are two main things that need are needed to make the app function offline on iOS. A link to a manifest file needs to be included at the top of the HTML document:


<html manifest="path/filename">

Add the following tags to instruct iOS to hide the status bar and tell iOS where to find the app’s startup and iOS icons:


<meta name="apple-mobile-web-app-capable" content="yes"/>
<meta names="apple-mobile-web-app-status-bar-style"content="black"/>
<link rel="apple-touch-icon-precomposed" href="apple-touch-icon-precomposed.png" />

Some additional tags instruct iOS to hide the status bar and tell iOS where to find the app’s startup and iOS icon images. When the user saves the app to their home screen, it shows up there just like a native app. When the user launches the app, the startup screen fills the screen of their device while the app loads.

Creating the manifest file is not as complex as it may sound. A manifest file is just a text file that is served with the MIME type of “text/cache-manifest” and saved with a filename ending in “.manifest.” Here is an example:


CACHE MANIFEST
# v20131109

# cache 

index.html

You can change the MIME type with your FTP program (or the command line). The manifest file is just a list of all the files your app needs to function offline.

HTML

The HTML structure is a container div surrounding a div for the todo list that contains a clear all button, header, and placeholder unordered list and an input form to add to the todo list. Simplicity is the goal so we omit an add button, instead an item will be added when the user exits the input field.

JavaScript

First we’ll create the function that runs the app (named Todos). This function will set most of the functions that will react to user input. The most important of these is the function below, bound to the change event on the input field (named description). This function will add whatever the user leaves in the form to the unordered list when the user exits the field and trigger the save function:


        $('#description').change( function() {
           var todo = $('#description').val();
           var listItems = $("#todos").children();
           var newID = listItems.length + 1;
           $('#todos').append("<li id='"+newID+"'><input id='checkbox"+newID+"'class='checkbox' type='checkbox'/><label for='todo"+newID+"'>" + todo + "</label><a class='delete-btn icon-cancel-7' id='delete-btn-"+newID+"'>Delete</a></li>");
           $('#form')[0].reset();
           addSwipeTo("#todos li");
           saveTodos();
           return false;
        }); 

The user’s input will be appended to the end of the list. The save function below will update local storage.


    var saveTodos = function() {
        $('li').each(function(i) {
            var todo = $(this).html();
            window.localStorage.setItem("ToDo:"+ (i+1), todo);
       });
    };

Creating a single save function means that this code won’t need to be re-written several times within the final app. All the local storage code will be within an if statement that checks whether the user’s browser supports local storage. The save function itself simply saves all the HTML content within each list item. Local storage can only save data in key value pairs. here we create a key by looping over the items in the list and adding “ToDo:” to the front of the incremental value (i) to avoid conflicts with any existing data in the user’s browser. The value corresponding to each key becomes the HTML content of each list item.

The CSS

The appearance of the check mark and the strike-through effect are accomplished using CSS and an icon font. It’s a nice effect that I stumbled upon on CSS Deck. The CSS uses a :before pseudo-element to position an attractive blue check box over the default HTML checkbox (which is hidden) and a :after pseudo-element to cover the empty box with a check-mark icon when the user checks off an item. The description of the todo item is crossed off using the plain HTML strike-through property. The icon font from Fontello allows us to add a check-mark icon to checked off items without using a CSS sprite. Icon fonts are vectors so they will display at full resolution on any display without adjustments, they are also smaller than their CSS sprite equivalent. Fontello makes it easy to include icon fonts in web projects.


	.to-do-list input[type=checkbox] {
		cursor: pointer;
		position: relative;
		visibility: hidden;
	}

The CSS above hides the HTML checkbox. The code below adds the custom checkbox icon after HTML checkbox and the strikethrough:


	.to-do-list input[type=checkbox]:checked:after {
                font-family: "todo";
                color: #999;
                font-size: 20px;
                font-weight: 100;
                /*absolutely positioned*/
                position: absolute; top: 0; left: 0;
		border: 2px solid #fff;
		color: #979797;
		content: '\e804';
	}
	.to-do-list input[type=checkbox]:checked + label {
		color: #979797;
		font-weight: normal;
		text-decoration: line-through;
	}

Mobile apps should support swipe gestures as well as tap/click equivalents. Luckily there are several libraries that allow us to detect swipe-events in mobile web apps. Here I use touch swipe to detect a generic right or left swipe gesture to show a delete button for over the swiped item. JQuery allows us to animate the appearance of the delete button. After the function has been defined it needs to be bound to the link items.

The delete button also uses our icon font for the “x” icon. The buttons already have event listeners and events attached to them in the ToDo function, shown below. When the user clicks on a delete button, the button’s parent list item’s ID is defined as a string, the ID is extracted from the end. We use slice here because we know the beginning of the list item’s ID will be prefixed with “todo” but we are not sure whether the ID will be one or two digits. We then search local storage for the item matching the ID and we remove it. The list item is then removed from the DOM with another JQuery animation. The complete JavaScript code is below:


(function() {
    //Enable swip to delete
    var addSwipeTo = function(selector) {  
         $(selector).swipe("destroy");
         $(selector).swipe({
            swipe:function(event, direction, distance, duration, fingerCount) {
                //console.log("You swiped " + direction );	
                if ( direction === 'left' || direction === 'right' ) {
                    (this).find('.delete-btn').animate({ opacity: 1, width: 'toggle' }, 200).addClass('open');
                } else { null }
            },
            threshold:0 //Default is 75px, set to 0 so any distance triggers swipe
          });
    };
    var saveTodos = function() {
        $('li').each(function(i) {
            var todo = $(this).html();
            window.localStorage.setItem("ToDo:"+ (i+1), todo);
       });
    };
    addSwipeTo("#todos li");
    if (window.localStorage) {
        function retrieveToDos() {
            var i = 0;
            var k;
            if ( localStorage.length > 0 ) {
                for (i = 0; i <= localStorage.length; i++) {
                    k = localStorage.key(i);
                    if (/ToDo:d+/.test(k)) {
                        $('#todos').append('<li>'+window.localStorage.getItem(k)+'</li>');
                    }
                }
            } else {
                var isTouchDevice = function() {  return 'ontouchstart' in window || 'onmsgesturechange' in window; };
                if (isTouchDevice != 'true') {
                    $('.tn-box').addClass('tn-box-active');
                } else { null }
            }
        } retrieveToDos();
        $('#description').change( function() {
           var todo = $('#description').val();
           var listItems = $("#todos").children();
           var newID = listItems.length + 1;
           $('#todos').append("<li id='"+newID+"'><input id='checkbox"+newID+"'class='checkbox' type='checkbox'/><label for='todo"+newID+"'>" + todo + "</label><a class='delete-btn icon-cancel-7' id='delete-btn-"+newID+"'>Delete</a></li>");
           $('#form')[0].reset();
           addSwipeTo("#todos li");
           saveTodos();
           return false;
        }); 
        setTimeout(ToDos(),2000); 
    } else {
        alert('your browser does not appear to support local storage');
    }
    
    function ToDos() {
        var deletebtn = $('.delete-btn');
        deletebtn.css('opacity', 0);
        deletebtn.toggle();
        
        $('.delete-btn').live('click', function(e) {
            var str = $(this).attr("id");
            var id = str.charAt( str.length-1 );
            console.log(id);
            window.localStorage.removeItem("ToDo:"+ id);
            $(this).parent().animate({ opacity: 0.25, left: '+=50', height: 'toggle'}, 500);
        });
        $(':checkbox').click( function() {
           var id = $(this).attr("id");
           if ($(this).is(':checked')) {
               $(this).attr('checked', 'checked');
               //console.log(id + ' is checked');
               saveTodos();
           } else {
               $(this).removeAttr('checked');
               //console.log(id + ' is unchecked');
               saveTodos();
           }
        });
        $('#clear').click( function() {
            window.localStorage.clear();
            location.reload();
            return false;
        });
        addSwipeTo("#todos li");
    } // end ToDos function
})();

Sources

HTML5 makes it possible to develop many different types of simple mobile apps using HTML, CSS, and JavaScript instead of Objective C or Java. For more info on how to Local storage check out the following:

How to use local storage for javascript

Refer to the following links to learn more about developing iOS web apps:

Backchannel

Offline Application Cache

A script is available below that generates an “add to home screen” reminder balloon to encourage repeat visitors to save the app on their device:

Add to Home Screen

Coffee Blog Post on RPA’s blog

Posted by on Feb 10, 2013 in Advertising | No Comments

In July I traveled to Costa Rica to attend a friend’s wedding. During the trip I was able to visit a coffee plantation. Ever since then I have thought about the visit whenever I fill my coffee cup at RPA. I wrote a short blog post about my experience on RPA’s culture blog.

Robert Egger

Posted by on Aug 6, 2012 in Design | No Comments

It’s always encouraging to hear a designer talk about what inspires them. Regardless of the field, design attracts people who are dedicated to making everything that they touch better.

Design ideas do seem to come directly from the experience of working with your hands, even if it just means sketching before heading to the computer.

Who killed the inactive button state? – Blog Post on UX Booth

Posted by on Jun 26, 2012 in UX | No Comments

A few weeks ago I was designing a web application that included a form. When it came time to design and spec the submit button and I realized that including an inactive (or disabled) state for submit button was not only unnecessary, but it would detract from the usability of the application. Please check out the blog post I wrote about inactive states on UXBooth.

Digital Photography Cheat Sheet

Posted by on Jun 5, 2012 in Art | No Comments

An awesome digital photography cheat sheet by Miguel Yatco for keeping all the details straight.

Instapol – MobileHackDays 2011 Winner

Posted by on Sep 20, 2011 in Web Design | No Comments

Last weekend I had the pleasure to work with three brilliant Harvey Mudd students, Ozzie Gooen, Rahul Swaminathan and Paul Hobbs building this mobile web app Instapol for Mobile Hack Days. Instapol is a mobile web application that allows presenters to gain instant feedback from their audience. Instapol creates real-time graphs of audience responses to questions.

Stephen Powers Sign Painter

Posted by on Aug 15, 2011 in Advertising, Art, Branding, Design | No Comments

Quoted from Drawn. Hand-painted lettering is powerful and eclectic, but also unique and surprising. There’s nothing else quite like a hand-painted sign.

Adobe Edge Experiment

Posted by on Aug 5, 2011 in Web Design | 2 Comments

The animation above isn’t Flash, it’s a JavaScript animation created with Adobe Edge. Adobe Edge is a prototype application that allows designers to create iPhone/iPad friendly JavaScript and CSS3 animations. Edge is an HTML5 technology and designed for mobile devices, but it’s not sophisticated enough to be a Flash replacement. Right now it lacks bells and whistles like filters, drop shadows and layer styles. There’s also no coding panel to insert scripts. The application interface looks and feels more like AfterEffects than Flash, hopefully it will eventually evolve into a robust tool for creating cross-platform rich media content.

Important Statistics about Copywriters

Posted by on Jun 30, 2011 in Advertising | No Comments

This made the rounds about a month ago, but it’s funny.