1import * as $ from 'jquery' 2import { htmlEscape } from '../util' 3import CoordCache from '../common/CoordCache' 4import Popover from '../common/Popover' 5import UnzonedRange from '../models/UnzonedRange' 6import ComponentFootprint from '../models/ComponentFootprint' 7import EventFootprint from '../models/event/EventFootprint' 8import BusinessHourRenderer from '../component/renderers/BusinessHourRenderer' 9import StandardInteractionsMixin from '../component/interactions/StandardInteractionsMixin' 10import InteractiveDateComponent from '../component/InteractiveDateComponent' 11import { default as DayTableMixin, DayTableInterface } from '../component/DayTableMixin' 12import DayGridEventRenderer from './DayGridEventRenderer' 13import DayGridHelperRenderer from './DayGridHelperRenderer' 14import DayGridFillRenderer from './DayGridFillRenderer' 15 16 17/* A component that renders a grid of whole-days that runs horizontally. There can be multiple rows, one per week. 18----------------------------------------------------------------------------------------------------------------------*/ 19 20export default class DayGrid extends InteractiveDateComponent { 21 22 rowCnt: DayTableInterface['rowCnt'] 23 colCnt: DayTableInterface['colCnt'] 24 daysPerRow: DayTableInterface['daysPerRow'] 25 sliceRangeByRow: DayTableInterface['sliceRangeByRow'] 26 updateDayTable: DayTableInterface['updateDayTable'] 27 renderHeadHtml: DayTableInterface['renderHeadHtml'] 28 getCellDate: DayTableInterface['getCellDate'] 29 renderBgTrHtml: DayTableInterface['renderBgTrHtml'] 30 renderIntroHtml: DayTableInterface['renderIntroHtml'] 31 getCellRange: DayTableInterface['getCellRange'] 32 sliceRangeByDay: DayTableInterface['sliceRangeByDay'] 33 34 view: any // TODO: make more general and/or remove 35 helperRenderer: any 36 37 cellWeekNumbersVisible: boolean = false // display week numbers in day cell? 38 39 bottomCoordPadding: number = 0 // hack for extending the hit area for the last row of the coordinate grid 40 41 headContainerEl: any // div that hold's the date header 42 rowEls: any // set of fake row elements 43 cellEls: any // set of whole-day elements comprising the row's background 44 45 rowCoordCache: any 46 colCoordCache: any 47 48 // isRigid determines whether the individual rows should ignore the contents and be a constant height. 49 // Relies on the view's colCnt and rowCnt. In the future, this component should probably be self-sufficient. 50 isRigid: boolean = false 51 52 hasAllDayBusinessHours: boolean = true 53 54 segPopover: any // the Popover that holds events that can't fit in a cell. null when not visible 55 popoverSegs: any // an array of segment objects that the segPopover holds. null when not visible 56 57 58 constructor(view) { // view is required, unlike superclass 59 super(view) 60 } 61 62 63 // Slices up the given span (unzoned start/end with other misc data) into an array of segments 64 componentFootprintToSegs(componentFootprint) { 65 let segs = this.sliceRangeByRow(componentFootprint.unzonedRange) 66 let i 67 let seg 68 69 for (i = 0; i < segs.length; i++) { 70 seg = segs[i] 71 72 if (this.isRTL) { 73 seg.leftCol = this.daysPerRow - 1 - seg.lastRowDayIndex 74 seg.rightCol = this.daysPerRow - 1 - seg.firstRowDayIndex 75 } else { 76 seg.leftCol = seg.firstRowDayIndex 77 seg.rightCol = seg.lastRowDayIndex 78 } 79 } 80 81 return segs 82 } 83 84 85 /* Date Rendering 86 ------------------------------------------------------------------------------------------------------------------*/ 87 88 89 renderDates(dateProfile) { 90 this.dateProfile = dateProfile 91 this.updateDayTable() 92 this.renderGrid() 93 } 94 95 96 unrenderDates() { 97 this.removeSegPopover() 98 } 99 100 101 // Renders the rows and columns into the component's `this.el`, which should already be assigned. 102 renderGrid() { 103 let view = this.view 104 let rowCnt = this.rowCnt 105 let colCnt = this.colCnt 106 let html = '' 107 let row 108 let col 109 110 if (this.headContainerEl) { 111 this.headContainerEl.html(this.renderHeadHtml()) 112 } 113 114 for (row = 0; row < rowCnt; row++) { 115 html += this.renderDayRowHtml(row, this.isRigid) 116 } 117 this.el.html(html) 118 119 this.rowEls = this.el.find('.fc-row') 120 this.cellEls = this.el.find('.fc-day, .fc-disabled-day') 121 122 this.rowCoordCache = new CoordCache({ 123 els: this.rowEls, 124 isVertical: true 125 }) 126 this.colCoordCache = new CoordCache({ 127 els: this.cellEls.slice(0, this.colCnt), // only the first row 128 isHorizontal: true 129 }) 130 131 // trigger dayRender with each cell's element 132 for (row = 0; row < rowCnt; row++) { 133 for (col = 0; col < colCnt; col++) { 134 this.publiclyTrigger('dayRender', { 135 context: view, 136 args: [ 137 this.getCellDate(row, col), 138 this.getCellEl(row, col), 139 view 140 ] 141 }) 142 } 143 } 144 } 145 146 147 // Generates the HTML for a single row, which is a div that wraps a table. 148 // `row` is the row number. 149 renderDayRowHtml(row, isRigid) { 150 let theme = this.view.calendar.theme 151 let classes = [ 'fc-row', 'fc-week', theme.getClass('dayRow') ] 152 153 if (isRigid) { 154 classes.push('fc-rigid') 155 } 156 157 return '' + 158 '<div class="' + classes.join(' ') + '">' + 159 '<div class="fc-bg">' + 160 '<table class="' + theme.getClass('tableGrid') + '">' + 161 this.renderBgTrHtml(row) + 162 '</table>' + 163 '</div>' + 164 '<div class="fc-content-skeleton">' + 165 '<table>' + 166 (this.getIsNumbersVisible() ? 167 '<thead>' + 168 this.renderNumberTrHtml(row) + 169 '</thead>' : 170 '' 171 ) + 172 '</table>' + 173 '</div>' + 174 '</div>' 175 } 176 177 178 getIsNumbersVisible() { 179 return this.getIsDayNumbersVisible() || this.cellWeekNumbersVisible 180 } 181 182 183 getIsDayNumbersVisible() { 184 return this.rowCnt > 1 185 } 186 187 188 /* Grid Number Rendering 189 ------------------------------------------------------------------------------------------------------------------*/ 190 191 192 renderNumberTrHtml(row) { 193 return '' + 194 '<tr>' + 195 (this.isRTL ? '' : this.renderNumberIntroHtml(row)) + 196 this.renderNumberCellsHtml(row) + 197 (this.isRTL ? this.renderNumberIntroHtml(row) : '') + 198 '</tr>' 199 } 200 201 202 renderNumberIntroHtml(row) { 203 return this.renderIntroHtml() 204 } 205 206 207 renderNumberCellsHtml(row) { 208 let htmls = [] 209 let col 210 let date 211 212 for (col = 0; col < this.colCnt; col++) { 213 date = this.getCellDate(row, col) 214 htmls.push(this.renderNumberCellHtml(date)) 215 } 216 217 return htmls.join('') 218 } 219 220 221 // Generates the HTML for the <td>s of the "number" row in the DayGrid's content skeleton. 222 // The number row will only exist if either day numbers or week numbers are turned on. 223 renderNumberCellHtml(date) { 224 let view = this.view 225 let html = '' 226 let isDateValid = this.dateProfile.activeUnzonedRange.containsDate(date) // TODO: called too frequently. cache somehow. 227 let isDayNumberVisible = this.getIsDayNumbersVisible() && isDateValid 228 let classes 229 let weekCalcFirstDoW 230 231 if (!isDayNumberVisible && !this.cellWeekNumbersVisible) { 232 // no numbers in day cell (week number must be along the side) 233 return '<td></td>' // will create an empty space above events :( 234 } 235 236 classes = this.getDayClasses(date) 237 classes.unshift('fc-day-top') 238 239 if (this.cellWeekNumbersVisible) { 240 // To determine the day of week number change under ISO, we cannot 241 // rely on moment.js methods such as firstDayOfWeek() or weekday(), 242 // because they rely on the locale's dow (possibly overridden by 243 // our firstDay option), which may not be Monday. We cannot change 244 // dow, because that would affect the calendar start day as well. 245 if (date._locale._fullCalendar_weekCalc === 'ISO') { 246 weekCalcFirstDoW = 1 // Monday by ISO 8601 definition 247 } else { 248 weekCalcFirstDoW = date._locale.firstDayOfWeek() 249 } 250 } 251 252 html += '<td class="' + classes.join(' ') + '"' + 253 (isDateValid ? 254 ' data-date="' + date.format() + '"' : 255 '' 256 ) + 257 '>' 258 259 if (this.cellWeekNumbersVisible && (date.day() === weekCalcFirstDoW)) { 260 html += view.buildGotoAnchorHtml( 261 { date: date, type: 'week' }, 262 { 'class': 'fc-week-number' }, 263 date.format('w') // inner HTML 264 ) 265 } 266 267 if (isDayNumberVisible) { 268 html += view.buildGotoAnchorHtml( 269 date, 270 { 'class': 'fc-day-number' }, 271 date.format('D') // inner HTML 272 ) 273 } 274 275 html += '</td>' 276 277 return html 278 } 279 280 281 /* Hit System 282 ------------------------------------------------------------------------------------------------------------------*/ 283 284 285 prepareHits() { 286 this.colCoordCache.build() 287 this.rowCoordCache.build() 288 this.rowCoordCache.bottoms[this.rowCnt - 1] += this.bottomCoordPadding // hack 289 } 290 291 292 releaseHits() { 293 this.colCoordCache.clear() 294 this.rowCoordCache.clear() 295 } 296 297 298 queryHit(leftOffset, topOffset) { 299 if (this.colCoordCache.isLeftInBounds(leftOffset) && this.rowCoordCache.isTopInBounds(topOffset)) { 300 let col = this.colCoordCache.getHorizontalIndex(leftOffset) 301 let row = this.rowCoordCache.getVerticalIndex(topOffset) 302 303 if (row != null && col != null) { 304 return this.getCellHit(row, col) 305 } 306 } 307 } 308 309 310 getHitFootprint(hit) { 311 let range = this.getCellRange(hit.row, hit.col) 312 313 return new ComponentFootprint( 314 new UnzonedRange(range.start, range.end), 315 true // all-day? 316 ) 317 } 318 319 320 getHitEl(hit) { 321 return this.getCellEl(hit.row, hit.col) 322 } 323 324 325 /* Cell System 326 ------------------------------------------------------------------------------------------------------------------*/ 327 // FYI: the first column is the leftmost column, regardless of date 328 329 330 getCellHit(row, col): any { 331 return { 332 row: row, 333 col: col, 334 component: this, // needed unfortunately :( 335 left: this.colCoordCache.getLeftOffset(col), 336 right: this.colCoordCache.getRightOffset(col), 337 top: this.rowCoordCache.getTopOffset(row), 338 bottom: this.rowCoordCache.getBottomOffset(row) 339 } 340 } 341 342 343 getCellEl(row, col) { 344 return this.cellEls.eq(row * this.colCnt + col) 345 } 346 347 348 /* Event Rendering 349 ------------------------------------------------------------------------------------------------------------------*/ 350 351 352 // Unrenders all events currently rendered on the grid 353 executeEventUnrender() { 354 this.removeSegPopover() // removes the "more.." events popover 355 super.executeEventUnrender() 356 } 357 358 359 // Retrieves all rendered segment objects currently rendered on the grid 360 getOwnEventSegs() { 361 // append the segments from the "more..." popover 362 return super.getOwnEventSegs().concat(this.popoverSegs || []) 363 } 364 365 366 /* Event Drag Visualization 367 ------------------------------------------------------------------------------------------------------------------*/ 368 369 370 // Renders a visual indication of an event or external element being dragged. 371 // `eventLocation` has zoned start and end (optional) 372 renderDrag(eventFootprints, seg, isTouch) { 373 let i 374 375 for (i = 0; i < eventFootprints.length; i++) { 376 this.renderHighlight(eventFootprints[i].componentFootprint) 377 } 378 379 // render drags from OTHER components as helpers 380 if (eventFootprints.length && seg && seg.component !== this) { 381 this.helperRenderer.renderEventDraggingFootprints(eventFootprints, seg, isTouch) 382 383 return true // signal helpers rendered 384 } 385 } 386 387 388 // Unrenders any visual indication of a hovering event 389 unrenderDrag() { 390 this.unrenderHighlight() 391 this.helperRenderer.unrender() 392 } 393 394 395 /* Event Resize Visualization 396 ------------------------------------------------------------------------------------------------------------------*/ 397 398 399 // Renders a visual indication of an event being resized 400 renderEventResize(eventFootprints, seg, isTouch) { 401 let i 402 403 for (i = 0; i < eventFootprints.length; i++) { 404 this.renderHighlight(eventFootprints[i].componentFootprint) 405 } 406 407 this.helperRenderer.renderEventResizingFootprints(eventFootprints, seg, isTouch) 408 } 409 410 411 // Unrenders a visual indication of an event being resized 412 unrenderEventResize() { 413 this.unrenderHighlight() 414 this.helperRenderer.unrender() 415 } 416 417 418 /* More+ Link Popover 419 ------------------------------------------------------------------------------------------------------------------*/ 420 421 422 removeSegPopover() { 423 if (this.segPopover) { 424 this.segPopover.hide() // in handler, will call segPopover's removeElement 425 } 426 } 427 428 429 // Limits the number of "levels" (vertically stacking layers of events) for each row of the grid. 430 // `levelLimit` can be false (don't limit), a number, or true (should be computed). 431 limitRows(levelLimit) { 432 let rowStructs = this.eventRenderer.rowStructs || [] 433 let row // row # 434 let rowLevelLimit 435 436 for (row = 0; row < rowStructs.length; row++) { 437 this.unlimitRow(row) 438 439 if (!levelLimit) { 440 rowLevelLimit = false 441 } else if (typeof levelLimit === 'number') { 442 rowLevelLimit = levelLimit 443 } else { 444 rowLevelLimit = this.computeRowLevelLimit(row) 445 } 446 447 if (rowLevelLimit !== false) { 448 this.limitRow(row, rowLevelLimit) 449 } 450 } 451 } 452 453 454 // Computes the number of levels a row will accomodate without going outside its bounds. 455 // Assumes the row is "rigid" (maintains a constant height regardless of what is inside). 456 // `row` is the row number. 457 computeRowLevelLimit(row): (number | false) { 458 let rowEl = this.rowEls.eq(row) // the containing "fake" row div 459 let rowHeight = rowEl.height() // TODO: cache somehow? 460 let trEls = this.eventRenderer.rowStructs[row].tbodyEl.children() 461 let i 462 let trEl 463 let trHeight 464 465 function iterInnerHeights(i, childNode) { 466 trHeight = Math.max(trHeight, $(childNode).outerHeight()) 467 } 468 469 // Reveal one level <tr> at a time and stop when we find one out of bounds 470 for (i = 0; i < trEls.length; i++) { 471 trEl = trEls.eq(i).removeClass('fc-limited') // reset to original state (reveal) 472 473 // with rowspans>1 and IE8, trEl.outerHeight() would return the height of the largest cell, 474 // so instead, find the tallest inner content element. 475 trHeight = 0 476 trEl.find('> td > :first-child').each(iterInnerHeights) 477 478 if (trEl.position().top + trHeight > rowHeight) { 479 return i 480 } 481 } 482 483 return false // should not limit at all 484 } 485 486 487 // Limits the given grid row to the maximum number of levels and injects "more" links if necessary. 488 // `row` is the row number. 489 // `levelLimit` is a number for the maximum (inclusive) number of levels allowed. 490 limitRow(row, levelLimit) { 491 let rowStruct = this.eventRenderer.rowStructs[row] 492 let moreNodes = [] // array of "more" <a> links and <td> DOM nodes 493 let col = 0 // col #, left-to-right (not chronologically) 494 let levelSegs // array of segment objects in the last allowable level, ordered left-to-right 495 let cellMatrix // a matrix (by level, then column) of all <td> jQuery elements in the row 496 let limitedNodes // array of temporarily hidden level <tr> and segment <td> DOM nodes 497 let i 498 let seg 499 let segsBelow // array of segment objects below `seg` in the current `col` 500 let totalSegsBelow // total number of segments below `seg` in any of the columns `seg` occupies 501 let colSegsBelow // array of segment arrays, below seg, one for each column (offset from segs's first column) 502 let td 503 let rowspan 504 let segMoreNodes // array of "more" <td> cells that will stand-in for the current seg's cell 505 let j 506 let moreTd 507 let moreWrap 508 let moreLink 509 510 // Iterates through empty level cells and places "more" links inside if need be 511 let emptyCellsUntil = (endCol) => { // goes from current `col` to `endCol` 512 while (col < endCol) { 513 segsBelow = this.getCellSegs(row, col, levelLimit) 514 if (segsBelow.length) { 515 td = cellMatrix[levelLimit - 1][col] 516 moreLink = this.renderMoreLink(row, col, segsBelow) 517 moreWrap = $('<div>').append(moreLink) 518 td.append(moreWrap) 519 moreNodes.push(moreWrap[0]) 520 } 521 col++ 522 } 523 } 524 525 if (levelLimit && levelLimit < rowStruct.segLevels.length) { // is it actually over the limit? 526 levelSegs = rowStruct.segLevels[levelLimit - 1] 527 cellMatrix = rowStruct.cellMatrix 528 529 limitedNodes = rowStruct.tbodyEl.children().slice(levelLimit) // get level <tr> elements past the limit 530 .addClass('fc-limited').get() // hide elements and get a simple DOM-nodes array 531 532 // iterate though segments in the last allowable level 533 for (i = 0; i < levelSegs.length; i++) { 534 seg = levelSegs[i] 535 emptyCellsUntil(seg.leftCol) // process empty cells before the segment 536 537 // determine *all* segments below `seg` that occupy the same columns 538 colSegsBelow = [] 539 totalSegsBelow = 0 540 while (col <= seg.rightCol) { 541 segsBelow = this.getCellSegs(row, col, levelLimit) 542 colSegsBelow.push(segsBelow) 543 totalSegsBelow += segsBelow.length 544 col++ 545 } 546 547 if (totalSegsBelow) { // do we need to replace this segment with one or many "more" links? 548 td = cellMatrix[levelLimit - 1][seg.leftCol] // the segment's parent cell 549 rowspan = td.attr('rowspan') || 1 550 segMoreNodes = [] 551 552 // make a replacement <td> for each column the segment occupies. will be one for each colspan 553 for (j = 0; j < colSegsBelow.length; j++) { 554 moreTd = $('<td class="fc-more-cell">').attr('rowspan', rowspan) 555 segsBelow = colSegsBelow[j] 556 moreLink = this.renderMoreLink( 557 row, 558 seg.leftCol + j, 559 [ seg ].concat(segsBelow) // count seg as hidden too 560 ) 561 moreWrap = $('<div>').append(moreLink) 562 moreTd.append(moreWrap) 563 segMoreNodes.push(moreTd[0]) 564 moreNodes.push(moreTd[0]) 565 } 566 567 td.addClass('fc-limited').after($(segMoreNodes)) // hide original <td> and inject replacements 568 limitedNodes.push(td[0]) 569 } 570 } 571 572 emptyCellsUntil(this.colCnt) // finish off the level 573 rowStruct.moreEls = $(moreNodes) // for easy undoing later 574 rowStruct.limitedEls = $(limitedNodes) // for easy undoing later 575 } 576 } 577 578 579 // Reveals all levels and removes all "more"-related elements for a grid's row. 580 // `row` is a row number. 581 unlimitRow(row) { 582 let rowStruct = this.eventRenderer.rowStructs[row] 583 584 if (rowStruct.moreEls) { 585 rowStruct.moreEls.remove() 586 rowStruct.moreEls = null 587 } 588 589 if (rowStruct.limitedEls) { 590 rowStruct.limitedEls.removeClass('fc-limited') 591 rowStruct.limitedEls = null 592 } 593 } 594 595 596 // Renders an <a> element that represents hidden event element for a cell. 597 // Responsible for attaching click handler as well. 598 renderMoreLink(row, col, hiddenSegs) { 599 let view = this.view 600 601 return $('<a class="fc-more">') 602 .text( 603 this.getMoreLinkText(hiddenSegs.length) 604 ) 605 .on('click', (ev) => { 606 let clickOption = this.opt('eventLimitClick') 607 let date = this.getCellDate(row, col) 608 let moreEl = $(ev.currentTarget) 609 let dayEl = this.getCellEl(row, col) 610 let allSegs = this.getCellSegs(row, col) 611 612 // rescope the segments to be within the cell's date 613 let reslicedAllSegs = this.resliceDaySegs(allSegs, date) 614 let reslicedHiddenSegs = this.resliceDaySegs(hiddenSegs, date) 615 616 if (typeof clickOption === 'function') { 617 // the returned value can be an atomic option 618 clickOption = this.publiclyTrigger('eventLimitClick', { 619 context: view, 620 args: [ 621 { 622 date: date.clone(), 623 dayEl: dayEl, 624 moreEl: moreEl, 625 segs: reslicedAllSegs, 626 hiddenSegs: reslicedHiddenSegs 627 }, 628 ev, 629 view 630 ] 631 }) 632 } 633 634 if (clickOption === 'popover') { 635 this.showSegPopover(row, col, moreEl, reslicedAllSegs) 636 } else if (typeof clickOption === 'string') { // a view name 637 view.calendar.zoomTo(date, clickOption) 638 } 639 }) 640 } 641 642 643 // Reveals the popover that displays all events within a cell 644 showSegPopover(row, col, moreLink, segs) { 645 let view = this.view 646 let moreWrap = moreLink.parent() // the <div> wrapper around the <a> 647 let topEl // the element we want to match the top coordinate of 648 let options 649 650 if (this.rowCnt === 1) { 651 topEl = view.el // will cause the popover to cover any sort of header 652 } else { 653 topEl = this.rowEls.eq(row) // will align with top of row 654 } 655 656 options = { 657 className: 'fc-more-popover ' + view.calendar.theme.getClass('popover'), 658 content: this.renderSegPopoverContent(row, col, segs), 659 parentEl: view.el, // attach to root of view. guarantees outside of scrollbars. 660 top: topEl.offset().top, 661 autoHide: true, // when the user clicks elsewhere, hide the popover 662 viewportConstrain: this.opt('popoverViewportConstrain'), 663 hide: () => { 664 // kill everything when the popover is hidden 665 // notify events to be removed 666 if (this.popoverSegs) { 667 this.triggerBeforeEventSegsDestroyed(this.popoverSegs) 668 } 669 this.segPopover.removeElement() 670 this.segPopover = null 671 this.popoverSegs = null 672 } 673 } 674 675 // Determine horizontal coordinate. 676 // We use the moreWrap instead of the <td> to avoid border confusion. 677 if (this.isRTL) { 678 options.right = moreWrap.offset().left + moreWrap.outerWidth() + 1 // +1 to be over cell border 679 } else { 680 options.left = moreWrap.offset().left - 1 // -1 to be over cell border 681 } 682 683 this.segPopover = new Popover(options) 684 this.segPopover.show() 685 686 // the popover doesn't live within the grid's container element, and thus won't get the event 687 // delegated-handlers for free. attach event-related handlers to the popover. 688 this.bindAllSegHandlersToEl(this.segPopover.el) 689 690 this.triggerAfterEventSegsRendered(segs) 691 } 692 693 694 // Builds the inner DOM contents of the segment popover 695 renderSegPopoverContent(row, col, segs) { 696 let view = this.view 697 let theme = view.calendar.theme 698 let title = this.getCellDate(row, col).format(this.opt('dayPopoverFormat')) 699 let content = $( 700 '<div class="fc-header ' + theme.getClass('popoverHeader') + '">' + 701 '<span class="fc-close ' + theme.getIconClass('close') + '"></span>' + 702 '<span class="fc-title">' + 703 htmlEscape(title) + 704 '</span>' + 705 '<div class="fc-clear"></div>' + 706 '</div>' + 707 '<div class="fc-body ' + theme.getClass('popoverContent') + '">' + 708 '<div class="fc-event-container"></div>' + 709 '</div>' 710 ) 711 let segContainer = content.find('.fc-event-container') 712 let i 713 714 // render each seg's `el` and only return the visible segs 715 segs = this.eventRenderer.renderFgSegEls(segs, true) // disableResizing=true 716 this.popoverSegs = segs 717 718 for (i = 0; i < segs.length; i++) { 719 720 // because segments in the popover are not part of a grid coordinate system, provide a hint to any 721 // grids that want to do drag-n-drop about which cell it came from 722 this.hitsNeeded() 723 segs[i].hit = this.getCellHit(row, col) 724 this.hitsNotNeeded() 725 726 segContainer.append(segs[i].el) 727 } 728 729 return content 730 } 731 732 733 // Given the events within an array of segment objects, reslice them to be in a single day 734 resliceDaySegs(segs, dayDate) { 735 let dayStart = dayDate.clone() 736 let dayEnd = dayStart.clone().add(1, 'days') 737 let dayRange = new UnzonedRange(dayStart, dayEnd) 738 let newSegs = [] 739 let i 740 let seg 741 let slicedRange 742 743 for (i = 0; i < segs.length; i++) { 744 seg = segs[i] 745 slicedRange = seg.footprint.componentFootprint.unzonedRange.intersect(dayRange) 746 747 if (slicedRange) { 748 newSegs.push( 749 $.extend({}, seg, { 750 footprint: new EventFootprint( 751 new ComponentFootprint( 752 slicedRange, 753 seg.footprint.componentFootprint.isAllDay 754 ), 755 seg.footprint.eventDef, 756 seg.footprint.eventInstance 757 ), 758 isStart: seg.isStart && slicedRange.isStart, 759 isEnd: seg.isEnd && slicedRange.isEnd 760 }) 761 ) 762 } 763 } 764 765 // force an order because eventsToSegs doesn't guarantee one 766 // TODO: research if still needed 767 this.eventRenderer.sortEventSegs(newSegs) 768 769 return newSegs 770 } 771 772 773 // Generates the text that should be inside a "more" link, given the number of events it represents 774 getMoreLinkText(num) { 775 let opt = this.opt('eventLimitText') 776 777 if (typeof opt === 'function') { 778 return opt(num) 779 } else { 780 return '+' + num + ' ' + opt 781 } 782 } 783 784 785 // Returns segments within a given cell. 786 // If `startLevel` is specified, returns only events including and below that level. Otherwise returns all segs. 787 getCellSegs(row, col, startLevel?) { 788 let segMatrix = this.eventRenderer.rowStructs[row].segMatrix 789 let level = startLevel || 0 790 let segs = [] 791 let seg 792 793 while (level < segMatrix.length) { 794 seg = segMatrix[level][col] 795 if (seg) { 796 segs.push(seg) 797 } 798 level++ 799 } 800 801 return segs 802 } 803 804} 805 806DayGrid.prototype.eventRendererClass = DayGridEventRenderer 807DayGrid.prototype.businessHourRendererClass = BusinessHourRenderer 808DayGrid.prototype.helperRendererClass = DayGridHelperRenderer 809DayGrid.prototype.fillRendererClass = DayGridFillRenderer 810 811StandardInteractionsMixin.mixInto(DayGrid) 812DayTableMixin.mixInto(DayGrid) 813