1 /*
  2  * ***** BEGIN LICENSE BLOCK *****
  3  * Zimbra Collaboration Suite Web Client
  4  * Copyright (C) 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) 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 task list view classes.
 27  */
 28 
 29 /**
 30  * Creates the task list view.
 31  * @class
 32  * This class represents the task list view.
 33  * 
 34  * @param	{DwtComposite}	parent		the parent
 35  * @param	{ZmTaskController}		controller		the controller
 36  * @param	{DwtDropTarget}	dropTgt		the drop target	
 37  * 
 38  * @extends		ZmListView
 39  */
 40 ZmTaskListView = function(parent, controller, dropTgt) {
 41 
 42     this._controller = controller;
 43     
 44 	var headerList = this._getHeaderList(parent);
 45 
 46 	var idParams = {
 47 		skinComponent:  ZmId.SKIN_APP_MAIN,
 48 		app:            ZmId.APP_TASKS,
 49 		componentType:  ZmId.WIDGET_VIEW,
 50 		componentName:  ZmId.VIEW_TASKLIST
 51 	};
 52     var params = {
 53 	    parent:     parent,
 54         posStyle:   Dwt.ABSOLUTE_STYLE,
 55 	    view:       this._controller.getCurrentViewId(),
 56 	    id:         ZmId.create(idParams, "The main task list view"),
 57 	    pageless:   false,
 58 		type:       ZmItem.TASK,
 59 	    controller: controller,
 60 	    headerList: headerList,
 61 	    dropTgt:    dropTgt
 62     };
 63 
 64 	ZmListView.call(this, params);
 65 };
 66 
 67 ZmTaskListView.prototype = new ZmListView;
 68 ZmTaskListView.prototype.constructor = ZmTaskListView;
 69 
 70 ZmTaskListView.prototype.isZmTaskListView = true;
 71 ZmTaskListView.prototype.toString = function() { return "ZmTaskListView"; };
 72 
 73 
 74 
 75 ZmTaskListView.SASH_THRESHOLD = 5;
 76 
 77 // Consts
 78 ZmTaskListView.COL_WIDTH_STATUS		= ZmMsg.COLUMN_WIDTH_STATUS_TLV;
 79 ZmTaskListView.COL_WIDTH_PCOMPLETE	= ZmMsg.COLUMN_WIDTH_PCOMPLETE_TLV;
 80 ZmTaskListView.COL_WIDTH_DATE_DUE	= ZmMsg.COLUMN_WIDTH_DATE_DUE_TLV;
 81 
 82 //Consts
 83 ZmTaskListView.SEC_UPCOMING = "UPCOMING";
 84 ZmTaskListView.SEC_PASTDUE = "PASTDUE";
 85 ZmTaskListView.SEC_TODAY = "TODAY";
 86 ZmTaskListView.SEC_NODUEDATE = "NODUEDATE";
 87 
 88 ZmTaskListView.SEC_MSG_KEY = {};
 89 ZmTaskListView.SEC_MSG_KEY[ZmTaskListView.SEC_UPCOMING] = ZmMsg.taskSecUpcoming;
 90 ZmTaskListView.SEC_MSG_KEY[ZmTaskListView.SEC_PASTDUE] = ZmMsg.taskSecPastDue;
 91 ZmTaskListView.SEC_MSG_KEY[ZmTaskListView.SEC_TODAY] = ZmMsg.taskSecToday;
 92 ZmTaskListView.SEC_MSG_KEY[ZmTaskListView.SEC_NODUEDATE] = ZmMsg.taskSecNoDuedate;
 93 
 94 ZmTaskListView.SEC_COLOR = {};
 95 ZmTaskListView.SEC_COLOR[ZmTaskListView.SEC_UPCOMING] = "OrangeC";
 96 ZmTaskListView.SEC_COLOR[ZmTaskListView.SEC_PASTDUE] = "RedC";
 97 ZmTaskListView.SEC_COLOR[ZmTaskListView.SEC_TODAY] = "GreenC";
 98 ZmTaskListView.SEC_COLOR[ZmTaskListView.SEC_NODUEDATE] = "GrayDarkC";
 99 
100 ZmTaskListView.SINGLE_COLUMN_SORT = [
101     {field:ZmItem.F_SUBJECT,msg:"subject"},
102     {field:ZmItem.F_DATE,	msg:"date"},
103     {field:ZmItem.F_PRIORITY, msg:"priority" },
104     {field:ZmItem.F_STATUS, msg:"status" },
105     {field:ZmItem.F_PCOMPLETE, msg:"pComplete" },
106     {field:ZmItem.F_ATTACHMENT, msg:"attachment" }
107 ];
108 
109 ZmTaskListView.SORTBY_HASH = [];
110 ZmTaskListView.SORTBY_HASH[ZmSearch.SUBJ_ASC] = {field:ZmItem.F_SUBJECT, msg:"subject"};
111 ZmTaskListView.SORTBY_HASH[ZmSearch.SUBJ_DESC] = {field:ZmItem.F_SUBJECT, msg:"subject"};
112 ZmTaskListView.SORTBY_HASH[ZmSearch.DUE_DATE_ASC ] = {field:ZmItem.F_DATE, msg:"date"};
113 ZmTaskListView.SORTBY_HASH[ZmSearch.DUE_DATE_DESC ] = {field:ZmItem.F_DATE, msg:"date"};
114 ZmTaskListView.SORTBY_HASH[ZmSearch.PCOMPLETE_ASC] = {field:ZmItem.F_PCOMPLETE, msg:"pComplete"};
115 ZmTaskListView.SORTBY_HASH[ZmSearch.PCOMPLETE_DESC] = {field:ZmItem.F_PCOMPLETE, msg:"pComplete"};
116 ZmTaskListView.SORTBY_HASH[ZmSearch.STATUS_ASC] = {field:ZmItem.F_STATUS, msg:"status"};
117 ZmTaskListView.SORTBY_HASH[ZmSearch.STATUS_DESC] = {field:ZmItem.F_STATUS, msg:"status"};
118 ZmTaskListView.SORTBY_HASH[ZmSearch.PRIORITY_ASC] = {field:ZmItem.F_PRIORITY, msg:"priority"};
119 ZmTaskListView.SORTBY_HASH[ZmSearch.PRIORITY_DESC] = {field:ZmItem.F_PRIORITY, msg:"priority"};
120 ZmTaskListView.SORTBY_HASH[ZmSearch.ATTACH_ASC] = {field:ZmItem.F_ATTACHMENT, msg:"attachment"};
121 ZmTaskListView.SORTBY_HASH[ZmSearch.ATTACH_DESC] = {field:ZmItem.F_ATTACHMENT, msg:"attachment"};
122 ZmTaskListView.SORTBY_HASH[ZmSearch.FLAG_ASC] = {field:ZmItem.F_TAG, msg:"tag"};
123 ZmTaskListView.SORTBY_HASH[ZmSearch.FLAG_DESC] = {field:ZmItem.F_TAG, msg:"tag"};
124 
125 // Consts
126 ZmTaskListView.ROW_DOUBLE_CLASS	= "RowDouble";
127 
128 ZmTaskListView._NEW_TASK_ROW_ID = "_newTaskBannerId";
129 
130 // Public Methods
131 
132 ZmTaskListView.prototype.getNewTaskRowId = function() {
133 	  return this._htmlElId + ZmTaskListView._NEW_TASK_ROW_ID;
134 };
135 
136 
137 ZmTaskListView.prototype.setSize =
138 function(width, height) {
139 	ZmListView.prototype.setSize.call(this, width, height);
140 	this._resetColWidth();
141 };
142 
143 ZmTaskListView.prototype.hideNewTask =
144     function() {
145         if (this._newTaskInputEl && Dwt.getVisibility(this._newTaskInputEl)) {
146             Dwt.setVisibility(this._newTaskInputEl, false);
147         }
148     };
149 
150 /**
151  * Saves the new task.
152  * 
153  * @param	{Boolean}	keepFocus		if <code>true</code>, keep focus after the save
154  */
155 ZmTaskListView.prototype.saveNewTask =
156 function(keepFocus) {
157 	if (this._newTaskInputEl && Dwt.getVisibility(this._newTaskInputEl)) {
158 		var name = AjxStringUtil.trim(this._newTaskInputEl.value);
159 		if (name != "") {
160 			var respCallback = new AjxCallback(this, this._saveNewTaskResponse, [keepFocus]);
161 			var errorCallback = new AjxCallback(this, this._handleNewTaskError);
162 			this._controller.quickSave(name, respCallback, errorCallback);
163  		} else {
164 			this._saveNewTaskResponse(keepFocus);
165 		}
166 	}
167 };
168 
169 ZmTaskListView.prototype.showErrorMessage =
170     function(errorMsg) {
171         var dialog = appCtxt.getMsgDialog();
172         dialog.reset();
173         var msg = errorMsg ? AjxMessageFormat.format(ZmMsg.errorSavingWithMessage, errorMsg) : ZmMsg.errorSaving;
174         dialog.setMessage(msg, DwtMessageDialog.CRITICAL_STYLE);
175         dialog.popup();
176         dialog.registerCallback(DwtDialog.OK_BUTTON, function() {
177                 dialog.popdown();
178                 this._newTaskInputEl.focus();
179             },this);
180         this.enableToolbar(true);
181     };
182 
183 ZmTaskListView.prototype._saveNewTaskResponse =
184 function(keepFocus) {
185     this._newTaskInputEl.value = "";
186 	if (keepFocus) {
187 		this._newTaskInputEl.focus();
188 	} else {
189 		Dwt.setVisibility(this._newTaskInputEl, false);
190 	}
191 };
192 
193 ZmTaskListView.prototype._handleNewTaskError =
194 function(ex) {
195     if(ex) {
196         this.discardNewTask();   
197     }
198 };
199 
200 ZmTaskListView.prototype.handleKeyAction =
201 function(actionCode, ev) {
202 	if (this._editing) {
203 		switch (actionCode) {
204 			case DwtKeyMap.DBLCLICK:		break;
205 			default: DwtListView.prototype.handleKeyAction.call(this,actionCode,ev);
206 		}
207 	} else {
208 		DwtListView.prototype.handleKeyAction.call(this,actionCode,ev);
209 	}
210 };
211 
212 /**
213  * Discards the task.
214  * 
215  */
216 ZmTaskListView.prototype.discardNewTask =
217 function() {
218 	if (this._newTaskInputEl && Dwt.getVisibility(this._newTaskInputEl)) {
219 		this._newTaskInputEl.value = "";
220 		Dwt.setVisibility(this._newTaskInputEl, false);
221 		this.focus();
222 		this._editing =  false;
223 	}
224 };
225 
226 /**
227  * Gets the title.
228  * 
229  * @return	{String}		the title
230  */
231 ZmTaskListView.prototype.getTitle =
232 function() {
233 	return [ZmMsg.zimbraTitle, this._controller.getApp().getDisplayName()].join(": ");
234 };
235 
236 ZmTaskListView.prototype._renderTaskListItemHdr = 
237 function(sechdr) {
238     if(!this._newSecHdrHtml[sechdr]) {
239 
240         var htmlArr = [];
241         var idx = 0;
242 
243         htmlArr[idx++] = "<div id='_upComingTaskListHdr'>";
244         htmlArr[idx++] = "<table width=100% class='DwtListView-Column'><tr>";
245         this.dId = Dwt.getNextId();
246         htmlArr[idx++] = "<td><div class='DwtListHeaderItem-label ";
247         htmlArr[idx++] = ZmTaskListView.SEC_COLOR[sechdr];
248         htmlArr[idx++] = "' style='padding:0px 0px 2px 2px; font-weight:bold;' id='";
249         htmlArr[idx++] = this.dId;	// bug: 17653 - for QA
250         htmlArr[idx++] = "'>";
251         htmlArr[idx++] = ZmTaskListView.SEC_MSG_KEY[sechdr];
252         htmlArr[idx++] = "</div></td>";
253         htmlArr[idx++] = "</tr></table></div>";
254         return this._newSecHdrHtml[sechdr] = htmlArr.join("");
255    } else {
256         return null;     
257    }
258 };
259 
260 ZmTaskListView.prototype.setTaskInputVisible = function(visible) {
261     var el = document.getElementById(this.getNewTaskRowId());
262     if (el) {
263         Dwt.setVisible(el, visible);
264     }
265 };
266 
267 // Private Methods
268 ZmTaskListView.prototype._renderList =
269 function(list, noResultsOk, doAdd) {
270 	// call base class first
271 	//ZmListView.prototype._renderList.apply(this, arguments);
272     this._newSecHdrHtml = {};
273 
274     if (list instanceof AjxVector && list.size()) {
275 		var now = new Date();
276 		var size = list.size();
277 		var htmlArr = [];
278         var currentSec = null;
279 
280         var htmlUpcomingArr = [];
281         var htmlPastDueArr = [];
282         var htmlTodayArr = [];
283         var htmlNoDueArr = [];
284         
285 		for (var i = 0; i < size; i++) {
286 			var item = list.get(i);
287 
288             var today = new Date();
289             today.setHours(0,0,0,0);
290             today = today.getTime();
291 
292             var dueDate = item.endDate;
293             if(dueDate != null) {
294                 dueDate.setHours(0,0,0,0);
295                 dueDate = dueDate.getTime();
296             } else {
297                 dueDate = null;
298             }
299             
300             if(dueDate != null && dueDate > today) {
301                var newSecHdrHtml = this._renderTaskListItemHdr(ZmTaskListView.SEC_UPCOMING);
302                if(newSecHdrHtml) htmlUpcomingArr.push(newSecHdrHtml);
303                 currentSec = ZmTaskListView.SEC_UPCOMING;
304             } else if(dueDate != null && dueDate == today) {
305                 var newSecHdrHtml = this._renderTaskListItemHdr(ZmTaskListView.SEC_TODAY);
306                 if(newSecHdrHtml) htmlTodayArr.push(newSecHdrHtml);
307                 currentSec = ZmTaskListView.SEC_TODAY;
308             } else if(dueDate != null && dueDate < today) {
309                 var newSecHdrHtml = this._renderTaskListItemHdr(ZmTaskListView.SEC_PASTDUE);
310                 if(newSecHdrHtml) htmlPastDueArr.push(newSecHdrHtml);
311                 currentSec = ZmTaskListView.SEC_PASTDUE; 
312             } else if(dueDate == null) {
313                 var newSecHdrHtml = this._renderTaskListItemHdr(ZmTaskListView.SEC_NODUEDATE);
314                 if(newSecHdrHtml) htmlNoDueArr.push(newSecHdrHtml);
315                 currentSec = ZmTaskListView.SEC_NODUEDATE;
316             } else {
317                 currentSec = null;
318             }
319 
320             var taskStatusClass = this._normalClass;
321 
322             if(item.status == ZmCalendarApp.STATUS_COMP) {
323                taskStatusClass += " ZmCompletedtask";
324             } else if(item.status != ZmCalendarApp.STATUS_COMP && currentSec == ZmTaskListView.SEC_PASTDUE) {
325                taskStatusClass += " ZmOverduetask";
326             }
327 
328 			var div = this._createItemHtml(item, {now:now,divClass:taskStatusClass}, true, i);
329             if (div) {
330 				if (div instanceof Array) {
331 					for (var j = 0; j < div.length; j++){
332 						this._addRow(div[j]);
333 					}
334 				} else if (div.tagName || doAdd) {
335 					this._addRow(div);
336 				} else {
337                     //bug:47781
338 					if(this._controller.getAllowableTaskStatus() == ZmTaskListController.SOAP_STATUS[ZmId.VIEW_TASK_TODO] && item.status == ZmCalendarApp.STATUS_WAIT) {
339 							if(currentSec == ZmTaskListView.SEC_PASTDUE) {
340 								htmlPastDueArr.push(div);
341 							}
342 							continue;
343 					}
344 					
345                     if(currentSec == ZmTaskListView.SEC_UPCOMING) {
346 					    htmlUpcomingArr.push(div);
347                     } else if(currentSec == ZmTaskListView.SEC_TODAY) {
348                         htmlTodayArr.push(div);
349                     } else if(currentSec == ZmTaskListView.SEC_PASTDUE) {
350                         htmlPastDueArr.push(div);
351                     } else if(currentSec == ZmTaskListView.SEC_NODUEDATE) {
352                         htmlNoDueArr.push(div); 
353                     } else {
354                         htmlArr.push(div);
355                     }
356 				}
357 			}
358 		}
359         
360         //bug:50890 in chronological order
361         var sortBy = appCtxt.get(ZmSetting.SORTING_PREF, this.view);
362 
363         if(sortBy == ZmSearch.DUE_DATE_DESC) {
364             if(htmlUpcomingArr.length) htmlArr.push(htmlUpcomingArr.join(""));
365             if(htmlTodayArr.length) htmlArr.push(htmlTodayArr.join(""));
366             if(htmlPastDueArr.length) htmlArr.push(htmlPastDueArr.join(""));
367             if(htmlNoDueArr.length) htmlArr.push(htmlNoDueArr.join(""));
368         } else {
369             if(htmlPastDueArr.length) htmlArr.push(htmlPastDueArr.join(""));
370             if(htmlTodayArr.length) htmlArr.push(htmlTodayArr.join(""));
371             if(htmlUpcomingArr.length) htmlArr.push(htmlUpcomingArr.join(""));
372             if(htmlNoDueArr.length) htmlArr.push(htmlNoDueArr.join(""));
373         }
374 
375 		if (htmlArr.length) {
376 			this._parentEl.innerHTML = htmlArr.join("");
377 		}
378 	} else if (!noResultsOk) {
379 		this._setNoResultsHtml();
380 	}
381 
382     if (doAdd || (this._controller && this._controller.isReadOnly())) { return; }
383 
384 	// add custom row to allow user to quickly enter tasks from w/in listview
385 	div = document.createElement("DIV");
386 	div.id = this.getNewTaskRowId();
387 
388 	htmlArr = [];
389 	var idx = 0;
390 
391 	htmlArr[idx++] = "<table width=100% class='newTaskBannerSep'><tr>";
392 	for (var i = 0; i < this._headerList.length; i++) {
393 		var hdr = this._headerList[i];
394 		if (!hdr._visible) { continue; }
395 
396 		if (hdr._field == ZmItem.F_SUBJECT || hdr._field == ZmItem.F_SORTED_BY) {
397 			this.dId = Dwt.getNextId();
398 			htmlArr[idx++] = "<td><div class='newTaskBanner' onclick='ZmTaskListView._handleOnClick(this)' id='";
399 			htmlArr[idx++] = this.dId;	// bug: 17653 - for QA
400 			htmlArr[idx++] = "'>";
401 			htmlArr[idx++] = ZmMsg.createNewTaskHint;
402 			htmlArr[idx++] = "</div></td>";
403 		} else {
404 			htmlArr[idx++] = "<td width=";
405 			htmlArr[idx++] = hdr._width;
406 			htmlArr[idx++] = "> </td>";
407 		}
408 	}
409 	htmlArr[idx++] = "</tr></table>";
410 	div.innerHTML = htmlArr.join("");
411 	this._addRow(div, 0);
412     //this._renderTaskListItemHdr();
413 };
414 
415 ZmTaskListView.prototype._resetListView =
416 function() {
417 	// explicitly remove each child (setting innerHTML causes mem leak)
418 	var cDiv;
419 	var newTaskRowId = this.getNewTaskRowId();
420 	while (this._parentEl.hasChildNodes()) {
421 		if (this._parentEl.lastChild.id === newTaskRowId) { break; }
422 		cDiv = this._parentEl.removeChild(this._parentEl.lastChild);
423 		this._data[cDiv.id] = null;
424 	}
425 	this._selectedItems.removeAll();
426 	this._rightSelItems = null;
427 };
428 
429 ZmTaskListView.prototype._getCellId =
430 function(item, field) {
431     if(field == ZmItem.F_PRIORITY || field == ZmItem.F_SUBJECT || field == ZmItem.F_STATUS || field == ZmItem.F_PCOMPLETE || field == ZmItem.F_DATE) {
432 	    return this._getFieldId(item, field)
433     } else if (field == ZmItem.F_SELECTION) {
434 		return this._getFieldId(item, ZmItem.F_SELECTION_CELL);
435 	} else {
436 		return DwtListView.prototype._getCellId.apply(this, arguments);
437 	}
438 };
439 
440 ZmTaskListView.prototype.setTask =
441 function(task) {
442 	this._taskReadOnlyView.set(task);
443 };
444 
445 ZmTaskListView.prototype._getAbridgedCell =
446 function(htmlArr, idx, item, field, colIdx, width, attr) {
447 	var params = {};
448 
449 	htmlArr[idx++] = "<td";
450 	if (width) {
451 		htmlArr[idx++] = " width='";
452 		htmlArr[idx++] = width;
453 		htmlArr[idx++] = "'";
454 	}
455 	htmlArr[idx++] = " id='";
456 	htmlArr[idx++] = this._getCellId(item, field, params);
457 	htmlArr[idx++] = "'";
458 	var className = this._getCellClass(item, field, params);
459 	if (className) {
460 		htmlArr[idx++] = " class='";
461 		htmlArr[idx++] = className;
462 		htmlArr[idx++] = "'";
463 	}
464 	if (attr) {
465 		htmlArr[idx++] = " ";
466 		htmlArr[idx++] = attr;
467 	}
468 	htmlArr[idx++] = ">";
469 	idx = this._getCellContents(htmlArr, idx, item, field, colIdx, params);
470 	htmlArr[idx++] = "</td>";
471 
472 	return idx;
473 };
474 
475 ZmTaskListView.prototype.getColorForStatus =
476 function(status) {
477     switch (status) {
478 		case ZmCalendarApp.STATUS_CANC: return "YellowDark";
479 		case ZmCalendarApp.STATUS_COMP: return "Green";
480 		case ZmCalendarApp.STATUS_DEFR: return "Red";
481 		case ZmCalendarApp.STATUS_INPR: return "Blue";
482 		case ZmCalendarApp.STATUS_NEED: return "";
483 		case ZmCalendarApp.STATUS_WAIT: return "Orange";
484 	}
485 	return "";
486 };
487 
488 ZmTaskListView.prototype._getAbridgedContent =
489 function(task, colIdx) {
490 	var htmlArr = [];
491 	var idx = 0;
492 	var width = (AjxEnv.isIE || AjxEnv.isSafari) ? "22" : "16";
493 
494 	// first row
495 	htmlArr[idx++] = "<table width=100% class='TopRow'>";
496 	htmlArr[idx++] = "<tr id='";
497 	htmlArr[idx++] = DwtId.getListViewItemId(DwtId.WIDGET_ITEM_FIELD, this._view, task.id, ZmItem.F_ITEM_ROW_3PANE);
498 	htmlArr[idx++] = "'>";
499 
500     idx = this._getAbridgedCell(htmlArr, idx, task, ZmItem.F_SUBJECT, colIdx);
501 
502     idx = this._getAbridgedCell(htmlArr, idx, task, ZmItem.F_DATE, colIdx, ZmMsg.COLUMN_WIDTH_DATE, "align=right");
503 
504 	htmlArr[idx++] = "</tr></table>";
505 
506     // second row
507     htmlArr[idx++] = "<table width=100% class='BottomRow'><tr><td>";
508 	if (task.pComplete) {
509 		htmlArr[idx++] = "<div class='ZmTaskProgress'><div";
510 		htmlArr[idx++] = " class='";
511 		htmlArr[idx++] = this.getColorForStatus(task.status);
512 		htmlArr[idx++] = "' style='width:"+ task.pComplete + "%;'></div></div>";
513 	}
514     htmlArr[idx++] = "</td><td width=75 align=right><table><tr>";
515 
516     idx = this._getAbridgedCell(htmlArr, idx, task, ZmItem.F_TAG, colIdx, width);
517     if(task.priority == ZmCalItem.PRIORITY_HIGH || task.priority == ZmCalItem.PRIORITY_LOW) {
518         idx = this._getAbridgedCell(htmlArr, idx, task, ZmItem.F_PRIORITY, colIdx, width, "align=right");
519     }
520     if (task.hasAttach) {
521         idx = this._getAbridgedCell(htmlArr, idx, task, ZmItem.F_ATTACHMENT, colIdx, width);
522     }
523     htmlArr[idx++] = "</tr></table></td>";
524     htmlArr[idx++] = "</tr></table>";
525 
526 	return htmlArr.join("");
527 
528 };
529 
530 
531 ZmTaskListView.prototype._getCellContents =
532 function(htmlArr, idx, task, field, colIdx, params) {
533 
534 	if (field == ZmItem.F_SELECTION) {
535 		var icon = params.bContained ? "CheckboxChecked" : "CheckboxUnchecked";
536 		idx = this._getImageHtml(htmlArr, idx, icon, this._getFieldId(task, field));
537 
538 	} else if (field == ZmItem.F_PRIORITY) {
539 		htmlArr[idx++] = "<center>";
540 		htmlArr[idx++] = ZmCalItem.getImageForPriority(task, params.fieldId);
541 		htmlArr[idx++] = "</center>";
542 
543 	} else if (field == ZmItem.F_SUBJECT) {
544 		htmlArr[idx++] = AjxStringUtil.htmlEncode(task.getName(), true);
545 
546 	} else if (field == ZmItem.F_STATUS) {
547 		htmlArr[idx++] = ZmCalItem.getLabelForStatus(task.status);
548 
549 	} else if (field == ZmItem.F_PCOMPLETE) {	// percent complete
550         var formatter = new AjxMessageFormat(AjxMsg.percentageString);
551 		htmlArr[idx++] = formatter.format(task.pComplete || 0);
552 	} else if (field == ZmItem.F_DATE) {
553 		// due date - dont call base class since we *always* want to show date (not time)
554 		htmlArr[idx++] = task.endDate != null
555 			? AjxDateUtil.simpleComputeDateStr(task.endDate)
556 			: " ";
557 	} else if (field == ZmItem.F_SORTED_BY) {
558         htmlArr[idx++] = this._getAbridgedContent(task, colIdx);
559     } else if (field == ZmItem.F_TAG) {
560         idx = this._getImageHtml(htmlArr, idx, task.getTagImageInfo(), this._getFieldId(task, field));
561     } else {
562 		idx = ZmListView.prototype._getCellContents.apply(this, arguments);
563 	}
564 	
565 	return idx;
566 };
567 
568 ZmTaskListView.prototype._getHeaderToolTip =
569 function(field, itemIdx) {
570 	switch (field) {
571 		case ZmItem.F_STATUS:		return ZmMsg.sortByStatus;
572 		case ZmItem.F_PCOMPLETE:	return ZmMsg.sortByPComplete;
573 		case ZmItem.F_DATE:			return ZmMsg.sortByDueDate;
574         case ZmItem.F_PRIORITY:	    return ZmMsg.sortByPriority;
575         case ZmItem.F_ATTACHMENT:	return ZmMsg.sortByAttachment;
576         case ZmItem.F_TAG:	        return ZmMsg.sortByTag;
577 	}
578 	return ZmListView.prototype._getHeaderToolTip.call(this, field, itemIdx);
579 };
580 
581 ZmTaskListView.prototype._sortColumn =
582 function(columnItem, bSortAsc) {
583 	// change the sort preference for this view in the settings
584 	var sortBy;
585 	switch (columnItem._sortable) {
586 		case ZmItem.F_SUBJECT:		sortBy = bSortAsc ? ZmSearch.SUBJ_ASC : ZmSearch.SUBJ_DESC; break;
587 		case ZmItem.F_STATUS:		sortBy = bSortAsc ? ZmSearch.STATUS_ASC : ZmSearch.STATUS_DESC; break;
588 		case ZmItem.F_PCOMPLETE:	sortBy = bSortAsc ? ZmSearch.PCOMPLETE_ASC : ZmSearch.PCOMPLETE_DESC; break;
589 		case ZmItem.F_DATE:			sortBy = bSortAsc ? ZmSearch.DUE_DATE_ASC : ZmSearch.DUE_DATE_DESC;	break; //bug:50890 changed the default order
590         case ZmItem.F_PRIORITY:     sortBy = bSortAsc ? ZmSearch.PRIORITY_ASC : ZmSearch.PRIORITY_DESC;	break;
591         case ZmItem.F_ATTACHMENT:   sortBy = bSortAsc ? ZmSearch.ATTACH_ASC : ZmSearch.ATTACH_DESC;	break;
592         case ZmItem.F_TAG:          sortBy = bSortAsc ? ZmSearch.FLAG_ASC : ZmSearch.FLAG_DESC;	break;
593         case ZmItem.F_SORTED_BY:    sortBy = bSortAsc ? ZmSearch.DUE_DATE_ASC : ZmSearch.DUE_DATE_DESC;	break;
594 	}
595 
596     if (sortBy) {
597 		this._sortByString = sortBy;
598         if (!appCtxt.isExternalAccount()) {
599 		    appCtxt.set(ZmSetting.SORTING_PREF, sortBy, this.view);
600         }
601 	}
602 
603 	var list = this.getList();
604 	var size = list ? list.size() : 0;
605 	if (size > 0 && this._sortByString) {
606 		var params = {
607 			query: this._controller.getSearchString(),
608 			queryHint: this._controller.getSearchStringHint(),
609 			types: [ZmItem.TASK],
610 			sortBy: this._sortByString,
611 			limit: this.getLimit()
612 		};
613 		appCtxt.getSearchController().search(params);
614 	}
615 };
616 
617 ZmTaskListView.prototype._handleNewTaskClick =
618 function(el) {
619     if  (appCtxt.isExternalAccount()) {
620         return;
621     }
622 	if (!this._newTaskInputEl) {
623 		this._newTaskInputEl = document.createElement("INPUT");
624 		this._newTaskInputEl.type = "text";
625 		this._newTaskInputEl.className = "InlineWidget";
626 		this._newTaskInputEl.style.position = "absolute";
627 		this._newTaskInputEl.id = Dwt.getNextId();								// bug: 17653 - for QA
628 
629 		Dwt.setHandler(this._newTaskInputEl, DwtEvent.ONBLUR, ZmTaskListView._handleOnBlur);
630 		Dwt.setHandler(this._newTaskInputEl, DwtEvent.ONKEYPRESS, ZmTaskListView._handleKeyPress);
631 
632 		// The input field must be a child of the list container, otherwise it will not be shown/hidden properly.
633 		// However, it cannot be a child of the list itself, since it should have a fixed position.
634 		var parentEl = document.getElementById(this._htmlElId);
635 		parentEl.appendChild(this._newTaskInputEl);
636 
637 		this._newTaskInputEl.style.padding  = "0px";
638 		this._newTaskInputEl.style.borderWidth  = "0px";
639 
640 		this._resetInputSize(el);
641 	} else {
642         // Preserve any existing newTask text.  This will be cleared when
643         // a task is successfully created, leaving it empty for the next task
644 		//this._newTaskInputEl.value = "";
645 	}
646 	Dwt.setVisibility(this._newTaskInputEl, true);
647 	this._newTaskInputEl.focus();
648 	this._editing =  true;
649 };
650 
651 
652 ZmTaskListView.prototype._handleColHeaderResize =
653 function(ev) {
654 	ZmListView.prototype._handleColHeaderResize.call(this, ev);
655 	this._newTaskInputEl = null;
656 };
657 
658 ZmTaskListView.prototype._getHeaderList =
659 function(parent) {
660 
661 	var hList = [];
662     var sortBy = "date";
663     var field  =  ZmItem.F_DATE;
664     var activeSortBy = this.getActiveSearchSortBy();
665     if (activeSortBy && ZmTaskListView.SORTBY_HASH[activeSortBy]) {
666 		sortBy = ZmTaskListView.SORTBY_HASH[activeSortBy].msg;
667         field = ZmTaskListView.SORTBY_HASH[activeSortBy].field;
668 	}
669 
670     if (appCtxt.get(ZmSetting.SHOW_SELECTION_CHECKBOX)) {
671         hList.push(new DwtListHeaderItem({field:ZmItem.F_SELECTION, icon:"CheckboxUnchecked", width:ZmListView.COL_WIDTH_ICON, name:ZmMsg.selection}));
672     }
673     if (this.isMultiColumn()) {
674         if (appCtxt.get(ZmSetting.TAGGING_ENABLED)) {
675             hList.push(new DwtListHeaderItem({field:ZmItem.F_TAG, icon:"Tag", width:ZmListView.COL_WIDTH_ICON, name:ZmMsg.tag, sortable:ZmItem.F_TAG}));
676         }
677         hList.push(new DwtListHeaderItem({field:ZmItem.F_PRIORITY, icon:"PriorityHigh_list", width:ZmListView.COL_WIDTH_ICON, name:ZmMsg.priority, sortable:ZmItem.F_PRIORITY}));
678         hList.push(new DwtListHeaderItem({field:ZmItem.F_ATTACHMENT, icon:"Attachment", width:ZmListView.COL_WIDTH_ICON, name:ZmMsg.attachment, sortable:ZmItem.F_ATTACHMENT}));
679         hList.push(new DwtListHeaderItem({field:ZmItem.F_SUBJECT, text:ZmMsg.subject, sortable:ZmItem.F_SUBJECT, resizeable:true, noRemove:true}));
680         hList.push(new DwtListHeaderItem({field:ZmItem.F_STATUS, text:ZmMsg.status, width:ZmTaskListView.COL_WIDTH_STATUS, resizeable:true, sortable:ZmItem.F_STATUS}));
681         hList.push(new DwtListHeaderItem({field:ZmItem.F_PCOMPLETE, text:ZmMsg.pComplete, width:ZmTaskListView.COL_WIDTH_PCOMPLETE, sortable:ZmItem.F_PCOMPLETE}));
682         hList.push(new DwtListHeaderItem({field:ZmItem.F_DATE, text:ZmMsg.dateDue, width:ZmTaskListView.COL_WIDTH_DATE_DUE, sortable:ZmItem.F_DATE}));
683     }
684 	else {
685         hList.push(new DwtListHeaderItem({field:ZmItem.F_SORTED_BY, text:AjxMessageFormat.format(ZmMsg.arrangedBy, ZmMsg[sortBy]), sortable:field, resizeable:false}));
686 	}
687 	return hList;
688 };
689 
690 ZmTaskListView.prototype._createHeader =
691 function(htmlArr, idx, headerCol, i, numCols, id, defaultColumnSort) {
692     if (headerCol._field == ZmItem.F_SORTED_BY) {
693 		var field = headerCol._field;
694 		var textTdId = this._itemCountTextTdId = DwtId.makeId(this.view, ZmSetting.RP_RIGHT, "td");
695 		htmlArr[idx++] = "<td id='";
696 		htmlArr[idx++] = id;
697 		htmlArr[idx++] = "' class='";
698 		htmlArr[idx++] = (id == this._currentColId)	? "DwtListView-Column DwtListView-ColumnActive'" :
699 													  "DwtListView-Column'";
700 		htmlArr[idx++] = " width='auto'><table width='100%'><tr><td id='";
701 		htmlArr[idx++] = DwtId.getListViewHdrId(DwtId.WIDGET_HDR_LABEL, this._view, field);
702 		htmlArr[idx++] = "' class='DwtListHeaderItem-label'>";
703 		htmlArr[idx++] = headerCol._label;
704 		htmlArr[idx++] = "</td>";
705 
706 		// sort icon
707 		htmlArr[idx++] = "<td class='itemSortIcon' id='";
708 		htmlArr[idx++] = DwtId.getListViewHdrId(DwtId.WIDGET_HDR_ARROW, this._view, field);
709 		htmlArr[idx++] = "'>";
710 		htmlArr[idx++] = AjxImg.getImageHtml(this._bSortAsc ? "ColumnUpArrow" : "ColumnDownArrow");
711 		htmlArr[idx++] = "</td>";
712 
713 		// item count text
714 		htmlArr[idx++] = "<td align=right class='itemCountText' id='";
715 		htmlArr[idx++] = textTdId;
716 		htmlArr[idx++] = "'></td></tr></table></div></td>";
717 	} else {
718         return DwtListView.prototype._createHeader.apply(this, arguments);
719 	}
720 };
721 
722 
723 // Listeners
724 // this method simply appends the given list to this current one
725 ZmTaskListView.prototype.replenish =
726 function(list) {
727 	this._list.addList(list);
728 	this._renderList(this.getList(),true,false);
729 };
730 
731 ZmTaskListView.prototype.checkTaskReplenishListView = function() {
732     this._controller._app._checkReplenishListView = this;
733 };
734 
735 ZmTaskListView.prototype._changeListener =
736 function(ev) {
737 	if (ev.type != this.type)
738 		return;
739 
740     var resort = false;
741     var folderId = this._controller.getList().search.folderId;
742     if (appCtxt.getById(folderId) &&
743         appCtxt.getById(folderId).isRemote())
744     {
745         folderId = appCtxt.getById(folderId).getRemoteId();
746     }
747 
748     if (appCtxt.isOffline) {
749         folderId = ZmOrganizer.getSystemId(folderId);
750     }
751 
752     //TODO: Optimize ChangeListener logic
753 	var items = ev.getDetail("items") || ev.items;
754     var filter = this._controller.getAllowableTaskStatus();
755     items = AjxUtil.toArray(items);
756     if (ev.event == ZmEvent.E_CREATE || (ev.event == ZmEvent.E_MODIFY && !this._getElFromItem(items[0]))) {
757 		for (var i = 0; i < items.length; i++) {
758 			var item = items[i];
759 
760 			// skip if this item does not belong in this list.
761 
762 			if (!folderId || folderId != item.folderId) { continue; }			// does not belong to this folder
763 			if (this._list && this._list.contains(item)) { continue; }			// skip if we already have it
764 
765 
766 			if (!this._list) {
767 				this._list = new AjxVector();
768 			}
769 			// clear the "no results" message before adding!
770 			if (this._list.size() == 0) {
771 				this._resetList();
772 			}
773 			// Check if the item is part of current view
774             if (!filter || filter.indexOf(item.status) != -1){
775                 // add new item at the beg. of list view's internal list
776                 this._list.add(item, 0);
777                 this._renderList(this.getList(),true,false);
778                 if(this._list && this._list.size() == 1) { this.setSelection(this._list.get(0)); }
779 				this.checkTaskReplenishListView();
780             }
781 		}
782 	} else if (ev.event == ZmEvent.E_MODIFY) {
783 		var task = items[0];
784         var div = this._getElFromItem(task);
785         if (this._list) {
786             var origTaskIndex = this._list.indexOfLike(task, task.getId);
787             if (origTaskIndex != -1) this._list.replace(origTaskIndex, task);
788         }
789 		if (div) {
790             if (filter && filter.indexOf(task.status) == -1){
791                 // If task status is modified and item is not part of current view
792                 var parentNode = div.parentNode;
793                 parentNode && parentNode.removeChild(div);
794                 if(this._controller.isReadingPaneOn()) {
795                     this._controller.getTaskMultiView().getTaskView().reset();
796                 }
797             } else{
798                 var bContained = this._selectedItems.contains(div);
799 
800 				var today = new Date();
801 		        today.setHours(0,0,0,0);
802 		        today = today.getTime();
803 
804 		        var dueDate = task.endDate;
805 		        if (dueDate != null) {
806 		            dueDate.setHours(0,0,0,0);
807 		            dueDate = dueDate.getTime();
808                     // May change the section the task is sorted under
809                     resort = true;
810 		        }
811 
812 				var taskStatusClass = this._normalClass;
813 				if (task.status == ZmCalendarApp.STATUS_COMP) {
814 		           taskStatusClass += " ZmCompletedtask";
815 		        } else if (dueDate != null && dueDate < today) {
816 		           taskStatusClass += " ZmOverduetask";
817 		        }
818 
819                 this._createItemHtml(task, {div:div, bContained:bContained, divClass:taskStatusClass});
820                 this.associateItemWithElement(task, div);
821                 if(this._controller.isReadingPaneOn()) {
822                     task.message = null;
823 			        task.getDetails(ZmCalItem.MODE_EDIT, new AjxCallback(this._controller, this._controller._showTaskReadOnlyView, [task, false]))
824                 }
825                 this.checkTaskReplenishListView();
826             }
827 		}
828 	} else if (ev.event == ZmEvent.E_DELETE || ev.event == ZmEvent.E_MOVE) {
829         var needsSort = false;
830         for (var i = 0, len = items.length; i < len; i++) {
831 			var item = items[i];
832             var evOp = (ev.event == ZmEvent.E_MOVE) ? ZmEvent.E_MOVE : ZmEvent.E_DELETE;
833             var movedHere = (item.type === ZmId.ITEM_CONV) ? item.folders[folderId] : item.folderId === folderId;
834 			if (movedHere && ev.event == ZmEvent.E_MOVE) {
835 				// We've moved the item into this folder
836 				if (this._getRowIndex(item) === null) { // Not already here
837 					this.addItem(item);
838 					needsSort = true;
839 				}
840 			} else {
841 				this.removeItem(item, true, ev.batchMode);
842 				// if we've removed it from the view, we should remove it from the reference
843 				// list as well so it doesn't get resurrected via replenishment *unless*
844 				// we're dealing with a canonical list (i.e. contacts)
845 				var itemList = this.getItemList();
846 				if (ev.event != ZmEvent.E_MOVE || !itemList.isCanonical) {
847 					itemList.remove(item);
848 				}
849 			}
850 		}
851         if(needsSort) {
852             this.checkTaskReplenishListView();
853         }
854         this._controller._resetNavToolBarButtons();
855 		if(this._controller.isReadingPaneOn()) {
856 			this._controller.getTaskMultiView().getTaskView().reset();
857 		}
858 	} else {
859 		ZmListView.prototype._changeListener.call(this, ev);
860 	}
861 	this._controller._resetToolbarOperations(this.view);
862 
863     //Handle Create Notification
864     if(ev.event == ZmEvent.E_MOVE){
865         for (var i = 0; i < items.length; i++) {
866             var item = items[i];
867             if(item && item.folderId == folderId && this._getRowIndex(item) === null){
868                 this.addItem(item, null, true);
869             }
870         }
871     }
872 
873 	if (ev.event == ZmEvent.E_CREATE ||
874 		ev.event == ZmEvent.E_DELETE ||
875 		ev.event == ZmEvent.E_MOVE)
876 	{
877 		this._resetColWidth();
878 	}
879 
880     if (resort) {
881        this._renderList(this._list);
882     }
883     //this.reRenderListView();
884 };
885 
886 
887 // Static Methods
888 
889 ZmTaskListView._handleOnClick =
890 function(div) {
891 	var appCtxt = window.parentAppCtxt || window.appCtxt;
892 	var tlv = appCtxt.getApp(ZmApp.TASKS).getTaskListController().getListView();
893 	tlv._handleNewTaskClick(div);
894 };
895 
896 ZmTaskListView._handleOnBlur = function(ev) {
897 	
898 	var appCtxt = window.parentAppCtxt || window.appCtxt,
899     	tlv = appCtxt.getApp(ZmApp.TASKS).getTaskListController().getListView(),
900 		value = AjxStringUtil.trim(tlv._newTaskInputEl.value);
901 	
902 	if (!value) {
903 		tlv.hideNewTask();
904 	}
905 };
906 
907 ZmTaskListView.prototype._selectItem =
908 function(next, addSelect, kbNavEvent) {
909 	if (!next) {
910 		var itemDiv = (this._kbAnchor)
911 		? this._getSiblingElement(this._kbAnchor, next)
912 		: this._parentEl.firstChild;
913 		if (itemDiv && itemDiv.id === this.getNewTaskRowId()) {
914 			document.getElementById(this.dId).onclick();
915 			return;
916 		}
917 	}
918 	DwtListView.prototype._selectItem.call(this,next,addSelect,kbNavEvent);
919 };
920 
921 ZmTaskListView._handleKeyPress = function(ev) {
922 
923 	var key = DwtKeyEvent.getCharCode(ev);
924 	var appCtxt = window.parentAppCtxt || window.appCtxt;
925 	var tlv = appCtxt.getApp(ZmApp.TASKS).getTaskListController().getListView();
926 
927 	if (DwtKeyEvent.IS_RETURN[key]) {
928 		tlv.saveNewTask(true);
929 	}
930 };
931 
932 /**
933  * Returns true if the reading pane is turned off or set to bottom. We use this
934  * call to tell the UI whether to re-render the listview with multiple columns
935  * or a single column (for right-pane).
936  */
937 ZmTaskListView.prototype.isMultiColumn =
938 function(controller) {
939 	var ctlr = controller || this._controller;
940 	return !ctlr.isReadingPaneOnRight();
941 };
942 
943 
944 ZmTaskListView.prototype.updateListViewEl = 
945 function(task) {
946 	var div = this._getElFromItem(task);
947 	if (div) {
948         if (this._controller.isHiddenTask(task)){
949             this.removeItem(task, true);
950             if(this._controller.isReadingPaneOn()) {
951 			    this._controller.getTaskMultiView().getTaskView().reset();
952 		    }
953         }else{
954 		    var bContained = this._selectedItems.contains(div);
955 		    this._createItemHtml(task, {div:div, bContained:bContained});
956 		    this.associateItemWithElement(task, div);
957         }
958 
959 	}
960 };
961 
962 
963 /**
964  * Called by the controller whenever the reading pane preference changes
965  *
966  * @private
967  */
968 ZmTaskListView.prototype.reRenderListView =
969 function() {
970 	var isMultiColumn = this.isMultiColumn();
971 	if (isMultiColumn != this._isMultiColumn) {
972 		this._saveState({selection:true, focus:true, scroll:true, expansion:true});
973 		this._isMultiColumn = isMultiColumn;
974 		this.headerColCreated = false;
975 		this._headerList = this._getHeaderList();
976 		this._rowHeight = null;
977 		this._normalClass = isMultiColumn ? DwtListView.ROW_CLASS : ZmTaskListView.ROW_DOUBLE_CLASS;
978 		var list = this.getList() || (new AjxVector());
979 		this.set(list.clone(), this.getActiveSearchSortBy());
980         this._restoreState();
981 	}
982 };
983 
984 ZmTaskListView.prototype.resetSize =
985 function(newWidth, newHeight) {
986 	this.setSize(newWidth, newHeight);
987 	var height = (newHeight == Dwt.DEFAULT) ? newHeight : newHeight - DwtListView.HEADERITEM_HEIGHT;
988 	Dwt.setSize(this._parentEl, newWidth, height);
989 	this._resetInputSize();
990 };
991 
992 ZmTaskListView.prototype._resetColWidth =
993 function() {
994 
995 	if (!this.headerColCreated) { return; }
996 
997 	var lastColIdx = this._getLastColumnIndex();
998     if (lastColIdx) {
999         var lastCol = this._headerList[lastColIdx];
1000 		if (lastCol._field != ZmItem.F_SORTED_BY) {
1001 			DwtListView.prototype._resetColWidth.apply(this, arguments);
1002 		}
1003 	}
1004 };
1005 
1006 ZmTaskListView.prototype._resetInputSize =
1007 function(el) {
1008 
1009 	if (this._newTaskInputEl) {
1010 		el = el || document.getElementById(this.dId);
1011 		if (el) {
1012 			var bounds = Dwt.getBounds(el);
1013 			// Get the container location.  Dwt.getBounds does not work - the container is positioned absolute with
1014 			// no top/left specified.
1015 			var taskListContainerEl = this.getHtmlElement();
1016 			var taskListLocationPt = Dwt.toWindow(taskListContainerEl, 0, 0, null, null);
1017 			// Offset the input field over the 'new Task' text, inside the container.
1018 			Dwt.setBounds(this._newTaskInputEl, bounds.x - taskListLocationPt.x, bounds.y - taskListLocationPt.y, bounds.width, bounds.height);
1019 		}
1020 	}
1021 };
1022 
1023 ZmTaskListView.prototype._getSingleColumnSortFields =
1024 function() {
1025     var sortFields = (appCtxt.get(ZmSetting.TAGGING_ENABLED)) ?
1026                     ZmTaskListView.SINGLE_COLUMN_SORT.concat({field:ZmItem.F_TAG, msg:"tag" }) : ZmTaskListView.SINGLE_COLUMN_SORT;
1027 	return sortFields;
1028 };
1029 
1030 /**
1031  * return the active search sortby value
1032  * @return {String} sortby value or null
1033  */
1034 ZmTaskListView.prototype.getActiveSearchSortBy =
1035 function() {
1036 	var sortBy = AjxUtil.get(this._controller, "_activeSearch", "search", "sortBy") || null;
1037 	return sortBy;
1038 };
1039 
1040 ZmTaskListView.prototype._getPrefSortField =
1041 function(){
1042 	var activeSortBy = this.getActiveSearchSortBy();
1043 	return activeSortBy && ZmTaskListView.SORTBY_HASH[activeSortBy] ?
1044        ZmTaskListView.SORTBY_HASH[activeSortBy].field : ZmItem.F_DATE;
1045 };
1046 
1047 
1048 ZmTaskListView.prototype._getActionMenuForColHeader =
1049 function(force) {
1050 	if (!this.isMultiColumn()) {
1051 		if (!this._colHeaderActionMenu || force) {
1052 			this._colHeaderActionMenu = this._getSortMenu(this._getSingleColumnSortFields(), this._getPrefSortField());
1053 		}
1054         return this._colHeaderActionMenu;
1055 	}
1056 
1057 	var menu = ZmListView.prototype._getActionMenuForColHeader.call(this, force, null, "header");
1058 
1059 	return menu;
1060 };
1061 
1062 
1063 
1064 
1065