DayGridEventRenderer.ts 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  1. import { createElement, removeElement, appendToElement, prependToElement } from '../util/dom-manip'
  2. import DayGrid from './DayGrid'
  3. import { Seg } from '../component/DateComponent'
  4. import SimpleDayGridEventRenderer from './SimpleDayGridEventRenderer'
  5. /* Event-rendering methods for the DayGrid class
  6. ----------------------------------------------------------------------------------------------------------------------*/
  7. export default class DayGridEventRenderer extends SimpleDayGridEventRenderer {
  8. dayGrid: DayGrid
  9. rowStructs: any // an array of objects, each holding information about a row's foreground event-rendering
  10. constructor(dayGrid: DayGrid) {
  11. super(dayGrid.context)
  12. this.dayGrid = dayGrid
  13. }
  14. // Renders the given foreground event segments onto the grid
  15. attachSegs(segs: Seg[], mirrorInfo) {
  16. let rowStructs = this.rowStructs = this.renderSegRows(segs)
  17. // append to each row's content skeleton
  18. this.dayGrid.rowEls.forEach(function(rowNode, i) {
  19. rowNode.querySelector('.fc-content-skeleton > table').appendChild(
  20. rowStructs[i].tbodyEl
  21. )
  22. })
  23. // removes the "more.." events popover
  24. if (!mirrorInfo) {
  25. this.dayGrid.removeSegPopover()
  26. }
  27. }
  28. // Unrenders all currently rendered foreground event segments
  29. detachSegs() {
  30. let rowStructs = this.rowStructs || []
  31. let rowStruct
  32. while ((rowStruct = rowStructs.pop())) {
  33. removeElement(rowStruct.tbodyEl)
  34. }
  35. this.rowStructs = null
  36. }
  37. // Uses the given events array to generate <tbody> elements that should be appended to each row's content skeleton.
  38. // Returns an array of rowStruct objects (see the bottom of `renderSegRow`).
  39. // PRECONDITION: each segment shoud already have a rendered and assigned `.el`
  40. renderSegRows(segs: Seg[]) {
  41. let rowStructs = []
  42. let segRows
  43. let row
  44. segRows = this.groupSegRows(segs) // group into nested arrays
  45. // iterate each row of segment groupings
  46. for (row = 0; row < segRows.length; row++) {
  47. rowStructs.push(
  48. this.renderSegRow(row, segRows[row])
  49. )
  50. }
  51. return rowStructs
  52. }
  53. // Given a row # and an array of segments all in the same row, render a <tbody> element, a skeleton that contains
  54. // the segments. Returns object with a bunch of internal data about how the render was calculated.
  55. // NOTE: modifies rowSegs
  56. renderSegRow(row, rowSegs) {
  57. let { dayGrid } = this
  58. let { colCnt, isRtl } = dayGrid
  59. let segLevels = this.buildSegLevels(rowSegs) // group into sub-arrays of levels
  60. let levelCnt = Math.max(1, segLevels.length) // ensure at least one level
  61. let tbody = document.createElement('tbody')
  62. let segMatrix = [] // lookup for which segments are rendered into which level+col cells
  63. let cellMatrix = [] // lookup for all <td> elements of the level+col matrix
  64. let loneCellMatrix = [] // lookup for <td> elements that only take up a single column
  65. let i
  66. let levelSegs
  67. let col
  68. let tr: HTMLTableRowElement
  69. let j
  70. let seg
  71. let td: HTMLTableCellElement
  72. // populates empty cells from the current column (`col`) to `endCol`
  73. function emptyCellsUntil(endCol) {
  74. while (col < endCol) {
  75. // try to grab a cell from the level above and extend its rowspan. otherwise, create a fresh cell
  76. td = (loneCellMatrix[i - 1] || [])[col]
  77. if (td) {
  78. td.rowSpan = (td.rowSpan || 1) + 1
  79. } else {
  80. td = document.createElement('td')
  81. tr.appendChild(td)
  82. }
  83. cellMatrix[i][col] = td
  84. loneCellMatrix[i][col] = td
  85. col++
  86. }
  87. }
  88. for (i = 0; i < levelCnt; i++) { // iterate through all levels
  89. levelSegs = segLevels[i]
  90. col = 0
  91. tr = document.createElement('tr')
  92. segMatrix.push([])
  93. cellMatrix.push([])
  94. loneCellMatrix.push([])
  95. // levelCnt might be 1 even though there are no actual levels. protect against this.
  96. // this single empty row is useful for styling.
  97. if (levelSegs) {
  98. for (j = 0; j < levelSegs.length; j++) { // iterate through segments in level
  99. seg = levelSegs[j]
  100. let leftCol = isRtl ? (colCnt - 1 - seg.lastCol) : seg.firstCol
  101. let rightCol = isRtl ? (colCnt - 1 - seg.firstCol) : seg.lastCol
  102. emptyCellsUntil(leftCol)
  103. // create a container that occupies or more columns. append the event element.
  104. td = createElement('td', { className: 'fc-event-container' }, seg.el) as HTMLTableCellElement
  105. if (leftCol !== rightCol) {
  106. td.colSpan = rightCol - leftCol + 1
  107. } else { // a single-column segment
  108. loneCellMatrix[i][col] = td
  109. }
  110. while (col <= rightCol) {
  111. cellMatrix[i][col] = td
  112. segMatrix[i][col] = seg
  113. col++
  114. }
  115. tr.appendChild(td)
  116. }
  117. }
  118. emptyCellsUntil(colCnt) // finish off the row
  119. let introHtml = dayGrid.renderProps.renderIntroHtml()
  120. if (introHtml) {
  121. if (dayGrid.isRtl) {
  122. appendToElement(tr, introHtml)
  123. } else {
  124. prependToElement(tr, introHtml)
  125. }
  126. }
  127. tbody.appendChild(tr)
  128. }
  129. return { // a "rowStruct"
  130. row: row, // the row number
  131. tbodyEl: tbody,
  132. cellMatrix: cellMatrix,
  133. segMatrix: segMatrix,
  134. segLevels: segLevels,
  135. segs: rowSegs
  136. }
  137. }
  138. // Stacks a flat array of segments, which are all assumed to be in the same row, into subarrays of vertical levels.
  139. // NOTE: modifies segs
  140. buildSegLevels(segs: Seg[]) {
  141. let { isRtl, colCnt } = this.dayGrid
  142. let levels = []
  143. let i
  144. let seg
  145. let j
  146. // Give preference to elements with certain criteria, so they have
  147. // a chance to be closer to the top.
  148. segs = this.sortEventSegs(segs)
  149. for (i = 0; i < segs.length; i++) {
  150. seg = segs[i]
  151. // loop through levels, starting with the topmost, until the segment doesn't collide with other segments
  152. for (j = 0; j < levels.length; j++) {
  153. if (!isDaySegCollision(seg, levels[j])) {
  154. break
  155. }
  156. }
  157. // `j` now holds the desired subrow index
  158. seg.level = j
  159. seg.leftCol = isRtl ? (colCnt - 1 - seg.lastCol) : seg.firstCol // for sorting only
  160. seg.rightCol = isRtl ? (colCnt - 1 - seg.firstCol) : seg.lastCol // for sorting only
  161. // create new level array if needed and append segment
  162. ;(levels[j] || (levels[j] = [])).push(seg)
  163. }
  164. // order segments left-to-right. very important if calendar is RTL
  165. for (j = 0; j < levels.length; j++) {
  166. levels[j].sort(compareDaySegCols)
  167. }
  168. return levels
  169. }
  170. // Given a flat array of segments, return an array of sub-arrays, grouped by each segment's row
  171. groupSegRows(segs: Seg[]) {
  172. let segRows = []
  173. let i
  174. for (i = 0; i < this.dayGrid.rowCnt; i++) {
  175. segRows.push([])
  176. }
  177. for (i = 0; i < segs.length; i++) {
  178. segRows[segs[i].row].push(segs[i])
  179. }
  180. return segRows
  181. }
  182. // Computes a default `displayEventEnd` value if one is not expliclty defined
  183. computeDisplayEventEnd() {
  184. return this.dayGrid.colCnt === 1 // we'll likely have space if there's only one day
  185. }
  186. }
  187. // Computes whether two segments' columns collide. They are assumed to be in the same row.
  188. function isDaySegCollision(seg: Seg, otherSegs: Seg) {
  189. let i
  190. let otherSeg
  191. for (i = 0; i < otherSegs.length; i++) {
  192. otherSeg = otherSegs[i]
  193. if (
  194. otherSeg.firstCol <= seg.lastCol &&
  195. otherSeg.lastCol >= seg.firstCol
  196. ) {
  197. return true
  198. }
  199. }
  200. return false
  201. }
  202. // A cmp function for determining the leftmost event
  203. function compareDaySegCols(a: Seg, b: Seg) {
  204. return a.leftCol - b.leftCol
  205. }