tvheadend/vendor/ext-3.4.1/examples/calendar/calendar-all-debug.js
Adam Sutton bafcfff42d webui: restructure webui/extjs source files
I want to keep the 3rd-party packages away from the main source
where possible.
2013-06-03 17:11:01 +01:00

5273 lines
No EOL
185 KiB
JavaScript

/*
This file is part of Ext JS 3.4
Copyright (c) 2011-2013 Sencha Inc
Contact: http://www.sencha.com/contact
GNU General Public License Usage
This file may be used under the terms of the GNU General Public License version 3.0 as
published by the Free Software Foundation and appearing in the file LICENSE included in the
packaging of this file.
Please review the following information to ensure the GNU General Public License version 3.0
requirements will be met: http://www.gnu.org/copyleft/gpl.html.
If you are unsure which license is appropriate for your use, please contact the sales department
at http://www.sencha.com/contact.
Build date: 2013-04-03 15:07:25
*/
Ext.ns('Ext.calendar');
(function() {
Ext.apply(Ext.calendar, {
Date: {
diffDays: function(start, end) {
day = 1000 * 60 * 60 * 24;
diff = end.clearTime(true).getTime() - start.clearTime(true).getTime();
return Math.ceil(diff / day);
},
copyTime: function(fromDt, toDt) {
var dt = toDt.clone();
dt.setHours(
fromDt.getHours(),
fromDt.getMinutes(),
fromDt.getSeconds(),
fromDt.getMilliseconds());
return dt;
},
compare: function(dt1, dt2, precise) {
if (precise !== true) {
dt1 = dt1.clone();
dt1.setMilliseconds(0);
dt2 = dt2.clone();
dt2.setMilliseconds(0);
}
return dt2.getTime() - dt1.getTime();
},
// private helper fn
maxOrMin: function(max) {
var dt = (max ? 0: Number.MAX_VALUE),
i = 0,
args = arguments[1],
ln = args.length;
for (; i < ln; i++) {
dt = Math[max ? 'max': 'min'](dt, args[i].getTime());
}
return new Date(dt);
},
max: function() {
return this.maxOrMin.apply(this, [true, arguments]);
},
min: function() {
return this.maxOrMin.apply(this, [false, arguments]);
}
}
});
})();/**
* @class Ext.calendar.DayHeaderTemplate
* @extends Ext.XTemplate
* <p>This is the template used to render the all-day event container used in {@link Ext.calendar.DayView DayView} and
* {@link Ext.calendar.WeekView WeekView}. Internally the majority of the layout logic is deferred to an instance of
* {@link Ext.calendar.BoxLayoutTemplate}.</p>
* <p>This template is automatically bound to the underlying event store by the
* calendar components and expects records of type {@link Ext.calendar.EventRecord}.</p>
* <p>Note that this template would not normally be used directly. Instead you would use the {@link Ext.calendar.DayViewTemplate}
* that internally creates an instance of this template along with a {@link Ext.calendar.DayBodyTemplate}.</p>
* @constructor
* @param {Object} config The config object
*/
Ext.calendar.DayHeaderTemplate = function(config){
Ext.apply(this, config);
this.allDayTpl = new Ext.calendar.BoxLayoutTemplate(config);
this.allDayTpl.compile();
Ext.calendar.DayHeaderTemplate.superclass.constructor.call(this,
'<div class="ext-cal-hd-ct">',
'<table class="ext-cal-hd-days-tbl" cellspacing="0" cellpadding="0">',
'<tbody>',
'<tr>',
'<td class="ext-cal-gutter"></td>',
'<td class="ext-cal-hd-days-td"><div class="ext-cal-hd-ad-inner">{allDayTpl}</div></td>',
'<td class="ext-cal-gutter-rt"></td>',
'</tr>',
'</tobdy>',
'</table>',
'</div>'
);
};
Ext.extend(Ext.calendar.DayHeaderTemplate, Ext.XTemplate, {
applyTemplate : function(o){
return Ext.calendar.DayHeaderTemplate.superclass.applyTemplate.call(this, {
allDayTpl: this.allDayTpl.apply(o)
});
}
});
Ext.calendar.DayHeaderTemplate.prototype.apply = Ext.calendar.DayHeaderTemplate.prototype.applyTemplate;
/**
* @class Ext.calendar.DayBodyTemplate
* @extends Ext.XTemplate
* <p>This is the template used to render the scrolling body container used in {@link Ext.calendar.DayView DayView} and
* {@link Ext.calendar.WeekView WeekView}. This template is automatically bound to the underlying event store by the
* calendar components and expects records of type {@link Ext.calendar.EventRecord}.</p>
* <p>Note that this template would not normally be used directly. Instead you would use the {@link Ext.calendar.DayViewTemplate}
* that internally creates an instance of this template along with a {@link Ext.calendar.DayHeaderTemplate}.</p>
* @constructor
* @param {Object} config The config object
*/
Ext.calendar.DayBodyTemplate = function(config){
Ext.apply(this, config);
Ext.calendar.DayBodyTemplate.superclass.constructor.call(this,
'<table class="ext-cal-bg-tbl" cellspacing="0" cellpadding="0">',
'<tbody>',
'<tr height="1">',
'<td class="ext-cal-gutter"></td>',
'<td colspan="{dayCount}">',
'<div class="ext-cal-bg-rows">',
'<div class="ext-cal-bg-rows-inner">',
'<tpl for="times">',
'<div class="ext-cal-bg-row">',
'<div class="ext-cal-bg-row-div ext-row-{[xindex]}"></div>',
'</div>',
'</tpl>',
'</div>',
'</div>',
'</td>',
'</tr>',
'<tr>',
'<td class="ext-cal-day-times">',
'<tpl for="times">',
'<div class="ext-cal-bg-row">',
'<div class="ext-cal-day-time-inner">{.}</div>',
'</div>',
'</tpl>',
'</td>',
'<tpl for="days">',
'<td class="ext-cal-day-col">',
'<div class="ext-cal-day-col-inner">',
'<div id="{[this.id]}-day-col-{.:date("Ymd")}" class="ext-cal-day-col-gutter"></div>',
'</div>',
'</td>',
'</tpl>',
'</tr>',
'</tbody>',
'</table>'
);
};
Ext.extend(Ext.calendar.DayBodyTemplate, Ext.XTemplate, {
// private
applyTemplate : function(o){
this.today = new Date().clearTime();
this.dayCount = this.dayCount || 1;
var i = 0, days = [],
dt = o.viewStart.clone(),
times;
for(; i<this.dayCount; i++){
days[i] = dt.add(Date.DAY, i);
}
times = [];
dt = new Date().clearTime();
for(i=0; i<24; i++){
times.push(dt.format('ga'));
dt = dt.add(Date.HOUR, 1);
}
return Ext.calendar.DayBodyTemplate.superclass.applyTemplate.call(this, {
days: days,
dayCount: days.length,
times: times
});
}
});
Ext.calendar.DayBodyTemplate.prototype.apply = Ext.calendar.DayBodyTemplate.prototype.applyTemplate;
/**
* @class Ext.calendar.DayViewTemplate
* @extends Ext.XTemplate
* <p>This is the template used to render the all-day event container used in {@link Ext.calendar.DayView DayView} and
* {@link Ext.calendar.WeekView WeekView}. Internally this class simply defers to instances of {@link Ext.calerndar.DayHeaderTemplate}
* and {@link Ext.calerndar.DayBodyTemplate} to perform the actual rendering logic, but it also provides the overall calendar view
* container that contains them both. As such this is the template that should be used when rendering day or week views.</p>
* <p>This template is automatically bound to the underlying event store by the
* calendar components and expects records of type {@link Ext.calendar.EventRecord}.</p>
* @constructor
* @param {Object} config The config object
*/
Ext.calendar.DayViewTemplate = function(config){
Ext.apply(this, config);
this.headerTpl = new Ext.calendar.DayHeaderTemplate(config);
this.headerTpl.compile();
this.bodyTpl = new Ext.calendar.DayBodyTemplate(config);
this.bodyTpl.compile();
Ext.calendar.DayViewTemplate.superclass.constructor.call(this,
'<div class="ext-cal-inner-ct">',
'{headerTpl}',
'{bodyTpl}',
'</div>'
);
};
Ext.extend(Ext.calendar.DayViewTemplate, Ext.XTemplate, {
// private
applyTemplate : function(o){
return Ext.calendar.DayViewTemplate.superclass.applyTemplate.call(this, {
headerTpl: this.headerTpl.apply(o),
bodyTpl: this.bodyTpl.apply(o)
});
}
});
Ext.calendar.DayViewTemplate.prototype.apply = Ext.calendar.DayViewTemplate.prototype.applyTemplate;
/**
* @class Ext.calendar.BoxLayoutTemplate
* @extends Ext.XTemplate
* <p>This is the template used to render calendar views based on small day boxes within a non-scrolling container (currently
* the {@link Ext.calendar.MonthView MonthView} and the all-day headers for {@link Ext.calendar.DayView DayView} and
* {@link Ext.calendar.WeekView WeekView}. This template is automatically bound to the underlying event store by the
* calendar components and expects records of type {@link Ext.calendar.EventRecord}.</p>
* @constructor
* @param {Object} config The config object
*/
Ext.calendar.BoxLayoutTemplate = function(config){
Ext.apply(this, config);
var weekLinkTpl = this.showWeekLinks ? '<div id="{weekLinkId}" class="ext-cal-week-link">{weekNum}</div>' : '';
Ext.calendar.BoxLayoutTemplate.superclass.constructor.call(this,
'<tpl for="weeks">',
'<div id="{[this.id]}-wk-{[xindex-1]}" class="ext-cal-wk-ct" style="top:{[this.getRowTop(xindex, xcount)]}%; height:{[this.getRowHeight(xcount)]}%;">',
weekLinkTpl,
'<table class="ext-cal-bg-tbl" cellpadding="0" cellspacing="0">',
'<tbody>',
'<tr>',
'<tpl for=".">',
'<td id="{[this.id]}-day-{date:date("Ymd")}" class="{cellCls}">&nbsp;</td>',
'</tpl>',
'</tr>',
'</tbody>',
'</table>',
'<table class="ext-cal-evt-tbl" cellpadding="0" cellspacing="0">',
'<tbody>',
'<tr>',
'<tpl for=".">',
'<td id="{[this.id]}-ev-day-{date:date("Ymd")}" class="{titleCls}"><div>{title}</div></td>',
'</tpl>',
'</tr>',
'</tbody>',
'</table>',
'</div>',
'</tpl>', {
getRowTop: function(i, ln){
return ((i-1)*(100/ln));
},
getRowHeight: function(ln){
return 100/ln;
}
}
);
};
Ext.extend(Ext.calendar.BoxLayoutTemplate, Ext.XTemplate, {
// private
applyTemplate : function(o){
Ext.apply(this, o);
var w = 0, title = '', first = true, isToday = false, showMonth = false, prevMonth = false, nextMonth = false,
weeks = [[]],
today = new Date().clearTime(),
dt = this.viewStart.clone(),
thisMonth = this.startDate.getMonth();
for(; w < this.weekCount || this.weekCount == -1; w++){
if(dt > this.viewEnd){
break;
}
weeks[w] = [];
for(var d = 0; d < this.dayCount; d++){
isToday = dt.getTime() === today.getTime();
showMonth = first || (dt.getDate() == 1);
prevMonth = (dt.getMonth() < thisMonth) && this.weekCount == -1;
nextMonth = (dt.getMonth() > thisMonth) && this.weekCount == -1;
if(dt.getDay() == 1){
// The ISO week format 'W' is relative to a Monday week start. If we
// make this check on Sunday the week number will be off.
weeks[w].weekNum = this.showWeekNumbers ? dt.format('W') : '&nbsp;';
weeks[w].weekLinkId = 'ext-cal-week-'+dt.format('Ymd');
}
if(showMonth){
if(isToday){
title = this.getTodayText();
}
else{
title = dt.format(this.dayCount == 1 ? 'l, F j, Y' : (first ? 'M j, Y' : 'M j'));
}
}
else{
var dayFmt = (w == 0 && this.showHeader !== true) ? 'D j' : 'j';
title = isToday ? this.getTodayText() : dt.format(dayFmt);
}
weeks[w].push({
title: title,
date: dt.clone(),
titleCls: 'ext-cal-dtitle ' + (isToday ? ' ext-cal-dtitle-today' : '') +
(w==0 ? ' ext-cal-dtitle-first' : '') +
(prevMonth ? ' ext-cal-dtitle-prev' : '') +
(nextMonth ? ' ext-cal-dtitle-next' : ''),
cellCls: 'ext-cal-day ' + (isToday ? ' ext-cal-day-today' : '') +
(d==0 ? ' ext-cal-day-first' : '') +
(prevMonth ? ' ext-cal-day-prev' : '') +
(nextMonth ? ' ext-cal-day-next' : '')
});
dt = dt.add(Date.DAY, 1);
first = false;
}
}
return Ext.calendar.BoxLayoutTemplate.superclass.applyTemplate.call(this, {
weeks: weeks
});
},
// private
getTodayText : function(){
var dt = new Date().format('l, F j, Y'),
todayText = this.showTodayText !== false ? this.todayText : '',
timeText = this.showTime !== false ? ' <span id="'+this.id+'-clock" class="ext-cal-dtitle-time">' +
new Date().format('g:i a') + '</span>' : '',
separator = todayText.length > 0 || timeText.length > 0 ? ' &mdash; ' : '';
if(this.dayCount == 1){
return dt + separator + todayText + timeText;
}
fmt = this.weekCount == 1 ? 'D j' : 'j';
return todayText.length > 0 ? todayText + timeText : new Date().format(fmt) + timeText;
}
});
Ext.calendar.BoxLayoutTemplate.prototype.apply = Ext.calendar.BoxLayoutTemplate.prototype.applyTemplate;
/**
* @class Ext.calendar.MonthViewTemplate
* @extends Ext.XTemplate
* <p>This is the template used to render the {@link Ext.calendar.MonthView MonthView}. Internally this class defers to an
* instance of {@link Ext.calerndar.BoxLayoutTemplate} to handle the inner layout rendering and adds containing elements around
* that to form the month view.</p>
* <p>This template is automatically bound to the underlying event store by the
* calendar components and expects records of type {@link Ext.calendar.EventRecord}.</p>
* @constructor
* @param {Object} config The config object
*/
Ext.calendar.MonthViewTemplate = function(config){
Ext.apply(this, config);
this.weekTpl = new Ext.calendar.BoxLayoutTemplate(config);
this.weekTpl.compile();
var weekLinkTpl = this.showWeekLinks ? '<div class="ext-cal-week-link-hd">&nbsp;</div>' : '';
Ext.calendar.MonthViewTemplate.superclass.constructor.call(this,
'<div class="ext-cal-inner-ct {extraClasses}">',
'<div class="ext-cal-hd-ct ext-cal-month-hd">',
weekLinkTpl,
'<table class="ext-cal-hd-days-tbl" cellpadding="0" cellspacing="0">',
'<tbody>',
'<tr>',
'<tpl for="days">',
'<th class="ext-cal-hd-day{[xindex==1 ? " ext-cal-day-first" : ""]}" title="{.:date("l, F j, Y")}">{.:date("D")}</th>',
'</tpl>',
'</tr>',
'</tbody>',
'</table>',
'</div>',
'<div class="ext-cal-body-ct">{weeks}</div>',
'</div>'
);
};
Ext.extend(Ext.calendar.MonthViewTemplate, Ext.XTemplate, {
// private
applyTemplate : function(o){
var days = [],
weeks = this.weekTpl.apply(o),
dt = o.viewStart;
for(var i = 0; i < 7; i++){
days.push(dt.add(Date.DAY, i));
}
var extraClasses = this.showHeader === true ? '' : 'ext-cal-noheader';
if(this.showWeekLinks){
extraClasses += ' ext-cal-week-links';
}
return Ext.calendar.MonthViewTemplate.superclass.applyTemplate.call(this, {
days: days,
weeks: weeks,
extraClasses: extraClasses
});
}
});
Ext.calendar.MonthViewTemplate.prototype.apply = Ext.calendar.MonthViewTemplate.prototype.applyTemplate;
/**
* @class Ext.dd.ScrollManager
* <p>Provides automatic scrolling of overflow regions in the page during drag operations.</p>
* <p>The ScrollManager configs will be used as the defaults for any scroll container registered with it,
* but you can also override most of the configs per scroll container by adding a
* <tt>ddScrollConfig</tt> object to the target element that contains these properties: {@link #hthresh},
* {@link #vthresh}, {@link #increment} and {@link #frequency}. Example usage:
* <pre><code>
var el = Ext.get('scroll-ct');
el.ddScrollConfig = {
vthresh: 50,
hthresh: -1,
frequency: 100,
increment: 200
};
Ext.dd.ScrollManager.register(el);
</code></pre>
* <b>Note: This class uses "Point Mode" and is untested in "Intersect Mode".</b>
* @singleton
*/
Ext.dd.ScrollManager = function() {
var ddm = Ext.dd.DragDropMgr,
els = {},
dragEl = null,
proc = {},
onStop = function(e) {
dragEl = null;
clearProc();
},
triggerRefresh = function() {
if (ddm.dragCurrent) {
ddm.refreshCache(ddm.dragCurrent.groups);
}
},
doScroll = function() {
if (ddm.dragCurrent) {
var dds = Ext.dd.ScrollManager,
inc = proc.el.ddScrollConfig ? proc.el.ddScrollConfig.increment: dds.increment;
if (!dds.animate) {
if (proc.el.scroll(proc.dir, inc)) {
triggerRefresh();
}
} else {
proc.el.scroll(proc.dir, inc, true, dds.animDuration, triggerRefresh);
}
}
},
clearProc = function() {
if (proc.id) {
clearInterval(proc.id);
}
proc.id = 0;
proc.el = null;
proc.dir = "";
},
startProc = function(el, dir) {
clearProc();
proc.el = el;
proc.dir = dir;
var freq = (el.ddScrollConfig && el.ddScrollConfig.frequency) ?
el.ddScrollConfig.frequency: Ext.dd.ScrollManager.frequency,
group = el.ddScrollConfig ? el.ddScrollConfig.ddGroup: undefined;
if (group === undefined || ddm.dragCurrent.ddGroup == group) {
proc.id = setInterval(doScroll, freq);
}
},
onFire = function(e, isDrop) {
if (isDrop || !ddm.dragCurrent) {
return;
}
var dds = Ext.dd.ScrollManager;
if (!dragEl || dragEl != ddm.dragCurrent) {
dragEl = ddm.dragCurrent;
// refresh regions on drag start
dds.refreshCache();
}
var xy = Ext.lib.Event.getXY(e),
pt = new Ext.lib.Point(xy[0], xy[1]),
id,
el,
r,
c;
for (id in els) {
if (els.hasOwnProperty(id)) {
el = els[id];
r = el._region;
c = el.ddScrollConfig ? el.ddScrollConfig: dds;
if (r && r.contains(pt) && el.isScrollable()) {
if (r.bottom - pt.y <= c.vthresh) {
if (proc.el != el) {
startProc(el, "down");
}
return;
} else if (r.right - pt.x <= c.hthresh) {
if (proc.el != el) {
startProc(el, "left");
}
return;
} else if (pt.y - r.top <= c.vthresh) {
if (proc.el != el) {
startProc(el, "up");
}
return;
} else if (pt.x - r.left <= c.hthresh) {
if (proc.el != el) {
startProc(el, "right");
}
return;
}
}
}
}
clearProc();
};
ddm.fireEvents = ddm.fireEvents.createSequence(onFire, ddm);
ddm.stopDrag = ddm.stopDrag.createSequence(onStop, ddm);
return {
/**
* Registers new overflow element(s) to auto scroll
* @param {Mixed/Array} el The id of or the element to be scrolled or an array of either
*/
register: function(el) {
if (Ext.isArray(el)) {
var i = 0,
len = el.length;
for (; i < len; i++) {
this.register(el[i]);
}
} else {
el = Ext.get(el);
els[el.id] = el;
}
},
/**
* Unregisters overflow element(s) so they are no longer scrolled
* @param {Mixed/Array} el The id of or the element to be removed or an array of either
*/
unregister: function(el) {
if (Ext.isArray(el)) {
var i = 0,
len = el.length;
for (; i < len; i++) {
this.unregister(el[i]);
}
} else {
el = Ext.get(el);
delete els[el.id];
}
},
/**
* The number of pixels from the top or bottom edge of a container the pointer needs to be to
* trigger scrolling (defaults to 25)
* @type Number
*/
vthresh: 25,
/**
* The number of pixels from the right or left edge of a container the pointer needs to be to
* trigger scrolling (defaults to 25)
* @type Number
*/
hthresh: 25,
/**
* The number of pixels to scroll in each scroll increment (defaults to 50)
* @type Number
*/
increment: 100,
/**
* The frequency of scrolls in milliseconds (defaults to 500)
* @type Number
*/
frequency: 500,
/**
* True to animate the scroll (defaults to true)
* @type Boolean
*/
animate: true,
/**
* The animation duration in seconds -
* MUST BE less than Ext.dd.ScrollManager.frequency! (defaults to .4)
* @type Number
*/
animDuration: 0.4,
/**
* Manually trigger a cache refresh.
*/
refreshCache: function() {
var id;
for (id in els) {
if (els.hasOwnProperty(id)) {
if (typeof els[id] == 'object') {
// for people extending the object prototype
els[id]._region = els[id].getRegion();
}
}
}
}
};
}();/*
* @class Ext.calendar.StatusProxy
* A specialized drag proxy that supports a drop status icon, {@link Ext.Layer} styles and auto-repair. It also
* contains a calendar-specific drag status message containing details about the dragged event's target drop date range.
* This is the default drag proxy used by all calendar views.
* @constructor
* @param {Object} config
*/
Ext.calendar.StatusProxy = function(config) {
Ext.apply(this, config);
this.id = this.id || Ext.id();
this.el = new Ext.Layer({
dh: {
id: this.id,
cls: 'ext-dd-drag-proxy x-dd-drag-proxy ' + this.dropNotAllowed,
cn: [
{
cls: 'x-dd-drop-icon'
},
{
cls: 'ext-dd-ghost-ct',
cn: [
{
cls: 'x-dd-drag-ghost'
},
{
cls: 'ext-dd-msg'
}
]
}
]
},
shadow: !config || config.shadow !== false
});
this.ghost = Ext.get(this.el.dom.childNodes[1].childNodes[0]);
this.message = Ext.get(this.el.dom.childNodes[1].childNodes[1]);
this.dropStatus = this.dropNotAllowed;
};
Ext.extend(Ext.calendar.StatusProxy, Ext.dd.StatusProxy, {
/**
* @cfg {String} moveEventCls
* The CSS class to apply to the status element when an event is being dragged (defaults to 'ext-cal-dd-move').
*/
moveEventCls: 'ext-cal-dd-move',
/**
* @cfg {String} addEventCls
* The CSS class to apply to the status element when drop is not allowed (defaults to 'ext-cal-dd-add').
*/
addEventCls: 'ext-cal-dd-add',
// inherit docs
update: function(html) {
if (typeof html == 'string') {
this.ghost.update(html);
} else {
this.ghost.update('');
html.style.margin = '0';
this.ghost.dom.appendChild(html);
}
var el = this.ghost.dom.firstChild;
if (el) {
Ext.fly(el).setStyle('float', 'none').setHeight('auto');
Ext.getDom(el).id += '-ddproxy';
}
},
/**
* Update the calendar-specific drag status message without altering the ghost element.
* @param {String} msg The new status message
*/
updateMsg: function(msg) {
this.message.update(msg);
}
});/*
* Internal drag zone implementation for the calendar components. This provides base functionality
* and is primarily for the month view -- DayViewDD adds day/week view-specific functionality.
*/
Ext.calendar.DragZone = Ext.extend(Ext.dd.DragZone, {
ddGroup: 'CalendarDD',
eventSelector: '.ext-cal-evt',
constructor: function(el, config) {
if (!Ext.calendar._statusProxyInstance) {
Ext.calendar._statusProxyInstance = new Ext.calendar.StatusProxy();
}
this.proxy = Ext.calendar._statusProxyInstance;
Ext.calendar.DragZone.superclass.constructor.call(this, el, config);
},
getDragData: function(e) {
// Check whether we are dragging on an event first
var t = e.getTarget(this.eventSelector, 3);
if (t) {
var rec = this.view.getEventRecordFromEl(t);
return {
type: 'eventdrag',
ddel: t,
eventStart: rec.data[Ext.calendar.EventMappings.StartDate.name],
eventEnd: rec.data[Ext.calendar.EventMappings.EndDate.name],
proxy: this.proxy
};
}
// If not dragging an event then we are dragging on
// the calendar to add a new event
t = this.view.getDayAt(e.getPageX(), e.getPageY());
if (t.el) {
return {
type: 'caldrag',
start: t.date,
proxy: this.proxy
};
}
return null;
},
onInitDrag: function(x, y) {
if (this.dragData.ddel) {
var ghost = this.dragData.ddel.cloneNode(true),
child = Ext.fly(ghost).child('dl');
Ext.fly(ghost).setWidth('auto');
if (child) {
// for IE/Opera
child.setHeight('auto');
}
this.proxy.update(ghost);
this.onStartDrag(x, y);
}
else if (this.dragData.start) {
this.onStartDrag(x, y);
}
this.view.onInitDrag();
return true;
},
afterRepair: function() {
if (Ext.enableFx && this.dragData.ddel) {
Ext.Element.fly(this.dragData.ddel).highlight(this.hlColor || 'c3daf9');
}
this.dragging = false;
},
getRepairXY: function(e) {
if (this.dragData.ddel) {
return Ext.Element.fly(this.dragData.ddel).getXY();
}
},
afterInvalidDrop: function(e, id) {
Ext.select('.ext-dd-shim').hide();
},
destroy: function() {
// The point of this override is to preserve the singleton proxy object across
// instances of CalendarDD. It must not be destroyed by the superclass code.
this.proxy = {};
Ext.calendar.DragZone.superclass.destroy.call(this);
this.proxy = Ext.calendar._statusProxyInstance;
}
});
/*
* Internal drop zone implementation for the calendar components. This provides base functionality
* and is primarily for the month view -- DayViewDD adds day/week view-specific functionality.
*/
Ext.calendar.DropZone = Ext.extend(Ext.dd.DropZone, {
ddGroup: 'CalendarDD',
eventSelector: '.ext-cal-evt',
// private
shims: [],
getTargetFromEvent: function(e) {
var dragOffset = this.dragOffset || 0,
y = e.getPageY() - dragOffset,
d = this.view.getDayAt(e.getPageX(), y);
return d.el ? d: null;
},
onNodeOver: function(n, dd, e, data) {
var D = Ext.calendar.Date,
start = data.type == 'eventdrag' ? n.date: D.min(data.start, n.date),
end = data.type == 'eventdrag' ? n.date.add(Date.DAY, D.diffDays(data.eventStart, data.eventEnd)) :
D.max(data.start, n.date);
if (!this.dragStartDate || !this.dragEndDate || (D.diffDays(start, this.dragStartDate) != 0) || (D.diffDays(end, this.dragEndDate) != 0)) {
this.dragStartDate = start;
this.dragEndDate = end.clearTime().add(Date.DAY, 1).add(Date.MILLI, -1);
this.shim(start, end);
var range = start.format('n/j');
if (D.diffDays(start, end) > 0) {
range += '-' + end.format('n/j');
}
var msg = String.format(data.type == 'eventdrag' ? this.moveText: this.createText, range);
data.proxy.updateMsg(msg);
}
return this.dropAllowed;
},
shim: function(start, end) {
this.currWeek = -1;
var dt = start.clone(),
i = 0,
shim,
box,
cnt = Ext.calendar.Date.diffDays(dt, end) + 1;
Ext.each(this.shims,
function(shim) {
if (shim) {
shim.isActive = false;
}
}
);
while (i++<cnt) {
var dayEl = this.view.getDayEl(dt);
// if the date is not in the current view ignore it (this
// can happen when an event is dragged to the end of the
// month so that it ends outside the view)
if (dayEl) {
var wk = this.view.getWeekIndex(dt);
shim = this.shims[wk];
if (!shim) {
shim = this.createShim();
this.shims[wk] = shim;
}
if (wk != this.currWeek) {
shim.boxInfo = dayEl.getBox();
this.currWeek = wk;
}
else {
box = dayEl.getBox();
shim.boxInfo.right = box.right;
shim.boxInfo.width = box.right - shim.boxInfo.x;
}
shim.isActive = true;
}
dt = dt.add(Date.DAY, 1);
}
Ext.each(this.shims,
function(shim) {
if (shim) {
if (shim.isActive) {
shim.show();
shim.setBox(shim.boxInfo);
}
else if (shim.isVisible()) {
shim.hide();
}
}
});
},
createShim: function() {
if (!this.shimCt) {
this.shimCt = Ext.get('ext-dd-shim-ct');
if (!this.shimCt) {
this.shimCt = document.createElement('div');
this.shimCt.id = 'ext-dd-shim-ct';
Ext.getBody().appendChild(this.shimCt);
}
}
var el = document.createElement('div');
el.className = 'ext-dd-shim';
this.shimCt.appendChild(el);
return new Ext.Layer({
shadow: false,
useDisplay: true,
constrain: false
},
el);
},
clearShims: function() {
Ext.each(this.shims,
function(shim) {
if (shim) {
shim.hide();
}
});
},
onContainerOver: function(dd, e, data) {
return this.dropAllowed;
},
onCalendarDragComplete: function() {
delete this.dragStartDate;
delete this.dragEndDate;
this.clearShims();
},
onNodeDrop: function(n, dd, e, data) {
if (n && data) {
if (data.type == 'eventdrag') {
var rec = this.view.getEventRecordFromEl(data.ddel),
dt = Ext.calendar.Date.copyTime(rec.data[Ext.calendar.EventMappings.StartDate.name], n.date);
this.view.onEventDrop(rec, dt);
this.onCalendarDragComplete();
return true;
}
if (data.type == 'caldrag') {
this.view.onCalendarEndDrag(this.dragStartDate, this.dragEndDate,
this.onCalendarDragComplete.createDelegate(this));
//shims are NOT cleared here -- they stay visible until the handling
//code calls the onCalendarDragComplete callback which hides them.
return true;
}
}
this.onCalendarDragComplete();
return false;
},
onContainerDrop: function(dd, e, data) {
this.onCalendarDragComplete();
return false;
},
destroy: function() {
Ext.calendar.DropZone.superclass.destroy.call(this);
Ext.destroy(this.shimCt);
}
});
/*
* Internal drag zone implementation for the calendar day and week views.
*/
Ext.calendar.DayViewDragZone = Ext.extend(Ext.calendar.DragZone, {
ddGroup: 'DayViewDD',
resizeSelector: '.ext-evt-rsz',
getDragData: function(e) {
var t = e.getTarget(this.resizeSelector, 2, true),
p,
rec;
if (t) {
p = t.parent(this.eventSelector);
rec = this.view.getEventRecordFromEl(p);
return {
type: 'eventresize',
ddel: p.dom,
eventStart: rec.data[Ext.calendar.EventMappings.StartDate.name],
eventEnd: rec.data[Ext.calendar.EventMappings.EndDate.name],
proxy: this.proxy
};
}
t = e.getTarget(this.eventSelector, 3);
if (t) {
rec = this.view.getEventRecordFromEl(t);
return {
type: 'eventdrag',
ddel: t,
eventStart: rec.data[Ext.calendar.EventMappings.StartDate.name],
eventEnd: rec.data[Ext.calendar.EventMappings.EndDate.name],
proxy: this.proxy
};
}
// If not dragging/resizing an event then we are dragging on
// the calendar to add a new event
t = this.view.getDayAt(e.getPageX(), e.getPageY());
if (t.el) {
return {
type: 'caldrag',
dayInfo: t,
proxy: this.proxy
};
}
return null;
}
});
/*
* Internal drop zone implementation for the calendar day and week views.
*/
Ext.calendar.DayViewDropZone = Ext.extend(Ext.calendar.DropZone, {
ddGroup: 'DayViewDD',
onNodeOver: function(n, dd, e, data) {
var dt,
box,
endDt,
text = this.createText,
curr,
start,
end,
evtEl,
dayCol;
if (data.type == 'caldrag') {
if (!this.dragStartMarker) {
// Since the container can scroll, this gets a little tricky.
// There is no el in the DOM that we can measure by default since
// the box is simply calculated from the original drag start (as opposed
// to dragging or resizing the event where the orig event box is present).
// To work around this we add a placeholder el into the DOM and give it
// the original starting time's box so that we can grab its updated
// box measurements as the underlying container scrolls up or down.
// This placeholder is removed in onNodeDrop.
this.dragStartMarker = n.el.parent().createChild({
style: 'position:absolute;'
});
this.dragStartMarker.setBox(n.timeBox);
this.dragCreateDt = n.date;
}
box = this.dragStartMarker.getBox();
box.height = Math.ceil(Math.abs(e.xy[1] - box.y) / n.timeBox.height) * n.timeBox.height;
if (e.xy[1] < box.y) {
box.height += n.timeBox.height;
box.y = box.y - box.height + n.timeBox.height;
endDt = this.dragCreateDt.add(Date.MINUTE, 30);
}
else {
n.date = n.date.add(Date.MINUTE, 30);
}
this.shim(this.dragCreateDt, box);
curr = Ext.calendar.Date.copyTime(n.date, this.dragCreateDt);
this.dragStartDate = Ext.calendar.Date.min(this.dragCreateDt, curr);
this.dragEndDate = endDt || Ext.calendar.Date.max(this.dragCreateDt, curr);
dt = this.dragStartDate.format('g:ia-') + this.dragEndDate.format('g:ia');
}
else {
evtEl = Ext.get(data.ddel);
dayCol = evtEl.parent().parent();
box = evtEl.getBox();
box.width = dayCol.getWidth();
if (data.type == 'eventdrag') {
if (this.dragOffset === undefined) {
this.dragOffset = n.timeBox.y - box.y;
box.y = n.timeBox.y - this.dragOffset;
}
else {
box.y = n.timeBox.y;
}
dt = n.date.format('n/j g:ia');
box.x = n.el.getLeft();
this.shim(n.date, box);
text = this.moveText;
}
if (data.type == 'eventresize') {
if (!this.resizeDt) {
this.resizeDt = n.date;
}
box.x = dayCol.getLeft();
box.height = Math.ceil(Math.abs(e.xy[1] - box.y) / n.timeBox.height) * n.timeBox.height;
if (e.xy[1] < box.y) {
box.y -= box.height;
}
else {
n.date = n.date.add(Date.MINUTE, 30);
}
this.shim(this.resizeDt, box);
curr = Ext.calendar.Date.copyTime(n.date, this.resizeDt);
start = Ext.calendar.Date.min(data.eventStart, curr);
end = Ext.calendar.Date.max(data.eventStart, curr);
data.resizeDates = {
StartDate: start,
EndDate: end
};
dt = start.format('g:ia-') + end.format('g:ia');
text = this.resizeText;
}
}
data.proxy.updateMsg(String.format(text, dt));
return this.dropAllowed;
},
shim: function(dt, box) {
Ext.each(this.shims,
function(shim) {
if (shim) {
shim.isActive = false;
shim.hide();
}
});
var shim = this.shims[0];
if (!shim) {
shim = this.createShim();
this.shims[0] = shim;
}
shim.isActive = true;
shim.show();
shim.setBox(box);
},
onNodeDrop: function(n, dd, e, data) {
var rec;
if (n && data) {
if (data.type == 'eventdrag') {
rec = this.view.getEventRecordFromEl(data.ddel);
this.view.onEventDrop(rec, n.date);
this.onCalendarDragComplete();
delete this.dragOffset;
return true;
}
if (data.type == 'eventresize') {
rec = this.view.getEventRecordFromEl(data.ddel);
this.view.onEventResize(rec, data.resizeDates);
this.onCalendarDragComplete();
delete this.resizeDt;
return true;
}
if (data.type == 'caldrag') {
Ext.destroy(this.dragStartMarker);
delete this.dragStartMarker;
delete this.dragCreateDt;
this.view.onCalendarEndDrag(this.dragStartDate, this.dragEndDate,
this.onCalendarDragComplete.createDelegate(this));
//shims are NOT cleared here -- they stay visible until the handling
//code calls the onCalendarDragComplete callback which hides them.
return true;
}
}
this.onCalendarDragComplete();
return false;
}
});
/**
* @class Ext.calendar.EventMappings
* @extends Object
* A simple object that provides the field definitions for EventRecords so that they can be easily overridden.
*/
Ext.calendar.EventMappings = {
EventId: {
name: 'EventId',
mapping: 'id',
type: 'int'
},
CalendarId: {
name: 'CalendarId',
mapping: 'cid',
type: 'int'
},
Title: {
name: 'Title',
mapping: 'title',
type: 'string'
},
StartDate: {
name: 'StartDate',
mapping: 'start',
type: 'date',
dateFormat: 'c'
},
EndDate: {
name: 'EndDate',
mapping: 'end',
type: 'date',
dateFormat: 'c'
},
Location: {
name: 'Location',
mapping: 'loc',
type: 'string'
},
Notes: {
name: 'Notes',
mapping: 'notes',
type: 'string'
},
Url: {
name: 'Url',
mapping: 'url',
type: 'string'
},
IsAllDay: {
name: 'IsAllDay',
mapping: 'ad',
type: 'boolean'
},
Reminder: {
name: 'Reminder',
mapping: 'rem',
type: 'string'
},
IsNew: {
name: 'IsNew',
mapping: 'n',
type: 'boolean'
}
};
/**
* @class Ext.calendar.EventRecord
* @extends Ext.data.Record
* <p>This is the {@link Ext.data.Record Record} specification for calendar event data used by the
* {@link Ext.calendar.CalendarPanel CalendarPanel}'s underlying store. It can be overridden as
* necessary to customize the fields supported by events, although the existing column names should
* not be altered. If your model fields are named differently you should update the <b>mapping</b>
* configs accordingly.</p>
* <p>The only required fields when creating a new event record instance are StartDate and
* EndDate. All other fields are either optional are will be defaulted if blank.</p>
* <p>Here is a basic example for how to create a new record of this type:<pre><code>
rec = new Ext.calendar.EventRecord({
StartDate: '2101-01-12 12:00:00',
EndDate: '2101-01-12 13:30:00',
Title: 'My cool event',
Notes: 'Some notes'
});
</code></pre>
* If you have overridden any of the record's data mappings via the Ext.calendar.EventMappings object
* you may need to set the values using this alternate syntax to ensure that the fields match up correctly:<pre><code>
var M = Ext.calendar.EventMappings;
rec = new Ext.calendar.EventRecord();
rec.data[M.StartDate.name] = '2101-01-12 12:00:00';
rec.data[M.EndDate.name] = '2101-01-12 13:30:00';
rec.data[M.Title.name] = 'My cool event';
rec.data[M.Notes.name] = 'Some notes';
</code></pre>
* @constructor
* @param {Object} data (Optional) An object, the properties of which provide values for the new Record's
* fields. If not specified the {@link Ext.data.Field#defaultValue defaultValue}
* for each field will be assigned.
* @param {Object} id (Optional) The id of the Record. The id is used by the
* {@link Ext.data.Store} object which owns the Record to index its collection
* of Records (therefore this id should be unique within each store). If an
* id is not specified a {@link #phantom}
* Record will be created with an {@link #Record.id automatically generated id}.
*/
(function() {
var M = Ext.calendar.EventMappings;
Ext.calendar.EventRecord = Ext.data.Record.create([
M.EventId,
M.CalendarId,
M.Title,
M.StartDate,
M.EndDate,
M.Location,
M.Notes,
M.Url,
M.IsAllDay,
M.Reminder,
M.IsNew
]);
/**
* Reconfigures the default record definition based on the current Ext.calendar.EventMappings object
*/
Ext.calendar.EventRecord.reconfigure = function() {
Ext.calendar.EventRecord = Ext.data.Record.create([
M.EventId,
M.CalendarId,
M.Title,
M.StartDate,
M.EndDate,
M.Location,
M.Notes,
M.Url,
M.IsAllDay,
M.Reminder,
M.IsNew
]);
};
})();
/*
* This is the view used internally by the panel that displays overflow events in the
* month view. Anytime a day cell cannot display all of its events, it automatically displays
* a link at the bottom to view all events for that day. When clicked, a panel pops up that
* uses this view to display the events for that day.
*/
Ext.calendar.MonthDayDetailView = Ext.extend(Ext.BoxComponent, {
initComponent: function() {
Ext.calendar.CalendarView.superclass.initComponent.call(this);
this.addEvents({
eventsrendered: true
});
if (!this.el) {
this.el = document.createElement('div');
}
},
afterRender: function() {
this.tpl = this.getTemplate();
Ext.calendar.MonthDayDetailView.superclass.afterRender.call(this);
this.el.on({
'click': this.view.onClick,
'mouseover': this.view.onMouseOver,
'mouseout': this.view.onMouseOut,
scope: this.view
});
},
getTemplate: function() {
if (!this.tpl) {
this.tpl = new Ext.XTemplate(
'<div class="ext-cal-mdv x-unselectable">',
'<table class="ext-cal-mvd-tbl" cellpadding="0" cellspacing="0">',
'<tbody>',
'<tpl for=".">',
'<tr><td class="ext-cal-ev">{markup}</td></tr>',
'</tpl>',
'</tbody>',
'</table>',
'</div>'
);
}
this.tpl.compile();
return this.tpl;
},
update: function(dt) {
this.date = dt;
this.refresh();
},
refresh: function() {
if (!this.rendered) {
return;
}
var eventTpl = this.view.getEventTemplate(),
templateData = [];
evts = this.store.queryBy(function(rec) {
var thisDt = this.date.clearTime(true).getTime(),
recStart = rec.data[Ext.calendar.EventMappings.StartDate.name].clearTime(true).getTime(),
startsOnDate = (thisDt == recStart),
spansDate = false;
if (!startsOnDate) {
var recEnd = rec.data[Ext.calendar.EventMappings.EndDate.name].clearTime(true).getTime();
spansDate = recStart < thisDt && recEnd >= thisDt;
}
return startsOnDate || spansDate;
},
this);
evts.each(function(evt) {
var item = evt.data,
M = Ext.calendar.EventMappings;
item._renderAsAllDay = item[M.IsAllDay.name] || Ext.calendar.Date.diffDays(item[M.StartDate.name], item[M.EndDate.name]) > 0;
item.spanLeft = Ext.calendar.Date.diffDays(item[M.StartDate.name], this.date) > 0;
item.spanRight = Ext.calendar.Date.diffDays(this.date, item[M.EndDate.name]) > 0;
item.spanCls = (item.spanLeft ? (item.spanRight ? 'ext-cal-ev-spanboth':
'ext-cal-ev-spanleft') : (item.spanRight ? 'ext-cal-ev-spanright': ''));
templateData.push({
markup: eventTpl.apply(this.getTemplateEventData(item))
});
},
this);
this.tpl.overwrite(this.el, templateData);
this.fireEvent('eventsrendered', this, this.date, evts.getCount());
},
getTemplateEventData: function(evt) {
var data = this.view.getTemplateEventData(evt);
data._elId = 'dtl-' + data._elId;
return data;
}
});
Ext.reg('monthdaydetailview', Ext.calendar.MonthDayDetailView);
/**
* @class Ext.calendar.CalendarPicker
* @extends Ext.form.ComboBox
* <p>A custom combo used for choosing from the list of available calendars to assign an event to.</p>
* <p>This is pretty much a standard combo that is simply pre-configured for the options needed by the
* calendar components. The default configs are as follows:<pre><code>
fieldLabel: 'Calendar',
valueField: 'CalendarId',
displayField: 'Title',
triggerAction: 'all',
mode: 'local',
forceSelection: true,
width: 200
</code></pre>
* @constructor
* @param {Object} config The config object
*/
Ext.calendar.CalendarPicker = Ext.extend(Ext.form.ComboBox, {
fieldLabel: 'Calendar',
valueField: 'CalendarId',
displayField: 'Title',
triggerAction: 'all',
mode: 'local',
forceSelection: true,
width: 200,
// private
initComponent: function() {
Ext.calendar.CalendarPicker.superclass.initComponent.call(this);
this.tpl = this.tpl ||
'<tpl for="."><div class="x-combo-list-item ext-color-{' + this.valueField +
'}"><div class="ext-cal-picker-icon">&nbsp;</div>{' + this.displayField + '}</div></tpl>';
},
// private
afterRender: function() {
Ext.calendar.CalendarPicker.superclass.afterRender.call(this);
this.wrap = this.el.up('.x-form-field-wrap');
this.wrap.addClass('ext-calendar-picker');
this.icon = Ext.DomHelper.append(this.wrap, {
tag: 'div',
cls: 'ext-cal-picker-icon ext-cal-picker-mainicon'
});
},
// inherited docs
setValue: function(value) {
this.wrap.removeClass('ext-color-' + this.getValue());
if (!value && this.store !== undefined) {
// always default to a valid calendar
value = this.store.getAt(0).data.CalendarId;
}
Ext.calendar.CalendarPicker.superclass.setValue.call(this, value);
this.wrap.addClass('ext-color-' + value);
}
});
Ext.reg('calendarpicker', Ext.calendar.CalendarPicker);
/*
* This is an internal helper class for the calendar views and should not be overridden.
* It is responsible for the base event rendering logic underlying all of the calendar views.
*/
Ext.calendar.WeekEventRenderer = function() {
var getEventRow = function(id, week, index) {
var indexOffset = 1,
//skip row with date #'s
evtRow,
wkRow = Ext.get(id + '-wk-' + week);
if (wkRow) {
var table = wkRow.child('.ext-cal-evt-tbl', true);
evtRow = table.tBodies[0].childNodes[index + indexOffset];
if (!evtRow) {
evtRow = Ext.DomHelper.append(table.tBodies[0], '<tr></tr>');
}
}
return Ext.get(evtRow);
};
return {
render: function(o) {
var w = 0,
grid = o.eventGrid,
dt = o.viewStart.clone(),
eventTpl = o.tpl,
max = o.maxEventsPerDay != undefined ? o.maxEventsPerDay: 999,
weekCount = o.weekCount < 1 ? 6: o.weekCount,
dayCount = o.weekCount == 1 ? o.dayCount: 7,
cellCfg;
for (; w < weekCount; w++) {
if (!grid[w] || grid[w].length == 0) {
// no events or span cells for the entire week
if (weekCount == 1) {
row = getEventRow(o.id, w, 0);
cellCfg = {
tag: 'td',
cls: 'ext-cal-ev',
id: o.id + '-empty-0-day-' + dt.format('Ymd'),
html: '&nbsp;'
};
if (dayCount > 1) {
cellCfg.colspan = dayCount;
}
Ext.DomHelper.append(row, cellCfg);
}
dt = dt.add(Date.DAY, 7);
} else {
var row,
d = 0,
wk = grid[w],
startOfWeek = dt.clone(),
endOfWeek = startOfWeek.add(Date.DAY, dayCount).add(Date.MILLI, -1);
for (; d < dayCount; d++) {
if (wk[d]) {
var ev = emptyCells = skipped = 0,
day = wk[d],
ct = day.length,
evt;
for (; ev < ct; ev++) {
if (!day[ev]) {
emptyCells++;
continue;
}
if (emptyCells > 0 && ev - emptyCells < max) {
row = getEventRow(o.id, w, ev - emptyCells);
cellCfg = {
tag: 'td',
cls: 'ext-cal-ev',
id: o.id + '-empty-' + ct + '-day-' + dt.format('Ymd')
};
if (emptyCells > 1 && max - ev > emptyCells) {
cellCfg.rowspan = Math.min(emptyCells, max - ev);
}
Ext.DomHelper.append(row, cellCfg);
emptyCells = 0;
}
if (ev >= max) {
skipped++;
continue;
}
evt = day[ev];
if (!evt.isSpan || evt.isSpanStart) {
//skip non-starting span cells
var item = evt.data || evt.event.data;
item._weekIndex = w;
item._renderAsAllDay = item[Ext.calendar.EventMappings.IsAllDay.name] || evt.isSpanStart;
item.spanLeft = item[Ext.calendar.EventMappings.StartDate.name].getTime() < startOfWeek.getTime();
item.spanRight = item[Ext.calendar.EventMappings.EndDate.name].getTime() > endOfWeek.getTime();
item.spanCls = (item.spanLeft ? (item.spanRight ? 'ext-cal-ev-spanboth':
'ext-cal-ev-spanleft') : (item.spanRight ? 'ext-cal-ev-spanright': ''));
row = getEventRow(o.id, w, ev);
cellCfg = {
tag: 'td',
cls: 'ext-cal-ev',
cn: eventTpl.apply(o.templateDataFn(item))
};
var diff = Ext.calendar.Date.diffDays(dt, item[Ext.calendar.EventMappings.EndDate.name]) + 1,
cspan = Math.min(diff, dayCount - d);
if (cspan > 1) {
cellCfg.colspan = cspan;
}
Ext.DomHelper.append(row, cellCfg);
}
}
if (ev > max) {
row = getEventRow(o.id, w, max);
Ext.DomHelper.append(row, {
tag: 'td',
cls: 'ext-cal-ev-more',
id: 'ext-cal-ev-more-' + dt.format('Ymd'),
cn: {
tag: 'a',
html: '+' + skipped + ' more...'
}
});
}
if (ct < o.evtMaxCount[w]) {
row = getEventRow(o.id, w, ct);
if (row) {
cellCfg = {
tag: 'td',
cls: 'ext-cal-ev',
id: o.id + '-empty-' + (ct + 1) + '-day-' + dt.format('Ymd')
};
var rowspan = o.evtMaxCount[w] - ct;
if (rowspan > 1) {
cellCfg.rowspan = rowspan;
}
Ext.DomHelper.append(row, cellCfg);
}
}
} else {
row = getEventRow(o.id, w, 0);
if (row) {
cellCfg = {
tag: 'td',
cls: 'ext-cal-ev',
id: o.id + '-empty-day-' + dt.format('Ymd')
};
if (o.evtMaxCount[w] > 1) {
cellCfg.rowSpan = o.evtMaxCount[w];
}
Ext.DomHelper.append(row, cellCfg);
}
}
dt = dt.add(Date.DAY, 1);
}
}
}
}
};
}();
/**
* @class Ext.calendar.CalendarView
* @extends Ext.BoxComponent
* <p>This is an abstract class that serves as the base for other calendar views. This class is not
* intended to be directly instantiated.</p>
* <p>When extending this class to create a custom calendar view, you must provide an implementation
* for the <code>renderItems</code> method, as there is no default implementation for rendering events
* The rendering logic is totally dependent on how the UI structures its data, which
* is determined by the underlying UI template (this base class does not have a template).</p>
* @constructor
* @param {Object} config The config object
*/
Ext.calendar.CalendarView = Ext.extend(Ext.BoxComponent, {
/**
* @cfg {Number} startDay
* The 0-based index for the day on which the calendar week begins (0=Sunday, which is the default)
*/
startDay: 0,
/**
* @cfg {Boolean} spansHavePriority
* Allows switching between two different modes of rendering events that span multiple days. When true,
* span events are always sorted first, possibly at the expense of start dates being out of order (e.g.,
* a span event that starts at 11am one day and spans into the next day would display before a non-spanning
* event that starts at 10am, even though they would not be in date order). This can lead to more compact
* layouts when there are many overlapping events. If false (the default), events will always sort by start date
* first which can result in a less compact, but chronologically consistent layout.
*/
spansHavePriority: false,
/**
* @cfg {Boolean} trackMouseOver
* Whether or not the view tracks and responds to the browser mouseover event on contained elements (defaults to
* true). If you don't need mouseover event highlighting you can disable this.
*/
trackMouseOver: true,
/**
* @cfg {Boolean} enableFx
* Determines whether or not visual effects for CRUD actions are enabled (defaults to true). If this is false
* it will override any values for {@link #enableAddFx}, {@link #enableUpdateFx} or {@link enableRemoveFx} and
* all animations will be disabled.
*/
enableFx: true,
/**
* @cfg {Boolean} enableAddFx
* True to enable a visual effect on adding a new event (the default), false to disable it. Note that if
* {@link #enableFx} is false it will override this value. The specific effect that runs is defined in the
* {@link #doAddFx} method.
*/
enableAddFx: true,
/**
* @cfg {Boolean} enableUpdateFx
* True to enable a visual effect on updating an event, false to disable it (the default). Note that if
* {@link #enableFx} is false it will override this value. The specific effect that runs is defined in the
* {@link #doUpdateFx} method.
*/
enableUpdateFx: false,
/**
* @cfg {Boolean} enableRemoveFx
* True to enable a visual effect on removing an event (the default), false to disable it. Note that if
* {@link #enableFx} is false it will override this value. The specific effect that runs is defined in the
* {@link #doRemoveFx} method.
*/
enableRemoveFx: true,
/**
* @cfg {Boolean} enableDD
* True to enable drag and drop in the calendar view (the default), false to disable it
*/
enableDD: true,
/**
* @cfg {Boolean} monitorResize
* True to monitor the browser's resize event (the default), false to ignore it. If the calendar view is rendered
* into a fixed-size container this can be set to false. However, if the view can change dimensions (e.g., it's in
* fit layout in a viewport or some other resizable container) it is very important that this config is true so that
* any resize event propagates properly to all subcomponents and layouts get recalculated properly.
*/
monitorResize: true,
/**
* @cfg {String} ddCreateEventText
* The text to display inside the drag proxy while dragging over the calendar to create a new event (defaults to
* 'Create event for {0}' where {0} is a date range supplied by the view)
*/
ddCreateEventText: 'Create event for {0}',
/**
* @cfg {String} ddMoveEventText
* The text to display inside the drag proxy while dragging an event to reposition it (defaults to
* 'Move event to {0}' where {0} is the updated event start date/time supplied by the view)
*/
ddMoveEventText: 'Move event to {0}',
/**
* @cfg {String} ddResizeEventText
* The string displayed to the user in the drag proxy while dragging the resize handle of an event (defaults to
* 'Update event to {0}' where {0} is the updated event start-end range supplied by the view). Note that
* this text is only used in views
* that allow resizing of events.
*/
ddResizeEventText: 'Update event to {0}',
//private properties -- do not override:
weekCount: 1,
dayCount: 1,
eventSelector: '.ext-cal-evt',
eventOverClass: 'ext-evt-over',
eventElIdDelimiter: '-evt-',
dayElIdDelimiter: '-day-',
/**
* Returns a string of HTML template markup to be used as the body portion of the event template created
* by {@link #getEventTemplate}. This provdes the flexibility to customize what's in the body without
* having to override the entire XTemplate. This string can include any valid {@link Ext.Template} code, and
* any data tokens accessible to the containing event template can be referenced in this string.
* @return {String} The body template string
*/
getEventBodyMarkup: Ext.emptyFn,
// must be implemented by a subclass
/**
* <p>Returns the XTemplate that is bound to the calendar's event store (it expects records of type
* {@link Ext.calendar.EventRecord}) to populate the calendar views with events. Internally this method
* by default generates different markup for browsers that support CSS border radius and those that don't.
* This method can be overridden as needed to customize the markup generated.</p>
* <p>Note that this method calls {@link #getEventBodyMarkup} to retrieve the body markup for events separately
* from the surrounding container markup. This provdes the flexibility to customize what's in the body without
* having to override the entire XTemplate. If you do override this method, you should make sure that your
* overridden version also does the same.</p>
* @return {Ext.XTemplate} The event XTemplate
*/
getEventTemplate: Ext.emptyFn,
// must be implemented by a subclass
// private
initComponent: function() {
this.setStartDate(this.startDate || new Date());
Ext.calendar.CalendarView.superclass.initComponent.call(this);
this.addEvents({
/**
* @event eventsrendered
* Fires after events are finished rendering in the view
* @param {Ext.calendar.CalendarView} this
*/
eventsrendered: true,
/**
* @event eventclick
* Fires after the user clicks on an event element
* @param {Ext.calendar.CalendarView} this
* @param {Ext.calendar.EventRecord} rec The {@link Ext.calendar.EventRecord record} for the event that was clicked on
* @param {HTMLNode} el The DOM node that was clicked on
*/
eventclick: true,
/**
* @event eventover
* Fires anytime the mouse is over an event element
* @param {Ext.calendar.CalendarView} this
* @param {Ext.calendar.EventRecord} rec The {@link Ext.calendar.EventRecord record} for the event that the cursor is over
* @param {HTMLNode} el The DOM node that is being moused over
*/
eventover: true,
/**
* @event eventout
* Fires anytime the mouse exits an event element
* @param {Ext.calendar.CalendarView} this
* @param {Ext.calendar.EventRecord} rec The {@link Ext.calendar.EventRecord record} for the event that the cursor exited
* @param {HTMLNode} el The DOM node that was exited
*/
eventout: true,
/**
* @event datechange
* Fires after the start date of the view changes
* @param {Ext.calendar.CalendarView} this
* @param {Date} startDate The start date of the view (as explained in {@link #getStartDate}
* @param {Date} viewStart The first displayed date in the view
* @param {Date} viewEnd The last displayed date in the view
*/
datechange: true,
/**
* @event rangeselect
* Fires after the user drags on the calendar to select a range of dates/times in which to create an event
* @param {Ext.calendar.CalendarView} this
* @param {Object} dates An object containing the start (StartDate property) and end (EndDate property) dates selected
* @param {Function} callback A callback function that MUST be called after the event handling is complete so that
* the view is properly cleaned up (shim elements are persisted in the view while the user is prompted to handle the
* range selection). The callback is already created in the proper scope, so it simply needs to be executed as a standard
* function call (e.g., callback()).
*/
rangeselect: true,
/**
* @event eventmove
* Fires after an event element is dragged by the user and dropped in a new position
* @param {Ext.calendar.CalendarView} this
* @param {Ext.calendar.EventRecord} rec The {@link Ext.calendar.EventRecord record} for the event that was moved with
* updated start and end dates
*/
eventmove: true,
/**
* @event initdrag
* Fires when a drag operation is initiated in the view
* @param {Ext.calendar.CalendarView} this
*/
initdrag: true,
/**
* @event dayover
* Fires while the mouse is over a day element
* @param {Ext.calendar.CalendarView} this
* @param {Date} dt The date that is being moused over
* @param {Ext.Element} el The day Element that is being moused over
*/
dayover: true,
/**
* @event dayout
* Fires when the mouse exits a day element
* @param {Ext.calendar.CalendarView} this
* @param {Date} dt The date that is exited
* @param {Ext.Element} el The day Element that is exited
*/
dayout: true
/*
* @event eventdelete
* Fires after an event element is deleted by the user. Not currently implemented directly at the view level -- currently
* deletes only happen from one of the forms.
* @param {Ext.calendar.CalendarView} this
* @param {Ext.calendar.EventRecord} rec The {@link Ext.calendar.EventRecord record} for the event that was deleted
*/
//eventdelete: true
});
},
// private
afterRender: function() {
Ext.calendar.CalendarView.superclass.afterRender.call(this);
this.renderTemplate();
if (this.store) {
this.setStore(this.store, true);
}
this.el.on({
'mouseover': this.onMouseOver,
'mouseout': this.onMouseOut,
'click': this.onClick,
'resize': this.onResize,
scope: this
});
this.el.unselectable();
if (this.enableDD && this.initDD) {
this.initDD();
}
this.on('eventsrendered', this.forceSize);
this.forceSize.defer(100, this);
},
// private
forceSize: function() {
if (this.el && this.el.child) {
var hd = this.el.child('.ext-cal-hd-ct'),
bd = this.el.child('.ext-cal-body-ct');
if (bd == null || hd == null) return;
var headerHeight = hd.getHeight(),
sz = this.el.parent().getSize();
bd.setHeight(sz.height - headerHeight);
}
},
refresh: function() {
this.prepareData();
this.renderTemplate();
this.renderItems();
},
getWeekCount: function() {
var days = Ext.calendar.Date.diffDays(this.viewStart, this.viewEnd);
return Math.ceil(days / this.dayCount);
},
// private
prepareData: function() {
var lastInMonth = this.startDate.getLastDateOfMonth(),
w = 0,
row = 0,
dt = this.viewStart.clone(),
weeks = this.weekCount < 1 ? 6: this.weekCount;
this.eventGrid = [[]];
this.allDayGrid = [[]];
this.evtMaxCount = [];
var evtsInView = this.store.queryBy(function(rec) {
return this.isEventVisible(rec.data);
},
this);
for (; w < weeks; w++) {
this.evtMaxCount[w] = 0;
if (this.weekCount == -1 && dt > lastInMonth) {
//current week is fully in next month so skip
break;
}
this.eventGrid[w] = this.eventGrid[w] || [];
this.allDayGrid[w] = this.allDayGrid[w] || [];
for (d = 0; d < this.dayCount; d++) {
if (evtsInView.getCount() > 0) {
var evts = evtsInView.filterBy(function(rec) {
var startsOnDate = (dt.getTime() == rec.data[Ext.calendar.EventMappings.StartDate.name].clearTime(true).getTime());
var spansFromPrevView = (w == 0 && d == 0 && (dt > rec.data[Ext.calendar.EventMappings.StartDate.name]));
return startsOnDate || spansFromPrevView;
},
this);
this.sortEventRecordsForDay(evts);
this.prepareEventGrid(evts, w, d);
}
dt = dt.add(Date.DAY, 1);
}
}
this.currentWeekCount = w;
},
// private
prepareEventGrid: function(evts, w, d) {
var row = 0,
dt = this.viewStart.clone(),
max = this.maxEventsPerDay ? this.maxEventsPerDay: 999;
evts.each(function(evt) {
var M = Ext.calendar.EventMappings,
days = Ext.calendar.Date.diffDays(
Ext.calendar.Date.max(this.viewStart, evt.data[M.StartDate.name]),
Ext.calendar.Date.min(this.viewEnd, evt.data[M.EndDate.name])) + 1;
if (days > 1 || Ext.calendar.Date.diffDays(evt.data[M.StartDate.name], evt.data[M.EndDate.name]) > 1) {
this.prepareEventGridSpans(evt, this.eventGrid, w, d, days);
this.prepareEventGridSpans(evt, this.allDayGrid, w, d, days, true);
} else {
row = this.findEmptyRowIndex(w, d);
this.eventGrid[w][d] = this.eventGrid[w][d] || [];
this.eventGrid[w][d][row] = evt;
if (evt.data[M.IsAllDay.name]) {
row = this.findEmptyRowIndex(w, d, true);
this.allDayGrid[w][d] = this.allDayGrid[w][d] || [];
this.allDayGrid[w][d][row] = evt;
}
}
if (this.evtMaxCount[w] < this.eventGrid[w][d].length) {
this.evtMaxCount[w] = Math.min(max + 1, this.eventGrid[w][d].length);
}
return true;
},
this);
},
// private
prepareEventGridSpans: function(evt, grid, w, d, days, allday) {
// this event spans multiple days/weeks, so we have to preprocess
// the events and store special span events as placeholders so that
// the render routine can build the necessary TD spans correctly.
var w1 = w,
d1 = d,
row = this.findEmptyRowIndex(w, d, allday),
dt = this.viewStart.clone();
var start = {
event: evt,
isSpan: true,
isSpanStart: true,
spanLeft: false,
spanRight: (d == 6)
};
grid[w][d] = grid[w][d] || [];
grid[w][d][row] = start;
while (--days) {
dt = dt.add(Date.DAY, 1);
if (dt > this.viewEnd) {
break;
}
if (++d1 > 6) {
// reset counters to the next week
d1 = 0;
w1++;
row = this.findEmptyRowIndex(w1, 0);
}
grid[w1] = grid[w1] || [];
grid[w1][d1] = grid[w1][d1] || [];
grid[w1][d1][row] = {
event: evt,
isSpan: true,
isSpanStart: (d1 == 0),
spanLeft: (w1 > w) && (d1 % 7 == 0),
spanRight: (d1 == 6) && (days > 1)
};
}
},
// private
findEmptyRowIndex: function(w, d, allday) {
var grid = allday ? this.allDayGrid: this.eventGrid,
day = grid[w] ? grid[w][d] || [] : [],
i = 0,
ln = day.length;
for (; i < ln; i++) {
if (day[i] == null) {
return i;
}
}
return ln;
},
// private
renderTemplate: function() {
if (this.tpl) {
this.tpl.overwrite(this.el, this.getParams());
this.lastRenderStart = this.viewStart.clone();
this.lastRenderEnd = this.viewEnd.clone();
}
},
disableStoreEvents: function() {
this.monitorStoreEvents = false;
},
enableStoreEvents: function(refresh) {
this.monitorStoreEvents = true;
if (refresh === true) {
this.refresh();
}
},
// private
onResize: function() {
this.refresh();
},
// private
onInitDrag: function() {
this.fireEvent('initdrag', this);
},
// private
onEventDrop: function(rec, dt) {
if (Ext.calendar.Date.compare(rec.data[Ext.calendar.EventMappings.StartDate.name], dt) === 0) {
// no changes
return;
}
var diff = dt.getTime() - rec.data[Ext.calendar.EventMappings.StartDate.name].getTime();
rec.set(Ext.calendar.EventMappings.StartDate.name, dt);
rec.set(Ext.calendar.EventMappings.EndDate.name, rec.data[Ext.calendar.EventMappings.EndDate.name].add(Date.MILLI, diff));
this.fireEvent('eventmove', this, rec);
},
// private
onCalendarEndDrag: function(start, end, onComplete) {
// set this flag for other event handlers that might conflict while we're waiting
this.dragPending = true;
// have to wait for the user to save or cancel before finalizing the dd interation
var o = {};
o[Ext.calendar.EventMappings.StartDate.name] = start;
o[Ext.calendar.EventMappings.EndDate.name] = end;
this.fireEvent('rangeselect', this, o, this.onCalendarEndDragComplete.createDelegate(this, [onComplete]));
},
// private
onCalendarEndDragComplete: function(onComplete) {
// callback for the drop zone to clean up
onComplete();
// clear flag for other events to resume normally
this.dragPending = false;
},
// private
onUpdate: function(ds, rec, operation) {
if (this.monitorStoreEvents === false) {
return;
}
if (operation == Ext.data.Record.COMMIT) {
this.refresh();
if (this.enableFx && this.enableUpdateFx) {
this.doUpdateFx(this.getEventEls(rec.data[Ext.calendar.EventMappings.EventId.name]), {
scope: this
});
}
}
},
doUpdateFx: function(els, o) {
this.highlightEvent(els, null, o);
},
// private
onAdd: function(ds, records, index) {
if (this.monitorStoreEvents === false) {
return;
}
var rec = records[0];
this.tempEventId = rec.id;
this.refresh();
if (this.enableFx && this.enableAddFx) {
this.doAddFx(this.getEventEls(rec.data[Ext.calendar.EventMappings.EventId.name]), {
scope: this
});
};
},
doAddFx: function(els, o) {
els.fadeIn(Ext.apply(o, {
duration: 2
}));
},
// private
onRemove: function(ds, rec) {
if (this.monitorStoreEvents === false) {
return;
}
if (this.enableFx && this.enableRemoveFx) {
this.doRemoveFx(this.getEventEls(rec.data[Ext.calendar.EventMappings.EventId.name]), {
remove: true,
scope: this,
callback: this.refresh
});
}
else {
this.getEventEls(rec.data[Ext.calendar.EventMappings.EventId.name]).remove();
this.refresh();
}
},
doRemoveFx: function(els, o) {
els.fadeOut(o);
},
/**
* Visually highlights an event using {@link Ext.Fx#highlight} config options.
* If {@link #highlightEventActions} is false this method will have no effect.
* @param {Ext.CompositeElement} els The element(s) to highlight
* @param {Object} color (optional) The highlight color. Should be a 6 char hex
* color without the leading # (defaults to yellow: 'ffff9c')
* @param {Object} o (optional) Object literal with any of the {@link Ext.Fx} config
* options. See {@link Ext.Fx#highlight} for usage examples.
*/
highlightEvent: function(els, color, o) {
if (this.enableFx) {
var c;
! (Ext.isIE || Ext.isOpera) ?
els.highlight(color, o) :
// Fun IE/Opera handling:
els.each(function(el) {
el.highlight(color, Ext.applyIf({
attr: 'color'
},
o));
c = el.child('.ext-cal-evm');
if (c) {
c.highlight(color, o);
}
},
this);
}
},
/**
* Retrieve an Event object's id from its corresponding node in the DOM.
* @param {String/Element/HTMLElement} el An {@link Ext.Element}, DOM node or id
*/
getEventIdFromEl: function(el) {
el = Ext.get(el);
var id = el.id.split(this.eventElIdDelimiter)[1];
if (id.indexOf('-') > -1) {
//This id has the index of the week it is rendered in as the suffix.
//This allows events that span across weeks to still have reproducibly-unique DOM ids.
id = id.split('-')[0];
}
return id;
},
// private
getEventId: function(eventId) {
if (eventId === undefined && this.tempEventId) {
eventId = this.tempEventId;
}
return eventId;
},
/**
*
* @param {String} eventId
* @param {Boolean} forSelect
* @return {String} The selector class
*/
getEventSelectorCls: function(eventId, forSelect) {
var prefix = forSelect ? '.': '';
return prefix + this.id + this.eventElIdDelimiter + this.getEventId(eventId);
},
/**
*
* @param {String} eventId
* @return {Ext.CompositeElement} The matching CompositeElement of nodes
* that comprise the rendered event. Any event that spans across a view
* boundary will contain more than one internal Element.
*/
getEventEls: function(eventId) {
var els = Ext.select(this.getEventSelectorCls(this.getEventId(eventId), true), false, this.el.id);
return new Ext.CompositeElement(els);
},
/**
* Returns true if the view is currently displaying today's date, else false.
* @return {Boolean} True or false
*/
isToday: function() {
var today = new Date().clearTime().getTime();
return this.viewStart.getTime() <= today && this.viewEnd.getTime() >= today;
},
// private
onDataChanged: function(store) {
this.refresh();
},
// private
isEventVisible: function(evt) {
var start = this.viewStart.getTime(),
end = this.viewEnd.getTime(),
M = Ext.calendar.EventMappings,
evStart = (evt.data ? evt.data[M.StartDate.name] : evt[M.StartDate.name]).getTime(),
evEnd = (evt.data ? evt.data[M.EndDate.name] : evt[M.EndDate.name]).add(Date.SECOND, -1).getTime(),
startsInRange = (evStart >= start && evStart <= end),
endsInRange = (evEnd >= start && evEnd <= end),
spansRange = (evStart < start && evEnd > end);
return (startsInRange || endsInRange || spansRange);
},
// private
isOverlapping: function(evt1, evt2) {
var ev1 = evt1.data ? evt1.data: evt1,
ev2 = evt2.data ? evt2.data: evt2,
M = Ext.calendar.EventMappings,
start1 = ev1[M.StartDate.name].getTime(),
end1 = ev1[M.EndDate.name].add(Date.SECOND, -1).getTime(),
start2 = ev2[M.StartDate.name].getTime(),
end2 = ev2[M.EndDate.name].add(Date.SECOND, -1).getTime();
if (end1 < start1) {
end1 = start1;
}
if (end2 < start2) {
end2 = start2;
}
var ev1startsInEv2 = (start1 >= start2 && start1 <= end2),
ev1EndsInEv2 = (end1 >= start2 && end1 <= end2),
ev1SpansEv2 = (start1 < start2 && end1 > end2);
return (ev1startsInEv2 || ev1EndsInEv2 || ev1SpansEv2);
},
getDayEl: function(dt) {
return Ext.get(this.getDayId(dt));
},
getDayId: function(dt) {
if (Ext.isDate(dt)) {
dt = dt.format('Ymd');
}
return this.id + this.dayElIdDelimiter + dt;
},
/**
* Returns the start date of the view, as set by {@link #setStartDate}. Note that this may not
* be the first date displayed in the rendered calendar -- to get the start and end dates displayed
* to the user use {@link #getViewBounds}.
* @return {Date} The start date
*/
getStartDate: function() {
return this.startDate;
},
/**
* Sets the start date used to calculate the view boundaries to display. The displayed view will be the
* earliest and latest dates that match the view requirements and contain the date passed to this function.
* @param {Date} dt The date used to calculate the new view boundaries
*/
setStartDate: function(start, refresh) {
this.startDate = start.clearTime();
this.setViewBounds(start);
this.store.load({
params: {
start: this.viewStart.format('m-d-Y'),
end: this.viewEnd.format('m-d-Y')
}
});
if (refresh === true) {
this.refresh();
}
this.fireEvent('datechange', this, this.startDate, this.viewStart, this.viewEnd);
},
// private
setViewBounds: function(startDate) {
var start = startDate || this.startDate,
offset = start.getDay() - this.startDay;
switch (this.weekCount) {
case 0:
case 1:
this.viewStart = this.dayCount < 7 ? start: start.add(Date.DAY, -offset).clearTime(true);
this.viewEnd = this.viewStart.add(Date.DAY, this.dayCount || 7).add(Date.SECOND, -1);
return;
case - 1:
// auto by month
start = start.getFirstDateOfMonth();
offset = start.getDay() - this.startDay;
if (offset < 0) {
offset += 7;
}
this.viewStart = start.add(Date.DAY, -offset).clearTime(true);
// start from current month start, not view start:
var end = start.add(Date.MONTH, 1).add(Date.SECOND, -1);
// fill out to the end of the week:
offset = this.startDay;
if (offset > end.getDay()){
// if the offset is larger than the end day index then the last row will be empty so skip it
offset -= 7;
}
this.viewEnd = end.add(Date.DAY, 6 - end.getDay() + offset);
return;
default:
this.viewStart = start.add(Date.DAY, -offset).clearTime(true);
this.viewEnd = this.viewStart.add(Date.DAY, this.weekCount * 7).add(Date.SECOND, -1);
}
},
// private
getViewBounds: function() {
return {
start: this.viewStart,
end: this.viewEnd
};
},
/* private
* Sort events for a single day for display in the calendar. This sorts allday
* events first, then non-allday events are sorted either based on event start
* priority or span priority based on the value of {@link #spansHavePriority}
* (defaults to event start priority).
* @param {MixedCollection} evts A {@link Ext.util.MixedCollection MixedCollection}
* of {@link #Ext.calendar.EventRecord EventRecord} objects
*/
sortEventRecordsForDay: function(evts) {
if (evts.length < 2) {
return;
}
evts.sort('ASC',
function(evtA, evtB) {
var a = evtA.data,
b = evtB.data,
M = Ext.calendar.EventMappings;
// Always sort all day events before anything else
if (a[M.IsAllDay.name]) {
return - 1;
}
else if (b[M.IsAllDay.name]) {
return 1;
}
if (this.spansHavePriority) {
// This logic always weights span events higher than non-span events
// (at the possible expense of start time order). This seems to
// be the approach used by Google calendar and can lead to a more
// visually appealing layout in complex cases, but event order is
// not guaranteed to be consistent.
var diff = Ext.calendar.Date.diffDays;
if (diff(a[M.StartDate.name], a[M.EndDate.name]) > 0) {
if (diff(b[M.StartDate.name], b[M.EndDate.name]) > 0) {
// Both events are multi-day
if (a[M.StartDate.name].getTime() == b[M.StartDate.name].getTime()) {
// If both events start at the same time, sort the one
// that ends later (potentially longer span bar) first
return b[M.EndDate.name].getTime() - a[M.EndDate.name].getTime();
}
return a[M.StartDate.name].getTime() - b[M.StartDate.name].getTime();
}
return - 1;
}
else if (diff(b[M.StartDate.name], b[M.EndDate.name]) > 0) {
return 1;
}
return a[M.StartDate.name].getTime() - b[M.StartDate.name].getTime();
}
else {
// Doing this allows span and non-span events to intermingle but
// remain sorted sequentially by start time. This seems more proper
// but can make for a less visually-compact layout when there are
// many such events mixed together closely on the calendar.
return a[M.StartDate.name].getTime() - b[M.StartDate.name].getTime();
}
}.createDelegate(this));
},
/**
* Updates the view to contain the passed date
* @param {Date} dt The date to display
*/
moveTo: function(dt, noRefresh) {
if (Ext.isDate(dt)) {
this.setStartDate(dt);
if (noRefresh !== false) {
this.refresh();
}
return this.startDate;
}
return dt;
},
/**
* Updates the view to the next consecutive date(s)
*/
moveNext: function(noRefresh) {
return this.moveTo(this.viewEnd.add(Date.DAY, 1));
},
/**
* Updates the view to the previous consecutive date(s)
*/
movePrev: function(noRefresh) {
var days = Ext.calendar.Date.diffDays(this.viewStart, this.viewEnd) + 1;
return this.moveDays( - days, noRefresh);
},
/**
* Shifts the view by the passed number of months relative to the currently set date
* @param {Number} value The number of months (positive or negative) by which to shift the view
*/
moveMonths: function(value, noRefresh) {
return this.moveTo(this.startDate.add(Date.MONTH, value), noRefresh);
},
/**
* Shifts the view by the passed number of weeks relative to the currently set date
* @param {Number} value The number of weeks (positive or negative) by which to shift the view
*/
moveWeeks: function(value, noRefresh) {
return this.moveTo(this.startDate.add(Date.DAY, value * 7), noRefresh);
},
/**
* Shifts the view by the passed number of days relative to the currently set date
* @param {Number} value The number of days (positive or negative) by which to shift the view
*/
moveDays: function(value, noRefresh) {
return this.moveTo(this.startDate.add(Date.DAY, value), noRefresh);
},
/**
* Updates the view to show today
*/
moveToday: function(noRefresh) {
return this.moveTo(new Date(), noRefresh);
},
/**
* Sets the event store used by the calendar to display {@link Ext.calendar.EventRecord events}.
* @param {Ext.data.Store} store
*/
setStore: function(store, initial) {
if (!initial && this.store) {
this.store.un("datachanged", this.onDataChanged, this);
this.store.un("add", this.onAdd, this);
this.store.un("remove", this.onRemove, this);
this.store.un("update", this.onUpdate, this);
this.store.un("clear", this.refresh, this);
}
if (store) {
store.on("datachanged", this.onDataChanged, this);
store.on("add", this.onAdd, this);
store.on("remove", this.onRemove, this);
store.on("update", this.onUpdate, this);
store.on("clear", this.refresh, this);
}
this.store = store;
if (store && store.getCount() > 0) {
this.refresh();
}
},
getEventRecord: function(id) {
var idx = this.store.find(Ext.calendar.EventMappings.EventId.name, id);
return this.store.getAt(idx);
},
getEventRecordFromEl: function(el) {
return this.getEventRecord(this.getEventIdFromEl(el));
},
// private
getParams: function() {
return {
viewStart: this.viewStart,
viewEnd: this.viewEnd,
startDate: this.startDate,
dayCount: this.dayCount,
weekCount: this.weekCount,
title: this.getTitle()
};
},
getTitle: function() {
return this.startDate.format('F Y');
},
/*
* Shared click handling. Each specific view also provides view-specific
* click handling that calls this first. This method returns true if it
* can handle the click (and so the subclass should ignore it) else false.
*/
onClick: function(e, t) {
var el = e.getTarget(this.eventSelector, 5);
if (el) {
var id = this.getEventIdFromEl(el);
this.fireEvent('eventclick', this, this.getEventRecord(id), el);
return true;
}
},
// private
onMouseOver: function(e, t) {
if (this.trackMouseOver !== false && (this.dragZone == undefined || !this.dragZone.dragging)) {
if (!this.handleEventMouseEvent(e, t, 'over')) {
this.handleDayMouseEvent(e, t, 'over');
}
}
},
// private
onMouseOut: function(e, t) {
if (this.trackMouseOver !== false && (this.dragZone == undefined || !this.dragZone.dragging)) {
if (!this.handleEventMouseEvent(e, t, 'out')) {
this.handleDayMouseEvent(e, t, 'out');
}
}
},
// private
handleEventMouseEvent: function(e, t, type) {
var el = e.getTarget(this.eventSelector, 5, true),
rel,
els,
evtId;
if (el) {
rel = Ext.get(e.getRelatedTarget());
if (el == rel || el.contains(rel)) {
return true;
}
evtId = this.getEventIdFromEl(el);
if (this.eventOverClass != '') {
els = this.getEventEls(evtId);
els[type == 'over' ? 'addClass': 'removeClass'](this.eventOverClass);
}
this.fireEvent('event' + type, this, this.getEventRecord(evtId), el);
return true;
}
return false;
},
// private
getDateFromId: function(id, delim) {
var parts = id.split(delim);
return parts[parts.length - 1];
},
// private
handleDayMouseEvent: function(e, t, type) {
t = e.getTarget('td', 3);
if (t) {
if (t.id && t.id.indexOf(this.dayElIdDelimiter) > -1) {
var dt = this.getDateFromId(t.id, this.dayElIdDelimiter),
rel = Ext.get(e.getRelatedTarget()),
relTD,
relDate;
if (rel) {
relTD = rel.is('td') ? rel: rel.up('td', 3);
relDate = relTD && relTD.id ? this.getDateFromId(relTD.id, this.dayElIdDelimiter) : '';
}
if (!rel || dt != relDate) {
var el = this.getDayEl(dt);
if (el && this.dayOverClass != '') {
el[type == 'over' ? 'addClass': 'removeClass'](this.dayOverClass);
}
this.fireEvent('day' + type, this, Date.parseDate(dt, "Ymd"), el);
}
}
}
},
// private
renderItems: function() {
throw 'This method must be implemented by a subclass';
}
});
/**
* @class Ext.calendar.MonthView
* @extends Ext.calendar.CalendarView
* <p>Displays a calendar view by month. This class does not usually need ot be used directly as you can
* use a {@link Ext.calendar.CalendarPanel CalendarPanel} to manage multiple calendar views at once including
* the month view.</p>
* @constructor
* @param {Object} config The config object
*/
Ext.calendar.MonthView = Ext.extend(Ext.calendar.CalendarView, {
/**
* @cfg {Boolean} showTime
* True to display the current time in today's box in the calendar, false to not display it (defautls to true)
*/
showTime: true,
/**
* @cfg {Boolean} showTodayText
* True to display the {@link #todayText} string in today's box in the calendar, false to not display it (defautls to true)
*/
showTodayText: true,
/**
* @cfg {String} todayText
* The text to display in the current day's box in the calendar when {@link #showTodayText} is true (defaults to 'Today')
*/
todayText: 'Today',
/**
* @cfg {Boolean} showHeader
* True to display a header beneath the navigation bar containing the week names above each week's column, false not to
* show it and instead display the week names in the first row of days in the calendar (defaults to false).
*/
showHeader: false,
/**
* @cfg {Boolean} showWeekLinks
* True to display an extra column before the first day in the calendar that links to the {@link Ext.calendar.WeekView view}
* for each individual week, false to not show it (defaults to false). If true, the week links can also contain the week
* number depending on the value of {@link #showWeekNumbers}.
*/
showWeekLinks: false,
/**
* @cfg {Boolean} showWeekNumbers
* True to show the week number for each week in the calendar in the week link column, false to show nothing (defaults to false).
* Note that if {@link #showWeekLinks} is false this config will have no affect even if true.
*/
showWeekNumbers: false,
/**
* @cfg {String} weekLinkOverClass
* The CSS class name applied when the mouse moves over a week link element (only applies when {@link #showWeekLinks} is true,
* defaults to 'ext-week-link-over').
*/
weekLinkOverClass: 'ext-week-link-over',
//private properties -- do not override:
daySelector: '.ext-cal-day',
moreSelector: '.ext-cal-ev-more',
weekLinkSelector: '.ext-cal-week-link',
weekCount: -1,
// defaults to auto by month
dayCount: 7,
moreElIdDelimiter: '-more-',
weekLinkIdDelimiter: 'ext-cal-week-',
// private
initComponent: function() {
Ext.calendar.MonthView.superclass.initComponent.call(this);
this.addEvents({
/**
* @event dayclick
* Fires after the user clicks within the view container and not on an event element
* @param {Ext.calendar.MonthView} this
* @param {Date} dt The date/time that was clicked on
* @param {Boolean} allday True if the day clicked on represents an all-day box, else false. Clicks within the
* MonthView always return true for this param.
* @param {Ext.Element} el The Element that was clicked on
*/
dayclick: true,
/**
* @event weekclick
* Fires after the user clicks within a week link (when {@link #showWeekLinks is true)
* @param {Ext.calendar.MonthView} this
* @param {Date} dt The start date of the week that was clicked on
*/
weekclick: true,
// inherited docs
dayover: true,
// inherited docs
dayout: true
});
},
// private
initDD: function() {
var cfg = {
view: this,
createText: this.ddCreateEventText,
moveText: this.ddMoveEventText,
ddGroup: 'MonthViewDD'
};
this.dragZone = new Ext.calendar.DragZone(this.el, cfg);
this.dropZone = new Ext.calendar.DropZone(this.el, cfg);
},
// private
onDestroy: function() {
Ext.destroy(this.ddSelector);
Ext.destroy(this.dragZone);
Ext.destroy(this.dropZone);
Ext.calendar.MonthView.superclass.onDestroy.call(this);
},
// private
afterRender: function() {
if (!this.tpl) {
this.tpl = new Ext.calendar.MonthViewTemplate({
id: this.id,
showTodayText: this.showTodayText,
todayText: this.todayText,
showTime: this.showTime,
showHeader: this.showHeader,
showWeekLinks: this.showWeekLinks,
showWeekNumbers: this.showWeekNumbers
});
}
this.tpl.compile();
this.addClass('ext-cal-monthview ext-cal-ct');
Ext.calendar.MonthView.superclass.afterRender.call(this);
},
// private
onResize: function() {
if (this.monitorResize) {
this.maxEventsPerDay = this.getMaxEventsPerDay();
this.refresh();
}
},
// private
forceSize: function() {
// Compensate for the week link gutter width if visible
if (this.showWeekLinks && this.el && this.el.child) {
var hd = this.el.select('.ext-cal-hd-days-tbl'),
bgTbl = this.el.select('.ext-cal-bg-tbl'),
evTbl = this.el.select('.ext-cal-evt-tbl'),
wkLinkW = this.el.child('.ext-cal-week-link').getWidth(),
w = this.el.getWidth() - wkLinkW;
hd.setWidth(w);
bgTbl.setWidth(w);
evTbl.setWidth(w);
}
Ext.calendar.MonthView.superclass.forceSize.call(this);
},
//private
initClock: function() {
if (Ext.fly(this.id + '-clock') !== null) {
this.prevClockDay = new Date().getDay();
if (this.clockTask) {
Ext.TaskMgr.stop(this.clockTask);
}
this.clockTask = Ext.TaskMgr.start({
run: function() {
var el = Ext.fly(this.id + '-clock'),
t = new Date();
if (t.getDay() == this.prevClockDay) {
if (el) {
el.update(t.format('g:i a'));
}
}
else {
this.prevClockDay = t.getDay();
this.moveTo(t);
}
},
scope: this,
interval: 1000
});
}
},
// inherited docs
getEventBodyMarkup: function() {
if (!this.eventBodyMarkup) {
this.eventBodyMarkup = ['{Title}',
'<tpl if="_isReminder">',
'<i class="ext-cal-ic ext-cal-ic-rem">&nbsp;</i>',
'</tpl>',
'<tpl if="_isRecurring">',
'<i class="ext-cal-ic ext-cal-ic-rcr">&nbsp;</i>',
'</tpl>',
'<tpl if="spanLeft">',
'<i class="ext-cal-spl">&nbsp;</i>',
'</tpl>',
'<tpl if="spanRight">',
'<i class="ext-cal-spr">&nbsp;</i>',
'</tpl>'
].join('');
}
return this.eventBodyMarkup;
},
// inherited docs
getEventTemplate: function() {
if (!this.eventTpl) {
var tpl,
body = this.getEventBodyMarkup();
tpl = !(Ext.isIE || Ext.isOpera) ?
new Ext.XTemplate(
'<div id="{_elId}" class="{_selectorCls} {_colorCls} {values.spanCls} ext-cal-evt ext-cal-evr">',
body,
'</div>'
)
: new Ext.XTemplate(
'<tpl if="_renderAsAllDay">',
'<div id="{_elId}" class="{_selectorCls} {values.spanCls} {_colorCls} ext-cal-evt ext-cal-evo">',
'<div class="ext-cal-evm">',
'<div class="ext-cal-evi">',
'</tpl>',
'<tpl if="!_renderAsAllDay">',
'<div id="{_elId}" class="{_selectorCls} {_colorCls} ext-cal-evt ext-cal-evr">',
'</tpl>',
body,
'<tpl if="_renderAsAllDay">',
'</div>',
'</div>',
'</tpl>',
'</div>'
);
tpl.compile();
this.eventTpl = tpl;
}
return this.eventTpl;
},
// private
getTemplateEventData: function(evt) {
var M = Ext.calendar.EventMappings,
selector = this.getEventSelectorCls(evt[M.EventId.name]),
title = evt[M.Title.name];
return Ext.applyIf({
_selectorCls: selector,
_colorCls: 'ext-color-' + (evt[M.CalendarId.name] ?
evt[M.CalendarId.name] : 'default') + (evt._renderAsAllDay ? '-ad': ''),
_elId: selector + '-' + evt._weekIndex,
_isRecurring: evt.Recurrence && evt.Recurrence != '',
_isReminder: evt[M.Reminder.name] && evt[M.Reminder.name] != '',
Title: (evt[M.IsAllDay.name] ? '': evt[M.StartDate.name].format('g:ia ')) + (!title || title.length == 0 ? '(No title)': title)
},
evt);
},
// private
refresh: function() {
if (this.detailPanel) {
this.detailPanel.hide();
}
Ext.calendar.MonthView.superclass.refresh.call(this);
if (this.showTime !== false) {
this.initClock();
}
},
// private
renderItems: function() {
Ext.calendar.WeekEventRenderer.render({
eventGrid: this.allDayOnly ? this.allDayGrid: this.eventGrid,
viewStart: this.viewStart,
tpl: this.getEventTemplate(),
maxEventsPerDay: this.maxEventsPerDay,
id: this.id,
templateDataFn: this.getTemplateEventData.createDelegate(this),
evtMaxCount: this.evtMaxCount,
weekCount: this.weekCount,
dayCount: this.dayCount
});
this.fireEvent('eventsrendered', this);
},
// private
getDayEl: function(dt) {
return Ext.get(this.getDayId(dt));
},
// private
getDayId: function(dt) {
if (Ext.isDate(dt)) {
dt = dt.format('Ymd');
}
return this.id + this.dayElIdDelimiter + dt;
},
// private
getWeekIndex: function(dt) {
var el = this.getDayEl(dt).up('.ext-cal-wk-ct');
return parseInt(el.id.split('-wk-')[1], 10);
},
// private
getDaySize: function(contentOnly) {
var box = this.el.getBox(),
w = box.width / this.dayCount,
h = box.height / this.getWeekCount();
if (contentOnly) {
var hd = this.el.select('.ext-cal-dtitle').first().parent('tr');
h = hd ? h - hd.getHeight(true) : h;
}
return {
height: h,
width: w
};
},
// private
getEventHeight: function() {
if (!this.eventHeight) {
var evt = this.el.select('.ext-cal-evt').first();
this.eventHeight = evt ? evt.parent('tr').getHeight() : 18;
}
return this.eventHeight;
},
// private
getMaxEventsPerDay: function() {
var dayHeight = this.getDaySize(true).height,
h = this.getEventHeight(),
max = Math.max(Math.floor((dayHeight - h) / h), 0);
return max;
},
// private
getDayAt: function(x, y) {
var box = this.el.getBox(),
daySize = this.getDaySize(),
dayL = Math.floor(((x - box.x) / daySize.width)),
dayT = Math.floor(((y - box.y) / daySize.height)),
days = (dayT * 7) + dayL,
dt = this.viewStart.add(Date.DAY, days);
return {
date: dt,
el: this.getDayEl(dt)
};
},
// inherited docs
moveNext: function() {
return this.moveMonths(1);
},
// inherited docs
movePrev: function() {
return this.moveMonths( - 1);
},
// private
onInitDrag: function() {
Ext.calendar.MonthView.superclass.onInitDrag.call(this);
Ext.select(this.daySelector).removeClass(this.dayOverClass);
if (this.detailPanel) {
this.detailPanel.hide();
}
},
// private
onMoreClick: function(dt) {
if (!this.detailPanel) {
this.detailPanel = new Ext.Panel({
id: this.id + '-details-panel',
title: dt.format('F j'),
layout: 'fit',
floating: true,
renderTo: Ext.getBody(),
tools: [{
id: 'close',
handler: function(e, t, p) {
p.hide();
}
}],
items: {
xtype: 'monthdaydetailview',
id: this.id + '-details-view',
date: dt,
view: this,
store: this.store,
listeners: {
'eventsrendered': this.onDetailViewUpdated.createDelegate(this)
}
}
});
}
else {
this.detailPanel.setTitle(dt.format('F j'));
}
this.detailPanel.getComponent(this.id + '-details-view').update(dt);
},
// private
onDetailViewUpdated: function(view, dt, numEvents) {
var p = this.detailPanel,
frameH = p.getFrameHeight(),
evtH = this.getEventHeight(),
bodyH = frameH + (numEvents * evtH) + 3,
dayEl = this.getDayEl(dt),
box = dayEl.getBox();
p.updateBox(box);
p.setHeight(bodyH);
p.setWidth(Math.max(box.width, 220));
p.show();
p.getPositionEl().alignTo(dayEl, 't-t?');
},
// private
onHide: function() {
Ext.calendar.MonthView.superclass.onHide.call(this);
if (this.detailPanel) {
this.detailPanel.hide();
}
},
// private
onClick: function(e, t) {
if (this.detailPanel) {
this.detailPanel.hide();
}
if (Ext.calendar.MonthView.superclass.onClick.apply(this, arguments)) {
// The superclass handled the click already so exit
return;
}
if (this.dropZone) {
this.dropZone.clearShims();
}
var el = e.getTarget(this.weekLinkSelector, 3),
dt,
parts;
if (el) {
dt = el.id.split(this.weekLinkIdDelimiter)[1];
this.fireEvent('weekclick', this, Date.parseDate(dt, 'Ymd'));
return;
}
el = e.getTarget(this.moreSelector, 3);
if (el) {
dt = el.id.split(this.moreElIdDelimiter)[1];
this.onMoreClick(Date.parseDate(dt, 'Ymd'));
return;
}
el = e.getTarget('td', 3);
if (el) {
if (el.id && el.id.indexOf(this.dayElIdDelimiter) > -1) {
parts = el.id.split(this.dayElIdDelimiter);
dt = parts[parts.length - 1];
this.fireEvent('dayclick', this, Date.parseDate(dt, 'Ymd'), false, Ext.get(this.getDayId(dt)));
return;
}
}
},
// private
handleDayMouseEvent: function(e, t, type) {
var el = e.getTarget(this.weekLinkSelector, 3, true);
if (el) {
el[type == 'over' ? 'addClass': 'removeClass'](this.weekLinkOverClass);
return;
}
Ext.calendar.MonthView.superclass.handleDayMouseEvent.apply(this, arguments);
}
});
Ext.reg('monthview', Ext.calendar.MonthView);
/**
* @class Ext.calendar.DayHeaderView
* @extends Ext.calendar.MonthView
* <p>This is the header area container within the day and week views where all-day events are displayed.
* Normally you should not need to use this class directly -- instead you should use {@link Ext.calendar.DayView DayView}
* which aggregates this class and the {@link Ext.calendar.DayBodyView DayBodyView} into the single unified view
* presented by {@link Ext.calendar.CalendarPanel CalendarPanel}.</p>
* @constructor
* @param {Object} config The config object
*/
Ext.calendar.DayHeaderView = Ext.extend(Ext.calendar.MonthView, {
// private configs
weekCount: 1,
dayCount: 1,
allDayOnly: true,
monitorResize: false,
/**
* @event dayclick
* Fires after the user clicks within the day view container and not on an event element
* @param {Ext.calendar.DayBodyView} this
* @param {Date} dt The date/time that was clicked on
* @param {Boolean} allday True if the day clicked on represents an all-day box, else false. Clicks within the
* DayHeaderView always return true for this param.
* @param {Ext.Element} el The Element that was clicked on
*/
// private
afterRender: function() {
if (!this.tpl) {
this.tpl = new Ext.calendar.DayHeaderTemplate({
id: this.id,
showTodayText: this.showTodayText,
todayText: this.todayText,
showTime: this.showTime
});
}
this.tpl.compile();
this.addClass('ext-cal-day-header');
Ext.calendar.DayHeaderView.superclass.afterRender.call(this);
},
// private
forceSize: Ext.emptyFn,
// private
refresh: function() {
Ext.calendar.DayHeaderView.superclass.refresh.call(this);
this.recalcHeaderBox();
},
// private
recalcHeaderBox: function() {
var tbl = this.el.child('.ext-cal-evt-tbl'),
h = tbl.getHeight();
this.el.setHeight(h + 7);
if (Ext.isIE && Ext.isStrict) {
this.el.child('.ext-cal-hd-ad-inner').setHeight(h + 4);
}
if (Ext.isOpera) {
//TODO: figure out why Opera refuses to refresh height when
//the new height is lower than the previous one
// var ct = this.el.child('.ext-cal-hd-ct');
// ct.repaint();
}
},
// private
moveNext: function(noRefresh) {
this.moveDays(this.dayCount, noRefresh);
},
// private
movePrev: function(noRefresh) {
this.moveDays( - this.dayCount, noRefresh);
},
// private
onClick: function(e, t) {
var el = e.getTarget('td', 3),
parts,
dt;
if (el) {
if (el.id && el.id.indexOf(this.dayElIdDelimiter) > -1) {
parts = el.id.split(this.dayElIdDelimiter);
dt = parts[parts.length - 1];
this.fireEvent('dayclick', this, Date.parseDate(dt, 'Ymd'), true, Ext.get(this.getDayId(dt)));
return;
}
}
Ext.calendar.DayHeaderView.superclass.onClick.apply(this, arguments);
}
});
Ext.reg('dayheaderview', Ext.calendar.DayHeaderView);
/**S
* @class Ext.calendar.DayBodyView
* @extends Ext.calendar.CalendarView
* <p>This is the scrolling container within the day and week views where non-all-day events are displayed.
* Normally you should not need to use this class directly -- instead you should use {@link Ext.calendar.DayView DayView}
* which aggregates this class and the {@link Ext.calendar.DayHeaderView DayHeaderView} into the single unified view
* presented by {@link Ext.calendar.CalendarPanel CalendarPanel}.</p>
* @constructor
* @param {Object} config The config object
*/
Ext.calendar.DayBodyView = Ext.extend(Ext.calendar.CalendarView, {
//private
dayColumnElIdDelimiter: '-day-col-',
//private
initComponent: function() {
Ext.calendar.DayBodyView.superclass.initComponent.call(this);
this.addEvents({
/**
* @event eventresize
* Fires after the user drags the resize handle of an event to resize it
* @param {Ext.calendar.DayBodyView} this
* @param {Ext.calendar.EventRecord} rec The {@link Ext.calendar.EventRecord record} for the event that was resized
* containing the updated start and end dates
*/
eventresize: true,
/**
* @event dayclick
* Fires after the user clicks within the day view container and not on an event element
* @param {Ext.calendar.DayBodyView} this
* @param {Date} dt The date/time that was clicked on
* @param {Boolean} allday True if the day clicked on represents an all-day box, else false. Clicks within the
* DayBodyView always return false for this param.
* @param {Ext.Element} el The Element that was clicked on
*/
dayclick: true
});
},
//private
initDD: function() {
var cfg = {
createText: this.ddCreateEventText,
moveText: this.ddMoveEventText,
resizeText: this.ddResizeEventText
};
this.el.ddScrollConfig = {
// scrolling is buggy in IE/Opera for some reason. A larger vthresh
// makes it at least functional if not perfect
vthresh: Ext.isIE || Ext.isOpera ? 100: 40,
hthresh: -1,
frequency: 50,
increment: 100,
ddGroup: 'DayViewDD'
};
this.dragZone = new Ext.calendar.DayViewDragZone(this.el, Ext.apply({
view: this,
containerScroll: true
},
cfg));
this.dropZone = new Ext.calendar.DayViewDropZone(this.el, Ext.apply({
view: this
},
cfg));
},
//private
refresh: function() {
var top = this.el.getScroll().top;
this.prepareData();
this.renderTemplate();
this.renderItems();
// skip this if the initial render scroll position has not yet been set.
// necessary since IE/Opera must be deferred, so the first refresh will
// override the initial position by default and always set it to 0.
if (this.scrollReady) {
this.scrollTo(top);
}
},
/**
* Scrolls the container to the specified vertical position. If the view is large enough that
* there is no scroll overflow then this method will have no affect.
* @param {Number} y The new vertical scroll position in pixels
* @param {Boolean} defer (optional) <p>True to slightly defer the call, false to execute immediately.</p>
* <p>This method will automatically defer itself for IE and Opera (even if you pass false) otherwise
* the scroll position will not update in those browsers. You can optionally pass true, however, to
* force the defer in all browsers, or use your own custom conditions to determine whether this is needed.</p>
* <p>Note that this method should not generally need to be called directly as scroll position is managed internally.</p>
*/
scrollTo: function(y, defer) {
defer = defer || (Ext.isIE || Ext.isOpera);
if (defer) {
(function() {
this.el.scrollTo('top', y);
this.scrollReady = true;
}).defer(10, this);
}
else {
this.el.scrollTo('top', y);
this.scrollReady = true;
}
},
// private
afterRender: function() {
if (!this.tpl) {
this.tpl = new Ext.calendar.DayBodyTemplate({
id: this.id,
dayCount: this.dayCount,
showTodayText: this.showTodayText,
todayText: this.todayText,
showTime: this.showTime
});
}
this.tpl.compile();
this.addClass('ext-cal-body-ct');
Ext.calendar.DayBodyView.superclass.afterRender.call(this);
// default scroll position to 7am:
this.scrollTo(7 * 42);
},
// private
forceSize: Ext.emptyFn,
// private
onEventResize: function(rec, data) {
var D = Ext.calendar.Date,
start = Ext.calendar.EventMappings.StartDate.name,
end = Ext.calendar.EventMappings.EndDate.name;
if (D.compare(rec.data[start], data.StartDate) === 0 &&
D.compare(rec.data[end], data.EndDate) === 0) {
// no changes
return;
}
rec.set(start, data.StartDate);
rec.set(end, data.EndDate);
this.fireEvent('eventresize', this, rec);
},
// inherited docs
getEventBodyMarkup: function() {
if (!this.eventBodyMarkup) {
this.eventBodyMarkup = ['{Title}',
'<tpl if="_isReminder">',
'<i class="ext-cal-ic ext-cal-ic-rem">&nbsp;</i>',
'</tpl>',
'<tpl if="_isRecurring">',
'<i class="ext-cal-ic ext-cal-ic-rcr">&nbsp;</i>',
'</tpl>'
// '<tpl if="spanLeft">',
// '<i class="ext-cal-spl">&nbsp;</i>',
// '</tpl>',
// '<tpl if="spanRight">',
// '<i class="ext-cal-spr">&nbsp;</i>',
// '</tpl>'
].join('');
}
return this.eventBodyMarkup;
},
// inherited docs
getEventTemplate: function() {
if (!this.eventTpl) {
this.eventTpl = !(Ext.isIE || Ext.isOpera) ?
new Ext.XTemplate(
'<div id="{_elId}" class="{_selectorCls} {_colorCls} ext-cal-evt ext-cal-evr" style="left: {_left}%; width: {_width}%; top: {_top}px; height: {_height}px;">',
'<div class="ext-evt-bd">', this.getEventBodyMarkup(), '</div>',
'<div class="ext-evt-rsz"><div class="ext-evt-rsz-h">&nbsp;</div></div>',
'</div>'
)
: new Ext.XTemplate(
'<div id="{_elId}" class="ext-cal-evt {_selectorCls} {_colorCls}-x" style="left: {_left}%; width: {_width}%; top: {_top}px;">',
'<div class="ext-cal-evb">&nbsp;</div>',
'<dl style="height: {_height}px;" class="ext-cal-evdm">',
'<dd class="ext-evt-bd">',
this.getEventBodyMarkup(),
'</dd>',
'<div class="ext-evt-rsz"><div class="ext-evt-rsz-h">&nbsp;</div></div>',
'</dl>',
'<div class="ext-cal-evb">&nbsp;</div>',
'</div>'
);
this.eventTpl.compile();
}
return this.eventTpl;
},
/**
* <p>Returns the XTemplate that is bound to the calendar's event store (it expects records of type
* {@link Ext.calendar.EventRecord}) to populate the calendar views with <strong>all-day</strong> events.
* Internally this method by default generates different markup for browsers that support CSS border radius
* and those that don't. This method can be overridden as needed to customize the markup generated.</p>
* <p>Note that this method calls {@link #getEventBodyMarkup} to retrieve the body markup for events separately
* from the surrounding container markup. This provdes the flexibility to customize what's in the body without
* having to override the entire XTemplate. If you do override this method, you should make sure that your
* overridden version also does the same.</p>
* @return {Ext.XTemplate} The event XTemplate
*/
getEventAllDayTemplate: function() {
if (!this.eventAllDayTpl) {
var tpl,
body = this.getEventBodyMarkup();
tpl = !(Ext.isIE || Ext.isOpera) ?
new Ext.XTemplate(
'<div id="{_elId}" class="{_selectorCls} {_colorCls} {values.spanCls} ext-cal-evt ext-cal-evr" style="left: {_left}%; width: {_width}%; top: {_top}px; height: {_height}px;">',
body,
'</div>'
)
: new Ext.XTemplate(
'<div id="{_elId}" class="ext-cal-evt" style="left: {_left}%; width: {_width}%; top: {_top}px; height: {_height}px;">',
'<div class="{_selectorCls} {values.spanCls} {_colorCls} ext-cal-evo">',
'<div class="ext-cal-evm">',
'<div class="ext-cal-evi">',
body,
'</div>',
'</div>',
'</div></div>'
);
tpl.compile();
this.eventAllDayTpl = tpl;
}
return this.eventAllDayTpl;
},
// private
getTemplateEventData: function(evt) {
var selector = this.getEventSelectorCls(evt[Ext.calendar.EventMappings.EventId.name]),
data = {},
M = Ext.calendar.EventMappings;
this.getTemplateEventBox(evt);
data._selectorCls = selector;
data._colorCls = 'ext-color-' + evt[M.CalendarId.name] + (evt._renderAsAllDay ? '-ad': '');
data._elId = selector + (evt._weekIndex ? '-' + evt._weekIndex: '');
data._isRecurring = evt.Recurrence && evt.Recurrence != '';
data._isReminder = evt[M.Reminder.name] && evt[M.Reminder.name] != '';
var title = evt[M.Title.name];
data.Title = (evt[M.IsAllDay.name] ? '': evt[M.StartDate.name].format('g:ia ')) + (!title || title.length == 0 ? '(No title)': title);
return Ext.applyIf(data, evt);
},
// private
getTemplateEventBox: function(evt) {
var heightFactor = 0.7,
start = evt[Ext.calendar.EventMappings.StartDate.name],
end = evt[Ext.calendar.EventMappings.EndDate.name],
startMins = start.getHours() * 60 + start.getMinutes(),
endMins = end.getHours() * 60 + end.getMinutes(),
diffMins = endMins - startMins;
evt._left = 0;
evt._width = 100;
evt._top = Math.round(startMins * heightFactor) + 1;
evt._height = Math.max((diffMins * heightFactor) - 2, 15);
},
// private
renderItems: function() {
var day = 0,
evts = [],
ev,
d,
ct,
item,
i,
j,
l,
overlapCols,
prevCol,
colWidth,
evtWidth,
markup,
target;
for (; day < this.dayCount; day++) {
ev = emptyCells = skipped = 0;
d = this.eventGrid[0][day];
ct = d ? d.length: 0;
for (; ev < ct; ev++) {
evt = d[ev];
if (!evt) {
continue;
}
item = evt.data || evt.event.data;
if (item._renderAsAllDay) {
continue;
}
Ext.apply(item, {
cls: 'ext-cal-ev',
_positioned: true
});
evts.push({
data: this.getTemplateEventData(item),
date: this.viewStart.add(Date.DAY, day)
});
}
}
// overlapping event pre-processing loop
i = j = overlapCols = prevCol = 0;
l = evts.length;
for (; i < l; i++) {
evt = evts[i].data;
evt2 = null;
prevCol = overlapCols;
for (j = 0; j < l; j++) {
if (i == j) {
continue;
}
evt2 = evts[j].data;
if (this.isOverlapping(evt, evt2)) {
evt._overlap = evt._overlap == undefined ? 1: evt._overlap + 1;
if (i < j) {
if (evt._overcol === undefined) {
evt._overcol = 0;
}
evt2._overcol = evt._overcol + 1;
overlapCols = Math.max(overlapCols, evt2._overcol);
}
}
}
}
// rendering loop
for (i = 0; i < l; i++) {
evt = evts[i].data;
if (evt._overlap !== undefined) {
colWidth = 100 / (overlapCols + 1);
evtWidth = 100 - (colWidth * evt._overlap);
evt._width = colWidth;
evt._left = colWidth * evt._overcol;
}
markup = this.getEventTemplate().apply(evt);
target = this.id + '-day-col-' + evts[i].date.format('Ymd');
Ext.DomHelper.append(target, markup);
}
this.fireEvent('eventsrendered', this);
},
// private
getDayEl: function(dt) {
return Ext.get(this.getDayId(dt));
},
// private
getDayId: function(dt) {
if (Ext.isDate(dt)) {
dt = dt.format('Ymd');
}
return this.id + this.dayColumnElIdDelimiter + dt;
},
// private
getDaySize: function() {
var box = this.el.child('.ext-cal-day-col-inner').getBox();
return {
height: box.height,
width: box.width
};
},
// private
getDayAt: function(x, y) {
var sel = '.ext-cal-body-ct',
xoffset = this.el.child('.ext-cal-day-times').getWidth(),
viewBox = this.el.getBox(),
daySize = this.getDaySize(false),
relX = x - viewBox.x - xoffset,
dayIndex = Math.floor(relX / daySize.width),
// clicked col index
scroll = this.el.getScroll(),
row = this.el.child('.ext-cal-bg-row'),
// first avail row, just to calc size
rowH = row.getHeight() / 2,
// 30 minute increment since a row is 60 minutes
relY = y - viewBox.y - rowH + scroll.top,
rowIndex = Math.max(0, Math.ceil(relY / rowH)),
mins = rowIndex * 30,
dt = this.viewStart.add(Date.DAY, dayIndex).add(Date.MINUTE, mins),
el = this.getDayEl(dt),
timeX = x;
if (el) {
timeX = el.getLeft();
}
return {
date: dt,
el: el,
// this is the box for the specific time block in the day that was clicked on:
timeBox: {
x: timeX,
y: (rowIndex * 21) + viewBox.y - scroll.top,
width: daySize.width,
height: rowH
}
};
},
// private
onClick: function(e, t) {
if (this.dragPending || Ext.calendar.DayBodyView.superclass.onClick.apply(this, arguments)) {
// The superclass handled the click already so exit
return;
}
if (e.getTarget('.ext-cal-day-times', 3) !== null) {
// ignore clicks on the times-of-day gutter
return;
}
var el = e.getTarget('td', 3);
if (el) {
if (el.id && el.id.indexOf(this.dayElIdDelimiter) > -1) {
var dt = this.getDateFromId(el.id, this.dayElIdDelimiter);
this.fireEvent('dayclick', this, Date.parseDate(dt, 'Ymd'), true, Ext.get(this.getDayId(dt, true)));
return;
}
}
var day = this.getDayAt(e.xy[0], e.xy[1]);
if (day && day.date) {
this.fireEvent('dayclick', this, day.date, false, null);
}
}
});
Ext.reg('daybodyview', Ext.calendar.DayBodyView);
/**
* @class Ext.calendar.DayView
* @extends Ext.Container
* <p>Unlike other calendar views, is not actually a subclass of {@link Ext.calendar.CalendarView CalendarView}.
* Instead it is a {@link Ext.Container Container} subclass that internally creates and manages the layouts of
* a {@link Ext.calendar.DayHeaderView DayHeaderView} and a {@link Ext.calendar.DayBodyView DayBodyView}. As such
* DayView accepts any config values that are valid for DayHeaderView and DayBodyView and passes those through
* to the contained views. It also supports the interface required of any calendar view and in turn calls methods
* on the contained views as necessary.</p>
* @constructor
* @param {Object} config The config object
*/
Ext.calendar.DayView = Ext.extend(Ext.Container, {
/**
* @cfg {Boolean} showTime
* True to display the current time in today's box in the calendar, false to not display it (defautls to true)
*/
showTime: true,
/**
* @cfg {Boolean} showTodayText
* True to display the {@link #todayText} string in today's box in the calendar, false to not display it (defautls to true)
*/
showTodayText: true,
/**
* @cfg {String} todayText
* The text to display in the current day's box in the calendar when {@link #showTodayText} is true (defaults to 'Today')
*/
todayText: 'Today',
/**
* @cfg {String} ddCreateEventText
* The text to display inside the drag proxy while dragging over the calendar to create a new event (defaults to
* 'Create event for {0}' where {0} is a date range supplied by the view)
*/
ddCreateEventText: 'Create event for {0}',
/**
* @cfg {String} ddMoveEventText
* The text to display inside the drag proxy while dragging an event to reposition it (defaults to
* 'Move event to {0}' where {0} is the updated event start date/time supplied by the view)
*/
ddMoveEventText: 'Move event to {0}',
/**
* @cfg {Number} dayCount
* The number of days to display in the view (defaults to 1)
*/
dayCount: 1,
// private
initComponent : function(){
// rendering more than 7 days per view is not supported
this.dayCount = this.dayCount > 7 ? 7 : this.dayCount;
var cfg = Ext.apply({}, this.initialConfig);
cfg.showTime = this.showTime;
cfg.showTodatText = this.showTodayText;
cfg.todayText = this.todayText;
cfg.dayCount = this.dayCount;
cfg.wekkCount = 1;
var header = Ext.applyIf({
xtype: 'dayheaderview',
id: this.id+'-hd'
}, cfg);
var body = Ext.applyIf({
xtype: 'daybodyview',
id: this.id+'-bd'
}, cfg);
this.items = [header, body];
this.addClass('ext-cal-dayview ext-cal-ct');
Ext.calendar.DayView.superclass.initComponent.call(this);
},
// private
afterRender : function(){
Ext.calendar.DayView.superclass.afterRender.call(this);
this.header = Ext.getCmp(this.id+'-hd');
this.body = Ext.getCmp(this.id+'-bd');
this.body.on('eventsrendered', this.forceSize, this);
},
// private
refresh : function(){
this.header.refresh();
this.body.refresh();
},
// private
forceSize: function(){
// The defer call is mainly for good ol' IE, but it doesn't hurt in
// general to make sure that the window resize is good and done first
// so that we can properly calculate sizes.
(function(){
var ct = this.el.up('.x-panel-body'),
hd = this.el.child('.ext-cal-day-header'),
h = ct.getHeight() - hd.getHeight();
this.el.child('.ext-cal-body-ct').setHeight(h);
}).defer(10, this);
},
// private
onResize : function(){
this.forceSize();
},
// private
getViewBounds : function(){
return this.header.getViewBounds();
},
/**
* Returns the start date of the view, as set by {@link #setStartDate}. Note that this may not
* be the first date displayed in the rendered calendar -- to get the start and end dates displayed
* to the user use {@link #getViewBounds}.
* @return {Date} The start date
*/
getStartDate : function(){
return this.header.getStartDate();
},
/**
* Sets the start date used to calculate the view boundaries to display. The displayed view will be the
* earliest and latest dates that match the view requirements and contain the date passed to this function.
* @param {Date} dt The date used to calculate the new view boundaries
*/
setStartDate: function(dt){
this.header.setStartDate(dt, true);
this.body.setStartDate(dt, true);
},
// private
renderItems: function(){
this.header.renderItems();
this.body.renderItems();
},
/**
* Returns true if the view is currently displaying today's date, else false.
* @return {Boolean} True or false
*/
isToday : function(){
return this.header.isToday();
},
/**
* Updates the view to contain the passed date
* @param {Date} dt The date to display
*/
moveTo : function(dt, noRefresh){
this.header.moveTo(dt, noRefresh);
this.body.moveTo(dt, noRefresh);
},
/**
* Updates the view to the next consecutive date(s)
*/
moveNext : function(noRefresh){
this.header.moveNext(noRefresh);
this.body.moveNext(noRefresh);
},
/**
* Updates the view to the previous consecutive date(s)
*/
movePrev : function(noRefresh){
this.header.movePrev(noRefresh);
this.body.movePrev(noRefresh);
},
/**
* Shifts the view by the passed number of days relative to the currently set date
* @param {Number} value The number of days (positive or negative) by which to shift the view
*/
moveDays : function(value, noRefresh){
this.header.moveDays(value, noRefresh);
this.body.moveDays(value, noRefresh);
},
/**
* Updates the view to show today
*/
moveToday : function(noRefresh){
this.header.moveToday(noRefresh);
this.body.moveToday(noRefresh);
}
});
Ext.reg('dayview', Ext.calendar.DayView);
/**
* @class Ext.calendar.WeekView
* @extends Ext.calendar.DayView
* <p>Displays a calendar view by week. This class does not usually need ot be used directly as you can
* use a {@link Ext.calendar.CalendarPanel CalendarPanel} to manage multiple calendar views at once including
* the week view.</p>
* @constructor
* @param {Object} config The config object
*/
Ext.calendar.WeekView = Ext.extend(Ext.calendar.DayView, {
/**
* @cfg {Number} dayCount
* The number of days to display in the view (defaults to 7)
*/
dayCount: 7
});
Ext.reg('weekview', Ext.calendar.WeekView);/**
* @class Ext.calendar.DateRangeField
* @extends Ext.form.Field
* <p>A combination field that includes start and end dates and times, as well as an optional all-day checkbox.</p>
* @constructor
* @param {Object} config The config object
*/
Ext.calendar.DateRangeField = Ext.extend(Ext.form.Field, {
/**
* @cfg {String} toText
* The text to display in between the date/time fields (defaults to 'to')
*/
toText: 'to',
/**
* @cfg {String} toText
* The text to display as the label for the all day checkbox (defaults to 'All day')
*/
allDayText: 'All day',
// private
onRender: function(ct, position) {
if (!this.el) {
this.startDate = new Ext.form.DateField({
id: this.id + '-start-date',
format: 'n/j/Y',
width: 100,
listeners: {
'change': {
fn: function() {
this.checkDates('date', 'start');
},
scope: this
}
}
});
this.startTime = new Ext.form.TimeField({
id: this.id + '-start-time',
hidden: this.showTimes === false,
labelWidth: 0,
hideLabel: true,
width: 90,
listeners: {
'select': {
fn: function() {
this.checkDates('time', 'start');
},
scope: this
}
}
});
this.endTime = new Ext.form.TimeField({
id: this.id + '-end-time',
hidden: this.showTimes === false,
labelWidth: 0,
hideLabel: true,
width: 90,
listeners: {
'select': {
fn: function() {
this.checkDates('time', 'end');
},
scope: this
}
}
});
this.endDate = new Ext.form.DateField({
id: this.id + '-end-date',
format: 'n/j/Y',
hideLabel: true,
width: 100,
listeners: {
'change': {
fn: function() {
this.checkDates('date', 'end');
},
scope: this
}
}
});
this.allDay = new Ext.form.Checkbox({
id: this.id + '-allday',
hidden: this.showTimes === false || this.showAllDay === false,
boxLabel: this.allDayText,
handler: function(chk, checked) {
this.startTime.setVisible(!checked);
this.endTime.setVisible(!checked);
},
scope: this
});
this.toLabel = new Ext.form.Label({
xtype: 'label',
id: this.id + '-to-label',
text: this.toText
});
this.fieldCt = new Ext.Container({
autoEl: {
id: this.id
},
//make sure the container el has the field's id
cls: 'ext-dt-range',
renderTo: ct,
layout: 'table',
layoutConfig: {
columns: 6
},
defaults: {
hideParent: true
},
items: [
this.startDate,
this.startTime,
this.toLabel,
this.endTime,
this.endDate,
this.allDay
]
});
this.fieldCt.ownerCt = this;
this.el = this.fieldCt.getEl();
this.items = new Ext.util.MixedCollection();
this.items.addAll([this.startDate, this.endDate, this.toLabel, this.startTime, this.endTime, this.allDay]);
}
Ext.calendar.DateRangeField.superclass.onRender.call(this, ct, position);
},
// private
checkDates: function(type, startend) {
var startField = Ext.getCmp(this.id + '-start-' + type),
endField = Ext.getCmp(this.id + '-end-' + type),
startValue = this.getDT('start'),
endValue = this.getDT('end');
if (startValue > endValue) {
if (startend == 'start') {
endField.setValue(startValue);
} else {
startField.setValue(endValue);
this.checkDates(type, 'start');
}
}
if (type == 'date') {
this.checkDates('time', startend);
}
},
/**
* Returns an array containing the following values in order:<div class="mdetail-params"><ul>
* <li><b><code>DateTime</code></b> : <div class="sub-desc">The start date/time</div></li>
* <li><b><code>DateTime</code></b> : <div class="sub-desc">The end date/time</div></li>
* <li><b><code>Boolean</code></b> : <div class="sub-desc">True if the dates are all-day, false
* if the time values should be used</div></li><ul></div>
* @return {Array} The array of return values
*/
getValue: function() {
return [
this.getDT('start'),
this.getDT('end'),
this.allDay.getValue()
];
},
// private getValue helper
getDT: function(startend) {
var time = this[startend + 'Time'].getValue(),
dt = this[startend + 'Date'].getValue();
if (Ext.isDate(dt)) {
dt = dt.format(this[startend + 'Date'].format);
}
else {
return null;
};
if (time != '' && this[startend + 'Time'].isVisible()) {
return Date.parseDate(dt + ' ' + time, this[startend + 'Date'].format + ' ' + this[startend + 'Time'].format);
}
return Date.parseDate(dt, this[startend + 'Date'].format);
},
/**
* Sets the values to use in the date range.
* @param {Array/Date/Object} v The value(s) to set into the field. Valid types are as follows:<div class="mdetail-params"><ul>
* <li><b><code>Array</code></b> : <div class="sub-desc">An array containing, in order, a start date, end date and all-day flag.
* This array should exactly match the return type as specified by {@link #getValue}.</div></li>
* <li><b><code>DateTime</code></b> : <div class="sub-desc">A single Date object, which will be used for both the start and
* end dates in the range. The all-day flag will be defaulted to false.</div></li>
* <li><b><code>Object</code></b> : <div class="sub-desc">An object containing properties for StartDate, EndDate and IsAllDay
* as defined in {@link Ext.calendar.EventMappings}.</div></li><ul></div>
*/
setValue: function(v) {
if (Ext.isArray(v)) {
this.setDT(v[0], 'start');
this.setDT(v[1], 'end');
this.allDay.setValue( !! v[2]);
}
else if (Ext.isDate(v)) {
this.setDT(v, 'start');
this.setDT(v, 'end');
this.allDay.setValue(false);
}
else if (v[Ext.calendar.EventMappings.StartDate.name]) {
//object
this.setDT(v[Ext.calendar.EventMappings.StartDate.name], 'start');
if (!this.setDT(v[Ext.calendar.EventMappings.EndDate.name], 'end')) {
this.setDT(v[Ext.calendar.EventMappings.StartDate.name], 'end');
}
this.allDay.setValue( !! v[Ext.calendar.EventMappings.IsAllDay.name]);
}
},
// private setValue helper
setDT: function(dt, startend) {
if (dt && Ext.isDate(dt)) {
this[startend + 'Date'].setValue(dt);
this[startend + 'Time'].setValue(dt.format(this[startend + 'Time'].format));
return true;
}
},
// inherited docs
isDirty: function() {
var dirty = false;
if (this.rendered && !this.disabled) {
this.items.each(function(item) {
if (item.isDirty()) {
dirty = true;
return false;
}
});
}
return dirty;
},
// private
onDisable: function() {
this.delegateFn('disable');
},
// private
onEnable: function() {
this.delegateFn('enable');
},
// inherited docs
reset: function() {
this.delegateFn('reset');
},
// private
delegateFn: function(fn) {
this.items.each(function(item) {
if (item[fn]) {
item[fn]();
}
});
},
// private
beforeDestroy: function() {
Ext.destroy(this.fieldCt);
Ext.calendar.DateRangeField.superclass.beforeDestroy.call(this);
},
/**
* @method getRawValue
* @hide
*/
getRawValue: Ext.emptyFn,
/**
* @method setRawValue
* @hide
*/
setRawValue: Ext.emptyFn
});
Ext.reg('daterangefield', Ext.calendar.DateRangeField);
/**
* @class Ext.calendar.ReminderField
* @extends Ext.form.ComboBox
* <p>A custom combo used for choosing a reminder setting for an event.</p>
* <p>This is pretty much a standard combo that is simply pre-configured for the options needed by the
* calendar components. The default configs are as follows:<pre><code>
width: 200,
fieldLabel: 'Reminder',
mode: 'local',
triggerAction: 'all',
forceSelection: true,
displayField: 'desc',
valueField: 'value'
</code></pre>
* @constructor
* @param {Object} config The config object
*/
Ext.calendar.ReminderField = Ext.extend(Ext.form.ComboBox, {
width: 200,
fieldLabel: 'Reminder',
mode: 'local',
triggerAction: 'all',
forceSelection: true,
displayField: 'desc',
valueField: 'value',
// private
initComponent: function() {
Ext.calendar.ReminderField.superclass.initComponent.call(this);
this.store = this.store || new Ext.data.ArrayStore({
fields: ['value', 'desc'],
idIndex: 0,
data: [
['', 'None'],
['0', 'At start time'],
['5', '5 minutes before start'],
['15', '15 minutes before start'],
['30', '30 minutes before start'],
['60', '1 hour before start'],
['90', '1.5 hours before start'],
['120', '2 hours before start'],
['180', '3 hours before start'],
['360', '6 hours before start'],
['720', '12 hours before start'],
['1440', '1 day before start'],
['2880', '2 days before start'],
['4320', '3 days before start'],
['5760', '4 days before start'],
['7200', '5 days before start'],
['10080', '1 week before start'],
['20160', '2 weeks before start']
]
});
},
// inherited docs
initValue: function() {
if (this.value !== undefined) {
this.setValue(this.value);
}
else {
this.setValue('');
}
this.originalValue = this.getValue();
}
});
Ext.reg('reminderfield', Ext.calendar.ReminderField);
/**
* @class Ext.calendar.EventEditForm
* @extends Ext.form.FormPanel
* <p>A custom form used for detailed editing of events.</p>
* <p>This is pretty much a standard form that is simply pre-configured for the options needed by the
* calendar components. It is also configured to automatically bind records of type {@link Ext.calendar.EventRecord}
* to and from the form.</p>
* <p>This form also provides custom events specific to the calendar so that other calendar components can be easily
* notified when an event has been edited via this component.</p>
* <p>The default configs are as follows:</p><pre><code>
labelWidth: 65,
title: 'Event Form',
titleTextAdd: 'Add Event',
titleTextEdit: 'Edit Event',
bodyStyle: 'background:transparent;padding:20px 20px 10px;',
border: false,
buttonAlign: 'center',
autoHeight: true,
cls: 'ext-evt-edit-form',
</code></pre>
* @constructor
* @param {Object} config The config object
*/
Ext.calendar.EventEditForm = Ext.extend(Ext.form.FormPanel, {
labelWidth: 65,
title: 'Event Form',
titleTextAdd: 'Add Event',
titleTextEdit: 'Edit Event',
bodyStyle: 'background:transparent;padding:20px 20px 10px;',
border: false,
buttonAlign: 'center',
autoHeight: true,
// to allow for the notes field to autogrow
cls: 'ext-evt-edit-form',
// private properties:
newId: 10000,
layout: 'column',
// private
initComponent: function() {
this.addEvents({
/**
* @event eventadd
* Fires after a new event is added
* @param {Ext.calendar.EventEditForm} this
* @param {Ext.calendar.EventRecord} rec The new {@link Ext.calendar.EventRecord record} that was added
*/
eventadd: true,
/**
* @event eventupdate
* Fires after an existing event is updated
* @param {Ext.calendar.EventEditForm} this
* @param {Ext.calendar.EventRecord} rec The new {@link Ext.calendar.EventRecord record} that was updated
*/
eventupdate: true,
/**
* @event eventdelete
* Fires after an event is deleted
* @param {Ext.calendar.EventEditForm} this
* @param {Ext.calendar.EventRecord} rec The new {@link Ext.calendar.EventRecord record} that was deleted
*/
eventdelete: true,
/**
* @event eventcancel
* Fires after an event add/edit operation is canceled by the user and no store update took place
* @param {Ext.calendar.EventEditForm} this
* @param {Ext.calendar.EventRecord} rec The new {@link Ext.calendar.EventRecord record} that was canceled
*/
eventcancel: true
});
this.titleField = new Ext.form.TextField({
fieldLabel: 'Title',
name: Ext.calendar.EventMappings.Title.name,
anchor: '90%'
});
this.dateRangeField = new Ext.calendar.DateRangeField({
fieldLabel: 'When',
anchor: '90%'
});
this.reminderField = new Ext.calendar.ReminderField({
name: 'Reminder'
});
this.notesField = new Ext.form.TextArea({
fieldLabel: 'Notes',
name: Ext.calendar.EventMappings.Notes.name,
grow: true,
growMax: 150,
anchor: '100%'
});
this.locationField = new Ext.form.TextField({
fieldLabel: 'Location',
name: Ext.calendar.EventMappings.Location.name,
anchor: '100%'
});
this.urlField = new Ext.form.TextField({
fieldLabel: 'Web Link',
name: Ext.calendar.EventMappings.Url.name,
anchor: '100%'
});
var leftFields = [this.titleField, this.dateRangeField, this.reminderField],
rightFields = [this.notesField, this.locationField, this.urlField];
if (this.calendarStore) {
this.calendarField = new Ext.calendar.CalendarPicker({
store: this.calendarStore,
name: Ext.calendar.EventMappings.CalendarId.name
});
leftFields.splice(2, 0, this.calendarField);
};
this.items = [{
id: 'left-col',
columnWidth: 0.65,
layout: 'form',
border: false,
items: leftFields
},
{
id: 'right-col',
columnWidth: 0.35,
layout: 'form',
border: false,
items: rightFields
}];
this.fbar = [{
text: 'Save',
scope: this,
handler: this.onSave
},
{
cls: 'ext-del-btn',
text: 'Delete',
scope: this,
handler: this.onDelete
},
{
text: 'Cancel',
scope: this,
handler: this.onCancel
}];
Ext.calendar.EventEditForm.superclass.initComponent.call(this);
},
// inherited docs
loadRecord: function(rec) {
this.form.loadRecord.apply(this.form, arguments);
this.activeRecord = rec;
this.dateRangeField.setValue(rec.data);
if (this.calendarStore) {
this.form.setValues({
'calendar': rec.data[Ext.calendar.EventMappings.CalendarId.name]
});
}
this.isAdd = !!rec.data[Ext.calendar.EventMappings.IsNew.name];
if (this.isAdd) {
rec.markDirty();
this.setTitle(this.titleTextAdd);
Ext.select('.ext-del-btn').setDisplayed(false);
}
else {
this.setTitle(this.titleTextEdit);
Ext.select('.ext-del-btn').setDisplayed(true);
}
this.titleField.focus();
},
// inherited docs
updateRecord: function() {
var dates = this.dateRangeField.getValue();
this.form.updateRecord(this.activeRecord);
this.activeRecord.set(Ext.calendar.EventMappings.StartDate.name, dates[0]);
this.activeRecord.set(Ext.calendar.EventMappings.EndDate.name, dates[1]);
this.activeRecord.set(Ext.calendar.EventMappings.IsAllDay.name, dates[2]);
},
// private
onCancel: function() {
this.cleanup(true);
this.fireEvent('eventcancel', this, this.activeRecord);
},
// private
cleanup: function(hide) {
if (this.activeRecord && this.activeRecord.dirty) {
this.activeRecord.reject();
}
delete this.activeRecord;
if (this.form.isDirty()) {
this.form.reset();
}
},
// private
onSave: function() {
if (!this.form.isValid()) {
return;
}
this.updateRecord();
if (!this.activeRecord.dirty) {
this.onCancel();
return;
}
this.fireEvent(this.isAdd ? 'eventadd': 'eventupdate', this, this.activeRecord);
},
// private
onDelete: function() {
this.fireEvent('eventdelete', this, this.activeRecord);
}
});
Ext.reg('eventeditform', Ext.calendar.EventEditForm);
/**
* @class Ext.calendar.EventEditWindow
* @extends Ext.Window
* <p>A custom window containing a basic edit form used for quick editing of events.</p>
* <p>This window also provides custom events specific to the calendar so that other calendar components can be easily
* notified when an event has been edited via this component.</p>
* @constructor
* @param {Object} config The config object
*/
Ext.calendar.EventEditWindow = function(config) {
var formPanelCfg = {
xtype: 'form',
labelWidth: 65,
frame: false,
bodyStyle: 'background:transparent;padding:5px 10px 10px;',
bodyBorder: false,
border: false,
items: [{
id: 'title',
name: Ext.calendar.EventMappings.Title.name,
fieldLabel: 'Title',
xtype: 'textfield',
anchor: '100%'
},
{
xtype: 'daterangefield',
id: 'date-range',
anchor: '100%',
fieldLabel: 'When'
}]
};
if (config.calendarStore) {
this.calendarStore = config.calendarStore;
delete config.calendarStore;
formPanelCfg.items.push({
xtype: 'calendarpicker',
id: 'calendar',
name: 'calendar',
anchor: '100%',
store: this.calendarStore
});
}
Ext.calendar.EventEditWindow.superclass.constructor.call(this, Ext.apply({
titleTextAdd: 'Add Event',
titleTextEdit: 'Edit Event',
width: 600,
autocreate: true,
border: true,
closeAction: 'hide',
modal: false,
resizable: false,
buttonAlign: 'left',
savingMessage: 'Saving changes...',
deletingMessage: 'Deleting event...',
fbar: [{
xtype: 'tbtext',
text: '<a href="#" id="tblink">Edit Details...</a>'
},
'->', {
text: 'Save',
disabled: false,
handler: this.onSave,
scope: this
},
{
id: 'delete-btn',
text: 'Delete',
disabled: false,
handler: this.onDelete,
scope: this,
hideMode: 'offsets'
},
{
text: 'Cancel',
disabled: false,
handler: this.onCancel,
scope: this
}],
items: formPanelCfg
},
config));
};
Ext.extend(Ext.calendar.EventEditWindow, Ext.Window, {
// private
newId: 10000,
// private
initComponent: function() {
Ext.calendar.EventEditWindow.superclass.initComponent.call(this);
this.formPanel = this.items.items[0];
this.addEvents({
/**
* @event eventadd
* Fires after a new event is added
* @param {Ext.calendar.EventEditWindow} this
* @param {Ext.calendar.EventRecord} rec The new {@link Ext.calendar.EventRecord record} that was added
*/
eventadd: true,
/**
* @event eventupdate
* Fires after an existing event is updated
* @param {Ext.calendar.EventEditWindow} this
* @param {Ext.calendar.EventRecord} rec The new {@link Ext.calendar.EventRecord record} that was updated
*/
eventupdate: true,
/**
* @event eventdelete
* Fires after an event is deleted
* @param {Ext.calendar.EventEditWindow} this
* @param {Ext.calendar.EventRecord} rec The new {@link Ext.calendar.EventRecord record} that was deleted
*/
eventdelete: true,
/**
* @event eventcancel
* Fires after an event add/edit operation is canceled by the user and no store update took place
* @param {Ext.calendar.EventEditWindow} this
* @param {Ext.calendar.EventRecord} rec The new {@link Ext.calendar.EventRecord record} that was canceled
*/
eventcancel: true,
/**
* @event editdetails
* Fires when the user selects the option in this window to continue editing in the detailed edit form
* (by default, an instance of {@link Ext.calendar.EventEditForm}. Handling code should hide this window
* and transfer the current event record to the appropriate instance of the detailed form by showing it
* and calling {@link Ext.calendar.EventEditForm#loadRecord loadRecord}.
* @param {Ext.calendar.EventEditWindow} this
* @param {Ext.calendar.EventRecord} rec The {@link Ext.calendar.EventRecord record} that is currently being edited
*/
editdetails: true
});
},
// private
afterRender: function() {
Ext.calendar.EventEditWindow.superclass.afterRender.call(this);
this.el.addClass('ext-cal-event-win');
Ext.get('tblink').on('click',
function(e) {
e.stopEvent();
this.updateRecord();
this.fireEvent('editdetails', this, this.activeRecord);
},
this);
},
/**
* Shows the window, rendering it first if necessary, or activates it and brings it to front if hidden.
* @param {Ext.data.Record/Object} o Either a {@link Ext.data.Record} if showing the form
* for an existing event in edit mode, or a plain object containing a StartDate property (and
* optionally an EndDate property) for showing the form in add mode.
* @param {String/Element} animateTarget (optional) The target element or id from which the window should
* animate while opening (defaults to null with no animation)
* @return {Ext.Window} this
*/
show: function(o, animateTarget) {
// Work around the CSS day cell height hack needed for initial render in IE8/strict:
var anim = (Ext.isIE8 && Ext.isStrict) ? null: animateTarget;
Ext.calendar.EventEditWindow.superclass.show.call(this, anim,
function() {
Ext.getCmp('title').focus(false, 100);
});
Ext.getCmp('delete-btn')[o.data && o.data[Ext.calendar.EventMappings.EventId.name] ? 'show': 'hide']();
var rec,
f = this.formPanel.form;
if (o.data) {
rec = o;
this.isAdd = !!rec.data[Ext.calendar.EventMappings.IsNew.name];
if (this.isAdd) {
// Enable adding the default record that was passed in
// if it's new even if the user makes no changes
rec.markDirty();
this.setTitle(this.titleTextAdd);
}
else {
this.setTitle(this.titleTextEdit);
}
f.loadRecord(rec);
}
else {
this.isAdd = true;
this.setTitle(this.titleTextAdd);
var M = Ext.calendar.EventMappings,
eventId = M.EventId.name,
start = o[M.StartDate.name],
end = o[M.EndDate.name] || start.add('h', 1);
rec = new Ext.calendar.EventRecord();
rec.data[M.EventId.name] = this.newId++;
rec.data[M.StartDate.name] = start;
rec.data[M.EndDate.name] = end;
rec.data[M.IsAllDay.name] = !!o[M.IsAllDay.name] || start.getDate() != end.clone().add(Date.MILLI, 1).getDate();
rec.data[M.IsNew.name] = true;
f.reset();
f.loadRecord(rec);
}
if (this.calendarStore) {
Ext.getCmp('calendar').setValue(rec.data[Ext.calendar.EventMappings.CalendarId.name]);
}
Ext.getCmp('date-range').setValue(rec.data);
this.activeRecord = rec;
return this;
},
// private
roundTime: function(dt, incr) {
incr = incr || 15;
var m = parseInt(dt.getMinutes(), 10);
return dt.add('mi', incr - (m % incr));
},
// private
onCancel: function() {
this.cleanup(true);
this.fireEvent('eventcancel', this);
},
// private
cleanup: function(hide) {
if (this.activeRecord && this.activeRecord.dirty) {
this.activeRecord.reject();
}
delete this.activeRecord;
if (hide === true) {
// Work around the CSS day cell height hack needed for initial render in IE8/strict:
//var anim = afterDelete || (Ext.isIE8 && Ext.isStrict) ? null : this.animateTarget;
this.hide();
}
},
// private
updateRecord: function() {
var f = this.formPanel.form,
dates = Ext.getCmp('date-range').getValue(),
M = Ext.calendar.EventMappings;
f.updateRecord(this.activeRecord);
this.activeRecord.set(M.StartDate.name, dates[0]);
this.activeRecord.set(M.EndDate.name, dates[1]);
this.activeRecord.set(M.IsAllDay.name, dates[2]);
this.activeRecord.set(M.CalendarId.name, this.formPanel.form.findField('calendar').getValue());
},
// private
onSave: function() {
if (!this.formPanel.form.isValid()) {
return;
}
this.updateRecord();
if (!this.activeRecord.dirty) {
this.onCancel();
return;
}
this.fireEvent(this.isAdd ? 'eventadd': 'eventupdate', this, this.activeRecord);
},
// private
onDelete: function() {
this.fireEvent('eventdelete', this, this.activeRecord);
}
});/**
* @class Ext.calendar.CalendarPanel
* @extends Ext.Panel
* <p>This is the default container for Ext calendar views. It supports day, week and month views as well
* as a built-in event edit form. The only requirement for displaying a calendar is passing in a valid
* {@link #calendarStore} config containing records of type {@link Ext.calendar.EventRecord EventRecord}. In order
* to make the calendar interactive (enable editing, drag/drop, etc.) you can handle any of the various
* events fired by the underlying views and exposed through the CalendarPanel.</p>
* {@link #layoutConfig} option if needed.</p>
* @constructor
* @param {Object} config The config object
* @xtype calendarpanel
*/
Ext.calendar.CalendarPanel = Ext.extend(Ext.Panel, {
/**
* @cfg {Boolean} showDayView
* True to include the day view (and toolbar button), false to hide them (defaults to true).
*/
showDayView: true,
/**
* @cfg {Boolean} showWeekView
* True to include the week view (and toolbar button), false to hide them (defaults to true).
*/
showWeekView: true,
/**
* @cfg {Boolean} showMonthView
* True to include the month view (and toolbar button), false to hide them (defaults to true).
* If the day and week views are both hidden, the month view will show by default even if
* this config is false.
*/
showMonthView: true,
/**
* @cfg {Boolean} showNavBar
* True to display the calendar navigation toolbar, false to hide it (defaults to true). Note that
* if you hide the default navigation toolbar you'll have to provide an alternate means of navigating the calendar.
*/
showNavBar: true,
/**
* @cfg {String} todayText
* Alternate text to use for the 'Today' nav bar button.
*/
todayText: 'Today',
/**
* @cfg {Boolean} showTodayText
* True to show the value of {@link #todayText} instead of today's date in the calendar's current day box,
* false to display the day number(defaults to true).
*/
showTodayText: true,
/**
* @cfg {Boolean} showTime
* True to display the current time next to the date in the calendar's current day box, false to not show it
* (defaults to true).
*/
showTime: true,
/**
* @cfg {String} dayText
* Alternate text to use for the 'Day' nav bar button.
*/
dayText: 'Day',
/**
* @cfg {String} weekText
* Alternate text to use for the 'Week' nav bar button.
*/
weekText: 'Week',
/**
* @cfg {String} monthText
* Alternate text to use for the 'Month' nav bar button.
*/
monthText: 'Month',
// private
layoutConfig: {
layoutOnCardChange: true,
deferredRender: true
},
// private property
startDate: new Date(),
// private
initComponent: function() {
this.tbar = {
cls: 'ext-cal-toolbar',
border: true,
buttonAlign: 'center',
items: [{
id: this.id + '-tb-prev',
handler: this.onPrevClick,
scope: this,
iconCls: 'x-tbar-page-prev'
}]
};
this.viewCount = 0;
if (this.showDayView) {
this.tbar.items.push({
id: this.id + '-tb-day',
text: this.dayText,
handler: this.onDayClick,
scope: this,
toggleGroup: 'tb-views'
});
this.viewCount++;
}
if (this.showWeekView) {
this.tbar.items.push({
id: this.id + '-tb-week',
text: this.weekText,
handler: this.onWeekClick,
scope: this,
toggleGroup: 'tb-views'
});
this.viewCount++;
}
if (this.showMonthView || this.viewCount == 0) {
this.tbar.items.push({
id: this.id + '-tb-month',
text: this.monthText,
handler: this.onMonthClick,
scope: this,
toggleGroup: 'tb-views'
});
this.viewCount++;
this.showMonthView = true;
}
this.tbar.items.push({
id: this.id + '-tb-next',
handler: this.onNextClick,
scope: this,
iconCls: 'x-tbar-page-next'
});
this.tbar.items.push('->');
var idx = this.viewCount - 1;
this.activeItem = this.activeItem === undefined ? idx: (this.activeItem > idx ? idx: this.activeItem);
if (this.showNavBar === false) {
delete this.tbar;
this.addClass('x-calendar-nonav');
}
Ext.calendar.CalendarPanel.superclass.initComponent.call(this);
this.addEvents({
/**
* @event eventadd
* Fires after a new event is added to the underlying store
* @param {Ext.calendar.CalendarPanel} this
* @param {Ext.calendar.EventRecord} rec The new {@link Ext.calendar.EventRecord record} that was added
*/
eventadd: true,
/**
* @event eventupdate
* Fires after an existing event is updated
* @param {Ext.calendar.CalendarPanel} this
* @param {Ext.calendar.EventRecord} rec The new {@link Ext.calendar.EventRecord record} that was updated
*/
eventupdate: true,
/**
* @event eventdelete
* Fires after an event is removed from the underlying store
* @param {Ext.calendar.CalendarPanel} this
* @param {Ext.calendar.EventRecord} rec The new {@link Ext.calendar.EventRecord record} that was removed
*/
eventdelete: true,
/**
* @event eventcancel
* Fires after an event add/edit operation is canceled by the user and no store update took place
* @param {Ext.calendar.CalendarPanel} this
* @param {Ext.calendar.EventRecord} rec The new {@link Ext.calendar.EventRecord record} that was canceled
*/
eventcancel: true,
/**
* @event viewchange
* Fires after a different calendar view is activated (but not when the event edit form is activated)
* @param {Ext.calendar.CalendarPanel} this
* @param {Ext.CalendarView} view The view being activated (any valid {@link Ext.calendar.CalendarView CalendarView} subclass)
* @param {Object} info Extra information about the newly activated view. This is a plain object
* with following properties:<div class="mdetail-params"><ul>
* <li><b><code>activeDate</code></b> : <div class="sub-desc">The currently-selected date</div></li>
* <li><b><code>viewStart</code></b> : <div class="sub-desc">The first date in the new view range</div></li>
* <li><b><code>viewEnd</code></b> : <div class="sub-desc">The last date in the new view range</div></li>
* </ul></div>
*/
viewchange: true
//
// NOTE: CalendarPanel also relays the following events from contained views as if they originated from this:
//
/**
* @event eventsrendered
* Fires after events are finished rendering in the view
* @param {Ext.calendar.CalendarPanel} this
*/
/**
* @event eventclick
* Fires after the user clicks on an event element
* @param {Ext.calendar.CalendarPanel} this
* @param {Ext.calendar.EventRecord} rec The {@link Ext.calendar.EventRecord record} for the event that was clicked on
* @param {HTMLNode} el The DOM node that was clicked on
*/
/**
* @event eventover
* Fires anytime the mouse is over an event element
* @param {Ext.calendar.CalendarPanel} this
* @param {Ext.calendar.EventRecord} rec The {@link Ext.calendar.EventRecord record} for the event that the cursor is over
* @param {HTMLNode} el The DOM node that is being moused over
*/
/**
* @event eventout
* Fires anytime the mouse exits an event element
* @param {Ext.calendar.CalendarPanel} this
* @param {Ext.calendar.EventRecord} rec The {@link Ext.calendar.EventRecord record} for the event that the cursor exited
* @param {HTMLNode} el The DOM node that was exited
*/
/**
* @event datechange
* Fires after the start date of the view changes
* @param {Ext.calendar.CalendarPanel} this
* @param {Date} startDate The start date of the view (as explained in {@link #getStartDate}
* @param {Date} viewStart The first displayed date in the view
* @param {Date} viewEnd The last displayed date in the view
*/
/**
* @event rangeselect
* Fires after the user drags on the calendar to select a range of dates/times in which to create an event
* @param {Ext.calendar.CalendarPanel} this
* @param {Object} dates An object containing the start (StartDate property) and end (EndDate property) dates selected
* @param {Function} callback A callback function that MUST be called after the event handling is complete so that
* the view is properly cleaned up (shim elements are persisted in the view while the user is prompted to handle the
* range selection). The callback is already created in the proper scope, so it simply needs to be executed as a standard
* function call (e.g., callback()).
*/
/**
* @event eventmove
* Fires after an event element is dragged by the user and dropped in a new position
* @param {Ext.calendar.CalendarPanel} this
* @param {Ext.calendar.EventRecord} rec The {@link Ext.calendar.EventRecord record} for the event that was moved with
* updated start and end dates
*/
/**
* @event initdrag
* Fires when a drag operation is initiated in the view
* @param {Ext.calendar.CalendarPanel} this
*/
/**
* @event eventresize
* Fires after the user drags the resize handle of an event to resize it
* @param {Ext.calendar.CalendarPanel} this
* @param {Ext.calendar.EventRecord} rec The {@link Ext.calendar.EventRecord record} for the event that was resized
* containing the updated start and end dates
*/
/**
* @event dayclick
* Fires after the user clicks within a day/week view container and not on an event element
* @param {Ext.calendar.CalendarPanel} this
* @param {Date} dt The date/time that was clicked on
* @param {Boolean} allday True if the day clicked on represents an all-day box, else false.
* @param {Ext.Element} el The Element that was clicked on
*/
});
this.layout = 'card';
// do not allow override
if (this.showDayView) {
var day = Ext.apply({
xtype: 'dayview',
title: this.dayText,
showToday: this.showToday,
showTodayText: this.showTodayText,
showTime: this.showTime
},
this.dayViewCfg);
day.id = this.id + '-day';
day.store = day.store || this.eventStore;
this.initEventRelay(day);
this.add(day);
}
if (this.showWeekView) {
var wk = Ext.applyIf({
xtype: 'weekview',
title: this.weekText,
showToday: this.showToday,
showTodayText: this.showTodayText,
showTime: this.showTime
},
this.weekViewCfg);
wk.id = this.id + '-week';
wk.store = wk.store || this.eventStore;
this.initEventRelay(wk);
this.add(wk);
}
if (this.showMonthView) {
var month = Ext.applyIf({
xtype: 'monthview',
title: this.monthText,
showToday: this.showToday,
showTodayText: this.showTodayText,
showTime: this.showTime,
listeners: {
'weekclick': {
fn: function(vw, dt) {
this.showWeek(dt);
},
scope: this
}
}
},
this.monthViewCfg);
month.id = this.id + '-month';
month.store = month.store || this.eventStore;
this.initEventRelay(month);
this.add(month);
}
this.add(Ext.applyIf({
xtype: 'eventeditform',
id: this.id + '-edit',
calendarStore: this.calendarStore,
listeners: {
'eventadd': {
scope: this,
fn: this.onEventAdd
},
'eventupdate': {
scope: this,
fn: this.onEventUpdate
},
'eventdelete': {
scope: this,
fn: this.onEventDelete
},
'eventcancel': {
scope: this,
fn: this.onEventCancel
}
}
},
this.editViewCfg));
},
// private
initEventRelay: function(cfg) {
cfg.listeners = cfg.listeners || {};
cfg.listeners.afterrender = {
fn: function(c) {
// relay the view events so that app code only has to handle them in one place
this.relayEvents(c, ['eventsrendered', 'eventclick', 'eventover', 'eventout', 'dayclick',
'eventmove', 'datechange', 'rangeselect', 'eventdelete', 'eventresize', 'initdrag']);
},
scope: this,
single: true
};
},
// private
afterRender: function() {
Ext.calendar.CalendarPanel.superclass.afterRender.call(this);
this.fireViewChange();
},
// private
onLayout: function() {
Ext.calendar.CalendarPanel.superclass.onLayout.call(this);
if (!this.navInitComplete) {
this.updateNavState();
this.navInitComplete = true;
}
},
// private
onEventAdd: function(form, rec) {
rec.data[Ext.calendar.EventMappings.IsNew.name] = false;
this.eventStore.add(rec);
this.hideEditForm();
this.fireEvent('eventadd', this, rec);
},
// private
onEventUpdate: function(form, rec) {
rec.commit();
this.hideEditForm();
this.fireEvent('eventupdate', this, rec);
},
// private
onEventDelete: function(form, rec) {
this.eventStore.remove(rec);
this.hideEditForm();
this.fireEvent('eventdelete', this, rec);
},
// private
onEventCancel: function(form, rec) {
this.hideEditForm();
this.fireEvent('eventcancel', this, rec);
},
/**
* Shows the built-in event edit form for the passed in event record. This method automatically
* hides the calendar views and navigation toolbar. To return to the calendar, call {@link #hideEditForm}.
* @param {Ext.calendar.EventRecord} record The event record to edit
* @return {Ext.calendar.CalendarPanel} this
*/
showEditForm: function(rec) {
this.preEditView = this.layout.activeItem.id;
this.setActiveView(this.id + '-edit');
this.layout.activeItem.loadRecord(rec);
return this;
},
/**
* Hides the built-in event edit form and returns to the previous calendar view. If the edit form is
* not currently visible this method has no effect.
* @return {Ext.calendar.CalendarPanel} this
*/
hideEditForm: function() {
if (this.preEditView) {
this.setActiveView(this.preEditView);
delete this.preEditView;
}
return this;
},
// private
setActiveView: function(id) {
var l = this.layout;
l.setActiveItem(id);
if (id == this.id + '-edit') {
this.getTopToolbar().hide();
this.doLayout();
}
else {
l.activeItem.refresh();
this.getTopToolbar().show();
this.updateNavState();
}
this.activeView = l.activeItem;
this.fireViewChange();
},
// private
fireViewChange: function() {
var info = null,
view = this.layout.activeItem;
if (view.getViewBounds) {
vb = view.getViewBounds();
info = {
activeDate: view.getStartDate(),
viewStart: vb.start,
viewEnd: vb.end
};
};
this.fireEvent('viewchange', this, view, info);
},
// private
updateNavState: function() {
if (this.showNavBar !== false) {
var item = this.layout.activeItem,
suffix = item.id.split(this.id + '-')[1];
var btn = Ext.getCmp(this.id + '-tb-' + suffix);
btn.toggle(true);
}
},
/**
* Sets the start date for the currently-active calendar view.
* @param {Date} dt
*/
setStartDate: function(dt) {
this.layout.activeItem.setStartDate(dt, true);
this.updateNavState();
this.fireViewChange();
},
// private
showWeek: function(dt) {
this.setActiveView(this.id + '-week');
this.setStartDate(dt);
},
// private
onPrevClick: function() {
this.startDate = this.layout.activeItem.movePrev();
this.updateNavState();
this.fireViewChange();
},
// private
onNextClick: function() {
this.startDate = this.layout.activeItem.moveNext();
this.updateNavState();
this.fireViewChange();
},
// private
onDayClick: function() {
this.setActiveView(this.id + '-day');
},
// private
onWeekClick: function() {
this.setActiveView(this.id + '-week');
},
// private
onMonthClick: function() {
this.setActiveView(this.id + '-month');
},
/**
* Return the calendar view that is currently active, which will be a subclass of
* {@link Ext.calendar.CalendarView CalendarView}.
* @return {Ext.calendar.CalendarView} The active view
*/
getActiveView: function() {
return this.layout.activeItem;
}
});
Ext.reg('calendarpanel', Ext.calendar.CalendarPanel);