DayGrid.ts 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755
  1. import { assignTo } from '../util/object'
  2. import {
  3. createElement,
  4. insertAfterElement,
  5. findElements,
  6. findChildren,
  7. removeElement
  8. } from '../util/dom-manip'
  9. import { computeRect } from '../util/dom-geom'
  10. import View from '../View'
  11. import PositionCache from '../common/PositionCache'
  12. import Popover from '../common/Popover'
  13. import { default as DayTableMixin, DayTableInterface } from '../component/DayTableMixin'
  14. import DayGridEventRenderer from './DayGridEventRenderer'
  15. import DayGridMirrorRenderer from './DayGridMirrorRenderer'
  16. import DayGridFillRenderer from './DayGridFillRenderer'
  17. import { addDays } from '../datelib/marker'
  18. import { createFormatter } from '../datelib/formatting'
  19. import DateComponent, { Seg } from '../component/DateComponent'
  20. import { EventStore } from '../structs/event-store'
  21. import DayTile from './DayTile'
  22. import { Hit } from '../interactions/HitDragging'
  23. import { DateRange, rangeContainsMarker, intersectRanges } from '../datelib/date-range'
  24. import OffsetTracker from '../common/OffsetTracker'
  25. import { EventRenderRange, EventUiHash } from '../component/event-rendering'
  26. const DAY_NUM_FORMAT = createFormatter({ day: 'numeric' })
  27. const WEEK_NUM_FORMAT = createFormatter({ week: 'numeric' })
  28. /* A component that renders a grid of whole-days that runs horizontally. There can be multiple rows, one per week.
  29. ----------------------------------------------------------------------------------------------------------------------*/
  30. export default class DayGrid extends DateComponent {
  31. rowCnt: DayTableInterface['rowCnt']
  32. colCnt: DayTableInterface['colCnt']
  33. daysPerRow: DayTableInterface['daysPerRow']
  34. sliceRangeByRow: DayTableInterface['sliceRangeByRow']
  35. updateDayTable: DayTableInterface['updateDayTable']
  36. renderHeadHtml: DayTableInterface['renderHeadHtml']
  37. getCellDate: DayTableInterface['getCellDate']
  38. renderBgTrHtml: DayTableInterface['renderBgTrHtml']
  39. renderIntroHtml: DayTableInterface['renderIntroHtml']
  40. getCellRange: DayTableInterface['getCellRange']
  41. sliceRangeByDay: DayTableInterface['sliceRangeByDay']
  42. bookendCells: DayTableInterface['bookendCells']
  43. breakOnWeeks: DayTableInterface['breakOnWeeks']
  44. isInteractable = true
  45. doesDragMirror = false
  46. doesDragHighlight = true
  47. slicingType: 'all-day' = 'all-day' // stupid TypeScript
  48. view: View // TODO: make more general and/or remove
  49. mirrorRenderer: any
  50. cellWeekNumbersVisible: boolean = false // display week numbers in day cell?
  51. bottomCoordPadding: number = 0 // hack for extending the hit area for the last row of the coordinate grid
  52. headContainerEl: HTMLElement // div that hold's the date header
  53. rowEls: HTMLElement[] // set of fake row elements
  54. cellEls: HTMLElement[] // set of whole-day elements comprising the row's background
  55. rowPositions: PositionCache
  56. colPositions: PositionCache
  57. offsetTracker: OffsetTracker
  58. // isRigid determines whether the individual rows should ignore the contents and be a constant height.
  59. // Relies on the view's colCnt and rowCnt. In the future, this component should probably be self-sufficient.
  60. isRigid: boolean = false
  61. segPopover: Popover // the Popover that holds events that can't fit in a cell. null when not visible
  62. segPopoverTile: DayTile
  63. constructor(view) { // view is required, unlike superclass
  64. super(view)
  65. }
  66. // Slices up the given span (unzoned start/end with other misc data) into an array of segments
  67. rangeToSegs(range: DateRange): Seg[] {
  68. range = intersectRanges(range, this.dateProfile.validRange)
  69. if (range) {
  70. let segs = this.sliceRangeByRow(range)
  71. for (let i = 0; i < segs.length; i++) {
  72. let seg = segs[i]
  73. seg.component = this
  74. if (this.isRTL) {
  75. seg.leftCol = this.daysPerRow - 1 - seg.lastRowDayIndex
  76. seg.rightCol = this.daysPerRow - 1 - seg.firstRowDayIndex
  77. } else {
  78. seg.leftCol = seg.firstRowDayIndex
  79. seg.rightCol = seg.lastRowDayIndex
  80. }
  81. }
  82. return segs
  83. } else {
  84. return []
  85. }
  86. }
  87. /* Date Rendering
  88. ------------------------------------------------------------------------------------------------------------------*/
  89. renderDates() {
  90. this.updateDayTable()
  91. this.renderGrid()
  92. }
  93. unrenderDates() {
  94. this.removeSegPopover()
  95. }
  96. // Renders the rows and columns into the component's `this.el`, which should already be assigned.
  97. renderGrid() {
  98. let view = this.view
  99. let dateEnv = this.getDateEnv()
  100. let rowCnt = this.rowCnt
  101. let colCnt = this.colCnt
  102. let html = ''
  103. let row
  104. let col
  105. if (this.headContainerEl) {
  106. this.headContainerEl.innerHTML = this.renderHeadHtml()
  107. }
  108. for (row = 0; row < rowCnt; row++) {
  109. html += this.renderDayRowHtml(row, this.isRigid)
  110. }
  111. this.el.innerHTML = html
  112. this.rowEls = findElements(this.el, '.fc-row')
  113. this.cellEls = findElements(this.el, '.fc-day, .fc-disabled-day')
  114. this.rowPositions = new PositionCache(
  115. this.el,
  116. this.rowEls,
  117. false,
  118. true // vertical
  119. )
  120. this.colPositions = new PositionCache(
  121. this.el,
  122. this.cellEls.slice(0, this.colCnt), // only the first row
  123. true,
  124. false // horizontal
  125. )
  126. // trigger dayRender with each cell's element
  127. for (row = 0; row < rowCnt; row++) {
  128. for (col = 0; col < colCnt; col++) {
  129. this.publiclyTrigger('dayRender', [
  130. {
  131. date: dateEnv.toDate(this.getCellDate(row, col)),
  132. isAllDay: true,
  133. el: this.getCellEl(row, col),
  134. view
  135. }
  136. ])
  137. }
  138. }
  139. }
  140. // Generates the HTML for a single row, which is a div that wraps a table.
  141. // `row` is the row number.
  142. renderDayRowHtml(row, isRigid) {
  143. let theme = this.getTheme()
  144. let classes = [ 'fc-row', 'fc-week', theme.getClass('dayRow') ]
  145. if (isRigid) {
  146. classes.push('fc-rigid')
  147. }
  148. return '' +
  149. '<div class="' + classes.join(' ') + '">' +
  150. '<div class="fc-bg">' +
  151. '<table class="' + theme.getClass('tableGrid') + '">' +
  152. this.renderBgTrHtml(row) +
  153. '</table>' +
  154. '</div>' +
  155. '<div class="fc-content-skeleton">' +
  156. '<table>' +
  157. (this.getIsNumbersVisible() ?
  158. '<thead>' +
  159. this.renderNumberTrHtml(row) +
  160. '</thead>' :
  161. ''
  162. ) +
  163. '</table>' +
  164. '</div>' +
  165. '</div>'
  166. }
  167. getIsNumbersVisible() {
  168. return this.getIsDayNumbersVisible() || this.cellWeekNumbersVisible
  169. }
  170. getIsDayNumbersVisible() {
  171. return this.rowCnt > 1
  172. }
  173. /* Grid Number Rendering
  174. ------------------------------------------------------------------------------------------------------------------*/
  175. renderNumberTrHtml(row) {
  176. return '' +
  177. '<tr>' +
  178. (this.isRTL ? '' : this.renderNumberIntroHtml(row)) +
  179. this.renderNumberCellsHtml(row) +
  180. (this.isRTL ? this.renderNumberIntroHtml(row) : '') +
  181. '</tr>'
  182. }
  183. renderNumberIntroHtml(row) {
  184. return this.renderIntroHtml()
  185. }
  186. renderNumberCellsHtml(row) {
  187. let htmls = []
  188. let col
  189. let date
  190. for (col = 0; col < this.colCnt; col++) {
  191. date = this.getCellDate(row, col)
  192. htmls.push(this.renderNumberCellHtml(date))
  193. }
  194. return htmls.join('')
  195. }
  196. // Generates the HTML for the <td>s of the "number" row in the DayGrid's content skeleton.
  197. // The number row will only exist if either day numbers or week numbers are turned on.
  198. renderNumberCellHtml(date) {
  199. let view = this.view
  200. let dateEnv = this.getDateEnv()
  201. let html = ''
  202. let isDateValid = rangeContainsMarker(this.dateProfile.activeRange, date) // TODO: called too frequently. cache somehow.
  203. let isDayNumberVisible = this.getIsDayNumbersVisible() && isDateValid
  204. let classes
  205. let weekCalcFirstDow
  206. if (!isDayNumberVisible && !this.cellWeekNumbersVisible) {
  207. // no numbers in day cell (week number must be along the side)
  208. return '<td></td>' // will create an empty space above events :(
  209. }
  210. classes = this.getDayClasses(date)
  211. classes.unshift('fc-day-top')
  212. if (this.cellWeekNumbersVisible) {
  213. weekCalcFirstDow = dateEnv.weekDow
  214. }
  215. html += '<td class="' + classes.join(' ') + '"' +
  216. (isDateValid ?
  217. ' data-date="' + dateEnv.formatIso(date, { omitTime: true }) + '"' :
  218. ''
  219. ) +
  220. '>'
  221. if (this.cellWeekNumbersVisible && (date.getUTCDay() === weekCalcFirstDow)) {
  222. html += view.buildGotoAnchorHtml(
  223. { date: date, type: 'week' },
  224. { 'class': 'fc-week-number' },
  225. dateEnv.format(date, WEEK_NUM_FORMAT) // inner HTML
  226. )
  227. }
  228. if (isDayNumberVisible) {
  229. html += view.buildGotoAnchorHtml(
  230. date,
  231. { 'class': 'fc-day-number' },
  232. dateEnv.format(date, DAY_NUM_FORMAT) // inner HTML
  233. )
  234. }
  235. html += '</td>'
  236. return html
  237. }
  238. /* Sizing
  239. ------------------------------------------------------------------------------------------------------------------*/
  240. buildPositionCaches() {
  241. this.colPositions.build()
  242. this.rowPositions.build()
  243. this.rowPositions.bottoms[this.rowCnt - 1] += this.bottomCoordPadding // hack
  244. }
  245. /* Hit System
  246. ------------------------------------------------------------------------------------------------------------------*/
  247. prepareHits() {
  248. this.offsetTracker = new OffsetTracker(this.el)
  249. }
  250. releaseHits() {
  251. this.offsetTracker.destroy()
  252. }
  253. queryHit(leftOffset, topOffset): Hit {
  254. let { colPositions, rowPositions, offsetTracker } = this
  255. if (offsetTracker.isWithinClipping(leftOffset, topOffset)) {
  256. let leftOrigin = offsetTracker.computeLeft()
  257. let topOrigin = offsetTracker.computeTop()
  258. let col = colPositions.leftToIndex(leftOffset - leftOrigin)
  259. let row = rowPositions.topToIndex(topOffset - topOrigin)
  260. if (row != null && col != null) {
  261. return {
  262. component: this,
  263. dateSpan: {
  264. range: this.getCellRange(row, col),
  265. isAllDay: true
  266. },
  267. dayEl: this.getCellEl(row, col),
  268. rect: {
  269. left: colPositions.lefts[col] + leftOrigin,
  270. right: colPositions.rights[col] + leftOrigin,
  271. top: rowPositions.tops[row] + topOrigin,
  272. bottom: rowPositions.bottoms[row] + topOrigin
  273. },
  274. layer: 0
  275. }
  276. }
  277. }
  278. }
  279. /* Cell System
  280. ------------------------------------------------------------------------------------------------------------------*/
  281. // FYI: the first column is the leftmost column, regardless of date
  282. getCellEl(row, col) {
  283. return this.cellEls[row * this.colCnt + col]
  284. }
  285. /* Event Rendering
  286. ------------------------------------------------------------------------------------------------------------------*/
  287. // Unrenders all events currently rendered on the grid
  288. unrenderEvents() {
  289. this.removeSegPopover() // removes the "more.." events popover
  290. super.unrenderEvents()
  291. }
  292. // Retrieves all rendered segment objects currently rendered on the grid
  293. getAllEventSegs() {
  294. // append the segments from the "more..." popover
  295. return super.getAllEventSegs().concat(
  296. this.segPopoverTile ?
  297. this.segPopoverTile.getAllEventSegs() :
  298. []
  299. )
  300. }
  301. /* Event Resize Visualization
  302. ------------------------------------------------------------------------------------------------------------------*/
  303. // Renders a visual indication of an event being resized
  304. renderEventResize(eventStore: EventStore, eventUis: EventUiHash, origSeg) {
  305. let segs = this.eventRangesToSegs(
  306. this.eventStoreToRanges(eventStore, eventUis)
  307. )
  308. this.renderHighlightSegs(segs)
  309. this.mirrorRenderer.renderEventResizingSegs(segs, origSeg)
  310. }
  311. // Unrenders a visual indication of an event being resized
  312. unrenderEventResize() {
  313. this.unrenderHighlight()
  314. this.mirrorRenderer.unrender()
  315. }
  316. /* More+ Link Popover
  317. ------------------------------------------------------------------------------------------------------------------*/
  318. removeSegPopover() {
  319. if (this.segPopover) {
  320. this.segPopover.hide() // in handler, will call segPopover's removeElement
  321. }
  322. }
  323. // Limits the number of "levels" (vertically stacking layers of events) for each row of the grid.
  324. // `levelLimit` can be false (don't limit), a number, or true (should be computed).
  325. limitRows(levelLimit) {
  326. let rowStructs = this.eventRenderer.rowStructs || []
  327. let row // row #
  328. let rowLevelLimit
  329. for (row = 0; row < rowStructs.length; row++) {
  330. this.unlimitRow(row)
  331. if (!levelLimit) {
  332. rowLevelLimit = false
  333. } else if (typeof levelLimit === 'number') {
  334. rowLevelLimit = levelLimit
  335. } else {
  336. rowLevelLimit = this.computeRowLevelLimit(row)
  337. }
  338. if (rowLevelLimit !== false) {
  339. this.limitRow(row, rowLevelLimit)
  340. }
  341. }
  342. }
  343. // Computes the number of levels a row will accomodate without going outside its bounds.
  344. // Assumes the row is "rigid" (maintains a constant height regardless of what is inside).
  345. // `row` is the row number.
  346. computeRowLevelLimit(row): (number | false) {
  347. let rowEl = this.rowEls[row] // the containing "fake" row div
  348. let rowBottom = rowEl.getBoundingClientRect().bottom // relative to viewport!
  349. let trEls = findChildren(this.eventRenderer.rowStructs[row].tbodyEl) as HTMLTableRowElement[]
  350. let i
  351. let trEl: HTMLTableRowElement
  352. // Reveal one level <tr> at a time and stop when we find one out of bounds
  353. for (i = 0; i < trEls.length; i++) {
  354. trEl = trEls[i]
  355. trEl.classList.remove('fc-limited') // reset to original state (reveal)
  356. if (trEl.getBoundingClientRect().bottom > rowBottom) {
  357. return i
  358. }
  359. }
  360. return false // should not limit at all
  361. }
  362. // Limits the given grid row to the maximum number of levels and injects "more" links if necessary.
  363. // `row` is the row number.
  364. // `levelLimit` is a number for the maximum (inclusive) number of levels allowed.
  365. limitRow(row, levelLimit) {
  366. let rowStruct = this.eventRenderer.rowStructs[row]
  367. let moreNodes = [] // array of "more" <a> links and <td> DOM nodes
  368. let col = 0 // col #, left-to-right (not chronologically)
  369. let levelSegs // array of segment objects in the last allowable level, ordered left-to-right
  370. let cellMatrix // a matrix (by level, then column) of all <td> elements in the row
  371. let limitedNodes // array of temporarily hidden level <tr> and segment <td> DOM nodes
  372. let i
  373. let seg
  374. let segsBelow // array of segment objects below `seg` in the current `col`
  375. let totalSegsBelow // total number of segments below `seg` in any of the columns `seg` occupies
  376. let colSegsBelow // array of segment arrays, below seg, one for each column (offset from segs's first column)
  377. let td: HTMLTableCellElement
  378. let rowSpan
  379. let segMoreNodes // array of "more" <td> cells that will stand-in for the current seg's cell
  380. let j
  381. let moreTd: HTMLTableCellElement
  382. let moreWrap
  383. let moreLink
  384. // Iterates through empty level cells and places "more" links inside if need be
  385. let emptyCellsUntil = (endCol) => { // goes from current `col` to `endCol`
  386. while (col < endCol) {
  387. segsBelow = this.getCellSegs(row, col, levelLimit)
  388. if (segsBelow.length) {
  389. td = cellMatrix[levelLimit - 1][col]
  390. moreLink = this.renderMoreLink(row, col, segsBelow)
  391. moreWrap = createElement('div', null, moreLink)
  392. td.appendChild(moreWrap)
  393. moreNodes.push(moreWrap[0])
  394. }
  395. col++
  396. }
  397. }
  398. if (levelLimit && levelLimit < rowStruct.segLevels.length) { // is it actually over the limit?
  399. levelSegs = rowStruct.segLevels[levelLimit - 1]
  400. cellMatrix = rowStruct.cellMatrix
  401. limitedNodes = findChildren(rowStruct.tbodyEl).slice(levelLimit) // get level <tr> elements past the limit
  402. limitedNodes.forEach(function(node) {
  403. node.classList.add('fc-limited') // hide elements and get a simple DOM-nodes array
  404. })
  405. // iterate though segments in the last allowable level
  406. for (i = 0; i < levelSegs.length; i++) {
  407. seg = levelSegs[i]
  408. emptyCellsUntil(seg.leftCol) // process empty cells before the segment
  409. // determine *all* segments below `seg` that occupy the same columns
  410. colSegsBelow = []
  411. totalSegsBelow = 0
  412. while (col <= seg.rightCol) {
  413. segsBelow = this.getCellSegs(row, col, levelLimit)
  414. colSegsBelow.push(segsBelow)
  415. totalSegsBelow += segsBelow.length
  416. col++
  417. }
  418. if (totalSegsBelow) { // do we need to replace this segment with one or many "more" links?
  419. td = cellMatrix[levelLimit - 1][seg.leftCol] // the segment's parent cell
  420. rowSpan = td.rowSpan || 1
  421. segMoreNodes = []
  422. // make a replacement <td> for each column the segment occupies. will be one for each colspan
  423. for (j = 0; j < colSegsBelow.length; j++) {
  424. moreTd = createElement('td', { className: 'fc-more-cell', rowSpan }) as HTMLTableCellElement
  425. segsBelow = colSegsBelow[j]
  426. moreLink = this.renderMoreLink(
  427. row,
  428. seg.leftCol + j,
  429. [ seg ].concat(segsBelow) // count seg as hidden too
  430. )
  431. moreWrap = createElement('div', null, moreLink)
  432. moreTd.appendChild(moreWrap)
  433. segMoreNodes.push(moreTd)
  434. moreNodes.push(moreTd)
  435. }
  436. td.classList.add('fc-limited')
  437. insertAfterElement(td, segMoreNodes)
  438. limitedNodes.push(td)
  439. }
  440. }
  441. emptyCellsUntil(this.colCnt) // finish off the level
  442. rowStruct.moreEls = moreNodes // for easy undoing later
  443. rowStruct.limitedEls = limitedNodes // for easy undoing later
  444. }
  445. }
  446. // Reveals all levels and removes all "more"-related elements for a grid's row.
  447. // `row` is a row number.
  448. unlimitRow(row) {
  449. let rowStruct = this.eventRenderer.rowStructs[row]
  450. if (rowStruct.moreEls) {
  451. rowStruct.moreEls.forEach(removeElement)
  452. rowStruct.moreEls = null
  453. }
  454. if (rowStruct.limitedEls) {
  455. rowStruct.limitedEls.forEach(function(limitedEl) {
  456. limitedEl.classList.remove('fc-limited')
  457. })
  458. rowStruct.limitedEls = null
  459. }
  460. }
  461. // Renders an <a> element that represents hidden event element for a cell.
  462. // Responsible for attaching click handler as well.
  463. renderMoreLink(row, col, hiddenSegs) {
  464. let view = this.view
  465. let dateEnv = this.getDateEnv()
  466. let a = createElement('a', { className: 'fc-more' })
  467. a.innerText = this.getMoreLinkText(hiddenSegs.length)
  468. a.addEventListener('click', (ev) => {
  469. let clickOption = this.opt('eventLimitClick')
  470. let date = this.getCellDate(row, col)
  471. let moreEl = ev.currentTarget as HTMLElement
  472. let dayEl = this.getCellEl(row, col)
  473. let allSegs = this.getCellSegs(row, col)
  474. // rescope the segments to be within the cell's date
  475. let reslicedAllSegs = this.resliceDaySegs(allSegs, date)
  476. let reslicedHiddenSegs = this.resliceDaySegs(hiddenSegs, date)
  477. if (typeof clickOption === 'function') {
  478. // the returned value can be an atomic option
  479. clickOption = this.publiclyTrigger('eventLimitClick', [
  480. {
  481. date: dateEnv.toDate(date),
  482. isAllDay: true,
  483. dayEl: dayEl,
  484. moreEl: moreEl,
  485. segs: reslicedAllSegs,
  486. hiddenSegs: reslicedHiddenSegs,
  487. jsEvent: ev,
  488. view
  489. }
  490. ])
  491. }
  492. if (clickOption === 'popover') {
  493. this.showSegPopover(row, col, moreEl, reslicedAllSegs)
  494. } else if (typeof clickOption === 'string') { // a view name
  495. view.calendar.zoomTo(date, clickOption)
  496. }
  497. })
  498. return a
  499. }
  500. // Reveals the popover that displays all events within a cell
  501. showSegPopover(row, col, moreLink: HTMLElement, segs) {
  502. let view = this.view
  503. let moreWrap = moreLink.parentNode as HTMLElement // the <div> wrapper around the <a>
  504. let topEl: HTMLElement // the element we want to match the top coordinate of
  505. let options
  506. if (this.rowCnt === 1) {
  507. topEl = view.el // will cause the popover to cover any sort of header
  508. } else {
  509. topEl = this.rowEls[row] // will align with top of row
  510. }
  511. options = {
  512. className: 'fc-more-popover ' + view.calendar.theme.getClass('popover'),
  513. parentEl: this.el,
  514. top: computeRect(topEl).top,
  515. autoHide: true, // when the user clicks elsewhere, hide the popover
  516. content: (el) => {
  517. this.segPopoverTile.setElement(el)
  518. // it would be more proper to call render() with a full render state,
  519. // but hackily rendering segs directly is much easier
  520. // simlate a lot of what happens in render() and renderEventRanges()
  521. this.segPopoverTile.renderSkeleton()
  522. this.segPopoverTile.eventRenderer.rangeUpdated()
  523. this.segPopoverTile.eventRenderer.renderSegs(segs)
  524. this.segPopoverTile.renderedFlags.events = true // so unrendering works
  525. this.segPopoverTile.triggerRenderedSegs(segs) // for eventAfterRender
  526. },
  527. hide: () => {
  528. this.segPopoverTile.removeElement()
  529. this.segPopover.removeElement()
  530. this.segPopover = null
  531. }
  532. }
  533. // Determine horizontal coordinate.
  534. // We use the moreWrap instead of the <td> to avoid border confusion.
  535. if (this.isRTL) {
  536. options.right = computeRect(moreWrap).right + 1 // +1 to be over cell border
  537. } else {
  538. options.left = computeRect(moreWrap).left - 1 // -1 to be over cell border
  539. }
  540. this.segPopoverTile = new DayTile(this.view, this.getCellDate(row, col))
  541. this.segPopover = new Popover(options)
  542. this.segPopover.show()
  543. this.getCalendar().releaseAfterSizingTriggers() // hack for eventAfterRender
  544. }
  545. // Given the events within an array of segment objects, reslice them to be in a single day
  546. resliceDaySegs(segs, dayDate) {
  547. let dayStart = dayDate
  548. let dayEnd = addDays(dayStart, 1)
  549. let dayRange = { start: dayStart, end: dayEnd }
  550. let newSegs = []
  551. for (let seg of segs) {
  552. let eventRange = seg.eventRange
  553. let origRange = eventRange.range
  554. let slicedRange = intersectRanges(origRange, dayRange)
  555. if (slicedRange) {
  556. newSegs.push(
  557. assignTo({}, seg, {
  558. eventRange: {
  559. def: eventRange.def,
  560. ui: assignTo({}, eventRange.ui, { durationEditable: false }), // hack to disable resizing
  561. instance: eventRange.instance,
  562. range: slicedRange
  563. } as EventRenderRange,
  564. isStart: seg.isStart && slicedRange.start.valueOf() === origRange.start.valueOf(),
  565. isEnd: seg.isEnd && slicedRange.end.valueOf() === origRange.end.valueOf()
  566. })
  567. )
  568. }
  569. }
  570. return newSegs
  571. }
  572. // Generates the text that should be inside a "more" link, given the number of events it represents
  573. getMoreLinkText(num) {
  574. let opt = this.opt('eventLimitText')
  575. if (typeof opt === 'function') {
  576. return opt(num)
  577. } else {
  578. return '+' + num + ' ' + opt
  579. }
  580. }
  581. // Returns segments within a given cell.
  582. // If `startLevel` is specified, returns only events including and below that level. Otherwise returns all segs.
  583. getCellSegs(row, col, startLevel?) {
  584. let segMatrix = this.eventRenderer.rowStructs[row].segMatrix
  585. let level = startLevel || 0
  586. let segs = []
  587. let seg
  588. while (level < segMatrix.length) {
  589. seg = segMatrix[level][col]
  590. if (seg) {
  591. segs.push(seg)
  592. }
  593. level++
  594. }
  595. return segs
  596. }
  597. }
  598. DayGrid.prototype.eventRendererClass = DayGridEventRenderer
  599. DayGrid.prototype.mirrorRendererClass = DayGridMirrorRenderer
  600. DayGrid.prototype.fillRendererClass = DayGridFillRenderer
  601. DayTableMixin.mixInto(DayGrid)