1 /*
  2  * ***** BEGIN LICENSE BLOCK *****
  3  * Zimbra Collaboration Suite Web Client
  4  * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Synacor, Inc.
  5  *
  6  * The contents of this file are subject to the Common Public Attribution License Version 1.0 (the "License");
  7  * you may not use this file except in compliance with the License.
  8  * You may obtain a copy of the License at: https://www.zimbra.com/license
  9  * The License is based on the Mozilla Public License Version 1.1 but Sections 14 and 15
 10  * have been added to cover use of software over a computer network and provide for limited attribution
 11  * for the Original Developer. In addition, Exhibit A has been modified to be consistent with Exhibit B.
 12  *
 13  * Software distributed under the License is distributed on an "AS IS" basis,
 14  * WITHOUT WARRANTY OF ANY KIND, either express or implied.
 15  * See the License for the specific language governing rights and limitations under the License.
 16  * The Original Code is Zimbra Open Source Web Client.
 17  * The Initial Developer of the Original Code is Zimbra, Inc.  All rights to the Original Code were
 18  * transferred by Zimbra, Inc. to Synacor, Inc. on September 14, 2015.
 19  *
 20  * All portions of the code are Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Synacor, Inc. All Rights Reserved.
 21  * ***** END LICENSE BLOCK *****
 22  */
 23 
 24 /**
 25  * @overview
 26  * This file contains the contact picker classes.
 27  * 
 28  */
 29 
 30 /**
 31  * Creates a dialog that lets the user select addresses from a contact list.
 32  * @constructor
 33  * @class
 34  * This class creates and manages a dialog that lets the user select addresses
 35  * from a contact list. Two lists are maintained, one with contacts to select
 36  * from, and one that contains the selected addresses. Between them are buttons
 37  * to shuffle addresses back and forth between the two lists.
 38  *
 39  * @author Conrad Damon
 40  * 
 41  * @param {Array}	buttonInfo		the transfer button IDs and labels
 42  * 
 43  * @extends		DwtDialog
 44  */
 45 ZmContactPicker = function(buttonInfo) {
 46 
 47 	DwtDialog.call(this, {parent:appCtxt.getShell(), title:ZmMsg.selectAddresses, id: "ZmContactPicker"});
 48 
 49 	this._buttonInfo = buttonInfo;
 50 	this._initialized = false;
 51 	this._emailListOffset = 0; //client side paginating over email list. Offset of current page of email addresses. Quite different than _lastServerOffset if contacts have 0 or more than 1 email addresses.
 52 	this._serverContactOffset = 0; //server side paginating over contact list. Offset of last contact block we got from the server (each contact could have 0, 1, or more emails so we have to keep track of this separate from the view offset.
 53 	this._ascending = true; //asending or descending search. Keep it stored for pagination to do the right sort.
 54 	this._emailList = new AjxVector();
 55 	this._detailedSearch = appCtxt.get(ZmSetting.DETAILED_CONTACT_SEARCH_ENABLED);
 56 	this._ignoreSetDragBoundries = true;
 57 
 58 	this.setSize(Dwt.DEFAULT, this._getDialogHeight());
 59 
 60 	this._searchErrorCallback = new AjxCallback(this, this._handleErrorSearch);
 61 };
 62 
 63 ZmContactPicker.prototype = new DwtDialog;
 64 ZmContactPicker.prototype.constructor = ZmContactPicker;
 65 
 66 ZmContactPicker.prototype.isZmContactPicker = true;
 67 ZmContactPicker.prototype.toString = function() { return "ZmContactPicker"; };
 68 
 69 // Consts
 70 
 71 ZmContactPicker.DIALOG_HEIGHT = 460;
 72 
 73 ZmContactPicker.SEARCH_BASIC = "search";
 74 ZmContactPicker.SEARCH_NAME = "name";
 75 ZmContactPicker.SEARCH_EMAIL = "email";
 76 ZmContactPicker.SEARCH_DEPT = "dept";
 77 ZmContactPicker.SEARCH_PHONETIC = "phonetic";
 78 
 79 ZmContactPicker.SHOW_ON_GAL = [ZmContactPicker.SEARCH_BASIC, ZmContactPicker.SEARCH_NAME, ZmContactPicker.SEARCH_EMAIL, ZmContactPicker.SEARCH_DEPT];
 80 ZmContactPicker.SHOW_ON_NONGAL = [ZmContactPicker.SEARCH_BASIC, ZmContactPicker.SEARCH_NAME, ZmContactPicker.SEARCH_PHONETIC, ZmContactPicker.SEARCH_EMAIL];
 81 ZmContactPicker.ALL = [ ZmContactPicker.SEARCH_BASIC, ZmContactPicker.SEARCH_NAME, ZmContactPicker.SEARCH_PHONETIC, ZmContactPicker.SEARCH_EMAIL, ZmContactPicker.SEARCH_DEPT ];
 82 
 83 // Public methods
 84 
 85 
 86 /**
 87 * Displays the contact picker dialog. The source list is populated with
 88 * contacts, and the target list is populated with any addresses that are
 89 * passed in. The address button that was used to popup the dialog is set
 90 * as the active button.
 91 *
 92 * @param {String}	buttonId	the button ID of the button that called us
 93 * @param {Hash}	addrs		a hash of 3 vectors (one for each type of address)
 94 * @param {String}	str		initial search string
 95 */
 96 ZmContactPicker.prototype.popup =
 97 function(buttonId, addrs, str, account) {
 98 
 99 	if (!this._initialized) {
100 		this._initialize(account);
101 		this._initialized = true;
102 	}
103 	else if (appCtxt.multiAccounts && this._account != account) {
104 		this._account = account;
105 		this._resetSelectDiv();
106 	}
107 	this._emailListOffset = 0;
108 
109 	var searchFor = this._searchInSelect ? this._searchInSelect.getValue() : ZmContactsApp.SEARCHFOR_CONTACTS;
110 
111 	// reset column sorting preference
112 	this._chooser.sourceListView.setSortByAsc(ZmItem.F_NAME, true);
113 
114 	// reset button states
115 	this._chooser.reset();
116 	if (buttonId) {
117 		this._chooser._setActiveButton(buttonId);
118 	}
119 
120 	// populate target list if addrs were passed in
121 	if (addrs) {
122 		for (var id in addrs) {
123 			this._chooser.addItems(addrs[id], DwtChooserListView.TARGET, true, id);
124 		}
125 	}
126 
127 	for (var fieldId in this._searchField) {
128 		var field = this._searchField[fieldId];
129 		field.disabled = false;
130 		field.value = (AjxUtil.isObject(str) ? str[fieldId] : str) || "";
131 	}
132 
133 	// reset paging buttons
134 	this._prevButton.setEnabled(false);
135 	this._nextButton.setEnabled(false);
136 
137 	this.search(null, true, true);
138 
139     DwtDialog.prototype.popup.call(this);
140 
141 	this._resizeChooser();
142 
143     if ((this.getLocation().x < 0 ||  this.getLocation().y < 0) ){
144                 // parent window size is smaller than Dialog size
145                 this.setLocation(0,30);
146                 var size = Dwt.getWindowSize();
147                 var currentSize = this.getSize();
148                 var dragElement = document.getElementById(this._dragHandleId);
149                 DwtDraggable.setDragBoundaries(dragElement, 100 - currentSize.x, size.x - 100, 0, size.y - 100);
150     }
151 
152 	var focusField = this._searchField[ZmContactPicker.SEARCH_BASIC] || this._searchField[ZmContactPicker.SEARCH_NAME];
153 	appCtxt.getKeyboardMgr().grabFocus(focusField);
154 
155 };
156 
157 
158 ZmContactPicker.prototype._resetResults =
159 function() {
160 	this._emailList.removeAll();
161 	this._serverContactOffset = 0;
162 	this._emailListOffset = 0;
163 };
164 
165 /**
166  * Closes the dialog.
167  * 
168  */
169 ZmContactPicker.prototype.popdown =
170 function() {
171 	// disable search field (hack to fix bleeding cursor)
172 
173 	for (var fieldId in this._searchField) {
174 		this._searchField[fieldId].disabled = true;
175 	}
176 
177 	this._contactSource = null;
178 	this._resetResults();
179 
180 	DwtDialog.prototype.popdown.call(this);
181 };
182 
183 /**
184  * Performs a search.
185  * 
186  * @private
187  */
188 ZmContactPicker.prototype.search =
189 function(colItem, ascending, firstTime, lastId, lastSortVal, offset) {
190 	if (offset == undefined) {
191 		//this could be a call from DwtChooserListView.prototype._sortColumn, which means we have to reset the result and both server and client pagination.
192 		//In any case the results should be reset or are already reset so doesn't hurt to reset.
193 		this._resetResults();
194 	}
195 
196 	if (ascending === null || ascending === undefined) {
197 		ascending = this._ascending;
198 	}
199 	else {
200 		this._ascending = ascending;
201 	}
202 	
203 	var query;
204 	var queryHint = [];
205 	var emailQueryTerm = "";
206 	var phoneticQueryTerms = [];
207 	var nameQueryTerms = [];
208 	var conds = [];
209 	if (this._detailedSearch) {
210 		var nameQuery = this.getSearchFieldValue(ZmContactPicker.SEARCH_NAME);
211 		var emailQuery = this.getSearchFieldValue(ZmContactPicker.SEARCH_EMAIL);
212 		var deptQuery = this.getSearchFieldValue(ZmContactPicker.SEARCH_DEPT);
213 		var phoneticQuery = this.getSearchFieldValue(ZmContactPicker.SEARCH_PHONETIC);
214 		var isGal = this._searchInSelect && (this._searchInSelect.getValue() == ZmContactsApp.SEARCHFOR_GAL);
215 		if (nameQuery && !isGal) {
216 			var nameQueryPieces = nameQuery.split(/\s+/);
217 			for (var i = 0; i < nameQueryPieces.length; i++) {
218 				var nameQueryPiece = nameQueryPieces[i];
219 				nameQueryTerms.push("#"+ZmContact.F_firstName + ":" + nameQueryPiece);
220 				nameQueryTerms.push("#"+ZmContact.F_lastName + ":" + nameQueryPiece);
221 				nameQueryTerms.push("#"+ZmContact.F_middleName + ":" + nameQueryPiece);
222 				nameQueryTerms.push("#"+ZmContact.F_nickname + ":" + nameQueryPiece);
223 			}
224 			query = "(" + nameQueryTerms.join(" OR ") + ")";
225 		} else {
226 			if (nameQuery && isGal) {
227 				conds.push([{attr:ZmContact.F_firstName, op:"has", value: nameQuery},
228 				            {attr:ZmContact.F_lastName,  op:"has", value: nameQuery},
229 				            {attr:ZmContact.F_middleName, op:"has", value: nameQuery},
230 				            {attr:ZmContact.F_nickname,  op:"has", value: nameQuery},
231 				            {attr:ZmContact.F_phoneticFirstName, op:"has", value: nameQuery},
232 				            {attr:ZmContact.F_phoneticLastName,  op:"has", value: nameQuery}]);
233 			}
234 			query = "";
235 		}
236 		if (emailQuery) {
237 			if (isGal) {
238 				conds.push([{attr:ZmContact.F_email, op:"has", value: emailQuery},
239 				{attr:ZmContact.F_email2, op:"has", value: emailQuery},
240 				{attr:ZmContact.F_email3, op:"has", value: emailQuery},
241 				{attr:ZmContact.F_email4, op:"has", value: emailQuery},
242 				{attr:ZmContact.F_email5, op:"has", value: emailQuery},
243 				{attr:ZmContact.F_email6, op:"has", value: emailQuery},
244 				{attr:ZmContact.F_email7, op:"has", value: emailQuery},
245 				{attr:ZmContact.F_email8, op:"has", value: emailQuery},
246 				{attr:ZmContact.F_email9, op:"has", value: emailQuery},
247 				{attr:ZmContact.F_email10, op:"has", value: emailQuery},
248 				{attr:ZmContact.F_email11, op:"has", value: emailQuery},
249 				{attr:ZmContact.F_email12, op:"has", value: emailQuery},
250 				{attr:ZmContact.F_email13, op:"has", value: emailQuery},
251 				{attr:ZmContact.F_email14, op:"has", value: emailQuery},
252 				{attr:ZmContact.F_email15, op:"has", value: emailQuery},
253 				{attr:ZmContact.F_email16, op:"has", value: emailQuery}
254 				]);
255 			} else {
256 				emailQueryTerm = "to:"+emailQuery+"*";
257 			}
258 		}
259 		if (deptQuery && isGal) {
260 			conds.push({attr:ZmContact.F_department, op:"has", value: deptQuery});
261 		}
262 		if (phoneticQuery && !isGal) {
263 			var phoneticQueryPieces = phoneticQuery.split(/\s+/);
264 			for (var i=0; i<phoneticQueryPieces.length; i++) {
265 				phoneticQueryTerms.push("#"+ZmContact.F_phoneticFirstName + ":" + phoneticQueryPieces[i]);
266 				phoneticQueryTerms.push("#"+ZmContact.F_phoneticLastName + ":" + phoneticQueryPieces[i]);
267 			}
268 		}
269 	} else {
270 		query = this.getSearchFieldValue(ZmContactPicker.SEARCH_BASIC);
271 	}
272 	
273 
274 	if (this._searchInSelect) {
275 		var searchFor = this._searchInSelect.getValue();
276 		this._contactSource = (searchFor == ZmContactsApp.SEARCHFOR_CONTACTS || searchFor == ZmContactsApp.SEARCHFOR_PAS)
277 			? ZmItem.CONTACT
278 			: ZmId.SEARCH_GAL;
279 
280 		if (searchFor == ZmContactsApp.SEARCHFOR_PAS) {
281 			queryHint.push(ZmSearchController.generateQueryForShares(ZmId.ITEM_CONTACT) || "is:local");
282 		} else if (searchFor == ZmContactsApp.SEARCHFOR_CONTACTS) {
283 			queryHint.push("is:local");
284 		} else if (searchFor == ZmContactsApp.SEARCHFOR_GAL) {
285             ascending = true;
286         }
287 	} else {
288 		this._contactSource = appCtxt.get(ZmSetting.CONTACTS_ENABLED, null, this._account)
289 			? ZmItem.CONTACT
290 			: ZmId.SEARCH_GAL;
291 
292 		if (this._contactSource == ZmItem.CONTACT) {
293 			queryHint.push("is:local");
294 		}
295 	}
296 
297 	if (this._contactSource == ZmItem.CONTACT && query != "" && !query.startsWith ("(")) {
298 		query = query.replace(/\"/g, '\\"');
299 		query = "\"" + query + "\"";
300 	}
301 	if (phoneticQueryTerms.length) {
302 		query = query + " (" + phoneticQueryTerms.join(" OR ") + ")";
303 	}
304 	if (emailQueryTerm.length) {
305 		query = query + " " + emailQueryTerm;  // MUST match email term, hence AND rather than OR
306 	}
307 
308 	if (this._searchIcon) { //does not exist in ZmGroupView case
309 		this._searchIcon.className = "DwtWait16Icon";
310 	}
311 
312 	// XXX: line below doesn't have intended effect (turn off column sorting for GAL search)
313 	if (this._chooser) { //_chooser not defined in ZmGroupView but we also do not support sorting there anyway
314 		this._chooser.sourceListView.sortingEnabled = (this._contactSource == ZmItem.CONTACT);
315 	}
316 
317 	var params = {
318 		obj:			this,
319 		ascending:		ascending,
320 		query:			query,
321 		queryHint:		queryHint.join(" "),
322 		conds:			conds,
323 		offset:			offset || 0,
324 		lastId:			lastId,
325 		lastSortVal:	lastSortVal,
326 		respCallback:	(new AjxCallback(this, this._handleResponseSearch, [firstTime])),
327 		errorCallback:	this._searchErrorCallback,
328 		accountName:	(this._account && this._account.name),
329 		expandDL:		true
330 	};
331 	ZmContactsHelper.search(params);
332 };
333 
334 /**
335  * @private
336  */
337 ZmContactPicker.prototype._contentHtml =
338 function(account) {
339 	var showSelect;
340 	if (appCtxt.multiAccounts) {
341 		var list = appCtxt.accountList.visibleAccounts;
342 		for (var i = 0; i < list.length; i++) {
343 			var account = list[i];
344 			if (appCtxt.get(ZmSetting.CONTACTS_ENABLED, null, account) &&
345 				(appCtxt.get(ZmSetting.GAL_ENABLED, null, account) ||
346 				 appCtxt.get(ZmSetting.SHARING_ENABLED, null, account)))
347 			{
348 				showSelect = true;
349 				break;
350 			}
351 		}
352 	} else {
353 		showSelect = (appCtxt.get(ZmSetting.CONTACTS_ENABLED) &&
354 					  (appCtxt.get(ZmSetting.GAL_ENABLED) ||
355 					   appCtxt.get(ZmSetting.SHARING_ENABLED)));
356 	}
357 
358 	var subs = {
359 		id: this._htmlElId,
360 		showSelect: showSelect,
361 		detailed: this._detailedSearch
362 	};
363 
364 	return (AjxTemplate.expand("abook.Contacts#ZmContactPicker", subs));
365 };
366 
367 /**
368  * @private
369  */
370 ZmContactPicker.prototype._resetSelectDiv =
371 function() {
372     this._searchInSelect.clearOptions();
373 
374     if (appCtxt.multiAccounts) {
375         var accts = appCtxt.accountList.visibleAccounts;
376         var org = ZmOrganizer.ITEM_ORGANIZER;
377         org = ZmOrganizer.ITEM_ORGANIZER[ZmItem.CONTACT];
378 
379         for (var i = 0; i < accts.length; i++) {
380             this._searchInSelect.addOption(accts[i].displayName, false, accts[i].id);
381             var folderTree = appCtxt.getFolderTree(accts[i]);
382             var data = [];
383             data = data.concat(folderTree.getByType(org));
384             for (var j = 0; j < data.length; j++) {
385                 var addrsbk = data[j];
386                 if(addrsbk.noSuchFolder) { continue; }
387                 this._searchInSelect.addOption(addrsbk.getName(), false, addrsbk.id, "ImgContact");
388             }
389             if(accts[i].isZimbraAccount && !accts[i].isMain) {
390                 if (appCtxt.get(ZmSetting.CONTACTS_ENABLED, null, this._account)) {
391                     if (appCtxt.get(ZmSetting.SHARING_ENABLED, null, this._account))
392                         this._searchInSelect.addOption(ZmMsg.searchPersonalSharedContacts, false, ZmContactsApp.SEARCHFOR_PAS, "ImgContact");
393                 }
394 
395                 if (appCtxt.get(ZmSetting.GAL_ENABLED, null, this._account)) {
396                     this._searchInSelect.addOption(ZmMsg.GAL, true, ZmContactsApp.SEARCHFOR_GAL, "ImgContact");
397                 }
398 
399                 if (!appCtxt.get(ZmSetting.INITIALLY_SEARCH_GAL, null, this._account) ||
400                         !appCtxt.get(ZmSetting.GAL_ENABLED, null, this._account))
401                 {
402                     this._searchInSelect.setSelectedValue(ZmContactsApp.SEARCHFOR_CONTACTS);
403                 }
404             }
405         }
406 
407         for (var k = 0; k < accts.length; k++) {
408             this._searchInSelect.enableOption(accts[k].id, false);
409         }
410     } else {
411 
412         if (appCtxt.get(ZmSetting.CONTACTS_ENABLED, null, this._account)) {
413             this._searchInSelect.addOption(ZmMsg.contacts, false, ZmContactsApp.SEARCHFOR_CONTACTS);
414 
415             if (appCtxt.get(ZmSetting.SHARING_ENABLED, null, this._account))
416                 this._searchInSelect.addOption(ZmMsg.searchPersonalSharedContacts, false, ZmContactsApp.SEARCHFOR_PAS);
417         }
418 
419         if (appCtxt.get(ZmSetting.GAL_ENABLED, null, this._account)) {
420             this._searchInSelect.addOption(ZmMsg.GAL, true, ZmContactsApp.SEARCHFOR_GAL);
421         }
422 
423         if (!appCtxt.get(ZmSetting.INITIALLY_SEARCH_GAL, null, this._account) ||
424                 !appCtxt.get(ZmSetting.GAL_ENABLED, null, this._account))
425         {
426             this._searchInSelect.setSelectedValue(ZmContactsApp.SEARCHFOR_CONTACTS);
427         }
428 
429     }
430 };
431 
432 ZmContactPicker.prototype.getSearchFieldValue =
433 function(fieldId) {
434 	if (!fieldId && !this._detailedSearch) {
435 		fieldId = ZmContactPicker.SEARCH_BASIC;
436 	}
437 	var field = this._searchField[fieldId];
438 	return field && AjxStringUtil.trim(field.value) || "";
439 };
440 
441 
442 ZmContactPicker.prototype._getDialogHeight =
443 function() {
444 	return ZmContactPicker.DIALOG_HEIGHT - (appCtxt.isChildWindow ? 100 : 0);
445 };
446 
447 ZmContactPicker.prototype._getSectionHeight =
448 function(idSuffix) {
449 	return Dwt.getSize(document.getElementById(this._htmlElId + idSuffix)).y;
450 
451 };
452 
453 ZmContactPicker.prototype._resizeChooser =
454 function() {
455 
456 	var chooserHeight = this._getDialogHeight()
457 			- this._getSectionHeight("_handle")  //the header
458 			- this._getSectionHeight("_searchTable")
459 			- this._getSectionHeight("_paging")
460 			- this._getSectionHeight("_buttonsSep")
461 			- this._getSectionHeight("_buttons")
462 			- 30; //still need some magic to account for some margins etc.
463 
464 	this._chooser.resize(this.getSize().x - 25, chooserHeight);
465 };
466 
467 /**
468  * called only when ZmContactPicker is first created. Sets up initial layout.
469  * 
470  * @private
471  */
472 ZmContactPicker.prototype._initialize =
473 function(account) {
474 
475 	// create static content and append to dialog parent
476 	this.setContent(this._contentHtml(account));
477 
478 	this._searchIcon = document.getElementById(this._htmlElId + "_searchIcon");
479 
480 	// add search button
481 	this._searchButton = new DwtButton({parent:this, parentElement:(this._htmlElId+"_searchButton")});
482 	this._searchButton.setText(ZmMsg.search);
483 	this._searchButton.addSelectionListener(new AjxListener(this, this._searchButtonListener));
484 
485 	// add select menu
486 	var selectCellId = this._htmlElId + "_listSelect";
487 	var selectCell = document.getElementById(selectCellId);
488 	if (selectCell) {
489 		this._searchInSelect = new DwtSelect({
490 			parent:         this,
491 			parentElement:  selectCellId,
492 			id:             Dwt.getNextId("ZmContactPickerSelect_"),
493 			legendId:       this._htmlElId + '_listSelectLbl'
494 		});
495 		this._resetSelectDiv();
496 		this._searchInSelect.addChangeListener(new AjxListener(this, this._searchTypeListener));
497 	} else {
498 		this.setSize("600");
499 	}
500 
501 	// add chooser
502 	this._chooser = new ZmContactChooser({parent:this, buttonInfo:this._buttonInfo});
503 	this._chooser.reparentHtmlElement(this._htmlElId + "_chooser");
504 
505 	// add paging buttons
506 	var pageListener = new AjxListener(this, this._pageListener);
507 	this._prevButton = new DwtButton({parent:this, parentElement:(this._htmlElId+"_pageLeft")});
508 	this._prevButton.setText(ZmMsg.previous);
509 	this._prevButton.setImage("LeftArrow");
510 	this._prevButton.addSelectionListener(pageListener);
511 
512 	this._nextButton = new DwtButton({parent:this, style:DwtLabel.IMAGE_RIGHT, parentElement:(this._htmlElId+"_pageRight")});
513 	this._nextButton.setText(ZmMsg.next);
514 	this._nextButton.setImage("RightArrow");
515 	this._nextButton.addSelectionListener(pageListener);
516 
517 	var pageContainer = document.getElementById(this._htmlElId + "_paging");
518 	if (pageContainer) {
519 		Dwt.setSize(pageContainer, this._chooser.sourceListView.getSize().x);
520 	}
521 
522 	// init listeners
523 	this.setButtonListener(DwtDialog.OK_BUTTON, new AjxListener(this, this._okButtonListener));
524 	this.setButtonListener(DwtDialog.CANCEL_BUTTON, new AjxListener(this, this._cancelButtonListener));
525 
526 	var fieldMap = {};
527 	var rowMap = {};
528 	this.mapFields(fieldMap, rowMap);
529 
530 	this._searchField = {};
531 	for (var fieldId in fieldMap) {
532 		var field = Dwt.byId(fieldMap[fieldId]);
533 		if (field) {
534 			this._searchField[fieldId] = field;
535 			Dwt.setHandler(field, DwtEvent.ONKEYUP, ZmContactPicker._keyPressHdlr);
536 		}
537 	}
538 
539 	this._searchRow = {};
540 	for (var rowId in rowMap) {
541 		var row = Dwt.byId(rowMap[rowId]);
542 		if (row) {
543 			this._searchRow[rowId] = row;
544 		}
545 	}
546 	this._updateSearchRows(this._searchInSelect && this._searchInSelect.getValue() || ZmContactsApp.SEARCHFOR_CONTACTS);
547 	this._keyPressCallback = new AjxCallback(this, this._searchButtonListener);
548     this.sharedContactGroups = [];
549 
550 	//add tabgroups for keyboard navigation
551 	this._tabGroup = new DwtTabGroup(this.toString());
552 	this._tabGroup.removeAllMembers();
553 	for (var i = 0; i < ZmContactPicker.ALL.length; i++) {
554 		field = Dwt.byId(fieldMap[ZmContactPicker.ALL[i]]);
555 		if (Dwt.getVisible(field)) {
556 			this._tabGroup.addMember(field);
557 		}
558 	}
559 	this._tabGroup.addMember(this._searchButton);
560 	this._tabGroup.addMember(this._searchInSelect);
561 	this._tabGroup.addMember(this._chooser.getTabGroupMember());
562 	this._tabGroup.addMember(this._prevButton);
563 	this._tabGroup.addMember(this._nextButton);
564 	for (var i = 0; i < this._buttonList.length; i++) {
565 		this._tabGroup.addMember(this._button[this._buttonList[i]]);
566 	}
567 };
568 
569 ZmContactPicker.prototype.mapFields =
570 function(fieldMap, rowMap) {
571 	if (this._detailedSearch) {
572 		fieldMap[ZmContactPicker.SEARCH_NAME] = this._htmlElId + "_searchNameField";
573 		fieldMap[ZmContactPicker.SEARCH_EMAIL] = this._htmlElId + "_searchEmailField";
574 		fieldMap[ZmContactPicker.SEARCH_DEPT] = this._htmlElId + "_searchDepartmentField";
575 		fieldMap[ZmContactPicker.SEARCH_PHONETIC] = this._htmlElId + "_searchPhoneticField";
576 		rowMap[ZmContactPicker.SEARCH_NAME] = this._htmlElId + "_searchNameRow";
577 		rowMap[ZmContactPicker.SEARCH_PHONETIC] = this._htmlElId + "_searchPhoneticRow";
578 		rowMap[ZmContactPicker.SEARCH_EMAIL] = this._htmlElId + "_searchEmailRow";
579 		rowMap[ZmContactPicker.SEARCH_DEPT] = this._htmlElId + "_searchDepartmentRow";
580 	}
581 	else {
582 		fieldMap[ZmContactPicker.SEARCH_BASIC] = this._htmlElId + "_searchField";
583 		rowMap[ZmContactPicker.SEARCH_BASIC] = this._htmlElId + "_searchRow";
584 	}
585 };		
586 
587 // Listeners
588 
589 /**
590  * @private
591  */
592 ZmContactPicker.prototype._searchButtonListener =
593 function(ev) {
594 	this._resetResults();
595 	this.search();
596 };
597 
598 /**
599  * @private
600  */
601 ZmContactPicker.prototype._handleResponseSearch =
602 function(firstTime, result) {
603 	var resp = result.getResponse();
604 	var serverHasMore = resp.getAttribute("more");
605 	var serverPaginationSupported = resp.getAttribute("paginationSupported") !== false; //if it's not specified (such as the case of SearchResponse, i.e. not Gal) it IS supported.
606 	this._serverHasMoreAndPaginationSupported = serverHasMore && serverPaginationSupported;
607 	var offset = resp.getAttribute("offset");
608 	this._serverContactOffset = offset || 0;
609 	var info = resp.getAttribute("info");
610 	var expanded = info && info[0].wildcard[0].expanded == "0";
611 
612 	//the check for firstTime is so when the picker is popped up we probably don't want to overwhelm them with a warning message. So only show it if the user plays with the picker, using the drop-down or the search box.
613 	if (!firstTime && !serverPaginationSupported && (serverHasMore || expanded)) { //no idea what the expanded case is
614 		var d = appCtxt.getMsgDialog();
615 		d.setMessage(ZmMsg.errorSearchNotExpanded);
616 		d.popup();
617 		if (expanded) { return; }
618 	}
619 
620 	// this method will expand the list depending on the number of email
621 	// addresses per contact.
622 	var emailArray = ZmContactsHelper._processSearchResponse(resp, this._includeContactsWithNoEmail); //this._includeContactsWithNoEmail - true in the ZmGroupView case 
623 	var emailList = AjxVector.fromArray(emailArray);
624 
625 	if (serverPaginationSupported) {
626 		this._emailList.addList(emailArray); //this internally calls concat. we do not need "merge" here because we use the _serverContactOffset as a marker of where to search next, never searching a block we already did.
627 	}
628 	else {
629 		this._emailList = emailList;
630 	}
631     var list = this.getSubList();
632     if (this.toString() === "ZmContactPicker") {
633         list = this.loadSharedGroupContacts(list) || list;
634     }
635 	this._showResults(list);
636 
637 };
638 
639 ZmContactPicker.prototype._showResults =
640 function(aList) {
641     var list = aList || this.getSubList();
642 	// special case 1 - search forward another server block, to fill up a page. Could search several times.
643 	if (list.size() < ZmContactsApp.SEARCHFOR_MAX && this._serverHasMoreAndPaginationSupported) {
644 		this.search(null, null, null, null, null, this._serverContactOffset + ZmContactsApp.SEARCHFOR_MAX); //search another page
645 		return;
646 	}
647 
648 	if (this._searchIcon) { //does not exist in ZmGroupView case
649 		this._searchIcon.className = "";
650 	}
651 	this._searchButton.setEnabled(true);
652 
653 	// special case 2 - no results, and no more to search (that was covered in special case 1) - so display the "no results" text.
654 	if (list.size() == 0 && this._emailListOffset == 0) {
655 		this._setResultsInView(list); //empty the list
656 		this._nextButton.setEnabled(false);
657 		this._prevButton.setEnabled(false);
658 		this._setNoResultsHtml();
659 		return;
660 	}
661 
662 	// special case 3 - If the AB ends with a long list of contacts w/o addresses,
663 	// we may get an empty list.  If that's the case, roll back the offset
664 	// not 100% sure this case could still happen after all my changes but it was there in the code, so I keep it just in case.
665 	if (list.size() == 0) {
666 		this._emailListOffset -= ZmContactsApp.SEARCHFOR_MAX;
667 		this._emailListOffset  = Math.max(0, this._emailListOffset);
668 	}
669 
670 	var more = this._serverHasMoreAndPaginationSupported  //we can get more from the server
671 				|| (this._emailListOffset + ZmContactsApp.SEARCHFOR_MAX) < this._emailList.size(); //or we have more on the client we didn't yet show
672 	this._prevButton.setEnabled(this._emailListOffset > 0);
673 	this._nextButton.setEnabled(more);
674 
675 	this._resetSearchColHeaders(); // bug #2269 - enable/disable sort column per type of search
676 	this._setResultsInView(list);
677 };
678 
679 ZmContactPicker.prototype.loadSharedGroupContacts =
680     function(aList) {
681 
682     var listLen,
683         listArray,
684         contact,
685         item,
686         i,
687         j,
688         k,
689         sharedContactGroupArray,
690         len1,
691         jsonObj,
692         batchRequest,
693         request,
694         response;
695 
696         listArray = aList.getArray();
697         listLen = aList.size();
698         sharedContactGroupArray = [];
699 
700     for (i = 0 ; i < listLen; i++) {
701         item = listArray[i];
702         contact = item.__contact;
703         if (contact.isGroup() && contact.isShared()) {
704             if (this.sharedContactGroups.indexOf(item.value) !== -1) {
705                 return;
706             }
707             this.sharedContactGroups.push(item.value);
708             sharedContactGroupArray.push(item.value);
709         }
710     }
711 
712     len1 = sharedContactGroupArray.length;
713     jsonObj = {BatchRequest:{GetContactsRequest:[],_jsns:"urn:zimbra", onerror:'continue'}};
714     batchRequest = jsonObj.BatchRequest;
715     request = batchRequest.GetContactsRequest;
716 
717     for (j = 0,k =0; j < len1; j++) {
718         request.push({ cn: {id: sharedContactGroupArray[j]}, _jsns: 'urn:zimbraMail', derefGroupMember: '1', requestId: k++ });
719     }
720         var respCallback = new AjxCallback(this, this.handleSharedContactResponse,[aList]);
721        response =  appCtxt.getAppController().sendRequest({
722             jsonObj:jsonObj,
723             asyncMode:true,
724             callback:respCallback
725         });
726    };
727 
728 ZmContactPicker.prototype.handleSharedContactResponse =
729     function(aList,response) {
730 
731      var contactResponse,
732          contactResponseLength,
733          listArray,
734          listArrayLength,
735          sharedGroupMembers,
736          i,
737          j,
738          k,
739          resp,
740          contact,
741          member,
742          isGal,
743          memberContact,
744          loadMember,
745          listArrElement,
746          sharedGroupMembers;
747 
748         if (response && response.getResponse() && response.getResponse().BatchResponse) {
749             contactResponse = response.getResponse().BatchResponse.GetContactsResponse;
750         }
751         if (!contactResponse) {
752             return;
753         }
754         contactResponseLength = contactResponse.length;
755         listArray = aList.getArray();
756         listArrayLength = aList.size();
757 
758         for (k= 0; k < listArrayLength; k++) {
759             sharedGroupMembers = [];
760             for (j = 0; j < contactResponseLength; j++) {
761                 resp = contactResponse[j];
762                 contact = resp.cn[0];
763 
764                 if (contact.m) {
765                     for (i = 0; i < contact.m.length; i++) {
766                         member = contact.m[i];
767                         isGal = false;
768                         if (member.type == ZmContact.GROUP_GAL_REF) {
769                             isGal = true;
770                         }
771                         if (member.cn && member.cn.length > 0) {
772                             memberContact = member.cn[0];
773                             memberContact.ref = memberContact.ref || (isGal && member.value);
774                             loadMember = ZmContact.createFromDom(memberContact, {list: this.list, isGal: isGal});
775                             loadMember.isDL = isGal && loadMember.attr[ZmContact.F_type] === "group";
776                             appCtxt.cacheSet(member.value, loadMember);
777                             listArrElement = listArray[k];
778                             if (listArrElement.value === contact.id) {
779                                 sharedGroupMembers.push( '"'+loadMember.getFullName()+'"' +' <' + loadMember.getEmails() +'>;' ); // Updating the original list with shared members of shared contact group that comes in 'contactResponse'.
780                                 aList._array[k].address = sharedGroupMembers.join("");
781                             }
782                         }
783                     }
784                     ZmContact.prototype._loadFromDom(contact);
785                 }
786             }
787         }
788         this._showResults(aList); // As async = true, when the response has come, again we render/update the  list with  contactResponse shared contact members,
789     };
790 
791 
792 /**
793  * extracted this so it can be used in ZmGroupView where this is different.
794  * @param list
795  */
796 ZmContactPicker.prototype._setResultsInView =
797 function(list) {
798 	this._chooser.setItems(list);
799 };
800 
801 /**
802  * extracted this so it can be used in ZmGroupView where this is different.
803  * @param list
804  */
805 ZmContactPicker.prototype._setNoResultsHtml =
806 function(list) {
807 	this._chooser.sourceListView._setNoResultsHtml();
808 };
809 
810 
811 ZmContactPicker.prototype._updateSearchRows =
812 function(searchFor) {
813 	var fieldIds = (searchFor == ZmContactsApp.SEARCHFOR_GAL) ? ZmContactPicker.SHOW_ON_GAL : ZmContactPicker.SHOW_ON_NONGAL;
814 	for (var fieldId in this._searchRow) {
815 		Dwt.setVisible(this._searchRow[fieldId], AjxUtil.indexOf(fieldIds, fieldId)!=-1);
816 	}
817 	for (var fieldId in this._searchField) {
818 		var field = this._searchField[fieldId];
819 		if (this._tabGroup.contains(field))
820 			this._tabGroup.removeMember(field);
821 	}
822 	for (var i=0; i<fieldIds.length; i++) {
823 		this._tabGroup.addMember(this._searchField[fieldIds[i]]);
824 	}
825 
826 	this._resizeChooser();
827 };
828 
829 /**
830  * @private
831  */
832 ZmContactPicker.prototype._handleErrorSearch =
833 function() {
834 	this._searchButton.setEnabled(true);
835 	return false;
836 };
837 
838 /**
839  * @private
840  */
841 ZmContactPicker.prototype._pageListener =
842 function(ev) {
843 	if (ev.item == this._prevButton) {
844 		this._emailListOffset -= ZmContactsApp.SEARCHFOR_MAX;
845 		this._emailListOffset  = Math.max(0, this._emailListOffset);
846 	}
847 	else {
848 		this._emailListOffset += ZmContactsApp.SEARCHFOR_MAX;
849 	}
850 	this._showResults();
851 };
852 
853 /**
854  * Gets a sub-list of contacts.
855  * 
856  * @return	{AjxVector}		a vector of {ZmContact} objects
857  */
858 ZmContactPicker.prototype.getSubList =
859 function() {
860 	var size = this._emailList.size();
861 
862 	var end = this._emailListOffset + ZmContactsApp.SEARCHFOR_MAX;
863 
864 	if (end > size) {
865 		end = size;
866 	}
867 
868 	var a = (this._emailListOffset < end) ? this._emailList.getArray().slice(this._emailListOffset, end) : [];
869 	return AjxVector.fromArray(a);
870 };
871 
872 /**
873  * @private
874  */
875 ZmContactPicker.prototype._searchTypeListener =
876 function(ev) {
877 	var oldValue = ev._args.oldValue;
878 	var newValue = ev._args.newValue;
879 
880 	if (oldValue != newValue) {
881 		this._updateSearchRows(newValue);
882 		this._searchButtonListener();
883 	}
884 };
885 
886 /**
887  * @private
888  */
889 ZmContactPicker.prototype._resetSearchColHeaders =
890 function () {
891     var slv = this._chooser.sourceListView;
892     var tlv = this._chooser.targetListView;
893     slv.headerColCreated = false;
894     tlv.headerColCreated = false;
895     var isGal = this._searchInSelect && (this._searchInSelect.getValue() == ZmContactsApp.SEARCHFOR_GAL);
896 
897     // find the participant column
898     var part = 0;
899     for (var i = 0; i < slv._headerList.length; i++) {
900         var field = slv._headerList[i]._field;
901         if (field == ZmItem.F_NAME) {
902             part = i;
903         }
904         if (field == ZmItem.F_DEPARTMENT) {
905             slv._headerList[i]._visible = isGal && this._detailedSearch;
906         }
907     }
908 
909     var sortable = isGal ? null : ZmItem.F_NAME;
910     slv._headerList[part]._sortable = sortable;
911     slv.createHeaderHtml(sortable);
912 
913     for (i = 0; i < tlv._headerList.length; i++) {
914         if (tlv._headerList[i]._field == ZmItem.F_DEPARTMENT) {
915             tlv._headerList[i]._visible = isGal && this._detailedSearch;
916         }
917     }
918     tlv.createHeaderHtml();
919 };
920 
921 /**
922  * Done choosing addresses, add them to the compose form.
923  * 
924  * @private
925  */
926 ZmContactPicker.prototype._okButtonListener =
927 function(ev) {
928 	var data = this._chooser.getItems();
929 	DwtDialog.prototype._buttonListener.call(this, ev, [data]);
930 };
931 
932 /**
933  * Call custom popdown method.
934  * 
935  * @private
936  */
937 ZmContactPicker.prototype._cancelButtonListener =
938 function(ev) {
939 	DwtDialog.prototype._buttonListener.call(this, ev);
940 	this.popdown();
941 };
942 
943 /**
944  * @private
945  */
946 ZmContactPicker._keyPressHdlr =
947 function(ev) {
948 	var stb = DwtControl.getTargetControl(ev);
949 	var charCode = DwtKeyEvent.getCharCode(ev);
950 	if (stb._keyPressCallback && (charCode == 13 || charCode == 3)) {
951 		stb._keyPressCallback.run();
952 		return false;
953 	}
954 	return true;
955 };
956 
957 
958 /***********************************************************************************/
959 
960 /**
961  * Creates a contact chooser.
962  * @class
963  * This class creates a specialized chooser for the contact picker.
964  *
965  * @param {DwtComposite}	parent			the contact picker
966  * @param {Array}		buttonInfo		transfer button IDs and labels
967  * 
968  * @extends		DwtChooser
969  * 
970  * @private
971  */
972 ZmContactChooser = function(params) {
973 	DwtChooser.call(this, params);
974 };
975 
976 ZmContactChooser.prototype = new DwtChooser;
977 ZmContactChooser.prototype.constructor = ZmContactChooser;
978 
979 /**
980  * @private
981  */
982 ZmContactChooser.prototype._createSourceListView =
983 function() {
984 	return new ZmContactChooserSourceListView(this);
985 };
986 
987 /**
988  * @private
989  */
990 ZmContactChooser.prototype._createTargetListView =
991 function() {
992 	return new ZmContactChooserTargetListView(this, (this._buttonInfo.length > 1));
993 };
994 
995 /**
996  * The item is a AjxEmailAddress. Its address is used for comparison.
997  *
998  * @param {AjxEmailAddress}	item	an email address
999  * @param {AjxVector}	list	list to check in
1000  * 
1001  * @private
1002  */
1003 ZmContactChooser.prototype._isDuplicate =
1004 function(item, list) {
1005 	return list.containsLike(item, item.getAddress);
1006 };
1007 
1008 /***********************************************************************************/
1009 
1010 /**
1011  * Creates a source list view.
1012  * @class
1013  * This class creates a specialized source list view for the contact chooser.
1014  * 
1015  * @param {DwtComposite}	parent			the contact picker
1016  * 
1017  * @extends		DwtChooserListView
1018  * 
1019  * @private
1020  */
1021 ZmContactChooserSourceListView = function(parent) {
1022 	DwtChooserListView.call(this, {parent:parent, type:DwtChooserListView.SOURCE});
1023 	this.setScrollStyle(Dwt.CLIP);
1024 };
1025 
1026 ZmContactChooserSourceListView.prototype = new DwtChooserListView;
1027 ZmContactChooserSourceListView.prototype.constructor = ZmContactChooserSourceListView;
1028 
1029 /**
1030  * Returns a string representation of the object.
1031  * 
1032  * @return		{String}		a string representation of the object
1033  * @private
1034  */
1035 ZmContactChooserSourceListView.prototype.toString =
1036 function() {
1037 	return "ZmContactChooserSourceListView";
1038 };
1039 
1040 ZmContactChooserSourceListView.prototype.getToolTipContent =
1041 function(ev) {
1042 	
1043 	if (this._hoveredItem) {
1044 		var ttParams = {
1045 			address:		this._hoveredItem.address,
1046 			contact:		this._hoveredItem.__contact,
1047 			ev:				ev,
1048 			noRightClick:	true
1049 		};
1050 		var ttCallback = new AjxCallback(this,
1051 			function(callback) {
1052 				appCtxt.getToolTipMgr().getToolTip(ZmToolTipMgr.PERSON, ttParams, callback);
1053 			});
1054 		return {callback:ttCallback};
1055 	}
1056 	else {
1057 		return "";
1058 	}
1059 };
1060 
1061 /**
1062  * @private
1063  */
1064 ZmContactChooserSourceListView.prototype._getHeaderList =
1065 function() {
1066 	var headerList = [];
1067 	headerList.push(new DwtListHeaderItem({field:ZmItem.F_TYPE, icon:"Folder", width:ZmMsg.COLUMN_WIDTH_FOLDER_CN}));
1068 	headerList.push(new DwtListHeaderItem({field:ZmItem.F_NAME, text:ZmMsg._name, width:ZmMsg.COLUMN_WIDTH_NAME_CN, resizeable: true}));
1069 	headerList.push(new DwtListHeaderItem({field:ZmItem.F_DEPARTMENT, text:ZmMsg.department, width:ZmMsg.COLUMN_WIDTH_DEPARTMENT_CN, resizeable: true}));
1070 	headerList.push(new DwtListHeaderItem({field:ZmItem.F_EMAIL, text:ZmMsg.email, resizeable: true}));
1071 
1072 
1073 	return headerList;
1074 };
1075 
1076  
1077 // Override of DwtListView.prototype._resetColWidth to set width; without overrriding causes vertical scrollbars to disapper
1078 // on header resize
1079 ZmContactChooserSourceListView.prototype._resetColWidth =
1080 function() {
1081 
1082 	if (!this.headerColCreated) { return; }
1083 
1084 	var lastColIdx = this._getLastColumnIndex();
1085     if (lastColIdx) {
1086         var lastCol = this._headerList[lastColIdx];
1087         var lastCell = document.getElementById(lastCol._id);
1088 		if (lastCell) {
1089 			var div = lastCell.firstChild;
1090 			lastCell.style.width = div.style.width = (lastCol._width || ""); 
1091 		}
1092     }
1093 };
1094 
1095 /**
1096  * override for scrollbars in IE
1097  * @param headerIdx
1098  */
1099 ZmContactChooserSourceListView.prototype._calcRelativeWidth =
1100 function(headerIdx) {
1101 	var column = this._headerList[headerIdx];
1102 	if (!column._width || (column._width && column._width == "auto")) {
1103 		var cell = document.getElementById(column._id);
1104 		// UGH: clientWidth is 5px more than HTML-width (20px for IE to deal with scrollbars)
1105 		return (cell) ? (cell.clientWidth - (AjxEnv.isIE ? Dwt.SCROLLBAR_WIDTH : 5)) : null;
1106 	}
1107 	return column._width;
1108 };
1109 
1110 /**
1111  * @private
1112  */
1113 ZmContactChooserSourceListView.prototype._mouseOverAction =
1114 function(ev, div) {
1115 	DwtChooserListView.prototype._mouseOverAction.call(this, ev, div);
1116 	var id = ev.target.id || div.id;
1117 	var item = this.getItemFromElement(div);
1118 	this._hoveredItem = (id && item) ? item : null;
1119 	return true;
1120 };
1121 
1122 /**
1123  * @private
1124  */
1125 ZmContactChooserSourceListView.prototype._getCellContents =
1126 function(html, idx, item, field, colIdx, params) {
1127 	if (field == ZmItem.F_EMAIL && AjxEnv.isIE) {
1128 		var maxWidth = AjxStringUtil.getWidth(item.address);
1129 		html[idx++] = "<div style='float; left; overflow: visible; width: " + maxWidth + ";'>";
1130 		idx = ZmContactsHelper._getEmailField(html, idx, item, field, colIdx, params);
1131 		html[idx++] = "</div>";		
1132 	}
1133 	else {
1134 		idx = ZmContactsHelper._getEmailField(html, idx, item, field, colIdx, params);
1135 	}
1136 	return idx;
1137 };
1138 
1139 /**
1140  * Returns a string of any extra attributes to be used for the TD.
1141  *
1142  * @param item		[object]	item to render
1143  * @param field		[constant]	column identifier
1144  * @param params	[hash]*		hash of optional params
1145  * 
1146  * @private
1147  */
1148 ZmContactChooserSourceListView.prototype._getCellAttrText =
1149 function(item, field, params) {
1150 	if (field == ZmItem.F_EMAIL) {
1151 		return "style='position: relative; overflow: visible;'";
1152 	}
1153 };
1154 
1155 /***********************************************************************************/
1156 
1157 /**
1158  * Creates the target list view.
1159  * @class
1160  * This class creates a specialized target list view for the contact chooser.
1161  * 
1162  * @param {DwtComposite}	parent			the contact picker
1163  * @param {constant}		showType		the show type
1164  * @extends		DwtChooserListView
1165  * 
1166  * @private
1167  */
1168 ZmContactChooserTargetListView = function(parent, showType) {
1169 	this._showType = showType; // call before base class since base calls getHeaderList
1170 
1171 	DwtChooserListView.call(this, {parent:parent, type:DwtChooserListView.TARGET});
1172 
1173 	this.setScrollStyle(Dwt.CLIP);
1174 };
1175 
1176 ZmContactChooserTargetListView.prototype = new DwtChooserListView;
1177 ZmContactChooserTargetListView.prototype.constructor = ZmContactChooserTargetListView;
1178 
1179 /**
1180  * Returns a string representation of the object.
1181  * 
1182  * @return		{String}		a string representation of the object
1183  */
1184 ZmContactChooserTargetListView.prototype.toString =
1185 function() {
1186 	return "ZmContactChooserTargetListView";
1187 };
1188 
1189 /**
1190  * @private
1191  */
1192 ZmContactChooserTargetListView.prototype._getHeaderList =
1193 function() {
1194 	var headerList = [];
1195 	var view = this._view;
1196 	if (this._showType) {
1197 		headerList.push(new DwtListHeaderItem({field:ZmItem.F_TYPE, icon:"ContactsPicker", width:ZmMsg.COLUMN_WIDTH_TYPE_CN}));
1198 	}
1199 	headerList.push(new DwtListHeaderItem({field:ZmItem.F_NAME, text:ZmMsg._name, width:ZmMsg.COLUMN_WIDTH_NAME_CN, resizeable: true}));
1200     headerList.push(new DwtListHeaderItem({field:ZmItem.F_DEPARTMENT, text:ZmMsg.department, width:ZmMsg.COLUMN_WIDTH_DEPARTMENT_CN, resizeable: true}));
1201     headerList.push(new DwtListHeaderItem({field:ZmItem.F_EMAIL, text:ZmMsg.email, resizeable: true}));
1202 
1203 	return headerList;
1204 };
1205 
1206 ZmContactChooserTargetListView.prototype._mouseOverAction =
1207 ZmContactChooserSourceListView.prototype._mouseOverAction;
1208 
1209 /**
1210  * The items are AjxEmailAddress objects.
1211  * 
1212  * @private
1213  */
1214 ZmContactChooserTargetListView.prototype._getCellContents =
1215 function(html, idx, item, field, colIdx, params) {
1216 	if (field == ZmItem.F_TYPE) {
1217 		item.setType(item._buttonId);
1218 		html[idx++] = ZmMsg[item.getTypeAsString()];
1219 		html[idx++] = ":";
1220 	}
1221 	else if (field == ZmItem.F_EMAIL && AjxEnv.isIE) {
1222 		var maxWidth = AjxStringUtil.getWidth(item.address) + 10;
1223 		html[idx++] = "<div style='float; left;  width: " + maxWidth + ";'>";
1224 		idx = ZmContactsHelper._getEmailField(html, idx, item, field, colIdx, params);
1225 		html[idx++] = "</div>";
1226 	}
1227 	else {
1228 		idx = ZmContactsHelper._getEmailField(html, idx, item, field, colIdx);
1229 	}
1230 	return idx;
1231 };
1232 
1233 
1234 // Override of DwtListView.prototype._resetColWidth to set width; without overrriding causes vertical scrollbars to disapper
1235 // on header resize
1236 ZmContactChooserTargetListView.prototype._resetColWidth =
1237 function() {
1238 
1239 	if (!this.headerColCreated) { return; }
1240 
1241 	var lastColIdx = this._getLastColumnIndex();
1242 
1243 	
1244     if (lastColIdx) {
1245         var lastCol = this._headerList[lastColIdx];
1246         var lastCell = document.getElementById(lastCol._id);
1247 		if (lastCell) {
1248 			var div = lastCell.firstChild;
1249 			lastCell.style.width = div.style.width = (lastCol._width || "");
1250 		}
1251     }
1252 };
1253 
1254 /**
1255  * override for scrollbars in IE
1256  * @param headerIdx
1257  */
1258 ZmContactChooserTargetListView.prototype._calcRelativeWidth =
1259 function(headerIdx) {
1260 	var column = this._headerList[headerIdx];
1261 	if (!column._width || (column._width && column._width == "auto")) {
1262 		var cell = document.getElementById(column._id);
1263 		// UGH: clientWidth is 5px more than HTML-width (20px for IE to deal with scrollbars)
1264 		return (cell) ? (cell.clientWidth - (AjxEnv.isIE ? Dwt.SCROLLBAR_WIDTH : 5)) : null;
1265 	}
1266 	return column._width;
1267 };
1268 
1269 /**
1270  * Returns a string of any extra attributes to be used for the TD.
1271  *
1272  * @param item		[object]	item to render
1273  * @param field		[constant]	column identifier
1274  * @param params	[hash]*		hash of optional params
1275  * 
1276  * @private
1277  */
1278 ZmContactChooserTargetListView.prototype._getCellAttrText =
1279 function(item, field, params) {
1280 	if (field == ZmItem.F_EMAIL) {
1281 		return "style='position: relative; overflow: visible;'";
1282 	}
1283 };
1284