| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030 |
- import { attrsToStr, htmlEscape } from '../util/html'
- import { elementClosest } from '../util/dom-manip'
- import { default as Component, RenderForceFlags } from './Component'
- import Calendar from '../Calendar'
- import View from '../View'
- import { DateProfile } from '../DateProfileGenerator'
- import { DateMarker, DAY_IDS, addDays, startOfDay, diffDays, diffWholeDays } from '../datelib/marker'
- import { Duration, createDuration, asRoughMs } from '../datelib/duration'
- import { DateSpan } from '../reducers/date-span'
- import UnzonedRange from '../models/UnzonedRange'
- import { EventRenderRange, sliceEventStore } from '../reducers/event-rendering'
- import { EventStore } from '../reducers/event-store'
- import { BusinessHourDef, buildBusinessHourEventStore } from '../reducers/business-hours'
- import { DateEnv } from '../datelib/env'
- import Theme from '../theme/Theme'
- import { EventInteractionState } from '../reducers/event-interaction'
- import { assignTo } from '../util/object'
- import browserContext from '../common/browser-context'
- import { Hit } from '../interactions/HitDragging'
- export interface DateComponentRenderState {
- dateProfile: DateProfile
- eventStore: EventStore
- selection: DateSpan | null
- dragState: EventInteractionState | null
- eventResizeState: EventInteractionState | null
- businessHoursDef: BusinessHourDef // BusinessHourDef's `false` is the empty state
- selectedEventInstanceId: string | null
- }
- // NOTE: for fg-events, eventRange.range is NOT sliced,
- // thus, we need isStart/isEnd
- export interface Seg {
- component: DateComponent
- isStart: boolean
- isEnd: boolean
- eventRange?: EventRenderRange
- el?: HTMLElement
- [otherProp: string]: any
- }
- export type DateComponentHash = { [id: string]: DateComponent }
- let uid = 0
- export default abstract class DateComponent extends Component {
- // self-config, overridable by subclasses
- isInteractable: boolean = false
- segSelector: string = '.fc-event-container > *' // what constitutes an event element?
- // if defined, holds the unit identified (ex: "year" or "month") that determines the level of granularity
- // of the date areas. if not defined, assumes to be day and time granularity.
- // TODO: port isTimeScale into same system?
- largeUnit: any
- eventRendererClass: any
- helperRendererClass: any
- fillRendererClass: any
- uid: any
- childrenByUid: any
- isRTL: boolean = false // frequently accessed options
- nextDayThreshold: Duration // "
- view: View
- eventRenderer: any
- helperRenderer: any
- fillRenderer: any
- hasAllDayBusinessHours: boolean = false // TODO: unify with largeUnit and isTimeScale?
- renderedFlags: any = {}
- dirtySizeFlags: any = {}
- dateProfile: DateProfile = null
- businessHoursDef: BusinessHourDef = false
- selection: DateSpan = null
- eventStore: EventStore = null
- dragState: EventInteractionState = null
- eventResizeState: EventInteractionState = null
- interactingEventDefId: string = null
- selectedEventInstanceId: string = null
- constructor(_view, _options?) {
- super()
- // hack to set options prior to the this.opt calls
- this.view = _view || this
- if (_options) {
- this['options'] = _options
- }
- this.uid = String(uid++)
- this.childrenByUid = {}
- this.nextDayThreshold = createDuration(this.opt('nextDayThreshold'))
- this.isRTL = this.opt('isRTL')
- if (this.fillRendererClass) {
- this.fillRenderer = new this.fillRendererClass(this)
- }
- if (this.eventRendererClass) { // fillRenderer is optional -----v
- this.eventRenderer = new this.eventRendererClass(this, this.fillRenderer)
- }
- if (this.helperRendererClass && this.eventRenderer) {
- this.helperRenderer = new this.helperRendererClass(this, this.eventRenderer)
- }
- }
- addChild(child) {
- if (!this.childrenByUid[child.uid]) {
- this.childrenByUid[child.uid] = child
- return true
- }
- return false
- }
- removeChild(child) {
- if (this.childrenByUid[child.uid]) {
- delete this.childrenByUid[child.uid]
- return true
- }
- return false
- }
- updateSize(totalHeight, isAuto, force) {
- let flags = this.dirtySizeFlags
- if (force || flags.skeleton || flags.dates || flags.events) {
- // sort of the catch-all sizing
- // anything that might cause dimension changes
- this.updateBaseSize(totalHeight, isAuto)
- this.buildCoordCaches()
- }
- if (force || flags.businessHours) {
- this.computeBusinessHoursSize()
- }
- // don't worry about updating the resize of the helper
- if (force || flags.selection || flags.drag || flags.eventResize) {
- this.computeHighlightSize()
- }
- if (force || flags.drag || flags.eventResize) {
- this.computeHelperSize()
- }
- if (force || flags.events) {
- this.computeEventsSize()
- }
- if (force || flags.businessHours) {
- this.assignBusinessHoursSize()
- }
- if (force || flags.selection || flags.drag || flags.eventResize) {
- this.assignHighlightSize()
- }
- if (force || flags.drag || flags.eventResize) {
- this.assignHelperSize()
- }
- if (force || flags.events) {
- this.assignEventsSize()
- }
- this.dirtySizeFlags = {}
- this.callChildren('updateSize', arguments) // always do this at end?
- }
- updateBaseSize(totalHeight, isAuto) {
- }
- buildCoordCaches() {
- }
- queryHit(leftOffset, topOffset): Hit {
- return null // this should be abstract
- }
- bindGlobalHandlers() {
- if (this.isInteractable) {
- browserContext.registerComponent(this)
- }
- }
- unbindGlobalHandlers() {
- if (this.isInteractable) {
- browserContext.unregisterComponent(this)
- }
- }
- // Options
- // -----------------------------------------------------------------------------------------------------------------
- opt(name) {
- return this.view.options[name]
- }
- // Triggering
- // -----------------------------------------------------------------------------------------------------------------
- publiclyTrigger(name, args) {
- let calendar = this.getCalendar()
- return calendar.publiclyTrigger(name, args)
- }
- publiclyTriggerAfterSizing(name, args) {
- let calendar = this.getCalendar()
- return calendar.publiclyTriggerAfterSizing(name, args)
- }
- hasPublicHandlers(name) {
- let calendar = this.getCalendar()
- return calendar.hasPublicHandlers(name)
- }
- triggerRenderedSegs(segs: Seg[]) {
- if (this.hasPublicHandlers('eventAfterRender')) {
- for (let seg of segs) {
- this.publiclyTriggerAfterSizing('eventAfterRender', [
- {
- event: seg.eventRange, // what to do here?
- el: seg.el,
- view: this
- }
- ])
- }
- }
- }
- triggerWillRemoveSegs(segs: Seg[]) {
- if (this.hasPublicHandlers('eventDestroy')) {
- for (let seg of segs) {
- this.publiclyTrigger('eventDestroy', [
- {
- event: seg.eventRange, // what to do here?
- el: seg.el,
- view: this
- }
- ])
- }
- }
- }
- // Root Rendering
- // -----------------------------------------------------------------------------------------------------------------
- render(renderState: DateComponentRenderState, forceFlags: RenderForceFlags) {
- let { renderedFlags } = this
- let dirtyFlags = {
- skeleton: false,
- dates: renderState.dateProfile !== this.dateProfile,
- businessHours: renderState.businessHoursDef !== this.businessHoursDef,
- selection: renderState.selection !== this.selection,
- events: renderState.eventStore !== this.eventStore,
- selectedEvent: renderState.selectedEventInstanceId !== this.selectedEventInstanceId,
- drag: renderState.dragState !== this.dragState,
- eventResize: renderState.eventResizeState !== this.eventResizeState
- }
- assignTo(dirtyFlags, forceFlags)
- if (forceFlags === true) {
- // everthing must be marked as dirty when doing a forced resize
- for (let name in dirtyFlags) {
- dirtyFlags[name] = true
- }
- } else {
- // mark things that are still not rendered as dirty
- for (let name in dirtyFlags) {
- if (!renderedFlags[name]) {
- dirtyFlags[name] = true
- }
- }
- // when the dates are dirty, mark nearly everything else as dirty too
- if (dirtyFlags.dates) {
- for (let name in dirtyFlags) {
- if (name !== 'skeleton') {
- forceFlags = true
- }
- }
- }
- }
- this.unrender(dirtyFlags) // only unrender dirty things
- assignTo(this, renderState) // assign incoming state to local state
- this.renderByFlag(renderState, dirtyFlags) // only render dirty things
- this.renderChildren(renderState, forceFlags)
- }
- renderByFlag(renderState: DateComponentRenderState, flags) {
- let { renderedFlags, dirtySizeFlags } = this
- if (flags.skeleton) {
- this.renderSkeleton()
- renderedFlags.skeleton = true
- dirtySizeFlags.skeleton = true
- }
- if (flags.dates && renderState.dateProfile) {
- this.renderDates() // pass in dateProfile too?
- renderedFlags.dates = true
- dirtySizeFlags.dates = true
- }
- if (flags.businessHours && renderState.businessHoursDef) {
- this.renderBusinessHours(renderState.businessHoursDef)
- renderedFlags.businessHours = true
- dirtySizeFlags.businessHours = true
- }
- if (flags.selection && renderState.selection) {
- this.renderSelection(renderState.selection)
- renderedFlags.selection = true
- dirtySizeFlags.selection = true
- }
- if (flags.events && renderState.eventStore) {
- this.renderEvents(renderState.eventStore)
- renderedFlags.events = true
- dirtySizeFlags.events = true
- }
- if (flags.selectedEvent) {
- this.selectEventsByInstanceId(renderState.selectedEventInstanceId)
- renderedFlags.selectedEvent = true
- dirtySizeFlags.selectedEvent = true
- }
- if (flags.drag && renderState.dragState) {
- this.renderDragState(renderState.dragState)
- renderedFlags.drag = true
- dirtySizeFlags.drag = true
- }
- if (flags.eventResize && renderState.eventResizeState) {
- this.renderEventResizeState(renderState.eventResizeState)
- renderedFlags.eventResize = true
- dirtySizeFlags.eventResize = true
- }
- }
- unrender(flags?: any) {
- let { renderedFlags } = this
- if ((!flags || flags.eventResize) && renderedFlags.eventResize) {
- this.unrenderEventResizeState()
- renderedFlags.eventResize = false
- }
- if ((!flags || flags.drag) && renderedFlags.drag) {
- this.unrenderDragState()
- renderedFlags.drag = false
- }
- if ((!flags || flags.selectedEvent) && renderedFlags.selectedEvent) {
- this.unselectAllEvents()
- renderedFlags.selectedEvent = false
- }
- if ((!flags || flags.events) && renderedFlags.events) {
- this.unrenderEvents()
- renderedFlags.events = false
- }
- if ((!flags || flags.selection) && renderedFlags.selection) {
- this.unrenderSelection()
- renderedFlags.selection = false
- }
- if ((!flags || flags.businessHours) && renderedFlags.businessHours) {
- this.unrenderBusinessHours()
- renderedFlags.businessHours = false
- }
- if ((!flags || flags.dates) && renderedFlags.dates) {
- this.unrenderDates()
- renderedFlags.dates = false
- }
- if ((!flags || flags.skeleton) && renderedFlags.skeleton) {
- this.unrenderSkeleton()
- renderedFlags.skeleton = false
- }
- }
- renderChildren(renderState: DateComponentRenderState, forceFlags: RenderForceFlags) {
- this.callChildren('render', arguments)
- }
- removeElement() {
- this.unrender()
- this.dirtySizeFlags = {}
- super.removeElement()
- }
- // Skeleton
- // -----------------------------------------------------------------------------------------------------------------
- renderSkeleton() {
- // subclasses should implement
- }
- unrenderSkeleton() {
- // subclasses should implement
- }
- // Date
- // -----------------------------------------------------------------------------------------------------------------
- // date-cell content only
- renderDates() {
- // subclasses should implement
- }
- // date-cell content only
- unrenderDates() {
- // subclasses should override
- }
- // Now-Indicator
- // -----------------------------------------------------------------------------------------------------------------
- // Returns a string unit, like 'second' or 'minute' that defined how often the current time indicator
- // should be refreshed. If something falsy is returned, no time indicator is rendered at all.
- getNowIndicatorUnit() {
- // subclasses should implement
- }
- // Renders a current time indicator at the given datetime
- renderNowIndicator(date) {
- this.callChildren('renderNowIndicator', arguments)
- }
- // Undoes the rendering actions from renderNowIndicator
- unrenderNowIndicator() {
- this.callChildren('unrenderNowIndicator', arguments)
- }
- // Business Hours
- // ---------------------------------------------------------------------------------------------------------------
- renderBusinessHours(businessHoursDef: BusinessHourDef) {
- if (this.fillRenderer) {
- this.fillRenderer.renderSegs(
- 'businessHours',
- this.eventStoreToSegs(
- buildBusinessHourEventStore(
- businessHoursDef,
- this.hasAllDayBusinessHours,
- this.dateProfile.activeUnzonedRange,
- this.getCalendar()
- )
- ),
- {
- getClasses(seg) {
- return [ 'fc-bgevent' ].concat(seg.eventRange.eventDef.className)
- }
- }
- )
- }
- }
- // Unrenders previously-rendered business-hours
- unrenderBusinessHours() {
- if (this.fillRenderer) {
- this.fillRenderer.unrender('businessHours')
- }
- }
- computeBusinessHoursSize() {
- if (this.fillRenderer) {
- this.fillRenderer.computeSize('businessHours')
- }
- }
- assignBusinessHoursSize() {
- if (this.fillRenderer) {
- this.fillRenderer.assignSize('businessHours')
- }
- }
- // Event Displaying
- // -----------------------------------------------------------------------------------------------------------------
- renderEvents(eventStore: EventStore) {
- if (this.eventRenderer) {
- this.eventRenderer.rangeUpdated() // poorly named now
- this.eventRenderer.renderSegs(
- this.eventStoreToSegs(eventStore)
- )
- this.triggerRenderedSegs(this.eventRenderer.getSegs())
- }
- }
- unrenderEvents() {
- if (this.eventRenderer) {
- this.triggerWillRemoveSegs(this.eventRenderer.getSegs())
- this.eventRenderer.unrender()
- }
- }
- computeEventsSize() {
- if (this.eventRenderer) {
- this.eventRenderer.computeFgSize()
- }
- }
- assignEventsSize() {
- if (this.eventRenderer) {
- this.eventRenderer.assignFgSize()
- }
- }
- // Drag-n-Drop Rendering (for both events and external elements)
- // ---------------------------------------------------------------------------------------------------------------
- renderDragState(dragState: EventInteractionState) {
- if (dragState.origSeg) {
- this.hideRelatedSegs(dragState.origSeg)
- }
- this.renderDrag(dragState.eventStore, dragState.origSeg, dragState.willCreateEvent)
- }
- unrenderDragState() {
- if (this.dragState.origSeg) {
- this.showRelatedSegs(this.dragState.origSeg)
- }
- this.unrenderDrag()
- }
- // Renders a visual indication of a event or external-element drag over the given drop zone.
- // If an external-element, seg will be `null`.
- // Must return elements used for any mock events.
- renderDrag(eventStore: EventStore, origSeg, willCreateEvent) {
- // subclasses can implement
- }
- // Unrenders a visual indication of an event or external-element being dragged.
- unrenderDrag() {
- // subclasses can implement
- }
- // Event Resizing
- // ---------------------------------------------------------------------------------------------------------------
- renderEventResizeState(eventResizeState: EventInteractionState) {
- if (eventResizeState.origSeg) {
- this.hideRelatedSegs(eventResizeState.origSeg)
- }
- this.renderEventResize(eventResizeState.eventStore, eventResizeState.origSeg)
- }
- unrenderEventResizeState() {
- if (this.eventResizeState.origSeg) {
- this.showRelatedSegs(this.eventResizeState.origSeg)
- }
- this.unrenderEventResize()
- }
- // Renders a visual indication of an event being resized.
- renderEventResize(eventStore: EventStore, origSeg: any) {
- // subclasses can implement
- }
- // Unrenders a visual indication of an event being resized.
- unrenderEventResize() {
- // subclasses can implement
- }
- // Seg Utils
- // -----------------------------------------------------------------------------------------------------------------
- hideRelatedSegs(targetSeg: Seg) {
- this.getRelatedSegs(targetSeg).forEach(function(seg) {
- seg.el.style.visibility = 'hidden'
- })
- }
- showRelatedSegs(targetSeg: Seg) {
- this.getRelatedSegs(targetSeg).forEach(function(seg) {
- seg.el.style.visibility = ''
- })
- }
- getRelatedSegs(targetSeg: Seg) {
- let targetEventDef = targetSeg.eventRange.eventDef
- return this.getAllEventSegs().filter(function(seg: Seg) {
- let segEventDef = seg.eventRange.eventDef
- return segEventDef.defId === targetEventDef.defId || // include defId as well???
- segEventDef.groupId && segEventDef.groupId === targetEventDef.groupId
- })
- }
- getAllEventSegs() {
- if (this.eventRenderer) {
- return this.eventRenderer.getSegs()
- } else {
- return []
- }
- }
- // Event Instance Selection (aka long-touch focus)
- // -----------------------------------------------------------------------------------------------------------------
- // TODO: show/hide according to groupId?
- selectEventsByInstanceId(instanceId) {
- this.getAllEventSegs().forEach(function(seg) {
- if (
- seg.eventRange.eventInstance.instanceId === instanceId &&
- seg.el // necessary?
- ) {
- seg.el.classList.add('fc-selected')
- }
- })
- }
- unselectAllEvents() {
- this.getAllEventSegs().forEach(function(seg) {
- if (seg.el) { // necessary?
- seg.el.classList.remove('fc-selected')
- }
- })
- }
- // EXTERNAL Drag-n-Drop
- // ---------------------------------------------------------------------------------------------------------------
- // Doesn't need to implement a response, but must pass to children
- handlExternalDragStart(ev, el, skipBinding) {
- this.callChildren('handlExternalDragStart', arguments)
- }
- handleExternalDragMove(ev) {
- this.callChildren('handleExternalDragMove', arguments)
- }
- handleExternalDragStop(ev) {
- this.callChildren('handleExternalDragStop', arguments)
- }
- // DateSpan
- // ---------------------------------------------------------------------------------------------------------------
- // Renders a visual indication of the selection
- renderSelection(selection: DateSpan) {
- this.renderHighlightSegs(this.selectionToSegs(selection))
- }
- // Unrenders a visual indication of selection
- unrenderSelection() {
- this.unrenderHighlight()
- }
- // Highlight
- // ---------------------------------------------------------------------------------------------------------------
- // Renders an emphasis on the given date range. Given a span (unzoned start/end and other misc data)
- renderHighlightSegs(segs) {
- if (this.fillRenderer) {
- this.fillRenderer.renderSegs('highlight', segs, {
- getClasses() {
- return [ 'fc-highlight' ]
- }
- })
- }
- }
- // Unrenders the emphasis on a date range
- unrenderHighlight() {
- if (this.fillRenderer) {
- this.fillRenderer.unrender('highlight')
- }
- }
- computeHighlightSize() {
- if (this.fillRenderer) {
- this.fillRenderer.computeSize('highlight')
- }
- }
- assignHighlightSize() {
- if (this.fillRenderer) {
- this.fillRenderer.assignSize('highlight')
- }
- }
- /*
- ------------------------------------------------------------------------------------------------------------------*/
- computeHelperSize() {
- if (this.helperRenderer) {
- this.helperRenderer.computeSize()
- }
- }
- assignHelperSize() {
- if (this.helperRenderer) {
- this.helperRenderer.assignSize()
- }
- }
- /* Converting selection/eventRanges -> segs
- ------------------------------------------------------------------------------------------------------------------*/
- eventStoreToSegs(eventStore: EventStore): Seg[] {
- let activeUnzonedRange = this.dateProfile.activeUnzonedRange
- let eventRenderRanges = sliceEventStore(eventStore, activeUnzonedRange)
- let allSegs: Seg[] = []
- for (let eventRenderRange of eventRenderRanges) {
- let segs = this.rangeToSegs(eventRenderRange.range, eventRenderRange.eventDef.isAllDay)
- for (let seg of segs) {
- seg.eventRange = eventRenderRange
- allSegs.push(seg)
- }
- }
- return allSegs
- }
- selectionToSegs(selection: DateSpan): Seg[] {
- return this.rangeToSegs(selection.range, selection.isAllDay)
- }
- // must implement if want to use many of the rendering utils
- rangeToSegs(range: UnzonedRange, isAllDay: boolean): Seg[] {
- return []
- }
- // Utils
- // ---------------------------------------------------------------------------------------------------------------
- callChildren(methodName, args) {
- this.iterChildren(function(child) {
- child[methodName].apply(child, args)
- })
- }
- iterChildren(func) {
- let childrenByUid = this.childrenByUid
- let uid
- for (uid in childrenByUid) {
- func(childrenByUid[uid])
- }
- }
- getCalendar(): Calendar {
- return this.view.calendar
- }
- getDateEnv(): DateEnv {
- return this.getCalendar().dateEnv
- }
- getTheme(): Theme {
- return this.getCalendar().theme
- }
- // Generates HTML for an anchor to another view into the calendar.
- // Will either generate an <a> tag or a non-clickable <span> tag, depending on enabled settings.
- // `gotoOptions` can either be a date input, or an object with the form:
- // { date, type, forceOff }
- // `type` is a view-type like "day" or "week". default value is "day".
- // `attrs` and `innerHtml` are use to generate the rest of the HTML tag.
- buildGotoAnchorHtml(gotoOptions, attrs, innerHtml) {
- let dateEnv = this.getDateEnv()
- let date
- let type
- let forceOff
- let finalOptions
- if (gotoOptions instanceof Date || typeof gotoOptions !== 'object') {
- date = gotoOptions // a single date-like input
- } else {
- date = gotoOptions.date
- type = gotoOptions.type
- forceOff = gotoOptions.forceOff
- }
- date = dateEnv.createMarker(date) // if a string, parse it
- finalOptions = { // for serialization into the link
- date: dateEnv.formatIso(date, { omitTime: true }),
- type: type || 'day'
- }
- if (typeof attrs === 'string') {
- innerHtml = attrs
- attrs = null
- }
- attrs = attrs ? ' ' + attrsToStr(attrs) : '' // will have a leading space
- innerHtml = innerHtml || ''
- if (!forceOff && this.opt('navLinks')) {
- return '<a' + attrs +
- ' data-goto="' + htmlEscape(JSON.stringify(finalOptions)) + '">' +
- innerHtml +
- '</a>'
- } else {
- return '<span' + attrs + '>' +
- innerHtml +
- '</span>'
- }
- }
- getAllDayHtml() {
- return this.opt('allDayHtml') || htmlEscape(this.opt('allDayText'))
- }
- // Computes HTML classNames for a single-day element
- getDayClasses(date: DateMarker, noThemeHighlight?) {
- let view = this.view
- let classes = []
- let todayStart: DateMarker
- let todayEnd: DateMarker
- if (!this.dateProfile.activeUnzonedRange.containsDate(date)) {
- classes.push('fc-disabled-day') // TODO: jQuery UI theme?
- } else {
- classes.push('fc-' + DAY_IDS[date.getUTCDay()])
- if (view.isDateInOtherMonth(date, this.dateProfile)) { // TODO: use DateComponent subclass somehow
- classes.push('fc-other-month')
- }
- todayStart = startOfDay(view.calendar.getNow())
- todayEnd = addDays(todayStart, 1)
- if (date < todayStart) {
- classes.push('fc-past')
- } else if (date >= todayEnd) {
- classes.push('fc-future')
- } else {
- classes.push('fc-today')
- if (noThemeHighlight !== true) {
- classes.push(view.calendar.theme.getClass('today'))
- }
- }
- }
- return classes
- }
- // Compute the number of the give units in the "current" range.
- // Won't go more precise than days.
- // Will return `0` if there's not a clean whole interval.
- currentRangeAs(unit) { // PLURAL :(
- let dateEnv = this.getDateEnv()
- let range = this.dateProfile.currentUnzonedRange
- let res = null
- if (unit === 'years') {
- res = dateEnv.diffWholeYears(range.start, range.end)
- } else if (unit === 'months') {
- res = dateEnv.diffWholeMonths(range.start, range.end)
- } else if (unit === 'weeks') {
- res = dateEnv.diffWholeMonths(range.start, range.end)
- } else if (unit === 'days') {
- res = diffWholeDays(range.start, range.end)
- }
- return res || 0
- }
- // Returns the date range of the full days the given range visually appears to occupy.
- // Returns a plain object with start/end, NOT an UnzonedRange!
- computeDayRange(unzonedRange): UnzonedRange {
- return computeDayRange(unzonedRange, this.nextDayThreshold)
- }
- // Does the given range visually appear to occupy more than one day?
- isMultiDayRange(unzonedRange) {
- let dayRange = this.computeDayRange(unzonedRange)
- return diffDays(dayRange.start, dayRange.end) > 1
- }
- isValidSegInteraction(evTarget: HTMLElement) {
- return !this.dragState &&
- !this.eventResizeState &&
- !elementClosest(evTarget, '.fc-helper')
- }
- isValidDateInteraction(evTarget: HTMLElement) {
- return !elementClosest(evTarget, this.segSelector) &&
- !elementClosest(evTarget, '.fc-more') && // a "more.." link
- !elementClosest(evTarget, 'a[data-goto]') // a clickable nav link
- }
- }
- function computeDayRange(unzonedRange: UnzonedRange, nextDayThreshold: Duration): UnzonedRange {
- let startDay: DateMarker = startOfDay(unzonedRange.start) // the beginning of the day the range starts
- let end: DateMarker = unzonedRange.end
- let endDay: DateMarker = startOfDay(end)
- let endTimeMS: number = end.valueOf() - endDay.valueOf() // # of milliseconds into `endDay`
- // If the end time is actually inclusively part of the next day and is equal to or
- // beyond the next day threshold, adjust the end to be the exclusive end of `endDay`.
- // Otherwise, leaving it as inclusive will cause it to exclude `endDay`.
- if (endTimeMS && endTimeMS >= asRoughMs(nextDayThreshold)) {
- endDay = addDays(endDay, 1)
- }
- // If end is within `startDay` but not past nextDayThreshold, assign the default duration of one day.
- if (endDay <= startDay) {
- endDay = addDays(startDay, 1)
- }
- return new UnzonedRange(startDay, endDay)
- }
|