mirror of
https://github.com/wassname/fullcalendar.git
synced 2026-07-28 11:20:16 +08:00
321 lines
9.4 KiB
JavaScript
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;
|
|
}
|
|
|
|
});
|