| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313 |
- /* Event-rendering methods for the DayGrid class
- ----------------------------------------------------------------------------------------------------------------------*/
- var DayGridEventRenderer = EventRenderer.extend({
- dayGrid: null,
- rowStructs: null, // an array of objects, each holding information about a row's foreground event-rendering
- constructor: function(dayGrid) {
- EventRenderer.apply(this, arguments);
- this.dayGrid = dayGrid;
- },
- renderBgRanges: function(eventRanges) {
- // don't render timed background events
- eventRanges = $.grep(eventRanges, function(eventRange) {
- return eventRange.eventDef.isAllDay();
- });
- EventRenderer.prototype.renderBgRanges.call(this, eventRanges);
- },
- // Renders the given foreground event segments onto the grid
- renderFgSegs: function(segs) {
- var rowStructs = this.rowStructs = this.renderSegRows(segs);
- // append to each row's content skeleton
- this.dayGrid.rowEls.each(function(i, rowNode) {
- $(rowNode).find('.fc-content-skeleton > table').append(
- rowStructs[i].tbodyEl
- );
- });
- },
- // Unrenders all currently rendered foreground event segments
- unrenderFgSegs: function() {
- var rowStructs = this.rowStructs || [];
- var rowStruct;
- while ((rowStruct = rowStructs.pop())) {
- rowStruct.tbodyEl.remove();
- }
- this.rowStructs = null;
- },
- // Uses the given events array to generate <tbody> elements that should be appended to each row's content skeleton.
- // Returns an array of rowStruct objects (see the bottom of `renderSegRow`).
- // PRECONDITION: each segment shoud already have a rendered and assigned `.el`
- renderSegRows: function(segs) {
- var rowStructs = [];
- var segRows;
- var row;
- segRows = this.groupSegRows(segs); // group into nested arrays
- // iterate each row of segment groupings
- for (row = 0; row < segRows.length; row++) {
- rowStructs.push(
- this.renderSegRow(row, segRows[row])
- );
- }
- return rowStructs;
- },
- // Given a row # and an array of segments all in the same row, render a <tbody> element, a skeleton that contains
- // the segments. Returns object with a bunch of internal data about how the render was calculated.
- // NOTE: modifies rowSegs
- renderSegRow: function(row, rowSegs) {
- var colCnt = this.dayGrid.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 = $('<tbody/>');
- var segMatrix = []; // lookup for which segments are rendered into which level+col cells
- var cellMatrix = []; // lookup for all <td> elements of the level+col matrix
- var loneCellMatrix = []; // lookup for <td> 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 = $('<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 = $('<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 = $('<td class="fc-event-container"/>').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.dayGrid.bookendCells(tr);
- 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.
- // NOTE: modifies segs
- 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.
- this.sortEventSegs(segs);
- 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 segRows = [];
- var i;
- for (i = 0; i < this.dayGrid.rowCnt; i++) {
- segRows.push([]);
- }
- for (i = 0; i < segs.length; i++) {
- segRows[segs[i].row].push(segs[i]);
- }
- return segRows;
- },
- // Computes a default event time formatting string if `timeFormat` is not explicitly defined
- computeEventTimeFormat: function() {
- return this.opt('extraSmallTimeFormat'); // like "6p" or "6:30p"
- },
- // Computes a default `displayEventEnd` value if one is not expliclty defined
- computeDisplayEventEnd: function() {
- return this.dayGrid.colCnt === 1; // we'll likely have space if there's only one day
- },
- // Builds the HTML to be used for the default element for an individual segment
- fgSegHtml: function(seg, disableResizing) {
- var view = this.view;
- var eventDef = seg.footprint.eventDef;
- var isAllDay = seg.footprint.componentFootprint.isAllDay;
- var isDraggable = view.isEventDefDraggable(eventDef);
- var isResizableFromStart = !disableResizing && isAllDay &&
- seg.isStart && view.isEventDefResizableFromStart(eventDef);
- var isResizableFromEnd = !disableResizing && isAllDay &&
- seg.isEnd && view.isEventDefResizableFromEnd(eventDef);
- var classes = this.getSegClasses(seg, isDraggable, isResizableFromStart || isResizableFromEnd);
- var skinCss = cssToStr(this.getSkinCss(eventDef));
- var timeHtml = '';
- var timeText;
- var titleHtml;
- classes.unshift('fc-day-grid-event', 'fc-h-event');
- // Only display a timed events time if it is the starting segment
- if (seg.isStart) {
- timeText = this.getTimeText(seg.footprint);
- if (timeText) {
- timeHtml = '<span class="fc-time">' + htmlEscape(timeText) + '</span>';
- }
- }
- titleHtml =
- '<span class="fc-title">' +
- (htmlEscape(eventDef.title || '') || ' ') + // we always want one line of height
- '</span>';
- return '<a class="' + classes.join(' ') + '"' +
- (eventDef.url ?
- ' href="' + htmlEscape(eventDef.url) + '"' :
- ''
- ) +
- (skinCss ?
- ' style="' + skinCss + '"' :
- ''
- ) +
- '>' +
- '<div class="fc-content">' +
- (this.isRTL ?
- titleHtml + ' ' + timeHtml : // put a natural space in between
- timeHtml + ' ' + titleHtml //
- ) +
- '</div>' +
- (isResizableFromStart ?
- '<div class="fc-resizer fc-start-resizer" />' :
- ''
- ) +
- (isResizableFromEnd ?
- '<div class="fc-resizer fc-end-resizer" />' :
- ''
- ) +
- '</a>';
- }
- });
- // 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;
- }
|