Hello developer. Login with your existing account. New to Vodafone Developer? Register your account.

+ Login or create an account

Address Book Code Notes

Introduction

The Vodafone developer widget samples series demonstrates how to use the JIL 1.2.2/WAC 1.0 widget APIs. These notes document the Address Book sample widget.

The JIL/WAC PIM APIs enable widgets to interact with the phone's on-board address book and calendar. For example, widgets can create new address book and calendar entries, search for and read existing entries, export entries as VCards, and respond to calendar alerts.

The Address Book sample widget demonstrates how to fetch and display address book entries from the phone's address book; display and filter the results, for example to find a match on a name; and display details for a selected entry. The user can launch appropriate actions directly from the contact details, for example dialling a number or composing a message. You can reuse the sample code as a starting point for your own projects.

Download the widget source files here - address-book.zip (9.6 KB)

You can find more samples in this series at Widgets Getting Started .

JIL APIs used

The Address Book widget uses the following JIL APIs:

  • Widget.PIM
  • Widget.PIM.AddressBookItem

JIL Features declared

The Address Book sample widget declares the following JIL features:

Required features:
  • http://jil.org/jil/api/1.1/widget
  • http://jil.org/jil/api/1.1/device
  • http://jil.org/jil/api/1.1.1/pim
  • http://jil.org/jil/api/1.1/addressbookitem
Optional features:

None.

Compatibility

  • Android: Yes
  • Symbian: No
  • Nokia N8: Yes

Overview

The Address Book sample shows how to fetch and display entries from the device's on-board address book. A search bar enables the user to filter the displayed entries using a search term, for example a contact name. Actions can be launched from contact details.

On launch, the widget fetches all address book entries and displays them in a scrollable list. Because this can cause a noticeable delay if the list is large, the widget pops up an in-progress dialog while it builds the list. If the user enters a search term in the search bar, it is used to filter the list of displayed entries. When an entry is selected from the list its details are displayed in an overlay page, with a "Close" button. Closing the detail page returns the user to the list; selecting a contact detail launches an appropriate action, for example selecting a phone number launches the phone dialler, and selecting an email address launches the messaging editor. Actions are based on the "tel" and "mailto" URI scheme protocols.

The screenshots below show the Address Book sample widget running on the HTC Desire:

The JIL/WAC PIM APIs

The JIL PIM APIs are based on the Widget.PIM object and its AddressBookItem, CalendarItem, and EventRecurrenceTypes subobjects. The APIs provide methods to create, add, delete, export, search and count address book and calendar entries, and set alerts.

The AddressBookItem and CalendarItem subobjects encapsulate the content of address book and calendar entries.

The basic behaviour of the calendar and address book APIs is similar: the main query methods are asynchronous and return results to callback methods that clients must implement and register, respectively onAddressBookItemsFound() and onCalendarItemsFound(). In addition the calendar API provides the onCalendarItemAlert() callback method that widgets can implement to respond to alarms and the snooze timer timeout. The address book API has additional methods that allow items to be grouped. Exporting calendar items supports VCard standards.

The Widget.PIM.EventRecurrenceTypes subobject represents recurring event types, for example DAILY, MONTHLY ON DAY, and so on, that can be applied to CalendarItems.

The Address Book sample widget demonstrates use of the PIM API to fetch entries from the address book, using the following steps:

  • Get the number of address book entries by calling Widget.PIM.getAddressBookItemsCount(). Note that the runtime displays a permission request dialog when this method is called for the first time. Users must either grant or decline permission to the widget.
  • Get all entries from the address book by calling Widget.PIM.findAddressBookItems() and passing a comparison contact and start and end indexes. This method also displays a permission request dialog when called for the first time. Use the entry count returned above as the end index. The comparison contact is a new AddressBookItem object with the fullName attribute set to the wild card value *. Note that the API provides a createAddressBookItem() method which is deprecated; instead, use the AddressBookItem() object constructor.
  • Because findAddressBookItems() is an asynchronous method, you must first provide a callback implementation to which the results will be returned, and register the callback, for example Widget.PIM.onAddressBookItemsFound = myCallBack;

In code, the basic sequence of calls is therefore:

// Provide and register a callback implementation
function myCallBack(AddressBookItem) {
    ...
}
Widget.PIM.onAddressBookItemsFound = myCallBack;

// Item count
var numItems = getAddressBookItemsCount();

// Set up a wild card comparison contact
var comparisonContact = new Widget.PIM.AddressBookItem();
comparisonContact.setAttributeValue("fullName", "*");

// Make the call. Results will be returned to myCallback()
Widget.PIM.findAddressBookItems(comparisonContact, 0, numItems);

Security and privacy

When you use the PIM APIs, it is important to consider the privacy implications of accessing users' private data. The Address Book sample shows the JIL security model in action: when the widget launches for the first time and tries to access the address book, user alerts are displayed for each API call, and user action is required to continue. After the user has granted permission, alerts are not shown in future uses of the widget, unless it is uninstalled and reinstalled.

Code notes

The Address Book sample provides a simple demonstration of how to use JIL PIM APIs to fetch and display entries from the phone's address book, and how to launch actions from contact details.

The sample consists of the following source code files:

  • config.xml
  • address-book.html
  • address-book.js
  • common/js/util.js
  • common/css/base.css
  • common/css/forms.css
  • common/css/layout.css

The notes below walk through the code in more detail.

config.xml

Every widget must provide a config.xml configuration file that defines the widget package and is used at install time by the runtime.

For a JIL widget, this is also where you should declare the JIL features your widget uses, including required features and optional features. Declaring a feature initialises the appropriate runtime object; if you don't declare the feature, the object value may be undefined at runtime. Because the widget and device objects are implicitly required to access any JIL API, it is good practice to always declare both features as required="true", even though some runtimes may initialise these objects. It is a design choice whether you declare additional JIL APIs your widget uses as required or optional:

  • Declaring a feature as required will cause installation to fail if the device does not support that specific feature
  • Declaring a feature as optional will enable installation even if the device does not support that specific feature, but at runtime the API object will be undefined. Trying to access the API will lead to an undefined or unsupported exception.

The Address Book sample widget declares JIL features as follows:

<?xml version="1.0" encoding="utf-8"?>
<widget xmlns="http://www.w3.org/ns/widgets" ... >
    <name>address-book</name>
    ...
    <feature name="http://jil.org/jil/api/1.1/widget" required="true"/>

    <feature name="http://jil.org/jil/api/1.1/device" required="true"/>
    <description>An example address book widget.</description>
    <feature name="http://jil.org/jil/api/1.1.1/pim" required="true"/>
    <feature name="http://jil.org/jil/api/1.1/addressbookitem" required="true"/>
</widget>

For more about the structure and contents of config.xml for JIL widgets, see the Basic Application sample widget code notes.

address-book.html

The HTML page uses the HTML5 DOCTYPE declaration <!DOCTYPE html>. The code is straightforward: it loads page styles using style sheet <link> tags, defines all page elements, and finally loads the JavaScript code using <script> tags.

Page elements include:

  • Header with search bar and "submit" button
  • Info box displaying "Fetching addresses..." message at startup

All other display elements are created dynamically using DOM methods.

address-book.js

The address-book.js code performs the following main functions:

  • Implements the basic application patterns which are common to all the widget samples, including:
    • Encapsulates all of the widget's functionality in a single global app object to avoid polluting the global runtime namespace
    • Caches DOM references for later use to reduce the scope chain
    • Registers UI event listeners to capture user touches
  • Calls the JIL PIM API to get address book data. The call is made from a timer to allow time for the app UI to display and render
  • Builds a list of fetched address book entries for display, and handles user interactions to:
    • Filter the displayed list to match a user-entered filter term e.g. to find a contact name
    • Launch a detail overlay showing contact details when a list item is selected

For more about common widget patterns like encapsulation and scope chain reduction, see the Basic Application sample code notes.

getAddressBookEntries()

The app.init() function completes the widget initialisation by calling app.getAddressBookEntries() and passing a reference to app.buildContactList i.e. the callback parameter in the definition of getAddressBookEntries:

/*
    Fetches all address book items and passes them to a callback.
*/
getAddressBookEntries: function(callback) {
    // Only fetch address book entries if we don't have a cached list
    if (!app._addressBookEntries) {

        // ... Use the PIM API to get the address book data and
        // pass it to the callback with: callback(addressList);

    } else {
        // If we have cached addresses, pass directly to the callback
        callback(app._addressBookEntries);
    }
}

Here, if a cached address list app._addressBookEntries already exists it is passed directly to buildContactList(); otherwise, addresses are fetched using the PIM API.

Fetching addresses requires two steps:

  • Define and register the PIM API callback method onAddressBookItemsFound
  • Initialise and perform the address book search, the API returns results by calling back onAddressBookItemsFound

In code, the API callback method is defined and registered in the following assignment:

Widget.PIM.onAddressBookItemsFound = function onAddressBookItemsFound(Addresslist) {
    // Remove the listener which has invoked this code
    if (Widget.PIM.onAddressBookItemsFound === onAddressBookItemsFound) {
        Widget.PIM.onAddressBookItemsFound = null;
    }

    // Store all address book entries on the "app" object
    // addressList is supplied by the PIM API implementation,
    // and is an empty array if no items were found
    app._addressBookEntries = addressList; 
    // Build a hash map: address book item id => address book item
    var byId = app._addressBookEntriesById = {};
    for (var i = 0, item; (item = addressList[i]); i++) {
        var id = item.addressBookItemId;
        byId[id] = item;
    }
    // Call the callback function
    callback(addressList);
}

Here, the method first unregisters itself. Next, the addressList object is assigned to a local variable to bring it into local scope before use. Then a hash is built to index the returned items by their addressBookItemIds. Finally the items are passed to buildContactList() – recall that this was the method reference passed in the callback parameter. buildContactList() creates the displayable list using DOM techniques, see below.

The hash is required to provide a persistent identifier for list items, since list indexes will change with filtering. The "hash map" is just a JavaScript object, which behaves like an associative array, constructed with the object literal assignment ... = {}.

After onAddressBookItemsFound() has been defined and assigned, the code for getAddressBookEntries performs the actual address book search:

// Get the number of address book items
// This call shows a permission request dialog
var numItems = Widget.PIM.getAddressBookItemsCount();
// Fetch all items, match against a wild card comparison contact
var comparisonContact = new Widget.PIM.AddressBookItem();
comparisonContact.setAttributeValue("fullName", "*");
// Start the search
Widget.PIM.findAddressBookItems(comparisonContact, 0, numItems);

Here, the PIM method getAddressBookItemsCount() is called to get the number of items in the address book. Next, a dummy addressbook item is created that will be matched against; the wild card value for fullName ensures the match will always succeed. Finally, findAddressBookItems() is called with the dummy item to get all matches from index 0 up to the count value i.e. all entries in the address book. findAddressBookItems() is the asynchronous PIM API method; when the search is complete, the results are returned via the onAddressBookItemsFound() callback.

buildContactList()

The displayable list is built from HTML list elements; each element id is derived from the addressBookItemId of the original item, and the displayed content is the item fullName property:

// Builds the list of contacts from an array of
// Widget.PIM.AddressBookItem instances.
buildContactList: function(addressList) {
    var mainBody = app.dom.mainBody;

    // DOM node templates for an entry
    var ul = document.createElement("ul");
    ul.className = "listing nav";
    var li = document.createElement("li");
    li.innerHTML = '<a href="#"></a>';

    for (var i = 0, item; (item = addressList[i]); i++) {
        var fullName = item.fullName;
        if (!fullName) {
            // Only show items with a displayable name
            continue; 
        }
        var itemNode = li.cloneNode(true);
        var a = itemNode.getElementsByTagName("a")[0];

        // Add a property containing the name for filtering
        itemNode._fullName = fullName.toLowerCase();
	// Add an id
        a.id = app.ID_PREFIX + item.addressBookItemId;
        // Add the full name
        a.appendChild(document.createTextNode(fullName)); 
        // Append the entry to the main list
        ul.appendChild(itemNode); 
    }

    mainBody.innerHTML = ""; // delete any previous list
    mainBody.appendChild(ul); // append the newly created list
}

Filtering displayed items

Filtering the contacts list hides all list entries except those that match a user-entered term e.g. a name.

To capture user input for filtering, the _initFilter() method registers an event listener to capture key presses from the device (virtual) keyboard. Note the in-code comments on handling focus.

Filtering is performed with a simple string search: the _fullName attribute of each list element is searched for the search term, and a utility method to hide or show the node is called depending on the result:

// Compare to the name property we created with the list
if (node._fullName.indexOf(filterValue) === -1) {
    util.hide(node);
}
else {
    util.show(node);
}

Contact details overlay

When a list entry is selected a details overlay is dynamically created and displayed. The method _onContactListClicked() is registered as an event listener on the main list displayed by the widget:

/*
    Registers an event listener on the main contacts list.
*/
_initMainBody: function() {
    var mainBody = app.dom.mainBody;
    mainBody.addEventListener("click", app._onContactListClicked, false);
}

When a list item is selected by the user, i.e. when a click event is detected, _onContactListClicked() is called. The method does the following:

  • The event target is checked to see if an address list element was selected
  • If so, the element's id is used to return the matching item from the hash table, and the overlay is constructed
  • Once constructed, the details are filled in from the original address book item
  • The "Close" button element is created, and a listener method is defined and registered that removes the overlay when the button is clicked
  • Finally, the overlay is attached to the document body, making it visible

In code:

/*
    Event listener for the main contact list – opens
    an overlay that shows all of the addresses.
*/
_onContactListClicked: function(event) {
var t = event.target;
// Check if an address book item has been clicked
if (t.id != null && t.id.indexOf && t.id.indexOf(app.ID_PREFIX) === 0) {
    // Get the id from the id attribute
    var itemId = t.id.replace(app.ID_PREFIX, "");
    // Use it to get the address book item from our cache
    item = app._addressBookEntriesById[itemId];

    if (item) {
        // Create the overlay, uses a utility method
        var overlay = util.getModuleDOM("overlay", true, false);
        var children = overlay.getElementsByTagName("div");
        var header = children[0];
        var body = children[1];
        // Release reference to live collection
        children = null;

        // Now fill in the overlay values, omitted, see below
        ...

        // Add a button to close the overlay.
        body.innerHTML +=
            '<p class="buttons"><button type="button"
                name="close" class="button">Close</button></p>';

        // Listener for the close button, removes the overlay
        overlay.addEventListener("click", function(event) {
            var t = event.target;
                if (t.nodeName.toLowerCase() == "button" &&
                        t.name == "close"){
                    overlay.parentNode.removeChild(overlay);
                }
        }, false);

        // Add the overlay
        document.body.appendChild(overlay);
        }
    }
}

The code to fill in the overlay values from the address book item is a good example of effective and compact JavaScript. Firstly, it assumes a common object template for all phone number and email address details to be displayed:

/*{
    proto: the URI scheme protocol to use for this detail, either
           tel or mailto
    label: the label to display
    value: the detail content, an addressBookItem property,
           one of homePhone, mobilePhone, workPhone, or eMail
}*/

Details are built by the following function, where e is the function util.escapeHTML which replaces "special" characters with their HTML entity encodings, and JavaScript's encodeURIComponent() returns an encoded URI:

function(item) {
    // Create a detail from an AddressBookItem
    return '<li><a href="' +
        item.proto + encodeURIComponent(item.value) + '">' +
            e(item.label + item.value) + '</a></li>';
}

The function is applied using JavaScript's map() to the list of all possible details i.e. home, mobile, work, and e-mail, filtered using JavaScript's filter() to remove details which are empty in the selected AddressBookItem; the string results of the mapped function are joined using JavaScript's join(), and the joined string is set as the value of body.innerHTML.

In code:

body.innerHTML = '<ul class="listing nav">' +
    [
        { proto: "tel:", label: "home: ", value: item.homePhone },
        { proto: "tel:", label: "mobile: ", value: item.mobilePhone },
        { proto: "tel:", label: "work: ", value: item.workPhone },
        { proto: "mailto:", label: "e-mail: ", value: item.eMail }
    ].filter(function(item) {
        // Filter empty values
        return item.value; 
        }).map(function(item) {
            // Create a detail from an AddressBookItem
            return '<li><a href="' +
                item.proto + encodeURIComponent(item.value) + '">' +
                e(item.label + item.value) + '</a></li>';
            }).join("") +
        "</ul>";

The result is a displayable <ul> list containing href links to any of the home, mobile, work, or email fields contained in the selected AddressBookItem. Because the href value includes the tel or mailto URI protocol specifier, selecting a link launches the link with the relevant protocol:

  • For href="tel:..." the runtime launches the phone dialler
  • For href="mailto:..." the runtime launches the messaging app email editor

Common

The Address Book sample uses the following utility functions from common/js/util.js:

  • escapeHTML() – replace special characters with their HTML character entities
  • util.hide() – hide a DOM node
  • util.show() – show a DOM node
  • util.getModuleutilDOM() – create a new element with optional header and footer

All CSS styling is provided by the common style sheets common/css/base.css, common/css/forms.css, and common/css/layout.css.

The base.css and layout.css styles, including the use of media queries and the fixed header and footer layouts, are discussed in the Basic Application sample code notes.

The forms.css style sheet contains basic button and label styles.

Concluding remarks

The Address Book sample widget is a good example of using JIL APIs and classic JavaScript and DOM techniques to create simple but powerful mobile apps that can use user data on the device.