Address Book examples

Note: Thunderbird and SeaMonkey user interfaces now reference 'Contacts' not 'Cards' however, as the backend still uses the 'Cards' terminology, that is what is used here

This article provides examples on accessing and manipulating Thunderbird address books. Address book interfaces provides an overview of the related interfaces. See An overview of Thunderbird components for a general description of the Thunderbird user interface and related programmatic interfaces.

How do I get all address books?

Use the Address Book Manager to get an nsISimpleEnumerator, then progress through the enumerator, looking for nsIAbItem, nsIAbCollection or nsIAbDirectory interfaces as you require.

let abManager = Components.classes["@mozilla.org/abmanager;1"]
                          .getService(Components.interfaces.nsIAbManager);

let allAddressBooks = abManager.directories;

while (allAddressBooks.hasMoreElements()) {
  let addressBook = allAddressBooks.getNext()
                                   .QueryInterface(Components.interfaces.nsIAbDirectory);
  if (addressBook instanceof Components.interfaces.nsIAbDirectory) { // or nsIAbItem or nsIAbCollection
    // alert ("Directory Name:" + addressBook.dirName);
    ...
  }
}

How do I get one address book?

Address Books are uniquely identified by a URI. This can be obtained via nsIAbDirectory.URI. The code stores URIs in various places (e.g. preferences, in address book trees etc). Assuming you have the URI, then use the Address Book Manager to get an nsIAbDirectory representing the address book:

let abManager = Components.classes["@mozilla.org/abmanager;1"]
                          .getService(Components.interfaces.nsIAbManager);

let oneAddressBook = abManager.getDirectory(addressBookURI);

How do I search for a particular contact property (name, email)?

Once you have an object that implements nsIAbCollection (see above), you can search for cards with particular properties within that collection using getCardFromProperty. This function will return the first match that it finds in the collection or null.

let collection = ...

let card = collection.getCardFromProperty("JobTitle", "software engineer", false);

If you are searching for a card with a particular email address, you can use the cardForEmailAddress function that will match against multiple email addresses stored in one card. Again, this function returns the first match that it finds in the collection, or null.

let collection = ...

let card = collection.cardForEmailAddress("foo@bar.invalid.com");

Note: Both of these functions may raise an NS_ERROR_NOT_IMPLEMENTED exception if the collection has not implemented that function. This is currently true in the case of LDAP, but may be true in other cases as well.

How do I search for multiple contacts?

Formulate a boolean search string (see nsIAbCard for built-in property names):

var searchQuery = "(or(PrimaryEmail,bw,@V)(NickName,bw,@V)(and(IsMailList,=,TRUE)(Notes,bw,@V)))";
searchQuery = searchQuery.replace(/@V/g, encodeURIComponent("mystr")

The search queries use Lisp syntax with operators enumerated in nsAbQueryStringToExpression.cpp.

Assuming you wish to search across multiple directories:

let abManager = Components.classes["@mozilla.org/abmanager;1"]
                          .getService(Components.interfaces.nsIAbManager);
let allAddressBooks = abManager.directories;

while (allAddressBooks.hasMoreElements()) {

  let ab = allAddressBooks.getNext();
  if (ab instanceof Components.interfaces.nsIAbDirectory &&
      !ab.isRemote) {
    let searchResult = abManager.getDirectory(ab.URI + "?" + searchQuery).childCards;

    // ... use searchResult as required.
  }
}

Note: This method doesn't work with remote address books (e.g. LDAP). Hence the additional !ab.isRemote check

How do I add/edit/delete contacts?

Note: Not all address books can be written to. Use the nsIAbCollection.readOnly attribute to determine the read-only status of an address book

Adding Contacts

Create a card and set its properties using the nsIAbCard interface (see the interface documentation for property names supported by the application):

let card = Components.classes["@mozilla.org/addressbook/cardproperty;1"]
                     .createInstance(Components.interfaces.nsIAbCard);
  card.setProperty("FirstName", "John"); card.setProperty("LastName", "Smith");
  card.primaryEmail = "john@invalid.com";

Now add the card to an address book via its nsIAbDirectory interface:

let newCard = addressbook.addCard(card);

The addCard function returns a new nsIAbCard object that may have had nsIAbDirectory specific data added to it, e.g. database specific information, that is used on edits.

Editing Contacts

Once you have obtained a card from an nsIAbDirectory (see above), you can edit it in a two step process, firstly, change the properties that you to edit:

card.setProperty("FirstName", "Jane");
card.setProperty("LastName", "Doe");

Secondly, call modifyCard to save the card in the database.

addressbook.modifyCard(card);

Deleting Contacts

Once you have obtained a card(s) from an nsIAbDirectory (see above) you can delete one or more simply by calling the deleteCards function:

let cardsToDelete = Components.classes["@mozilla.org/array;1"]
                              .createInstance(Components.interfaces.nsIMutableArray);
cardsToDelete.appendElement(card);
// Repeat append as necessary

addressbook.deleteCards(cardsToDelete);

How do I add and use my own properties?

Use the setProperty/getProperty functions on nsIAbCard to set your property in the same way as you would do with normal properties:

card.setProperty("Random1", "xyz");
card.setProperty("Random2", "foo");
...
my value = card.getProperty("Random1");

Note: Local (mork) address books are currently the only built-in type of address book that supports saving of non-built-in properties. However, outlook directories are the only other built-in write capable directories.

How do I add/edit/delete mailing lists?

Adding a Mailing List

First create a mailing list object and initialize it:

var mailList = Components.classes["@mozilla.org/addressbook/directoryproperty;1"]
                         .createInstance(Components.interfaces.nsIAbDirectory);
mailList.isMailList = true;

Now fill in the details you want to store:

mailList.dirName = "My Mailing List";
mailList.listNickName = "NickName for List";
mailList.description = "List Description";

Add the cards you want to include in the list:

for (let i = 0; i < numCards; i++)
  mailList.addressLists.appendElement(card[i], false);

Now save the list:

var parentDirectory = ...; // An nsIAbDirectory for the parent of the mailing list.

parentDirectory.addMailList(mailList);

Modifying a Mailing List

To modify a mailing list, you first need an object that implements nsIAbCard. This can be obtained via searching in address books as mentioned above. Once you have the nsIAbCard object you can modify the names and description of it like this:

mailListCard.displayName = "New List Name";
mailListCard.lastName = mailListCard.displayName;
mailListCard.setProperty("NickName", "New NickName for List");
mailListCard.setProperty("Notes", "New List Description");

Then you need to get the equivalent mailing list object that implements nsIAbDirectory:

let abManager = Components.classes["@mozilla.org/abmanager;1"]
                          .getService(Components.interfaces.nsIAbManager);

let mailListDirectory = abManager.getDirectory(mailListCard.mailListURI);

You can then adjust the items in the actual mailing list:

mailListDirectory.addressLists.appendElement(newCard, false);

Then save the updated list to the database:

mailListDirectory.editMailListToDatabase(mailListCard);

Deleting a Mailing List

There are two ways to delete a mailing list. If you have it in its nsIAbCard form, then you can delete it in the same way as per deleting a contact. If you have it in its nsIAbDirectory form, then you can delete it like this:

var abManager = Components.classes["@mozilla.org/abmanager;1"]
                          .createInstance(Components.interfaces.nsIAbManager);
abManager.deleteAddressBook(mailList.URI);

How do I display properties dialogs to the user?

In the following examples:

  • selectedAB is the URI of an address book to default the dialog to saving the card in. This may be blank if not required.
  • abURI and card are the URI of the address book and the card contained within that address book.

New Contact Dialog

window.openDialog("chrome://messenger/content/addressbook/abNewCardDialog.xul",
                  "",
                  "chrome,resizable=no,titlebar,modal,centerscreen",
                  {selectedAB:selectedAB});

Edit Contact Dialog

window.openDialog("chrome://messenger/content/addressbook/abEditCardDialog.xul",
                  "",
                  "chrome,resizable=no,modal,titlebar,centerscreen",
                  {abURI:abURI, card:card});

New List Dialog

window.openDialog("chrome://messenger/content/addressbook/abMailListDialog.xul",
                  "",
                  "chrome,resizable=no,titlebar,modal,centerscreen",
                  {selectedAB:selectedAB});

Edit List Dialog

window.openDialog("chrome://messenger/content/addressbook/abEditListDialog.xul",
                  "",
                  "chrome,resizable=no,titlebar,modal,centerscreen",
                  {abCard:abCard, listURI:listURI});

New Address Book Dialog

Unlike the edit address book dialog, the new address book dialog does not currently have the facility to get the chrome URI based on the address book type. The following options are available.

For local (mork) address books:

window.openDialog(
    "chrome://messenger/content/addressbook/abAddressBookNameDialog.xul",
    "", "chrome,modal=yes,resizable=no,centerscreen", null);

For LDAP address books:

window.openDialog("chrome://messenger/content/addressbook/pref-directory-add.xul",
                  "",
                  "chrome,modal=yes,resizable=no,centerscreen",
                  null);

Note: There is no new address book dialog for the OS X address book. As this is a system address book, there is only one address book represented, and therefore a new address book dialog does not make sense. In Thunderbird a menu option is provided instead.

Edit Address Book Dialog

addressbook is an nsIAbDirectory that provides the chrome URI that is used in the call to openDialog. Using the propertiesChromeURI means that the different implementations can call up their own dialog.

window.openDialog(addressbook.propertiesChromeURI,
                  "editDirectory", "chrome,modal=yes,resizable=no",
                  {selectedDirectory: addressbook});

How do I set up my own address book?

Draft
This page is not complete.

As of Thunderbird 7.0, implementing your own address book type can be done with either JavaScript or with a binary component. In either case, you'll need:

If you are using C++, you can inherit from nsAbDirProperty and override functions for your specific implementation to save implementing all the functions on nsIAbDirectory yourself. The best example of a binary implementation of an address book is the OS X Address Book source code.

If you are implementing in JavaScript then you will have to implement the full interfaces mentioned above. At the moment, the best example of this is the EDS contacts integration add-on.

Once you've added your own address book implementation, you might also want to extend the address book user interface.

How do I extend the address book user interface?

There are several hooks in the address book code exposed to add-on developers to help extend the behavior of the address book.

Load and save listeners

Edit dialogs for both contacts and mailing lists expose functions for registering load and save listeners. For example, in order to register a load listener for a contact, the following should take place within the scope of the contact editor dialog:

/* An example load listener for a contact
 * aCard the nsIAbCard being loaded
 * aDocument a reference to the contact editor document
 */
function foo(aCard, aDocument) {
  // Do something useful, like disabling
  // input fields that cards for this
  // address book type do not support.
} 

RegisterLoadListener(foo);

Save listeners are functions that take the same parameters, and can be registered with:

RegisterSaveListener(foo);

Load and save listeners can be unregistered using UnregisterLoadListener(foo) and UnregisterSaveListener(foo), respectively.

Photo handlers

Photo handlers allow developers to customize the behavior of the contact editor when loading or saving contact photos. This is useful if, for example, cards within your address book implementation need to save contact photos within a database.

A photo handler defines the behavior of the contact editor for a particular photo type. Each photo handler must implement the following interface:

/*
 * onLoad: function(aCard, aDocument):
 *   Called when the editor wants to populate the contact editor
 *   input fields with information about aCard's photo.  Note that
 *   this does NOT make aCard's photo appear in the contact editor -
 *   this is left to the onShow function.  Returns true on success.
 *   If the function returns false, the generic photo handler onLoad
 *   function will be called.
 *
 * onShow: function(aCard, aDocument, aTargetID):
 *   Called when the editor wants to show this photo type.
 *   The onShow method should take the input fields in the document,
 *   and render the requested photo in the IMG tag with id
 *   aTargetID.  Note that onShow does NOT save the photo for aCard -
 *   this job is left to the onSave function.  Returns true on success.
 *   If the function returns false, the generic photo handler onShow
 *   function will be called.
 *
 * onSave: function(aCard, aDocument)
 *   Called when the editor wants to save this photo type.  The
 *   onSave method is responsible for analyzing the photo of this
 *   type requested by the user, and storing it, as well as the
 *   other fields required by onLoad/onShow to retrieve and display
 *   the photo again.  Returns true on success.  If the function
 *   returns false, the generic photo handler onSave function will
 *   be called.
 */

Photo handlers are registered within the context of the contact editor dialog using the registerPhotoHandler function, as follows:

registerPhotoHandler(aType, aPhotoHandler)

...where aType is a string with a unique identifier for that particular photo type. In order for a photo handler to be used for a contact, the PhotoType property of the contact must return a string equal to aType.

The address book defaults with three photo handler types, identified by "generic", "file" and "web". These can be overridden by calling registerPhotoHandler with these identifiers and a custom photo handler.

Photo display handlers

A photo display handler determines how a contact photo can be extracted from a contact and displayed in an IMG element. This is useful in the contact viewer within the address book.

Photo display handlers are registered with the registerPhotoDisplayHandler function within the context of the address book card view overlay.

A photo display handler is a function that behaves as follows:

/*
 * function(aCard, aImg):
 *    The function is responsible for determining how to retrieve
 *    the photo from nsIAbCard aCard, and for displaying it in img
 *    img element aImg.  Returns true if successful.  If it returns
 *    false, the generic photo display handler will be called.
 */

How do I set up autocomplete to use the address book?

There are 3 address book autocomplete widgets:

  • "mydomain" - use to autocomplete a domain for an email identity, e.g. for a user @bar.com, entering foo would become foo@bar.com
  • "addrbook" - use to autocomplete from a local address book, e.g. a Mork based local store or a OS X address book
  • LDAP - use for searching in LDAP address books.

MyDomain and Addrbook Autocomplete

"mydomain" and "addrbook" both use the toolkit interfaces to provide autocomplete facilities. To use them in code all you need to do is set up your text box as follows:

 <textbox id="myautocompletetextbox" type="autocomplete" autocompletesearch="mydomain addrbook"/>

The attributes of the toolkit autocomplete element can also be used here.

The "mydomain" and "addrbook" behaviours can be changed by passing an identity key (see nsIMsgIdentity.key) via an attribute autocompletesearchparam on the textbox element.

LDAP Autocomplete

LDAP autocomplete still uses the XPFE interfaces, it is planned (bug 452232) to move this over to the toolkit interfaces at some stage.

The best example of how to use this is via the existing code that can be found here.

Click here for a addon demonstrating autocomplete with LDAP and addrbook working with both Thunderbird 2 and Thunderbird 3.