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 ZmMailListView = function(params) {
 25 
 26 	if (arguments.length == 0) { return; }
 27 
 28 	params.pageless = true;
 29 	ZmListView.call(this, params);
 30 
 31 	this._folderId = null;
 32 	this._selectAllEnabled = true;
 33 
 34 	this._isMultiColumn = this.isMultiColumn();
 35 	if (!this._isMultiColumn) {
 36 		this._normalClass = ZmMailListView.ROW_DOUBLE_CLASS;
 37 	}
 38 
 39 	this._disallowSelection[ZmItem.F_READ] = true;
 40 };
 41 
 42 ZmMailListView.prototype = new ZmListView;
 43 ZmMailListView.prototype.constructor = ZmMailListView;
 44 
 45 ZmMailListView.prototype.isZmMailListView = true;
 46 ZmMailListView.prototype.toString = function() { return "ZmMailListView"; };
 47 
 48 // Consts
 49 ZmMailListView.ROW_DOUBLE_CLASS	= "RowDouble";
 50 
 51 ZmMailListView.FIRST_ITEM	= -1;
 52 ZmMailListView.LAST_ITEM	= -2;
 53 
 54 ZmMailListView.SINGLE_COLUMN_SORT = [
 55 	{field:ZmItem.F_FROM,	msg:"from"		},
 56 	{field:ZmItem.F_TO,		msg:"to"		},
 57 	{field:ZmItem.F_SUBJECT,msg:"subject"	},
 58 	{field:ZmItem.F_SIZE,	msg:"size"		},
 59 	{field:ZmItem.F_DATE,	msg:"date"		},
 60     {field:ZmItem.F_ATTACHMENT, msg:"attachment" },
 61     {field:ZmItem.F_FLAG, msg:"flag" },
 62     {field:ZmItem.F_PRIORITY, msg:"priority" },
 63 	{field:ZmItem.F_READ, msg:"readUnread" }
 64 ];
 65 
 66 ZmMailListView.SORTBY_HASH = [];
 67 ZmMailListView.SORTBY_HASH[ZmSearch.NAME_ASC] = {field:ZmItem.F_FROM, msg:"from"};
 68 ZmMailListView.SORTBY_HASH[ZmSearch.NAME_DESC] = {field:ZmItem.F_FROM, msg:"from"};
 69 ZmMailListView.SORTBY_HASH[ZmSearch.SUBJ_ASC] = {field:ZmItem.F_SUBJECT, msg:"subject"};
 70 ZmMailListView.SORTBY_HASH[ZmSearch.SUBJ_DESC] = {field:ZmItem.F_SUBJECT, msg:"subject"};
 71 ZmMailListView.SORTBY_HASH[ZmSearch.SIZE_ASC] = {field:ZmItem.F_SIZE, msg:"size"};
 72 ZmMailListView.SORTBY_HASH[ZmSearch.SIZE_DESC] = {field:ZmItem.F_SIZE, msg:"size"};
 73 ZmMailListView.SORTBY_HASH[ZmSearch.DATE_ASC] = {field:ZmItem.F_DATE, msg:"date"};
 74 ZmMailListView.SORTBY_HASH[ZmSearch.DATE_DESC] = {field:ZmItem.F_DATE, msg:"date"};
 75 ZmMailListView.SORTBY_HASH[ZmSearch.ATTACH_ASC] = {field:ZmItem.F_ATTACHMENT, msg:"attachment"};
 76 ZmMailListView.SORTBY_HASH[ZmSearch.ATTACH_DESC] = {field:ZmItem.F_ATTACHMENT, msg:"attachment"};
 77 ZmMailListView.SORTBY_HASH[ZmSearch.FLAG_ASC] = {field:ZmItem.F_FLAG, msg:"flag"};
 78 ZmMailListView.SORTBY_HASH[ZmSearch.FLAG_DESC] = {field:ZmItem.F_FLAG, msg:"flag"};
 79 ZmMailListView.SORTBY_HASH[ZmSearch.MUTE_ASC] = {field:ZmItem.F_MUTE, msg:"mute"};
 80 ZmMailListView.SORTBY_HASH[ZmSearch.MUTE_DESC] = {field:ZmItem.F_MUTE, msg:"mute"};
 81 ZmMailListView.SORTBY_HASH[ZmSearch.READ_ASC] = {field:ZmItem.F_READ, msg:"readUnread"};
 82 ZmMailListView.SORTBY_HASH[ZmSearch.READ_DESC] = {field:ZmItem.F_READ, msg:"readUnread"};
 83 ZmMailListView.SORTBY_HASH[ZmSearch.PRIORITY_ASC] = {field:ZmItem.F_PRIORITY, msg:"priority"};
 84 ZmMailListView.SORTBY_HASH[ZmSearch.PRIORITY_DESC] = {field:ZmItem.F_PRIORITY, msg:"priority"};
 85 ZmMailListView.SORTBY_HASH[ZmSearch.RCPT_ASC] = {field:ZmItem.F_TO, msg:"to"};
 86 ZmMailListView.SORTBY_HASH[ZmSearch.RCPT_DESC] = {field:ZmItem.F_TO, msg:"to"};
 87 
 88 
 89 // Public methods
 90 
 91 
 92 // Reset row style
 93 ZmMailListView.prototype.markUIAsMute =
 94 function(item) {
 95     //Removed
 96 };
 97 
 98 // Reset row style
 99 ZmMailListView.prototype.markUIAsRead =
100 function(item, oldValue) {
101 	this._setImage(item, ZmItem.F_READ, item.getReadIcon(), this._getClasses(ZmItem.F_READ));
102 
103 	var newCssClass = this._getRowClass(item);
104 	var oldCssClass = this._getRowClassValue(oldValue);
105 	var oldCssClass = this._getRowClassValue(oldValue);
106 	var row = this._getElement(item, ZmItem.F_ITEM_ROW);
107 	if (row) {
108 		if (oldCssClass) {
109 			$(row).removeClass(oldCssClass);
110 		}
111 		if (newCssClass) {
112 			$(row).addClass(newCssClass);
113 		}
114 	}
115 	this._controller._checkKeepReading();
116 };
117 
118 ZmMailListView.prototype.set =
119 function(list, sortField) {
120 
121 	var s = this._controller._activeSearch && this._controller._activeSearch.search;
122 	this._folderId = s && s.folderId;
123     if (this._folderId) {
124         this._group = this.getGroup(this._folderId);
125     }
126 
127 	var sortBy = s && s.sortBy;
128 	if (sortBy) {
129 		var column;
130 		if (sortBy == ZmSearch.SUBJ_DESC || sortBy == ZmSearch.SUBJ_ASC) {
131 			column = ZmItem.F_SUBJECT;
132 		} else if (sortBy == ZmSearch.DATE_DESC || sortBy == ZmSearch.DATE_ASC) {
133 			column = ZmItem.F_DATE;
134 		} else if (sortBy == ZmSearch.NAME_DESC || sortBy == ZmSearch.NAME_ASC) {
135 			column = ZmItem.F_FROM;
136 		} else if (sortBy == ZmSearch.SIZE_DESC || sortBy == ZmSearch.SIZE_ASC) {
137 			column = ZmItem.F_SIZE;
138 		}
139 		if (column) {
140 			var sortByAsc = (sortBy == ZmSearch.SUBJ_ASC || sortBy == ZmSearch.DATE_ASC || sortBy == ZmSearch.NAME_ASC || sortBy == ZmSearch.SIZE_ASC);
141 			this.setSortByAsc(column, sortByAsc);
142 		}
143 	}
144 
145 
146     ZmListView.prototype.set.apply(this, arguments);
147 
148     this.markDefaultSelection(list);
149 };
150 
151 
152 ZmMailListView.prototype.markDefaultSelection =
153 function(list) {
154 	if(window.defaultSelection) {
155 		var sel = [];
156 		var a = list.getArray();
157 		for (var i in a) {
158 			if (window.defaultSelection[a[i].id]) {
159 				sel.push(a[i]);
160 			}
161 		}
162 		if (sel.length > 0) {
163 			this.setSelectedItems(sel);
164 		}
165 		window.defaultSelection = null;
166 	}
167 };
168 
169 ZmMailListView.prototype.handleKeyAction =
170 function(actionCode, ev) {
171 
172 	switch (actionCode) {
173 		case DwtKeyMap.SELECT_ALL:
174 			ZmListView.prototype.handleKeyAction.apply(this, arguments);
175 			var ctlr = this._controller;
176 			ctlr._resetOperations(ctlr.getCurrentToolbar(), this.getSelectionCount());
177 			return true;
178 
179 		case DwtKeyMap.SELECT_NEXT:
180 		case DwtKeyMap.SELECT_PREV:
181 			// Block widget shortcut for space since we want to handle it as app shortcut.
182 			if (ev.charCode === 32) {
183 				return false;
184 			}
185 			this._controller.lastListAction = actionCode;
186 		
187 		default:
188 			return ZmListView.prototype.handleKeyAction.apply(this, arguments);
189 	}
190 };
191 
192 ZmMailListView.prototype.getTitle =
193 function() {
194 	var search = this._controller._activeSearch ? this._controller._activeSearch.search : null;
195 	return search ? search.getTitle() : "";
196 };
197 
198 ZmMailListView.prototype.replenish = 
199 function(list) {
200 	DwtListView.prototype.replenish.call(this, list);
201 	this._resetColWidth();
202 };
203 
204 ZmMailListView.prototype.resetSize =
205 function(newWidth, newHeight) {
206 	this.setSize(newWidth, newHeight);
207 	var margins = this.getMargins();
208 	var listInsets = Dwt.getInsets(this._parentEl);
209 
210 	if (newWidth !== Dwt.DEFAULT) {
211 		newWidth -= margins.left + margins.right;
212 		newWidth -= listInsets.left + listInsets.right;
213 	}
214 
215 	if (newHeight !== Dwt.DEFAULT) {
216 		newHeight -= Dwt.getOuterSize(this._listColDiv).y;
217 		newHeight -= margins.top + margins.bottom;
218 		newHeight -= listInsets.top + listInsets.bottom;
219 	}
220 
221 	Dwt.setSize(this._parentEl, newWidth, newHeight);
222 };
223 
224 ZmMailListView.prototype.calculateMaxEntries =
225 function() {
226 	return (Math.floor(this._parentEl.clientHeight / (this._isMultiColumn ? 20 : 40)) + 5);
227 };
228 
229 /**
230  * Returns true if the reading pane is turned off or set to bottom. We use this
231  * call to tell the UI whether to re-render the listview with multiple columns
232  * or a single column (for right-pane).
233  */
234 ZmMailListView.prototype.isMultiColumn =
235 function() {
236 	return !this._controller.isReadingPaneOnRight();
237 };
238 
239 
240 ZmMailListView.prototype._getExtraStyle =
241 function(item,start,end) {
242 	if (!appCtxt.get(ZmSetting.COLOR_MESSAGES)) {
243 		return null;
244 	}
245 	var color = item.getColor && item.getColor();
246 	if (!color) {
247 		return null;
248 	}
249 	start = start || 0.75;
250 	end = end || 0.25;
251 
252 	return Dwt.createLinearGradientCss(AjxColor.lighten(color, start), AjxColor.lighten(color, end), "v");
253 };
254 
255 
256 ZmMailListView.prototype._getAbridgedContent =
257 function(item, colIdx) {
258 	// override me
259 };
260 
261 ZmMailListView.prototype._getListFlagsWrapper =
262 function(htmlArr, idx, item) {
263 	htmlArr[idx++] = "<div class='ZmListFlagsWrapper'";
264 	//compute the start and end of gradient based on height of this div and its position
265 	var extraStyle = this._getExtraStyle(item,0.49,0.33);
266 	if (extraStyle) {
267 		htmlArr[idx++] = " style='" + extraStyle + ";'>";
268 	} else {
269 		htmlArr[idx++] = ">";
270 	}
271 	return idx;
272 };
273 
274 //apply colors to from and subject cells via zimlet
275 ZmMailListView.prototype._getStyleViaZimlet =
276 function(field, item) {
277 	if (field != "fr" && field != "su" && field != "st")
278 		return "";
279 
280 	if (appCtxt.zimletsPresent() && this._ignoreProcessingGetMailCellStyle == undefined) {
281 		if (!this._zimletMgr) {
282 			this._zimletMgr = appCtxt.getZimletMgr();//cache zimletMgr
283 		}
284 		var style = this._zimletMgr.processARequest("getMailCellStyle", item, field);
285 		if (style != undefined && style != null) {
286 			return style;//set style
287 		} else if (style == null && this._zimletMgr.isLoaded()) {
288 			//zimlet not available or disabled, set _ignoreProcessingGetMailCellStyle to true
289 			//to ignore this entire section for this session
290 			this._ignoreProcessingGetMailCellStyle = true;
291 		}
292 	}
293 	return "";
294 };
295 
296 
297 ZmMailListView.prototype._getAbridgedCell =
298 function(htmlArr, idx, item, field, colIdx, width, attr, classes) {
299 	var params = {};
300 	classes = classes || [];
301 
302 	/* TODO: Find an alternate way for Zimlets to add styles to the field.
303 	htmlArr[idx++] = this._getStyleViaZimlet(field, item);
304 	*/
305 
306 	var className = this._getCellClass(item, field, params);
307 	if (className) {
308 		classes.push(className);
309 	}
310 	idx = this._getCellContents(htmlArr, idx, item, field, colIdx, params, classes);
311 
312 	return idx;
313 };
314 
315 ZmMailListView.prototype._getCellContents =
316 function(htmlArr, idx, item, field, colIdx, params, classes) {
317 	if (field == ZmItem.F_ACCOUNT) {
318 		idx = this._getImageHtml(htmlArr, idx, item.getAccount().getIcon(), this._getFieldId(item, field), classes);
319 	} 
320 	else if (field == ZmItem.F_DATE) {
321 		var date = AjxDateUtil.computeDateStr(params.now || new Date(), item.date);
322 		htmlArr[idx++] = "<div id='";
323 		htmlArr[idx++] = this._getFieldId(item, field);
324 		htmlArr[idx++] = "' ";
325 		if (!this.isMultiColumn()) {
326 			//compute the start and end of gradient based on height of this div and its position
327 			var extraStyle = this._getExtraStyle(item,0.69,0.55);
328 			if (extraStyle) {
329 				htmlArr[idx++] = " style='" + extraStyle + "'";
330 			}
331 		}
332 		htmlArr[idx++] = AjxUtil.getClassAttr(classes);
333 		htmlArr[idx++] = ">" + date + "</div>";
334 	}
335 	else {
336 		idx = ZmListView.prototype._getCellContents.apply(this, arguments);
337 	}
338 
339 	return idx;
340 };
341 
342 /**
343  * Called by the controller whenever the reading pane preference changes
344  * 
345  * @private
346  */
347 ZmMailListView.prototype.reRenderListView =
348 function() {
349 	var isMultiColumn = this.isMultiColumn();
350 	if (isMultiColumn != this._isMultiColumn) {
351 		this._saveState({selection:true, focus:true, scroll:true, expansion:true});
352 		this._isMultiColumn = isMultiColumn;
353 		this.headerColCreated = false;
354 		this._headerList = this._getHeaderList();
355 		this._rowHeight = null;
356 		this._normalClass = isMultiColumn ? DwtListView.ROW_CLASS : ZmMailListView.ROW_DOUBLE_CLASS;
357 		var list = this.getList() || (new AjxVector());
358         this.clearGroupSections(this._folderId);
359 		this.set(list.clone());
360 		this._restoreState();
361 		this._resetFromColumnLabel();
362 	}
363 };
364 
365 // Private / protected methods
366 
367 ZmMailListView.prototype._getLabelForField =
368 function(item, field) {
369 	switch (field) {
370 	case ZmItem.F_READ:
371 		// usually included in the status tooltip
372 		break;
373 
374 	case ZmItem.F_FLAG:
375 		return item.isFlagged ? ZmMsg.flagged : '';
376 
377 	case ZmItem.F_ATTACHMENT:
378 		return item.hasAttach && ZmMsg.hasAttachment;
379 
380 	case ZmItem.F_STATUS:
381 		return item.getStatusTooltip();
382 
383 	case ZmItem.F_SUBJECT:
384 		return item.subject || ZmMsg.noSubject;
385 
386 	case ZmItem.F_PRIORITY:
387 		if (item.isLowPriority) {
388 			return ZmMsg.priorityLow;
389 		} else if (item.isHighPriority) {
390 			return ZmMsg.priorityHigh;
391 		}
392 
393 		break;
394 
395 	case ZmItem.F_TAG:
396 		if (item.tags.length > 0) {
397 			var tags = item.tags.join(' & ');
398 			return AjxMessageFormat.format(ZmMsg.taggedAs, [tags]);
399 		}
400 
401 		break;
402 
403 	case ZmItem.F_DATE:
404 		return AjxDateUtil.computeWordyDateStr(new Date(), item.date);
405 
406 	case ZmItem.F_FROM:
407 		var addrtype = this._isOutboundFolder() ?
408 			AjxEmailAddress.TO : AjxEmailAddress.FROM;
409 		var participants = item.getAddresses(addrtype) || item.participants || new AjxVector();
410 		var addrs = []
411 
412 		if (participants.size() <= 0) {
413 			return AjxStringUtil.stripTags(ZmMsg.noRecipients);
414 		}
415 
416 		for (var i = 0; i < Math.min(participants.size(), 3); i++) {
417 			addrs.push(participants.get(i).toString(true, true));
418 		}
419 
420 		return addrs.join(", ");
421 
422 	case ZmItem.F_SIZE:
423 		if (item.size) {
424 			return AjxUtil.formatSize(item.size);
425 		}
426 
427 		break;
428 	}
429 
430 	return ZmListView.prototype._getLabelForField.apply(this, arguments);
431 };
432 
433 ZmMailListView.prototype._initHeaders =
434 function() {
435 	if (!this._headerInit) {
436 		this._headerInit = {};
437 		this._headerInit[ZmItem.F_SELECTION]	= {icon:"CheckboxUnchecked", width:ZmListView.COL_WIDTH_ICON, name:ZmMsg.selection, precondition:ZmSetting.SHOW_SELECTION_CHECKBOX, cssClass:"ZmMsgListColSelection"};
438 		this._headerInit[ZmItem.F_FLAG]			= {icon:"FlagRed", width:ZmListView.COL_WIDTH_ICON, name:ZmMsg.flag, sortable:ZmItem.F_FLAG, noSortArrow:true, precondition:ZmSetting.FLAGGING_ENABLED, cssClass:"ZmMsgListColFlag"};
439 		this._headerInit[ZmItem.F_PRIORITY]		= {icon:"PriorityHigh_list", width:ZmListView.COL_WIDTH_NARROW_ICON, name:ZmMsg.priority, sortable:ZmItem.F_PRIORITY, noSortArrow:true, precondition:ZmSetting.MAIL_PRIORITY_ENABLED, cssClass:"ZmMsgListColPriority"};
440 		this._headerInit[ZmItem.F_TAG]			= {icon:"Tag", width:ZmListView.COL_WIDTH_ICON, name:ZmMsg.tag, precondition:ZmSetting.TAGGING_ENABLED, cssClass:"ZmMsgListColTag"};
441 		this._headerInit[ZmItem.F_ACCOUNT]		= {icon:"AccountAll", width:ZmListView.COL_WIDTH_ICON, name:ZmMsg.account, noRemove:true, resizeable:true, cssClass:"ZmMsgListColAccount"};
442 		this._headerInit[ZmItem.F_STATUS]		= {icon:"MsgStatus", width:ZmListView.COL_WIDTH_ICON, name:ZmMsg.status, cssClass:"ZmMsgListColStatus"};
443 		this._headerInit[ZmItem.F_MUTE]			= {icon:"Mute", width:ZmListView.COL_WIDTH_ICON, name:ZmMsg.muteUnmute, sortable: false /*ZmItem.F_MUTE*/, noSortArrow:true, cssClass:"ZmMsgListColMute"}; //todo - once server supports readAsc/readDesc sort orders, uncomment the sortable
444 		this._headerInit[ZmItem.F_READ]			= {icon:"MsgUnread", width:ZmListView.COL_WIDTH_ICON, name:ZmMsg.readUnread, sortable: ZmItem.F_READ, noSortArrow:true, cssClass:"ZmMsgListColRead"};
445 		this._headerInit[ZmItem.F_FROM]			= {text:ZmMsg.from, width:ZmMsg.COLUMN_WIDTH_FROM_MLV, resizeable:true, sortable:ZmItem.F_FROM, cssClass:"ZmMsgListColFrom"};
446 		this._headerInit[ZmItem.F_ATTACHMENT]	= {icon:"Attachment", width:ZmListView.COL_WIDTH_ICON, name:ZmMsg.attachment, sortable:ZmItem.F_ATTACHMENT, noSortArrow:true, cssClass:"ZmMsgListColAttachment"};
447 		this._headerInit[ZmItem.F_SUBJECT]		= {text:ZmMsg.subject, sortable:ZmItem.F_SUBJECT, noRemove:true, resizeable:true, cssClass:"ZmMsgListColSubject"};
448 		this._headerInit[ZmItem.F_FOLDER]		= {text:ZmMsg.folder, width:ZmMsg.COLUMN_WIDTH_FOLDER, resizeable:true, cssClass:"ZmMsgListColFolder"};
449 		this._headerInit[ZmItem.F_SIZE]			= {text:ZmMsg.size, width:ZmMsg.COLUMN_WIDTH_SIZE, sortable:ZmItem.F_SIZE, resizeable:true, cssClass:"ZmMsgListColSize"};
450 		this._headerInit[ZmItem.F_DATE]			= {text:ZmMsg.received, width:ZmMsg.COLUMN_WIDTH_DATE, sortable:ZmItem.F_DATE, resizeable:true, cssClass:"ZmMsgListColDate"};
451 		this._headerInit[ZmItem.F_SORTED_BY]	= {text:AjxMessageFormat.format(ZmMsg.arrangedBy, ZmMsg.date), sortable:ZmItem.F_SORTED_BY, resizeable:false};
452 	}
453 };
454 
455 ZmMailListView.prototype._getLabelFieldList =
456 function() {
457 	var headers = [];
458 	headers.push(ZmItem.F_SELECTION);
459 	if (appCtxt.get(ZmSetting.FLAGGING_ENABLED)) {
460 		headers.push(ZmItem.F_FLAG);
461 	}
462 	headers.push(
463 		ZmItem.F_PRIORITY,
464 		ZmItem.F_TAG,
465 		ZmItem.F_READ,
466 		ZmItem.F_STATUS,
467 		ZmItem.F_FROM,
468 		ZmItem.F_ATTACHMENT,
469 		ZmItem.F_SUBJECT,
470 		ZmItem.F_FOLDER,
471 		ZmItem.F_SIZE
472 	);
473 	if (appCtxt.accountList.size() > 2) {
474 		headers.push(ZmItem.F_ACCOUNT);
475 	}
476 	headers.push(ZmItem.F_DATE);
477 
478 	return headers;
479 }
480 
481 ZmMailListView.prototype._getHeaderList =
482 function() {
483 	var headers;
484 	if (this.isMultiColumn()) {
485 		headers = this._getLabelFieldList();
486 	}
487 	else {
488 		headers = [
489 			ZmItem.F_SELECTION,
490 			ZmItem.F_SORTED_BY
491 		];
492 	}
493 
494 	return this._getHeaders(this._mode, headers);
495 };
496 
497 ZmMailListView.prototype._getHeaders =
498 function(viewId, headerList) {
499 
500 	this._initHeaders();
501 	var hList = [];
502 
503 	this._defaultCols = headerList.join(ZmListView.COL_JOIN);
504 	var isMultiColumn = !this._controller.isReadingPaneOnRight();
505 	var userHeaders = isMultiColumn && appCtxt.get(ZmSetting.LIST_VIEW_COLUMNS, viewId);
506 	var headers = headerList;
507 	if (userHeaders && isMultiColumn) {
508 		headers = userHeaders.split(ZmListView.COL_JOIN);
509 		//we have to do it regardless of the size of headers and headerList, as items could be added and removed, masking each other as far as length (previous code compared length)
510 		headers = this._normalizeHeaders(headers, headerList);
511 	}
512     // adding account header in _normalizeHeader method
513     // sometimes doesn't work since we check for array length which is bad.
514 
515     // in ZD in case of All-Mailbox search always make sure account header is added to header array
516     if(appCtxt.isOffline && appCtxt.getSearchController().searchAllAccounts && isMultiColumn) {
517         var isAccHdrEnabled = false;
518         for (var k=0; k< headers.length; k++) {
519             if(headers[k] == ZmItem.F_ACCOUNT) {
520                 isAccHdrEnabled = true;
521             }
522         }
523         if(!isAccHdrEnabled) {
524             headers.splice(headers.length - 1, 0, ZmId.FLD_ACCOUNT);
525         }
526 
527     }
528 
529 	for (var i = 0, len = headers.length; i < len; i++) {
530 		var header = headers[i];
531 		var field = header.substr(0, 2);
532 		var hdrParams = this._headerInit[field];
533 		if (!hdrParams) { continue; }
534 		var pre = hdrParams.precondition;
535 		if (!pre || appCtxt.get(pre)) {
536 			hdrParams.field = field;
537 			// multi-account, account header is always initially invisible
538 			// unless user is showing global inbox. Ugh.
539 			if (appCtxt.multiAccounts &&
540 				appCtxt.accountList.size() > 2 &&
541 				appCtxt.get(ZmSetting.OFFLINE_SHOW_ALL_MAILBOXES) &&
542 				header.indexOf(ZmItem.F_ACCOUNT) != -1)
543 			{
544 				hdrParams.visible = true;
545 				this._showingAccountColumn = true;
546 			} else {
547 				var visible = (appCtxt.multiAccounts && header == ZmItem.F_ACCOUNT && !userHeaders)
548 					? false : (header.indexOf("*") == -1);
549                 if (!userHeaders && isMultiColumn) {
550                     //this is the default header
551                     if (typeof hdrParams.visible === "undefined") {
552                         //if the visible header is not set than use the computed value
553                         hdrParams.visible = visible;
554                     }
555                 } else {
556                     hdrParams.visible = visible;
557                 }
558 			}
559 			hList.push(new DwtListHeaderItem(hdrParams));
560 		}
561 	}
562 
563 	return hList;
564 };
565 
566 /**
567  * Cleans up the list of columns in various ways:
568  * 		- Add new fields in penultimate position
569  * 		- Remove duplicate fields
570  * 		- Remove fields that aren't valid for the view
571  *
572  * @param userHeaders	[Array]		user-defined set of column headers
573  * @param headerList	[Array]		default set of column headers
574  */
575 ZmMailListView.prototype._normalizeHeaders =
576 function(userHeaders, headerList) {
577 
578 	// strip duplicates and invalid headers
579 	var allHeaders = AjxUtil.arrayAsHash(headerList);
580 	var headers = [], used = {}, starred = {};
581 	for (var i = 0; i < userHeaders.length; i++) {
582 		var hdr = userHeaders[i];
583 		var idx = hdr.indexOf("*");
584 		if (idx != -1) {
585 			hdr = hdr.substr(0, idx);
586 			starred[hdr] = true;
587 		}
588 		if (allHeaders[hdr] && !used[hdr]) {
589 			headers.push(hdr);
590 			used[hdr] = true;
591 		}
592 	}
593 
594 	// add columns this account doesn't know about
595 	for (var j = 0; j < headerList.length; j++) {
596 		var hdr = headerList[j];
597 		if (!used[hdr]) {
598 			// if account field, add it but initially invisible
599 			if (hdr == ZmId.FLD_ACCOUNT) {
600 				starred[ZmItem.F_ACCOUNT] = true;
601 			}
602 			if (hdr == ZmId.FLD_SELECTION) {
603 				//re-add selection checkbox at the beginning (no idea why the rest is added one before last item, but not gonna change it for now
604 				headers.unshift(hdr); //unshift adds item at the beginning
605 			}
606 			else {
607 				headers.splice(headers.length - 1, 0, hdr);
608 			}
609 		}
610 	}
611 
612 	// rebuild the list, preserve invisibility
613 	var list = AjxUtil.map(headers, function(hdr) {
614 		return starred[hdr] ? hdr + "*" : hdr; });
615 
616 	// save implicit pref with newly added column
617 	appCtxt.set(ZmSetting.LIST_VIEW_COLUMNS, list.join(ZmListView.COL_JOIN), this.view);
618 	return list;
619 };
620 
621 ZmMailListView.prototype.createHeaderHtml =
622 function(defaultColumnSort) {
623 
624 	var activeSortBy = this.getActiveSearchSortBy();
625 	// for multi-account, hide/show Account column header depending on whether
626 	// user is search across all accounts or not.
627 	if (appCtxt.multiAccounts) {
628 		var searchAllAccounts = appCtxt.getSearchController().searchAllAccounts;
629 		if (this._headerHash &&
630 			((this._showingAccountColumn && !searchAllAccounts) ||
631 			(!this._showingAccountColumn && searchAllAccounts)))
632 		{
633 			var accountHeader = this._headerHash[ZmItem.F_ACCOUNT];
634 			if (accountHeader) {
635 				accountHeader._visible = this._showingAccountColumn = searchAllAccounts;
636 				this.headerColCreated = false;
637 			}
638 		}
639 	}
640 
641 	if (this._headerList && !this.headerColCreated) {
642 		var rpLoc = this._controller._getReadingPanePref();
643 		if (rpLoc == ZmSetting.RP_RIGHT && this._controller._itemCountText[rpLoc]) {
644 			this._controller._itemCountText[rpLoc].dispose();
645 		}
646 
647 		if (activeSortBy && ZmMailListView.SORTBY_HASH[activeSortBy]) {
648 			defaultColumnSort = ZmMailListView.SORTBY_HASH[activeSortBy].field;
649 		}
650 		DwtListView.prototype.createHeaderHtml.call(this, defaultColumnSort, this._isMultiColumn);
651 
652 		if (rpLoc == ZmSetting.RP_RIGHT) {
653 			var td = document.getElementById(this._itemCountTextTdId);
654 			if (td) {
655 				var textId = DwtId.makeId(this.view, rpLoc, "text");
656 				var textDiv = document.getElementById(textId);
657 				if (!textDiv) {
658 					var text = this._controller._itemCountText[rpLoc] =
659 							   new DwtText({parent:this, className:"itemCountText", id:textId});
660 					td.appendChild(text.getHtmlElement());
661 				}
662 			}
663 		}
664 	}
665 
666 	// Setting label on date column
667 	this._resetFromColumnLabel();
668 	var col = Dwt.byId(this._currentColId);
669     var headerCol = this._isMultiColumn ? this._headerHash[ZmItem.F_DATE] :
670 		            (col && this.getItemFromElement(col)) || (this._headerHash && this._headerHash[ZmItem.F_SORTED_BY]) || null;
671 	if (headerCol) {
672 		var colLabel = "";
673 		var column;
674 		if (this._isMultiColumn) {
675 			// set the received column name based on search folder
676 			colLabel = ZmMsg.received;
677 			if (this._isOutboundFolder()) {
678 				colLabel = (this._folderId == ZmFolder.ID_DRAFTS) ? ZmMsg.lastSaved : ZmMsg.sentAt;
679 				colLabel = " " + colLabel;
680 			}
681 		}
682 		else if (activeSortBy && ZmMailListView.SORTBY_HASH[activeSortBy]){
683 			var msg = ZmMailListView.SORTBY_HASH[activeSortBy].msg;
684 			var field = ZmMailListView.SORTBY_HASH[activeSortBy].field;
685 			if (msg) {
686 				colLabel = AjxMessageFormat.format(ZmMsg.arrangedBy, ZmMsg[msg]);
687 			}
688 			if (field) {
689 				headerCol._sortable = field;
690 			}
691 		}
692 
693 		//Set column label; for multi-column this changes the received text. For single column this sets to the sort by text
694 		var colSpan = document.getElementById(DwtId.getListViewHdrId(DwtId.WIDGET_HDR_LABEL, this._view, headerCol._field));
695 		if (colSpan) {
696 			colSpan.innerHTML = colLabel;
697 		}
698 		if (this._colHeaderActionMenu) {
699 			if (!this._isMultiColumn) {
700 				var mi = this._colHeaderActionMenu.getMenuItem(field);
701 				if (mi) {
702 					mi.setChecked(true, true);
703 				}
704 			}
705 		}
706 
707 		// Now that we've created headers, we can update the View menu so that it doesn't get created before headers,
708 		// since we want to know the header IDs when we create the Display submenu
709 		var ctlr = this._controller,
710 			viewType = ctlr.getCurrentViewType();
711 		ctlr._updateViewMenu(viewType);
712 		ctlr._updateViewMenu(ctlr._getReadingPanePref());
713 		ctlr._updateViewMenu(appCtxt.get(ZmSetting.CONVERSATION_ORDER));
714 	}
715 };
716 
717 ZmMailListView.prototype._createHeader =
718 function(htmlArr, idx, headerCol, i, numCols, id, defaultColumnSort) {
719 
720 	if (headerCol._field == ZmItem.F_SORTED_BY) {
721 		var field = headerCol._field;
722 		var textTdId = this._itemCountTextTdId = DwtId.makeId(this.view, ZmSetting.RP_RIGHT, "td");
723 		htmlArr[idx++] = "<td id='";
724 		htmlArr[idx++] = id;
725 		htmlArr[idx++] = "' class='";
726 		htmlArr[idx++] = (id == this._currentColId)	? "DwtListView-Column DwtListView-ColumnActive'" :
727 													  "DwtListView-Column'";
728 		htmlArr[idx++] = " width='auto'><table width='100%'><tr><td id='";
729 		htmlArr[idx++] = DwtId.getListViewHdrId(DwtId.WIDGET_HDR_LABEL, this._view, field);
730 		htmlArr[idx++] = "' class='DwtListHeaderItem-label'>";
731 		htmlArr[idx++] = headerCol._label;
732 		htmlArr[idx++] = "</td>";
733 
734 		// sort icon
735 		htmlArr[idx++] = "<td class='itemSortIcon' id='";
736 		htmlArr[idx++] = DwtId.getListViewHdrId(DwtId.WIDGET_HDR_ARROW, this._view, field);
737 		htmlArr[idx++] = "'>";
738 		htmlArr[idx++] = AjxImg.getImageHtml(this._bSortAsc ? "ColumnUpArrow" : "ColumnDownArrow");
739 		htmlArr[idx++] = "</td>";
740 
741 		// item count text
742 		htmlArr[idx++] = "<td align=right class='itemCountText' id='";
743 		htmlArr[idx++] = textTdId;
744 		htmlArr[idx++] = "'></td></tr></table></td>";
745 
746 		return idx;
747 	} else {
748 		return DwtListView.prototype._createHeader.apply(this, arguments);
749 	}
750 };
751 
752 ZmMailListView.prototype._resetColWidth =
753 function() {
754 
755 	if (!this.headerColCreated) { return; }
756 
757 	var lastColIdx = this._getLastColumnIndex();
758     if (lastColIdx) {
759         var lastCol = this._headerList[lastColIdx];
760 		if (lastCol._field != ZmItem.F_SORTED_BY) {
761 			DwtListView.prototype._resetColWidth.apply(this, arguments);
762 		}
763 	}
764 };
765 
766 ZmMailListView.prototype._mouseOverAction =
767 function(mouseEv, div) {
768 
769 	var type = this._getItemData(div, "type");
770 	if (type == DwtListView.TYPE_HEADER_ITEM){
771 		var hdr = this.getItemFromElement(div);
772 		if (hdr && this.sortingEnabled && hdr._sortable && hdr._sortable == ZmItem.F_FROM) {
773 			if (this._isOutboundFolder()) {
774 				div.className = "DwtListView-Column DwtListView-ColumnHover";
775 				return true;
776 			}
777 		}
778 	}
779 
780 	return ZmListView.prototype._mouseOverAction.apply(this, arguments);
781 };
782 
783 ZmMailListView.prototype._columnClicked =
784 function(clickedCol, ev) {
785 
786 	var hdr = this.getItemFromElement(clickedCol);
787 	var group = this.getGroup(this._folderId);
788 	if (group && hdr && hdr._sortable) {
789         var groupId = ZmMailListGroup.getGroupIdFromSortField(hdr._sortable, this.type);
790 		if (groupId != group.id) {
791             this.setGroup(groupId);
792 		}
793     }
794 
795 	ZmListView.prototype._columnClicked.call(this, clickedCol, ev);
796 };
797 
798 ZmMailListView.prototype._resetFromColumnLabel =
799 function() {
800 
801 	// set the from column name based on query string
802 	var headerCol = this._headerHash[ZmItem.F_FROM];
803 	if (headerCol) { //this means viewing pane on bottom
804 		var colLabel = this._isOutboundFolder() ? ZmMsg.to : ZmMsg.from;
805         //bug:1108 & 43789#c19 since sort-by-rcpt affects server performance avoid using in convList instead used in outbound folder
806         headerCol._sortable = this._isOutboundFolder() ? ZmItem.F_TO : ZmItem.F_FROM;
807 
808         var fromColSpan = document.getElementById(DwtId.getListViewHdrId(DwtId.WIDGET_HDR_LABEL, this._view, headerCol._field));
809 		if (fromColSpan) {
810 			fromColSpan.innerHTML = " " + colLabel;
811 		}
812 		var item = this._colHeaderActionMenu ? this._colHeaderActionMenu.getItem(headerCol._index) : null;
813 		if (item) {
814 			item.setText(colLabel);
815 		}
816 	}
817 };
818 
819 /**
820  * Returns true if the given folder is for outbound mail.
821  *
822  * @param folder
823  *
824  * @private
825  */
826 ZmMailListView.prototype._isOutboundFolder =
827 function(folder) {
828 	folder = folder || (this._folderId && appCtxt.getById(this._folderId));
829 	return (folder && folder.isOutbound());
830 };
831 
832 /**
833  * Returns the current folder
834  *
835  */
836 ZmMailListView.prototype.getFolder =
837 function() {
838 	return this._folderId && appCtxt.getById(this._folderId);
839 };
840 
841 ZmMailListView.prototype.useListElement =
842 function() {
843 	return true;
844 }
845 
846 ZmMailListView.prototype._getRowClass =
847 function(item) {
848 	var classes = this._isMultiColumn ? ["DwtMsgListMultiCol"]:["ZmRowDoubleHeader"];
849 	var value = this._getRowClassValue(item.isUnread && !item.isMute);
850 	if (value) {
851 		classes.push(value);
852 	}
853 	return classes.join(" ");
854 };
855 
856 ZmMailListView.prototype._getRowClassValue =
857 	function(value) {
858 		return value ? "Unread" : null;
859 	};
860 
861 ZmMailListView.prototype._getCellId =
862 function(item, field) {
863 	if (field == ZmItem.F_DATE) {
864 		return null;
865 	}
866 	else if (field == ZmItem.F_SORTED_BY) {
867 		return this._getFieldId(item, field);
868 	}
869 	else {
870 		return ZmListView.prototype._getCellId.apply(this, arguments);
871 	}
872 };
873 
874 ZmMailListView.prototype._getHeaderToolTip =
875 function(field, itemIdx) {
876 
877 	var isOutboundFolder = this._isOutboundFolder();
878 	if (field == ZmItem.F_FROM && isOutboundFolder) {
879 	   return ZmMsg.to;
880 	}
881 	if (field == ZmItem.F_STATUS) {
882 		return ZmMsg.messageStatus;
883 	}
884     if (field == ZmItem.F_MUTE) {
885 		return ZmMsg.muteUnmute;
886 	}
887 	if (field == ZmItem.F_READ) {
888 		return ZmMsg.readUnread;
889 	}
890 
891 	return ZmListView.prototype._getHeaderToolTip.call(this, field, itemIdx, isOutboundFolder);
892 };
893 
894 ZmMailListView.prototype._getToolTip =
895 function(params) {
896 
897 	var tooltip,
898 		field = params.field,
899 		item = params.item,
900 		matchIndex = params.match && params.match.participant || 0;
901 
902 	if (!item) {
903 		return;
904 	}
905 
906 	if (field === ZmItem.F_STATUS) {
907 		tooltip = item.getStatusTooltip();
908 	}
909 	else if (appCtxt.get(ZmSetting.CONTACTS_ENABLED) && (field === ZmItem.F_FROM || field === ZmItem.F_PARTICIPANT)) {
910 		var addr;
911 		if (!item.getAddress) {
912 			return;
913 		}
914 		if (field === ZmItem.F_FROM) {
915 			if (this._isOutboundFolder()) {
916 				// this needs to be in sync with code that sets field IDs in ZmMailMsgListView::_getCellContents
917 				var addrs = item.getAddresses(AjxEmailAddress.TO).getArray();
918 				addr = addrs[matchIndex];
919 			}
920 			else {
921 				addr = item.getAddress(AjxEmailAddress.FROM);
922 			}
923 		}
924 		else if (field === ZmItem.F_PARTICIPANT) {
925 			addr = item.participants && item.participants.get(matchIndex);
926 		}
927 		if (!addr) {
928 			return;
929 		}
930 		
931 		var ttParams = {
932 			address:	addr,
933 			ev:			params.ev
934 		}
935 		var ttCallback = new AjxCallback(this,
936 			function(callback) {
937 				appCtxt.getToolTipMgr().getToolTip(ZmToolTipMgr.PERSON, ttParams, callback);
938 			});
939 		tooltip = { callback:ttCallback };
940 	}
941 	else if (field === ZmItem.F_SUBJECT || field === ZmItem.F_FRAGMENT) {
942 		var invite = (item.type === ZmItem.MSG) && item.isInvite() && item.invite;
943 		if (invite && item.needsRsvp()) {
944 			tooltip = invite.getToolTip();
945 		}
946 		else if (invite && !invite.isEmpty()) {
947 			var bp = item.getBodyPart();
948 			tooltip = bp && ZmInviteMsgView.truncateBodyContent(bp.getContent(), true);
949 			tooltip = AjxStringUtil.stripTags(tooltip);
950 		}
951 		else {
952 			tooltip = AjxStringUtil.htmlEncode(item.fragment || (item.hasAttach ? "" : ZmMsg.fragmentIsEmpty));
953         }
954         // Strip surrounding whitespace from the tooltip
955         tooltip = AjxStringUtil.trim(tooltip, false, "\\s");
956 	}
957 	else if (field === ZmItem.F_FOLDER) {
958 		var folder = appCtxt.getById(item.folderId);
959 		if (folder && folder.parent) {
960 			var path = folder.getPath();
961 			if (path !== folder.getName()) {
962 				tooltip = path;
963 			}
964 		}
965 	}
966 	else if (field === ZmItem.F_ACCOUNT) {
967 		tooltip = item.getAccount().getDisplayName();
968 	}
969 	else {
970 		tooltip = ZmListView.prototype._getToolTip.apply(this, arguments);
971 	}
972     return tooltip;
973 };
974 
975 /**
976  * (override of ZmListView to add hooks to zimletMgr)
977  * Creates a TD and its content for a single field of the given item. Subclasses
978  * may override several dependent functions to customize the TD and its content.
979  *
980  * @param htmlArr	[array]		array that holds lines of HTML
981  * @param idx		[int]		current line of array
982  * @param item		[object]	item to render
983  * @param field		[constant]	column identifier
984  * @param colIdx	[int]		index of column (starts at 0)
985  * @param params	[hash]*		hash of optional params
986  * 
987  * @private
988  */
989 ZmMailListView.prototype._getCell =
990 function(htmlArr, idx, item, field, colIdx, params) {
991 	var className = this._getCellClass(item, field, params, colIdx);
992 
993 	/* TODO Identify a way for Zimlets to add styles to fields.
994 	var cellId = this._getCellId(item, field, params);
995 	var idText = cellId ? [" id=", "'", cellId, "'"].join("") : "";
996 	var width = this._getCellWidth(colIdx, params);
997 	var widthText = width ? ([" width=", width].join("")) : (" width='100%'");
998 	var classText = className ? [" class=", className].join("") : "";
999 	var alignValue = this._getCellAlign(colIdx, params);
1000 	var alignText = alignValue ? [" align=", alignValue].join("") : "";
1001 	var otherText = (this._getCellAttrText(item, field, params)) || "";
1002 	var attrText = [idText, widthText, classText, alignText, otherText].join(" ");
1003 
1004 	htmlArr[idx++] = "<td";
1005 	htmlArr[idx++] = this._getStyleViaZimlet(field, item);
1006 	htmlArr[idx++] = attrText ? (" " + attrText) : "";
1007 	htmlArr[idx++] = ">";
1008 
1009 	idx = this._getCellContents(htmlArr, idx, item, field, colIdx, params);
1010 	htmlArr[idx++] = "</td>";*/
1011 
1012 	idx = this._getCellContents(htmlArr, idx, item, field, colIdx, params, [className || ""]);
1013 
1014 	return idx;
1015 };
1016 
1017 ZmMailListView.prototype._getCellClass =
1018 function(item, field, params, colIdx) {
1019 	var classes = null;
1020 	if (!this._isMultiColumn && field == ZmItem.F_SUBJECT) {
1021 		classes = "SubjectDoubleRow ";
1022 	}
1023 	if (colIdx == null) { return classes; }
1024 	var headerList = params.headerList || this._headerList;
1025 	return classes ? classes + headerList[colIdx]._cssClass: headerList[colIdx]._cssClass;
1026 };
1027 
1028 ZmMailListView.prototype._getFlagIcon =
1029 function(isFlagged, isMouseover) {
1030 	return (isFlagged || isMouseover)
1031 		? "FlagRed"
1032 		: (this._isMultiColumn ? "Blank_16" : "FlagDis");
1033 };
1034 
1035 
1036 /**
1037  * Returns a list of the largest subset of the given participants that will fit within the
1038  * given width. The participants are assumed to be ordered oldest to most recent. We return
1039  * as many of the most recent as possible.
1040  *
1041  * @private
1042  * @param {array}		participants		list of AjxEmailAddress
1043  * @param {ZmMailItem}	item				item that contains the participants
1044  * @param {int}			width				available space in pixels
1045  * 
1046  * @return list of participant objects with 'name' and 'index' fields
1047  */
1048 ZmMailListView.prototype._fitParticipants =
1049 function(participants, item, availWidth) {
1050 
1051 	availWidth -= 15;	// safety margin
1052 
1053 	var sepWidth = AjxStringUtil.getWidth(AjxStringUtil.LIST_SEP, item.isUnread);
1054 	var ellWidth = AjxStringUtil.getWidth(AjxStringUtil.ELLIPSIS, item.isUnread);
1055 
1056 	// first see if we can fit everyone with their full names
1057 	var list = [];
1058 	var pLen = Math.min(20, participants.length);
1059 	var width = 0;
1060 	for (var i = 0; i < pLen; i++) {
1061 		var p = participants[i];
1062 		var field = p.name || p.address || p.company || "";
1063 		width += AjxStringUtil.getWidth(AjxStringUtil.htmlEncode(field), item.isUnread);
1064 		list.push({name:field, index:i});
1065 	}
1066 	width += (pLen - 1) * sepWidth;
1067 	if (width < availWidth) {
1068 		return list;
1069 	}
1070 
1071 	// now try with display (first) names; fit as many of the most recent as we can
1072 	list = [];
1073 	for (var i = 0; i < pLen; i++) {
1074 		var p = participants[i];
1075 		var field = p.dispName || p.address || p.company || "";
1076 		list.push({name:field, index:i});
1077 	}
1078 	while (list.length) {
1079 		var width = 0;
1080 		// total the width of the names
1081 		for (var i = 0; i < list.length; i++) {
1082 			width += AjxStringUtil.getWidth(AjxStringUtil.htmlEncode(list[i].name), item.isUnread);
1083 		}
1084 		// add the width of the separators
1085 		width += (list.length - 1) * sepWidth;
1086 		// add the width of the ellipsis if we've dropped anyone
1087 		if (list.length < pLen) {
1088 			width += ellWidth;
1089 		}
1090 		if (width < availWidth) {
1091 			return list;
1092 		} else {
1093 			list.shift();
1094 		}
1095 	}
1096 
1097 	// not enough room for even one participant, just return the last one
1098 	var p = participants[pLen - 1];
1099 	var field = p.dispName || p.address || p.company || "";
1100 	return [{name:field, index:pLen - 1}];
1101 };
1102 
1103 ZmMailListView.prototype._getActionMenuForColHeader =
1104 function(force) {
1105 
1106 	var menu;
1107 	if (this.isMultiColumn()) {
1108 		var doReset = (!this._colHeaderActionMenu || force);
1109 		menu = ZmListView.prototype._getActionMenuForColHeader.call(this, force, this, "header");
1110 		if (doReset) {
1111 			this._resetFromColumnLabel();
1112 			this._groupByActionMenu = this._getGroupByActionMenu(menu);
1113 		}
1114 		else if (this._groupByActionMenu) {
1115 			this._setGroupByCheck();
1116 		}
1117 	}
1118 	else {
1119 		if (!this._colHeaderActionMenu || force) {
1120 			this._colHeaderActionMenu = this._setupSortMenu(null, true);
1121 		}
1122 		menu = this._colHeaderActionMenu;
1123 	}
1124 
1125 	return menu;
1126 };
1127 
1128 
1129 ZmMailListView.prototype._getSingleColumnSortFields =
1130 function() {
1131 	return ZmMailListView.SINGLE_COLUMN_SORT;
1132 };
1133 
1134 
1135 ZmMailListView.prototype._setupSortMenu = function(parent, includeGroupByMenu) {
1136 
1137 	var activeSortBy = this.getActiveSearchSortBy();
1138 	var defaultSort = activeSortBy && ZmMailListView.SORTBY_HASH[activeSortBy] ?
1139 		ZmMailListView.SORTBY_HASH[activeSortBy].field : ZmItem.F_DATE;
1140 	var sortMenu = this._getSortMenu(this._getSingleColumnSortFields(), defaultSort, parent);
1141 	if (includeGroupByMenu) {
1142 		this._groupByActionMenu = this._getGroupByActionMenu(sortMenu);
1143 		this._setGroupByCheck();
1144 	}
1145 	var mi = sortMenu.getMenuItem(ZmItem.F_FROM);
1146 	if (mi) {
1147 		mi.setVisible(!this._isOutboundFolder());
1148 	}
1149 	mi = sortMenu.getMenuItem(ZmItem.F_TO);
1150 	if (mi) {
1151 		mi.setVisible(this._isOutboundFolder());
1152 	}
1153 
1154 	return sortMenu;
1155 };
1156 
1157 ZmMailListView.prototype._getNoResultsMessage =
1158 function() {
1159 	if (appCtxt.isOffline && !appCtxt.getSearchController().searchAllAccounts) {
1160 		// offline folders which are "syncable" but currently not syncing should
1161 		// display a different message
1162 		var fid = ZmOrganizer.getSystemId(this._controller._getSearchFolderId());
1163 		var folder = fid && appCtxt.getById(fid);
1164 		if (folder) {
1165 			if (folder.isOfflineSyncable && !folder.isOfflineSyncing) {
1166 				var link = "ZmMailListView.toggleSync('" + folder.id + "', '" + this._htmlElId + "');";
1167 				return AjxMessageFormat.format(ZmMsg.notSyncing, link);
1168 			}
1169 		}
1170 	}
1171 
1172 	return DwtListView.prototype._getNoResultsMessage.call(this);
1173 };
1174 
1175 ZmMailListView.toggleSync =
1176 function(folderId, htmlElementId) {
1177 	var folder = appCtxt.getById(folderId);
1178 	var htmlEl = folder ? document.getElementById(htmlElementId) : null;
1179 	var listview = htmlEl ? DwtControl.fromElement(htmlEl) : null;
1180 	if (listview) {
1181 		var callback = new AjxCallback(listview, listview._handleToggleSync, [folder]);
1182 		folder.toggleSyncOffline(callback);
1183 	}
1184 };
1185 
1186 ZmMailListView.prototype._handleToggleSync =
1187 function(folder) {
1188 	folder.getAccount().sync();
1189 	// bug fix #27846 - just clear the list view and let instant notify populate
1190 	this.removeAll(true);
1191 };
1192 
1193 
1194 // Listeners
1195 
1196 ZmMailListView.prototype.handleUnmuteConv =
1197 function(items) {
1198     //overridden in ZmConvListView
1199 };
1200 
1201 ZmMailListView.prototype._changeListener =
1202 function(ev) {
1203 
1204 	var item = this._getItemFromEvent(ev);
1205 	if (!item || ev.handled || !this._handleEventType[item.type]) {
1206 		if (ev && ev.event == ZmEvent.E_CREATE) {
1207 			AjxDebug.println(AjxDebug.NOTIFY, "ZmMailListView: initial check failed");
1208 		}
1209 		return;
1210 	}
1211 
1212 	if ((!this.isMultiColumn() || appCtxt.get(ZmSetting.COLOR_MESSAGES))
1213 			&& (ev.event == ZmEvent.E_TAGS || ev.event == ZmEvent.E_REMOVE_ALL)) {
1214 		DBG.println(AjxDebug.DBG2, "ZmMailListView: TAG");
1215 		this.redrawItem(item);
1216         ZmListView.prototype._changeListener.call(this, ev);
1217         ev.handled = true;
1218 	}
1219 
1220 	if (ev.event == ZmEvent.E_FLAGS) { // handle "unread" flag
1221 		DBG.println(AjxDebug.DBG2, "ZmMailListView: FLAGS");
1222 		var flags = ev.getDetail("flags");
1223 		for (var j = 0; j < flags.length; j++) {
1224 			var flag = flags[j];
1225 			if (flag == ZmItem.FLAG_MUTE) {
1226 				var on = item[ZmItem.FLAG_PROP[flag]];
1227 				this.markUIAsMute(item, !on);
1228 			}
1229             else if (flag == ZmItem.FLAG_UNREAD) {
1230 				var on = item[ZmItem.FLAG_PROP[flag]];
1231 				this.markUIAsRead(item, !on);
1232 			}
1233 		}
1234 	}
1235 	
1236 	if (ev.event == ZmEvent.E_CREATE) {
1237 		DBG.println(AjxDebug.DBG2, "ZmMailListView: CREATE");
1238 		AjxDebug.println(AjxDebug.NOTIFY, "ZmMailListView: handle create " + item.id);
1239 
1240 		if (this._controller.actionedMsgId) {
1241 			var newMsg = appCtxt.getById(this._controller.actionedMsgId);
1242 			if (newMsg) {
1243 				this._itemToSelect = this._controller.isZmConvListController ? appCtxt.getById(newMsg.cid) : newMsg;
1244 			}
1245 			this._controller.actionedMsgId = null;
1246 		}
1247 
1248 		if (this._list && this._list.contains(item)) {
1249 			AjxDebug.println(AjxDebug.NOTIFY, "ZmMailListView: list already has item " + item.id);
1250 			return;
1251 		}
1252 		if (!this._handleEventType[item.type]) {
1253 			AjxDebug.println(AjxDebug.NOTIFY, "ZmMailListView: list view of type " + this._mode + " does not handle " + item.type);
1254 			return;
1255 		}
1256 
1257 		// Check to see if ZmMailList::notifyCreate gave us an index for the item.
1258 		// If not, we assume that the new conv/msg is the most recent one. The only case
1259 		// we handle is where the user is on the first page.
1260 		//
1261 		// TODO: handle other sort orders, arbitrary insertion points
1262 		//about the above - for now we insert new items on top (index would be 0 if not sorted by date).
1263 		// I believe that way the users won't lose new messages if they are sorted by a different order.
1264 		if (this._isPageless || this.offset == 0) {
1265 			var sortIndex = ev.getDetail("sortIndex") || 0;
1266 			AjxDebug.println(AjxDebug.NOTIFY, "ZmMailListView: adding item " + item.id + " at index " + sortIndex);
1267 			this.addItem(item, sortIndex);
1268 
1269 			if (appCtxt.isOffline && appCtxt.getActiveAccount().isOfflineInitialSync()) {
1270 				this._controller._app.numEntries++;
1271 			}
1272 		}
1273 		ev.handled = true;
1274 	}
1275 
1276 	if (!ev.handled) {
1277 		ZmListView.prototype._changeListener.call(this, ev);
1278         if (ev.event == ZmEvent.E_MOVE || ev.event == ZmEvent.E_DELETE){
1279             var cv = this._controller.getItemView();
1280             var currentItem =  cv && cv.getItem();
1281             if (currentItem == item){
1282                 cv.set(null, true)
1283             }
1284         }
1285 	}
1286 };
1287 
1288 /**
1289  * If we're showing content in the reading pane and there is exactly one item selected,
1290  * make sure the content is for that selected item. Otherwise, clear the content.
1291  */
1292 ZmMailListView.prototype._itemClicked =
1293 function(clickedEl, ev) {
1294 
1295     //bug:67455 request permission for desktop notifications
1296     if(window.webkitNotifications && appCtxt.get(ZmSetting.MAIL_NOTIFY_TOASTER)){
1297         var perm = webkitNotifications.checkPermission();
1298         if(perm == 1){
1299            webkitNotifications.requestPermission(function(){});
1300         }
1301         //else if(perm == 2) ){ /*ignore when browser has disabled notifications*/ }
1302     }
1303 
1304 	Dwt.setLoadingTime("ZmMailItem");
1305 	ZmListView.prototype._itemClicked.apply(this, arguments);
1306 	
1307 	var ctlr = this._controller;
1308 	if (ctlr.isReadingPaneOn()) {
1309 		if (appCtxt.get(ZmSetting.SHOW_SELECTION_CHECKBOX) && ev.button == DwtMouseEvent.LEFT) {
1310 			if (!ev.shiftKey && !ev.ctrlKey) {
1311 				// get the field being clicked
1312 				var target = this._getEventTarget(ev);
1313 				var id = (target && target.id && target.id.indexOf("AjxImg") == -1) ? target.id : clickedEl.id;
1314 				var m = id ? this._parseId(id) : null;
1315 				if (m && m.field == ZmItem.F_SELECTION) {
1316 					if (this.getSelectionCount() == 1) {
1317 						var item = this.getSelection()[0];
1318 						var msg = (item instanceof ZmConv) ? item.getFirstHotMsg() : item;
1319 						if (msg && ctlr._curItem && (msg.id != ctlr._curItem.id)) {
1320 							ctlr.reset();
1321 						}
1322 					}
1323 				}
1324 			}
1325 		}
1326 	}
1327 };
1328 
1329 ZmMailListView.prototype._setNextSelection =
1330 function() {
1331 
1332 	if (this._itemToSelect) {
1333 		var item = this._getItemToSelect();
1334 		if (item) {
1335 			DBG.println(AjxDebug.DBG1, "ZmMailListView._setNextSelection: select item with ID: " + item.id);
1336 			this.setSelection(item, false);
1337 			this._itemToSelect = null;
1338 		}
1339 	}
1340 };
1341 
1342 /**
1343  * Returns the next item to select, typically set by the controller.
1344  * 
1345  * @private
1346  */
1347 ZmMailListView.prototype._getItemToSelect =
1348 function() {
1349 	var item = this._itemToSelect || (this._list && this._list.get(0));
1350 	var list = this.getList(true);
1351 	if (item == ZmMailListView.FIRST_ITEM) {
1352 		list = list && list.getArray();
1353 		item = list && list[0];
1354 	} else if (item == ZmMailListView.LAST_ITEM) {
1355 		list = list && list.getArray();
1356 		item = list && list[list.length - 1];
1357 	}
1358 	return item;
1359 };
1360 
1361 ZmMailListView.prototype._getSearchForSort =
1362 function(sortField, controller) {
1363 	controller = controller || this._controller;
1364 	var query = controller.getSearchString();
1365 	if (!query) { return ""; }
1366 	if (sortField != ZmItem.F_READ) {
1367 		return; //shouldn't happen. READ/Unread is the only current filter
1368 	}
1369 
1370 	var str = "is:unread";
1371 	if (query.indexOf(str) != -1) {
1372 		query = AjxStringUtil.trim(query.replace(str, ""));
1373 	} else {
1374 		query = query + " " + str;
1375 	}
1376 	return query;
1377 };
1378 
1379 ZmMailListView.prototype._columnHasCustomQuery =
1380 function(columnItem) {
1381 	return columnItem._sortable == ZmItem.F_READ;
1382 };
1383 
1384 ZmMailListView.prototype._isDefaultSortAscending =
1385 function(colHeader) {
1386 	// if date, flag, attachment or size fields, sort desc by default - otherwise ascending.
1387 	var sortable = colHeader._sortable;
1388 	var desc = sortable === ZmItem.F_DATE
1389 			|| sortable === ZmItem.F_FLAG
1390 			|| sortable === ZmItem.F_ATTACHMENT
1391 			|| sortable === ZmItem.F_SIZE;
1392 	return !desc;
1393 };
1394 
1395 //GROUP SUPPORT
1396 ZmMailListView.prototype.reset =
1397 function() {
1398 	this.clearGroupSections(this.getActiveSearchFolderId());
1399 	ZmListView.prototype.reset.call(this);
1400 };
1401 
1402 ZmMailListView.prototype.removeAll =
1403 function() {
1404 	//similar to reset, but can't call reset since it sets _rendered to false and that prevents pagination from working.
1405 	this.clearGroupSections(this.getActiveSearchFolderId());
1406 	ZmListView.prototype.removeAll.call(this);
1407 };
1408 
1409 
1410 /**
1411  * Clear groups
1412  * @param {int} folderId folderId to get group
1413  */
1414 ZmMailListView.prototype.clearGroupSections =
1415 function(folderId) {
1416   if (folderId) {
1417       var group = this.getGroup(folderId);
1418       if (group) {
1419           group.clearSections();
1420       }
1421   }
1422   else if (this._group) {
1423       this._group.clearSections();
1424   }
1425 };
1426 
1427 /**
1428  * Set the group
1429  * @param {String} groupId
1430  */
1431 ZmMailListView.prototype.setGroup =
1432 function(groupId) {
1433     this._group = ZmMailListGroup.getGroup(groupId);
1434     if (this._folderId && !this._controller.isSearchResults) {
1435 	    appCtxt.set(ZmSetting.GROUPBY_LIST, groupId || ZmId.GROUPBY_NONE, this._folderId); //persist group Id
1436 	    appCtxt.set(ZmSetting.GROUPBY_HASH, this._group, this._folderId); //local cache for group object
1437     }
1438 };
1439 
1440 /**
1441  * get the group
1442  * @param {int} folderId
1443  * @return {ZmMailListGroup} group object or null
1444  */
1445 ZmMailListView.prototype.getGroup =
1446 function(folderId) {
1447     if (folderId) {
1448 	    var group = appCtxt.get(ZmSetting.GROUPBY_HASH, folderId);
1449 	    if (!group) {
1450 			var groupId = appCtxt.get(ZmSetting.GROUPBY_LIST, folderId);
1451 			group = ZmMailListGroup.getGroup(groupId);
1452 			appCtxt.set(ZmSetting.GROUPBY_HASH, group, folderId);
1453 	    }
1454 
1455 	    var activeSortBy = this.getActiveSearchSortBy();
1456 	    if (activeSortBy && ZmMailListView.SORTBY_HASH[activeSortBy] && group && group.field != ZmMailListView.SORTBY_HASH[activeSortBy].field) {
1457 		    //switching views can cause problems; make sure the group and sortBy match
1458 		    group = null;
1459 		    appCtxt.set(ZmSetting.GROUPBY_HASH, group, folderId); //clear cache
1460 		    appCtxt.set(ZmSetting.GROUPBY_LIST, ZmId.GROUPBY_NONE, folderId); //persist groupId
1461 	    }
1462 
1463 
1464 	    return group;
1465     }
1466 	else {
1467 	    return this._group;
1468     }
1469 };
1470 
1471 ZmMailListView.prototype._getGroupByActionMenu = function(parent, noSeparator, menuItemIsParent) {
1472 
1473     var list = [ ZmOperation.GROUPBY_NONE, ZmOperation.GROUPBY_DATE, ZmOperation.GROUPBY_FROM, ZmOperation.GROUPBY_SIZE ];
1474     if (this._mode == ZmId.VIEW_CONVLIST || this._isOutboundFolder()) {
1475         AjxUtil.arrayRemove(list, ZmOperation.GROUPBY_FROM);
1476     }
1477 
1478     var actionListener = this._groupByActionListener.bind(this);
1479     var sortActionListener = this._sortByActionListener.bind(this);
1480 
1481 	if (!noSeparator) {
1482 	    parent.createSeparator();
1483 	}
1484 
1485 	var menuItem = parent.createMenuItem(Dwt.getNextId("GROUP_BY_"), { text: ZmMsg.groupBy, style: DwtMenuItem.NO_STYLE });
1486 	var menu = new ZmPopupMenu(menuItemIsParent ? menuItem : parent);
1487 
1488 	var groupById = Dwt.getNextId("GroupByActionMenu_");
1489     var sortById = Dwt.getNextId("SortByActionMenu_");
1490     for (var i = 0; i < list.length; i++) {
1491         var mi = menu.createMenuItem(list[i], {
1492 	        text:           ZmMsg[ZmOperation.getProp(list[i], "textKey")],
1493 	        style:          DwtMenuItem.RADIO_STYLE,
1494 	        radioGroupId:   groupById
1495         });
1496         mi.addSelectionListener(actionListener);
1497         if (this._group && this._group.id === list[i]) {
1498            mi.setChecked(true, true);
1499         }
1500         else if (!this._group && list[i] === ZmOperation.GROUPBY_NONE) {
1501            mi.setChecked(true, true);
1502         }
1503     }
1504 
1505     menu.createSeparator();
1506 
1507     var sortAsc = menu.createMenuItem(ZmOperation.SORT_ASC, {
1508 	    text:           ZmMsg[ZmOperation.getProp(ZmOperation.SORT_ASC, "textKey")],
1509 	    style:          DwtMenuItem.RADIO_STYLE,
1510 	    radioGroupId:   sortById
1511     });
1512     sortAsc.addSelectionListener(sortActionListener);
1513 
1514     var sortDesc = menu.createMenuItem(ZmOperation.SORT_DESC, {
1515 	    text:           ZmMsg[ZmOperation.getProp(ZmOperation.SORT_DESC, "textKey")],
1516 	    style:          DwtMenuItem.RADIO_STYLE,
1517 	    radioGroupId:   sortById
1518     });
1519     sortDesc.addSelectionListener(sortActionListener);
1520 
1521     if (this._bSortAsc) {
1522         sortAsc.setChecked(true, true);
1523     }
1524     else {
1525         sortDesc.setChecked(true, true);
1526     }
1527     menuItem.setMenu(menu);
1528 
1529     return menu;
1530 };
1531 
1532 ZmMailListView.prototype._groupByActionListener = function(ev) {
1533 
1534 	var groupId = ev && ev.item && ev.item.getData(ZmOperation.MENUITEM_ID);
1535 	//var oldGroup = this._group ? this._group : this.getGroup(this._folderId);
1536 	var oldGroup = this.getGroup(this._folderId);
1537 	var field = ZmMailListGroup.getHeaderField(groupId, this._isMultiColumn);
1538 	var hdr = this._headerHash[field];
1539 	if (!hdr) {
1540 		if (oldGroup) {
1541 			field = ZmMailListGroup.getHeaderField(oldGroup.id, this._isMultiColumn); //groups turned off, keep sort the same
1542 		}
1543 		else {
1544 		    field = ZmId.FLD_DATE;
1545 		}
1546 		hdr = this._headerHash[field];
1547 		this.setGroup(null);
1548 	}
1549 	else {
1550 		if (!oldGroup || (oldGroup.id != groupId)) {
1551 			hdr._sortable = ZmMailListGroup.getHeaderField(groupId);
1552 			this.setGroup(groupId);
1553 		}
1554 	}
1555 
1556 	if (!this._isMultiColumn) {
1557 	    //this sets the "Sort by: Field" for reading pane on right
1558 		var column = ZmMailListGroup.getHeaderField(groupId);
1559 		for (var i = 0; i < ZmMailListView.SINGLE_COLUMN_SORT.length; i++) {
1560 			if (column == ZmMailListView.SINGLE_COLUMN_SORT[i].field) {
1561 				if (this._colHeaderActionMenu) {
1562 					var mi = this._colHeaderActionMenu.getMenuItem(column);
1563 					if (mi) {
1564 						mi.setChecked(true, true);
1565 						var label = AjxMessageFormat.format(ZmMsg.arrangeBy, ZmMsg[ZmMailListView.SINGLE_COLUMN_SORT[i].msg]);
1566 						column = this._headerHash[ZmItem.F_SORTED_BY];
1567 						var cell = document.getElementById(DwtId.getListViewHdrId(DwtId.WIDGET_HDR_LABEL, this._view, field));
1568 						if (cell) {
1569 							cell.innerHTML = label;
1570 						}
1571 						break;
1572 					}
1573 				}
1574 			}
1575 		}
1576 	}
1577 
1578     if (ev && ev.item) {
1579         ev.item.setChecked(true, ev, true);
1580     }
1581     this._sortColumn(hdr, this._bSortAsc);
1582     //Hack: we don't re-fetch search results when list is size of 1; but if user changes their group let's re-render
1583     var list = this.getList();
1584     if (list && list.size() == 1 && this._sortByString) {
1585 	    this._renderList(list);
1586     }
1587 
1588 	var ctlr = this._controller;
1589 	ctlr._updateViewMenu(groupId, ctlr._groupByViewMenu);
1590 };
1591 
1592 ZmMailListView.prototype._sortByActionListener = function(ev) {
1593 
1594     var data = ev && ev.item && ev.item.getData(ZmOperation.MENUITEM_ID);
1595     var sortAsc = (data === ZmId.OP_SORT_ASC);
1596     var oldSort = this._bSortAsc;
1597     if (oldSort != sortAsc) {
1598         this._bSortAsc = sortAsc;
1599         var col = Dwt.byId(this._currentColId);
1600         var hdr = (col && this.getItemFromElement(col)) || (this._headerHash && this._headerHash[ZmItem.F_SORTED_BY]) || null;
1601         if (hdr) {
1602             this.clearGroupSections(this._folderId);
1603             this._sortColumn(hdr, this._bSortAsc);
1604             if (!this._isMultiColumn) {
1605                 this._setSortedColStyle(hdr._id);
1606             }
1607         }
1608     }
1609 
1610     if (ev && ev.item) {
1611         ev.item.setChecked(true, ev, true);
1612     }
1613 
1614 	var ctlr = this._controller;
1615 	ctlr._updateViewMenu(data, ctlr._groupByViewMenu);
1616 };
1617 
1618 ZmMailListView.prototype._sortMenuListener =
1619 function(ev) {
1620 
1621 	if (this._groupByActionMenu) {
1622 	    var mId = this._bSortAsc ? ZmOperation.OP_SORT_DESC : ZmOperation.OP_SORT_ASC;
1623 	    var mi = this._groupByActionMenu.getMenuItem(mId);
1624 	    if (mi) {
1625 	        mi.setChecked(true, true);
1626 	    }
1627 	}
1628     var sortField = ev && ev.item && ev.item.getData(ZmOperation.MENUITEM_ID);
1629     if (this._group && sortField) {
1630         var groupId = ZmMailListGroup.getGroupIdFromSortField(sortField, this.type);
1631         this.setGroup(groupId);
1632     }
1633     this._setGroupByCheck();
1634     ZmListView.prototype._sortMenuListener.call(this, ev);
1635 };
1636 
1637 ZmMailListView.prototype._sortColumn = function(columnItem, bSortAsc, callback) {
1638 
1639 	ZmListView.prototype._sortColumn.apply(this, arguments);
1640 
1641 	var ctlr = this._controller;
1642 	ctlr._updateViewMenu(columnItem._sortable, ctlr._sortViewMenu);
1643 };
1644 
1645 ZmMailListView.prototype._setGroupByCheck =
1646 function() {
1647 
1648 	if (this._groupByActionMenu) {
1649 		var mi = this._group && this._group.id ? this._groupByActionMenu.getMenuItem(this._group.id) : this._groupByActionMenu.getMenuItem(ZmOperation.GROUPBY_NONE);
1650 		if (mi) {
1651 			mi.setChecked(true, true);
1652 		}
1653 
1654 	    mi = this._bSortAsc ? this._groupByActionMenu.getMenuItem(ZmOperation.SORT_ASC) : this._groupByActionMenu.getMenuItem(ZmOperation.SORT_DESC);
1655 		if (mi) {
1656 			mi.setChecked(true, true);
1657 		}
1658 	}
1659 };
1660 
1661 /**
1662  * Adds a row for the given item to the list view.
1663  * Supports adding section header when group is set.
1664  *
1665  * @param {Object}	item			the data item
1666  * @param {number}	index			the index at which to add item to list and list view
1667  * @param {boolean}	skipNotify	if <code>true</code>, do not notify listeners
1668  * @param {number}	itemIndex		index at which to add item to list, if different
1669  * 									from the one for the list view
1670  */
1671 ZmMailListView.prototype.addItem =
1672 function(item, index, skipNotify, itemIndex) {
1673     var group = this._group;
1674     if (!group) {
1675         return ZmListView.prototype.addItem.call(this, item, index, skipNotify, itemIndex);
1676     }
1677 
1678 	if (!this._list) {
1679 		this._list = new AjxVector();
1680 	}
1681 
1682 	// clear the "no results" message before adding!
1683 	if (this._list.size() == 0) {
1684 		this._resetList();
1685 	}
1686 
1687     var section;
1688     var headerDiv;
1689 
1690 	this._list.add(item, (itemIndex != null) ? itemIndex : index);
1691 	var div = this._createItemHtml(item);
1692 	if (div) {
1693 		if (div instanceof Array) {
1694 			for (var j = 0; j < div.length; j++) {
1695                 section = group.addMsgToSection(item, div[j]);
1696                 if (group.getSectionSize(section) == 1){
1697                     headerDiv = this._getSectionHeaderDiv(group, section);
1698                     this._addRow(headerDiv);
1699                 }
1700 				this._addRow(div[j]);
1701 			}
1702 		}
1703 		else {
1704             section = group.addMsgToSection(item, div);
1705             if (group.getSectionSize(section) == 1){
1706                 headerDiv = this._getSectionHeaderDiv(group, section);
1707                 this._addRow(headerDiv, index);
1708             }
1709 			index = parseInt(index) || 0;  //check for NaN index
1710             this._addRow(div, index+1); //account for header
1711 
1712 		}
1713 	}
1714 
1715 	if (!skipNotify && this._evtMgr.isListenerRegistered(DwtEvent.STATE_CHANGE)) {
1716 		this._evtMgr.notifyListeners(DwtEvent.STATE_CHANGE, this._stateChangeEv);
1717 	}
1718 };
1719 
1720 /**
1721  * return the active search sortby value
1722  * @return {String} sortby value or null
1723  */
1724 ZmMailListView.prototype.getActiveSearchSortBy =
1725 function() {
1726 	var sortBy = AjxUtil.get(this._controller, "_activeSearch", "search", "sortBy") || null;
1727 	return sortBy;
1728 };
1729 
1730 /**
1731  * return folderId for the active search
1732  * @return {String} folderId or null
1733  */
1734 ZmMailListView.prototype.getActiveSearchFolderId =
1735 function() {
1736 	var folderId = AjxUtil.get(this._controller, "_activeSearch", "search", "folderId") || null;
1737 	return folderId;
1738 };
1739 
1740 ZmMailListView.prototype._changeFolderName = 
1741 function(msg, oldFolderId) {
1742 
1743 	var folder = appCtxt.getById(msg.folderId);
1744 
1745 	if (!this._controller.isReadingPaneOn() || !this._controller.isReadingPaneOnRight()) {
1746 		var folderCell = folder ? this._getElement(msg, ZmItem.F_FOLDER) : null;
1747 		if (folderCell) {
1748 			folderCell.innerHTML = folder.getName();
1749 		}
1750 	}
1751 
1752 	if (folder && (folder.nId == ZmFolder.ID_TRASH || oldFolderId == ZmFolder.ID_TRASH)) {
1753 		this._changeTrashStatus(msg);
1754 	}
1755 };
1756 
1757 ZmMailListView.prototype._changeTrashStatus = 
1758 function(msg) {
1759 
1760 	var row = this._getElement(msg, ZmItem.F_ITEM_ROW);
1761 	if (row) {
1762 		if (msg.isUnread) {
1763 			Dwt.addClass(row, "Unread");
1764 		}
1765 		var folder = appCtxt.getById(msg.folderId);
1766 		if (folder && folder.isInTrash()) {
1767 			Dwt.addClass(row, "Trash");
1768 		} else {
1769 			Dwt.delClass(row, "Trash");
1770 		}
1771 		if (msg.isSent) {
1772 			Dwt.addClass(row, "Sent");
1773 		}
1774 	}
1775 };
1776