Ext.namespace('Zarafa.calendar.ui');
/**
* @class Zarafa.calendar.ui.AbstractCalendarBoxView
* @extends Zarafa.calendar.ui.AbstractCalendarView
*
* The main calendar showing appointments as boxed (see {@link Zarafa.calendar.ui.AbstractCalendarBoxView BoxView}.
* A {@link Zarafa.calendar.ui.CalendarPanel CalendarPanel} may show several individual calendars,
* each represented by a subclass of {@link Zarafa.calendar.ui.AbstractCalendarBoxView AbstractCalendarBoxView}.
*
* The BoxView is used to display multiple weeks in a single view (e.g. a month). For displaying only a few days,
* it is recommended to use the {@link Zarafa.calendar.ui.AbstractCalendarDaysView DaysView} instead.
* The days which have been loaded will not always match the days which are visible in the view. This is because
* the view will only display whole weeks, while in a monthview, the first day of the month might be in the middle
* of the week. Hence some extra days will be rendered into the view which are marked as inactive. These days do not
* contain any appointments, and will be displayed differently than active days, to clearly show their special status.
*/
Zarafa.calendar.ui.AbstractCalendarBoxView = Ext.extend(Zarafa.calendar.ui.AbstractCalendarView, {
/**
* Array of {@link Zarafa.calendar.data.DayBoxConfiguration configuration} objects for the
* Day positions within this view.
* @property
* @type Array
*/
dayBoxConfigurations : undefined,
/**
* @cfg {Number} firstDayOfWeek The day number of the first day of the week (0: sunday, 1: monday, ...)
* This day will be shown as the left column.
*/
firstDayOfWeek : 1,
* @cfg {Number} dayHeaderHeight The height of the Box Header (containing the date)
*/
dayHeaderHeight : 24,
* @cfg {Number} headerHeight The height of the Calendar Header (containing the column title).
*/
headerHeight : 30,
/**
* @cfg {Number} appointmentHeight The height of an appointment within the calendar view.
*/
appointmentHeight : 30,
/**
* @cfg {Number} expandThreshold The threshold in pixels which must remain empty at the bottom of
* box. If there are more appointments that should be visible in that box, they will not be rendered.
*/
expandThreshold : 10,
/**
* The number of days per week.
* @property
* @type Number
*/
numDaysInWeek : 7,
/**
* The format which must be passed to the {@link Date#format} function when
* the width of the dayHeader is smaller then the configured
* {@link Zarafa.calendar.ui.CalendarMultiView#minHeaderDayTextWidth minHeaderDayTextWidth}.
* If there is sufficient width, then {@link #longDayHeaderFormat} will be used.
* @property
* @type String
*/
shortDayHeaderFormat : _('l'),
/**
* The format which must be passed to the {@link Date#format} function when
* the width of the dayHeader is greater then the configured
* {@link Zarafa.calendar.ui.CalendarMultiView#minHeaderDayTextWidth minHeaderDayTextWidth}.
* If there is not sufficient width, then {@link #shortDayHeaderFormat} will be used.
* @property
* @type String
*/
longDayHeaderFormat : _('l'),
/**
* @constructor
* @param {Object} config Configuration object
*/
constructor : function(config)
{
config = config || {};
// If not explicitely configured, the firstDayOfWeek is determined
// by the Settings option.
if (!Ext.isDefined(config.firstDayOfWeek)) {
config.firstDayOfWeek = container.getSettingsModel().get('zarafa/v1/main/week_start');
}
Ext.applyIf(config, {
appointmentBodyLeftMargin : 6,
appointmentBodyRightMargin : 6
});
// If Drag & Drop is enabled by the enableDD option, we must split it up
// to only enable Drag & Drop for the body element, as we have no functional
// Drag & Drop support for the header of this view.
if (config.enableDD === true) {
Ext.apply(config, {
enableDD : false,
enableBodyDD : true
});
}
Zarafa.calendar.ui.AbstractCalendarBoxView.superclass.constructor.call(this, config);
},
* The {@link Zarafa.calendar.ui.CalendarMultiView CalendarMultiView} view has a header area that automatically
* resizes when its child views require more space. In the days view for instance, appointments that span
* more than 24 hours are laid out in the header.
* @return {Number} height in pixels the calendar view needs to properly lay out its header.
*/
getDesiredHeaderHeight : function()
{
return this.headerHeight;
},
* Determine the Header Text which must be displayed for the given column.
* This will apply the {@link #dayHeaderFormat}.
* @param {Date} date The date for which the header title is requested
* @param {Number} width The available width for the header
* @private
*/
getDayHeaderTitle : function(date, width)
{
var dateFormat;
if (width >= this.parentView.minHeaderDayTextWidth) {
dateFormat = this.longDayHeaderFormat;
} else {
dateFormat = this.shortDayHeaderFormat;
}
return date.format(dateFormat);
},
/**
* Converts a location in page coordinates to a corresponding date.
* @param {Number} x horizontal component of the location
* @param {Number} y vertical component of the location
* @return {Date} a Date object that corresponds to the given location
*/
screenLocationToDate : function(x, y)
{
var bodyX = x - this.body.getLeft();
var bodyY = y - this.body.getTop();
for (var i=0, configuration; configuration=this.dayBoxConfigurations[i]; i++) {
if (bodyX>configuration.left && bodyX<=configuration.right && bodyY>configuration.top && bodyY<=configuration.bottom)
{
// Check if the location is in the left or right half of the box. If it's in the right half
// the location is actually closer to the date of the next box (next day), so we'll return
// the date plus one day instead.
// return (configuration.right-bodyX < bodyX-configuration.left) ? configuration.date.add(Date.DAY, 1) : configuration.date;
return configuration.date;
}
}
return null;
},
/**
* Converts a location in page coordinates to a corresponding daterange.
* @param {Number} x horizontal component of the location
* @param {Number} y vertical component of the location
* @return {Zarafa.core.DateRange} A DateRange object that corresponds to the given location
*/
screenLocationToDateRange : function(x, y)
{
// Both the start and dueDate need a clearTime() call, this
// is in case of DST switches for Brasil where 00:00 doesn't exist
// and clearTime() will return 01:00.
var date = this.screenLocationToDate(x, y);
// check for the valid date object
if (Ext.isDate(date)) {
date.setHours(12);
var dueDate = date.add(Date.DAY, 1);
return new Zarafa.core.DateRange({ startDate : date.clearTime(), dueDate : dueDate.clearTime() });
}
return null;
},
/**
* Converts a date range ([startDate, dueDate>) to zero or more (left, right, top, bottom) bounds objects.
* This method is used to lay out appointments (and proxies) on the calendar body.
* @param {Zarafa.core.DateRange} dateRange date range
* @param {Number} column (optional) when several appointments are overlapping a column may be assigned
* @param {Number} columnCount (optional) the number of overlapping appointments in an overlap dependency graph
* @param {Boolean} useMargin (optional) True to apply a right margin to the appointments to prevent them from
* filling up the entire width of a daybox.
*/
dateRangeToBodyBounds : function(dateRange, slot, slotCount, useMargin)
{
var visible = this.getVisibleDateRange();
var visibleWeeks = this.getVisibleWeekCount(visible);
var fill = slot === undefined;
slot = slot || 0;
var weekStart = visible.getStartDate();
var startDate = dateRange.getStartDate();
var dueDate = dateRange.getDueDate();
// To determine the number of days between the 2 dates.
// To improve the result we should force both dates to be
// around the same time. This prevents problems when the
// start of the date variable is on a DST day where (in case
// of Brasil) the DST switch occurs on midnight. Causing
// a off-by-one error in the Math.floor(Date.diff()) line.
weekStart = weekStart.clone();
weekStart.setHours(12);
startDate = startDate.clone();
startDate.setHours(12);
dueDate = dueDate.clone();
dueDate.setHours(12);
var ret = [];
var leftMargin = useMargin ? this.appointmentBodyLeftMargin : 0;
var rightMargin = useMargin ? this.appointmentBodyRightMargin : 0;
for (var i=0; i<visibleWeeks; i++)
{
var weekDue = weekStart.add(Date.DAY, this.numDaysInWeek);
weekDue.setHours(12);
var startDay = Math.floor(Date.diff(Date.DAY, startDate, weekStart));
var dueDay = Math.floor(Date.diff(Date.DAY, dueDate, weekStart));
// Handles a corner case where the appointment ends at exactly 00:00 the next day,
// which means that the appointment is actually the end of the previous day.
var origDue = dateRange.getDueDate();
if (origDue.getTime() == origDue.clearTime(true).getTime()) {
// Here it will check that appointment is not 0 minute appointment.
if(!dateRange.isZeroMinuteRange()) {
dueDay--;
}
}
startDay = Math.max(startDay, 0);
dueDay = Math.min(dueDay, 6);
if (startDay<7 && dueDay>=0)
{
var startdayBoxConfigurations = this.dayBoxConfigurations[i*7 + startDay];
var duedayBoxConfigurations = this.dayBoxConfigurations[i*7 + dueDay];
var top = startdayBoxConfigurations.top + this.dayHeaderHeight + this.appointmentHeight * slot - 3*slot;
var bottom = fill ? startdayBoxConfigurations.bottom : top + this.appointmentHeight;
if (fill || bottom < startdayBoxConfigurations.bottom - this.expandThreshold) {
ret.push({
left : startdayBoxConfigurations.left + leftMargin + 1,
right : duedayBoxConfigurations.right - rightMargin - 1,
top : top,
bottom : bottom,
firstBox : true,
lastBox : true
});
}
}
weekStart = weekDue;
if (weekStart > dueDate) {
break;
}
}
return ret;
},
/**
* Find all {@link Zarafa.calendar.ui.AppointmentView appointments} which
* fall within the given {@link Zarafa.core.DateRange dateRange}.
* @param {Zarafa.core.DateRange} The daterange for which the appointments are sought.
* @return {Array} The array of {@link Zarafa.calendar.ui.AppointmentView appointments}.
*/
findAppointmentsByRange : function(dateRange)
{
var ret = [];
for (var i=0, appointment; appointment=this.appointments[i]; i++) {
if (dateRange.inside(appointment.getDateRange())) {
ret.push(appointment);
}
}
return ret;
},
/**
* Find the {@link Zarafa.calendar.ui.AppointmentView appointment} with the highest
* {@link Zarafa.calendar.ui.AppointmentView#slot slot} value. within the given
* {@link Zarafa.core.DateRange dateRange}.
* @param {Zarafa.core.DateRange} The daterange for which the appointments are sought.
* @return {Number} The highest slot value
*/
maxSlotInRange : function(dateRange)
{
var ret = 0;
for (var i=0, appointment; appointment=this.appointments[i]; i++) {
if (dateRange.overlaps(appointment.getDateRange())) {
ret = Math.max(ret, appointment.slot);
}
}
return ret;
},
/**
* Return the {@link Zarafa.core.DateRange dateRange} of the period
* which is visible within this calendar view. This might be a larger
* number of days, as the loaded dateRange might start (or end) in the middle
* of the week. But the BoxView could show the entire week instead, meaning
* that extra boxes will have been rendered.
* @return {Zarafa.core.DateRange} The visible daterange
*/
getVisibleDateRange : function()
{
// Detect the Date of the first day of the week for the start date of the loaded range
var visibleStart = this.getStartDate().getPreviousWeekDay(this.firstDayOfWeek);
// Detect the Date of the last day of the week for the due date of the loaded range
var visibleDue = this.getDueDate().getNextWeekDay(this.firstDayOfWeek);
return new Zarafa.core.DateRange({ startDate : visibleStart, dueDate : visibleDue });
},
/**
* Return the number of visible weeks (See {@link #getVisibleDateRange}.
* @param {Zarafa.core.DateRange} range (optional) The daterange for which the number of weeks must be calculated,
* if not provided, then {@link #getVisibleDateRange} will be used.
* @return {Number} The number of visible weeks
*/
getVisibleWeekCount : function(range)
{
range = range || this.getVisibleDateRange();
return Math.round(Date.diff(Date.DAY, range.getDueDate(), range.getStartDate()) / 7);
},
/**
* Initializes and returns the {@link #dayBoxConfigurations} array.
* @param {Number} height The height over which all the dayboxes must be spread
* @return {Array} The {@link Zarafa.calendar.data.DayBoxConfiguration DayBox} Configuration objects
*/
calculateDayBoxConfigurations : function(height)
{
if (!height) {
height = this.body.getHeight();
}
var visibleRange = this.getVisibleDateRange();
var visibleWeeks = this.getVisibleWeekCount(visibleRange);
this.dayBoxConfigurations = [];
var dayWidth = (this.body.getWidth()-1) / this.numDaysInWeek;
var dayHeight = (height-1) / visibleWeeks;
// Set the hours to 12 so we can safely use Date.add(Date.DAY, ...)
// for cases where the DST switch occurs at 00:00 (like in Brasil).
var firstVisibleDay = visibleRange.getStartDate().clone();
firstVisibleDay.setHours(12);
var todayTime = (new Date()).clearTime().getTime();
for (var i=0; i<visibleWeeks; i++) {
for (var j=0; j<this.numDaysInWeek; j++)
{
var busyStatus = 0;
var date = firstVisibleDay.add(Date.DAY, (i * this.numDaysInWeek) + j);
var dueDate = date.add(Date.DAY, 1);
date.clearTime();
dueDate.clearTime();
var dayRange = new Zarafa.core.DateRange({ startDate : date, dueDate : dueDate });
var workingDay = (this.parentView.workingDays.indexOf(date.getDay()) >= 0);
var appointments = this.findAppointmentsByRange(dayRange);
Ext.each(appointments, function(appointment) {
var appointmentStatus = appointment.getRecord().get('busystatus');
busyStatus = Math.max(busyStatus, appointmentStatus);
});
var boxHeight = Math.round((i+1) * dayHeight) + 1 - Math.round(i * dayHeight);
var overflowSlot = Math.floor((boxHeight - this.headerHeight - this.expandThreshold) / this.appointmentHeight);
// Here it will get the first and last date of selected month.
var activeDateRange = this.contextModel.getActiveDateRange();
this.dayBoxConfigurations.push(new Zarafa.calendar.data.DayBoxConfiguration({
left : Math.round(j * dayWidth),
right : Math.round((j+1) * dayWidth) + 1,
top : Math.round(i * dayHeight),
bottom : Math.round((i+1) * dayHeight) + 1,
date : date,
today : todayTime == date.getTime(),
workingDay : workingDay,
busyStatus : busyStatus,
overflow : this.maxSlotInRange(dayRange) >= overflowSlot,
active : date.getTime() >= activeDateRange.getStartDate().getTime() && date.getTime() < activeDateRange.getDueDate().getTime()
}));
}
}
return this.dayBoxConfigurations;
}
});