mirror of
https://github.com/wassname/fullcalendar.git
synced 2026-07-14 01:10:12 +08:00
305 lines
8.8 KiB
JavaScript
305 lines
8.8 KiB
JavaScript
|
|
/* A component that renders a grid of whole-days that runs horizontally. There can be multiple rows, one per week.
|
|
----------------------------------------------------------------------------------------------------------------------*/
|
|
|
|
function DayGrid(view) {
|
|
Grid.call(this, view); // call the super-constructor
|
|
}
|
|
|
|
|
|
DayGrid.prototype = createObject(Grid.prototype); // declare the super-class
|
|
$.extend(DayGrid.prototype, {
|
|
|
|
numbersVisible: false, // should render a row for day/week numbers? manually set by the view
|
|
cellDuration: moment.duration({ days: 1 }), // required for Grid.event.js. Each cell is always a single day
|
|
bottomCoordPadding: 0, // hack for extending the hit area for the last row of the coordinate grid
|
|
|
|
rowEls: null, // set of fake row elements
|
|
dayEls: null, // set of whole-day elements comprising the row's background
|
|
helperEls: null, // set of cell skeleton elements for rendering the mock event "helper"
|
|
highlightEls: null, // set of cell skeleton elements for rendering the highlight
|
|
|
|
|
|
// Renders the rows and columns into the component's `this.el`, which should already be assigned.
|
|
// isRigid determins whether the individual rows should ignore the contents and be a constant height.
|
|
// Relies on the view's colCnt and rowCnt. In the future, this component should probably be self-sufficient.
|
|
render: function(isRigid) {
|
|
var view = this.view;
|
|
var html = '';
|
|
var row;
|
|
|
|
for (row = 0; row < view.rowCnt; row++) {
|
|
html += this.dayRowHtml(row, isRigid);
|
|
}
|
|
this.el.html(html);
|
|
|
|
this.rowEls = this.el.find('.fc-row');
|
|
this.dayEls = this.el.find('.fc-day');
|
|
|
|
// run all the day cells through the dayRender callback
|
|
this.dayEls.each(function(i, node) {
|
|
var date = view.cellToDate(Math.floor(i / view.colCnt), i % view.colCnt);
|
|
view.trigger('dayRender', null, date, $(node));
|
|
});
|
|
|
|
Grid.prototype.render.call(this); // call the super-method
|
|
},
|
|
|
|
|
|
destroy: function() {
|
|
this.destroySegPopover();
|
|
},
|
|
|
|
|
|
// Generates the HTML for a single row. `row` is the row number.
|
|
dayRowHtml: function(row, isRigid) {
|
|
var view = this.view;
|
|
var classes = [ 'fc-row', 'fc-week', view.widgetContentClass ];
|
|
|
|
if (isRigid) {
|
|
classes.push('fc-rigid');
|
|
}
|
|
|
|
return '' +
|
|
'<div class="' + classes.join(' ') + '">' +
|
|
'<div class="fc-bg">' +
|
|
'<table>' +
|
|
this.rowHtml('day', row) + // leverages RowRenderer. calls dayCellHtml()
|
|
'</table>' +
|
|
'</div>' +
|
|
'<div class="fc-content-skeleton">' +
|
|
'<table>' +
|
|
(this.numbersVisible ?
|
|
'<thead>' +
|
|
this.rowHtml('number', row) + // leverages RowRenderer. View will define render method
|
|
'</thead>' :
|
|
''
|
|
) +
|
|
'</table>' +
|
|
'</div>' +
|
|
'</div>';
|
|
},
|
|
|
|
|
|
// Renders the HTML for a whole-day cell. Will eventually end up in the day-row's background.
|
|
// We go through a 'day' row type instead of just doing a 'bg' row type so that the View can do custom rendering
|
|
// specifically for whole-day rows, whereas a 'bg' might also be used for other purposes (TimeGrid bg for example).
|
|
dayCellHtml: function(row, col, date) {
|
|
return this.bgCellHtml(row, col, date);
|
|
},
|
|
|
|
|
|
/* Coordinates & Cells
|
|
------------------------------------------------------------------------------------------------------------------*/
|
|
|
|
|
|
// Populates the empty `rows` and `cols` arrays with coordinates of the cells. For CoordGrid.
|
|
buildCoords: function(rows, cols) {
|
|
var colCnt = this.view.colCnt;
|
|
var e, n, p;
|
|
|
|
this.dayEls.slice(0, colCnt).each(function(i, _e) { // iterate the first row of day elements
|
|
e = $(_e);
|
|
n = e.offset().left;
|
|
if (i) {
|
|
p[1] = n;
|
|
}
|
|
p = [ n ];
|
|
cols[i] = p;
|
|
});
|
|
p[1] = n + e.outerWidth();
|
|
|
|
this.rowEls.each(function(i, _e) {
|
|
e = $(_e);
|
|
n = e.offset().top;
|
|
if (i) {
|
|
p[1] = n;
|
|
}
|
|
p = [ n ];
|
|
rows[i] = p;
|
|
});
|
|
p[1] = n + e.outerHeight() + this.bottomCoordPadding; // hack to extend hit area of last row
|
|
},
|
|
|
|
|
|
// Converts a cell to a date
|
|
getCellDate: function(cell) {
|
|
return this.view.cellToDate(cell); // leverages the View's cell system
|
|
},
|
|
|
|
|
|
// Gets the whole-day element associated with the cell
|
|
getCellDayEl: function(cell) {
|
|
return this.dayEls.eq(cell.row * this.view.colCnt + cell.col);
|
|
},
|
|
|
|
|
|
// Converts a range with an inclusive `start` and an exclusive `end` into an array of segment objects
|
|
rangeToSegs: function(start, end) {
|
|
return this.view.rangeToSegments(start, end); // leverages the View's cell system
|
|
},
|
|
|
|
|
|
/* Event Drag Visualization
|
|
------------------------------------------------------------------------------------------------------------------*/
|
|
|
|
|
|
// Renders a visual indication of an event hovering 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) {
|
|
var opacity;
|
|
|
|
// always render a highlight underneath
|
|
this.renderHighlight(
|
|
start,
|
|
end || this.view.calendar.getDefaultEventEnd(true, start)
|
|
);
|
|
|
|
// if a segment from the same calendar but another component is being dragged, render a helper event
|
|
if (seg && !seg.el.closest(this.el).length) {
|
|
|
|
this.renderRangeHelper(start, end, seg);
|
|
|
|
opacity = this.view.opt('dragOpacity');
|
|
if (opacity !== undefined) {
|
|
this.helperEls.css('opacity', opacity);
|
|
}
|
|
|
|
return true; // a helper has been rendered
|
|
}
|
|
},
|
|
|
|
|
|
// Unrenders any visual indication of a hovering event
|
|
destroyDrag: function() {
|
|
this.destroyHighlight();
|
|
this.destroyHelper();
|
|
},
|
|
|
|
|
|
/* Event Resize Visualization
|
|
------------------------------------------------------------------------------------------------------------------*/
|
|
|
|
|
|
// Renders a visual indication of an event being resized
|
|
renderResize: function(start, end, seg) {
|
|
this.renderHighlight(start, end);
|
|
this.renderRangeHelper(start, end, seg);
|
|
},
|
|
|
|
|
|
// Unrenders a visual indication of an event being resized
|
|
destroyResize: function() {
|
|
this.destroyHighlight();
|
|
this.destroyHelper();
|
|
},
|
|
|
|
|
|
/* Event Helper
|
|
------------------------------------------------------------------------------------------------------------------*/
|
|
|
|
|
|
// Renders a mock "helper" event. `sourceSeg` is the associated internal segment object. It can be null.
|
|
renderHelper: function(event, sourceSeg) {
|
|
var helperNodes = [];
|
|
var rowStructs = this.renderEventRows([ event ]);
|
|
|
|
// inject each new event skeleton into each associated row
|
|
this.rowEls.each(function(row, rowNode) {
|
|
var rowEl = $(rowNode); // the .fc-row
|
|
var skeletonEl = $('<div class="fc-helper-skeleton"><table/></div>'); // will be absolutely positioned
|
|
var skeletonTop;
|
|
|
|
// If there is an original segment, match the top position. Otherwise, put it at the row's top level
|
|
if (sourceSeg && sourceSeg.row === row) {
|
|
skeletonTop = sourceSeg.el.position().top;
|
|
}
|
|
else {
|
|
skeletonTop = rowEl.find('.fc-content-skeleton tbody').position().top;
|
|
}
|
|
|
|
skeletonEl.css('top', skeletonTop)
|
|
.find('table')
|
|
.append(rowStructs[row].tbodyEl);
|
|
|
|
rowEl.append(skeletonEl);
|
|
helperNodes.push(skeletonEl[0]);
|
|
});
|
|
|
|
this.helperEls = $(helperNodes); // array -> jQuery set
|
|
},
|
|
|
|
|
|
// Unrenders any visual indication of a mock helper event
|
|
destroyHelper: function() {
|
|
if (this.helperEls) {
|
|
this.helperEls.remove();
|
|
this.helperEls = null;
|
|
}
|
|
},
|
|
|
|
|
|
/* Highlighting
|
|
------------------------------------------------------------------------------------------------------------------*/
|
|
|
|
|
|
// Renders an emphasis on the given date range. `start` is an inclusive, `end` is exclusive.
|
|
renderHighlight: function(start, end) {
|
|
var segs = this.rangeToSegs(start, end);
|
|
var highlightNodes = [];
|
|
var i, seg;
|
|
var el;
|
|
|
|
// build an event skeleton for each row that needs it
|
|
for (i = 0; i < segs.length; i++) {
|
|
seg = segs[i];
|
|
el = $(
|
|
this.highlightSkeletonHtml(seg.leftCol, seg.rightCol + 1) // make end exclusive
|
|
);
|
|
el.appendTo(this.rowEls[seg.row]);
|
|
highlightNodes.push(el[0]);
|
|
}
|
|
|
|
this.highlightEls = $(highlightNodes); // array -> jQuery set
|
|
},
|
|
|
|
|
|
// Unrenders any visual emphasis on a date range
|
|
destroyHighlight: function() {
|
|
if (this.highlightEls) {
|
|
this.highlightEls.remove();
|
|
this.highlightEls = null;
|
|
}
|
|
},
|
|
|
|
|
|
// Generates the HTML used to build a single-row "highlight skeleton", a table that frames highlight cells
|
|
highlightSkeletonHtml: function(startCol, endCol) {
|
|
var colCnt = this.view.colCnt;
|
|
var cellHtml = '';
|
|
|
|
if (startCol > 0) {
|
|
cellHtml += '<td colspan="' + startCol + '"/>';
|
|
}
|
|
if (endCol > startCol) {
|
|
cellHtml += '<td colspan="' + (endCol - startCol) + '" class="fc-highlight" />';
|
|
}
|
|
if (colCnt > endCol) {
|
|
cellHtml += '<td colspan="' + (colCnt - endCol) + '"/>';
|
|
}
|
|
|
|
cellHtml = this.bookendCells(cellHtml, 'highlight');
|
|
|
|
return '' +
|
|
'<div class="fc-highlight-skeleton">' +
|
|
'<table>' +
|
|
'<tr>' +
|
|
cellHtml +
|
|
'</tr>' +
|
|
'</table>' +
|
|
'</div>';
|
|
}
|
|
|
|
});
|