2009-10-10 10:12:40 +02:00
|
|
|
|
|
|
|
/* Methods & Utilities for All Views
|
|
|
|
-----------------------------------------------------------------------------*/
|
|
|
|
|
|
|
|
var viewMethods = {
|
|
|
|
|
|
|
|
// TODO: maybe change the 'vis' variables to 'excl'
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Objects inheriting these methods must implement the following properties/methods:
|
|
|
|
* - title
|
|
|
|
* - start
|
|
|
|
* - end
|
|
|
|
* - visStart
|
|
|
|
* - visEnd
|
|
|
|
* - defaultEventEnd(event)
|
|
|
|
* - visEventEnd(event)
|
|
|
|
* - render(events)
|
|
|
|
* - rerenderEvents()
|
|
|
|
*
|
|
|
|
*
|
|
|
|
* z-index reservations:
|
|
|
|
* 3 - day-overlay
|
|
|
|
* 8 - events
|
|
|
|
* 9 - dragging/resizing events
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
init: function(element, options) {
|
|
|
|
this.element = element;
|
|
|
|
this.options = options;
|
|
|
|
this.cachedEvents = [];
|
|
|
|
this.eventsByID = {};
|
|
|
|
this.eventElements = [];
|
|
|
|
this.eventElementsByID = {};
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// triggers an event handler, always append view as last arg
|
|
|
|
|
|
|
|
trigger: function(name, thisObj) {
|
|
|
|
if (this.options[name]) {
|
|
|
|
return this.options[name].apply(thisObj || this, Array.prototype.slice.call(arguments, 2).concat([this]));
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// returns a Date object for an event's end
|
|
|
|
|
|
|
|
eventEnd: function(event) {
|
|
|
|
return event.end ? cloneDate(event.end) : this.defaultEventEnd(event); // TODO: make sure always using copies
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// report when view receives new events
|
|
|
|
|
|
|
|
reportEvents: function(events) { // events are already normalized at this point
|
|
|
|
var i, len=events.length, event,
|
|
|
|
eventsByID = this.eventsByID = {},
|
|
|
|
cachedEvents = this.cachedEvents = [];
|
|
|
|
for (i=0; i<len; i++) {
|
|
|
|
event = events[i];
|
|
|
|
if (eventsByID[event._id]) {
|
|
|
|
eventsByID[event._id].push(event);
|
|
|
|
}else{
|
|
|
|
eventsByID[event._id] = [event];
|
|
|
|
}
|
|
|
|
cachedEvents.push(event);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// report when view creates an element for an event
|
|
|
|
|
|
|
|
reportEventElement: function(event, element) {
|
|
|
|
this.eventElements.push(element);
|
|
|
|
var eventElementsByID = this.eventElementsByID;
|
|
|
|
if (eventElementsByID[event._id]) {
|
|
|
|
eventElementsByID[event._id].push(element);
|
|
|
|
}else{
|
|
|
|
eventElementsByID[event._id] = [element];
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// event element manipulation
|
|
|
|
|
|
|
|
clearEvents: function() { // only remove ELEMENTS
|
|
|
|
$.each(this.eventElements, function() {
|
|
|
|
this.remove();
|
|
|
|
});
|
|
|
|
this.eventElements = [];
|
|
|
|
this.eventElementsByID = {};
|
|
|
|
},
|
|
|
|
|
|
|
|
showEvents: function(event, exceptElement) {
|
|
|
|
this._eee(event, exceptElement, 'show');
|
|
|
|
},
|
|
|
|
|
|
|
|
hideEvents: function(event, exceptElement) {
|
|
|
|
this._eee(event, exceptElement, 'hide');
|
|
|
|
},
|
|
|
|
|
|
|
|
_eee: function(event, exceptElement, funcName) { // event-element-each
|
|
|
|
var elements = this.eventElementsByID[event._id],
|
|
|
|
i, len = elements.length;
|
|
|
|
for (i=0; i<len; i++) {
|
|
|
|
if (elements[i] != exceptElement) {
|
|
|
|
elements[i][funcName]();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// event modification reporting
|
|
|
|
|
|
|
|
eventDrop: function(e, event, dayDelta, minuteDelta, allDay, ev, ui) {
|
|
|
|
var view = this,
|
|
|
|
oldAllDay = event.allDay;
|
|
|
|
view.moveEvents(view.eventsByID[event._id], dayDelta, minuteDelta, allDay);
|
|
|
|
view.trigger('eventDrop', e, event, dayDelta, minuteDelta, allDay, function() { // TODO: change docs
|
|
|
|
// TODO: investigate cases where this inverse technique might not work
|
|
|
|
view.moveEvents(view.eventsByID[event._id], -dayDelta, -minuteDelta, oldAllDay);
|
|
|
|
view.rerenderEvents();
|
|
|
|
}, ev, ui);
|
|
|
|
view.eventsChanged = true;
|
|
|
|
view.rerenderEvents();
|
|
|
|
},
|
|
|
|
|
|
|
|
eventResize: function(e, event, dayDelta, minuteDelta, ev, ui) {
|
|
|
|
var view = this;
|
|
|
|
view.elongateEvents(view.eventsByID[event._id], dayDelta, minuteDelta);
|
|
|
|
view.trigger('eventResize', e, event, dayDelta, minuteDelta, function() {
|
|
|
|
// TODO: investigate cases where this inverse technique might not work
|
|
|
|
view.elongateEvents(view.eventsByID[event._id], -dayDelta, -minuteDelta);
|
|
|
|
view.rerenderEvents();
|
|
|
|
}, ev, ui);
|
|
|
|
view.eventsChanged = true;
|
|
|
|
view.rerenderEvents();
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// event modification
|
|
|
|
|
|
|
|
moveEvents: function(events, dayDelta, minuteDelta, allDay) {
|
|
|
|
minuteDelta = minuteDelta || 0;
|
|
|
|
for (var e, len=events.length, i=0; i<len; i++) {
|
|
|
|
e = events[i];
|
|
|
|
if (allDay != undefined) {
|
|
|
|
e.allDay = allDay;
|
|
|
|
}
|
|
|
|
addMinutes(addDays(e.start, dayDelta, true), minuteDelta);
|
|
|
|
if (e.end) {
|
|
|
|
e.end = addMinutes(addDays(e.end, dayDelta, true), minuteDelta);
|
|
|
|
}
|
|
|
|
normalizeEvent(e, this.options);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
elongateEvents: function(events, dayDelta, minuteDelta) {
|
|
|
|
minuteDelta = minuteDelta || 0;
|
|
|
|
for (var e, len=events.length, i=0; i<len; i++) {
|
|
|
|
e = events[i];
|
|
|
|
e.end = addMinutes(addDays(this.eventEnd(e), dayDelta, true), minuteDelta);
|
|
|
|
normalizeEvent(e, this.options);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// semi-transparent overlay (while dragging)
|
|
|
|
|
|
|
|
showOverlay: function(props) {
|
|
|
|
if (!this.dayOverlay) {
|
|
|
|
this.dayOverlay = $("<div class='fc-cell-overlay' style='position:absolute;z-index:3;display:none'/>")
|
|
|
|
.appendTo(this.element);
|
|
|
|
}
|
|
|
|
var o = this.element.offset();
|
|
|
|
this.dayOverlay
|
|
|
|
.css({
|
|
|
|
top: props.top - o.top,
|
|
|
|
left: props.left - o.left,
|
|
|
|
width: props.width,
|
|
|
|
height: props.height
|
|
|
|
})
|
|
|
|
.show();
|
|
|
|
},
|
|
|
|
|
|
|
|
hideOverlay: function() {
|
|
|
|
if (this.dayOverlay) {
|
|
|
|
this.dayOverlay.hide();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// common horizontal event resizing
|
|
|
|
|
|
|
|
resizableDayEvent: function(event, eventElement, colWidth) {
|
|
|
|
var view = this;
|
|
|
|
if (!view.options.disableResizing && eventElement.resizable) {
|
|
|
|
eventElement.resizable({
|
|
|
|
handles: view.options.isRTL ? 'w' : 'e',
|
|
|
|
grid: colWidth,
|
|
|
|
minWidth: colWidth/2, // need this or else IE throws errors when too small
|
2009-10-12 08:35:33 +02:00
|
|
|
containment: view.element.parent().parent(), // the main element...
|
|
|
|
// ... a fix. wouldn't allow extending to last column in agenda views (jq ui bug?)
|
2009-10-10 10:12:40 +02:00
|
|
|
start: function(ev, ui) {
|
|
|
|
eventElement.css('z-index', 9);
|
|
|
|
view.hideEvents(event, eventElement);
|
|
|
|
view.trigger('eventResizeStart', this, event, ev, ui);
|
|
|
|
},
|
|
|
|
stop: function(ev, ui) {
|
|
|
|
view.trigger('eventResizeStop', this, event, ev, ui);
|
|
|
|
// ui.size.width wasn't working with grid correctly, use .width()
|
|
|
|
var dayDelta = Math.round((eventElement.width() - ui.originalSize.width) / colWidth);
|
|
|
|
if (dayDelta) {
|
|
|
|
view.eventResize(this, event, dayDelta, 0, ev, ui);
|
|
|
|
}else{
|
|
|
|
eventElement.css('z-index', 8);
|
|
|
|
view.showEvents(event, eventElement);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
|
2009-10-12 08:35:33 +02:00
|
|
|
// attaches eventClick, eventMouseover, eventMouseout
|
2009-10-10 10:12:40 +02:00
|
|
|
|
2009-10-12 08:35:33 +02:00
|
|
|
eventElementHandlers: function(event, eventElement) {
|
|
|
|
var view = this;
|
|
|
|
eventElement
|
|
|
|
.click(function(ev) {
|
|
|
|
if (!eventElement.hasClass('ui-draggable-dragging') &&
|
|
|
|
!eventElement.hasClass('ui-resizable-resizing')) {
|
|
|
|
return view.trigger('eventClick', this, event, ev);
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.hover(
|
|
|
|
function(ev) {
|
|
|
|
view.trigger('eventMouseover', this, event, ev);
|
|
|
|
},
|
|
|
|
function(ev) {
|
|
|
|
view.trigger('eventMouseout', this, event, ev);
|
2009-10-10 10:12:40 +02:00
|
|
|
}
|
2009-10-12 08:35:33 +02:00
|
|
|
);
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// get a property from the 'options' object, using smart view naming
|
|
|
|
|
|
|
|
option: function(name, viewName) {
|
2009-10-13 06:22:40 +02:00
|
|
|
var v = this.options[name];
|
|
|
|
if (typeof v == 'object') {
|
|
|
|
return smartProperty(v, viewName || this.name);
|
|
|
|
}
|
|
|
|
return v;
|
2009-10-10 10:12:40 +02:00
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// event rendering utilities
|
|
|
|
|
|
|
|
sliceSegs: function(events, start, end) {
|
|
|
|
var segs = [],
|
|
|
|
i, len=events.length, event,
|
|
|
|
eventStart, eventEnd,
|
|
|
|
segStart, segEnd,
|
|
|
|
isStart, isEnd;
|
|
|
|
for (i=0; i<len; i++) {
|
|
|
|
event = events[i];
|
|
|
|
eventStart = event.start;
|
|
|
|
eventEnd = this.visEventEnd(event);
|
|
|
|
if (eventEnd > start && eventStart < end) {
|
|
|
|
if (eventStart < start) {
|
|
|
|
segStart = cloneDate(start);
|
|
|
|
isStart = false;
|
|
|
|
}else{
|
|
|
|
segStart = eventStart;
|
|
|
|
isStart = true;
|
|
|
|
}
|
|
|
|
if (eventEnd > end) {
|
|
|
|
segEnd = cloneDate(end);
|
|
|
|
isEnd = false;
|
|
|
|
}else{
|
|
|
|
segEnd = eventEnd;
|
|
|
|
isEnd = true;
|
|
|
|
}
|
|
|
|
segs.push({
|
|
|
|
event: event,
|
|
|
|
start: segStart,
|
|
|
|
end: segEnd,
|
|
|
|
isStart: isStart,
|
|
|
|
isEnd: isEnd,
|
|
|
|
msLength: segEnd - segStart
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return segs.sort(segCmp);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// event rendering calculation utilities
|
|
|
|
|
|
|
|
function stackSegs(segs) {
|
|
|
|
var levels = [],
|
|
|
|
i, len = segs.length, seg,
|
|
|
|
j, collide, k;
|
|
|
|
for (i=0; i<len; i++) {
|
|
|
|
seg = segs[i];
|
|
|
|
j = 0; // the level index where seg should belong
|
|
|
|
while (true) {
|
|
|
|
collide = false;
|
|
|
|
if (levels[j]) {
|
|
|
|
for (k=0; k<levels[j].length; k++) {
|
|
|
|
if (segsCollide(levels[j][k], seg)) {
|
|
|
|
collide = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (collide) {
|
|
|
|
j++;
|
|
|
|
}else{
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (levels[j]) {
|
|
|
|
levels[j].push(seg);
|
|
|
|
}else{
|
|
|
|
levels[j] = [seg];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return levels;
|
|
|
|
}
|
|
|
|
|
|
|
|
function segCmp(a, b) {
|
|
|
|
return (b.msLength - a.msLength) * 100 + (a.event.start - b.event.start);
|
|
|
|
}
|
|
|
|
|
|
|
|
function segsCollide(seg1, seg2) {
|
|
|
|
return seg1.end > seg2.start && seg1.start < seg2.end;
|
|
|
|
}
|
|
|
|
|