/* 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 + '' + '
' + '
'; } });