/* Event-rendering methods for the TimeGrid class ----------------------------------------------------------------------------------------------------------------------*/ $.extend(TimeGrid.prototype, { segs: null, // segment objects rendered in the component. null of events haven't been rendered yet eventSkeletonEl: null, // has cells with event-containers, which contain absolutely positioned event elements // Renders the events onto the grid and returns an array of segments that have been rendered renderEvents: function(events) { var res = this.renderEventTable(events); this.eventSkeletonEl = $('
').append(res.tableEl); this.el.append(this.eventSkeletonEl); this.segs = res.segs; }, // Retrieves rendered segment objects getSegs: function() { return this.segs || []; }, // Removes all event segment elements from the view destroyEvents: function() { Grid.prototype.destroyEvents.call(this); // call the super-method if (this.eventSkeletonEl) { this.eventSkeletonEl.remove(); this.eventSkeletonEl = null; } this.segs = null; }, // Renders and returns the portion of the event-skeleton. // Returns an object with properties 'tbodyEl' and 'segs'. renderEventTable: function(events) { var tableEl = $('
'); var trEl = tableEl.find('tr'); var segs = this.eventsToSegs(events); var segCols; var i, seg; var col, colSegs; var containerEl; segs = this.renderSegs(segs); // returns only the visible segs segCols = this.groupSegCols(segs); // group into sub-arrays, and assigns 'col' to each seg this.computeSegVerticals(segs); // compute and assign top/bottom for (col = 0; col < segCols.length; col++) { // iterate each column grouping colSegs = segCols[col]; placeSlotSegs(colSegs); // compute horizontal coordinates, z-index's, and reorder the array containerEl = $('
'); // assign positioning CSS and insert into container for (i = 0; i < colSegs.length; i++) { seg = colSegs[i]; seg.el.css(this.generateSegPositionCss(seg)); // if the height is short, add a className for alternate styling if (seg.bottom - seg.top < 30) { seg.el.addClass('fc-short'); } containerEl.append(seg.el); } trEl.append($('').append(containerEl)); } this.bookendCells(trEl, 'eventSkeleton'); return { tableEl: tableEl, segs: segs }; }, // Refreshes the CSS top/bottom coordinates for each segment element. Probably after a window resize/zoom. updateSegVerticals: function() { var segs = this.segs; var i; if (segs) { this.computeSegVerticals(segs); for (i = 0; i < segs.length; i++) { segs[i].el.css( this.generateSegVerticalCss(segs[i]) ); } } }, // For each segment in an array, computes and assigns its top and bottom properties computeSegVerticals: function(segs) { var i, seg; for (i = 0; i < segs.length; i++) { seg = segs[i]; seg.top = this.computeDateTop(seg.start, seg.start); seg.bottom = this.computeDateTop(seg.end, seg.start); } }, // Renders the HTML for a single event segment's default rendering renderSegHtml: function(seg, disableResizing) { var view = this.view; var event = seg.event; var isDraggable = view.isEventDraggable(event); var isResizable = !disableResizing && seg.isEnd && view.isEventResizable(event); var classes = this.getSegClasses(seg, isDraggable, isResizable); var skinCss = this.getEventSkinCss(event); var timeText; var fullTimeText; // more verbose time text. for the print stylesheet var startTimeText; // just the start time text classes.unshift('fc-time-grid-event'); if (view.isMultiDayEvent(event)) { // if the event appears to span more than one day... // Don't display time text on segments that run entirely through a day. // That would appear as midnight-midnight and would look dumb. // Otherwise, display the time text for the *segment's* times (like 6pm-midnight or midnight-10am) if (seg.isStart || seg.isEnd) { timeText = view.getEventTimeText(seg.start, seg.end); fullTimeText = view.getEventTimeText(seg.start, seg.end, 'LT'); startTimeText = view.getEventTimeText(seg.start, null); } } else { // Display the normal time text for the *event's* times timeText = view.getEventTimeText(event); fullTimeText = view.getEventTimeText(event, 'LT'); startTimeText = view.getEventTimeText(event.start, null); } return '' + '
' + (timeText ? '
' + '' + htmlEscape(timeText) + '' + '
' : '' ) + (event.title ? '
' + htmlEscape(event.title) + '
' : '' ) + '
' + '
' + (isResizable ? '
' : '' ) + ''; }, // Generates an object with CSS properties/values that should be applied to an event segment element. // Contains important positioning-related properties that should be applied to any event element, customized or not. generateSegPositionCss: function(seg) { var view = this.view; var isRTL = view.opt('isRTL'); var shouldOverlap = view.opt('slotEventOverlap'); var backwardCoord = seg.backwardCoord; // the left side if LTR. the right side if RTL. floating-point var forwardCoord = seg.forwardCoord; // the right side if LTR. the left side if RTL. floating-point var props = this.generateSegVerticalCss(seg); // get top/bottom first var left; // amount of space from left edge, a fraction of the total width var right; // amount of space from right edge, a fraction of the total width if (shouldOverlap) { // double the width, but don't go beyond the maximum forward coordinate (1.0) forwardCoord = Math.min(1, backwardCoord + (forwardCoord - backwardCoord) * 2); } if (isRTL) { left = 1 - forwardCoord; right = backwardCoord; } else { left = backwardCoord; right = 1 - forwardCoord; } props.zIndex = seg.level + 1; // convert from 0-base to 1-based props.left = left * 100 + '%'; props.right = right * 100 + '%'; if (shouldOverlap && seg.forwardPressure) { // add padding to the edge so that forward stacked events don't cover the resizer's icon props[isRTL ? 'marginLeft' : 'marginRight'] = 10 * 2; // 10 is a guesstimate of the icon's width } return props; }, // Generates an object with CSS properties for the top/bottom coordinates of a segment element generateSegVerticalCss: function(seg) { return { top: seg.top, bottom: -seg.bottom // flipped because needs to be space beyond bottom edge of event container }; }, // Given a flat array of segments, return an array of sub-arrays, grouped by each segment's col groupSegCols: function(segs) { var view = this.view; var segCols = []; var i; for (i = 0; i < view.colCnt; i++) { segCols.push([]); } for (i = 0; i < segs.length; i++) { segCols[segs[i].col].push(segs[i]); } return segCols; } }); // Given an array of segments that are all in the same column, sets the backwardCoord and forwardCoord on each. // Also reorders the given array by date! function placeSlotSegs(segs) { var levels; var level0; var i; segs.sort(compareSegs); // order by date levels = buildSlotSegLevels(segs); computeForwardSlotSegs(levels); if ((level0 = levels[0])) { for (i = 0; i < level0.length; i++) { computeSlotSegPressures(level0[i]); } for (i = 0; i < level0.length; i++) { computeSlotSegCoords(level0[i], 0, 0); } } } // Builds an array of segments "levels". The first level will be the leftmost tier of segments if the calendar is // left-to-right, or the rightmost if the calendar is right-to-left. Assumes the segments are already ordered by date. function buildSlotSegLevels(segs) { var levels = []; var i, seg; var j; for (i=0; i seg2.top && seg1.top < seg2.bottom; } // A cmp function for determining which forward segment to rely on more when computing coordinates. function compareForwardSlotSegs(seg1, seg2) { // put higher-pressure first return seg2.forwardPressure - seg1.forwardPressure || // put segments that are closer to initial edge first (and favor ones with no coords yet) (seg1.backwardCoord || 0) - (seg2.backwardCoord || 0) || // do normal sorting... compareSegs(seg1, seg2); }