/* Event-rendering methods for the DayGrid class ----------------------------------------------------------------------------------------------------------------------*/ $.extend(DayGrid.prototype, { segs: null, rowStructs: null, // an array of objects, each holding information about a row's event-rendering // Render the given events onto the Grid and return the rendered segments renderEvents: function(events) { var rowStructs = this.rowStructs = this.renderEventRows(events); var segs = []; // append to each row's content skeleton this.rowEls.each(function(i, rowNode) { $(rowNode).find('.fc-content-skeleton > table').append( rowStructs[i].tbodyEl ); segs.push.apply(segs, rowStructs[i].segs); }); this.segs = segs; }, // Retrieves all segment objects that have been rendered getSegs: function() { return (this.segs || []).concat( this.popoverSegs || [] // segs rendered in the "more" events popover ); }, // Removes all rendered event elements destroyEvents: function() { var rowStructs; var rowStruct; Grid.prototype.destroyEvents.call(this); // call the super-method rowStructs = this.rowStructs || []; while ((rowStruct = rowStructs.pop())) { rowStruct.tbodyEl.remove(); } this.segs = null; this.destroySegPopover(); // removes the "more.." events popover }, // Uses the given events array to generate elements that should be appended to each row's content skeleton. // Returns an array of rowStruct objects (see the bottom of `renderEventRow`). renderEventRows: function(events) { var segs = this.eventsToSegs(events); var rowStructs = []; var segRows; var row; segs = this.renderSegs(segs); // returns a new array with only visible segments segRows = this.groupSegRows(segs); // group into nested arrays // iterate each row of segment groupings for (row = 0; row < segRows.length; row++) { rowStructs.push( this.renderEventRow(row, segRows[row]) ); } return rowStructs; }, // Builds the HTML to be used for the default element for an individual segment renderSegHtml: function(seg, disableResizing) { var view = this.view; var isRTL = view.opt('isRTL'); var event = seg.event; var isDraggable = view.isEventDraggable(event); var isResizable = !disableResizing && event.allDay && seg.isEnd && view.isEventResizable(event); var classes = this.getSegClasses(seg, isDraggable, isResizable); var skinCss = this.getEventSkinCss(event); var timeHtml = ''; var titleHtml; classes.unshift('fc-day-grid-event'); // Only display a timed events time if it is the starting segment if (!event.allDay && seg.isStart) { timeHtml = '' + htmlEscape(view.getEventTimeText(event)) + ''; } titleHtml = '' + (htmlEscape(event.title || '') || ' ') + // we always want one line of height ''; return '' + '
' + (isRTL ? titleHtml + ' ' + timeHtml : // put a natural space in between timeHtml + ' ' + titleHtml // ) + '
' + (isResizable ? '
' : '' ) + ''; }, // Given a row # and an array of segments all in the same row, render a element, a skeleton that contains // the segments. Returns object with a bunch of internal data about how the render was calculated. renderEventRow: function(row, rowSegs) { var view = this.view; var colCnt = view.colCnt; var segLevels = this.buildSegLevels(rowSegs); // group into sub-arrays of levels var levelCnt = Math.max(1, segLevels.length); // ensure at least one level var tbody = $(''); var segMatrix = []; // lookup for which segments are rendered into which level+col cells var cellMatrix = []; // lookup for all elements of the level+col matrix var loneCellMatrix = []; // lookup for elements that only take up a single column var i, levelSegs; var col; var tr; var j, seg; var td; // populates empty cells from the current column (`col`) to `endCol` function emptyCellsUntil(endCol) { while (col < endCol) { // try to grab a cell from the level above and extend its rowspan. otherwise, create a fresh cell td = (loneCellMatrix[i - 1] || [])[col]; if (td) { td.attr( 'rowspan', parseInt(td.attr('rowspan') || 1, 10) + 1 ); } else { td = $(''); tr.append(td); } cellMatrix[i][col] = td; loneCellMatrix[i][col] = td; col++; } } for (i = 0; i < levelCnt; i++) { // iterate through all levels levelSegs = segLevels[i]; col = 0; tr = $(''); segMatrix.push([]); cellMatrix.push([]); loneCellMatrix.push([]); // levelCnt might be 1 even though there are no actual levels. protect against this. // this single empty row is useful for styling. if (levelSegs) { for (j = 0; j < levelSegs.length; j++) { // iterate through segments in level seg = levelSegs[j]; emptyCellsUntil(seg.leftCol); // create a container that occupies or more columns. append the event element. td = $('').append(seg.el); if (seg.leftCol != seg.rightCol) { td.attr('colspan', seg.rightCol - seg.leftCol + 1); } else { // a single-column segment loneCellMatrix[i][col] = td; } while (col <= seg.rightCol) { cellMatrix[i][col] = td; segMatrix[i][col] = seg; col++; } tr.append(td); } } emptyCellsUntil(colCnt); // finish off the row this.bookendCells(tr, 'eventSkeleton'); tbody.append(tr); } return { // a "rowStruct" row: row, // the row number tbodyEl: tbody, cellMatrix: cellMatrix, segMatrix: segMatrix, segLevels: segLevels, segs: rowSegs }; }, // Stacks a flat array of segments, which are all assumed to be in the same row, into subarrays of vertical levels. buildSegLevels: function(segs) { var levels = []; var i, seg; var j; // Give preference to elements with certain criteria, so they have // a chance to be closer to the top. segs.sort(compareSegs); for (i = 0; i < segs.length; i++) { seg = segs[i]; // loop through levels, starting with the topmost, until the segment doesn't collide with other segments for (j = 0; j < levels.length; j++) { if (!isDaySegCollision(seg, levels[j])) { break; } } // `j` now holds the desired subrow index seg.level = j; // create new level array if needed and append segment (levels[j] || (levels[j] = [])).push(seg); } // order segments left-to-right. very important if calendar is RTL for (j = 0; j < levels.length; j++) { levels[j].sort(compareDaySegCols); } return levels; }, // Given a flat array of segments, return an array of sub-arrays, grouped by each segment's row groupSegRows: function(segs) { var view = this.view; var segRows = []; var i; for (i = 0; i < view.rowCnt; i++) { segRows.push([]); } for (i = 0; i < segs.length; i++) { segRows[segs[i].row].push(segs[i]); } return segRows; } }); // Computes whether two segments' columns collide. They are assumed to be in the same row. function isDaySegCollision(seg, otherSegs) { var i, otherSeg; for (i = 0; i < otherSegs.length; i++) { otherSeg = otherSegs[i]; if ( otherSeg.leftCol <= seg.rightCol && otherSeg.rightCol >= seg.leftCol ) { return true; } } return false; } // A cmp function for determining the leftmost event function compareDaySegCols(a, b) { return a.leftCol - b.leftCol; }