Chapter 14. Packaging Apps as Bookmarks: Bookmarklets and Data URLs

Because iPhone Web applications function inside the Safari environment, as a Web developer, you have two seemingly obvious restrictions: you must live with the built-in capabilities of the Safari/WebKit browser; and you need a constant Wi-Fi (or, for iPhone, 3G) connection to run any application.

As you already found out in Chapter 13, "Bandwidth and Performance Optimizations," you can use HTML5 offline storage capabilities to get around the dependency on a live connection. However, two lesser-known technologies — bookmarklets and data URLs — can provide similar results. These technologies have actually been around for years, but they have tended to exist on the periphery of mainstream Web development. However, developers are now reexamining these two developer tools to maximize the potential of the iPhone Web application platform.

Bookmarklets (short for bookmark applets) are mini JavaScript "applets" that can be stored as bookmarks inside Safari. A data URL is a technique for storing an entire Web page or application (pages, styles, images, data, and scripts) inside a single URL, which can then be saved as an iPhone bookmark inside Safari. This application-in-a-bookmark can then be accessed in offline mode.

Bookmarklets

A bookmarklet is JavaScript stored as a URL and saved as a bookmark in the browser. It is typically used as a one-click applet that performs a specific task or performs an action on the current Web page. A bookmarklet uses the javascript: protocol followed by script code. For instance, here's the simplest of examples:

javascript:alert('iPhone')

Because the scripting code for a bookmarklet is housed inside a URL, the script must be condensed into one long string of code. Therefore, to enter multiple statements, separate each line with a semicolon:

javascript:alert('Bookmarklet 1'),alert('Bookmarklet 2')

In this case, I added spaces inside each of the strings. You can either substitute %20 for a blank space or let Safari do the conversion for you.

If the script returns a value, be sure to enclose it inside of void() to ensure that the JavaScript code runs as expected. For example, the following WikiSearch bookmarklet displays a JavaScript prompt dialog box (see Figure 14-1) and then calls a Wikipedia search URL using the user's value as the search term:

javascript:t=prompt('Search Wikipedia:',getSelection());if(t)void(
location.href='http://en.wikipedia.org/w/wiki.phtml?search='+escape(t))
WikiSearch bookmarklet

Figure 14-1. WikiSearch bookmarklet

Here's a second example that provides a front end onto Google's define service:

javascript:d=prompt('Define:',getSelection());if(d)void(
location.href='http://www.google.com/search?q=define:'+escape(d))

Adding a Bookmarklet to Safari for iPhone

Bookmarklets are normally added in a standard browser through a drag-and-drop action. However, because that user input is not available in Safari on iPhone, you need to add the bookmarklet through the following process:

  1. On your main computer, create your bookmarklet script and test it by pasting it into the Address box of Safari.

  2. Once the functionality works as expected, drag the javascript: URL from your Address box onto your Bookmarks bar in Safari. If you are going to have a set of bookmarklets, you may want to create a special Bookmarklets folder to store these scripts.

    Or, if your bookmarklet is contained within the href of an a link, drag the link onto the Bookmarks bar instead.

  3. Synch the bookmarks of your iPhone and main computer through iTunes.

  4. Access the bookmarklet in the Bookmarks inside Safari (see Figure 14-2).

    Accessing a bookmarklet from iPhone

    Figure 14-2. Accessing a bookmarklet from iPhone

Alternatively, you can add a bookmarklet directly into Safari's Bookmarks by creating a link to any normal Web page and then editing the URL of the bookmark.

Exploring How Bookmarklets Can Be Used

Although bookmarklets can be used for these sorts of general purposes, their real usefulness to the iPhone Web app developer is turning JavaScript into a macro language for Safari to extend the functionality of the browser. For example, Safari always opens normal links in the existing window, replacing the existing page. Richard Herrera from www.doctyper.com wrote a bookmarklet that transforms the links of a page and forces them to open in a new tab. Here is the script, which is tricky to read because it is contained within a one-line, encoded URL:

javascript:(function(){var%20a=document.getElementsByTagName('a'),for(var%20i=0,
j=a.length;i%3Cj;i++){a[i].setAttribute('target','_blank'),var%20img=document.
createElement('img'),img.setAttribute('class',%20'new-window'),img.setAttribute
('src','data:image/gif;base64,'+'R0lGODlhEAAMALMLAL66tBISEjExMdTQyBoaGjs7OyUlJ
WZmZgAAAMzMzP///////wAAAAAAAAAAAAAA'+'ACH5BAEAAAsALAAAAAAQAAwAAAQ/cMlZqr2Tps13
yVJBjOT4gYairqohCTDMsu4iHHgwr7UA/LqdopZS'+'DBBIpGG5lBQH0GgtU9xNJ9XZ1cnsNicRADs
='),img.setAttribute('style','width:16px!important;height:12px!important;
border:none!important;'),a[i].appendChild(img);}})();

When executed, the script adds a target="_blank" attribute to all links on the page and adds a small "new window" icon after the link (see Figure 14-3).

New window icons added after links

Figure 14-3. New window icons added after links

iPhone users can then use this self-contained "applet" on any page in which they want to transform the links. Notice that the icon image used in the script is encoded inside a data URL, so the script does not depend on external files.

Although the entire script needs to be condensed into a single string of commands, Safari is actually smart enough to convert the hard breaks for you when a multilined script is pasted into the URL box. Just make sure each statement is separated by a semicolon. Therefore, the following code, which is much easier to work with and debug, would still execute properly when pasted directly into the URL box:

javascript:(
 function(){
        var a=document.getElementsByTagName('a'),
        for(var i=0,j=a.length;i%3Cj;i++) {
               a[i].setAttribute('target','_blank'),
               var img=document.createElement('img'),
               img.setAttribute('class','new-window'),
 img.setAttribute('src','data:image/gif;base64,'+'R0lGODlhEAAMALMLAL66tBISEjExMdTQ
yBoaGjs7OyUlJWZmZgAAAMzMzP///////wAAAAAAAAAAAAAA'+'ACH5BAEAAAsALAAAAAAQAAwAAAQ/cMl
Zqr2Tps13yVJBjOT4gYairqohCTDMsu4iHHgwr7UA/LqdopZS'+'DBBIpGG5lBQH0GgtU9xNJ9XZ1cnsNi
cRADs='),
               img.setAttribute('style','width:16px!important;
               height:12px!important;
               border:none!important;'),
               a[i].appendChild(img);
        }
 })();

Bookmarklets can be handy developer tools to assist in testing and debugging on iPhone. For example, the following bookmarklet, based on a script created at www.iPhoneWebDev.com, gives you View Source functionality (see Figure 14-4) on iPhone:

javascript:
var sourceWindow = window.open("about:blank");
var newDoc = sourceWindow.document;
newDoc.open();
newDoc.write(
"<html><head><title>Source of " + document.location.href +
"</title><meta name="viewport" id="viewport" content="initial-scale=1.0;" +
"user-scalable=0;maximum-scale=0.6667;width=480"/><script>function do_onload()"
+ "{setTimeout(function(){window.scrollTo(0,1);},100);}if(navigator.userAgent.
indexOf" + "("iPhone")!=−1)window.onload=do_onload;</script></head><body>
</body></html>");
newDoc.close();
var pre = newDoc.body.appendChild(newDoc.createElement("pre")); pre.appendChild(
newDoc.createTextNode(document.documentElement.innerHTML));
Viewing a page's source on iPhone

Figure 14-4. Viewing a page's source on iPhone

If you'd like to work with these sample bookmarklets, save yourself some typing time by going to www.wrox.com and downloading these instead.

Storing an Application in a Data URL

In addition to JavaScript functionality, you can store a Web page or even a complete application inside of a bookmark. The data: protocol allows you to encode an entire page's content — HTML, CSS, JavaScript, and images — inside a single URL. To be clear, data URLs store not a simple link to a remote page, but the actual contents of the page. This data URL can then be saved as a bookmark. When users access this bookmark in Safari, they can interact with the page whether or not they have Internet access. The implications are significant — you can use data URLs to package certain types of Web applications and get around the live Internet connection requirement.

Constraints and Issues with Using Data URLs

Although the potential of data URLs is exciting for the developer, make sure you keep the following constraints and issues in mind before working with them:

  • You can store client-side technologies — such as HTML, CSS, JavaScript, and XML — inside a data URL. However, you cannot package PHP, MySQL, or any server-side applications in a bookmark.

  • Any Web application that requires server access for data or application functionality needs to have a way to pack and go: (1) use client-side JavaScript for application functionality, and (2) package a snapshot of the data and put it in a form accessible from a client script. However, in most cases in which you need to use cached data, you'll want to use HTML5 offline storage instead (see Chapter 11).

  • The Web application must be entirely self-contained. Therefore, every external resource the application needs, such as images, style sheets, and .js libraries, must be encoded inside the main HTML file.

  • External resources that are referenced multiple times cannot be cached. Therefore, each separate reference must be encoded and embedded in the file.

  • Images must be encoded as Base64, although the conversion increases their size by approximately 33 percent. (Base64 is the process of encoding binary data so it can be represented with normal character set characters. Encoded Base64 data must then be decoded before it can be used.)

  • The maximum size of a data URL in Safari for iPhone is technically 128KB, although in actual practice, you can work with URLs much larger, at least up to several megabytes. However, performance of the Safari Bookmark manager suffers significantly when large amounts of data are stored inside a bookmark. Therefore, think thin for data URL-based applications.

  • Safari has issues working with complex JavaScript routines embedded in a data URL application. For example, the UI frameworks discussed in Chapter 3, "Building with Web App Frameworks," may not functional inside a data URL, thus greatly limiting the potential for Web developers to take advantage of this offline storage option.

  • If your development computer is a Mac, you should be okay working with data URLs. However, if you are working with the Windows version of Safari and trying to synch the bookmark with Safari for iPhone, be careful: Safari for Windows has major limitations in the size of data it can store inside a bookmark. Consider using HTML5 offline cache (see Chapter 11) instead.

Creating a Data URL App

After examining these constraints, it is clear that the best candidates for data URL apps are those that are relatively small in both scope and overall code base. A tip calculator, for example, is a good sample applet because its UI would be simple and its programming logic would be straightforward and not require accessing complex JavaScript libraries. I'll walk you through the steps needed to create a data URL application.

After reviewing the constraints and making sure that your application will likely work in an offline mode, you will want to begin by designing and programming as if it were a normal iPhone Web app application. For this sample applet, the interface of the tip calculator is based on a subset of a legacy version of the iUI framework. (To limit the size of the app, I am not including references to the framework.)

Figure 14-5 shows the Tipster application interface that you will be constructing.

Tipster application design

Figure 14-5. Tipster application design

The following source file shows the core HTML and JavaScript code:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
         "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Tipster</title>
<meta name="viewport" content="width=320; initial-scale=1.0; maximum-scale=1.0;
user-scalable=0;"/>
<script type="text/javascript" language="javascript">

addEventListener('load', function()
{
  setTimeout(function() {
       window.scrollTo(0, 1);
  }, 100);
 }, false);

function checkTotal(fld)
{
var x=fld.value;
     var n=/(^d+$)|(^d+.d+$)/;
     if (n.test(x))
     {
          if (fldTipPercent.selectedIndex != 0) getRec();
     }
     else
     {
          alert('Please enter a valid total')
          clearTotal(fld);
     }
}

function clearTotal(fld)
{
  fld.value = '';
}

function getRec()
{
    if (fldTipPercent.selectedIndex == 0)
    {
        alert('Please rate the service first.'), return;
    }
    var selPercent = Number( eval( fldTipPercent.value));
    var billAmount = Number( eval( fldBillTotal.value));
    var tipAmount = ((selPercent / 100) * billAmount);
    var finalP = tipAmount + billAmount;
    fldTipRec.value = '$' + tipAmount.toFixed(2);
    fldFinalTotal.value = '$' + finalP.toFixed(2);
}
</script>
</head>
<body>
    <div class="toolbar">
        <h1 id="pageTitle">The Tipster</h1>
        <a id="backButton" class="button" href="#"></a>
    </div>
     <div id="main" title="Tipster" class="panel" selected="true">
        <h2 class="tip">Let the Tipster ease your pain and calculate the tip for
        you.</h2>
        <fieldset>
            <div class="row">
                <label>Bill amount:</label>
                <input type="text" id="fldBillTotal" value="20.00" tabindex="1"
                onfocus="clearTotal(this)" onchange="checkTotal(this)"/>
            </div>
            <div class="row">
                <label>Rating:</label>
                                <select id="fldTipPercent" onchange="getRec()"
                                            tabindex="2">
                                  <option value="0">(Rate service)</option>
                                  <option value="10">Very poor</option>
                                            <option value="12.5">Poor</option>
<option value="15">Just as
                                                    expected</option>
                                            <option value="17.5">Above
                                                    average</option>
                                            <option value="20">Exceptional</option>
                                            <option value="25">Wow!</option>
                                        </select>
             </div>
        </fieldset>
        <fieldset>
            <div class="row">
                <label>Tip: </label>
                <input type="text" id="fldTipRec" value="0.00" readonly="true"
                disabled="true"/>
             </div>
           <div class="row">
                <label>Final total:</label>
                <input type="text" id="fldFinalTotal" value="0.00" readonly="true"
                disabled="true"/>
             </div>
        </fieldset>
    </div>
</body>
</html>

The fldBillTotal input field captures the total before the tip. The fldTipPercent select list displays a set of ratings for the service, each corresponding to a percentage value (see Figure 14-6). These two factors are then calculated together to generate the output values in the fldTipRec and fldFinalTotal input fields.

Scrolling through the select list

Figure 14-6. Scrolling through the select list

Next, I need to define the style rules for the mini app's UI. For core iPhone UI styling, I will use a subset of styles from a legacy version of iUI. I will add these inside a style element in the document head:

<style type="text/css" media="screen">
body
{
    margin: 0;
    font-family: Helvetica;
    background: #FFFFFF;
    color: #000000;
    overflow-x: hidden;
    -webkit-user-select: none;
    -webkit-text-size-adjust: none;
}
body > .toolbar
{
    box-sizing: border-box;
    -moz-box-sizing: border-box;
    border-bottom: 1px solid #2d3642;
    border-top: 1px solid #6d84a2;
    padding: 10px;
    height: 45px;
    background: url(toolbar.png) #6d84a2 repeat-x;
}
.toolbar > h1
{
    position: absolute;
    overflow: hidden;
    left: 50%;
    margin: 1px 0 0 −75px;
    height: 45px;
    font-size: 20px;
    width: 150px;
    font-weight: bold;
    text-shadow: rgba(0, 0, 0, 0.4) 0px −1px 0;
    text-align: center;
    text-overflow: ellipsis;
    white-space: nowrap;
    color: #FFFFFF;
}
input
{
    box-sizing: border-box;
    width: 100%;
    margin: 8px 0 0 0;
    padding: 6px 6px 6px 44px;
    font-size: 16px;
    font-weight: normal;
}
body > .panel
{
    box-sizing: border-box;
padding: 10px;
    background: #c8c8c8 url(pinstripes.png);
}
.panel > fieldset
{
    position: relative;
    margin: 0 0 20px 0;
    padding: 0;
    background: #FFFFFF;
    -webkit-border-radius: 10px;
    border: 1px solid #999999;
    text-align: right;
    font-size: 16px;
}
.row

{
    position: relative;
    min-height: 42px;
    border-bottom: 1px solid #999999;
    -webkit-border-radius: 0;
    text-align: right;
}
fieldset > .row:last-child

{
    border-bottom: none !important;
}
.row > input
{
    box-sizing: border-box;
    margin: 0;
    border: none;
    padding: 12px 10px 0 110px;
    height: 42px;
    background: none;
}
.row > label
{
    position: absolute;
    margin: 0 0 0 14px;
    line-height: 42px;
    font-weight: bold;
}
.panel > h2
{
    margin: 0 0 8px 14px;
    font-size: inherit;
    font-weight: bold;
    color: #4d4d70;
    text-shadow: rgba(255, 255, 255, 0.75) 2px 2px 0;
}
</style>

In addition to core iPhone UI styling, I need to define several app-specific style rules. tip classes are defined for the h2, label, input, and select elements. A separate style element is added to the document head to contain these styles:

<style type="text/css" media="screen">
h2.tip
{
    margin-top: 10px;
    margin-bottom: 20px;
}
.row > label.tip

{
    position: absolute;
    margin: 0 0 0 14px;
    line-height: 42px;
    font-weight: bold;
    color: #7388a5;
}
.row > input.tip
{
    display: block;
    margin: 0;
    border: none;
    padding: 12px 10px 0 160px;
    text-align: left;
    font-weight: bold;
    text-decoration: inherit;
    height: 42px;
    color: inherit;
    box-sizing: border-box;
}
.row > select.tip
{
    display: inline;
    text-align: left;
    font-weight: bold;
    font-size: 12px;
    text-decoration: inherit;
    height: 36px;
    color: inherit;
    border: none;
    padding: 12px 0 0 10px;
    float: none;
    position: absolute;
    left: 150px;
    top: 3px;
    width: 140px;
}
</style>

Encoding Images

Although you now have all the styles and scripting code inside the HTML document, there is one last issue. Two of the styles reference external images for backgrounds. To use them, you need to encode these images first. The easiest way to do this is to use an online converter, such as the data: URI Image Encoder available at www.scalora.org/projects/uriencoder. This service performs a base64 encoding of a local file or a URL. You can then replace the image file reference with the attached encoded string:

body > .toolbar
{
    box-sizing: border-box;
    -moz-box-sizing: border-box;
    border-bottom: 1px solid #2d3642;
    border-top: 1px solid #6d84a2;
    padding: 10px;
    height: 45px;
    background: url(
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAArCAIAAAA2QHWOAAAAGXRFWHRTb2Z0
d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAE1JREFUCNddjDEOgEAQAgn//5qltYWFnb1GB4vdSy4WBAY
StKyb9+O0FJMYyjMyMWCC35lJM71r6vF1P07/lFSfPx6ZxNLcy1HtihzpA/RWcOj0zlDhAAAAAElFTkSuQm
CC"
    ) #6d84a2 repeat-x;
}
body > .panel
{
    box-sizing: border-box;
    padding: 10px;
    background: #c8c8c8
url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAABCAIAAACdaSOZAAAAGXRFWHRT
b2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAABdJREFUeNpiPHrmCgMC/GNjYwNSAAEGADdNA3dnzPl
QAAAAAElFTkSuQmCC");
}

Now that all external resources are embedded, the application is fully standalone. However, you are not there yet. You now need to get it into a form that is accessible when the browser is offline.

Converting Your Application to a Data URL

You are now ready to convert your Web application into an encoded URL. Fortunately, several free tools can automate this process for you:

  • The data: URI Kitchen (software.hixie.ch/utilities/cgi/data/data): This is probably the best-known encoder on the Web. It converts source code, a URL, or a local file to a data URL.

  • Url2iphone (www.somewhere.com/url2iphone.html): This enables you to convert a URL into a bookmark. The most powerful aspect of this tool is that it looks for images, style sheets, and other files that are referenced and encodes these as well.

  • data: URI image encoder (www.scalora.org/projects/uriencoder): This tool is great for encoding images into base64 format. You can specify a URL or upload a local file (see Figure 14-7).

  • Filemark Maker (http://mac.softpedia.com/get/Utilities/Filemark-Maker.shtml): This is a free Mac-based utility that is oriented toward storing Word, Excel, and PDF documents as data URLs. However, it can also be used for HTML pages.

  • Encoding bookmarklet: Developer David Lindquist developed a handy bookmarklet that grabs the current page's source, generates a data: URL, and loads the URL. You can then drag the generated URL onto your Bookmarks bar. Here's the JavaScript code:

    javascript:x=new XMLHttpRequest();x.onreadystatechange=function(){
    if(x.readyState==4)location='data:text/html;charset=utf-
    8;base64,'+btoa(x.responseText)};x.open('GET',location);x.send(''),
    Encoding a Web application

    Figure 14-7. Encoding a Web application

  • Perl: The following Perl syntax turns HTML into a data URL:

    perl −0777 -e 'use MIME::Base64; $text = <>; $text = encode_base64($text);
    $text =~ s/s+//g; print "data:text/html;charset=utf-8;base64,$text
    ";'
  • PHP: In PHP, you can create a function to do the same thing:

    <?php
    function data_url($file)
    {
      $contents = file_get_contents($file);
      $base64   = base64_encode($contents);
      return ('data:text/html;charset=utf-8;base64,' . $base64);
    }
    ?>

Once you have used one of these tools to create a data: URL, make sure it is in the Address bar of Safari. Then drag the URL onto your Bookmarks bar. Synch up with your iPhone, and your application is now ready to run in offline mode. Figure 14-8 shows a fully functional Tipster.

The Tipster application

Figure 14-8. The Tipster application

Summary

In this chapter, you discovered how to work with bookmarklets and data URLs to create a special breed of offline iPhone web application. Bookmarklets are mini JavaScript scripts that can be stored as a bookmarks inside of Safari on iPhone. A bookmarklet, which uses the javascript: protocol followed by script code, is typically used as a mini app that performs a very specific task or performs an action on the current Web page. The primary usefulness of bookmarklets to the iPhone Web application developer is turning JavaScript into a sort of macro language to extend the functionality of Safari.

A data URL is a technique you can use for storing a Web page or complete application inside of a URL, which can then be saved as an iPhone bookmark inside Safari. The data: protocol allows you to encode an entire page's content inside a single URL. You can use data URLs to package Web applications and get around the requirement of a live 3G or Wi-Fi connection.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.216.155.130