1 /* 2 * ***** BEGIN LICENSE BLOCK ***** 3 * Zimbra Collaboration Suite Web Client 4 * Copyright (C) 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) 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 a calendar tree controller. 26 * @constructor 27 * @class 28 * This class manages the calendar tree controller. 29 * 30 * @author Parag Shah 31 * 32 * @extends ZmTreeController 33 */ 34 ZmCalendarTreeController = function() { 35 36 ZmTreeController.call(this, ZmOrganizer.CALENDAR); 37 38 this._listeners[ZmOperation.NEW_CALENDAR] = this._newListener.bind(this); 39 this._listeners[ZmOperation.ADD_EXTERNAL_CALENDAR] = this._addExternalCalendarListener.bind(this); 40 this._listeners[ZmOperation.CHECK_ALL] = this._checkAllListener.bind(this); 41 this._listeners[ZmOperation.CLEAR_ALL] = this._clearAllListener.bind(this); 42 this._listeners[ZmOperation.DETACH_WIN] = this._detachListener.bind(this); 43 this._listeners[ZmOperation.SHARE_CALENDAR] = this._shareCalListener.bind(this); 44 this._listeners[ZmOperation.MOVE] = this._moveListener.bind(this); 45 this._listeners[ZmOperation.RECOVER_DELETED_ITEMS] = this._recoverListener.bind(this); 46 47 this._eventMgrs = {}; 48 }; 49 50 ZmCalendarTreeController.prototype = new ZmTreeController; 51 ZmCalendarTreeController.prototype.constructor = ZmCalendarTreeController; 52 53 ZmCalendarTreeController.prototype.isZmCalendarTreeController = true; 54 ZmCalendarTreeController.prototype.toString = function() { return "ZmCalendarTreeController"; }; 55 56 ZmCalendarTreeController.prototype._initializeActionMenus = function() { 57 ZmTreeController.prototype._initializeActionMenus.call(this); 58 59 var ops = this._getRemoteActionMenuOps(); 60 if (!this._remoteActionMenu && ops) { 61 var args = [this._shell, ops]; 62 this._remoteActionMenu = new AjxCallback(this, this._createActionMenu, args); 63 } 64 65 } 66 67 ZmCalendarTreeController.prototype._treeListener = 68 function(ev) { 69 70 ZmTreeController.prototype._treeListener.call(this, ev); 71 72 if(ev.detail == DwtTree.ITEM_EXPANDED){ 73 var calItem = ev.item; 74 var calendar = calItem.getData(Dwt.KEY_OBJECT); 75 if(calendar && calendar.isRemote() && calendar.isMountpoint){ 76 this._fixupTreeNode(calItem, calendar, calItem._tree); 77 } 78 } 79 }; 80 81 // Public methods 82 83 /** 84 * Displays the tree of this type. 85 * 86 * @param {Hash} params a hash of parameters 87 * @param {constant} params.overviewId the overview ID 88 * @param {Boolean} params.showUnread if <code>true</code>, unread counts will be shown 89 * @param {Object} params.omit a hash of organizer IDs to ignore 90 * @param {Object} params.include a hash of organizer IDs to include 91 * @param {Boolean} params.forceCreate if <code>true</code>, tree view will be created 92 * @param {String} params.app the app that owns the overview 93 * @param {Boolean} params.hideEmpty if <code>true</code>, don't show header if there is no data 94 * @param {Boolean} params.noTooltips if <code>true</code>, don't show tooltips for tree items 95 */ 96 ZmCalendarTreeController.prototype.show = function(params) { 97 params.include = params.include || {}; 98 params.include[ZmFolder.ID_TRASH] = true; 99 params.showUnread = false; 100 return ZmFolderTreeController.prototype.show.call(this, params); 101 }; 102 103 /** 104 * Gets all calendars. 105 * 106 * @param {String} overviewId the overview id 107 * @param {boolean} includeTrash True to include trash, if checked. 108 * @return {Array} an array of {@link ZmCalendar} objects 109 */ 110 ZmCalendarTreeController.prototype.getCalendars = 111 function(overviewId, includeTrash) { 112 var calendars = []; 113 var items = this._getItems(overviewId); 114 for (var i = 0; i < items.length; i++) { 115 var item = items[i]; 116 if (item._isSeparator) { continue; } 117 var calendar = item.getData(Dwt.KEY_OBJECT); 118 if (calendar) { 119 if (calendar.id == ZmOrganizer.ID_TRASH && !includeTrash) continue; 120 calendars.push(calendar); 121 } 122 } 123 124 return calendars; 125 }; 126 127 /** 128 * Gets the owned calendars. 129 * 130 * @param {String} overviewId the overview id 131 * @param {String} owner the owner 132 * @return {Array} an array of {@link ZmCalendar} objects 133 */ 134 ZmCalendarTreeController.prototype.getOwnedCalendars = 135 function(overviewId, owner) { 136 var calendars = []; 137 var items = this._getItems(overviewId); 138 for (var i = 0; i < items.length; i++) { 139 var item = items[i]; 140 if (!item || item._isSeparator) { continue; } 141 var calendar = item.getData(Dwt.KEY_OBJECT); 142 if (calendar && calendar.getOwner() == owner) { 143 calendars.push(calendar); 144 } 145 } 146 147 return calendars; 148 }; 149 150 ZmCalendarTreeController.prototype.addSelectionListener = 151 function(overviewId, listener) { 152 // Each overview gets its own event manager 153 if (!this._eventMgrs[overviewId]) { 154 this._eventMgrs[overviewId] = new AjxEventMgr; 155 // Each event manager has its own selection event to avoid multi-threaded 156 // collisions 157 this._eventMgrs[overviewId]._selEv = new DwtSelectionEvent(true); 158 } 159 this._eventMgrs[overviewId].addListener(DwtEvent.SELECTION, listener); 160 }; 161 162 ZmCalendarTreeController.prototype.removeSelectionListener = 163 function(overviewId, listener) { 164 if (this._eventMgrs[overviewId]) { 165 this._eventMgrs[overviewId].removeListener(DwtEvent.SELECTION, listener); 166 } 167 }; 168 169 // Protected methods 170 171 ZmCalendarTreeController.prototype.resetOperations = 172 function(actionMenu, type, id) { 173 if (actionMenu && !appCtxt.isWebClientOffline()) { 174 var calendar = appCtxt.getById(id); 175 var nId; 176 if (calendar) { 177 nId = calendar.nId; 178 var isShareVisible = (!calendar.link || calendar.isAdmin()) && nId != ZmFolder.ID_TRASH; 179 if (appCtxt.isOffline) { 180 var acct = calendar.getAccount(); 181 isShareVisible = !acct.isMain && acct.isZimbraAccount; 182 } 183 actionMenu.enable(ZmOperation.SHARE_CALENDAR, isShareVisible); 184 actionMenu.enable(ZmOperation.SYNC, calendar.isFeed()); 185 } else { 186 nId = ZmOrganizer.normalizeId(id); 187 } 188 var isTrash = (nId == ZmFolder.ID_TRASH); 189 actionMenu.enable(ZmOperation.DELETE_WITHOUT_SHORTCUT, (nId != ZmOrganizer.ID_CALENDAR && nId != ZmOrganizer.ID_TRASH)); 190 this.setVisibleIfExists(actionMenu, ZmOperation.EMPTY_FOLDER, nId == ZmFolder.ID_TRASH); 191 var hasContent = ((calendar.numTotal > 0) || (calendar.children && (calendar.children.size() > 0))); 192 actionMenu.enable(ZmOperation.EMPTY_FOLDER,hasContent); 193 194 var moveItem = actionMenu.getItemById(ZmOperation.KEY_ID,ZmOperation.MOVE); 195 196 var rootId = ZmOrganizer.getSystemId(ZmOrganizer.ID_ROOT); 197 if (id == rootId) { 198 var items = this._getItems(this._actionedOverviewId); 199 var foundChecked = false; 200 var foundUnchecked = false; 201 for (var i = 0; i < items.length; i++) { 202 var item = items[i]; 203 if (item._isSeparator) continue; 204 item.getChecked() ? foundChecked = true : foundUnchecked = true; 205 } 206 actionMenu.enable(ZmOperation.CHECK_ALL, foundUnchecked); 207 actionMenu.enable(ZmOperation.CLEAR_ALL, foundChecked); 208 } 209 210 this._enableRecoverDeleted(actionMenu, isTrash); 211 212 // we always enable sharing in case we're in multi-mbox mode 213 this._resetButtonPerSetting(actionMenu, ZmOperation.SHARE_CALENDAR, appCtxt.get(ZmSetting.SHARING_ENABLED)); 214 this._resetButtonPerSetting(actionMenu, ZmOperation.FREE_BUSY_LINK, appCtxt.getActiveAccount().isZimbraAccount); 215 216 var fbLinkMenuItem = actionMenu.getMenuItem(ZmOperation.FREE_BUSY_LINK); 217 if (fbLinkMenuItem){ 218 //setting up free busy link submenu 219 actionMenu._fbLinkSubMenu = actionMenu._fbLinkSubMenu || this._getFreeBusySubMenu(actionMenu, calendar.restUrl); 220 221 fbLinkMenuItem.setMenu(actionMenu._fbLinkSubMenu); 222 } 223 224 actionMenu.enable(ZmOperation.NEW_CALENDAR, !isTrash && !appCtxt.isExternalAccount() && !appCtxt.isWebClientOffline()); 225 226 } 227 }; 228 229 ZmCalendarTreeController.prototype._getFreeBusySubMenu = 230 function(actionMenu, restUrl){ 231 var subMenuItems = [ZmOperation.SEND_FB_HTML,ZmOperation.SEND_FB_ICS,ZmOperation.SEND_FB_ICS_EVENT]; 232 var params = {parent:actionMenu, menuItems:subMenuItems}; 233 var subMenu = new ZmActionMenu(params); 234 for(var s=0;s<subMenuItems.length;s++){ 235 subMenu.addSelectionListener(subMenuItems[s], this._freeBusyLinkListener.bind(this, subMenuItems[s], restUrl) ); 236 } 237 return subMenu; 238 } 239 240 ZmCalendarTreeController.prototype._detachListener = 241 function(ev){ 242 var folder = this._getActionedOrganizer(ev); 243 if (!folder){ 244 return; 245 } 246 var acct = folder.getAccount(); 247 var noRemote = true; // noRemote is to achieve a restUrl that points to user's mailbox instead of the shared calendar owner's mailbox 248 var url = folder.getRestUrl(acct, noRemote); 249 if (url) { 250 window.open(url+".html?tz=" + AjxTimezone.DEFAULT, "_blank"); 251 } 252 }; 253 254 ZmCalendarTreeController.prototype._freeBusyLinkListener = 255 function(op, restUrl, ev){ 256 var inNewWindow = false; 257 var app = appCtxt.getApp(ZmApp.CALENDAR); 258 if (app) { 259 inNewWindow = app._inNewWindow(ev); 260 } 261 restUrl = restUrl || appCtxt.get(ZmSetting.REST_URL); 262 if (restUrl) { 263 restUrl += op === ZmOperation.SEND_FB_ICS_EVENT ? "?fmt=ifb&fbfmt=event" : op === ZmOperation.SEND_FB_ICS ? "?fmt=ifb" : "?fmt=freebusy"; 264 } 265 var params = { 266 action: ZmOperation.NEW_MESSAGE, 267 inNewWindow: inNewWindow, 268 msg: (new ZmMailMsg()), 269 extraBodyText: restUrl 270 }; 271 AjxDispatcher.run("Compose", params); 272 }; 273 274 ZmCalendarTreeController.prototype._recoverListener = 275 function(ev) { 276 appCtxt.getDumpsterDialog().popup(this._getSearchFor(), this._getSearchTypes()); 277 }; 278 279 ZmCalendarTreeController.prototype._getSearchFor = 280 function(ev) { 281 return ZmItem.APPT; 282 }; 283 284 ZmCalendarTreeController.prototype._getSearchTypes = 285 function(ev) { 286 return [ZmItem.APPT]; 287 }; 288 289 // Returns a list of desired header action menu operations 290 ZmCalendarTreeController.prototype._getHeaderActionMenuOps = 291 function() { 292 var ops = []; 293 if (appCtxt.getCurrentApp().containsWritableFolder()) { 294 ops.push(ZmOperation.NEW_CALENDAR, 295 ZmOperation.ADD_EXTERNAL_CALENDAR, 296 ZmOperation.CHECK_ALL, 297 ZmOperation.CLEAR_ALL, 298 ZmOperation.SEP, 299 ZmOperation.FREE_BUSY_LINK); 300 } 301 else { 302 ops.push(ZmOperation.CHECK_ALL, 303 ZmOperation.CLEAR_ALL); 304 } 305 306 ops.push(ZmOperation.FIND_SHARES); 307 308 return ops; 309 }; 310 311 312 // Returns a list of desired remote shared mailbox action menu operations 313 ZmCalendarTreeController.prototype._getRemoteActionMenuOps = function() { 314 return [ZmOperation.NEW_CALENDAR, 315 ZmOperation.ADD_EXTERNAL_CALENDAR, 316 ZmOperation.FREE_BUSY_LINK]; 317 }; 318 319 // Returns a list of desired action menu operations 320 ZmCalendarTreeController.prototype._getActionMenuOps = function() { 321 322 if (appCtxt.getCurrentApp().containsWritableFolder()) { 323 return [ 324 ZmOperation.NEW_CALENDAR, 325 ZmOperation.SYNC, 326 ZmOperation.EMPTY_FOLDER, 327 ZmOperation.RECOVER_DELETED_ITEMS, 328 ZmOperation.SHARE_CALENDAR, 329 ZmOperation.MOVE, 330 ZmOperation.DELETE_WITHOUT_SHORTCUT, 331 ZmOperation.EDIT_PROPS, 332 ZmOperation.DETACH_WIN 333 ]; 334 } 335 else { 336 return [ 337 ZmOperation.EDIT_PROPS, 338 ZmOperation.DETACH_WIN 339 ]; 340 } 341 }; 342 343 ZmCalendarTreeController.prototype.getItemActionMenu = function(ev, item) { 344 var actionMenu = null; 345 if (item.isRemoteRoot()) { 346 actionMenu = this._getRemoteActionMenu(); 347 } else { 348 actionMenu = ZmTreeController.prototype.getItemActionMenu.apply(this, arguments); 349 } 350 return actionMenu; 351 } 352 353 ZmCalendarTreeController.prototype._getRemoteActionMenu = function() { 354 if (this._remoteActionMenu instanceof AjxCallback) { 355 var callback = this._remoteActionMenu; 356 this._remoteActionMenu = callback.run(); 357 } 358 return this._remoteActionMenu; 359 }; 360 361 ZmCalendarTreeController.prototype._getActionMenu = 362 function(ev) { 363 var organizer = ev.item.getData(Dwt.KEY_OBJECT); 364 if (organizer.type != this.type && 365 organizer.nId != ZmOrganizer.ID_TRASH) { 366 return null; 367 } 368 var menu = ZmTreeController.prototype._getActionMenu.apply(this, arguments); 369 if (appCtxt.isWebClientOffline()) { 370 menu.enableAll(false); 371 } else { 372 var isTrash = organizer.nId == ZmOrganizer.ID_TRASH; 373 //bug 67531: "Move" Option should be disabled for the default calendar 374 var isCalendar = organizer.nId == ZmOrganizer.ID_CALENDAR; 375 menu.enableAll(!isTrash); 376 menu.enable(ZmOperation.MOVE, !isCalendar && !isTrash); 377 menu.enable(ZmOperation.EMPTY_FOLDER, isTrash); 378 var menuItem = menu.getMenuItem(ZmOperation.EMPTY_FOLDER); 379 if (menuItem) { 380 menuItem.setText(isTrash ? ZmMsg.emptyTrash : ZmMsg.emptyFolder); 381 } 382 } 383 return menu; 384 }; 385 386 // Method that is run when a tree item is left-clicked 387 ZmCalendarTreeController.prototype._itemClicked = 388 function(organizer) { 389 if ((organizer.type != ZmOrganizer.CALENDAR) && !organizer.isRemoteRoot()) { 390 if (organizer._showFoldersCallback) { 391 organizer._showFoldersCallback.run(); 392 return; 393 } 394 395 if (organizer.nId == ZmOrganizer.ID_TRASH) { 396 return; 397 } 398 399 var appId = ZmOrganizer.APP[organizer.type]; 400 var app = appId && appCtxt.getApp(appId); 401 if (app) { 402 var callback = new AjxCallback(this, this._postActivateApp, [organizer, app]); 403 appCtxt.getAppController().activateApp(appId, null, callback); 404 } 405 else { 406 appCtxt.setStatusMsg({ 407 msg: AjxMessageFormat.format(ZmMsg.appUnknown, [appId]), 408 level: ZmStatusView.LEVEL_WARNING 409 }); 410 } 411 } 412 }; 413 414 ZmCalendarTreeController.prototype._postActivateApp = 415 function(organizer, app) { 416 var controller = appCtxt.getOverviewController(); 417 var overviewId = app.getOverviewId(); 418 var treeId = organizer.type; 419 var treeView = controller.getTreeView(overviewId, treeId); 420 if (treeView) { 421 treeView.setSelected(organizer); 422 } 423 }; 424 425 // Handles a drop event 426 ZmCalendarTreeController.prototype._dropListener = 427 function(ev) { 428 var data = ev.srcData.data; 429 var dropFolder = ev.targetControl.getData(Dwt.KEY_OBJECT); 430 431 var appts = (!(data instanceof Array)) ? [data] : data; 432 var isShiftKey = (ev.shiftKey || ev.uiEvent.shiftKey); 433 434 if (ev.action == DwtDropEvent.DRAG_ENTER) { 435 436 var type = ev.targetControl.getData(ZmTreeView.KEY_TYPE); 437 438 if(data instanceof ZmCalendar){ 439 ev.doIt = dropFolder.mayContain(data, type) && !data.isSystem(); 440 } 441 else if (!(appts[0] instanceof ZmAppt)) { 442 ev.doIt = false; 443 } 444 else if (this._dropTgt.isValidTarget(data)) { 445 var type = ev.targetControl.getData(ZmTreeView.KEY_TYPE); 446 ev.doIt = dropFolder.mayContain(data, type); 447 448 var action; 449 // walk thru the array and find out what action is allowed 450 for (var i = 0; i < appts.length; i++) { 451 if (appts[i] instanceof ZmItem) { 452 action |= appts[i].getDefaultDndAction(isShiftKey); 453 } 454 } 455 456 if (((action & ZmItem.DND_ACTION_COPY) != 0)) { 457 var plusDiv = (appts.length == 1) 458 ? ev.dndProxy.firstChild.nextSibling 459 : ev.dndProxy.firstChild.nextSibling.nextSibling; 460 461 Dwt.setVisibility(plusDiv, true); 462 } 463 } 464 } 465 else if (ev.action == DwtDropEvent.DRAG_DROP) { 466 var ctlr = ev.srcData.controller; 467 var cc = AjxDispatcher.run("GetCalController"); 468 if (!isShiftKey && cc.isMovingBetwAccounts(appts, dropFolder.id)) { 469 var dlg = appCtxt.getMsgDialog(); 470 dlg.setMessage(ZmMsg.orgChange, DwtMessageDialog.WARNING_STYLE); 471 dlg.popup(); 472 } else { 473 if (data instanceof ZmCalendar) { 474 this._doMove(data, dropFolder); 475 } else { 476 ctlr._doMove(appts, dropFolder, null, isShiftKey); 477 } 478 } 479 } 480 }; 481 482 ZmCalendarTreeController.prototype._dropToRemoteFolder = 483 function(name) { 484 appCtxt.setStatusMsg(AjxMessageFormat.format(ZmMsg.calStatusUpdate, name)); 485 } 486 487 ZmCalendarTreeController.prototype._changeOrgCallback = 488 function(controller, dialog, appts, dropFolder) { 489 dialog.popdown(); 490 if(!dropFolder.noSuchFolder){ 491 controller._doMove(appts, dropFolder, null, false); 492 } 493 else{ 494 var dialog = appCtxt.getMsgDialog(); 495 var msg = AjxMessageFormat.format(ZmMsg.noFolderExists, dropFolder.name); 496 dialog.setMessage(msg); 497 dialog.popup(); 498 } 499 }; 500 501 /* 502 * Returns a "New Calendar" dialog. 503 */ 504 ZmCalendarTreeController.prototype._getNewDialog = 505 function() { 506 return appCtxt.getNewCalendarDialog(); 507 }; 508 509 ZmCalendarTreeController.prototype._newCallback = 510 function(params) { 511 // For a calendar, set the parent folder (params.l) if specified 512 var folder = this._pendingActionData instanceof ZmOrganizer ? this._pendingActionData : 513 (this._pendingActionData && this._pendingActionData.organizer); 514 if (folder) { 515 params.l = folder.id; 516 } 517 ZmTreeController.prototype._newCallback.call(this, params); 518 }; 519 520 521 522 /* 523 * Returns an "External Calendar" dialog. 524 */ 525 ZmCalendarTreeController.prototype.getExternalCalendarDialog = 526 function() { 527 if(!this._externalCalendarDialog) { 528 AjxDispatcher.require(["MailCore", "CalendarCore", "Calendar", "CalendarAppt"]); 529 this._externalCalendarDialog = new ZmExternalCalendarDialog({parent: this._shell, controller: this}); 530 } 531 return this._externalCalendarDialog; 532 }; 533 534 // Listener callbacks 535 536 /* 537 * Listener to handle new external calendar. 538 */ 539 ZmCalendarTreeController.prototype._addExternalCalendarListener = 540 function() { 541 var dialog = this.getExternalCalendarDialog(); 542 dialog.popup(); 543 }; 544 545 546 ZmCalendarTreeController.prototype._changeListener = 547 function(ev, treeView, overviewId) { 548 ZmTreeController.prototype._changeListener.call(this, ev, treeView, overviewId); 549 550 if (ev.type != this.type || ev.handled) { return; } 551 552 var fields = ev.getDetail("fields") || {}; 553 if (ev.event == ZmEvent.E_CREATE || 554 ev.event == ZmEvent.E_DELETE || 555 (ev.event == ZmEvent.E_MODIFY && fields[ZmOrganizer.F_FLAGS])) 556 { 557 var aCtxt = appCtxt.isChildWindow ? parentAppCtxt : appCtxt; 558 var controller = aCtxt.getApp(ZmApp.CALENDAR).getCalController(); 559 controller._updateCheckedCalendars(); 560 561 // if calendar is deleted, notify will initiate the refresh action 562 if (ev.event != ZmEvent.E_DELETE) { 563 var calIds = controller.getCheckedCalendarFolderIds(); 564 AjxDebug.println(AjxDebug.CALENDAR, "tree change listener refreshing calendar event '" + ev.event + "' with checked folder ids " + calIds.join(",")); 565 controller._refreshAction(true); 566 ev.handled = true; 567 } 568 } 569 }; 570 571 ZmCalendarTreeController.prototype._treeViewListener = 572 function(ev) { 573 // handle item(s) clicked 574 if (ev.detail == DwtTree.ITEM_CHECKED) { 575 var overviewId = ev.item.getData(ZmTreeView.KEY_ID); 576 var calendar = ev.item.getData(Dwt.KEY_OBJECT); 577 578 //checkbox event may not be propagated to close action menu 579 if (this._getActionMenu(ev)) { 580 this._getActionMenu(ev).popdown(); 581 } 582 583 // notify listeners of selection 584 if (this._eventMgrs[overviewId]) { 585 this._eventMgrs[overviewId].notifyListeners(DwtEvent.SELECTION, ev); 586 } 587 return; 588 } 589 590 // default processing 591 ZmTreeController.prototype._treeViewListener.call(this, ev); 592 }; 593 594 ZmCalendarTreeController.prototype._checkAllListener = 595 function(ev) { 596 this._setAllChecked(ev, true); 597 }; 598 599 ZmCalendarTreeController.prototype._clearAllListener = 600 function(ev) { 601 this._setAllChecked(ev, false); 602 }; 603 604 ZmCalendarTreeController.prototype._shareCalListener = 605 function(ev) { 606 this._pendingActionData = this._getActionedOrganizer(ev); 607 appCtxt.getSharePropsDialog().popup(ZmSharePropsDialog.NEW, this._pendingActionData); 608 }; 609 610 ZmCalendarTreeController.prototype._deleteListener = 611 function(ev) { 612 var organizer = this._getActionedOrganizer(ev); 613 if (organizer.isInTrash()) { 614 var callback = new AjxCallback(this, this._deleteListener2, [organizer]); 615 var message = AjxMessageFormat.format(ZmMsg.confirmDeleteCalendar, AjxStringUtil.htmlEncode(organizer.name)); 616 617 appCtxt.getConfirmationDialog().popup(message, callback); 618 } 619 else { 620 this._doMove(organizer, appCtxt.getById(ZmFolder.ID_TRASH)); 621 } 622 }; 623 624 ZmCalendarTreeController.prototype._deleteListener2 = 625 function(organizer) { 626 this._doDelete(organizer); 627 }; 628 629 /** 630 * Empties a folder. 631 * It removes all the items in the folder except sub-folders. 632 * If the folder is Trash, it empties even the sub-folders. 633 * A warning dialog will be shown before any folder is emptied. 634 * 635 * @param {DwtUiEvent} ev the UI event 636 * 637 * @private 638 */ 639 ZmCalendarTreeController.prototype._emptyListener = 640 function(ev) { 641 this._getEmptyShieldWarning(ev); 642 }; 643 644 ZmCalendarTreeController.prototype._notifyListeners = 645 function(overviewId, type, items, detail, srcEv, destEv) { 646 if (this._eventMgrs[overviewId] && 647 this._eventMgrs[overviewId].isListenerRegistered(type)) 648 { 649 if (srcEv) DwtUiEvent.copy(destEv, srcEv); 650 destEv.items = items; 651 if (items.length == 1) destEv.item = items[0]; 652 destEv.detail = detail; 653 this._eventMgrs[overviewId].notifyListeners(type, destEv); 654 } 655 }; 656 657 ZmCalendarTreeController.prototype._getItems = 658 function(overviewId) { 659 var treeView = this.getTreeView(overviewId); 660 if (treeView) { 661 var account = appCtxt.multiAccounts ? treeView._overview.account : null; 662 if (!appCtxt.get(ZmSetting.CALENDAR_ENABLED, null, account)) { return []; } 663 664 var rootId = ZmOrganizer.getSystemId(ZmOrganizer.ID_ROOT, account); 665 var root = treeView.getTreeItemById(rootId); 666 if (root) { 667 var totalItems = []; 668 this._getSubItems(root, totalItems); 669 return totalItems; 670 } 671 } 672 return []; 673 }; 674 675 ZmCalendarTreeController.prototype._getSubItems = 676 function(root, totalItems) { 677 if (!root || (root && root._isSeparator)) { return; } 678 679 var items = root.getItems(); 680 //items is an array 681 for (var i = 0; i < items.length; i++) { 682 var item = items[i]; 683 if (item && !item._isSeparator) { 684 totalItems.push(item); 685 this._getSubItems(item, totalItems); 686 } 687 } 688 }; 689 690 ZmCalendarTreeController.prototype._setAllChecked = 691 function(ev, checked) { 692 var overviewId = this._actionedOverviewId; 693 var items = this._getItems(overviewId); 694 var checkedItems = []; 695 var item, organizer; 696 for (var i = 0; i < items.length; i++) { 697 item = items[i]; 698 if (item._isSeparator) { continue; } 699 organizer = item.getData(Dwt.KEY_OBJECT); 700 if (!organizer || organizer.type != ZmOrganizer.CALENDAR) { continue; } 701 item.setChecked(checked); 702 checkedItems.push(item); 703 } 704 705 // notify listeners of selection 706 if (checkedItems.length && this._eventMgrs[overviewId]) { 707 this._notifyListeners(overviewId, DwtEvent.SELECTION, checkedItems, DwtTree.ITEM_CHECKED, ev, this._eventMgrs[overviewId]._selEv); 708 } 709 }; 710 711 ZmCalendarTreeController.prototype._createTreeView = 712 function(params) { 713 if (params.treeStyle == null) { 714 params.treeStyle = DwtTree.CHECKEDITEM_STYLE; 715 } 716 params.showUnread = false; 717 return new ZmTreeView(params); 718 }; 719 720 ZmCalendarTreeController.prototype._postSetup = 721 function(overviewId, account) { 722 ZmTreeController.prototype._postSetup.apply(this, arguments); 723 724 // bug: 43067 - remove the default calendar since its only a place holder 725 // for caldav based accounts 726 if (account && account.isCalDavBased()) { 727 var treeView = this.getTreeView(overviewId); 728 var calendarId = ZmOrganizer.getSystemId(ZmOrganizer.ID_CALENDAR, account); 729 var treeItem = treeView.getTreeItemById(calendarId); 730 treeItem.dispose(); 731 } 732 }; 733 734 /** 735 * Pops up the appropriate "New ..." dialog. 736 * 737 * @param {DwtUiEvent} ev the UI event 738 * @param {ZmZimbraAccount} account used by multi-account mailbox (optional) 739 * 740 * @private 741 */ 742 ZmCalendarTreeController.prototype._newListener = 743 function(ev, account, isExternalCalendar) { 744 this._pendingActionData = this._getActionedOrganizer(ev); 745 var newDialog = this._getNewDialog(); 746 747 // Fix for Bug: 85158 and regression due to Bug: 82811 748 // Pass a flag isExternalCalendar from ZmExternalCalendarDialog::_nextButtonListener to help decide creating external calendar or local calendar 749 if (isExternalCalendar && this._extCalData) { 750 var iCalData = this._extCalData.iCal; 751 newDialog.setICalData(iCalData); 752 newDialog.setTitle(ZmMsg.addExternalCalendar); 753 newDialog.getButton(ZmNewCalendarDialog.BACK_BUTTON).setVisibility(true); 754 } 755 else { 756 newDialog.setTitle(ZmMsg.createNewCalendar); 757 newDialog.getButton(ZmNewCalendarDialog.BACK_BUTTON).setVisibility(false); 758 } 759 if (!this._newCb) { 760 this._newCb = new AjxCallback(this, this._newCallback); 761 } 762 if (this._pendingActionData && !appCtxt.getById(this._pendingActionData.id)) { 763 this._pendingActionData = appCtxt.getFolderTree(account).root; 764 } 765 766 if (!account && appCtxt.multiAccounts) { 767 var ov = this._opc.getOverview(this._actionedOverviewId); 768 account = ov && ov.account; 769 } 770 771 ZmController.showDialog(newDialog, this._newCb, this._pendingActionData, account); 772 773 newDialog.registerCallback(DwtDialog.CANCEL_BUTTON, this._clearDialog, this, newDialog); 774 }; 775 776 ZmCalendarTreeController.prototype.setExternalCalendarData = 777 function(extCalData) { 778 this._extCalData = extCalData; 779 }; 780 781 ZmCalendarTreeController.prototype._clearDialog = 782 function(dialog) { 783 ZmTreeController.prototype._clearDialog.apply(this, arguments); 784 if(this._externalCalendarDialog) { 785 this._externalCalendarDialog.popdown(); 786 } 787 }; 788 789 ZmCalendarTreeController.prototype.createDataSourceErrorCallback = 790 function(response) { 791 appCtxt.setStatusMsg(ZmMsg.addExternalCalendarError); 792 }; 793 794 ZmCalendarTreeController.prototype.createDataSourceCallback = 795 function(response) { 796 var dsResponse = response.getResponse(), 797 sourceId = dsResponse && dsResponse.caldav ? dsResponse.caldav[0].id : "", 798 jsonObj, 799 params; 800 if(sourceId) { 801 jsonObj = { 802 ImportDataRequest : { 803 _jsns : "urn:zimbraMail", 804 caldav : { 805 id : sourceId 806 } 807 } 808 }; 809 params = { 810 soapDoc: jsonObj, 811 asyncMode: false 812 }; 813 appCtxt.getAppController().sendRequest(params); 814 } 815 816 appCtxt.setStatusMsg(ZmMsg.addExternalCalendarSuccess); 817 return response; 818 }; 819 820 ZmCalendarTreeController.POLLING_INTERVAL = "1m"; 821 ZmCalendarTreeController.CONN_TYPE_CLEARTEXT = "cleartext"; 822 ZmCalendarTreeController.CONN_TYPE_SSL = "ssl"; 823 ZmCalendarTreeController.SSL_PORT = "443"; 824 ZmCalendarTreeController.GOOGLE_CALDAV_SERVER = "www.google.com"; 825 ZmCalendarTreeController.ALT_GOOGLE_CALDAV_SERVER = "apidata.googleusercontent.com"; 826 ZmCalendarTreeController.DATA_SOURCE_ATTR_YAHOO = "p:/principals/users/_USERNAME_"; 827 ZmCalendarTreeController.DATA_SOURCE_ATTR = "p:/calendar/dav/_USERNAME_/user"; 828 829 ZmCalendarTreeController.prototype.createDataSource = 830 function(organizer, errorCallback) { 831 var calDav = this._extCalData && this._extCalData.calDav ? this._extCalData.calDav : null; 832 if(!calDav) { return; } 833 834 var url, 835 port, 836 urlComponents, 837 hostUrl, 838 jsonObj, 839 connType = ZmCalendarTreeController.CONN_TYPE_CLEARTEXT, 840 dsa = ZmCalendarTreeController.DATA_SOURCE_ATTR; 841 842 843 hostUrl = calDav.hostUrl; 844 urlComponents = AjxStringUtil.parseURL(hostUrl); 845 url = urlComponents.domain; 846 port = urlComponents.port || ZmCalendarTreeController.SSL_PORT; 847 dsa = urlComponents.path ? "p:" + urlComponents.path : ZmCalendarTreeController.DATA_SOURCE_ATTR; 848 849 850 if(port == ZmCalendarTreeController.SSL_PORT) { 851 connType = ZmCalendarTreeController.CONN_TYPE_SSL; 852 } 853 854 if (calDav.hostUrl.indexOf(ZmCalendarTreeController.GOOGLE_CALDAV_SERVER) === -1 855 && calDav.hostUrl.indexOf(ZmCalendarTreeController.ALT_GOOGLE_CALDAV_SERVER) === -1) { // Not google url 856 dsa = ZmCalendarTreeController.DATA_SOURCE_ATTR_YAHOO; 857 } 858 859 jsonObj = { 860 CreateDataSourceRequest : { 861 _jsns : "urn:zimbraMail", 862 caldav : { 863 name : organizer.name, 864 pollingInterval : ZmCalendarTreeController.POLLING_INTERVAL, 865 isEnabled : "1", 866 l : organizer.nId, 867 host : url, 868 port : port, 869 connectionType : connType, 870 username : calDav.userName, 871 password : calDav.password, 872 a : { 873 n : "zimbraDataSourceAttribute", 874 _content : dsa 875 } 876 } 877 } 878 }; 879 880 this._extCalData = null; 881 delete this._extCalData; 882 var accountName = (appCtxt.multiAccounts ? appCtxt.accountList.mainAccount.name : null); 883 884 var params = { 885 jsonObj: jsonObj, 886 asyncMode: true, 887 sensitive: true, 888 callback: new AjxCallback(this, this.createDataSourceCallback), 889 errorCallback: new AjxCallback(this, this.createDataSourceErrorCallback), 890 accountName: accountName 891 }; 892 appCtxt.getAppController().sendRequest(params); 893 }; 894