import * as $ from 'jquery' import { matchCellWidths, uncompensateScroll, compensateScroll, subtractInnerElHeight, distributeHeight, undistributeHeight, htmlEscape } from '../util' import Scroller from '../common/Scroller' import View from '../View' import BasicViewDateProfileGenerator from './BasicViewDateProfileGenerator' import DayGrid from './DayGrid' /* 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 dateProfileGeneratorClass: any dayGridClass: any // class the dayGrid will be instantiated from (overridable by subclasses) scroller: Scroller dayGrid: any // the main subcomponent that does most of the heavy lifting weekNumberWidth: any // width of all the week-number cells running down the side constructor(calendar, viewSpec) { super(calendar, viewSpec) this.dayGrid = this.instantiateDayGrid() this.dayGrid.isRigid = this.hasRigidRows() if (this.opt('weekNumbers')) { if (this.opt('weekNumbersWithinDays')) { this.dayGrid.cellWeekNumbersVisible = true this.dayGrid.colWeekNumbersVisible = false } else { this.dayGrid.cellWeekNumbersVisible = false this.dayGrid.colWeekNumbersVisible = true } } this.addChild(this.dayGrid) this.scroller = new Scroller({ overflowX: 'hidden', overflowY: 'auto' }) } // Generates the DayGrid object this view needs. Draws from this.dayGridClass instantiateDayGrid() { // generate a subclass on the fly with BasicView-specific behavior // TODO: cache this subclass let subclass: any = makeDayGridSubclass(this.dayGridClass) return new subclass(this) } executeDateRender(dateProfile) { this.dayGrid.breakOnWeeks = /year|month|week/.test(dateProfile.currentRangeUnit) super.executeDateRender(dateProfile) } renderSkeleton() { let dayGridContainerEl let dayGridEl this.el.addClass('fc-basic-view').html(this.renderSkeletonHtml()) this.scroller.render() dayGridContainerEl = this.scroller.el dayGridContainerEl.classList.add('fc-day-grid-container') dayGridEl = $('
').appendTo(dayGridContainerEl) this.el.find('.fc-body > tr > td').append(dayGridContainerEl) this.dayGrid.headContainerEl = this.el.find('.fc-head-container') this.dayGrid.setElement(dayGridEl) } unrenderSkeleton() { this.dayGrid.removeElement() this.scroller.destroy() } // 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.calendar.theme 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 updateSize(totalHeight, isAuto, isResize) { let eventLimit = this.opt('eventLimit') let headRowEl = this.dayGrid.headContainerEl.find('.fc-row') 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 (!this.dayGrid.rowEls) { if (!isAuto) { scrollerHeight = this.computeScrollerHeight(totalHeight) this.scroller.setHeight(scrollerHeight) } return } super.updateSize(totalHeight, isAuto, isResize) if (this.dayGrid.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( this.el.find('.fc-week-number') ) } // reset all heights to be natural this.scroller.clear() uncompensateScroll(headRowEl) this.dayGrid.removeSegPopover() // kill the "more" popover if displayed // is the event limit a constant level number? if (eventLimit && typeof eventLimit === 'number') { this.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') { this.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? 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) } } // 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.prototype.dateProfileGeneratorClass = BasicViewDateProfileGenerator BasicView.prototype.dayGridClass = DayGrid // customize the rendering behavior of BasicView's dayGrid function makeDayGridSubclass(SuperClass) { return class SubClass extends SuperClass { colWeekNumbersVisible: boolean = false // display week numbers along the side? // Generates the HTML that will go before the day-of week header cells renderHeadIntroHtml() { let view = this.view if (this.colWeekNumbersVisible) { return '' + '' + '' + // needed for matchCellWidths htmlEscape(this.opt('weekNumberTitle')) + '' + '' } return '' } // Generates the HTML that will go before content-skeleton cells that display the day/week numbers renderNumberIntroHtml(row) { let view = this.view let weekStart = this.getCellDate(row, 0) if (this.colWeekNumbersVisible) { return '' + '' + view.buildGotoAnchorHtml( // aside from link, important for matchCellWidths { date: weekStart, type: 'week', forceOff: this.colCnt === 1 }, weekStart.format('w') // inner HTML ) + '' } return '' } // Generates the HTML that goes before the day bg cells for each day-row renderBgIntroHtml() { let view = this.view if (this.colWeekNumbersVisible) { return '' } return '' } // Generates the HTML that goes before every other type of row generated by DayGrid. // Affects helper-skeleton and highlight-skeleton rows. renderIntroHtml() { let view = this.view if (this.colWeekNumbersVisible) { return '' } return '' } getIsNumbersVisible() { return DayGrid.prototype.getIsNumbersVisible.apply(this, arguments) || this.colWeekNumbersVisible } } }