function DayEventRenderer() { var t = this; // exports t.renderDayEvents = renderDayEvents; t.draggableDayEvent = draggableDayEvent; // made public so that subclasses can override t.resizableDayEvent = resizableDayEvent; // " // imports var opt = t.opt; var trigger = t.trigger; var isEventDraggable = t.isEventDraggable; var isEventResizable = t.isEventResizable; var reportEventElement = t.reportEventElement; var eventElementHandlers = t.eventElementHandlers; var showEvents = t.showEvents; var hideEvents = t.hideEvents; var eventDrop = t.eventDrop; var eventResize = t.eventResize; var getRowCnt = t.getRowCnt; var getColCnt = t.getColCnt; var allDayRow = t.allDayRow; // TODO: rename var colLeft = t.colLeft; var colRight = t.colRight; var colContentLeft = t.colContentLeft; var colContentRight = t.colContentRight; var getDaySegmentContainer = t.getDaySegmentContainer; var renderDayOverlay = t.renderDayOverlay; var clearOverlays = t.clearOverlays; var clearSelection = t.clearSelection; var getHoverListener = t.getHoverListener; var rangeToSegments = t.rangeToSegments; var cellToDate = t.cellToDate; var cellToCellOffset = t.cellToCellOffset; var cellOffsetToDayOffset = t.cellOffsetToDayOffset; var dateToDayOffset = t.dateToDayOffset; var dayOffsetToCellOffset = t.dayOffsetToCellOffset; var calendar = t.calendar; var getEventEnd = calendar.getEventEnd; // Render `events` onto the calendar, attach mouse event handlers, and call the `eventAfterRender` callback for each. // Mouse event will be lazily applied, except if the event has an ID of `modifiedEventId`. // Can only be called when the event container is empty (because it wipes out all innerHTML). function renderDayEvents(events, modifiedEventId) { // do the actual rendering. Receive the intermediate "segment" data structures. var segments = _renderDayEvents( events, false, // don't append event elements true // set the heights of the rows ); // report the elements to the View, for general drag/resize utilities segmentElementEach(segments, function(segment, element) { reportEventElement(segment.event, element); }); // attach mouse handlers attachHandlers(segments, modifiedEventId); // call `eventAfterRender` callback for each event segmentElementEach(segments, function(segment, element) { trigger('eventAfterRender', segment.event, segment.event, element); }); } // Render an event on the calendar, but don't report them anywhere, and don't attach mouse handlers. // Append this event element to the event container, which might already be populated with events. // If an event's segment will have row equal to `adjustRow`, then explicitly set its top coordinate to `adjustTop`. // This hack is used to maintain continuity when user is manually resizing an event. // Returns an array of DOM elements for the event. function renderTempDayEvent(event, adjustRow, adjustTop) { // actually render the event. `true` for appending element to container. // Recieve the intermediate "segment" data structures. var segments = _renderDayEvents( [ event ], true, // append event elements false // don't set the heights of the rows ); var elements = []; // Adjust certain elements' top coordinates segmentElementEach(segments, function(segment, element) { if (segment.row === adjustRow) { element.css('top', adjustTop); } elements.push(element[0]); // accumulate DOM nodes }); return elements; } // Render events onto the calendar. Only responsible for the VISUAL aspect. // Not responsible for attaching handlers or calling callbacks. // Set `doAppend` to `true` for rendering elements without clearing the existing container. // Set `doRowHeights` to allow setting the height of each row, to compensate for vertical event overflow. function _renderDayEvents(events, doAppend, doRowHeights) { // where the DOM nodes will eventually end up var finalContainer = getDaySegmentContainer(); // the container where the initial HTML will be rendered. // If `doAppend`==true, uses a temporary container. var renderContainer = doAppend ? $("
") : finalContainer; var segments = buildSegments(events); var html; var elements; // calculate the desired `left` and `width` properties on each segment object calculateHorizontals(segments); // build the HTML string. relies on `left` property html = buildHTML(segments); // render the HTML. innerHTML is considerably faster than jQuery's .html() renderContainer[0].innerHTML = html; // retrieve the individual elements elements = renderContainer.children(); // if we were appending, and thus using a temporary container, // re-attach elements to the real container. if (doAppend) { finalContainer.append(elements); } // assigns each element to `segment.event`, after filtering them through user callbacks resolveElements(segments, elements); // Calculate the left and right padding+margin for each element. // We need this for setting each element's desired outer width, because of the W3C box model. // It's important we do this in a separate pass from acually setting the width on the DOM elements // because alternating reading/writing dimensions causes reflow for every iteration. segmentElementEach(segments, function(segment, element) { segment.hsides = hsides(element, true); // include margins = `true` }); // Set the width of each element segmentElementEach(segments, function(segment, element) { element.width( Math.max(0, segment.outerWidth - segment.hsides) ); }); // Grab each element's outerHeight (setVerticals uses this). // To get an accurate reading, it's important to have each element's width explicitly set already. segmentElementEach(segments, function(segment, element) { segment.outerHeight = element.outerHeight(true); // include margins = `true` }); // Set the top coordinate on each element (requires segment.outerHeight) setVerticals(segments, doRowHeights); return segments; } // Generate an array of "segments" for all events. function buildSegments(events) { var resources = t.getResources; var segments = []; var i, eventSegments; if (typeof resources === 'undefined'){ for (i=0; i" + "
"; if (!event.allDay && segment.isStart) { html += "" + htmlEscape(t.getEventTimeText(event)) + ""; } html += "" + htmlEscape(event.title || '') + "" + "
"; if (event.allDay && segment.isEnd && isEventResizable(event)) { html += "
" + "   " + // makes hit area a lot better for IE6/7 "
"; } html += ""; // TODO: // When these elements are initially rendered, they will be briefly visibile on the screen, // even though their widths/heights are not set. // SOLUTION: initially set them as visibility:hidden ? return html; } // Associate each segment (an object) with an element (a jQuery object), // by setting each `segment.element`. // Run each element through the `eventRender` filter, which allows developers to // modify an existing element, supply a new one, or cancel rendering. function resolveElements(segments, elements) { for (var i=0; i div'); } return rowDivs; } /* Mouse Handlers ---------------------------------------------------------------------------------------------------*/ // TODO: better documentation! function attachHandlers(segments, modifiedEventId) { var segmentContainer = getDaySegmentContainer(); segmentElementEach(segments, function(segment, element, i) { var event = segment.event; if (event._id === modifiedEventId) { bindDaySeg(event, element, segment); }else{ element[0]._fci = i; // for lazySegBind } }); lazySegBind(segmentContainer, segments, bindDaySeg); } function bindDaySeg(event, eventElement, segment) { if (isEventDraggable(event)) { t.draggableDayEvent(event, eventElement, segment); // use `t` so subclasses can override } if ( event.allDay && segment.isEnd && // only allow resizing on the final segment for an event isEventResizable(event) ) { t.resizableDayEvent(event, eventElement, segment); // use `t` so subclasses can override } // attach all other handlers. // needs to be after, because resizableDayEvent might stopImmediatePropagation on click eventElementHandlers(event, eventElement); } function draggableDayEvent(event, eventElement) { var hoverListener = getHoverListener(); var dayDelta; var eventStart; eventElement.draggable({ delay: 50, opacity: opt('dragOpacity'), revertDuration: opt('dragRevertDuration'), start: function(ev, ui) { trigger('eventDragStart', eventElement[0], event, ev, ui); hideEvents(event, eventElement); hoverListener.start(function(cell, origCell, rowDelta, colDelta) { eventElement.draggable('option', 'revert', !cell || !rowDelta && !colDelta); clearOverlays(); if (cell) { var origCellDate = cellToDate(origCell); var cellDate = cellToDate(cell); dayDelta = cellDate.diff(origCellDate, 'days'); eventStart = event.start.clone().add(dayDelta, 'days'); renderDayOverlay( eventStart, getEventEnd(event).add(dayDelta, 'days') ); } else { dayDelta = 0; } }, ev, 'drag'); }, stop: function(ev, ui) { hoverListener.stop(); clearOverlays(); trigger('eventDragStop', eventElement[0], event, ev, ui); if (dayDelta) { eventDrop( eventElement[0], event, eventStart, ev, ui ); } else { eventElement.css('filter', ''); // clear IE opacity side-effects showEvents(event, eventElement); } } }); } function resizableDayEvent(event, element, segment) { var isRTL = opt('isRTL'); var direction = isRTL ? 'w' : 'e'; var handle = element.find('.ui-resizable-' + direction); // TODO: stop using this class because we aren't using jqui for this var isResizing = false; // TODO: look into using jquery-ui mouse widget for this stuff disableTextSelection(element); // prevent native selection for IE element .mousedown(function(ev) { // prevent native selection for others ev.preventDefault(); }) .click(function(ev) { if (isResizing) { ev.preventDefault(); // prevent link from being visited (only method that worked in IE6) ev.stopImmediatePropagation(); // prevent fullcalendar eventClick handler from being called // (eventElementHandlers needs to be bound after resizableDayEvent) } }); handle.mousedown(function(ev) { if (ev.which != 1) { return; // needs to be left mouse button } isResizing = true; var hoverListener = getHoverListener(); var elementTop = element.css('top'); var dayDelta; var eventEnd; var helpers; var eventCopy = $.extend({}, event); var minCellOffset = dayOffsetToCellOffset(dateToDayOffset(event.start)); clearSelection(); $('body') .css('cursor', direction + '-resize') .one('mouseup', mouseup); trigger('eventResizeStart', element[0], event, ev, {}); // {} is dummy jqui event hoverListener.start(function(cell, origCell) { if (cell) { var origCellOffset = cellToCellOffset(origCell); var cellOffset = cellToCellOffset(cell); // don't let resizing move earlier than start date cell cellOffset = Math.max(cellOffset, minCellOffset); dayDelta = cellOffsetToDayOffset(cellOffset) - cellOffsetToDayOffset(origCellOffset); eventEnd = getEventEnd(event).add(dayDelta, 'days'); // assumed to already have a stripped time if (dayDelta) { eventCopy.end = eventEnd; var oldHelpers = helpers; helpers = renderTempDayEvent(eventCopy, segment.row, elementTop); helpers = $(helpers); // turn array into a jQuery object helpers.find('*').css('cursor', direction + '-resize'); if (oldHelpers) { oldHelpers.remove(); } hideEvents(event); } else { if (helpers) { showEvents(event); helpers.remove(); helpers = null; } } clearOverlays(); renderDayOverlay( // coordinate grid already rebuilt with hoverListener.start() event.start, eventEnd // TODO: instead of calling renderDayOverlay() with dates, // call _renderDayOverlay (or whatever) with cell offsets. ); } }, ev); function mouseup(ev) { trigger('eventResizeStop', element[0], event, ev, {}); // {} is dummy jqui event $('body').css('cursor', ''); hoverListener.stop(); clearOverlays(); if (dayDelta) { eventResize( element[0], event, eventEnd, ev, {} // dummy jqui event ); // event redraw will clear helpers } // otherwise, the drag handler already restored the old events setTimeout(function() { // make this happen after the element's click event isResizing = false; },0); } }); } } /* Generalized Segment Utilities -------------------------------------------------------------------------------------------------*/ function isDaySegmentCollision(segment, otherSegments) { for (var i=0; i= segment.leftCol ) { return true; } } return false; } function segmentElementEach(segments, callback) { // TODO: use in AgendaView? for (var i=0; i