Files
fullcalendar/src/common/Grid.js
T

321 lines
9.4 KiB
JavaScript

/* An abstract class comprised of a "grid" of cells that each represent a specific datetime
----------------------------------------------------------------------------------------------------------------------*/
function Grid(view) {
RowRenderer.call(this, view); // call the super-constructor
this.coordMap = new GridCoordMap(this);
}
Grid.prototype = createObject(RowRenderer.prototype); // declare the super-class
$.extend(Grid.prototype, {
el: null, // the containing element
coordMap: null, // a GridCoordMap that converts pixel values to datetimes
cellDuration: null, // a cell's duration. subclasses must assign this ASAP
// Renders the grid into the `el` element.
// Subclasses should override and call this super-method when done.
render: function() {
this.bindHandlers();
},
// Called when the grid's resources need to be cleaned up
destroy: function() {
// subclasses can implement
},
/* Coordinates & Cells
------------------------------------------------------------------------------------------------------------------*/
// Populates the given empty arrays with the y and x coordinates of the cells
buildCoords: function(rows, cols) {
// subclasses must implement
},
// Given a cell object, returns the date for that cell
getCellDate: function(cell) {
// subclasses must implement
},
// Given a cell object, returns the element that represents the cell's whole-day
getCellDayEl: function(cell) {
// subclasses must implement
},
// Converts a range with an inclusive `start` and an exclusive `end` into an array of segment objects
rangeToSegs: function(start, end) {
// subclasses must implement
},
/* Handlers
------------------------------------------------------------------------------------------------------------------*/
// Attach handlers to `this.el`, using bubbling to listen to all ancestors.
// We don't need to undo any of this in a "destroy" method, because the view will simply remove `this.el` from the
// DOM and jQuery will be smart enough to garbage collect the handlers.
bindHandlers: function() {
var _this = this;
this.el.on('mousedown', function(ev) {
if (
!$(ev.target).is('.fc-event-container *, .fc-more') && // not an an event element, or "more.." link
!$(ev.target).closest('.fc-popover').length // not on a popover (like the "more.." events one)
) {
_this.dayMousedown(ev);
}
});
this.bindSegHandlers(); // attach event-element-related handlers. in Grid.events.js
},
// Process a mousedown on an element that represents a day. For day clicking and selecting.
dayMousedown: function(ev) {
var _this = this;
var view = this.view;
var isSelectable = view.opt('selectable');
var dates = null; // the inclusive dates of the selection. will be null if no selection
var start; // the inclusive start of the selection
var end; // the *exclusive* end of the selection
var dayEl;
// this listener tracks a mousedown on a day element, and a subsequent drag.
// if the drag ends on the same day, it is a 'dayClick'.
// if 'selectable' is enabled, this listener also detects selections.
var dragListener = new DragListener(this.coordMap, {
//distance: 5, // needs more work if we want dayClick to fire correctly
scroll: view.opt('dragScroll'),
dragStart: function() {
view.unselect(); // since we could be rendering a new selection, we want to clear any old one
},
cellOver: function(cell, date) {
if (dragListener.origDate) { // click needs to have started on a cell
dayEl = _this.getCellDayEl(cell);
dates = [ date, dragListener.origDate ].sort(dateCompare);
start = dates[0];
end = dates[1].clone().add(_this.cellDuration);
if (isSelectable) {
_this.renderSelection(start, end);
}
}
},
cellOut: function(cell, date) {
dates = null;
_this.destroySelection();
},
listenStop: function(ev) {
if (dates) { // started and ended on a cell?
if (dates[0].isSame(dates[1])) {
view.trigger('dayClick', dayEl[0], start, ev);
}
if (isSelectable) {
// the selection will already have been rendered. just report it
view.reportSelection(start, end, ev);
}
}
}
});
dragListener.mousedown(ev); // start listening, which will eventually initiate a dragStart
},
/* Event Dragging
------------------------------------------------------------------------------------------------------------------*/
// Renders a visual indication of a event being dragged over the given date(s).
// `end` can be null, as well as `seg`. See View's documentation on renderDrag for more info.
// A returned value of `true` signals that a mock "helper" event has been rendered.
renderDrag: function(start, end, seg) {
// subclasses must implement
},
// Unrenders a visual indication of an event being dragged
destroyDrag: function() {
// subclasses must implement
},
/* Event Resizing
------------------------------------------------------------------------------------------------------------------*/
// Renders a visual indication of an event being resized.
// `start` and `end` are the updated dates of the event. `seg` is the original segment object involved in the drag.
renderResize: function(start, end, seg) {
// subclasses must implement
},
// Unrenders a visual indication of an event being resized.
destroyResize: function() {
// subclasses must implement
},
/* Event Helper
------------------------------------------------------------------------------------------------------------------*/
// Renders a mock event over the given date(s).
// `end` can be null, in which case the mock event that is rendered will have a null end time.
// `sourceSeg` is the internal segment object involved in the drag. If null, something external is dragging.
renderRangeHelper: function(start, end, sourceSeg) {
var view = this.view;
var fakeEvent;
// compute the end time if forced to do so (this is what EventManager does)
if (!end && view.opt('forceEventDuration')) {
end = view.calendar.getDefaultEventEnd(!start.hasTime(), start);
}
fakeEvent = sourceSeg ? createObject(sourceSeg.event) : {}; // mask the original event object if possible
fakeEvent.start = start;
fakeEvent.end = end;
fakeEvent.allDay = !(start.hasTime() || (end && end.hasTime())); // freshly compute allDay
// this extra className will be useful for differentiating real events from mock events in CSS
fakeEvent.className = (fakeEvent.className || []).concat('fc-helper');
// if something external is being dragged in, don't render a resizer
if (!sourceSeg) {
fakeEvent.editable = false;
}
this.renderHelper(fakeEvent, sourceSeg); // do the actual rendering
},
// Renders a mock event
renderHelper: function(event, sourceSeg) {
// subclasses must implement
},
// Unrenders a mock event
destroyHelper: function() {
// subclasses must implement
},
/* Selection
------------------------------------------------------------------------------------------------------------------*/
// Renders a visual indication of a selection. Will highlight by default but can be overridden by subclasses.
renderSelection: function(start, end) {
this.renderHighlight(start, end);
},
// Unrenders any visual indications of a selection. Will unrender a highlight by default.
destroySelection: function() {
this.destroyHighlight();
},
/* Highlight
------------------------------------------------------------------------------------------------------------------*/
// Puts visual emphasis on a certain date range
renderHighlight: function(start, end) {
// subclasses should implement
},
// Removes visual emphasis on a date range
destroyHighlight: function() {
// subclasses should implement
},
/* Generic rendering utilities for subclasses
------------------------------------------------------------------------------------------------------------------*/
// Renders a day-of-week header row
headHtml: function() {
return '' +
'<div class="fc-row ' + this.view.widgetHeaderClass + '">' +
'<table>' +
'<thead>' +
this.rowHtml('head') + // leverages RowRenderer
'</thead>' +
'</table>' +
'</div>';
},
// Used by the `headHtml` method, via RowRenderer, for rendering the HTML of a day-of-week header cell
headCellHtml: function(row, col, date) {
var view = this.view;
var calendar = view.calendar;
var colFormat = view.opt('columnFormat');
return '' +
'<th class="fc-day-header ' + view.widgetHeaderClass + ' fc-' + dayIDs[date.day()] + '">' +
htmlEscape(calendar.formatDate(date, colFormat)) +
'</th>';
},
// Renders the HTML for a single-day background cell
bgCellHtml: function(row, col, date) {
var view = this.view;
var classes = this.getDayClasses(date);
classes.unshift('fc-day', view.widgetContentClass);
return '<td class="' + classes.join(' ') + '" data-date="' + date.format() + '"></td>';
},
// Computes HTML classNames for a single-day cell
getDayClasses: function(date) {
var view = this.view;
var today = view.calendar.getNow().stripTime();
var classes = [ 'fc-' + dayIDs[date.day()] ];
if (
view.name === 'month' &&
date.month() != view.intervalStart.month()
) {
classes.push('fc-other-month');
}
if (date.isSame(today, 'day')) {
classes.push(
'fc-today',
view.highlightStateClass
);
}
else if (date < today) {
classes.push('fc-past');
}
else {
classes.push('fc-future');
}
return classes;
}
});