1 /*
  2  * ***** BEGIN LICENSE BLOCK *****
  3  * Zimbra Collaboration Suite Web Client
  4  * Copyright (C) 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) 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Synacor, Inc. All Rights Reserved.
 21  * ***** END LICENSE BLOCK *****
 22  */
 23 /**
 24  * Creates a new calendar item edit view.
 25  * @constructor
 26  * @class
 27  * This is the main screen for creating/editing a calendar item. It provides
 28  * inputs for the various appointment/task details.
 29  *
 30  * @author Parag Shah
 31  *
 32  * @param {DwtControl}	parent			the container
 33  * @param {Hash}	attendees			the attendees/locations/equipment
 34  * @param {ZmController}	controller		the compose controller for this view
 35  * @param {Object}	dateInfo			a hash of date info
 36  * @param {static|relative|absolute}	posStyle			the position style
 37  * @param {string}  className   Class name
 38  * 
 39  * @extends	DwtComposite
 40  * 
 41  * @private
 42  */
 43 ZmCalItemEditView = function(parent, attendees, controller, dateInfo, posStyle, className, uid) {
 44 	if (arguments.length == 0) { return; }
 45 
 46 	DwtComposite.call(this, {parent:parent, posStyle:posStyle, className:className, id:uid});
 47 
 48     this.uid = uid;
 49 	this._attendees = attendees;
 50 	this._controller = controller;
 51 	this._dateInfo = dateInfo;
 52 
 53 	this.setScrollStyle(DwtControl.SCROLL);
 54 	this._rendered = false;
 55 
 56 	var bComposeEnabled = appCtxt.get(ZmSetting.HTML_COMPOSE_ENABLED);
 57 	var composeFormat = appCtxt.get(ZmSetting.COMPOSE_AS_FORMAT);
 58 	this._composeMode = bComposeEnabled && composeFormat == ZmSetting.COMPOSE_HTML
 59 		? Dwt.HTML : Dwt.TEXT;
 60 
 61 	this._repeatSelectDisabled = false;
 62 	this._attachCount = 0;
 63 	this._calendarOrgs = {};
 64 
 65 	this._kbMgr = appCtxt.getKeyboardMgr();
 66     this._isForward = false;
 67     this._isProposeTime = false;
 68 
 69     this._customRecurDialogCallback = null;
 70     this._enableCustomRecurCallback = true;
 71 
 72 	this.addControlListener(this._controlListener.bind(this));
 73 };
 74 
 75 ZmCalItemEditView.prototype = new DwtComposite;
 76 ZmCalItemEditView.prototype.constructor = ZmCalItemEditView;
 77 
 78 ZmCalItemEditView.prototype.toString =
 79 function() {
 80 	return "ZmCalItemEditView";
 81 };
 82 
 83 // Consts
 84 
 85 ZmCalItemEditView.UPLOAD_FIELD_NAME = "__calAttUpload__";
 86 ZmCalItemEditView.SHOW_MAX_ATTACHMENTS = AjxEnv.is800x600orLower ? 2 : 3;
 87 
 88 ZmCalItemEditView._REPEAT_CHANGE = "REPEAT_CHANGE";
 89 
 90 // Public
 91 
 92 ZmCalItemEditView.prototype.show =
 93 function() {
 94 	this.resize();
 95 };
 96 
 97 ZmCalItemEditView.prototype.isRendered =
 98 function() {
 99 	return this._rendered;
100 };
101 
102 /**
103  * Gets the calendar item.
104  * 
105  * @return	{ZmCalItem}	the item
106  */
107 ZmCalItemEditView.prototype.getCalItem =
108 function(attId) {
109 	// attempt to submit attachments first!
110 	if (!attId && this._gotAttachments()) {
111 		this._submitAttachments();
112 		return null;
113 	}
114 
115 	return this._populateForSave(this._getClone());
116 };
117 
118 ZmCalItemEditView.prototype.initialize =
119 function(calItem, mode, isDirty, apptComposeMode) {
120 
121     this._calItem = calItem;
122 	this._isDirty = isDirty;
123 
124 	var firstTime = !this._rendered;
125 	this.createHtml();
126 
127 	this._mode = (mode == ZmCalItem.MODE_NEW_FROM_QUICKADD || !mode) ? ZmCalItem.MODE_NEW : mode;
128 	this._reset(calItem, mode || ZmCalItem.MODE_NEW, firstTime);
129 };
130 
131 ZmCalItemEditView.prototype.cleanup =
132 function() {
133 	if (this._recurDialog) {
134 		this._recurDialog.clearState();
135 		this._recurDialogRepeatValue = null;
136 	}
137 
138 	delete this._calItem;
139 	this._calItem = null;
140 
141 	// clear out all input fields
142 	this._subjectField.setValue("");
143     this._notesHtmlEditor.clear();
144 
145     if(this._hasRepeatSupport) {
146         this._repeatDescField.innerHTML = "";
147         // reinit non-time sensitive selects option values
148         this._repeatSelect.setSelectedValue(ZmApptViewHelper.REPEAT_OPTIONS[0].value);
149     }
150 
151 	// remove attachments if any were added
152 	this._removeAllAttachments();
153 
154 	// disable all input fields
155 	this.enableInputs(false);
156 };
157 
158 ZmCalItemEditView.prototype.addRepeatChangeListener =
159 function(listener) {
160 	this.addListener(ZmCalItemEditView._REPEAT_CHANGE, listener);
161 };
162 
163 // Acceptable hack needed to prevent cursor from bleeding thru higher z-index'd views
164 ZmCalItemEditView.prototype.enableInputs =
165 function(bEnableInputs) {
166 	this._subjectField.setEnabled(bEnableInputs);
167 	this._startDateField.disabled = !(bEnableInputs || this._isProposeTime);
168 	this._endDateField.disabled = !(bEnableInputs || this._isProposeTime);
169 };
170 
171 ZmCalItemEditView.prototype.enableSubjectField =
172 function(bEnableInputs) {
173 	this._subjectField.setEnabled(bEnableInputs);
174 };
175 
176 /**
177  * Checks to see if the recurring (repeat custom - CUS) changes dialog was edited.
178  *
179  */
180 ZmCalItemEditView.prototype.areRecurringChangesDirty =
181 function() {
182 	if (this._recurDialog)
183 		return this._recurDialog.isDirty();
184 	else
185 		return false;
186 };
187 
188 /**
189  * Checks for dirty fields.
190  * 
191  * @param {Boolean}	excludeAttendees		if <code>true</code> check for dirty fields excluding the attendees field
192  */
193 ZmCalItemEditView.prototype.isDirty =
194 function(excludeAttendees) {
195     if(this._controller.inactive) {
196         return false;
197     }
198 	var formValue = excludeAttendees && this._origFormValueMinusAttendees
199 		? this._origFormValueMinusAttendees
200 		: this._origFormValue;
201 
202 	return (this._gotAttachments() || this._removedAttachments()) ||
203 			this._isDirty ||
204 		   (this._formValue(excludeAttendees) != formValue);
205 };
206 
207 /**
208  * Checks if reminder only is changed.
209  * 
210  * @return	{Boolean}	<code>true</code> if reminder only changed
211  */
212 ZmCalItemEditView.prototype.isReminderOnlyChanged =
213 function() {
214 
215 	if (!this._hasReminderSupport) { return false; }
216 
217 	var formValue = this._origFormValueMinusReminder;
218 
219 	var isDirty = (this._gotAttachments() || this._removedAttachments()) ||
220 			this._isDirty ||
221 		   (this._formValue(false, true) != formValue);
222 
223 	var isReminderChanged = this._reminderSelectInput && (this._origReminderValue != this._reminderSelectInput.getValue());
224 
225 	return isReminderChanged && !isDirty;
226 };
227 
228 ZmCalItemEditView.prototype.isValid =
229 function() {
230 	// override
231 };
232 
233 ZmCalItemEditView.prototype.getComposeMode =
234 function() {
235 	return this._composeMode;
236 };
237 
238 ZmCalItemEditView.prototype.setComposeMode =
239 function(composeMode) {
240 	this._composeMode = composeMode || this._composeMode;
241     this._notesHtmlModeFirstTime = !this._notesHtmlEditor.isHtmlModeInited();
242 	this._notesHtmlEditor.setMode(this._composeMode, true);
243 	this.resize();
244 };
245 
246 ZmCalItemEditView.prototype.reEnableDesignMode =
247 function() {
248 	if (this._composeMode == Dwt.HTML)
249 		this._notesHtmlEditor.reEnableDesignMode();
250 };
251 
252 ZmCalItemEditView.prototype.createHtml =
253 function() {
254 	if (!this._rendered) {
255 		var width = AjxEnv.is800x600orLower ? "150" : "250";
256 
257 		this._createHTML();
258 		this._createWidgets(width);
259 		this._cacheFields();
260 		this._addEventHandlers();
261 		this._rendered = true;
262 	}
263 };
264 
265 /**
266  * Adds an attachment (file input field) to the appointment view. If none
267  * already exist, creates the attachments container. If <code>attach</code> parameters is
268  * provided, user is opening an existing appointment w/ an attachment and therefore
269  * display differently.
270  * 
271  * @param	{ZmCalItem}	calItem		the calendar item
272  * @param	{Object}	attach		the attachment
273  * 
274  * @private
275  */
276 ZmCalItemEditView.prototype.addAttachmentField =
277 function(calItem, attach) {
278 	if (this._attachCount == 0) {
279 		this._initAttachContainer();
280 	}
281 
282 	this._attachCount++;
283 
284 	// add file input field
285 	var div = document.createElement("div");
286     var id = this._htmlElId;
287 	var attachRemoveId = id + "_att_" + Dwt.getNextId();
288 	var attachInputId = id + "_att_" + Dwt.getNextId();
289     var sizeContId = id + "_att_" + Dwt.getNextId();
290 
291 	if (attach) {
292 		div.innerHTML = ZmApptViewHelper.getAttachListHtml(calItem, attach, true);
293 	} else {
294 		var subs = {
295 			id: id,
296 			attachInputId: attachInputId,
297 			attachRemoveId: attachRemoveId,
298             sizeId: sizeContId,
299 			uploadFieldName: ZmCalItemEditView.UPLOAD_FIELD_NAME
300 		};
301 		div.innerHTML = AjxTemplate.expand("calendar.Appointment#AttachAdd", subs);
302 	}
303 
304 	if (this._attachDiv == null) {
305 		this._attachDiv = document.getElementById(this._attachDivId);
306 	}
307 	this._attachDiv.appendChild(div);
308 
309 	if (attach == null) {
310 		// add event handlers as necessary
311 		var tvpId = AjxCore.assignId(this);
312 		var attachRemoveSpan = document.getElementById(attachRemoveId);
313 		attachRemoveSpan._editViewId = tvpId;
314 		attachRemoveSpan._parentDiv = div;
315 		Dwt.setHandler(attachRemoveSpan, DwtEvent.ONCLICK, ZmCalItemEditView._onClick);
316 
317         var attachInputEl = document.getElementById(attachInputId);
318 		// trap key presses in IE for input field so we can ignore ENTER key (bug 961)
319 		if (AjxEnv.isIE) {
320 			//var attachInputEl = document.getElementById(attachInputId);
321 			attachInputEl._editViewId = tvpId;
322 			Dwt.setHandler(attachInputEl, DwtEvent.ONKEYDOWN, ZmCalItemEditView._onKeyDown);
323         }
324 
325         //HTML5
326         if(AjxEnv.supportsHTML5File){
327             var sizeEl = document.getElementById(sizeContId);
328             Dwt.setHandler(attachInputEl, "onchange", AjxCallback.simpleClosure(this._handleFileSize, this, attachInputEl, sizeEl));
329         }
330     }
331 
332     this.resize();
333 };
334 
335 ZmCalItemEditView.prototype._handleFileSize =
336 function(inputEl, sizeEl){
337 
338     var files = inputEl.files;
339     if(!files) return;
340 
341     var sizeStr = [], className, totalSize =0;
342     for(var i=0; i<files.length;i++){
343         var file = files[i];
344         var size = file.size || file.fileSize /*Safari*/ || 0;
345         if ((-1 /* means unlimited */ != appCtxt.get(ZmSetting.MESSAGE_SIZE_LIMIT)) &&
346             (size > appCtxt.get(ZmSetting.MESSAGE_SIZE_LIMIT))) {
347             className = "RedC";
348         }
349         totalSize += size;
350     }
351 
352     if(sizeEl) {
353         sizeEl.innerHTML = "  ("+AjxUtil.formatSize(totalSize, true)+")";
354         if(className)
355             Dwt.addClass(sizeEl, "RedC");
356         else
357             Dwt.delClass(sizeEl, "RedC");
358     }
359 };
360 
361 ZmCalItemEditView.prototype.resize =
362 function() {
363 	if (!this._rendered) { return; }
364 
365     this._resizeNotes();
366 
367 	var subjectContainer = this._subjectField.getHtmlElement().parentNode;
368 	this._subjectField.setSize(0, Dwt.DEFAULT);
369 	var containerBounds = Dwt.getInsetBounds(subjectContainer);
370 	this._subjectField.setSize(containerBounds.width - 20, Dwt.DEFAULT);
371 };
372 
373 ZmCalItemEditView.prototype.getHtmlEditor =
374 function() {
375 	return this._notesHtmlEditor;
376 };
377 
378 ZmCalItemEditView.prototype.getOrganizer =
379 function() {
380 	var folderId = this._folderSelect.getValue();
381 	var organizer = new ZmContact(null);
382 	var acct = appCtxt.multiAccounts && appCtxt.getById(folderId).getAccount();
383 	organizer.initFromEmail(ZmApptViewHelper.getOrganizerEmail(this._calendarOrgs[folderId], acct), true);
384 
385 	return organizer;
386 };
387 
388 
389 // Private / protected methods
390 
391 ZmCalItemEditView.prototype._addTabGroupMembers =
392 function(tabGroup) {
393 	// override
394 };
395 
396 ZmCalItemEditView.prototype._reset =
397 function(calItem, mode, firstTime) {
398     this._calendarOrgs = {};
399 	ZmApptViewHelper.populateFolderSelect(this._folderSelect, this._folderRow, this._calendarOrgs, calItem);
400 
401 	this.enableInputs(true);
402 
403     var enableTimeSelection = !this._isForward;
404 
405 	// lets always attempt to populate even if we're dealing w/ a "new" calItem
406 	this._populateForEdit(calItem, mode);
407 
408 	// disable the recurrence select object for editing single instance
409     var enableRepeat = ((mode != ZmCalItem.MODE_EDIT_SINGLE_INSTANCE) && enableTimeSelection && !this._isProposeTime);
410     var repeatOptions = document.getElementById(this._htmlElId + "_repeat_options");
411 	if(repeatOptions) this._enableRepeat(enableRepeat);
412 
413     //show 'to' fields for forward action
414     var forwardOptions = document.getElementById(this._htmlElId + "_forward_options");
415     if(forwardOptions) Dwt.setVisible(forwardOptions, this._isForward || this._isProposeTime);
416 
417     this._resetReminders();
418 
419     // Delay of 500ms to call the finishReset
420     // It should be called only when all the items are loaded properly including the scheduler
421     var ta = new AjxTimedAction(this, this._finishReset);
422     AjxTimedAction.scheduleAction(ta, 500);
423 };
424 
425 ZmCalItemEditView.prototype._resetReminders = function() {
426     if (!this._hasReminderSupport) return;
427     
428     var reminderOptions = document.getElementById(this._htmlElId + "_reminder_options");
429     if(reminderOptions) {
430         var enableReminder = !this._isForward && !this._isProposeTime;
431         this._reminderSelectInput.setEnabled(enableReminder);
432         this._reminderButton.setEnabled(enableReminder);
433     }
434 };
435 
436 ZmCalItemEditView.prototype._finishReset =
437 function() {
438     // save the original form data in its initialized state
439     this._origFormValue = this._formValue(false);
440 };
441 
442 ZmCalItemEditView.prototype._getClone =
443 function() {
444 	// override
445 };
446 
447 ZmCalItemEditView.prototype._populateForSave =
448 function(calItem) {
449 	// create a copy of the appointment so we don't muck w/ the original
450 	calItem.setViewMode(this._mode);
451 
452 	// bug fix #5617 - check if there are any existing attachments that were unchecked
453 	var attCheckboxes = document.getElementsByName(ZmCalItem.ATTACHMENT_CHECKBOX_NAME);
454 	if (attCheckboxes && attCheckboxes.length > 0) {
455 		for (var i = 0; i < attCheckboxes.length; i++) {
456 			if (!attCheckboxes[i].checked)
457 				calItem.removeAttachment(attCheckboxes[i].value);
458 		}
459 	}
460 
461 	// save field values of this view w/in given appt
462 	calItem.setName(this._subjectField.getValue());
463 
464 	var folderId = this._folderSelect.getValue();
465 	if (this._mode != ZmCalItem.MODE_NEW && this._calItem.folderId != folderId) {
466 		// if moving existing calitem across mail boxes, cache the new folderId
467 		// so we can save it as a separate request
468 		var origFolder = appCtxt.getById(this._calItem.folderId);
469 		var newFolder = appCtxt.getById(folderId);
470 		if (origFolder.isRemote() || newFolder.isRemote()) {
471 			calItem.__newFolderId = folderId;
472 			folderId = this._calItem.folderId;
473 		}
474 	}
475 
476 	calItem.setFolderId(folderId);
477 	calItem.setOrganizer(this._calItem.organizer || this._calendarOrgs[folderId]);
478 
479 	// set the notes parts (always add text part)
480 	var top = new ZmMimePart();
481 	if (this._composeMode == Dwt.HTML) {
482 		top.setContentType(ZmMimeTable.MULTI_ALT);
483 
484 		// create two more mp's for text and html content types
485 		var textPart = new ZmMimePart();
486 		textPart.setContentType(ZmMimeTable.TEXT_PLAIN);
487 		textPart.setContent(this._notesHtmlEditor.getTextVersion());
488 		top.children.add(textPart);
489 
490 		var htmlPart = new ZmMimePart();
491 		htmlPart.setContentType(ZmMimeTable.TEXT_HTML);
492         htmlPart.setContent(this._notesHtmlEditor.getContent(true, true));
493 		top.children.add(htmlPart);
494 	} else {
495 		top.setContentType(ZmMimeTable.TEXT_PLAIN);
496 		top.setContent(this._notesHtmlEditor.getContent());
497 	}
498 
499 	calItem.notesTopPart = top;
500 
501 	//set the reminder time for alarm
502 	if (this._hasReminderSupport) {
503 		//calItem.setReminderMinutes(this._reminderSelect.getValue());
504         var reminderString = this._reminderSelectInput && this._reminderSelectInput.getValue();
505         if (!reminderString || reminderString == ZmMsg.apptRemindNever) {
506             calItem.setReminderMinutes(-1);
507         } else {
508             var reminderInfo = ZmCalendarApp.parseReminderString(reminderString);
509             var reminders = [
510                 { control: this._reminderEmailCheckbox,       action: ZmCalItem.ALARM_EMAIL        },
511                 { control: this._reminderDeviceEmailCheckbox, action: ZmCalItem.ALARM_DEVICE_EMAIL }
512             ];
513             for (var i = 0; i < reminders.length; i++) {
514                 var reminder = reminders[i];
515                 if (reminder.control.getEnabled() && reminder.control.isSelected()) {
516                     calItem.addReminderAction(reminder.action);
517                 }
518                 else {
519                     calItem.removeReminderAction(reminder.action);
520                 }
521             }
522             calItem.setReminderUnits(reminderInfo.reminderValue,  reminderInfo.reminderUnits);
523         }
524 	}
525 	return calItem;
526 };
527 
528 ZmCalItemEditView.prototype._populateForEdit =
529 function(calItem, mode) {
530 	// set subject
531     var subject = calItem.getName(),
532         buttonText;
533     
534 	this._subjectField.setValue(subject);
535     if(subject) {
536         buttonText = subject.substr(0, ZmAppViewMgr.TAB_BUTTON_MAX_TEXT);
537         appCtxt.getAppViewMgr().setTabTitle(this._controller.getCurrentViewId(), buttonText);
538     }
539     if (this._hasRepeatSupport) {
540         this._repeatSelect.setSelectedValue(calItem.isCustomRecurrence() ? "CUS" : calItem.getRecurType());
541         this._initRecurDialog(calItem.getRecurType());
542         // recurrence string
543 	    this._setRepeatDesc(calItem);
544     }
545 
546     if (this._hasReminderSupport) {
547         this._setEmailReminderControls();
548     }
549 
550 	// attachments
551 	this._attachDiv = document.getElementById(this._attachDivId);
552 	if (this._attachDiv) {
553 		// Bug 19993: clear out the attachments to prevent duplicates in the display.
554 		this._attachDiv.innerHTML = "";
555 	}
556 	var attachList = calItem.getAttachments();
557 	if (attachList) {
558 		for (var i = 0; i < attachList.length; i++)
559 			this.addAttachmentField(calItem, attachList[i]);
560 	}
561 
562 	this._setContent(calItem, mode);
563 	if (this._hasReminderSupport) {
564 		this.adjustReminderValue(calItem);
565         var actions = calItem.alarmActions;
566         this._reminderEmailCheckbox.setSelected(actions.contains(ZmCalItem.ALARM_EMAIL));
567         this._reminderDeviceEmailCheckbox.setSelected(actions.contains(ZmCalItem.ALARM_DEVICE_EMAIL));
568 	}
569 };
570 
571 ZmCalItemEditView.prototype.adjustReminderValue =
572 function(calItem) {
573     this._reminderSelectInput.setValue(ZmCalendarApp.getReminderSummary(calItem._reminderMinutes));
574 };
575 
576 ZmCalItemEditView.prototype._setRepeatDesc =
577 function(calItem) {
578 	if (calItem.isCustomRecurrence()) {
579         //Bug fix # 58493 - Set the classname if for the first time directly custom weekly/monthly/yearly repetition is selected
580         this._repeatDescField.className = "FakeAnchor";
581 		this._repeatDescField.innerHTML = calItem.getRecurBlurb();
582 	} else {
583 		this._repeatDescField.innerHTML = (calItem.getRecurType() != "NON")
584 			? AjxStringUtil.htmlEncode(ZmMsg.customize) : "";
585 	}
586 };
587 
588 ZmCalItemEditView.prototype._setContent =
589 function(calItem, mode) {
590 
591     var isSavedinHTML = false,
592         notesHtmlPart = calItem.getNotesPart(ZmMimeTable.TEXT_HTML),
593         notesPart;
594 
595     if (calItem.notesTopPart) { //Already existing appointment
596         var pattern = /<([A-Z][A-Z0-9]*)\b[^>]*>(.*?)<\/\1>/ig; // improved regex to parse html tags
597         if (notesHtmlPart && notesHtmlPart.match(pattern)) {
598             isSavedinHTML = true;
599         }
600     }
601     else if (appCtxt.get(ZmSetting.HTML_COMPOSE_ENABLED) && (appCtxt.get(ZmSetting.COMPOSE_AS_FORMAT) === ZmSetting.COMPOSE_HTML)) {
602         isSavedinHTML = true;
603     }
604 
605     if( !isSavedinHTML ){
606         notesPart = calItem.getNotesPart(ZmMimeTable.TEXT_PLAIN);
607     }
608 
609     this._controller.setFormatBtnItem(true, isSavedinHTML ? ZmMimeTable.TEXT_HTML : ZmMimeTable.TEXT_PLAIN);
610     this.setComposeMode(isSavedinHTML ? Dwt.HTML : Dwt.TEXT);
611 
612     if(this._isForward /* && !calItem.isOrganizer() */) {
613         var preface = [ZmMsg.DASHES, " ", ZmMsg.originalAppointment, " ", ZmMsg.DASHES].join("");
614         if(isSavedinHTML) {
615             var crlf2 = "<br><br>";
616             var crlf = "<br>";
617             notesHtmlPart = crlf2 + preface + crlf + calItem.getInviteDescription(true);
618             notesHtmlPart = this.formatContent(notesHtmlPart, true);
619         } else {
620             var crlf2 = AjxStringUtil.CRLF2;
621             var crlf = AjxStringUtil.CRLF;
622             notesPart = crlf2 + preface + crlf + calItem.getInviteDescription(false);
623             notesPart = this.formatContent(notesPart, false);
624         }
625     }
626     if (isSavedinHTML && notesHtmlPart) notesHtmlPart = AjxStringUtil.defangHtmlContent(notesHtmlPart);
627 
628     this._notesHtmlEditor.setContent(isSavedinHTML ? notesHtmlPart : notesPart);
629 };
630 
631 ZmCalItemEditView.prototype.formatContent =
632 function(body, composingHtml) {
633 
634     var includePref = appCtxt.get(ZmSetting.FORWARD_INCLUDE_ORIG);
635     if (includePref == ZmSetting.INCLUDE_PREFIX || includePref == ZmSetting.INCLUDE_PREFIX_FULL) {
636         var preface = (composingHtml ? '<br>' : '\n');
637 		var wrapParams = {
638 			text:				body,
639 			htmlMode:			composingHtml,
640 			preserveReturns:	true
641 		}
642         body = preface + AjxStringUtil.wordWrap(wrapParams);
643     }
644     return body;
645 };
646 
647 ZmCalItemEditView.prototype.getRepeatType =
648 function() {
649     return this._repeatSelectDisabled ? "NON" : this._repeatSelect.getValue();
650 }
651 
652 /**
653  * sets any recurrence rules w/in given ZmCalItem object
654 */
655 ZmCalItemEditView.prototype._getRecurrence =
656 function(calItem) {
657 	var repeatType = this._repeatSelect.getValue();
658 
659 	if (this._recurDialog && repeatType == "CUS") {
660 		calItem.setRecurType(this._recurDialog.getSelectedRepeatValue());
661 
662 		switch (calItem.getRecurType()) {
663 			case "DAI": this._recurDialog.setCustomDailyValues(calItem); break;
664 			case "WEE": this._recurDialog.setCustomWeeklyValues(calItem); break;
665 			case "MON": this._recurDialog.setCustomMonthlyValues(calItem); break;
666 			case "YEA": this._recurDialog.setCustomYearlyValues(calItem); break;
667 		}
668 
669 		// set the end recur values
670 		this._recurDialog.setRepeatEndValues(calItem);
671 	} else {
672 		calItem.setRecurType(repeatType != "CUS" ? repeatType : "NON");
673 		this._resetRecurrence(calItem);
674 	}
675 };
676 
677 ZmCalItemEditView.prototype._enableRepeat =
678 function(enable) {
679 	if (enable) {
680 		this._repeatSelect.enable();
681 		this._repeatDescField.className = (this._repeatSelect.getValue() == "NON") ? "DisabledText" : "FakeAnchor";
682 	}  else {
683 		this._repeatSelect.disable();
684 		this._repeatDescField.className = "DisabledText";
685 	}
686 	this._repeatSelectDisabled = !enable;
687 	this._repeatSelect.setAlign(DwtLabel.ALIGN_LEFT); // XXX: hack b/c bug w/ DwtSelect
688 };
689 
690 ZmCalItemEditView.prototype._createHTML =
691 function() {
692 	// override
693 };
694 
695 ZmCalItemEditView.prototype._createWidgets =
696 function(width) {
697 	// subject DwtInputField
698 	var params = {
699 		parent: this,
700 		parentElement: (this._htmlElId + "_subject"),
701 		inputId: this._htmlElId + "_subject_input",
702 		type: DwtInputField.STRING,
703 		label: ZmMsg.subject,
704 		errorIconStyle: DwtInputField.ERROR_ICON_NONE,
705 		validationStyle: DwtInputField.CONTINUAL_VALIDATION
706 	};
707 	this._subjectField = new DwtInputField(params);
708 	Dwt.setSize(this._subjectField.getInputElement(), "100%", "2rem");
709 
710 	// CalItem folder DwtSelect
711 	this._folderSelect = new DwtSelect({parent:this, parentElement:(this._htmlElId + "_folderSelect")});
712 	this._folderSelect.setAttribute('aria-label', ZmMsg.folder);
713 
714     this._hasRepeatSupport = Boolean(Dwt.byId(this._htmlElId + "_repeatSelect") != null);
715 
716     if(this._hasRepeatSupport) {
717         // recurrence DwtSelect
718         this._repeatSelect = new DwtSelect({parent:this, parentElement:(this._htmlElId + "_repeatSelect")});
719 		this._repeatSelect.setAttribute('aria-label', ZmMsg.repeat);
720         this._repeatSelect.addChangeListener(new AjxListener(this, this._repeatChangeListener));
721         for (var i = 0; i < ZmApptViewHelper.REPEAT_OPTIONS.length; i++) {
722             var option = ZmApptViewHelper.REPEAT_OPTIONS[i];
723             this._repeatSelect.addOption(option.label, option.selected, option.value);
724         }
725     }
726 
727 	this._hasReminderSupport = Dwt.byId(this._htmlElId + "_reminderSelect") != null;
728 
729 	// start/end date DwtButton's
730 	var dateButtonListener = new AjxListener(this, this._dateButtonListener);
731 	var dateCalSelectionListener = new AjxListener(this, this._dateCalSelectionListener);
732 
733 	// start/end date DwtCalendar's
734 	this._startDateButton = ZmCalendarApp.createMiniCalButton(this, this._htmlElId + "_startMiniCalBtn", dateButtonListener, dateCalSelectionListener, ZmMsg.startDate);
735 	this._endDateButton = ZmCalendarApp.createMiniCalButton(this, this._htmlElId + "_endMiniCalBtn", dateButtonListener, dateCalSelectionListener, ZmMsg.endDate);
736 	this._startDateButton.setSize("20");
737 	this._startDateButton.setAttribute('aria-label', ZmMsg.startDate);
738 	this._endDateButton.setSize("20");
739 	this._endDateButton.setAttribute('aria-label', ZmMsg.endDate);
740 
741 	if (this._hasReminderSupport) {
742 		var params = {
743 			parent: this,
744 			parentElement: (this._htmlElId + "_reminderSelectInput"),
745 			type: DwtInputField.STRING,
746 			label: ZmMsg.reminder,
747 			errorIconStyle: DwtInputField.ERROR_ICON_NONE,
748 			validationStyle: DwtInputField.CONTINUAL_VALIDATION,
749 			className: "DwtInputField ReminderInput"
750 		};
751 		this._reminderSelectInput = new DwtInputField(params);
752 		var reminderInputEl = this._reminderSelectInput.getInputElement();
753         // Fix for bug: 83100. Fix adapted from ZmReminderDialog::_createButtons
754 		Dwt.setSize(reminderInputEl, "120px", "2rem");
755 		reminderInputEl.onblur = AjxCallback.simpleClosure(this._handleReminderOnBlur, this, reminderInputEl);
756 
757 		var reminderButtonListener = new AjxListener(this, this._reminderButtonListener);
758 		var reminderSelectionListener = new AjxListener(this, this._reminderSelectionListener);
759 		this._reminderButton = ZmCalendarApp.createReminderButton(this, this._htmlElId + "_reminderSelect", reminderButtonListener, reminderSelectionListener);
760 		this._reminderButton.setSize("20");
761 		this._reminderButton.setAttribute('aria-label', ZmMsg.reminder);
762         this._reminderEmailCheckbox = new DwtCheckbox({parent: this});
763         this._reminderEmailCheckbox.replaceElement(document.getElementById(this._htmlElId + "_reminderEmailCheckbox"));
764         this._reminderEmailCheckbox.setText(ZmMsg.email);
765         this._reminderDeviceEmailCheckbox = new DwtCheckbox({parent: this});
766         this._reminderDeviceEmailCheckbox.replaceElement(document.getElementById(this._htmlElId + "_reminderDeviceEmailCheckbox"));
767         this._reminderDeviceEmailCheckbox.setText(ZmMsg.deviceEmail);
768         this._reminderConfigure = new DwtText({parent:this,className:"FakeAnchor"});
769         this._reminderConfigure.setText(ZmMsg.remindersConfigure);
770         // NOTE: We can't query the section name based on the pref id
771         // NOTE: because that info won't be available until the first time
772         // NOTE: prefs app is launched.
773         this._reminderConfigure.getHtmlElement().onclick = AjxCallback.simpleClosure(skin.gotoPrefs, skin, "NOTIFICATIONS");
774         this._reminderConfigure.replaceElement(document.getElementById(this._htmlElId+"_reminderConfigure"));
775 		this._setEmailReminderControls();
776 	    var settings = appCtxt.getSettings();
777         var listener = new AjxListener(this, this._settingChangeListener);
778         settings.getSetting(ZmSetting.CAL_EMAIL_REMINDERS_ADDRESS).addChangeListener(listener);
779         settings.getSetting(ZmSetting.CAL_DEVICE_EMAIL_REMINDERS_ADDRESS).addChangeListener(listener);
780 	}
781 
782     this._notesContainer = document.getElementById(this._htmlElId + "_notes");
783     this._topContainer = document.getElementById(this._htmlElId + "_top");
784 
785     this._notesHtmlEditor = new ZmHtmlEditor(this, null, null, this._composeMode, null, this._htmlElId + "_notes");
786     this._notesHtmlEditor.addOnContentInitializedListener(new AjxCallback(this,this.resize));
787 };
788 
789 ZmCalItemEditView.prototype._handleReminderOnBlur =
790 function(inputEl) {
791 	var reminderString = inputEl.value;
792 
793 	if (!reminderString) {
794 		inputEl.value = ZmMsg.apptRemindNever;
795 		return;
796 	}
797 
798 	var reminderInfo = ZmCalendarApp.parseReminderString(reminderString);
799 	var reminderMinutes = ZmCalendarApp.convertReminderUnits(reminderInfo.reminderValue, reminderInfo.reminderUnits);
800 	inputEl.value = ZmCalendarApp.getReminderSummary(reminderMinutes);
801 };
802 
803 ZmCalItemEditView.prototype._addEventHandlers =
804 function() {
805 	// override
806 };
807 
808 // cache all input fields so we dont waste time traversing DOM each time
809 ZmCalItemEditView.prototype._cacheFields =
810 function() {
811 	this._folderRow			= document.getElementById(this._htmlElId + "_folderRow");
812 	this._startDateField 	= document.getElementById(this._htmlElId + "_startDateField");
813 	this._endDateField 		= document.getElementById(this._htmlElId + "_endDateField");
814 	this._repeatDescField 	= document.getElementById(this._repeatDescId); 		// dont delete!
815 };
816 
817 ZmCalItemEditView.prototype._initAttachContainer =
818 function() {
819 	// create new table row which will contain parent fieldset
820 	var table = document.getElementById(this._htmlElId + "_table");
821     this._attachmentRow = document.getElementById(this._htmlElId + "_attachment_container");
822     if (!this._attachmentRow){
823        this._attachmentRow = table.insertRow(-1);
824        this._attachmentRow.id = this._htmlElId + "_attachment_container";
825     }
826 	var cell = this._attachmentRow.insertCell(-1);
827 	cell.colSpan = 2;
828 
829 	this._uploadFormId = Dwt.getNextId();
830 	this._attachDivId = Dwt.getNextId();
831 
832 	var subs = {
833 		uploadFormId: this._uploadFormId,
834 		attachDivId: this._attachDivId,
835 		url: appCtxt.get(ZmSetting.CSFE_UPLOAD_URI)+"&fmt=extended"
836 	};
837 
838 	cell.innerHTML = AjxTemplate.expand("calendar.Appointment#AttachContainer", subs);
839 };
840 
841 // Returns true if any of the attachment fields are populated
842 ZmCalItemEditView.prototype._gotAttachments =
843 function() {
844     var id = this._htmlElId;
845     if(!this._attachCount || !this._attachDiv) {
846         return false;
847     }
848 	var atts = document.getElementsByName(ZmCalItemEditView.UPLOAD_FIELD_NAME);
849 
850 	for (var i = 0; i < atts.length; i++) {
851 		if (atts[i].id.indexOf(id) === 0 && atts[i].value.length)
852 			return true;
853 	}
854 
855 	return false;
856 };
857 
858 ZmCalItemEditView.prototype.gotNewAttachments =
859 function() {
860     return this._gotAttachments();
861 };
862 
863 ZmCalItemEditView.prototype._removedAttachments =
864 function(){
865     var attCheckboxes = document.getElementsByName(ZmCalItem.ATTACHMENT_CHECKBOX_NAME);
866 	if (attCheckboxes && attCheckboxes.length > 0) {
867 		for (var i = 0; i < attCheckboxes.length; i++) {
868 			if (!attCheckboxes[i].checked) {
869 				return true;
870 			}
871 		}
872 	}
873     return false;
874 };
875 
876 ZmCalItemEditView.prototype._removeAttachment =
877 function(removeId) {
878 	// get document of attachment's iframe
879 	var removeSpan = document.getElementById(removeId);
880 	if (removeSpan) {
881 		// have my parent kill me
882 		removeSpan._parentDiv.parentNode.removeChild(removeSpan._parentDiv);
883 		if ((this._attachCount-1) == 0) {
884 			this._removeAllAttachments();
885 		} else {
886 			this._attachCount--;
887 		}
888 		if (this._attachCount == ZmCalItemEditView.SHOW_MAX_ATTACHMENTS) {
889 			this._attachDiv.style.height = "";
890 		}
891 
892         this.resize();
893 	}
894 };
895 
896 ZmCalItemEditView.prototype._removeAllAttachments =
897 function() {
898 	if (this._attachCount == 0) { return; }
899     var attachRow = document.getElementById(this._htmlElId + "_attachment_container");
900     if (attachRow)  Dwt.removeChildren(attachRow);
901 
902 	// let's be paranoid and really cleanup
903 	delete this._uploadFormId;
904 	delete this._attachDivId;
905 	delete this._attachRemoveId;
906 	delete this._attachDiv;
907 	this._attachDiv = this._attachRemoveId = this._attachDivId = this._uploadFormId = null;
908 
909 	if (this._attachmentRow) delete this._attachmentRow;
910 	this._attachmentRow = null;
911 	// reset any attachment related vars
912 	this._attachCount = 0;
913 };
914 
915 ZmCalItemEditView.prototype._submitAttachments =
916 function() {
917 	var callback = new AjxCallback(this, this._attsDoneCallback);
918 	var um = appCtxt.getUploadManager();
919 	window._uploadManager = um;
920 	um.execute(callback, document.getElementById(this._uploadFormId));
921 };
922 
923 ZmCalItemEditView.prototype._showRecurDialog =
924 function(repeatType) {
925 	if (!this._repeatSelectDisabled) {
926 		this._initRecurDialog(repeatType);
927 		this._recurDialog.popup();
928 	}
929 };
930 
931 ZmCalItemEditView.prototype._initRecurDialog =
932 function(repeatType) {
933 	if (!this._recurDialog) {
934 		this._recurDialog = new ZmApptRecurDialog(appCtxt.getShell(), this.uid);
935 		this._recurDialog.addSelectionListener(DwtDialog.OK_BUTTON, new AjxListener(this, this._recurOkListener));
936 		this._recurDialog.addSelectionListener(DwtDialog.CANCEL_BUTTON, new AjxListener(this, this._recurCancelListener));
937 	}
938 	var type = repeatType || this._recurDialogRepeatValue;
939 	var sd = (AjxDateUtil.simpleParseDateStr(this._startDateField.value)) || (new Date());
940 	var ed = (AjxDateUtil.simpleParseDateStr(this._endDateField.value)) || (new Date());
941 	this._recurDialog.initialize(sd, ed, type, this._calItem);
942 };
943 
944 ZmCalItemEditView.prototype._showTimeFields =
945 function(show) {
946 	// override if applicable
947 };
948 
949 // Returns a string representing the form content
950 ZmCalItemEditView.prototype._formValue =
951 function(excludeAttendees) {
952 	// override
953 };
954 
955 ZmCalItemEditView.prototype._getComponents =
956 function() {
957 	return { above: [this._topContainer], aside: [] };
958 };
959 
960 ZmCalItemEditView.prototype._resizeNotes =
961 function() {
962 	var bodyFieldId = this._notesHtmlEditor.getBodyFieldId();
963 	if (this._bodyFieldId != bodyFieldId) {
964 		this._bodyFieldId = bodyFieldId;
965 		this._bodyField = document.getElementById(this._bodyFieldId);
966 	}
967 
968 	var editorBounds = this.boundsForChild(this._notesHtmlEditor);
969 
970 	var rowWidth = editorBounds.width;
971 	var rowHeight = editorBounds.height;
972 
973 	var components = this._getComponents();
974 
975 	AjxUtil.foreach(components.above, function(c) {
976 		rowHeight -= Dwt.getOuterSize(c).y || 0;
977 	});
978 
979 	AjxUtil.foreach(components.aside, function(c) {
980 		rowWidth -= Dwt.getOuterSize(c).x || 0;
981 	});
982 
983 	if (rowWidth > 0) {
984 		// ensure a sensible minimum height
985 		rowHeight = Math.max(rowHeight, DwtCssStyle.asPixelCount('20rem'));
986 		this._notesHtmlEditor.setSize(rowWidth, rowHeight);
987 	}
988 
989 	Dwt.setSize(this._topContainer, rowWidth, Dwt.CLEAR);
990 };
991 
992 ZmCalItemEditView.prototype._handleRepeatDescFieldHover =
993 function(ev, isHover) {
994 	if (isHover) {
995 		var html = this._repeatDescField.innerHTML;
996 		if (html && html.length > 0) {
997 			this._repeatDescField.style.cursor = (this._repeatSelectDisabled || this._repeatSelect.getValue() == "NON")
998 				? "default" : "pointer";
999 
1000 			if (this._rdfTooltip == null) {
1001 				this._rdfTooltip = appCtxt.getShell().getToolTip();
1002 			}
1003 
1004 			var content = ["<div style='width:300px'>", html, "</div>"].join("");
1005 			this._rdfTooltip.setContent(content);
1006 			this._rdfTooltip.popup((ev.pageX || ev.clientX), (ev.pageY || ev.clientY));
1007 		}
1008 	} else {
1009 		if (this._rdfTooltip) {
1010 			this._rdfTooltip.popdown();
1011 		}
1012 
1013         this._repeatDescField.style.cursor = (this._repeatSelectDisabled || this._repeatSelect.getValue() == "NON")
1014             ? "default" : "pointer";
1015 
1016 	}
1017 };
1018 
1019 
1020 // Listeners
1021 
1022 ZmCalItemEditView.prototype._dateButtonListener =
1023 function(ev) {
1024 	var calDate = ev.item == this._startDateButton
1025 		? AjxDateUtil.simpleParseDateStr(this._startDateField.value)
1026 		: AjxDateUtil.simpleParseDateStr(this._endDateField.value);
1027 
1028 	// if date was input by user and its foobar, reset to today's date
1029 	if (calDate == null || isNaN(calDate)) {
1030 		calDate = new Date();
1031 	}
1032 
1033 	// always reset the date to current field's date
1034 	var menu = ev.item.getMenu();
1035 	var cal = menu.getItem(0);
1036 	cal.setDate(calDate, true);
1037 	ev.item.popup();
1038     if (AjxEnv.isIE) {
1039         menu.getHtmlElement().style.width = "180px";
1040     }        
1041 };
1042 
1043 ZmCalItemEditView.prototype._reminderButtonListener =
1044 function(ev) {
1045 	var menu = ev.item.getMenu();
1046 	var reminderItem = menu.getItem(0);
1047 	ev.item.popup();
1048 };
1049 
1050 ZmCalItemEditView.prototype._reminderSelectionListener =
1051 function(ev) {
1052     if(ev.item && ev.item instanceof DwtMenuItem){
1053         this._reminderSelectInput.setValue(ev.item.getText());
1054         this._reminderValue = ev.item.getData("value");
1055 
1056         var enabled = this._reminderValue != 0;
1057         this._reminderEmailCheckbox.setEnabled(enabled);
1058         this._reminderDeviceEmailCheckbox.setEnabled(enabled);
1059 
1060         // make sure that we're really allowed to enable these controls!
1061         if (enabled) {
1062             this._setEmailReminderControls();
1063         }
1064         return;
1065     }    
1066 };
1067 
1068 ZmCalItemEditView.prototype._dateCalSelectionListener =
1069 function(ev) {
1070 	var parentButton = ev.item.parent.parent;
1071 	var newDate = AjxDateUtil.simpleComputeDateStr(ev.detail);
1072 
1073 	this._oldStartDate = AjxDateUtil.simpleParseDateStr(this._startDateField.value);
1074 	this._oldEndDate = AjxDateUtil.simpleParseDateStr(this._endDateField.value);	
1075 
1076 	// change the start/end date if they mismatch
1077     var calItem = this._calItem;
1078 	if (parentButton == this._startDateButton) {
1079 		var ed = AjxDateUtil.simpleParseDateStr(this._endDateField.value);
1080 		if (ed && (ed.valueOf() < ev.detail.valueOf())) {
1081 			this._endDateField.value = newDate;
1082         } else if (this._oldEndDate && this._endDateField.value != newDate && (calItem.type === ZmItem.APPT)) {
1083             // Only preserve duration for Appts
1084             var delta = this._oldEndDate.getTime() - this._oldStartDate.getTime();
1085             this._endDateField.value = AjxDateUtil.simpleComputeDateStr(new Date(ev.detail.getTime() + delta));
1086         }
1087 		this._startDateField.value = newDate;
1088 	} else if(parentButton == this._endDateButton) {
1089 		var sd = AjxDateUtil.simpleParseDateStr(this._startDateField.value);
1090 		if (sd && (sd.valueOf() > ev.detail.valueOf()))
1091 			this._startDateField.value = newDate;
1092 		this._endDateField.value = newDate;
1093 	}
1094 
1095     if(this._hasRepeatSupport) {
1096         var repeatType = this._repeatSelect.getValue();
1097 
1098         if (calItem.isCustomRecurrence() &&
1099             this._mode != ZmCalItem.MODE_EDIT_SINGLE_INSTANCE)
1100         {
1101             this._checkRecurrenceValidity = true;
1102             this._initRecurDialog(repeatType);
1103             // Internal call of the custom recurrence dialog code -
1104             // Suppress the callback function
1105             this._enableCustomRecurCallback = false;
1106             this._recurOkListener();
1107             this._enableCustomRecurCallback = true;
1108         }
1109         else
1110         {
1111             var sd = AjxDateUtil.simpleParseDateStr(this._startDateField.value);
1112             if(sd) {
1113                 this._calItem._recurrence.setRecurrenceStartTime(sd.getTime());
1114                 this._setRepeatDesc(this._calItem);
1115             }
1116         }
1117     }    
1118 };
1119 
1120 ZmCalItemEditView.prototype._resetRecurrence =
1121 function(calItem) {
1122 	var recur = calItem._recurrence;
1123 	if(!recur) { return; }
1124 	var startTime = calItem.getStartTime();
1125 	recur.setRecurrenceStartTime(startTime);
1126 };
1127 
1128 ZmCalItemEditView.prototype._repeatChangeListener =
1129 function(ev) {
1130 	var newSelectVal = ev._args.newValue;
1131 	if (newSelectVal == "CUS") {
1132 		this._oldRepeatValue = ev._args.oldValue;
1133 		this._showRecurDialog();
1134 	} else {
1135 		this._repeatDescField.innerHTML = newSelectVal != "NON" ? AjxStringUtil.htmlEncode(ZmMsg.customize) : "";
1136 		this._repeatDescField.className = newSelectVal != "NON" ? "FakeAnchor" : "";
1137 	}
1138 	this.notifyListeners(ZmCalItemEditView._REPEAT_CHANGE, ev);
1139 };
1140 
1141 ZmCalItemEditView.prototype._recurOkListener =
1142 function(ev) {
1143 	var popdown = true;
1144 	this._recurDialogRepeatValue = this._recurDialog.getSelectedRepeatValue();
1145 	if (this._recurDialogRepeatValue == "NON") {
1146         this._repeatSelect.setSelectedValue(this._recurDialogRepeatValue);
1147         this._repeatDescField.innerHTML = "";
1148 	} else {
1149 		if (this._recurDialog.isValid()) {
1150 			this._repeatSelect.setSelectedValue("CUS");
1151 			// update the recur language
1152 			var temp = this._getClone(this._calItem);
1153 			this._getRecurrence(temp);
1154 			var sd = (AjxDateUtil.simpleParseDateStr(this._startDateField.value));
1155 			// If date changed...chnage the values
1156 			if (temp._recurrence._startDate.getDate() != sd.getDate() ||
1157 				temp._recurrence._startDate.getMonth() != sd.getMonth() ||
1158 				temp._recurrence._startDate.getFullYear() != sd.getFullYear())
1159 			{
1160 				if (this._checkRecurrenceValidity) {
1161 					this.validateRecurrence(temp._recurrence._startDate, temp._recurrence._startDate, sd, temp);
1162 					this._checkRecurrenceValidity = false;
1163 				} else {
1164 					this._startDateField.value = AjxDateUtil.simpleComputeDateStr(temp._recurrence._startDate);
1165 					this._endDateField.value = AjxDateUtil.simpleComputeDateStr(temp._recurrence._startDate);
1166 					this.startDate = temp._recurrence._startDate;
1167 					this.endDate = temp._recurrence._startDate;
1168 					this._calItem._startDate = this.startDate ;
1169 					this._calItem._endDate = this.startDate ;
1170 					this._setRepeatDesc(temp);
1171 				}
1172 
1173 			} else {
1174 				this._setRepeatDesc(temp);
1175 			}
1176 		} else {
1177 			// give feedback to user about errors in recur dialog
1178 			popdown = false;
1179 		}
1180 	}
1181 
1182 	if (popdown) {
1183 		this._recurDialog.popdown();
1184 	}
1185     if (this._customRecurDialogCallback && this._enableCustomRecurCallback) {
1186         this._customRecurDialogCallback.run();
1187     }
1188 };
1189 
1190 ZmCalItemEditView.prototype.validateRecurrence =
1191 function(startDate,  endDate, sd, temp) {
1192 	this._newRecurrenceStartDate = startDate;
1193 	this._newRecurrenceEndDate = endDate;	
1194 
1195 	var ps = this._dateResetWarningDlg = appCtxt.getYesNoMsgDialog();
1196 	ps.reset();
1197 	ps.setMessage(ZmMsg.validateRecurrence, DwtMessageDialog.WARNING_STYLE);
1198 
1199 	ps.registerCallback(DwtDialog.YES_BUTTON, this._dateChangeCallback, this, [startDate, endDate, sd, temp]);
1200 	ps.registerCallback(DwtDialog.NO_BUTTON, this._ignoreDateChangeCallback, this, [startDate, endDate, sd, temp]);
1201 	ps.popup();
1202 };
1203 
1204 ZmCalItemEditView.prototype._dateChangeCallback =
1205 function(startDate,  endDate, sd, temp) {
1206 	this._dateResetWarningDlg .popdown();
1207 	this._startDateField.value = AjxDateUtil.simpleComputeDateStr(temp._recurrence._startDate);
1208 	this._endDateField.value = AjxDateUtil.simpleComputeDateStr(temp._recurrence._startDate);
1209 	this.startDate = temp._recurrence._startDate;
1210 	this.endDate = temp._recurrence._startDate;
1211 	this._calItem._startDate = this.startDate ;
1212 	this._calItem._endDate = this.startDate ;
1213 	this._setRepeatDesc(temp);
1214 };
1215 
1216 ZmCalItemEditView.prototype._ignoreDateChangeCallback =
1217 function(startDate,  endDate, sd, temp) {
1218 	this._dateResetWarningDlg.popdown();
1219 	if (this._oldStartDate && this._oldEndDate) {
1220 		this._startDateField.value = AjxDateUtil.simpleComputeDateStr(this._oldStartDate);
1221 		this._endDateField.value = AjxDateUtil.simpleComputeDateStr(this._oldEndDate);
1222 		this.startDate = this._oldStartDate;
1223 		this.endDate = this._oldEndDate;
1224 		this._calItem._startDate = this.startDate;
1225 		this._calItem._endDate = this.endDate;
1226 		if (this._calItem._recurrence) {
1227 			this._calItem._recurrence._startDate.setTime(this.startDate.getTime());
1228 		}
1229 		this._setRepeatDesc(this._calItem);
1230 	}
1231 };
1232 
1233 ZmCalItemEditView.prototype._recurCancelListener =
1234 function(ev) {
1235 	// reset the selected option to whatever it was before user canceled
1236 	this._repeatSelect.setSelectedValue(this._oldRepeatValue);
1237 	this._recurDialog.popdown();
1238 };
1239 
1240 ZmCalItemEditView.prototype._controlListener =
1241 function(ev) {
1242 	this.resize();
1243 };
1244 
1245 
1246 // Callbacks
1247 
1248 ZmCalItemEditView.prototype._attsDoneCallback = function(status, attId) {
1249 	DBG.println(AjxDebug.DBG1, "Attachments: status = " + status + ", attId = " + attId);
1250 	if (status == AjxPost.SC_OK) {
1251 		//Checking for Zero sized/wrong path attachments
1252 		var zeroSizedAttachments = false;
1253 		if (typeof attId != "string") {
1254 			var attachmentIds = [];
1255 			for (var i = 0; i < attId.length; i++) {
1256 				var att = attId[i];
1257 				if (att.s == 0) {
1258 					zeroSizedAttachments = true;
1259 					continue;
1260 				}
1261 				attachmentIds.push(att.aid);
1262 			}
1263 			attId = attachmentIds.length > 0 ? attachmentIds.join(",") : null;
1264 		}
1265 		if (zeroSizedAttachments){
1266 			appCtxt.setStatusMsg(ZmMsg.zeroSizedAtts);
1267 		}
1268 		this._controller.saveCalItem(attId);
1269 
1270 	} else if (status == AjxPost.SC_UNAUTHORIZED) {
1271 		// It looks like the re-login code was copied from mail's ZmComposeView, and it never worked here.
1272 		// Just let it present the login screen.
1273 		var ex = new AjxException("Authorization Error during attachment upload", ZmCsfeException.SVC_AUTH_EXPIRED);
1274 		this._controller._handleException(ex);
1275 	} else {
1276 		// bug fix #2131 - handle errors during attachment upload.
1277 		this._controller.popupUploadErrorDialog(ZmItem.APPT, status, ZmMsg.errorTryAgain);
1278 		this._controller.enableToolbar(true);
1279 	}
1280 };
1281 
1282 
1283 ZmCalItemEditView.prototype._getDefaultFocusItem =
1284 function() {
1285 	return this._subjectField;
1286 };
1287 
1288 ZmCalItemEditView.prototype._handleOnClick =
1289 function(el) {
1290 	// figure out which input field was clicked
1291 	if (el.id == this._repeatDescId) {
1292         this._oldRepeatValue = this._repeatSelect.getValue();
1293         if(this._oldRepeatValue != "NON") {
1294 		    this._showRecurDialog(this._oldRepeatValue);
1295         }
1296 	} else if (el.id.indexOf("_att_") != -1) {
1297 		this._removeAttachment(el.id);
1298 	}
1299 };
1300 
1301 ZmCalItemEditView.prototype.handleDateFocus =
1302 function(el) {
1303     var isStartDate = (el == this._startDateField);
1304     if(isStartDate) {
1305         this._oldStartDateValue = el.value;
1306     }else {
1307         this._oldEndDateValue = el.value;
1308     }
1309 };
1310 
1311 ZmCalItemEditView.prototype.handleDateFieldChange =
1312 function(el) {
1313     var sdField = this._startDateField;
1314     var edField = this._endDateField;
1315     var oldStartDate = this._oldStartDateValue ? AjxDateUtil.simpleParseDateStr(this._oldStartDateValue) : null;
1316     ZmApptViewHelper.handleDateChange(sdField, edField, (el == sdField), false, oldStartDate);
1317 };
1318 
1319 ZmCalItemEditView.prototype.handleStartDateChange =
1320 function(sd) {
1321 	var calItem = this._calItem;
1322 	var repeatType = this._repeatSelect.getValue();
1323 	if (calItem.isCustomRecurrence() &&
1324 		this._mode != ZmCalItem.MODE_EDIT_SINGLE_INSTANCE)
1325 	{
1326 		var temp = this._getClone(this._calItem);		
1327 		this._oldStartDate = temp._startDate;
1328 		this._oldEndDate = temp._endDate;
1329 		this._checkRecurrenceValidity = true;
1330 		this._initRecurDialog(repeatType);
1331 		// Internal call of the custom recurrence dialog code -
1332 		// Suppress the callback function
1333 		this._enableCustomRecurCallback = false;
1334 		this._recurOkListener();
1335 		this._enableCustomRecurCallback = true;
1336 	}
1337 	else
1338 	{
1339 		calItem._recurrence.setRecurrenceStartTime(sd.getTime());
1340 		this._setRepeatDesc(calItem);
1341 	}
1342 };
1343 
1344 ZmCalItemEditView.prototype._setEmailReminderControls =
1345 function() {
1346     var email = appCtxt.get(ZmSetting.CAL_EMAIL_REMINDERS_ADDRESS);
1347     var emailText = ZmCalItemEditView.__getReminderCheckboxText(ZmMsg.emailWithAddress, AjxStringUtil.htmlEncode(email));
1348     var emailEnabled = Boolean(email);
1349     this._reminderEmailCheckbox.setEnabled(emailEnabled);
1350     this._reminderEmailCheckbox.setText(emailText);
1351 
1352     var deviceEmail = appCtxt.get(ZmSetting.CAL_DEVICE_EMAIL_REMINDERS_ADDRESS);
1353     var deviceEmailText = ZmCalItemEditView.__getReminderCheckboxText(ZmMsg.deviceEmailWithAddress, AjxStringUtil.htmlEncode(deviceEmail));
1354     var deviceEmailEnabled = appCtxt.get(ZmSetting.CAL_DEVICE_EMAIL_REMINDERS_ENABLED) && Boolean(deviceEmail);
1355     this._reminderDeviceEmailCheckbox.setEnabled(deviceEmailEnabled);
1356     this._reminderDeviceEmailCheckbox.setText(deviceEmailText);
1357 
1358     var configureEnabled = !emailEnabled && !deviceEmailEnabled;
1359     this._reminderEmailCheckbox.setVisible(!configureEnabled);
1360     this._reminderDeviceEmailCheckbox.setVisible((!configureEnabled && appCtxt.get(ZmSetting.CAL_DEVICE_EMAIL_REMINDERS_ENABLED)));
1361 };
1362 
1363 ZmCalItemEditView.__getReminderCheckboxText = function(pattern, email) {
1364     if (!email) {
1365         var onclick = 'skin.gotoPrefs("NOTIFICATIONS");return false;';
1366         email = [
1367             "<a href='#notifications' onclick='",onclick,"'>",
1368                 ZmMsg.remindersConfigureNow,
1369             "</a>"
1370         ].join("");
1371     }
1372     return AjxMessageFormat.format(pattern,[email]);
1373 };
1374 
1375 ZmCalItemEditView.prototype._settingChangeListener =
1376 function(ev) {
1377 	if (ev.type != ZmEvent.S_SETTING) { return; }
1378 	var id = ev.source.id;
1379 	if (id == ZmSetting.CAL_EMAIL_REMINDERS_ADDRESS || id == ZmSetting.CAL_DEVICE_EMAIL_REMINDERS_ADDRESS) {
1380 		this._setEmailReminderControls();
1381 	}
1382 };
1383 
1384 ZmCalItemEditView.prototype.deactivate =
1385 function() {
1386 	this._controller.inactive = true;
1387 };
1388 
1389 // Static methods
1390 
1391 ZmCalItemEditView._onClick =
1392 function(ev) {
1393 	ev = ev || window.event;
1394 	var el = DwtUiEvent.getTarget(ev);
1395 	var edv = AjxCore.objectWithId(el._editViewId);
1396 	if (edv) {
1397 		edv._handleOnClick(el);
1398 	}
1399 };
1400 
1401 ZmCalItemEditView._onKeyDown =
1402 function(ev) {
1403 	ev = ev || window.event;
1404 	var el = DwtUiEvent.getTarget(ev);
1405 	if (el.id.indexOf("_att_") != -1) {
1406 		// ignore enter key press in IE otherwise it tries to send the attachment!
1407 		var key = DwtKeyEvent.getCharCode(ev);
1408 		return !DwtKeyEvent.IS_RETURN[key];
1409 	}
1410 };
1411 
1412 ZmCalItemEditView._onMouseOver =
1413 function(ev) {
1414 	ev = DwtUiEvent.getEvent(ev);
1415 	var el = DwtUiEvent.getTarget(ev);
1416 	var edv = AjxCore.objectWithId(el._editViewId);
1417 	if (el == edv._repeatDescField) {
1418 		edv._handleRepeatDescFieldHover(ev, true);
1419 	}
1420 };
1421 
1422 ZmCalItemEditView._onMouseOut =
1423 function(ev) {
1424 	ev = DwtUiEvent.getEvent(ev);
1425 	var el = DwtUiEvent.getTarget(ev);
1426 	var edv = AjxCore.objectWithId(el._editViewId);
1427 	if (el == edv._repeatDescField) {
1428 		edv._handleRepeatDescFieldHover(ev, false);
1429 	}
1430 };
1431 
1432 ZmCalItemEditView._onChange =
1433 function(ev) {
1434 	var el = DwtUiEvent.getTarget(ev);
1435 	var edv = AjxCore.objectWithId(el._editViewId);
1436 	var sdField = edv._startDateField;
1437     edv.handleDateFieldChange(el);
1438 
1439 	var calItem = edv._calItem;
1440 	var sd = AjxDateUtil.simpleParseDateStr(sdField.value);
1441 	edv.handleStartDateChange(sd);
1442 };
1443 
1444 ZmCalItemEditView._onFocus =
1445 function(ev) {
1446 	var el = DwtUiEvent.getTarget(ev);
1447 	var edv = AjxCore.objectWithId(el._editViewId);
1448 	edv.handleDateFocus(el);
1449 };
1450