refactoring/bugfixes to selectable code. new methods: select/unselect. new options: unselectAuto/unselectCancel

This commit is contained in:
Adam Shaw 2010-05-22 17:30:16 -07:00
parent 1b24a5f63b
commit a469d8d3c4
18 changed files with 708 additions and 553 deletions

View file

@ -9,6 +9,7 @@ JS_SRC_FILES = \
${SRC_DIR}/grid.js \
${SRC_DIR}/agenda.js \
${SRC_DIR}/view.js \
${SRC_DIR}/selection_util.js \
${SRC_DIR}/util.js
CSS_SRC_FILES = \
@ -37,9 +38,6 @@ zip:
@cat ${SRC_DIR}/gcal.js \
| ${VER_SED} | ${DATE_SED} \
> ${BUILD_DIR}/fullcalendar/gcal.js
@cat ${SRC_DIR}/selectable.js \
| ${VER_SED} | ${DATE_SED} \
> ${BUILD_DIR}/fullcalendar/selectable.js
@echo "compressing js..."
@java -jar ${BUILD_DIR}/compiler.jar --js ${BUILD_DIR}/fullcalendar/fullcalendar.js \

View file

@ -13,6 +13,7 @@
<script type='text/javascript' src='../src/grid.js'></script>
<script type='text/javascript' src='../src/agenda.js'></script>
<script type='text/javascript' src='../src/view.js'></script>
<script type='text/javascript' src='../src/selection_util.js'></script>
<script type='text/javascript' src='../src/util.js'></script>
<!--</src>-->
<!--

View file

@ -13,6 +13,7 @@
<script type='text/javascript' src='../src/grid.js'></script>
<script type='text/javascript' src='../src/agenda.js'></script>
<script type='text/javascript' src='../src/view.js'></script>
<script type='text/javascript' src='../src/selection_util.js'></script>
<script type='text/javascript' src='../src/util.js'></script>
<!--</src>-->
<!--

View file

@ -13,6 +13,7 @@
<script type='text/javascript' src='../src/grid.js'></script>
<script type='text/javascript' src='../src/agenda.js'></script>
<script type='text/javascript' src='../src/view.js'></script>
<script type='text/javascript' src='../src/selection_util.js'></script>
<script type='text/javascript' src='../src/util.js'></script>
<!--</src>-->
<!--

View file

@ -11,6 +11,7 @@
<script type='text/javascript' src='../src/agenda.js'></script>
<script type='text/javascript' src='../src/view.js'></script>
<script type='text/javascript' src='../src/util.js'></script>
<script type='text/javascript' src='../src/selection_util.js'></script>
<script type='text/javascript' src='../src/gcal.js'></script>
<!--</src>-->
<!--

View file

@ -13,6 +13,7 @@
<script type='text/javascript' src='../src/grid.js'></script>
<script type='text/javascript' src='../src/agenda.js'></script>
<script type='text/javascript' src='../src/view.js'></script>
<script type='text/javascript' src='../src/selection_util.js'></script>
<script type='text/javascript' src='../src/util.js'></script>
<!--</src>-->
<!--

View file

@ -13,8 +13,8 @@
<script type='text/javascript' src='../src/grid.js'></script>
<script type='text/javascript' src='../src/agenda.js'></script>
<script type='text/javascript' src='../src/view.js'></script>
<script type='text/javascript' src='../src/selection_util.js'></script>
<script type='text/javascript' src='../src/util.js'></script>
<script type='text/javascript' src='../src/selectable.js'></script>
<!--</src>-->
<!--
<dist>
@ -24,7 +24,6 @@
<script type='text/javascript' src='../jquery/ui.draggable.js'></script>
<script type='text/javascript' src='../jquery/ui.resizable.js'></script>
<script type='text/javascript' src='../fullcalendar.min.js'></script>
<script type='text/javascript' src='../selectable.js'></script>
</dist>
-->
<script type='text/javascript'>
@ -36,39 +35,30 @@
var m = date.getMonth();
var y = date.getFullYear();
$('#calendar').fullCalendar({
var calendar = $('#calendar').fullCalendar({
header: {
left: 'prev,next today',
center: 'title',
right: 'month,agendaWeek,agendaDay'
},
editable: true,
/********** new selecting options **********/
selectable: true, // activate selecting!
unselectable: true, // automatically hide the selection when user clicks elsewhere? (defaults to true)
selectHelper: true, // use a "fake" event for selecting? (only works in agenda views right now)
select: function(start, end, allDay, view) {
console.log(
'---- selection ----\n' +
'start: ' + start + '\n' +
'end: ' + end + '\n' + // exclusive!!
'allDay: ' + allDay
selectable: true,
selectHelper: true,
select: function(start, end, allDay) {
var title = prompt('Event Title:');
if (title) {
calendar.fullCalendar('renderEvent',
{
title: title,
start: start,
end: end,
allDay: allDay
},
true
);
/*
if (confirm("clear the selection?")) {
$('#calendar').fullCalendar('unselect'); // 'unselect' method to manually clear selection
// a 'select' method coming soon...
}
*/
calendar.fullCalendar('unselect');
},
unselect: function() {
console.log('unselected');
},
/******************************************/
editable: true,
events: [
{
title: 'All Day Event',

View file

@ -14,6 +14,7 @@
<script type='text/javascript' src='../src/grid.js'></script>
<script type='text/javascript' src='../src/agenda.js'></script>
<script type='text/javascript' src='../src/view.js'></script>
<script type='text/javascript' src='../src/selection_util.js'></script>
<script type='text/javascript' src='../src/util.js'></script>
<!--</src>-->
<!--

View file

@ -88,6 +88,8 @@ function Agenda(element, options, methods) {
return bg.find('td:eq(' + col + ') div div');
}),
slotTopCache = {},
daySelectionManager,
slotSelectionManager,
// ...
view = $.extend(this, viewMethods, methods, {
@ -184,7 +186,7 @@ function Agenda(element, options, methods) {
}
s+= "</table></div>";
head = $(s).appendTo(element);
bindDayHandlers(head.find('td'));
dayBind(head.find('td'));
// all-day event container
daySegmentContainer = $("<div style='position:absolute;z-index:8;top:0;left:0'/>").appendTo(head);
@ -207,10 +209,10 @@ function Agenda(element, options, methods) {
}
s += "</table>";
body = $("<div class='fc-agenda-body' style='position:relative;z-index:2;overflow:auto'/>")
.append(view.bodyContent = bodyContent = $("<div style='position:relative;overflow:hidden'>")
.append(bodyContent = $("<div style='position:relative;overflow:hidden'>")
.append(bodyTable = $(s)))
.appendTo(element);
bindSlotHandlers(body.find('td')); // .click(slotClick);
slotBind(body.find('td'));
// slot event container
slotSegmentContainer = $("<div style='position:absolute;z-index:8;top:0;left:0'/>").appendTo(bodyContent);
@ -348,22 +350,20 @@ function Agenda(element, options, methods) {
-----------------------------------------------------------------------*/
function bindDayHandlers(tds) {
function dayBind(tds) {
tds.click(slotClick);
if ($.fullCalendar.bindBgHandlers) {
$.fullCalendar.bindBgHandlers(view, tds, true);
if (daySelectionManager) {
tds.mousedown(daySelectionMousedown);
}
}
view.bindDayHandlers = bindDayHandlers;
function bindSlotHandlers(tds) {
function slotBind(tds) {
tds.click(slotClick);
if ($.fullCalendar.bindBgHandlers) {
$.fullCalendar.bindBgHandlers(view, tds, false);
if (slotSelectionManager) {
tds.mousedown(slotSelectionMousedown);
}
}
view.bindSlotHandlers = bindSlotHandlers;
function slotClick(ev) {
@ -478,13 +478,13 @@ function Agenda(element, options, methods) {
return head.find('tr.fc-all-day');
},
function(dayOfWeek) {
return axisWidth + colContentPositions.left(day2col(dayOfWeek));
return axisWidth + colContentPositions.left(dayOfWeekCol(dayOfWeek));
},
function(dayOfWeek) {
return axisWidth + colContentPositions.right(day2col(dayOfWeek));
return axisWidth + colContentPositions.right(dayOfWeekCol(dayOfWeek));
},
daySegmentContainer,
bindDaySegHandlers,
daySegBind,
modifiedEventId
);
setHeight(viewHeight); // might have pushed the body down, so resize
@ -554,7 +554,7 @@ function Agenda(element, options, methods) {
seg.left = left;
seg.outerWidth = outerWidth;
seg.outerHeight = bottom - top;
html += segHtml(event, seg, className);
html += slotSegHtml(event, seg, className);
}
slotSegmentContainer[0].innerHTML = html; // faster than html()
eventElements = slotSegmentContainer.children();
@ -580,7 +580,7 @@ function Agenda(element, options, methods) {
}
seg.element = eventElement;
if (event._id === modifiedEventId) {
bindSlotSegHandlers(event, eventElement, seg);
slotSegBind(event, eventElement, seg);
}else{
eventElement[0]._fci = i; // for lazySegBind
}
@ -588,7 +588,7 @@ function Agenda(element, options, methods) {
}
}
lazySegBind(slotSegmentContainer, segs, bindSlotSegHandlers);
lazySegBind(slotSegmentContainer, segs, slotSegBind);
// record event sides and title positions
for (i=0; i<segCnt; i++) {
@ -625,8 +625,7 @@ function Agenda(element, options, methods) {
}
function segHtml(event, seg, className) {
function slotSegHtml(event, seg, className) {
return "<div class='" + className + event.className.join(' ') + "' style='position:absolute;z-index:8;top:" + seg.top + "px;left:" + seg.left + "px'>" +
"<a" + (event.url ? " href='" + htmlEscape(event.url) + "'" : '') + ">" +
"<span class='fc-event-bg'></span>" +
@ -638,33 +637,10 @@ function Agenda(element, options, methods) {
: '') +
"</div>";
}
view.segHtml = segHtml;
function visEventEnd(event) { // returns exclusive 'visible' end, for rendering
if (event.allDay) {
return visEventEndAllDay(event);
}
if (event.end) {
return cloneDate(event.end);
}else{
return addMinutes(cloneDate(event.start), options.defaultEventMinutes);
}
}
function visEventEndAllDay(event) {
if (event.end) {
var end = cloneDate(event.end);
return (event.allDay || end.getHours() || end.getMinutes()) ? addDays(end, 1) : end;
}else{
return addDays(cloneDate(event.start), 1);
}
}
function bindDaySegHandlers(event, eventElement, seg) {
function daySegBind(event, eventElement, seg) {
view.eventElementHandlers(event, eventElement);
if (event.editable || event.editable === undefined && options.editable) {
draggableDayEvent(event, eventElement, seg.isStart);
@ -676,7 +652,7 @@ function Agenda(element, options, methods) {
function bindSlotSegHandlers(event, eventElement, seg) {
function slotSegBind(event, eventElement, seg) {
view.eventElementHandlers(event, eventElement);
if (event.editable || event.editable === undefined && options.editable) {
var timeElement = eventElement.find('span.fc-event-time');
@ -721,7 +697,7 @@ function Agenda(element, options, methods) {
allDay = true;
}
};
matrix = buildMainMatrix(function(cell) {
matrix = buildDayMatrix(function(cell) {
eventElement.draggable('option', 'revert', !cell || !cell.rowDelta && !cell.colDelta);
view.clearOverlays();
if (cell) {
@ -729,8 +705,8 @@ function Agenda(element, options, methods) {
// on full-days
renderDayOverlay(
matrix,
cellOffset(addDays(cloneDate(event.start), cell.colDelta)),
cellOffset(addDays(visEventEnd(event), cell.colDelta)) // visEventEnd returns a clone
dayDateCol(addDays(cloneDate(event.start), cell.colDelta)),
dayDateCol(addDays(visEventEnd(event), cell.colDelta)) // visEventEnd returns a clone
);
resetElement();
}else{
@ -748,11 +724,11 @@ function Agenda(element, options, methods) {
}
}
}
});
matrix.mouse(ev.pageX, ev.pageY);
},true);
matrix.mouse(ev);
},
drag: function(ev, ui) {
matrix.mouse(ev.pageX, ev.pageY);
matrix.mouse(ev);
},
stop: function(ev, ui) {
view.trigger('eventDragStop', eventElement, event, ev, ui);
@ -821,7 +797,7 @@ function Agenda(element, options, methods) {
}
};
prevSlotDelta = 0;
matrix = buildMainMatrix(function(cell) {
matrix = buildDayMatrix(function(cell) {
eventElement.draggable('option', 'revert', !cell);
view.clearOverlays();
if (cell) {
@ -834,15 +810,16 @@ function Agenda(element, options, methods) {
}
renderDayOverlay(
matrix,
cellOffset(addDays(cloneDate(event.start), cell.colDelta)),
cellOffset(addDays(visEventEndAllDay(event), cell.colDelta)) // visEventEnd returns a clone
dayDateCol(addDays(cloneDate(event.start), cell.colDelta)),
dayDateCol(addDays(visEventEndAllDay(event), cell.colDelta)) // visEventEndAllDay returns a clone
// TODO: test this stuff further!!!
);
}else{ // on slots
resetElement();
}
}
});
matrix.mouse(ev.pageX, ev.pageY);
},true);
matrix.mouse(ev);
},
drag: function(ev, ui) {
slotDelta = Math.round((ui.position.top - origPosition.top) / slotHeight);
@ -859,7 +836,7 @@ function Agenda(element, options, methods) {
}
prevSlotDelta = slotDelta;
}
matrix.mouse(ev.pageX, ev.pageY);
matrix.mouse(ev);
},
stop: function(ev, ui) {
view.clearOverlays();
@ -948,11 +925,209 @@ function Agenda(element, options, methods) {
/* Misc
/* Selecting
-----------------------------------------------------------------------------*/
if (view.option('selectable')) {
var selectionHelper;
var selectionMatrix;
if (options.allDaySlot) {
// all-day selecting
daySelectionManager = new SelectionManager(
view,
function(startDate, endDate) {
renderDayOverlay(
selectionMatrix,
dayDateCol(startDate),
dayDateCol(addDays(endDate, 1))
);
},
clearSelection
);
function daySelectionMousedown(ev) {
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);
ev.stopPropagation(); // prevent auto-unselect
}
}
// slot selecting
slotSelectionManager = new SelectionManager(
view,
renderSlotSelection,
clearSelection
);
function slotSelectionMousedown(ev) {
selectionMatrix = buildSlotMatrix(function(cell) {
if (cell) {
var d = slotCellDate(cell.row, cell.origCol);
slotSelectionManager.drag(d, addMinutes(cloneDate(d), options.slotMinutes), false);
}else{
slotSelectionManager.drag();
}
});
documentDragHelp(
function(ev) {
selectionMatrix.mouse(ev);
},
function(ev) {
slotSelectionManager.dragStop(ev);
}
);
slotSelectionManager.dragStart(ev);
selectionMatrix.mouse(ev);
ev.stopPropagation(); // prevent auto-unselect
}
documentAutoUnselect(view, unselect);
}
function renderSlotSelection(startDate, endDate) {
// startDate and endDate are assumed to be in same day
var helperOption = view.option('selectHelper');
if (helperOption) {
var startCell = slotDateCell(startDate);
var endCell = slotDateCell(endDate);
var rect = selectionMatrix.rect(startCell[0], startCell[1], endCell[0], startCell[1]+1, bodyContent);
rect.left += 2;
rect.width -= 5;
if ($.isFunction(helperOption)) {
selectionHelper = helperOption(startDate, endDate);
if (selectionHelper) {
selectionHelper.css(rect);
}
}else{
selectionHelper = $(slotSegHtml(
{
title: '',
start: startDate,
end: endDate,
className: [],
editable: false
},
rect,
'fc-event fc-event-vert fc-corner-top fc-corner-bottom '
));
if (!$.browser.msie) {
// IE makes the event completely clear!!?
selectionHelper.css('opacity', view.option('dragOpacity'));
}
}
if (selectionHelper) {
slotBind(selectionHelper);
bodyContent.append(selectionHelper);
setOuterWidth(selectionHelper, rect.width, true);
setOuterHeight(selectionHelper, rect.height, true);
}
}else{
renderSlotOverlay(
selectionMatrix,
slotDateCell(startDate),
slotDateCell(endDate)
);
}
}
function clearSelection() {
clearOverlays();
if (selectionHelper) {
selectionHelper.remove();
selectionHelper = null;
}
}
this.select = function(start, end, allDay) {
if (allDay) {
if (daySelectionManager) {
selectionMatrix = buildDayMatrix();
daySelectionManager.select(start, end, allDay);
}
}else{
if (slotSelectionManager) {
selectionMatrix = buildSlotMatrix();
slotSelectionManager.select(start, end, allDay);
}
}
};
function unselect() {
if (slotSelectionManager) {
slotSelectionManager.unselect();
if (daySelectionManager) {
daySelectionManager.unselect();
}
}
}
this.unselect = unselect;
/* Semi-transparent Overlay Helpers
-----------------------------------------------------*/
function renderDayOverlay(matrix, startCol, endCol) {
renderDayOverlayRect(
matrix.rect(0, startCol, 1, endCol, head)
);
}
function renderDayOverlayRect(rect) {
dayBind(
view.renderOverlay(rect, head)
);
}
function renderSlotOverlay(matrix, startCell, endCell) {
renderSlotOverlayRect(
matrix.rect(startCell[0], startCell[1], endCell[0], startCell[1]+1, bodyContent)
);
// TODO: implement wrapping
}
function renderSlotOverlayRect(rect) {
slotBind(
view.renderOverlay(rect, bodyContent)
);
}
function clearOverlays() {
view.clearOverlays();
}
/* Coordinate Utilities
-----------------------------------------------------------------------------*/
// 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)) {
@ -973,41 +1148,7 @@ function Agenda(element, options, methods) {
));
}
function day2col(dayOfWeek) {
return ((dayOfWeek - Math.max(firstDay,nwe)+colCnt) % colCnt)*dis+dit;
}
function cellOffset(date) { // the "offset" index in the matrix
var d = rtl ? addDays(cloneDate(view.visEnd), -1) : cloneDate(view.visStart);
for (var i=0; i<colCnt; i++) {
addDays(d, dis);
if (nwe) {
skipWeekend(d, dis);
}
if (d > date) {
return i;
}
}
return i;
}
function slotTime(dayIndex, slotIndex) { // TODO: immune to DST???
var d = clearTime(addDays(cloneDate(view.visStart), dayIndex));
addMinutes(d, minMinute);
for (var i=0; i < slotIndex; i++) { // need a loop here !!!!!!!!!!!??????????
addMinutes(d, options.slotMinutes);
}
return d;
}
view.slotTime = slotTime;
// matrix building
function buildMainMatrix(changeCallback) {
function buildDayMatrix(changeCallback, includeSlotArea) {
var matrix = new HoverMatrix(changeCallback);
if (options.allDaySlot) {
matrix.row(head.find('td'));
@ -1015,10 +1156,11 @@ function Agenda(element, options, methods) {
bg.find('td').each(function() {
matrix.col(this);
});
if (includeSlotArea) {
matrix.row(body);
}
return matrix;
}
view.buildMainMatrix = buildMainMatrix;
function buildSlotMatrix(changeCallback) {
var matrix = new HoverMatrix(changeCallback);
@ -1030,18 +1172,77 @@ function Agenda(element, options, methods) {
});
return matrix;
}
view.buildSlotMatrix = buildSlotMatrix;
// overlay for dropping and selecting
function renderDayOverlay(matrix, startCol, endCol) {
view.renderOverlay(
matrix.rect(0, startCol, 1, endCol, head),
head
);
/* Date Utilities
----------------------------------------------------*/
// generating event end dates
function visEventEnd(event) { // returns exclusive 'visible' end, for rendering
if (event.allDay) {
return visEventEndAllDay(event);
}
view.renderDayOverlay = renderDayOverlay;
if (event.end) {
return cloneDate(event.end);
}else{
return addMinutes(cloneDate(event.start), options.defaultEventMinutes);
}
}
function visEventEndAllDay(event) {
if (event.end) {
var end = cloneDate(event.end);
return (event.allDay || end.getHours() || end.getMinutes()) ? addDays(end, 1) : end;
}else{
return addDays(cloneDate(event.start), 1);
}
}
// generating indexes from dates
function dayDateCol(date) {
var d = cloneDate(view.visStart);
var c;
for (c=0; c<colCnt; c++) {
addDays(d, 1);
if (nwe) {
skipWeekend(d);
}
if (d > date) {
break;
}
}
return c*dis+dit;
}
function slotDateCell(date) {
var col = dayDateCol(date);
var row = Math.floor((date.getHours() * 60 + date.getMinutes()) / options.slotMinutes);
return [row, col];
}
function dayOfWeekCol(dayOfWeek) {
return ((dayOfWeek - Math.max(firstDay,nwe)+colCnt) % colCnt)*dis+dit;
}
// generating dates from cell row & columns
function dayColDate(col) {
return addDays(cloneDate(view.visStart), col*dis+dit);
}
function slotCellDate(row, col) {
var d = dayColDate(col);
addMinutes(d, minMinute + row*options.slotMinutes);
return d;
}
}

View file

@ -121,6 +121,7 @@ function Grid(element, options, methods) {
dayContentPositions = new HorizontalPositionCache(function(dayOfWeek) {
return tbody.find('td:eq(' + ((dayOfWeek - Math.max(firstDay,nwe)+colCnt) % colCnt) + ') div div');
}),
selectionManager,
// ...
// initialize superclass
@ -155,7 +156,7 @@ function Grid(element, options, methods) {
}
rowCnt = r;
colCnt = view.colCnt = c;
colCnt = c;
// update option-derived variables
tm = options.theme ? 'ui' : 'fc';
@ -214,7 +215,7 @@ function Grid(element, options, methods) {
s += "</tr>";
}
tbody = $(s + "</tbody>").appendTo(table);
bindDayHandlers(tbody.find('td'));
dayBind(tbody.find('td'));
segmentContainer = $("<div style='position:absolute;z-index:8;top:0;left:0'/>").appendTo(element);
@ -247,7 +248,7 @@ function Grid(element, options, methods) {
}
tbody.append(s);
}
bindDayHandlers(tbody.find('td.fc-new').removeClass('fc-new'));
dayBind(tbody.find('td.fc-new').removeClass('fc-new'));
// re-label and re-class existing cells
d = cloneDate(view.visStart);
@ -452,27 +453,27 @@ function Grid(element, options, methods) {
start: function(ev, ui) {
view.hideEvents(event, eventElement);
view.trigger('eventDragStart', eventElement, event, ev, ui);
matrix = buildMatrix(function(cell) {
matrix = buildDayMatrix(function(cell) {
eventElement.draggable('option', 'revert', !cell || !cell.rowDelta && !cell.colDelta);
view.clearOverlays();
clearOverlays();
if (cell) {
dayDelta = cell.rowDelta*7 + cell.colDelta*dis;
renderOverlays(
renderDayOverlays(
matrix,
cellOffset(addDays(cloneDate(event.start), dayDelta)),
cellOffset(addDays(visEventEnd(event), dayDelta)) // visEventEnd returns a clone
dateCell(addDays(cloneDate(event.start), dayDelta)),
dateCell(addDays(visEventEnd(event), dayDelta))
);
}else{
dayDelta = 0;
}
});
matrix.mouse(ev.pageX, ev.pageY);
matrix.mouse(ev);
},
drag: function(ev) {
matrix.mouse(ev.pageX, ev.pageY);
matrix.mouse(ev);
},
stop: function(ev, ui) {
view.clearOverlays();
clearOverlays();
view.trigger('eventDragStop', eventElement, event, ev, ui);
if (dayDelta) {
eventElement.find('a').removeAttr('href'); // prevents safari from visiting the link
@ -490,18 +491,15 @@ function Grid(element, options, methods) {
/* Day clicking and day event binding
/* Day clicking and binding
---------------------------------------------------------*/
function bindDayHandlers(days) {
function dayBind(days) {
days.click(dayClick);
if ($.fullCalendar.bindBgHandlers) {
$.fullCalendar.bindBgHandlers(view, days, true);
if (selectionManager) {
days.mousedown(selectionMousedown);
}
}
view.bindDayHandlers = bindDayHandlers;
function dayClick(ev) {
var n = parseInt(this.className.match(/fc\-day(\d+)/)[1]),
@ -509,62 +507,106 @@ function Grid(element, options, methods) {
cloneDate(view.visStart),
Math.floor(n/colCnt) * 7 + n % colCnt
);
// TODO: what about weekends in middle of week?
view.trigger('dayClick', this, date, true, ev);
}
/* Selecting
--------------------------------------------------------*/
/* Utilities
if (view.option('selectable')) {
var selectionMatrix;
selectionManager = new SelectionManager(
view,
function(startDate, endDate) {
renderDayOverlays(selectionMatrix, dateCell(startDate), dateCell(addDays(endDate, 1)));
},
clearOverlays
);
function selectionMousedown(ev) {
selectionMatrix = buildDayMatrix(function(cell) {
if (cell) {
var d = cellDate(cell.row, cell.col);
selectionManager.drag(d, d, true);
}else{
selectionManager.drag();
}
});
documentDragHelp(
function(ev) {
selectionMatrix.mouse(ev);
},
function(ev) {
selectionManager.dragStop(ev);
}
);
selectionManager.dragStart(ev);
selectionMatrix.mouse(ev);
ev.stopPropagation(); // prevent auto-unselect
}
documentAutoUnselect(view, unselect);
}
view.select = function(start, end) {
if (selectionManager) {
selectionMatrix = buildDayMatrix();
selectionManager.select(start, end, true);
}
};
function unselect() {
if (selectionManager) {
selectionManager.unselect();
}
}
view.unselect = unselect;
/* Semi-transparent Overlay Helpers
------------------------------------------------------*/
function renderDayOverlays(matrix, startCell, endCell) { // for rendering overlays across weeks
var r0 = startCell[0];
var c0 = startCell[1];
var r1 = endCell[0];
var c1 = endCell[1];
var localC0, localC1;
for (var r=r0; r<=r1; r++) {
if (rtl) {
localC0 = r==r1 ? c1+1 : 0;
localC1 = r==r0 ? c0+1 : colCnt;
}else{
localC0 = r==r0 ? c0 : 0;
localC1 = r==r1 ? c1 : colCnt;
}
if (localC0 < localC1) {
var rect = matrix.rect(r, localC0, r+1, localC1, element);
dayBind(
view.renderOverlay(rect, element)
);
}
}
}
function clearOverlays() {
view.clearOverlays();
}
/* Matrix Construction
---------------------------------------------------*/
function cellOffset(date) { // always returns index in range
var d = cloneDate(view.visStart),
i, j, k=0;
for (i=0; i<rowCnt; i++) {
for (j=0; j<colCnt; j++) {
addDays(d, 1);
if (nwe) {
skipWeekend(d);
}
if (d > date) {
return k;
}
k++;
}
}
return k;
}
function offset2date(cellOffset) {
return addDays(cloneDate(view.visStart), cellOffset);
}
view.offset2date = offset2date;
function renderOverlays(matrix, offset, endOffset) {
var len = endOffset - offset,
localLen,
r = Math.floor(offset / colCnt),
c = offset % colCnt,
origin = element.offset();
while (len > 0) {
localLen = Math.min(len, colCnt - c);
view.renderOverlay(
matrix.rect(r, c, r+1, c+localLen, element),
element
);
len -= localLen;
r += 1;
c = 0;
}
}
view.renderOverlays = renderOverlays;
function buildMatrix(changeCallback) {
function buildDayMatrix(changeCallback) {
var matrix = new HoverMatrix(changeCallback);
tbody.find('tr').each(function() {
matrix.row(this);
@ -578,7 +620,38 @@ function Grid(element, options, methods) {
});
return matrix;
}
view.buildMatrix = buildMatrix;
/* Date Utilities
------------------------------------------------------*/
function dateCell(date) { // convert date to [row, col]
var d = cloneDate(view.visStart);
var r, c;
var found = false;
for (r=0; r<rowCnt; r++) {
for (c=0; c<colCnt; c++) {
addDays(d, 1);
if (nwe) {
skipWeekend(d);
}
if (d > date) {
found = true;
break;
}
}
if (found) {
break;
}
}
return [r, c*dis+dit];
}
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?
}
}

View file

@ -71,7 +71,7 @@ var defaults = {
},
//selectable: false,
unselectable: true
unselectAuto: true
};
@ -114,9 +114,6 @@ $.fn.fullCalendar = function(options) {
var data = $.data(this, 'fullCalendar');
if (data) {
var meth = data[options];
if (!meth) {
meth = $.fullCalendar.publicMethods[options];
}
if (meth) {
var r = meth.apply(this, args);
if (res === undefined) {
@ -202,7 +199,7 @@ $.fn.fullCalendar = function(options) {
if (v != viewName) {
ignoreWindowResize++; // because setMinHeight might change the height before render (and subsequently setSize) is reached
viewUpdate();
viewUnselect();
var oldView = view,
newViewElement;
@ -225,7 +222,7 @@ $.fn.fullCalendar = function(options) {
if (viewInstances[v]) {
(view = viewInstances[v]).element.show();
}else{
view = viewInstances[v] = $.fullCalendar.views[v](
view = viewInstances[v] = fc.views[v](
newViewElement = absoluteViewElement =
$("<div class='fc-view fc-view-" + v + "' style='position:absolute'/>")
.appendTo(content),
@ -258,7 +255,7 @@ $.fn.fullCalendar = function(options) {
if (elementVisible()) {
ignoreWindowResize++; // because view.renderEvents might temporarily change the height before setSize is reached
viewUpdate();
viewUnselect();
if (suggestedViewHeight === undefined) {
calcSize();
@ -314,10 +311,9 @@ $.fn.fullCalendar = function(options) {
return $('body')[0].offsetWidth !== 0;
}
function viewUpdate() {
// this function is ONLY for the ghetto plugin archicture
if (view && view.viewUpdate) {
view.viewUpdate();
function viewUnselect() {
if (view) {
view.unselect();
}
}
@ -667,6 +663,18 @@ $.fn.fullCalendar = function(options) {
refetchEvents: function() {
fetchEvents(eventsChanged);
},
//
// selection
//
select: function(start, end, allDay) {
view.select(start, end, allDay);
},
unselect: function() {
view.unselect();
}
};

View file

@ -1,328 +0,0 @@
(function($, undefined) {
var fc = $.fullCalendar,
setOuterWidth = fc.setOuterWidth,
setOuterHeight = fc.setOuterHeight,
addDays = fc.addDays,
cloneDate = fc.cloneDate,
cmp = fc.cmp;
var selectionManagers = {};
$.fullCalendar.bindBgHandlers = function(view, elements, allDay) {
if (view.option('selectable')) {
getSelectionManager(view).bind(elements, allDay);
}
};
function getSelectionManager(view) {
var name = view.name,
manager = selectionManagers[name];
if (!manager) {
if (name.indexOf('agenda') == 0) {
manager = new AgendaSelectionManager(view);
}else{
manager = new GridSelectionManager(view);
}
selectionManagers[name] = manager;
}
return manager;
}
/* methods
---------------------------------------------------*/
$.extend($.fullCalendar.publicMethods, {
select: function(start, end, allDay) {
getSelectionManager($(this).fullCalendar('getView')).select(start, end, allDay);
// not yet implemented...
},
unselect: function() {
for (var name in selectionManagers) {
selectionManagers[name].unselect();
}
}
});
/* month, basicWeek, basicDay
---------------------------------------------------*/
function GridSelectionManager(view) {
var selected=false,
matrix,
start, // the "offset" (row*colCnt+col) of the cell
end, // the "offset" (row*colCnt+col) of the cell (inclusive)
range; // sorted array
this.bind = function(elements) {
elements.mousedown(mousedown);
};
function mousedown(ev) {
start = undefined;
matrix = view.buildMatrix(function(cell) {
view.clearOverlays();
if (cell) {
end = cell.row * view.colCnt + cell.col;
if (start === undefined) {
unselect();
start = end;
}
range = [start, end].sort(cmp);
view.renderOverlays(matrix, range[0], range[1]+1);
$.each(view.overlays, function() {
view.bindDayHandlers(this);
});
selected = true;
}else{
selected = false;
}
});
$(document)
.mousemove(mousemove)
.mouseup(mouseup);
matrix.mouse(ev.pageX, ev.pageY);
ev.stopPropagation();
}
function mousemove(ev) {
matrix.mouse(ev.pageX, ev.pageY);
}
function mouseup(ev) {
$(document)
.unbind('mousemove', mousemove)
.unbind('mouseup', mouseup);
if (selected) {
view.trigger(
'select',
view,
view.offset2date(range[0]),
view.offset2date(range[1]+1),
true
);
}
}
function unselect() {
if (selected) {
view.clearOverlays();
view.trigger('unselect', view);
selected = false;
}
}
this.unselect = unselect;
view.viewUpdate = unselect;
if (view.option('unselectable')) {
$(document).mousedown(function() {
unselect();
});
}
}
/* agenda views
-----------------------------------------------*/
function AgendaSelectionManager(view) {
var selected=false,
matrix,
start, // for all-day, the COLUMN of the day. for slot, the ROW of the slot
end, // for all-day, the COLUMN of the day. for slot, the ROW of the slot
day, // only used for slots. the COLUMN of the day
range, // start & end, sorted array
helper;
this.bind = function(elements, allDay) {
if (allDay) {
dayBind(elements);
}else{
slotBind(elements);
}
};
// all-day
function dayBind(elements) {
elements.mousedown(dayMousedown);
}
function dayMousedown(ev) {
start = undefined;
matrix = view.buildMainMatrix(function(cell) {
clear();
if (cell) {
end = cell.col;
if (start === undefined) {
unselect();
start = end;
}
range = [start, end].sort(cmp);
view.renderDayOverlay(matrix, range[0], range[1]+1);
view.bindDayHandlers(view.overlays[0]);
selected = true;
}else{
selected = false;
}
});
$(document)
.mousemove(dayMousemove)
.mouseup(dayMouseup);
matrix.mouse(ev.pageX, ev.pageY);
ev.stopPropagation();
}
function dayMousemove(ev) {
matrix.mouse(ev.pageX, ev.pageY);
}
function dayMouseup(ev) {
$(document)
.unbind('mousemove', dayMousemove)
.unbind('mouseup', dayMouseup);
if (selected) {
view.trigger(
'select',
view,
addDays(cloneDate(view.visStart), range[0]),
addDays(cloneDate(view.visStart), range[1]+1),
true
);
}
}
// slot
function slotBind(elements) {
elements.mousedown(slotMousedown);
}
function slotMousedown(ev) {
day = undefined;
matrix = view.buildSlotMatrix(function(cell) {
clear();
if (cell) {
if (day === undefined) {
unselect();
day = cell.col;
start = cell.row;
}
end = cell.row;
range = [start, end].sort(cmp);
var helperOption = view.option('selectHelper'),
bodyContent = view.bodyContent;
if (helperOption) {
var rect = matrix.rect(range[0], day, range[1]+1, day+1, bodyContent);
rect.left += 2;
rect.width -= 5;
if ($.isFunction(helperOption)) {
helper = helperOption();
if (helper) {
helper.css(rect);
}
}else{
helper = $(view.segHtml(
{
title: '',
start: view.slotTime(day, range[0]),
end: view.slotTime(day, range[1]+1),
className: [],
editable: false
},
rect,
'fc-event fc-event-vert fc-corner-top fc-corner-bottom '
));
if (!$.browser.msie) {
// IE makes the event completely clear!!?
helper.css('opacity', view.option('dragOpacity'));
}
}
if (helper) {
// TODO: change cursor
view.bindSlotHandlers(helper);
bodyContent.append(helper);
setOuterWidth(helper, rect.width, true);
setOuterHeight(helper, rect.height, true);
}
}else{
view.renderOverlay(
matrix.rect(range[0], day, range[1]+1, day+1, bodyContent),
bodyContent
);
view.bindSlotHandlers(view.overlays[0]);
}
selected = true;
}else{
selected = false;
}
});
matrix.mouse(ev.pageX, ev.pageY);
$(document)
.mousemove(slotMousemove)
.mouseup(slotMouseup);
ev.stopPropagation();
}
function slotMousemove(ev) {
matrix.mouse(ev.pageX, ev.pageY);
}
function slotMouseup(ev) {
$(document)
.unbind('mousemove', slotMousemove)
.unbind('mouseup', slotMouseup);
if (selected) {
view.trigger('select',
view,
view.slotTime(day, range[0]),
view.slotTime(day, range[1]+1),
false
);
}
}
// common
function unselect() {
if (selected) {
clear();
view.trigger('unselect', view);
selected = false;
}
}
this.unselect = unselect;
view.viewUpdate = unselect;
function clear() {
if (helper) {
helper.remove();
helper = null;
}
view.clearOverlays();
}
if (view.option('unselectable')) {
$(document).mousedown(function() {
unselect();
});
}
}
})(jQuery);

103
src/selection_util.js Normal file
View file

@ -0,0 +1,103 @@
function SelectionManager(view, displayFunc, clearFunc) {
var t = this;
var selected = false;
var initialElement;
var initialRange;
var start;
var end;
var allDay;
t.dragStart = function(ev) {
unselect();
start = end = undefined;
initialRange = undefined;
initialElement = ev.currentTarget;
};
t.drag = function(currentStart, currentEnd, currentAllDay) {
if (currentStart) {
var range = [currentStart, currentEnd];
if (!initialRange) {
initialRange = range;
}
var dates = initialRange.concat(range).sort(cmp);
start = dates[0];
end = dates[3];
allDay = currentAllDay;
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);
}
selected = true;
view.trigger('select', view, start, end, allDay);
}
};
t.select = function(newStart, newEnd, newAllDay) {
unselect();
start = newStart;
end = newEnd;
allDay = newAllDay;
selected = true;
displayFunc(start, end, allDay);
};
function 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 documentAutoUnselect(view, unselectFunc) {
if (view.option('unselectAuto')) {
$(document).mousedown(function(ev) {
var ignore = view.option('unselectCancel');
if (ignore) {
if ($(ev.target).parents(ignore).length) { // could be optimized to stop after first match
return;
}
}
unselectFunc();
});
}
}

View file

@ -397,7 +397,9 @@ function HoverMatrix(changeCallback) {
lefts.push(prevColE.offset().left);
};
t.mouse = function(x, y) {
t.mouse = function(ev) {
var x = ev.pageX;
var y = ev.pageY;
if (origRow === undefined) {
tops.push(tops[tops.length-1] + prevRowE.outerHeight());
lefts.push(lefts[lefts.length-1] + prevColE.outerWidth());
@ -425,6 +427,8 @@ function HoverMatrix(changeCallback) {
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
@ -525,7 +529,3 @@ function cmp(a, b) {
fc.cmp = cmp;
fc.publicMethods = {};

View file

@ -180,6 +180,7 @@ var viewMethods = {
.css(rect)
.appendTo(parent);
this.overlays.push(e);
return e;
},
clearOverlays: function() {

View file

@ -83,6 +83,7 @@ if (_build) {
includeJS('../src/grid.js');
includeJS('../src/agenda.js');
includeJS('../src/view.js');
includeJS('../src/selection_util.js');
includeJS('../src/util.js');
includeJS('../src/gcal.js');
}

View file

@ -19,18 +19,6 @@
right: 'month,agendaWeek,basicWeek,agendaDay,basicDay'
},
editable: true,
//defaultView: 'agendaWeek',
selectable: true,
selectHelper: true,
select: function(start, end, allDay) {
console.log('select');
console.log(start);
console.log(end);
console.log(allDay);
},
unselect: function() {
console.log('unselect');
},
events: [
{
title: 'All Day Event',

114
tests/selectable.html Normal file
View file

@ -0,0 +1,114 @@
<!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({
header: {
left: 'prev,next today',
center: 'title',
right: 'month,agendaWeek,basicWeek,agendaDay,basicDay'
},
//defaultView: 'agendaWeek',
//isRTL: true,
selectable: true,
selectHelper: true,
//unselectAuto: false,
//unselectCancel: '.fc',
select: function(start, end, allDay) {
console.log(
'---- selection ----\n' +
'start: ' + start + '\n' +
'end: ' + end + '\n' + // exclusive!!
'allDay: ' + allDay
);
},
unselect: function() {
console.log('unselect');
},
dayClick: function(date, allDay) {
console.log(date, allDay);
console.log(this);
},
editable: true,
events: [
{
title: 'All Day Event',
start: new Date(y, m, 1)
},
{
title: 'Long Event',
start: new Date(y, m, d-5, 5, 0),
end: new Date(y, m, d-2, 2, 0),
allDay: false
},
{
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/'
}
]
});
});
</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;
margin: 0 auto;
}
</style>
</head>
<body>
<div id='calendar'></div>
</body>
</html>