mirror of
https://github.com/wassname/fullcalendar.git
synced 2026-08-17 11:19:14 +08:00
395 lines
12 KiB
JavaScript
395 lines
12 KiB
JavaScript
|
|
/* 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
|
|
});
|
|
|
|
|
|
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 AgendaView(calendar) {
|
|
View.call(this, calendar); // call the super-constructor
|
|
|
|
this.timeGrid = new TimeGrid(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;
|
|
}
|
|
}
|
|
|
|
|
|
AgendaView.prototype = createObject(View.prototype); // define the super-class
|
|
$.extend(AgendaView.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 <hr> 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 <hr> that sometimes displays under the time-grid
|
|
this.bottomRuleEl = $('<hr class="' + this.widgetHeaderClass + '"/>')
|
|
.appendTo(this.timeGrid.el); // inject it into the time-grid
|
|
|
|
if (this.dayGrid) {
|
|
this.dayRowThemeClass = this.widgetHeaderClass; // forces this class on each day-row
|
|
|
|
this.dayGrid.el = this.el.find('.fc-day-grid');
|
|
this.dayGrid.render();
|
|
|
|
// 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();
|
|
}
|
|
|
|
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
|
|
},
|
|
|
|
|
|
// 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>';
|
|
},
|
|
|
|
|
|
// 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 '' +
|
|
'<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>';
|
|
}
|
|
},
|
|
|
|
|
|
// 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>';
|
|
},
|
|
|
|
|
|
/* Dimensions
|
|
------------------------------------------------------------------------------------------------------------------*/
|
|
|
|
|
|
// 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 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
|
|
|
|
// reset all dimensions back to the original state
|
|
this.scrollerEl.height('').removeClass('fc-scroller');
|
|
uncompensateScroll(this.noScrollRowEls);
|
|
|
|
if (!isAuto) { // should we force dimensions of the scroll container, or let the contents be natural height?
|
|
|
|
scrollerHeight = this.computeScrollerHeight(totalHeight);
|
|
timeGridHeight = this.timeGrid.el.height();
|
|
this.scrollerEl.height(scrollerHeight);
|
|
|
|
if (timeGridHeight > scrollerHeight) { // do we need scrollbars?
|
|
|
|
// 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);
|
|
|
|
this.restoreScroll();
|
|
}
|
|
else {
|
|
// display the <hr> if there is enough extra space
|
|
extraHeight = scrollerHeight - timeGridHeight;
|
|
if (extraHeight > this.bottomRuleHeight + 5) {
|
|
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();
|
|
|
|
this.segs = daySegs.concat(timedSegs); // needed by the View super-class
|
|
|
|
View.prototype.renderEvents.call(this, events); // call the super-method
|
|
},
|
|
|
|
|
|
// Unrenders all event elements and clears internal segment data
|
|
destroyEvents: function() {
|
|
|
|
// 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();
|
|
}
|
|
|
|
this.updateHeight();
|
|
|
|
View.prototype.destroyEvents.call(this); // call the super-method. will kill `this.segs`
|
|
},
|
|
|
|
|
|
/* 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();
|
|
}
|
|
}
|
|
|
|
});
|