import { htmlEscape } from '../util/html' import { createElement, findElements } from '../util/dom-manip' import { matchCellWidths, uncompensateScroll, compensateScroll, subtractInnerElHeight, distributeHeight, undistributeHeight } from '../util/misc' import { createFormatter } from '../datelib/formatting' import ScrollComponent from '../common/ScrollComponent' import View from '../View' import BasicViewDateProfileGenerator from './BasicViewDateProfileGenerator' import DayGrid from './DayGrid' import { DateProfile } from '../DateProfileGenerator' import { buildGotoAnchorHtml } from '../component/date-rendering' import { DateComponentProps } from '../component/DateComponent' const WEEK_NUM_FORMAT = createFormatter({ week: 'numeric' }) /* An abstract class for the "basic" views, as well as month view. Renders one or more rows of day cells. ----------------------------------------------------------------------------------------------------------------------*/ // It is a manager for a DayGrid subcomponent, which does most of the heavy lifting. // It is responsible for managing width/height. export default class BasicView extends View { // initialized after class dayGridClass: any // class the dayGrid will be instantiated from (overridable by subclasses) scroller: ScrollComponent dayGrid: DayGrid // the main subcomponent that does most of the heavy lifting colWeekNumbersVisible: boolean = false weekNumberWidth: any // width of all the week-number cells running down the side initialize() { this.el.classList.add('fc-basic-view') this.el.innerHTML = this.renderSkeletonHtml() this.scroller = new ScrollComponent( 'hidden', // overflow x 'auto' // overflow y ) let dayGridContainerEl = this.scroller.el this.el.querySelector('.fc-body > tr > td').appendChild(dayGridContainerEl) dayGridContainerEl.classList.add('fc-day-grid-container') let dayGridEl = createElement('div', { className: 'fc-day-grid' }) dayGridContainerEl.appendChild(dayGridEl) this.dayGrid = this.instantiateDayGrid( this.el.querySelector('.fc-head-container'), dayGridEl ) this.dayGrid.isRigid = this.hasRigidRows() if (this.opt('weekNumbers')) { if (this.opt('weekNumbersWithinDays')) { this.dayGrid.cellWeekNumbersVisible = true this.colWeekNumbersVisible = false } else { this.dayGrid.cellWeekNumbersVisible = false this.colWeekNumbersVisible = true } } } destroy() { super.destroy() this.dayGrid.destroy() this.scroller.destroy() } // Generates the DayGrid object this view needs. Draws from this.dayGridClass instantiateDayGrid(headerContainerEl: HTMLElement, el: HTMLElement) { // generate a subclass on the fly with BasicView-specific behavior // TODO: cache this subclass let subclass: any = makeDayGridSubclass(this.dayGridClass) return new subclass(this.context, headerContainerEl, el) } render(props: DateComponentProps) { super.render(props) this.dayGrid.receiveProps(props) } renderDates(dateProfile: DateProfile) { this.dayGrid.breakOnWeeks = /year|month|week/.test( this.props.dateProfile.currentRangeUnit ) super.renderDates(dateProfile) } // Builds the HTML skeleton for the view. // The day-grid component will render inside of a container defined by this HTML. renderSkeletonHtml() { let { theme } = this return '' + '' + (this.opt('columnHeader') ? '' + '' + '' + '' + '' : '' ) + '' + '' + '' + '' + '' + '
 
' } // Generates an HTML attribute string for setting the width of the week number column, if it is known weekNumberStyleAttr() { if (this.weekNumberWidth != null) { return 'style="width:' + this.weekNumberWidth + 'px"' } return '' } // Determines whether each row should have a constant height hasRigidRows() { let eventLimit = this.opt('eventLimit') return eventLimit && typeof eventLimit !== 'number' } /* Dimensions ------------------------------------------------------------------------------------------------------------------*/ // Refreshes the horizontal dimensions of the view updateHeight(totalHeight, isAuto, isResize) { super.updateHeight(totalHeight, isAuto, isResize) let { dayGrid } = this let eventLimit = this.opt('eventLimit') let headRowEl = dayGrid.headerContainerEl ? dayGrid.headerContainerEl.querySelector('.fc-row') as HTMLElement : null let scrollerHeight let scrollbarWidths // hack to give the view some height prior to dayGrid's columns being rendered // TODO: separate setting height from scroller VS dayGrid. if (!dayGrid.rowEls) { if (!isAuto) { scrollerHeight = this.computeScrollerHeight(totalHeight) this.scroller.setHeight(scrollerHeight) } return } if (this.colWeekNumbersVisible) { // Make sure all week number cells running down the side have the same width. // Record the width for cells created later. this.weekNumberWidth = matchCellWidths( findElements(this.el, '.fc-week-number') ) } // reset all heights to be natural this.scroller.clear() if (headRowEl) { uncompensateScroll(headRowEl) } dayGrid.removeSegPopover() // kill the "more" popover if displayed // is the event limit a constant level number? if (eventLimit && typeof eventLimit === 'number') { dayGrid.limitRows(eventLimit) // limit the levels first so the height can redistribute after } // distribute the height to the rows // (totalHeight is a "recommended" value if isAuto) scrollerHeight = this.computeScrollerHeight(totalHeight) this.setGridHeight(scrollerHeight, isAuto) // is the event limit dynamically calculated? if (eventLimit && typeof eventLimit !== 'number') { dayGrid.limitRows(eventLimit) // limit the levels after the grid's row heights have been set } if (!isAuto) { // should we force dimensions of the scroll container? this.scroller.setHeight(scrollerHeight) scrollbarWidths = this.scroller.getScrollbarWidths() if (scrollbarWidths.left || scrollbarWidths.right) { // using scrollbars? if (headRowEl) { compensateScroll(headRowEl, scrollbarWidths) } // doing the scrollbar compensation might have created text overflow which created more height. redo scrollerHeight = this.computeScrollerHeight(totalHeight) this.scroller.setHeight(scrollerHeight) } // guarantees the same scrollbar widths this.scroller.lockOverflow(scrollbarWidths) } } updateSize(isResize: boolean) { this.dayGrid.updateSize(isResize) } // given a desired total height of the view, returns what the height of the scroller should be computeScrollerHeight(totalHeight) { return totalHeight - subtractInnerElHeight(this.el, this.scroller.el) // everything that's NOT the scroller } // Sets the height of just the DayGrid component in this view setGridHeight(height, isAuto) { if (isAuto) { undistributeHeight(this.dayGrid.rowEls) // let the rows be their natural height with no expanding } else { distributeHeight(this.dayGrid.rowEls, height, true) // true = compensate for height-hogging rows } } /* Scroll ------------------------------------------------------------------------------------------------------------------*/ computeInitialDateScroll() { return { top: 0 } } queryDateScroll() { return { top: this.scroller.getScrollTop() } } applyDateScroll(scroll) { if (scroll.top !== undefined) { this.scroller.setScrollTop(scroll.top) } } } BasicView.dateProfileGeneratorClass = BasicViewDateProfileGenerator BasicView.prototype.dayGridClass = DayGrid // customize the rendering behavior of BasicView's dayGrid function makeDayGridSubclass(SuperClass) { return class SubClass extends SuperClass { // Generates the HTML that will go before the day-of week header cells renderHeadIntroHtml(this: DayGrid) { let { view, theme } = this if ((view as BasicView).colWeekNumbersVisible) { return '' + '' + '' + // needed for matchCellWidths htmlEscape(this.opt('weekLabel')) + '' + '' } return '' } // Generates the HTML that will go before content-skeleton cells that display the day/week numbers renderNumberIntroHtml(this: DayGrid, row) { let { view, dateEnv } = this let weekStart = this.getCellDate(row, 0) if ((view as BasicView).colWeekNumbersVisible) { return '' + '' + buildGotoAnchorHtml( // aside from link, important for matchCellWidths view, { date: weekStart, type: 'week', forceOff: this.colCnt === 1 }, dateEnv.format(weekStart, WEEK_NUM_FORMAT) // inner HTML ) + '' } return '' } // Generates the HTML that goes before the day bg cells for each day-row renderBgIntroHtml(this: DayGrid) { let { view, theme } = this if ((view as BasicView).colWeekNumbersVisible) { return '' } return '' } // Generates the HTML that goes before every other type of row generated by DayGrid. // Affects mirror-skeleton and highlight-skeleton rows. renderIntroHtml(this: DayGrid) { let { view } = this if ((view as BasicView).colWeekNumbersVisible) { return '' } return '' } getIsNumbersVisible(this: DayGrid) { let { view } = this return DayGrid.prototype.getIsNumbersVisible.apply(this, arguments) || (view as BasicView).colWeekNumbersVisible } } }