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 * show history of the status window 26 * @param parent the element that created this view 27 * @private 28 */ 29 ZmReminderDialog = function(parent, reminderController, calController, apptType) { 30 31 // init custom buttons 32 this._apptType = apptType; 33 34 this.ALL_APPTS = "ALL" + apptType; 35 36 // call base class 37 DwtDialog.call(this, {id:"ZmReminderDialog_" + apptType, parent:parent, standardButtons:DwtDialog.NO_BUTTONS}); 38 39 this._reminderController = reminderController; 40 this._calController = calController; 41 42 this._listId = Dwt.getNextId("ZmReminderDialogContent"); 43 44 this.setContent(this._contentHtml()); 45 if(this._calController instanceof ZmTaskMgr) { 46 this.setTitle(ZmMsg.taskReminders); 47 } else { 48 this.setTitle(ZmMsg.apptReminders); 49 } 50 51 // we want all children in the tab order, so just reuse the 52 // composite tab group 53 this.getTabGroupMember().addMember(this._compositeTabGroup); 54 }; 55 56 ZmReminderDialog.prototype = new DwtDialog; 57 ZmReminderDialog.prototype.constructor = ZmReminderDialog; 58 ZmReminderDialog.prototype.role = 'alertdialog'; 59 60 61 // Consts 62 63 ZmReminderDialog.SOON = -AjxDateUtil.MSEC_PER_FIFTEEN_MINUTES; 64 65 // Public methods 66 67 ZmReminderDialog.prototype.toString = 68 function() { 69 return "ZmReminderDialog"; 70 }; 71 72 ZmReminderDialog.prototype.popup = 73 function() { 74 DwtDialog.prototype.popup.call(this); 75 this._cancelSnooze(); 76 77 if (appCtxt.get(ZmSetting.CAL_REMINDER_NOTIFY_BROWSER)) { 78 AjxPackage.require("Alert"); 79 ZmBrowserAlert.getInstance().start(ZmMsg.reminders); 80 } 81 82 if (appCtxt.get(ZmSetting.CAL_REMINDER_NOTIFY_SOUNDS)) { 83 AjxPackage.require("Alert"); 84 ZmSoundAlert.getInstance().start(); 85 } 86 87 if (appCtxt.get(ZmSetting.CAL_REMINDER_NOTIFY_TOASTER)) { 88 AjxPackage.require("Alert"); 89 var winText = []; 90 var appts = this._list.getArray(); 91 // only show, at most, five appointment reminders 92 for (var i = 0; i < appts.length && i < 5; i++) { 93 var appt = appts[i]; 94 var startDelta = this._computeDelta(appt); 95 var delta = startDelta ? ZmReminderDialog.formatDeltaString(startDelta, appt.isAllDayEvent()) : ""; 96 var text = [appt.getName(), ", ", this._getDurationText(appt), "\n(", delta, ")"].join(""); 97 if (AjxEnv.isMac) { 98 ZmDesktopAlert.getInstance().start(ZmMsg.reminders, text, true); 99 } else if (AjxEnv.isWindows) { 100 winText.push(text); 101 } 102 } 103 104 if (AjxEnv.isWindows && winText.length > 0) { 105 if (appts.length > 5) { 106 winText.push(ZmMsg.andMore); 107 } 108 ZmDesktopAlert.getInstance().start(ZmMsg.reminders, winText.join("\n"), true); 109 } 110 } 111 112 this._snoozeSelectInputs[this.ALL_APPTS].focus(); 113 }; 114 115 ZmReminderDialog.prototype.initialize = 116 function(list) { 117 this._list = new AjxVector(); 118 this._apptData = {}; 119 120 var html = []; 121 var idx = 0; 122 var size = list.size(); 123 124 AjxDebug.println(AjxDebug.REMINDER, "---Reminders [" + (new Date().getTime())+ "]---"); 125 126 html[idx++] = "<table style='min-width:375px'>"; 127 for (var i = 0; i < size; i++) { 128 var appt = list.get(i); 129 if (appt.isShared() && appt.isReadOnly()) { continue; } 130 this._list.add(appt); 131 var uid = appt.getUniqueId(true); 132 var data = this._apptData[uid] = {appt:appt}; 133 idx = this._addAppt(html, idx, appt, data); 134 } 135 html[idx++] = "</table>"; 136 137 this._addAllSection(html, idx); 138 139 // cleanup before using 140 this._cleanupButtons(this._dismissButtons); 141 this._cleanupButtons(this._openButtons); 142 this._cleanupButtons(this._snoozeButtons); 143 this._cleanupButtons(this._snoozeSelectButtons); 144 this._cleanupButtons(this._snoozeSelectInputs); 145 this._dismissButtons = {}; 146 this._openButtons = {}; //those are link buttons (the reminder name is now a link) 147 this._snoozeButtons = {}; 148 this._snoozeSelectButtons = {}; 149 this._snoozeSelectInputs = {}; 150 151 var dismissListener = new AjxListener(this, this._dismissButtonListener); 152 var openListener = new AjxListener(this, this._openButtonListener); 153 var snoozeListener = this._snoozeButtonListener.bind(this); 154 var snoozeSelectButtonListener = this._snoozeSelectButtonListener.bind(this); 155 var snoozeSelectMenuListener = this._snoozeSelectMenuListener.bind(this); 156 157 var div = document.getElementById(this._listId); 158 div.innerHTML = html.join(""); 159 160 for (var i = 0; i < this._list.size(); i++) { 161 var appt = this._list.get(i); 162 var uid = appt.getUniqueId(true); 163 var id = appt.id; 164 var data = this._apptData[uid]; 165 166 var alarmData = appt.getAlarmData(); 167 alarmData = (alarmData && alarmData.length > 0) ? alarmData[0] : {}; 168 //bug: 60692 - Add troubleshooting code for late reminders 169 AjxDebug.println(AjxDebug.REMINDER, appt.getReminderName() + " : " + (alarmData.nextAlarm || " NA ") + " / " + (alarmData.alarmInstStart || " NA ")); 170 171 this._createButtons(uid, id, dismissListener, openListener, snoozeListener, snoozeSelectButtonListener, snoozeSelectMenuListener); 172 173 this._updateDelta(data); 174 } 175 176 this._createButtons(this.ALL_APPTS, this.ALL_APPTS, dismissListener, openListener, snoozeListener, snoozeSelectButtonListener, snoozeSelectMenuListener); 177 178 this._updateIndividualSnoozeActionsVisibility(); 179 180 //hide the separator from the dialog buttons since we do not use dialog buttons for this dialog. 181 document.getElementById(this._htmlElId + "_buttonsSep").style.display = "none"; 182 183 }; 184 185 ZmReminderDialog.prototype._createButtons = 186 function(uid, id, dismissListener, openListener, snoozeListener, snoozeSelectButtonListener, snoozeSelectMenuListener) { 187 //id should probably not be used, and only uid should - but I'm afraid it would confuse seleniun. This was added for bug 62376 188 189 var data = this._apptData[uid]; 190 var appt = data.appt; 191 192 if (uid !== this.ALL_APPTS) { 193 // open button 194 var openBtn = this._openButtons[uid] = new DwtLinkButton({id: "openBtn_" + id, parent: this, parentElement: data.openLinkId, noDropDown: true}); 195 openBtn.setText(AjxStringUtil.htmlEncode(appt.getReminderName())); 196 openBtn.addSelectionListener(openListener); 197 openBtn.setAttribute('aria-labelledby', [ 198 openBtn._textEl.id, data.reminderDescContainerId, data.deltaId 199 ].join(' ')); 200 openBtn.apptUid = uid; 201 this.getTabGroupMember().addMember(openBtn); 202 } 203 204 var className = uid === this.ALL_APPTS ? "ZButton" : "DwtToolbarButton"; 205 206 // snooze input field 207 var params = { 208 parent: this, 209 parentElement: data.snoozeSelectInputId, 210 type: DwtInputField.STRING, 211 errorIconStyle: DwtInputField.ERROR_ICON_NONE, 212 validationStyle: DwtInputField.CONTINUAL_VALIDATION, 213 className: "DwtInputField ReminderInput" 214 }; 215 var snoozeSelectInput = this._snoozeSelectInputs[uid] = new DwtInputField(params); 216 var snoozeSelectInputEl = snoozeSelectInput.getInputElement(); 217 Dwt.setSize(snoozeSelectInputEl, "120px", "2rem"); 218 snoozeSelectInputEl.setAttribute('aria-labelledby', 219 data.snoozeAllLabelId || ''); 220 221 // snoooze button 222 var snoozeSelectBtn = this._snoozeSelectButtons[uid] = new DwtButton({id: "snoozeSelectBtn_" + id, parent: this, className: "DwtToolbarButton", parentElement: data.snoozeSelectBtnId}); 223 snoozeSelectBtn.apptUid = uid; 224 snoozeSelectBtn.addDropDownSelectionListener(snoozeSelectButtonListener); 225 226 var snoozeBtn = this._snoozeButtons[uid] = new DwtButton({id: "snoozeBtn_" + id, parent: this, className: className, parentElement: data.snoozeBtnId}); 227 snoozeBtn.setText(ZmMsg.snooze); 228 snoozeBtn.addSelectionListener(snoozeListener); 229 snoozeBtn.apptUid = uid; 230 231 // dismiss button 232 var dismissBtn = this._dismissButtons[uid] = new DwtButton({id: "dismissBtn_" + id, parent: this, className: className, parentElement: data.dismissBtnId}); 233 dismissBtn.setText(ZmMsg.dismiss); 234 dismissBtn.addSelectionListener(dismissListener); 235 dismissBtn.apptUid = uid; 236 237 this._createSnoozeMenu(snoozeSelectBtn, snoozeSelectInput, snoozeSelectMenuListener, uid === this.ALL_APPTS ? this._list : appt); 238 }; 239 240 ZmReminderDialog.prototype._cleanupButtons = 241 function(buttons) { 242 if (!buttons) { 243 return; 244 } 245 for (var id in buttons) { 246 buttons[id].dispose(); 247 } 248 }; 249 250 ZmReminderDialog.prototype._contentHtml = 251 function() { 252 return ["<div class='ZmReminderDialog' id='", this._listId, "'>"].join(""); 253 }; 254 255 256 ZmReminderDialog.DEFAULT_SNOOZE = -5; 257 ZmReminderDialog.SNOOZE_MINUTES = 258 // Snooze period in minutes (negative is 'minutes before appt', zero is 'At time of event', 'separator' is for seperator icon. 259 [-30, -15, -5, -1, 0, 'seperator', 260 1, 5, 10, 15, 30, 45, 60, 120, 240, 480, 1440, 2880, 4320, 5760, 10080, 20160]; 261 // 1hr 2hr 4hr 8hr 1day 2days 3days 4days 1week 2weeks 262 263 // Snooze period in msec (Entries must match SNOOZE_MINUTES) 264 ZmReminderDialog.SNOOZE_MSEC = 265 [ -30*60*1000, -15*60*1000, -5*60*1000, -1*60*1000, 0, 266 1*60*1000, 5*60*1000, 10*60*1000, 15*60*1000, 30*60*1000, 45*60*1000, 60*60*1000, 267 120*60*1000, 240*60*1000, 480*60*1000, 1440*60*1000, 2880*60*1000, 4320*60*1000, 5760*60*1000, 268 10080*60*1000, 20160*60*1000]; 269 270 // Minutes per: minute hour day week endMarker 271 ZmReminderDialog.SCALE_MINUTES = [ 1, 60, 1440, 10080, 1000000]; 272 273 ZmReminderDialog.prototype._createSnoozeMenu = 274 function(snoozeSelectButton, snoozeSelectInput, menuSelectionListener, apptList) { 275 // create menu for button 276 var snoozeMenu = new DwtMenu({parent:snoozeSelectButton, style:DwtMenu.DROPDOWN_STYLE}); 277 snoozeMenu.setSize("150"); 278 snoozeSelectButton.setMenu(snoozeMenu, true); 279 280 var appts = AjxUtil.toArray(apptList); 281 282 var maxStartDelta = -Infinity; //It was called minStartDelta which was true if you think of the absolute value (as it is negative). But it's actually max. 283 284 if (this._apptType == "task") { 285 // Tasks are simpler: No 'before' times allowed, and all fixed times are allowed 286 maxStartDelta = 0; 287 } 288 else { 289 for (var i = 0; i < appts.length; i++) { 290 var appt = appts[i]; 291 var startDelta = this._computeDelta(appt); 292 maxStartDelta = Math.max(startDelta, maxStartDelta); 293 } 294 //if maxStartDelta is >= 0, there was at least one appt that is already started, in which case for the aggregate "snooze" we do not show any "before" item 295 maxStartDelta = Math.min(maxStartDelta, 0); //don't get positive - we don't care about that later in the loop below. We want max to be 0. 296 } 297 298 var snoozeFormatter = []; 299 var snoozeFormatterBefore = new AjxMessageFormat(ZmMsg.apptRemindNMinutesBefore); // Before Appt Formatter 300 snoozeFormatter[0] = new AjxMessageFormat(ZmMsg.reminderSnoozeMinutes); // Minute Formatter 301 snoozeFormatter[1] = new AjxMessageFormat(ZmMsg.reminderSnoozeHours); // Hour Formatter 302 snoozeFormatter[2] = new AjxMessageFormat(ZmMsg.reminderSnoozeDays); // Day Formatter 303 snoozeFormatter[3] = new AjxMessageFormat(ZmMsg.reminderSnoozeWeeks); // Week Formatter 304 var iFormatter = 0; 305 var formatter = null; 306 var snoozeDisplayValue = -1; 307 var scale = 1; 308 var defaultSet = false; 309 var firstMenuItem = null; 310 var addSeparator = false; 311 var anyAdded = false; 312 for (var i = 0; i < ZmReminderDialog.SNOOZE_MINUTES.length; i++) { 313 if (ZmReminderDialog.SNOOZE_MSEC[i] > maxStartDelta) { // only those values will come in snooze reminder, which are valid i.e avoid 'before minutes' in menu , when appointment has started . Donot include 0, when the event has started, value of maxStartDelta is 0,(see min) and we donot want 0 i.e 'at time of event' to be included in the menu , when the event has started . 314 // Found a snooze period to display 315 snoozeDisplayValue = ZmReminderDialog.SNOOZE_MINUTES[i]; 316 if (snoozeDisplayValue == 'seperator') { 317 // Set up to add a separator if any 'before' time were added; do the 318 // actual add if any fixed times are added 319 addSeparator = anyAdded; 320 } 321 else { 322 if (addSeparator) { 323 new DwtMenuItem({parent:snoozeMenu, style:DwtMenuItem.SEPARATOR_STYLE}); 324 addSeparator = false; 325 } 326 anyAdded = true; 327 if (snoozeDisplayValue < 0) { 328 snoozeDisplayValue = -snoozeDisplayValue; 329 formatter = snoozeFormatterBefore; 330 scale = 1; 331 332 } 333 else if (snoozeDisplayValue == 0){ 334 label = ZmMsg.apptRemindAtEventTime; 335 } 336 else { 337 if (snoozeDisplayValue >= ZmReminderDialog.SCALE_MINUTES[iFormatter+1]) { 338 iFormatter++; 339 } 340 scale = ZmReminderDialog.SCALE_MINUTES[iFormatter]; 341 formatter = snoozeFormatter[iFormatter]; 342 } 343 if (snoozeDisplayValue != 0) { 344 var label = formatter.format(snoozeDisplayValue / scale); 345 } 346 var mi = new DwtMenuItem({parent: snoozeMenu, style: DwtMenuItem.NO_STYLE}); 347 mi.setText(label); 348 mi.setData("value", snoozeDisplayValue); 349 if(menuSelectionListener) mi.addSelectionListener(menuSelectionListener); 350 351 if (!firstMenuItem) { 352 // Set the first item as the default 353 firstMenuItem = mi; 354 mi.setChecked(true); 355 snoozeSelectInput.setValue(label); 356 defaultSet = true; 357 } 358 } 359 } 360 } 361 362 }; 363 364 ZmReminderDialog.prototype._snoozeSelectButtonListener = 365 function(ev) { 366 ev.item.popup(); 367 }; 368 369 ZmReminderDialog.prototype._snoozeSelectMenuListener = 370 function(ev) { 371 if (!ev.item || !(ev.item instanceof DwtMenuItem)) { 372 return; 373 } 374 375 var obj = DwtControl.getTargetControl(ev); 376 obj = obj.parent.parent; //get the button - the parent of the menu which is the parent of the menu item which is this target control. 377 var uid = obj.apptUid; 378 var data = this._apptData[uid]; 379 if (!data) { 380 return; 381 } 382 this._snoozeSelectInputs[uid].setValue(ev.item.getText()); 383 // this._snoozeValue = ev.item.getData("value"); 384 }; 385 386 ZmReminderDialog.prototype._updateDelta = 387 function(data) { 388 var td = document.getElementById(data.deltaId); 389 if (td) { 390 var startDelta = this._computeDelta(data.appt); 391 392 td.className = startDelta >= 0 ? "ZmReminderOverdue" 393 : startDelta > ZmReminderDialog.SOON ? "ZmReminderSoon" 394 : "ZmReminderFuture"; 395 396 td.innerHTML = startDelta ? ZmReminderDialog.formatDeltaString(startDelta, data.appt.isAllDayEvent()) : ""; 397 } 398 }; 399 400 /** 401 * display the individual actions (snooze, dismiss) only if there's more than one reminder. 402 * @private 403 */ 404 ZmReminderDialog.prototype._updateIndividualSnoozeActionsVisibility = 405 function() { 406 var appts = this._list.getArray(); 407 if (appts.length === 0) { 408 return; //all snoozed or dismissed, nothing to do here) 409 } 410 var multiple = appts.length > 1; 411 for (var i = 0; i < appts.length; i++) { 412 var appt = appts[i]; 413 var uid = appt.getUniqueId(true); 414 var data = this._apptData[uid]; 415 var actionsRow = document.getElementById(data.actionsRowId); 416 actionsRow.style.display = multiple ? "block" : "none"; 417 } 418 419 //update the all text 420 var dismissAllBtn = this._dismissButtons[this.ALL_APPTS]; 421 dismissAllBtn.setText(multiple ? ZmMsg.dismissAll : ZmMsg.dismiss); 422 var snoozeAllBtn = this._snoozeButtons[this.ALL_APPTS]; 423 snoozeAllBtn.setText(multiple ? ZmMsg.snoozeAllLabel : ZmMsg.snooze); 424 425 var snoozeAllLabelId = this._apptData[this.ALL_APPTS].snoozeAllLabelId; 426 var allLabelSpan = document.getElementById(snoozeAllLabelId); 427 allLabelSpan.innerHTML = multiple ? ZmMsg.snoozeAll : ZmMsg.snoozeFor; 428 }; 429 430 431 ZmReminderDialog.prototype._addAppt = 432 function(html, idx, appt, data) { 433 434 var uid = appt.id; 435 this._addData(data, uid); 436 437 var calName = (appt.folderId != ZmOrganizer.ID_CALENDAR && appt.folderId != ZmOrganizer.ID_TASKS && this._calController) 438 ? this._calController.getCalendarName(appt.folderId) : null; 439 440 441 var calendar = appCtxt.getById(appt.folderId); 442 443 var params = { 444 rowId: data.rowId, 445 calName: AjxStringUtil.htmlEncode(calName), 446 accountName: (appCtxt.multiAccounts && calendar && calendar.getAccount().getDisplayName()), 447 location: (AjxStringUtil.htmlEncode(appt.getReminderLocation())), 448 apptIconHtml: (AjxImg.getImageHtml(appt.otherAttendees ? "ApptMeeting" : "Appointment")), 449 organizer: appt.otherAtt ? appt.organizer : null, 450 reminderName: (AjxStringUtil.htmlEncode(appt.getReminderName())), 451 durationText: (AjxStringUtil.trim(this._getDurationText(appt))), 452 deltaId: data.deltaId, 453 openLinkId: data.openLinkId, 454 dismissBtnId: data.dismissBtnId, 455 snoozeSelectInputId: data.snoozeSelectInputId, 456 snoozeSelectBtnId: data.snoozeSelectBtnId, 457 snoozeBtnId: data.snoozeBtnId, 458 actionsRowId: data.actionsRowId, 459 reminderNameContainerId: data.reminderNameContainerId, 460 reminderDescContainerId: data.reminderDescContainerId, 461 type: appt.type ? appt.type : ZmItem.APPT 462 }; 463 html[idx++] = AjxTemplate.expand("calendar.Calendar#ReminderDialogRow", params); 464 return idx; 465 }; 466 467 ZmReminderDialog.prototype._addAllSection = 468 function(html, idx) { 469 470 var uid = this.ALL_APPTS; 471 472 var data = this._apptData[uid] = {}; 473 this._addData(data, uid); 474 data.snoozeAllLabelId = "snoozeAllLabelContainerId_" + uid; 475 476 var params = { 477 rowId: data.rowId, 478 dismissBtnId: data.dismissBtnId, 479 snoozeSelectInputId: data.snoozeSelectInputId, 480 snoozeSelectBtnId: data.snoozeSelectBtnId, 481 snoozeBtnId: data.snoozeBtnId, 482 snoozeAllLabelId: data.snoozeAllLabelId 483 }; 484 html[idx++] = AjxTemplate.expand("calendar.Calendar#ReminderDialogAllSection", params); 485 return idx; 486 }; 487 488 ZmReminderDialog.prototype._addData = 489 function(data, uid) { 490 data.dismissBtnId = "dismissBtnContainer_" + uid; 491 data.snoozeSelectInputId = "snoozeSelectInputContainer_" + uid; 492 data.snoozeSelectBtnId = "snoozeSelectBtnContainer_" + uid; 493 data.snoozeBtnId = "snoozeBtnContainer_" + uid; 494 data.openLinkId = "openLinkContainer_" + uid; 495 data.actionsRowId = "actionsRowContainer_" + uid; 496 data.deltaId = "delta_" + uid; 497 data.rowId = "apptRow_" + uid; 498 data.reminderNameContainerId = "reminderNameContainerId_" + uid; 499 data.reminderDescContainerId = "reminderDescContainerId_" + uid; 500 }; 501 502 ZmReminderDialog.prototype._openButtonListener = 503 function(ev) { 504 505 appCtxt.getAppController().setStatusMsg(ZmMsg.allRemindersAreSnoozed, ZmStatusView.LEVEL_INFO); 506 507 var obj = DwtControl.getTargetControl(ev); 508 var data = this._apptData[obj.apptUid]; 509 510 this._snoozeButtonListener(null, true); //do it after getting the obj and data since snoozing gets rid of the elements. 511 512 var appt = data ? data.appt : null; 513 var type = appt.type ? appt.type : ZmItem.APPT; 514 if (appt && type == ZmItem.APPT) { 515 AjxDispatcher.require(["MailCore", "CalendarCore", "Calendar"]); 516 517 var cc = AjxDispatcher.run("GetCalController"); 518 519 // the give appt object is a ZmCalBaseItem. We need a ZmAppt 520 var newAppt = new ZmAppt(); 521 for (var i in appt) { 522 if (!AjxUtil.isFunction(appt[i])) { 523 newAppt[i] = appt[i]; 524 } 525 } 526 var mode = newAppt.isRecurring() ? ZmCalItem.MODE_EDIT_SINGLE_INSTANCE : null; 527 var callback = new AjxCallback(cc, cc._showAppointmentDetails, newAppt); 528 newAppt.getDetails(mode, callback, null, null, true); 529 } else if(appt && type == ZmItem.TASK) { 530 AjxDispatcher.require(["TasksCore", "Tasks"]); 531 532 var tlc = AjxDispatcher.run("GetTaskListController"); 533 534 // the give appt object is a ZmCalBaseItem. We need a ZmAppt 535 var newTask = new ZmTask(); 536 for (var i in appt) { 537 if (!AjxUtil.isFunction(appt[i])) { 538 newTask[i] = appt[i]; 539 } 540 } 541 var callback = new AjxCallback(tlc, tlc._editTask, newTask); 542 newTask.getDetails(null, callback, null, null, true); 543 } 544 }; 545 546 ZmReminderDialog.prototype._dismissButtonListener = 547 function(ev) { 548 var obj = DwtControl.getTargetControl(ev); 549 var uid = obj.apptUid; 550 var data = this._apptData[uid]; 551 if (!data) { return; } 552 var appts; 553 if (uid === this.ALL_APPTS) { 554 appts = this._getApptsClone(); 555 } 556 else { 557 appts = data.appt; //note - this could be all the appts this._list 558 } 559 560 this._reminderController.dismissAppt(appts); 561 562 this._removeAppts(appts); 563 }; 564 565 ZmReminderDialog.prototype._cleanupButton = 566 function(buttons, uid) { 567 var button = buttons[uid]; 568 if (!button) { 569 return; 570 } 571 button.dispose(); 572 delete buttons[uid]; 573 }; 574 575 ZmReminderDialog.prototype._removeAppts = 576 function(appts) { 577 appts = AjxUtil.toArray(appts); 578 for (i = 0; i < appts.length; i++) { 579 this._removeAppt(appts[i]); 580 } 581 this._updateIndividualSnoozeActionsVisibility(); 582 583 }; 584 585 ZmReminderDialog.prototype._removeAppt = 586 function(appt) { 587 var uid = appt.getUniqueId(true); 588 var data = this._apptData[uid]; 589 590 // cleanup HTML 591 this._cleanupButton(this._dismissButtons, uid); 592 this._cleanupButton(this._openButtons, uid); 593 this._cleanupButton(this._snoozeButtons, uid); 594 this._cleanupButton(this._snoozeSelectButtons, uid); 595 this._cleanupButton(this._snoozeSelectInputs, uid); 596 597 var row = document.getElementById(data.rowId); 598 if (row) { 599 var nextRow = row.nextSibling; 600 if (nextRow && nextRow.getAttribute("name") === "rdsep") { 601 nextRow.parentNode.removeChild(nextRow); 602 } 603 row.parentNode.removeChild(row); 604 } 605 606 delete this._apptData[uid]; 607 this._list.remove(appt); 608 609 if (this._list.size() === 0) { 610 this._cleanupButton(this._dismissButtons, this.ALL_APPTS); 611 this._cleanupButton(this._snoozeButtons, this.ALL_APPTS); 612 this._cleanupButton(this._snoozeSelectButtons, this.ALL_APPTS); 613 this._cleanupButton(this._snoozeSelectInputs, this.ALL_APPTS); 614 this.popdown(); 615 } 616 }; 617 618 619 ZmReminderDialog.prototype._getApptsClone = 620 function() { 621 //make a shallow copy of this_list.getArray(), so that stuff can work while or after removing things from the _list. This is a must. 622 return this._list.getArray().slice(0); 623 }; 624 625 ZmReminderDialog.prototype._snoozeButtonListener = 626 function(ev, all) { 627 628 var data; 629 var uid; 630 var appts; 631 if (all) { //all is true in the case of "open" where we snooze everything artificially 632 uid = this.ALL_APPTS; 633 appts = this._getApptsClone(); 634 } 635 else { 636 var obj = DwtControl.getTargetControl(ev); 637 uid = obj.apptUid; 638 if (uid === this.ALL_APPTS) { 639 appts = this._getApptsClone(); 640 } 641 else { 642 data = this._apptData[uid]; 643 appts = AjxUtil.toArray(data.appt); 644 } 645 } 646 647 var snoozeString = this._snoozeSelectInputs[uid].getValue(); 648 649 // check if all fields are populated w/ valid values 650 var errorMsg = []; 651 var snoozeInfo = null; 652 var beforeAppt = false; 653 if (!snoozeString) { 654 errorMsg.push(ZmMsg.reminderSnoozeClickNoDuration); 655 } 656 else { 657 snoozeInfo = ZmCalendarApp.parseReminderString(snoozeString); 658 if (snoozeInfo.reminderValue === "" ) { 659 // Returned when no number was specified in the snooze input field 660 errorMsg.push(ZmMsg.reminderSnoozeClickNoNumber); 661 } else { 662 // Test if the unit is a known one (default behaviour for parseReminderString 663 // returns unknowns as hours) 664 var valid = this._testSnoozeString(snoozeString); 665 if (!valid) { 666 errorMsg.push(ZmMsg.reminderSnoozeClickUnknownUnit); 667 } else { 668 // Detect 'before' 669 //Fix for Bug: 80651 - Check for snooze before object 670 beforeAppt = snoozeInfo.before; 671 } 672 } 673 } 674 if (errorMsg.length > 0) { 675 var msg = errorMsg.join("<br>"); 676 var dialog = appCtxt.getMsgDialog(); 677 dialog.reset(); 678 dialog.setMessage(msg, DwtMessageDialog.WARNING_STYLE); 679 dialog.popup(); 680 return; 681 } 682 683 var snoozeMinutes = ZmCalendarApp.convertReminderUnits(snoozeInfo.reminderValue, snoozeInfo.reminderUnits); 684 this._reminderController.snoozeAppt(appts); 685 this._reminderController._snoozeApptAction(appts, snoozeMinutes, beforeAppt); 686 687 this._removeAppts(appts, true); 688 689 }; 690 691 692 693 /** 694 * Parses the given string to insure the units are recognized 695 * @param snoozeString snooze string eg. "10 minutes" 696 * 697 * @private 698 */ 699 ZmReminderDialog.prototype._testSnoozeString = 700 function(snoozeString) { 701 var snoozeUnitStrings = []; 702 snoozeUnitStrings[0] = AjxMsg.minute; 703 snoozeUnitStrings[1] = AjxMsg.hour; 704 snoozeUnitStrings[2] = AjxMsg.day; 705 snoozeUnitStrings[3] = AjxMsg.week; 706 // Plural 707 snoozeUnitStrings[4] = AjxMsg.minutes; 708 snoozeUnitStrings[5] = AjxMsg.hours; 709 snoozeUnitStrings[6] = AjxMsg.days; 710 snoozeUnitStrings[7] = AjxMsg.weeks; 711 snoozeUnitStrings[8] = AjxMsg.atEventTime; 712 713 snoozeString = snoozeString.toLowerCase(); 714 var found = false; 715 for (var i = 0; i < snoozeUnitStrings.length; i++) { 716 if (snoozeString.indexOf(snoozeUnitStrings[i].toLowerCase()) >= 0) { 717 found = true; 718 break; 719 } 720 } 721 return found; 722 }; 723 724 725 726 ZmReminderDialog.prototype._cancelSnooze = 727 function() { 728 if (this._snoozeActionId) { 729 AjxTimedAction.cancelAction(this._snoozeActionId); 730 delete this._snoozeActionId; 731 } 732 }; 733 734 ZmReminderDialog.prototype._getDurationText = 735 function(appt) { 736 var isMultiDay = appt.isMultiDay(); 737 var start = appt._alarmInstStart ? new Date(appt._alarmInstStart) : appt.startDate ? appt.startDate : null; 738 // bug: 28598 - alarm for recurring appt might still point to old alarm time 739 // cannot take endTime directly 740 var endTime = appt._alarmInstStart ? (start.getTime() + appt.getDuration()) : appt.getEndTime(); 741 var end = new Date(endTime); 742 743 //for task 744 if(appt.type == ZmItem.TASK && !start && !endTime) { return null; } 745 746 if (appt.isAllDayEvent()) { 747 end = appt.type != ZmItem.TASK ? new Date(endTime - (isMultiDay ? 2 * AjxDateUtil.MSEC_PER_HOUR : 0)) : end; 748 var pattern = isMultiDay ? ZmMsg.apptTimeAllDayMulti : ZmMsg.apptTimeAllDay; 749 return start ? AjxMessageFormat.format(pattern, [start, end]) : AjxMessageFormat.format(pattern, [end]); //for task 750 } 751 var pattern = isMultiDay ? ZmMsg.apptTimeInstanceMulti : ZmMsg.apptTimeInstance; 752 return AjxMessageFormat.format(pattern, [start, end, ""]); 753 }; 754 755 ZmReminderDialog.prototype._computeDelta = 756 function(appt) { 757 var deltaTime = null; 758 759 // Split out task processing - the deltaTime is used to split the tasks into sections (Past Due, Upcoming, No 760 // Due Date), and a task is only overdue when it is later than its end time. AlarmData is the 761 // next reminder trigger time, and is inappropriate to use for sorting the tasks. 762 var now = (new Date()).getTime(); 763 if (appt.type === ZmItem.TASK) { 764 if (appt.getEndTime()) { 765 deltaTime = now - appt.getEndTime(); 766 } 767 } else { 768 // Calendar Appt 769 770 //I refactored the big nested ternary operator (?:) to make it more readable and try to understand what's the logic here. 771 // After doing so, this doesn't make sense to me. But I have no idea if it should be this way on purpose, and that is the way it was with the ?: 772 // Basically if there is NO alarmData it uses the appt startTime, which makes sense. But if there IS alarmData but no alarmInstStart it uses the endTime? WHY? What about the startTime? 773 // I don't get it. Seems wrong. 774 if (!appt.alarmData || appt.alarmData.length === 0) { 775 deltaTime = now - appt.getStartTime(); 776 } else { 777 var alarmInstStart = appt.alarmData[0].alarmInstStart; //returned from the server in case i'm wondering 778 if (alarmInstStart) { 779 deltaTime = now - appt.adjustMS(alarmInstStart, appt.tzo); 780 } else if (appt.getEndTime()) { 781 deltaTime = now - appt.getEndTime(); 782 } 783 } 784 } 785 return deltaTime; 786 }; 787 788 ZmReminderDialog.formatDeltaString = function(deltaMSec, isAllDay) { 789 if (deltaMSec > 0 && deltaMSec < 60000) { // less than 1 minute i.e 60 seconds 790 return ZmMsg.reminderNow; 791 } 792 var prefix = deltaMSec < 0 ? "In" : "OverdueBy"; 793 deltaMSec = Math.abs(deltaMSec); 794 795 // calculate parts 796 var years = 0; 797 var months = 0; 798 var days = 0; 799 var hours = 0; 800 var mins = 0; 801 var secs = 0; 802 803 years = Math.floor(deltaMSec / (AjxDateUtil.MSEC_PER_DAY * 365)); 804 if (years !== 0) { 805 deltaMSec -= years * AjxDateUtil.MSEC_PER_DAY * 365; 806 } 807 months = Math.floor(deltaMSec / (AjxDateUtil.MSEC_PER_DAY * 30.42)); 808 if (months > 0) { 809 deltaMSec -= Math.floor(months * AjxDateUtil.MSEC_PER_DAY * 30.42); 810 } 811 days = Math.floor(deltaMSec / AjxDateUtil.MSEC_PER_DAY); 812 if (days > 0) { 813 deltaMSec -= days * AjxDateUtil.MSEC_PER_DAY; 814 } 815 hours = Math.floor(deltaMSec / AjxDateUtil.MSEC_PER_HOUR); 816 if (hours > 0) { 817 deltaMSec -= hours * AjxDateUtil.MSEC_PER_HOUR; 818 } 819 mins = Math.floor(deltaMSec / 60000); 820 if (mins > 0) { 821 deltaMSec -= mins * 60000; 822 } 823 secs = Math.floor(deltaMSec / 1000); 824 if (secs > 30 && mins < 59) { 825 mins++; 826 } 827 secs = 0; 828 829 // determine message 830 var amount; 831 if (years > 0) { 832 amount = "Years"; 833 if (years <= 3 && months > 0) { 834 amount = "YearsMonths"; 835 } 836 } 837 else if (months > 0) { 838 amount = "Months"; 839 if (months <= 3 && days > 0) { 840 amount = "MonthsDays"; 841 } 842 } 843 else if (days > 0) { 844 amount = "Days"; 845 if (!isAllDay && (days <= 2 && hours > 0)) { 846 // Only include hours if not an all day appt/task 847 amount = "DaysHours"; 848 } 849 } 850 else { 851 if (isAllDay) { 852 // 'Overdue' from start of day, which really means due today 853 amount ="Today"; 854 } else { 855 if (hours > 0) { 856 amount = "Hours"; 857 if (hours < 5 && mins > 0) { 858 amount = "HoursMinutes"; 859 } 860 } else { 861 amount = "Minutes"; 862 } 863 } 864 } 865 866 // format message 867 var key = ["reminder", prefix, amount].join(""); 868 var args = [deltaMSec, years, months, days, hours, mins, secs]; 869 if (amount == "Minutes" && mins == 0) { // In 0 minutes 870 return ZmMsg.reminderNow; 871 } 872 return AjxMessageFormat.format(ZmMsg[key], args); 873 }; 874 875 876 //Bug 65466: Method overridden to remove the ESC button behavior 877 878 ZmReminderDialog.prototype.handleKeyAction = 879 function(actionCode, ev) { 880 switch (actionCode) { 881 882 case DwtKeyMap.ENTER: 883 this.notifyListeners(DwtEvent.ENTER, ev); 884 break; 885 886 case DwtKeyMap.CANCEL: 887 // Dont do anything 888 break; 889 890 case DwtKeyMap.YES: 891 if (this._buttonDesc[DwtDialog.YES_BUTTON]) { 892 this._runCallbackForButtonId(DwtDialog.YES_BUTTON); 893 } 894 break; 895 896 case DwtKeyMap.NO: 897 if (this._buttonDesc[DwtDialog.NO_BUTTON]) { 898 this._runCallbackForButtonId(DwtDialog.NO_BUTTON); 899 } 900 break; 901 902 default: 903 return false; 904 } 905 return true; 906 }; 907