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 ZmCalBaseView = function(parent, className, posStyle, controller, view, readonly) { 25 if (arguments.length == 0) { return; } 26 27 DwtComposite.call(this, {parent:parent, className:className, posStyle:posStyle, id:ZmId.getViewId(view)}); 28 29 this._isReadOnly = readonly; 30 31 // BEGIN LIST-RELATED 32 this._setMouseEventHdlrs(); 33 this.setCursor("default"); 34 35 this.addListener(DwtEvent.ONMOUSEOUT, this._mouseOutListener.bind(this)); 36 this.addListener(DwtEvent.ONDBLCLICK, this._doubleClickListener.bind(this)); 37 this.addListener(DwtEvent.ONMOUSEDOWN, this._mouseDownListener.bind(this)); 38 this.addListener(DwtEvent.ONMOUSEUP, this._mouseUpListener.bind(this)); 39 this.addListener(DwtEvent.ONMOUSEMOVE, this._mouseMoveListener.bind(this)); 40 this.addListener(DwtEvent.ONFOCUS, this._focusListener.bind(this)); 41 42 this._controller = controller; 43 this.view = view; 44 this._evtMgr = new AjxEventMgr(); 45 this._selectedItems = new AjxVector(); 46 this._selEv = new DwtSelectionEvent(true); 47 this._actionEv = new DwtListViewActionEvent(true); 48 this._focusFirstAppt = true; 49 50 this._normalClass = "appt"; 51 this._selectedClass = [this._normalClass, DwtCssStyle.SELECTED].join('-'); 52 this._disabledSelectedClass = [this._selectedClass, DwtCssStyle.DISABLED].join("-"); 53 54 // the key is the HTML ID of the item's associated DIV; the value is an object 55 // with information about that row 56 this._data = {}; 57 58 // END LIST-RELATED 59 60 this._timeRangeStart = 0; 61 this._timeRangeEnd = 0; 62 this.addControlListener(this._controlListener.bind(this)); 63 this._createHtml(); 64 this._needsRefresh = true; 65 }; 66 67 ZmCalBaseView.prototype = new DwtComposite; 68 ZmCalBaseView.prototype.constructor = ZmCalBaseView; 69 70 ZmCalBaseView.TIME_SELECTION = "ZmCalTimeSelection"; 71 ZmCalBaseView.VIEW_ACTION = "ZmCalViewAction"; 72 73 ZmCalBaseView.TYPE_APPTS_DAYGRID = 1; // grid holding days, for example 74 ZmCalBaseView.TYPE_APPT = 2; // an appt 75 ZmCalBaseView.TYPE_HOURS_COL = 3; // hours on lefthand side 76 ZmCalBaseView.TYPE_APPT_BOTTOM_SASH = 4; // a sash for appt duration 77 ZmCalBaseView.TYPE_APPT_TOP_SASH = 5; // a sash for appt duration 78 ZmCalBaseView.TYPE_DAY_HEADER = 6; // over date header for a day 79 ZmCalBaseView.TYPE_MONTH_DAY = 7; // over a day in month view 80 ZmCalBaseView.TYPE_ALL_DAY = 8; // all day div area in day view 81 ZmCalBaseView.TYPE_SCHED_FREEBUSY = 9; // free/busy union 82 ZmCalBaseView.TYPE_DAY_SEP = 10;//allday separator 83 84 ZmCalBaseView.headerColorDelta = 0; 85 ZmCalBaseView.bodyColorDelta = .5; 86 ZmCalBaseView.deepenColorAdjustment = .9; 87 ZmCalBaseView.darkThreshold = (256 * 3) / 2; 88 ZmCalBaseView.deepenThreshold = .3; 89 ZmCalBaseView.WORK_HOURS_TIME_FORMAT = "HHmm"; 90 91 ZmCalBaseView._getColors = function(color) { 92 // generate header and body colors 93 color = color || ZmOrganizer.COLOR_VALUES[ZmOrganizer.DEFAULT_COLOR[ZmOrganizer.CALENDAR]]; 94 var hs = { bgcolor: AjxColor.darken(color, ZmCalBaseView.headerColorDelta) }; 95 var hd = { bgcolor: AjxColor.deepen(hs.bgcolor, ZmCalBaseView.deepenColorAdjustment) }; 96 var bs = { bgcolor: AjxColor.lighten(color, ZmCalBaseView.bodyColorDelta) }; 97 var bd = { bgcolor: AjxColor.deepen(bs.bgcolor, ZmCalBaseView.deepenColorAdjustment) }; 98 99 // ensure enough difference between background and deeper colors 100 var cs = AjxColor.components(hs.bgcolor); 101 var cd = AjxColor.components(hd.bgcolor); 102 var ss = cs[0]+cs[1]+cs[2]; 103 var sd = cd[0]+cd[1]+cd[2]; 104 if (ss/sd > 1 - ZmCalBaseView.deepenThreshold) { 105 hs.bgcolor = AjxColor.lighten(hd.bgcolor, ZmCalBaseView.deepenThreshold); 106 bs.bgcolor = AjxColor.lighten(bd.bgcolor, ZmCalBaseView.deepenThreshold); 107 } 108 109 // use light text color for dark backgrounds 110 hs.color = ZmCalBaseView._isDark(hs.bgcolor) && "#ffffff"; 111 hd.color = ZmCalBaseView._isDark(hd.bgcolor) && "#ffffff"; 112 bs.color = ZmCalBaseView._isDark(bs.bgcolor) && "#ffffff"; 113 bd.color = ZmCalBaseView._isDark(bd.bgcolor) && "#ffffff"; 114 115 return { standard: { header: hs, body: bs }, deeper: { header: hd, body: bd } }; 116 }; 117 118 /** 119 * Gets the key map name. 120 * 121 * @return {String} the key map name 122 */ 123 ZmCalBaseView.prototype.getKeyMapName = 124 function() { 125 return ZmKeyMap.MAP_CALENDAR; 126 }; 127 128 /** 129 * Handles the key action. 130 * 131 * @param {constant} actionCode the action code 132 * @param {Object} ev the event 133 * @see ZmApp.ACTION_CODES_R 134 * @see ZmKeyMap 135 * @see DwtControl 136 */ 137 ZmCalBaseView.prototype.handleKeyAction = function(actionCode, ev) { 138 switch (actionCode) { 139 // the next/prev appointment just iterate over all appointments in 140 // view in a chronological order 141 case ZmKeyMap.NEXT_APPT: 142 case ZmKeyMap.PREV_APPT: 143 this._iterateSelection(actionCode === ZmKeyMap.NEXT_APPT); 144 return true; 145 146 // the next/prev appointment whose start day differs from the 147 // current selection 148 case ZmKeyMap.NEXT_DAY: 149 case ZmKeyMap.PREV_DAY: 150 this._iterateSelection(actionCode === ZmKeyMap.NEXT_DAY, function(appt) { 151 return appt.startDate.toDateString(); 152 }); 153 return true; 154 155 // pagination 156 case ZmKeyMap.PREV_PAGE: 157 case ZmKeyMap.NEXT_PAGE: 158 this._paginate(actionCode === ZmKeyMap.NEXT_PAGE); 159 return true; 160 161 //Gets the Esc key handle 162 case DwtKeyMap.CANCEL: 163 this.deselectAll(); 164 return true; 165 166 case DwtKeyMap.SELECT: 167 return this._doubleClickListener(ev); 168 169 case DwtKeyMap.SUBMENU: 170 // notify action listeners so we pop up a context menu -- 171 // however, 'ev' is a keyboard event and has no position, so 172 // we create one for positioning the menu over the appointment 173 var rect = this.getTargetItemDiv(ev).getBoundingClientRect(); 174 ev.item = this.getTargetItem(ev); 175 ev.docX = rect.left + rect.width / 2; 176 ev.docY = rect.top + rect.height / 2; 177 178 this._evtMgr.notifyListeners(DwtEvent.ACTION, ev); 179 180 return true; 181 } 182 183 return DwtComposite.prototype.handleKeyAction.apply(this, arguments); 184 }; 185 186 ZmCalBaseView.prototype._paginate = function(forward) { 187 this._focusFirstAppt = forward; 188 this._controller._paginate(this.view, forward); 189 }; 190 191 /** 192 * Iterate the selection over appoinments; if no next/previous 193 * appointment found, switch pages. 194 * 195 * @param forward [Boolean]* whether to iterate forwards or backwards 196 * 197 * @param keyFunc [Function] function to map appointments to values; 198 * select the first appointnment with a value 199 * different from the current selection 200 * 201 * @private 202 */ 203 ZmCalBaseView.prototype._iterateSelection = function(forward, keyFunc) { 204 var list = this.getList(); 205 206 if (!list.size()) { 207 this._paginate(forward); 208 return; 209 } 210 211 if (this.getSelectionCount() == 0) { 212 this.setSelection(forward ? list.get(0) : list.getLast()); 213 } 214 215 if (!keyFunc) { 216 keyFunc = function(x) { return x; } 217 } 218 219 var selappt = this.getSelection()[0] 220 var selIdx = list.indexOf(selappt); 221 var selKey = keyFunc(selappt); 222 223 for (var i = selIdx; i < list.size() && i >= 0; i += (forward ? 1 : -1)) { 224 var appt = list.get(i); 225 226 if (keyFunc(appt) != selKey) { 227 this.setSelection(appt); 228 return; 229 } 230 } 231 232 this._paginate(forward); 233 }; 234 235 ZmCalBaseView._toColorsCss = 236 function(object) { 237 var a = [ "background-color:",object.bgcolor,";" ]; 238 if (object.color) { 239 a.push("color:",object.color,";"); 240 } 241 return a.join(""); 242 }; 243 244 ZmCalBaseView._isDark = 245 function(color) { 246 var c = AjxColor.components(color); 247 return c[0]+c[1]+c[2] < ZmCalBaseView.darkThreshold; 248 }; 249 250 ZmCalBaseView.prototype.getController = 251 function() { 252 return this._controller; 253 }; 254 255 ZmCalBaseView.prototype.firstDayOfWeek = 256 function() { 257 return appCtxt.get(ZmSetting.CAL_FIRST_DAY_OF_WEEK) || 0; 258 }; 259 260 261 ZmCalBaseView.getWorkingHours = 262 function() { 263 return appCtxt.get(ZmSetting.CAL_WORKING_HOURS) || 0; 264 }; 265 266 ZmCalBaseView.parseWorkingHours = 267 function(wHrsString) { 268 if(wHrsString === 0) { 269 return []; 270 } 271 var userTimeZone = appCtxt.get(ZmSetting.DEFAULT_TIMEZONE), 272 currentTimeZone = AjxTimezone.getServerId(AjxTimezone.DEFAULT), 273 wHrsPerDay = wHrsString.split(','), 274 i, 275 wHrs = [], 276 wDay, 277 w, 278 offset1, 279 offset2, 280 hourMinOffset = 0, 281 idx, 282 startDate = new Date(), 283 endDate = new Date(), 284 hourMin, 285 startDayIdx, 286 endDayIdx, 287 curDayIdx = endDate.getDay(), 288 tf = new AjxDateFormat(ZmCalBaseView.WORK_HOURS_TIME_FORMAT); 289 290 //Helper inner functions, these functions takes the advantage of the fact that wHrs is available in local scope 291 function isWorkingDay(idx) { 292 return wHrs[idx] && wHrs[idx].isWorkingDay; 293 } 294 295 function setWorkingDay(idx, startTime, endTime) { 296 if(isWorkingDay(idx)) { 297 addWorkingTime(idx, startTime, endTime); 298 } 299 else { 300 addWorkingDay(idx, startTime, endTime); 301 } 302 } 303 304 function setNonWorkingDay(idx) { 305 wHrs[idx] = {}; 306 wHrs[idx].isWorkingDay = false; 307 wHrs[idx].startTime = ["0000"]; 308 wHrs[idx].endTime = ["0000"]; 309 } 310 311 function addWorkingDay(idx, startTime, endTime) { 312 wHrs[idx] = {}; 313 wHrs[idx].isWorkingDay = true; 314 wHrs[idx].startTime = [startTime]; 315 wHrs[idx].endTime = [endTime]; 316 } 317 318 function addWorkingTime(idx, startTime, endTime) { 319 wHrs[idx].startTime.push(startTime); 320 wHrs[idx].endTime.push(endTime); 321 } 322 323 if(userTimeZone != currentTimeZone) { 324 offset1 = AjxTimezone.getOffset(AjxTimezone.getClientId(currentTimeZone), startDate); 325 offset2 = AjxTimezone.getOffset(AjxTimezone.getClientId(userTimeZone), startDate); 326 hourMinOffset = offset2 - offset1; 327 } 328 for(i=0; i<wHrsPerDay.length; i++) { 329 wDay = wHrsPerDay[i].split(':'); 330 w = {}; 331 idx = wDay[0]-1; 332 if(wDay[1] === "N") { 333 if(!isWorkingDay(idx)) { 334 setNonWorkingDay(idx); 335 } 336 continue; 337 } 338 339 if(hourMinOffset) { 340 endDate = new Date(); 341 startDate = new Date(); 342 343 endDate.setHours(wDay[3]/100, wDay[3]%100); 344 hourMin = endDate.getHours() * 60 + endDate.getMinutes() - hourMinOffset; 345 endDate.setHours(hourMin/60, hourMin%60); 346 endDayIdx = endDate.getDay(); 347 348 startDate.setHours(wDay[2]/100, wDay[2]%100); 349 hourMin = startDate.getHours() * 60 + startDate.getMinutes() - hourMinOffset; 350 startDate.setHours(hourMin/60, hourMin%60); 351 startDayIdx = startDate.getDay(); 352 353 if(startDayIdx == curDayIdx && endDayIdx == curDayIdx) { 354 //Case 1 working time starts current day and ends on the current day -- IDEAL one :) 355 setWorkingDay(idx, tf.format(startDate), tf.format(endDate)); 356 } 357 else if((endDayIdx == 0 && startDayIdx == 6) || 358 (startDayIdx < curDayIdx && endDayIdx == curDayIdx)) { 359 //Case 2 working time starts prev day and ends on current day 360 startDayIdx = idx-1; 361 if(startDayIdx < 0) { 362 startDayIdx = 6; 363 } 364 setWorkingDay(startDayIdx, tf.format(startDate), "2400"); 365 setWorkingDay(idx, "0000", tf.format(endDate)); 366 } 367 else if((startDayIdx == 6 && endDayIdx == 0) || 368 (startDayIdx == curDayIdx && endDayIdx > curDayIdx)) { 369 //Case 3 working time starts current day and ends on next day 370 endDayIdx = idx+1; 371 if(endDayIdx > 6) { 372 endDayIdx = 0; 373 } 374 setWorkingDay(endDayIdx, "0000", tf.format(endDate)); 375 setWorkingDay(idx, tf.format(startDate), "2400"); 376 } 377 else if(startDayIdx < curDayIdx && 378 endDayIdx < curDayIdx && 379 startDayIdx == endDayIdx) { 380 //EDGE CASE 1: working time starts and ends on the prev day 381 startDayIdx = idx-1; 382 setWorkingDay(startDayIdx, tf.format(startDate), tf.format(endDate)); 383 if(!isWorkingDay(idx)) { 384 setNonWorkingDay(idx); 385 } 386 } 387 388 else if(startDayIdx > curDayIdx && 389 endDayIdx > curDayIdx && 390 startDayIdx == endDayIdx) { 391 //EDGE CASE 2: working time starts and ends on the next day 392 endDayIdx = idx+1; 393 setWorkingDay(endDayIdx, tf.format(startDate), tf.format(endDate)); 394 if(!isWorkingDay(idx)) { 395 setNonWorkingDay(idx); 396 } 397 } 398 } 399 else { 400 //There is no timezone diff, client and server are in the same timezone 401 setWorkingDay(idx, wDay[2], wDay[3]); 402 } 403 404 } 405 return wHrs; 406 }; 407 408 ZmCalBaseView.prototype.addViewActionListener = 409 function(listener) { 410 this._evtMgr.addListener(ZmCalBaseView.VIEW_ACTION, listener); 411 }; 412 413 ZmCalBaseView.prototype.removeViewActionListener = 414 function(listener) { 415 this._evtMgr.removeListener(ZmCalBaseView.VIEW_ACTION, listener); 416 }; 417 418 // BEGIN LIST-RELATED 419 420 ZmCalBaseView.prototype.addSelectionListener = 421 function(listener) { 422 this._evtMgr.addListener(DwtEvent.SELECTION, listener); 423 }; 424 425 ZmCalBaseView.prototype.removeSelectionListener = 426 function(listener) { 427 this._evtMgr.removeListener(DwtEvent.SELECTION, listener); 428 }; 429 430 ZmCalBaseView.prototype.addActionListener = 431 function(listener) { 432 this._evtMgr.addListener(DwtEvent.ACTION, listener); 433 }; 434 435 ZmCalBaseView.prototype.removeActionListener = 436 function(listener) { 437 this._evtMgr.removeListener(DwtEvent.ACTION, listener); 438 }; 439 440 ZmCalBaseView.prototype.getList = 441 function() { 442 return this._list; 443 }; 444 445 ZmCalBaseView.prototype.associateItemWithElement = 446 function (item, element, type, optionalId) { 447 DwtListView.prototype.associateItemWithElement.apply(this, arguments); 448 }; 449 450 ZmCalBaseView.prototype.getItemFromElement = 451 function(el) { 452 return DwtListView.prototype.getItemFromElement.apply(this, arguments); 453 }; 454 455 ZmCalBaseView.prototype.getTargetItemDiv = 456 function(ev) { 457 return this.findItemDiv(DwtUiEvent.getTarget(ev)); 458 }; 459 460 ZmCalBaseView.prototype.getTargetItem = 461 function(ev) { 462 return this.findItem(DwtUiEvent.getTarget(ev)); 463 }; 464 465 ZmCalBaseView.prototype.findItem = 466 function(el) { 467 return DwtListView.prototype.findItem.apply(this, arguments); 468 }; 469 470 ZmCalBaseView.prototype.findItemDiv = 471 function(el) { 472 return DwtListView.prototype.findItemDiv.apply(this, arguments); 473 }; 474 475 ZmCalBaseView.prototype._getItemData = 476 function(el, field, id) { 477 return DwtListView.prototype._getItemData.apply(this, arguments); 478 }; 479 480 ZmCalBaseView.prototype._setItemData = 481 function(id, field, value) { 482 DwtListView.prototype._setItemData.apply(this, arguments); 483 }; 484 485 ZmCalBaseView.prototype.deselectAll = 486 function() { 487 this.deselectAppt(this._selectedItems); 488 }; 489 490 /** 491 * Returns a style appropriate to the given item type. Subclasses should override to return 492 * styles for different item types. This implementation does not consider the type. 493 * 494 * @param type [constant]* a type constant 495 * @param selected [boolean]* if true, return a style for an item that has been selected 496 * @param disabled [boolean]* if true, return a style for an item that has been disabled 497 * @param item [object]* item behind the div 498 * 499 * @private 500 */ 501 ZmCalBaseView.prototype._getStyle = 502 function(type, selected, disabled, item) { 503 return (!selected) 504 ? this._normalClass 505 : (disabled ? this._disabledSelectedClass : this._selectedClass); 506 }; 507 508 ZmCalBaseView.prototype.getToolTipContent = 509 function(ev) { 510 var div = this.getTargetItemDiv(ev); 511 if (!div) { return null; } 512 if (this._getItemData(div, "type") != ZmCalBaseView.TYPE_APPT) { return null; } 513 514 var item = this.getItemFromElement(div); 515 return item.getToolTip(this._controller); 516 }; 517 518 // tooltip position will be based on cursor 519 ZmCalBaseView.prototype.getTooltipBase = 520 function(hoverEv) { 521 return null; 522 }; 523 524 ZmCalBaseView.prototype.getApptDetails = 525 function(appt, callback, uid) { 526 if (this._currentMouseOverApptId && 527 this._currentMouseOverApptId == uid) 528 { 529 this._currentMouseOverApptId = null; 530 appt.getDetails(null, callback, null, null, true); 531 } 532 }; 533 534 ZmCalBaseView.prototype._mouseOutListener = 535 function(ev) { 536 var div = this.getTargetItemDiv(ev); 537 if (!div) { return; } 538 539 // NOTE: The DwtListView handles the mouse events on the list items 540 // that have associated tooltip text. Therefore, we must 541 // explicitly null out the tooltip content whenever we handle 542 // a mouse out event. This will prevent the tooltip from 543 // being displayed when we re-enter the listview even though 544 // we're not over a list item. 545 if (this._getItemData(div, "type") == ZmCalBaseView.TYPE_APPT) { 546 this.setToolTipContent(null); 547 } 548 this._mouseOutAction(ev, div); 549 }; 550 551 ZmCalBaseView.prototype._mouseOutAction = 552 function(ev, div) { 553 return true; 554 }; 555 556 557 ZmCalBaseView.prototype._mouseMoveListener = 558 function(ev) { 559 // do nothing 560 }; 561 562 ZmCalBaseView.prototype._focusListener = 563 function(ev) { 564 var item = this.getTargetItem(ev); 565 566 if (item) { 567 this.setSelection(item); 568 } 569 }; 570 571 // XXX: why not use Dwt.findAncestor? 572 ZmCalBaseView.prototype._findAncestor = 573 function(elem, attr) { 574 while (elem && (elem[attr] == null)) { 575 elem = elem.parentNode; 576 } 577 return elem; 578 }; 579 580 ZmCalBaseView.prototype._mouseDownListener = 581 function(ev) { 582 if (this._isReadOnly) { return; } 583 584 var div = this.getTargetItemDiv(ev); 585 if (!div) { 586 return this._mouseDownAction(ev, div); 587 } 588 589 this._clickDiv = div; 590 if (this._getItemData(div, "type") == ZmCalBaseView.TYPE_APPT) { 591 if (ev.button == DwtMouseEvent.LEFT || ev.button == DwtMouseEvent.RIGHT) { 592 this._itemClicked(div, ev); 593 } 594 } 595 return this._mouseDownAction(ev, div); 596 }; 597 598 ZmCalBaseView.prototype._mouseDownAction = 599 function(ev, div) { 600 return !Dwt.ffScrollbarCheck(ev); 601 }; 602 603 ZmCalBaseView.prototype._mouseUpListener = 604 function(ev) { 605 delete this._clickDiv; 606 return this._mouseUpAction(ev, this.getTargetItemDiv(ev)); 607 }; 608 609 ZmCalBaseView.prototype._mouseUpAction = 610 function(ev, div) { 611 return !Dwt.ffScrollbarCheck(ev); 612 }; 613 614 ZmCalBaseView.prototype._doubleClickAction = 615 function(ev, div) { return true; }; 616 617 ZmCalBaseView.prototype._doubleClickListener = 618 function(ev) { 619 var div = this.getTargetItemDiv(ev); 620 if (!div) { return; } 621 622 var handled = false; 623 624 if (this._getItemData(div, "type") == ZmCalBaseView.TYPE_APPT) { 625 if (this._evtMgr.isListenerRegistered(DwtEvent.SELECTION)) { 626 DwtUiEvent.copy(this._selEv, ev); 627 var item = this.getItemFromElement(div); 628 var orig = item.getOrig(); 629 item = orig && orig.isMultiDay() ? orig : item; 630 this._selEv.item = item; 631 this._selEv.detail = DwtListView.ITEM_DBL_CLICKED; 632 this._evtMgr.notifyListeners(DwtEvent.SELECTION, this._selEv); 633 634 handled = true; 635 } 636 } 637 return this._doubleClickAction(ev, div) || handled; 638 }; 639 640 ZmCalBaseView.prototype._itemClicked = 641 function(clickedEl, ev) { 642 var i; 643 var selected = this._selectedItems.contains(clickedEl); 644 var item = this.getItemFromElement(clickedEl); 645 var type = this._getItemData(clickedEl, "type"); 646 647 if (ev.shiftKey && selected) { 648 //Deselect the current selected appointment 649 this.deselectAppt([clickedEl]); 650 } else if (!selected) { 651 this.setSelection(item); 652 } 653 654 if (ev.button == DwtMouseEvent.RIGHT) { 655 DwtUiEvent.copy(this._actionEv, ev); 656 this._actionEv.item = item; 657 this._evtMgr.notifyListeners(DwtEvent.ACTION, this._actionEv); 658 } 659 }; 660 661 // YUCK: ZmListView overloads b/c ZmListController thinks its always dealing w/ ZmListView's 662 ZmCalBaseView.prototype.setSelectionCbox = function(obj, bContained) {}; 663 ZmCalBaseView.prototype.setSelectionHdrCbox = function(check) {}; 664 665 ZmCalBaseView.prototype.setSelection = 666 function(item, skipNotify) { 667 var el = this._getElFromItem(item); 668 669 if (el) { 670 var i; 671 var a = this._selectedItems.getArray(); 672 var sz = this._selectedItems.size(); 673 for (i = 0; i < sz; i++) { 674 a[i].className = this._getStyle(this._getItemData(a[i], "type")); 675 } 676 this._selectedItems.removeAll(); 677 this._selectedItems.add(el); 678 679 el.className = this._getStyle(this._getItemData(el, "type"), true, !this.getEnabled(), item); 680 681 this.setFocusElement(el); 682 Dwt.clearHandler(el, DwtEvent.ONCLICK); 683 684 if (!skipNotify && this._evtMgr.isListenerRegistered(DwtEvent.SELECTION)) { 685 var selEv = new DwtSelectionEvent(true); 686 selEv.button = DwtMouseEvent.LEFT; 687 selEv.target = el; 688 selEv.item = item; 689 selEv.detail = DwtListView.ITEM_SELECTED; 690 this._evtMgr.notifyListeners(DwtEvent.SELECTION, selEv); 691 } 692 } 693 }; 694 695 ZmCalBaseView.prototype._getItemCountType = function() { 696 return ZmId.ITEM_APPOINTMENT; 697 }; 698 699 ZmCalBaseView.prototype.getSelectionCount = 700 function() { 701 return this._selectedItems.size(); 702 }; 703 704 ZmCalBaseView.prototype.getSelection = 705 function() { 706 var a = new Array(); 707 var sa = this._selectedItems.getArray(); 708 var saLen = this._selectedItems.size(); 709 for (var i = 0; i < saLen; i++) { 710 a[i] = this.getItemFromElement(sa[i]); 711 } 712 return a; 713 }; 714 715 ZmCalBaseView.prototype.getSelectedItems = 716 function() { 717 return this._selectedItems; 718 }; 719 720 ZmCalBaseView.prototype.handleActionPopdown = 721 function(ev) { 722 // clear out old right click selection 723 ZmCalViewController._contextMenuOpened = false; 724 725 if(ev && ev._ev && ev._ev.type === "mousedown"){//Only check for mouse events 726 var htmlEl = DwtUiEvent.getTarget(ev._ev), 727 element = document.getElementById(this._bodyDivId) || document.getElementById(this._daysId); 728 729 if(element){ 730 while (htmlEl !== null) { 731 if(htmlEl === element){ 732 ZmCalViewController._contextMenuOpened = true; 733 break; 734 } 735 htmlEl = htmlEl.parentNode; 736 } 737 } 738 } 739 }; 740 741 // END LIST-RELATED 742 743 ZmCalBaseView.prototype.getTitle = 744 function() { 745 return [ZmMsg.zimbraTitle, this.getCalTitle()].join(": "); 746 }; 747 748 ZmCalBaseView.prototype.needsRefresh = 749 function() { 750 return this._needsRefresh; 751 }; 752 753 ZmCalBaseView.prototype.setNeedsRefresh = 754 function(refresh) { 755 this._needsRefresh = refresh; 756 }; 757 758 ZmCalBaseView.prototype._getItemId = 759 function(item) { 760 return item ? (DwtId.getListViewItemId(DwtId.WIDGET_ITEM, this.view, item.getUniqueId())) : null; 761 }; 762 763 ZmCalBaseView.prototype.addTimeSelectionListener = 764 function(listener) { 765 this.addListener(ZmCalBaseView.TIME_SELECTION, listener); 766 }; 767 768 ZmCalBaseView.prototype.removeTimeSelectionListener = 769 function(listener) { 770 this.removeListener(ZmCalBaseView.TIME_SELECTION, listener); 771 }; 772 773 ZmCalBaseView.prototype.addDateRangeListener = 774 function(listener) { 775 this.addListener(DwtEvent.DATE_RANGE, listener); 776 }; 777 778 ZmCalBaseView.prototype.removeDateRangeListener = 779 function(listener) { 780 this.removeListener(DwtEvent.DATE_RANGE, listener); 781 }; 782 783 ZmCalBaseView.prototype.getRollField = 784 function() { 785 // override. 786 return 0; 787 }; 788 789 ZmCalBaseView.prototype.getDate = 790 function() { 791 return this._date; 792 }; 793 //to override 794 ZmCalBaseView.prototype.getAtttendees = 795 function() { 796 return null; 797 }; 798 799 ZmCalBaseView.prototype.getTimeRange = 800 function() { 801 return { start: this._timeRangeStart, end: this._timeRangeEnd }; 802 }; 803 804 ZmCalBaseView.prototype.isInView = 805 function(appt) { 806 return appt.isInRange(this._timeRangeStart, this._timeRangeEnd); 807 }; 808 809 ZmCalBaseView.prototype.isStartInView = 810 function(appt) { 811 return appt.isStartInRange(this._timeRangeStart, this._timeRangeEnd); 812 }; 813 814 ZmCalBaseView.prototype.isEndInView = 815 function(appt) { 816 return appt.isEndInRange(this._timeRangeStart, this._timeRangeEnd); 817 }; 818 819 ZmCalBaseView.prototype._dayKey = 820 function(date) { 821 return (date.getFullYear()+"/"+date.getMonth()+"/"+date.getDate()); 822 }; 823 824 ZmCalBaseView.prototype.setDate = 825 function(date, duration, roll) { 826 this._duration = duration; 827 this._date = new Date(date.getTime()); 828 var d = new Date(date.getTime()); 829 d.setHours(0, 0, 0, 0); 830 var t = d.getTime(); 831 if (roll || t < this._timeRangeStart || t >= this._timeRangeEnd) { 832 this._resetList(); 833 this._updateRange(); 834 this._dateUpdate(true); 835 this._updateTitle(); 836 837 // Notify any listeners 838 if (this.isListenerRegistered(DwtEvent.DATE_RANGE)) { 839 if (!this._dateRangeEvent) 840 this._dateRangeEvent = new DwtDateRangeEvent(true); 841 this._dateRangeEvent.item = this; 842 this._dateRangeEvent.start = new Date(this._timeRangeStart); 843 this._dateRangeEvent.end = new Date(this._timeRangeEnd); 844 this.notifyListeners(DwtEvent.DATE_RANGE, this._dateRangeEvent); 845 } 846 } else { 847 this._dateUpdate(false); 848 } 849 }; 850 851 ZmCalBaseView.prototype._dateUpdate = 852 function(rangeChanged) { 853 // override: responsible for updating any view-specific data when the date 854 // changes during a setDate call. 855 }; 856 857 ZmCalBaseView.prototype._apptSelected = 858 function() { 859 // override: called when an appointment is clicked to see if the view should 860 // de-select a selected time range. For example, in day view if you have 861 // selected the 8:00 AM row and then click on an appt, the 8:00 AM row 862 // should be de-selected. If you are in month view though and have a day 863 // selected, thne day should still be selected if the appt you clicked on is 864 // in the same day. 865 }; 866 867 // override 868 ZmCalBaseView.prototype._updateRange = 869 function() { 870 this._updateDays(); 871 this._timeRangeStart = this._days[0].date.getTime(); 872 //this._timeRangeEnd = this._days[this.numDays-1].date.getTime() + AjxDateUtil.MSEC_PER_DAY; 873 var endDate = this._days[this.numDays-1].date; 874 endDate.setHours(23, 59, 59, 999); 875 this._timeRangeEnd = endDate.getTime(); 876 }; 877 878 // override 879 ZmCalBaseView.prototype._updateTitle = 880 function() { }; 881 882 ZmCalBaseView.prototype.addAppt = 883 function(ao) { 884 var item = this._createItemHtml(ao); 885 if (!item) { 886 return; 887 } 888 var div = this._getDivForAppt(ao); 889 if (div) div.appendChild(item); 890 891 this._postApptCreate(ao,div); 892 }; 893 894 // override 895 ZmCalBaseView.prototype._postApptCreate = 896 function(appt,div) { 897 }; 898 899 ZmCalBaseView.prototype.set = 900 function(list) { 901 this._preSet(); 902 this.deselectAll(); 903 list = list.filter(this.isInView.bind(this)); 904 this._resetList(); 905 this._list = list; 906 var showDeclined = appCtxt.get(ZmSetting.CAL_SHOW_DECLINED_MEETINGS); 907 if (list) { 908 var size = list.size(); 909 if (size != 0) { 910 for (var i=0; i < size; i++) { 911 var ao = list.get(i); 912 if (showDeclined || (ao.ptst != ZmCalBaseItem.PSTATUS_DECLINED)) { 913 this.addAppt(ao); 914 } 915 } 916 } 917 } 918 919 this._postSet(list); 920 921 // the calendar itself may have focus; this re-focuses any items 922 // within it 923 var selappt = this._focusFirstAppt ? this._list.get(0) : this._list.getLast(); 924 this._focusFirstAppt = true; 925 926 if (selappt) { 927 this.setSelection(selappt); 928 } 929 }; 930 931 // override 932 ZmCalBaseView.prototype._fanoutAllDay = 933 function(appt) { 934 return true; 935 }; 936 937 // override 938 ZmCalBaseView.prototype._postSet = 939 function(appt) {}; 940 941 // override 942 ZmCalBaseView.prototype._preSet = 943 function(appt) {}; 944 945 // override 946 ZmCalBaseView.prototype._getDivForAppt = 947 function(appt) {}; 948 949 ZmCalBaseView.prototype._addApptIcons = 950 function(appt, html, idx) { 951 html[idx++] = "<table border=0 cellpadding=0 cellspacing=0 style='display:inline'><tr>"; 952 953 if (appt.otherAttendees) { 954 html[idx++] = "<td>"; 955 html[idx++] = AjxImg.getImageHtml("ApptMeeting"); 956 html[idx++] = "</td>"; 957 } 958 959 if (appt.isException) { 960 html[idx++] = "<td>"; 961 html[idx++] = AjxImg.getImageHtml("ApptException"); 962 html[idx++] = "</td>"; 963 } else if (appt.isRecurring()) { 964 html[idx++] = "<td>"; 965 html[idx++] = AjxImg.getImageHtml("ApptRecur"); 966 html[idx++] = "</td>"; 967 } 968 969 if (appt.alarm) { 970 html[idx++] = "<td>"; 971 html[idx++] = AjxImg.getImageHtml("ApptReminder"); 972 html[idx++] = "</td>"; 973 } 974 html[idx++] = "</tr></table>"; 975 976 return idx; 977 }; 978 979 ZmCalBaseView.prototype._getElFromItem = 980 function(item) { 981 return document.getElementById(this._getItemId(item)); 982 }; 983 984 ZmCalBaseView.prototype._resetList = 985 function() { 986 var list = this.getList(); 987 var size = list ? list.size() : 0; 988 if (size == 0) return; 989 990 this.setFocusElement(this.getHtmlElement()); 991 992 for (var i=0; i < size; i++) { 993 var ao = list.get(i); 994 var id = this._getItemId(ao); 995 var appt = document.getElementById(id); 996 if (appt) { 997 appt.parentNode.removeChild(appt); 998 this._data[id] = null; 999 } 1000 } 1001 list.removeAll(); 1002 this.removeAll(); 1003 }; 1004 1005 ZmCalBaseView.prototype.removeAll = 1006 function() { 1007 this._selectedItems.removeAll(); 1008 }; 1009 1010 ZmCalBaseView.prototype.layoutView = 1011 function() { 1012 this._layout(); 1013 }; 1014 1015 ZmCalBaseView.prototype.getCalTitle = 1016 function() { 1017 return this._title; 1018 }; 1019 1020 ZmCalBaseView.prototype._getStartDate = 1021 function() { 1022 var timeRange = this.getTimeRange(); 1023 return new Date(timeRange.start); 1024 }; 1025 1026 // override 1027 ZmCalBaseView.prototype._createItemHtml = 1028 function(appt) {}; 1029 1030 // override 1031 ZmCalBaseView.prototype._createHtml = 1032 function() {}; 1033 1034 // override 1035 ZmCalBaseView.prototype.checkIndicatorNeed = 1036 function(viewId, startDate) {}; 1037 1038 ZmCalBaseView.prototype._controlListener = 1039 function(ev) { 1040 if ((ev.oldWidth != ev.newWidth) || 1041 (ev.oldHeight != ev.newHeight)) 1042 { 1043 this._layout(); 1044 } 1045 }; 1046 1047 // override 1048 ZmCalBaseView.prototype._layout = 1049 function() {}; 1050 1051 ZmCalBaseView.prototype._timeSelectionEvent = 1052 function(date, duration, isDblClick, allDay, folderId, shiftKey) { 1053 if (!this._selectionEvent) this._selectionEvent = new DwtSelectionEvent(true); 1054 var sev = this._selectionEvent; 1055 sev._isDblClick = isDblClick; 1056 sev.item = this; 1057 sev.detail = date; 1058 sev.duration = duration; 1059 sev.isAllDay = allDay; 1060 sev.folderId = folderId; 1061 sev.force = false; 1062 sev.shiftKey = shiftKey; 1063 this.notifyListeners(ZmCalBaseView.TIME_SELECTION, this._selectionEvent); 1064 sev._isDblClick = false; 1065 }; 1066 1067 1068 ZmCalBaseView._setApptOpacity = 1069 function(appt, div) { 1070 var opacity = this.getApptOpacity(appt); 1071 Dwt.setOpacity(div, opacity); 1072 }; 1073 1074 ZmCalBaseView.getApptOpacity = 1075 function(appt) { 1076 var opacity = 100; 1077 1078 switch (appt.ptst) { 1079 case ZmCalBaseItem.PSTATUS_DECLINED: opacity = ZmCalColView._OPACITY_APPT_DECLINED; break; 1080 case ZmCalBaseItem.PSTATUS_TENTATIVE: opacity = ZmCalColView._OPACITY_APPT_TENTATIVE; break; 1081 default: opacity = ZmCalColView._OPACITY_APPT_NORMAL; break; 1082 } 1083 1084 // obey free busy status for organizer's appts 1085 if (appt.fba && appt.isOrganizer()) { 1086 switch (appt.fba) { 1087 case "F": opacity = ZmCalColView._OPACITY_APPT_FREE; break; 1088 case "B": opacity = ZmCalColView._OPACITY_APPT_BUSY; break; 1089 case "T": opacity = ZmCalColView._OPACITY_APPT_TENTATIVE; break; 1090 } 1091 } 1092 return opacity; 1093 }; 1094 1095 ZmCalBaseView._emptyHdlr = 1096 function(ev) { 1097 var mouseEv = DwtShell.mouseEvent; 1098 mouseEv.setFromDhtmlEvent(ev); 1099 mouseEv._stopPropagation = true; 1100 mouseEv._returnValue = false; 1101 mouseEv.setToDhtmlEvent(ev); 1102 return false; 1103 }; 1104 1105 1106 ZmCalBaseView.prototype._apptMouseDownAction = 1107 function(ev, apptEl, appt) { 1108 if (ev.button != DwtMouseEvent.LEFT) { return false; } 1109 1110 if (!appt) { 1111 appt = this.getItemFromElement(apptEl); 1112 } 1113 var calendar = appCtxt.getById(appt.folderId); 1114 var isRemote = Boolean(calendar.url); 1115 if (appt.isReadOnly() || isRemote || appCtxt.isWebClientOffline()) return false; 1116 1117 var apptOffset = Dwt.toWindow(ev.target, ev.elementX, ev.elementY, apptEl, false); 1118 1119 var data = { 1120 dndStarted: false, 1121 appt: appt, 1122 view: this, 1123 apptEl: apptEl, 1124 apptOffset: apptOffset, 1125 docX: ev.docX, 1126 docY: ev.docY 1127 }; 1128 1129 var capture = new DwtMouseEventCapture({ 1130 targetObj:data, 1131 mouseOverHdlr:ZmCalBaseView._emptyHdlr, 1132 mouseDownHdlr:ZmCalBaseView._emptyHdlr, // mouse down (already handled by action) 1133 mouseMoveHdlr:ZmCalBaseView._apptMouseMoveHdlr, 1134 mouseUpHdlr: ZmCalBaseView._apptMouseUpHdlr, 1135 mouseOutHdlr: ZmCalBaseView._emptyHdlr 1136 }); 1137 DBG.println(AjxDebug.DBG3,"data.docX,Y: " + data.docX + "," + data.docY); 1138 1139 this._createContainerRect(data); 1140 // Problem with Month View ?? 1141 this._controller.setCurrentListView(this); 1142 1143 capture.capture(); 1144 return false; 1145 }; 1146 1147 1148 1149 ZmCalBaseView.prototype._getApptDragProxy = 1150 function(data) { 1151 // set icon 1152 var icon; 1153 if (this._apptDragProxyDivId == null) { 1154 icon = document.createElement("div"); 1155 icon.id = this._apptDragProxyDivId = Dwt.getNextId(); 1156 Dwt.setPosition(icon, Dwt.ABSOLUTE_STYLE); 1157 this.shell.getHtmlElement().appendChild(icon); 1158 Dwt.setZIndex(icon, Dwt.Z_DND); 1159 } else { 1160 icon = document.getElementById(this._apptDragProxyDivId); 1161 } 1162 icon.className = DwtCssStyle.NOT_DROPPABLE; 1163 1164 var appt = data.appt; 1165 var formatter = AjxDateFormat.getDateInstance(AjxDateFormat.SHORT); 1166 var color = ZmCalendarApp.COLORS[this._controller.getCalendarColor(appt.folderId)]; 1167 if (appt.ptst != ZmCalBaseItem.PSTATUS_NEEDS_ACTION) { 1168 color += "Bg"; 1169 } 1170 1171 var proxyData = { 1172 shortDate: formatter.format(appt.startDate), 1173 dur: appt.getShortStartHour(), 1174 color: color, 1175 apptName: AjxStringUtil.htmlEncode(appt.getName()) 1176 }; 1177 1178 icon.innerHTML = AjxTemplate.expand("calendar.Calendar#ApptDragProxy", proxyData); 1179 1180 var imgHtml = AjxImg.getImageHtml("RoundPlus", "position:absolute; top:30; left:-11; visibility:hidden"); 1181 icon.appendChild(Dwt.parseHtmlFragment(imgHtml)); 1182 1183 return icon; 1184 }; 1185 1186 1187 1188 ZmCalBaseView._apptMouseMoveHdlr = 1189 function(ev) { 1190 var data = DwtMouseEventCapture.getTargetObj(); 1191 if (!data) return false; 1192 1193 var mouseEv = DwtShell.mouseEvent; 1194 mouseEv.setFromDhtmlEvent(ev, true); 1195 var view = data.view; 1196 1197 var deltaX = mouseEv.docX - data.docX; 1198 var deltaY = mouseEv.docY - data.docY; 1199 DBG.println(AjxDebug.DBG3,"_apptMouseMoveHdlr mouseEv.docY: " + mouseEv.docY + ", data.docY: " + data.docY); 1200 1201 if (!data.dndStarted) { 1202 var withinThreshold = (Math.abs(deltaX) < ZmCalColView.DRAG_THRESHOLD && Math.abs(deltaY) < ZmCalColView.DRAG_THRESHOLD); 1203 if (withinThreshold || !view._apptDndBegin(data)) { 1204 mouseEv._stopPropagation = true; 1205 mouseEv._returnValue = false; 1206 mouseEv.setToDhtmlEvent(ev); 1207 return false; 1208 } 1209 } 1210 1211 if (view._apptDraggedOut(mouseEv.docX, mouseEv.docY)) { 1212 // simulate DND 1213 DBG.println(AjxDebug.DBG3,"MouseMove DragOut"); 1214 view._dragOut(mouseEv, data); 1215 } 1216 else 1217 { 1218 if (data._lastDraggedOut) { 1219 data._lastDraggedOut = false; 1220 if (data.icon) { 1221 Dwt.setVisible(data.icon, false); 1222 } 1223 view._restoreHighlight(data); 1224 } 1225 var obj = data.dndObj; 1226 obj._lastDestDwtObj = null; 1227 if (!data.disableScroll) { 1228 var scrollOffset = view._handleApptScrollRegion(mouseEv.docX, mouseEv.docY, ZmCalColView._HOUR_HEIGHT, data); 1229 if (scrollOffset != 0) { 1230 deltaY += scrollOffset; 1231 } 1232 } 1233 1234 // snap new location to grid 1235 view._doApptMove(data, deltaX, deltaY); 1236 } 1237 mouseEv._stopPropagation = true; 1238 mouseEv._returnValue = false; 1239 mouseEv.setToDhtmlEvent(ev); 1240 return false; 1241 }; 1242 1243 1244 1245 ZmCalBaseView.prototype._dragOut = 1246 function(mouseEv, data) { 1247 // simulate DND 1248 var obj = data.dndObj; 1249 if (!data._lastDraggedOut) { 1250 data._lastDraggedOut = true; 1251 this._clearSnap(data.snap); 1252 data.startDate = new Date(data.appt.getStartTime()); 1253 this._restoreApptLoc(data); 1254 if (!data.icon) { 1255 data.icon = this._getApptDragProxy(data); 1256 } 1257 Dwt.setVisible(data.icon, true); 1258 } 1259 Dwt.setLocation(data.icon, mouseEv.docX+5, mouseEv.docY+5); 1260 var destDwtObj = mouseEv.dwtObj; 1261 var obj = data.dndObj; 1262 1263 if (destDwtObj && destDwtObj._dropTarget) 1264 { 1265 if (destDwtObj != obj._lastDestDwtObj || 1266 destDwtObj._dropTarget.hasMultipleTargets()) 1267 { 1268 //DBG.println("dwtObj = "+destDwtObj._dropTarget); 1269 if (destDwtObj._dropTarget._dragEnter(Dwt.DND_DROP_MOVE, destDwtObj, {data: data.appt}, mouseEv, data.icon)) { 1270 //obj._setDragProxyState(true); 1271 data.icon.className = DwtCssStyle.DROPPABLE; 1272 obj._dropAllowed = true; 1273 destDwtObj._dragEnter(mouseEv); 1274 } else { 1275 //obj._setDragProxyState(false); 1276 data.icon.className = DwtCssStyle.NOT_DROPPABLE; 1277 obj._dropAllowed = false; 1278 } 1279 } else if (obj._dropAllowed) { 1280 destDwtObj._dragOver(mouseEv); 1281 } 1282 } else { 1283 data.icon.className = DwtCssStyle.NOT_DROPPABLE; 1284 //obj._setDragProxyState(false); 1285 } 1286 1287 if (obj._lastDestDwtObj && 1288 obj._lastDestDwtObj != destDwtObj && 1289 obj._lastDestDwtObj._dropTarget && 1290 obj._lastDestDwtObj != obj) 1291 { 1292 obj._lastDestDwtObj._dragLeave(mouseEv); 1293 obj._lastDestDwtObj._dropTarget._dragLeave(); 1294 } 1295 obj._lastDestDwtObj = destDwtObj; 1296 1297 } 1298 1299 ZmCalBaseView.prototype._apptDraggedOut = 1300 function(docX, docY) { 1301 var draggedOut = this._containerRect ? true : false; 1302 return draggedOut && 1303 ((docY < this._containerRect.y) || 1304 (docY > (this._containerRect.y + this._containerRect.height)) || 1305 (docX < this._containerRect.x) || 1306 (docX > (this._containerRect.x + this._containerRect.width))); 1307 }; 1308 1309 ZmCalBaseView._apptMouseUpHdlr = 1310 function(ev) { 1311 //DBG.println("ZmCalBaseView._apptMouseUpHdlr: "+ev.shiftKey); 1312 var data = DwtMouseEventCapture.getTargetObj(); 1313 1314 1315 var mouseEv = DwtShell.mouseEvent; 1316 if (ev && mouseEv) { 1317 mouseEv.setFromDhtmlEvent(ev, true); 1318 } 1319 DwtMouseEventCapture.getCaptureObj().release(); 1320 1321 var draggedOut = data.view._apptDraggedOut(mouseEv.docX, mouseEv.docY); 1322 1323 if (data.dndStarted && data.appt) { 1324 data.view._deselectDnDHighlight(data); 1325 //notify Zimlet when an appt is dragged. 1326 appCtxt.notifyZimlets("onApptDrag", [data]); 1327 if (data.startDate.getTime() != data.appt._orig.getStartTime() && !draggedOut) { 1328 if (data.icon) Dwt.setVisible(data.icon, false); 1329 // save before we muck with start/end dates 1330 var origDuration = data.appt._orig.getDuration(); 1331 data.view._autoScrollDisabled = true; 1332 var cc = appCtxt.getCurrentController(); 1333 var endDate = new Date(data.startDate.getTime() + origDuration); 1334 var errorCallback = new AjxCallback(null, ZmCalColView._handleDnDError, data); 1335 var sdOffset = data.startDate ? (data.startDate.getTime() - data.appt._orig.getStartTime()) : null; 1336 var edOffset = endDate ? (endDate.getTime() - data.appt._orig.getEndTime() ) : null; 1337 cc.dndUpdateApptDate(data.appt._orig, sdOffset, edOffset, null, errorCallback, mouseEv); 1338 } else { 1339 data.view._restoreAppt(data); 1340 } 1341 1342 if (draggedOut) { 1343 var obj = data.dndObj; 1344 obj._lastDestDwtObj = null; 1345 var destDwtObj = mouseEv.dwtObj; 1346 if (destDwtObj != null && 1347 destDwtObj._dropTarget != null && 1348 obj._dropAllowed && 1349 destDwtObj != obj) 1350 { 1351 destDwtObj._drop(mouseEv); 1352 var srcData = { 1353 data: data.appt, 1354 controller: data.view._controller 1355 }; 1356 destDwtObj._dropTarget._drop(srcData, mouseEv); 1357 obj._dragging = DwtControl._NO_DRAG; 1358 if (data.icon) Dwt.setVisible(data.icon, false); 1359 } 1360 else { 1361 // The following code sets up the drop effect for when an 1362 // item is dropped onto an invalid target. Basically the 1363 // drag icon will spring back to its starting location. 1364 var bd = data.view._badDrop = { dragEndX: mouseEv.docX, dragEndY: mouseEv.docY, dragStartX: data.docX, dragStartY: data.docY }; 1365 bd.icon = data.icon; 1366 if (data.view._badDropAction == null) { 1367 data.view._badDropAction = new AjxTimedAction(data.view, data.view._apptBadDropEffect); 1368 } 1369 1370 // Line equation is y = mx + c. Solve for c, and set up d (direction) 1371 var m = (bd.dragEndY - bd.dragStartY) / (bd.dragEndX - bd.dragStartX); 1372 data.view._badDropAction.args = [m, bd.dragStartY - (m * bd.dragStartX), (bd.dragStartX - bd.dragEndX < 0) ? -1 : 1]; 1373 AjxTimedAction.scheduleAction(data.view._badDropAction, 0); 1374 } 1375 } 1376 } 1377 1378 if (mouseEv) { 1379 mouseEv._stopPropagation = true; 1380 mouseEv._returnValue = false; 1381 if (ev) { 1382 mouseEv.setToDhtmlEvent(ev); 1383 } 1384 } 1385 return false; 1386 }; 1387 1388 ZmCalBaseView.prototype._deselectDnDHighlight = 1389 function(data) { 1390 } 1391 ZmCalBaseView.prototype._restoreAppt = 1392 function(data) { 1393 } 1394 1395 1396 ZmCalBaseView.prototype._apptBadDropEffect = 1397 function(m, c, d) { 1398 var usingX = (Math.abs(m) <= 1); 1399 // Use the bigger delta to control the snap effect 1400 var bd = this._badDrop; 1401 var delta = usingX ? bd.dragStartX - bd.dragEndX : bd.dragStartY - bd.dragEndY; 1402 if (delta * d > 0) { 1403 if (usingX) { 1404 bd.dragEndX += (30 * d); 1405 bd.icon.style.top = m * bd.dragEndX + c; 1406 bd.icon.style.left = bd.dragEndX; 1407 } else { 1408 bd.dragEndY += (30 * d); 1409 bd.icon.style.top = bd.dragEndY; 1410 bd.icon.style.left = (bd.dragEndY - c) / m; 1411 } 1412 AjxTimedAction.scheduleAction(this._badDropAction, 0); 1413 } else { 1414 Dwt.setVisible(bd.icon, false); 1415 bd.icon = null; 1416 } 1417 }; 1418 1419 // --- Functions to be overridden for DnD 1420 ZmCalBaseView.prototype._createContainerRect = 1421 function(data) { 1422 this._containerRect = new DwtRectangle(0,0,0,0); 1423 } 1424 1425 ZmCalBaseView.prototype._clearSnap = 1426 function(snap) { } 1427 1428 ZmCalBaseView.prototype._apptDndBegin = 1429 function(data) { 1430 return false; 1431 } 1432 1433 ZmCalBaseView.prototype._restoreHighlight = 1434 function(data) { } 1435 1436 ZmCalBaseView.prototype._doApptMove = 1437 function(data, deltaX, deltaY) { } 1438 1439 ZmCalBaseView.prototype._restoreApptLoc = 1440 function(data) { } 1441 1442 ZmCalBaseView.prototype._cancelNewApptDrag = 1443 function(data) { 1444 if (data && data.newApptDivEl) { 1445 // ESC key is pressed while dragging the mouse 1446 // Undo the drag event and hide the new appt div 1447 data.gridEl.style.cursor = 'auto'; 1448 var col = data.view._getColFromX(data.gridX); 1449 data.folderId = col ? (col.cal ? col.cal.id : null) : null; 1450 Dwt.setVisible(data.newApptDivEl, false); 1451 } 1452 }; 1453 1454 ZmCalBaseView.prototype._handleApptScrollRegion = 1455 function(docX, docY, incr, data) { } 1456 1457 ZmCalBaseView.prototype.startIndicatorTimer=function() { }; 1458 1459 ZmCalBaseView.prototype.setTimer=function(min){ 1460 var period = min*60*1000; 1461 return AjxTimedAction.scheduleAction(new AjxTimedAction(this, this.updateTimeIndicator), period); 1462 }; 1463 1464 ZmCalBaseView.prototype.updateTimeIndicator=function() { }; 1465 1466 /** 1467 * De-selects a selected appointment 1468 * 1469 * @param {array} appts an array of appointments 1470 */ 1471 ZmCalBaseView.prototype.deselectAppt = 1472 function (appts) { 1473 appts = AjxUtil.toArray(appts); 1474 1475 var type = this._getItemData(appts, "type"); 1476 1477 for(var i = 0; i < appts.length; i++) { 1478 var selIdx = this._selectedItems.indexOf(appts[i]); 1479 1480 if (selIdx < 0) { 1481 continue; 1482 } 1483 1484 // despite their code and general architecture, calendar 1485 // views never have more than one selected item, so just 1486 // switch to focus to the view itself 1487 this.setFocusElement(this.getHtmlElement()); 1488 1489 appts[i].className = this._getStyle(type); 1490 this._selectedItems.remove(appts[i]); 1491 this._selEv.detail = DwtListView.ITEM_DESELECTED; 1492 this._selEv.item = appts[i]; 1493 this._evtMgr.notifyListeners(DwtEvent.SELECTION, this._selEv); 1494 } 1495 }; 1496