|
@@ -1,4 +1,9 @@
|
|
|
|
|
|
|
|
|
|
+/* An abstract class for all agenda-related views. Displays one more columns with time slots running vertically.
|
|
|
|
|
+----------------------------------------------------------------------------------------------------------------------*/
|
|
|
|
|
+// Is a manager for the TimeGrid subcomponent and possibly the DayGrid subcomponent (if allDaySlot is on).
|
|
|
|
|
+// Responsible for managing width/height.
|
|
|
|
|
+
|
|
|
setDefaults({
|
|
setDefaults({
|
|
|
allDaySlot: true,
|
|
allDaySlot: true,
|
|
|
allDayText: 'all-day',
|
|
allDayText: 'all-day',
|
|
@@ -12,9 +17,6 @@ setDefaults({
|
|
|
agenda: generateAgendaTimeFormat
|
|
agenda: generateAgendaTimeFormat
|
|
|
},
|
|
},
|
|
|
|
|
|
|
|
- dragOpacity: {
|
|
|
|
|
- agenda: .5
|
|
|
|
|
- },
|
|
|
|
|
minTime: '00:00:00',
|
|
minTime: '00:00:00',
|
|
|
maxTime: '24:00:00',
|
|
maxTime: '24:00:00',
|
|
|
slotEventOverlap: true
|
|
slotEventOverlap: true
|
|
@@ -35,923 +37,358 @@ function generateAgendaTimeFormat(options, langData) {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
-// TODO: make it work in quirks mode (event corners, all-day height)
|
|
|
|
|
-// TODO: test liquid width, especially in IE6
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
-function AgendaView(element, calendar, viewName) {
|
|
|
|
|
- var t = this;
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
- // exports
|
|
|
|
|
- t.renderAgenda = renderAgenda;
|
|
|
|
|
- t.setWidth = setWidth;
|
|
|
|
|
- t.setHeight = setHeight;
|
|
|
|
|
- t.afterRender = afterRender;
|
|
|
|
|
- t.computeDateTop = computeDateTop;
|
|
|
|
|
- t.getIsCellAllDay = getIsCellAllDay;
|
|
|
|
|
- t.allDayRow = function() { return allDayRow; }; // badly named
|
|
|
|
|
- t.getCoordinateGrid = function() { return coordinateGrid; }; // specifically for AgendaEventRenderer
|
|
|
|
|
- t.getHoverListener = function() { return hoverListener; };
|
|
|
|
|
- t.colLeft = colLeft;
|
|
|
|
|
- t.colRight = colRight;
|
|
|
|
|
- t.colContentLeft = colContentLeft;
|
|
|
|
|
- t.colContentRight = colContentRight;
|
|
|
|
|
- t.getDaySegmentContainer = function() { return daySegmentContainer; };
|
|
|
|
|
- t.getSlotSegmentContainer = function() { return slotSegmentContainer; };
|
|
|
|
|
- t.getSlotContainer = function() { return slotContainer; };
|
|
|
|
|
- t.getRowCnt = function() { return 1; };
|
|
|
|
|
- t.getColCnt = function() { return colCnt; };
|
|
|
|
|
- t.getColWidth = function() { return colWidth; };
|
|
|
|
|
- t.getSnapHeight = function() { return snapHeight; };
|
|
|
|
|
- t.getSnapDuration = function() { return snapDuration; };
|
|
|
|
|
- t.getSlotHeight = function() { return slotHeight; };
|
|
|
|
|
- t.getSlotDuration = function() { return slotDuration; };
|
|
|
|
|
- t.getMinTime = function() { return minTime; };
|
|
|
|
|
- t.getMaxTime = function() { return maxTime; };
|
|
|
|
|
- t.defaultSelectionEnd = defaultSelectionEnd;
|
|
|
|
|
- t.renderDayOverlay = renderDayOverlay;
|
|
|
|
|
- t.renderSelection = renderSelection;
|
|
|
|
|
- t.clearSelection = clearSelection;
|
|
|
|
|
- t.reportDayClick = reportDayClick; // selection mousedown hack
|
|
|
|
|
- t.dragStart = dragStart;
|
|
|
|
|
- t.dragStop = dragStop;
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
- // imports
|
|
|
|
|
- View.call(t, element, calendar, viewName);
|
|
|
|
|
- OverlayManager.call(t);
|
|
|
|
|
- SelectionManager.call(t);
|
|
|
|
|
- AgendaEventRenderer.call(t);
|
|
|
|
|
- var opt = t.opt;
|
|
|
|
|
- var trigger = t.trigger;
|
|
|
|
|
- var renderOverlay = t.renderOverlay;
|
|
|
|
|
- var clearOverlays = t.clearOverlays;
|
|
|
|
|
- var reportSelection = t.reportSelection;
|
|
|
|
|
- var unselect = t.unselect;
|
|
|
|
|
- var daySelectionMousedown = t.daySelectionMousedown;
|
|
|
|
|
- var slotSegHtml = t.slotSegHtml;
|
|
|
|
|
- var cellToDate = t.cellToDate;
|
|
|
|
|
- var dateToCell = t.dateToCell;
|
|
|
|
|
- var rangeToSegments = t.rangeToSegments;
|
|
|
|
|
- var formatDate = calendar.formatDate;
|
|
|
|
|
- var calculateWeekNumber = calendar.calculateWeekNumber;
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
- // locals
|
|
|
|
|
-
|
|
|
|
|
- var dayTable;
|
|
|
|
|
- var dayHead;
|
|
|
|
|
- var dayHeadCells;
|
|
|
|
|
- var dayBody;
|
|
|
|
|
- var dayBodyCells;
|
|
|
|
|
- var dayBodyCellInners;
|
|
|
|
|
- var dayBodyCellContentInners;
|
|
|
|
|
- var dayBodyFirstCell;
|
|
|
|
|
- var dayBodyFirstCellStretcher;
|
|
|
|
|
- var slotLayer;
|
|
|
|
|
- var daySegmentContainer;
|
|
|
|
|
- var allDayTable;
|
|
|
|
|
- var allDayRow;
|
|
|
|
|
- var slotScroller;
|
|
|
|
|
- var slotContainer;
|
|
|
|
|
- var slotSegmentContainer;
|
|
|
|
|
- var slotTable;
|
|
|
|
|
- var selectionHelper;
|
|
|
|
|
-
|
|
|
|
|
- var viewWidth;
|
|
|
|
|
- var viewHeight;
|
|
|
|
|
- var axisWidth;
|
|
|
|
|
- var colWidth;
|
|
|
|
|
- var gutterWidth;
|
|
|
|
|
-
|
|
|
|
|
- var slotDuration;
|
|
|
|
|
- var slotHeight; // TODO: what if slotHeight changes? (see issue 650)
|
|
|
|
|
-
|
|
|
|
|
- var snapDuration;
|
|
|
|
|
- var snapRatio; // ratio of number of "selection" slots to normal slots. (ex: 1, 2, 4)
|
|
|
|
|
- var snapHeight; // holds the pixel hight of a "selection" slot
|
|
|
|
|
-
|
|
|
|
|
- var colCnt;
|
|
|
|
|
- var slotCnt;
|
|
|
|
|
- var coordinateGrid;
|
|
|
|
|
- var hoverListener;
|
|
|
|
|
- var colPositions;
|
|
|
|
|
- var colContentPositions;
|
|
|
|
|
- var slotTopCache = {};
|
|
|
|
|
-
|
|
|
|
|
- var tm;
|
|
|
|
|
- var rtl;
|
|
|
|
|
- var minTime;
|
|
|
|
|
- var maxTime;
|
|
|
|
|
- var colFormat;
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
- /* Rendering
|
|
|
|
|
- -----------------------------------------------------------------------------*/
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
- disableTextSelection(element.addClass('fc-agenda'));
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
- function renderAgenda(c) {
|
|
|
|
|
- colCnt = c;
|
|
|
|
|
- updateOptions();
|
|
|
|
|
-
|
|
|
|
|
- if (!dayTable) { // first time rendering?
|
|
|
|
|
- buildSkeleton(); // builds day table, slot area, events containers
|
|
|
|
|
- }
|
|
|
|
|
- else {
|
|
|
|
|
- buildDayTable(); // rebuilds day table
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
- function updateOptions() {
|
|
|
|
|
|
|
+function AgendaView(calendar) {
|
|
|
|
|
+ View.call(this, calendar); // call the super-constructor
|
|
|
|
|
|
|
|
- tm = opt('theme') ? 'ui' : 'fc';
|
|
|
|
|
- rtl = opt('isRTL');
|
|
|
|
|
- colFormat = opt('columnFormat');
|
|
|
|
|
|
|
+ this.timeGrid = new TimeGrid(this);
|
|
|
|
|
|
|
|
- minTime = moment.duration(opt('minTime'));
|
|
|
|
|
- maxTime = moment.duration(opt('maxTime'));
|
|
|
|
|
|
|
+ if (this.opt('allDaySlot')) { // should we display the "all-day" area?
|
|
|
|
|
+ this.dayGrid = new DayGrid(this); // the all-day subcomponent of this view
|
|
|
|
|
|
|
|
- slotDuration = moment.duration(opt('slotDuration'));
|
|
|
|
|
- snapDuration = opt('snapDuration');
|
|
|
|
|
- snapDuration = snapDuration ? moment.duration(snapDuration) : slotDuration;
|
|
|
|
|
|
|
+ // the coordinate grid will be a combination of both subcomponents' grids
|
|
|
|
|
+ this.coordMap = new ComboCoordMap([
|
|
|
|
|
+ this.dayGrid.coordMap,
|
|
|
|
|
+ this.timeGrid.coordMap
|
|
|
|
|
+ ]);
|
|
|
|
|
+ }
|
|
|
|
|
+ else {
|
|
|
|
|
+ this.coordMap = this.timeGrid.coordMap;
|
|
|
}
|
|
}
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
+AgendaView.prototype = createObject(View.prototype); // define the super-class
|
|
|
|
|
+$.extend(AgendaView.prototype, {
|
|
|
|
|
|
|
|
- /* Build DOM
|
|
|
|
|
- -----------------------------------------------------------------------*/
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
- function buildSkeleton() {
|
|
|
|
|
- var s;
|
|
|
|
|
- var headerClass = tm + "-widget-header";
|
|
|
|
|
- var contentClass = tm + "-widget-content";
|
|
|
|
|
- var slotTime;
|
|
|
|
|
- var slotDate;
|
|
|
|
|
- var minutes;
|
|
|
|
|
- var slotNormal = slotDuration.asMinutes() % 15 === 0;
|
|
|
|
|
-
|
|
|
|
|
- buildDayTable();
|
|
|
|
|
-
|
|
|
|
|
- slotLayer =
|
|
|
|
|
- $("<div style='position:absolute;z-index:2;left:0;width:100%'/>")
|
|
|
|
|
- .appendTo(element);
|
|
|
|
|
-
|
|
|
|
|
- if (opt('allDaySlot')) {
|
|
|
|
|
-
|
|
|
|
|
- daySegmentContainer =
|
|
|
|
|
- $("<div class='fc-event-container' style='position:absolute;z-index:8;top:0;left:0'/>")
|
|
|
|
|
- .appendTo(slotLayer);
|
|
|
|
|
-
|
|
|
|
|
- s =
|
|
|
|
|
- "<table style='width:100%' class='fc-agenda-allday' cellspacing='0'>" +
|
|
|
|
|
- "<tr>" +
|
|
|
|
|
- "<th class='" + headerClass + " fc-agenda-axis'>" +
|
|
|
|
|
- (
|
|
|
|
|
- opt('allDayHTML') ||
|
|
|
|
|
- htmlEscape(opt('allDayText'))
|
|
|
|
|
- ) +
|
|
|
|
|
- "</th>" +
|
|
|
|
|
- "<td>" +
|
|
|
|
|
- "<div class='fc-day-content'><div style='position:relative'/></div>" +
|
|
|
|
|
- "</td>" +
|
|
|
|
|
- "<th class='" + headerClass + " fc-agenda-gutter'> </th>" +
|
|
|
|
|
- "</tr>" +
|
|
|
|
|
- "</table>";
|
|
|
|
|
- allDayTable = $(s).appendTo(slotLayer);
|
|
|
|
|
- allDayRow = allDayTable.find('tr');
|
|
|
|
|
-
|
|
|
|
|
- dayBind(allDayRow.find('td'));
|
|
|
|
|
-
|
|
|
|
|
- slotLayer.append(
|
|
|
|
|
- "<div class='fc-agenda-divider " + headerClass + "'>" +
|
|
|
|
|
- "<div class='fc-agenda-divider-inner'/>" +
|
|
|
|
|
- "</div>"
|
|
|
|
|
- );
|
|
|
|
|
-
|
|
|
|
|
- }else{
|
|
|
|
|
-
|
|
|
|
|
- daySegmentContainer = $([]); // in jQuery 1.4, we can just do $()
|
|
|
|
|
-
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- slotScroller =
|
|
|
|
|
- $("<div style='position:absolute;width:100%;overflow-x:hidden;overflow-y:auto'/>")
|
|
|
|
|
- .appendTo(slotLayer);
|
|
|
|
|
-
|
|
|
|
|
- slotContainer =
|
|
|
|
|
- $("<div style='position:relative;width:100%;overflow:hidden'/>")
|
|
|
|
|
- .appendTo(slotScroller);
|
|
|
|
|
-
|
|
|
|
|
- slotSegmentContainer =
|
|
|
|
|
- $("<div class='fc-event-container' style='position:absolute;z-index:8;top:0;left:0'/>")
|
|
|
|
|
- .appendTo(slotContainer);
|
|
|
|
|
-
|
|
|
|
|
- s =
|
|
|
|
|
- "<table class='fc-agenda-slots' style='width:100%' cellspacing='0'>" +
|
|
|
|
|
- "<tbody>";
|
|
|
|
|
-
|
|
|
|
|
- slotTime = moment.duration(+minTime); // i wish there was .clone() for durations
|
|
|
|
|
- slotCnt = 0;
|
|
|
|
|
- while (slotTime < maxTime) {
|
|
|
|
|
- slotDate = t.start.clone().time(slotTime); // will be in UTC but that's good. to avoid DST issues
|
|
|
|
|
- minutes = slotDate.minutes();
|
|
|
|
|
- s +=
|
|
|
|
|
- "<tr class='fc-slot" + slotCnt + ' ' + (!minutes ? '' : 'fc-minor') + "'>" +
|
|
|
|
|
- "<th class='fc-agenda-axis " + headerClass + "'>" +
|
|
|
|
|
- ((!slotNormal || !minutes) ?
|
|
|
|
|
- htmlEscape(formatDate(slotDate, opt('axisFormat'))) :
|
|
|
|
|
- ' '
|
|
|
|
|
- ) +
|
|
|
|
|
- "</th>" +
|
|
|
|
|
- "<td class='" + contentClass + "'>" +
|
|
|
|
|
- "<div style='position:relative'> </div>" +
|
|
|
|
|
- "</td>" +
|
|
|
|
|
- "</tr>";
|
|
|
|
|
- slotTime.add(slotDuration);
|
|
|
|
|
- slotCnt++;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ timeGrid: null, // the main time-grid subcomponent of this view
|
|
|
|
|
+ dayGrid: null, // the "all-day" subcomponent. if all-day is turned off, this will be null
|
|
|
|
|
|
|
|
- s +=
|
|
|
|
|
- "</tbody>" +
|
|
|
|
|
- "</table>";
|
|
|
|
|
|
|
+ axisWidth: null, // the width of the time axis running down the side
|
|
|
|
|
|
|
|
- slotTable = $(s).appendTo(slotContainer);
|
|
|
|
|
-
|
|
|
|
|
- slotBind(slotTable.find('td'));
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ noScrollRowEls: null, // set of fake row elements that must compensate when scrollerEl has scrollbars
|
|
|
|
|
|
|
|
|
|
+ // when the time-grid isn't tall enough to occupy the given height, we render an <hr> underneath
|
|
|
|
|
+ bottomRuleEl: null,
|
|
|
|
|
+ bottomRuleHeight: null,
|
|
|
|
|
|
|
|
|
|
|
|
|
- /* Build Day Table
|
|
|
|
|
- -----------------------------------------------------------------------*/
|
|
|
|
|
|
|
+ /* Rendering
|
|
|
|
|
+ ------------------------------------------------------------------------------------------------------------------*/
|
|
|
|
|
|
|
|
|
|
|
|
|
- function buildDayTable() {
|
|
|
|
|
- var html = buildDayTableHTML();
|
|
|
|
|
|
|
+ // Renders the view into `this.el`, which has already been assigned.
|
|
|
|
|
+ // `colCnt` has been calculated by a subclass and passed here.
|
|
|
|
|
+ render: function(colCnt) {
|
|
|
|
|
|
|
|
- if (dayTable) {
|
|
|
|
|
- dayTable.remove();
|
|
|
|
|
- }
|
|
|
|
|
- dayTable = $(html).appendTo(element);
|
|
|
|
|
-
|
|
|
|
|
- dayHead = dayTable.find('thead');
|
|
|
|
|
- dayHeadCells = dayHead.find('th').slice(1, -1); // exclude gutter
|
|
|
|
|
- dayBody = dayTable.find('tbody');
|
|
|
|
|
- dayBodyCells = dayBody.find('td').slice(0, -1); // exclude gutter
|
|
|
|
|
- dayBodyCellInners = dayBodyCells.find('> div');
|
|
|
|
|
- dayBodyCellContentInners = dayBodyCells.find('.fc-day-content > div');
|
|
|
|
|
-
|
|
|
|
|
- dayBodyFirstCell = dayBodyCells.eq(0);
|
|
|
|
|
- dayBodyFirstCellStretcher = dayBodyCellInners.eq(0);
|
|
|
|
|
-
|
|
|
|
|
- markFirstLast(dayHead.add(dayHead.find('tr')));
|
|
|
|
|
- markFirstLast(dayBody.add(dayBody.find('tr')));
|
|
|
|
|
-
|
|
|
|
|
- // TODO: now that we rebuild the cells every time, we should call dayRender
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ // needed for cell-to-date and date-to-cell calculations in View
|
|
|
|
|
+ this.rowCnt = 1;
|
|
|
|
|
+ this.colCnt = colCnt;
|
|
|
|
|
|
|
|
|
|
+ this.el.addClass('fc-agenda-view').html(this.renderHtml());
|
|
|
|
|
|
|
|
- function buildDayTableHTML() {
|
|
|
|
|
- var html =
|
|
|
|
|
- "<table style='width:100%' class='fc-agenda-days fc-border-separate' cellspacing='0'>" +
|
|
|
|
|
- buildDayTableHeadHTML() +
|
|
|
|
|
- buildDayTableBodyHTML() +
|
|
|
|
|
- "</table>";
|
|
|
|
|
|
|
+ // the element that wraps the time-grid that will probably scroll
|
|
|
|
|
+ this.scrollerEl = this.el.find('.fc-time-grid-container');
|
|
|
|
|
+ this.timeGrid.coordMap.containerEl = this.scrollerEl; // don't accept clicks/etc outside of this
|
|
|
|
|
|
|
|
- return html;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ this.timeGrid.el = this.el.find('.fc-time-grid');
|
|
|
|
|
+ this.timeGrid.render();
|
|
|
|
|
|
|
|
|
|
+ // the <hr> that sometimes displays under the time-grid
|
|
|
|
|
+ this.bottomRuleEl = $('<hr class="' + this.widgetHeaderClass + '"/>')
|
|
|
|
|
+ .appendTo(this.timeGrid.el); // inject it into the time-grid
|
|
|
|
|
|
|
|
- function buildDayTableHeadHTML() {
|
|
|
|
|
- var headerClass = tm + "-widget-header";
|
|
|
|
|
- var date;
|
|
|
|
|
- var html = '';
|
|
|
|
|
- var weekText;
|
|
|
|
|
- var col;
|
|
|
|
|
|
|
+ if (this.dayGrid) {
|
|
|
|
|
+ this.dayRowThemeClass = this.widgetHeaderClass; // forces this class on each day-row
|
|
|
|
|
|
|
|
- html +=
|
|
|
|
|
- "<thead>" +
|
|
|
|
|
- "<tr>";
|
|
|
|
|
|
|
+ this.dayGrid.el = this.el.find('.fc-day-grid');
|
|
|
|
|
+ this.dayGrid.render();
|
|
|
|
|
|
|
|
- if (opt('weekNumbers')) {
|
|
|
|
|
- date = cellToDate(0, 0);
|
|
|
|
|
- weekText = calculateWeekNumber(date);
|
|
|
|
|
- if (rtl) {
|
|
|
|
|
- weekText += opt('weekNumberTitle');
|
|
|
|
|
- }
|
|
|
|
|
- else {
|
|
|
|
|
- weekText = opt('weekNumberTitle') + weekText;
|
|
|
|
|
- }
|
|
|
|
|
- html +=
|
|
|
|
|
- "<th class='fc-agenda-axis fc-week-number " + headerClass + "'>" +
|
|
|
|
|
- htmlEscape(weekText) +
|
|
|
|
|
- "</th>";
|
|
|
|
|
- }
|
|
|
|
|
- else {
|
|
|
|
|
- html += "<th class='fc-agenda-axis " + headerClass + "'> </th>";
|
|
|
|
|
|
|
+ // have the day-grid extend it's coordinate area over the <hr> dividing the two grids
|
|
|
|
|
+ this.dayGrid.bottomCoordPadding = this.dayGrid.el.next('hr').outerHeight();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- for (col=0; col<colCnt; col++) {
|
|
|
|
|
- date = cellToDate(0, col);
|
|
|
|
|
- html +=
|
|
|
|
|
- "<th class='fc-" + dayIDs[date.day()] + " fc-col" + col + ' ' + headerClass + "'>" +
|
|
|
|
|
- htmlEscape(formatDate(date, colFormat)) +
|
|
|
|
|
- "</th>";
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ this.noScrollRowEls = this.el.find('.fc-row:not(.fc-scroller *)'); // fake rows not within the scroller
|
|
|
|
|
|
|
|
- html +=
|
|
|
|
|
- "<th class='fc-agenda-gutter " + headerClass + "'> </th>" +
|
|
|
|
|
- "</tr>" +
|
|
|
|
|
- "</thead>";
|
|
|
|
|
|
|
+ View.prototype.render.call(this); // call the super-method
|
|
|
|
|
|
|
|
- return html;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ this.resetScroll(); // do this after sizes have been set
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ // Builds the HTML skeleton for the view.
|
|
|
|
|
+ // The day-grid and time-grid components will render inside containers defined by this HTML.
|
|
|
|
|
+ renderHtml: function() {
|
|
|
|
|
+ return '' +
|
|
|
|
|
+ '<table>' +
|
|
|
|
|
+ '<thead>' +
|
|
|
|
|
+ '<tr>' +
|
|
|
|
|
+ '<td class="' + this.widgetHeaderClass + '">' +
|
|
|
|
|
+ this.timeGrid.headHtml() + // render the day-of-week headers
|
|
|
|
|
+ '</td>' +
|
|
|
|
|
+ '</tr>' +
|
|
|
|
|
+ '</thead>' +
|
|
|
|
|
+ '<tbody>' +
|
|
|
|
|
+ '<tr>' +
|
|
|
|
|
+ '<td class="' + this.widgetHeaderClass + '">' +
|
|
|
|
|
+ (this.dayGrid ?
|
|
|
|
|
+ '<div class="fc-day-grid"/>' +
|
|
|
|
|
+ '<hr class="' + this.widgetHeaderClass + '"/>' :
|
|
|
|
|
+ ''
|
|
|
|
|
+ ) +
|
|
|
|
|
+ '<div class="fc-time-grid-container">' +
|
|
|
|
|
+ '<div class="fc-time-grid"/>' +
|
|
|
|
|
+ '</div>' +
|
|
|
|
|
+ '</td>' +
|
|
|
|
|
+ '</tr>' +
|
|
|
|
|
+ '</tbody>' +
|
|
|
|
|
+ '</table>';
|
|
|
|
|
+ },
|
|
|
|
|
|
|
|
|
|
|
|
|
- function buildDayTableBodyHTML() {
|
|
|
|
|
- var headerClass = tm + "-widget-header"; // TODO: make these when updateOptions() called
|
|
|
|
|
- var contentClass = tm + "-widget-content";
|
|
|
|
|
|
|
+ // Generates the HTML that will go before the day-of week header cells.
|
|
|
|
|
+ // Queried by the TimeGrid subcomponent when generating rows. Ordering depends on isRTL.
|
|
|
|
|
+ headIntroHtml: function() {
|
|
|
var date;
|
|
var date;
|
|
|
- var today = calendar.getNow().stripTime();
|
|
|
|
|
- var col;
|
|
|
|
|
- var cellsHTML;
|
|
|
|
|
- var cellHTML;
|
|
|
|
|
- var classNames;
|
|
|
|
|
- var html = '';
|
|
|
|
|
-
|
|
|
|
|
- html +=
|
|
|
|
|
- "<tbody>" +
|
|
|
|
|
- "<tr>" +
|
|
|
|
|
- "<th class='fc-agenda-axis " + headerClass + "'> </th>";
|
|
|
|
|
-
|
|
|
|
|
- cellsHTML = '';
|
|
|
|
|
-
|
|
|
|
|
- for (col=0; col<colCnt; col++) {
|
|
|
|
|
-
|
|
|
|
|
- date = cellToDate(0, col);
|
|
|
|
|
-
|
|
|
|
|
- classNames = [
|
|
|
|
|
- 'fc-col' + col,
|
|
|
|
|
- 'fc-' + dayIDs[date.day()],
|
|
|
|
|
- contentClass
|
|
|
|
|
- ];
|
|
|
|
|
- if (date.isSame(today, 'day')) {
|
|
|
|
|
- classNames.push(
|
|
|
|
|
- tm + '-state-highlight',
|
|
|
|
|
- 'fc-today'
|
|
|
|
|
- );
|
|
|
|
|
- }
|
|
|
|
|
- else if (date < today) {
|
|
|
|
|
- classNames.push('fc-past');
|
|
|
|
|
|
|
+ var weekNumber;
|
|
|
|
|
+ var weekTitle;
|
|
|
|
|
+ var weekText;
|
|
|
|
|
+
|
|
|
|
|
+ if (this.opt('weekNumbers')) {
|
|
|
|
|
+ date = this.cellToDate(0, 0);
|
|
|
|
|
+ weekNumber = this.calendar.calculateWeekNumber(date);
|
|
|
|
|
+ weekTitle = this.opt('weekNumberTitle');
|
|
|
|
|
+
|
|
|
|
|
+ if (this.opt('isRTL')) {
|
|
|
|
|
+ weekText = weekNumber + weekTitle;
|
|
|
}
|
|
}
|
|
|
else {
|
|
else {
|
|
|
- classNames.push('fc-future');
|
|
|
|
|
|
|
+ weekText = weekTitle + weekNumber;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- cellHTML =
|
|
|
|
|
- "<td class='" + classNames.join(' ') + "'>" +
|
|
|
|
|
- "<div>" +
|
|
|
|
|
- "<div class='fc-day-content'>" +
|
|
|
|
|
- "<div style='position:relative'> </div>" +
|
|
|
|
|
- "</div>" +
|
|
|
|
|
- "</div>" +
|
|
|
|
|
- "</td>";
|
|
|
|
|
-
|
|
|
|
|
- cellsHTML += cellHTML;
|
|
|
|
|
|
|
+ return '' +
|
|
|
|
|
+ '<th class="fc-axis fc-week-number ' + this.widgetHeaderClass + '">' +
|
|
|
|
|
+ '<span>' + // needed for matchCellWidths
|
|
|
|
|
+ htmlEscape(weekText) +
|
|
|
|
|
+ '</span>' +
|
|
|
|
|
+ '</th>';
|
|
|
|
|
+ }
|
|
|
|
|
+ else {
|
|
|
|
|
+ return '<th class="fc-axis ' + this.widgetHeaderClass + '"' +
|
|
|
|
|
+ (this.axisWidth !== null ?
|
|
|
|
|
+ ' style="width:' + this.axisWidth + 'px"' :
|
|
|
|
|
+ ''
|
|
|
|
|
+ ) +
|
|
|
|
|
+ '></th>';
|
|
|
}
|
|
}
|
|
|
|
|
+ },
|
|
|
|
|
|
|
|
- html += cellsHTML;
|
|
|
|
|
- html +=
|
|
|
|
|
- "<td class='fc-agenda-gutter " + contentClass + "'> </td>" +
|
|
|
|
|
- "</tr>" +
|
|
|
|
|
- "</tbody>";
|
|
|
|
|
|
|
|
|
|
- return html;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ // Generates the HTML that goes before the all-day cells.
|
|
|
|
|
+ // Queried by the DayGrid subcomponent when generating rows. Ordering depends on isRTL.
|
|
|
|
|
+ dayIntroHtml: function() {
|
|
|
|
|
+ return '' +
|
|
|
|
|
+ '<td class="' + this.widgetHeaderClass + ' fc-axis">' +
|
|
|
|
|
+ '<span>' + // needed for matchCellWidths
|
|
|
|
|
+ (this.opt('allDayHTML') || htmlEscape(this.opt('allDayText'))) +
|
|
|
|
|
+ '</span>' +
|
|
|
|
|
+ '</td>';
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
|
|
|
|
|
+ // Generates the HTML that goes before all other types of cells.
|
|
|
|
|
+ // Affects content-skeleton, helper-skeleton, highlight-skeleton for both the time-grid and day-grid.
|
|
|
|
|
+ // Queried by the TimeGrid and DayGrid subcomponents when generating rows. Ordering depends on isRTL.
|
|
|
|
|
+ introHtml: function() {
|
|
|
|
|
+ return '<td class="fc-axis"' +
|
|
|
|
|
+ (this.axisWidth !== null ?
|
|
|
|
|
+ ' style="width:' + this.axisWidth + 'px"' :
|
|
|
|
|
+ ''
|
|
|
|
|
+ ) +
|
|
|
|
|
+ '></td>';
|
|
|
|
|
+ },
|
|
|
|
|
|
|
|
- // TODO: data-date on the cells
|
|
|
|
|
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
/* Dimensions
|
|
/* Dimensions
|
|
|
- -----------------------------------------------------------------------*/
|
|
|
|
|
|
|
+ ------------------------------------------------------------------------------------------------------------------*/
|
|
|
|
|
|
|
|
-
|
|
|
|
|
- function setHeight(height) {
|
|
|
|
|
- if (height === undefined) {
|
|
|
|
|
- height = viewHeight;
|
|
|
|
|
- }
|
|
|
|
|
- viewHeight = height;
|
|
|
|
|
- slotTopCache = {};
|
|
|
|
|
-
|
|
|
|
|
- var headHeight = dayBody.position().top;
|
|
|
|
|
- var allDayHeight = slotScroller.position().top; // including divider
|
|
|
|
|
- var bodyHeight = Math.min( // total body height, including borders
|
|
|
|
|
- height - headHeight, // when scrollbars
|
|
|
|
|
- slotTable.height() + allDayHeight + 1 // when no scrollbars. +1 for bottom border
|
|
|
|
|
- );
|
|
|
|
|
-
|
|
|
|
|
- dayBodyFirstCellStretcher
|
|
|
|
|
- .height(bodyHeight - vsides(dayBodyFirstCell));
|
|
|
|
|
-
|
|
|
|
|
- slotLayer.css('top', headHeight);
|
|
|
|
|
-
|
|
|
|
|
- slotScroller.height(bodyHeight - allDayHeight - 1);
|
|
|
|
|
-
|
|
|
|
|
- // the stylesheet guarantees that the first row has no border.
|
|
|
|
|
- // this allows .height() to work well cross-browser.
|
|
|
|
|
- var slotHeight0 = slotTable.find('tr:first').height() + 1; // +1 for bottom border
|
|
|
|
|
- var slotHeight1 = slotTable.find('tr:eq(1)').height();
|
|
|
|
|
- // HACK: i forget why we do this, but i think a cross-browser issue
|
|
|
|
|
- slotHeight = (slotHeight0 + slotHeight1) / 2;
|
|
|
|
|
-
|
|
|
|
|
- snapRatio = slotDuration / snapDuration;
|
|
|
|
|
- snapHeight = slotHeight / snapRatio;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
- function setWidth(width) {
|
|
|
|
|
- viewWidth = width;
|
|
|
|
|
- colPositions.clear();
|
|
|
|
|
- colContentPositions.clear();
|
|
|
|
|
-
|
|
|
|
|
- var axisFirstCells = dayHead.find('th:first');
|
|
|
|
|
- if (allDayTable) {
|
|
|
|
|
- axisFirstCells = axisFirstCells.add(allDayTable.find('th:first'));
|
|
|
|
|
- }
|
|
|
|
|
- axisFirstCells = axisFirstCells.add(slotTable.find('th:first'));
|
|
|
|
|
-
|
|
|
|
|
- axisWidth = 0;
|
|
|
|
|
- setOuterWidth(
|
|
|
|
|
- axisFirstCells
|
|
|
|
|
- .width('')
|
|
|
|
|
- .each(function(i, _cell) {
|
|
|
|
|
- axisWidth = Math.max(axisWidth, $(_cell).outerWidth());
|
|
|
|
|
- }),
|
|
|
|
|
- axisWidth
|
|
|
|
|
- );
|
|
|
|
|
-
|
|
|
|
|
- var gutterCells = dayTable.find('.fc-agenda-gutter');
|
|
|
|
|
- if (allDayTable) {
|
|
|
|
|
- gutterCells = gutterCells.add(allDayTable.find('th.fc-agenda-gutter'));
|
|
|
|
|
- }
|
|
|
|
|
|
|
|
|
|
- var slotTableWidth = slotScroller[0].clientWidth; // needs to be done after axisWidth (for IE7)
|
|
|
|
|
-
|
|
|
|
|
- gutterWidth = slotScroller.width() - slotTableWidth;
|
|
|
|
|
- if (gutterWidth) {
|
|
|
|
|
- setOuterWidth(gutterCells, gutterWidth);
|
|
|
|
|
- gutterCells
|
|
|
|
|
- .show()
|
|
|
|
|
- .prev()
|
|
|
|
|
- .removeClass('fc-last');
|
|
|
|
|
- }else{
|
|
|
|
|
- gutterCells
|
|
|
|
|
- .hide()
|
|
|
|
|
- .prev()
|
|
|
|
|
- .addClass('fc-last');
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- colWidth = Math.floor((slotTableWidth - axisWidth) / colCnt);
|
|
|
|
|
- setOuterWidth(dayHeadCells.slice(0, -1), colWidth);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
|
|
+ // Refreshes the horizontal dimensions of the view
|
|
|
|
|
+ updateWidth: function() {
|
|
|
|
|
+ // make all axis cells line up, and record the width so newly created axis cells will have it
|
|
|
|
|
+ this.axisWidth = matchCellWidths(this.el.find('.fc-axis'));
|
|
|
|
|
+ },
|
|
|
|
|
|
|
|
|
|
|
|
|
- /* Scrolling
|
|
|
|
|
- -----------------------------------------------------------------------*/
|
|
|
|
|
|
|
+ // Adjusts the vertical dimensions of the view to the specified values
|
|
|
|
|
+ setHeight: function(totalHeight, isAuto) {
|
|
|
|
|
+ var scrollerHeight;
|
|
|
|
|
+ var timeGridHeight;
|
|
|
|
|
+ var extraHeight; // # of pixels the time-grid element needs to expand to fill the scroller
|
|
|
|
|
|
|
|
|
|
+ if (this.bottomRuleHeight === null) {
|
|
|
|
|
+ // calculate the height of the rule the very first time
|
|
|
|
|
+ this.bottomRuleHeight = this.bottomRuleEl.outerHeight();
|
|
|
|
|
+ }
|
|
|
|
|
+ this.bottomRuleEl.hide(); // .show() will be called later if this <hr> is necessary
|
|
|
|
|
|
|
|
- function resetScroll() {
|
|
|
|
|
- var top = computeTimeTop(
|
|
|
|
|
- moment.duration(opt('scrollTime'))
|
|
|
|
|
- ) + 1; // +1 for the border
|
|
|
|
|
|
|
+ // reset all dimensions back to the original state
|
|
|
|
|
+ this.scrollerEl.height('').removeClass('fc-scroller');
|
|
|
|
|
+ uncompensateScroll(this.noScrollRowEls);
|
|
|
|
|
|
|
|
- function scroll() {
|
|
|
|
|
- slotScroller.scrollTop(top);
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ if (!isAuto) { // should we force dimensions of the scroll container, or let the contents be natural height?
|
|
|
|
|
|
|
|
- scroll();
|
|
|
|
|
- setTimeout(scroll, 0); // overrides any previous scroll state made by the browser
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ scrollerHeight = this.computeScrollerHeight(totalHeight);
|
|
|
|
|
+ timeGridHeight = this.timeGrid.el.height();
|
|
|
|
|
+ this.scrollerEl.height(scrollerHeight);
|
|
|
|
|
|
|
|
|
|
+ if (timeGridHeight > scrollerHeight) { // do we need scrollbars?
|
|
|
|
|
|
|
|
- function afterRender() { // after the view has been freshly rendered and sized
|
|
|
|
|
- resetScroll();
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
- /* Slot/Day clicking and binding
|
|
|
|
|
- -----------------------------------------------------------------------*/
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
- function dayBind(cells) {
|
|
|
|
|
- cells.click(slotClick)
|
|
|
|
|
- .mousedown(daySelectionMousedown);
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ // force scrollbars and make the all-day and header rows lines up
|
|
|
|
|
+ this.scrollerEl.addClass('fc-scroller');
|
|
|
|
|
+ compensateScroll(this.noScrollRowEls, getScrollbarWidths(this.scrollerEl));
|
|
|
|
|
|
|
|
|
|
+ // the scrollbar compensation might have changed text flow, which might affect height, so recalculate
|
|
|
|
|
+ // and reapply the desired height to the scroller.
|
|
|
|
|
+ scrollerHeight = this.computeScrollerHeight(totalHeight);
|
|
|
|
|
+ this.scrollerEl.height(scrollerHeight);
|
|
|
|
|
|
|
|
- function slotBind(cells) {
|
|
|
|
|
- cells.click(slotClick)
|
|
|
|
|
- .mousedown(slotSelectionMousedown);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
- function slotClick(ev) {
|
|
|
|
|
- if (!opt('selectable')) { // if selectable, SelectionManager will worry about dayClick
|
|
|
|
|
- var col = Math.min(colCnt-1, Math.floor((ev.pageX - dayTable.offset().left - axisWidth) / colWidth));
|
|
|
|
|
- var date = cellToDate(0, col);
|
|
|
|
|
- var match = this.parentNode.className.match(/fc-slot(\d+)/); // TODO: maybe use data
|
|
|
|
|
- if (match) {
|
|
|
|
|
- var slotIndex = parseInt(match[1], 10);
|
|
|
|
|
- date.add(minTime + slotIndex * slotDuration);
|
|
|
|
|
- date = calendar.rezoneDate(date);
|
|
|
|
|
- trigger(
|
|
|
|
|
- 'dayClick',
|
|
|
|
|
- dayBodyCells[col],
|
|
|
|
|
- date,
|
|
|
|
|
- ev
|
|
|
|
|
- );
|
|
|
|
|
- }else{
|
|
|
|
|
- trigger(
|
|
|
|
|
- 'dayClick',
|
|
|
|
|
- dayBodyCells[col],
|
|
|
|
|
- date,
|
|
|
|
|
- ev
|
|
|
|
|
- );
|
|
|
|
|
|
|
+ this.restoreScroll();
|
|
|
|
|
+ }
|
|
|
|
|
+ else {
|
|
|
|
|
+ // display the <hr> if there is enough extra space
|
|
|
|
|
+ extraHeight = scrollerHeight - timeGridHeight;
|
|
|
|
|
+ if (extraHeight > this.bottomRuleHeight + 5) {
|
|
|
|
|
+ this.bottomRuleEl.show();
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
- /* Semi-transparent Overlay Helpers
|
|
|
|
|
- -----------------------------------------------------*/
|
|
|
|
|
- // TODO: should be consolidated with BasicView's methods
|
|
|
|
|
|
|
+ },
|
|
|
|
|
|
|
|
|
|
|
|
|
- function renderDayOverlay(overlayStart, overlayEnd, refreshCoordinateGrid) { // overlayEnd is exclusive
|
|
|
|
|
|
|
+ // Sets the scroll value of the scroller to the intial pre-configured state prior to allowing the user to change it.
|
|
|
|
|
+ resetScroll: function() {
|
|
|
|
|
+ var _this = this;
|
|
|
|
|
+ var scrollTime = moment.duration(this.opt('scrollTime'));
|
|
|
|
|
+ var top = this.timeGrid.computeTimeTop(scrollTime);
|
|
|
|
|
|
|
|
- if (refreshCoordinateGrid) {
|
|
|
|
|
- coordinateGrid.build();
|
|
|
|
|
|
|
+ // zoom can give weird floating-point values. rather scroll a little bit further
|
|
|
|
|
+ top = Math.ceil(top);
|
|
|
|
|
+
|
|
|
|
|
+ if (top) {
|
|
|
|
|
+ top++; // to overcome top border that slots beyond the first have. looks better
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- var segments = rangeToSegments(overlayStart, overlayEnd);
|
|
|
|
|
-
|
|
|
|
|
- for (var i=0; i<segments.length; i++) {
|
|
|
|
|
- var segment = segments[i];
|
|
|
|
|
- dayBind(
|
|
|
|
|
- renderCellOverlay(
|
|
|
|
|
- segment.row,
|
|
|
|
|
- segment.leftCol,
|
|
|
|
|
- segment.row,
|
|
|
|
|
- segment.rightCol
|
|
|
|
|
- )
|
|
|
|
|
- );
|
|
|
|
|
|
|
+ function scroll() {
|
|
|
|
|
+ _this.scrollerEl.scrollTop(top);
|
|
|
}
|
|
}
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
- function renderCellOverlay(row0, col0, row1, col1) { // only for all-day?
|
|
|
|
|
- var rect = coordinateGrid.rect(row0, col0, row1, col1, slotLayer);
|
|
|
|
|
- return renderOverlay(rect, slotLayer);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
|
|
|
|
|
- function renderSlotOverlay(overlayStart, overlayEnd) {
|
|
|
|
|
|
|
+ scroll();
|
|
|
|
|
+ setTimeout(scroll, 0); // overrides any previous scroll state made by the browser
|
|
|
|
|
+ },
|
|
|
|
|
|
|
|
- // normalize, because dayStart/dayEnd have stripped time+zone
|
|
|
|
|
- overlayStart = overlayStart.clone().stripZone();
|
|
|
|
|
- overlayEnd = overlayEnd.clone().stripZone();
|
|
|
|
|
|
|
|
|
|
- for (var i=0; i<colCnt; i++) { // loop through the day columns
|
|
|
|
|
|
|
+ /* Events
|
|
|
|
|
+ ------------------------------------------------------------------------------------------------------------------*/
|
|
|
|
|
|
|
|
- var dayStart = cellToDate(0, i);
|
|
|
|
|
- var dayEnd = dayStart.clone().add('days', 1);
|
|
|
|
|
|
|
|
|
|
- var stretchStart = dayStart < overlayStart ? overlayStart : dayStart; // the max of the two
|
|
|
|
|
- var stretchEnd = dayEnd < overlayEnd ? dayEnd : overlayEnd; // the min of the two
|
|
|
|
|
|
|
+ // Renders events onto the view and populates the View's segment array
|
|
|
|
|
+ renderEvents: function(events) {
|
|
|
|
|
+ var dayEvents = [];
|
|
|
|
|
+ var timedEvents = [];
|
|
|
|
|
+ var daySegs = [];
|
|
|
|
|
+ var timedSegs;
|
|
|
|
|
+ var i;
|
|
|
|
|
|
|
|
- if (stretchStart < stretchEnd) {
|
|
|
|
|
- var rect = coordinateGrid.rect(0, i, 0, i, slotContainer); // only use it for horizontal coords
|
|
|
|
|
- var top = computeDateTop(stretchStart, dayStart);
|
|
|
|
|
- var bottom = computeDateTop(stretchEnd, dayStart);
|
|
|
|
|
-
|
|
|
|
|
- rect.top = top;
|
|
|
|
|
- rect.height = bottom - top;
|
|
|
|
|
- slotBind(
|
|
|
|
|
- renderOverlay(rect, slotContainer)
|
|
|
|
|
- );
|
|
|
|
|
|
|
+ // separate the events into all-day and timed
|
|
|
|
|
+ for (i = 0; i < events.length; i++) {
|
|
|
|
|
+ if (events[i].allDay) {
|
|
|
|
|
+ dayEvents.push(events[i]);
|
|
|
}
|
|
}
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
- /* Coordinate Utilities
|
|
|
|
|
- -----------------------------------------------------------------------------*/
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
- coordinateGrid = new CoordinateGrid(function(rows, cols) {
|
|
|
|
|
- var e, n, p;
|
|
|
|
|
- dayHeadCells.each(function(i, _e) {
|
|
|
|
|
- e = $(_e);
|
|
|
|
|
- n = e.offset().left;
|
|
|
|
|
- if (i) {
|
|
|
|
|
- p[1] = n;
|
|
|
|
|
|
|
+ else {
|
|
|
|
|
+ timedEvents.push(events[i]);
|
|
|
}
|
|
}
|
|
|
- p = [n];
|
|
|
|
|
- cols[i] = p;
|
|
|
|
|
- });
|
|
|
|
|
- p[1] = n + e.outerWidth();
|
|
|
|
|
- if (opt('allDaySlot')) {
|
|
|
|
|
- e = allDayRow;
|
|
|
|
|
- n = e.offset().top;
|
|
|
|
|
- rows[0] = [n, n+e.outerHeight()];
|
|
|
|
|
- }
|
|
|
|
|
- var slotTableTop = slotContainer.offset().top;
|
|
|
|
|
- var slotScrollerTop = slotScroller.offset().top;
|
|
|
|
|
- var slotScrollerBottom = slotScrollerTop + slotScroller.outerHeight();
|
|
|
|
|
- function constrain(n) {
|
|
|
|
|
- return Math.max(slotScrollerTop, Math.min(slotScrollerBottom, n));
|
|
|
|
|
- }
|
|
|
|
|
- for (var i=0; i<slotCnt*snapRatio; i++) { // adapt slot count to increased/decreased selection slot count
|
|
|
|
|
- rows.push([
|
|
|
|
|
- constrain(slotTableTop + snapHeight*i),
|
|
|
|
|
- constrain(slotTableTop + snapHeight*(i+1))
|
|
|
|
|
- ]);
|
|
|
|
|
}
|
|
}
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
- hoverListener = new HoverListener(coordinateGrid);
|
|
|
|
|
-
|
|
|
|
|
- colPositions = new HorizontalPositionCache(function(col) {
|
|
|
|
|
- return dayBodyCellInners.eq(col);
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- colContentPositions = new HorizontalPositionCache(function(col) {
|
|
|
|
|
- return dayBodyCellContentInners.eq(col);
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
- function colLeft(col) {
|
|
|
|
|
- return colPositions.left(col);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
- function colContentLeft(col) {
|
|
|
|
|
- return colContentPositions.left(col);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
- function colRight(col) {
|
|
|
|
|
- return colPositions.right(col);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
- function colContentRight(col) {
|
|
|
|
|
- return colContentPositions.right(col);
|
|
|
|
|
- }
|
|
|
|
|
|
|
|
|
|
|
|
+ // render the events in the subcomponents
|
|
|
|
|
+ timedSegs = this.timeGrid.renderEvents(timedEvents);
|
|
|
|
|
+ if (this.dayGrid) {
|
|
|
|
|
+ daySegs = this.dayGrid.renderEvents(dayEvents);
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- // NOTE: the row index of these "cells" doesn't correspond to the slot index, but rather the "snap" index
|
|
|
|
|
|
|
+ // the all-day area is flexible and might have a lot of events, so shift the height
|
|
|
|
|
+ this.updateHeight();
|
|
|
|
|
|
|
|
|
|
+ this.segs = daySegs.concat(timedSegs); // needed by the View super-class
|
|
|
|
|
|
|
|
- function getIsCellAllDay(cell) { // TODO: remove because mom.hasTime() from realCellToDate() is better
|
|
|
|
|
- return opt('allDaySlot') && !cell.row;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ View.prototype.renderEvents.call(this, events); // call the super-method
|
|
|
|
|
+ },
|
|
|
|
|
|
|
|
|
|
|
|
|
- function realCellToDate(cell) { // ugh "real" ... but blame it on our abuse of the "cell" system
|
|
|
|
|
- var date = cellToDate(0, cell.col);
|
|
|
|
|
- var snapIndex = cell.row;
|
|
|
|
|
|
|
+ // Unrenders all event elements and clears internal segment data
|
|
|
|
|
+ destroyEvents: function() {
|
|
|
|
|
|
|
|
- if (opt('allDaySlot')) {
|
|
|
|
|
- snapIndex--;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ // if destroyEvents is being called as part of an event rerender, renderEvents will be called shortly
|
|
|
|
|
+ // after, so remember what the scroll value was so we can restore it.
|
|
|
|
|
+ this.recordScroll();
|
|
|
|
|
|
|
|
- if (snapIndex >= 0) {
|
|
|
|
|
- date.time(moment.duration(minTime + snapIndex * snapDuration));
|
|
|
|
|
- date = calendar.rezoneDate(date);
|
|
|
|
|
|
|
+ // destroy the events in the subcomponents
|
|
|
|
|
+ this.timeGrid.destroyEvents();
|
|
|
|
|
+ if (this.dayGrid) {
|
|
|
|
|
+ this.dayGrid.destroyEvents();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- return date;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ this.updateHeight();
|
|
|
|
|
|
|
|
|
|
+ View.prototype.destroyEvents.call(this); // call the super-method. will kill `this.segs`
|
|
|
|
|
+ },
|
|
|
|
|
|
|
|
- function computeDateTop(date, startOfDayDate) {
|
|
|
|
|
- return computeTimeTop(
|
|
|
|
|
- moment.duration(
|
|
|
|
|
- date.clone().stripZone() - startOfDayDate.clone().stripTime()
|
|
|
|
|
- )
|
|
|
|
|
- );
|
|
|
|
|
- }
|
|
|
|
|
|
|
|
|
|
|
|
+ /* Event Dragging
|
|
|
|
|
+ ------------------------------------------------------------------------------------------------------------------*/
|
|
|
|
|
|
|
|
- function computeTimeTop(time) { // time is a duration
|
|
|
|
|
|
|
|
|
|
- if (time < minTime) {
|
|
|
|
|
- return 0;
|
|
|
|
|
|
|
+ // Renders a visual indication of an event being dragged over the view.
|
|
|
|
|
+ // A returned value of `true` signals that a mock "helper" event has been rendered.
|
|
|
|
|
+ renderDrag: function(start, end, seg) {
|
|
|
|
|
+ if (start.hasTime()) {
|
|
|
|
|
+ return this.timeGrid.renderDrag(start, end, seg);
|
|
|
}
|
|
}
|
|
|
- if (time >= maxTime) {
|
|
|
|
|
- return slotTable.height();
|
|
|
|
|
|
|
+ else if (this.dayGrid) {
|
|
|
|
|
+ return this.dayGrid.renderDrag(start, end, seg);
|
|
|
}
|
|
}
|
|
|
|
|
+ },
|
|
|
|
|
|
|
|
- var slots = (time - minTime) / slotDuration;
|
|
|
|
|
- var slotIndex = Math.floor(slots);
|
|
|
|
|
- var slotPartial = slots - slotIndex;
|
|
|
|
|
- var slotTop = slotTopCache[slotIndex];
|
|
|
|
|
-
|
|
|
|
|
- // find the position of the corresponding <tr>
|
|
|
|
|
- // need to use this tecnhique because not all rows are rendered at same height sometimes.
|
|
|
|
|
- if (slotTop === undefined) {
|
|
|
|
|
- slotTop = slotTopCache[slotIndex] =
|
|
|
|
|
- slotTable.find('tr').eq(slotIndex).find('td div')[0].offsetTop;
|
|
|
|
|
- // .eq() is faster than ":eq()" selector
|
|
|
|
|
- // [0].offsetTop is faster than .position().top (do we really need this optimization?)
|
|
|
|
|
- // a better optimization would be to cache all these divs
|
|
|
|
|
- }
|
|
|
|
|
|
|
|
|
|
- var top =
|
|
|
|
|
- slotTop - 1 + // because first row doesn't have a top border
|
|
|
|
|
- slotPartial * slotHeight; // part-way through the row
|
|
|
|
|
|
|
+ // Unrenders a visual indications of an event being dragged over the view
|
|
|
|
|
+ destroyDrag: function() {
|
|
|
|
|
+ this.timeGrid.destroyDrag();
|
|
|
|
|
+ if (this.dayGrid) {
|
|
|
|
|
+ this.dayGrid.destroyDrag();
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
|
|
|
- top = Math.max(top, 0);
|
|
|
|
|
|
|
|
|
|
- return top;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
/* Selection
|
|
/* Selection
|
|
|
- ---------------------------------------------------------------------------------*/
|
|
|
|
|
|
|
+ ------------------------------------------------------------------------------------------------------------------*/
|
|
|
|
|
|
|
|
-
|
|
|
|
|
- function defaultSelectionEnd(start) {
|
|
|
|
|
- if (start.hasTime()) {
|
|
|
|
|
- return start.clone().add(slotDuration);
|
|
|
|
|
- }
|
|
|
|
|
- else {
|
|
|
|
|
- return start.clone().add('days', 1);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
- function renderSelection(start, end) {
|
|
|
|
|
|
|
+
|
|
|
|
|
+ // Renders a visual indication of a selection
|
|
|
|
|
+ renderSelection: function(start, end) {
|
|
|
if (start.hasTime() || end.hasTime()) {
|
|
if (start.hasTime() || end.hasTime()) {
|
|
|
- renderSlotSelection(start, end);
|
|
|
|
|
|
|
+ this.timeGrid.renderSelection(start, end);
|
|
|
}
|
|
}
|
|
|
- else if (opt('allDaySlot')) {
|
|
|
|
|
- renderDayOverlay(start, end, true); // true for refreshing coordinate grid
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
- function renderSlotSelection(startDate, endDate) {
|
|
|
|
|
- var helperOption = opt('selectHelper');
|
|
|
|
|
- coordinateGrid.build();
|
|
|
|
|
- if (helperOption) {
|
|
|
|
|
- var col = dateToCell(startDate).col;
|
|
|
|
|
- if (col >= 0 && col < colCnt) { // only works when times are on same day
|
|
|
|
|
- var rect = coordinateGrid.rect(0, col, 0, col, slotContainer); // only for horizontal coords
|
|
|
|
|
- var top = computeDateTop(startDate, startDate);
|
|
|
|
|
- var bottom = computeDateTop(endDate, startDate);
|
|
|
|
|
- if (bottom > top) { // protect against selections that are entirely before or after visible range
|
|
|
|
|
- rect.top = top;
|
|
|
|
|
- rect.height = bottom - top;
|
|
|
|
|
- rect.left += 2;
|
|
|
|
|
- rect.width -= 5;
|
|
|
|
|
- if ($.isFunction(helperOption)) {
|
|
|
|
|
- var helperRes = helperOption(startDate, endDate);
|
|
|
|
|
- if (helperRes) {
|
|
|
|
|
- rect.position = 'absolute';
|
|
|
|
|
- selectionHelper = $(helperRes)
|
|
|
|
|
- .css(rect)
|
|
|
|
|
- .appendTo(slotContainer);
|
|
|
|
|
- }
|
|
|
|
|
- }else{
|
|
|
|
|
- rect.isStart = true; // conside rect a "seg" now
|
|
|
|
|
- rect.isEnd = true; //
|
|
|
|
|
- selectionHelper = $(slotSegHtml(
|
|
|
|
|
- {
|
|
|
|
|
- title: '',
|
|
|
|
|
- start: startDate,
|
|
|
|
|
- end: endDate,
|
|
|
|
|
- className: ['fc-select-helper'],
|
|
|
|
|
- editable: false
|
|
|
|
|
- },
|
|
|
|
|
- rect
|
|
|
|
|
- ));
|
|
|
|
|
- selectionHelper.css('opacity', opt('dragOpacity'));
|
|
|
|
|
- }
|
|
|
|
|
- if (selectionHelper) {
|
|
|
|
|
- slotBind(selectionHelper);
|
|
|
|
|
- slotContainer.append(selectionHelper);
|
|
|
|
|
- setOuterWidth(selectionHelper, rect.width, true); // needs to be after appended
|
|
|
|
|
- setOuterHeight(selectionHelper, rect.height, true);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }else{
|
|
|
|
|
- renderSlotOverlay(startDate, endDate);
|
|
|
|
|
|
|
+ else if (this.dayGrid) {
|
|
|
|
|
+ this.dayGrid.renderSelection(start, end);
|
|
|
}
|
|
}
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
- function clearSelection() {
|
|
|
|
|
- clearOverlays();
|
|
|
|
|
- if (selectionHelper) {
|
|
|
|
|
- selectionHelper.remove();
|
|
|
|
|
- selectionHelper = null;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
- function slotSelectionMousedown(ev) {
|
|
|
|
|
- if (ev.which == 1 && opt('selectable')) { // ev.which==1 means left mouse button
|
|
|
|
|
- unselect(ev);
|
|
|
|
|
- var dates;
|
|
|
|
|
- hoverListener.start(function(cell, origCell) {
|
|
|
|
|
- clearSelection();
|
|
|
|
|
- if (cell && cell.col == origCell.col && !getIsCellAllDay(cell)) {
|
|
|
|
|
- var d1 = realCellToDate(origCell);
|
|
|
|
|
- var d2 = realCellToDate(cell);
|
|
|
|
|
- dates = [
|
|
|
|
|
- d1,
|
|
|
|
|
- d1.clone().add(snapDuration), // calculate minutes depending on selection slot minutes
|
|
|
|
|
- d2,
|
|
|
|
|
- d2.clone().add(snapDuration)
|
|
|
|
|
- ].sort(dateCompare);
|
|
|
|
|
- renderSlotSelection(dates[0], dates[3]);
|
|
|
|
|
- }else{
|
|
|
|
|
- dates = null;
|
|
|
|
|
- }
|
|
|
|
|
- }, ev);
|
|
|
|
|
- $(document).one('mouseup', function(ev) {
|
|
|
|
|
- hoverListener.stop();
|
|
|
|
|
- if (dates) {
|
|
|
|
|
- if (+dates[0] == +dates[1]) {
|
|
|
|
|
- reportDayClick(dates[0], ev);
|
|
|
|
|
- }
|
|
|
|
|
- reportSelection(dates[0], dates[3], ev);
|
|
|
|
|
- }
|
|
|
|
|
- });
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ },
|
|
|
|
|
|
|
|
|
|
|
|
|
- function reportDayClick(date, ev) {
|
|
|
|
|
- trigger('dayClick', dayBodyCells[dateToCell(date).col], date, ev);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
- /* External Dragging
|
|
|
|
|
- --------------------------------------------------------------------------------*/
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
- function dragStart(_dragElement, ev, ui) {
|
|
|
|
|
- hoverListener.start(function(cell) {
|
|
|
|
|
- clearOverlays();
|
|
|
|
|
- if (cell) {
|
|
|
|
|
- var d1 = realCellToDate(cell);
|
|
|
|
|
- var d2 = d1.clone();
|
|
|
|
|
- if (d1.hasTime()) {
|
|
|
|
|
- d2.add(calendar.defaultTimedEventDuration);
|
|
|
|
|
- renderSlotOverlay(d1, d2);
|
|
|
|
|
- }
|
|
|
|
|
- else {
|
|
|
|
|
- d2.add(calendar.defaultAllDayEventDuration);
|
|
|
|
|
- renderDayOverlay(d1, d2);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }, ev);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
- function dragStop(_dragElement, ev, ui) {
|
|
|
|
|
- var cell = hoverListener.stop();
|
|
|
|
|
- clearOverlays();
|
|
|
|
|
- if (cell) {
|
|
|
|
|
- trigger(
|
|
|
|
|
- 'drop',
|
|
|
|
|
- _dragElement,
|
|
|
|
|
- realCellToDate(cell),
|
|
|
|
|
- ev,
|
|
|
|
|
- ui
|
|
|
|
|
- );
|
|
|
|
|
|
|
+ // Unrenders a visual indications of a selection
|
|
|
|
|
+ destroySelection: function() {
|
|
|
|
|
+ this.timeGrid.destroySelection();
|
|
|
|
|
+ if (this.dayGrid) {
|
|
|
|
|
+ this.dayGrid.destroySelection();
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
|
|
|
|
|
-}
|
|
|
|
|
|
|
+});
|