1 /*
  2  * ***** BEGIN LICENSE BLOCK *****
  3  * Zimbra Collaboration Suite Web Client
  4  * Copyright (C) 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) 2010, 2011, 2012, 2013, 2014, 2015, 2016 Synacor, Inc. All Rights Reserved.
 21  * ***** END LICENSE BLOCK *****
 22  */
 23 
 24 /**
 25  * Creates a new dialog that can be used to choose attendees, locations, or equipment.
 26  * @constructor
 27  * @class
 28  * This class allows the user to search for attendees, locations, or
 29  * equipment. It presents a chooser which allows the user to select items from
 30  * the search results.
 31  *
 32  * @author Sathishkumar
 33  *
 34  * @param {DwtComposite}	editView		the edit view that pops up this dialog
 35  * @param {Hash}	attendees		the attendees/locations/equipment
 36  * @param {ZmApptComposeController}	controller	the appt compose controller
 37  * @param {constant}	type			the chooser page type
 38  * 
 39  * @extends		DwtTabViewPage
 40  */
 41 ZmAttendeePicker = function(editView, attendees, controller, type, dateInfo) {
 42 
 43     DwtDialog.call(this, {
 44         parent: appCtxt.getShell(),
 45         title:  ZmAttendeePicker.TOP_LEGEND[type],
 46         id:     [ 'ZmAttendeePicker', type ].join('_')
 47     });
 48 
 49 	this._attendees = attendees;
 50 	this._controller = controller;
 51 	this._editView = editView;
 52 	this.type = type;
 53 	this._dateInfo = dateInfo;
 54 
 55 	this._offset = 0;
 56 	this._rendered = false;
 57 	this._isClean = true;
 58 	this._searchFields = {};
 59 	this._searchFieldIds = {};
 60 	this._keyPressCallback = new AjxCallback(this, this._searchButtonListener);
 61 	this._kbMgr = appCtxt.getKeyboardMgr();    
 62     this._list = new AjxVector();
 63     this.showSelect = false;
 64 };
 65 
 66 ZmAttendeePicker.COL_LABEL = {};
 67 ZmAttendeePicker.COL_LABEL[ZmItem.F_FOLDER]		= "folder";
 68 ZmAttendeePicker.COL_LABEL[ZmItem.F_NAME]		= "_name";
 69 ZmAttendeePicker.COL_LABEL[ZmItem.F_EMAIL]		= "email";
 70 ZmAttendeePicker.COL_LABEL[ZmItem.F_WORK_PHONE]	= "AB_FIELD_workPhone";
 71 ZmAttendeePicker.COL_LABEL[ZmItem.F_HOME_PHONE]	= "AB_FIELD_homePhone";
 72 ZmAttendeePicker.COL_LABEL[ZmItem.F_LOCATION]	= "location";
 73 ZmAttendeePicker.COL_LABEL[ZmItem.F_CONTACT]	= "contact";
 74 ZmAttendeePicker.COL_LABEL[ZmItem.F_CAPACITY]	= "capacity";
 75 ZmAttendeePicker.COL_LABEL["FBSTATUS"]          = "status";
 76 
 77 ZmAttendeePicker.COL_IMAGE = {};
 78 ZmAttendeePicker.COL_IMAGE[ZmItem.F_NOTES]		= "Page";
 79 
 80 ZmAttendeePicker.COL_WIDTH = {};
 81 ZmAttendeePicker.COL_WIDTH[ZmItem.F_FOLDER]		= ZmMsg.COLUMN_WIDTH_FOLDER_NA;
 82 ZmAttendeePicker.COL_WIDTH[ZmItem.F_NAME]		= ZmMsg.COLUMN_WIDTH_NAME_NA;
 83 ZmAttendeePicker.COL_WIDTH[ZmItem.F_EMAIL]		= null;
 84 ZmAttendeePicker.COL_WIDTH[ZmItem.F_WORK_PHONE]	= ZmMsg.COLUMN_WIDTH_WORK_PHONE_NA;
 85 ZmAttendeePicker.COL_WIDTH[ZmItem.F_HOME_PHONE]	= ZmMsg.COLUMN_WIDTH_HOME_PHONE_NA;
 86 ZmAttendeePicker.COL_WIDTH[ZmItem.F_LOCATION]	= null;
 87 ZmAttendeePicker.COL_WIDTH[ZmItem.F_CONTACT]	= ZmMsg.COLUMN_WIDTH_CONTACT_NA;
 88 ZmAttendeePicker.COL_WIDTH[ZmItem.F_CAPACITY]	= ZmMsg.COLUMN_WIDTH_CAPACITY_NA;
 89 ZmAttendeePicker.COL_WIDTH[ZmItem.F_NOTES]		= ZmMsg.COLUMN_WIDTH_NOTES_NA;
 90 ZmAttendeePicker.COL_WIDTH["FBSTATUS"]			= ZmMsg.COLUMN_WIDTH_FBSTATUS_NA;
 91 
 92 ZmAttendeePicker.COLS = {};
 93 ZmAttendeePicker.COLS[ZmCalBaseItem.PERSON]		= [ZmItem.F_FOLDER, ZmItem.F_NAME, ZmItem.F_EMAIL, ZmItem.F_WORK_PHONE, ZmItem.F_HOME_PHONE, "FBSTATUS"];
 94 ZmAttendeePicker.COLS[ZmCalBaseItem.LOCATION]	= [ZmItem.F_NAME, ZmItem.F_LOCATION, ZmItem.F_CONTACT, ZmItem.F_CAPACITY, "FBSTATUS", ZmItem.F_NOTES];
 95 ZmAttendeePicker.COLS[ZmCalBaseItem.EQUIPMENT]	= [ZmItem.F_NAME, ZmItem.F_LOCATION, ZmItem.F_CONTACT, "FBSTATUS", ZmItem.F_NOTES];
 96 
 97 // search fields
 98 (function () {
 99 	var i = 1;
100 	ZmAttendeePicker.SF_ATT_NAME	= i++;
101 	ZmAttendeePicker.SF_NAME		= i++;
102 	ZmAttendeePicker.SF_SOURCE		= i++;
103 	ZmAttendeePicker.SF_CAPACITY	= i++;
104 	ZmAttendeePicker.SF_DESCRIPTION	= i++;
105 	ZmAttendeePicker.SF_SITE		= i++;
106 	ZmAttendeePicker.SF_BUILDING	= i++;
107 	ZmAttendeePicker.SF_FLOOR		= i++;
108 })();
109 
110 // search field labels
111 ZmAttendeePicker.SF_LABEL = {};
112 ZmAttendeePicker.SF_LABEL[ZmAttendeePicker.SF_ATT_NAME]	= "find";
113 ZmAttendeePicker.SF_LABEL[ZmAttendeePicker.SF_NAME]		= "_name";
114 ZmAttendeePicker.SF_LABEL[ZmAttendeePicker.SF_SOURCE]	= "source";
115 ZmAttendeePicker.SF_LABEL[ZmAttendeePicker.SF_CAPACITY]	= "minimumCapacity";
116 ZmAttendeePicker.SF_LABEL[ZmAttendeePicker.SF_DESCRIPTION]	= "description";
117 ZmAttendeePicker.SF_LABEL[ZmAttendeePicker.SF_CONTACT]	= "contact";
118 ZmAttendeePicker.SF_LABEL[ZmAttendeePicker.SF_SITE]		= "site";
119 ZmAttendeePicker.SF_LABEL[ZmAttendeePicker.SF_BUILDING]	= "building";
120 ZmAttendeePicker.SF_LABEL[ZmAttendeePicker.SF_FLOOR]	= "floor";
121 
122 // corresponding attributes for search command
123 ZmAttendeePicker.SF_ATTR = {};
124 ZmAttendeePicker.SF_ATTR[ZmAttendeePicker.SF_NAME]		  = "fullName";
125 ZmAttendeePicker.SF_ATTR[ZmAttendeePicker.SF_CAPACITY]	  = "zimbraCalResCapacity";
126 ZmAttendeePicker.SF_ATTR[ZmAttendeePicker.SF_DESCRIPTION] = "description";
127 ZmAttendeePicker.SF_ATTR[ZmAttendeePicker.SF_CONTACT]	  = "zimbraCalResContactName";
128 ZmAttendeePicker.SF_ATTR[ZmAttendeePicker.SF_SITE]		  = "zimbraCalResSite";
129 ZmAttendeePicker.SF_ATTR[ZmAttendeePicker.SF_BUILDING]	  = "zimbraCalResBuilding";
130 ZmAttendeePicker.SF_ATTR[ZmAttendeePicker.SF_FLOOR]		  = "zimbraCalResFloor";
131 
132 // search field compares ops - listed here if not substring ("has")
133 ZmAttendeePicker.SF_OP = {};
134 ZmAttendeePicker.SF_OP[ZmAttendeePicker.SF_CAPACITY]	= "ge";
135 ZmAttendeePicker.SF_OP[ZmAttendeePicker.SF_FLOOR]		= "eq";
136 
137 ZmAttendeePicker.ATTRS = {};
138 ZmAttendeePicker.ATTRS[ZmCalBaseItem.LOCATION] =
139 	["fullName", "email", "zimbraCalResLocationDisplayName",
140 	 "zimbraCalResCapacity", "zimbraCalResContactEmail", "description", "zimbraCalResType"];
141 ZmAttendeePicker.ATTRS[ZmCalBaseItem.EQUIPMENT] =
142 	["fullName", "email", "zimbraCalResLocationDisplayName",
143 	 "zimbraCalResContactEmail", "description", "zimbraCalResType"];
144 
145 ZmAttendeePicker.SEARCH_FIELDS = {};
146 ZmAttendeePicker.SEARCH_FIELDS[ZmCalBaseItem.PERSON] =
147 	[ZmAttendeePicker.SF_ATT_NAME, ZmAttendeePicker.SF_SOURCE];
148 ZmAttendeePicker.SEARCH_FIELDS[ZmCalBaseItem.LOCATION] =
149 	[ZmAttendeePicker.SF_NAME, ZmAttendeePicker.SF_SITE,
150 	 ZmAttendeePicker.SF_CAPACITY, ZmAttendeePicker.SF_BUILDING,
151 	 ZmAttendeePicker.SF_DESCRIPTION, ZmAttendeePicker.SF_FLOOR];
152 ZmAttendeePicker.SEARCH_FIELDS[ZmCalBaseItem.EQUIPMENT] =
153 	[ZmAttendeePicker.SF_NAME, ZmAttendeePicker.SF_SITE,
154 	 ZmAttendeePicker.SF_DESCRIPTION, ZmAttendeePicker.SF_BUILDING,
155 	 ZmAttendeePicker.SF_CONTACT, ZmAttendeePicker.SF_FLOOR];
156 
157 ZmAttendeePicker.SETTINGS_SEARCH_FIELDS = {};
158 
159 ZmAttendeePicker.SORT_BY = {};
160 ZmAttendeePicker.SORT_BY[ZmCalBaseItem.PERSON]				= ZmSearch.NAME_ASC;
161 ZmAttendeePicker.SORT_BY[ZmCalBaseItem.LOCATION]			= ZmSearch.NAME_ASC;
162 ZmAttendeePicker.SORT_BY[ZmCalBaseItem.EQUIPMENT]			= ZmSearch.NAME_ASC;
163 
164 ZmAttendeePicker.TOP_LEGEND = {};
165 ZmAttendeePicker.TOP_LEGEND[ZmCalBaseItem.PERSON]			= ZmMsg.findAttendees;
166 ZmAttendeePicker.TOP_LEGEND[ZmCalBaseItem.LOCATION]			= ZmMsg.findLocations;
167 ZmAttendeePicker.TOP_LEGEND[ZmCalBaseItem.EQUIPMENT]		= ZmMsg.findEquipment;
168 
169 ZmAttendeePicker.SUGGEST_LEGEND = {};
170 ZmAttendeePicker.SUGGEST_LEGEND[ZmCalBaseItem.PERSON]			= ZmMsg.suggestedAttendees;
171 ZmAttendeePicker.SUGGEST_LEGEND[ZmCalBaseItem.LOCATION]			= ZmMsg.suggestedLocations;
172 ZmAttendeePicker.SUGGEST_LEGEND[ZmCalBaseItem.EQUIPMENT]		= ZmMsg.suggestedResources;
173 
174 ZmAttendeePicker.BOTTOM_LEGEND = {};
175 ZmAttendeePicker.BOTTOM_LEGEND[ZmCalBaseItem.PERSON]		= ZmMsg.apptAttendees;
176 ZmAttendeePicker.BOTTOM_LEGEND[ZmCalBaseItem.LOCATION]		= ZmMsg.apptLocations;
177 ZmAttendeePicker.BOTTOM_LEGEND[ZmCalBaseItem.EQUIPMENT]		= ZmMsg.apptEquipment;
178 
179 // images for the bottom fieldset legend
180 ZmAttendeePicker.ICON = {};
181 ZmAttendeePicker.ICON[ZmCalBaseItem.PERSON]					= appContextPath+"/img/hiRes/calendar/ApptMeeting.gif";
182 ZmAttendeePicker.ICON[ZmCalBaseItem.LOCATION]				= appContextPath+"/img/hiRes/calendar/Location.gif";
183 ZmAttendeePicker.ICON[ZmCalBaseItem.EQUIPMENT]				= appContextPath+"/img/hiRes/calendar/Resource.gif";
184 
185 ZmAttendeePicker.CHOOSER_HEIGHT = 300;
186 
187 
188 ZmAttendeePicker.prototype = new DwtDialog;
189 ZmAttendeePicker.prototype.constructor = ZmAttendeePicker;
190 
191 ZmAttendeePicker.prototype.toString =
192 function() {
193 	return "ZmAttendeePicker";
194 };
195 
196 /**
197  * Done choosing addresses, add them to the appt compose view.
198  *
199  * @private
200  */
201 ZmAttendeePicker.prototype._okButtonListener =
202 function(ev) {
203 	var data = this._chooser.getItems();
204 	DwtDialog.prototype._buttonListener.call(this, ev, [data]);
205 };
206 
207 /**
208  * Call custom popdown method.
209  *
210  * @private
211  */
212 ZmAttendeePicker.prototype._cancelButtonListener =
213 function(ev) {
214 	DwtDialog.prototype._buttonListener.call(this, ev);
215 	this.popdown();
216 };
217 
218 ZmAttendeePicker.prototype.showSuggestedItems =
219 function(items) {
220     this.popup(true);
221     this._fillFreeBusy(items, AjxCallback.simpleClosure(function(items) {
222 		this._chooser.setItems(items);
223 	}, this));
224 };
225 
226 ZmAttendeePicker.prototype.setLabel =
227 function(title) {
228     this.setTitle(title);
229     var sourceTitle = document.getElementById(this._searchTableId + '_legend');
230     if(sourceTitle) sourceTitle.innerHTML = title;
231 };
232 
233 ZmAttendeePicker.prototype.popup =
234 function(showSuggestions) {
235 
236     this.setLabel(showSuggestions ? ZmAttendeePicker.SUGGEST_LEGEND[this.type] : ZmAttendeePicker.TOP_LEGEND[this.type]);    
237 
238     DwtDialog.prototype.popup.call(this);
239     
240 	// Update FB status if the time is changed
241     this._setAttendees();
242 
243 	if (this.type == ZmCalBaseItem.EQUIPMENT && this._dateInfo.isTimeModified) {
244 		this.refreshResourcesFBStatus();
245 		this._dateInfo.isTimeModified = false;
246 	}
247 
248 
249 };
250 
251 ZmAttendeePicker.prototype.refreshResourcesFBStatus =
252 function() {
253 	var items = this._chooser.getItems();
254 	this._fillFreeBusy(items, AjxCallback.simpleClosure(function(items) {
255 		this._chooser.setItems(items);
256 	}, this));
257 };
258 
259 ZmAttendeePicker.prototype.initialize =
260 function(appt, mode, isDirty, apptComposeMode) {
261 	this._appt = appt;
262 	this._isDirty = isDirty;
263 	this._isForward = (apptComposeMode == ZmApptComposeView.FORWARD);
264 	this._isProposeTime = (apptComposeMode == ZmApptComposeView.PROPOSE_TIME);
265 	this._list.removeAll();
266 
267 	if (this._rendered) {
268 		this._chooser.reset();
269 	} else {
270         this._loadSettings();
271 		this._createPageHtml();
272 		this._addDwtObjects();
273 		this._rendered = true;
274 	}
275     if (appCtxt.isOffline && this.type == ZmCalBaseItem.PERSON) {
276         this.setSelectVisibility();
277     }
278 	this._resetSelectDiv();
279 
280     // init listeners
281     this.setButtonListener(DwtDialog.OK_BUTTON, new AjxListener(this, this._okButtonListener));
282     this.setButtonListener(DwtDialog.CANCEL_BUTTON, new AjxListener(this, this._cancelButtonListener));    
283 };
284 
285 ZmAttendeePicker.prototype.resize =
286 function() {
287 	if (!this._rendered) { return; }
288     this._chooser.resize(Dwt.DEFAULT, ZmAttendeePicker.CHOOSER_HEIGHT);
289 };
290 
291 ZmAttendeePicker.prototype.cleanup =
292 function() {
293 	this._chooser.reset();
294 
295 	if (this._prevButton && this._nextButton) {
296 		this._prevButton.setEnabled(false);
297 		this._nextButton.setEnabled(false);
298 	}
299 	this._isClean = true;
300 	this._offset = 0;
301 
302 	for (var i in this._searchFieldIds) {
303 		var id = this._searchFieldIds[i];
304 		var el = document.getElementById(id);
305 		if (el && el.value) {
306 			el.value = "";
307 		}
308 	}
309 };
310 
311 ZmAttendeePicker.prototype.isValid =
312 function() {
313 	return true;
314 };
315 
316 /**
317  * Enables/disables multiple locations.
318  *
319  * @param {Boolean}	enable		if <code>true</code>, allow multiple locations
320  */
321 ZmAttendeePicker.prototype.enableMultipleLocations =
322 function(enable) {
323 	if (this._multLocsCheckboxId) {
324 		var cb = document.getElementById(this._multLocsCheckboxId);
325 		if (cb.checked != enable) {
326 			cb.checked = enable;
327 			this._chooser.setSelectStyle(cb.checked ? DwtChooser.MULTI_SELECT : DwtChooser.SINGLE_SELECT, true);
328             this.resize(); // force resize to adjust chooser layout
329 		}
330 	}
331 };
332 
333 ZmAttendeePicker.prototype._loadSettings = function(){
334 
335     if (!appCtxt.get(ZmSetting.CAL_SHOW_RESOURCE_TABS)) {
336 		ZmAttendeePicker.TOP_LEGEND[ZmCalBaseItem.PERSON]			= ZmMsg.findAttendeesRooms;
337 		ZmAttendeePicker.BOTTOM_LEGEND[ZmCalBaseItem.PERSON]		= ZmMsg.apptAttendeesRooms;
338 	}
339 
340     if (this.type === ZmCalBaseItem.LOCATION) {
341         var fields_disabled = appCtxt.get(ZmSetting.CAL_LOCATION_FIELDS_DISABLED);
342         if (fields_disabled) {
343             fields_disabled = fields_disabled.split(",");
344             var fields_disabled_mapping = {
345                 CAPACITY : ZmAttendeePicker["SF_CAPACITY"],
346                 DESCRIPTION : ZmAttendeePicker["SF_DESCRIPTION"],
347                 SITE : ZmAttendeePicker["SF_SITE"],
348                 BUILDING : ZmAttendeePicker["SF_BUILDING"],
349                 FLOOR : ZmAttendeePicker["SF_FLOOR"]
350             };
351             ZmAttendeePicker.SETTINGS_SEARCH_FIELDS[this.type] = [];
352             //hash map of disabled fields
353             var isFieldDisabled = {};
354             for ( var i = 0; i < fields_disabled.length; i++ ) {
355                 if ( fields_disabled_mapping.hasOwnProperty(fields_disabled[i]) ) {
356                     isFieldDisabled[ fields_disabled_mapping[fields_disabled[i]] ] = true;
357                 }
358             }
359             var fields = ZmAttendeePicker.SEARCH_FIELDS[this.type];
360             for ( var i = 0; i < fields.length; i++ ) {
361                 if(!isFieldDisabled[fields[i]]) {
362 		            ZmAttendeePicker.SETTINGS_SEARCH_FIELDS[this.type].push(fields[i]);
363     	        }
364             }
365         }
366     }
367 };
368 
369 ZmAttendeePicker.prototype._createPageHtml =
370 function() {
371 	this._searchTableId	= Dwt.getNextId();
372 
373 	this._chooserSourceListViewDivId	= Dwt.getNextId();
374 	this._chooserButtonsDivId	= Dwt.getNextId();
375 	this._chooserTargetListViewDivId	= Dwt.getNextId();
376 
377 	var fields = ZmAttendeePicker.SETTINGS_SEARCH_FIELDS[this.type] || ZmAttendeePicker.SEARCH_FIELDS[this.type];
378 	for (var i = 0; i < fields.length; i++) {
379 		this._searchFieldIds[fields[i]] = Dwt.getNextId();
380 	}
381 
382 	var html = [];
383 	var i = 0;
384 
385 	html[i++] = "<fieldset";
386 	if (AjxEnv.isMozilla) {
387 		html[i++] = " style='border:1px dotted #555'";
388 	}
389 	html[i++] = "><legend style='color:#555555' id='" + this._searchTableId + "_legend'>";
390 	html[i++] = ZmAttendeePicker.TOP_LEGEND[this.type];
391 	html[i++] = "</legend>";
392 
393 	html[i++] = "<div style='margin-top:10px' id='";
394 	html[i++] = this._searchTableId;
395 	html[i++] = "'>";
396 
397 	html[i++] = "<table class='ZPropertySheet' cellspacing='6'><tr>";
398 
399 	for (var j = 0; j < fields.length; j++) {
400 		var isEven = ((j % 2) == 0);
401 		if (isEven) {
402 			html[i++] = "<tr>";
403 		}
404 		var sf = fields[j];
405         var addButton = (j == 1 || (this.type == ZmCalBaseItem.LOCATION && j === 0 && j === fields.length-1 ) );
406 		var addMultLocsCheckbox = (this.type == ZmCalBaseItem.LOCATION && j == fields.length - 1);
407 		i = this._getSearchFieldHtml(sf, html, i, addButton, addMultLocsCheckbox);
408 		if (!isEven || j == fields.length - 1) {
409             this._prevButtonId = Dwt.getNextId();
410             this._nextButtonId = Dwt.getNextId();
411             html[i++] = "<td> </td>";
412             html[i++] = "<td id='";
413             html[i++] = this._prevButtonId;
414             html[i++] = "'></td><td id='";
415             html[i++] = this._nextButtonId;
416             html[i++] = "'></td>";
417 			html[i++] = "</tr>";
418 		}
419 	}
420 
421 	html[i++] = "</table></div>";
422 
423 	// placeholder for the chooser's source list view
424 	html[i++] = "<div id='";
425 	html[i++] = this._chooserSourceListViewDivId;
426 	html[i++] = "'></div>";
427 	html[i++] = "</fieldset>";
428 
429 	// placeholder for the chooser's buttons
430 	html[i++] = "<div id='";
431 	html[i++] = this._chooserButtonsDivId;
432 	html[i++] = "'></div>";
433 
434 	html[i++] = "<fieldset";
435 	if (AjxEnv.isMozilla) {
436 		html[i++] = " style='border: 1px dotted #555555'";
437 	}
438 	html[i++] = "><legend style='color:#555555'>";
439 	html[i++] = ZmAttendeePicker.BOTTOM_LEGEND[this.type];
440 	html[i++] = "</legend>";
441 
442 	// placeholder for the chooser's target list view
443 	html[i++] = "<div id='";
444 	html[i++] = this._chooserTargetListViewDivId;
445 	html[i++] = "'></div>";
446 	html[i++] = "</fieldset>";
447 
448 	this.setContent(html.join(""));
449 };
450 
451 ZmAttendeePicker.prototype._getSearchFieldHtml =
452 function(id, html, i, addButton, addMultLocsCheckbox) {
453 	if (id == ZmAttendeePicker.SF_SOURCE) {
454 		// no need for source select if not more than one choice to choose from
455 		this.showSelect = false;
456 		if (appCtxt.get(ZmSetting.CONTACTS_ENABLED)) {
457 			if (appCtxt.get(ZmSetting.GAL_ENABLED) || appCtxt.get(ZmSetting.SHARING_ENABLED))
458 				this.showSelect = true;
459 		}
460 
461 		if (this.showSelect || appCtxt.isOffline) {
462             this._listSelectId = this._searchFieldIds[id];
463 			html[i++] = "<td class='ZmFieldLabelRight' id='";
464             html[i++] = this._listSelectId+"_label";
465             html[i++] = "'>";
466 			html[i++] = ZmMsg[ZmAttendeePicker.SF_LABEL[id]];
467 			html[i++] = ":</td><td id='";
468 			html[i++] = this._listSelectId;
469 			html[i++] = "' width='130'></td>";
470 		} else {
471 			html[i++] = "<td> </td>";
472 		}
473 	} else {
474 		html[i++] = "<td class='ZmFieldLabelRight'>";
475 		html[i++] = ZmMsg[ZmAttendeePicker.SF_LABEL[id]];
476 		html[i++] = ":</td><td>";
477 		html[i++] = "<input type='text' autocomplete='off' size=30 nowrap id='";
478 		html[i++] = this._searchFieldIds[id];
479 		html[i++] = "' />";
480 		html[i++] = "</td>";
481 	}
482 
483 	if (addButton) {
484 		this._searchBtnTdId	= Dwt.getNextId();
485 		html[i++] = "<td id='";
486 		html[i++] = this._searchBtnTdId;
487 		html[i++] = "'></td>";
488 	}
489 	if (addMultLocsCheckbox) {
490 		this._multLocsCheckboxId = Dwt.getNextId();
491         var fields = ZmAttendeePicker.SETTINGS_SEARCH_FIELDS[this.type] || ZmAttendeePicker.SEARCH_FIELDS[this.type];
492         if (fields.length === 1) {
493             html[i++] = "<tr>";
494         }
495         else if (fields.length === 2) {
496             html[i++] = "<tr>";
497             html[i++] = "<td></td>";
498             html[i++] = "<td></td>";
499             html[i++] = "<td></td>";
500             html[i++] = "<td></td>";
501             html[i++] = "<td></td>";
502             html[i++] = "<td></td>";
503         }
504         if (fields.length % 2 === 1) {
505             html[i++] = "<td></td>";
506             html[i++] = "<td></td>";
507             html[i++] = "<td></td>";
508         }
509 		html[i++] = "<td><table><tr><td>";
510 		html[i++] = "<input type='checkbox' id='";
511 		html[i++] = this._multLocsCheckboxId;
512 		html[i++] = "' /></td><td class='ZmFieldLabelLeft'><label for='";
513 		html[i++] = this._multLocsCheckboxId;
514 		html[i++] = "'>";
515 		html[i++] = ZmMsg.allowMultipleLocations;
516 		html[i++] = "</label></td></tr></table></td>";
517 	}
518 
519     if (appCtxt.isOffline && this.type == ZmCalBaseItem.PERSON) {
520         this.setSelectVisibility(this.showSelect);
521     }
522 	return i;
523 };
524 
525 ZmAttendeePicker.prototype._addDwtObjects =
526 function() {
527 	// add search button
528 	if (this._searchBtnTdId) {
529 		var element = document.getElementById(this._searchBtnTdId);
530 		var searchButton = this._searchButton = new DwtButton({parent:this});
531 		searchButton.setText(ZmMsg.search);
532 		searchButton.addSelectionListener(new AjxListener(this, this._searchButtonListener));
533 		element.appendChild(searchButton.getHtmlElement());
534 		// attendees tab: search button enabled only if there is search field input
535 		if (this.type == ZmCalBaseItem.PERSON) {
536 			searchButton.setEnabled(false);
537 		}
538 	}
539 
540 	// add select menu for contact source if we need one
541 	if (this.showSelect) {
542 		var listSelect = document.getElementById(this._listSelectId);
543 		this._selectDiv = new DwtSelect({parent:this});
544 		this._resetSelectDiv();
545 		listSelect.appendChild(this._selectDiv.getHtmlElement());
546 		this._selectDiv.addChangeListener(new AjxListener(this, this._searchTypeListener));
547 	}
548 
549 	// add paging buttons
550 	if (this._prevButtonId && this._nextButtonId) {
551 		var pageListener = new AjxListener(this, this._pageListener);
552 
553 		this._prevButton = new DwtButton({parent:this});
554 		this._prevButton.setImage("LeftArrow");
555 		this._prevButton.addSelectionListener(pageListener);
556 		this._prevButton.reparentHtmlElement(this._prevButtonId);
557 		this._prevButton.setEnabled(false);
558 
559 		this._nextButton = new DwtButton({parent:this});
560 		this._nextButton.setImage("RightArrow");
561 		this._nextButton.addSelectionListener(pageListener);
562 		this._nextButton.reparentHtmlElement(this._nextButtonId);
563 		this._nextButton.setEnabled(false);
564 	}
565 
566     var width = this.getSize().x;
567 	// add chooser
568 	this._chooser = new ZmApptChooser(this);
569     this._chooserWidth = width - 50;
570     this._chooser.resize(this._chooserWidth, ZmAttendeePicker.CHOOSER_HEIGHT);
571     
572 	var chooserSourceListViewDiv = document.getElementById(this._chooserSourceListViewDivId);
573 	var sourceListView = this._chooser.getSourceListView();
574 	chooserSourceListViewDiv.appendChild(sourceListView);
575 	var chooserButtonsDiv = document.getElementById(this._chooserButtonsDivId);
576 	var buttons = this._chooser.getButtons();
577 	chooserButtonsDiv.appendChild(buttons);
578 	var chooserTargetListViewDiv = document.getElementById(this._chooserTargetListViewDivId);
579 	var targetListView = this._chooser.getTargetListView();
580 	chooserTargetListViewDiv.appendChild(targetListView);
581 
582 	// save search fields, and add handler for Return key to them
583 	var fields = ZmAttendeePicker.SETTINGS_SEARCH_FIELDS[this.type] || ZmAttendeePicker.SEARCH_FIELDS[this.type];
584 	for (var i = 0; i < fields.length; i++) {
585 		var sf = fields[i];
586 		var searchField = this._searchFields[sf] = document.getElementById(this._searchFieldIds[sf]);
587 		if (searchField) {
588 			searchField.onkeypress = AjxCallback.simpleClosure(this._handleKeyPress, this);
589 			searchField.onkeyup = AjxCallback.simpleClosure(this._handleKeyUp, this);
590 		}
591 	}
592 
593 	if (this._multLocsCheckboxId) {
594 		var cb = document.getElementById(this._multLocsCheckboxId);
595 		cb.onclick = AjxCallback.simpleClosure(this._handleMultiLocsCheckbox, this);
596 	}
597 };
598 
599 ZmAttendeePicker.prototype._addTabGroupMembers =
600 function(tabGroup) {
601 	var fields = ZmAttendeePicker.SETTINGS_SEARCH_FIELDS[this.type] || ZmAttendeePicker.SEARCH_FIELDS[this.type];
602 	for (var i = 0; i < fields.length; i++) {
603 		if (fields[i] != ZmAttendeePicker.SF_SOURCE) {
604 			tabGroup.addMember(this._searchFields[fields[i]]);
605 		}
606 	}
607 };
608 
609 ZmAttendeePicker.prototype._searchButtonListener =
610 function(ev) {
611     this._list.removeAll();
612     this._offset = 0;    
613 	this.searchCalendarResources();
614 };
615 
616 ZmAttendeePicker.prototype._searchTypeListener =
617 function(ev) {
618 	var oldValue = ev._args.oldValue;
619 	var newValue = ev._args.newValue;
620 
621 	if (oldValue != newValue) {
622 		this._searchButtonListener();
623 	}
624 };
625 
626 ZmAttendeePicker.prototype._pageListener =
627 function(ev) {
628 	if (ev.item == this._prevButton) {
629 		this._offset -= ZmContactsApp.SEARCHFOR_MAX;
630 		this._showResults(true, true, this.getSubList()); // show cached results
631 	}
632 	else {
633 		var lastId;
634 		var lastSortVal;
635 		this._offset += ZmContactsApp.SEARCHFOR_MAX;
636 		var list = this.getSubList();
637 		if (!list) {
638 			list = this._chooser.sourceListView.getList();
639 			var contact = (list.size() > 0) ? list.getLast() : null;
640 			if (contact) {
641 				lastId = contact.id;
642 				lastSortVal = contact.sf;
643 			}
644             this.searchCalendarResources(false, null, lastId, lastSortVal);
645 		} else {
646 			var more = this._list.hasMore;
647 			if (!more) {
648 				more = (this._offset+ZmContactsApp.SEARCHFOR_MAX) < this._list.size();
649 			}
650 			this._showResults(true, more, list); // show cached results
651 		}
652 	}
653 };
654 
655 ZmAttendeePicker.prototype.getSubList =
656 function() {
657 	var size = this._list.size();
658 
659 	var end = (this._offset + ZmContactsApp.SEARCHFOR_MAX > size)
660 		? size : (this._offset + ZmContactsApp.SEARCHFOR_MAX);
661 
662 	return (this._offset < end)
663 		? (AjxVector.fromArray(this._list.getArray().slice(this._offset, end))) : null;
664 };
665 
666 /*
667 * Sets the target list to the current set of attendees.
668 */
669 ZmAttendeePicker.prototype._setAttendees =
670 function() {
671 	var attendees = this._attendees[this.type].getArray();
672 	if (attendees.length) {
673 		if (this.type == ZmCalBaseItem.LOCATION && attendees.length > 1) {
674 			this.enableMultipleLocations(true);
675 			this.resize();
676 		}
677 		this._chooser.setItems(attendees, DwtChooserListView.TARGET);
678 	}
679 	else {
680 		this._chooser.reset(DwtChooserListView.TARGET);
681 	}
682 };
683 
684 ZmAttendeePicker.prototype._resetSelectDiv =
685 function() {
686 	if (this._selectDiv) {
687 		var currAcct = this._editView.getCalendarAccount();
688 
689 		this._selectDiv.clearOptions();
690 		this._selectDiv.addOption(ZmMsg.contacts, false, ZmContactsApp.SEARCHFOR_CONTACTS);
691 		if (appCtxt.get(ZmSetting.SHARING_ENABLED, null, currAcct))
692 			this._selectDiv.addOption(ZmMsg.searchPersonalSharedContacts, false, ZmContactsApp.SEARCHFOR_PAS);
693 		if (appCtxt.get(ZmSetting.GAL_ENABLED, null, currAcct))
694 			this._selectDiv.addOption(ZmMsg.GAL, true, ZmContactsApp.SEARCHFOR_GAL);
695 		if (!appCtxt.get(ZmSetting.INITIALLY_SEARCH_GAL, null, currAcct) || !appCtxt.get(ZmSetting.GAL_ENABLED, null, currAcct)) {
696 			this._selectDiv.setSelectedValue(ZmContactsApp.SEARCHFOR_CONTACTS);
697 		}
698 	}
699 };
700 
701 ZmAttendeePicker.prototype.setSelectVisibility =
702 function(showSelect) {
703     if(typeof(showSelect) == "undefined") {
704         showSelect = false;
705         if (appCtxt.get(ZmSetting.CONTACTS_ENABLED)) {
706             if (appCtxt.get(ZmSetting.GAL_ENABLED) || appCtxt.get(ZmSetting.SHARING_ENABLED)) {
707                 showSelect = true;
708             }
709         }
710     }
711     var listSelect = document.getElementById(this._listSelectId);
712     var selectLabel = document.getElementById(this._listSelectId+"_label");
713     if(listSelect && selectLabel) {
714         Dwt.setDisplay(selectLabel, showSelect ? Dwt.DISPLAY_TABLE_CELL : Dwt.DISPLAY_NONE);
715         Dwt.setDisplay(listSelect, showSelect ? Dwt.DISPLAY_TABLE_CELL : Dwt.DISPLAY_NONE);        
716     }
717 };
718 
719 
720 ZmAttendeePicker.prototype._showResults =
721 function(isPagingSupported, more, list) {
722 	if (this._prevButton && this._nextButton) {
723 		// if offset is returned, then this account support gal paging
724 		if (this._contactSource == ZmId.SEARCH_GAL && !isPagingSupported) {
725 			this._prevButton.setEnabled(false);
726 			this._nextButton.setEnabled(false);
727 		} else {
728 			this._prevButton.setEnabled(this._offset > 0);
729 			this._nextButton.setEnabled(more);
730 		}
731 	}
732 
733 	var list1 = [];
734 	var contactList = list ? list : [];
735 	if (!(contactList instanceof Array)) {
736 		contactList = contactList.getArray();
737 	}
738 
739 	for (var i = 0; i < contactList.length; i++) {
740 		if (!contactList[i]) { continue; }
741 		var contact = (contactList[i] && contactList[i].__contact) ? contactList[i].__contact : contactList[i];
742 		var emails = contact.isGal ? [contact.getEmail()] : contact.getEmails();
743 		if (emails && emails.length > 1) {
744 			var workPhone = contact.getAttr(ZmContact.F_workPhone);
745 			var homePhone = contact.getAttr(ZmContact.F_homePhone);
746 			for (var j = 0; j < emails.length; j++) {
747 				var clone = new ZmContact(null);
748 				clone._fullName = contact.getFullName();
749 				clone.folderId = contact.folderId;
750 				clone.setAttr(ZmContact.F_workPhone, workPhone);
751 				clone.setAttr(ZmContact.F_homePhone, homePhone);
752 				clone.setAttr(ZmContact.F_email, emails[j]);
753 				list1.push(clone);
754 			}
755 		} else {
756 			list1.push(contact);
757 		}
758 	}
759 
760 	this._fillFreeBusy(list1, AjxCallback.simpleClosure(function(list1) { this._chooser.setItems(list1); }, this));
761 	this._chooser.sourceListView.focus();
762 };
763 
764 ZmAttendeePicker.prototype.searchCalendarResources =
765 function(defaultSearch, sortBy, lastId, lastSortVal) {
766 	var currAcct = this._editView.getCalendarAccount();
767 	var fields = ZmAttendeePicker.SETTINGS_SEARCH_FIELDS[this.type] || ZmAttendeePicker.SEARCH_FIELDS[this.type];
768 	var conds = [];
769 	var value = (this.type == ZmCalBaseItem.LOCATION) ? "Location" : "Equipment";
770 	conds.push({attr: "zimbraCalResType", op: "eq", value: value});
771 	var gotValue = false;
772 	for (var i = 0; i < fields.length; i++) {
773 		var sf = fields[i];
774 		var searchField = document.getElementById(this._searchFieldIds[sf]);
775 		value = AjxStringUtil.trim(searchField.value);
776 		if (value.length) {
777 			gotValue = true;
778 			var attr = ZmAttendeePicker.SF_ATTR[sf];
779 			var op = ZmAttendeePicker.SF_OP[sf] ? ZmAttendeePicker.SF_OP[sf] : "has";
780 			conds.push({attr: attr, op: op, value: value});
781 		}
782 	}
783 	var params = {
784 		sortBy: sortBy,
785 		offset: this._offset,
786 		limit: ZmContactsApp.SEARCHFOR_MAX,
787 		conds: conds,
788 		attrs: ZmAttendeePicker.ATTRS[this.type],
789         lastId: lastId,
790         lastSortVal: lastSortVal,        
791 		accountName: appCtxt.isOffline ? currAcct.name : null
792 	};
793 	var search = new ZmSearch(params);
794 	search.execute({callback: new AjxCallback(this, this._handleResponseSearchCalendarResources, [defaultSearch])});
795 };
796 
797 ZmAttendeePicker.prototype._getTimeFrame =
798 function() {
799 	var di = {};
800 	ZmApptViewHelper.getDateInfo(this._editView, di);
801 	var startDate = AjxDateUtil.simpleParseDateStr(di.startDate);
802 	var endDate;
803 	if (di.isAllDay) {
804 		startDate.setHours(0, 0, 0, 0);
805         endDate = AjxDateUtil.simpleParseDateStr(di.endDate);
806         endDate.setHours(23, 59, 0, 0);
807 	} else {
808 		endDate = AjxDateUtil.simpleParseDateStr(di.endDate);
809 		startDate = this._editView._startTimeSelect.getValue(startDate);
810 		endDate = this._editView._endTimeSelect.getValue(endDate);
811 	}
812 
813 	return {start:startDate, end:endDate};
814 };
815 
816 ZmAttendeePicker.prototype._fillFreeBusy =
817 function(items, callback) {
818 
819 	var currAcct = this._editView.getCalendarAccount();
820 	// Bug: 48189 Don't send GetFreeBusyRequest for non-ZCS accounts.
821 	if (appCtxt.isOffline && (!currAcct.isZimbraAccount || currAcct.isMain)) {
822 		if (callback) {
823 			callback(items);
824 		}
825 		return;
826 	}
827 
828 	var tf = this._getTimeFrame();
829 	var list = (items instanceof AjxVector) ? items.getArray() : (items instanceof Array) ? items : [items];
830 	var emails = [];
831 	var itemsById = {};
832 	for (var i = list.length; --i >= 0;) {
833 		var item = list[i];
834 		emails[i] = item.getEmail();
835 
836 		// bug: 30824 - Don't list all addresses/aliases of a resource in
837 		// GetFreeBusyRequest.  One should suffice.
838 		if (emails[i] instanceof Array) {
839 			emails[i] = emails[i][0];
840 		}
841 
842 		itemsById[emails[i]] = item;
843 		item.__fbStatus = { txt: ZmMsg.unknown };
844 	}
845 	callback(items);
846 
847 	if (this._freeBusyRequest) {
848 		appCtxt.getRequestMgr().cancelRequest(this._freeBusyRequest, null, true);
849 	}
850 	this._freeBusyRequest = this._controller.getFreeBusyInfo(tf.start.getTime(),
851 															 tf.end.getTime(),
852 															 emails.join(","),
853 															 new AjxCallback(this, this._handleResponseFreeBusy, [itemsById]),
854 															 null,
855 															 true);
856 };
857 
858 ZmAttendeePicker.prototype._handleResponseFreeBusy =
859 function(itemsById, result) {
860 	this._freeBusyRequest = null;
861 
862 	var args = result.getResponse().GetFreeBusyResponse.usr;
863 	for (var i = args.length; --i >= 0;) {
864 		var el = args[i];
865 		var id = el.id;
866 		if (!id) {
867 			continue;
868 		}
869 		var item = itemsById[id];
870 		if (!item) {
871 			continue;
872 		}
873 		var status = ZmMsg.free;
874 		item.__fbStatus.status = 0;
875 		if (el.b) {
876 			status = "<b style='color: red'>" + ZmMsg.busy + "</b>";
877 			item.__fbStatus.status = 1;
878 		} else if (el.u) {
879 			status = "<b style='color: red'>" + ZmMsg.outOfOffice + "</b>";
880 			item.__fbStatus.status = 2;
881 		} else if (el.t) {
882 			status = "<b style='color: orange'>" + ZmMsg.tentative + "</b>";
883 			item.__fbStatus.status = 3;
884 		}
885 		item.__fbStatus.txt = status;
886 		this._updateStatus(item, this._chooser.sourceListView);
887 		this._updateStatus(item, this._chooser.targetListView);
888 	}
889 };
890 
891 ZmAttendeePicker.prototype._updateStatus =
892 function(item, view) {
893 	var id = view._getFieldId(item, "FBSTATUS"),
894 		element = document.getElementById(id);
895 	
896 	if (element) {
897 		element.innerHTML = item.__fbStatus.txt;
898 	}
899 };
900 
901 ZmAttendeePicker.prototype._handleResponseSearchCalendarResources =
902 function(defaultSearch, result) {
903 	var resp = result.getResponse();
904     var offset = resp.getAttribute("offset");
905     var isPagingSupported = resp.getAttribute("paginationSupported");
906     var more = resp.getAttribute("more");
907     var info = resp.getAttribute("info");
908     var expanded = info && info[0].wildcard[0].expanded == "0";
909 
910     var list = resp.getResults(ZmItem.RESOURCE).getVector();
911     if (isPagingSupported) {
912         this._list.merge(offset, list);
913         this._list.hasMore = more;
914     }
915 
916     this._showResults(isPagingSupported, more, list.getArray());    
917 };
918 
919 ZmAttendeePicker.prototype._getDefaultFocusItem =
920 function() {
921 	var fields = ZmAttendeePicker.SETTINGS_SEARCH_FIELDS[this.type] || ZmAttendeePicker.SEARCH_FIELDS[this.type];
922 	return this._searchFields[fields[0]];
923 };
924 
925 ZmAttendeePicker.prototype._handleKeyPress =
926 function(ev) {
927 	var charCode = DwtKeyEvent.getCharCode(ev);
928 	if (this._keyPressCallback && (charCode == 13 || charCode == 3)) {
929 		this._keyPressCallback.run();
930 	    return false;
931 	}
932 	return true;
933 };
934 
935 ZmAttendeePicker.prototype._handleKeyUp =
936 function(ev) {
937 	var field = DwtUiEvent.getTarget(ev);
938 
939 	return true;
940 };
941 
942 ZmAttendeePicker.prototype._handleMultiLocsCheckbox =
943 function(ev) {
944 	var cb = DwtUiEvent.getTarget(ev);
945 	this._chooser.setSelectStyle(cb.checked ? DwtChooser.MULTI_SELECT : DwtChooser.SINGLE_SELECT, true);
946 
947 	this.resize(); // force resize to adjust chooser layout
948 };
949 
950 // *********************
951 
952 /**
953  * @class
954  * This class creates a specialized chooser for the attendee picker.
955  *
956  * @param {DwtComposite}	parent			the attendee tab view
957  * @param {Array}		buttonInfo		transfer button IDs and labels
958  * 
959  * @extends		DwtChooser
960  * 
961  * @private
962  */
963 ZmApptChooser = function(parent, buttonInfo) {
964 	var selectStyle = (parent.type == ZmCalBaseItem.LOCATION) ? DwtChooser.SINGLE_SELECT : null;
965 	DwtChooser.call(this, {parent: parent, buttonInfo: buttonInfo, layoutStyle: DwtChooser.VERT_STYLE,
966 						   mode: DwtChooser.MODE_MOVE, selectStyle: selectStyle, allButtons: true});
967 };
968 
969 ZmApptChooser.prototype = new DwtChooser;
970 ZmApptChooser.prototype.constructor = ZmApptChooser;
971 
972 ZmApptChooser.prototype.toString =
973 function() {
974 	return "ZmApptChooser";
975 };
976 
977 // overload to handle contact groups - see bug 28398
978 ZmApptChooser.prototype.addItems =
979 function(items, view, skipNotify, id) {
980 	var newList;
981 
982 	if (view == DwtChooserListView.TARGET) {
983 		newList = [];
984 		var list = (items instanceof AjxVector) ? items.getArray() : (items instanceof Array) ? items : [items];
985 
986 		for (var i = 0; i < list.length; i++) {
987 			var item = list[i];
988 			if (item instanceof ZmContact && item.isGroup()) {
989 				var addrs = item.getGroupMembers().good.getArray();
990 				for (var j = 0; j < addrs.length; j++) {
991 					var contact = new ZmContact(null);
992 					contact.initFromEmail(addrs[j]);
993 					newList.push(contact);
994 				}
995 			} else {
996 				newList.push(item);
997 			}
998 		}
999 	} else {
1000 		newList = items;
1001 	}
1002     if(this.parent.type == ZmCalBaseItem.LOCATION && newList.length <= 0) {
1003         this.sourceListView.setUI(null, false);
1004     }
1005     DwtChooser.prototype.addItems.call(this, newList, view, skipNotify, id);
1006 };
1007 
1008 ZmApptChooser.prototype._createSourceListView =
1009 function() {
1010 	return new ZmApptChooserListView(this, DwtChooserListView.SOURCE, this.parent.type);
1011 };
1012 
1013 ZmApptChooser.prototype._createTargetListView =
1014 function() {
1015 	return new ZmApptChooserListView(this, DwtChooserListView.TARGET, this.parent.type);
1016 };
1017 
1018 ZmApptChooser.prototype._notify =
1019 function(event, details) {
1020 	details.type = this.parent.type;
1021 	DwtChooser.prototype._notify.call(this, event, details);
1022 };
1023 
1024 /**
1025  * The item is a {@link ZmContact} or {@link ZmResource}. Its address is used for comparison.
1026  *
1027  * @param {ZmContact}	item	the ZmContact or ZmResource
1028  * @param {AjxVector}	list	list to check against
1029  * 
1030  * @private
1031  */
1032 ZmApptChooser.prototype._isDuplicate =
1033 function(item, list) {
1034 	return list.containsLike(item, item.getEmail);
1035 };
1036 
1037 ZmApptChooser.prototype._reset =
1038 function(view) {
1039 	if (appCtxt.isOffline && appCtxt.accountList.size() > 1 && !view) {
1040 		this.parent._resetSelectDiv();
1041 	}
1042 	DwtChooser.prototype._reset.apply(this, arguments);
1043 };
1044 
1045 /**
1046  * Removes items from target list, paying attention to current mode. Also handles button state.
1047  *
1048  * @param {AjxVector|array|Object|hash}	list			a list of items or hash of lists
1049  * @param {boolean}	skipNotify	if <code>true</code>, do not notify listeners
1050  */
1051 ZmApptChooser.prototype.remove =
1052 function(list, skipNotify) {
1053 	list = (list instanceof AjxVector) ? list.getArray() : (list instanceof Array) ? list : [list];
1054 	if (this._mode == DwtChooser.MODE_MOVE) {
1055 		for (var i = 0; i < list.length; i++) {
1056             var itemIndex = this.sourceListView.getItemIndex(list[i]);
1057             //avoid adding duplicate entries
1058             if(itemIndex == null) {
1059 			    var index = this._getInsertionIndex(this.sourceListView, list[i]);
1060 			    this.sourceListView.addItem(list[i], index, true);
1061             }
1062 		}
1063 		this._sourceSize = list ? list.length : 0;
1064 	}
1065 	this.removeItems(list, DwtChooserListView.TARGET);
1066 };
1067 
1068 
1069 /**
1070  * This class creates a specialized source list view for the contact chooser. The items
1071  * it manages are of type ZmContact or its subclass ZmResource.
1072  *
1073  * @param {DwtChooser}	parent		chooser that owns this list view
1074  * @param {constant}	type			list view type (source or target)
1075  * @param {constant}	chooserType		type of owning chooser (attendee/location/resource)
1076  * 
1077  * @extends		DwtChooserListView
1078  * 
1079  * @private
1080  */
1081 ZmApptChooserListView = function(parent, type, chooserType) {
1082 
1083 	this._chooserType = chooserType;
1084 	DwtChooserListView.call(this, {parent:parent, type:type});
1085 
1086 	this._notes = {};
1087 };
1088 
1089 ZmApptChooserListView.prototype = new DwtChooserListView;
1090 ZmApptChooserListView.prototype.constructor = ZmApptChooserListView;
1091 
1092 ZmApptChooserListView.prototype.toString =
1093 function() {
1094 	return "ZmApptChooserListView";
1095 };
1096 
1097 ZmApptChooserListView.prototype._getHeaderList =
1098 function() {
1099 	var headerList = [];
1100 	var cols = ZmAttendeePicker.COLS[this._chooserType];
1101     if ( ZmAttendeePicker.SETTINGS_SEARCH_FIELDS[this._chooserType] && ZmAttendeePicker.SETTINGS_SEARCH_FIELDS[this._chooserType].length === 1 ){
1102         var auto_width = true;
1103     }
1104 	for (var i = 0; i < cols.length; i++) {
1105 		var id = cols[i];
1106 		var text = ZmMsg[ZmAttendeePicker.COL_LABEL[id]];
1107 		var image = ZmAttendeePicker.COL_IMAGE[id];
1108         var width = ( auto_width ) ? null : ZmAttendeePicker.COL_WIDTH[id];
1109 		headerList.push(new DwtListHeaderItem({field:id, text:text, icon:image, width:width,
1110 											   resizeable:(id == ZmItem.F_NAME)}));
1111 	}
1112 
1113 	return headerList;
1114 };
1115 
1116 ZmApptChooserListView.prototype._getCellId =
1117 function(item, field) {
1118 	return field == "FBSTATUS" ? this._getFieldId(item, field) : null;
1119 };
1120 
1121 ZmApptChooserListView.prototype._getCellContents =
1122 function(html, idx, item, field, colIdx, params) {
1123 	if (field != ZmItem.F_NOTES) {
1124 		html[idx++] = " ";
1125 	}
1126 	if (field == ZmItem.F_FOLDER) {
1127 		var name = "";
1128 		if (item.isGal) {
1129 			name = ZmMsg.GAL;
1130 		} else {
1131 			var folder = appCtxt.getById(item.folderId);
1132 			name = folder ? folder.name : "";
1133 		}
1134 		html[idx++] = AjxStringUtil.htmlEncode(name);
1135 	} else if (field == ZmItem.F_NAME) {
1136 		var name = (this._chooserType == ZmCalBaseItem.PERSON) ? item.getFullName() : item.getAttr(ZmResource.F_name);
1137 		if (this._chooserType != ZmCalBaseItem.PERSON && item instanceof ZmContact) {
1138 			name = item.getFullName() || item.getAttr(ZmResource.F_locationName);
1139 		}
1140 		html[idx++] = AjxStringUtil.htmlEncode(name);
1141 	} else if (field == ZmItem.F_EMAIL) {
1142 		html[idx++] = AjxStringUtil.htmlEncode(item.getEmail());
1143 	} else if (field == ZmItem.F_WORK_PHONE) {
1144 		html[idx++] = AjxStringUtil.htmlEncode(item.getAttr(ZmContact.F_workPhone));
1145 	} else if (field == ZmItem.F_HOME_PHONE) {
1146 		html[idx++] = AjxStringUtil.htmlEncode(item.getAttr(ZmContact.F_homePhone));
1147 	} else if (field == ZmItem.F_LOCATION) {
1148 		html[idx++] = AjxStringUtil.htmlEncode(item.getAttr(ZmResource.F_locationName) || item.getFullName());
1149 	} else if (field == ZmItem.F_CONTACT) {
1150 		html[idx++] = AjxStringUtil.htmlEncode(item.getAttr(ZmResource.F_contactMail));
1151 	} else if (field == ZmItem.F_CAPACITY) {
1152 		html[idx++] = AjxStringUtil.htmlEncode(item.getAttr(ZmResource.F_capacity));
1153 	} else if (field == ZmItem.F_NOTES) {
1154 		var notes = item.getAttr(ZmContact.F_description);
1155 		if (notes) {
1156 			var notesId = this._getFieldId(item, field);
1157 			this._notes[notesId] = notes;
1158 			html[idx++] = AjxImg.getImageHtml("Page", null, ["id='", notesId, "'"].join(""));
1159 		}
1160 	} else if (field == "FBSTATUS" && item.__fbStatus) {
1161 		html[idx++] = item.__fbStatus.txt;
1162 	}
1163 	return idx;
1164 };
1165 
1166 ZmApptChooserListView.prototype._mouseOverAction =
1167 function(ev, div) {
1168 	DwtListView.prototype._mouseOverAction.call(this, ev, div);
1169 	var id = ev.target.id || div.id;
1170 	if (!id) { return true; }
1171 
1172 	// check if we're hovering over a column header
1173 	var type = Dwt.getAttr(div, "_type");
1174 	if (type && type == DwtListView.TYPE_HEADER_ITEM) {
1175 		var hdr = this.getItemFromElement(div);
1176 		if (hdr) {
1177 			if (hdr._field == ZmItem.F_NOTES) {
1178 				this.setToolTipContent(ZmMsg.notes);
1179 			}
1180 		}
1181 	} else {
1182 		var note = this._notes[id];
1183 		if (!note) {
1184 			var item = this.getItemFromElement(div);
1185 			if (item) {
1186 				var notesId = this._getFieldId(item, ZmItem.F_NOTES);
1187 				note = this._notes[notesId];
1188 			}
1189 		}
1190 		if (note) {
1191 			this.setToolTipContent(AjxStringUtil.htmlEncode(note));
1192 		}
1193 	}
1194 
1195 	return true;
1196 };
1197