buildCellObject(date, text, rowUnit) {
   date = date.clone() // ensure our own reference
   const spanHtml = this.buildGotoAnchorHtml(
     {
       date,
       type: rowUnit,
       forceOff: !rowUnit
     },
     {
       'class': 'fc-cell-text'
     },
     htmlEscape(text)
   )
   return { text, spanHtml, date, colspan: 1 }
 }
 // given a resource and an optional date
 renderHeadResourceCellHtml(resource, date?, colspan = 1) {
   return '<th class="fc-resource-cell"' +
     ' data-resource-id="' + resource.id + '"' +
     (date ?
       ' data-date="' + date.format('YYYY-MM-DD') + '"' :
       '') +
     (colspan > 1 ?
       ' colspan="' + colspan + '"' :
       '') +
   '>' +
     htmlEscape(
       this.view.getResourceText(resource)
     ) +
   '</th>'
 }
  fgSegHtml(seg, disableResizing) {
    const { eventDef } = seg.footprint
    const isDraggable = this.view.isEventDefDraggable(eventDef)
    const isResizableFromStart = seg.isStart && this.view.isEventDefResizableFromStart(eventDef)
    const isResizableFromEnd = seg.isEnd && this.view.isEventDefResizableFromEnd(eventDef)

    const classes = this.getSegClasses(seg, isDraggable, isResizableFromStart || isResizableFromEnd)
    classes.unshift('fc-timeline-event', 'fc-h-event')

    const timeText = this.getTimeText(seg.footprint)

    return '<a class="' + classes.join(' ') + '" style="' + cssToStr(this.getSkinCss(seg.footprint.eventDef)) + '"' +
      (eventDef.url ?
        ' href="' + htmlEscape(eventDef.url) + '"' :
        '') +
      '>' +
      '<div class="fc-content">' +
        (timeText ?
          '<span class="fc-time">' +
            htmlEscape(timeText) +
          '</span>'
        :
          '') +
        '<span class="fc-title">' +
          (eventDef.title ? htmlEscape(eventDef.title) : '&nbsp;') +
        '</span>' +
      '</div>' +
      '<div class="fc-bg" />' +
      (isResizableFromStart ?
        '<div class="fc-resizer fc-start-resizer"></div>' :
        '') +
      (isResizableFromEnd ?
        '<div class="fc-resizer fc-end-resizer"></div>' :
        '') +
    '</a>'
  }
  /*
  Populates the TR with cells containing data about the resource
  */
  renderSpreadsheetSkeleton(tr) {
    const { theme } = this.view.calendar
    const { resource } = this

    for (let colSpec of this.view.colSpecs) {

      if (colSpec.group) { // not responsible for group-based rows. VRowGroup is
        continue
      }

      const input = // the source text, and the main argument for the filter functions
        colSpec.field ?
          resource[colSpec.field] || null :
          resource

      const text =
        typeof colSpec.text === 'function' ?
          colSpec.text(resource, input) : // the colspec provided a text filter function
          input

      let contentEl = $(
        '<div class="fc-cell-content">' +
          (colSpec.isMain ? this.renderGutterHtml() : '') +
          '<span class="fc-cell-text">' +
            (text ? htmlEscape(text) : '&nbsp;') +
          '</span>' +
        '</div>'
      )

      if (typeof colSpec.render === 'function') { // a filter function for the element
        contentEl = colSpec.render(resource, contentEl, input) || contentEl
      }

      const td = $('<td class="' + theme.getClass('widgetContent') + '"/>')
        .append(contentEl)

      // the first cell of the row needs to have an inner div for setTrInnerHeight
      if (colSpec.isMain) {
        td.wrapInner('<div/>')
      }

      tr.append(td)
    }

    tr.attr('data-resource-id', resource.id)
  }
  renderHeadHtml() {
    const { theme } = this.view.calendar
    const { colSpecs } = this.view

    let html = '<table class="' + theme.getClass('tableGrid') + '">'

    let colGroupHtml = '<colgroup>'
    for (let o of colSpecs) {
      if (o.isMain) {
        colGroupHtml += '<col class="fc-main-col"/>'
      } else {
        colGroupHtml += '<col/>'
      }
    }

    colGroupHtml += '</colgroup>'
    this.colGroupHtml = colGroupHtml
    html += colGroupHtml

    html += '<tbody>'

    if (this.view.superHeaderText) {
      html +=
        '<tr class="fc-super">' +
          '<th class="' + theme.getClass('widgetHeader') + '" colspan="' + colSpecs.length + '">' +
            '<div class="fc-cell-content">' +
              '<span class="fc-cell-text">' +
                htmlEscape(this.view.superHeaderText) +
              '</span>' +
            '</div>' +
          '</th>' +
        '</tr>'
    }

    html += '<tr>'

    for (let i = 0; i < colSpecs.length; i++) {
      let o = colSpecs[i]
      const isLast = i === (colSpecs.length - 1)

      html +=
        `<th class="` + theme.getClass('widgetHeader') + `">` +
          '<div>' +
            '<div class="fc-cell-content">' +
              (o.isMain ?
                '<span class="fc-expander-space">' +
                  '<span class="fc-icon"></span>' +
                '</span>' :
                '') +
              '<span class="fc-cell-text">' +
                htmlEscape(o.labelText || '') + // what about normalizing this value ahead of time?
              '</span>' +
            '</div>' +
            (!isLast ? '<div class="fc-col-resizer"></div>' : '') +
          '</div>' +
        '</th>'
    }

    html += '</tr>'
    html += '</tbody></table>'

    return html
  }