DayGrid.ts 23 KB

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