/** * This class is used to generate the rendering parameters for an event * in a {@link Ext.calendar.view.Days}. The purpose of this class is * to provide the rendering logic insulated from the DOM. * * @private */Ext.define('Ext.calendar.view.DaysRenderer', { /** * @cfg {Date} end * The end for the day. */ end: null, /** * @cfg {Date} start * The start for the day. */ start: null, /** * @cfg {Ext.calendar.view.Days} view * The view. */ view: null, constructor: function(config) { var me = this, D = Ext.Date, view, slotTicks; Ext.apply(me, config); view = me.view; slotTicks = view.slotTicks; me.slots = (view.getEndTime() - view.getStartTime()) * (60 / slotTicks); me.offset = view.MS_TO_MINUTES * slotTicks; me.events = []; }, /** * Adds the event to this renderer if it is valid. * @param {Ext.calendar.model.EventBase} event. */ addIf: function(event) { var me = this, start = me.start, view = me.view, offset = me.offset, startSlot, endSlot; if (!event.isSpan() && event.isContainedByRange(start, me.end)) { startSlot = Math.max(0, (view.roundDate(event.getStartDate()) - start) / offset); endSlot = Math.min(me.slots, (view.roundDate(event.getEndDate()) - start) / offset); this.events.push({ event: event, start: startSlot, end: endSlot, len: endSlot - startSlot, colIdx: -1, overlaps: [], edgeWeight: -1, forwardPos: -1, backwardPos: -1 }); } }, /** * Indicates that all events are added and the positions can be calculated. */ calculate: function() { var me = this, events = me.events, columns, len, i, firstCol; events.sort(me.sortEvents); columns = me.buildColumns(events); me.constructOverlaps(columns); firstCol = columns[0]; if (firstCol) { len = firstCol.length; for (i = 0, len = firstCol.length; i < len; ++i) { me.calculateEdgeWeights(firstCol[i]); } for (i = 0, len = firstCol.length; i < len; ++i) { me.calculatePositions(firstCol[i], 0, 0); } } }, /** * Checks if this renderer has any events. * @return {Boolean} `true` if there are events. */ hasEvents: function() { return this.events.length > 0; }, privates: { /** * Finds any vertically overlapping events from the candidates and * pushes them into the events overlap collection. * @param {Object} event The event meta object. * @param {Object[]} candidates The possible overlapping candidates. * * @private */ appendOverlappingEvents: function(event, candidates) { this.doOverlap(event, candidates, event.overlaps); }, /** * Construct a series of columns for events. If there is no * vertical collision between events, they can exist in the same column. * @param {Object[]} events The events. * @return {Object[][]} The columns. Each column will be an array of events. * * @private */ buildColumns: function(events) { var len = events.length, columns = [], i, j, colLen, col, event, idx; for (i = 0; i < len; ++i) { idx = -1; event = events[i]; for (j = 0, colLen = columns.length; j < colLen; ++j) { if (!this.hasOverlappingEvents(event, columns[j])) { idx = j; break; } } if (idx === -1) { idx = columns.length; columns[idx] = []; } columns[idx].push(event); event.colIdx = idx; } return columns; }, /** * Calculate the distance of this event to the edge in terms of items that * are overlapping in the horizontal group. A larger weight means there are * more items between the event and the edge, * @param {Object} event The event. * @return {Number} The weight. * * @private */ calculateEdgeWeights: function(event) { var overlaps = event.overlaps, len = overlaps.length, weight = event.edgeWeight, i; if (weight === -1) { weight = 0; for (i = 0; i < len; ++i) { weight = Math.max(weight, this.calculateEdgeWeights(overlaps[i]) + 1); } event.edgeWeight = weight; } return weight; }, /** * Calculate the backward/forward position from the start/end edges. The values * for each offset will be a percentage value. * @param {Object} event The event * @param {Number} edgeOffset The number of items before this one in the horizontal series. * @param {Number} backOffset The starting offset of an item in this horizontal series. * * @private */ calculatePositions: function(event, edgeOffset, backOffset) { var overlaps = event.overlaps, len = overlaps.length, nextEdgeOffset = edgeOffset + 1, fwd, i, first, availWidth; if (event.forwardPos === -1) { if (len === 0) { event.forwardPos = 1; } else { overlaps.sort(this.sortOverlaps); first = overlaps[0]; // Calculate the forward pos from the backward pos of the first item // in the series. This will be the item with the highest edge weight in // the overlaps collection. this.calculatePositions(first, nextEdgeOffset, backOffset); event.forwardPos = first.backwardPos; } fwd = event.forwardPos; availWidth = fwd - backOffset; event.backwardPos = fwd - availWidth / nextEdgeOffset; // Start from 1, either we have 0 overlaps or we already calculated it above. for (i = 1; i < len; ++i) { this.calculatePositions(overlaps[i], 0, fwd); } } }, /** * Finds events in subsequent columns that have a vertical overlap and tracks * them on the event. * @param {Object[][]} columns The columns. * * @private */ constructOverlaps: function(columns) { var len = columns.length, col, i, j, colLen, event; for (i = 0; i < len; ++i) { col = columns[i]; for (j = 0, colLen = col.length; j < colLen; ++j) { event = col[j]; for (k = i + 1; k < len; ++k) { this.appendOverlappingEvents(event, columns[k]) } } } }, /** * Utility method for checking events. Either returns true once * it finds an overlap or appends items to an array. * * @param {Object} event The event. * @param {Object[]} candidates The overlap candidates. * @param {Object[]} append If specified, overlapping items will be appended here. * @return {Boolean} `true` if there are overlapping events. Only returned if append * is not specified. * * @private */ doOverlap: function(event, candidates, append) { var overlaps = [], len = candidates.length, i, other; for (i = 0; i < len; ++i) { other = candidates[i]; if (this.overlaps(event, other)) { if (append) { append.push(other); } else { return true; } } } return false; }, /** * Checks if overlaps exist between an event and candidates. * @param {Object} event The event. * @param {Object[]} candidates The candidates. * @return {Boolean} `true` if there are overlapping events. * * @private */ hasOverlappingEvents: function(event, candidates) { return this.doOverlap(event, candidates); }, /** * Checks whether two events vertically overlap. * @param {Object} e1 The first event. * @param {Object} e2 The second event. * @return {Boolean} `true` if the events overlap. * * @private */ overlaps: function(e1, e2) { return e1.start < e2.end && e1.end > e2.start; }, /** * A sort comparator function for processing events. * @param {Object} e1 The first event. * @param {Object} e2 The second event, * @return {Number} A standard sort comparator. * * @private */ sortEvents: function(e1, e2) { return Ext.calendar.model.EventBase.sort(e1.event, e2.event); }, /** * A sort comparator function for overlapping events. * @param {Object} e1 The first event. * @param {Object} e2 The second event, * @return {Number} A standard sort comparator. * * @private */ sortOverlaps: function(e1, e2) { // Higher edge weights go first, since are at the start. return e2.edgeWeight - e1.edgeWeight || // Otherwise sort by events closer to the start edge, giving // precedence to those where the backwardPos hasn't be calculated (e1.backwardPos || 0) - (e2.backwardPos || 0) || Ext.calendar.model.EventBase.sort(e1.event, e2.event); } }});