1 
  2 /*
  3  * ***** BEGIN LICENSE BLOCK *****
  4  * Zimbra Collaboration Suite Web Client
  5  * Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Synacor, Inc.
  6  *
  7  * The contents of this file are subject to the Common Public Attribution License Version 1.0 (the "License");
  8  * you may not use this file except in compliance with the License.
  9  * You may obtain a copy of the License at: https://www.zimbra.com/license
 10  * The License is based on the Mozilla Public License Version 1.1 but Sections 14 and 15
 11  * have been added to cover use of software over a computer network and provide for limited attribution
 12  * for the Original Developer. In addition, Exhibit A has been modified to be consistent with Exhibit B.
 13  *
 14  * Software distributed under the License is distributed on an "AS IS" basis,
 15  * WITHOUT WARRANTY OF ANY KIND, either express or implied.
 16  * See the License for the specific language governing rights and limitations under the License.
 17  * The Original Code is Zimbra Open Source Web Client.
 18  * The Initial Developer of the Original Code is Zimbra, Inc.  All rights to the Original Code were
 19  * transferred by Zimbra, Inc. to Synacor, Inc. on September 14, 2015.
 20  *
 21  * All portions of the code are Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Synacor, Inc. All Rights Reserved.
 22  * ***** END LICENSE BLOCK *****
 23  */
 24 
 25 /**
 26  * Creates a new appointment controller to manage appointment creation/editing.
 27  * @constructor
 28  * @class
 29  * This class manages appointment creation/editing.
 30  *
 31  * @author Parag Shah
 32  *
 33  * @param {DwtShell}	container	the containing shell
 34  * @param {ZmApp}		app			the containing app
 35  * @param {constant}	type		controller type
 36  * @param {string}		sessionId	the session id
 37  * 
 38  * @extends		ZmBaseController
 39  */
 40 ZmCalItemComposeController = function(container, app, type, sessionId) {
 41 	if (arguments.length == 0) { return; }
 42 	ZmBaseController.apply(this, arguments);
 43 	this._elementsToHide = ZmAppViewMgr.LEFT_NAV;
 44 
 45 	this._onAuthTokenWarningListener = this._onAuthTokenWarningListener.bind(this);
 46 	appCtxt.addAuthTokenWarningListener(this._onAuthTokenWarningListener);
 47 };
 48 
 49 ZmCalItemComposeController.prototype = new ZmBaseController;
 50 ZmCalItemComposeController.prototype.constructor = ZmCalItemComposeController;
 51 
 52 ZmCalItemComposeController.prototype.isZmCalItemComposeController = true;
 53 ZmCalItemComposeController.prototype.toString = function() { return "ZmCalItemComposeController"; };
 54 
 55 ZmCalItemComposeController.DEFAULT_TAB_TEXT = ZmMsg.appointment;
 56 
 57 ZmCalItemComposeController.SAVE_CLOSE 	= "SAVE_CLOSE";
 58 ZmCalItemComposeController.SEND 		= "SEND";
 59 ZmCalItemComposeController.SAVE  		= "SAVE";
 60 ZmCalItemComposeController.APPT_MODE  	= "APPT";
 61 ZmCalItemComposeController.MEETING_MODE	= "MEETING";
 62 
 63 // Public methods
 64 
 65 ZmCalItemComposeController.prototype.show =
 66 function(calItem, mode, isDirty) {
 67 
 68     this._mode = mode;
 69 	if (this._toolbar.toString() != "ZmButtonToolBar") {
 70 		this._createToolBar();
 71 	}
 72 	var initial = this.initComposeView();
 73 	this._app.pushView(this._currentViewId);
 74 	this._composeView.set(calItem, mode, isDirty);
 75 	this._composeView.reEnableDesignMode();
 76     this._initToolbar(mode);
 77 	if (initial) {
 78 		this._setComposeTabGroup();
 79 	}
 80 };
 81 
 82 ZmCalItemComposeController.prototype._preHideCallback =
 83 function(view, force) {
 84 
 85 	ZmController.prototype._preHideCallback.call(this);
 86 	return force ? true : this.popShield();
 87 };
 88 
 89 ZmCalItemComposeController.prototype._preUnloadCallback =
 90 function(view) {
 91 	return !this._composeView.isDirty();
 92 };
 93 
 94 
 95 ZmCalItemComposeController.prototype._preShowCallback =
 96 function() {
 97 	return true;
 98 };
 99 
100 ZmCalItemComposeController.prototype._postShowCallback =
101 function(view, force) {
102 	var ta = new AjxTimedAction(this, this._setFocus);
103 	AjxTimedAction.scheduleAction(ta, 10);
104 };
105 
106 ZmCalItemComposeController.prototype._postHideCallback =
107 function() {
108 	// overload me
109 };
110 
111 ZmCalItemComposeController.prototype.popShield =
112 function() {
113 	if (!this._composeView.isDirty()) {
114 		this._composeView.cleanup();
115 		return true;
116 	}
117 
118 	var ps = this._popShield = appCtxt.getYesNoCancelMsgDialog();
119 	ps.reset();
120 	ps.setMessage(ZmMsg.askToSave, DwtMessageDialog.WARNING_STYLE);
121 	ps.registerCallback(DwtDialog.YES_BUTTON, this._popShieldYesCallback, this);
122 	ps.registerCallback(DwtDialog.NO_BUTTON, this._popShieldNoCallback, this);
123 	ps.popup(this._composeView._getDialogXY());
124 
125 	return false;
126 };
127 
128 ZmCalItemComposeController.prototype._onAuthTokenWarningListener =
129 function() {
130 	// The auth token will expire in less than five minutes, so we must issue
131 	// issue a last, hard save. This method is typically called more than once.
132 	try {
133 		if (this._composeView && this._composeView.isDirty()) {
134 			// bypass most of the validity checking logic
135 			var calItem = this._composeView.getCalItem();
136 			return this._saveCalItemFoRealz(calItem, null, null, true);
137 		}
138 	} catch(ex) {
139 		var msg = AjxUtil.isString(ex) ?
140 			AjxMessageFormat.format(ZmMsg.errorSavingWithMessage, errorMsg) :
141 			ZmMsg.errorSaving;
142 
143 		appCtxt.setStatusMsg(msg, ZmStatusView.LEVEL_CRITICAL);
144 	}
145 };
146 
147 /**
148  * Gets the appt view.
149  * 
150  * @return	{ZmApptView}	the appt view
151  */
152 ZmCalItemComposeController.prototype.getItemView = function() {
153 	return this._composeView;
154 };
155 
156 /**
157  * Gets the toolbar.
158  *
159  * @return	{ZmButtonToolBar}	the toolbar
160  */
161 ZmCalItemComposeController.prototype.getToolbar =
162 function() {
163 	return this._toolbar;
164 };
165 
166 /**
167  * Saves the calendar item.
168  * 
169  * @param	{String}	attId		the item id
170  */
171 ZmCalItemComposeController.prototype.saveCalItem =
172 function(attId) {
173 	// override
174 };
175 
176 /**
177  * Toggles the spell check button.
178  * 
179  * @param	{Boolean}	toggled		if <code>true</code>, select the spell check button 
180  */
181 ZmCalItemComposeController.prototype.toggleSpellCheckButton =
182 function(toggled) {
183 	var spellCheckButton = this._toolbar.getButton(ZmOperation.SPELL_CHECK);
184 	if (spellCheckButton) {
185 		spellCheckButton.setSelected((toggled || false));
186 	}
187 };
188 
189 ZmCalItemComposeController.prototype.initComposeView =
190 function(initHide) {
191 	if (!this._composeView) {
192 		this._composeView = this._createComposeView();
193 		var callbacks = {};
194 		callbacks[ZmAppViewMgr.CB_PRE_HIDE] = new AjxCallback(this, this._preHideCallback);
195 		callbacks[ZmAppViewMgr.CB_PRE_UNLOAD] = new AjxCallback(this, this._preUnloadCallback);
196 		callbacks[ZmAppViewMgr.CB_POST_SHOW] = new AjxCallback(this, this._postShowCallback);
197 		callbacks[ZmAppViewMgr.CB_PRE_SHOW] = new AjxCallback(this, this._preShowCallback);
198 		callbacks[ZmAppViewMgr.CB_POST_HIDE] = new AjxCallback(this, this._postHideCallback);
199 		if (this._toolbar.toString() != "ZmButtonToolBar")
200 			this._createToolBar();
201 		var elements = this.getViewElements(null, this._composeView, this._toolbar);
202 
203 		this._app.createView({	viewId:		this._currentViewId,
204 								viewType:	this._currentViewType,
205 								elements:	elements,
206 								hide:		this._elementsToHide,
207 								controller:	this,
208 								callbacks:	callbacks,
209 								tabParams:	this._getTabParams()});
210 		if (initHide) {
211 			this._composeView.preload();
212 		}
213 		return true;
214 	}
215 	return false;
216 };
217 
218 ZmCalItemComposeController.prototype._getTabParams =
219 function() {
220 	return {id:this.tabId, image:"CloseGray", hoverImage:"Close", text:ZmCalItemComposeController.DEFAULT_TAB_TEXT, textPrecedence:76,
221 			tooltip:ZmCalItemComposeController.DEFAULT_TAB_TEXT, style: DwtLabel.IMAGE_RIGHT};
222 };
223 
224 ZmCalItemComposeController.prototype._createComposeView =
225 function() {
226 	// override
227 };
228 
229 ZmCalItemComposeController.prototype._setComposeTabGroup =
230 function(setFocus) {
231 	// override
232 };
233 
234 ZmCalItemComposeController.prototype._setFocus =
235 function(focusItem, noFocus) {
236 	DBG.println(AjxDebug.KEYBOARD, "timed action restoring focus to " + focusItem + "; noFocus = " + noFocus);
237 	this._restoreFocus(focusItem, noFocus);
238 };
239 
240 ZmCalItemComposeController.prototype.getKeyMapName =
241 function() {
242 	// override
243 };
244 
245 ZmCalItemComposeController.prototype.handleKeyAction =
246 function(actionCode) {
247 	DBG.println(AjxDebug.DBG2, "ZmCalItemComposeController.handleKeyAction");
248 	switch (actionCode) {
249 		case ZmKeyMap.SAVE:
250 			this._saveListener();
251 			break;
252 
253 		case ZmKeyMap.CANCEL:
254 			this._cancelListener();
255 			break;
256 
257 
258 		case ZmKeyMap.HTML_FORMAT:
259 			if (appCtxt.get(ZmSetting.HTML_COMPOSE_ENABLED)) {
260 				var mode = this._composeView.getComposeMode();
261 				var newMode = (mode == Dwt.TEXT) ? Dwt.HTML : Dwt.TEXT;
262 				this._formatListener(null, newMode);
263 				// reset the radio button for the format button menu
264 				var formatBtn = this._toolbar.getButton(ZmOperation.COMPOSE_OPTIONS);
265 				if (formatBtn) {
266 					formatBtn.getMenu().checkItem(ZmHtmlEditor.VALUE, newMode, true);
267 				}
268 			}
269 			break;
270 
271 		default:
272 			return ZmController.prototype.handleKeyAction.call(this, actionCode);
273 			break;
274 	}
275 	return true;
276 };
277 
278 ZmCalItemComposeController.prototype.mapSupported =
279 function(map) {
280 	return (map == "editor");
281 };
282 
283 ZmCalItemComposeController.prototype.getTabView =
284 function() {
285 	return this._composeView;
286 };
287 
288 /**
289  * inits check mark for menu item depending on compose mode preference.
290  * 
291  * @private
292  */
293 ZmCalItemComposeController.prototype.setFormatBtnItem =
294 function(skipNotify, composeMode) {
295 	var mode;
296 	if (composeMode) {
297 		mode = composeMode;
298 	} else {
299 		var bComposeEnabled = appCtxt.get(ZmSetting.HTML_COMPOSE_ENABLED);
300 		var composeFormat = appCtxt.get(ZmSetting.COMPOSE_AS_FORMAT);
301 		mode = (bComposeEnabled && composeFormat == ZmSetting.COMPOSE_HTML)
302 			? Dwt.HTML : Dwt.TEXT;
303 	}
304 
305 	var formatBtn = this._toolbar.getButton(ZmOperation.COMPOSE_OPTIONS);
306 	if (formatBtn) {
307         var menu = formatBtn.getMenu ? formatBtn.getMenu() : null;
308         if(menu) {
309 		    menu.checkItem(ZmHtmlEditor.VALUE, mode, skipNotify);
310         }
311 	}
312 };
313 
314 ZmCalItemComposeController.prototype.setOptionsBtnItem =
315 function(skipNotify, composeMode) {
316 	var mode;
317 	if (composeMode) {
318 		mode = composeMode;
319 	} else {
320 		var bComposeEnabled = appCtxt.get(ZmSetting.HTML_COMPOSE_ENABLED);
321 		var composeFormat = appCtxt.get(ZmSetting.COMPOSE_AS_FORMAT);
322 		mode = (bComposeEnabled && composeFormat == ZmSetting.COMPOSE_HTML)
323 			? Dwt.HTML : Dwt.TEXT;
324 	}
325 
326 	var formatBtn = this._toolbar.getButton(ZmOperation.COMPOSE_OPTIONS);
327 	if (formatBtn) {
328 		formatBtn.getMenu().checkItem(ZmHtmlEditor.VALUE, mode, skipNotify);
329 	}
330 };
331 
332 // Private / Protected methods
333 
334 
335 ZmCalItemComposeController.prototype._initToolbar =
336 function(mode) {
337 	if (this._toolbar.toString() != "ZmButtonToolBar") {
338 		this._createToolBar();
339 	}
340 
341     this.enableToolbar(true);
342 
343 	var isNew = (mode == null || mode == ZmCalItem.MODE_NEW || mode == ZmCalItem.MODE_NEW_FROM_QUICKADD);
344 
345 	var cancelButton = this._toolbar.getButton(ZmOperation.CANCEL);
346 	if (isNew) {
347 		cancelButton.setText(ZmMsg.cancel);
348 	} else {
349 		cancelButton.setText(ZmMsg.close);
350 	}
351 
352     var saveButton = this._toolbar.getButton(ZmOperation.SAVE);
353     //use send button for forward appt view
354     if(ZmCalItem.FORWARD_MAPPING[mode]) {
355         saveButton.setText(ZmMsg.send);
356     }
357 
358 	var printButton = this._toolbar.getButton(ZmOperation.PRINT);
359 	if (printButton) {
360 		printButton.setEnabled(!isNew);
361 	}
362 
363 	appCtxt.notifyZimlets("initializeToolbar", [this._app, this._toolbar, this, this._currentViewId], {waitUntilLoaded:true});
364 };
365 
366 
367 ZmCalItemComposeController.prototype._createToolBar =
368 function() {
369 
370 	var buttons = [ZmOperation.SEND_INVITE, ZmOperation.SAVE, ZmOperation.CANCEL, ZmOperation.SEP];
371 
372 	if (appCtxt.get(ZmSetting.ATTACHMENT_ENABLED)) {
373 		buttons.push(ZmOperation.ATTACHMENT);
374 	}
375 
376     if (appCtxt.get(ZmSetting.PRINT_ENABLED)) {
377 		buttons.push(ZmOperation.PRINT);
378 	}
379 
380 	if (appCtxt.isSpellCheckerAvailable()) {
381 		buttons.push(ZmOperation.SPELL_CHECK);
382 	}
383 	buttons.push(ZmOperation.SEP, ZmOperation.COMPOSE_OPTIONS);
384 
385 	this._toolbar = new ZmButtonToolBar({
386 		parent:     this._container,
387 		buttons:    buttons,
388 		overrides:  this._getButtonOverrides(buttons),
389 		context:    this._currentViewId,
390 		controller: this
391 	});
392 	this._toolbar.addSelectionListener(ZmOperation.SAVE, new AjxListener(this, this._saveListener));
393 	this._toolbar.addSelectionListener(ZmOperation.CANCEL, new AjxListener(this, this._cancelListener));
394 
395 	if (appCtxt.get(ZmSetting.PRINT_ENABLED)) {
396 		this._toolbar.addSelectionListener(ZmOperation.PRINT, new AjxListener(this, this._printListener));
397 	}
398 
399 	if (appCtxt.get(ZmSetting.ATTACHMENT_ENABLED)) {
400 		this._toolbar.addSelectionListener(ZmOperation.ATTACHMENT, new AjxListener(this, this._attachmentListener));
401 	}
402 
403     var sendButton = this._toolbar.getButton(ZmOperation.SEND_INVITE);
404     sendButton.setVisible(false);
405 
406 	// change default button style to toggle for spell check button
407 	var spellCheckButton = this._toolbar.getButton(ZmOperation.SPELL_CHECK);
408 	if (spellCheckButton) {
409 		spellCheckButton.setAlign(DwtLabel.IMAGE_LEFT | DwtButton.TOGGLE_STYLE);
410 	}
411 
412 	var optionsButton = this._toolbar.getButton(ZmOperation.COMPOSE_OPTIONS);
413 	if (optionsButton) {
414 		optionsButton.setVisible(false); //start it hidden, and show in case it's needed.
415 	}
416 
417 	if (optionsButton && appCtxt.get(ZmSetting.HTML_COMPOSE_ENABLED)) {
418 		optionsButton.setVisible(true); 
419 
420 		var m = new DwtMenu({parent:optionsButton});
421 		optionsButton.setMenu(m);
422 
423 		var mi = new DwtMenuItem({parent:m, style:DwtMenuItem.RADIO_STYLE, id:[ZmId.WIDGET_MENU_ITEM,this._currentViewId,ZmOperation.FORMAT_HTML].join("_")});
424 		mi.setImage("HtmlDoc");
425 		mi.setText(ZmMsg.formatAsHtml);
426 		mi.setData(ZmHtmlEditor.VALUE, Dwt.HTML);
427         mi.addSelectionListener(new AjxListener(this, this._formatListener));
428 
429 		mi = new DwtMenuItem({parent:m, style:DwtMenuItem.RADIO_STYLE, id:[ZmId.WIDGET_MENU_ITEM,this._currentViewId,ZmOperation.FORMAT_TEXT].join("_")});
430 		mi.setImage("GenericDoc");
431 		mi.setText(ZmMsg.formatAsText);
432 		mi.setData(ZmHtmlEditor.VALUE, Dwt.TEXT);
433         mi.addSelectionListener(new AjxListener(this, this._formatListener));
434 	}
435 
436 	this._toolbar.addSelectionListener(ZmOperation.SPELL_CHECK, new AjxListener(this, this._spellCheckListener));
437 };
438 
439 ZmCalItemComposeController.prototype.showErrorMessage =
440 function(errorMsg) {
441 	var dialog = appCtxt.getMsgDialog();
442     dialog.reset();
443 	//var msg = ZmMsg.errorSaving + (errorMsg ? (":<p>" + errorMsg) : ".");
444 	var msg = errorMsg ? AjxMessageFormat.format(ZmMsg.errorSavingWithMessage, errorMsg) : ZmMsg.errorSaving;
445 	dialog.setMessage(msg, DwtMessageDialog.CRITICAL_STYLE);
446 	dialog.popup();
447     this.enableToolbar(true);
448 };
449 
450 ZmCalItemComposeController.prototype._saveCalItemFoRealz = function(calItem, attId, notifyList, force) {
451 
452     var recurringChanges = this._composeView.areRecurringChangesDirty();
453 
454 	if (this._composeView.isDirty() || recurringChanges || force) {
455 		// bug: 16112 - check for folder existance
456 		if (calItem.getFolder() && calItem.getFolder().noSuchFolder) {
457 			var msg = AjxMessageFormat.format(ZmMsg.errorInvalidFolder, calItem.getFolder().name);
458 			this.showErrorMessage(msg);
459 			return false;
460 		}
461         if(this._composeView.isReminderOnlyChanged()) {
462             calItem.setMailNotificationOption(false);
463         }
464         var callback = new AjxCallback(this, this._handleResponseSave, calItem);
465 		var errorCallback = new AjxCallback(this, this._handleErrorSave, calItem);
466         this._doSaveCalItem(calItem, attId, callback, errorCallback, notifyList);
467 	} else {
468         if (this._action == ZmCalItemComposeController.SAVE && !this._composeView.isDirty()) {
469             this.enableToolbar(true);
470         }
471         
472         if (this.isCloseAction()){
473             this._composeView.cleanup();  // bug: 27600 clean up edit view to avoid stagnant attendees
474             this.closeView();
475         }
476 	}
477 };
478 
479 ZmCalItemComposeController.prototype._doSaveCalItem =
480 function(calItem, attId, callback, errorCallback, notifyList){
481     if(this._action == ZmCalItemComposeController.SEND)
482         calItem.send(attId, callback, errorCallback, notifyList);
483     else
484         calItem.save(attId, callback, errorCallback, notifyList);
485 };
486 
487 ZmCalItemComposeController.prototype.isCloseAction =
488 function() {
489     return ( this._action == ZmCalItemComposeController.SEND ||  this._action == ZmCalItemComposeController.SAVE_CLOSE );
490 };
491 
492 ZmCalItemComposeController.prototype._handleResponseSave =
493 function(calItem, result) {
494     try {
495         if (calItem.__newFolderId) {
496             var folder = appCtxt.getById(calItem.__newFolderId);
497             calItem.__newFolderId = null;
498             this._app.getListController()._doMove(calItem, folder, null, false);
499         }
500 
501         calItem.handlePostSaveCallbacks();
502         if(this.isCloseAction()) {
503         	this.closeView();
504         }
505         appCtxt.notifyZimlets("onSaveApptSuccess", [this, calItem, result]);//notify Zimlets on success
506     } catch (ex) {
507         DBG.println(ex);
508     } finally {
509         this._composeView.cleanup();
510     }
511 };
512 
513 ZmCalItemComposeController.prototype._handleErrorSave =
514 function(calItem, ex) {
515 	var status = this._getErrorSaveStatus(calItem, ex);
516 	return status.handled;
517 };
518 
519 ZmCalItemComposeController.prototype._getErrorSaveStatus =
520 function(calItem, ex) {
521 	// TODO: generalize error message for calItem instead of just Appt
522 	var status = calItem.processErrorSave(ex);
523 	status.handled = false;
524 
525     if (status.continueSave) {
526         this.saveCalItemContinue(calItem);
527         status.handled = true;
528     } else {
529         // Enable toolbar if not attempting to continue the Save
530         this.enableToolbar(true);
531         if (status.errorMessage) {
532             // Handled the error, display the error message
533             status.handled = true;
534             var dialog = appCtxt.getMsgDialog();
535             dialog.setMessage(status.errorMessage, DwtMessageDialog.CRITICAL_STYLE);
536             dialog.popup();
537         }
538         appCtxt.notifyZimlets("onSaveApptFailure", [this, calItem, ex]);
539     }
540 
541     return status;
542 };
543 
544 // Spell check methods
545 
546 ZmCalItemComposeController.prototype._spellCheckAgain =
547 function() {
548 	this._composeView.getHtmlEditor().discardMisspelledWords();
549 	this._doSpellCheck();
550 	return false;
551 };
552 
553 ZmCalItemComposeController.prototype.enableToolbar =
554 function(enabled) {
555     this._toolbar.enableAll(enabled);
556 };
557 
558 // Listeners
559 
560 // Save button was pressed
561 ZmCalItemComposeController.prototype._saveListener =
562 function(ev) {
563     this._action = ZmCalItemComposeController.SAVE;
564     this.enableToolbar(false);
565 	if (this._doSave() === false) {
566 		return;
567     }
568 };
569 
570 // Cancel button was pressed
571 ZmCalItemComposeController.prototype._cancelListener =
572 function(ev) {
573 	this._action = ZmCalItemComposeController.SAVE_CLOSE;
574 	this._app.popView();
575 };
576 
577 ZmCalItemComposeController.prototype._printListener =
578 function() {
579 	// overload me.
580 };
581 
582 // Attachment button was pressed
583 ZmCalItemComposeController.prototype._attachmentListener =
584 function(ev) {
585 	this._composeView.addAttachmentField();
586 };
587 
588 ZmCalItemComposeController.prototype._formatListener =
589 function(ev, mode) {
590 	if (!mode && !(ev && ev.item.getChecked())) return;
591 
592 	mode = mode || ev.item.getData(ZmHtmlEditor.VALUE);
593 	if (mode == this._composeView.getComposeMode()) return;
594 
595 	if (mode == Dwt.TEXT) {
596 		// if formatting from html to text, confirm w/ user!
597 		if (!this._textModeOkCancel) {
598 			var dlgId = this._composeView.getHTMLElId() + "_formatWarning";
599 			this._textModeOkCancel = new DwtMessageDialog({id: dlgId, parent:this._shell, buttons:[DwtDialog.OK_BUTTON, DwtDialog.CANCEL_BUTTON]});
600 			this._textModeOkCancel.setMessage(ZmMsg.switchToText, DwtMessageDialog.WARNING_STYLE);
601 			this._textModeOkCancel.registerCallback(DwtDialog.OK_BUTTON, this._textModeOkCallback, this);
602 			this._textModeOkCancel.registerCallback(DwtDialog.CANCEL_BUTTON, this._textModeCancelCallback, this);
603 		}
604 		this._textModeOkCancel.popup(this._composeView._getDialogXY());
605 	} else {
606 		this._composeView.setComposeMode(mode);
607 	}
608 };
609 
610 ZmCalItemComposeController.prototype._spellCheckListener =
611 function(ev) {
612 	var spellCheckButton = this._toolbar.getButton(ZmOperation.SPELL_CHECK);
613 	var htmlEditor = this._composeView.getHtmlEditor();
614 
615 	if (spellCheckButton.isToggled()) {
616 		var callback = new AjxCallback(this, this.toggleSpellCheckButton)
617 		if (!htmlEditor.spellCheck(callback))
618 			this.toggleSpellCheckButton(false);
619 	} else {
620 		htmlEditor.discardMisspelledWords();
621 	}
622 };
623 
624 ZmCalItemComposeController.prototype._doSave =
625 function() {
626 	// check if all fields are populated w/ valid values
627 	try {
628 		if (this._composeView.isValid()) {
629 			return this.saveCalItem();
630 		}
631 	} catch(ex) {
632 		if (AjxUtil.isString(ex)) {
633 			this.showErrorMessage(ex);
634 		} else {
635 			DBG.dumpObj(AjxDebug.DBG1, ex);
636 		}
637 
638 		return false;
639 	}
640 };
641 
642 
643 // Callbacks
644 
645 ZmCalItemComposeController.prototype._doSpellCheck =
646 function() {
647 	var text = this._composeView.getHtmlEditor().getTextVersion();
648 	var soap = AjxSoapDoc.create("CheckSpellingRequest", "urn:zimbraMail");
649 	soap.getMethod().appendChild(soap.getDoc().createTextNode(text));
650 	var cmd = new ZmCsfeCommand();
651 	var callback = new AjxCallback(this, this._spellCheckCallback);
652 	cmd.invoke({soapDoc:soap, asyncMode:true, callback:callback});
653 };
654 
655 ZmCalItemComposeController.prototype._popShieldYesCallback =
656 function() {
657 	this._popShield.popdown();
658 	this._action = ZmCalItemComposeController.SAVE_CLOSE;
659 	if (this._doSave()) {
660 		appCtxt.getAppViewMgr().showPendingView(true);
661 	}
662 };
663 
664 ZmCalItemComposeController.prototype._popShieldNoCallback =
665 function() {
666 	this._popShield.popdown();
667     this.enableToolbar(true);
668 	try {
669 		// bug fix #33001 - prism throws exception with this method:
670 		appCtxt.getAppViewMgr().showPendingView(true);
671 	} catch(ex) {
672 		// so do nothing
673 	} finally {
674 		// but make sure cleanup is *always* called
675 		this._composeView.cleanup();
676 	}
677 };
678 
679 ZmCalItemComposeController.prototype._closeView =
680 function() {
681 	this._app.popView(true,this._currentViewId);
682     this._composeView.cleanup();
683 };
684 
685 ZmCalItemComposeController.prototype._textModeOkCallback =
686 function(ev) {
687 	this._textModeOkCancel.popdown();
688 	this._composeView.setComposeMode(Dwt.TEXT);
689 };
690 
691 ZmCalItemComposeController.prototype._textModeCancelCallback =
692 function(ev) {
693 	this._textModeOkCancel.popdown();
694 	// reset the radio button for the format button menu
695 	var formatBtn = this._toolbar.getButton(ZmOperation.COMPOSE_OPTIONS);
696 	if (formatBtn) {
697 		formatBtn.getMenu().checkItem(ZmHtmlEditor.VALUE, Dwt.HTML, true);
698 	}
699 	this._composeView.reEnableDesignMode();
700 };
701