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