/* Copyright (c) 2008 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Add support for Array.some() on older browsers.
*/
if (!Array.prototype.some)
{
Array.prototype.some = function(fun)
{
var len = this.length;
if (typeof fun != "function")
throw new TypeError();
var thisp = arguments[1];
for (var i = 0; i < len; i++)
{
if (i in this &&
fun.call(thisp, this[i], i, this))
return true;
}
return false;
};
}
var Picker = {
AUTH_SCOPE: 'http://www.google.com/m8/feeds/',
CONTACTS_URL: 'http://www.google.com/m8/feeds/contacts/default/full',
GROUPS_URL: 'http://www.google.com/m8/feeds/groups/default/full',
GROUPS_PANE_ID: 'picker_groups_pane',
CONTACTS_PANE_ID: 'picker_contacts_pane',
INFO_PANEL_ID: 'picker_info_pane',
INFO_CONTAINER_ID: 'picker_info_container',
GROUPS_LIST_ID: 'picker_groups',
CONTACT_LIST_ID: 'picker_contacts',
GROUP_SELECTED_CLASS: 'picker_selected',
GROUP_END_SPECIAL_CLASS: 'picker_endspecial',
CONTACT_SELECTED_CLASS: 'picker_selected',
CONTACT_INFO_BLOCK_CLASS: 'picker_info_block',
CONTACT_INFO_TITLE_CLASS: 'picker_info_title',
CONTACT_INFO_META_CLASS: 'picker_info_meta',
GOOGLE_MAPS_QUERY_URL: 'http://maps.google.com/?q=',
serviceName: 0,
contactsService: 0,
container: 0,
groupSelector: 0,
userAddCallback: 0,
userRemoveCallback: 0,
errorCallback: 0,
selectedGroup: 0,
selectedUser: 0,
selectedUsers: [],
/* Callback functions */
/**
* Callback function invoked by the Google Contacts library after
* calling populateGroups(). Generates the DOM entries that comprise the
* list of group entries. Do not call directly.
*
* @param feedRoot The list of groups as returned by the Contacts
* service.
*/
processGroupFeed: function(feedRoot) {
// Convert result set to an array of entries
var entries = feedRoot.feed.entry;
var newGroupList = document.createElement('ul');
newGroupList.id = Picker.GROUPS_LIST_ID;
// Unconditionally add an 'All Contacts' group
var allContactsGroup = document.createElement('li');
allContactsGroup.id = 'picker-group-all';
allContactsGroup.className = Picker.GROUP_END_SPECIAL_CLASS;
var allContactsGroupSelector = document.createElement('a')
allContactsGroupSelector.setAttribute('href',
'javascript:Picker.displayContactGroup(\'' + Picker.CONTACTS_URL +
'\', \'' + allContactsGroup.id + '\')');
allContactsGroupSelector.appendChild(document.createTextNode(
'All Contacts'));
allContactsGroup.appendChild(allContactsGroupSelector);
newGroupList.appendChild(allContactsGroup);
// Write out new list of groups
for (var i = 0; i < entries.length; i++) {
var entry = entries[i];
var newGroup = document.createElement('li');
newGroup.id = 'picker-group-' + i;
// Create a hyperlink to represent the group
var newGroupSelector = document.createElement('a');
newGroupSelector.setAttribute('href',
'javascript:Picker.displayContactGroup(\''
+ entry.getId().getValue() + '\', \'' + newGroup.id + '\')');
var name = entry.getTitle().getText();
// Finalize group and add to groups pane
newGroupSelector.appendChild(document.createTextNode(name));
newGroup.appendChild(newGroupSelector);
newGroupList.appendChild(newGroup);
}
// Replace the old group list with the newly generated one
var groupsPane = document.getElementById(Picker.GROUPS_PANE_ID);
var oldGroupsList = document.getElementById(Picker.GROUPS_LIST_ID);
groupsPane.replaceChild(newGroupList, oldGroupsList);
// Update the list of contacts
Picker.displayContactGroup(Picker.CONTACTS_URL, allContactsGroup.id);
},
/**
* Callback function invoked by the Google Contacts library after calling
* populateContatcts(). Generates the DOM entries that comprise the list
* of contact entries. Do not call directly.
*
* @param feedRoot The list of contact entries as returned by the
* Contacts service.
*/
processContactFeed: function(feedRoot) {
// Convert result set to an array of entries
var entries = feedRoot.feed.entry;
var newContactList = document.createElement('ul');
newContactList.id = Picker.CONTACT_LIST_ID;
// Write out new list of groups
for (var i = 0; i < entries.length; i++) {
var entry = entries[i];
var id = entry.getId().getValue()
var newContact = document.createElement('li');
newContact.id = 'picker_contact_' + i;
// Create a hyperlink to allow viewing contact details
var newContactDetailsLink = document.createElement('a');
newContactDetailsLink.setAttribute('href',
'javascript:Picker.showContactDetails(\'' + id + '\', \'' +
newContact.id + '\')')
// Create a checkbox to allow selection of this contact
var newContactSelector = document.createElement('input');
newContactSelector.type = 'checkbox';
newContactSelector.name = 'contact_selector';
newContactSelector.id = 'contact_selector_' + i;
newContactSelector.value = id;
// Check to see if the contact has already been added before,
// perhaps while browsing a differnt group
var isExistingUser = Picker.selectedUsers.some(
function(selectedUserId) {
return (selectedUserId == newContactSelector.value)
}
);
if (isExistingUser) {
newContactSelector.checked = true;
}
// Now that we've optionally toggled the contact's checkbox, we
// can set an event handler
newContactSelector.onclick = function() {Picker.updateStatus(this.id);};
// List the contact's name next to the checkbox
var name = 0;
if (entry.getTitle() && entry.getTitle().getText()) {
name = entry.getTitle().getText();
} else if (entry.getEmailAddresses()) {
name = entry.getEmailAddresses()[0].getAddress();
} else {
// This should never actually be reached, since users are currently
// required to have either a name or an email address
name = 'Untitled Contact';
}
var newContactLabel = document.createTextNode(name);
newContactDetailsLink.appendChild(newContactSelector);
newContactDetailsLink.appendChild(newContactLabel);
newContact.appendChild(newContactDetailsLink);
newContactList.appendChild(newContact);
}
// Replace the old contact list with the newly generated one
var contactsPane = document.getElementById(Picker.CONTACTS_PANE_ID);
var oldContactList = document.getElementById(Picker.CONTACT_LIST_ID);
contactsPane.replaceChild(newContactList, oldContactList);
},
/**
* Display the details for a given conatct in the info pane.
*/
processContactDetails: function(entryRoot) {
var entry = entryRoot.entry;
// Create a new div to hold the user's info
var newPanel = document.createElement('div');
newPanel.id = Picker.INFO_PANEL_ID;
// Output the user's name and position
var personalInfo = document.createElement('div');
personalInfo.className = Picker.CONTACT_INFO_BLOCK_CLASS;
var name = entry.getTitle();
if (name && name.getText()) {
var nameP = document.createElement('p');
nameP.className = Picker.CONTACT_INFO_TITLE_CLASS;
nameP.appendChild(document.createTextNode(name.getText()));
personalInfo.appendChild(nameP);
}
var emails = entry.getEmailAddresses();
for (var i = 0; i < emails.length; i++) {
var email = emails[i];
if (email.getAddress()) {
var emailP = document.createElement('p');
var emailA = document.createElement('a');
var emailTxt = email.getAddress();
emailA.setAttribute('href', 'mailto:' + emailTxt);
emailA.appendChild(document.createTextNode(emailTxt)),
emailP.appendChild(emailA);
personalInfo.appendChild(emailP);
}
}
var organizations = entry.getOrganizations();
if (organizations[0]) {
var title = organizations[0].getOrgTitle();
if (title && title.getValue()) {
var titleP = document.createElement('p');
titleP.appendChild(document.createTextNode(title.getValue()));
personalInfo.appendChild(titleP);
}
var organization = organizations[0].getOrgName();
if (organization && organization.getValue()) {
var organizationP = document.createElement('p');
organizationP.appendChild(document.createTextNode(
organization.getValue()));
personalInfo.appendChild(organizationP);
}
}
newPanel.appendChild(personalInfo);
// List the contact's telephone information
var telephoneInfo = document.createElement('div');
telephoneInfo.className = Picker.CONTACT_INFO_BLOCK_CLASS;
var phones = entry.getPhoneNumbers();
for (var i = 0; i < phones.length; i++) {
var phone = phones[i];
if (phone.getValue()) {
var phoneP = document.createElement('p');
var phoneTxt = phone.getValue();
phoneP.appendChild(document.createTextNode(phoneTxt + ' '));
var phoneSpan = document.createElement('span');
phoneSpan.className = Picker.CONTACT_INFO_META_CLASS;
var phoneType = 0;
if (phone.getLabel()) {
phoneType = phone.getLabel();
} else if (phone.getRel()) {
phoneType = phone.getRel();
// We're not provided with a human-readable label, so the
// next line extracts the phone type directly from the fragment
// name in the gd:phoneNumber's rel attribute.
phoneType = phoneType.substring(33, phoneType.length);
}
if (phoneType) {
phoneSpan.appendChild(document.createTextNode(phoneType));
phoneP.appendChild(phoneSpan);
}
telephoneInfo.appendChild(phoneP);
}
}
newPanel.appendChild(telephoneInfo);
// List the contact's IM information
var imInfo = document.createElement('div');
imInfo.className = Picker.CONTACT_INFO_BLOCK_CLASS;
var imAddresses = entry.getImAddresses();
for (var i = 0; i < imAddresses.length; i++) {
var imAddress = imAddresses[i];
if (imAddress.getAddress()) {
var imAddressP = document.createElement('p');
var imAddressTxt = imAddress.getAddress();
imAddressP.appendChild(document.createTextNode(imAddressTxt + ' '));
var imAddressSpan = document.createElement('span');
imAddressSpan.className = Picker.CONTACT_INFO_META_CLASS;
if (imAddress.getProtocol()) {
var imAddressProtocol = imAddress.getProtocol();
// We're not provided with a human-friendly label, so the
// next line extracts the protocol name directly from the fragment
// name in the gd:imAddress's protocol attribute.
imAddressProtocol = imAddressProtocol.substring(33,
imAddressProtocol.length).toLowerCase();
imAddressSpan.appendChild(
document.createTextNode(imAddressProtocol));
imAddressP.appendChild(imAddressSpan);
imInfo.appendChild(imAddressP);
}
}
}
newPanel.appendChild(imInfo);
// List the contact's postal address
var postalInfo = document.createElement('div');
postalInfo.className = Picker.CONTACT_INFO_BLOCK_CLASS;
var postalAddresses = entry.getPostalAddresses();
for (var i = 0; i < postalAddresses.length; i++) {
var postalAddress = postalAddresses[i];
if (postalAddress.getValue()) {
var postalAddressP = document.createElement('p');
var postalAddressTxt = postalAddress.getValue();
// We're creating this element out of order because the postal
// address will be transformed in a later step.
var postalAddressMapHref = Picker.GOOGLE_MAPS_QUERY_URL
+ postalAddressTxt;
while(postalAddressMapHref.match('\n'))
postalAddressMapHref = postalAddressMapHref.replace('\n', ', ');
var postalAddressMapLink = document.createElement('a');
postalAddressMapLink.setAttribute('href', postalAddressMapHref);
// Convert newlines in the postalAddressMapLink
postalAddressMapLink.appendChild(document.createTextNode('map'));
// Convert newlines in the postalAddressTxt to HTML equivilents
// Keep in mind this logic should be more complicated. A
// maliciously crafted address field could achieve a cross-site
// scripting attack.
while(postalAddressTxt.match('\n'))
postalAddressTxt = postalAddressTxt.replace('\n', '
');
postalAddressP.innerHTML = postalAddressTxt + '
';
var postalAddressSpan = document.createElement('span');
postalAddressSpan.className = Picker.CONTACT_INFO_META_CLASS;
var postalAddressType = postalAddress.getRel();
// We're not provided with a human-friendly label, so the
// next line extracts the address type directly from the fragment
// name in the gd:postalAddress's rel attribute.
postalAddressType = postalAddressType.substring(33,
postalAddressType.length).toLowerCase()
postalAddressSpan.appendChild(document.createTextNode(
postalAddressType));
postalAddressP.appendChild(postalAddressSpan);
postalAddressP.appendChild(document.createTextNode(' - '));
postalAddressP.appendChild(postalAddressMapLink);
postalInfo.appendChild(postalAddressP);
}
}
newPanel.appendChild(postalInfo);
// List any notes associated with the contact
// List the contact's IM information
var notesInfo = document.createElement('div');
notesInfo.className = Picker.CONTACT_INFO_BLOCK_CLASS;
var notes = entry.getContent();
if (notes && notes.getText()) {
var notesP = document.createElement('p');
var notesTxt = notes.getText();
notesP.appendChild(document.createTextNode(notesTxt));
notesInfo.appendChild(notesP);
}
newPanel.appendChild(notesInfo);
// Update the contacts view
var infoContainer = document.getElementById(Picker.INFO_CONTAINER_ID)
var oldPanel = document.getElementById(Picker.INFO_PANEL_ID);
infoContainer.replaceChild(newPanel, oldPanel);
},
/**
* Callback method invoked when a user has been added by selecting the
* user's checkbox. Used by updateStatus(), do not call directly.
*
* @param entryRoot The root node for the entry that should be added
* as returned by the Contacts service.
*/
callUserAddCallback: function(entryRoot) {
var entry = entryRoot.entry;
var id = entry.getId().getValue();
// Check to see if the contact has already been added. If not, add
// the contact to the list of added IDs.
var match = Picker.selectedUsers.some(function(selectedUserId) {
return (selectedUserId == id);
});
if (!match) {
Picker.selectedUsers.push(id);
} else {
Picker.error('Attempt to add a contact which has already been added.');
}
// Invoke callback
if (Picker.userAddCallback != 0 && typeof(Picker.userAddCallback) !=
'undefined') {
Picker.userAddCallback(entry);
} else {
Picker.error('User callback function undefined: userAddCallback');
}
},
/**
* Callback method invoked when a user has been removed by deselecting
* the user's checkbox. Used by updateStatus(), do not call directly.
*
* @param entryRoot The root node for the entry that should be removed
* as returned by the Contacts service.
*/
callUserRemoveCallback: function(entryRoot) {
var entry = entryRoot.entry;
var id = entry.getId().getValue();
// Check to see if the contact has already been added. If so, remove
// the contact to the list of added IDs.
var match = false;
for (var i = 0; i < Picker.selectedUsers.length; i++) {
if (Picker.selectedUsers[i] == id) {
match = true;
break;
}
}
if (match) {
Picker.selectedUsers.splice(i, 1);
} else {
Picker.error('Attempt to remove a contact which has not been added.');
}
// Invoke callback
if (Picker.userRemoveCallback != 0 && typeof(Picker.userRemoveCallback)
!= 'undefined') {
Picker.userRemoveCallback(entry);
} else {
Picker.error('User callback function undefined: userRemoveCallback');
}
},
/* Class methods */
/**
* Create an authenticated session with the Google Contacts server.
* Requires that setServiceName() has been called previously.
*/
login: function() {
if (this.serviceName != 0 && typeof(this.serviceName) != 'undefined') {
// Obtain a login token
google.accounts.user.login(this.AUTH_SCOPE);
// Create a new persistant service object
this.contactsService = new google.gdata.contacts.ContactsService(
this.serviceName);
} else {
this.error('Service name undefined, call setServiceName()');
}
},
/**
* Destroy the current Google Contacts session, including cached
* authentication tokens.
*/
logout: function() {
google.accounts.user.logout();
this.container.innerHTML = '
Friend Picker Sample for Google Contacts\ Data API
\