1 /*
  2  * ***** BEGIN LICENSE BLOCK *****
  3  * Zimbra Collaboration Suite Web Client
  4  * Copyright (C) 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) 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 split view class.
 27  */
 28 
 29 /**
 30  * Creates a contact split view.
 31  * @class
 32  * This class represents the contact split view.
 33  * 
 34  * @param	{Hash}	params		a hash of parameters
 35  * @extends	DwtComposite
 36  */
 37 ZmContactSplitView = function(params) {
 38 	if (arguments.length == 0) { return; }
 39 
 40 	params.className = params.className || "ZmContactSplitView";
 41 	params.posStyle = params.posStyle || Dwt.ABSOLUTE_STYLE;
 42 	params.id = Dwt.getNextId('ZmContactSplitView_');
 43 	DwtComposite.call(this, params);
 44 
 45 	this._controller = params.controller;
 46 	this.setScrollStyle(Dwt.CLIP);
 47 
 48 	this._changeListener = new AjxListener(this, this._contactChangeListener);
 49 
 50 	this._initialize(params.controller, params.dropTgt);
 51 
 52 	var folderTree = appCtxt.getFolderTree();
 53 	if (folderTree) {
 54 		folderTree.addChangeListener(new AjxListener(this, this._addrbookTreeListener));
 55 	}
 56 
 57 	ZmTagsHelper.setupListeners(this);
 58 
 59 };
 60 
 61 ZmContactSplitView.prototype = new DwtComposite;
 62 ZmContactSplitView.prototype.constructor = ZmContactSplitView;
 63 
 64 ZmContactSplitView.prototype.isZmContactSplitView = true
 65 ZmContactSplitView.prototype.toString = function() { return "ZmContactSplitView"; };
 66 
 67 // Consts
 68 ZmContactSplitView.ALPHABET_HEIGHT = 35;
 69 
 70 ZmContactSplitView.NUM_DL_MEMBERS = 10;	// number of distribution list members to show initially
 71 
 72 ZmContactSplitView.LIST_MIN_WIDTH = 100;
 73 ZmContactSplitView.CONTENT_MIN_WIDTH = 200;
 74 
 75 ZmContactSplitView.SUBSCRIPTION_POLICY_ACCEPT = "ACCEPT";
 76 ZmContactSplitView.SUBSCRIPTION_POLICY_REJECT = "REJECT";
 77 ZmContactSplitView.SUBSCRIPTION_POLICY_APPROVAL = "APPROVAL";
 78 
 79 /**
 80  * Gets the list view.
 81  * 
 82  * @return	{ZmContactSimpleView}	the list view
 83  */
 84 ZmContactSplitView.prototype.getListView =
 85 function() {
 86 	return this._listPart;
 87 };
 88 
 89 /**
 90  * Gets the controller.
 91  * 
 92  * @return	{ZmContactController}	the controller
 93  */
 94 ZmContactSplitView.prototype.getController =
 95 function() {
 96 	return this._controller;
 97 };
 98 
 99 /**
100  * Gets the alphabet bar.
101  * 
102  * @return	{ZmContactAlphabetBar}	the alphabet bar
103  */
104 ZmContactSplitView.prototype.getAlphabetBar =
105 function() {
106 	return this._alphabetBar;
107 };
108 
109 /**
110  * Sets the view size.
111  * 
112  * @param	{int}	width		the width (in pixels)
113  * @param	{int}	height		the height (in pixels)
114  */
115 ZmContactSplitView.prototype.setSize =
116 function(width, height) {
117 	DwtComposite.prototype.setSize.call(this, width, height);
118 	this._sizeChildren(width, height);
119 };
120 
121 /**
122  * Gets the title.
123  * 
124  * @return	{String}	the title
125  */
126 ZmContactSplitView.prototype.getTitle =
127 function() {
128 	return [ZmMsg.zimbraTitle, this._controller.getApp().getDisplayName()].join(": ");
129 };
130 
131 /**
132  * Gets the size limit.
133  * 
134  * @param	{int}	offset		the offset
135  * @return	{int}	the size
136  */
137 ZmContactSplitView.prototype.getLimit =
138 function(offset) {
139 	return this._listPart.getLimit(offset);
140 };
141 
142 /**
143  * Sets the contact.
144  * 
145  * @param	{ZmContact}	contact		the contact
146  * @param	{Boolean}	isGal		<code>true</code> if is GAL
147  * 
148  */
149 ZmContactSplitView.prototype.setContact =
150 function(contact, isGal) {
151 	if (contact.isDistributionList() || !isGal) {
152 		// Remove and re-add listeners for current contact if exists
153 		if (this._contact) {
154 			this._contact.removeChangeListener(this._changeListener);
155 		}
156 		contact.addChangeListener(this._changeListener);
157 	}
158 
159 	var oldContact = this._contact;
160 	this._contact = this._item = contact;
161 
162 	if (this._contact.isLoaded) {
163 		this._setContact(contact, isGal, oldContact);
164 	} else {
165 		var callback = new AjxCallback(this, this._handleResponseLoad, [isGal, oldContact]);
166 		var errorCallback = new AjxCallback(this, this._handleErrorLoad);
167 		this._contact.load(callback, errorCallback, null, contact.isGroup());
168 	}
169 };
170 
171 ZmContactSplitView.expandDL =
172 function(viewId, expand) {
173 	var view = DwtControl.fromElementId(viewId);
174 	if (view) {
175 		view._setContact(view._contact, true, null, expand);
176 	}
177 };
178 
179 ZmContactSplitView.handleDLScroll =
180 function(ev) {
181 
182 	var target = DwtUiEvent.getTarget(ev);
183 	var view = DwtControl.findControl(target);
184 	if (!view) { return; }
185 	var div = view._dlScrollDiv;
186 	if (div.clientHeight == div.scrollHeight) { return; }
187 	var contactDL = appCtxt.getApp(ZmApp.CONTACTS).getDL(view._dlContact.getEmail());
188 	var listSize = view.getDLSize();
189 	if (contactDL && (contactDL.more || (listSize < contactDL.list.length))) {
190 		var params = {scrollDiv:	div,
191 					  rowHeight:	view._rowHeight,
192 					  threshold:	10,
193 					  limit:		ZmContact.DL_PAGE_SIZE,
194 					  listSize:		listSize};
195 		var needed = ZmListView.getRowsNeeded(params);
196 		DBG.println("dl", "scroll, items needed: " + needed);
197 		if (needed) {
198 			DBG.println("dl", "new offset: " + listSize);
199 			var respCallback = new AjxCallback(null, ZmContactSplitView._handleResponseDLScroll, [view]);
200 			view._dlContact.getDLMembers(listSize, null, respCallback);
201 		}
202 	}
203 };
204 
205 ZmContactSplitView._handleResponseDLScroll =
206 function(view, result) {
207 
208 	var list = result.list;
209 	if (!(list && list.length)) { return; }
210 
211 	var html = [];
212 	view._listPart._getImageHtml(html, 0, null);
213 	var subs = {first: false, html: html};
214 	var row = document.getElementById(view._dlLastRowId);
215 	var table = row && document.getElementById(view._detailsId);
216 	if (row) {
217 		var rowIndex = row.rowIndex + 1;
218 		for (var i = 0, len = list.length; i < len; i++) {
219 			view._distributionList.list.push(list[i]);
220 			subs.value = view._objectManager.findObjects(list[i], false, ZmObjectManager.EMAIL);
221 			var rowIdText = "";
222 			var newRow = table.insertRow(rowIndex + i);
223 			if (i == len - 1) {
224 				newRow.id = view._dlLastRowId = Dwt.getNextId();
225 			}
226 			newRow.valign = "top";
227 			newRow.innerHTML = AjxTemplate.expand("abook.Contacts#SplitView_dlmember-expanded", subs);
228 		}
229 //		view._dlScrollDiv.scrollTop = 0;
230 	}
231 	DBG.println("dl", table.rows.length + " rows");
232 };
233 
234 ZmContactSplitView.prototype.getDLSize =
235 function() {
236 	return this._distributionList && this._distributionList.list.length;
237 
238 };
239 
240 /**
241  * @private
242  */
243 ZmContactSplitView.prototype._handleResponseLoad =
244 function(isGal, oldContact, resp, contact) {
245 	if (contact.id == this._contact.id) {
246 		this._setContact(this._contact, isGal, oldContact);
247 	}
248 };
249 
250 /**
251  * @private
252  */
253 ZmContactSplitView.prototype._handleErrorLoad =
254 function(ex) {
255 	this.clear();
256 	// TODO - maybe display some kind of error?
257 };
258 
259 /**
260  * Clears the view.
261  * 
262  */
263 ZmContactSplitView.prototype.clear =
264 function() {
265 	var groupDiv = document.getElementById(this._contactBodyId);
266 	if (groupDiv) {
267 		groupDiv.innerHTML = "";
268 	}
269 
270 	this._contactView.clear();
271 	this._clearTags();
272 };
273 
274 /**
275  * Enables the alphabet bar.
276  * 
277  * @param	{Boolean}	enable		if <code>true</code>, enable the alphabet bar
278  */
279 ZmContactSplitView.prototype.enableAlphabetBar =
280 function(enable) {
281 	if (this._alphabetBar)
282 		this._alphabetBar.enable(enable);
283 };
284 
285 /**
286  * shows/hides the alphabet bar.
287  *
288  * @param	{Boolean}	visible		if <code>true</code>, show the alphabet bar
289  */
290 ZmContactSplitView.prototype.showAlphabetBar =
291 function(visible) {
292 	if (this._alphabetBar) {
293 		this._alphabetBar.setVisible(visible);
294 	}
295 };
296 
297 
298 /**
299  * @private
300  */
301 ZmContactSplitView.prototype._initialize =
302 function(controller, dropTgt) {
303 	this.getHtmlElement().innerHTML = AjxTemplate.expand("abook.Contacts#SplitView", {id:this._htmlElId});
304 
305 	// alphabet bar based on *optional* existence in template and msg properties
306 	var alphaDivId = this._htmlElId + "_alphabetbar";
307 	var alphaDiv = document.getElementById(alphaDivId);
308 	if (alphaDiv && ZmMsg.alphabet && ZmMsg.alphabet.length>0) {
309 		this._alphabetBar = new ZmContactAlphabetBar(this);
310 		this._alphabetBar.reparentHtmlElement(alphaDivId);
311 	}
312 
313 	var splitviewCellId = this._htmlElId + "_splitview";
314 	this._splitviewCell = document.getElementById(splitviewCellId);
315 
316 	// create listview based on *required* existence in template
317 	var listviewCellId = this._htmlElId + "_listview";
318 	this._listviewCell = document.getElementById(listviewCellId);
319 	this._listPart = new ZmContactSimpleView({parent:this, controller:controller, dropTgt:dropTgt});
320 	this._listPart.reparentHtmlElement(listviewCellId);
321 
322 	var sashCellId = this._htmlElId + "_sash";
323 	this._sash = new DwtSash(this, DwtSash.HORIZONTAL_STYLE, null, 5, Dwt.ABSOLUTE_STYLE);
324 	this._sash.registerCallback(this._sashCallback, this);
325 	this._sash.replaceElement(sashCellId, false, true);
326 
327 	var contentCellId = this._htmlElId + "_contentCell";
328 	this._contentCell = document.getElementById(contentCellId);
329 
330 	// define well-known Id's
331 	this._iconCellId	= this._htmlElId + "_icon";
332 	this._titleCellId	= this._htmlElId + "_title";
333 	this._tagCellId		= this._htmlElId + "_tags_contact";
334 	this._contactBodyId = this._htmlElId + "_body";
335 	this._contentId		= this._htmlElId + "_content";
336 	this._detailsId		= this._htmlElId + "_details";
337 
338 	// create an empty slate
339 	this._contactView = new ZmContactView({ parent: this, controller: this._controller });
340 	this._contactView.reparentHtmlElement(this._contentId);
341 	this._objectManager = new ZmObjectManager(this._contactView);
342 	this._contentCell.style.right = "0px";
343 
344 	this._tabGroup = new DwtTabGroup('ZmContactSplitView');
345 	this._tabGroup.addMember(this._contactView.getTabGroupMember());
346 };
347 
348 ZmContactSplitView.prototype.getTabGroupMember = function() {
349 	return this._tabGroup;
350 };
351 
352 /**
353  * @private
354  */
355 ZmContactSplitView.prototype._tabStateChangeListener =
356 function(ev) {
357 	this._setContact(this._contact, this._isGalSearch);
358 };
359 
360 /**
361  * @private
362  */
363 ZmContactSplitView.prototype._sizeChildren =
364 function(width, height) {
365 
366 	// Using toWindow instead of getY because getY calls Dwt.getLocation
367 	// which returns NaN if "top" is not set or is "auto"
368 	var listPartOffset = Dwt.toWindow(this._listPart.getHtmlElement(), 0, 0);
369 	var fudge = listPartOffset.y - this.getY();
370 
371 	this._listPart.setSize(Dwt.DEFAULT, height - fudge);
372 
373 	fudge = this._contactView.getY() - this.getY();
374 	this._contactView.setSize(Dwt.DEFAULT, height - fudge);
375 };
376 
377 /**
378  * @private
379  */
380 ZmContactSplitView.prototype._contactChangeListener =
381 function(ev) {
382 	if (ev.type != ZmEvent.S_CONTACT ||
383 		ev.source != this._contact ||
384 		ev.event == ZmEvent.E_DELETE)
385 	{
386 		return;
387 	}
388 
389 	this._setContact(ev.source);
390 };
391 
392 /**
393  * @private
394  */
395 ZmContactSplitView.prototype._addrbookTreeListener =
396 function(ev, treeView) {
397 	if (!this._contact) { return; }
398 
399 	var fields = ev.getDetail("fields");
400 	if (ev.event == ZmEvent.E_MODIFY && fields && fields[ZmOrganizer.F_COLOR]) {
401 		var organizers = ev.getDetail("organizers");
402 		if (!organizers && ev.source) {
403 			organizers = [ev.source];
404 		}
405 
406 		for (var i = 0; i < organizers.length; i++) {
407 			var organizer = organizers[i];
408 			var folderId = this._contact.isShared()
409 				? appCtxt.getById(this._contact.folderId).id
410 				: this._contact.folderId;
411 
412 			if (organizer.id == folderId) {
413 				this._setTags();
414 			}
415 		}
416 	}
417 };
418 
419 /**
420  * @private
421  */
422 ZmContactSplitView.prototype._setContact =
423 function(contact, isGal, oldContact, expandDL, isBack) {
424 
425 	//first gather the dl info and dl members. Those are async requests so calling back here after
426 	//it is done with isBack set to true.
427 	if (contact.isDistributionList() && !isBack) {
428 		var callbackHere = this._setContact.bind(this, contact, isGal, oldContact, expandDL, true);
429 		contact.gatherExtraDlStuff(callbackHere);
430 		return;
431 	}
432 
433 	var addrBook = contact.getAddressBook();
434 	var color = addrBook ? addrBook.color : ZmOrganizer.DEFAULT_COLOR[ZmOrganizer.ADDRBOOK];
435 	var subs = {
436 		id: this._htmlElId,
437 		contact: contact,
438 		addrbook: addrBook,
439 		contactHdrClass: (ZmOrganizer.COLOR_TEXT[color] + "Bg"),
440 		isInTrash: (addrBook && addrBook.isInTrash())
441 	};
442 
443 	if (contact.isGroup()) {
444 		this._objectManager.reset();
445 
446 		if (addrBook) {
447 			subs.folderIcon = addrBook.getIcon();
448 			subs.folderName = addrBook.getName();
449 		}
450 
451 		if (contact.isDistributionList()) {
452 			var dlInfo = subs.dlInfo = contact.dlInfo;
453 		}
454 		subs.groupMembers = contact.getAllGroupMembers();
455 		subs.findObjects = this._objectManager.findObjects.bind(this._objectManager);
456 
457 		this._resetVisibility(true);
458 
459 		this._contactView.createHtml("abook.Contacts#SplitViewGroup", subs);
460 
461 		if (contact.isDistributionList()) {
462 			if (this._subscriptionButton) {
463 				this._subscriptionButton.dispose();
464 			}
465 			this._subscriptionButton = new DwtButton({parent:this, parentElement:(this._htmlElId + "_subscriptionButton")});
466 			this._subscriptionButton.setEnabled(true);
467 			this._subscriptionMsg = document.getElementById(this._htmlElId + "_subscriptionMsg");
468 			this._updateSubscriptionButtonAndMsg(contact);
469 			var subListener = new AjxListener(this, this._subscriptionListener, contact);
470 			this._subscriptionButton.addSelectionListener(subListener);
471 		}
472 
473 		var size = this.getSize();
474 		this._sizeChildren(size.x, size.y);
475 	} else {
476 		subs.view = this;
477 		subs.isGal = isGal;
478 		subs.findObjects = this._objectManager.findObjects.bind(this._objectManager);
479 		subs.attrs = contact.getNormalizedAttrs();
480 		subs.expandDL = expandDL;
481 
482 		if (contact.isDL && contact.canExpand) {
483 			this._dlContact = contact;
484 			this._dlScrollDiv = this._dlScrollDiv || document.getElementById(this._contentId);
485 			var respCallback = new AjxCallback(this, this._showDL, [subs]);
486 			contact.getDLMembers(0, null, respCallback);
487 			return;
488 		}
489 		this._showContact(subs);
490 	}
491 
492 	this._setTags();
493 	Dwt.setLoadedTime("ZmContactItem");
494 };
495 
496 ZmContactSplitView.prototype.dispose =
497 function() {
498 	ZmTagsHelper.disposeListeners(this);
499 	DwtComposite.prototype.dispose.apply(this, arguments);
500 };
501 
502 
503 ZmContactSplitView.prototype._showContact =
504 function(subs) {
505 	this._objectManager.reset();
506 	this._resetVisibility(false);
507 
508 	subs.defaultImageUrl = ZmZimbraMail.DEFAULT_CONTACT_ICON;
509 
510 	this._contactView.createHtml("abook.Contacts#SplitView_content", subs);
511 
512 	// notify zimlets that a new contact is being shown.
513 	appCtxt.notifyZimlets("onContactView", [subs.contact, this._htmlElId]);
514 };
515 
516 ZmContactSplitView.prototype._subscriptionListener =
517 function(contact, ev) {
518 	var subscribe = !contact.dlInfo.isMember;
519 	this._subscriptionButton.setEnabled(false);
520 	var respHandler = this._handleSubscriptionResponse.bind(this, contact, subscribe);
521 	contact.toggleSubscription(respHandler);
522 };
523 
524 ZmContactSplitView.prototype._handleSubscriptionResponse =
525 function(contact, subscribe, result) {
526 	var status = result._data.SubscribeDistributionListResponse.status;
527 	var subscribed = status == "subscribed";
528 	var unsubscribed = status == "unsubscribed";
529 	var awaitingApproval = status == "awaiting_approval";
530 	this._subscriptionButton.setEnabled(!awaitingApproval);
531 	if (!awaitingApproval) {
532 		contact.dlInfo.isMember = subscribed;
533 	}
534 	if (subscribed || unsubscribed) {
535 		contact.clearDlInfo();
536 		contact._notify(ZmEvent.E_MODIFY);
537 	}
538 	var msg = subscribed ? ZmMsg.dlSubscribed
539 			: unsubscribed ? ZmMsg.dlUnsubscribed
540 			: awaitingApproval && subscribe ? ZmMsg.dlSubscriptionRequested
541 			: awaitingApproval && !subscribe ? ZmMsg.dlUnsubscriptionRequested
542 			: ""; //should not happen. Keep this as separate case for ease of debug when it does happen somehow.
543 	var dlg = appCtxt.getMsgDialog();
544 	var name = contact.getEmail();
545 	dlg.setMessage(AjxMessageFormat.format(msg, name), DwtMessageDialog.INFO_STYLE);
546 	dlg.popup();
547 
548 };
549 
550 ZmContactSplitView.prototype._updateSubscriptionButtonAndMsg =
551 function(contact) {
552 	var dlInfo = contact.dlInfo;
553 	var policy = dlInfo.isMember ? dlInfo.unsubscriptionPolicy : dlInfo.subscriptionPolicy;
554 	if (policy == ZmContactSplitView.SUBSCRIPTION_POLICY_REJECT) {
555 		this._subscriptionButton.setVisible(false);
556 	}
557 	else {
558 		this._subscriptionButton.setVisible(true);
559 		this._subscriptionButton.setText(dlInfo.isMember ? ZmMsg.dlUnsubscribe: ZmMsg.dlSubscribe);
560 	}
561 	var statusMsg = dlInfo.isOwner && dlInfo.isMember ? ZmMsg.youAreOwnerAndMember
562 			: dlInfo.isOwner ? ZmMsg.youAreOwner
563 			: dlInfo.isMember ? ZmMsg.youAreMember
564 			: "";
565 	if (statusMsg != '') {
566 		statusMsg = "<li>" + statusMsg + "</li>";
567 	}
568 	var actionMsg;
569 	if (!dlInfo.isMember) {
570 		actionMsg =	policy == ZmContactSplitView.SUBSCRIPTION_POLICY_APPROVAL ? ZmMsg.dlSubscriptionRequiresApproval
571 			: policy == ZmContactSplitView.SUBSCRIPTION_POLICY_REJECT ? ZmMsg.dlSubscriptionNotAllowed
572 			: "";
573 	}
574 	else {
575 		actionMsg =	policy == ZmContactSplitView.SUBSCRIPTION_POLICY_APPROVAL ? ZmMsg.dlUnsubscriptionRequiresApproval
576 			: policy == ZmContactSplitView.SUBSCRIPTION_POLICY_REJECT ? ZmMsg.dlUnsubscriptionNotAllowed
577 			: "";
578 
579 	}
580 	if (actionMsg != '') {
581 		actionMsg = "<li>" + actionMsg + "</li>";
582 	}
583 	this._subscriptionMsg.innerHTML = statusMsg + actionMsg;
584 
585 };
586 
587 // returns an object with common properties used for displaying a contact field
588 ZmContactSplitView._getListData =
589 function(data, label, objectType) {
590 	var itemListData = {
591 		id: data.id,
592 		attrs: data.attrs,
593 		labelId: data.id + '_' + label.replace(/[^\w]/g,""),
594 		label: label,
595 		first: true
596 	};
597 	if (objectType) {
598 		itemListData.findObjects = data.findObjects;
599 		itemListData.objectType = objectType;
600 	}
601 	itemListData.isDL = data.contact.isDL;
602 
603 	return itemListData;
604 };
605 
606 ZmContactSplitView._showContactList =
607 function(data, names, typeFunc, hideType) {
608 
609 	data.names = names;
610 	var html = [];
611 	for (var i = 0; i < names.length; i++) {
612 		var name = names[i];
613 		data.name = name;
614 		data.type = (typeFunc && typeFunc(data, name)) || ZmMsg["AB_FIELD_" + name];
615 		data.type = hideType ? "" : data.type;
616 		html.push(ZmContactSplitView._showContactListItem(data));
617 	}
618 
619 	return html.join("");
620 };
621 
622 ZmContactSplitView._showContactListItem =
623 function(data) {
624 
625 	var isEmail = (data.objectType == ZmObjectManager.EMAIL);
626 	var i = 0;
627 	var html = [];
628 	while (true) {
629 		data.name1 = ++i > 1 || ZmContact.IS_ADDONE[data.name] ? data.name + i : data.name;
630 		var values = data.attrs[data.name1];
631 		if (!values) { break; }
632 		data.name1 = AjxStringUtil.htmlEncode(data.name1);
633 		data.type = AjxStringUtil.htmlEncode(data.type);
634 		values = AjxUtil.toArray(values);
635 		for (var j=0; j<values.length; j++) {
636 			var value = values[j];
637 			if (!isEmail) {
638 				value = AjxStringUtil.htmlEncode(value);
639 			}
640 			if (ZmContact.IS_DATE[data.name]) {
641 				var date = ZmEditContactViewOther.parseDate(value);
642 				if (date) {
643 					var includeYear = date.getFullYear() != 0;
644 					var formatter = includeYear ?
645 					    AjxDateFormat.getDateInstance(AjxDateFormat.LONG) : new AjxDateFormat(ZmMsg.formatDateLongNoYear);
646 					value = formatter.format(date);
647 		        	}
648 			}
649 			if (data.findObjects) {
650 				value = data.findObjects(value, data.objectType);
651 			}
652 			if (data.encode) {
653 				value = data.encode(value);
654 			}
655 			data.value = value;
656 
657 			html.push(AjxTemplate.expand("#SplitView_list_item", data));
658 		}
659 		data.first = false;
660 	}
661 
662 	return html.join("");
663 };
664 
665 ZmContactSplitView.showContactEmails =
666 function(data) {
667 	var itemListData = ZmContactSplitView._getListData(data, ZmMsg.emailLabel, ZmObjectManager.EMAIL);
668 	var typeFunc = function(data, name) { return data.isDL && ZmMsg.distributionList; };
669 	return ZmContactSplitView._showContactList(itemListData, ZmEditContactView.LISTS.EMAIL.attrs, typeFunc, !data.isDL);
670 };
671 
672 ZmContactSplitView.showContactPhones =
673 function(data) {
674 	var itemListData = ZmContactSplitView._getListData(data, ZmMsg.phoneLabel, ZmObjectManager.PHONE);
675 	return ZmContactSplitView._showContactList(itemListData, ZmEditContactView.LISTS.PHONE.attrs);
676 };
677 
678 ZmContactSplitView.showContactIMs =
679 function(data) {
680 
681 	var itemListData = ZmContactSplitView._getListData(data, ZmMsg.imLabel);
682 	return ZmContactSplitView._showContactList(itemListData, ZmEditContactView.LISTS.IM.attrs);
683 };
684 
685 ZmContactSplitView.showContactAddresses =
686 function(data) {
687 
688 	var itemListData = ZmContactSplitView._getListData(data, ZmMsg.addressLabel);
689 	var types = {"work":ZmMsg.work, "home":ZmMsg.home, "other":ZmMsg.other};
690 	var prefixes = ZmContact.ADDR_PREFIXES;
691 	var suffixes = ZmContact.ADDR_SUFFIXES;
692 	var html = [];
693 	for (var i = 0; i < prefixes.length; i++) {
694 		var count = 0;
695 		var prefix = prefixes[i];
696 		itemListData.type = types[prefix] || prefix;
697 		while (true) {
698 			count++;
699 			itemListData.address = null;
700 			for (var j = 0; j < suffixes.length; j++) {
701 				var suffix = suffixes[j];
702 				var name = [prefix, suffix, count > 1 ? count : ""].join("");
703 				var value = data.attrs[name];
704 				if (!value) { continue; }
705 				value = AjxStringUtil.htmlEncode(value);
706 				if (!itemListData.address)  {
707 					itemListData.address = {};
708 				}
709 				itemListData.address[suffix] = value.replace(/\n/g,"<br/>");
710 			}
711 			if (!itemListData.address) { break; }
712 			itemListData.name = [prefix, "Address", count > 1 ? count : ""].join("");
713 			html.push(AjxTemplate.expand("#SplitView_address_value", itemListData));
714 			itemListData.first = false;
715 		}
716 	}
717 
718 	return html.join("");
719 };
720 
721 ZmContactSplitView.showContactUrls =
722 function(data) {
723 	var itemListData = ZmContactSplitView._getListData(data, ZmMsg.urlLabel, ZmObjectManager.URL);
724 	var typeFunc = function(data, name) { return ZmMsg["AB_FIELD_" + name.replace("URL", "")]; };
725 	return ZmContactSplitView._showContactList(itemListData, ZmEditContactView.LISTS.URL.attrs, typeFunc);
726 };
727 
728 ZmContactSplitView.showContactOther =
729 function(data) {
730 
731 	var itemListData = ZmContactSplitView._getListData(data, ZmMsg.otherLabel);
732 	itemListData.findObjects = data.findObjects;
733 	var html = [];
734 	html.push(ZmContactSplitView._showContactList(itemListData, ZmEditContactView.LISTS.OTHER.attrs));
735 
736 	// find unknown attributes
737 	var attrs = {};
738 	for (var a in itemListData.attrs) {
739 		var aname = ZmContact.getPrefix(a);
740 		if (aname in ZmContact.IS_IGNORE) { continue; }
741 		attrs[aname] = true;
742 	}
743 	for (var id in ZmEditContactView.ATTRS) {
744 		delete attrs[ZmEditContactView.ATTRS[id]];
745 	}
746 	for (var id in ZmEditContactView.LISTS) {
747 		var list = ZmEditContactView.LISTS[id];
748 		if (!list.attrs) { continue; }
749 		for (var i = 0; i < list.attrs.length; i++) {
750 			delete attrs[list.attrs[i]];
751 		}
752 	}
753 	var prefixes = ZmContact.ADDR_PREFIXES;
754 	var suffixes = ZmContact.ADDR_SUFFIXES;
755 	for (var i = 0; i < prefixes.length; i++) {
756 		for (var j = 0; j < suffixes.length; j++) {
757 			delete attrs[prefixes[i] + suffixes[j]];
758 		}
759 	}
760 
761 	// display custom
762 	for (var a in attrs) {
763 		if (a === "notesHtml") { continue; }
764 		itemListData.name = a;
765 		itemListData.type = AjxStringUtil.capitalizeWords(AjxStringUtil.fromMixed(a));
766 		html.push(ZmContactSplitView._showContactListItem(itemListData));
767 	}
768 
769 	return html.join("");
770 };
771 
772 ZmContactSplitView.showContactNotes =
773 function(data) {
774 
775 	var itemListData = ZmContactSplitView._getListData(data, ZmMsg.notesLabel);
776 	itemListData.encode = AjxStringUtil.nl2br;
777 	itemListData.name = ZmContact.F_notes;
778 	itemListData.names = [ZmContact.F_notes];
779 	return ZmContactSplitView._showContactListItem(itemListData);
780 };
781 
782 ZmContactSplitView.showContactDLMembers =
783 function(data) {
784 
785 	var html = [];
786 	var itemData = {contact:data.contact};
787 	if (data.dl) {
788 		var list = data.dl.list;
789 		var canExpand = data.contact.canExpand && (list.length > ZmContactSplitView.NUM_DL_MEMBERS || data.dl.more);
790 		var lv = data.view._listPart;
791 		var id = lv._expandId = Dwt.getNextId();
792 		var tdStyle = "", onclick = "";
793 		var list1 = [];
794 		var len = Math.min(list.length, ZmContactSplitView.NUM_DL_MEMBERS);
795 		for (var i = 0; i < len; i++) {
796 			list1.push(data.findObjects ? data.findObjects(list[i], ZmObjectManager.EMAIL) : list[i]);
797 		}
798 		if (canExpand) {
799 			if (!data.expandDL) {
800 				list1.push(" ... ");
801 			}
802 			tdStyle = "style='cursor:pointer;'";
803 			var viewId = '"' + data.id + '"';
804 			var doExpand = data.expandDL ? "false" : "true";
805 			onclick = "onclick='ZmContactSplitView.expandDL(" + viewId + ", " + doExpand + ");'";
806 		}
807 		itemData.value = list1.join(", ");
808 		itemData.expandTdText = [tdStyle, onclick].join(" ");
809 		if (!data.expandDL) {
810 			itemData.html = [];
811 			lv._getImageHtml(itemData.html, 0, canExpand ? "NodeCollapsed" : null, id);
812 			html.push("<tr valign='top'>");
813 			html.push(AjxTemplate.expand("abook.Contacts#SplitView_dlmember-collapsed", itemData));
814 			html.push("</tr>");
815 		} else {
816 			itemData.first = true;
817 			for (var i = 0, len = list.length; i < len; i++) {
818 				itemData.value = list[i];
819 				if (data.findObjects) {
820 					itemData.value = data.findObjects(itemData.value, ZmObjectManager.EMAIL);
821 				}
822 				itemData.html = [];
823 				lv._getImageHtml(itemData.html, 0, itemData.first ? "NodeExpanded" : null, id);
824 				var rowIdText = "";
825 				if (i == len - 1) {
826 					var rowId = data.view._dlLastRowId = Dwt.getNextId();
827 					rowIdText = "id='" + rowId + "'";
828 				}
829 				html.push("<tr valign='top' " + rowIdText + ">");
830 				html.push(AjxTemplate.expand("abook.Contacts#SplitView_dlmember-expanded", itemData));
831 				html.push("</tr>");
832 				itemData.first = false;
833 			}
834 		}
835 	}
836 
837 	return html.join("");
838 };
839 
840 /**
841  * Displays contact group
842  * @param data  {object}
843  * @return html {String} html representation of group
844  */
845 ZmContactSplitView.showContactGroup =
846 function(data) {
847 	var html = []; 
848 	if (!AjxUtil.isArray(data.groupMembers)) {
849 		return "";
850 	}
851 	for (var i = 0; i < data.groupMembers.length; i++) {
852 		var member = data.groupMembers[i];
853 		var itemListData = {};
854 		var contact = member.__contact;
855 		if (contact) {
856 			itemListData.imageUrl = contact.getImageUrl();
857 			itemListData.defaultImageUrl = ZmZimbraMail.DEFAULT_CONTACT_ICON;
858 			itemListData.imgClassName = contact.getIconLarge();
859 			itemListData.email = data.findObjects(contact.getEmail(), ZmObjectManager.EMAIL, true);
860 			itemListData.title = data.findObjects(contact.getAttr(ZmContact.F_jobTitle), ZmObjectManager.TITLE, true);
861 			itemListData.phone = data.findObjects(contact.getPhone(), ZmObjectManager.PHONE, true);
862 			var isPhonetic = appCtxt.get(ZmSetting.PHONETIC_CONTACT_FIELDS);
863 			var fullnameHtml = contact.getFullNameForDisplay(isPhonetic);
864 			if (!isPhonetic) {
865 				fullnameHtml = AjxStringUtil.htmlEncode(fullnameHtml);
866 			}
867 			itemListData.fullName = fullnameHtml;
868 		}
869 		else {
870 			itemListData.imgClassName = "PersonInline_48";
871 			itemListData.email = data.findObjects(member.value, ZmObjectManager.EMAIL, true);
872 		}
873 		html.push(AjxTemplate.expand("abook.Contacts#SplitView_group", itemListData));
874 	}
875 	return html.join("");
876 	
877 };
878 
879 ZmContactSplitView.prototype._showDL =
880 function(subs, result) {
881 
882 	subs.dl = this._distributionList = result;
883 	this._showContact(subs);
884 	this._setTags();
885 	if (!this._rowHeight) {
886 		var table = document.getElementById(this._detailsId);
887 		if (table) {
888 			this._rowHeight = Dwt.getSize(table.rows[0]).y;
889 		}
890 	}
891 
892 	if (subs.expandDL) {
893 		Dwt.setHandler(this._dlScrollDiv, DwtEvent.ONSCROLL, ZmContactSplitView.handleDLScroll);
894 	}
895 };
896 
897 /**
898  * @private
899  */
900 ZmContactSplitView.prototype._resetVisibility =
901 function(isGroup) {
902 };
903 
904 /**
905  * @private
906  */
907 ZmContactSplitView.prototype._setTags =
908 function() {
909 	//use the helper to get the tags.
910 	var tagsHtml = ZmTagsHelper.getTagsHtml(this._item, this);
911 	this._setTagsHtml(tagsHtml);
912 };
913 
914 /**
915  * @private
916  */
917 ZmContactSplitView.prototype._clearTags =
918 function() {
919 	this._setTagsHtml("");
920 };
921 
922 /**
923  * note this is called from ZmTagsHelper
924  * @param html
925  */
926 ZmContactSplitView.prototype._setTagsHtml =
927 function(html) {
928 	var tagCell = document.getElementById(this._tagCellId);
929 	if (!tagCell) { return; }
930 	tagCell.innerHTML = html;
931 };
932 
933 
934 ZmContactSplitView.prototype._sashCallback = function(delta) {
935 	var sashWidth = this._sash.getSize().x;
936 	var totalWidth = Dwt.getSize(this._splitviewCell).x;
937 
938 	var origListWidth = this._listPart.getSize().x;
939 	var newListWidth = origListWidth + delta;
940 	var newContentPos = newListWidth + sashWidth;
941 	var newContentWidth = totalWidth - newContentPos;
942 
943 	if (delta < 0 && newListWidth <= ZmContactSplitView.LIST_MIN_WIDTH) {
944 		newListWidth = ZmContactSplitView.LIST_MIN_WIDTH;
945 		newContentPos = newListWidth + sashWidth;
946 		newContentWidth = totalWidth - newContentPos;
947 	} else if (delta > 0 && newContentWidth <= ZmContactSplitView.CONTENT_MIN_WIDTH) {
948 		newContentWidth = ZmContactSplitView.CONTENT_MIN_WIDTH;
949 		newContentPos = totalWidth - newContentWidth;
950 		newListWidth = newContentPos - sashWidth;
951 	}
952 		
953 	delta = newListWidth - origListWidth;
954 	
955 	this._listPart.setSize(newListWidth, Dwt.DEFAULT);
956 	Dwt.setBounds(this._contentCell, newContentPos, Dwt.DEFAULT, newContentWidth, Dwt.DEFAULT);
957 
958 	return delta;
959 };
960 
961 /**
962  * View for displaying the contact information. Provides events for enabling text selection.
963  * @param {Object}  params      hash of params:
964  *                  parent      parent control
965  *                  controller  owning controller
966  */
967 ZmContactView = function(params) {
968 	DwtComposite.call(this, {parent:params.parent});
969 	this._controller = params.controller;
970 	this._tabGroup = new DwtTabGroup('ZmContactView');
971 	this.addListener(DwtEvent.ONSELECTSTART, this._selectStartListener.bind(this));
972 	this._setMouseEventHdlrs();
973 };
974 ZmContactView.prototype = new DwtControl;
975 ZmContactView.prototype.constructor = ZmContactView;
976 ZmContactView.prototype.isZmContactView = true;
977 ZmContactView.prototype.role = 'document';
978 ZmContactView.prototype.toString = function() { return "ZmContactView"; };
979 
980 ZmContactView.prototype.getTabGroupMember = 
981 function() {
982 	return this._tabGroup;
983 };
984 
985 ZmContactView.prototype._selectStartListener =
986 function(ev) {
987 	// reset mouse event to propagate event to browser (allows text selection)
988 	ev._stopPropagation = false;
989 	ev._returnValue = true;
990 };
991 
992 ZmContactView.prototype.clear = function() {
993 	this.getTabGroupMember().removeAllMembers();
994 	Dwt.removeChildren(this.getHtmlElement());
995 };
996 
997 ZmContactView.prototype.createHtml = function(templateid, subs) {
998 	this._createHtmlFromTemplate(templateid, subs);
999 
1000 	// add the header row and all objects to the tab order
1001 	var rows = Dwt.byClassName('rowValue', this.getHtmlElement());
1002 
1003 	this.getTabGroupMember().removeAllMembers();
1004 	this.getTabGroupMember().addMember(rows[0]);
1005 
1006 	AjxUtil.foreach(rows, this._makeRowFocusable.bind(this));
1007 };
1008 
1009 ZmContactView.prototype._makeRowFocusable = function(row) {
1010 	this._makeFocusable(row);
1011 
1012 	var objects = Dwt.byClassName('Object', row);
1013 
1014 	for (var i = 0; i < objects.length; i++) {
1015 		this._makeFocusable(objects[i]);
1016 		this.getTabGroupMember().addMember(objects[i]);
1017 
1018 		objects[i].setAttribute('aria-describedby', row.getAttribute('aria-labelledby'));
1019 	}
1020 };
1021 
1022 /**
1023  * Creates a simple view.
1024  * @class
1025  * This class represents a simple contact list view (contains only full name).
1026  * 
1027  * @param	{Hash}	params		a hash of parameters
1028  * @extends		ZmContactsBaseView
1029  */
1030 ZmContactSimpleView = function(params) {
1031 
1032 	if (arguments.length == 0) { return; }
1033 
1034 	this._view = params.view = params.controller.getCurrentViewId();
1035 	params.className = "ZmContactSimpleView";
1036 	ZmContactsBaseView.call(this, params);
1037 
1038 	this._normalClass = DwtListView.ROW_CLASS + " SimpleContact";
1039 	this._selectedClass = [DwtListView.ROW_CLASS, DwtCssStyle.SELECTED].join("-");
1040 };
1041 
1042 ZmContactSimpleView.prototype = new ZmContactsBaseView;
1043 ZmContactSimpleView.prototype.constructor = ZmContactSimpleView;
1044 
1045 ZmContactSimpleView.prototype.isZmContactSimpleView = true;
1046 ZmContactSimpleView.prototype.toString = function() { return "ZmContactSimpleView"; };
1047 
1048 /**
1049  * Sets the list.
1050  * 
1051  * @param	{ZmContactList}		list		the list
1052  * @param	{String}	defaultColumnSort		the sort field
1053  * @param	{String}	folderId		the folder id
1054  * @param	{Boolean}	isSearchResults	is this a search tab?
1055  */
1056 ZmContactSimpleView.prototype.set =
1057 function(list, defaultColumnSort, folderId, isSearchResults) {
1058 	var fid = folderId || this._controller.getFolderId();
1059 	ZmContactsBaseView.prototype.set.call(this, list, defaultColumnSort, fid);
1060 
1061 	if (!(this._list instanceof AjxVector) || this._list.size() == 0) {
1062 		this.parent.clear();
1063 	}
1064 
1065 	this.parent.showAlphabetBar(!isSearchResults);
1066 	this.parent.enableAlphabetBar(fid != ZmOrganizer.ID_DLS);
1067 };
1068 
1069 /**
1070  * Sets the selection.
1071  * 
1072  * @param	{Object}	item		the item
1073  * @param	{Boolean}	skipNotify	<code>true</code> to skip notification
1074  */
1075 ZmContactSimpleView.prototype.setSelection =
1076 function(item, skipNotify) {
1077 	// clear the right, content pane if no item to select
1078 	if (!item) {
1079 		this.parent.clear();
1080 	}
1081 
1082 	ZmContactsBaseView.prototype.setSelection.call(this, item, skipNotify);
1083 };
1084 
1085 /**
1086  * @private
1087  */
1088 ZmContactSimpleView.prototype._setNoResultsHtml =
1089 function() {
1090 
1091 	var	div = document.createElement("div");
1092 
1093 	var isSearch = this._controller._contactSearchResults;
1094 	if (isSearch){
1095 		isSearch = !(this._controller._currentSearch && this._controller._currentSearch.folderId);
1096 	}
1097 	//bug:28365  Show custom "No Results" for Search.
1098 	if ((isSearch || this._folderId == ZmFolder.ID_TRASH) && AjxTemplate.getTemplate("abook.Contacts#SimpleView-NoResults-Search")) {
1099 		div.innerHTML = AjxTemplate.expand("abook.Contacts#SimpleView-NoResults-Search");
1100 	} else {
1101 		// Shows "No Results", unless the skin has overridden to show links to plaxo.
1102 		div.innerHTML = AjxTemplate.expand("abook.Contacts#SimpleView-NoResults");
1103 	}
1104 	this._addRow(div);
1105 
1106 	this.parent.clear();
1107 };
1108 
1109 /**
1110  * @private
1111  */
1112 ZmContactSimpleView.prototype._changeListener =
1113 function(ev) {
1114 	ZmContactsBaseView.prototype._changeListener.call(this, ev);
1115 
1116 	// bug fix #14874 - if moved to trash, show strike-thru
1117 	var folderId = this._controller.getFolderId();
1118 	if (!folderId && ev.event == ZmEvent.E_MOVE) {
1119 		var contact = ev._details.items[0];
1120 		var folder = appCtxt.getById(contact.folderId);
1121 		var row = this._getElement(contact, ZmItem.F_ITEM_ROW);
1122 		if (row) {
1123 			row.className = (folder && folder.isInTrash()) ? "Trash" : "";
1124 		}
1125 	}
1126 };
1127 
1128 /**
1129  * @private
1130  */
1131 ZmContactSimpleView.prototype._modifyContact =
1132 function(ev) {
1133 	ZmContactsBaseView.prototype._modifyContact.call(this, ev);
1134 
1135 	if (ev.getDetail("fileAsChanged")) {
1136 		var selected = this.getSelection()[0];
1137 		this._layout();
1138 		this.setSelection(selected, true);
1139 	}
1140 };
1141 
1142 /**
1143  * @private
1144  */
1145 ZmContactSimpleView.prototype._layout =
1146 function() {
1147 	// explicitly remove each child (setting innerHTML causes mem leak)
1148 	while (this._parentEl.hasChildNodes()) {
1149 		cDiv = this._parentEl.removeChild(this._parentEl.firstChild);
1150 		this._data[cDiv.id] = null;
1151 	}
1152 
1153 	var now = new Date();
1154 	var size = this._list.size();
1155 	for (var i = 0; i < size; i++) {
1156 		var item = this._list.get(i);
1157 		var div = item ? this._createItemHtml(item, {now:now}) : null;
1158 		if (div) {
1159 			this._addRow(div);
1160 		}
1161 	}
1162 };
1163 
1164 ZmContactSimpleView.prototype.useListElement =
1165 function() {
1166 	return true;
1167 }
1168 
1169 /**
1170  * A contact is normally displayed in a list view with no headers, and shows
1171  * just an icon and name.
1172  *
1173  * @param {ZmContact}	contact	the contact to display
1174  * @param {Hash}	params	a hash of optional parameters
1175  * 
1176  * @private
1177  */
1178 ZmContactSimpleView.prototype._createItemHtml =
1179 function(contact, params, asHtml, count) {
1180 
1181 	params = params || {};
1182 
1183 	var htmlArr = [];
1184 	var idx = 0;
1185 	if (!params.isDragProxy) {
1186 		params.divClass = this._normalClass;
1187 	}
1188 	if (asHtml) {
1189 		idx = this._getDivHtml(contact, params, htmlArr, idx, count);
1190 	} else {
1191 		var div = this._getDiv(contact, params);
1192 	}
1193 	var folder = this._folderId && appCtxt.getById(this._folderId);
1194 	if (div) {
1195 		if (params.isDragProxy) {
1196 			div.style.width = "175px";
1197 			div.style.padding = "4px";
1198 		}
1199 	}
1200 
1201 	idx = this._getRow(htmlArr, idx, contact, params);
1202 
1203 	// checkbox selection
1204 	if (appCtxt.get(ZmSetting.SHOW_SELECTION_CHECKBOX)) {
1205 		idx = this._getImageHtml(htmlArr, idx, "CheckboxUnchecked", this._getFieldId(contact, ZmItem.F_SELECTION));
1206 	}
1207 
1208 	// icon
1209 	htmlArr[idx++] = AjxImg.getImageHtml(contact.getIcon(folder), null, "id=" + this._getFieldId(contact, "type"),null, null, ["ZmContactIcon"]);
1210 
1211 	// file as
1212 	htmlArr[idx++] = "<div id='" + this._getFieldId(contact, "fileas") + "'>";
1213 	htmlArr[idx++] = AjxStringUtil.htmlEncode(contact.getFileAs() || contact.getFileAsNoName());
1214 	htmlArr[idx++] = "</div>";
1215 	htmlArr[idx++] = "<div class='ZmListFlagsWrapper'>";
1216 
1217 	if (!params.isDragProxy) {
1218 		// if read only, show lock icon in place of the tag column since we dont
1219 		// currently support tags for "read-only" contacts (i.e. shares)
1220 		var isLocked = folder ? folder.link && folder.isReadOnly() : contact.isLocked();
1221 		if (isLocked) {
1222 			htmlArr[idx++] = AjxImg.getImageHtml("ReadOnly");
1223 		} else if (!contact.isReadOnly() && appCtxt.get(ZmSetting.TAGGING_ENABLED)) {
1224 			// otherwise, show tag if there is one
1225 			idx = this._getImageHtml(htmlArr, idx, contact.getTagImageInfo(), this._getFieldId(contact, ZmItem.F_TAG), ["Tag"]);
1226 		}
1227 	}
1228 
1229 	htmlArr[idx++] = "</div></div></li>";
1230 
1231 	if (div) {
1232 		div.innerHTML = htmlArr.join("");
1233 		return div;
1234 	} else {
1235 		return htmlArr.join("");
1236 	}
1237 };
1238 
1239 /**
1240  * @private
1241  */
1242 ZmContactSimpleView.prototype._getToolTip =
1243 function(params) {
1244 
1245 	var ttParams = {
1246 		contact:		params.item,
1247 		ev:				params.ev
1248 	};
1249 	var ttCallback = new AjxCallback(this,
1250 		function(callback) {
1251 			appCtxt.getToolTipMgr().getToolTip(ZmToolTipMgr.PERSON, ttParams, callback);
1252 		});
1253 	return {callback:ttCallback};
1254 };
1255 
1256 /**
1257  * @private
1258  */
1259 ZmContactSimpleView.prototype._getDateToolTip =
1260 function(item, div) {
1261 	div._dateStr = div._dateStr || this._getDateToolTipText(item.modified, ["<b>", ZmMsg.lastModified, "</b><br>"].join(""));
1262 	return div._dateStr;
1263 };
1264