/* An abstract class for the "basic" views, as well as month view. Renders one or more rows of day cells.
----------------------------------------------------------------------------------------------------------------------*/
// It is a manager for a DayGrid subcomponent, which does most of the heavy lifting.
// It is responsible for managing width/height.
var BasicView = FC.BasicView = View.extend({
scroller: null,
dayGridClass: DayGrid, // class the dayGrid will be instantiated from (overridable by subclasses)
dayGrid: null, // the main subcomponent that does most of the heavy lifting
dayNumbersVisible: false, // display day numbers on each day cell?
colWeekNumbersVisible: false, // display week numbers along the side?
cellWeekNumbersVisible: false, // display week numbers in day cell?
weekNumberWidth: null, // width of all the week-number cells running down the side
headContainerEl: null, // div that hold's the dayGrid's rendered date header
headRowEl: null, // the fake row element of the day-of-week header
initialize: function() {
this.dayGrid = this.instantiateDayGrid();
this.scroller = new Scroller({
overflowX: 'hidden',
overflowY: 'auto'
});
},
// Generates the DayGrid object this view needs. Draws from this.dayGridClass
instantiateDayGrid: function() {
// generate a subclass on the fly with BasicView-specific behavior
// TODO: cache this subclass
var subclass = this.dayGridClass.extend(basicDayGridMethods);
return new subclass(this);
},
// Computes the date range that will be rendered.
buildRenderRange: function(currentRange, unit) {
var renderRange = View.prototype.buildRenderRange.apply(this, arguments);
// year and month views should be aligned with weeks. this is already done for week
if (/^(year|month)$/.test(unit)) {
renderRange.start.startOf('week');
// make end-of-week if not already
if (renderRange.end.weekday()) {
renderRange.end.add(1, 'week').startOf('week'); // exclusively move backwards
}
}
return this.trimHiddenDays(renderRange);
},
// Renders the view into `this.el`, which should already be assigned
renderDates: function() {
this.dayGrid.breakOnWeeks = /year|month|week/.test(this.currentRangeUnit); // do before Grid::setRange
this.dayGrid.setRange(this.renderRange);
this.dayNumbersVisible = this.dayGrid.rowCnt > 1; // TODO: make grid responsible
if (this.opt('weekNumbers')) {
if (this.opt('weekNumbersWithinDays')) {
this.cellWeekNumbersVisible = true;
this.colWeekNumbersVisible = false;
}
else {
this.cellWeekNumbersVisible = false;
this.colWeekNumbersVisible = true;
};
}
this.dayGrid.numbersVisible = this.dayNumbersVisible ||
this.cellWeekNumbersVisible || this.colWeekNumbersVisible;
this.el.addClass('fc-basic-view').html(this.renderSkeletonHtml());
this.renderHead();
this.scroller.render();
var dayGridContainerEl = this.scroller.el.addClass('fc-day-grid-container');
var dayGridEl = $('
').appendTo(dayGridContainerEl);
this.el.find('.fc-body > tr > td').append(dayGridContainerEl);
this.dayGrid.setElement(dayGridEl);
this.dayGrid.renderDates(this.hasRigidRows());
},
// render the day-of-week headers
renderHead: function() {
this.headContainerEl =
this.el.find('.fc-head-container')
.html(this.dayGrid.renderHeadHtml());
this.headRowEl = this.headContainerEl.find('.fc-row');
},
// Unrenders the content of the view. Since we haven't separated skeleton rendering from date rendering,
// always completely kill the dayGrid's rendering.
unrenderDates: function() {
this.dayGrid.unrenderDates();
this.dayGrid.removeElement();
this.scroller.destroy();
},
renderBusinessHours: function() {
this.dayGrid.renderBusinessHours();
},
unrenderBusinessHours: function() {
this.dayGrid.unrenderBusinessHours();
},
// Builds the HTML skeleton for the view.
// The day-grid component will render inside of a container defined by this HTML.
renderSkeletonHtml: function() {
return '' +
'' +
'' +
'' +
'' +
'
' +
'' +
'' +
'' +
' | ' +
'
' +
'' +
'
';
},
// Generates an HTML attribute string for setting the width of the week number column, if it is known
weekNumberStyleAttr: function() {
if (this.weekNumberWidth !== null) {
return 'style="width:' + this.weekNumberWidth + 'px"';
}
return '';
},
// Determines whether each row should have a constant height
hasRigidRows: function() {
var eventLimit = this.opt('eventLimit');
return eventLimit && typeof eventLimit !== 'number';
},
/* Dimensions
------------------------------------------------------------------------------------------------------------------*/
// Refreshes the horizontal dimensions of the view
updateWidth: function() {
if (this.colWeekNumbersVisible) {
// Make sure all week number cells running down the side have the same width.
// Record the width for cells created later.
this.weekNumberWidth = matchCellWidths(
this.el.find('.fc-week-number')
);
}
},
// Adjusts the vertical dimensions of the view to the specified values
setHeight: function(totalHeight, isAuto) {
var eventLimit = this.opt('eventLimit');
var scrollerHeight;
var scrollbarWidths;
// reset all heights to be natural
this.scroller.clear();
uncompensateScroll(this.headRowEl);
this.dayGrid.removeSegPopover(); // kill the "more" popover if displayed
// is the event limit a constant level number?
if (eventLimit && typeof eventLimit === 'number') {
this.dayGrid.limitRows(eventLimit); // limit the levels first so the height can redistribute after
}
// distribute the height to the rows
// (totalHeight is a "recommended" value if isAuto)
scrollerHeight = this.computeScrollerHeight(totalHeight);
this.setGridHeight(scrollerHeight, isAuto);
// is the event limit dynamically calculated?
if (eventLimit && typeof eventLimit !== 'number') {
this.dayGrid.limitRows(eventLimit); // limit the levels after the grid's row heights have been set
}
if (!isAuto) { // should we force dimensions of the scroll container?
this.scroller.setHeight(scrollerHeight);
scrollbarWidths = this.scroller.getScrollbarWidths();
if (scrollbarWidths.left || scrollbarWidths.right) { // using scrollbars?
compensateScroll(this.headRowEl, scrollbarWidths);
// doing the scrollbar compensation might have created text overflow which created more height. redo
scrollerHeight = this.computeScrollerHeight(totalHeight);
this.scroller.setHeight(scrollerHeight);
}
// guarantees the same scrollbar widths
this.scroller.lockOverflow(scrollbarWidths);
}
},
// given a desired total height of the view, returns what the height of the scroller should be
computeScrollerHeight: function(totalHeight) {
return totalHeight -
subtractInnerElHeight(this.el, this.scroller.el); // everything that's NOT the scroller
},
// Sets the height of just the DayGrid component in this view
setGridHeight: function(height, isAuto) {
if (isAuto) {
undistributeHeight(this.dayGrid.rowEls); // let the rows be their natural height with no expanding
}
else {
distributeHeight(this.dayGrid.rowEls, height, true); // true = compensate for height-hogging rows
}
},
/* Scroll
------------------------------------------------------------------------------------------------------------------*/
computeInitialScroll: function() {
return { top: 0 };
},
queryScroll: function() {
return { top: this.scroller.getScrollTop() };
},
setScroll: function(scroll) {
this.scroller.setScrollTop(scroll.top);
},
/* Hit Areas
------------------------------------------------------------------------------------------------------------------*/
// forward all hit-related method calls to dayGrid
hitsNeeded: function() {
this.dayGrid.hitsNeeded();
},
hitsNotNeeded: function() {
this.dayGrid.hitsNotNeeded();
},
prepareHits: function() {
this.dayGrid.prepareHits();
},
releaseHits: function() {
this.dayGrid.releaseHits();
},
queryHit: function(left, top) {
return this.dayGrid.queryHit(left, top);
},
getHitSpan: function(hit) {
return this.dayGrid.getHitSpan(hit);
},
getHitEl: function(hit) {
return this.dayGrid.getHitEl(hit);
},
/* Events
------------------------------------------------------------------------------------------------------------------*/
// Renders the given events onto the view and populates the segments array
renderEvents: function(events) {
this.dayGrid.renderEvents(events);
this.updateHeight(); // must compensate for events that overflow the row
},
// Retrieves all segment objects that are rendered in the view
getEventSegs: function() {
return this.dayGrid.getEventSegs();
},
// Unrenders all event elements and clears internal segment data
unrenderEvents: function() {
this.dayGrid.unrenderEvents();
// we DON'T need to call updateHeight() because
// a renderEvents() call always happens after this, which will eventually call updateHeight()
},
/* Dragging (for both events and external elements)
------------------------------------------------------------------------------------------------------------------*/
// A returned value of `true` signals that a mock "helper" event has been rendered.
renderDrag: function(dropLocation, seg) {
return this.dayGrid.renderDrag(dropLocation, seg);
},
unrenderDrag: function() {
this.dayGrid.unrenderDrag();
},
/* Selection
------------------------------------------------------------------------------------------------------------------*/
// Renders a visual indication of a selection
renderSelection: function(span) {
this.dayGrid.renderSelection(span);
},
// Unrenders a visual indications of a selection
unrenderSelection: function() {
this.dayGrid.unrenderSelection();
}
});
// Methods that will customize the rendering behavior of the BasicView's dayGrid
var basicDayGridMethods = {
// Generates the HTML that will go before the day-of week header cells
renderHeadIntroHtml: function() {
var view = this.view;
if (view.colWeekNumbersVisible) {
return '' +
'';
}
return '';
},
// Generates the HTML that will go before content-skeleton cells that display the day/week numbers
renderNumberIntroHtml: function(row) {
var view = this.view;
var weekStart = this.getCellDate(row, 0);
if (view.colWeekNumbersVisible) {
return '' +
'' +
view.buildGotoAnchorHtml( // aside from link, important for matchCellWidths
{ date: weekStart, type: 'week', forceOff: this.colCnt === 1 },
weekStart.format('w') // inner HTML
) +
' | ';
}
return '';
},
// Generates the HTML that goes before the day bg cells for each day-row
renderBgIntroHtml: function() {
var view = this.view;
if (view.colWeekNumbersVisible) {
return ' | ';
}
return '';
},
// Generates the HTML that goes before every other type of row generated by DayGrid.
// Affects helper-skeleton and highlight-skeleton rows.
renderIntroHtml: function() {
var view = this.view;
if (view.colWeekNumbersVisible) {
return ' | ';
}
return '';
}
};