DayTableHeader.ts 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. import Component, { ComponentContext } from '../component/Component'
  2. import { htmlToElement, removeElement } from '../util/dom-manip'
  3. import { DateMarker, DAY_IDS } from '../datelib/marker'
  4. import { DateProfile } from '../DateProfileGenerator'
  5. import { rangeContainsMarker } from '../datelib/date-range'
  6. import { htmlEscape } from '../util/html'
  7. import { buildGotoAnchorHtml, getDayClasses } from '../component/date-rendering'
  8. import { createFormatter } from '../datelib/formatting'
  9. export interface DayTableHeaderProps {
  10. dates: DateMarker[]
  11. dateProfile: DateProfile
  12. datesRepDistinctDays: boolean
  13. renderIntroHtml?: () => string
  14. }
  15. export default class DayTableHeader extends Component<DayTableHeaderProps> {
  16. el: HTMLElement
  17. thead: HTMLElement
  18. constructor(context: ComponentContext, parentEl: HTMLElement) {
  19. super(context)
  20. parentEl.innerHTML = '' // because might be nbsp
  21. parentEl.appendChild(
  22. this.el = htmlToElement(
  23. '<div class="fc-row ' + this.theme.getClass('headerRow') + '">' +
  24. '<table class="' + this.theme.getClass('tableGrid') + '">' +
  25. '<thead></thead>' +
  26. '</table>' +
  27. '</div>'
  28. )
  29. )
  30. this.thead = this.el.querySelector('thead')
  31. }
  32. destroy() {
  33. removeElement(this.el)
  34. }
  35. render(props: DayTableHeaderProps) {
  36. let { dates, datesRepDistinctDays } = props
  37. let parts = []
  38. if (props.renderIntroHtml) {
  39. parts.push(props.renderIntroHtml())
  40. }
  41. let colHeadFormat = createFormatter(
  42. this.opt('columnHeaderFormat') ||
  43. computeFallbackHeaderFormat(datesRepDistinctDays, dates.length)
  44. )
  45. for (let date of dates) {
  46. parts.push(
  47. renderDateCell(
  48. date,
  49. props.dateProfile,
  50. datesRepDistinctDays,
  51. dates.length,
  52. colHeadFormat,
  53. this.context
  54. )
  55. )
  56. }
  57. if (this.isRtl) {
  58. parts.reverse()
  59. }
  60. this.thead.innerHTML = '<tr>' + parts.join('') + '</tr>'
  61. }
  62. }
  63. // Computes a default column header formatting string if `colFormat` is not explicitly defined
  64. function computeFallbackHeaderFormat(datesRepDistinctDays: boolean, dayCnt: number) {
  65. // if more than one week row, or if there are a lot of columns with not much space,
  66. // put just the day numbers will be in each cell
  67. if (!datesRepDistinctDays || dayCnt > 10) {
  68. return { weekday: 'short' } // "Sat"
  69. } else if (dayCnt > 1) {
  70. return { weekday: 'short', month: 'numeric', day: 'numeric', omitCommas: true } // "Sat 11/12"
  71. } else {
  72. return { weekday: 'long' } // "Saturday"
  73. }
  74. }
  75. function renderDateCell(
  76. date: DateMarker,
  77. dateProfile: DateProfile,
  78. datesRepDistinctDays,
  79. colCnt,
  80. colHeadFormat,
  81. context: ComponentContext,
  82. colspan?,
  83. otherAttrs?
  84. ): string {
  85. let { view, dateEnv, theme, options } = context
  86. let isDateValid = rangeContainsMarker(dateProfile.activeRange, date) // TODO: called too frequently. cache somehow.
  87. let classNames = [
  88. 'fc-day-header',
  89. theme.getClass('widgetHeader')
  90. ]
  91. let innerHtml
  92. if (typeof options.columnHeaderHtml === 'function') {
  93. innerHtml = options.columnHeaderHtml(date)
  94. } else if (typeof options.columnHeaderText === 'function') {
  95. innerHtml = htmlEscape(
  96. options.columnHeaderText(date)
  97. )
  98. } else {
  99. innerHtml = htmlEscape(dateEnv.format(date, colHeadFormat))
  100. }
  101. // if only one row of days, the classNames on the header can represent the specific days beneath
  102. if (datesRepDistinctDays) {
  103. classNames = classNames.concat(
  104. // includes the day-of-week class
  105. // noThemeHighlight=true (don't highlight the header)
  106. getDayClasses(date, dateProfile, context, true)
  107. )
  108. } else {
  109. classNames.push('fc-' + DAY_IDS[date.getUTCDay()]) // only add the day-of-week class
  110. }
  111. return '' +
  112. '<th class="' + classNames.join(' ') + '"' +
  113. ((isDateValid && datesRepDistinctDays) ?
  114. ' data-date="' + dateEnv.formatIso(date, { omitTime: true }) + '"' :
  115. '') +
  116. (colspan > 1 ?
  117. ' colspan="' + colspan + '"' :
  118. '') +
  119. (otherAttrs ?
  120. ' ' + otherAttrs :
  121. '') +
  122. '>' +
  123. (isDateValid ?
  124. // don't make a link if the heading could represent multiple days, or if there's only one day (forceOff)
  125. buildGotoAnchorHtml(
  126. view,
  127. { date: date, forceOff: !datesRepDistinctDays || colCnt === 1 },
  128. innerHtml
  129. ) :
  130. // if not valid, display text, but no link
  131. innerHtml
  132. ) +
  133. '</th>'
  134. }