1 /* 2 * ***** BEGIN LICENSE BLOCK ***** 3 * Zimbra Collaboration Suite Web Client 4 * Copyright (C) 2005, 2006, 2007, 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) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2016 Synacor, Inc. All Rights Reserved. 21 * ***** END LICENSE BLOCK ***** 22 */ 23 24 /** 25 * Creates a calendar widget 26 * @constructor 27 * @class 28 * This class provides a calendar view. 29 * 30 * @author Ross Dargahi 31 * @author Roland Schemers 32 * 33 * @param {hash} params a hash of parameters 34 * @param {DwtComposite} params.parent the parent widget 35 * @param {string} params.className the CSS class 36 * @param {constant} params.posStyle the positioning style (see {@link Dwt}) 37 * @param {constant} [params.firstDayOfWeek=DwtCalendar.SUN] the first day of the week 38 * @param {boolean} [params.forceRollOver=true] if <code>true</code>, then clicking on (or setting) the widget to a 39 * date that is not part of the current month (i.e. one of 40 * the grey prev or next month days) will result in the 41 * widget rolling the date to that month. 42 * @param {array} params.workingDays a list of days that are work days. This array assumes that 43 * index 0 is Sunday. Defaults to Mon-Fri being work days. 44 * @param {boolean} params.hidePrevNextMo a flag indicating whether widget should hide days of the 45 * previous/next month 46 * @param {boolean} params.readOnly a flag indicating that this widget is read-only (should not 47 * process events such as mouse clicks) 48 * @param {boolean} params.showWeekNumber a flag indicating whether widget should show week number 49 * 50 * @extends DwtComposite 51 */ 52 DwtCalendar = function(params) { 53 if (arguments.length == 0) { return; } 54 params = Dwt.getParams(arguments, DwtCalendar.PARAMS); 55 params.className = params.className || "DwtCalendar"; 56 DwtComposite.call(this, params); 57 58 this._skipNotifyOnPage = false; 59 this._hidePrevNextMo = params.hidePrevNextMo; 60 this._readOnly = params.readOnly; 61 this._showWeekNumber = params.showWeekNumber; 62 this._uuid = Dwt.getNextId(); 63 var cn = this._origDayClassName = params.className + "Day"; 64 this._todayClassName = " " + params.className + "Day-today"; 65 this._selectedDayClassName = " " + cn + "-" + DwtCssStyle.SELECTED; 66 this._hoveredDayClassName = " " + cn + "-" + DwtCssStyle.HOVER; 67 this._activeDayClassName = " " + cn + "-" + DwtCssStyle.ACTIVE; 68 this._hiliteClassName = " " + cn + "-hilited"; 69 this._greyClassName = " " + cn + "-grey"; 70 71 if (!this._readOnly) { 72 this._installListeners(); 73 } 74 75 this._selectionMode = DwtCalendar.DAY; 76 77 this._init(); 78 79 this._weekDays = new Array(7); 80 this._workingDays = params.workingDays || DwtCalendar._DEF_WORKING_DAYS; 81 this._useISO8601WeekNo = params.useISO8601WeekNo; 82 this.setFirstDayOfWeek(params.firstDayOfWeek || DwtCalendar.SUN); 83 84 this._forceRollOver = (params.forceRollOver !== false); 85 }; 86 87 DwtCalendar.PARAMS = ["parent", "className", "posStyle", "firstDayOfWeek", "forceRollOver", 88 "workingDaysArray", "hidePrevNextMo", "readOnly"]; 89 90 DwtCalendar.prototype = new DwtComposite; 91 DwtCalendar.prototype.constructor = DwtCalendar; 92 93 /** 94 * Sunday. 95 */ 96 DwtCalendar.SUN = 0; 97 /** 98 * Monday. 99 */ 100 DwtCalendar.MON = 1; 101 /** 102 * Tuesday. 103 */ 104 DwtCalendar.TUE = 2; 105 /** 106 * Wednesday. 107 */ 108 DwtCalendar.WED = 3; 109 /** 110 * Thursday. 111 */ 112 DwtCalendar.THU = 4; 113 /** 114 * Friday. 115 */ 116 DwtCalendar.FRI = 5; 117 /** 118 * Saturday. 119 */ 120 DwtCalendar.SAT = 6; 121 122 // Selection modes 123 /** 124 * Defines the "day" selection mode. 125 */ 126 DwtCalendar.DAY = 1; 127 /** 128 * Defines the "week" selection mode. 129 */ 130 DwtCalendar.WEEK = 2; 131 /** 132 * Defines the "work week" selection mode. 133 */ 134 DwtCalendar.WORK_WEEK = 3; 135 /** 136 * Defines the "month" selection mode. 137 */ 138 DwtCalendar.MONTH = 4; 139 140 DwtCalendar.RANGE_CHANGE = "DwtCalendar.RANGE_CHANGE"; 141 142 DwtCalendar._FULL_WEEK = [1, 1, 1, 1, 1, 1, 1]; 143 DwtCalendar._DEF_WORKING_DAYS = [0, 1, 1, 1, 1, 1, 0]; 144 DwtCalendar._DAYS_IN_MONTH = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; 145 146 DwtCalendar._NO_MONTH = -2; 147 DwtCalendar._PREV_MONTH = -1; 148 DwtCalendar._THIS_MONTH = 0; 149 DwtCalendar._NEXT_MONTH = 1; 150 151 DwtCalendar._NORMAL = 1; 152 DwtCalendar._HOVERED = 2; 153 DwtCalendar._ACTIVE = 3; 154 DwtCalendar._SELECTED = 4; 155 DwtCalendar._DESELECTED = 5; 156 157 DwtCalendar.DATE_SELECTED = 1; 158 DwtCalendar.DATE_DESELECTED = 2; 159 DwtCalendar.DATE_DBL_CLICKED = 3; 160 161 DwtCalendar._LAST_DAY_CELL_IDX = 41; 162 163 DwtCalendar._BUTTON_CLASS = "DwtCalendarButton"; 164 DwtCalendar._BUTTON_HOVERED_CLASS = DwtCalendar._BUTTON_CLASS + "-" + DwtCssStyle.HOVER; 165 DwtCalendar._BUTTON_ACTIVE_CLASS = DwtCalendar._BUTTON_CLASS + "-" + DwtCssStyle.ACTIVE; 166 167 DwtCalendar._TITLE_CLASS = "DwtCalendarTitle"; 168 DwtCalendar._TITLE_HOVERED_CLASS = DwtCalendar._TITLE_CLASS + "-" + DwtCssStyle.HOVER; 169 DwtCalendar._TITLE_ACTIVE_CLASS = DwtCalendar._TITLE_CLASS + "-" + DwtCssStyle.ACTIVE; 170 171 /** 172 * Returns a string representation of the object. 173 * 174 * @return {string} a string representation of the object 175 */ 176 DwtCalendar.prototype.toString = 177 function() { 178 return "DwtCalendar"; 179 }; 180 181 /** 182 * Adds a selection listener. 183 * 184 * @param {AjxListener} listener the listener 185 */ 186 DwtCalendar.prototype.addSelectionListener = 187 function(listener) { 188 this.addListener(DwtEvent.SELECTION, listener); 189 }; 190 191 /** 192 * Removes a selection listener. 193 * 194 * @param {AjxListener} listener the listener 195 */ 196 DwtCalendar.prototype.removeSelectionListener = 197 function(listener) { 198 this.removeListener(DwtEvent.SELECTION, listener); 199 }; 200 201 /** 202 * Adds an action listener. 203 * 204 * @param {AjxListener} listener the listener 205 */ 206 DwtCalendar.prototype.addActionListener = 207 function(listener) { 208 this.addListener(DwtEvent.ACTION, listener); 209 }; 210 211 /** 212 * Removes an action listener. 213 * 214 * @param {AjxListener} listener the listener 215 */ 216 DwtCalendar.prototype.removeActionListener = 217 function(listener) { 218 this.removeListener(DwtEvent.ACTION, listener); 219 }; 220 221 /** 222 * Adds a date range listener. Date range listeners are called whenever the date range of the calendar 223 * changes (i.e. when it rolls over due to a programatic action via {@link #setDate} or 224 * via user selection). 225 * 226 * @param {AjxListener} listener the listener 227 */ 228 DwtCalendar.prototype.addDateRangeListener = 229 function(listener) { 230 this.addListener(DwtEvent.DATE_RANGE, listener); 231 }; 232 233 /** 234 * Removes a date range listener. 235 * 236 * @param {AjxListener} listener the listener 237 */ 238 DwtCalendar.prototype.removeDateRangeListener = 239 function(listener) { 240 this.removeListener(DwtEvent.DATE_RANGE, listener); 241 }; 242 243 /** 244 * Sets the skip notify on page. This method notify (or not) selection when paging arrow buttons 245 * are clicked. 246 * 247 * @param {boolean} skip if <code>true</code>, do not notify selection 248 */ 249 DwtCalendar.prototype.setSkipNotifyOnPage = 250 function(skip) { 251 this._skipNotifyOnPage = skip; 252 }; 253 254 /** 255 * Gets the skip notify on page setting. 256 * 257 * @return {boolean} <code>true</code>, do not notify selection 258 */ 259 DwtCalendar.prototype.getSkipNotifyOnPage = 260 function() { 261 return this._skipNotifyOnPage; 262 }; 263 264 /** 265 * Sets the date. 266 * 267 * @param {Date} date the date 268 * @param {boolean} skipNotify if <code>true</code>, do not notify selection 269 * @param {boolean} forceRollOver if <code>true</code>, then clicking on (or setting) the widget to a 270 * date that is not part of the current month (i.e. one of 271 * the grey prev or next month days) will result in the 272 * widget rolling the date to that month. 273 * @param {boolean} dblClick if <code>true</code>, require a double click 274 */ 275 DwtCalendar.prototype.setDate = 276 function(date, skipNotify, forceRollOver, dblClick) { 277 278 forceRollOver = (forceRollOver == null) ? this._forceRollOver : forceRollOver; 279 280 // Check if the date is available for selection. Basically it is unless we are in 281 // work week selection mode and <date> is not a working day 282 //if (this._selectionMode == DwtCalendar.WORK_WEEK && !this._currWorkingDays[date.getDay()]) 283 // return false; 284 285 if(!date) { 286 date = new Date(); 287 } 288 var newDate = new Date(date.getTime()); 289 var oldDate = this._date; 290 291 var layout = false; 292 var notify = false; 293 var cellId; 294 295 if (this._date2CellId != null) { 296 var idx = (newDate.getFullYear() * 10000) + (newDate.getMonth() * 100) + newDate.getDate(); 297 var cellId = this._date2CellId[idx]; 298 299 if (cellId) { 300 if (cellId == this._selectedCellId) 301 notify = true; 302 303 var cell = document.getElementById(cellId); 304 if (cell._dayType == DwtCalendar._THIS_MONTH) 305 notify = true; 306 else if (forceRollOver) 307 notify = layout = true; 308 else 309 notify = true; 310 } else { 311 notify = layout = true; 312 } 313 } else { 314 notify = layout = true; 315 } 316 317 // update before layout, notify after layout 318 if (notify) { 319 if (this._date){ 320 // 5/13/2005 EMC -- I'm not sure why this was setting the hours to 0. 321 // I think it should respect what the user passed in, and only change 322 // the parts of the date that it is responsible for. 323 //newDate.setHours(0,0,0,0); 324 //handle daylight saving 325 if(AjxDateUtil.isDayShifted(newDate)) { 326 AjxDateUtil.rollToNextDay(newDate); 327 } 328 newDate.setHours(this._date.getHours(), this._date.getMinutes(), this._date.getSeconds(), 0); 329 } 330 331 this._date = newDate; 332 if (!layout && !this._readOnly) { 333 this._setSelectedDate(); 334 this._setToday(); 335 } 336 } 337 338 if (layout) { 339 this._layout(); 340 } 341 342 if (notify && !skipNotify) { 343 var type = dblClick ? DwtCalendar.DATE_DBL_CLICKED : DwtCalendar.DATE_SELECTED; 344 this._notifyListeners(DwtEvent.SELECTION, type, this._date); 345 } 346 347 return true; 348 }; 349 350 /** 351 * Checks if the cell is selected. 352 * 353 * @param {string} cellId the cell id 354 * @return {boolean} <code>true</code> if the cell is the selected day 355 */ 356 DwtCalendar.prototype.isSelected = 357 function(cellId) { 358 // if cellId is the selected day, then return true, else if we are NOT in 359 // day selection mode (i.e. week/work week) then compute the row and index 360 // of cellId and look it up in the week array to see if it is a selectable day 361 if (cellId == this._selectedDayElId) { 362 return true; 363 } else if (this._selectionMode != DwtCalendar.DAY) { 364 // If the cell is in the same row as the currently selected cell and it 365 // is a selectable day (i.e. a working day in the case of work week), 366 // then say it is selected 367 var cellIdx = this._getDayCellIndex(cellId); 368 if (Math.floor(cellIdx / 7) == Math.floor(this._getDayCellIndex(this._selectedDayElId) / 7) 369 && this._currWorkingDays[cellIdx % 7]) 370 return true; 371 } 372 return false; 373 }; 374 375 /** 376 * Gets the force roll over setting. Force roll over is occurs when a date that 377 * is not part of the current month (i.e. one of the grey prev or next month 378 * days) will result in the widget rolling the date to that month. 379 * 380 * @return {boolean} <code>true</code> if force roll over is set 381 */ 382 DwtCalendar.prototype.getForceRollOver = 383 function() { 384 return this._forceRollOver; 385 }; 386 387 /** 388 * Sets the force roll over setting. Force roll over is occurs when a date that 389 * is not part of the current month (i.e. one of the grey prev or next month 390 * days) will result in the widget rolling the date to that month. 391 * 392 * @param {boolean} force if <code>true</code>, force roll over 393 */ 394 DwtCalendar.prototype.setForceRollOver = 395 function(force) { 396 if (force == null) { return; } 397 398 if (this._forceRollOver != force) { 399 this._forceRollOver = force; 400 this._layout(); 401 } 402 }; 403 404 /** 405 * Gets the selection mode. 406 * 407 * @return {constant} the selection mode 408 */ 409 DwtCalendar.prototype.getSelectionMode = 410 function() { 411 return this._selectionMode; 412 }; 413 414 /** 415 * Sets the selection mode. 416 * 417 * @return {constant} selectionMode the selection mode 418 */ 419 DwtCalendar.prototype.setSelectionMode = 420 function(selectionMode) { 421 if (this._selectionMode == selectionMode) { return; } 422 423 this._selectionMode = selectionMode; 424 if (selectionMode == DwtCalendar.WEEK) { 425 this._currWorkingDays = DwtCalendar._FULL_WEEK; 426 } else if (selectionMode == DwtCalendar.WORK_WEEK) { 427 this._currWorkingDays = this._workingDays; 428 } 429 430 this._layout(); 431 }; 432 433 /** 434 * Sets the working week. 435 * 436 * @param {array} workingDaysArray an array of days 437 */ 438 DwtCalendar.prototype.setWorkingWeek = 439 function(workingDaysArray) { 440 // TODO Should really create a copy of workingDaysArray 441 this._workingDays = this._currWorkingDays = workingDaysArray; 442 443 if (this._selectionMode == DwtCalendar.WORK_WEEK) { 444 DBG.println("FOO!!!"); 445 this._layout(); 446 } 447 }; 448 449 /** 450 * Enables/disables the highlight (i.e. "bolding") on the dates in <code><dates></code>. 451 * 452 * @param {object} dates associative array of {@link Date} objects for 453 * which to enable/disable highlighting 454 * @param {boolean} enable if <code>true</code>, enable highlighting 455 * @param {boolean} clear if <code>true</code>, clear current highlighting 456 */ 457 DwtCalendar.prototype.setHilite = 458 function(dates, enable, clear) { 459 if (this._date2CellId == null) { return; } 460 461 var cell; 462 var aDate; 463 if (clear) { 464 for (aDate in this._date2CellId) { 465 cell = document.getElementById(this._date2CellId[aDate]); 466 if (cell._isHilited) { 467 cell._isHilited = false; 468 this._setClassName(cell, DwtCalendar._NORMAL); 469 } 470 } 471 } 472 473 var cellId; 474 for (var i in dates) { 475 // NOTE: Protect from prototype extensions. 476 if (dates.hasOwnProperty(i)) { 477 aDate = dates[i]; 478 cellId = this._date2CellId[aDate.getFullYear() * 10000 + aDate.getMonth() * 100 + aDate.getDate()]; 479 480 if (cellId) { 481 cell = document.getElementById(cellId); 482 if (cell._isHilited != enable) { 483 cell._isHilited = enable; 484 this._setClassName(cell, DwtCalendar._NORMAL); 485 } 486 } 487 } 488 } 489 }; 490 491 /** 492 * Gets the date. 493 * 494 * @return {Date} the date 495 */ 496 DwtCalendar.prototype.getDate = 497 function() { 498 return this._date; 499 }; 500 501 /** 502 * Sets the first date of week. 503 * 504 * @param {constant} firstDayOfWeek the first day of week 505 */ 506 DwtCalendar.prototype.setFirstDayOfWeek = 507 function(firstDayOfWeek) { 508 for (var i = 0; i < 7; i++) { 509 this._weekDays[i] = (i < firstDayOfWeek) 510 ? (6 - (firstDayOfWeek -i - 1)) 511 : (i - firstDayOfWeek); 512 513 var dowCell = document.getElementById(this._getDOWCellId(i)); 514 dowCell.innerHTML = AjxDateUtil.WEEKDAY_SHORT[(firstDayOfWeek + i) % 7]; 515 } 516 this._firstDayOfWeek = firstDayOfWeek 517 this._layout(); 518 }; 519 520 /** 521 * Gets the date range. 522 * 523 * @return {Object} the range (<code>range.start</code> and <code>range.end</code>) 524 */ 525 DwtCalendar.prototype.getDateRange = 526 function () { 527 return this._range; 528 }; 529 530 DwtCalendar.prototype._getDayCellId = 531 function(cellId) { 532 return ("c:" + cellId + ":" + this._uuid); 533 }; 534 535 DwtCalendar.prototype._getDayCellIndex = 536 function(cellId) { 537 return cellId.substring(2, cellId.indexOf(":", 3)); 538 }; 539 540 DwtCalendar.prototype._getDOWCellId = 541 function(cellId) { 542 return ("w:" + cellId + ":" + this._uuid); 543 }; 544 545 DwtCalendar.prototype._getWeekNumberCellId = 546 function(cellId) { 547 return ("k:" + cellId + ":" + this._uuid); 548 }; 549 550 DwtCalendar.prototype._getDaysInMonth = 551 function(mo, yr) { 552 /* If we are not dealing with Feb, then simple lookup 553 * Leap year rules 554 * 1. Every year divisible by 4 is a leap year. 555 * 2. But every year divisible by 100 is NOT a leap year 556 * 3. Unless the year is also divisible by 400, then it is still a leap year.*/ 557 if (mo != 1) { 558 return DwtCalendar._DAYS_IN_MONTH[mo]; 559 } 560 561 if (yr % 4 != 0 || (yr % 100 == 0 && yr % 400 != 0)) { 562 return 28; 563 } 564 565 return 29; 566 }; 567 568 DwtCalendar.prototype._installListeners = 569 function() { 570 this._setMouseEventHdlrs(); 571 this.addListener(DwtEvent.ONMOUSEOVER, new AjxListener(this, this._mouseOverListener)); 572 this.addListener(DwtEvent.ONMOUSEOUT, new AjxListener(this, this._mouseOutListener)); 573 this.addListener(DwtEvent.ONMOUSEDOWN, new AjxListener(this, this._mouseDownListener)); 574 this.addListener(DwtEvent.ONMOUSEUP, new AjxListener(this, this._mouseUpListener)); 575 this.addListener(DwtEvent.ONDBLCLICK, new AjxListener(this, this._doubleClickListener)); 576 }; 577 578 DwtCalendar.prototype._notifyListeners = 579 function(eventType, type, detail, ev) { 580 if (!this.isListenerRegistered(eventType)) { return; } 581 582 var selEv = DwtShell.selectionEvent; 583 if (ev) { 584 DwtUiEvent.copy(selEv, ev); 585 } else { 586 selEv.reset(); 587 } 588 selEv.item = this; 589 selEv.detail = detail; 590 selEv.type = type; 591 this.notifyListeners(eventType, selEv); 592 }; 593 594 DwtCalendar.prototype._layout = 595 function() { 596 if (this._date == null) { this._date = new Date(); } 597 598 if (!this._calWidgetInited) { 599 this._init(); 600 } 601 602 var date = new Date(this._date.getTime()); 603 date.setDate(1); 604 var year = date.getFullYear(); 605 var month = date.getMonth(); 606 var firstDay = date.getDay(); 607 var daysInMonth = this._getDaysInMonth(month, year); 608 var day = 1; 609 var nextMoDay = 1; 610 611 this._date2CellId = new Object(); 612 this._selectedDayElId = null; 613 614 // Figure out how many days from the previous month we have to fill in 615 // (see comment below) 616 var lastMoDay, lastMoYear, lastMoMonth, nextMoMonth, nextMoYear; 617 if (!this._hidePrevNextMo) { 618 if (month != 0) { 619 lastMoDay = this._getDaysInMonth(month - 1, year) - this._weekDays[firstDay] + 1; 620 lastMoYear = year; 621 lastMoMonth = month - 1; 622 if (month != 11) { 623 nextMoMonth = month + 1; 624 nextMoYear = year; 625 } else { 626 nextMoMonth = 0; 627 nextMoYear = year + 1; 628 } 629 } else { 630 lastMoDay = this._getDaysInMonth(11, year - 1) - this._weekDays[firstDay] + 1; 631 lastMoYear = year - 1; 632 lastMoMonth = 11; 633 nextMoMonth = 1; 634 nextMoYear = year; 635 } 636 } 637 638 for (var i = 0; i < 6; i++) { 639 for (var j = 0; j < 7; j++) { 640 var dayCell = document.getElementById(this._getDayCellId(i * 7 + j)); 641 642 if (dayCell._isHilited == null) { 643 dayCell._isHilited = false; 644 } 645 646 if (day <= daysInMonth) { 647 /* The following if statement deals with the first day of this month not being 648 * the first day of the week. In this case we must fill the preceding days with 649 * the final days of the previous month */ 650 if (i != 0 || j >= this._weekDays[firstDay]) { 651 this._date2CellId[(year * 10000) + (month * 100) + day] = dayCell.id; 652 dayCell._day = day; 653 dayCell._month = month; 654 dayCell._year = year; 655 dayCell.innerHTML = day++; 656 dayCell._dayType = DwtCalendar._THIS_MONTH; 657 if (this._readOnly) { 658 dayCell.style.fontFamily = "Arial"; 659 dayCell.style.fontSize = "10px"; 660 } 661 } else { 662 if (this._hidePrevNextMo) { 663 dayCell.innerHTML = ""; 664 } else { 665 this._date2CellId[(lastMoYear * 10000) + (lastMoMonth * 100) + lastMoDay] = dayCell.id; 666 dayCell._day = lastMoDay; 667 dayCell._month = lastMoMonth; 668 dayCell._year = lastMoYear; 669 dayCell.innerHTML = lastMoDay++; 670 dayCell._dayType = DwtCalendar._PREV_MONTH; 671 } 672 } 673 } else if (!this._hidePrevNextMo) { 674 // Fill any remaining slots with days from next month 675 this._date2CellId[(nextMoYear * 10000) + (nextMoMonth * 100) + nextMoDay] = dayCell.id; 676 dayCell._day = nextMoDay; 677 dayCell._month = nextMoMonth; 678 dayCell._year = nextMoYear; 679 dayCell.innerHTML = nextMoDay++; 680 dayCell._dayType = DwtCalendar._NEXT_MONTH; 681 } 682 this._setClassName(dayCell, DwtCalendar._NORMAL); 683 } 684 685 if (this._showWeekNumber) { 686 var kwCellId = this._getWeekNumberCellId('kw' + i * 7); 687 var kwCell = document.getElementById(kwCellId); 688 if (kwCell) { 689 var firstDayCell = document.getElementById(this._getDayCellId(i * 7)); 690 kwCell.innerHTML = AjxDateUtil.getWeekNumber(new Date(firstDayCell._year, firstDayCell._month, firstDayCell._day), this._firstDayOfWeek, null, this._useISO8601WeekNo); 691 } 692 } 693 } 694 695 this._setTitle(month, year); 696 697 // Compute the currently selected day 698 if (!this._readOnly) { 699 this._setSelectedDate(); 700 this._setToday(); 701 } 702 703 this._setRange(); 704 }; 705 706 DwtCalendar.prototype._setRange = 707 function() { 708 var cell = document.getElementById(this._getDayCellId(0)); 709 var start = new Date(cell._year, cell._month, cell._day, 0, 0, 0, 0); 710 711 cell = document.getElementById(this._getDayCellId(DwtCalendar._LAST_DAY_CELL_IDX)); 712 713 var daysInMo = this._getDaysInMonth(cell._month, cell._year); 714 var end; 715 if (cell._day < daysInMo) { 716 end = new Date(cell._year, cell._month, cell._day + 1, 0, 0, 0, 0); 717 } else if (cell._month < 11) { 718 end = new Date(cell._year, cell._month + 1, 1, 0, 0, 0, 0); 719 } else { 720 end = new Date(cell._year + 1, 0, 1, 0, 0, 0, 0); 721 } 722 723 if (this._range == null) { 724 this._range = {}; 725 } else if (this._range.start.getTime() == start.getTime() && this._range.end.getTime() == end.getTime()) { 726 return false; 727 } 728 729 this._range.start = start; 730 this._range.end = end; 731 732 // Notify any listeners 733 if (!this.isListenerRegistered(DwtEvent.DATE_RANGE)) { return; } 734 735 if (!this._dateRangeEvent) { 736 this._dateRangeEvent = new DwtDateRangeEvent(true); 737 } 738 739 this._dateRangeEvent.item = this; 740 this._dateRangeEvent.start = start; 741 this._dateRangeEvent.end = end; 742 this.notifyListeners(DwtEvent.DATE_RANGE, this._dateRangeEvent); 743 }; 744 745 DwtCalendar.prototype._setToday = 746 function() { 747 var cell; 748 var today = new Date(); 749 var todayDay = today.getDate(); 750 751 if (!this._todayDay || this._todayDay != todayDay) { 752 if (this._todayCellId != null) { 753 cell = document.getElementById(this._todayCellId); 754 cell._isToday = false; 755 this._setClassName(cell, DwtCalendar._NORMAL); 756 } 757 758 this._todayCellId = this._date2CellId[(today.getFullYear() * 10000) + (today.getMonth() * 100) + todayDay]; 759 if (this._todayCellId != null) { 760 cell = document.getElementById(this._todayCellId); 761 cell._isToday = true; 762 this._setClassName(cell, DwtCalendar._NORMAL); 763 } 764 } 765 }; 766 767 DwtCalendar.prototype._setSelectedDate = 768 function() { 769 var day = this._date.getDate(); 770 var month = this._date.getMonth(); 771 var year = this._date.getFullYear(); 772 var cell; 773 774 if (this._selectedDayElId) { 775 cell = document.getElementById(this._selectedDayElId); 776 this._setClassName(cell, DwtCalendar._DESELECTED); 777 } 778 779 var cellId = this._date2CellId[(year * 10000) + (month * 100) + day]; 780 cell = document.getElementById(cellId); 781 this._selectedDayElId = cellId; 782 this._setClassName(cell, DwtCalendar._SELECTED); 783 }; 784 785 DwtCalendar.prototype._setCellClassName = 786 function(cell, className, mode) { 787 if (cell._dayType != DwtCalendar._THIS_MONTH) { 788 className += this._greyClassName; 789 } 790 791 if (this._selectionMode == DwtCalendar.DAY && 792 cell.id == this._selectedDayElId && 793 mode != DwtCalendar._DESELECTED) 794 { 795 className += this._selectedDayClassName; 796 } 797 else if (this._selectionMode != DwtCalendar.DAY && 798 mode != DwtCalendar._DESELECTED && 799 this._selectedDayElId != null) 800 { 801 var idx = this._getDayCellIndex(cell.id); 802 if (Math.floor(this._getDayCellIndex(this._selectedDayElId) / 7) == Math.floor(idx / 7) && 803 this._currWorkingDays[idx % 7]) 804 { 805 className += this._selectedDayClassName; 806 } 807 } 808 809 if (cell._isHilited) { 810 className += this._hiliteClassName; 811 } 812 813 if (cell._isToday) { 814 className += this._todayClassName; 815 } 816 817 return className; 818 }; 819 820 DwtCalendar.prototype._setClassName = 821 function(cell, mode) { 822 var className = ""; 823 824 if (mode == DwtCalendar._NORMAL) { 825 className = this._origDayClassName; 826 } else if (mode == DwtCalendar._HOVERED) { 827 className = this._hoveredDayClassName; 828 } else if (mode == DwtCalendar._ACTIVE) { 829 className = this._activeDayClassName; 830 } else if (mode == DwtCalendar._DESELECTED && this._selectionMode == DwtCalendar.DAY) { 831 className = this._origDayClassName; 832 } else if (this._selectionMode != DwtCalendar.DAY && 833 (mode == DwtCalendar._SELECTED || mode == DwtCalendar._DESELECTED)) 834 { 835 // If we are not in day mode, then we need to highlite multiple cells 836 // e.g. the whole week if we are in week mode 837 var firstCellIdx = Math.floor(this._getDayCellIndex(this._selectedDayElId) / 7) * 7; 838 839 for (var i = 0; i < 7; i++) { 840 className = this._origDayClassName; 841 var aCell = document.getElementById(this._getDayCellId(firstCellIdx++)); 842 aCell.className = this._setCellClassName(aCell, className, mode); 843 } 844 return; 845 } 846 847 cell.className = this._setCellClassName(cell, className, mode); 848 }; 849 850 DwtCalendar.prototype._setTitle = 851 function(month, year) { 852 var cell = document.getElementById(this._monthCell); 853 var formatter = DwtCalendar.getMonthFormatter(); 854 var date = new Date(year, month); 855 cell.innerHTML = formatter.format(date); 856 }; 857 858 DwtCalendar.prototype._init = 859 function() { 860 var html = new Array(100); 861 var idx = 0; 862 this._monthCell = "t:" + this._uuid; 863 864 // Construct the header row with the prev/next year and prev/next month 865 // icons as well as the month/year title cell 866 html[idx++] = "<table width=100%>"; 867 html[idx++] = "<tr><td class='DwtCalendarTitlebar'>"; 868 html[idx++] = "<table>"; 869 html[idx++] = "<tr>"; 870 html[idx++] = "<td align='center' class='"; 871 html[idx++] = DwtCalendar._BUTTON_CLASS; 872 html[idx++] = "' id='b:py:"; 873 html[idx++] = this._uuid; 874 html[idx++] = "'>"; 875 html[idx++] = AjxImg.getImageHtml("FastRevArrowSmall", null, ["id='b:py:img:", this._uuid, "'"].join("")); 876 html[idx++] = "</td>"; 877 html[idx++] = "<td align='center' class='"; 878 html[idx++] = DwtCalendar._BUTTON_CLASS; 879 html[idx++] = "' id='b:pm:"; 880 html[idx++] = this._uuid; 881 html[idx++] = "'>"; 882 html[idx++] = AjxImg.getImageHtml("RevArrowSmall", null, ["id='b:pm:img:", this._uuid, "'"].join("")); 883 html[idx++] = "</td>"; 884 html[idx++] = "<td class='DwtCalendarTitleCell' 'nowrap' style='width: 60%'><span class='"; 885 html[idx++] = DwtCalendar._TITLE_CLASS; 886 html[idx++] = "' id='"; 887 html[idx++] = this._monthCell; 888 html[idx++] = "'></span></td>"; 889 html[idx++] = "<td align='center' class='"; 890 html[idx++] = DwtCalendar._BUTTON_CLASS; 891 html[idx++] = "' id='b:nm:"; 892 html[idx++] = this._uuid; 893 html[idx++] = "'>"; 894 html[idx++] = AjxImg.getImageHtml("FwdArrowSmall", null, ["id='b:nm:img:", this._uuid, "'"].join("")); 895 html[idx++] = "</td>"; 896 html[idx++] = "<td align='center' class='"; 897 html[idx++] = DwtCalendar._BUTTON_CLASS; 898 html[idx++] = "' id='b:ny:"; 899 html[idx++] = this._uuid; 900 html[idx++] = "'>"; 901 html[idx++] = AjxImg.getImageHtml("FastFwdArrowSmall", null, ["id='b:ny:img:", this._uuid, "'"].join("")); 902 html[idx++] = "</td>"; 903 html[idx++] = "</tr>"; 904 html[idx++] = "</table>"; 905 html[idx++] = "</td></tr>"; 906 html[idx++] = "<tr><td class='DwtCalendarBody'>"; 907 html[idx++] = "<table width='100%' style='border-collapse:separate;' cellspacing='0'>"; 908 html[idx++] = "<tr>"; 909 910 if (this._showWeekNumber) { 911 html[idx++] = "<td class='DwtCalendarWeekNoTitle' width='14%' id='"; 912 html[idx++] = this._getWeekNumberCellId('kw'); 913 html[idx++] = "'>"; 914 html[idx++] = AjxMsg.calendarWeekTitle; 915 html[idx++] = "</td>"; 916 } 917 918 for (var i = 0; i < 7; i++) { 919 html[idx++] = "<td class='DwtCalendarDow' width='"; 920 html[idx++] = (i < 5 ? "14%" : "15%"); 921 html[idx++] = "' id='"; 922 html[idx++] = this._getDOWCellId(i); 923 html[idx++] = "'> </td>"; 924 } 925 html[idx++] = "</tr>"; 926 927 for (var i = 0; i < 6; i++) { 928 html[idx++] = "<tr>"; 929 if (this._showWeekNumber) { 930 html[idx++] = "<td class='DwtCalendarWeekNo' id='" + this._getWeekNumberCellId('kw' + i * 7) + "'> </td>"; 931 } 932 for (var j = 0; j < 7; j++) { 933 html[idx++] = "<td id='"; 934 html[idx++] = this._getDayCellId(i * 7 + j); 935 html[idx++] = "'> </td>"; 936 } 937 html[idx++] ="</tr>"; 938 } 939 940 html[idx++] = "</td></tr></table></table>"; 941 942 this.getHtmlElement().innerHTML = html.join(""); 943 if (!this._readOnly) { 944 document.getElementById("b:py:img:" + this._uuid)._origClassName = AjxImg.getClassForImage("FastRevArrowSmall"); 945 document.getElementById("b:pm:img:" + this._uuid)._origClassName = AjxImg.getClassForImage("RevArrowSmall"); 946 document.getElementById("b:nm:img:" + this._uuid)._origClassName = AjxImg.getClassForImage("FwdArrowSmall"); 947 document.getElementById("b:ny:img:" + this._uuid)._origClassName = AjxImg.getClassForImage("FastFwdArrowSmall"); 948 } 949 950 this._calWidgetInited = true; 951 }; 952 953 /** 954 * Sets the mouse over day callback. 955 * 956 * @param {AjxCallback} callback the callback 957 */ 958 DwtCalendar.prototype.setMouseOverDayCallback = 959 function(callback) { 960 this._mouseOverDayCB = callback; 961 }; 962 963 /** 964 * Sets the mouse out day callback. 965 * 966 * @param {AjxCallback} callback the callback 967 */ 968 DwtCalendar.prototype.setMouseOutDayCallback = 969 function(callback) { 970 this._mouseOutDayCB = callback; 971 }; 972 973 /** 974 * Gets the date value for the last cell that the most recent 975 * Drag-and-drop operation occurred over. Typically it will be called by a DwtDropTarget 976 * listener when an item is dropped onto the mini calendar 977 * 978 * @return {Date} the date or <code>null</code> for none 979 */ 980 DwtCalendar.prototype.getDndDate = 981 function() { 982 var dayCell = this._lastDndCell; 983 if (dayCell) { 984 return new Date(dayCell._year, dayCell._month, dayCell._day); 985 } 986 987 return null; 988 }; 989 990 // Temp date used for callback in mouseOverListener 991 DwtCalendar._tmpDate = new Date(); 992 DwtCalendar._tmpDate.setHours(0, 0, 0, 0); 993 994 DwtCalendar.prototype._mouseOverListener = 995 function(ev) { 996 var target = ev.target; 997 if (target.id.charAt(0) == 'c') { 998 this._setClassName(target, DwtCalendar._HOVERED); 999 // If a mouse over callback has been registered, then call it to give it 1000 // chance do work like setting the tooltip content 1001 if (this._mouseOverDayCB) { 1002 DwtCalendar._tmpDate.setFullYear(target._year, target._month, target._day); 1003 this._mouseOverDayCB.run(this, DwtCalendar._tmpDate); 1004 } 1005 } else if (target.id.charAt(0) == 't') { 1006 // Dont activate title for now 1007 return; 1008 } else if (target.id.charAt(0) == 'b') { 1009 var img; 1010 if (target.firstChild == null) { 1011 img = target; 1012 AjxImg.getParentElement(target).className = DwtCalendar._BUTTON_HOVERED_CLASS; 1013 } else { 1014 target.className = DwtCalendar._BUTTON_HOVERED_CLASS; 1015 img = AjxImg.getImageElement(target); 1016 } 1017 img.className = img._origClassName; 1018 } 1019 1020 ev._stopPropagation = true; 1021 }; 1022 1023 DwtCalendar.prototype._mouseOutListener = 1024 function(ev) { 1025 this.setToolTipContent(null); 1026 var target = ev.target; 1027 if (target.id.charAt(0) == 'c') { 1028 this._setClassName(target, DwtCalendar._NORMAL); 1029 if (this._mouseOutDayCB) { 1030 this._mouseOutDayCB.run(this); 1031 } 1032 } else if (target.id.charAt(0) == 'b') { 1033 var img; 1034 target.className = DwtCalendar._BUTTON_CLASS; 1035 if (target.firstChild == null) { 1036 img = target; 1037 AjxImg.getParentElement(target).className = DwtCalendar._BUTTON_CLASS; 1038 } else { 1039 target.className = DwtCalendar._BUTTON_CLASS; 1040 img = AjxImg.getImageElement(target); 1041 } 1042 img.className = img._origClassName; 1043 } 1044 }; 1045 1046 DwtCalendar.prototype._mouseDownListener = 1047 function(ev) { 1048 if (ev.button == DwtMouseEvent.LEFT) { 1049 var target = ev.target; 1050 if (target.id.charAt(0) == 'c') { 1051 this._setClassName(target, DwtCalendar._ACTIVE); 1052 } else if (target.id.charAt(0) == 't') { 1053 target.className = DwtCalendar._TITLE_ACTIVE_CLASS; 1054 } else if (target.id.charAt(0) == 'b') { 1055 var img; 1056 if (target.firstChild == null) { 1057 img = target; 1058 AjxImg.getParentElement(target).className = DwtCalendar._BUTTON_ACTIVE_CLASS; 1059 } else { 1060 target.className = DwtCalendar._BUTTON_ACTIVE_CLASS; 1061 img = AjxImg.getImageElement(target); 1062 } 1063 img.className = img._origClassName; 1064 } else if (target.id.charAt(0) == 'w') { 1065 } 1066 } 1067 }; 1068 1069 DwtCalendar.prototype._mouseUpListener = 1070 function(ev) { 1071 var target = ev.target; 1072 if (ev.button == DwtMouseEvent.LEFT) { 1073 if (target.id.charAt(0) == 'c') { 1074 // If our parent is a menu then we need to have it close 1075 if (this.parent instanceof DwtMenu) 1076 DwtMenu.closeActiveMenu(); 1077 1078 var sDate = new Date(target._year, target._month, target._day); 1079 if(sDate.getDate() != target._day) { 1080 sDate.setDate(target._day); 1081 } 1082 if (this.setDate(sDate)) { return; } 1083 1084 this._setClassName(target, DwtCalendar._HOVERED); 1085 } else if (target.id.charAt(0) == 'b') { 1086 var img; 1087 if (target.firstChild == null) { 1088 img = target; 1089 AjxImg.getParentElement(target).className = DwtCalendar._BUTTON_HOVERED_CLASS; 1090 } else { 1091 target.className = DwtCalendar._BUTTON_HOVERED_CLASS; 1092 img = AjxImg.getImageElement(target); 1093 } 1094 img.className = img._origClassName; 1095 1096 if (img.id.indexOf("py") != -1) { 1097 this._prevYear(); 1098 } else if (img.id.indexOf("pm") != -1) { 1099 this._prevMonth(); 1100 } else if (img.id.indexOf("nm") != -1) { 1101 this._nextMonth(); 1102 } else { 1103 this._nextYear(); 1104 } 1105 } else if (target.id.charAt(0) == 't') { 1106 // TODO POPUP MENU 1107 target.className = DwtCalendar._TITLE_HOVERED_CLASS; 1108 this.setDate(new Date(), this._skipNotifyOnPage); 1109 // If our parent is a menu then we need to have it close 1110 if (this.parent instanceof DwtMenu) { 1111 DwtMenu.closeActiveMenu(); 1112 } 1113 } 1114 } else if (ev.button == DwtMouseEvent.RIGHT && target.id.charAt(0) == 'c') { 1115 this._notifyListeners(DwtEvent.ACTION, 0, new Date(target._year, target._month, target._day), ev); 1116 } 1117 }; 1118 1119 DwtCalendar.prototype._doubleClickListener = 1120 function(ev) { 1121 var target = ev.target; 1122 if (this._selectionEvent) { 1123 this._selectionEvent.type = DwtCalendar.DATE_DBL_CLICKED; 1124 } 1125 if (target.id.charAt(0) == 'c') { 1126 // If our parent is a menu then we need to have it close 1127 if (this.parent instanceof DwtMenu) { 1128 DwtMenu.closeActiveMenu(); 1129 } 1130 this.setDate(new Date(target._year, target._month, target._day), false, false, true) 1131 } 1132 }; 1133 1134 DwtCalendar.prototype._prevMonth = 1135 function(ev) { 1136 var d = new Date(this._date.getTime()); 1137 this.setDate(AjxDateUtil.roll(d, AjxDateUtil.MONTH, -1), this._skipNotifyOnPage); 1138 }; 1139 1140 DwtCalendar.prototype._nextMonth = 1141 function(ev) { 1142 var d = new Date(this._date.getTime()); 1143 this.setDate(AjxDateUtil.roll(d, AjxDateUtil.MONTH, 1), this._skipNotifyOnPage); 1144 }; 1145 1146 DwtCalendar.prototype._prevYear = 1147 function(ev) { 1148 var d = new Date(this._date.getTime()); 1149 this.setDate(AjxDateUtil.roll(d, AjxDateUtil.YEAR, -1), this._skipNotifyOnPage); 1150 }; 1151 1152 DwtCalendar.prototype._nextYear = 1153 function(ev) { 1154 var d = new Date(this._date.getTime()); 1155 this.setDate(AjxDateUtil.roll(d, AjxDateUtil.YEAR, 1), this._skipNotifyOnPage); 1156 }; 1157 1158 /** 1159 * Gets the date formatter. 1160 * 1161 * @return {AjxDateFormat} the date formatter 1162 * 1163 * @private 1164 */ 1165 DwtCalendar.getDateFormatter = 1166 function() { 1167 if (!DwtCalendar._dateFormatter) { 1168 DwtCalendar._dateFormatter = new AjxDateFormat(AjxMsg.formatCalDate); 1169 } 1170 return DwtCalendar._dateFormatter; 1171 }; 1172 1173 /** 1174 * Gets the date long formatter. 1175 * 1176 * @return {AjxDateFormat} the date formatter 1177 * 1178 * @private 1179 */ 1180 DwtCalendar.getDateLongFormatter = 1181 function() { 1182 if (!DwtCalendar._dateLongFormatter) { 1183 DwtCalendar._dateLongFormatter = new AjxDateFormat(AjxMsg.formatCalDateLong); 1184 } 1185 return DwtCalendar._dateLongFormatter; 1186 }; 1187 1188 /** 1189 * Gets the date full formatter. 1190 * 1191 * @return {AjxDateFormat} the date formatter 1192 * 1193 * @private 1194 */ 1195 DwtCalendar.getDateFullFormatter = 1196 function() { 1197 if (!DwtCalendar._dateFullFormatter) { 1198 DwtCalendar._dateFullFormatter = new AjxDateFormat(AjxMsg.formatCalDateFull); 1199 } 1200 return DwtCalendar._dateFullFormatter; 1201 }; 1202 1203 /** 1204 * Gets the hour formatter. 1205 * 1206 * @return {AjxDateFormat} the date formatter 1207 * 1208 * @private 1209 */ 1210 DwtCalendar.getHourFormatter = 1211 function() { 1212 if (!DwtCalendar._hourFormatter) { 1213 DwtCalendar._hourFormatter = new AjxMessageFormat(AjxMsg.formatCalHour); 1214 } 1215 return DwtCalendar._hourFormatter; 1216 }; 1217 1218 /** 1219 * Gets the day formatter. 1220 * 1221 * @return {AjxDateFormat} the date formatter 1222 * 1223 * @private 1224 */ 1225 DwtCalendar.getDayFormatter = 1226 function() { 1227 if (!DwtCalendar._dayFormatter) { 1228 DwtCalendar._dayFormatter = new AjxDateFormat(AjxMsg.formatCalDay); 1229 } 1230 return DwtCalendar._dayFormatter; 1231 }; 1232 1233 /** 1234 * Gets the month formatter. 1235 * 1236 * @return {AjxDateFormat} the date formatter 1237 * 1238 * @private 1239 */ 1240 DwtCalendar.getMonthFormatter = 1241 function() { 1242 if (!DwtCalendar._monthFormatter) { 1243 DwtCalendar._monthFormatter = new AjxDateFormat(AjxMsg.formatCalMonth); 1244 } 1245 return DwtCalendar._monthFormatter; 1246 }; 1247 1248 /** 1249 * Gets the short month formatter. 1250 * 1251 * @return {AjxDateFormat} the date formatter 1252 * 1253 * @private 1254 */ 1255 DwtCalendar.getShortMonthFormatter = 1256 function() { 1257 if (!DwtCalendar._shortMonthFormatter) { 1258 DwtCalendar._shortMonthFormatter = new AjxDateFormat(AjxMsg.formatShortCalMonth); 1259 } 1260 return DwtCalendar._shortMonthFormatter; 1261 }; 1262 1263 DwtCalendar.prototype._dragEnter = 1264 function(ev) { 1265 }; 1266 1267 DwtCalendar.prototype._dragHover = 1268 function(ev) { 1269 }; 1270 1271 DwtCalendar.prototype._dragOver = 1272 function(ev) { 1273 var target = ev.target; 1274 if (target.id.charAt(0) == 'c') { 1275 this._setClassName(target, DwtCalendar._HOVERED); 1276 this._lastDndCell = target; 1277 } else { 1278 this._lastDndCell = null; 1279 } 1280 }; 1281 1282 DwtCalendar.prototype._dragLeave = 1283 function(ev) { 1284 }; 1285