1 /* 2 * ***** BEGIN LICENSE BLOCK ***** 3 * Zimbra Collaboration Suite Web Client 4 * Copyright (C) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 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) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2016 Synacor, Inc. All Rights Reserved. 21 * ***** END LICENSE BLOCK ***** 22 */ 23 24 ZmCalMgr = function(container) { 25 this._container = container; 26 this.clearCache(); 27 28 this._listeners = {}; 29 this._folderNames = {}; 30 31 this._listeners[ZmOperation.NEW_APPT] = new AjxListener(this, this._newApptAction); 32 this._listeners[ZmOperation.NEW_ALLDAY_APPT] = new AjxListener(this, this._newAllDayApptAction); 33 this._listeners[ZmOperation.SEARCH_MAIL] = new AjxListener(this, this._searchMailAction); 34 }; 35 36 ZmCalMgr.prototype.toString = 37 function() { 38 return "ZmCalMgr"; 39 }; 40 41 ZmCalMgr.prototype.clearCache = 42 function() { 43 this._miniCalData = {}; 44 }; 45 46 ZmCalMgr.prototype._createMiniCalendar = 47 function(date) { 48 date = date ? date : new Date(); 49 50 var firstDayOfWeek = appCtxt.get(ZmSetting.CAL_FIRST_DAY_OF_WEEK) || 0; 51 52 //todo: need to use server setting to decide the weekno standard 53 var serverId = AjxTimezone.getServerId(AjxTimezone.DEFAULT); 54 var useISO8601WeekNo = (serverId && serverId.indexOf("Europe")==0 && serverId != "Europe/London"); 55 56 this._miniCalendar = new DwtCalendar({parent: this._container, posStyle:DwtControl.ABSOLUTE_STYLE, 57 firstDayOfWeek: firstDayOfWeek, showWeekNumber: appCtxt.get(ZmSetting.CAL_SHOW_CALENDAR_WEEK), useISO8601WeekNo: useISO8601WeekNo}); 58 this._miniCalendar.setDate(date); 59 this._miniCalendar.setScrollStyle(Dwt.CLIP); 60 this._miniCalendar.addSelectionListener(new AjxListener(this, this._miniCalSelectionListener)); 61 this._miniCalendar.addActionListener(new AjxListener(this, this._miniCalActionListener)); 62 this._miniCalendar.addDateRangeListener(new AjxListener(this, this._miniCalDateRangeListener)); 63 this._miniCalendar.setMouseOverDayCallback(new AjxCallback(this, this._miniCalMouseOverDayCallback)); 64 this._miniCalendar.setMouseOutDayCallback(new AjxCallback(this, this._miniCalMouseOutDayCallback)); 65 66 var list = []; 67 if (appCtxt.get(ZmSetting.MAIL_ENABLED)) { 68 list.push("ZmMailMsg"); 69 list.push("ZmConv"); 70 } 71 if (appCtxt.get(ZmSetting.CONTACTS_ENABLED)) { 72 list.push("ZmContact"); 73 } 74 this._miniCalDropTarget = new DwtDropTarget(list); 75 this._miniCalDropTarget.addDropListener(new AjxListener(this, this._miniCalDropTargetListener)); 76 this._miniCalendar.setDropTarget(this._miniCalDropTarget); 77 78 var workingWeek = []; 79 for (var i = 0; i < 7; i++) { 80 var d = (i + firstDayOfWeek) % 7; 81 workingWeek[i] = (d > 0 && d < 6); 82 } 83 this._miniCalendar.setWorkingWeek(workingWeek); 84 85 // add mini-calendar to skin 86 var components = {}; 87 components[ZmAppViewMgr.C_TREE_FOOTER] = this._miniCalendar; 88 appCtxt.getAppViewMgr().setViewComponents(ZmAppViewMgr.GLOBAL, components, true); 89 90 var app = appCtxt.getApp(ZmApp.CALENDAR); 91 var show = app._active || appCtxt.get(ZmSetting.CAL_ALWAYS_SHOW_MINI_CAL); 92 this._miniCalendar.setSkipNotifyOnPage(show && !app._active); 93 if (!app._active) { 94 this._miniCalendar.setSelectionMode(DwtCalendar.DAY); 95 } 96 97 this._dayRollTimer = null; 98 }; 99 100 101 102 ZmCalMgr.prototype.startDayRollTimer = 103 function(){ 104 if(!this._dayRollTimer){ 105 var curTime = new Date(); 106 var rollTime = new Date(); 107 rollTime.setHours(23,59,59,999); 108 var interval = rollTime.getTime() - curTime.getTime(); 109 var dayRollAction = new AjxTimedAction(this,this._rollDay); 110 AjxTimedAction.scheduleAction(dayRollAction,interval); 111 } 112 } 113 114 115 ZmCalMgr.prototype._rollDay = 116 function(){ 117 this._dayRollTimer = null; 118 this._miniCalendar.setDate(new Date(),true); 119 this.startDayRollTimer(); 120 } 121 122 ZmCalMgr.prototype._miniCalDropTargetListener = 123 function(ev) { 124 var calController = this.getCalViewController(); 125 calController._miniCalDropTargetListener(ev); 126 }; 127 128 ZmCalMgr.prototype.getMiniCalendar = 129 function() { 130 if (!this._miniCalendar) { 131 this._createMiniCalendar(); 132 } 133 134 return this._miniCalendar; 135 }; 136 137 ZmCalMgr.prototype._refreshCallback = 138 function(list) { 139 this.getReminderController()._refreshCallback(list); 140 }; 141 142 ZmCalMgr.prototype.getReminderController = 143 function() { 144 if (!this._reminderController) { 145 this._reminderController = new ZmReminderController(this, "appt"); 146 } 147 return this._reminderController; 148 }; 149 150 ZmCalMgr.prototype._miniCalSelectionListener = 151 function(ev) { 152 if (ev.item instanceof DwtCalendar) { 153 var calController = this.getCalViewController(); 154 calController._handleLoadMiniCalSelection(ev); 155 } 156 }; 157 158 ZmCalMgr.prototype._miniCalActionListener = 159 function(ev) { 160 if (appCtxt.isExternalAccount()) { return; } 161 var mm = this._getMiniCalActionMenu(); 162 mm.__detail = ev.detail; 163 mm.popup(0, ev.docX, ev.docY); 164 }; 165 166 ZmCalMgr.prototype._getMiniCalActionMenu = 167 function() { 168 if (this._minicalMenu == null) { 169 170 this.postInitListeners(); 171 172 var list = [ZmOperation.NEW_APPT, ZmOperation.NEW_ALLDAY_APPT, ZmOperation.SEP, ZmOperation.SEARCH_MAIL]; 173 //Zimlet hack 174 var zimletOps = ZmZimlet.actionMenus ? ZmZimlet.actionMenus["ZmCalViewController"] : null; 175 if (zimletOps && zimletOps.length) { 176 for (var i = 0; i < zimletOps.length; i++) { 177 var op = zimletOps[i]; 178 ZmOperation.defineOperation(null, op); 179 list.push(op.id); 180 } 181 } 182 var params = {parent: appCtxt.getShell(), menuItems:list}; 183 this._minicalMenu = new ZmActionMenu(params); 184 list = this._minicalMenu.opList; 185 var cnt = list.length; 186 for(var ix=0; ix < cnt; ix++) { 187 if(this._listeners[list[ix]]) { 188 this._minicalMenu.addSelectionListener(list[ix], this._listeners[list[ix]]); 189 } 190 } 191 } 192 return this._minicalMenu; 193 }; 194 195 // Zimlet hack 196 ZmCalMgr.prototype.postInitListeners = 197 function () { 198 if (ZmZimlet.listeners && ZmZimlet.listeners["ZmCalViewController"]) { 199 for (var ix in ZmZimlet.listeners["ZmCalViewController"]) { 200 if (ZmZimlet.listeners["ZmCalViewController"][ix] instanceof AjxListener) { 201 this._listeners[ix] = ZmZimlet.listeners["ZmCalViewController"][ix]; 202 } else { 203 this._listeners[ix] = new AjxListener(this, this._proxyListeners, [ZmZimlet.listeners["ZmCalViewController"][ix]]); 204 } 205 } 206 } 207 }; 208 209 // Few zimlets might expect listeners from calendar view controller object 210 ZmCalMgr.prototype._proxyListeners = 211 function(zimletListener, event) { 212 var calController = this.getCalViewController(); 213 return (new AjxListener(calController, zimletListener)).handleEvent(event); 214 }; 215 216 ZmCalMgr.prototype.isMiniCalCreated = 217 function() { 218 return (this._miniCalendar != null); 219 }; 220 221 ZmCalMgr.prototype._miniCalDateRangeListener = 222 function(ev) { 223 var viewId = appCtxt.getCurrentViewId(); 224 if (viewId == ZmId.VIEW_CAL) { 225 var calController = this.getCalViewController(); 226 calController._scheduleMaintenance(ZmCalViewController.MAINT_MINICAL); 227 } else { 228 this.highlightMiniCal(); 229 } 230 }; 231 232 ZmCalMgr.prototype._miniCalMouseOverDayCallback = 233 function(control, day) { 234 this._currentMouseOverDay = day; 235 var action = new AjxTimedAction(this, this._getDayToolTipOnDelay, [control, day]); 236 AjxTimedAction.scheduleAction(action, 1000); 237 }; 238 239 ZmCalMgr.prototype.getCalViewController = 240 function() { 241 var calController = AjxDispatcher.run("GetCalController"); 242 calController._miniCalendar = this._miniCalendar; 243 calController._minicalMenu = this._minicalMenu; 244 calController._miniCalDropTarget = this._miniCalDropTarget; 245 return calController; 246 }; 247 248 ZmCalMgr.prototype._miniCalMouseOutDayCallback = 249 function(control) { 250 this._currentMouseOverDay = null; 251 }; 252 253 ZmCalMgr.prototype._getDayToolTipOnDelay = 254 function(control, day) { 255 if (!this._currentMouseOverDay) { return; } 256 if ((this._currentMouseOverDay.getDate() == day.getDate()) && 257 (this._currentMouseOverDay.getMonth() == day.getMonth())) 258 { 259 this._currentMouseOverDay = null; 260 var mouseEv = DwtShell.mouseEvent; 261 if(mouseEv && mouseEv.docX > 0 && mouseEv.docY > 0) { 262 var callback = new AjxCallback(this, this.showTooltip, [control, mouseEv.docX, mouseEv.docY]); 263 this.getCalViewController().getDayToolTipText(day, false, callback, true); 264 } 265 } 266 }; 267 268 ZmCalMgr.prototype.showTooltip = 269 function(control, x, y, tooltipContent) { 270 control.setToolTipContent(tooltipContent); 271 if(x > 0 && y > 0) { 272 var shell = DwtShell.getShell(window); 273 var tooltip = shell.getToolTip(); 274 tooltip.setContent(tooltipContent); 275 tooltip.popup(x, y); 276 control.__tooltipClosed = false; 277 } 278 }; 279 280 ZmCalMgr.prototype.getApptSummaries = 281 function(params) { 282 var apptVec = this.setSearchParams(params); 283 284 if (apptVec != null && (apptVec instanceof AjxVector)) { 285 return apptVec; 286 } 287 288 // this array will hold a list of appts as we collect them from the server 289 this._rawAppts = []; 290 291 if (params.callback) { 292 this._search(params); 293 } else { 294 return this._search(params); 295 } 296 }; 297 298 ZmCalMgr.prototype.setSearchParams = 299 function(params) { 300 if (!(params.folderIds instanceof Array)) { 301 params.folderIds = [params.folderIds]; 302 } else if (params.folderIds.length == 0) { 303 var newVec = new AjxVector(); 304 if (params.callback) { 305 params.callback.run(newVec); 306 } 307 return newVec; 308 } 309 310 var folderIdMapper = {}; 311 var query = ""; 312 for (var i=0; i < params.folderIds.length; i++) { 313 var fid = params.folderIds[i]; 314 var systemFolderId = appCtxt.getActiveAccount().isMain 315 ? fid : ZmOrganizer.getSystemId(fid); 316 317 // map remote folder ids into local ones while processing search since 318 // server wont do it for us (see bug 7083) 319 var folder = appCtxt.getById(systemFolderId); 320 var rid = folder ? folder.getRemoteId() : systemFolderId; 321 folderIdMapper[rid] = systemFolderId; 322 323 if (query.length) { 324 query += " OR "; 325 } 326 var idText = AjxUtil.isNumeric(fid) ? fid : ['"', fid, '"'].join(""); 327 query += "inid:" + idText; 328 329 } 330 params.queryHint = query; 331 params.needToFetch = params.folderIds; 332 params.folderIdMapper = folderIdMapper; 333 params.offset = 0; 334 }; 335 336 ZmCalMgr.prototype._search = 337 function(params) { 338 var jsonObj = {SearchRequest:{_jsns:"urn:zimbraMail"}}; 339 var request = jsonObj.SearchRequest; 340 341 this._setSoapParams(request, params); 342 343 var calController = this.getCalViewController(); 344 var apptCache = calController.getApptCache(); 345 346 var accountName = appCtxt.multiAccounts ? appCtxt.accountList.mainAccount.name : null; 347 if (params.callback) { 348 appCtxt.getAppController().sendRequest({ 349 jsonObj: jsonObj, 350 asyncMode: true, 351 callback: (new AjxCallback(this, this._getApptSummariesResponse, [params])), 352 offlineCallback: apptCache.offlineSearchAppts(null, null, params), 353 noBusyOverlay: params.noBusyOverlay, 354 accountName: accountName 355 }); 356 } else { 357 var response = appCtxt.getAppController().sendRequest({jsonObj: jsonObj, accountName: accountName}); 358 var result = new ZmCsfeResult(response, false); 359 return this._getApptSummariesResponse(params, result); 360 } 361 }; 362 363 ZmCalMgr.prototype._setSoapParams = 364 function(request, params) { 365 request.sortBy = "none"; 366 request.limit = "500"; 367 request.calExpandInstStart = params.start; 368 request.calExpandInstEnd = params.end; 369 request.types = ZmSearch.TYPE[ZmItem.APPT]; 370 request.offset = params.offset; 371 372 var query = params.query; 373 if (params.queryHint) { 374 query = (query != null) 375 ? (query + " (" + params.queryHint + ")") 376 : params.queryHint; 377 } 378 request.query = {_content:query}; 379 }; 380 381 382 ZmCalMgr.prototype._getApptSummariesResponse = 383 function(params, result) { 384 // TODO: mark both as needing refresh? 385 if (!result) { return; } 386 387 var callback = params.callback; 388 var resp; 389 try { 390 resp = result.getResponse(); 391 } catch (ex) { 392 if (callback) { 393 callback.run(new AjxVector()); 394 } 395 return; 396 } 397 398 var searchResp = resp.SearchResponse; 399 var newList = this.processSearchResponse(searchResp, params); 400 if (newList == null) { return; } 401 402 if (callback) { 403 callback.run(newList, params.query); 404 } else { 405 return newList; 406 } 407 }; 408 409 ZmCalMgr.prototype.processSearchResponse = 410 function(searchResp, params) { 411 if(!searchResp) { return; } 412 413 if (searchResp && searchResp.appt && searchResp.appt.length) { 414 this._rawAppts = this._rawAppts != null 415 ? this._rawAppts.concat(searchResp.appt) 416 : searchResp.appt; 417 418 // if "more" flag set, keep requesting more appts 419 if (searchResp.more) { 420 var lastAppt = searchResp.appt[searchResp.appt.length-1]; 421 if (lastAppt) { 422 params.offset += 500; 423 this._search(params); 424 return; 425 } 426 } 427 } 428 429 var newList = new AjxVector(); 430 if (this._rawAppts && this._rawAppts.length) { 431 this._list = new ZmList(ZmItem.APPT); 432 for (var i = 0; i < this._rawAppts.length; i++) { 433 DBG.println(AjxDebug.DBG2, "appt[j]:" + this._rawAppts[i].name); 434 var apptNode = this._rawAppts[i]; 435 var instances = apptNode ? apptNode.inst : null; 436 if (instances) { 437 var args = {list:this._list}; 438 for (var j = 0; j < instances.length; j++) { 439 var appt = ZmCalBaseItem.createFromDom(apptNode, args, instances[j]); 440 DBG.println(AjxDebug.DBG2, "lite appt :" + appt); 441 if (appt) newList.add(appt); 442 } 443 } 444 } 445 446 } 447 return newList; 448 }; 449 450 ZmCalMgr.prototype.getCalendarName = 451 function(folderId) { 452 var app = appCtxt.getApp(ZmApp.CALENDAR); 453 return app.getCalendarName(folderId); 454 }; 455 456 // Mini calendar action menu listeners, calview controller is loaded and than 457 // event handling listener functions are called 458 ZmCalMgr.prototype._newApptAction = 459 function(ev) { 460 var calController = this.getCalViewController(); 461 calController._newApptAction(ev); 462 }; 463 464 ZmCalMgr.prototype._newAllDayApptAction = 465 function(ev) { 466 var calController = this.getCalViewController(); 467 calController._newAllDayApptAction(ev); 468 }; 469 470 ZmCalMgr.prototype._searchMailAction = 471 function(ev) { 472 var calController = this.getCalViewController(); 473 calController._searchMailAction(ev); 474 }; 475 476 ZmCalMgr.prototype.getCheckedCalendarFolderIds = 477 function(localOnly) { 478 var app = appCtxt.getApp(ZmApp.CALENDAR); 479 return app.getCheckedCalendarFolderIds(localOnly); 480 }; 481 482 ZmCalMgr.prototype.getReminderCalendarFolderIds = 483 function() { 484 var app = appCtxt.getApp(ZmApp.CALENDAR); 485 return app.getReminderCalendarFolderIds(); 486 }; 487 488 ZmCalMgr.prototype._handleError = 489 function(ex) { 490 if (ex.code == 'mail.INVITE_OUT_OF_DATE' || ex.code == 'mail.NO_SUCH_APPT') { 491 var msgDialog = appCtxt.getMsgDialog(); 492 msgDialog.setMessage(ZmMsg.apptOutOfDate, DwtMessageDialog.INFO_STYLE); 493 msgDialog.popup(); 494 return true; 495 } 496 return false; 497 }; 498 499 ZmCalMgr.prototype.highlightMiniCal = 500 function() { 501 this.getMiniCalCache()._getMiniCalData(this.getMiniCalendarParams()); 502 }; 503 504 ZmCalMgr.prototype.getMiniCalendarParams = 505 function() { 506 var dr = this.getMiniCalendar().getDateRange(); 507 return { 508 start: dr.start.getTime(), 509 end: dr.end.getTime(), 510 fanoutAllDay: true, 511 noBusyOverlay: true, 512 folderIds: this.getCheckedCalendarFolderIds(), 513 tz: AjxTimezone.DEFAULT 514 }; 515 }; 516 517 ZmCalMgr.prototype.getMiniCalCache = 518 function() { 519 if (!this._miniCalCache) { 520 this._miniCalCache = new ZmMiniCalCache(this); 521 } 522 return this._miniCalCache; 523 }; 524 525 ZmCalMgr.prototype.getQuickReminderSearchTimeRange = 526 function() { 527 var endOfDay = new Date(); 528 endOfDay.setHours(23,59,59,999); 529 530 var end = new Date(endOfDay.getTime()); 531 532 var start = endOfDay; 533 start.setHours(0,0,0, 0); 534 535 return { start: start.getTime(), end: end.getTime() }; 536 }; 537 538 ZmCalMgr.prototype.showQuickReminder = 539 function() { 540 var params = this.getQuickReminderParams(); 541 this.getApptSummaries(params); 542 }; 543 544 ZmCalMgr.prototype.getQuickReminderParams = 545 function() { 546 547 var timeRange = this.getQuickReminderSearchTimeRange(); 548 return { 549 start: timeRange.start, 550 end: timeRange.end, 551 fanoutAllDay: false, 552 folderIds: this.getCheckedCalendarFolderIds(true), 553 callback: (new AjxCallback(this, this._quickReminderCallback)), 554 includeReminders: true 555 }; 556 }; 557 558 ZmCalMgr.prototype._quickReminderCallback = 559 function(list) { 560 var newList = new AjxVector(); 561 this._cacheMap = {}; 562 var size = list.size(); 563 564 var currentTime = (new Date()).getTime(); 565 566 for (var i = 0; i < size; i++) { 567 var appt = list.get(i); 568 var id = appt.id; 569 if (!this._cacheMap[id]) { 570 this._cacheMap[id] = appt; 571 if(appt.isAllDayEvent()) continue; 572 var diff = appt.getStartTime() - currentTime; 573 var isUpcomingEvent = (diff >= 0 && diff <= AjxDateUtil.MSEC_PER_HOUR) 574 if((currentTime >= appt.getStartTime() && currentTime <= appt.getEndTime()) || isUpcomingEvent) { 575 appt.isUpcomingEvent = isUpcomingEvent; 576 newList.add(appt); 577 } 578 } 579 } 580 581 var qDlg = this.getQuickReminderDialog(); 582 qDlg.initialize(newList); 583 qDlg.popup(); 584 }; 585 586 587 /** 588 * Gets the quick reminder dialog. 589 * 590 * @return {ZmQuickReminderDialog} the dialog 591 */ 592 ZmCalMgr.prototype.getQuickReminderDialog = 593 function() { 594 if (this._reminderDialog == null) { 595 this._reminderDialog = new ZmQuickReminderDialog(appCtxt.getShell(), this, this._calController); 596 } 597 return this._reminderDialog; 598 }; 599