1 /* 2 * ***** BEGIN LICENSE BLOCK ***** 3 * Zimbra Collaboration Suite Web Client 4 * Copyright (C) 2004, 2005, 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) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Synacor, Inc. All Rights Reserved. 21 * ***** END LICENSE BLOCK ***** 22 */ 23 24 /** 25 * Creates the calendar view controller. 26 * @class 27 * The strategy for the calendar is to leverage the list view stuff by creating a single 28 * view (i.e. {@link ZmCalViewMgr}) and have it manage the schedule views (e.g. week, month) and 29 * a single calendar view (the calendar widget to the right). Each of the schedule views 30 * will be a list view that are managed by the {@link ZmCalViewMgr}. 31 * <p> 32 * To do this we have to trick the {@link ZmListController}. Specifically we have only one toolbar and 33 * directly manipulate this._toolbar elements to point to a single instance of the toolbar. We also 34 * directly override the {@link ZmListControl.initializeToolBar} method. 35 * 36 * @param {DwtComposite} container the containing element 37 * @param {ZmCalendarApp} calApp the handle to the calendar application 38 * @param {string} sessionId the session id 39 * @param {ZmSearchResultsController} searchResultsController containing controller 40 * 41 * @extends ZmListController 42 */ 43 ZmCalViewController = function(container, calApp, sessionId, searchResultsController) { 44 45 ZmListController.apply(this, arguments); 46 47 var apptListener = this._handleApptRespondAction.bind(this); 48 var apptEditListener = this._handleApptEditRespondAction.bind(this); 49 50 // get view based on op 51 ZmCalViewController.OP_TO_VIEW = {}; 52 ZmCalViewController.OP_TO_VIEW[ZmOperation.DAY_VIEW] = ZmId.VIEW_CAL_DAY; 53 ZmCalViewController.OP_TO_VIEW[ZmOperation.WEEK_VIEW] = ZmId.VIEW_CAL_WEEK; 54 ZmCalViewController.OP_TO_VIEW[ZmOperation.WORK_WEEK_VIEW] = ZmId.VIEW_CAL_WORK_WEEK; 55 ZmCalViewController.OP_TO_VIEW[ZmOperation.MONTH_VIEW] = ZmId.VIEW_CAL_MONTH; 56 ZmCalViewController.OP_TO_VIEW[ZmOperation.CAL_LIST_VIEW] = ZmId.VIEW_CAL_LIST; 57 ZmCalViewController.OP_TO_VIEW[ZmOperation.FB_VIEW] = ZmId.VIEW_CAL_FB; 58 59 // get op based on view 60 ZmCalViewController.VIEW_TO_OP = {}; 61 for (var op in ZmCalViewController.OP_TO_VIEW) { 62 ZmCalViewController.VIEW_TO_OP[ZmCalViewController.OP_TO_VIEW[op]] = op; 63 } 64 65 this._listeners[ZmOperation.REPLY_ACCEPT] = apptListener; 66 this._listeners[ZmOperation.REPLY_DECLINE] = apptListener; 67 this._listeners[ZmOperation.REPLY_TENTATIVE] = apptListener; 68 this._listeners[ZmOperation.EDIT_REPLY_ACCEPT] = apptEditListener; 69 this._listeners[ZmOperation.EDIT_REPLY_DECLINE] = apptEditListener; 70 this._listeners[ZmOperation.EDIT_REPLY_TENTATIVE] = apptEditListener; 71 72 this._listeners[ZmOperation.SHOW_ORIG] = this._showOrigListener.bind(this); 73 this._listeners[ZmOperation.VIEW_APPOINTMENT] = this._handleMenuViewAction.bind(this); 74 this._listeners[ZmOperation.OPEN_APPT_INSTANCE] = this._handleMenuViewAction.bind(this); 75 this._listeners[ZmOperation.OPEN_APPT_SERIES] = this._handleMenuViewAction.bind(this); 76 this._listeners[ZmOperation.TODAY] = this._todayButtonListener.bind(this); 77 this._listeners[ZmOperation.NEW_APPT] = this._newApptAction.bind(this); 78 this._listeners[ZmOperation.NEW_ALLDAY_APPT] = this._newAllDayApptAction.bind(this); 79 this._listeners[ZmOperation.SEARCH_MAIL] = this._searchMailAction.bind(this); 80 this._listeners[ZmOperation.MOVE] = this._apptMoveListener.bind(this); 81 this._listeners[ZmOperation.DELETE_INSTANCE] = this._deleteListener.bind(this); 82 this._listeners[ZmOperation.DELETE_SERIES] = this._deleteListener.bind(this); 83 this._listeners[ZmOperation.FORWARD_APPT] = this._forwardListener.bind(this); 84 this._listeners[ZmOperation.PROPOSE_NEW_TIME] = this._proposeTimeListener.bind(this); 85 this._listeners[ZmOperation.REINVITE_ATTENDEES] = this._reinviteAttendeesListener.bind(this); 86 this._listeners[ZmOperation.FORWARD_APPT_INSTANCE] = this._forwardListener.bind(this); 87 this._listeners[ZmOperation.FORWARD_APPT_SERIES] = this._forwardListener.bind(this); 88 this._listeners[ZmOperation.REPLY] = this._replyListener.bind(this); 89 this._listeners[ZmOperation.REPLY_ALL] = this._replyAllListener.bind(this); 90 this._listeners[ZmOperation.DUPLICATE_APPT] = this._duplicateApptListener.bind(this); 91 this._listeners[ZmOperation.PRINT_CALENDAR] = this._printCalendarListener.bind(this); 92 93 this._treeSelectionListener = this._calTreeSelectionListener.bind(this); 94 this._maintTimedAction = new AjxTimedAction(this, this._maintenanceAction); 95 this._pendingWork = ZmCalViewController.MAINT_NONE; 96 this.apptCache = new ZmApptCache(this); 97 this._currentViewIdOverride = ZmId.VIEW_CAL; // public view ID 98 99 ZmCalViewController.OPS = [ 100 ZmOperation.DAY_VIEW, ZmOperation.WORK_WEEK_VIEW, ZmOperation.WEEK_VIEW, 101 ZmOperation.MONTH_VIEW, ZmOperation.CAL_LIST_VIEW 102 ]; 103 if (appCtxt.get(ZmSetting.FREE_BUSY_VIEW_ENABLED)) { 104 ZmCalViewController.OPS.push(ZmOperation.FB_VIEW); 105 } 106 var viewOpsListener = new AjxListener(this, this._viewActionMenuItemListener); 107 for (var i = 0; i < ZmCalViewController.OPS.length; i++) { 108 var op = ZmCalViewController.OPS[i]; 109 this._listeners[op] = viewOpsListener; 110 } 111 112 this._errorCallback = this._handleError.bind(this); 113 114 // needed by ZmCalListView: 115 if (this.supportsDnD()) { 116 this._dragSrc = new DwtDragSource(Dwt.DND_DROP_MOVE); 117 this._dragSrc.addDragListener(this._dragListener.bind(this)); 118 } 119 120 this._clearCacheFolderMap = {}; 121 this._apptSessionId = {}; 122 }; 123 124 ZmCalViewController.prototype = new ZmListController; 125 ZmCalViewController.prototype.constructor = ZmCalViewController; 126 127 ZmCalViewController.prototype.isZmCalViewController = true; 128 ZmCalViewController.prototype.toString = function() { return "ZmCalViewController"; }; 129 130 ZmCalViewController.DEFAULT_APPOINTMENT_DURATION = 30*60*1000; 131 132 // maintenance needed on views and/or minical 133 ZmCalViewController.MAINT_NONE = 0x0; // no work to do 134 ZmCalViewController.MAINT_MINICAL = 0x1; // minical needs refresh 135 ZmCalViewController.MAINT_VIEW = 0x2; // view needs refresh 136 ZmCalViewController.MAINT_REMINDER = 0x4; // reminders need refresh 137 138 // get view based on op 139 ZmCalViewController.ACTION_CODE_TO_VIEW = {}; 140 ZmCalViewController.ACTION_CODE_TO_VIEW[ZmKeyMap.CAL_DAY_VIEW] = ZmId.VIEW_CAL_DAY; 141 ZmCalViewController.ACTION_CODE_TO_VIEW[ZmKeyMap.CAL_WEEK_VIEW] = ZmId.VIEW_CAL_WEEK; 142 ZmCalViewController.ACTION_CODE_TO_VIEW[ZmKeyMap.CAL_WORK_WEEK_VIEW] = ZmId.VIEW_CAL_WORK_WEEK; 143 ZmCalViewController.ACTION_CODE_TO_VIEW[ZmKeyMap.CAL_MONTH_VIEW] = ZmId.VIEW_CAL_MONTH; 144 ZmCalViewController.ACTION_CODE_TO_VIEW[ZmKeyMap.CAL_LIST_VIEW] = ZmId.VIEW_CAL_LIST; 145 ZmCalViewController.ACTION_CODE_TO_VIEW[ZmKeyMap.CAL_FB_VIEW] = ZmId.VIEW_CAL_FB; 146 147 ZmCalViewController.CHECKED_STATE_KEY = "CalendarsChecked"; 148 149 // Zimlet hack 150 ZmCalViewController.prototype.postInitListeners = 151 function () { 152 if (ZmZimlet.listeners && ZmZimlet.listeners["ZmCalViewController"]) { 153 for(var ix in ZmZimlet.listeners["ZmCalViewController"]) { 154 if(ZmZimlet.listeners["ZmCalViewController"][ix] instanceof AjxListener) { 155 this._listeners[ix] = ZmZimlet.listeners["ZmCalViewController"][ix]; 156 } else { 157 this._listeners[ix] = new AjxListener(this, ZmZimlet.listeners["ZmCalViewController"][ix]); 158 } 159 } 160 } 161 }; 162 163 ZmCalViewController.getDefaultViewType = 164 function() { 165 var setting = appCtxt.get(ZmSetting.CALENDAR_INITIAL_VIEW); 166 //This if loop has to be removed once bug 68708 is fixed 167 if(setting === "schedule"){ 168 setting = ZmSetting.CAL_DAY;//Assigning day view as default view instead of schedule view as it is removed 169 } 170 return ZmCalendarApp.VIEW_FOR_SETTING[setting] || ZmId.VIEW_CAL_WORK_WEEK; 171 }; 172 ZmCalViewController.prototype.getDefaultViewType = ZmCalViewController.getDefaultViewType; 173 174 /** 175 * Gets the first day of week setting. 176 * 177 * @return {int} the first day of week index 178 */ 179 ZmCalViewController.prototype.firstDayOfWeek = 180 function() { 181 return appCtxt.get(ZmSetting.CAL_FIRST_DAY_OF_WEEK) || 0; 182 }; 183 184 ZmCalViewController.prototype.setCurrentViewId = 185 function(viewId) { 186 if (viewId != ZmId.VIEW_CAL) { 187 // Only store a real view id; VIEW_CAL is a placeholder used to identity the 188 // constellation of calendar views to the AppViewMgr. Cal views are handled 189 // by their own ZmCalViewMgr. 190 ZmController.prototype.setCurrentViewId.call(this, viewId); 191 } 192 }; 193 194 ZmCalViewController.prototype.show = 195 function(viewId, startDate, skipMaintenance) { 196 AjxDispatcher.require(["MailCore", "CalendarCore", "Calendar"]); 197 DBG.println(AjxDebug.DBG1, "ZmCalViewController.show, viewId = " + viewId); 198 if (!viewId || viewId == ZmId.VIEW_CAL) { 199 viewId = this.getCurrentViewType() || this.getDefaultViewType(); 200 } 201 202 if (!this._viewMgr) { 203 this.createViewMgr(startDate); 204 this._setup(viewId); // TODO: Needed? Wouldn't this be handled by next conditional? 205 } 206 207 if (!this._viewMgr.getView(viewId)) { 208 this._setup(viewId); 209 } 210 211 var previousView = this._viewMgr.getCurrentView(); 212 var hadFocus = previousView && previousView.hasFocus(); 213 214 this._viewMgr.setView(viewId); 215 DBG.timePt("setup and set view"); 216 217 var elements = this.getViewElements(ZmId.VIEW_CAL, this._viewMgr); 218 219 // we need to mark the current view _before_ actually setting the view, 220 // otherwise we won't get the tab group added if the view already exists 221 this._currentViewId = this._currentViewType = this._viewMgr.getCurrentViewName(); 222 223 this._setView({ view: ZmId.VIEW_CAL, 224 viewType: this._currentViewType, 225 elements: elements, 226 isAppView: true}); 227 228 this.setCurrentListView(null); 229 var currentView = this._listView[this._currentViewId] = this._viewMgr.getCurrentView(); 230 this._resetToolbarOperations(viewId); 231 232 233 DBG.timePt("switching selection mode and tooltips"); 234 235 switch(viewId) { 236 case ZmId.VIEW_CAL_DAY: 237 this._miniCalendar.setSelectionMode(DwtCalendar.DAY); 238 this._navToolBar[ZmId.VIEW_CAL].setToolTip(ZmOperation.PAGE_BACK, ZmMsg.previousDay); 239 this._navToolBar[ZmId.VIEW_CAL].setToolTip(ZmOperation.PAGE_FORWARD, ZmMsg.nextDay); 240 break; 241 case ZmId.VIEW_CAL_WORK_WEEK: 242 this._miniCalendar.setSelectionMode(DwtCalendar.WORK_WEEK); 243 this._navToolBar[ZmId.VIEW_CAL].setToolTip(ZmOperation.PAGE_BACK, ZmMsg.previousWorkWeek); 244 this._navToolBar[ZmId.VIEW_CAL].setToolTip(ZmOperation.PAGE_FORWARD, ZmMsg.nextWorkWeek); 245 break; 246 case ZmId.VIEW_CAL_WEEK: 247 this._miniCalendar.setSelectionMode(DwtCalendar.WEEK); 248 this._navToolBar[ZmId.VIEW_CAL].setToolTip(ZmOperation.PAGE_BACK, ZmMsg.previousWeek); 249 this._navToolBar[ZmId.VIEW_CAL].setToolTip(ZmOperation.PAGE_FORWARD, ZmMsg.nextWeek); 250 break; 251 case ZmId.VIEW_CAL_MONTH: 252 // use day until month does something 253 this._miniCalendar.setSelectionMode(DwtCalendar.DAY); 254 this._navToolBar[ZmId.VIEW_CAL].setToolTip(ZmOperation.PAGE_BACK, ZmMsg.previousMonth); 255 this._navToolBar[ZmId.VIEW_CAL].setToolTip(ZmOperation.PAGE_FORWARD, ZmMsg.nextMonth); 256 break; 257 case ZmId.VIEW_CAL_LIST: 258 this._miniCalendar.setSelectionMode(DwtCalendar.DAY); 259 break; 260 } 261 if (viewId == ZmId.VIEW_CAL_LIST) { 262 this._navToolBar[ZmId.VIEW_CAL].setVisible(false); 263 } else { 264 if(viewId!=ZmId.VIEW_CAL_MONTH){this._viewMgr.getView(viewId).initializeTimeScroll();} 265 this._navToolBar[ZmId.VIEW_CAL].setVisible(true); 266 var navText = viewId == ZmId.VIEW_CAL_MONTH 267 ? currentView.getShortCalTitle() 268 : currentView.getCalTitle(); 269 this._navToolBar[ZmId.VIEW_CAL].setText(navText); 270 DBG.println(AjxDebug.DBG1, "ZmCalViewController.show, skipMaintenance = " + skipMaintenance); 271 if (!skipMaintenance) { 272 var work = ZmCalViewController.MAINT_VIEW; 273 if (window.inlineCalSearchResponse) { 274 this.processInlineCalSearch(); 275 window.inlineCalSearchResponse = null; 276 work = ZmCalViewController.MAINT_MINICAL|ZmCalViewController.MAINT_VIEW|ZmCalViewController.MAINT_REMINDER; 277 } 278 this._scheduleMaintenance(work); 279 } 280 if (!this.isSearchResults) { 281 this.updateTimeIndicator(viewId); 282 } 283 DBG.timePt("scheduling maintenance"); 284 } 285 286 if (hadFocus) { 287 currentView.focus(); 288 } 289 290 // do this last 291 if (!this._calTreeController) { 292 this._calTreeController = appCtxt.getOverviewController().getTreeController(ZmOrganizer.CALENDAR); 293 if (this._calTreeController) { 294 if (appCtxt.multiAccounts) { 295 var overviews = this._app.getOverviewContainer().getOverviews(); 296 for (var i in overviews) { 297 this._calTreeController.addSelectionListener(i, this._treeSelectionListener); 298 } 299 } else { 300 this._calTreeController.addSelectionListener(this._app.getOverviewId(), this._treeSelectionListener); 301 } 302 var calTree = appCtxt.getFolderTree(); 303 if (calTree) { 304 calTree.addChangeListener(new AjxListener(this, this._calTreeChangeListener)); 305 } 306 } 307 DBG.timePt("getting tree controller", true); 308 } 309 }; 310 311 /** 312 * Updates the time indicator strip. 313 * 314 */ 315 ZmCalViewController.prototype.updateTimeIndicator = 316 function(viewId) { 317 viewId = viewId || this.getCurrentViewType(); 318 this._viewMgr.getView(viewId).startIndicatorTimer(this.isSearchResults); 319 }; 320 321 /** 322 * Gets the calendar tree controller. 323 * 324 * @return {ZmCalendarTreeController} the controller 325 */ 326 ZmCalViewController.prototype.getCalTreeController = 327 function() { 328 return this._calTreeController; 329 }; 330 331 ZmCalViewController.prototype.createViewMgr = 332 function(startDate) { 333 var newDate = startDate || (this._miniCalendar ? this._miniCalendar.getDate() : new Date()); 334 335 if (!this._miniCalendar) { 336 this._createMiniCalendar(newDate); 337 } 338 339 this._viewMgr = new ZmCalViewMgr(this._container, this, this._dropTgt); 340 this._viewMgr.setDate(newDate); 341 this._viewMgr.addTimeSelectionListener(new AjxListener(this, this._timeSelectionListener)); 342 this._viewMgr.addDateRangeListener(new AjxListener(this, this._dateRangeListener)); 343 this._viewMgr.addViewActionListener(new AjxListener(this, this._viewActionListener)); 344 DBG.timePt("created view manager"); 345 }; 346 347 /** 348 * Gets the checked calendars. 349 * 350 * @param {String} overviewId the overview id 351 * @return {Array} an array of {ZmCalendar} objects 352 */ 353 ZmCalViewController.prototype.getCheckedCalendars = 354 function(includeTrash) { 355 if (!this._checkedCalendars) { 356 this._updateCheckedCalendars(); 357 } 358 return includeTrash ? this._checkedCalendarsAll : this._checkedCalendars; 359 }; 360 361 ZmCalViewController.prototype.getMainAccountCheckedCalendarIds = 362 function() { 363 if (!this._checkedAccountCalendarIds) { 364 // Generate the checked calendar data 365 this._updateCheckedCalendars(); 366 } 367 // If there are any account calendar ids, the 0th should be the main account - copy its checked calendars 368 return (this._checkedAccountCalendarIds.length == 0) ? [] : this._checkedAccountCalendarIds[0].slice(0); 369 } 370 371 // Called whether starting on or offline 372 ZmCalViewController.prototype.getOfflineSearchCalendarIds = 373 function() { 374 if (!this._offlineSearchCalendarIds) { 375 376 var checkedCalendarHash = null; 377 if (appCtxt.isWebClientOffline()) { 378 checkedCalendarHash = this._retrieveCalendarCheckedState(); 379 } 380 381 // Generate the checked calendar data - this will also store the calendar entries, if we are starting up online 382 this._updateCheckedCalendars(checkedCalendarHash); 383 } 384 return (this._offlineSearchCalendarIds); 385 } 386 387 ZmCalViewController.prototype._storeCalendarCheckedState = 388 function() { 389 // if !offline, store which calendars are currently checked, in localStorage. This will be updated 390 // while running, and reloaded if the app starts in offline mode. 391 var calendarCheckedState = this._checkedCalendarIds.join(":"); 392 localStorage.setItem(ZmCalendarApp.CHECKED_STATE_KEY, calendarCheckedState); 393 } 394 395 396 ZmCalViewController.prototype._retrieveCalendarCheckedState = 397 function() { 398 // Starting up offline, retrieve the stored calendar checked state 399 var checkedStateText = localStorage.getItem(ZmCalendarApp.CHECKED_STATE_KEY); 400 var checkedCalendarFolderIds = checkedStateText.split(":"); 401 var checkedCalendarHash = {}; 402 for (var i = 0; i < checkedCalendarFolderIds.length; i++) { 403 checkedCalendarHash[checkedCalendarFolderIds[i]] = true; 404 } 405 return checkedCalendarHash; 406 } 407 408 409 /** 410 * Gets the unchecked calendar folder ids for a owner email 411 * 412 * @param {String} ownerEmailId email id of the owner account 413 * @return {Array} an array of folder ids 414 */ 415 ZmCalViewController.prototype.getUncheckedCalendarIdsByOwner = 416 function(ownerEmailId) { 417 var dataTree = appCtxt.getTree(ZmId.ORG_CALENDAR, appCtxt.getActiveAccount()), 418 calendars = dataTree.getByType(ZmId.ORG_CALENDAR), 419 len = calendars.length, 420 calIds = [], 421 calendar, 422 i; 423 424 for (i=0; i<len; i++) { 425 calendar = calendars[i]; 426 if(!calendar.isChecked && calendar.owner && calendar.owner == ownerEmailId) { 427 calIds.push(calendar.id); 428 } 429 } 430 return calIds; 431 }; 432 433 /** 434 * Gets the checked calendar folder ids. 435 * 436 * @param {Boolean} localOnly if <code>true</code>, include local calendars only 437 * @return {Array} an array of folder ids 438 */ 439 ZmCalViewController.prototype.getCheckedCalendarFolderIds = 440 function(localOnly, includeTrash) { 441 if (!this._checkedCalendarIds) { 442 this.getCheckedCalendars(includeTrash); 443 if (!this._checkedCalendarIds) { 444 return [ZmOrganizer.ID_CALENDAR]; 445 } 446 } 447 // TODO: Do we also need to handle includeTrash here? 448 return localOnly 449 ? this._checkedLocalCalendarIds 450 : this._checkedCalendarIds; 451 }; 452 453 /** 454 * Gets the checked calendar folder ids. 455 * 456 * @param {Boolean} localOnly if <code>true</code>, include local calendars only 457 * @return {Array} an array of folder ids 458 */ 459 ZmCalViewController.prototype.getOwnedCalendarIds = 460 function(email, includeTrash) { 461 var i, 462 cal, 463 calendars, 464 calIds = []; 465 if(!this._calTreeController) { 466 this._calTreeController = appCtxt.getOverviewController().getTreeController(ZmOrganizer.CALENDAR); 467 } 468 469 calendars = this._calTreeController.getOwnedCalendars(this._app.getOverviewId(), email); 470 for (i = 0; i < calendars.length; i++) { 471 cal = calendars[i]; 472 if (cal) { 473 if(!includeTrash && (cal.nId == ZmFolder.ID_TRASH)) { continue; } 474 calIds.push(appCtxt.multiAccounts ? cal.id : cal.nId); 475 } 476 } 477 478 return calIds; 479 }; 480 481 /** 482 * Gets the calendar folder ids used to create reminders. 483 * All local calendars and shared calendars with the reminder flag set, checked or unchecked. 484 * Does not include the trash folder. 485 * 486 * @return {Array} an array of folder ids 487 */ 488 ZmCalViewController.prototype.getReminderCalendarFolderIds = 489 function() { 490 if (!this._reminderCalendarIds) { 491 this._updateCheckedCalendars(); 492 if (!this._reminderCalendarIds) { 493 return [ZmOrganizer.ID_CALENDAR]; 494 } 495 } 496 return this._reminderCalendarIds; 497 }; 498 499 /** 500 * Gets the checked organizers. 501 * 502 * @return {Array} array of {ZmOrganizer} 503 */ 504 ZmCalViewController.prototype.getCheckedOrganizers = function(includeTrash, acct) { 505 var controller = appCtxt.getOverviewController(); 506 var overviewId = appCtxt.getApp(ZmApp.CALENDAR).getOverviewId(acct); 507 var treeId = ZmOrganizer.CALENDAR; 508 var treeView = controller.getTreeView(overviewId, treeId); 509 var organizers = treeView.getSelected(); 510 if (!includeTrash) { 511 for (var i = 0; i < organizers.length; i++) { 512 if (organizers[i].id == ZmOrganizer.ID_TRASH) { 513 organizers.splice(i, 1); 514 break; 515 } 516 } 517 } 518 return organizers; 519 }; 520 521 /** 522 * Gets the checked organizer IDs. 523 * 524 * @return {Array} array of strings 525 */ 526 ZmCalViewController.prototype.getCheckedOrganizerIds = function() { 527 return AjxUtil.map(this.getCheckedOrganizers(), ZmCalViewController.__map_id); 528 }; 529 ZmCalViewController.__map_id = function(item) { 530 return item.id; 531 }; 532 533 ZmCalViewController.prototype._updateCheckedCalendars = 534 function(checkedCalendarHash) { 535 var allCalendars = []; 536 if (this._calTreeController) { 537 if (appCtxt.multiAccounts) { 538 var overviews = this._app.getOverviewContainer().getOverviews(); 539 for (var i in overviews) { 540 allCalendars = allCalendars.concat(this._calTreeController.getCalendars(i)); 541 } 542 } else { 543 // bug fix #25512 - avoid race condition 544 if (!this._app._overviewPanelContent) { 545 this._app.setOverviewPanelContent(true); 546 } 547 allCalendars = this._calTreeController.getCalendars(this._app.getOverviewId()); 548 } 549 } else { 550 this._app._createDeferredFolders(ZmApp.CALENDAR); 551 var ac = window.parentAppCtxt || window.appCtxt; 552 var list = ac.accountList.visibleAccounts; 553 for (var i = 0; i < list.length; i++) { 554 var acct = list[i]; 555 if (!ac.get(ZmSetting.CALENDAR_ENABLED, null, acct)) { continue; } 556 557 var folderTree = ac.getFolderTree(acct); 558 var calendars = folderTree && folderTree.getByType(ZmOrganizer.CALENDAR); 559 if (calendars) { 560 for (var j = 0; j < calendars.length; j++) { 561 // bug: 43067: skip the default calendar for caldav based accounts 562 if (acct.isCalDavBased() && calendars[j].nId == ZmOrganizer.ID_CALENDAR) { 563 continue; 564 } 565 allCalendars.push(calendars[j]); 566 } 567 } 568 } 569 } 570 571 this._checkedCalendars = []; 572 this._checkedCalendarIds = []; 573 this._checkedLocalCalendarIds = []; 574 this._checkedAccountCalendarIds = []; 575 this._offlineSearchCalendarIds = []; 576 this._reminderCalendarIds = []; 577 var checkedAccountCalendarIds = {}; 578 var trashFolder = null; 579 for (var i = 0; i < allCalendars.length; i++) { 580 var cal = allCalendars[i]; 581 if (!cal.noSuchFolder) { 582 this._offlineSearchCalendarIds.push(cal.id); 583 } 584 if (!cal.noSuchFolder && (cal.id != ZmOrganizer.ID_TRASH) && 585 (cal.isRemote && !cal.isRemote())) { 586 this._reminderCalendarIds.push(cal.id); 587 } 588 var checked = cal.isChecked; 589 if (checkedCalendarHash) { 590 // LocalStorage values passed in upon offline startup, use those 591 checked = checkedCalendarHash[cal.id] ? true : false; 592 cal.isChecked = checked; 593 } 594 if (checked) { 595 if (cal.id == ZmOrganizer.ID_TRASH) { 596 trashFolder = cal; 597 } else { 598 this._checkedCalendars.push(cal); 599 } 600 if (!cal.noSuchFolder) { 601 this._checkedCalendarIds.push(cal.id); 602 if (cal.isRemote && !cal.isRemote()) { 603 this._checkedLocalCalendarIds.push(cal.id); 604 } 605 var acctId = (appCtxt.multiAccounts) ? cal.getAccount().id : appCtxt.accountList.mainAccount.id; 606 if (!checkedAccountCalendarIds[acctId]) { 607 checkedAccountCalendarIds[acctId] = []; 608 } 609 checkedAccountCalendarIds[acctId].push(cal.id); 610 } 611 } 612 } 613 614 this._checkedCalendarsAll = trashFolder ? this._checkedCalendars.concat(trashFolder) : this._checkedCalendars; 615 616 // convert hash to local array 617 for (var i in checkedAccountCalendarIds) { 618 this._checkedAccountCalendarIds.push(checkedAccountCalendarIds[i]); 619 } 620 621 if (!checkedCalendarHash) { 622 // Not the initial call, update the stored calendar info. Note this will be called when calendars are 623 // checked/unchecked by the user (online or offline), and when notifications modifying the calendars 624 // are received. 625 this._storeCalendarCheckedState(); 626 } 627 628 // return list of checked calendars 629 return this._checkedCalendars; 630 }; 631 632 ZmCalViewController.prototype._calTreeSelectionListener = 633 function(ev) { 634 if (ev.detail != DwtTree.ITEM_CHECKED) { return; } 635 636 // NOTE: This isn't called by the cal tree controller in all cases so 637 // NOTE: we need to make sure the checked calendar list is up-to-date. 638 this._updateCheckedCalendars(); 639 640 if (!this._calItemStatus) { 641 this._calItemStatus = {}; 642 } 643 644 if (ev.item) { 645 var organizer = ev.item.getData && ev.item.getData(Dwt.KEY_OBJECT); 646 if (organizer && organizer.nId == ZmOrganizer.ID_TRASH) { 647 var found = false; 648 var acct = organizer.getAccount(); 649 var organizers = this.getCheckedOrganizers(true, acct); 650 for (var i = 0; i < organizers.length; i++) { 651 var id = organizers[i].nId; 652 if (id == ZmOrganizer.ID_TRASH) { 653 found = true; 654 break; 655 } 656 } 657 this._viewMgr.setSubContentVisible(found); 658 return; 659 } 660 ev.items = [ ev.item ]; 661 } 662 if (ev.items && ev.items.length) { 663 for (var i = 0; i < ev.items.length; i++) { 664 var item = ev.items[i]; 665 this.__addCalItemStatus(item, item.getChecked()); 666 } 667 } 668 669 // update calendar state on time delay to avoid race condition 670 if (!this._updateCalItemStateActionId) { 671 this._updateCalItemStateActionId = AjxTimedAction.scheduleAction(new AjxTimedAction(this, this._updateCalItemState), 1200); 672 } 673 }; 674 675 ZmCalViewController.prototype.__addCalItemStatus = 676 function(item, checked) { 677 item.setChecked(checked); 678 var organizer = item.getData(Dwt.KEY_OBJECT); 679 if (organizer && organizer.type == ZmOrganizer.CALENDAR) { 680 AjxDebug.println(AjxDebug.CALENDAR, " ---------------- calendar " + organizer.name + " [" + organizer.id + "] is " + (checked ? "checked" : "unchecked")); 681 this._calItemStatus[organizer.id] = {item: organizer, checked: checked}; 682 } 683 684 // bug 6410 685 var items = item.getItems(); 686 for (var i = 0; i < items.length; i++) { 687 item = items[i]; 688 this.__addCalItemStatus(item, checked); 689 } 690 }; 691 692 ZmCalViewController.prototype._updateCalItemState = 693 function() { 694 if (!this._calItemStatus) { return; } 695 696 var accountName = appCtxt.isOffline && appCtxt.accountList.mainAccount.name; 697 var batchCmd = new ZmBatchCommand(null, accountName, true); 698 var itemCount = 0; 699 for (var i in this._calItemStatus) { 700 var info = this._calItemStatus[i]; 701 if (info.item) { 702 var calendar = info.item; 703 //If, Remote Calendars & not mount-points, dont send check/uncheck requests 704 if ((calendar.isRemote() && (!calendar.isMountpoint || !calendar.zid))) { 705 calendar.isChecked = info.checked; 706 calendar.checkedCallback(info.checked); 707 this._handleCheckedCalendarRefresh(calendar); 708 } else { 709 batchCmd.add(new AjxCallback(calendar, calendar.checkAction, [info.checked])); 710 itemCount++; 711 } 712 } 713 } 714 715 if (appCtxt.multiAccounts) { 716 this.apptCache.clearCache(); 717 } 718 719 if (itemCount > 0) { 720 if (appCtxt.isWebClientOffline()) { 721 // The offlineCallback gets bound inside batchCmd.run to this 722 var offlineCallback = this._handleOfflineCalendarCheck.bind(this, batchCmd); 723 batchCmd.run(null, null, offlineCallback); 724 } else { 725 this._calItemStatus = {}; 726 batchCmd.run(); 727 } 728 } 729 730 this._updateCalItemStateActionId = null; 731 }; 732 733 // Update the check change locally (a folder and its subfolders) and store the batch command into the request queue 734 ZmCalViewController.prototype._handleOfflineCalendarCheck = 735 function(batchCmd) { 736 737 // Json batchCommand created in _updaetCalItemState above. Must be Json to be stored in offline request queue 738 var jsonObj = batchCmd.getRequestBody(); 739 740 this.apptCache.clearCache(); 741 // Apply the changes locally 742 var calendarIds = []; 743 for (var i in this._calItemStatus) { 744 var info = this._calItemStatus[i]; 745 if (info.item) { 746 var calendar = info.item; 747 calendarIds.push(calendar.id); 748 // Remote non-mountpoint has aleady been processed for local display - ignore it 749 if (!(calendar.isRemote() && (!calendar.isMountpoint || !calendar.zid))) { 750 calendar.isChecked = info.checked; 751 calendar.checkedCallback(info.checked); 752 this._handleCheckedCalendarRefresh(calendar); 753 } 754 } 755 } 756 this._calItemStatus = {}; 757 758 // Store the request for playback 759 var jsonObjCopy = $.extend(true, {}, jsonObj); //Always clone the object. ?? Needed here ?? 760 // This should be the only BatchCommand queued for calendars for replay (for the FolderAction, checked). 761 jsonObjCopy.methodName = "BatchRequest"; 762 // Modify the id to thwart ZmOffline._handleResponseSendOfflineRequest, which sends a DELETE 763 // notification for the id - so a single calendar would get deleted in the UI 764 jsonObjCopy.id = "C" + calendarIds.join(":"); 765 var value = { 766 update: true, 767 methodName: jsonObjCopy.methodName, 768 id: jsonObjCopy.id, 769 value: jsonObjCopy 770 }; 771 772 // No callback - we apply the check changes, whether or not the request goes into the queue successfully 773 ZmOfflineDB.setItemInRequestQueue(value); 774 }; 775 776 777 ZmCalViewController.prototype._handleCheckedCalendarRefresh = 778 function(calendar){ 779 this._updateCheckedCalendars(); 780 this._refreshAction(true); 781 }; 782 783 ZmCalViewController.prototype._calTreeChangeListener = 784 function(ev) { 785 if (ev.event == ZmEvent.E_DELETE) { 786 this._updateCheckedCalendars(); 787 } 788 if (ev.event == ZmEvent.E_MODIFY) { 789 var details = ev.getDetails(), 790 fields = details ? details.fields : null; 791 792 if (fields && (fields[ZmOrganizer.F_COLOR] || fields[ZmOrganizer.F_RGB])) { 793 this._refreshAction(true); 794 } 795 } 796 }; 797 798 ZmCalViewController.prototype.getApptCache = 799 function(){ 800 return this.apptCache; 801 }; 802 803 /** 804 * Gets the calendar by folder id. 805 * 806 * @param {String} folderId the folder id 807 * @return {ZmCalendar} the calendar 808 */ 809 ZmCalViewController.prototype.getCalendar = 810 function(folderId) { 811 return appCtxt.getById(folderId); 812 }; 813 814 /** 815 * Gets the calendar for the specified account. 816 * 817 * @param {Hash} params Param hash 818 * @param {Boolean} params.includeLinks if <code>true</code>, include links 819 * @param {Boolean} params.onlyWritable if <code>true</code> only writable calendars 820 * @param {ZmAccount} params.account the account 821 * @return {Array} an array of {ZmCalendar} objects 822 */ 823 ZmCalViewController.prototype.getCalendars = 824 function(params) { 825 params = params || {}; 826 var account = params.account; 827 var includeLinks = params.includeLinks; 828 var onlyWritable = params.onlyWritable; 829 this._updateCheckedCalendars(); 830 var calendars = []; 831 var organizers = appCtxt.getFolderTree(account).getByType(ZmOrganizer.CALENDAR); 832 for (var i = 0; i < organizers.length; i++) { 833 var organizer = organizers[i]; 834 if ((organizer.zid && !includeLinks) || 835 (appCtxt.multiAccounts && 836 organizer.nId == ZmOrganizer.ID_CALENDAR && 837 organizer.getAccount().isCalDavBased())) 838 { 839 continue; 840 } 841 842 if (account && organizer.getAccount() != account) { continue; } 843 844 if (onlyWritable && organizer.isReadOnly()) continue; 845 846 calendars.push(organizer); 847 } 848 calendars.sort(ZmCalViewController.__BY_NAME); 849 return calendars; 850 }; 851 852 ZmCalViewController.__BY_NAME = 853 function(a, b) { 854 return a.name.localeCompare(b.name); 855 }; 856 857 // todo: change to currently "selected" calendar 858 ZmCalViewController.prototype.getDefaultCalendarFolderId = 859 function() { 860 return ZmOrganizer.ID_CALENDAR; 861 }; 862 863 /** 864 * Gets the calendar color. 865 * 866 * @param {String} folderId the folder id 867 * @return {String} the color 868 */ 869 ZmCalViewController.prototype.getCalendarColor = 870 function(folderId) { 871 if (!folderId) { return ZmOrganizer.DEFAULT_COLOR[ZmOrganizer.CALENDAR]; } 872 var cal = this.getCalendar(folderId); 873 return cal ? cal.color : ZmOrganizer.DEFAULT_COLOR[ZmOrganizer.CALENDAR]; 874 }; 875 876 ZmCalViewController.prototype._refreshButtonListener = 877 function(ev) { 878 //Return if a search is already in progress 879 if (this.searchInProgress) { 880 return; 881 } 882 this.setCurrentListView(null); 883 884 // bug fix #33830 - force sync for calendar refresh 885 if (appCtxt.isOffline) { 886 appCtxt.accountList.syncAll(); 887 } 888 889 var app = appCtxt.getApp(ZmApp.CALENDAR); 890 // reset possibly set user query 891 this._userQuery = null; 892 if (app === appCtxt.getCurrentApp()) { 893 var sc = appCtxt.getSearchController(); 894 sc.setSearchField(""); 895 sc.getSearchToolbar().blur(); 896 } 897 app.currentSearch = null; 898 app.currentQuery = null; 899 this._refreshMaintenance = true; 900 this.searchInProgress = false; 901 this._refreshAction(false); 902 903 var overview = appCtxt.getOverviewController().getOverview(app.getOverviewId()); 904 if (overview) { 905 overview.clearSelection(); 906 } 907 908 }; 909 910 // Move button has been pressed, show the dialog. 911 ZmCalViewController.prototype._apptMoveListener = 912 function(ev) { 913 var items = this.getSelection(); 914 var divvied = (items.length > 1) ? this._divvyItems(items) : null; 915 916 if (divvied && divvied.readonly.length > 0) { 917 var dlg = appCtxt.getMsgDialog(); 918 var list = []; 919 if (divvied.normal.length > 0) { 920 list = list.concat(divvied.normal); 921 } 922 if (divvied.recurring.length > 0) { 923 list = list.concat(this._dedupeSeries(divvied.recurring)); 924 } 925 var callback = (list.length > 0) 926 ? (new AjxCallback(this, this._moveListener, [ev, list])) : null; 927 var listener = new AjxListener(this, this._handleReadonlyOk, [callback, dlg]); 928 dlg.setButtonListener(DwtDialog.OK_BUTTON, listener); 929 dlg.setMessage(ZmMsg.moveReadonly); 930 dlg.popup(); 931 } 932 else { 933 this._moveListener(ev, this._dedupeSeries(items)); 934 } 935 }; 936 937 ZmCalViewController.prototype._isItemMovable = 938 function(item, isShiftKey, folder) { 939 return (!isShiftKey && !folder.isReadOnly() && !appCtxt.isWebClientOffline()); 940 }; 941 942 ZmCalViewController.prototype._moveCallback = 943 function(folder) { 944 if (this.isMovingBetwAccounts(this._pendingActionData, folder.id)) { 945 var dlg = appCtxt.getMsgDialog(); 946 dlg.setMessage(ZmMsg.orgChange, DwtMessageDialog.WARNING_STYLE); 947 dlg.popup(); 948 return false; 949 }else if (this.isMovingBetwAcctsAsAttendee(this._pendingActionData, folder.id)) { 950 var dlg = appCtxt.getMsgDialog(); 951 dlg.setMessage(ZmMsg.apptInviteOnly, DwtMessageDialog.WARNING_STYLE); 952 dlg.popup(); 953 } else { 954 this._doMove(this._pendingActionData, folder, null, false); 955 this._clearDialog(appCtxt.getChooseFolderDialog()); 956 this._pendingActionData = null; 957 } 958 }; 959 960 ZmCalViewController.prototype._changeOrgCallback = 961 function(dlg, folder) { 962 dlg.popdown(); 963 ZmListController.prototype._moveCallback.call(this, folder); 964 }; 965 966 ZmCalViewController.prototype._doTag = 967 function(items, tag, doTag) { 968 969 var list = this._getTaggableItems(items); 970 971 if (doTag) { 972 if (list.length > 0 && list.length == items.length) { 973 // there are items to tag, and all are taggable 974 ZmListController.prototype._doTag.call(this, list, tag, doTag); 975 } else { 976 var msg; 977 var dlg = appCtxt.getMsgDialog(); 978 if (list.length > 0 && list.length < items.length) { 979 // there are taggable and nontaggable items 980 var listener = new AjxListener(this, this._handleDoTag, [dlg, list, tag, doTag]); 981 dlg.setButtonListener(DwtDialog.OK_BUTTON, listener); 982 msg = ZmMsg.tagReadonly; 983 } else if (list.length == 0) { 984 // no taggable items 985 msg = ZmMsg.nothingToTag; 986 } 987 dlg.setMessage(msg); 988 dlg.popup(); 989 } 990 } else if (list.length > 0) { 991 ZmListController.prototype._doTag.call(this, list, tag, doTag); 992 } 993 }; 994 995 ZmCalViewController.prototype._doRemoveAllTags = 996 function(items) { 997 var list = this._getTaggableItems(items); 998 ZmListController.prototype._doRemoveAllTags.call(this, list); 999 }; 1000 1001 ZmCalViewController.prototype._handleDoTag = 1002 function(dlg, list, tag, doTag) { 1003 dlg.popdown(); 1004 ZmListController.prototype._doTag.call(this, list, tag, doTag); 1005 }; 1006 1007 ZmCalViewController.prototype._getTaggableItems = 1008 function(items) { 1009 var divvied = (items.length > 1) ? this._divvyItems(items) : null; 1010 1011 if (divvied && (divvied.readonly.length > 0 || divvied.shared.length > 0)) { 1012 // get a list of items that are "taggable" 1013 items = []; 1014 for (var i in divvied) { 1015 // we process read only appts b/c it can also mean any appt where 1016 // i'm not the organizer but still resides in my local folder. 1017 if (i == "shared") { continue; } 1018 1019 var list = divvied[i]; 1020 for (var j = 0; j < list.length; j++) { 1021 var appt = list[j]; 1022 var calendar = appt.getFolder(); 1023 if (calendar && !calendar.isRemote()) { 1024 items.push(appt); 1025 } 1026 } 1027 } 1028 } 1029 1030 return items; 1031 }; 1032 1033 ZmCalViewController.prototype._getToolBarOps = 1034 function() { 1035 var toolbarOptions = [ 1036 ZmOperation.DELETE, ZmOperation.SEP, ZmOperation.MOVE_MENU, 1037 ZmOperation.TAG_MENU, 1038 ZmOperation.SEP, 1039 ZmOperation.PRINT_CALENDAR, 1040 ZmOperation.SEP, 1041 ZmOperation.TODAY, 1042 ZmOperation.FILLER, 1043 ZmOperation.DAY_VIEW, 1044 ZmOperation.WORK_WEEK_VIEW, 1045 ZmOperation.WEEK_VIEW, 1046 ZmOperation.MONTH_VIEW, 1047 ZmOperation.CAL_LIST_VIEW 1048 ]; 1049 if( appCtxt.get(ZmSetting.FREE_BUSY_VIEW_ENABLED) ){ 1050 toolbarOptions.push(ZmOperation.FB_VIEW); 1051 } 1052 return toolbarOptions; 1053 }; 1054 1055 /* This method is called from ZmListController._setup. We control when this method is called in our 1056 * show method. We ensure it is only called once i.e the first time show is called 1057 */ 1058 ZmCalViewController.prototype._initializeToolBar = 1059 function(viewId) { 1060 if (this._toolbar[ZmId.VIEW_CAL]) { return; } 1061 1062 ZmListController.prototype._initializeToolBar.call(this, ZmId.VIEW_CAL_DAY); 1063 var toolbar = this._toolbar[ZmId.VIEW_CAL_DAY]; 1064 1065 // Set the other view toolbar entries to point to the Day view entry. Hack 1066 // to fool the ZmListController into thinking there are multiple toolbars 1067 this._toolbar[ZmId.VIEW_CAL_FB] = this._toolbar[ZmId.VIEW_CAL_WEEK] = 1068 this._toolbar[ZmId.VIEW_CAL_WORK_WEEK] = this._toolbar[ZmId.VIEW_CAL_MONTH] = 1069 this._toolbar[ZmId.VIEW_CAL_LIST] = this._toolbar[ZmId.VIEW_CAL_DAY]; 1070 1071 this._toolbar[ZmId.VIEW_CAL] = toolbar; 1072 1073 // Setup the toolbar stuff 1074 toolbar.enable([ZmOperation.PAGE_BACK, ZmOperation.PAGE_FORWARD], true); 1075 toolbar.enable([ZmOperation.WEEK_VIEW, ZmOperation.MONTH_VIEW, ZmOperation.DAY_VIEW], true); 1076 1077 // We have style sheets in place to position the navigation toolbar at the 1078 // center of the main toolbar. The filler is usually at that location, so 1079 // to ensure that the semantic order matches the visual order as close as 1080 // possible, we position the navigation toolbar after it. 1081 var pos = AjxUtil.indexOf(this._getToolBarOps(), ZmOperation.FILLER) + 1; 1082 1083 var tb = new ZmNavToolBar({ 1084 parent: toolbar, 1085 index: pos, 1086 className: "ZmNavToolbar ZmCalendarNavToolbar", 1087 context: ZmId.VIEW_CAL, 1088 posStyle: Dwt.ABSOLUTE_STYLE 1089 }); 1090 this._setNavToolBar(tb, ZmId.VIEW_CAL); 1091 1092 var printButton = toolbar.getButton(ZmOperation.PRINT_CALENDAR); 1093 if (printButton) { 1094 printButton.setToolTipContent(ZmMsg.printCalendar); 1095 } 1096 1097 toolbar.getButton(ZmOperation.DELETE).setToolTipContent(ZmMsg.hardDeleteTooltip); 1098 1099 appCtxt.notifyZimlets("initializeToolbar", [this._app, toolbar, this, viewId], {waitUntilLoaded:true}); 1100 }; 1101 1102 ZmCalViewController.prototype._initializeTabGroup = function(viewId) { 1103 if (this._tabGroups[viewId]) { 1104 return; 1105 } 1106 1107 ZmListController.prototype._initializeTabGroup.apply(this, arguments); 1108 1109 // this is kind of horrible, but since list views don't normally have 1110 // children, we can't do this the right way by having a tab group in 1111 // ZmCalListView 1112 if (viewId === ZmId.VIEW_CAL_LIST) { 1113 var view = this._view[viewId]; 1114 var topTabGroup = view._getSearchBarTabGroup(); 1115 1116 this._tabGroups[viewId].addMemberBefore(topTabGroup, view); 1117 } 1118 } 1119 1120 ZmCalViewController.prototype.runRefresh = 1121 function() { 1122 this._refreshButtonListener(); 1123 }; 1124 1125 ZmCalViewController.prototype._setView = 1126 function(params) { 1127 if (!this.isSearchResults) { 1128 ZmListController.prototype._setView.apply(this, arguments); 1129 } 1130 }; 1131 1132 ZmCalViewController.prototype._setViewContents = 1133 function(viewId) { 1134 // Ignore since this will always be ZmId.VIEW_CAL as we are fooling 1135 // ZmListController (see our show method) 1136 }; 1137 1138 ZmCalViewController.prototype._getTagMenuMsg = 1139 function(num) { 1140 return AjxMessageFormat.format(ZmMsg.tagAppts, num); 1141 }; 1142 1143 ZmCalViewController.prototype._createNewView = 1144 function(viewId) { 1145 return this._viewMgr.createView(viewId); 1146 }; 1147 1148 // Switch to selected view. 1149 ZmCalViewController.prototype._viewActionMenuItemListener = 1150 function(ev) { 1151 Dwt.setLoadingTime("ZmCalItemView"); 1152 if (appCtxt.multiAccounts) { 1153 this.apptCache.clearCache(); 1154 } 1155 var id = ev.item.getData(ZmOperation.KEY_ID); 1156 var viewType = ZmCalViewController.OP_TO_VIEW[id]; 1157 //Check if the view is current calendar view 1158 if (this.getCurrentViewType() === viewType) { 1159 return; 1160 } 1161 this.show(viewType); 1162 }; 1163 1164 // Switch to selected view. 1165 ZmCalViewController.prototype._viewMenuItemListener = 1166 function(ev) { 1167 1168 }; 1169 1170 /** 1171 * Creates the mini-calendar widget that sits below the overview. 1172 * 1173 * @param {Date} date the date to highlight (defaults to today) 1174 * @private 1175 */ 1176 ZmCalViewController.prototype._createMiniCalendar = 1177 function(date) { 1178 var calMgr = appCtxt.getCalManager(); 1179 if (calMgr._miniCalendar == null) { 1180 calMgr._createMiniCalendar(date); 1181 this._miniCalendar = calMgr.getMiniCalendar(); 1182 } else { 1183 this._miniCalendar = calMgr.getMiniCalendar(); 1184 if (date != null) { 1185 this._miniCalendar.setDate(date, true); 1186 } 1187 } 1188 this._minicalMenu = calMgr._miniCalMenu; 1189 this._miniCalDropTarget = calMgr._miniCalDropTarget; 1190 }; 1191 1192 ZmCalViewController.prototype.recreateMiniCalendar = 1193 function() { 1194 var calMgr = appCtxt.getCalManager(); 1195 if (calMgr._miniCalendar != null) { 1196 var mc = calMgr.getMiniCalendar(); 1197 var el = mc.getHtmlElement(); 1198 var date = mc.getDate(); 1199 if(el) { 1200 el.parentNode.removeChild(el); 1201 } 1202 this._miniCalendar = null; 1203 calMgr._miniCalendar = null; 1204 calMgr._createMiniCalendar(date); 1205 this._miniCalendar = calMgr.getMiniCalendar(); 1206 this._createMiniCalendar(); 1207 } 1208 }; 1209 1210 ZmCalViewController.prototype._miniCalDropTargetListener = 1211 function(ev) { 1212 1213 if (appCtxt.isWebClientOffline()) return; 1214 1215 var data = ((ev.srcData.data instanceof Array) && ev.srcData.data.length == 1) 1216 ? ev.srcData.data[0] : ev.srcData.data; 1217 1218 // use shiftKey to create new Tasks if enabled. NOTE: does not support Contacts yet 1219 var shiftKey = appCtxt.get(ZmSetting.TASKS_ENABLED) && ev.uiEvent.shiftKey; 1220 1221 if (ev.action == DwtDropEvent.DRAG_ENTER) { 1222 // Hack: in some instances ZmContact is reported as being an Array of 1223 // length 1 (as well as a ZmContact) under FF1.5 1224 if (data instanceof Array && data.length > 1) { 1225 var foundValid = false; 1226 for (var i = 0; i < data.length; i++) { 1227 if (!shiftKey && (data[i] instanceof ZmContact)) { 1228 if (data[i].isGroup() && data[i].getGroupMembers().good.size() > 0) { 1229 foundValid = true; 1230 } else { 1231 var email = data[i].getEmail(); 1232 if (email && email != "") 1233 foundValid = true; 1234 } 1235 } else { 1236 // theres other stuff besides contacts in here.. bail 1237 ev.doIt = false; 1238 return; 1239 } 1240 } 1241 1242 // if not a single valid email was found in list of contacts, bail 1243 if (!foundValid) { 1244 ev.doIt = false; 1245 return; 1246 } 1247 } else { 1248 if (!this._miniCalDropTarget.isValidTarget(data)) { 1249 ev.doIt = false; 1250 return; 1251 } 1252 1253 // If dealing with a contact, make sure it has a valid email address 1254 if (!shiftKey && data.isZmContact) { 1255 if (data.isGroup() && !data.isDistributionList()) { 1256 ev.doIt = (data.getGroupMembers().good.size() > 0); 1257 } else { 1258 var email = data.getEmail(); 1259 ev.doIt = (email != null && email != ""); 1260 } 1261 } 1262 } 1263 } else if (ev.action == DwtDropEvent.DRAG_DROP) { 1264 var dropDate = this._miniCalendar.getDndDate(); 1265 1266 if (dropDate) { 1267 // bug fix #5088 - reset time to next available slot 1268 var now = new Date(); 1269 dropDate.setHours(now.getHours()); 1270 dropDate.setMinutes(now.getMinutes()); 1271 dropDate = AjxDateUtil.roundTimeMins(dropDate, 30); 1272 1273 if ((data instanceof ZmContact) || 1274 ((data instanceof Array) && data[0] instanceof ZmContact)) 1275 { 1276 this.newApptFromContact(data, dropDate); 1277 } 1278 else 1279 { 1280 if (shiftKey) { 1281 AjxDispatcher.require(["TasksCore", "Tasks"]); 1282 appCtxt.getApp(ZmApp.TASKS).newTaskFromMailItem(data, dropDate); 1283 } else { 1284 this.newApptFromMailItem(data, dropDate); 1285 } 1286 } 1287 } 1288 } 1289 }; 1290 1291 /** 1292 * This method will create a new appointment from a conversation/mail message. In the case 1293 * of a conversation, the appointment will be populated by the first message in the 1294 * conversation (or matched msg in the case of a search). This method is asynchronous and 1295 * will return immediately if the mail message must load in the background. 1296 * 1297 * @param {ZmConv|ZmMailMsg} mailItem the may either be a conversation of message 1298 * @param {Date} date the date/time for the appointment 1299 */ 1300 ZmCalViewController.prototype.newApptFromMailItem = 1301 function(mailItem, date) { 1302 var subject = mailItem.subject || ""; 1303 if (mailItem instanceof ZmConv) { 1304 mailItem = mailItem.getFirstHotMsg(); 1305 } 1306 mailItem.load({getHtml:false, markRead: true, forceLoad: true, noTruncate: true, 1307 callback:new AjxCallback(this, this._msgLoadedCallback, [mailItem, date, subject])}); 1308 }; 1309 1310 ZmCalViewController.prototype._msgLoadedCallback = 1311 function(mailItem, date, subject) { 1312 var newAppt = this._newApptObject(date, null, null, mailItem); 1313 newAppt.setFromMailMessage(mailItem, subject); 1314 1315 if (appCtxt.get(ZmSetting.GROUP_CALENDAR_ENABLED)) { 1316 var addAttendeeDlg = this._attAttendeeDlg = appCtxt.getYesNoMsgDialog(); 1317 addAttendeeDlg.reset(); 1318 addAttendeeDlg.setMessage(ZmMsg.addRecipientstoAppt, DwtMessageDialog.WARNING_STYLE, ZmMsg.addAttendees); 1319 addAttendeeDlg.registerCallback(DwtDialog.YES_BUTTON, this._addAttendeeYesCallback, this, [newAppt]); 1320 addAttendeeDlg.registerCallback(DwtDialog.NO_BUTTON, this._addAttendeeNoCallback, this, [newAppt]); 1321 addAttendeeDlg.popup(); 1322 } 1323 else { 1324 this.newAppointment(newAppt, ZmCalItem.MODE_NEW, true); 1325 } 1326 }; 1327 1328 ZmCalViewController.prototype._addAttendeeYesCallback = 1329 function(newAppt) { 1330 this._attAttendeeDlg.popdown(); 1331 this.newAppointment(newAppt, ZmCalItem.MODE_NEW, true); 1332 }; 1333 1334 ZmCalViewController.prototype._addAttendeeNoCallback = 1335 function(newAppt) { 1336 this._attAttendeeDlg.popdown(); 1337 newAppt.setAttendees(null,ZmCalBaseItem.PERSON); 1338 this.newAppointment(newAppt, ZmCalItem.MODE_NEW, true); 1339 }; 1340 1341 1342 /** 1343 * This method will create a new appointment from a contact. 1344 * 1345 * @param {ZmContact} contact the contact 1346 * @param {Date} date the date/time for the appointment 1347 */ 1348 ZmCalViewController.prototype.newApptFromContact = 1349 function(contact, date) { 1350 var emails = []; 1351 var list = AjxUtil.toArray(contact); 1352 for (var i = 0; i < list.length; i++) { 1353 var item = list[i]; 1354 if (item.isGroup() && !item.isDistributionList()) { 1355 var members = item.getGroupMembers().good.getArray(); 1356 for (var j = 0; j < members.length; j++) { 1357 var e = members[j].address; 1358 if (e && e !== "") { 1359 emails.push(e); 1360 } 1361 } 1362 } 1363 else { 1364 // grab the first valid email address for this contact 1365 var e = item.getEmail(); 1366 if (e && e !== "") { 1367 emails.push(e); 1368 } 1369 } 1370 } 1371 1372 if (emails.length > 0) { 1373 var newAppt = this._newApptObject(date); 1374 newAppt.setAttendees(emails, ZmCalBaseItem.PERSON); 1375 this.newAppointment(newAppt, ZmCalItem.MODE_NEW); 1376 } 1377 }; 1378 1379 /** 1380 * This method will create a new appointment from an email address. 1381 * 1382 * @param {String} emailAddr the email address 1383 * @param {Date} date the date/time for the appointment 1384 */ 1385 ZmCalViewController.prototype.newApptFromEmailAddr = 1386 function(emailAddr, date) { 1387 if (!emailAddr || emailAddr == "") {return; } 1388 1389 var newAppt = this._newApptObject(date); 1390 newAppt.setAttendees(emailAddr, ZmCalBaseItem.PERSON); 1391 this.newAppointment(newAppt, ZmCalItem.MODE_NEW); 1392 }; 1393 1394 ZmCalViewController.prototype.getMiniCalendar = 1395 function(delay) { 1396 if (!this._miniCalendar) { 1397 this._createMiniCalendar(null, delay); 1398 } 1399 return this._miniCalendar; 1400 }; 1401 1402 ZmCalViewController.prototype._todayButtonListener = 1403 function(ev) { 1404 this.setCurrentListView(null); 1405 this.setDate(new Date(), 0, false); 1406 }; 1407 1408 ZmCalViewController.prototype._newApptAction = 1409 function(ev) { 1410 var d = this._minicalMenu ? this._minicalMenu.__detail : null; 1411 1412 if (d != null) { 1413 delete this._minicalMenu.__detail; 1414 } else { 1415 d = this._viewMgr ? this._viewMgr.getDate() : null; 1416 } 1417 1418 // Bug 15686, eshum 1419 // Uses the selected timeslot if possible. 1420 var curr = this._viewVisible ? this._viewMgr.getDate() : new Date(); //new Date(); 1421 if (d == null) { 1422 d = curr; 1423 } else { 1424 // bug fix #4693 - set the current time since it will be init'd to midnite 1425 d.setHours(curr.getHours()); 1426 d.setMinutes(curr.getMinutes()); 1427 } 1428 //bug:44423, Action Menu needs to select appropriate Calendar 1429 var calendarId = null; 1430 if (this._viewActionMenu && this._viewActionMenu._calendarId) { 1431 calendarId = this._viewActionMenu._calendarId; 1432 this._viewActionMenu._calendarId = null; 1433 } 1434 1435 var loadCallback = new AjxCallback(this, this._handleLoadNewApptAction, [d, calendarId]); 1436 AjxDispatcher.require(["MailCore", "CalendarCore", "Calendar"], false, loadCallback, null, true); 1437 }; 1438 1439 ZmCalViewController.prototype._handleLoadNewApptAction = 1440 function(d, calendarId) { 1441 appCtxt.getAppViewMgr().popView(true, ZmId.VIEW_LOADING); // pop "Loading..." page 1442 this.newAppointmentHelper(d, null, calendarId); 1443 }; 1444 1445 ZmCalViewController.prototype._searchMailAction = 1446 function(ev) { 1447 var d = this._minicalMenu ? this._minicalMenu.__detail : null; 1448 if (d != null) { 1449 delete this._minicalMenu.__detail; 1450 appCtxt.getSearchController().dateSearch(d, ZmId.SEARCH_MAIL); 1451 } 1452 }; 1453 1454 ZmCalViewController.prototype._newAllDayApptAction = 1455 function(ev) { 1456 var d = this._minicalMenu ? this._minicalMenu.__detail : null; 1457 if (d != null) delete this._minicalMenu.__detail; 1458 else d = this._viewMgr ? this._viewMgr.getDate() : null; 1459 if (d == null) d = new Date(); 1460 1461 //bug:44423, Action Menu needs to select appropriate Calendar 1462 var calendarId = null; 1463 if (this._viewActionMenu && this._viewActionMenu._calendarId) { 1464 calendarId = this._viewActionMenu._calendarId; 1465 this._viewActionMenu._calendarId = null; 1466 } 1467 1468 var loadCallback = new AjxCallback(this, this._handleLoadNewAllDayApptAction, [d, calendarId]); 1469 AjxDispatcher.require(["MailCore", "CalendarCore", "Calendar"], false, loadCallback, null, true); 1470 }; 1471 1472 ZmCalViewController.prototype._handleLoadNewAllDayApptAction = 1473 function(d, calendarId) { 1474 appCtxt.getAppViewMgr().popView(true, ZmId.VIEW_LOADING); // pop "Loading..." page 1475 this.newAllDayAppointmentHelper(d, null, calendarId); 1476 }; 1477 1478 ZmCalViewController.prototype._postShowCallback = 1479 function() { 1480 ZmController.prototype._postShowCallback.call(this); 1481 this._viewVisible = true; 1482 if (this._viewMgr.needsRefresh()) { 1483 this._scheduleMaintenance(ZmCalViewController.MAINT_MINICAL|ZmCalViewController.MAINT_VIEW); 1484 } 1485 //this._app.setOverviewPanelContent(); 1486 }; 1487 1488 ZmCalViewController.prototype._postHideCallback = 1489 function() { 1490 if (appCtxt.multiAccounts) { 1491 var ovc = this._app.getOverviewContainer(); 1492 var overviews = ovc.getOverviews(); 1493 var overview; 1494 for (var ov in overviews) { 1495 ovc.getOverview(ov).zShow(false); 1496 } 1497 } else { 1498 overview = this._app.getOverview(); 1499 overview.zShow(false); 1500 } 1501 this._viewVisible = false; 1502 }; 1503 1504 ZmCalViewController.prototype.isCurrent = 1505 function() { 1506 var currentView = this._viewMgr && this._viewMgr.getCurrentViewName(); 1507 return (this._currentViewId === currentView); 1508 }; 1509 1510 ZmCalViewController.prototype._paginate = 1511 function(viewId, forward) { 1512 this.setCurrentListView(null); 1513 var view = this._listView[viewId]; 1514 var field = view.getRollField(); 1515 var d = new Date(this._viewMgr.getDate()); 1516 d = AjxDateUtil.roll(d, field, forward ? 1 : -1); 1517 this.setDate(d, 0, true); 1518 this._viewMgr.getView(viewId).checkIndicatorNeed(viewId,d); 1519 }; 1520 1521 /** 1522 * Sets the date. 1523 * 1524 * @param {Date} date the date 1525 * @param {int} duration the duration 1526 * @param {Boolean} roll if <code>true</code>, roll 1527 */ 1528 ZmCalViewController.prototype.setDate = 1529 function(date, duration, roll) { 1530 AjxDispatcher.require(["MailCore", "CalendarCore", "Calendar"]); 1531 // set mini-cal first so it will cache appts we might need 1532 if (this._miniCalendar.getDate() == null || 1533 this._miniCalendar.getDate().getTime() != date.getTime()) 1534 { 1535 this._miniCalendar.setDate(date, true, roll); 1536 } 1537 if (this._viewMgr != null) { 1538 this._viewMgr.setDate(date, duration, roll); 1539 var viewId = this._viewMgr.getCurrentViewName(); 1540 var currentView = this._viewMgr.getCurrentView(); 1541 var title = currentView.getCalTitle(); 1542 Dwt.setTitle([ZmMsg.zimbraTitle, ": ", title].join("")); 1543 if (!roll && 1544 this._currentViewType == ZmId.VIEW_CAL_WORK_WEEK && 1545 !currentView.workingHours[date.getDay()].isWorkingDay && 1546 (date.getDay() == 0 || date.getDay() == 6)) 1547 { 1548 this.show(ZmId.VIEW_CAL_WEEK); 1549 } 1550 if (ZmId.VIEW_CAL_MONTH == this._currentViewType) { 1551 title = this._viewMgr.getCurrentView().getShortCalTitle(); 1552 } 1553 if (ZmId.VIEW_CAL_FB == this._currentViewType && roll && appCtxt.get(ZmSetting.FREE_BUSY_VIEW_ENABLED)) { 1554 currentView._navDateChangeListener(date); 1555 } 1556 this._navToolBar[ZmId.VIEW_CAL].setText(title); 1557 } 1558 }; 1559 1560 ZmCalViewController.prototype._dateSelectionListener = 1561 function(ev) { 1562 this.setDate(ev.detail, 0, ev.force); 1563 }; 1564 1565 ZmCalViewController.prototype._miniCalSelectionListener = 1566 function(ev) { 1567 if (ev.item instanceof DwtCalendar) { 1568 var loadCallback = new AjxCallback(this, this._handleLoadMiniCalSelection, [ev]); 1569 AjxDispatcher.require(["MailCore", "CalendarCore", "Calendar"], false, loadCallback, null, true); 1570 } 1571 }; 1572 1573 ZmCalViewController.prototype._handleLoadMiniCalSelection = 1574 function(ev) { 1575 this.setDate(ev.detail, 0, ev.item.getForceRollOver()); 1576 if (!this._viewVisible) { 1577 this.show(); 1578 } 1579 }; 1580 1581 ZmCalViewController.prototype.newApptObject = 1582 function(startDate, duration, folderId, mailItem) { 1583 return this._newApptObject(startDate, duration, folderId, mailItem); 1584 }; 1585 1586 ZmCalViewController.prototype._newApptObject = 1587 function(startDate, duration, folderId, mailItem) { 1588 var newAppt = new ZmAppt(); 1589 newAppt.setStartDate(AjxDateUtil.roundTimeMins(startDate, 30)); 1590 newAppt.setEndDate(newAppt.getStartTime() + (duration ? duration : ZmCalViewController.DEFAULT_APPOINTMENT_DURATION)); 1591 newAppt.resetRepeatWeeklyDays(); 1592 newAppt.resetRepeatMonthlyDayList(); 1593 newAppt.resetRepeatYearlyMonthsList(startDate.getMonth()+1); 1594 newAppt.resetRepeatCustomDayOfWeek(); 1595 var defaultWarningTime = appCtxt.get(ZmSetting.CAL_REMINDER_WARNING_TIME); 1596 if (defaultWarningTime) { 1597 newAppt.setReminderMinutes(defaultWarningTime); 1598 } 1599 1600 if (folderId) { 1601 newAppt.setFolderId(folderId); 1602 } else { 1603 // get folderId from mail message if being created off of one 1604 if (appCtxt.multiAccounts && mailItem) { 1605 newAppt.setFolderId(mailItem.getAccount().getDefaultCalendar().id); 1606 } else { 1607 // bug: 27646 case where only one calendar is checked 1608 var checkedFolderIds = this.getCheckedCalendarFolderIds(); 1609 if (checkedFolderIds && checkedFolderIds.length == 1) { 1610 var calId = checkedFolderIds[0]; 1611 var cal = appCtxt.getById(calId); 1612 // don't use calendar if feed, or remote and don't have write perms 1613 if (cal) { 1614 var share = cal.getMainShare(); 1615 var skipCal = (cal.isFeed() || (cal.link && share && !share.isWrite())); 1616 if (cal && !skipCal) { 1617 newAppt.setFolderId(calId); 1618 } 1619 } 1620 } else if (appCtxt.multiAccounts) { 1621 // calendar app has no notion of "active" app, so always set to default calendar 1622 this.defaultAccount = appCtxt.isFamilyMbox ? this.mainAccount : this.visibleAccounts[1]; 1623 newAppt.setFolderId(calId); 1624 } 1625 } 1626 } 1627 newAppt.setPrivacy((appCtxt.get(ZmSetting.CAL_APPT_VISIBILITY) == ZmSetting.CAL_VISIBILITY_PRIV)?"PRI" :"PUB"); 1628 return newAppt; 1629 }; 1630 1631 ZmCalViewController.prototype._timeSelectionListener = 1632 function(ev) { 1633 if (!this._app.containsWritableFolder()) { 1634 return; 1635 } 1636 var view = this._viewMgr.getCurrentView(); 1637 if (view.getSelectedItems().size() > 0) { 1638 view.deselectAll(); 1639 this._resetOperations(this._toolbar[ZmId.VIEW_CAL_DAY], 0); 1640 } 1641 this.setDate(ev.detail, 0, ev.force); 1642 1643 // popup the edit dialog 1644 if (ev._isDblClick){ 1645 this._apptFromView = view; 1646 var appt = this._newApptObject(ev.detail); 1647 appt.setAllDayEvent(ev.isAllDay); 1648 if (ev.folderId) { 1649 appt.setFolderId(ev.folderId); 1650 } 1651 this._showQuickAddDialog(appt, ev.shiftKey); 1652 } 1653 }; 1654 1655 ZmCalViewController.prototype._printCalendarListener = 1656 function(ev) { 1657 var url, 1658 viewId = this._viewMgr.getCurrentViewName(), 1659 printDialog = this._printDialog, 1660 wHrs = ZmCalBaseView.parseWorkingHours(ZmCalBaseView.getWorkingHours()), 1661 curDate = this._viewMgr.getDate() || new Date(); 1662 1663 if(!printDialog) { 1664 printDialog = this.createPrintDialog(); 1665 } 1666 1667 var org = ZmApp.ORGANIZER[this._app._name] || ZmOrganizer.FOLDER; 1668 var params = { 1669 overviewId: appCtxt.getOverviewId(["ZmCalPrintDialog", this._app._name], null), 1670 treeIds: [org], 1671 treeStyle: DwtTree.CHECKEDITEM_STYLE, 1672 appName: this._app._name, 1673 currentViewId: viewId, 1674 workHours: wHrs[curDate.getDay()], 1675 currentDate: curDate, 1676 timeRange: this.getViewMgr().getView(viewId).getTimeRange() 1677 }; 1678 1679 printDialog.popup(params); 1680 1681 this._printDialog = printDialog; 1682 /* 1683 if (viewId == ZmId.VIEW_CAL_LIST) 1684 { 1685 var ids = []; 1686 var list = this.getSelection(); 1687 for (var i = 0; i < list.length; i++) { 1688 ids.push(list[i].invId); 1689 } 1690 url = ["/h/printappointments?id=", ids.join(','), "&tz=", AjxTimezone.getServerId(AjxTimezone.DEFAULT)]; 1691 if(appCtxt.isOffline) { 1692 if (ids.length == 1) { 1693 var appt = this.getSelection()[0]; 1694 url.push("&acct=", appt.getFolder().getAccount().name); 1695 } 1696 url.push("&zd=", "true"); 1697 } 1698 url = url.join(""); 1699 } else { 1700 var date = this._viewMgr 1701 ? this._viewMgr.getDate() 1702 : (new Date()); 1703 1704 var day = (date.getDate() < 10) 1705 ? ('0' + date.getDate()) 1706 : date.getDate(); 1707 1708 var month = date.getMonth() + 1; 1709 if (month < 10) { 1710 month = '0' + month; 1711 } 1712 1713 var view; 1714 switch (viewId) { 1715 case ZmId.VIEW_CAL_DAY: view = "day"; break; 1716 case ZmId.VIEW_CAL_WORK_WEEK: view = "workWeek"; break; 1717 case ZmId.VIEW_CAL_WEEK: view = "week"; break; 1718 case ZmId.VIEW_CAL_SCHEDULE: view = "schedule"; break; 1719 default: view = "month"; break; // default is month 1720 } 1721 1722 var folderIds = this.getCheckedCalendarFolderIds(); 1723 var l = folderIds.join(","); 1724 1725 url = [ 1726 "/h/printcalendar?view=", view, 1727 "&l=", l, 1728 "&date=", date.getFullYear(), month, day, 1729 "&tz=",AjxTimezone.getServerId(AjxTimezone.DEFAULT) 1730 ].join(""); 1731 } */ 1732 1733 //window.open(appContextPath+url, "_blank"); 1734 }; 1735 1736 ZmCalViewController.prototype.createPrintDialog = 1737 function() { 1738 var pd, 1739 params = {}, 1740 curDate = this._viewMgr.getDate() || new Date(); 1741 1742 //params.calendars = this.getCalTreeController().getOwnedCalendars(this._app.getOverviewId(), appCtxt.getActiveAccount().getEmail()); 1743 params.parent = this._shell; 1744 pd = new ZmCalPrintDialog(params); 1745 return pd; 1746 }; 1747 1748 ZmCalViewController.prototype._printListener = 1749 function(ev) { 1750 var ids = []; 1751 var list = this.getSelection(); 1752 if (list.length == 0) { 1753 // Calendar list view is anomalous - on a right click (action menu) when resetOperations 1754 // is called, getSelection returns 1 item, but the selection actually only occurs if it was a 1755 // left click. So we can get here with no selections. Use the appt associated with the menu. 1756 var actionMenu = this.getActionMenu(); 1757 var appt = actionMenu.__appt; 1758 if (appt) { 1759 ids.push(appt.invId); 1760 } 1761 // bug:68735 if no selection is made in the calendar, open up the print dialog 1762 else{ 1763 this._printCalendarListener(ev); 1764 } 1765 1766 } 1767 else { 1768 for (var i = 0; i < list.length; i++) { 1769 ids.push(list[i].invId); 1770 } 1771 } 1772 if (ids.length == 0) return; 1773 1774 var url = ["/h/printappointments?id=", ids.join(','), "&tz=", AjxTimezone.getServerId(AjxTimezone.DEFAULT)]; 1775 if(appCtxt.isOffline) { 1776 if (ids.length == 1) { 1777 var appt = this.getSelection()[0]; 1778 url.push("&acct=", appt.getFolder().getAccount().name); 1779 } 1780 url.push("&zd=", "true"); 1781 } 1782 url = url.join(""); 1783 1784 window.open(appContextPath+url, "_blank"); 1785 }; 1786 1787 ZmCalViewController.prototype._deleteListener = 1788 function(ev) { 1789 var op = (ev && ev.item instanceof DwtMenuItem) 1790 ? ev.item.parent.getData(ZmOperation.KEY_ID) : null; 1791 this._doDelete(this.getSelection(), null, null, op); 1792 }; 1793 1794 ZmCalViewController.prototype._replyListener = 1795 function(ev) { 1796 var op = (ev && ev.item instanceof DwtMenuItem) 1797 ? ev.item.parent.getData(ZmOperation.KEY_ID) : null; 1798 var items = this.getSelection(); 1799 if (items && items.length) 1800 this._replyAppointment(items[0], false); 1801 }; 1802 1803 ZmCalViewController.prototype._replyAllListener = 1804 function(ev) { 1805 var op = (ev && ev.item instanceof DwtMenuItem) 1806 ? ev.item.parent.getData(ZmOperation.KEY_ID) : null; 1807 var items = this.getSelection(); 1808 if (items && items.length) 1809 this._replyAppointment(items[0], true); 1810 }; 1811 1812 ZmCalViewController.prototype._forwardListener = 1813 function(ev) { 1814 var op = (ev && ev.item instanceof DwtMenuItem) 1815 ? ev.item.parent.getData(ZmOperation.KEY_ID) : null; 1816 this._doForward(this.getSelection(), op); 1817 }; 1818 1819 ZmCalViewController.prototype._duplicateApptListener = 1820 function(ev) { 1821 var op = (ev && ev.item instanceof DwtMenuItem) 1822 ? ev.item.parent.getData(ZmOperation.KEY_ID) : null; 1823 var items = this.getSelection(); 1824 var appt = items[0]; 1825 var isException = (appt.isRecurring() && op == ZmOperation.VIEW_APPT_INSTANCE); 1826 this.duplicateAppt(appt, {isException: isException}); 1827 }; 1828 1829 ZmCalViewController.prototype.duplicateAppt = 1830 function(appt, params) { 1831 Dwt.setLoadingTime("ZmCalendarApp-cloneAppt"); 1832 var clone = ZmAppt.quickClone(appt); 1833 var mode = ZmCalItem.MODE_EDIT; 1834 if (appt.isRecurring()) { 1835 mode = params.isException ? ZmCalItem.MODE_COPY_SINGLE_INSTANCE : ZmCalItem.MODE_EDIT_SERIES; //at first I also created a MODE_COPY_SERIES but I'm afraid of the impact and regressions. So keep it as "edit". 1836 } 1837 clone.getDetails(mode, new AjxCallback(this, this._duplicateApptContinue, [clone, ZmCalItem.MODE_NEW, params])); 1838 Dwt.setLoadedTime("ZmCalendarApp-cloneAppt"); 1839 }; 1840 1841 ZmCalViewController.prototype._duplicateApptContinue = 1842 function(appt, mode, params) { 1843 if(params.isException) appt.clearRecurrence(); 1844 if(params.startDate) appt.setStartDate(params.startDate); 1845 if(params.endDate) appt.setEndDate(params.endDate); 1846 1847 if (!appt.isOrganizer() || appt.isReadOnly()) { //isOrganizer means current user is the organizer of the appt. (which is not the case if the appt is on a shared folder, even if the current user has admin or manager rights (i.e. not read only) 1848 var origOrganizer=appt.organizer; 1849 var myEmail=appt.getFolder().getAccount().getEmail(); 1850 appt.replaceAttendee(myEmail,origOrganizer); 1851 appt.organizer=myEmail; 1852 appt.isOrg=true; 1853 if(appt.isShared()) { 1854 appt.isSharedCopy = true; 1855 if (!appt.getFolderId()) { //not sure why the following line is done, but if the appt is of a certain folder, it should be kept that folder in the copy. 1856 appt.setFolderId(ZmOrganizer.ID_CALENDAR); 1857 } 1858 } 1859 var dlg = appCtxt.getMsgDialog(); 1860 var callback = new AjxCallback(this, this.newAppointment,[appt,mode,true]); 1861 var listener = new AjxListener(this, this._handleReadonlyOk, [callback, dlg]); 1862 dlg.setButtonListener(DwtDialog.OK_BUTTON, listener); 1863 dlg.setMessage(ZmMsg.confirmApptDuplication); 1864 dlg.popup(); 1865 } 1866 else { 1867 appt.organizer = null; // Let the organizer be set according to the choise in the form. (the calendar). 1868 this.newAppointment(appt, mode, true); 1869 } 1870 }; 1871 1872 ZmCalViewController.prototype._proposeTimeListener = 1873 function(ev) { 1874 var op = ev.item.parent.getData(ZmOperation.KEY_ID); 1875 1876 var items = this.getSelection(); 1877 1878 // listview cannot handle proposing time for multiple items at once 1879 if (this._viewMgr.getCurrentViewName() == ZmId.VIEW_CAL_LIST && items.length > 1) { 1880 return; 1881 } 1882 1883 var mode = ZmCalItem.MODE_EDIT; 1884 if (op == ZmOperation.VIEW_APPT_INSTANCE || op == ZmOperation.VIEW_APPT_SERIES) { 1885 mode = (op == ZmOperation.VIEW_APPT_INSTANCE) 1886 ? ZmCalItem.MODE_EDIT_SINGLE_INSTANCE 1887 : ZmCalItem.MODE_EDIT_SERIES; 1888 } 1889 1890 var appt = items[0]; 1891 var clone = ZmAppt.quickClone(appt); 1892 clone.setProposeTimeMode(true); 1893 clone.getDetails(mode, new AjxCallback(this, this._proposeTimeContinue, [clone, mode])); 1894 }; 1895 1896 ZmCalViewController.prototype._proposeTimeContinue = 1897 function(appt, mode) { 1898 appt.setViewMode(mode); 1899 AjxDispatcher.run("GetApptComposeController").proposeNewTime(appt); 1900 }; 1901 1902 ZmCalViewController.prototype._reinviteAttendeesListener = 1903 function(ev) { 1904 var items = this.getSelection(); 1905 // listview cannot handle reinvite for multiple items at once 1906 if (this._viewMgr.getCurrentViewName() == ZmId.VIEW_CAL_LIST && items.length > 1) { 1907 return; 1908 } 1909 // Get the details. Otherwise, on send, any missing information will be deleted server side 1910 var detailsSuccessCallback = this._sendAppt.bind(this, items[0]); 1911 items[0].getDetails(null, detailsSuccessCallback, this._errorCallback); 1912 }; 1913 1914 ZmCalViewController.prototype._sendAppt = 1915 function(appt) { 1916 var mode = appt.viewMode; 1917 appt.viewMode = ZmCalItem.MODE_EDIT; 1918 1919 var callback = new AjxCallback(this, this._handleResponseSave, appt); 1920 var errorCallback = new AjxCallback(this, this._handleErrorSave, appt); 1921 appt.send(null, callback, errorCallback); 1922 1923 appt.viewMode = mode; 1924 }; 1925 1926 ZmCalViewController.prototype._handleResponseSave = 1927 function(response) { 1928 appCtxt.setStatusMsg(ZmMsg.apptSent); 1929 }; 1930 1931 ZmCalViewController.prototype._handleErrorSave = 1932 function(calItem, ex) { 1933 var status = calItem.processErrorSave(ex); 1934 var handled = false; 1935 1936 if (status.continueSave) { 1937 this._sendAppt(calItem); 1938 handled = true; 1939 } else { 1940 if (status.errorMessage) { 1941 // Handled the error, display the error message 1942 handled = true; 1943 var dialog = appCtxt.getMsgDialog(); 1944 dialog.setMessage(status.errorMessage, DwtMessageDialog.CRITICAL_STYLE); 1945 dialog.popup(); 1946 } 1947 appCtxt.notifyZimlets("onSaveApptFailure", [this, calItem, ex]); 1948 } 1949 return handled; 1950 }; 1951 1952 ZmCalViewController.prototype._doForward = 1953 function(items, op) { 1954 // listview cannot handle forwarding multiple items at once 1955 if (this._viewMgr.getCurrentViewName() == ZmId.VIEW_CAL_LIST && items.length > 1) { 1956 return; 1957 } 1958 1959 // since base view has multiple selection turned off, always select first item 1960 var appt = items[0]; 1961 var mode = ZmCalItem.MODE_FORWARD; 1962 if (op == ZmOperation.VIEW_APPT_INSTANCE || op == ZmOperation.VIEW_APPT_SERIES) { 1963 mode = (op == ZmOperation.VIEW_APPT_INSTANCE) 1964 ? ZmCalItem.MODE_FORWARD_SINGLE_INSTANCE 1965 : ZmCalItem.MODE_FORWARD_SERIES; 1966 } 1967 this._forwardAppointment(appt, mode); 1968 }; 1969 1970 /** 1971 * Override the ZmListController method. 1972 * 1973 * @private 1974 */ 1975 ZmCalViewController.prototype._doDelete = 1976 function(items, hardDelete, attrs, op) { 1977 1978 var isTrash = false; 1979 1980 // listview can handle deleting multiple items at once 1981 if(items.length>0){ 1982 var calendar = items[0].getFolder(); 1983 isTrash = calendar && calendar.nId==ZmOrganizer.ID_TRASH; 1984 } 1985 1986 if ((this._viewMgr.getCurrentViewName() == ZmId.VIEW_CAL_LIST || isTrash) && items.length > 1) { 1987 var divvied = this._divvyItems(items); 1988 1989 // first attempt to deal with read-only appointments 1990 if (divvied.readonly.length > 0) { 1991 var dlg = appCtxt.getMsgDialog(); 1992 var callback = (divvied.recurring.length > 0) 1993 ? this._showTypeDialog.bind(this, [divvied.recurring, ZmCalItem.MODE_DELETE]) 1994 : null; 1995 var listener = new AjxListener(this, this._handleReadonlyOk, [callback, dlg]); 1996 dlg.setButtonListener(DwtDialog.OK_BUTTON, listener); 1997 dlg.setMessage(ZmMsg.deleteReadonly); 1998 dlg.popup(); 1999 } 2000 else if (divvied.recurring.length > 0) { 2001 this._showTypeDialog(divvied.recurring, ZmCalItem.MODE_DELETE); 2002 } 2003 else { 2004 this._promptDeleteApptList(divvied.normal); 2005 } 2006 } 2007 else { 2008 // since base view has multiple selection turned off, always select first item 2009 var appt = items[0]; 2010 if (op == ZmOperation.VIEW_APPT_INSTANCE || op == ZmOperation.VIEW_APPT_SERIES) { 2011 var mode = (op == ZmOperation.VIEW_APPT_INSTANCE) 2012 ? ZmCalItem.MODE_DELETE_INSTANCE 2013 : ZmCalItem.MODE_DELETE_SERIES; 2014 this._promptDeleteAppt(appt, mode); 2015 } else { 2016 this._deleteAppointment(appt); 2017 } 2018 } 2019 }; 2020 2021 ZmCalViewController.prototype._handleReadonlyOk = 2022 function(callback, dlg) { 2023 dlg.popdown(); 2024 if (callback) { 2025 callback.run(); 2026 } 2027 }; 2028 2029 ZmCalViewController.prototype._handleMultiDelete = 2030 function(deleteList, mode) { 2031 var batchCmd = new ZmBatchCommand(true, null, true); 2032 2033 // first, get details for each appointment 2034 for (var i = 0; i < deleteList.length; i++) { 2035 var appt = deleteList[i]; 2036 var args = [mode, null, null, null, null]; 2037 batchCmd.add(new AjxCallback(appt, appt.getDetails, args)); 2038 } 2039 batchCmd.run(this._handleGetDetails.bind(this, deleteList, mode)); 2040 }; 2041 2042 ZmCalViewController.prototype._handleGetDetails = 2043 function(deleteList, mode) { 2044 var batchCmd = new ZmBatchCommand(true, null, true); 2045 for (var i = 0; i < deleteList.length; i++) { 2046 var appt = deleteList[i]; 2047 var args = [mode, null, null, null]; 2048 batchCmd.add(new AjxCallback(appt, appt.cancel, args)); 2049 } 2050 batchCmd.run(); 2051 var summary = ZmList.getActionSummary({ 2052 actionTextKey: 'actionDelete', 2053 numItems: batchCmd.size(), 2054 type: ZmItem.APPT 2055 }); 2056 appCtxt.setStatusMsg(summary); 2057 appCtxt.notifyZimlets("onAppointmentDelete", deleteList);//notify Zimlets on delete 2058 }; 2059 2060 ZmCalViewController.prototype.getSelection = 2061 function() { 2062 return ZmListController.prototype.getSelection.call(this, this.getCurrentListView()); 2063 }; 2064 2065 ZmCalViewController.prototype.getSelectionCount = function() { 2066 return ZmListController.prototype.getSelectionCount.call(this, this.getCurrentListView()); 2067 }; 2068 2069 ZmCalViewController.prototype._divvyItems = 2070 function(items) { 2071 2072 var normal = []; 2073 var readonly = []; 2074 var recurring = []; 2075 var shared = []; 2076 2077 for (var i = 0; i < items.length; i++) { 2078 var appt = items[i]; 2079 if (appt.type != ZmItem.APPT) { continue; } 2080 2081 if (appt.isFolderReadOnly()) { 2082 readonly.push(appt); 2083 } 2084 else if (appt.isRecurring() && !appt.isException) { 2085 recurring.push(appt); 2086 } 2087 else { 2088 normal.push(appt); 2089 } 2090 2091 // keep a separate list of shared items. This means "recurring" and 2092 // "normal" can contain shared items as well. 2093 var calendar = appt.getFolder(); 2094 if (calendar && calendar.isRemote()) { 2095 shared.push(appt); 2096 } 2097 } 2098 2099 return {normal:normal, readonly:readonly, recurring:recurring, shared:shared}; 2100 }; 2101 2102 ZmCalViewController.prototype._promptDeleteApptList = 2103 function(deleteList) { 2104 if (deleteList.length === 0) { 2105 return; 2106 } 2107 var calendar = deleteList[0].getFolder(); 2108 var isTrash = calendar && calendar.nId == ZmOrganizer.ID_TRASH; 2109 var msg = (isTrash) ? ZmMsg.confirmCancelApptListPermanently : ZmMsg.confirmCancelApptList; 2110 var callback = this._handleMultiDelete.bind(this, deleteList, ZmCalItem.MODE_DELETE); 2111 appCtxt.getConfirmationDialog().popup(msg, callback); 2112 }; 2113 2114 ZmCalViewController.prototype._promptDeleteAppt = 2115 function(appt, mode) { 2116 if(!appt){ 2117 return; 2118 } 2119 if (appt instanceof Array) { 2120 this._continueDelete(appt, mode); 2121 } else { 2122 if (appt.isOrganizer()) { 2123 if (mode == ZmCalItem.MODE_DELETE_SERIES) { 2124 this._promptDeleteFutureInstances(appt, mode); 2125 } else { 2126 this._promptCancelReply(appt, mode); 2127 } 2128 } else { 2129 this._promptDeleteNotify(appt, mode); 2130 } 2131 } 2132 }; 2133 ZmCalViewController.prototype._confirmDeleteApptDialog = 2134 function(){ 2135 2136 if (!ZmCalViewController._confirmDialog) { 2137 var editMessageButton =new DwtDialog_ButtonDescriptor(DwtDialog.YES_BUTTON, ZmMsg.editMessage , DwtDialog.ALIGN_LEFT); 2138 var buttons = [ DwtDialog.NO_BUTTON, DwtDialog.CANCEL_BUTTON ]; 2139 var extraButtons = [editMessageButton]; 2140 ZmCalViewController._confirmDialog = new DwtConfirmDialog(this._shell, null, "CNF_DEL_SENDEDIT", buttons, extraButtons); 2141 var noButton = ZmCalViewController._confirmDialog.getButton(DwtDialog.NO_BUTTON); 2142 noButton.setText(ZmMsg.sendCancellation); // Changing the text for No button 2143 } 2144 return ZmCalViewController._confirmDialog; 2145 }; 2146 2147 ZmCalViewController.prototype._promptCancelReply = 2148 function(appt, mode) { 2149 var cancelNoReplyCallback = new AjxCallback(this, this._continueDelete, [appt, mode]); 2150 var confirmDialog; 2151 var calendar = appt && appt.getFolder(); 2152 var isTrash = calendar && calendar.nId==ZmOrganizer.ID_TRASH; 2153 2154 // Display traditional Yes/No Dialog if 2155 // - If appt has no attendees 2156 // - appt is from trash 2157 // - appt is saved but not sent 2158 if (!isTrash && appt.otherAttendees && !appt.inviteNeverSent && appCtxt.get(ZmSetting.MAIL_ENABLED)) { 2159 confirmDialog = this._confirmDeleteApptDialog(); 2160 var cancelReplyCallback = new AjxCallback(this, this._continueDeleteReply, [appt, mode]); 2161 2162 confirmDialog.setTitle(ZmMsg.confirmDeleteApptTitle); 2163 confirmDialog.popup(ZmMsg.confirmCancelApptReply, cancelReplyCallback, cancelNoReplyCallback); 2164 } else { 2165 confirmDialog = appCtxt.getConfirmationDialog("CNF_DEL_YESNO"); 2166 var msg = isTrash ? ZmMsg.confirmPermanentCancelAppt : ZmMsg.confirmCancelAppt; 2167 2168 if (appt.isRecurring() && !appt.isException && !isTrash) { 2169 msg = (mode == ZmCalItem.MODE_DELETE_INSTANCE) ? AjxMessageFormat.format(ZmMsg.confirmCancelApptInst, AjxStringUtil.htmlEncode(appt.name)) : ZmMsg.confirmCancelApptSeries; 2170 } 2171 confirmDialog.setTitle(ZmMsg.confirmDeleteApptTitle); 2172 confirmDialog.popup(msg, cancelNoReplyCallback); 2173 } 2174 }; 2175 2176 ZmCalViewController.prototype._promptDeleteFutureInstances = 2177 function(appt, mode) { 2178 2179 if (appt.getAttendees(ZmCalBaseItem.PERSON).length>0) { 2180 this._delFutureInstNotifyDlgWithAttendees = this._delFutureInstNotifyDlgWithAttendees ? 2181 this._delFutureInstNotifyDlgWithAttendees : (new ZmApptDeleteNotifyDialog({ 2182 parent: this._shell, 2183 title: AjxMsg.confirmTitle, 2184 confirmMsg: ZmMsg.confirmCancelApptSeries, 2185 choiceLabel1: ZmMsg.confirmCancelApptWholeSeries, 2186 choiceLabel2 : ZmMsg.confirmCancelApptFutureInstances, 2187 choice2WarningMsg : ZmMsg.deleteApptWarning 2188 })); 2189 this._delFutureInstNotifyDlg = this._delFutureInstNotifyDlgWithAttendees; 2190 } 2191 else{ 2192 this._delFutureInstNotifyDlgWithoutAtteendees = this._delFutureInstNotifyDlgWithoutAtteendees ? 2193 this._delFutureInstNotifyDlgWithoutAtteendees : (new ZmApptDeleteNotifyDialog({ 2194 parent: this._shell, 2195 title: ZmMsg.confirmDeleteApptTitle, 2196 confirmMsg: ZmMsg.confirmCancelApptSeries, 2197 choiceLabel1: ZmMsg.confirmDeleteApptWholeSeries, 2198 choiceLabel2 : ZmMsg.confirmDeleteApptFutureInstances, 2199 choice2WarningMsg : ZmMsg.deleteApptWarning 2200 })); 2201 this._delFutureInstNotifyDlg = this._delFutureInstNotifyDlgWithoutAtteendees; 2202 } 2203 this._delFutureInstNotifyDlg.popup(new AjxCallback(this, this._deleteFutureInstYesCallback, [appt,mode])); 2204 }; 2205 2206 ZmCalViewController.prototype._deleteFutureInstYesCallback = 2207 function(appt, mode) { 2208 var deleteWholeSeries = this._delFutureInstNotifyDlg.isDefaultOptionChecked(); 2209 if (!deleteWholeSeries) { 2210 appt.setCancelFutureInstances(true); 2211 } 2212 2213 var cancelNoReplyCallback = new AjxCallback(this, this._continueDelete, [appt, mode]); 2214 2215 var confirmDialog = this._confirmDeleteApptDialog(); 2216 if (appt.otherAttendees && !appt.inviteNeverSent && appCtxt.get(ZmSetting.MAIL_ENABLED)) { 2217 var cancelReplyCallback = new AjxCallback(this, this._continueDeleteReply, [appt, mode]); 2218 confirmDialog.popup(ZmMsg.sendCancellationConfirmation, cancelReplyCallback, cancelNoReplyCallback); 2219 } else { 2220 this._continueDelete(appt, mode); 2221 } 2222 }; 2223 2224 ZmCalViewController.prototype._promptDeleteNotify = 2225 function(appt, mode) { 2226 if (!this._deleteNotifyDialog) { 2227 this._deleteNotifyDialog = new ZmApptDeleteNotifyDialog({ 2228 parent: this._shell, 2229 title: AjxMsg.confirmTitle, 2230 confirmMsg: "", 2231 choiceLabel1: ZmMsg.dontNotifyOrganizer, 2232 choiceLabel2 : ZmMsg.notifyOrganizer 2233 }); 2234 } 2235 if(this._deleteMode != mode){ 2236 var msg = ZmMsg.confirmCancelAppt; 2237 if(appt.isRecurring()){ 2238 msg = (mode == ZmCalItem.MODE_DELETE_INSTANCE) 2239 ? AjxMessageFormat.format(ZmMsg.confirmCancelApptInst, AjxStringUtil.htmlEncode(appt.name)) 2240 : ZmMsg.confirmCancelApptSeries; 2241 } 2242 var msgDiv = document.getElementById(this._deleteNotifyDialog._confirmMessageDivId); 2243 msgDiv.innerHTML = msg; 2244 this._deleteMode = mode; 2245 } 2246 this._deleteNotifyDialog.popup(new AjxCallback(this, this._deleteNotifyYesCallback, [appt,mode])); 2247 }; 2248 2249 ZmCalViewController.prototype._deleteNotifyYesCallback = 2250 function(appt, mode) { 2251 var notifyOrg = !this._deleteNotifyDialog.isDefaultOptionChecked(); 2252 if (notifyOrg) { 2253 this._cancelBeforeDelete(appt, mode); 2254 } else { 2255 this._continueDelete(appt, mode); 2256 } 2257 }; 2258 2259 ZmCalViewController.prototype._cancelBeforeDelete = 2260 function(appt, mode) { 2261 var type = ZmOperation.REPLY_DECLINE; 2262 var respCallback = new AjxCallback(this, this._cancelBeforeDeleteContinue, [appt, type, mode]); 2263 appt.getDetails(null, respCallback, this._errorCallback); 2264 }; 2265 2266 ZmCalViewController.prototype._cancelBeforeDeleteContinue = 2267 function(appt, type, mode) { 2268 var msgController = this._getMsgController(); 2269 msgController.setMsg(appt.message); 2270 // poke the msgController 2271 var instanceDate = mode == ZmCalItem.MODE_DELETE_INSTANCE ? new Date(appt.uniqStartTime) : null; 2272 var delContCallback = new AjxCallback(this, this._continueDelete, [appt, mode]); 2273 msgController._sendInviteReply(type, appt.compNum || 0, instanceDate, appt.getRemoteFolderOwner(),false,null,null,delContCallback); 2274 }; 2275 2276 2277 ZmCalViewController.prototype._deleteAppointment = 2278 function(appt) { 2279 if (!appt) { return; } 2280 2281 var calendar = appt.getFolder(); 2282 var isTrash = calendar && calendar.nId == ZmOrganizer.ID_TRASH; 2283 2284 if (appt.isRecurring() && !isTrash && !appt.isException) { 2285 this._showTypeDialog(appt, ZmCalItem.MODE_DELETE); 2286 } else { 2287 this._promptDeleteAppt(appt, ZmCalItem.MODE_DELETE); 2288 } 2289 }; 2290 2291 ZmCalViewController.prototype._continueDeleteReply = 2292 function(appt, mode) { 2293 var action = ZmOperation.REPLY_CANCEL; 2294 var respCallback = new AjxCallback(this, this._continueDeleteReplyRespondAction, [appt, action, mode]); 2295 appt.getDetails(mode, respCallback, this._errorCallback); 2296 }; 2297 2298 ZmCalViewController.prototype._continueDeleteReplyRespondAction = 2299 function(appt, action, mode) { 2300 var msgController = this._getMsgController(); 2301 var msg = appt.message; 2302 msg._appt = appt; 2303 msg._mode = mode; 2304 msgController.setMsg(msg); 2305 var instanceDate = mode == ZmCalItem.MODE_DELETE_INSTANCE ? new Date(appt.uniqStartTime) : null; 2306 msg._instanceDate = instanceDate; 2307 msgController._editInviteReply(action, 0, instanceDate); 2308 }; 2309 2310 ZmCalViewController.prototype._continueDelete = 2311 function(appt, mode) { 2312 if (appt instanceof Array) { 2313 // if list of appointments, de-dupe the same series appointments 2314 var deleteList = (mode === ZmCalItem.MODE_DELETE_SERIES) ? this._dedupeSeries(appt) : appt; 2315 this._handleMultiDelete(deleteList, mode); 2316 } 2317 else { 2318 var respCallback = new AjxCallback(this, this._handleResponseContinueDelete, appt); 2319 appt.cancel(mode, null, respCallback, this._errorCallback); 2320 } 2321 }; 2322 2323 ZmCalViewController.prototype._handleResponseContinueDelete = 2324 function(appt) { 2325 2326 var currentView = appCtxt.getCurrentView(); 2327 if (currentView && currentView.isZmApptView) { 2328 currentView.close(); 2329 } 2330 2331 var summary = ZmList.getActionSummary({ 2332 actionTextKey: 'actionDelete', 2333 numItems: 1, 2334 type: ZmItem.APPT 2335 }); 2336 appCtxt.setStatusMsg(summary); 2337 appCtxt.notifyZimlets("onAppointmentDelete", [appt]);//notify Zimlets on delete 2338 }; 2339 2340 /** 2341 * This method takes a list of recurring appointments and returns a list of 2342 * unique appointments (removes instances) 2343 * 2344 * @param list [Array] List of *recurring* appointments 2345 * 2346 * @private 2347 */ 2348 ZmCalViewController.prototype._dedupeSeries = 2349 function(list) { 2350 var unique = []; 2351 var deduped = {}; 2352 for (var i = 0; i < list.length; i++) { 2353 var appt = list[i]; 2354 if (!deduped[appt.id]) { 2355 deduped[appt.id] = true; 2356 unique.push(appt); 2357 } 2358 } 2359 return unique; 2360 }; 2361 2362 ZmCalViewController.prototype._getMoveParams = 2363 function(dlg) { 2364 var params = ZmListController.prototype._getMoveParams.apply(this, arguments); 2365 var omit = {}; 2366 var folderTree = appCtxt.getFolderTree(); 2367 if (!folderTree) { return params; } 2368 2369 var folders = folderTree.getByType(ZmOrganizer.CALENDAR); 2370 for (var i = 0; i < folders.length; i++) { 2371 var folder = folders[i]; 2372 if (folder.link && folder.isReadOnly()) { 2373 omit[folder.id] = true; 2374 } 2375 } 2376 params.omit = omit; 2377 params.description = ZmMsg.targetCalendar; 2378 2379 return params; 2380 }; 2381 2382 ZmCalViewController.prototype._getMoveDialogTitle = 2383 function(num) { 2384 return AjxMessageFormat.format(ZmMsg.moveAppts, num); 2385 }; 2386 2387 /** 2388 * Shows a dialog for handling recurring appointments. User must choose to 2389 * perform the action on the instance of the series of a recurring appointment. 2390 * 2391 * @param appt [ZmAppt] This can be a single appt object or a *list* of appts 2392 * @param mode [Integer] Constant describing what kind of appointments we're dealing with 2393 * 2394 * @private 2395 */ 2396 ZmCalViewController.prototype._showTypeDialog = 2397 function(appt, mode) { 2398 if (this._typeDialog == null) { 2399 AjxDispatcher.require(["MailCore", "CalendarCore", "Calendar", "CalendarAppt"]); 2400 this._typeDialog = new ZmCalItemTypeDialog({ 2401 id: 'CAL_ITEM_TYPE_DIALOG', 2402 parent: this._shell 2403 }); 2404 this._typeDialog.registerCallback(DwtDialog.OK_BUTTON, this._typeOkListener, this); 2405 this._typeDialog.registerCallback(DwtDialog.CANCEL_BUTTON, this._typeCancelListener, this); 2406 } 2407 this._typeDialog.initialize(appt, mode, ZmItem.APPT); 2408 this._typeDialog.popup(); 2409 }; 2410 2411 ZmCalViewController.prototype.showApptReadOnlyView = 2412 function(appt, mode) { 2413 var clone = ZmAppt.quickClone(appt); 2414 clone.getDetails(mode, new AjxCallback(this, this._showApptReadOnlyView, [clone, mode])); 2415 }; 2416 2417 ZmCalViewController.prototype._showApptReadOnlyView = 2418 function(appt, mode) { 2419 var controller = this._app.getApptViewController(this._apptSessionId[appt.invId]); 2420 this._apptSessionId[appt.invId] = controller.getSessionId(); 2421 controller.show(appt, mode); 2422 }; 2423 2424 ZmCalViewController.prototype._showQuickAddDialog = 2425 function(appt, shiftKey) { 2426 if(appCtxt.isExternalAccount() || appCtxt.isWebClientOffline()) { return; } 2427 // find out if we really should display the quick add dialog 2428 var useQuickAdd = appCtxt.get(ZmSetting.CAL_USE_QUICK_ADD); 2429 if ((useQuickAdd && !shiftKey) || (!useQuickAdd && shiftKey)) { 2430 if (AjxTimezone.TIMEZONE_CONFLICT || AjxTimezone.DEFAULT_RULE.autoDetected) { 2431 var timezonePicker = this.getTimezonePicker(); 2432 var callback = new AjxCallback(this, this.handleTimezoneSelectionQuickAdd, [appt, shiftKey]); 2433 timezonePicker.setCallback(callback); 2434 timezonePicker.popup(); 2435 } else { 2436 this._showQuickAddDialogContinue(appt, shiftKey); 2437 } 2438 } else { 2439 this.newAppointment(appt); 2440 } 2441 }; 2442 2443 ZmCalViewController.prototype._showQuickAddDialogContinue = 2444 function(appt, shiftKey) { 2445 if (this._quickAddDialog == null) { 2446 AjxDispatcher.require(["MailCore", "CalendarCore", "Calendar", "CalendarAppt"]); 2447 this._quickAddDialog = new ZmApptQuickAddDialog(this._shell); 2448 this._quickAddDialog.setButtonListener(DwtDialog.OK_BUTTON, new AjxListener(this, this._quickAddOkListener)); 2449 this._quickAddDialog.addSelectionListener(ZmApptQuickAddDialog.MORE_DETAILS_BUTTON, new AjxListener(this, this._quickAddMoreListener)); 2450 } 2451 this._quickAddDialog.initialize(appt); 2452 this._quickAddDialog.popup(); 2453 }; 2454 2455 ZmCalViewController.prototype.newAppointmentHelper = 2456 function(startDate, optionalDuration, folderId, shiftKey) { 2457 var appt = this._newApptObject(startDate, optionalDuration, folderId); 2458 this._showQuickAddDialog(appt, shiftKey); 2459 }; 2460 2461 ZmCalViewController.prototype.newAllDayAppointmentHelper = 2462 function(startDate, endDate, folderId, shiftKey) { 2463 var appt = this._newApptObject(startDate, null, folderId); 2464 if (endDate) { 2465 appt.setEndDate(endDate); 2466 } 2467 appt.setAllDayEvent(true); 2468 appt.freeBusy = "F"; 2469 this._showQuickAddDialog(appt, shiftKey); 2470 }; 2471 2472 ZmCalViewController.prototype.newAppointment = 2473 function(newAppt, mode, isDirty, startDate) { 2474 AjxDispatcher.require(["MailCore", "CalendarCore", "Calendar"]); 2475 var sd = startDate || (this._viewVisible ? this._viewMgr.getDate() : new Date()); 2476 var appt = newAppt || this._newApptObject(sd, (appCtxt.get(ZmSetting.CAL_DEFAULT_APPT_DURATION) * 1000)); //bug:50121 added appt duration as configurable from preference 2477 2478 //certain views can set attendees before creating appointment 2479 if(this._viewVisible && this._viewMgr.getCurrentView().getAtttendees) { 2480 var attendees = this._viewMgr.getCurrentView().getAtttendees(); 2481 if(attendees && attendees.length > 0) appt.setAttendees(attendees, ZmCalBaseItem.PERSON); 2482 } 2483 2484 if (AjxTimezone.TIMEZONE_CONFLICT || AjxTimezone.DEFAULT_RULE.autoDetected) { 2485 var timezonePicker = this.getTimezonePicker(); 2486 var callback = new AjxCallback(this, this.handleTimezoneSelection, [appt, mode, isDirty]); 2487 timezonePicker.setCallback(callback); 2488 timezonePicker.popup(); 2489 } else { 2490 this._app.getApptComposeController().show(appt, mode, isDirty); 2491 } 2492 }; 2493 2494 ZmCalViewController.prototype.handleTimezoneSelection = 2495 function(appt, mode, isDirty, serverId) { 2496 this.updateTimezoneInfo(appt, serverId); 2497 this._app.getApptComposeController().show(appt, mode, isDirty); 2498 }; 2499 2500 ZmCalViewController.prototype.handleTimezoneSelectionQuickAdd = 2501 function(appt, shiftKey, serverId) { 2502 this.updateTimezoneInfo(appt, serverId); 2503 this._showQuickAddDialogContinue(appt, shiftKey); 2504 }; 2505 2506 ZmCalViewController.prototype.updateTimezoneInfo = 2507 function(appt, serverId) { 2508 appt.setTimezone(serverId); 2509 appCtxt.set(ZmSetting.DEFAULT_TIMEZONE, serverId); 2510 AjxTimezone.TIMEZONE_CONFLICT = false; 2511 this.updateDefaultTimezone(serverId); 2512 var settings = appCtxt.getSettings(); 2513 var tzSetting = settings.getSetting(ZmSetting.DEFAULT_TIMEZONE); 2514 settings.save([tzSetting], new AjxCallback(this, this._timezoneSaveCallback)); 2515 }; 2516 2517 ZmCalViewController.prototype.updateDefaultTimezone = 2518 function(serverId) { 2519 for (var i =0; i < AjxTimezone.MATCHING_RULES.length; i++) { 2520 if (AjxTimezone.MATCHING_RULES[i].serverId == serverId) { 2521 AjxTimezone.DEFAULT_RULE = AjxTimezone.MATCHING_RULES[i]; 2522 AjxTimezone.DEFAULT = AjxTimezone.getClientId(AjxTimezone.DEFAULT_RULE.serverId); 2523 break; 2524 } 2525 } 2526 }; 2527 2528 ZmCalViewController.prototype._timezoneSaveCallback = 2529 function() { 2530 appCtxt.setStatusMsg(ZmMsg.timezonePrefSaved); 2531 }; 2532 2533 ZmCalViewController.prototype.getTimezonePicker = 2534 function() { 2535 if (!this._timezonePicker) { 2536 this._timezonePicker = appCtxt.getTimezonePickerDialog(); 2537 } 2538 return this._timezonePicker; 2539 }; 2540 2541 /** 2542 * Edits the appointment. 2543 * 2544 * @param {ZmAppt} appt the appointment 2545 * @param {constant} mode the mode 2546 */ 2547 ZmCalViewController.prototype.editAppointment = 2548 function(appt, mode) { 2549 Dwt.setLoadingTime("ZmCalendarApp-editAppt"); 2550 AjxDispatcher.require(["MailCore", "CalendarCore", "Calendar"]); 2551 if (mode != ZmCalItem.MODE_NEW) { 2552 var clone = ZmAppt.quickClone(appt); 2553 clone.getDetails(mode, new AjxCallback(this, this._showApptComposeView, [clone, mode])); 2554 } else { 2555 this._app.getApptComposeController().show(appt, mode); 2556 } 2557 Dwt.setLoadedTime("ZmCalendarApp-editAppt"); 2558 }; 2559 2560 ZmCalViewController.prototype._replyAppointment = 2561 function(appt, all) { 2562 Dwt.setLoadingTime("ZmCalendarApp-replyAppt"); 2563 AjxDispatcher.require(["MailCore", "Mail"]); 2564 var clone = ZmAppt.quickClone(appt); 2565 var respCallback = new AjxCallback(this, this._replyDetailsHandler, [clone, all]); 2566 clone.getDetails(null, respCallback, this._errorCallback, true, true); 2567 Dwt.setLoadedTime("ZmCalendarApp-replyAppt"); 2568 }; 2569 2570 ZmCalViewController.prototype._replyDetailsHandler = 2571 function(appt, all, result) { 2572 2573 var msg = new ZmMailMsg(-1, null, true); 2574 var orig = appt.message; 2575 if (orig) { 2576 var inviteHtml = orig.getInviteDescriptionContent(ZmMimeTable.TEXT_HTML); 2577 if (inviteHtml) { 2578 var htmlContent = inviteHtml.getContent(); 2579 htmlContent && msg.setInviteDescriptionContent(ZmMimeTable.TEXT_HTML, htmlContent); 2580 } 2581 2582 var inviteText = orig.getInviteDescriptionContent(ZmMimeTable.TEXT_PLAIN); 2583 if (inviteText) { 2584 var textContent = inviteText.getContent(); 2585 textContent && msg.setInviteDescriptionContent(ZmMimeTable.TEXT_PLAIN, textContent); 2586 } 2587 if (htmlContent || textContent) { 2588 msg._loaded = true; 2589 } 2590 2591 msg.invite = orig.invite; 2592 msg.id = orig.id; 2593 msg.date = orig.date; 2594 } 2595 msg.setSubject(appt.name); 2596 2597 var organizer = appt.getOrganizer(); 2598 var organizerAddress = AjxEmailAddress.parse(organizer); 2599 var self = appCtxt.getActiveAccount().name; 2600 msg.getHeaderStr = AjxCallback.returnFalse; // Real ugly hack to prevent headers from showing in the message 2601 2602 var isOrganizer = appt.isOrganizer(); 2603 var folder = appt.getFolder(); 2604 var isRemote = folder ? folder.isRemote() : false; 2605 if (!isOrganizer || isRemote) { 2606 organizerAddress.setType(AjxEmailAddress.FROM); 2607 msg.addAddress(organizerAddress); 2608 } 2609 2610 if (all) { 2611 var omit = AjxUtil.arrayAsHash([ self, organizerAddress.getAddress() ]), 2612 attendees = appt.getAttendees(ZmCalBaseItem.PERSON); 2613 2614 this._addAttendeesToReply(attendees, msg, ZmCalItem.ROLE_REQUIRED, AjxEmailAddress.FROM, omit); 2615 this._addAttendeesToReply(attendees, msg, ZmCalItem.ROLE_OPTIONAL, AjxEmailAddress.CC, omit); 2616 } 2617 2618 var data = {action: all ? ZmOperation.CAL_REPLY_ALL : ZmOperation.CAL_REPLY, msg: msg}; 2619 AjxDispatcher.run("GetComposeController").doAction(data); 2620 }; 2621 2622 ZmCalViewController.prototype._addAttendeesToReply = function(attendees, msg, role, addrType, omit) { 2623 2624 var attendeesByRole = ZmApptViewHelper.getAttendeesArrayByRole(attendees, role), 2625 ln = attendeesByRole.length, i, addr; 2626 2627 for (i = 0; i < ln; i++) { 2628 addr = attendeesByRole[i].getAttendeeText(ZmCalBaseItem.PERSON); 2629 if (addr && !omit[addr]) { 2630 var addrObj = new AjxEmailAddress(addr); 2631 addrObj.setType(addrType); 2632 msg.addAddress(addrObj); 2633 } 2634 } 2635 }; 2636 2637 ZmCalViewController.prototype._forwardAppointment = 2638 function(appt, mode) { 2639 Dwt.setLoadingTime("ZmCalendarApp-fwdAppt"); 2640 AjxDispatcher.require(["MailCore", "CalendarCore", "Calendar"]); 2641 if (mode != ZmCalItem.MODE_NEW) { 2642 var clone = ZmAppt.quickClone(appt); 2643 clone.getDetails(mode, new AjxCallback(this, this._showApptForwardComposeView, [clone, mode])); 2644 } else { 2645 this._showApptForwardComposeView(appt, mode); 2646 } 2647 Dwt.setLoadedTime("ZmCalendarApp-fwdAppt"); 2648 }; 2649 2650 ZmCalViewController.prototype._showAppointmentDetails = 2651 function(appt) { 2652 // if we have an appointment, go get all the details. 2653 if (!appt.__creating) { 2654 var calendar = appt.getFolder(); 2655 var isSynced = Boolean(calendar.url); 2656 if (appt.isRecurring()) { 2657 // prompt user to edit instance vs. series if recurring but not for exception and from edit mode 2658 if (appt.isException || appt.editViewMode) { 2659 var mode = appt.editViewMode || ZmCalItem.MODE_EDIT_SINGLE_INSTANCE; 2660 if (appt.isReadOnly() || calendar.isReadOnly() || isSynced || appCtxt.isWebClientOffline()) { 2661 this.showApptReadOnlyView(appt, mode); 2662 } else { 2663 this.editAppointment(appt, mode); 2664 } 2665 } else { 2666 this._showTypeDialog(appt, ZmCalItem.MODE_EDIT); 2667 } 2668 } else { 2669 // if simple appointment, no prompting necessary 2670 var isTrash = calendar.nId == ZmOrganizer.ID_TRASH; 2671 if (appt.isReadOnly() || calendar.isReadOnly() || isSynced || isTrash || appCtxt.isWebClientOffline()) { 2672 var mode = appt.isRecurring() ? (appt.isException ? ZmCalItem.MODE_EDIT_SINGLE_INSTANCE : ZmCalItem.MODE_EDIT_SERIES) : ZmCalItem.MODE_EDIT; 2673 this.showApptReadOnlyView(appt, mode); 2674 } else { 2675 this.editAppointment(appt, ZmCalItem.MODE_EDIT); 2676 } 2677 } 2678 } else { 2679 this.newAppointment(appt); 2680 } 2681 }; 2682 2683 ZmCalViewController.prototype._typeOkListener = 2684 function(ev) { 2685 if (this._typeDialog) { 2686 this._typeDialog.popdown(); 2687 } 2688 2689 if (this._typeDialog.mode == ZmCalItem.MODE_DELETE) 2690 this._promptDeleteAppt(this._typeDialog.calItem, this._typeDialog.isInstance() ? ZmCalItem.MODE_DELETE_INSTANCE : ZmCalItem.MODE_DELETE_SERIES); 2691 else 2692 this._performApptAction(this._typeDialog.calItem, this._typeDialog.mode, this._typeDialog.isInstance()); 2693 }; 2694 2695 ZmCalViewController.prototype._performApptAction = 2696 function(appt, mode, isInstance) { 2697 if (mode == ZmCalItem.MODE_DELETE) { 2698 var delMode = isInstance ? ZmCalItem.MODE_DELETE_INSTANCE : ZmCalItem.MODE_DELETE_SERIES; 2699 if (appt instanceof Array) { 2700 this._continueDelete(appt, delMode); 2701 } else { 2702 if (appt.isOrganizer()) { 2703 this._continueDelete(appt, delMode); 2704 } else { 2705 this._promptDeleteNotify(appt, delMode); 2706 } 2707 } 2708 } 2709 else if (mode == ZmAppt.MODE_DRAG_OR_SASH) { 2710 var viewMode = isInstance ? ZmCalItem.MODE_EDIT_SINGLE_INSTANCE : ZmCalItem.MODE_EDIT_SERIES; 2711 var state = this._updateApptDateState; 2712 var args = [state.appt, viewMode, state.startDateOffset, state.endDateOffset, state.callback, state.errorCallback]; 2713 var respCallback = new AjxCallback(this, this._handleResponseUpdateApptDate, args); 2714 delete this._updateApptDateState; 2715 appt.getDetails(viewMode, respCallback, state.errorCallback); 2716 } 2717 else { 2718 var editMode = isInstance ? ZmCalItem.MODE_EDIT_SINGLE_INSTANCE : ZmCalItem.MODE_EDIT_SERIES; 2719 var calendar = appt.getFolder(); 2720 var isSynced = Boolean(calendar.url); 2721 2722 if (appt.isReadOnly() || calendar.isReadOnly() || isSynced || appCtxt.isWebClientOffline()) { 2723 this.showApptReadOnlyView(appt, editMode); 2724 } else { 2725 this.editAppointment(appt, editMode); 2726 } 2727 } 2728 }; 2729 2730 ZmCalViewController.prototype._typeCancelListener = 2731 function(ev) { 2732 if (this._typeDialog.mode == ZmAppt.MODE_DRAG_OR_SASH) { 2733 // we cancel the drag/sash, refresh view 2734 this._refreshAction(true); 2735 } 2736 2737 this._typeDialog.popdown(); 2738 }; 2739 2740 ZmCalViewController.prototype._quickAddOkListener = 2741 function(ev) { 2742 var isValid = this._quickAddDialog.isValid(); 2743 var appt = this._quickAddDialog.getAppt(); 2744 var closeCallback = this._quickAddCallback.bind(this, true); 2745 var errorCallback = this._quickAddCallback.bind(this, false); 2746 2747 var locations = appt.getAttendees(ZmCalBaseItem.LOCATION); 2748 // Send if any locations attendees are specified. This will send invites 2749 var action = (locations.length == 0) ? ZmCalItemComposeController.SAVE_CLOSE : 2750 ZmCalItemComposeController.SEND; 2751 this._saveSimpleAppt(isValid, appt, action, closeCallback, errorCallback); 2752 } 2753 2754 ZmCalViewController.prototype._saveSimpleAppt = 2755 function(isValid, appt, action, closeCallback, errorCallback, cancelCallback) { 2756 try { 2757 if (isValid && appt) { 2758 if (appt.getFolder() && appt.getFolder().noSuchFolder) { 2759 throw AjxMessageFormat.format(ZmMsg.errorInvalidFolder, appt.getFolder().name); 2760 } 2761 if (!this._simpleComposeController) { 2762 // Create a compose controller, used for saving the quick add 2763 // appt and modifications made via ZmCalColView drag and drop, in 2764 // order to trigger permission and resource checks. 2765 this._simpleComposeController = this._app.getSimpleApptComposeController(); 2766 } 2767 this._simpleComposeController.doSimpleSave(appt, action, closeCallback, 2768 errorCallback, cancelCallback); 2769 } 2770 } catch(ex) { 2771 if (typeof ex == "string") { 2772 var errorDialog = new DwtMessageDialog({parent:this._shell}); 2773 var msg = ex ? AjxMessageFormat.format(ZmMsg.errorSavingWithMessage, ex) : ZmMsg.errorSaving; 2774 errorDialog.setMessage(msg, DwtMessageDialog.CRITICAL_STYLE); 2775 errorDialog.popup(); 2776 }else{ 2777 ZmController.handleScriptError(ex); 2778 } 2779 } 2780 }; 2781 2782 ZmCalViewController.prototype._quickAddCallback = 2783 function(success) { 2784 if (success) { 2785 this._quickAddDialog.popdown(); 2786 } 2787 appCtxt.setStatusMsg(success ? ZmMsg.apptCreated : ZmMsg.apptCreationError); 2788 }; 2789 2790 ZmCalViewController.prototype._quickAddMoreListener = 2791 function(ev) { 2792 var appt = this._quickAddDialog.getAppt(); 2793 if (appt) { 2794 this._quickAddDialog.popdown(); 2795 this.newAppointment(appt, ZmCalItem.MODE_NEW_FROM_QUICKADD, this._quickAddDialog.isDirty()); 2796 } 2797 }; 2798 2799 ZmCalViewController.prototype._showApptComposeView = 2800 function(appt, mode) { 2801 this._app.getApptComposeController().show(appt, mode); 2802 }; 2803 2804 ZmCalViewController.prototype._showApptForwardComposeView = 2805 function(appt, mode) { 2806 /*if(!appt.isOrganizer()) { */ 2807 appt.name = ZmMsg.fwd + ": " + appt.name; 2808 //} 2809 this._app.getApptComposeController().show(appt, mode); 2810 }; 2811 2812 /** 2813 * appt - appt to change 2814 * startDate - new date or null to leave alone 2815 * endDate - new or null to leave alone 2816 * changeSeries - if recurring, change the whole series 2817 * 2818 * TODO: change this to work with _handleException, and take callback so view can 2819 * restore appt location/size on failure 2820 * 2821 * @private 2822 */ 2823 ZmCalViewController.prototype.dndUpdateApptDate = 2824 function(appt, startDateOffset, endDateOffset, callback, errorCallback, ev) { 2825 appt.dndUpdate = true; 2826 if (!appt.isRecurring()) { 2827 var viewMode = ZmCalItem.MODE_EDIT; 2828 var respCallback = new AjxCallback(this, this._handleResponseUpdateApptDate, [appt, viewMode, startDateOffset, endDateOffset, callback, errorCallback]); 2829 appt.getDetails(viewMode, respCallback, errorCallback); 2830 } 2831 else { 2832 if (ev.shiftKey || ev.altKey) { 2833 var viewMode = ev.altKey ? ZmCalItem.MODE_EDIT_SERIES : ZmCalItem.MODE_EDIT_SINGLE_INSTANCE; 2834 var respCallback = new AjxCallback(this, this._handleResponseUpdateApptDate, [appt, viewMode, startDateOffset, endDateOffset, callback, errorCallback]); 2835 appt.getDetails(viewMode, respCallback, errorCallback); 2836 } 2837 else { 2838 this._updateApptDateState = {appt:appt, startDateOffset: startDateOffset, endDateOffset: endDateOffset, callback: callback, errorCallback: errorCallback }; 2839 if (appt.isException) { 2840 this._performApptAction(appt, ZmAppt.MODE_DRAG_OR_SASH, true); 2841 } else { 2842 this._showTypeDialog(appt, ZmAppt.MODE_DRAG_OR_SASH); 2843 } 2844 } 2845 } 2846 appCtxt.notifyZimlets("onApptDrop", [appt, startDateOffset, endDateOffset]); 2847 }; 2848 2849 ZmCalViewController.prototype._handleResponseUpdateApptDate = 2850 function(appt, viewMode, startDateOffset, endDateOffset, callback, errorCallback, result) { 2851 // skip prompt if no attendees 2852 if (appt.inviteNeverSent || !appt.otherAttendees) { 2853 this._handleResponseUpdateApptDateSave.apply(this, arguments); 2854 return; 2855 } 2856 2857 // NOTE: We copy the arguments into an array because arguments 2858 // is *not* technically an array. So if anyone along the 2859 // line considers it such it will blow up -- this prevents 2860 // that at the expense of having to keep this array and 2861 // the actual argument list in sync. 2862 var args = [appt, viewMode, startDateOffset, endDateOffset, callback, errorCallback, result]; 2863 var edit = new AjxCallback(this, this._handleResponseUpdateApptDateEdit, args); 2864 var save = new AjxCallback(this, this._handleResponseUpdateApptDateSave, args); 2865 var ignore = new AjxCallback(this, this._handleResponseUpdateApptDateIgnore, args); 2866 2867 var dialog = appCtxt.getConfirmationDialog(); 2868 dialog.popup(ZmMsg.confirmModifyApptReply, edit, save, ignore); 2869 }; 2870 2871 ZmCalViewController.prototype._handleResponseUpdateApptDateEdit = 2872 function(appt, viewMode, startDateOffset, endDateOffset, callback, errorCallback, result) { 2873 var clone = ZmAppt.quickClone(appt); 2874 clone.editViewMode = viewMode; 2875 if (startDateOffset) clone.setStartDate(new Date(clone.getStartTime() + startDateOffset)); 2876 if (endDateOffset) clone.setEndDate(new Date(clone.getEndTime() + endDateOffset)); 2877 this._showAppointmentDetails(clone); 2878 }; 2879 ZmCalViewController.prototype._handleResponseUpdateApptDateEdit2 = 2880 function(appt, action, mode, startDateOffset, endDateOffset) { 2881 if (startDateOffset) appt.setStartDate(new Date(appt.getStartTime() + startDateOffset)); 2882 if (endDateOffset) appt.setEndDate(new Date(appt.getEndTime() + endDateOffset)); 2883 this._continueDeleteReplyRespondAction(appt, action, mode); 2884 }; 2885 2886 ZmCalViewController.prototype._handleResponseUpdateApptDateSave = 2887 function(appt, viewMode, startDateOffset, endDateOffset, callback, errorCallback, result) { 2888 var isExceptionAllowed = appCtxt.get(ZmSetting.CAL_EXCEPTION_ON_SERIES_TIME_CHANGE); 2889 var showWarning = appt.isRecurring() && appt.hasEx && appt.getAttendees(ZmCalBaseItem.PERSON) && !isExceptionAllowed && viewMode==ZmCalItem.MODE_EDIT_SERIES; 2890 if(showWarning){ 2891 var respCallback = new AjxCallback(this, this._handleResponseUpdateApptDateSaveContinue, [appt, viewMode, startDateOffset, endDateOffset, callback, errorCallback, result]); 2892 this._showExceptionWarning(respCallback); 2893 } 2894 else{ 2895 this._handleResponseUpdateApptDateSaveContinue(appt, viewMode, startDateOffset, endDateOffset, callback, errorCallback, result); 2896 } 2897 }; 2898 2899 ZmCalViewController.prototype._handleResponseUpdateApptDateSaveContinue = 2900 function(appt, viewMode, startDateOffset, endDateOffset, callback, errorCallback, result) { 2901 try { 2902 // NOTE: If the appt was already populated (perhaps by 2903 // dragging it once, canceling the change, and then 2904 // dragging it again), then the result will be null. 2905 if (result) { 2906 result.getResponse(); 2907 } 2908 var apptStartDate = appt.startDate; 2909 var apptEndDate = appt.endDate; 2910 var apptDuration = appt.getDuration(); 2911 2912 appt.setViewMode(viewMode); 2913 if (startDateOffset) { 2914 var sd = (viewMode == ZmCalItem.MODE_EDIT_SINGLE_INSTANCE) ? appt.getUniqueStartDate() : new Date(appt.getStartTime()); 2915 appt.setStartDate(new Date(sd.getTime() + startDateOffset)); 2916 appt.resetRepeatWeeklyDays(); 2917 } 2918 if (endDateOffset) { 2919 var endDateTime; 2920 2921 if (viewMode === ZmCalItem.MODE_EDIT_SINGLE_INSTANCE) { 2922 // For some reason the recurring all day events have their end date set to the next day while the normal all day events don't. 2923 // For e.g. an event with startDate 9th June and endDate 10th June when dragged to the next day has varying results: 2924 // -> Regular all day event has end Date as 9th June which with endDateOffset results in 10th June as new endDate 2925 // -> Recurring all day event has end Date as 10th June which with endDateOffset results in 11th June as new endDate 2926 // To tackle this the new End date is now calculated from the new start date and appt duration and equivalent of 1 day is subtracted from it. 2927 // TODO: Need a better solution for this -> investigate why the end date difference is present in the first place. 2928 if (appt.allDayEvent) { 2929 endDateTime = appt.getStartTime() + apptDuration - AjxDateUtil.MSEC_PER_DAY; 2930 } 2931 else { 2932 endDateTime = appt.getUniqueEndDate().getTime() + endDateOffset; 2933 } 2934 } 2935 else { 2936 endDateTime = appt.getEndTime() + endDateOffset; 2937 } 2938 appt.setEndDate(new Date(endDateTime)); 2939 } 2940 2941 if (viewMode == ZmCalItem.MODE_EDIT_SINGLE_INSTANCE) { 2942 //bug: 32231 - use proper timezone while creating exceptions 2943 appt.setOrigTimezone(AjxTimezone.getServerId(AjxTimezone.DEFAULT)); 2944 } 2945 2946 if(!appt.getTimezone()) appt.setTimezone(AjxTimezone.getServerId(AjxTimezone.DEFAULT)); 2947 var respCallback = new AjxCallback(this, this._handleResponseUpdateApptDateSave2, [callback]); 2948 var respErrCallback = new AjxCallback(this, this._handleResponseUpdateApptDateSave2, [errorCallback, appt, apptStartDate, apptEndDate]); 2949 appCtxt.getShell().setBusy(true); 2950 2951 var action = appt.inviteNeverSent ? ZmCalItemComposeController.SAVE_CLOSE : 2952 ZmCalItemComposeController.SEND; 2953 this._saveSimpleAppt(true, appt, action, respCallback, respErrCallback, respErrCallback); 2954 } catch (ex) { 2955 appCtxt.getShell().setBusy(false); 2956 if (ex.msg) { 2957 this.popupErrorDialog(AjxMessageFormat.format(ZmMsg.mailSendFailure, ex.msg)); 2958 } else { 2959 this.popupErrorDialog(ZmMsg.errorGeneric, ex); 2960 } 2961 if (errorCallback) errorCallback.run(ex); 2962 } 2963 if (callback) callback.run(result); 2964 } 2965 2966 ZmCalViewController.prototype._showExceptionWarning = function(yesCB,noCB) { 2967 var dialog = appCtxt.getYesNoMsgDialog(); 2968 dialog.setMessage(ZmMsg.recurrenceUpdateWarning, DwtMessageDialog.WARNING_STYLE); 2969 dialog.registerCallback(DwtDialog.YES_BUTTON, this._handleExceptionWarningResponse, this,[dialog,yesCB]); 2970 dialog.registerCallback(DwtDialog.NO_BUTTON, this._handleExceptionWarningResponse,this,[dialog,noCB]); 2971 dialog.popup(); 2972 } 2973 2974 ZmCalViewController.prototype._handleExceptionWarningResponse = function(dialog,respCallback) { 2975 if(respCallback){respCallback.run();} 2976 else{this._refreshAction(true);} 2977 if(dialog){ 2978 dialog.popdown(); 2979 } 2980 } 2981 2982 ZmCalViewController.prototype._handleResponseUpdateApptDateSave2 = 2983 function(callback, appt, apptStartDate, apptEndDate) { 2984 // Appt passed in for cancel/failure. Restore the start and endDates 2985 if (appt) { 2986 appt.setStartDate(apptStartDate); 2987 appt.setEndDate(apptEndDate); 2988 appt.resetRepeatWeeklyDays(); 2989 } 2990 if (callback) callback.run(); 2991 appCtxt.getShell().setBusy(false); 2992 2993 2994 }; 2995 2996 ZmCalViewController.prototype._handleResponseUpdateApptDateIgnore = 2997 function(appt, viewMode, startDateOffset, endDateOffset, callback, errorCallback, result) { 2998 this._refreshAction(true); 2999 if (callback) callback.run(result); 3000 }; 3001 3002 /** 3003 * Gets the day tool tip text. 3004 * 3005 * @param {Date} date the date 3006 * @param {Boolean} noheader if <code>true</code>, do not include tool tip header 3007 * @param {AjxCallback} callback the callback 3008 * @param {Boolean} getSimpleToolTip if <code>true</code>,show only plain text in tooltip for all day events. 3009 * Multi day "appts/allday events" would be shown by just one entry showing final start/end date&time. 3010 */ 3011 ZmCalViewController.prototype.getDayToolTipText = 3012 function(date, noheader, callback, isMinical, getSimpleToolTip) { 3013 try { 3014 var start = new Date(date.getTime()); 3015 start.setHours(0, 0, 0, 0); 3016 var startTime = start.getTime(); 3017 var end = start.getTime() + AjxDateUtil.MSEC_PER_DAY; 3018 var params = {start:startTime, end:end, fanoutAllDay:true}; 3019 if(callback) { 3020 params.callback = new AjxCallback(this, this._handleToolTipSearchResponse, [start, noheader, callback, isMinical]); 3021 this.getApptSummaries(params); 3022 } else { 3023 var result = this.getApptSummaries(params); 3024 return ZmApptViewHelper.getDayToolTipText(start, result, this, noheader, null, isMinical, getSimpleToolTip); 3025 } 3026 } catch (ex) { 3027 DBG.println(ex); 3028 return "<b>" + ZmMsg.errorGettingAppts + "</b>"; 3029 } 3030 }; 3031 3032 ZmCalViewController.prototype._handleToolTipSearchResponse = 3033 function(start, noheader, callback, isMinical, result) { 3034 try { 3035 var tooltip = ZmApptViewHelper.getDayToolTipText(start, result, this, noheader, null, isMinical); 3036 callback.run(tooltip); 3037 } catch (ex) { 3038 DBG.println(ex); 3039 callback.run("<b>" + ZmMsg.errorGettingAppts + "</b>"); 3040 } 3041 }; 3042 3043 ZmCalViewController.prototype.getUserStatusToolTipText = 3044 function(start, end, noheader, email, emptyMsg, calIds) { 3045 try { 3046 if(!calIds) { 3047 calIds = []; 3048 if (this._calTreeController) { 3049 var calendars = this._calTreeController.getOwnedCalendars(this._app.getOverviewId(),email); 3050 for (var i = 0; i < calendars.length; i++) { 3051 var cal = calendars[i]; 3052 if (cal && (cal.nId != ZmFolder.ID_TRASH)) { 3053 calIds.push(appCtxt.multiAccounts ? cal.id : cal.nId); 3054 } 3055 } 3056 } 3057 3058 if ((calIds.length == 0) || !email) { 3059 return "<b>" + ZmMsg.statusFree + "</b>"; 3060 } 3061 } 3062 3063 var startTime = start.getTime(); 3064 var endTime = end.getTime(); 3065 3066 var dayStart = new Date(start.getTime()); 3067 dayStart.setHours(0, 0, 0, 0); 3068 3069 var dayEnd = new Date(dayStart.getTime() + AjxDateUtil.MSEC_PER_DAY); 3070 3071 // to avoid frequent request to server we cache the appt for the entire 3072 // day first before getting the appts for selected time interval 3073 this.getApptSummaries({start:dayStart.getTime(), end:dayEnd.getTime(), fanoutAllDay:true, folderIds: calIds}); 3074 3075 var result = this.getApptSummaries({start:startTime, end:endTime, fanoutAllDay:true, folderIds: calIds}); 3076 3077 return ZmApptViewHelper.getDayToolTipText(start, result, this, noheader, emptyMsg); 3078 } catch (ex) { 3079 DBG.println(ex); 3080 return "<b>" + ZmMsg.meetingStatusUnknown + "</b>"; 3081 } 3082 }; 3083 3084 ZmCalViewController.prototype._miniCalDateRangeListener = 3085 function(ev) { 3086 this._scheduleMaintenance(ZmCalViewController.MAINT_MINICAL); 3087 }; 3088 3089 ZmCalViewController.prototype._dateRangeListener = 3090 function(ev) { 3091 ev.item.setNeedsRefresh(true); 3092 this._scheduleMaintenance(ZmCalViewController.MAINT_VIEW); 3093 }; 3094 3095 ZmCalViewController.prototype.processInlineCalSearch = 3096 function() { 3097 var srchResponse = window.inlineCalSearchResponse; 3098 if (!srchResponse) { return; } 3099 3100 var params = srchResponse.search; 3101 var response = srchResponse.Body; 3102 3103 if (params instanceof Array) { 3104 params = params[0]; 3105 } 3106 3107 var viewId = this._currentViewId || this.getDefaultViewType(); 3108 var fanoutAllDay = (viewId == ZmId.VIEW_CAL_MONTH); 3109 var searchParams = {start: params.s, end: params.e, fanoutAllDay: fanoutAllDay, callback: null}; 3110 searchParams.folderIds = params.l ? params.l.split(",") : []; 3111 searchParams.query = ""; 3112 3113 var miniCalParams = this.getMiniCalendarParams(); 3114 miniCalParams.folderIds = searchParams.folderIds; 3115 3116 this.apptCache.setSearchParams(searchParams); 3117 this.apptCache.processBatchResponse(response.BatchResponse, searchParams, miniCalParams); 3118 }; 3119 3120 ZmCalViewController.prototype.setCurrentView = 3121 function(view) { 3122 // do nothing 3123 }; 3124 3125 ZmCalViewController.prototype._resetNavToolBarButtons = 3126 function(view) { 3127 this._navToolBar[ZmId.VIEW_CAL].enable([ZmOperation.PAGE_BACK, ZmOperation.PAGE_FORWARD], true); 3128 }; 3129 3130 ZmCalViewController.prototype._resetToolbarOperations = 3131 function(viewId) { 3132 ZmListController.prototype._resetToolbarOperations.call(this); 3133 }; 3134 3135 ZmCalViewController.prototype._setNavToolbarPosition = 3136 function(navToolbar, currentViewName) { 3137 if(!navToolbar || !currentViewName) { return; } 3138 navToolbar.setVisible(currentViewName != ZmId.VIEW_CAL_LIST); 3139 }; 3140 3141 ZmCalViewController.prototype._resetOperations = 3142 function(parent, num) { 3143 if (!parent) return; 3144 3145 parent.enableAll(true); 3146 var currViewName = this._viewMgr.getCurrentViewName(); 3147 parent.enable(ZmOperation.TAG_MENU, appCtxt.get(ZmSetting.TAGGING_ENABLED) && !appCtxt.isWebClientOffline()); 3148 3149 if (currViewName == ZmId.VIEW_CAL_LIST && num > 1) { return; } 3150 3151 this._setNavToolbarPosition(this._navToolBar[ZmId.VIEW_CAL], currViewName); 3152 3153 var appt = this.getSelection()[0]; 3154 var calendar = appt && appt.getFolder(); 3155 var isTrash = calendar && calendar.nId == ZmOrganizer.ID_TRASH; 3156 num = ( isTrash && this.getCurrentListView() ) ? this.getCurrentListView().getSelectionCount() : num ; 3157 var isReadOnly = calendar ? calendar.isReadOnly() : false; 3158 var isSynced = Boolean(calendar && calendar.url); 3159 var isShared = calendar ? calendar.isRemote() : false; 3160 var disabled = isSynced || isReadOnly || (num == 0) || appCtxt.isWebClientOffline(); 3161 var isPrivate = appt && appt.isPrivate() && calendar.isRemote() && !calendar.hasPrivateAccess(); 3162 var isForwardable = !isTrash && calendar && !calendar.isReadOnly() && appCtxt.get(ZmSetting.GROUP_CALENDAR_ENABLED) && !appCtxt.isWebClientOffline(); 3163 var isReplyable = !isTrash && appt && (num == 1) && !appCtxt.isWebClientOffline(); 3164 var isTrashMultiple = isTrash && (num && num>1); 3165 3166 parent.enable([ZmOperation.REPLY, ZmOperation.REPLY_ALL], (isReplyable && !isTrashMultiple)); 3167 parent.enable(ZmOperation.TAG_MENU, ((!isReadOnly && !isSynced && num > 0) || isTrashMultiple) && !appCtxt.isWebClientOffline()); 3168 parent.enable(ZmOperation.VIEW_APPOINTMENT, !isPrivate && !isTrashMultiple); 3169 parent.enable([ZmOperation.FORWARD_APPT, ZmOperation.FORWARD_APPT_INSTANCE, ZmOperation.FORWARD_APPT_SERIES], isForwardable 3170 && !isTrashMultiple && !appCtxt.isWebClientOffline()); 3171 parent.enable(ZmOperation.PROPOSE_NEW_TIME, !isTrash && (appt && !appt.isOrganizer()) && !isTrashMultiple && !appCtxt.isWebClientOffline()); 3172 parent.enable(ZmOperation.SHOW_ORIG, num == 1 && appt && appt.getRestUrl() != null && !isTrashMultiple && !appCtxt.isWebClientOffline()); 3173 3174 parent.enable([ZmOperation.DELETE, ZmOperation.MOVE, ZmOperation.MOVE_MENU], !disabled || isTrashMultiple); 3175 3176 parent.enable(ZmOperation.VIEW_APPT_INSTANCE,!isTrash); 3177 3178 var apptAccess = ((appt && appt.isPrivate() && calendar.isRemote()) ? calendar.hasPrivateAccess() : true ); 3179 parent.enable(ZmOperation.DUPLICATE_APPT,apptAccess && !isTrashMultiple && this._app.containsWritableFolder() && !appCtxt.isWebClientOffline()); 3180 parent.enable(ZmOperation.SHOW_ORIG,apptAccess && !isTrashMultiple && !appCtxt.isWebClientOffline()); 3181 3182 /*if (currViewName == ZmId.VIEW_CAL_LIST) { 3183 parent.enable(ZmOperation.PRINT_CALENDAR, num > 0); 3184 } */ 3185 parent.enable(ZmOperation.PRINT_CALENDAR, !appCtxt.isWebClientOffline()); 3186 3187 // disable button for current view 3188 var op = ZmCalViewController.VIEW_TO_OP[currViewName]; 3189 // setSelected on a Toolbar; Do nothing for an ActionMenu 3190 if (op && parent.setSelected) { 3191 parent.setSelected(op); 3192 } 3193 3194 if (appCtxt.isWebClientOffline()) { 3195 // Disable the list view when offline 3196 //parent.enable(ZmCalViewController.VIEW_TO_OP[ZmOperation.CAL_LIST_VIEW], false); 3197 parent.enable(ZmOperation.CAL_LIST_VIEW, false); 3198 } 3199 3200 //this._resetQuickCommandOperations(parent); 3201 }; 3202 3203 ZmCalViewController.prototype._listSelectionListener = 3204 function(ev) { 3205 3206 ZmListController.prototype._listSelectionListener.call(this, ev); 3207 // to avoid conflicts on opening a readonly appointment in readonly view 3208 if (ev.detail == DwtListView.ITEM_SELECTED) { 3209 this._viewMgr.getCurrentView()._apptSelected(); 3210 } else if (ev.detail == DwtListView.ITEM_DBL_CLICKED) { 3211 var appt = ev.item; 3212 if (appt.isPrivate() && appt.getFolder().isRemote() && !appt.getFolder().hasPrivateAccess()) { 3213 var msgDialog = appCtxt.getMsgDialog(); 3214 msgDialog.setMessage(ZmMsg.apptIsPrivate, DwtMessageDialog.INFO_STYLE); 3215 msgDialog.popup(); 3216 } else { 3217 // open a appointment view 3218 this._apptIndexShowing = this._list.indexOf(appt); 3219 this._apptFromView = this._viewMgr.getCurrentView(); 3220 this._showAppointmentDetails(appt); 3221 } 3222 } 3223 }; 3224 3225 ZmCalViewController.prototype.getViewMgr = 3226 function(){ 3227 return this._viewMgr; 3228 } 3229 3230 ZmCalViewController.prototype._handleMenuViewAction = 3231 function(ev) { 3232 var actionMenu = this.getActionMenu(); 3233 var appt = actionMenu.__appt; 3234 delete actionMenu.__appt; 3235 3236 var orig = appt.getOrig(); 3237 appt = orig && orig.isMultiDay() ? orig : appt; 3238 var calendar = appt.getFolder(); 3239 var isTrash = calendar && calendar.nId == ZmOrganizer.ID_TRASH; 3240 var isSynced = Boolean(calendar.url); 3241 var mode = ZmCalItem.MODE_EDIT; 3242 var menuItem = ev.item; 3243 var menu = menuItem.parent; 3244 var id = menu.getData(ZmOperation.KEY_ID); 3245 switch(id) { 3246 case ZmOperation.VIEW_APPT_INSTANCE: mode = ZmCalItem.MODE_EDIT_SINGLE_INSTANCE; break; 3247 case ZmOperation.VIEW_APPT_SERIES: mode = ZmCalItem.MODE_EDIT_SERIES; break; 3248 } 3249 3250 if (appt.isReadOnly() || isSynced || isTrash || appCtxt.isWebClientOffline()) { 3251 var clone = ZmAppt.quickClone(appt); 3252 var callback = new AjxCallback(this, this._showApptReadOnlyView, [clone, mode]); 3253 clone.getDetails(mode, callback, this._errorCallback); 3254 } else { 3255 this.editAppointment(appt, mode); 3256 } 3257 }; 3258 3259 ZmCalViewController.prototype._handleApptRespondAction = 3260 function(ev) { 3261 var appt = this.getSelection()[0]; 3262 if(!appt){return;} 3263 var type = ev.item.getData(ZmOperation.KEY_ID); 3264 var op = ev.item.parent.getData(ZmOperation.KEY_ID); 3265 var respCallback = new AjxCallback(this, this._handleResponseHandleApptRespondAction, [appt, type, op, null]); 3266 appt.getDetails(null, respCallback, this._errorCallback); 3267 }; 3268 3269 ZmCalViewController.prototype._handleResponseHandleApptRespondAction = 3270 function(appt, type, op, callback) { 3271 var msgController = this._getMsgController(); 3272 msgController.setMsg(appt.message); 3273 // poke the msgController 3274 var instanceDate = op == ZmOperation.VIEW_APPT_INSTANCE ? new Date(appt.uniqStartTime) : null; 3275 3276 if(type == ZmOperation.REPLY_DECLINE) { 3277 var promptCallback = new AjxCallback(this, this._sendInviteReply, [type, instanceDate]); 3278 this._promptDeclineNotify(appt, promptCallback, callback); 3279 }else { 3280 msgController._sendInviteReply(type, appt.compNum || 0, instanceDate, appt.getRemoteFolderOwner(), false, null, null, callback); 3281 } 3282 }; 3283 3284 ZmCalViewController.prototype._promptDeclineNotify = 3285 function(appt, promptCallback, callback) { 3286 if (!this._declineNotifyDialog) { 3287 var msg = ZmMsg.confirmDeclineAppt; 3288 this._declineNotifyDialog = new ZmApptDeleteNotifyDialog({ 3289 parent: this._shell, 3290 title: AjxMsg.confirmTitle, 3291 confirmMsg: msg, 3292 choiceLabel1: ZmMsg.dontNotifyOrganizer, 3293 choiceLabel2 : ZmMsg.notifyOrganizer 3294 }); 3295 } 3296 this._declineNotifyDialog.popup(new AjxCallback(this, this._declineNotifyYesCallback, [appt, promptCallback, callback])); 3297 }; 3298 3299 ZmCalViewController.prototype._declineNotifyYesCallback = 3300 function(appt, promptCallback, callback) { 3301 var notifyOrg = !this._declineNotifyDialog.isDefaultOptionChecked(); 3302 if(promptCallback) promptCallback.run(appt, notifyOrg, callback); 3303 }; 3304 3305 3306 ZmCalViewController.prototype._sendInviteReply = 3307 function(type, instanceDate, appt, notifyOrg, callback) { 3308 var msgController = this._getMsgController(); 3309 msgController._sendInviteReply(type, appt.compNum || 0, instanceDate, appt.getRemoteFolderOwner(), !notifyOrg, null, null, callback); 3310 }; 3311 3312 ZmCalViewController.prototype._handleApptEditRespondAction = 3313 function(ev) { 3314 var appt = this.getSelection()[0]; 3315 var id = ev.item.getData(ZmOperation.KEY_ID); 3316 var op = ev.item.parent.parent.parent.getData(ZmOperation.KEY_ID); 3317 var respCallback = new AjxCallback(this, this._handleResponseHandleApptEditRespondAction, [appt, id, op]); 3318 appt.getDetails(null, respCallback, this._errorCallback); 3319 }; 3320 3321 ZmCalViewController.prototype._handleResponseHandleApptEditRespondAction = 3322 function(appt, id, op) { 3323 var msgController = this._getMsgController(); 3324 var msg = appt.message; 3325 msg.subject = msg.subject || appt.name; 3326 if (!msg.getAddress(AjxEmailAddress.REPLY_TO)) { 3327 msg.setAddress(AjxEmailAddress.REPLY_TO, new AjxEmailAddress(appt.organizer)); 3328 } 3329 msgController.setMsg(msg); 3330 3331 // poke the msgController 3332 switch (id) { 3333 case ZmOperation.EDIT_REPLY_ACCEPT: id = ZmOperation.REPLY_ACCEPT; break; 3334 case ZmOperation.EDIT_REPLY_DECLINE: id = ZmOperation.REPLY_DECLINE; break; 3335 case ZmOperation.EDIT_REPLY_TENTATIVE: id = ZmOperation.REPLY_TENTATIVE; break; 3336 } 3337 var instanceDate = op == ZmOperation.VIEW_APPT_INSTANCE ? new Date(appt.uniqStartTime) : null; 3338 msgController._editInviteReply(id, 0, instanceDate, appt.getRemoteFolderOwner()); 3339 }; 3340 3341 ZmCalViewController.prototype._handleError = 3342 function(ex) { 3343 if (ex.code == 'mail.INVITE_OUT_OF_DATE' || ex.code == 'mail.NO_SUCH_APPT') { 3344 var msgDialog = appCtxt.getMsgDialog(); 3345 msgDialog.registerCallback(DwtDialog.OK_BUTTON, this._handleError2, this, [msgDialog]); 3346 msgDialog.setMessage(ZmMsg.apptOutOfDate, DwtMessageDialog.INFO_STYLE); 3347 msgDialog.popup(); 3348 return true; 3349 } 3350 return false; 3351 }; 3352 3353 ZmCalViewController.prototype._handleError2 = 3354 function(msgDialog) { 3355 msgDialog.unregisterCallback(DwtDialog.OK_BUTTON); 3356 msgDialog.popdown(); 3357 this._refreshAction(false); 3358 }; 3359 3360 ZmCalViewController.prototype._initCalViewMenu = 3361 function(menu) { 3362 for (var i = 0; i < ZmCalViewController.OPS.length; i++) { 3363 var op = ZmCalViewController.OPS[i]; 3364 menu.addSelectionListener(op, this._listeners[op]); 3365 menu.enable(op, ((op != ZmOperation.CAL_LIST_VIEW) || !appCtxt.isWebClientOffline())); 3366 } 3367 }; 3368 3369 /** 3370 * Overrides ZmListController.prototype._getViewActionMenuOps 3371 * 3372 * @private 3373 */ 3374 ZmCalViewController.prototype._getViewActionMenuOps = 3375 function () { 3376 if(this._app.containsWritableFolder()) { 3377 return [ZmOperation.NEW_APPT, ZmOperation.NEW_ALLDAY_APPT, 3378 ZmOperation.SEP, 3379 ZmOperation.TODAY, ZmOperation.CAL_VIEW_MENU]; 3380 } 3381 else { 3382 return [ZmOperation.TODAY, 3383 ZmOperation.CAL_VIEW_MENU]; 3384 } 3385 }; 3386 3387 /** 3388 * Overrides ZmListController.prototype._initializeActionMenu 3389 * 3390 * @private 3391 */ 3392 ZmCalViewController.prototype._initializeActionMenu = 3393 function() { 3394 this._actionMenu = this._createActionMenu(this._getActionMenuOps()); 3395 if (appCtxt.get(ZmSetting.TAGGING_ENABLED)) { 3396 this._setupTagMenu(this._actionMenu); 3397 } 3398 3399 var recurrenceModeOps = this._getRecurrenceModeOps(); 3400 var params = {parent: this._shell, menuItems: recurrenceModeOps}; 3401 this._recurrenceModeActionMenu = new ZmActionMenu(params); 3402 for (var i = 0; i < recurrenceModeOps.length; i++) { 3403 var recurrenceMode = recurrenceModeOps[i]; 3404 var modeItem = this._recurrenceModeActionMenu.getMenuItem(recurrenceMode); 3405 var menuOpsForMode = this._getActionMenuOps(recurrenceMode); 3406 var actionMenuForMode = this._createActionMenu(menuOpsForMode, modeItem, recurrenceMode); 3407 if (appCtxt.get(ZmSetting.TAGGING_ENABLED)) { 3408 this._setupTagMenu(actionMenuForMode); 3409 } 3410 modeItem.setMenu(actionMenuForMode); 3411 // NOTE: Target object for listener is menu item 3412 var menuItemListener = this._recurrenceModeMenuPopup.bind(modeItem); 3413 modeItem.addListener(AjxEnv.isIE ? DwtEvent.ONMOUSEENTER : DwtEvent.ONMOUSEOVER, menuItemListener); 3414 } 3415 this._recurrenceModeActionMenu.addPopdownListener(this._menuPopdownListener); 3416 }; 3417 3418 ZmCalViewController.prototype._createActionMenu = 3419 function(menuItems, parentMenuItem, context) { 3420 var params = {parent: parentMenuItem || this._shell, menuItems: menuItems, context: this._getMenuContext() + (context ? "_" + context : "")}; 3421 var actionMenu = new ZmActionMenu(params); 3422 menuItems = actionMenu.opList; 3423 for (var i = 0; i < menuItems.length; i++) { 3424 var menuItem = menuItems[i]; 3425 if (menuItem == ZmOperation.INVITE_REPLY_MENU) { 3426 var menu = actionMenu.getOp(ZmOperation.INVITE_REPLY_MENU).getMenu(); 3427 menu.addSelectionListener(ZmOperation.EDIT_REPLY_ACCEPT, this._listeners[ZmOperation.EDIT_REPLY_ACCEPT]); 3428 menu.addSelectionListener(ZmOperation.EDIT_REPLY_TENTATIVE, this._listeners[ZmOperation.EDIT_REPLY_TENTATIVE]); 3429 menu.addSelectionListener(ZmOperation.EDIT_REPLY_DECLINE, this._listeners[ZmOperation.EDIT_REPLY_DECLINE]); 3430 } else if (menuItem == ZmOperation.CAL_VIEW_MENU) { 3431 var menu = actionMenu.getOp(ZmOperation.CAL_VIEW_MENU).getMenu(); 3432 this._initCalViewMenu(menu); 3433 } 3434 if (this._listeners[menuItem]) { 3435 actionMenu.addSelectionListener(menuItem, this._listeners[menuItem]); 3436 } 3437 } 3438 actionMenu.addPopdownListener(this._menuPopdownListener); 3439 return actionMenu; 3440 }; 3441 3442 /** 3443 * The <code>this</code> in this method is the menu item. 3444 * 3445 * @private 3446 */ 3447 ZmCalViewController.prototype._recurrenceModeMenuPopup = 3448 function(ev) { 3449 if (!this.getEnabled()) { return; } 3450 3451 var menu = this.getMenu(); 3452 var opId = this.getData(ZmOperation.KEY_ID); 3453 menu.setData(ZmOperation.KEY_ID, opId); 3454 }; 3455 3456 /** 3457 * Overrides ZmListController.prototype._getActionMenuOptions 3458 * 3459 * @private 3460 */ 3461 ZmCalViewController.prototype._getActionMenuOps = 3462 function(recurrenceMode) { 3463 3464 var deleteOp = ZmOperation.DELETE; 3465 var viewOp = ZmOperation.VIEW_APPOINTMENT; 3466 var forwardOp = ZmOperation.FORWARD_APPT; 3467 3468 if (recurrenceMode == ZmOperation.VIEW_APPT_INSTANCE) { 3469 deleteOp = ZmOperation.DELETE_INSTANCE; 3470 viewOp = ZmOperation.OPEN_APPT_INSTANCE; 3471 forwardOp = ZmOperation.FORWARD_APPT_INSTANCE; 3472 } else if (recurrenceMode == ZmOperation.VIEW_APPT_SERIES) { 3473 deleteOp = ZmOperation.DELETE_SERIES; 3474 viewOp = ZmOperation.OPEN_APPT_SERIES; 3475 forwardOp = ZmOperation.FORWARD_APPT_SERIES; 3476 } 3477 3478 var retVal = [viewOp, 3479 ZmOperation.PRINT, 3480 ZmOperation.SEP, 3481 ZmOperation.REPLY_ACCEPT, 3482 ZmOperation.REPLY_TENTATIVE, 3483 ZmOperation.REPLY_DECLINE, 3484 ZmOperation.INVITE_REPLY_MENU, 3485 ZmOperation.PROPOSE_NEW_TIME, 3486 ZmOperation.REINVITE_ATTENDEES, 3487 ZmOperation.DUPLICATE_APPT, 3488 ZmOperation.SEP, 3489 ZmOperation.REPLY, 3490 ZmOperation.REPLY_ALL, 3491 forwardOp, 3492 deleteOp, 3493 ZmOperation.SEP]; 3494 if (recurrenceMode != ZmOperation.VIEW_APPT_INSTANCE) { 3495 retVal.push(ZmOperation.MOVE); 3496 } 3497 retVal.push(ZmOperation.TAG_MENU); 3498 retVal.push(ZmOperation.SHOW_ORIG); 3499 //retVal.push(ZmOperation.QUICK_COMMANDS); 3500 return retVal; 3501 }; 3502 3503 ZmCalViewController.prototype._getRecurrenceModeOps = 3504 function() { 3505 return [ZmOperation.VIEW_APPT_INSTANCE, ZmOperation.VIEW_APPT_SERIES]; 3506 }; 3507 3508 ZmCalViewController.prototype._enableActionMenuReplyOptions = 3509 function(appt, actionMenu) { 3510 3511 if (!(appt && actionMenu)) { 3512 return; 3513 } 3514 var isExternalAccount = appCtxt.isExternalAccount(); 3515 var isFolderReadOnly = appt.isFolderReadOnly(); 3516 var isShared = appt.isShared(); 3517 3518 //Note - isOrganizer means the Calendar is the organizer of the appt. i.e. the appointment was created on this calendar. 3519 //This could be "true" even if the current user is just a sharee of the calendar. 3520 var isTheCalendarOrganizer = appt.isOrganizer(); 3521 3522 // find the checked calendar for this appt 3523 var calendar; 3524 var folderId = appt.getLocalFolderId(); 3525 var calendars = this.getCheckedCalendars(true); 3526 for (var i = 0; i < calendars.length; i++) { 3527 if (calendars[i].id == folderId) { 3528 calendar = calendars[i]; 3529 break; 3530 } 3531 } 3532 //bug:68452 if its a trash folder then its not present in the calendars array 3533 if (!calendar){ 3534 calendar = appt.getFolder(); 3535 } 3536 var share = calendar && calendar.link ? calendar.getMainShare() : null; 3537 var workflow = share ? share.isWorkflow() : true; 3538 var isTrash = calendar && calendar.nId == ZmOrganizer.ID_TRASH; 3539 var isPrivate = appt.isPrivate() && calendar.isRemote() && !calendar.hasPrivateAccess(); 3540 var isReplyable = !isTrash && appt.otherAttendees; 3541 var isForwardable = !isTrash && calendar && !calendar.isReadOnly() && appCtxt.get(ZmSetting.GROUP_CALENDAR_ENABLED); 3542 3543 //don't show for organizer calendar, or readOnly attendee calendar. 3544 var showAcceptDecline = !isTheCalendarOrganizer && !isFolderReadOnly; 3545 actionMenu.setItemVisible(ZmOperation.REPLY_ACCEPT, showAcceptDecline); 3546 actionMenu.setItemVisible(ZmOperation.REPLY_DECLINE, showAcceptDecline); 3547 actionMenu.setItemVisible(ZmOperation.REPLY_TENTATIVE, showAcceptDecline); 3548 actionMenu.setItemVisible(ZmOperation.INVITE_REPLY_MENU, showAcceptDecline); 3549 actionMenu.setItemVisible(ZmOperation.PROPOSE_NEW_TIME, showAcceptDecline); 3550 actionMenu.setItemVisible(ZmOperation.REINVITE_ATTENDEES, !isTrash && isTheCalendarOrganizer && !isFolderReadOnly && !appt.inviteNeverSent && appt.otherAttendees); 3551 actionMenu.setItemVisible(ZmOperation.TAG_MENU, appCtxt.get(ZmSetting.TAGGING_ENABLED)); 3552 3553 // Initially enabling all the options in the action menu. And then selectively disabling unsupported options for special users. 3554 actionMenu.enableAll(true); 3555 3556 //enable/disable specific actions, only if we actually show them. (we don't show for organizer or shared calendar) 3557 if (showAcceptDecline) { 3558 var enabled = isReplyable && workflow && !isPrivate && !isExternalAccount; 3559 actionMenu.enable(ZmOperation.REPLY_ACCEPT, enabled && appt.ptst != ZmCalBaseItem.PSTATUS_ACCEPT); 3560 actionMenu.enable(ZmOperation.REPLY_DECLINE, enabled && appt.ptst != ZmCalBaseItem.PSTATUS_DECLINED); 3561 actionMenu.enable(ZmOperation.REPLY_TENTATIVE, enabled && appt.ptst != ZmCalBaseItem.PSTATUS_TENTATIVE); 3562 actionMenu.enable(ZmOperation.INVITE_REPLY_MENU, enabled); 3563 // edit reply menu 3564 var mi = enabled && actionMenu.getMenuItem(ZmOperation.INVITE_REPLY_MENU); 3565 var replyMenu = mi && mi.getMenu(); 3566 if (replyMenu) { 3567 replyMenu.enable(ZmOperation.EDIT_REPLY_ACCEPT, appt.ptst != ZmCalBaseItem.PSTATUS_ACCEPT); 3568 replyMenu.enable(ZmOperation.EDIT_REPLY_DECLINE, appt.ptst != ZmCalBaseItem.PSTATUS_DECLINED); 3569 replyMenu.enable(ZmOperation.EDIT_REPLY_TENTATIVE, appt.ptst != ZmCalBaseItem.PSTATUS_TENTATIVE); 3570 } 3571 } 3572 3573 actionMenu.enable([ZmOperation.FORWARD_APPT, ZmOperation.FORWARD_APPT_INSTANCE, ZmOperation.FORWARD_APPT_SERIES], isForwardable); 3574 var myEmail = appt.getFolder().getAccount().getEmail(); 3575 //the last condition in the following is for the somewhat corner case user1 invites user2, and user1 is looking at user2's calendar as a share. 3576 actionMenu.enable(ZmOperation.REPLY, isReplyable && !isTheCalendarOrganizer && myEmail !== appt.organizer); //the organizer can't reply just to himself 3577 actionMenu.enable(ZmOperation.REPLY_ALL, isReplyable); 3578 3579 var disabledOps; 3580 if(appCtxt.isWebClientOffline()) { 3581 disabledOps = [ZmOperation.REINVITE_ATTENDEES, 3582 ZmOperation.PROPOSE_NEW_TIME, 3583 ZmOperation.REPLY, 3584 ZmOperation.REPLY_ALL, 3585 ZmOperation.DUPLICATE_APPT, 3586 ZmOperation.DELETE, 3587 ZmOperation.DELETE_INSTANCE, 3588 ZmOperation.DELETE_SERIES, 3589 ZmOperation.FORWARD_APPT, 3590 ZmOperation.FORWARD_APPT_INSTANCE, 3591 ZmOperation.FORWARD_APPT_SERIES, 3592 ZmOperation.SHOW_ORIG, 3593 ZmOperation.DUPLICATE_APPT, 3594 ZmOperation.MOVE, 3595 ZmOperation.PRINT, 3596 ZmOperation.TAG_MENU, 3597 ZmOperation.MOVE_MENU]; 3598 actionMenu.enable(disabledOps, false); 3599 } else { 3600 if(isExternalAccount) { 3601 disabledOps = [ZmOperation.REINVITE_ATTENDEES, 3602 ZmOperation.PROPOSE_NEW_TIME, 3603 ZmOperation.REPLY, 3604 ZmOperation.REPLY_ALL, 3605 ZmOperation.DUPLICATE_APPT, 3606 ZmOperation.DELETE, 3607 ZmOperation.DELETE_INSTANCE, 3608 ZmOperation.DELETE_SERIES]; 3609 3610 actionMenu.enable(disabledOps, false); 3611 } 3612 3613 // bug:71007 Disabling unsupported options for shared calendar with view only rights 3614 if (isFolderReadOnly) { 3615 disabledOps = [ZmOperation.REINVITE_ATTENDEES, 3616 ZmOperation.PROPOSE_NEW_TIME, 3617 ZmOperation.DELETE, 3618 ZmOperation.DELETE_INSTANCE, 3619 ZmOperation.DELETE_SERIES, 3620 ZmOperation.MOVE, 3621 ZmOperation.TAG_MENU, 3622 ZmOperation.MOVE_MENU]; 3623 3624 actionMenu.enable(disabledOps, false); 3625 } 3626 } 3627 3628 var del = actionMenu.getMenuItem(ZmOperation.DELETE); 3629 if (del && !isTrash) { 3630 del.setText((isTheCalendarOrganizer && appt.otherAttendees) ? ZmMsg.cancel : ZmMsg.del); 3631 var isSynced = Boolean(calendar.url); 3632 del.setEnabled(!calendar.isReadOnly() && !isSynced && !isPrivate && !appCtxt.isWebClientOffline()); 3633 } 3634 3635 // recurring action menu options 3636 if (this._recurrenceModeActionMenu && !isTrash) { 3637 this._recurrenceModeActionMenu.enable(ZmOperation.VIEW_APPT_SERIES, !appt.exception); 3638 } 3639 }; 3640 3641 ZmCalViewController.prototype._listActionListener = 3642 function(ev) { 3643 ZmListController.prototype._listActionListener.call(this, ev); 3644 var count = this.getSelectionCount(); 3645 var appt = ev.item; 3646 var actionMenu = this.getActionMenu(); 3647 actionMenu.enableAll(count === 1); 3648 if (count > 1) { 3649 var isExternalAccount = appCtxt.isExternalAccount(); 3650 var isFolderReadOnly = appt.isFolderReadOnly(); 3651 var enabled = !isExternalAccount && !isFolderReadOnly; 3652 actionMenu.enable([ZmOperation.TAG_MENU, ZmOperation.MOVE_MENU, ZmOperation.MOVE, ZmOperation.DELETE], enabled); 3653 actionMenu.popup(0, ev.docX, ev.docY); 3654 return; 3655 } 3656 var calendar = appt && appt.getFolder(); 3657 var isTrash = calendar && calendar.nId == ZmOrganizer.ID_TRASH; 3658 var menu = (appt.isRecurring() && !appt.isException && !isTrash) ? this._recurrenceModeActionMenu : actionMenu; 3659 this._enableActionMenuReplyOptions(appt, menu); 3660 var op = (menu == actionMenu) && appt.exception ? ZmOperation.VIEW_APPT_INSTANCE : null; 3661 actionMenu.__appt = appt; 3662 menu.setData(ZmOperation.KEY_ID, op); 3663 3664 if (appt.isRecurring() && !appt.isException && !isTrash) { 3665 var menuItem = menu.getMenuItem(ZmOperation.VIEW_APPT_INSTANCE); 3666 this._setTagMenu(menuItem.getMenu()); 3667 this._enableActionMenuReplyOptions(appt, menuItem.getMenu()); 3668 menuItem = menu.getMenuItem(ZmOperation.VIEW_APPT_SERIES); 3669 this._setTagMenu(menuItem.getMenu()); 3670 this._enableActionMenuReplyOptions(appt, menuItem.getMenu()); 3671 } 3672 menu.popup(0, ev.docX, ev.docY); 3673 }; 3674 3675 ZmCalViewController.prototype._clearViewActionMenu = 3676 function() { 3677 // This will cause it to regenerate next time its accessed 3678 this._viewActionMenu = null; 3679 } 3680 3681 ZmCalViewController.prototype._viewActionListener = 3682 function(ev) { 3683 if (!this._viewActionMenu) { 3684 var menuItems = this._getViewActionMenuOps(); 3685 if (!menuItems) return; 3686 var overrides = {}; 3687 overrides[ZmOperation.TODAY] = {textKey:"todayGoto"}; 3688 var params = {parent:this._shell, menuItems:menuItems, overrides:overrides}; 3689 this._viewActionMenu = new ZmActionMenu(params); 3690 menuItems = this._viewActionMenu.opList; 3691 for (var i = 0; i < menuItems.length; i++) { 3692 var menuItem = menuItems[i]; 3693 if (menuItem == ZmOperation.CAL_VIEW_MENU) { 3694 var menu = this._viewActionMenu.getOp(ZmOperation.CAL_VIEW_MENU).getMenu(); 3695 this._initCalViewMenu(menu); 3696 } else if (this._listeners[menuItem]) { 3697 this._viewActionMenu.addSelectionListener(menuItem, this._listeners[menuItem]); 3698 if ((menuItem == ZmOperation.NEW_APPT) || (menuItem == ZmOperation.NEW_ALLDAY_APPT)) { 3699 this._viewActionMenu.enable(menuItem, !appCtxt.isWebClientOffline()); 3700 } 3701 } 3702 } 3703 this._viewActionMenu.addPopdownListener(this._menuPopdownListener); 3704 } 3705 3706 if ( this._viewVisible && this._currentViewType == ZmId.VIEW_CAL_DAY) { 3707 var calId = ev.target.getAttribute(ZmCalDayTabView.ATTR_CAL_ID); 3708 if(!calId) { 3709 var view = this._viewMgr.getView(this._currentViewId); 3710 var gridLoc = Dwt.toWindow(ev.target, ev.elementX, ev.elementY, ev.target, true); 3711 var col = view._getColFromX(gridLoc.x); 3712 calId = (col && col.cal) ? col.cal.id : null; 3713 } 3714 this._viewActionMenu._calendarId = calId; 3715 } else { 3716 this._viewActionMenu._calendarId = null; 3717 } 3718 3719 this._viewActionMenu.__view = ev.item; 3720 this._viewActionMenu.popup(0, ev.docX, ev.docY); 3721 }; 3722 3723 ZmCalViewController.prototype._dropListener = 3724 function(ev) { 3725 var view = this._listView[this._currentViewId]; 3726 var div = view.getTargetItemDiv(ev.uiEvent); 3727 var item = div ? view.getItemFromElement(div) : null; 3728 3729 // only tags can be dropped on us *if* we are not readonly 3730 if (ev.action == DwtDropEvent.DRAG_ENTER) { 3731 if (item && item.type == ZmItem.APPT) { 3732 var calendar = item.getFolder(); 3733 var isReadOnly = calendar ? calendar.isReadOnly() : false; 3734 var isSynced = Boolean(calendar && calendar.url); 3735 if (isSynced || isReadOnly || appCtxt.isWebClientOffline()) { 3736 ev.doIt = false; // can't tag a GAL or shared contact 3737 view.dragSelect(div); 3738 return; 3739 } 3740 } 3741 } 3742 3743 ZmListController.prototype._dropListener.call(this, ev); 3744 }; 3745 3746 ZmCalViewController.prototype.sendRequest = 3747 function(soapDoc) { 3748 try { 3749 return appCtxt.getAppController().sendRequest({soapDoc: soapDoc}); 3750 } catch (ex) { 3751 // do nothing 3752 return null; 3753 } 3754 }; 3755 3756 /** 3757 * Caller is responsible for exception handling. caller should also not modify 3758 * appts in this list directly. 3759 * 3760 * @param {Hash} params a hash of parameters 3761 * @param {long} params.start the start time (in milliseconds) 3762 * @param {long} params.end the end time (in milliseconds) 3763 * @param {Boolean} params.fanoutAllDay if <code>true</code>, 3764 * @param {Array} params.folderIds the list of calendar folder Id's (null means use checked calendars in overview) 3765 * @param {AjxCallback} params.callback the callback triggered once search results are returned 3766 * @param {Boolean} params.noBusyOverlay if <code>true</code>, do not show veil during search 3767 * 3768 * @private 3769 */ 3770 ZmCalViewController.prototype.getApptSummaries = 3771 function(params) { 3772 if (!params.folderIds) { 3773 params.folderIds = this.getCheckedCalendarFolderIds(); 3774 } 3775 params.query = this._userQuery; 3776 3777 return this.apptCache.getApptSummaries(params); 3778 }; 3779 3780 ZmCalViewController.prototype.handleUserSearch = 3781 function(params, callback) { 3782 AjxDispatcher.require(["MailCore", "CalendarCore", "Calendar"]); 3783 //Calendar search is defaulted to list view when searched from the calendar app. 3784 if (params.origin === ZmId.SEARCH) { 3785 this.show(ZmId.VIEW_CAL_LIST, null, true); 3786 } 3787 else { 3788 this.show(null, null, true); 3789 } 3790 3791 this.apptCache.clearCache(); 3792 this._viewMgr.setNeedsRefresh(); 3793 this._userQuery = params.query; 3794 3795 // set start/end date boundaries 3796 var view = this._listView[this._currentViewId]; 3797 3798 if (view) { 3799 var rt = view.getTimeRange(); 3800 params.start = rt.start; 3801 params.end = rt.end; 3802 } else if (this._miniCalendar) { 3803 var calRange = this._miniCalendar.getDateRange(); 3804 params.start = calRange.start.getTime(); 3805 params.end = calRange.end.getTime(); 3806 } else { 3807 // TODO - generate start/end based on current month? 3808 } 3809 3810 params.fanoutAllDay = view && view._fanoutAllDay(); 3811 params.callback = this._searchResponseCallback.bind(this, callback); 3812 3813 this.getApptSummaries(params); 3814 }; 3815 3816 ZmCalViewController.prototype._searchResponseCallback = 3817 function(callback, list, userQuery, result) { 3818 3819 this.show(null, null, true); // always make sure a calendar view is being shown 3820 this._userQuery = userQuery; // cache the user-entered search query 3821 3822 this._maintGetApptCallback(ZmCalViewController.MAINT_VIEW, this._listView[this._currentViewId], list, true, userQuery); 3823 3824 if (callback) { 3825 callback.run(result); 3826 } 3827 }; 3828 3829 // TODO: appt is null for now. we are just clearing our caches... 3830 ZmCalViewController.prototype.notifyCreate = 3831 function(create) { 3832 // mark folders which needs to be cleared 3833 if (create && create.l) { 3834 var cal = appCtxt.getById(create.l); 3835 if (cal && cal.id) { 3836 this._clearCacheFolderMap[cal.id] = true; 3837 } 3838 } 3839 }; 3840 3841 ZmCalViewController.prototype.notifyDelete = 3842 function(ids) { 3843 if (this._modifyAppts) { 3844 var apptList = this._viewMgr.getSubContentView().getApptList(); 3845 for (var i = 0; i < ids.length; i++) { 3846 var appt = ZmCalViewController.__AjxVector_getById(apptList, ids[i]) 3847 if (appt) { 3848 apptList.remove(appt); 3849 this._modifyAppts.removes++; 3850 } 3851 } 3852 } 3853 3854 if (this._clearCache) { return; } 3855 3856 this._clearCache = this.apptCache.containsAnyId(ids); 3857 this.handleEditConflict(ids); 3858 }; 3859 3860 ZmCalViewController.prototype.handleEditConflict = 3861 function(ids) { 3862 //handling a case where appt is edited and related calendar is deleted 3863 if (appCtxt.getCurrentViewId() == ZmId.VIEW_APPOINTMENT) { 3864 var view = appCtxt.getCurrentView(); 3865 var appt = view.getAppt(true); 3866 var calendar = appt && appt.getFolder(); 3867 var idStr = ","+ ids+","; 3868 if (idStr.indexOf("," + calendar.id + ",") >= 0) { 3869 this._app.getApptComposeController()._closeView(); 3870 } 3871 } 3872 }; 3873 3874 ZmCalViewController.prototype.notifyModify = 3875 function(modifies) { 3876 var apptObjs = modifies.appt; 3877 if (apptObjs && this._viewMgr) { 3878 var listView = this._viewMgr.getSubContentView(); 3879 if (listView) { 3880 for (var i = 0; i < apptObjs.length; i++) { 3881 var apptObj = apptObjs[i]; 3882 // case 1: item moved *into* Trash 3883 if (apptObj.l == ZmOrganizer.ID_TRASH) { 3884 this._modifyAppts.adds++; 3885 ZmAppt.loadByUid(apptObj.uid, new AjxCallback(this, this._updateSubContent)); 3886 } 3887 // case 2: item moved *from* Trash 3888 else { 3889 var apptList = listView.getApptList(); 3890 var appt = ZmCalViewController.__AjxVector_getById(apptList, apptObj.id) 3891 if (appt) { 3892 apptList.remove(appt); 3893 this._modifyAppts.removes++; 3894 // TODO: was this appointment moved to a checked calendar w/in the specified range? 3895 } 3896 } 3897 // case 3: neither, ignore 3898 } 3899 } 3900 } 3901 3902 if (this._clearCache) { return; } 3903 3904 // if any of the ids are in the cache then... 3905 for (var name in modifies) { 3906 var list = modifies[name]; 3907 this._clearCache = this._clearCache || this.apptCache.containsAnyItem(list); 3908 } 3909 }; 3910 3911 ZmCalViewController.__AjxVector_getById = function(vector, id) { 3912 var array = vector.getArray(); 3913 for (var i = 0; i < array.length; i++) { 3914 var item = array[i]; 3915 if (item.id == id) return item; 3916 } 3917 return null; 3918 }; 3919 3920 ZmCalViewController.prototype._updateSubContent = function(appt) { 3921 if (appt) { 3922 var listView = this._viewMgr.getSubContentView(); 3923 if (listView) { 3924 if (!listView.hasItem(appt.id)) { 3925 listView.getApptList().add(appt); 3926 } 3927 listView.setNeedsRefresh(true); 3928 if (!this._modifyAppts || --this._modifyAppts.adds <= 0) { 3929 listView.refresh(); 3930 this._clearCache = true; 3931 this.refreshHandler(); 3932 } 3933 } 3934 } 3935 }; 3936 3937 // this gets called before all the above notify* methods get called 3938 ZmCalViewController.prototype.preNotify = function(notify) { 3939 DBG.println(AjxDebug.DBG2, "ZmCalViewController: preNotify"); 3940 this._modifyAppts = null; 3941 if (notify.deleted || (notify.modified && notify.modified.appt)) { 3942 var listView = this._viewMgr && this._viewMgr.getSubContentView(); 3943 if (listView) { 3944 this._modifyAppts = { adds: 0, removes:0 }; 3945 } 3946 } 3947 }; 3948 3949 // this gets called after all the above notify* methods get called 3950 ZmCalViewController.prototype.postNotify = 3951 function(notify) { 3952 DBG.println(AjxDebug.DBG2, "ZmCalViewController: postNotify: " + this._clearCache); 3953 3954 // update the trash list all at once 3955 if (this._modifyAppts) { 3956 var removes = this._modifyAppts.removes; 3957 var adds = this._modifyAppts.adds; 3958 if (removes || adds) { 3959 var listView = this._viewMgr.getSubContentView(); 3960 if (listView) { 3961 listView.setNeedsRefresh(true); 3962 listView.refresh(); 3963 this._clearCache = true; 3964 } 3965 } 3966 this._modifyAppts.removes = 0; 3967 } 3968 3969 var clearCacheByFolder = false; 3970 3971 for (var i in this._clearCacheFolderMap) { 3972 DBG.println("clear cache :" + i); 3973 this.apptCache.clearCache(i); 3974 clearCacheByFolder = true; 3975 } 3976 this._clearCacheFolderMap = {}; 3977 3978 //offline client makes batch request for each account configured causing 3979 //refresh overlap clearing calendar 3980 var timer = (appCtxt.isOffline && this.searchInProgress) ? 1000 : 0; 3981 3982 if (this._clearCache) { 3983 var act = new AjxTimedAction(this, this._refreshAction); 3984 AjxTimedAction.scheduleAction(act, timer); 3985 this._clearCache = false; 3986 } else if(clearCacheByFolder) { 3987 var act = new AjxTimedAction(this, this._refreshAction, [true]); 3988 AjxTimedAction.scheduleAction(act, timer); 3989 } 3990 }; 3991 3992 ZmCalViewController.prototype.setNeedsRefresh = 3993 function(refresh) { 3994 if (this._viewMgr != null && this._viewMgr.setNeedsRefresh) { 3995 this._viewMgr.setNeedsRefresh(refresh); 3996 } 3997 }; 3998 3999 // returns true if moving given appt from local to remote folder or vice versa 4000 ZmCalViewController.prototype.isMovingBetwAccounts = 4001 function(appts, newFolderId) { 4002 appts = (!(appts instanceof Array)) ? [appts] : appts; 4003 var isMovingBetw = false; 4004 for (var i = 0; i < appts.length; i++) { 4005 var appt = appts[i]; 4006 if (!appt.isReadOnly() && appt._orig && appt.otherAttendees && !appCtxt.isWebClientOffline()) { 4007 var origFolder = appt._orig.getFolder(); 4008 var newFolder = appCtxt.getById(newFolderId); 4009 if (origFolder && newFolder) { 4010 if ((origFolder.id != newFolderId) && 4011 ((origFolder.link && !newFolder.link) || (!origFolder.link && newFolder.link))) 4012 { 4013 isMovingBetw = true; 4014 break; 4015 } 4016 } 4017 } 4018 } 4019 4020 return isMovingBetw; 4021 }; 4022 4023 // returns true if moving given read-only invite appt between accounts 4024 ZmCalViewController.prototype.isMovingBetwAcctsAsAttendee = 4025 function(appts, newFolderId) { 4026 appts = (!(appts instanceof Array)) ? [appts] : appts; 4027 var isMovingBetw = false; 4028 for (var i = 0; i < appts.length; i++) { 4029 var appt = appts[i]; 4030 if (appt.isReadOnly() && appt._orig) { 4031 var origFolder = appt._orig.getFolder(); 4032 var newFolder = appCtxt.getById(newFolderId); 4033 if (origFolder && newFolder) { 4034 if ((origFolder.id != newFolderId) && origFolder.owner != newFolder.owner) 4035 { 4036 isMovingBetw = true; 4037 break; 4038 } 4039 } 4040 } 4041 } 4042 4043 return isMovingBetw; 4044 }; 4045 4046 // this gets called when we get a refresh block from the server 4047 ZmCalViewController.prototype.refreshHandler = 4048 function() { 4049 var act = new AjxTimedAction(this, this._refreshAction); 4050 AjxTimedAction.scheduleAction(act, 0); 4051 }; 4052 4053 ZmCalViewController.prototype._refreshAction = 4054 function(dontClearCache) { 4055 DBG.println(AjxDebug.DBG1, "ZmCalViewController: _refreshAction: " + dontClearCache); 4056 var forceMaintenance = false; 4057 // reset cache 4058 if (!dontClearCache) { 4059 this.apptCache.clearCache(); 4060 forceMaintenance = true; 4061 } 4062 4063 if (this._viewMgr != null) { 4064 // mark all views as dirty 4065 this._viewMgr.setNeedsRefresh(true); 4066 this._scheduleMaintenance(ZmCalViewController.MAINT_MINICAL|ZmCalViewController.MAINT_VIEW|ZmCalViewController.MAINT_REMINDER, forceMaintenance); 4067 } else if (this._miniCalendar != null) { 4068 this._scheduleMaintenance(ZmCalViewController.MAINT_MINICAL|ZmCalViewController.MAINT_REMINDER, forceMaintenance); 4069 } else { 4070 this._scheduleMaintenance(ZmCalViewController.MAINT_REMINDER, forceMaintenance); 4071 } 4072 }; 4073 4074 ZmCalViewController.prototype._maintErrorHandler = 4075 function(params) { 4076 // TODO: resched work? 4077 }; 4078 4079 ZmCalViewController.prototype._maintGetApptCallback = 4080 function(work, view, list, skipMiniCalUpdate, query) { 4081 if (list instanceof ZmCsfeException) { 4082 this.searchInProgress = false; 4083 this._handleError(list, new AjxCallback(this, this._maintErrorHandler)); 4084 return; 4085 } 4086 DBG.println(AjxDebug.DBG2, "ZmCalViewController: _maintGetApptCallback: " + work); 4087 this._userQuery = query; 4088 4089 if (work & ZmCalViewController.MAINT_VIEW) { 4090 this._list = new ZmApptList(); 4091 this._list.getVector().addList(list); 4092 var sel = view.getSelection(); 4093 view.set(list); 4094 4095 // For bug 27221, reset toolbar after refresh 4096 if (sel && sel.length > 0) { 4097 var id = sel[0].id; 4098 for (var i = 0; i < this._list.size(); i++) { 4099 if (this._list.getArray()[i].id == id) { 4100 view.setSelection(this._list.getArray[i],true); 4101 break; 4102 } 4103 } 4104 } 4105 this._resetToolbarOperations(); 4106 } 4107 4108 if (work & ZmCalViewController.MAINT_REMINDER) { 4109 this._app.getReminderController().refresh(); 4110 } 4111 4112 this.searchInProgress = false; 4113 4114 // initiate schedule action for pending work (process queued actions) 4115 if (this._pendingWork != ZmCalViewController.MAINT_NONE) { 4116 var newWork = this._pendingWork; 4117 this._pendingWork = ZmCalViewController.MAINT_NONE; 4118 this._scheduleMaintenance(newWork); 4119 } 4120 }; 4121 4122 4123 ZmCalViewController.prototype.refreshCurrentView = 4124 function() { 4125 var currentView = this.getCurrentView(); 4126 if (currentView) { 4127 currentView.setNeedsRefresh(true); 4128 this._scheduleMaintenance(ZmCalViewController.MAINT_VIEW); 4129 } 4130 } 4131 4132 4133 ZmCalViewController.prototype._scheduleMaintenance = 4134 function(work, forceMaintenance) { 4135 4136 DBG.println(AjxDebug.DBG1, "ZmCalViewController._scheduleMaintenance, work = " + work + ", forceMaintenance = " + forceMaintenance); 4137 DBG.println(AjxDebug.DBG1, "ZmCalViewController._scheduleMaintenance, searchInProgress = " + this.searchInProgress + ", pendingWork = " + this._pendingWork); 4138 this.onErrorRecovery = new AjxCallback(this, this._errorRecovery, [work, forceMaintenance]); 4139 4140 // schedule timed action 4141 if ((!this.searchInProgress || forceMaintenance) && 4142 (this._pendingWork == ZmCalViewController.MAINT_NONE)) 4143 { 4144 this._pendingWork |= work; 4145 AjxTimedAction.scheduleAction(this._maintTimedAction, 0); 4146 } 4147 else 4148 { 4149 this._pendingWork |= work; 4150 } 4151 }; 4152 4153 ZmCalViewController.prototype.resetSearchFlags = 4154 function() { 4155 this.searchInProgress = false; 4156 this._pendingWork = ZmCalViewController.MAINT_NONE; 4157 }; 4158 4159 ZmCalViewController.prototype._errorRecovery = 4160 function(work, forceMaintenance) { 4161 var maintainMiniCal = (work & ZmCalViewController.MAINT_MINICAL); 4162 var maintainView = (work & ZmCalViewController.MAINT_VIEW); 4163 var maintainRemainder = (work & ZmCalViewController.MAINT_REMINDER); 4164 if (maintainMiniCal || maintainView || maintainRemainder) { 4165 this.getCurrentView().setNeedsRefresh(true); 4166 } 4167 this._scheduleMaintenance(work, forceMaintenance); 4168 }; 4169 4170 ZmCalViewController.prototype._maintenanceAction = 4171 function() { 4172 DBG.println(AjxDebug.DBG1, "ZmCalViewController._maintenanceAction, work = " + this._pendingWork); 4173 var work = this._pendingWork; 4174 this.searchInProgress = true; 4175 this._pendingWork = ZmCalViewController.MAINT_NONE; 4176 4177 var maintainMiniCal = (work & ZmCalViewController.MAINT_MINICAL); 4178 var maintainView = (work & ZmCalViewController.MAINT_VIEW); 4179 var maintainRemainder = (work & ZmCalViewController.MAINT_REMINDER); 4180 4181 if (work == ZmCalViewController.MAINT_REMINDER) { 4182 this._app.getReminderController().refresh(); 4183 this.searchInProgress = false; 4184 } 4185 else if (maintainMiniCal || maintainView || maintainRemainder) { 4186 // NOTE: It's important that we go straight to the view! 4187 var view = this._viewMgr ? this._viewMgr.getView(this._currentViewId) : null; 4188 var needsRefresh = view && view.needsRefresh(); 4189 DBG.println(AjxDebug.DBG1, "ZmCalViewController._maintenanceAction, view = " + this._currentViewId + ", needsRefresh = " + needsRefresh); 4190 if (needsRefresh) { 4191 var rt = view.getTimeRange(); 4192 var params = { 4193 start: rt.start, 4194 end: rt.end, 4195 fanoutAllDay: view._fanoutAllDay(), 4196 callback: (new AjxCallback(this, this._maintGetApptCallback, [work, view])), 4197 accountFolderIds: ([].concat(this._checkedAccountCalendarIds)), // pass in a copy, not a reference 4198 query: this._userQuery 4199 }; 4200 4201 var reminderParams; 4202 if (maintainRemainder) { 4203 reminderParams = this._app.getReminderController().getRefreshParams(); 4204 reminderParams.callback = null; 4205 } 4206 4207 DBG.println(AjxDebug.DBG1, "ZmCalViewController._maintenanceAction, do apptCache.batchRequest"); 4208 this.apptCache.batchRequest(params, this.getMiniCalendarParams(), reminderParams); 4209 view.setNeedsRefresh(false); 4210 } else { 4211 this.searchInProgress = false; 4212 } 4213 } 4214 }; 4215 4216 ZmCalViewController.prototype.getKeyMapName = 4217 function() { 4218 return ZmKeyMap.MAP_CALENDAR; 4219 }; 4220 4221 ZmCalViewController.prototype.handleKeyAction = 4222 function(actionCode) { 4223 DBG.println(AjxDebug.DBG3, "ZmCalViewController.handleKeyAction"); 4224 var isWebClientOffline = appCtxt.isWebClientOffline(); 4225 4226 switch (actionCode) { 4227 4228 case ZmKeyMap.CAL_DAY_VIEW: 4229 case ZmKeyMap.CAL_WEEK_VIEW: 4230 case ZmKeyMap.CAL_WORK_WEEK_VIEW: 4231 case ZmKeyMap.CAL_MONTH_VIEW: 4232 this.show(ZmCalViewController.ACTION_CODE_TO_VIEW[actionCode]); 4233 break; 4234 4235 case ZmKeyMap.CAL_LIST_VIEW: 4236 if (!isWebClientOffline) { 4237 this.show(ZmCalViewController.ACTION_CODE_TO_VIEW[actionCode]); 4238 } 4239 break; 4240 4241 case ZmKeyMap.CAL_FB_VIEW: 4242 if(appCtxt.get(ZmSetting.FREE_BUSY_VIEW_ENABLED) && !isWebClientOffline) { 4243 this.show(ZmCalViewController.ACTION_CODE_TO_VIEW[actionCode]); 4244 } 4245 break; 4246 4247 case ZmKeyMap.TODAY: 4248 this._todayButtonListener(); 4249 break; 4250 4251 case ZmKeyMap.REFRESH: 4252 this._refreshButtonListener(); 4253 break; 4254 4255 case ZmKeyMap.QUICK_ADD: 4256 if (appCtxt.get(ZmSetting.CAL_USE_QUICK_ADD) && !isWebClientOffline) { 4257 var date = this._viewMgr ? this._viewMgr.getDate() : new Date(); 4258 this.newAppointmentHelper(date, ZmCalViewController.DEFAULT_APPOINTMENT_DURATION); 4259 } 4260 break; 4261 4262 case ZmKeyMap.EDIT: 4263 if (!isWebClientOffline) { 4264 var appt = this.getSelection()[0]; 4265 if (appt) { 4266 var ev = new DwtSelectionEvent(); 4267 ev.detail = DwtListView.ITEM_DBL_CLICKED; 4268 ev.item = appt; 4269 this._listSelectionListener(ev); 4270 } 4271 } 4272 break; 4273 4274 case ZmKeyMap.CANCEL: 4275 var currentView = this._viewMgr.getCurrentView(); 4276 if ((this._currentViewType == ZmId.VIEW_CAL_WORK_WEEK) || 4277 (this._currentViewType == ZmId.VIEW_CAL_WEEK) || 4278 (this._currentViewType == ZmId.VIEW_CAL_MONTH)) { 4279 // Abort - restore location and Mouse up 4280 var data = DwtMouseEventCapture.getTargetObj(); 4281 if (data) { 4282 currentView._restoreApptLoc(data); 4283 currentView._cancelNewApptDrag(data); 4284 data.startDate = data.appt ? data.appt._orig.startDate : null; 4285 ZmCalBaseView._apptMouseUpHdlr(null); 4286 } 4287 } 4288 break; 4289 4290 default: 4291 return ZmListController.prototype.handleKeyAction.call(this, actionCode); 4292 } 4293 return true; 4294 }; 4295 4296 ZmCalViewController.prototype._getDefaultFocusItem = 4297 function() { 4298 return this._toolbar[ZmId.VIEW_CAL]; 4299 }; 4300 4301 /** 4302 * Returns a reference to the singleton message controller, used to send mail (in our case, 4303 * invites and their replies). If mail is disabled, we create our own ZmMsgController so that 4304 * we don't load the mail package. 4305 * 4306 * @private 4307 */ 4308 ZmCalViewController.prototype._getMsgController = 4309 function() { 4310 if (!this._msgController) { 4311 if (appCtxt.get(ZmSetting.MAIL_ENABLED)) { 4312 this._msgController = AjxDispatcher.run("GetMsgController"); 4313 } else { 4314 AjxDispatcher.require("Mail"); 4315 this._msgController = new ZmMsgController(this._container, this._app); 4316 } 4317 } 4318 return this._msgController; 4319 }; 4320 4321 ZmCalViewController.prototype.getMiniCalendarParams = 4322 function() { 4323 var dr = this.getMiniCalendar().getDateRange(); 4324 return { 4325 start: dr.start.getTime(), 4326 end: dr.end.getTime(), 4327 fanoutAllDay: true, 4328 noBusyOverlay: true, 4329 folderIds: this.getCheckedCalendarFolderIds(), 4330 tz: AjxTimezone.DEFAULT 4331 }; 4332 }; 4333 4334 ZmCalViewController.prototype.getMiniCalCache = 4335 function() { 4336 if (!this._miniCalCache) { 4337 this._miniCalCache = new ZmMiniCalCache(this); 4338 } 4339 return this._miniCalCache; 4340 }; 4341 4342 /** 4343 * Gets the calendar name. 4344 * 4345 * @param {String} folderId the folder id 4346 * @return {String} the name 4347 */ 4348 ZmCalViewController.prototype.getCalendarName = 4349 function(folderId) { 4350 var cal = appCtxt.getById(folderId); 4351 return cal && cal.getName(); 4352 }; 4353 4354 ZmCalViewController.prototype._checkItemCount = 4355 function() { 4356 // No-op since this view doesn't do virtual paging. 4357 }; 4358 4359 4360 ZmCalViewController.prototype._showOrigListener = 4361 function(ev) { 4362 var actionMenu = this.getActionMenu(); 4363 var appt = actionMenu && actionMenu.__appt; 4364 if (appt) { 4365 setTimeout(this._showApptSource.bind(this, appt), 100); // Other listeners are focusing the main window, so delay the window opening for just a bit 4366 } 4367 }; 4368 4369 ZmCalViewController.prototype._showApptSource = 4370 function(appt) { 4371 var apptFetchUrl = appCtxt.get(ZmSetting.CSFE_MSG_FETCHER_URI) 4372 + "&id=" + AjxStringUtil.urlComponentEncode(appt.id || appt.invId) 4373 +"&mime=text/plain&noAttach=1&icalAttach=none"; 4374 // create a new window w/ generated msg based on msg id 4375 window.open(apptFetchUrl, "_blank", "menubar=yes,resizable=yes,scrollbars=yes"); 4376 }; 4377 4378 ZmCalViewController.prototype.getAppointmentByInvite = 4379 function(invite, callback) { 4380 4381 var apptId = invite.getAppointmentId(); 4382 if(apptId) { 4383 var jsonObj = {GetAppointmentRequest:{_jsns:"urn:zimbraMail"}}; 4384 var request = jsonObj.GetAppointmentRequest; 4385 request.id = apptId; 4386 request.includeContent = "1"; 4387 4388 var accountName = (appCtxt.multiAccounts ? appCtxt.accountList.mainAccount.name : null); 4389 4390 appCtxt.getAppController().sendRequest({ 4391 jsonObj: jsonObj, 4392 asyncMode: true, 4393 callback: (new AjxCallback(this, this._getApptItemInfoHandler, [invite, callback])), 4394 errorCallback: (new AjxCallback(this, this._getApptItemInfoErrorHandler, [invite, callback])), 4395 accountName: accountName 4396 }); 4397 } 4398 }; 4399 4400 ZmCalViewController.prototype._getApptItemInfoHandler = 4401 function(invite, callback, result) { 4402 var resp = result.getResponse(); 4403 resp = resp.GetAppointmentResponse; 4404 4405 var apptList = new ZmApptList(); 4406 var apptNode = resp.appt[0]; 4407 var appt = ZmAppt.createFromDom(apptNode, {list: apptList}, null); 4408 4409 var invites = apptNode.inv; 4410 appt.setInvIdFromProposedInvite(invites, invite); 4411 4412 if(callback) { 4413 callback.run(appt); 4414 } 4415 }; 4416 4417 ZmCalViewController.prototype._getApptItemInfoErrorHandler = 4418 function(invite, callback, result) { 4419 var msgDialog = appCtxt.getMsgDialog(); 4420 msgDialog.reset(); 4421 msgDialog.setMessage(ZmMsg.unableToAcceptTime, DwtMessageDialog.INFO_STYLE); 4422 msgDialog.popup(); 4423 return true; 4424 }; 4425 4426 ZmCalViewController.prototype.acceptProposedTime = 4427 function(apptId, invite, proposeTimeCallback) { 4428 4429 this._proposedTimeCallback = proposeTimeCallback; 4430 4431 if(apptId) { 4432 var callback = new AjxCallback(this, this._acceptProposedTimeContinue, [invite]); 4433 this.getAppointmentByInvite(invite, callback); 4434 }else { 4435 var msgDialog = appCtxt.getMsgDialog(); 4436 msgDialog.reset(); 4437 msgDialog.setMessage(ZmMsg.unableToAcceptTime, DwtMessageDialog.INFO_STYLE); 4438 msgDialog.popup(); 4439 } 4440 }; 4441 4442 ZmCalViewController.prototype._acceptProposedTimeContinue = 4443 function(invite, appt) { 4444 var mode = appt.isRecurring() ? (appt.isException ? ZmCalItem.MODE_EDIT_SINGLE_INSTANCE : ZmCalItem.MODE_EDIT_SERIES) : ZmCalItem.MODE_EDIT; 4445 appt.setProposedInvite(invite); 4446 appt.setProposedTimeCallback(this._proposedTimeCallback); 4447 this.editAppointment(appt, mode); 4448 }; 4449 4450 ZmCalViewController.prototype.proposeNewTime = 4451 function(msg) { 4452 var newAppt = this.newApptObject(new Date(), null, null, msg), 4453 mode = ZmCalItem.MODE_EDIT, 4454 apptId = msg.invite.getAppointmentId(); 4455 newAppt.setProposeTimeMode(true); 4456 newAppt.setFromMailMessageInvite(msg); 4457 if (apptId) { 4458 var msgId = msg.id; 4459 var inx = msg.id.indexOf(":"); 4460 var accountId = null; 4461 if (inx !== -1) { 4462 accountId = msgId.substr(0, inx); 4463 msgId = msgId.substr(inx + 1); 4464 } 4465 var invId = [apptId, msgId].join("-"); 4466 if (accountId) { 4467 invId = [accountId, invId].join(":"); 4468 } 4469 newAppt.invId = invId; 4470 newAppt.getDetails(mode, new AjxCallback(this, this.proposeNewTimeContinue, [newAppt, mode])); 4471 } 4472 else { 4473 newAppt.setViewMode(mode); 4474 AjxDispatcher.run("GetApptComposeController").proposeNewTime(newAppt); 4475 } 4476 }; 4477 4478 ZmCalViewController.prototype.proposeNewTimeContinue = 4479 function(newAppt, mode) { 4480 newAppt.setViewMode(mode); 4481 AjxDispatcher.run("GetApptComposeController").proposeNewTime(newAppt); 4482 }; 4483 4484 ZmCalViewController.prototype.forwardInvite = 4485 function(msg) { 4486 var invite = msg.invite; 4487 var apptId = invite.getAppointmentId(); 4488 4489 if(apptId && invite.isOrganizer()) { 4490 var callback = new AjxCallback(this, this.forwardInviteContinue, [invite, msg]); 4491 this.getAppointmentByInvite(invite, callback); 4492 }else { 4493 var newAppt = this.newApptObject(new Date(), null, null, msg); 4494 newAppt.setForwardMode(true); 4495 newAppt.setFromMailMessageInvite(msg); 4496 AjxDispatcher.run("GetApptComposeController").forwardInvite(newAppt); 4497 } 4498 }; 4499 4500 ZmCalViewController.prototype.forwardInviteContinue = 4501 function(invite, msg, appt) { 4502 appt.setForwardMode(true); 4503 appt.setFromMailMessageInvite(msg); 4504 var mode = appt.isRecurring() ? (appt.isException ? ZmCalItem.MODE_EDIT_SINGLE_INSTANCE : ZmCalItem.MODE_EDIT_SERIES) : ZmCalItem.MODE_EDIT; 4505 var clone = ZmAppt.quickClone(appt); 4506 clone.getDetails(mode, new AjxCallback(this, this._forwardInviteCompose, [clone])); 4507 }; 4508 4509 ZmCalViewController.prototype._forwardInviteCompose = 4510 function (appt) { 4511 AjxDispatcher.run("GetApptComposeController").forwardInvite(appt); 4512 }; 4513 4514 ZmCalViewController.prototype.getFreeBusyInfo = 4515 function(startTime, endTime, emailList, callback, errorCallback, noBusyOverlay) { 4516 var soapDoc = AjxSoapDoc.create("GetFreeBusyRequest", "urn:zimbraMail"); 4517 soapDoc.setMethodAttribute("s", startTime); 4518 soapDoc.setMethodAttribute("e", endTime); 4519 soapDoc.setMethodAttribute("uid", emailList); 4520 4521 var acct = (appCtxt.multiAccounts) 4522 ? this._composeView.getApptEditView().getCalendarAccount() : null; 4523 4524 return appCtxt.getAppController().sendRequest({ 4525 soapDoc: soapDoc, 4526 asyncMode: true, 4527 callback: callback, 4528 errorCallback: errorCallback, 4529 noBusyOverlay: noBusyOverlay, 4530 accountName: (acct ? acct.name : null) 4531 }); 4532 }; 4533 4534 ZmCalViewController.prototype.getWorkingInfo = 4535 function(startTime, endTime, emailList, callback, errorCallback, noBusyOverlay) { 4536 var soapDoc = AjxSoapDoc.create("GetWorkingHoursRequest", "urn:zimbraMail"); 4537 soapDoc.setMethodAttribute("s", startTime); 4538 soapDoc.setMethodAttribute("e", endTime); 4539 soapDoc.setMethodAttribute("name", emailList); 4540 4541 var acct = (appCtxt.multiAccounts) 4542 ? this._composeView.getApptEditView().getCalendarAccount() : null; 4543 4544 return appCtxt.getAppController().sendRequest({ 4545 soapDoc: soapDoc, 4546 asyncMode: true, 4547 callback: callback, 4548 errorCallback: errorCallback, 4549 noBusyOverlay: noBusyOverlay, 4550 accountName: (acct ? acct.name : null) 4551 }); 4552 }; 4553 4554 /** 4555 * Sets the current view. 4556 * 4557 * @param {ZmListView} view the view 4558 */ 4559 ZmCalViewController.prototype.setCurrentListView = function(view) { 4560 if (!view) { 4561 return; 4562 } 4563 4564 if (this._currentListView != view) { 4565 var hadFocus = this._currentListView && this._currentListView.hasFocus(); 4566 4567 this._currentListView = view; 4568 this._resetToolbarOperations(); 4569 4570 if (hadFocus) { 4571 this._currentListView.focus(); 4572 } 4573 } 4574 }; 4575 4576 ZmCalViewController.prototype.getCurrentListView = function() { 4577 return this._currentListView || this.getCurrentView(); 4578 }; 4579 4580 /** 4581 * Creates appointment as configured in the out-of-office preference 4582 */ 4583 ZmCalViewController.prototype.createAppointmentFromOOOPref= 4584 function(startDate, endDate, allDay, respCallback){ 4585 var newAppt = new ZmAppt(); 4586 newAppt.setAllDayEvent(allDay); 4587 newAppt.setStartDate(startDate); 4588 newAppt.setEndDate(endDate); 4589 newAppt.name = ZmMsg.outOfOffice; 4590 newAppt.freeBusy = (appCtxt.get(ZmSetting.VACATION_CALENDAR_TYPE)=="BUSY")?"B":"O"; 4591 newAppt.message = appCtxt.get(ZmSetting.VACATION_MSG); 4592 newAppt.convertToLocalTimezone(); 4593 newAppt.save(null, respCallback); 4594 }; 4595