| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446 |
- import { assignTo } from './util/object'
- import { parseFieldSpecs } from './util/misc'
- import DateProfileGenerator, { DateProfile } from './DateProfileGenerator'
- import { DateMarker, addMs } from './datelib/marker'
- import { createDuration, Duration } from './datelib/duration'
- import { default as EmitterMixin, EmitterInterface } from './common/EmitterMixin'
- import { ViewSpec } from './structs/view-spec'
- import { createElement } from './util/dom-manip'
- import { ComponentContext } from './component/Component'
- import DateComponent from './component/DateComponent'
- import { EventStore } from './structs/event-store'
- import { EventUiHash } from './component/event-ui'
- import { sliceEventStore, EventRenderRange } from './component/event-rendering'
- import { DateSpan } from './structs/date-span'
- import { EventInteractionState } from './interactions/event-interaction-state'
- import { memoizeRendering } from './component/memoized-rendering'
- export interface ViewProps {
- dateProfile: DateProfile
- businessHours: EventStore
- eventStore: EventStore
- eventUiBases: EventUiHash
- dateSelection: DateSpan | null
- eventSelection: string
- eventDrag: EventInteractionState | null
- eventResize: EventInteractionState | null
- }
- export default abstract class View extends DateComponent<ViewProps> {
- // config properties, initialized after class on prototype
- usesMinMaxTime: boolean // whether minTime/maxTime will affect the activeRange. Views must opt-in.
- dateProfileGeneratorClass: any // initialized after class. used by Calendar
- on: EmitterInterface['on']
- one: EmitterInterface['one']
- off: EmitterInterface['off']
- trigger: EmitterInterface['trigger']
- triggerWith: EmitterInterface['triggerWith']
- hasHandlers: EmitterInterface['hasHandlers']
- viewSpec: ViewSpec
- dateProfileGenerator: DateProfileGenerator
- type: string // subclass' view name (string). for the API
- title: string // the text that will be displayed in the header's title. SET BY CALLER for API
- queuedScroll: any
- eventOrderSpecs: any // criteria for ordering events when they have same date/time
- nextDayThreshold: Duration
- // now indicator
- isNowIndicatorRendered: boolean
- initialNowDate: DateMarker // result first getNow call
- initialNowQueriedMs: number // ms time the getNow was called
- nowIndicatorTimeoutID: any // for refresh timing of now indicator
- nowIndicatorIntervalID: any // "
- private renderDatesMem = memoizeRendering(this.renderDatesWrap, this.unrenderDatesWrap)
- private renderBusinessHoursMem = memoizeRendering(this.renderBusinessHours, this.unrenderBusinessHours, [ this.renderDatesMem ])
- private renderDateSelectionMem = memoizeRendering(this.renderDateSelectionWrap, this.unrenderDateSelectionWrap, [ this.renderDatesMem ])
- private renderEventsMem = memoizeRendering(this.renderEvents, this.unrenderEvents, [ this.renderDatesMem ])
- private renderEventSelectionMem = memoizeRendering(this.renderEventSelectionWrap, this.unrenderEventSelectionWrap, [ this.renderEventsMem ])
- private renderEventDragMem = memoizeRendering(this.renderEventDragWrap, this.unrenderEventDragWrap, [ this.renderDatesMem ])
- private renderEventResizeMem = memoizeRendering(this.renderEventResizeWrap, this.unrenderEventResizeWrap, [ this.renderDatesMem ])
- constructor(context: ComponentContext, viewSpec: ViewSpec, dateProfileGenerator: DateProfileGenerator, parentEl: HTMLElement) {
- super(
- {
- options: context.options,
- dateEnv: context.dateEnv,
- theme: context.theme,
- calendar: context.calendar
- },
- createElement('div', { className: 'fc-view fc-' + viewSpec.type + '-view' })
- )
- this.context.view = this // for when passing context to children
- this.viewSpec = viewSpec
- this.dateProfileGenerator = dateProfileGenerator
- this.type = viewSpec.type
- this.eventOrderSpecs = parseFieldSpecs(this.opt('eventOrder'))
- this.nextDayThreshold = createDuration(this.opt('nextDayThreshold'))
- parentEl.appendChild(this.el)
- this.initialize()
- }
- initialize() { // convenient for sublcasses
- }
- // Date Setting/Unsetting
- // -----------------------------------------------------------------------------------------------------------------
- get activeStart(): Date {
- return this.dateEnv.toDate(this.props.dateProfile.activeRange.start)
- }
- get activeEnd(): Date {
- return this.dateEnv.toDate(this.props.dateProfile.activeRange.end)
- }
- get currentStart(): Date {
- return this.dateEnv.toDate(this.props.dateProfile.currentRange.start)
- }
- get currentEnd(): Date {
- return this.dateEnv.toDate(this.props.dateProfile.currentRange.end)
- }
- // General Rendering
- // -----------------------------------------------------------------------------------------------------------------
- render(props: ViewProps) {
- this.renderDatesMem(props.dateProfile)
- this.renderBusinessHoursMem(props.businessHours)
- this.renderDateSelectionMem(props.dateSelection)
- this.renderEventsMem(props.eventStore)
- this.renderEventSelectionMem(props.eventSelection)
- this.renderEventDragMem(props.eventDrag)
- this.renderEventResizeMem(props.eventResize)
- }
- destroy() {
- super.destroy()
- this.renderDatesMem.unrender() // should unrender everything else
- }
- // Sizing
- // -----------------------------------------------------------------------------------------------------------------
- updateSize(isResize: boolean, viewHeight: number, isAuto: boolean) {
- let { calendar } = this
- if (isResize || calendar.isViewUpdated || calendar.isDatesUpdated || calendar.isEventsUpdated) {
- // sort of the catch-all sizing
- // anything that might cause dimension changes
- this.updateBaseSize(isResize, viewHeight, isAuto)
- }
- }
- updateBaseSize(isResize: boolean, viewHeight: number, isAuto: boolean) {
- }
- // Date Rendering
- // -----------------------------------------------------------------------------------------------------------------
- renderDatesWrap(dateProfile: DateProfile) {
- this.renderDates(dateProfile)
- this.addScroll({ isDateInit: true })
- this.startNowIndicator() // shouldn't render yet because updateSize will be called soon
- }
- unrenderDatesWrap() {
- this.stopNowIndicator()
- this.unrenderDates()
- }
- renderDates(dateProfile: DateProfile) {}
- unrenderDates() {}
- // Business Hours
- // -----------------------------------------------------------------------------------------------------------------
- renderBusinessHours(businessHours: EventStore) {}
- unrenderBusinessHours() {}
- // Date Selection
- // -----------------------------------------------------------------------------------------------------------------
- renderDateSelectionWrap(selection: DateSpan) {
- if (selection) {
- this.renderDateSelection(selection)
- }
- }
- unrenderDateSelectionWrap(selection: DateSpan) {
- if (selection) {
- this.unrenderDateSelection(selection)
- }
- }
- renderDateSelection(selection: DateSpan) {}
- unrenderDateSelection(selection: DateSpan) {}
- // Event Rendering
- // -----------------------------------------------------------------------------------------------------------------
- renderEvents(eventStore: EventStore) {}
- unrenderEvents() {}
- // util for subclasses
- sliceEvents(eventStore: EventStore, allDay: boolean): EventRenderRange[] {
- let { props } = this
- return sliceEventStore(
- eventStore,
- props.eventUiBases,
- props.dateProfile.activeRange,
- allDay ? this.nextDayThreshold : null
- ).fg
- }
- // Event Selection
- // -----------------------------------------------------------------------------------------------------------------
- renderEventSelectionWrap(instanceId: string) {
- if (instanceId) {
- this.renderEventSelection(instanceId)
- }
- }
- unrenderEventSelectionWrap(instanceId: string) {
- if (instanceId) {
- this.unrenderEventSelection(instanceId)
- }
- }
- renderEventSelection(instanceId: string) {}
- unrenderEventSelection(instanceId: string) {}
- // Event Drag
- // -----------------------------------------------------------------------------------------------------------------
- renderEventDragWrap(state: EventInteractionState) {
- if (state) {
- this.renderEventDrag(state)
- }
- }
- unrenderEventDragWrap(state: EventInteractionState) {
- if (state) {
- this.unrenderEventDrag(state)
- }
- }
- renderEventDrag(state: EventInteractionState) {}
- unrenderEventDrag(state: EventInteractionState) {}
- // Event Resize
- // -----------------------------------------------------------------------------------------------------------------
- renderEventResizeWrap(state: EventInteractionState) {
- if (state) {
- this.renderEventResize(state)
- }
- }
- unrenderEventResizeWrap(state: EventInteractionState) {
- if (state) {
- this.unrenderEventResize(state)
- }
- }
- renderEventResize(state: EventInteractionState) {}
- unrenderEventResize(state: EventInteractionState) {}
- /* Now Indicator
- ------------------------------------------------------------------------------------------------------------------*/
- // Immediately render the current time indicator and begins re-rendering it at an interval,
- // which is defined by this.getNowIndicatorUnit().
- // TODO: somehow do this for the current whole day's background too
- startNowIndicator() {
- let { dateEnv } = this
- let unit
- let update
- let delay // ms wait value
- if (this.opt('nowIndicator')) {
- unit = this.getNowIndicatorUnit()
- if (unit) {
- update = this.updateNowIndicator.bind(this)
- this.initialNowDate = this.calendar.getNow()
- this.initialNowQueriedMs = new Date().valueOf()
- // wait until the beginning of the next interval
- delay = dateEnv.add(
- dateEnv.startOf(this.initialNowDate, unit),
- createDuration(1, unit)
- ).valueOf() - this.initialNowDate.valueOf()
- // TODO: maybe always use setTimeout, waiting until start of next unit
- this.nowIndicatorTimeoutID = setTimeout(() => {
- this.nowIndicatorTimeoutID = null
- update()
- if (unit === 'second') {
- delay = 1000 // every second
- } else {
- delay = 1000 * 60 // otherwise, every minute
- }
- this.nowIndicatorIntervalID = setInterval(update, delay) // update every interval
- }, delay)
- }
- // rendering will be initiated in updateSize
- }
- }
- // rerenders the now indicator, computing the new current time from the amount of time that has passed
- // since the initial getNow call.
- updateNowIndicator() {
- if (
- this.props.dateProfile && // a way to determine if dates were rendered yet
- this.initialNowDate // activated before?
- ) {
- this.unrenderNowIndicator() // won't unrender if unnecessary
- this.renderNowIndicator(
- addMs(this.initialNowDate, new Date().valueOf() - this.initialNowQueriedMs)
- )
- this.isNowIndicatorRendered = true
- }
- }
- // Immediately unrenders the view's current time indicator and stops any re-rendering timers.
- // Won't cause side effects if indicator isn't rendered.
- stopNowIndicator() {
- if (this.isNowIndicatorRendered) {
- if (this.nowIndicatorTimeoutID) {
- clearTimeout(this.nowIndicatorTimeoutID)
- this.nowIndicatorTimeoutID = null
- }
- if (this.nowIndicatorIntervalID) {
- clearInterval(this.nowIndicatorIntervalID)
- this.nowIndicatorIntervalID = null
- }
- this.unrenderNowIndicator()
- this.isNowIndicatorRendered = false
- }
- }
- getNowIndicatorUnit() {
- // subclasses should implement
- }
- // Renders a current time indicator at the given datetime
- renderNowIndicator(date) {
- // SUBCLASSES MUST PASS TO CHILDREN!
- }
- // Undoes the rendering actions from renderNowIndicator
- unrenderNowIndicator() {
- // SUBCLASSES MUST PASS TO CHILDREN!
- }
- /* Scroller
- ------------------------------------------------------------------------------------------------------------------*/
- addScroll(scroll) {
- let queuedScroll = this.queuedScroll || (this.queuedScroll = {})
- assignTo(queuedScroll, scroll)
- }
- popScroll() {
- this.applyQueuedScroll()
- this.queuedScroll = null
- }
- applyQueuedScroll() {
- this.applyScroll(this.queuedScroll || {})
- }
- queryScroll() {
- let scroll = {} as any
- if (this.props.dateProfile) { // dates rendered yet?
- assignTo(scroll, this.queryDateScroll())
- }
- return scroll
- }
- applyScroll(scroll) {
- if (scroll.isDateInit) {
- delete scroll.isDateInit
- if (this.props.dateProfile) { // dates rendered yet?
- assignTo(scroll, this.computeInitialDateScroll())
- }
- }
- if (this.props.dateProfile) { // dates rendered yet?
- this.applyDateScroll(scroll)
- }
- }
- computeInitialDateScroll() {
- return {} // subclasses must implement
- }
- queryDateScroll() {
- return {} // subclasses must implement
- }
- applyDateScroll(scroll) {
- // subclasses must implement
- }
- }
- EmitterMixin.mixInto(View)
- View.prototype.usesMinMaxTime = false
- View.prototype.dateProfileGeneratorClass = DateProfileGenerator
|