/* 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 '' + '
' + '' + '' + this.rowHtml('head') + // leverages RowRenderer '' + '
' + '
'; }, // 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 '' + '' + htmlEscape(calendar.formatDate(date, colFormat)) + ''; }, // 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 ''; }, // 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; } });