/* 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 '' +
'
' +
'
' +
'
' +
this.rowHtml('day', row) + // leverages RowRenderer. calls dayCellHtml()
'
' +
'
' +
'
' +
'
' +
(this.numbersVisible ?
'' +
this.rowHtml('number', row) + // leverages RowRenderer. View will define render method
'' :
''
) +
'
' +
'
' +
'
';
},
// 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 = $(''); // 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 += ' | ';
}
if (endCol > startCol) {
cellHtml += ' | ';
}
if (colCnt > endCol) {
cellHtml += ' | ';
}
cellHtml = this.bookendCells(cellHtml, 'highlight');
return '' +
'' +
'
' +
'' +
cellHtml +
'
' +
'
' +
'
';
}
});