15. Contacts

The PhoneGap Contacts API provides applications with an interface that can be used to create, locate, edit, copy, and delete contact records from the device’s native Contacts application. The API is not proprietary; instead, it’s an implementation of the W3C’s Contacts API (www.w3.org/TR/2011/WD-contacts-api-20110616/). This API interfaces with the native Contacts APIs provided by the mobile platform, and because of the way the internal API views contact information, there are quite a few quirks that manifest themselves across mobile device platforms.


Example Applications

Two sample applications have been created to help illustrate the features of the Contacts API. Example 15-1 illustrates how to create a new contact within a PhoneGap application, and Example 15-2 shows how to use the Contacts API’s search capabilities to locate a contact in an application.

Because of the length of the applications, it was not possible to include the application source code in this chapter. Relevant portions of the application code and screen shots of the application in action are shown within the chapter, but to see the completed application code, you will need to point your browser of choice to the book’s web site at www.phonegapessentials.com and look for the example project files in the Code section of the site.


Creating a Contact

Creating a contact from within a PhoneGap application is pretty straightforward; simply execute the following code:

var contact = navigator.contacts.create();

When the call to navigator.contacts.create completes, the contact object exists that contains nothing more than a representation of the different fields that define a contact as would be rendered within the device’s native Contacts application. At this point, the contact object doesn’t contain any information about the contact; all you have is an object that can be populated with contact information and saved to the Contacts application’s database. You must manually save any changes to the contact using the navigator.contacts.save method described later in the chapter.


Image Note

One of the most common problems people encounter with the PhoneGap Contacts API stems from their failure to save their changes to a contact once they’ve made them. Many a developer has created a contact, set the appropriate properties for the contact, and then scratched their head when the changes are found to have not been written to the device’s contacts database. Be sure to call navigator.contacts.save when you’ve completed making changes to a contact’s properties.


The call to navigator.contacts.create is one of the few synchronous API calls implemented by PhoneGap. Instead of using callback functions to register success or failure of an API call as illustrated in other chapters, the call simply creates a contact object in memory and returns; there’s no need for callback functions here. Once an application has a contact object to work with, the application must populate the contact fields defined within the object and then save the contact to complete the process.

Defined within the contact object are a group of strings and objects that specify different aspects of the contact, as shown in the following list:

id: A unique identifier for the contact; this variable is assigned a unique value during the call to navigator.contacts.create.

displayName: The name of the contact; on most devices, this is the name that is displayed in contact lists and address picker dialogs. Unfortunately, this field is not supported on all platforms (such as iOS).

name: An object defining the different components of a contact’s name, such as given name, family name, middle name, and so on.

nickname: A casual name for the contact.

phoneNumbers: An array containing the contact’s phone numbers.

emails: An array containing the contact’s email addresses.

addresses: An array containing the contact’s physical addresses (home, business, and so on).

ims: An array containing the contact’s instant messaging (IM) addresses.

organizations: An array containing the organizations the contact is associated with.

birthday: The contact’s birthday.

note: A variable used to contain text-based notes related to the contact.

photos: An array containing photos of the contact.

categories: An array containing user-defined categories associated with the contact.

urls: An array containing the web addresses associated with the contact.

The name object includes the string values shown in the following list:

formatted: The contact’s complete name.

familyName: The contact’s family name.

givenName: The contact’s given name.

middleName: The contact’s middle name.

honorificPrefix: The prefix associated with the contact (examples: Dr., Mr., or Mrs.).

honorificSuffix: The suffix associated with the contact (example: Ph.D.).

When you look at these lists, you may notice that the contact’s name components are represented in different places in the object. The displayName and nickname values are associated with the contact, while everything else is associated with the contact.name object. I expected that all name components would be associated with the name object, but for some reason they’re not.

Many of the other components of the contact object are simple arrays representing multiple values of the same type. The addresses array is a two-dimensional array of ContactAddress objects consisting of the following values:

pref: Boolean value that defines whether the entry is the default address for the contact

type: A string value defining the type of address being defined such as home or work

formatted: The full address formatted for display

streetAddress: The full street address

locality: The city or locality associated with this address

region: The state or region associated with this address

postalCode: The ZIP or postal code associated with this address

country: The country associated with this address

The organizations array is a two-dimensional array of ContactOrganization objects consisting of the following values:

pref: Boolean value that defines whether the entry is the preferred or default organization for the contact

type: A string value defining the type of organization being defined such as home or work

name: The name of the organization

department: The department where the contact works

title: The contact’s title within the organization

The contact’s phoneNumbers, emails, and ims values are all populated the same way, using an array of values shown in the following list:

type: A string value defining the type of value being defined such as home or work

value: The contact value such as phone number or email address

pref: Boolean value that defines whether the entry is the default entry for this type of contact method

Some smartphone platforms are picky about the values assigned to the type property. Even though PhoneGap will accept anything for this value, your target mobile device might not display the values unless the right values are assigned here. My testing has shown that it’s best to use standard values like home, work, and mobile for type.

Now that you have a good understanding of the contact properties, let’s take a look at an example of how all of this is implemented in code. In the Example 15-1 sample application (available from the www.phonegapessentials.com web site), the application retrieves contact information from an external source (in this case external .js files) and allows the application user to add information about a selected user to the local contacts database. The following is an example of the contact object for one of the contacts used in the application:

{
  "FullName": "Michael Palin",
  "LastName": "Palin",
  "FirstName": "Michael",
  "EmailAddress": "[email protected]",
  "OfficePhone": "330.123.4567",
  "MobilePhone": "330.987.6543"
}


Image Note

I made up that email address for Michael Palin; he may have an email address, but I definitely don’t know what it is. It’s probably best not to send any email messages to that address; there’s no telling where it would go or what would happen.


In the application, the contact information shown is assigned to the contactInfo object and passed to the following function so the contact’s information can be added to the local contacts database:

function addContact(contactInfo) {
  //Create a new contact object
  var contact = navigator.contacts.create();

  //Populate the contact object with values
  contact.displayName = contactInfo.FullName;
  contact.nickname = contactInfo.FullName;

  //Populate the Contact's Name entries
  var tmpName = new ContactName();
  tmpName.givenName = contactInfo.FirstName;
  tmpName.familyName = contactInfo.LastName;
  tmpName.formatted = contactInfo.FullName;
  //Then add the name object to the contact object
  contact.name = tmpName;

  //Populate Phone Number Entries by creating and populating
  //an array of phone number information
  var phoneNums = [2];
  phoneNums[0] = new ContactField('work',
    contactInfo.OfficePhone, false);
  phoneNums[1] = new ContactField('mobile',
    contactInfo.MobilePhone, true);
  contact.phoneNumbers = phoneNums;

  //Populate Email Address the same way that you did the
  //phone numbers
  var emailAddresses = [1];
  emailAddresses[0] = new ContactField('home',
    contactInfo.EmailAddress, true);
  contact.emails = emailAddresses;

  // save the contact object to the device's contact database
  contact.save(onContactSaveSuccess, onContactSaveError);
}

You can also create a contact object and populate it at the same time by passing in a properly formatted contact object to the create method, as shown here:

var contact = navigator.contacts.create({displayName:
  'Michael Palin', nickname: 'Mike', name: {givenName:
  'Michael', familyName: 'Palin'}});
contact.save(onContactSaveSuccess, onContactSaveError);

There are some quirks in the way each individual mobile device platform stores the contact information passed to the contact object. You will need to refer to the PhoneGap documentation for specifics about these quirks since they’re likely to change over time. For some examples, though, the BlackBerry platform doesn’t support the displayName field directly, so the value is stored in the user1 field and the nickname field returns null. On iOS, the displayName field isn’t directly supported, but different values may be returned depending on what values are defined for the contact.

As mentioned previously, the changes you make to a contact’s properties will not be written to the device’s contacts database until you execute the following code:

contact.save(onContactSaveSuccess, onContactSaveError);

In this example, I’m passing in two functions: an onContactSaveSuccess function that is executed after the contact has been successfully saved to the contacts database and an onContactSaveError function that is executed if there’s an error writing to the database.

The onContactSaveSuccess function is quite simple; it just lets the user know that the save completed successfully, as shown in the following example:

function onContactSaveSuccess() {
  alert(contactInfo.FullName + " was successfully saved to the
   device contacts database");
}

If there’s an error saving the contact, the onContactSaveError function is called, and an error object is passed to the function that allows an application to understand the nature of the error and react according to the needs of the application. In the following example, the code displays a different error message for the user depending on the nature of the nature of the error encountered by the application.

function onContactSaveError(e) {
  var msgText;
  //Now build a message string based upon the error
  //returned by the API
  switch(e.code) {
    case ContactError.UNKNOWN_ERROR:
      msgText = "An Unknown Error was reported while saving
        the contact.";
      break;
    case ContactError.INVALID_ARGUMENT_ERROR:
      msgText = "An invalid argument was used with the Contact
        API.";
      break;
    case ContactError.TIMEOUT_ERROR:
      msgText = "Timeout Error.";
      break;
    case ContactError.PENDING_OPERATION_ERROR:
      msgText = "Pending Operation Error.";
      break;
    case ContactError.IO_ERROR:
      msgText = "IO Error.";
      break;
    case ContactError.NOT_SUPPORTED_ERROR:
      msgText = "Not Supported Error.";
      break;
    case ContactError.PERMISSION_DENIED_ERROR:
      msgText = "Permission Denied Error.";
      break;
    default:
      //Create a generic response, just in case the
      // switch fails
      msgText = "Unknown Error (" + e.code + ")";
 
  }
  //Now tell the user what happened
  navigator.notification.alert(msgText, null,
  "Contact Save Error");
}

In your applications, you will likely want to do something more substantive when an error occurs. For some of the errors, there’s not much the application can do except to perhaps try again later. The INVALID_ARGUMENT_ERROR is most likely caused by a coding error and would likely not appear in a properly tested application (unless PhoneGap changed the Contacts API options behind the scenes).

The PERMISSION_DENIED_ERROR is important and would affect users depending on how they answer the security prompt they receive on BlackBerry and Android devices when they install new applications. On each platform, you must configure the application project with a list of the APIs used by the application. If your application doesn’t properly identify that it uses the Contacts API, the device may block access to the API. On BlackBerry and Android, the user is prompted to allow access to the different APIs used by the application; if the user doesn’t allow access to the Contacts API when installing the application, the application will not function properly and will likely return the permission denied error. To configure your projects with the appropriate permissions, refer to the documentation that accompanies the mobile platform development tools you are using.

When working with PhoneGap Build (described in Chapter 9), the build service takes care of configuring each development environment for you. To enable access to the Contacts API on Android, add the following line to the project’s config.xml file, and be sure to include it with the project files uploaded to the PhoneGap Build service:

<feature name="http://api.phonegap.com/1.0/contacts" />

This enables the READ_CONTACTS, WRITE_CONTACTS, and GET_ACCOUNTS permissions on Android.

For a BlackBerry WebWorks application, you must include the following line in the config.xml file:

<feature id="blackberry.pim.Contact" />

The following listing shows a completed PhoneGap Build config.xml for this application:

<?xml version="1.0" encoding="UTF-8"?>
<widget xmlns = "http://www.w3.org/ns/widgets"
  xmlns:gap = "http://phonegap.com/ns/1.0"
  id = "com.phonegapessentials.ex151"
  version = "1.0.0">

  <name>Example 15-1</name>
  <description>An example application that uses the PhoneGap
    Contacts API</description>
  <author href="http://johnwargo.com"
  email="[email protected]">John M. Wargo</author>
  <gap:platforms>
    <gap:platform name="android" minVersion="2.1" />
    <gap:platform name="webos" />
    <gap:platform name="symbian.wrt" />
    <gap:platform name="blackberry" project="widgets"/>
  </gap:platforms>
  <feature name="http://api.phonegap.com/1.0/contacts" />
  <feature id="blackberry.pim.Contact" />

</widget>

One of the quirks of the Android platform is that an application must have a Google account configured on the device in order to access contact information from a PhoneGap application. I discovered this issue when I was testing on a device that I’d done a complete security wipe on before testing the application. Without a Google account defined in the Android Accounts and Sync area of Settings, the application returned an Unknown Error whenever it tried to write a contact to the contacts database. As soon as I configured the device for my Gmail account, the error went away. If you are using the Android emulator, you will need to use an emulator based upon the Google APIs, not the default SDK.

Let’s take a look at the application in action. Figure 15-1 shows the Example 15-1 application running on a BlackBerry Torch simulator. It starts by showing a list of contacts from an external data source.

Image

Figure 15-1 Example 15-1 running on a BlackBerry Torch simulator

When the user selects a contact, the application opens a page that displays detailed information about the contact, as shown in Figure 15-2.

Image

Figure 15-2 Example 15-1: contact details

When the user clicks the Add Contact button, the application adds the selected contact to the contacts database and displays the confirmation dialog shown in Figure 15-3.

Image

Figure 15-3 Example 15-1: save confirmation

Opening the BlackBerry Contacts application and searching for and then opening the new contact will show a screen similar to the one shown in Figure 15-4.

Image

Figure 15-4 New contact information in the BlackBerry Contacts application

Figure 15-5 shows the results of the same activity on an Android smartphone.

Figure 15-6 shows the results of the same activity on an Apple iPhone.

With each of these examples, the native contacts application makes the phone number, email address, and other electronic contact fields clickable so the user can click an item and initiate contact through the selected option.

Image

Figure 15-5 New contact information in the Android Contacts application

Image

Figure 15-6 New contact information in the iOS Contacts application

Searching for Contacts

Another useful feature of the Contacts API is the ability to search the device’s local contacts database for contacts. To initiate a search, an application should execute the following code:

navigator.contacts.find(contactFields, onContactSearchSuccess,
  onContactSearchError, searchOptions);

The contactFields parameter passed to the find method defines the list of contact field names whose values will be included in the search results. In some cases, you may want the search function to return all fields, in which case you would use the following:

contactFields = ['*'];

In other cases, you might want to limit your search results to a limited number of contact fields, as shown in the following example:

contactFields = ['displayName', 'name', 'nickname'];

The name entry refers to the name object described in the previous section and includes familyName, givenName, middleName, and more.

As you build your applications, you may think you can just use the displayName field and catch most contact names. The problem with this approach is that displayName is not supported on iOS, so if you rely upon that field name for searches targeted at iOS devices, the search will likely not return any values.

The onContactSearchSuccess and onContactSearchError functions passed as parameters to find are standard callback functions you’ve seen in other examples in this book. The onContactSearchSuccess function is executed when the search succeeds, and the onContactSearchError function is executed if an error is encountered performing the search. You’ll learn more about these functions later.

The searchOptions parameter is an object defining options used to control how the search is performed. It consists of two values, filter and multiple, as shown in the following example:

var searchOptions = { filter : searchStr, multiple : true };

The filter value defines the search string used when searching the device’s contacts database, and the multiple value is a Boolean value that defines whether the application should return multiple results or return a value as soon as a single item that matches the search criteria is found.

Let’s take a look at an example application that puts all of this to use. In the Example 15-2 application (available for download from www.phonegapessentials.com), the application displays the simple form shown in Figure 15-7. In the application, I used the jQuery (www.jquery.com) and jQuery Mobile (www.jquerymobile.com) frameworks to provide the application with a more professional-looking interface without having to write a bunch of interface code myself.

Image

Figure 15-7 Example 15-2 running on an Apple iPhone

On the form, the search field is defined using the following HTML markup:

<input type="search" id="editSearch" />

The search scope picker is defined using the following markup:

<select id="searchScope" name="searchScope">
  <option>All</option>
  <option>Name</option>
  <option>Address</option>
  <option>Notes</option>
</select>

The code behind the Search Contacts button is defined in the following function:

function searchContacts() {
  //Get the search string from the page
  var searchStr =
    document.getElementById("editSearch").value;
  //Figure out which search option is selected
  var searchScope =
    document.getElementById("searchScope").selectedIndex;
  //Then populate searchOptions with the list of fields being
  //searched
  var contactFields = [];
  switch(searchScope) {
    case 1:
      //Return just name fields
      contactFields = ['displayName', 'name', 'nickname'];
      break;
    case 2:
      //Return address fields
      contactFields = ['name', 'streetAddress', 'locality',
        'region', 'postalCode', 'country'];
      break;
    case 3:
      //Return name and contents of the Notes field
      contactFields = ['name', 'note'];
      break;
    default:
      //return all contact fields
      contactFields = ['*'];
  }
  //Populate the search options object
  var searchOptions = { filter : searchStr, multiple : true };
  //Execute the search
  navigator.contacts.find(contactFields,
    onContactSearchSuccess, onContactSearchError, searchOptions);
}

In the function, the code grabs the value from the search field and the selected value from the search scope picker. Using those values, it defines the values for the contactFields variable based upon which picker option was selected and passes the search string in the filter value in the searchOptions object.

That’s it—that’s all an application has to do to search the local contacts database. The onContactSearchError function is the same as the onContactSaveError function described in the previous section. When the search completes, the find method calls the onContactSearchSuccess function, which is shown next. The function essentially builds an on-screen list of the search results (displaying the name of the contact if it can be determined).

function onContactSearchSuccess(contacts) {
  // alert("onContactSearchSuccess");
  //Populate the contact list element of the contact list page
  var i, len, theList;
  //Store the contact data in our global variable so the
  //other functions have something to work with
  contactList = contacts;
  //Did we get any results from the search?
  len = contacts.length;
  if(len > 0) {
    theList = '<ul data-role="listview">';
    for( i = 0, len; i < len; i += 1) {
      //on iOS displayName isn't supported, so we can't
      //use it
      if(contacts[i].displayName == null) {
        theList += '<li><a onclick="showContact(' + i +
          '),">' + contacts[i].name.familyName + ", " +
          contacts[i].name.givenName + '</a></li>';
      } else {
        theList += '<li><a onclick="showContact(' + i +
          '),">' + contacts[i].displayName + '</a></li>';
      }
    }
    theList += '</ul>';
    $('#contacts').html(theList);
    //Then switch to the Contact Details page
    $.mobile.changePage("#contactList", "slide", false, true);
  } else {
    navigator.notification.alert('Search returned 0 results',
      null, 'Contact Search'),
  }
}

In onContactSearchSuccess, the function is passed an array containing contact entries for each of the contact records containing the search string. The code first checks to see whether the array has any values; if not, a message is displayed to the user letting them know that there are no search results. If there are results, the function loops through the results array and creates an unordered list containing the name of each contact included in the search results. In the application, the unordered list is assigned a data-role attribute of listview, which is used by jQuery Mobile to render the interactive list shown in Figure 15-8. In this case, there’s only one result, but if there had been more, the list would scroll down the length of the screen as needed.

Image

Figure 15-8 Example 15-2: search results

Looking at the function, you may have noticed that when creating the list view, the code first checks to see whether displayName is null and uses the contact’s givenName and familyName to create the list entry. This is because displayName is not supported on iOS, so I had to find another way to ensure that something would display in the list.

if(contacts[i].displayName == null) {
  theList += '<li><a onclick="showContact(' + i + '),">' +
    contacts[i].name.familyName + ", " +
    contacts[i].name.givenName + '</a></li>';
} else {
  theList += '<li><a onclick="showContact(' + i + '),">' +
    contacts[i].displayName + '</a></li>';
}

Each entry in the list view has an onclick event defined that causes the showContact function to be executed when the user selects a contact; passed to showContact is the index for the selected contact. The function retrieves information about the selected contact and displays a page similar to the one shown in Figure 15-9.

Image

Figure 15-9 Example 15-2: contact detail

As you can see from the figure, the application displays some common field values at the top of the page and lists all of the contact field values below the horizontal rule. I built the application this way to help me validate what was returned by the search function. When using the application, as you select different search scopes, you can easily see what contact fields are (and aren’t) returned to the application when performing a search. Here’s the code that generates the field/value list:

//Show all of the contact fields
dt = "<hr />";
for(myKey in contact) {
  dt += "Contact[" + myKey + "] = " + contact[myKey] + "<br />";
}
$('#detailContent').html(dt);

Cloning Contacts

To make a clone of an existing contact, an application simply calls the clone method, as shown in the following example:

var contact2 = contact1.clone();

The contact object points to an existing contact obtained by creating a new contact via a call to navigator.contacts.create, as described in the beginning of the chapter, or retrieved by searching the local device contacts database using the find method, as described in the previous section.

Once the application has the cloned copy of the original contact, it can manipulate the properties of the clone, changing whatever properties are appropriate for the application and then calling navigator.contacts.save to write the updates to the contacts database. When the clone is created, the cloned contact object exists solely in memory and must be written to disk for any changes to be maintained.

Removing Contacts

To remove an existing contact, an application simply calls the remove method, as shown in the following example:

contact.remove(onContactRemoveSuccess, onContactRemoveError);

The contact object points to an existing contact obtained by creating a new contact via a call to navigator.contacts.create, as described in the beginning of the chapter, or retrieved by searching the local device contacts database using the find method, as described in an earlier section.

The onContactRemoveSuccess and onContactRemoveError parameters passed to the call to remove are callback functions that are executed by the remove method. The onContactRemoveSuccess function is executed after the contact has been successfully removed, while the onContactRemoveError function is the same as the onContactSaveError function described in the first section of this chapter.

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

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