diff --git a/src/resource/gantt/HAgendaView.js b/src/resource/gantt/HAgendaView.js
new file mode 100644
index 0000000..7335cb7
--- /dev/null
+++ b/src/resource/gantt/HAgendaView.js
@@ -0,0 +1,430 @@
+
+/* 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({
+ allDaySlot: true,
+ allDayText: 'all-day',
+
+ scrollTime: '06:00:00',
+
+ slotDuration: '00:30:00',
+
+ axisFormat: generateAgendaAxisFormat,
+ timeFormat: {
+ agenda: generateAgendaTimeFormat
+ },
+
+ minTime: '00:00:00',
+ maxTime: '24:00:00',
+ slotEventOverlap: true
+});
+
+var AGENDA_ALL_DAY_EVENT_LIMIT = 5;
+
+
+function generateAgendaAxisFormat(options, langData) {
+ return langData.longDateFormat('LT')
+ .replace(':mm', '(:mm)')
+ .replace(/(\Wmm)$/, '($1)') // like above, but for foreign langs
+ .replace(/\s*a$/i, 'a'); // convert AM/PM/am/pm to lowercase. remove any spaces beforehand
+}
+
+
+function generateAgendaTimeFormat(options, langData) {
+ return langData.longDateFormat('LT')
+ .replace(/\s*a$/i, ''); // remove trailing AM/PM
+}
+
+
+function HAgendaView(calendar) {
+ View.call(this, calendar); // call the super-constructor
+
+ this.timeGrid = new HTimeGrid(this);
+
+ if (this.opt('allDaySlot')) { // should we display the "all-day" area?
+ this.dayGrid = new DayGrid(this); // the all-day subcomponent of this view
+
+ // 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;
+ }
+}
+
+
+HAgendaView.prototype = createObject(View.prototype); // define the super-class
+$.extend(HAgendaView.prototype, {
+
+ 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
+
+ axisWidth: null, // the width of the time axis running down the side
+
+ 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
underneath
+ bottomRuleEl: null,
+ bottomRuleHeight: null,
+
+
+ /* Rendering
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ // Renders the view into `this.el`, which has already been assigned.
+ // `colCnt` has been calculated by a subclass and passed here.
+ render: function(colCnt) {
+
+ // 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());
+
+ // 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
+
+ this.timeGrid.el = this.el.find('.fc-time-grid');
+ this.timeGrid.render();
+
+ // the
that sometimes displays under the time-grid
+ this.bottomRuleEl = $('')
+ .appendTo(this.timeGrid.el); // inject it into the time-grid
+
+ if (this.dayGrid) {
+ this.dayGrid.el = this.el.find('.fc-day-grid');
+ this.dayGrid.render();
+
+ // have the day-grid extend it's coordinate area over the
dividing the two grids
+ this.dayGrid.bottomCoordPadding = this.dayGrid.el.next('hr').outerHeight();
+ }
+
+ this.noScrollRowEls = this.el.find('.fc-row:not(.fc-scroller *)'); // fake rows not within the scroller
+
+ View.prototype.render.call(this); // call the super-method
+
+ this.resetScroll(); // do this after sizes have been set
+ },
+
+
+ // Make subcomponents ready for cleanup
+ destroy: function() {
+ this.timeGrid.destroy();
+ if (this.dayGrid) {
+ this.dayGrid.destroy();
+ }
+ View.prototype.destroy.call(this); // call the super-method
+ },
+
+
+ // 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 '' +
+ '' +
+ '' +
+ '' +
+ '' +
+ '
' +
+ '' +
+ '' +
+ '' +
+ '| ' +
+ (this.dayGrid ?
+ '' +
+ '' :
+ ''
+ ) +
+ '' +
+ ' | ' +
+ '
' +
+ '' +
+ '
';
+ },
+
+
+ // 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 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 {
+ weekText = weekTitle + weekNumber;
+ }
+
+ return '' +
+ '';
+ }
+ else {
+ return '';
+ }
+ },
+
+
+ // 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 '' +
+ '' +
+ '' + // needed for matchCellWidths
+ (this.opt('allDayHtml') || htmlEscape(this.opt('allDayText'))) +
+ '' +
+ ' | ';
+ },
+
+
+ // Generates the HTML that goes before the bg of the TimeGrid slot area. Long vertical column.
+ slotBgIntroHtml: function() {
+ return ' | ';
+ },
+
+
+ // 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 ' | ';
+ },
+
+
+ // Generates an HTML attribute string for setting the width of the axis, if it is known
+ axisStyleAttr: function() {
+ if (this.axisWidth !== null) {
+ return 'style="width:' + this.axisWidth + 'px"';
+ }
+ return '';
+ },
+
+
+ /* Dimensions
+ ------------------------------------------------------------------------------------------------------------------*/
+
+ updateSize: function(isResize) {
+ if (isResize) {
+ this.timeGrid.resize();
+ }
+ View.prototype.updateSize.call(this, isResize);
+ },
+
+
+ // 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'));
+ },
+
+
+ // Adjusts the vertical dimensions of the view to the specified values
+ setHeight: function(totalHeight, isAuto) {
+ var eventLimit;
+ var scrollerHeight;
+
+ 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
is necessary
+
+ // reset all dimensions back to the original state
+ this.scrollerEl.css('overflow', '');
+ unsetScroller(this.scrollerEl);
+ uncompensateScroll(this.noScrollRowEls);
+
+ // limit number of events in the all-day area
+ if (this.dayGrid) {
+ this.dayGrid.destroySegPopover(); // kill the "more" popover if displayed
+
+ eventLimit = this.opt('eventLimit');
+ if (eventLimit && typeof eventLimit !== 'number') {
+ eventLimit = AGENDA_ALL_DAY_EVENT_LIMIT; // make sure "auto" goes to a real number
+ }
+ if (eventLimit) {
+ this.dayGrid.limitRows(eventLimit);
+ }
+ }
+
+ if (!isAuto) { // should we force dimensions of the scroll container, or let the contents be natural height?
+
+ scrollerHeight = this.computeScrollerHeight(totalHeight);
+ if (setPotentialScroller(this.scrollerEl, scrollerHeight)) { // using scrollbars?
+
+ // make the all-day and header rows lines up
+ 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);
+
+ this.restoreScroll();
+ }
+ else { // no scrollbars
+ // still, force a height and display the bottom rule (marks the end of day)
+ this.scrollerEl.height(scrollerHeight).css('overflow', 'hidden'); // in case
goes outside
+ this.bottomRuleEl.show();
+ }
+ }
+ },
+
+
+ // 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);
+
+ // 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
+ }
+
+ function scroll() {
+ _this.scrollerEl.scrollTop(top);
+ }
+
+ scroll();
+ setTimeout(scroll, 0); // overrides any previous scroll state made by the browser
+ },
+
+
+ /* Events
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ // 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;
+
+ // separate the events into all-day and timed
+ for (i = 0; i < events.length; i++) {
+ if (events[i].allDay) {
+ dayEvents.push(events[i]);
+ }
+ else {
+ timedEvents.push(events[i]);
+ }
+ }
+
+ // render the events in the subcomponents
+ timedSegs = this.timeGrid.renderEvents(timedEvents);
+ if (this.dayGrid) {
+ daySegs = this.dayGrid.renderEvents(dayEvents);
+ }
+
+ // the all-day area is flexible and might have a lot of events, so shift the height
+ this.updateHeight();
+
+ View.prototype.renderEvents.call(this, events); // call the super-method
+ },
+
+
+ // Retrieves all segment objects that are rendered in the view
+ getSegs: function() {
+ return this.timeGrid.getSegs().concat(
+ this.dayGrid ? this.dayGrid.getSegs() : []
+ );
+ },
+
+
+ // Unrenders all event elements and clears internal segment data
+ destroyEvents: function() {
+ View.prototype.destroyEvents.call(this); // do this before the grids' segs have been cleared
+
+ // 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();
+
+ // destroy the events in the subcomponents
+ this.timeGrid.destroyEvents();
+ if (this.dayGrid) {
+ this.dayGrid.destroyEvents();
+ }
+
+ // we DON'T need to call updateHeight() because:
+ // A) a renderEvents() call always happens after this, which will eventually call updateHeight()
+ // B) in IE8, this causes a flash whenever events are rerendered
+ },
+
+
+ /* Event Dragging
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ // 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);
+ }
+ else if (this.dayGrid) {
+ return this.dayGrid.renderDrag(start, end, seg);
+ }
+ },
+
+
+ // Unrenders a visual indications of an event being dragged over the view
+ destroyDrag: function() {
+ this.timeGrid.destroyDrag();
+ if (this.dayGrid) {
+ this.dayGrid.destroyDrag();
+ }
+ },
+
+
+ /* Selection
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ // Renders a visual indication of a selection
+ renderSelection: function(start, end) {
+ if (start.hasTime() || end.hasTime()) {
+ this.timeGrid.renderSelection(start, end);
+ }
+ else if (this.dayGrid) {
+ this.dayGrid.renderSelection(start, end);
+ }
+ },
+
+
+ // Unrenders a visual indications of a selection
+ destroySelection: function() {
+ this.timeGrid.destroySelection();
+ if (this.dayGrid) {
+ this.dayGrid.destroySelection();
+ }
+ }
+
+});
diff --git a/src/resource/gantt/HResourceDayView.js b/src/resource/gantt/HResourceDayView.js
new file mode 100644
index 0000000..e5fe991
--- /dev/null
+++ b/src/resource/gantt/HResourceDayView.js
@@ -0,0 +1,62 @@
+
+/* A day view with an all-day cell area at the top, and a time grid below by resource
+----------------------------------------------------------------------------------------------------------------------*/
+
+fcViews.hResourceDay = HResourceDayView;
+
+function HResourceDayView(calendar) { // TODO: make a ResourceView mixin
+ HResourceView.call(this, calendar); // call the super-constructor
+}
+
+HResourceDayView.prototype = createObject(HResourceView.prototype); // define the super-class
+$.extend(HResourceDayView.prototype, {
+
+ name: 'hResourceDay',
+
+
+ incrementDate: function(date, delta) {
+ var out = date.clone().stripTime().add(delta, 'days');
+ out = this.skipHiddenDays(out, delta < 0 ? -1 : 1);
+ return out;
+ },
+
+
+ render: function(date) {
+ this.start = this.intervalStart = date.clone().stripTime();
+ this.end = this.intervalEnd = this.start.clone().add(1, 'days');
+
+ this.title = this.calendar.formatDate(this.start, this.opt('titleFormat'));
+
+ HResourceView.prototype.render.call(this, 12); // call the super-method
+ },
+
+ // Computes HTML classNames for a single-day cell
+ getDayClasses: function(date) {
+ var view = this.view;
+ var today = view.calendar.getNow().stripTime();
+ var classes = [ 'fc-' + dayIDs[date.day()] ];
+
+ if (
+ view.name === 'month' &&
+ date.month() != view.intervalStart.month()
+ ) {
+ classes.push('fc-other-month');
+ }
+
+ if (date.isSame(today, 'day')) {
+ classes.push(
+ 'fc-todaysss',
+ view.highlightStateClass
+ );
+ }
+ else if (date < today) {
+ classes.push('fc-past');
+ }
+ else {
+ classes.push('fc-future');
+ }
+
+ return classes;
+ }
+
+});
\ No newline at end of file
diff --git a/src/resource/gantt/HResourceTimeGrid.js b/src/resource/gantt/HResourceTimeGrid.js
new file mode 100644
index 0000000..bb2f726
--- /dev/null
+++ b/src/resource/gantt/HResourceTimeGrid.js
@@ -0,0 +1,46 @@
+
+/* A component that renders one or more columns of vertical time slots
+----------------------------------------------------------------------------------------------------------------------*/
+
+function HResourceTimeGrid(view) {
+ TimeGrid.call(this, view); // call the super-constructor
+}
+
+
+HResourceTimeGrid.prototype = createObject(TimeGrid.prototype); // define the super-class
+$.extend(HResourceTimeGrid.prototype, {
+// Slices up a date range into a segment for each column
+// Each column represents a resource. An event can be assigned to multiple resources
+// so we need to build segs accordingly
+ rangeToSegs: function(rangeStart, rangeEnd, resourceIds) {
+ var view = this.view;
+ var segs = [];
+ var seg;
+ var col;
+ var cellDate;
+ var colStart, colEnd;
+
+ var resources = view.calendar.fetchResources();
+
+ // normalize
+ rangeStart = rangeStart.clone().stripZone();
+ rangeEnd = rangeEnd.clone().stripZone();
+
+ for (col = 0; col < view.colCnt; col++) {
+ var resource = resources[col];
+ if (resource && resourceIds && resourceIds.indexOf(resource.id) > -1){
+ cellDate = view.cellToDate(0, col); // use the View's cell system for this
+ colStart = cellDate.clone().time(this.minTime);
+ colEnd = cellDate.clone().time(this.maxTime);
+ seg = intersectionToSeg(rangeStart, rangeEnd, colStart, colEnd);
+ if (seg) {
+ seg.col = col;
+ segs.push(seg);
+ }
+ }
+ }
+
+ return segs;
+ }
+
+});
diff --git a/src/resource/gantt/HResourceView.js b/src/resource/gantt/HResourceView.js
new file mode 100644
index 0000000..c678ec7
--- /dev/null
+++ b/src/resource/gantt/HResourceView.js
@@ -0,0 +1,84 @@
+
+/* 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({
+ allDaySlot: false,
+ allDayText: 'all-day',
+
+ scrollTime: '06:00:00',
+
+ slotDuration: '00:30:00',
+
+ axisFormat: generateAgendaAxisFormat,
+ timeFormat: {
+ agenda: generateAgendaTimeFormat
+ },
+
+ minTime: '00:00:00',
+ maxTime: '24:00:00',
+ slotEventOverlap: true
+});
+
+function HResourceView(calendar) {
+ View.call(this, calendar); // call the super-constructor
+
+ // overrides - the view.js should expose these on the prototype then we wouldn't have to do this.
+ this.cellToDate = HResourceView.prototype.cellToDate;
+
+ this.timeGrid = new HResourceTimeGrid(this);
+
+ // if (this.opt('allDaySlot')) { // should we display the "all-day" area?
+ // this.dayGrid = new ResourceDayGrid(this); // the all-day subcomponent of this view
+
+ // // 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;
+ // }
+}
+
+
+HResourceView.prototype = createObject(HAgendaView.prototype); // define the super-class
+$.extend(HResourceView.prototype, {
+
+ cellToDate: function() {
+ return this.start.clone();
+ },
+
+ // fix the classes, etc.
+ headCellHtml: function(row, col, date) {
+ var view = this;
+ var dateCell = date.add(col, 'hour').clone();
+
+ return '' +
+ '';
+ }
+
+// slotBgCellHtml: function(row, col, date) {
+ // var view = this.view;
+ // var classes = this.getDayClasses(date);
+
+ // classes.unshift('fc-day', view.widgetContentClass);
+
+ // return ' | ';
+ // }
+
+ // slotBgCellHtml: function(row, col, date) {
+ // var view = this.view;
+ // var classes = this.getDayClasses(date);
+
+ // classes.unshift('fc-day', view.widgetContentClass);
+
+ // return ' | ';
+ // }
+
+});
diff --git a/src/resource/gantt/HTimeGrid.js b/src/resource/gantt/HTimeGrid.js
new file mode 100644
index 0000000..3e971f8
--- /dev/null
+++ b/src/resource/gantt/HTimeGrid.js
@@ -0,0 +1,462 @@
+
+/* A component that renders one or more columns of vertical time slots
+----------------------------------------------------------------------------------------------------------------------*/
+
+function HTimeGrid(view) {
+ Grid.call(this, view); // call the super-constructor
+}
+
+
+HTimeGrid.prototype = createObject(Grid.prototype); // define the super-class
+$.extend(HTimeGrid.prototype, {
+
+ slotDuration: null, // duration of a "slot", a distinct time segment on given day, visualized by lines
+ snapDuration: null, // granularity of time for dragging and selecting
+
+ minTime: null, // Duration object that denotes the first visible time of any given day
+ maxTime: null, // Duration object that denotes the exclusive visible end time of any given day
+
+ dayEls: null, // cells elements in the day-row background
+ slatEls: null, // elements running horizontally across all columns
+
+ slatTops: null, // an array of top positions, relative to the container. last item holds bottom of last slot
+
+ highlightEl: null, // cell skeleton element for rendering the highlight
+ helperEl: null, // cell skeleton element for rendering the mock event "helper"
+
+
+ // Renders the time grid into `this.el`, which should already be assigned.
+ // Relies on the view's colCnt. In the future, this component should probably be self-sufficient.
+ render: function() {
+ this.processOptions();
+
+ this.el.html(this.renderHtml());
+
+ this.dayEls = this.el.find('.fc-day');
+ this.slatEls = this.el.find('.fc-slats tr');
+
+ this.computeSlatTops();
+
+ Grid.prototype.render.call(this); // call the super-method
+ },
+
+
+ // Renders the basic HTML skeleton for the grid
+ renderHtml: function() {
+ return '' +
+ '' +
+ '
' +
+ this.rowHtml('slotBg') + // leverages RowRenderer, which will call slotBgCellHtml
+ '
' +
+ '
' +
+ '' +
+ '
' +
+ this.slatRowHtml() +
+ '
' +
+ '
';
+ },
+
+
+ // Renders the HTML for a vertical background cell behind the slots.
+ // This method is distinct from 'bg' because we wanted a new `rowType` so the View could customize the rendering.
+ slotBgCellHtml: function(row, col, date) {
+ return this.bgCellHtml(row, col, date);
+ },
+
+
+ // Generates the HTML for the horizontal "slats" that run width-wise. Has a time axis on a side. Depends on RTL.
+ slatRowHtml: function() {
+ debugger;
+ var view = this.view;
+ var calendar = view.calendar;
+ var isRTL = view.opt('isRTL');
+ var html = '';
+ var slotNormal = this.slotDuration.asMinutes() % 15 === 0;
+ var slotTime = moment.duration(+this.minTime); // wish there was .clone() for durations
+ var slotDate; // will be on the view's first day, but we only care about its time
+ var minutes;
+ var axisHtml;
+
+ // Calculate the time for each slot
+ while (slotTime < this.maxTime) {
+ slotDate = view.start.clone().time(slotTime); // will be in UTC but that's good. to avoid DST issues
+ minutes = slotDate.minutes();
+
+ axisHtml =
+ '' +
+ ((!slotNormal || !minutes) ? // if irregular slot duration, or on the hour, then display the time
+ '' + // for matchCellWidths
+ htmlEscape(calendar.formatDate(slotDate, view.opt('axisFormat'))) +
+ '' :
+ ''
+ ) +
+ ' | ';
+
+ html +=
+ '' +
+ (!isRTL ? axisHtml : '') +
+ ' | ' +
+ (isRTL ? axisHtml : '') +
+ "
";
+
+ slotTime.add(this.slotDuration);
+ }
+
+ return html;
+ },
+
+
+ // Parses various options into properties of this object
+ processOptions: function() {
+ var view = this.view;
+ var slotDuration = view.opt('slotDuration');
+ var snapDuration = view.opt('snapDuration');
+
+ slotDuration = moment.duration(slotDuration);
+ snapDuration = snapDuration ? moment.duration(snapDuration) : slotDuration;
+
+ this.slotDuration = slotDuration;
+ this.snapDuration = snapDuration;
+ this.cellDuration = snapDuration; // important to assign this for Grid.events.js
+
+ this.minTime = moment.duration(view.opt('minTime'));
+ this.maxTime = moment.duration(view.opt('maxTime'));
+ },
+
+
+ // Slices up a date range into a segment for each column
+ rangeToSegs: function(rangeStart, rangeEnd) {
+ var view = this.view;
+ var segs = [];
+ var seg;
+ var col;
+ var cellDate;
+ var colStart, colEnd;
+
+ // normalize
+ rangeStart = rangeStart.clone().stripZone();
+ rangeEnd = rangeEnd.clone().stripZone();
+
+ for (col = 0; col < view.colCnt; col++) {
+ cellDate = view.cellToDate(0, col); // use the View's cell system for this
+ colStart = cellDate.clone().time(this.minTime);
+ colEnd = cellDate.clone().time(this.maxTime);
+ seg = intersectionToSeg(rangeStart, rangeEnd, colStart, colEnd);
+ if (seg) {
+ seg.col = col;
+ segs.push(seg);
+ }
+ }
+
+ return segs;
+ },
+
+
+ /* Coordinates
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ // Called when there is a window resize/zoom and we need to recalculate coordinates for the grid
+ resize: function() {
+ this.computeSlatTops();
+ this.updateSegVerticals();
+ },
+
+
+ // Populates the given empty `rows` and `cols` arrays with offset positions of the "snap" cells.
+ // "Snap" cells are different the slots because they might have finer granularity.
+ buildCoords: function(rows, cols) {
+ var colCnt = this.view.colCnt;
+ var originTop = this.el.offset().top;
+ var snapTime = moment.duration(+this.minTime);
+ var p = null;
+ var e, n;
+
+ this.dayEls.slice(0, colCnt).each(function(i, _e) {
+ e = $(_e);
+ n = e.offset().left;
+ if (p) {
+ p[1] = n;
+ }
+ p = [ n ];
+ cols[i] = p;
+ });
+ p[1] = n + e.outerWidth();
+
+ p = null;
+ while (snapTime < this.maxTime) {
+ n = originTop + this.computeTimeTop(snapTime);
+ if (p) {
+ p[1] = n;
+ }
+ p = [ n ];
+ rows.push(p);
+ snapTime.add(this.snapDuration);
+ }
+ p[1] = originTop + this.computeTimeTop(snapTime); // the position of the exclusive end
+ },
+
+
+ // Gets the datetime for the given slot cell
+ getCellDate: function(cell) {
+ var view = this.view;
+ var calendar = view.calendar;
+
+ return calendar.rezoneDate( // since we are adding a time, it needs to be in the calendar's timezone
+ view.cellToDate(0, cell.col) // View's coord system only accounts for start-of-day for column
+ .time(this.minTime + this.snapDuration * cell.row)
+ );
+ },
+
+
+ // Gets the element that represents the whole-day the cell resides on
+ getCellDayEl: function(cell) {
+ return this.dayEls.eq(cell.col);
+ },
+
+
+ // Computes the top coordinate, relative to the bounds of the grid, of the given date.
+ // A `startOfDayDate` must be given for avoiding ambiguity over how to treat midnight.
+ computeDateTop: function(date, startOfDayDate) {
+ return this.computeTimeTop(
+ moment.duration(
+ date.clone().stripZone() - startOfDayDate.clone().stripTime()
+ )
+ );
+ },
+
+
+ // Computes the top coordinate, relative to the bounds of the grid, of the given time (a Duration).
+ computeTimeTop: function(time) {
+ var slatCoverage = (time - this.minTime) / this.slotDuration; // floating-point value of # of slots covered
+ var slatIndex;
+ var slatRemainder;
+ var slatTop;
+ var slatBottom;
+
+ // constrain. because minTime/maxTime might be customized
+ slatCoverage = Math.max(0, slatCoverage);
+ slatCoverage = Math.min(this.slatEls.length, slatCoverage);
+
+ slatIndex = Math.floor(slatCoverage); // an integer index of the furthest whole slot
+ slatRemainder = slatCoverage - slatIndex;
+ slatTop = this.slatTops[slatIndex]; // the top position of the furthest whole slot
+
+ if (slatRemainder) { // time spans part-way into the slot
+ slatBottom = this.slatTops[slatIndex + 1];
+ return slatTop + (slatBottom - slatTop) * slatRemainder; // part-way between slots
+ }
+ else {
+ return slatTop;
+ }
+ },
+
+
+ // Queries each `slatEl` for its position relative to the grid's container and stores it in `slatTops`.
+ // Includes the the bottom of the last slat as the last item in the array.
+ computeSlatTops: function() {
+ var tops = [];
+ var top;
+
+ this.slatEls.each(function(i, node) {
+ top = $(node).position().top;
+ tops.push(top);
+ });
+
+ tops.push(top + this.slatEls.last().outerHeight()); // bottom of the last slat
+
+ this.slatTops = tops;
+ },
+
+
+ /* Event Drag Visualization
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ // Renders a visual indication of an event being dragged over the specified date(s).
+ // `end` and `seg` can be null. See View's documentation on renderDrag for more info.
+ renderDrag: function(start, end, seg) {
+ var opacity;
+
+ if (seg) { // if there is event information for this drag, render a helper event
+ this.renderRangeHelper(start, end, seg);
+
+ opacity = this.view.opt('dragOpacity');
+ if (opacity !== undefined) {
+ this.helperEl.css('opacity', opacity);
+ }
+
+ return true; // signal that a helper has been rendered
+ }
+ else {
+ // otherwise, just render a highlight
+ this.renderHighlight(
+ start,
+ end || this.view.calendar.getDefaultEventEnd(false, start)
+ );
+ }
+ },
+
+
+ // Unrenders any visual indication of an event being dragged
+ destroyDrag: function() {
+ this.destroyHelper();
+ this.destroyHighlight();
+ },
+
+
+ /* Event Resize Visualization
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ // Renders a visual indication of an event being resized
+ renderResize: function(start, end, seg) {
+ this.renderRangeHelper(start, end, seg);
+ },
+
+
+ // Unrenders any visual indication of an event being resized
+ destroyResize: function() {
+ this.destroyHelper();
+ },
+
+
+ /* Event Helper
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ // Renders a mock "helper" event. `sourceSeg` is the original segment object and might be null (an external drag)
+ renderHelper: function(event, sourceSeg) {
+ var res = this.renderEventTable([ event ]);
+ var tableEl = res.tableEl;
+ var segs = res.segs;
+ var i, seg;
+ var sourceEl;
+
+ // Try to make the segment that is in the same row as sourceSeg look the same
+ for (i = 0; i < segs.length; i++) {
+ seg = segs[i];
+ if (sourceSeg && sourceSeg.col === seg.col) {
+ sourceEl = sourceSeg.el;
+ seg.el.css({
+ left: sourceEl.css('left'),
+ right: sourceEl.css('right'),
+ 'margin-left': sourceEl.css('margin-left'),
+ 'margin-right': sourceEl.css('margin-right')
+ });
+ }
+ }
+
+ this.helperEl = $('')
+ .append(tableEl)
+ .appendTo(this.el);
+ },
+
+
+ // Unrenders any mock helper event
+ destroyHelper: function() {
+ if (this.helperEl) {
+ this.helperEl.remove();
+ this.helperEl = null;
+ }
+ },
+
+
+ /* Selection
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ // Renders a visual indication of a selection. Overrides the default, which was to simply render a highlight.
+ renderSelection: function(start, end) {
+ if (this.view.opt('selectHelper')) { // this setting signals that a mock helper event should be rendered
+ this.renderRangeHelper(start, end);
+ }
+ else {
+ this.renderHighlight(start, end);
+ }
+ },
+
+
+ // Unrenders any visual indication of a selection
+ destroySelection: function() {
+ this.destroyHelper();
+ this.destroyHighlight();
+ },
+
+
+ /* Highlight
+ ------------------------------------------------------------------------------------------------------------------*/
+
+
+ // Renders an emphasis on the given date range. `start` is inclusive. `end` is exclusive.
+ renderHighlight: function(start, end) {
+ this.highlightEl = $(
+ this.highlightSkeletonHtml(start, end)
+ ).appendTo(this.el);
+ },
+
+
+ // Unrenders the emphasis on a date range
+ destroyHighlight: function() {
+ if (this.highlightEl) {
+ this.highlightEl.remove();
+ this.highlightEl = null;
+ }
+ },
+
+
+ // Generates HTML for a table element with containers in each column, responsible for absolutely positioning the
+ // highlight elements to cover the highlighted slots.
+ highlightSkeletonHtml: function(start, end) {
+ var view = this.view;
+ var segs = this.rangeToSegs(start, end);
+ var cellHtml = '';
+ var col = 0;
+ var i, seg;
+ var dayDate;
+ var top, bottom;
+
+ for (i = 0; i < segs.length; i++) { // loop through the segments. one per column
+ seg = segs[i];
+
+ // need empty cells beforehand?
+ if (col < seg.col) {
+ cellHtml += ' | ';
+ col = seg.col;
+ }
+
+ // compute vertical position
+ dayDate = view.cellToDate(0, col);
+ top = this.computeDateTop(seg.start, dayDate);
+ bottom = this.computeDateTop(seg.end, dayDate); // the y position of the bottom edge
+
+ // generate the cell HTML. bottom becomes negative because it needs to be a CSS value relative to the
+ // bottom edge of the zero-height container.
+ cellHtml +=
+ '' +
+ '' +
+ ' | ';
+
+ col++;
+ }
+
+ // need empty cells after the last segment?
+ if (col < view.colCnt) {
+ cellHtml += ' | ';
+ }
+
+ cellHtml = this.bookendCells(cellHtml, 'highlight');
+
+ return '' +
+ '' +
+ '
' +
+ '' +
+ cellHtml +
+ '
' +
+ '
' +
+ '
';
+ }
+
+});
diff --git a/tests/resourceDayView.html b/tests/resourceDayView.html
index a7af103..d802eae 100644
--- a/tests/resourceDayView.html
+++ b/tests/resourceDayView.html
@@ -41,6 +41,12 @@
+
+
+
+
+
+