added droppable/drop, refactored selectable code, fixed issue 406

This commit is contained in:
Adam Shaw 2010-06-28 21:51:13 -07:00
parent 5fb7644920
commit 8ebda5094f
9 changed files with 600 additions and 457 deletions

View file

@ -88,10 +88,6 @@ function Agenda(element, options, methods) {
return bg.find('td:eq(' + col + ') div div'); return bg.find('td:eq(' + col + ') div div');
}), }),
slotTopCache = {}, slotTopCache = {},
daySelectionManager,
slotSelectionManager,
selectionHelper,
selectionMatrix,
// ... // ...
view = $.extend(this, viewMethods, methods, { view = $.extend(this, viewMethods, methods, {
@ -669,38 +665,27 @@ function Agenda(element, options, methods) {
function draggableDayEvent(event, eventElement, isStart) { function draggableDayEvent(event, eventElement, isStart) {
if (!options.disableDragging && eventElement.draggable) { if (!options.disableDragging && eventElement.draggable) {
var origPosition, origWidth, var origWidth;
resetElement, var allDay=true;
allDay=true, var dayDelta;
matrix;
eventElement.draggable({ eventElement.draggable({
zIndex: 9, zIndex: 9,
opacity: view.option('dragOpacity', 'month'), // use whatever the month view was using opacity: view.option('dragOpacity', 'month'), // use whatever the month view was using
revertDuration: options.dragRevertDuration, revertDuration: options.dragRevertDuration,
start: function(ev, ui) { start: function(ev, ui) {
view.hideEvents(event, eventElement);
view.trigger('eventDragStart', eventElement, event, ev, ui); view.trigger('eventDragStart', eventElement, event, ev, ui);
origPosition = eventElement.position(); view.hideEvents(event, eventElement);
origWidth = eventElement.width(); origWidth = eventElement.width();
resetElement = function() { hoverListener.start(function(cell, origCell, rowDelta, colDelta) {
if (!allDay) { eventElement.draggable('option', 'revert', !cell || !rowDelta && !colDelta);
eventElement clearOverlay();
.width(origWidth)
.height('')
.draggable('option', 'grid', null);
allDay = true;
}
};
matrix = buildDayMatrix(function(cell) {
eventElement.draggable('option', 'revert', !cell || !cell.rowDelta && !cell.colDelta);
view.clearOverlays();
if (cell) { if (cell) {
dayDelta = colDelta * dis;
if (!cell.row) { if (!cell.row) {
// on full-days // on full-days
renderDayOverlay( renderDayOverlay(
matrix, addDays(cloneDate(event.start), dayDelta),
addDays(cloneDate(event.start), cell.colDelta), addDays(exclEndDay(event), dayDelta)
addDays(exclEndDay(event), cell.colDelta)
); );
resetElement(); resetElement();
}else{ }else{
@ -711,49 +696,50 @@ function Agenda(element, options, methods) {
eventElement.width(colWidth - 10), // don't use entire width eventElement.width(colWidth - 10), // don't use entire width
slotHeight * Math.round( slotHeight * Math.round(
(event.end ? ((event.end - event.start) / MINUTE_MS) : options.defaultEventMinutes) (event.end ? ((event.end - event.start) / MINUTE_MS) : options.defaultEventMinutes)
/options.slotMinutes) / options.slotMinutes
)
); );
eventElement.draggable('option', 'grid', [colWidth, 1]); eventElement.draggable('option', 'grid', [colWidth, 1]);
allDay = false; allDay = false;
} }
} }
} }
},true); }, ev, 'drag');
matrix.mouse(ev);
},
drag: function(ev, ui) {
matrix.mouse(ev);
}, },
stop: function(ev, ui) { stop: function(ev, ui) {
var cell = hoverListener.stop();
clearOverlay();
view.trigger('eventDragStop', eventElement, event, ev, ui); view.trigger('eventDragStop', eventElement, event, ev, ui);
view.clearOverlays(); if (cell && (!allDay || dayDelta)) {
var cell = matrix.cell; // changed!
var dayDelta = dis * ( eventElement.find('a').removeAttr('href'); // prevents safari from visiting the link
allDay ? // can't trust cell.colDelta when using slot grid var minuteDelta = 0;
(cell ? cell.colDelta : 0) : if (!allDay) {
Math.floor((ui.position.left - origPosition.left) / colWidth) minuteDelta = Math.round((eventElement.offset().top - bodyContent.offset().top) / slotHeight)
); * options.slotMinutes
if (!cell || !dayDelta && !cell.rowDelta) { + minMinute
// over nothing (has reverted) - (event.start.getHours() * 60 + event.start.getMinutes());
}
view.eventDrop(this, event, dayDelta, minuteDelta, allDay, ev, ui);
}else{
// hasn't moved or is out of bounds (draggable has already reverted)
resetElement(); resetElement();
if ($.browser.msie) { if ($.browser.msie) {
eventElement.css('filter', ''); // clear IE opacity side-effects eventElement.css('filter', ''); // clear IE opacity side-effects
} }
view.showEvents(event, eventElement); view.showEvents(event, eventElement);
}else{
eventElement.find('a').removeAttr('href'); // prevents safari from visiting the link
view.eventDrop(
this, event, dayDelta,
allDay ? 0 : // minute delta
Math.round((eventElement.offset().top - bodyContent.offset().top) / slotHeight)
* options.slotMinutes
+ minMinute
- (event.start.getHours() * 60 + event.start.getMinutes()),
allDay, ev, ui
);
} }
} }
}); });
function resetElement() {
if (!allDay) {
eventElement
.width(origWidth)
.height('')
.draggable('option', 'grid', null);
allDay = true;
}
}
} }
} }
@ -763,11 +749,11 @@ function Agenda(element, options, methods) {
function draggableSlotEvent(event, eventElement, timeElement) { function draggableSlotEvent(event, eventElement, timeElement) {
if (!options.disableDragging && eventElement.draggable) { if (!options.disableDragging && eventElement.draggable) {
var origPosition, var origPosition;
resetElement, var allDay=false;
prevSlotDelta, slotDelta, var dayDelta;
allDay=false, var minuteDelta;
matrix; var prevMinuteDelta;
eventElement.draggable({ eventElement.draggable({
zIndex: 9, zIndex: 9,
scroll: false, scroll: false,
@ -776,26 +762,20 @@ function Agenda(element, options, methods) {
opacity: view.option('dragOpacity'), opacity: view.option('dragOpacity'),
revertDuration: options.dragRevertDuration, revertDuration: options.dragRevertDuration,
start: function(ev, ui) { start: function(ev, ui) {
view.hideEvents(event, eventElement);
view.trigger('eventDragStart', eventElement, event, ev, ui); view.trigger('eventDragStart', eventElement, event, ev, ui);
view.hideEvents(event, eventElement);
if ($.browser.msie) { if ($.browser.msie) {
eventElement.find('span.fc-event-bg').hide(); // nested opacities mess up in IE, just hide eventElement.find('span.fc-event-bg').hide(); // nested opacities mess up in IE, just hide
} }
origPosition = eventElement.position(); origPosition = eventElement.position();
resetElement = function() { minuteDelta = prevMinuteDelta = 0;
// convert back to original slot-event hoverListener.start(function(cell, origCell, rowDelta, colDelta) {
if (allDay) {
timeElement.css('display', ''); // show() was causing display=inline
eventElement.draggable('option', 'grid', [colWidth, slotHeight]);
allDay = false;
}
};
prevSlotDelta = 0;
matrix = buildDayMatrix(function(cell) {
eventElement.draggable('option', 'revert', !cell); eventElement.draggable('option', 'revert', !cell);
view.clearOverlays(); clearOverlay();
if (cell) { if (cell) {
if (!cell.row && options.allDaySlot) { // over full days dayDelta = colDelta * dis;
if (options.allDaySlot && !cell.row) {
// over full days
if (!allDay) { if (!allDay) {
// convert to temporary all-day event // convert to temporary all-day event
allDay = true; allDay = true;
@ -803,61 +783,63 @@ function Agenda(element, options, methods) {
eventElement.draggable('option', 'grid', null); eventElement.draggable('option', 'grid', null);
} }
renderDayOverlay( renderDayOverlay(
matrix, addDays(cloneDate(event.start), dayDelta),
addDays(cloneDate(event.start), cell.colDelta), addDays(exclEndDay(event), dayDelta)
addDays(exclEndDay(event), cell.colDelta)
); );
}else{ // on slots }else{
// on slots
resetElement(); resetElement();
} }
} }
},true); }, ev, 'drag');
matrix.mouse(ev);
}, },
drag: function(ev, ui) { drag: function(ev, ui) {
slotDelta = Math.round((ui.position.top - origPosition.top) / slotHeight); minuteDelta = Math.round((ui.position.top - origPosition.top) / slotHeight) * options.slotMinutes;
if (slotDelta != prevSlotDelta) { if (minuteDelta != prevMinuteDelta) {
if (!allDay) { if (!allDay) {
// update time header updateTimeText(minuteDelta);
var minuteDelta = slotDelta*options.slotMinutes, }
newStart = addMinutes(cloneDate(event.start), minuteDelta), prevMinuteDelta = minuteDelta;
newEnd; }
},
stop: function(ev, ui) {
var cell = hoverListener.stop();
clearOverlay();
view.trigger('eventDragStop', eventElement, event, ev, ui);
if (cell && (dayDelta || minuteDelta || allDay)) {
// changed!
view.eventDrop(this, event, dayDelta, allDay ? 0 : minuteDelta, allDay, ev, ui);
}else{
// either no change or out-of-bounds (draggable has already reverted)
resetElement();
eventElement.css(origPosition); // sometimes fast drags make event revert to wrong position
updateTimeText(0);
if ($.browser.msie) {
eventElement
.css('filter', '') // clear IE opacity side-effects
.find('span.fc-event-bg')
.css('display', ''); // .show() made display=inline
}
view.showEvents(event, eventElement);
}
}
});
function updateTimeText(minuteDelta) {
var newStart = addMinutes(cloneDate(event.start), minuteDelta);
var newEnd;
if (event.end) { if (event.end) {
newEnd = addMinutes(cloneDate(event.end), minuteDelta); newEnd = addMinutes(cloneDate(event.end), minuteDelta);
} }
timeElement.text(formatDates(newStart, newEnd, view.option('timeFormat'))); timeElement.text(formatDates(newStart, newEnd, view.option('timeFormat')));
} }
prevSlotDelta = slotDelta; function resetElement() {
} // convert back to original slot-event
matrix.mouse(ev); if (allDay) {
}, timeElement.css('display', ''); // show() was causing display=inline
stop: function(ev, ui) { eventElement.draggable('option', 'grid', [colWidth, slotHeight]);
view.clearOverlays(); allDay = false;
view.trigger('eventDragStop', eventElement, event, ev, ui);
var cell = matrix.cell,
dayDelta = dis * (
allDay ? // can't trust cell.colDelta when using slot grid
(cell ? cell.colDelta : 0) :
Math.floor((ui.position.left - origPosition.left) / colWidth)
);
if (!cell || !slotDelta && !dayDelta) {
resetElement();
if ($.browser.msie) {
eventElement
.css('filter', '') // clear IE opacity side-effects
.find('span.fc-event-bg').css('display', ''); // .show() made display=inline
}
eventElement.css(origPosition); // sometimes fast drags make event revert to wrong position
view.showEvents(event, eventElement);
}else{
view.eventDrop(
this, event, dayDelta,
allDay ? 0 : slotDelta * options.slotMinutes, // minute delta
allDay, ev, ui
);
} }
} }
});
} }
} }
@ -918,114 +900,152 @@ function Agenda(element, options, methods) {
/* Coordinate Utilities
-----------------------------------------------------------------------------*/
var coordinateGrid = new CoordinateGrid(function(rows, cols) {
var e, n, p;
bg.find('td').each(function(i, _e) {
e = $(_e);
n = e.offset().left;
if (i) {
p[1] = n;
}
p = [n];
cols[i] = p;
});
p[1] = n + e.outerWidth();
if (options.allDaySlot) {
e = head.find('td');
n = e.offset().top;
rows[0] = [n, n+e.outerHeight()];
}
var bodyContentTop = bodyContent.offset().top;
var bodyTop = body.offset().top;
var bodyBottom = bodyTop + body.outerHeight();
function constrain(n) {
return Math.max(bodyTop, Math.min(bodyBottom, n));
}
for (var i=0; i<slotCnt; i++) {
rows.push([
constrain(bodyContentTop + slotHeight*i),
constrain(bodyContentTop + slotHeight*(i+1))
]);
}
});
var hoverListener = new HoverListener(coordinateGrid);
// get the Y coordinate of the given time on the given day (both Date objects)
function timePosition(day, time) { // both date objects. day holds 00:00 of current day
day = cloneDate(day, true);
if (time < addMinutes(cloneDate(day), minMinute)) {
return 0;
}
if (time >= addMinutes(cloneDate(day), maxMinute)) {
return bodyContent.height();
}
var slotMinutes = options.slotMinutes,
minutes = time.getHours()*60 + time.getMinutes() - minMinute,
slotI = Math.floor(minutes / slotMinutes),
slotTop = slotTopCache[slotI];
if (slotTop === undefined) {
slotTop = slotTopCache[slotI] = body.find('tr:eq(' + slotI + ') td div')[0].offsetTop;
}
return Math.max(0, Math.round(
slotTop - 1 + slotHeight * ((minutes % slotMinutes) / slotMinutes)
));
}
/* Selecting /* Selecting
-----------------------------------------------------------------------------*/ -----------------------------------------------------------------------------*/
daySelectionManager = new SelectionManager( var selected = false;
view, var daySelectionMousedown = selection_dayMousedown(
unselect, view, hoverListener, cellDate, renderDayOverlay, clearOverlay, reportSelection, unselect
function(startDate, endDate, allDay) {
renderDayOverlay(
selectionMatrix,
startDate,
addDays(cloneDate(endDate), 1)
);
},
clearSelection
);
function daySelectionMousedown(ev) {
if (view.option('selectable')) {
selectionMatrix = buildDayMatrix(function(cell) {
if (cell) {
var d = dayColDate(cell.col);
daySelectionManager.drag(d, d, true);
}else{
daySelectionManager.drag();
}
});
documentDragHelp(
function(ev) {
selectionMatrix.mouse(ev);
},
function(ev) {
daySelectionManager.dragStop(ev);
}
);
daySelectionManager.dragStart(ev);
selectionMatrix.mouse(ev);
return false; // prevent auto-unselect and text selection
}
}
slotSelectionManager = new SelectionManager(
view,
unselect,
renderSlotSelection,
clearSelection
); );
function slotSelectionMousedown(ev) { function slotSelectionMousedown(ev) {
if (view.option('selectable')) { if (view.option('selectable')) {
selectionMatrix = buildSlotMatrix(function(cell) { unselect();
if (cell) { var dates;
var d = slotCellDate(cell.row, cell.origCol); hoverListener.start(function(cell, origCell) {
slotSelectionManager.drag(d, addMinutes(cloneDate(d), options.slotMinutes), false); clearSelection();
if (cell && cell.col == origCell.col) {
var d1 = cellDate(origCell);
var d2 = cellDate(cell);
dates = [
d1,
addMinutes(cloneDate(d1), options.slotMinutes),
d2,
addMinutes(cloneDate(d2), options.slotMinutes)
].sort(cmp);
renderSlotSelection(dates[0], dates[3]);
}else{ }else{
slotSelectionManager.drag(); dates = null;
}
}, ev);
$(document).one('mouseup', function() {
hoverListener.stop();
if (dates) {
reportSelection(dates[0], dates[3], false);
} }
}); });
documentDragHelp(
function(ev) {
selectionMatrix.mouse(ev);
},
function(ev) {
slotSelectionManager.dragStop(ev);
}
);
slotSelectionManager.dragStart(ev);
selectionMatrix.mouse(ev);
return false; // prevent auto-unselect and text selection
} }
} }
documentUnselectAuto(view, unselect); view.select = function(startDate, endDate, allDay) {
coordinateGrid.build();
this.select = function(start, end, allDay) { unselect();
if (allDay) { if (allDay) {
if (options.allDaySlot) { if (options.allDaySlot) {
if (!end) { if (!endDate) {
end = cloneDate(start); endDate = cloneDate(startDate);
} }
selectionMatrix = buildDayMatrix(); renderDayOverlay(startDate, addDays(cloneDate(endDate), 1));
daySelectionManager.select(start, end, allDay);
} }
}else{ }else{
if (!end) { if (!endDate) {
end = addMinutes(cloneDate(start), options.slotMinutes); endDate = addMinutes(cloneDate(startDate), options.slotMinutes);
} }
selectionMatrix = buildSlotMatrix(); renderSlotSelection(startDate, endDate);
slotSelectionManager.select(start, end, allDay);
} }
reportSelection(startDate, endDate, allDay);
}; };
function unselect() { function reportSelection(startDate, endDate, allDay) {
slotSelectionManager.unselect(); selected = true;
daySelectionManager.unselect(); view.trigger('select', view, startDate, endDate, allDay);
} }
this.unselect = unselect;
function unselect() {
if (selected) {
clearSelection();
selected = false;
view.trigger('unselect', view);
}
}
view.unselect = unselect;
selection_unselectAuto(view, unselect);
/* Selecting drawing utils /* Selecting drawing utils
-----------------------------------------------------------------------------*/ -----------------------------------------------------------------------------*/
var selectionHelper;
function renderSlotSelection(startDate, endDate) { function renderSlotSelection(startDate, endDate) {
var helperOption = view.option('selectHelper'); var helperOption = view.option('selectHelper');
if (helperOption) { if (helperOption) {
var col = dayDiff(startDate, view.visStart); var col = dayDiff(startDate, view.visStart) * dis + dit;
if (col >= 0 && col < colCnt) { // only works when times are on same day if (col >= 0 && col < colCnt) { // only works when times are on same day
var rect = selectionMatrix.rect(0, col*dis+dit, 1, col*dis+dit+1, bodyContent); // only for horizontal coords var rect = coordinateGrid.rect(0, col, 0, col, bodyContent); // only for horizontal coords
var top = timePosition(startDate, startDate); var top = timePosition(startDate, startDate);
var bottom = timePosition(startDate, endDate); var bottom = timePosition(startDate, endDate);
if (bottom > top) { // protect against selections that are entirely before or after visible range if (bottom > top) { // protect against selections that are entirely before or after visible range
@ -1068,12 +1088,12 @@ function Agenda(element, options, methods) {
} }
} }
}else{ }else{
renderSlotOverlay(selectionMatrix, startDate, endDate); renderSlotOverlay(startDate, endDate);
} }
} }
function clearSelection() { function clearSelection() {
clearOverlays(); clearOverlay();
if (selectionHelper) { if (selectionHelper) {
selectionHelper.remove(); selectionHelper.remove();
selectionHelper = null; selectionHelper = null;
@ -1083,11 +1103,10 @@ function Agenda(element, options, methods) {
/* Semi-transparent Overlay Helpers /* Semi-transparent Overlay Helpers
-----------------------------------------------------*/ -----------------------------------------------------*/
function renderDayOverlay(matrix, startDate, endDate) { function renderDayOverlay(startDate, endDate) {
var startCol, endCol; var startCol, endCol;
if (rtl) { if (rtl) {
startCol = dayDiff(endDate, view.visStart)*dis+dit+1; startCol = dayDiff(endDate, view.visStart)*dis+dit+1;
@ -1099,21 +1118,26 @@ function Agenda(element, options, methods) {
startCol = Math.max(0, startCol); startCol = Math.max(0, startCol);
endCol = Math.min(colCnt, endCol); endCol = Math.min(colCnt, endCol);
if (startCol < endCol) { if (startCol < endCol) {
var rect = matrix.rect(0, startCol, 1, endCol, head);
dayBind( dayBind(
view.renderOverlay(rect, head) _renderDayOverlay(0, startCol, 0, endCol-1)
); );
} }
} }
function renderSlotOverlay(matrix, overlayStart, overlayEnd) { function _renderDayOverlay(col0, row0, col1, row1) {
var rect = coordinateGrid.rect(col0, row0, col1, row1, head);
return view.renderOverlay(rect, head);
}
function renderSlotOverlay(overlayStart, overlayEnd) {
var dayStart = cloneDate(view.visStart); var dayStart = cloneDate(view.visStart);
var dayEnd = addDays(cloneDate(dayStart), 1); var dayEnd = addDays(cloneDate(dayStart), 1);
for (var i=0; i<colCnt; i++) { for (var i=0; i<colCnt; i++) {
var stretchStart = new Date(Math.max(dayStart, overlayStart)); var stretchStart = new Date(Math.max(dayStart, overlayStart));
var stretchEnd = new Date(Math.min(dayEnd, overlayEnd)); var stretchEnd = new Date(Math.min(dayEnd, overlayEnd));
if (stretchStart < stretchEnd) { if (stretchStart < stretchEnd) {
var rect = matrix.rect(0, i*dis+dit, 1, i*dis+dit+1, bodyContent); // only use it for horizontal coords var col = i*dis+dit;
var rect = coordinateGrid.rect(0, col, 0, col, bodyContent); // only use it for horizontal coords
var top = timePosition(dayStart, stretchStart); var top = timePosition(dayStart, stretchStart);
var bottom = timePosition(dayStart, stretchEnd); var bottom = timePosition(dayStart, stretchEnd);
rect.top = top; rect.top = top;
@ -1127,48 +1151,42 @@ function Agenda(element, options, methods) {
} }
} }
function clearOverlays() { function clearOverlay() {
view.clearOverlays(); view.clearOverlays();
} }
/* Coordinate Utilities /* External dragging
-----------------------------------------------------------------------------*/ -----------------------------------------------------*/
// get the Y coordinate of the given time on the given day (both Date objects) view.isExternalDraggable = function(_element) {
function timePosition(day, time) { // both date objects. day holds 00:00 of current day return _element.parentNode != daySegmentContainer[0] && _element.parentNode != slotSegmentContainer[0];
day = cloneDate(day, true); };
if (time < addMinutes(cloneDate(day), minMinute)) {
return 0;
}
if (time >= addMinutes(cloneDate(day), maxMinute)) {
return bodyContent.height();
}
var slotMinutes = options.slotMinutes,
minutes = time.getHours()*60 + time.getMinutes() - minMinute,
slotI = Math.floor(minutes / slotMinutes),
slotTop = slotTopCache[slotI];
if (slotTop === undefined) {
slotTop = slotTopCache[slotI] = body.find('tr:eq(' + slotI + ') td div')[0].offsetTop;
}
return Math.max(0, Math.round(
slotTop - 1 + slotHeight * ((minutes % slotMinutes) / slotMinutes)
));
}
function buildDayMatrix(changeCallback, includeSlotArea) { view.dragStart = function(ev) {
var rowElements = options.allDaySlot ? head.find('td') : $([]); hoverListener.start(function(cell) {
if (includeSlotArea) { clearOverlay();
rowElements = rowElements.add(body); if (cell) {
if (cellIsAllDay(cell)) {
_renderDayOverlay(cell.row, cell.col, cell.row, cell.col);
}else{
var d1 = cellDate(cell);
var d2 = addMinutes(cloneDate(d1), options.slotMinutes); //options.defaultEventMinutes);
renderSlotOverlay(d1, d2);
} }
return new HoverMatrix(rowElements, bg.find('td'), changeCallback);
} }
}, ev);
};
function buildSlotMatrix(changeCallback) { view.dragStop = function(ev, ui) {
return new HoverMatrix(bodyTable.find('td'), bg.find('td'), changeCallback); var cell = hoverListener.stop();
clearOverlay();
if (cell) {
view.trigger('drop', view, cellDate(cell), cellIsAllDay(cell), ev, ui);
} }
};
@ -1188,17 +1206,20 @@ function Agenda(element, options, methods) {
return ((dayOfWeek - Math.max(firstDay,nwe)+colCnt) % colCnt)*dis+dit; return ((dayOfWeek - Math.max(firstDay,nwe)+colCnt) % colCnt)*dis+dit;
} }
function cellDate(cell) {
// generating dates from cell row & columns var d = addDays(cloneDate(view.visStart), cell.col*dis+dit);
var slotIndex = cell.row;
function dayColDate(col) { if (options.allDaySlot) {
return addDays(cloneDate(view.visStart), col*dis+dit); slotIndex--;
}
if (slotIndex >= 0) {
addMinutes(d, minMinute + slotIndex*options.slotMinutes);
}
return d;
} }
function slotCellDate(row, col) { function cellIsAllDay(cell) {
var d = dayColDate(col); return options.allDaySlot && !cell.row;
addMinutes(d, minMinute + row*options.slotMinutes);
return d;
} }

View file

@ -121,8 +121,6 @@ function Grid(element, options, methods) {
dayContentPositions = new HorizontalPositionCache(function(dayOfWeek) { dayContentPositions = new HorizontalPositionCache(function(dayOfWeek) {
return tbody.find('td:eq(' + ((dayOfWeek - Math.max(firstDay,nwe)+colCnt) % colCnt) + ') div div'); return tbody.find('td:eq(' + ((dayOfWeek - Math.max(firstDay,nwe)+colCnt) % colCnt) + ') div div');
}), }),
selectionManager,
selectionMatrix,
// ... // ...
// initialize superclass // initialize superclass
@ -425,37 +423,32 @@ function Grid(element, options, methods) {
function draggableEvent(event, eventElement) { function draggableEvent(event, eventElement) {
if (!options.disableDragging && eventElement.draggable) { if (!options.disableDragging && eventElement.draggable) {
var matrix, var dayDelta;
dayDelta = 0;
eventElement.draggable({ eventElement.draggable({
zIndex: 9, zIndex: 9,
delay: 50, delay: 50,
opacity: view.option('dragOpacity'), opacity: view.option('dragOpacity'),
revertDuration: options.dragRevertDuration, revertDuration: options.dragRevertDuration,
start: function(ev, ui) { start: function(ev, ui) {
view.hideEvents(event, eventElement);
view.trigger('eventDragStart', eventElement, event, ev, ui); view.trigger('eventDragStart', eventElement, event, ev, ui);
matrix = buildDayMatrix(function(cell) { view.hideEvents(event, eventElement);
eventElement.draggable('option', 'revert', !cell || !cell.rowDelta && !cell.colDelta); hoverListener.start(function(cell, origCell, rowDelta, colDelta) {
clearOverlays(); eventElement.draggable('option', 'revert', !cell || !rowDelta && !colDelta);
clearOverlay();
if (cell) { if (cell) {
dayDelta = cell.rowDelta*7 + cell.colDelta*dis; dayDelta = rowDelta*7 + colDelta*dis;
renderDayOverlays( renderDayOverlay(
matrix,
addDays(cloneDate(event.start), dayDelta), addDays(cloneDate(event.start), dayDelta),
addDays(exclEndDay(event), dayDelta) addDays(exclEndDay(event), dayDelta)
); );
}else{ }else{
dayDelta = 0; dayDelta = 0;
} }
}); }, ev, 'drag');
matrix.mouse(ev);
},
drag: function(ev) {
matrix.mouse(ev);
}, },
stop: function(ev, ui) { stop: function(ev, ui) {
clearOverlays(); hoverListener.stop();
clearOverlay();
view.trigger('eventDragStop', eventElement, event, ev, ui); view.trigger('eventDragStop', eventElement, event, ev, ui);
if (dayDelta) { if (dayDelta) {
eventElement.find('a').removeAttr('href'); // prevents safari from visiting the link eventElement.find('a').removeAttr('href'); // prevents safari from visiting the link
@ -495,68 +488,108 @@ function Grid(element, options, methods) {
/* Coordinate Utilities
--------------------------------------------------------*/
var coordinateGrid = new CoordinateGrid(function(rows, cols) {
var e, n, p;
var tds = tbody.find('tr:first td');
if (rtl) {
tds = $(tds.get().reverse());
}
tds.each(function(i, _e) {
e = $(_e);
n = e.offset().left;
if (i) {
p[1] = n;
}
p = [n];
cols[i] = p;
});
p[1] = n + e.outerWidth();
tbody.find('tr').each(function(i, _e) {
e = $(_e);
n = e.offset().top;
if (i) {
p[1] = n;
}
p = [n];
rows[i] = p;
});
p[1] = n + e.outerHeight();
});
var hoverListener = new HoverListener(coordinateGrid);
/* Selecting /* Selecting
--------------------------------------------------------*/ --------------------------------------------------------*/
selectionManager = new SelectionManager( var selected = false;
view, var selectionMousedown = selection_dayMousedown(
unselect, view, hoverListener, cellDate, renderDayOverlay, clearOverlay, reportSelection, unselect
function(startDate, endDate, allDay) {
renderDayOverlays(
selectionMatrix,
startDate,
addDays(cloneDate(endDate), 1)
);
},
clearOverlays
); );
function selectionMousedown(ev) { view.select = function(startDate, endDate, allDay) {
if (view.option('selectable')) { coordinateGrid.build();
selectionMatrix = buildDayMatrix(function(cell) { unselect();
if (cell) { if (!endDate) {
var d = cellDate(cell.row, cell.col); endDate = cloneDate(startDate);
selectionManager.drag(d, d, true);
}else{
selectionManager.drag();
} }
}); renderDayOverlay(startDate, addDays(cloneDate(endDate), 1));
documentDragHelp( reportSelection(startDate, endDate, allDay);
function(ev) {
selectionMatrix.mouse(ev);
},
function(ev) {
selectionManager.dragStop(ev);
}
);
selectionManager.dragStart(ev);
selectionMatrix.mouse(ev);
return false; // prevent auto-unselect and text selection
}
}
documentUnselectAuto(view, unselect);
view.select = function(start, end, allDay) {
if (!end) {
end = cloneDate(start);
}
selectionMatrix = buildDayMatrix();
selectionManager.select(start, end, allDay);
}; };
function reportSelection(startDate, endDate, allDay) {
selected = true;
view.trigger('select', view, startDate, endDate, allDay);
}
function unselect() { function unselect() {
selectionManager.unselect(); if (selected) {
clearOverlay();
selected = false;
view.trigger('unselect', view);
}
} }
view.unselect = unselect; view.unselect = unselect;
selection_unselectAuto(view, unselect);
/* External dragging
------------------------------------------------------*/
view.isExternalDraggable = function(_element) {
return _element.parentNode != segmentContainer[0];
};
view.dragStart = function(ev, ui) {
hoverListener.start(function(cell) {
clearOverlay();
if (cell) {
_renderDayOverlay(cell.row, cell.col, cell.row, cell.col);
}
}, ev);
};
view.dragStop = function(ev, ui) {
var cell = hoverListener.stop();
clearOverlay();
if (cell) {
var d = cellDate(cell);
view.trigger('drop', view, d, true, ev, ui);
}
};
/* Semi-transparent Overlay Helpers /* Semi-transparent Overlay Helpers
------------------------------------------------------*/ ------------------------------------------------------*/
function renderDayOverlays(matrix, overlayStart, overlayEnd) { // overlayEnd is exclusive function renderDayOverlay(overlayStart, overlayEnd) { // overlayEnd is exclusive
var rowStart = cloneDate(view.visStart); var rowStart = cloneDate(view.visStart);
var rowEnd = addDays(cloneDate(rowStart), colCnt); var rowEnd = addDays(cloneDate(rowStart), colCnt);
for (var i=0; i<rowCnt; i++) { for (var i=0; i<rowCnt; i++) {
@ -571,9 +604,8 @@ function Grid(element, options, methods) {
colStart = dayDiff(stretchStart, rowStart); colStart = dayDiff(stretchStart, rowStart);
colEnd = dayDiff(stretchEnd, rowStart); colEnd = dayDiff(stretchEnd, rowStart);
} }
var rect = matrix.rect(i, colStart, i+1, colEnd, element);
dayBind( dayBind(
view.renderOverlay(rect, element) _renderDayOverlay(i, colStart, i, colEnd-1)
); );
} }
addDays(rowStart, 7); addDays(rowStart, 7);
@ -581,28 +613,23 @@ function Grid(element, options, methods) {
} }
} }
function clearOverlays() { function _renderDayOverlay(row0, col0, row1, col1) { // row1,col1 is inclusive
var rect = coordinateGrid.rect(row0, col0, row1, col1, element);
return view.renderOverlay(rect, element);
}
function clearOverlay() {
view.clearOverlays(); view.clearOverlays();
} }
/* Date Utils
/* Utils
---------------------------------------------------*/ ---------------------------------------------------*/
function buildDayMatrix(changeCallback) { function cellDate(cell) {
var tds = tbody.find('tr:first td'); return addDays(cloneDate(view.visStart), cell.row*7 + cell.col*dis+dit);
if (rtl) {
tds = $(tds.get().reverse());
}
return new HoverMatrix(tbody.find('tr'), tds, changeCallback);
}
function cellDate(r, c) { // convert r,c to date
return addDays(cloneDate(view.visStart), r*7 + c*dis+dit);
// TODO: what about weekends in middle of week? // TODO: what about weekends in middle of week?
} }

View file

@ -840,6 +840,21 @@ $.fn.fullCalendar = function(options) {
$(window).resize(windowResize); $(window).resize(windowResize);
if (options.droppable) {
$(document)
.bind('dragstart', function(ev, ui) {
if (view.isExternalDraggable(ev.target)) {
view.dragStart(ev, ui);
}
})
.bind('dragstop', function(ev, ui) {
if (view.isExternalDraggable(ev.target)) {
view.dragStop(ev, ui);
}
});
}
// let's begin... // let's begin...
changeView(options.defaultView); changeView(options.defaultView);

View file

@ -16,5 +16,5 @@
* *
*/ */
(function($) { (function($, undefined) {

View file

@ -1,96 +1,28 @@
function SelectionManager(view, initFunc, displayFunc, clearFunc) {
var t = this;
var selected = false;
var initialElement;
var initialRange;
var start;
var end;
var allDay;
t.dragStart = function(ev) { function selection_dayMousedown(view, hoverListener, cellDate, renderSelection, clearSelection, reportSelection, unselect) {
initFunc(); return function(ev) {
start = end = undefined; if (view.option('selectable')) {
initialRange = undefined; unselect();
initialElement = ev.currentTarget; var dates;
}; hoverListener.start(function(cell, origCell) {
clearSelection();
if (cell) {
t.drag = function(currentStart, currentEnd, currentAllDay) { dates = [ cellDate(origCell), cellDate(cell) ].sort(cmp);
if (currentStart) { renderSelection(dates[0], addDays(cloneDate(dates[1]), 1), true);
var range = [currentStart, currentEnd];
if (!initialRange) {
initialRange = range;
} }
var dates = initialRange.concat(range).sort(cmp); }, ev);
start = dates[0]; $(document).one('mouseup', function(ev) {
end = dates[3]; if (hoverListener.stop()) { // over a cell?
allDay = currentAllDay; reportSelection(dates[0], dates[1], true);
clearFunc();
displayFunc(cloneDate(start), cloneDate(end), allDay);
}else{
// called with no arguments
start = end = undefined;
clearFunc();
} }
}; });
t.dragStop = function(ev) {
if (start) {
if (+initialRange[0] == +start && +initialRange[1] == +end) {
view.trigger('dayClick', initialElement, start, allDay, ev);
} }
_select();
} }
};
t.select = function(newStart, newEnd, newAllDay) {
initFunc();
start = newStart;
end = newEnd;
allDay = newAllDay;
displayFunc(cloneDate(start), cloneDate(end), allDay);
_select();
};
function _select() { // just set the selected flag, and trigger
selected = true;
view.trigger('select', view, start, end, allDay);
} }
function unselect() { function selection_unselectAuto(view, unselect) {
if (selected) {
selected = false;
start = end = undefined;
clearFunc();
view.trigger('unselect', view);
}
}
t.unselect = unselect;
}
function documentDragHelp(mousemove, mouseup) {
function _mouseup(ev) {
mouseup(ev);
$(document)
.unbind('mousemove', mousemove)
.unbind('mouseup', _mouseup);
}
$(document)
.mousemove(mousemove)
.mouseup(_mouseup);
}
function documentUnselectAuto(view, unselectFunc) {
if (view.option('selectable') && view.option('unselectAuto')) { if (view.option('selectable') && view.option('unselectAuto')) {
$(document).mousedown(function(ev) { $(document).mousedown(function(ev) {
var ignore = view.option('unselectCancel'); var ignore = view.option('unselectCancel');
@ -99,10 +31,7 @@ function documentUnselectAuto(view, unselectFunc) {
return; return;
} }
} }
unselectFunc(); unselect();
}); });
} }
} }

View file

@ -378,72 +378,47 @@ function topCorrect(tr) { // tr/th/td or anything else
/* Hover Matrix /* Coordinate Grid
-----------------------------------------------------------------------------*/ -----------------------------------------------------------------------------*/
function HoverMatrix(rowElements, colElements, changeCallback) { function CoordinateGrid(buildFunc) {
var t=this, var t = this;
tops=[], lefts=[], var rows;
origRow, origCol, var cols;
currRow, currCol,
e;
$.each(rowElements, function(i, _e) { t.build = function() {
e = $(_e); rows = [];
tops.push(e.offset().top + topCorrect(e)); cols = [];
}); buildFunc(rows, cols);
tops.push(tops[tops.length-1] + e.outerHeight());
$.each(colElements, function(i, _e) {
e = $(_e);
lefts.push(e.offset().left);
});
lefts.push(lefts[lefts.length-1] + e.outerWidth());
t.mouse = function(ev) {
var x = ev.pageX;
var y = ev.pageY;
var r, c;
for (r=0; r<tops.length && y>=tops[r]; r++) {}
for (c=0; c<lefts.length && x>=lefts[c]; c++) {}
r = r >= tops.length ? -1 : r - 1;
c = c >= lefts.length ? -1 : c - 1;
if (r != currRow || c != currCol) {
currRow = r;
currCol = c;
if (r == -1 || c == -1) {
t.cell = null;
}else{
if (origRow === undefined) {
origRow = r;
origCol = c;
}
t.cell = {
row: r,
col: c,
top: tops[r],
left: lefts[c],
width: lefts[c+1] - lefts[c],
height: tops[r+1] - tops[r],
origRow: origRow,
origCol: origCol,
isOrig: r==origRow && c==origCol,
rowDelta: r-origRow,
colDelta: c-origCol
};
}
changeCallback(t.cell);
}
}; };
t.rect = function(row0, col0, row1, col1, originElement) { // row1,col1 are exclusive t.cell = function(x, y) {
var rowCnt = rows.length;
var colCnt = cols.length;
var i, r=-1, c=-1;
for (i=0; i<rowCnt; i++) {
if (y >= rows[i][0] && y < rows[i][1]) {
r = i;
break;
}
}
for (i=0; i<colCnt; i++) {
if (x >= cols[i][0] && x < cols[i][1]) {
c = i;
break;
}
}
return (r>=0 && c>=0) ? { row:r, col:c } : null;
};
t.rect = function(row0, col0, row1, col1, originElement) { // row1,col1 is inclusive
var origin = originElement.offset(); var origin = originElement.offset();
return { return {
top: tops[row0] - origin.top, top: rows[row0][0] - origin.top,
left: lefts[col0] - origin.left, left: cols[col0][0] - origin.left,
width: lefts[col1] - lefts[col0], width: cols[col1][1] - cols[col0][0],
height: tops[row1] - tops[row0] height: rows[row1][1] - rows[row0][0]
}; };
}; };
@ -451,11 +426,54 @@ function HoverMatrix(rowElements, colElements, changeCallback) {
/* Hover Listener
-----------------------------------------------------------------------------*/
function HoverListener(coordinateGrid) {
var t = this;
var bindType;
var change;
var firstCell;
var cell;
t.start = function(_change, ev, _bindType) {
change = _change;
firstCell = cell = null;
coordinateGrid.build();
mouse(ev);
bindType = _bindType || 'mousemove';
$(document).bind(bindType, mouse);
};
function mouse(ev) {
var newCell = coordinateGrid.cell(ev.pageX, ev.pageY);
if (!newCell != !cell || newCell && (newCell.row != cell.row || newCell.col != cell.col)) {
if (newCell) {
if (!firstCell) {
firstCell = newCell;
}
change(newCell, firstCell, newCell.row-firstCell.row, newCell.col-firstCell.col);
}else{
change(newCell, firstCell);
}
cell = newCell;
}
}
t.stop = function() {
$(document).unbind(bindType, mouse);
return cell;
};
}
/* Misc Utils /* Misc Utils
-----------------------------------------------------------------------------*/ -----------------------------------------------------------------------------*/
var undefined, var dayIDs = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'];
dayIDs = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'];
function zeroPad(n) { function zeroPad(n) {
return (n < 10 ? '0' : '') + n; return (n < 10 ? '0' : '') + n;

132
tests/droppable.html Normal file
View file

@ -0,0 +1,132 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN""http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<head>
<link rel='stylesheet' type='text/css' href='../examples/redmond/theme.css' />
<script type='text/javascript' src='loader.js'></script>
<script type='text/javascript'>
$(document).ready(function() {
var date = new Date();
var d = date.getDate();
var m = date.getMonth();
var y = date.getFullYear();
$('#calendar').fullCalendar({
droppable: true,
drop: function(date, allDay) {
console.log('drop', date, allDay);
},
//defaultView: 'agendaWeek',
//isRTL: true,
header: {
left: 'prev,next today',
center: 'title',
right: 'month,agendaWeek,basicWeek,agendaDay,basicDay'
},
editable: true,
events: [
{
title: 'All Day Event',
start: new Date(y, m, 1)
},
{
title: 'Long Event',
start: new Date(y, m, d-5),
end: new Date(y, m, d-2)
},
{
id: 999,
title: 'Repeating Event',
start: new Date(y, m, d-3, 16, 0),
allDay: false
},
{
id: 999,
title: 'Repeating Event',
start: new Date(y, m, d+4, 16, 0),
allDay: false
},
{
title: 'Meeting',
start: new Date(y, m, d, 10, 30),
allDay: false
},
{
title: 'Lunch',
start: new Date(y, m, d, 12, 5),
end: new Date(y, m, d, 14, 43),
allDay: false
},
{
title: 'Birthday Party',
start: new Date(y, m, d+1, 19, 0),
end: new Date(y, m, d+1, 22, 30),
allDay: false
},
{
title: 'Click for Google',
start: new Date(y, m, 28),
end: new Date(y, m, 29),
url: 'http://google.com/'
}
]
});
$('.external-event').draggable({
revert: true,
revertDuration: 0
});
});
</script>
<style type='text/css'>
body {
margin-top: 40px;
text-align: center;
font-size: 13px;
font-family: "Lucida Grande",Helvetica,Arial,Verdana,sans-serif;
}
#calendar {
width: 900px;
float: left;
}
#external-events {
position: relative;
left: 50px;
text-align: left;
float: left;
width: 140px;
padding: 10px;
border: 1px solid #aaa;
background: #ccc;
}
.external-event {
height: 20px;
line-height: 20px;
color: #fff;
background: blue;
margin-bottom: 10px;
padding-left: 5px;
cursor: pointer;
}
</style>
</head>
<body>
<div id='calendar'></div>
<div id='external-events'>
<div class='external-event'>Draggable 1</div>
<div class='external-event'>Draggable 2</div>
<div class='external-event'>Draggable 3</div>
</div>
</body>
</html>

View file

@ -19,6 +19,7 @@
right: 'month,agendaWeek,basicWeek,agendaDay,basicDay' right: 'month,agendaWeek,basicWeek,agendaDay,basicDay'
}, },
editable: true, editable: true,
//isRTL: true,
events: [ events: [
{ {
title: 'All Day Event', title: 'All Day Event',

View file

@ -1 +1 @@
1.4.6 1.4.7