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