4 * http://arshaw.com/fullcalendar/
6 * Use fullcalendar.css for basic styling.
7 * For event drag & drop, requires jQuery UI draggable.
8 * For event resizing, requires jQuery UI resizable.
10 * Copyright (c) 2011 Adam Shaw
11 * Dual licensed under the MIT and GPL licenses, located in
12 * MIT-LICENSE.txt and GPL-LICENSE.txt respectively.
14 * Date: Sat Mar 19 18:59:37 2011 -0700
18 (function($, undefined) {
29 right
: 'today prev,next'
35 //disableDragging: false,
36 //disableResizing: false,
49 week
: "MMM d[ yyyy]{ '—'[ MMM] d yyyy}",
50 day
: 'dddd, MMM d, yyyy'
57 timeFormat
: { // for event elements
58 '': 'h(:mm)t' // default
64 monthNames
: ['January','February','March','April','May','June','July','August','September','October','November','December'],
65 monthNamesShort
: ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'],
66 dayNames
: ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'],
67 dayNamesShort
: ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'],
69 prev
: ' ◄ ',
70 next
: ' ► ',
71 prevYear
: ' << ',
72 nextYear
: ' >> ',
82 prev
: 'circle-triangle-w',
83 next
: 'circle-triangle-e'
93 // right-to-left defaults
96 left
: 'next,prev today',
101 prev
: ' ► ',
102 next
: ' ◄ ',
103 prevYear
: ' >> ',
104 nextYear
: ' << '
107 prev
: 'circle-triangle-e',
108 next
: 'circle-triangle-w'
114 var fc
= $.fullCalendar
= { version
: "1.5" };
115 var fcViews
= fc
.views
= {};
118 $.fn
.fullCalendar = function(options
) {
122 if (typeof options
== 'string') {
123 var args
= Array
.prototype.slice
.call(arguments
, 1);
125 this.each(function() {
126 var calendar
= $.data(this, 'fullCalendar');
127 if (calendar
&& $.isFunction(calendar
[options
])) {
128 var r
= calendar
[options
].apply(calendar
, args
);
129 if (res
=== undefined) {
132 if (options
== 'destroy') {
133 $.removeData(this, 'fullCalendar');
137 if (res
!== undefined) {
144 // would like to have this logic in EventManager, but needs to happen before options are recursively extended
145 var eventSources
= options
.eventSources
|| [];
146 delete options
.eventSources
;
147 if (options
.events
) {
148 eventSources
.push(options
.events
);
149 delete options
.events
;
153 options
= $.extend(true, {},
155 (options
.isRTL
|| options
.isRTL
===undefined && defaults
.isRTL
) ? rtlDefaults
: {},
160 this.each(function(i
, _element
) {
161 var element
= $(_element
);
162 var calendar
= new Calendar(element
, options
, eventSources
);
163 element
.data('fullCalendar', calendar
); // TODO: look into memory leak implications
173 // function for adding/overriding defaults
174 function setDefaults(d
) {
175 $.extend(true, defaults
, d
);
181 function Calendar(element
, options
, eventSources
) {
189 t
.refetchEvents
= refetchEvents
;
190 t
.reportEvents
= reportEvents
;
191 t
.reportEventChange
= reportEventChange
;
192 t
.rerenderEvents
= rerenderEvents
;
193 t
.changeView
= changeView
;
195 t
.unselect
= unselect
;
198 t
.prevYear
= prevYear
;
199 t
.nextYear
= nextYear
;
201 t
.gotoDate
= gotoDate
;
202 t
.incrementDate
= incrementDate
;
203 t
.formatDate = function(format
, date
) { return formatDate(format
, date
, options
) };
204 t
.formatDates = function(format
, date1
, date2
) { return formatDates(format
, date1
, date2
, options
) };
212 EventManager
.call(t
, options
, eventSources
);
213 var isFetchNeeded
= t
.isFetchNeeded
;
214 var fetchEvents
= t
.fetchEvents
;
218 var _element
= element
[0];
222 var tm
; // for making theme classes
224 var viewInstances
= {};
225 var elementOuterWidth
;
226 var suggestedViewHeight
;
227 var absoluteViewElement
;
229 var ignoreWindowResize
= 0;
230 var date
= new Date();
237 -----------------------------------------------------------------------------*/
240 setYMD(date
, options
.year
, options
.month
, options
.date
);
243 function render(inc
) {
255 function initialRender() {
256 tm
= options
.theme
? 'ui' : 'fc';
257 element
.addClass('fc');
259 element
.addClass('fc-rtl');
262 element
.addClass('ui-widget');
264 content
= $("<div class='fc-content' style='position:relative'/>")
266 header
= new Header(t
, options
);
267 headerElement
= header
.render();
269 element
.prepend(headerElement
);
271 changeView(options
.defaultView
);
272 $(window
).resize(windowResize
);
273 // needed for IE in a 0x0 iframe, b/c when it is resized, never triggers a windowResize
274 if (!bodyVisible()) {
280 // called when we know the calendar couldn't be rendered when it was initialized,
281 // but we think it's ready now
282 function lateRender() {
283 setTimeout(function() { // IE7 needs this so dimensions are calculated correctly
284 if (!currentView
.start
&& bodyVisible()) { // !currentView.start makes sure this never happens more than once
292 $(window
).unbind('resize', windowResize
);
295 element
.removeClass('fc fc-rtl ui-widget');
300 function elementVisible() {
301 return _element
.offsetWidth
!== 0;
305 function bodyVisible() {
306 return $('body')[0].offsetWidth
!== 0;
312 -----------------------------------------------------------------------------*/
314 // TODO: improve view switching (still weird transition in IE, and FF has whiteout problem)
316 function changeView(newViewName
) {
317 if (!currentView
|| newViewName
!= currentView
.name
) {
318 ignoreWindowResize
++; // because setMinHeight might change the height before render (and subsequently setSize) is reached
322 var oldView
= currentView
;
326 (oldView
.beforeHide
|| noop
)(); // called before changing min-height. if called after, scroll state is reset (in Opera)
327 setMinHeight(content
, content
.height());
328 oldView
.element
.hide();
330 setMinHeight(content
, 1); // needs to be 1 (not 0) for IE7, or else view dimensions miscalculated
332 content
.css('overflow', 'hidden');
334 currentView
= viewInstances
[newViewName
];
336 currentView
.element
.show();
338 currentView
= viewInstances
[newViewName
] = new fcViews
[newViewName
](
339 newViewElement
= absoluteViewElement
=
340 $("<div class='fc-view fc-view-" + newViewName
+ "' style='position:absolute'/>")
342 t
// the calendar object
347 header
.deactivateButton(oldView
.name
);
349 header
.activateButton(newViewName
);
351 renderView(); // after height has been set, will make absoluteViewElement's position=relative, then set to null
353 content
.css('overflow', '');
355 setMinHeight(content
, 1);
358 if (!newViewElement
) {
359 (currentView
.afterShow
|| noop
)(); // called after setting min-height/overflow, so in final scroll state (for Opera)
362 ignoreWindowResize
--;
368 function renderView(inc
) {
369 if (elementVisible()) {
370 ignoreWindowResize
++; // because renderEvents might temporarily change the height before setSize is reached
374 if (suggestedViewHeight
=== undefined) {
378 var forceEventRender
= false;
379 if (!currentView
.start
|| inc
|| date
< currentView
.start
|| date
>= currentView
.end
) {
380 // view must render an entire new date range (and refetch/render events)
381 currentView
.render(date
, inc
|| 0); // responsible for clearing events
383 forceEventRender
= true;
385 else if (currentView
.sizeDirty
) {
386 // view must resize (and rerender events)
387 currentView
.clearEvents();
389 forceEventRender
= true;
391 else if (currentView
.eventsDirty
) {
392 currentView
.clearEvents();
393 forceEventRender
= true;
395 currentView
.sizeDirty
= false;
396 currentView
.eventsDirty
= false;
397 updateEvents(forceEventRender
);
399 elementOuterWidth
= element
.outerWidth();
401 header
.updateTitle(currentView
.title
);
402 var today
= new Date();
403 if (today
>= currentView
.start
&& today
< currentView
.end
) {
404 header
.disableButton('today');
406 header
.enableButton('today');
409 ignoreWindowResize
--;
410 currentView
.trigger('viewDisplay', _element
);
417 -----------------------------------------------------------------------------*/
420 function updateSize() {
422 if (elementVisible()) {
426 currentView
.clearEvents();
427 currentView
.renderEvents(events
);
428 currentView
.sizeDirty
= false;
433 function markSizesDirty() {
434 $.each(viewInstances
, function(i
, inst
) {
435 inst
.sizeDirty
= true;
440 function calcSize() {
441 if (options
.contentHeight
) {
442 suggestedViewHeight
= options
.contentHeight
;
444 else if (options
.height
) {
445 suggestedViewHeight
= options
.height
- (headerElement
? headerElement
.height() : 0) - vsides(content
);
448 suggestedViewHeight
= Math
.round(content
.width() / Math
.max(options
.aspectRatio
, .5));
453 function setSize(dateChanged
) { // todo: dateChanged?
454 ignoreWindowResize
++;
455 currentView
.setHeight(suggestedViewHeight
, dateChanged
);
456 if (absoluteViewElement
) {
457 absoluteViewElement
.css('position', 'relative');
458 absoluteViewElement
= null;
460 currentView
.setWidth(content
.width(), dateChanged
);
461 ignoreWindowResize
--;
465 function windowResize() {
466 if (!ignoreWindowResize
) {
467 if (currentView
.start
) { // view has already been rendered
468 var uid
= ++resizeUID
;
469 setTimeout(function() { // add a delay
470 if (uid
== resizeUID
&& !ignoreWindowResize
&& elementVisible()) {
471 if (elementOuterWidth
!= (elementOuterWidth
= element
.outerWidth())) {
472 ignoreWindowResize
++; // in case the windowResize callback changes the height
474 currentView
.trigger('windowResize', _element
);
475 ignoreWindowResize
--;
480 // calendar must have been initialized in a 0x0 iframe that has just been resized
488 /* Event Fetching/Rendering
489 -----------------------------------------------------------------------------*/
492 // fetches events if necessary, rerenders events if necessary (or if forced)
493 function updateEvents(forceRender
) {
494 if (!options
.lazyFetching
|| isFetchNeeded(currentView
.visStart
, currentView
.visEnd
)) {
497 else if (forceRender
) {
503 function refetchEvents() {
504 fetchEvents(currentView
.visStart
, currentView
.visEnd
); // will call reportEvents
508 // called when event data arrives
509 function reportEvents(_events
) {
515 // called when a single event's data has been changed
516 function reportEventChange(eventID
) {
517 rerenderEvents(eventID
);
521 // attempts to rerenderEvents
522 function rerenderEvents(modifiedEventID
) {
524 if (elementVisible()) {
525 currentView
.clearEvents();
526 currentView
.renderEvents(events
, modifiedEventID
);
527 currentView
.eventsDirty
= false;
532 function markEventsDirty() {
533 $.each(viewInstances
, function(i
, inst
) {
534 inst
.eventsDirty
= true;
541 -----------------------------------------------------------------------------*/
544 function select(start
, end
, allDay
) {
545 currentView
.select(start
, end
, allDay
===undefined ? true : allDay
);
549 function unselect() { // safe to be called before renderView
551 currentView
.unselect();
558 -----------------------------------------------------------------------------*/
571 function prevYear() {
577 function nextYear() {
589 function gotoDate(year
, month
, dateOfMonth
) {
590 if (year
instanceof Date
) {
591 date
= cloneDate(year
); // provided 1 argument, a Date
593 setYMD(date
, year
, month
, dateOfMonth
);
599 function incrementDate(years
, months
, days
) {
600 if (years
!== undefined) {
601 addYears(date
, years
);
603 if (months
!== undefined) {
604 addMonths(date
, months
);
606 if (days
!== undefined) {
614 return cloneDate(date
);
620 -----------------------------------------------------------------------------*/
628 function option(name
, value
) {
629 if (value
=== undefined) {
630 return options
[name
];
632 if (name
== 'height' || name
== 'contentHeight' || name
== 'aspectRatio') {
633 options
[name
] = value
;
639 function trigger(name
, thisObj
) {
641 return options
[name
].apply(
643 Array
.prototype.slice
.call(arguments
, 2)
651 ------------------------------------------------------------------------*/
653 if (options
.droppable
) {
655 .bind('dragstart', function(ev
, ui
) {
658 if (!e
.parents('.fc').length
) { // not already inside a calendar
659 var accept
= options
.dropAccept
;
660 if ($.isFunction(accept
) ? accept
.call(_e
, e
) : e
.is(accept
)) {
662 currentView
.dragStart(_dragElement
, ev
, ui
);
666 .bind('dragstop', function(ev
, ui
) {
668 currentView
.dragStop(_dragElement
, ev
, ui
);
677 function Header(calendar
, options
) {
684 t
.updateTitle
= updateTitle
;
685 t
.activateButton
= activateButton
;
686 t
.deactivateButton
= deactivateButton
;
687 t
.disableButton
= disableButton
;
688 t
.enableButton
= enableButton
;
698 tm
= options
.theme
? 'ui' : 'fc';
699 var sections
= options
.header
;
701 element
= $("<table class='fc-header' style='width:100%'/>")
704 .append(renderSection('left'))
705 .append(renderSection('center'))
706 .append(renderSection('right'))
718 function renderSection(position
) {
719 var e
= $("<td class='fc-header-" + position
+ "'/>");
720 var buttonStr
= options
.header
[position
];
722 $.each(buttonStr
.split(' '), function(i
) {
724 e
.append("<span class='fc-header-space'/>");
727 $.each(this.split(','), function(j
, buttonName
) {
728 if (buttonName
== 'title') {
729 e
.append("<span class='fc-header-title'><h2> </h2></span>");
731 prevButton
.addClass(tm
+ '-corner-right');
736 if (calendar
[buttonName
]) {
737 buttonClick
= calendar
[buttonName
]; // calendar method
739 else if (fcViews
[buttonName
]) {
740 buttonClick = function() {
741 button
.removeClass(tm
+ '-state-hover'); // forget why
742 calendar
.changeView(buttonName
);
746 var icon
= options
.theme
? smartProperty(options
.buttonIcons
, buttonName
) : null;
747 var text
= smartProperty(options
.buttonText
, buttonName
);
749 "<span class='fc-button fc-button-" + buttonName
+ " " + tm
+ "-state-default'>" +
750 "<span class='fc-button-inner'>" +
751 "<span class='fc-button-content'>" +
753 "<span class='fc-icon-wrap'>" +
754 "<span class='ui-icon ui-icon-" + icon
+ "'/>" +
759 "<span class='fc-button-effect'><span></span></span>" +
766 if (!button
.hasClass(tm
+ '-state-disabled')) {
770 .mousedown(function() {
772 .not('.' + tm
+ '-state-active')
773 .not('.' + tm
+ '-state-disabled')
774 .addClass(tm
+ '-state-down');
776 .mouseup(function() {
777 button
.removeClass(tm
+ '-state-down');
782 .not('.' + tm
+ '-state-active')
783 .not('.' + tm
+ '-state-disabled')
784 .addClass(tm
+ '-state-hover');
788 .removeClass(tm
+ '-state-hover')
789 .removeClass(tm
+ '-state-down');
794 button
.addClass(tm
+ '-corner-left');
802 prevButton
.addClass(tm
+ '-corner-right');
810 function updateTitle(html
) {
816 function activateButton(buttonName
) {
817 element
.find('span.fc-button-' + buttonName
)
818 .addClass(tm
+ '-state-active');
822 function deactivateButton(buttonName
) {
823 element
.find('span.fc-button-' + buttonName
)
824 .removeClass(tm
+ '-state-active');
828 function disableButton(buttonName
) {
829 element
.find('span.fc-button-' + buttonName
)
830 .addClass(tm
+ '-state-disabled');
834 function enableButton(buttonName
) {
835 element
.find('span.fc-button-' + buttonName
)
836 .removeClass(tm
+ '-state-disabled');
842 fc
.sourceNormalizers
= [];
843 fc
.sourceFetchers
= [];
853 function EventManager(options
, _sources
) {
858 t
.isFetchNeeded
= isFetchNeeded
;
859 t
.fetchEvents
= fetchEvents
;
860 t
.addEventSource
= addEventSource
;
861 t
.removeEventSource
= removeEventSource
;
862 t
.updateEvent
= updateEvent
;
863 t
.renderEvent
= renderEvent
;
864 t
.removeEvents
= removeEvents
;
865 t
.clientEvents
= clientEvents
;
866 t
.normalizeEvent
= normalizeEvent
;
870 var trigger
= t
.trigger
;
871 var getView
= t
.getView
;
872 var reportEvents
= t
.reportEvents
;
876 var stickySource
= { events
: [] };
877 var sources
= [ stickySource
];
878 var rangeStart
, rangeEnd
;
879 var currentFetchID
= 0;
880 var pendingSourceCnt
= 0;
881 var loadingLevel
= 0;
885 for (var i
=0; i
<_sources
.length
; i
++) {
886 _addEventSource(_sources
[i
]);
892 -----------------------------------------------------------------------------*/
895 function isFetchNeeded(start
, end
) {
896 return !rangeStart
|| start
< rangeStart
|| end
> rangeEnd
;
900 function fetchEvents(start
, end
) {
904 var fetchID
= ++currentFetchID
;
905 var len
= sources
.length
;
906 pendingSourceCnt
= len
;
907 for (var i
=0; i
<len
; i
++) {
908 fetchEventSource(sources
[i
], fetchID
);
913 function fetchEventSource(source
, fetchID
) {
914 _fetchEventSource(source
, function(events
) {
915 if (fetchID
== currentFetchID
) {
917 for (var i
=0; i
<events
.length
; i
++) {
918 events
[i
].source
= source
;
919 normalizeEvent(events
[i
]);
921 cache
= cache
.concat(events
);
924 if (!pendingSourceCnt
) {
932 function _fetchEventSource(source
, callback
) {
934 var fetchers
= fc
.sourceFetchers
;
936 for (i
=0; i
<fetchers
.length
; i
++) {
937 res
= fetchers
[i
](source
, rangeStart
, rangeEnd
, callback
);
939 // the fetcher is in charge. made its own async request
942 else if (typeof res
== 'object') {
943 // the fetcher returned a new source. process it
944 _fetchEventSource(res
, callback
);
948 var events
= source
.events
;
950 if ($.isFunction(events
)) {
952 events(cloneDate(rangeStart
), cloneDate(rangeEnd
), function(events
) {
957 else if ($.isArray(events
)) {
964 var url
= source
.url
;
966 var success
= source
.success
;
967 var error
= source
.error
;
968 var complete
= source
.complete
;
969 var data
= $.extend({}, source
.data
|| {});
970 var startParam
= firstDefined(source
.startParam
, options
.startParam
);
971 var endParam
= firstDefined(source
.endParam
, options
.endParam
);
973 data
[startParam
] = Math
.round(+rangeStart
/ 1000);
976 data
[endParam
] = Math
.round(+rangeEnd
/ 1000);
979 $.ajax($.extend({}, ajaxDefaults
, source
, {
981 success: function(events
) {
982 events
= events
|| [];
983 var res
= applyAll(success
, this, arguments
);
984 if ($.isArray(res
)) {
990 applyAll(error
, this, arguments
);
993 complete: function() {
994 applyAll(complete
, this, arguments
);
1007 -----------------------------------------------------------------------------*/
1010 function addEventSource(source
) {
1011 source
= _addEventSource(source
);
1014 fetchEventSource(source
, currentFetchID
); // will eventually call reportEvents
1019 function _addEventSource(source
) {
1020 if ($.isFunction(source
) || $.isArray(source
)) {
1021 source
= { events
: source
};
1023 else if (typeof source
== 'string') {
1024 source
= { url
: source
};
1026 if (typeof source
== 'object') {
1027 normalizeSource(source
);
1028 sources
.push(source
);
1034 function removeEventSource(source
) {
1035 sources
= $.grep(sources
, function(src
) {
1036 return !isSourcesEqual(src
, source
);
1038 // remove all client events from that source
1039 cache
= $.grep(cache
, function(e
) {
1040 return !isSourcesEqual(e
.source
, source
);
1042 reportEvents(cache
);
1048 -----------------------------------------------------------------------------*/
1051 function updateEvent(event
) { // update an existing event
1052 var i
, len
= cache
.length
, e
,
1053 defaultEventEnd
= getView().defaultEventEnd
, // getView???
1054 startDelta
= event
.start
- event
._start
,
1055 endDelta
= event
.end
?
1056 (event
.end
- (event
._end
|| defaultEventEnd(event
))) // event._end would be null if event.end
1057 : 0; // was null and event was just resized
1058 for (i
=0; i
<len
; i
++) {
1060 if (e
._id
== event
._id
&& e
!= event
) {
1061 e
.start
= new Date(+e
.start
+ startDelta
);
1064 e
.end
= new Date(+e
.end
+ endDelta
);
1066 e
.end
= new Date(+defaultEventEnd(e
) + endDelta
);
1071 e
.title
= event
.title
;
1073 e
.allDay
= event
.allDay
;
1074 e
.className
= event
.className
;
1075 e
.editable
= event
.editable
;
1076 e
.color
= event
.color
;
1077 e
.backgroudColor
= event
.backgroudColor
;
1078 e
.borderColor
= event
.borderColor
;
1079 e
.textColor
= event
.textColor
;
1083 normalizeEvent(event
);
1084 reportEvents(cache
);
1088 function renderEvent(event
, stick
) {
1089 normalizeEvent(event
);
1090 if (!event
.source
) {
1092 stickySource
.events
.push(event
);
1093 event
.source
= stickySource
;
1097 reportEvents(cache
);
1101 function removeEvents(filter
) {
1102 if (!filter
) { // remove all
1104 // clear all array sources
1105 for (var i
=0; i
<sources
.length
; i
++) {
1106 if ($.isArray(sources
[i
].events
)) {
1107 sources
[i
].events
= [];
1111 if (!$.isFunction(filter
)) { // an event ID
1112 var id
= filter
+ '';
1113 filter = function(e
) {
1117 cache
= $.grep(cache
, filter
, true);
1118 // remove events from array sources
1119 for (var i
=0; i
<sources
.length
; i
++) {
1120 if ($.isArray(sources
[i
].events
)) {
1121 sources
[i
].events
= $.grep(sources
[i
].events
, filter
, true);
1125 reportEvents(cache
);
1129 function clientEvents(filter
) {
1130 if ($.isFunction(filter
)) {
1131 return $.grep(cache
, filter
);
1133 else if (filter
) { // an event ID
1135 return $.grep(cache
, function(e
) {
1136 return e
._id
== filter
;
1139 return cache
; // else, return all
1145 -----------------------------------------------------------------------------*/
1148 function pushLoading() {
1149 if (!loadingLevel
++) {
1150 trigger('loading', null, true);
1155 function popLoading() {
1156 if (!--loadingLevel
) {
1157 trigger('loading', null, false);
1163 /* Event Normalization
1164 -----------------------------------------------------------------------------*/
1167 function normalizeEvent(event
) {
1168 var source
= event
.source
|| {};
1169 var ignoreTimezone
= firstDefined(source
.ignoreTimezone
, options
.ignoreTimezone
);
1170 event
._id
= event
._id
|| (event
.id
=== undefined ? '_fc' + eventGUID
++ : event
.id
+ '');
1173 event
.start
= event
.date
;
1177 event
._start
= cloneDate(event
.start
= parseDate(event
.start
, ignoreTimezone
));
1178 event
.end
= parseDate(event
.end
, ignoreTimezone
);
1179 if (event
.end
&& event
.end
<= event
.start
) {
1182 event
._end
= event
.end
? cloneDate(event
.end
) : null;
1183 if (event
.allDay
=== undefined) {
1184 event
.allDay
= firstDefined(source
.allDayDefault
, options
.allDayDefault
);
1186 if (event
.className
) {
1187 if (typeof event
.className
== 'string') {
1188 event
.className
= event
.className
.split(/\s+/);
1191 event
.className
= [];
1193 // TODO: if there is no start date, return false to indicate an invalid event
1199 ------------------------------------------------------------------------------*/
1202 function normalizeSource(source
) {
1203 if (source
.className
) {
1204 // TODO: repeat code, same code for event classNames
1205 if (typeof source
.className
== 'string') {
1206 source
.className
= source
.className
.split(/\s+/);
1209 source
.className
= [];
1211 var normalizers
= fc
.sourceNormalizers
;
1212 for (var i
=0; i
<normalizers
.length
; i
++) {
1213 normalizers
[i
](source
);
1218 function isSourcesEqual(source1
, source2
) {
1219 return source1
&& source2
&& getSourcePrimitive(source1
) == getSourcePrimitive(source2
);
1223 function getSourcePrimitive(source
) {
1224 return ((typeof source
== 'object') ? (source
.events
|| source
.url
) : '') || source
;
1231 fc
.addDays
= addDays
;
1232 fc
.cloneDate
= cloneDate
;
1233 fc
.parseDate
= parseDate
;
1234 fc
.parseISO8601
= parseISO8601
;
1235 fc
.parseTime
= parseTime
;
1236 fc
.formatDate
= formatDate
;
1237 fc
.formatDates
= formatDates
;
1242 -----------------------------------------------------------------------------*/
1244 var dayIDs
= ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'],
1250 function addYears(d
, n
, keepTime
) {
1251 d
.setFullYear(d
.getFullYear() + n
);
1259 function addMonths(d
, n
, keepTime
) { // prevents day overflow/underflow
1260 if (+d
) { // prevent infinite looping on invalid dates
1261 var m
= d
.getMonth() + n
,
1262 check
= cloneDate(d
);
1269 while (d
.getMonth() != check
.getMonth()) {
1270 d
.setDate(d
.getDate() + (d
< check
? 1 : -1));
1277 function addDays(d
, n
, keepTime
) { // deals with daylight savings
1279 var dd
= d
.getDate() + n
,
1280 check
= cloneDate(d
);
1281 check
.setHours(9); // set to middle of day
1293 function fixDate(d
, check
) { // force d to be on check's YMD, for daylight savings purposes
1294 if (+d
) { // prevent infinite looping on invalid dates
1295 while (d
.getDate() != check
.getDate()) {
1296 d
.setTime(+d
+ (d
< check
? 1 : -1) * HOUR_MS
);
1302 function addMinutes(d
, n
) {
1303 d
.setMinutes(d
.getMinutes() + n
);
1308 function clearTime(d
) {
1312 d
.setMilliseconds(0);
1317 function cloneDate(d
, dontKeepTime
) {
1319 return clearTime(new Date(+d
));
1321 return new Date(+d
);
1325 function zeroDate() { // returns a Date with time 00:00:00 and dateOfMonth=1
1328 d
= new Date(1970, i
++, 1);
1329 } while (d
.getHours()); // != 0
1334 function skipWeekend(date
, inc
, excl
) {
1336 while (!date
.getDay() || (excl
&& date
.getDay()==1 || !excl
&& date
.getDay()==6)) {
1343 function dayDiff(d1
, d2
) { // d1 - d2
1344 return Math
.round((cloneDate(d1
, true) - cloneDate(d2
, true)) / DAY_MS
);
1348 function setYMD(date
, y
, m
, d
) {
1349 if (y
!== undefined && y
!= date
.getFullYear()) {
1352 date
.setFullYear(y
);
1354 if (m
!== undefined && m
!= date
.getMonth()) {
1358 if (d
!== undefined) {
1366 -----------------------------------------------------------------------------*/
1369 function parseDate(s
, ignoreTimezone
) { // ignoreTimezone defaults to true
1370 if (typeof s
== 'object') { // already a Date object
1373 if (typeof s
== 'number') { // a UNIX timestamp
1374 return new Date(s
* 1000);
1376 if (typeof s
== 'string') {
1377 if (s
.match(/^\d+$/)) { // a UNIX timestamp
1378 return new Date(parseInt(s
, 10) * 1000);
1380 if (ignoreTimezone
=== undefined) {
1381 ignoreTimezone
= true;
1383 return parseISO8601(s
, ignoreTimezone
) || (s
? new Date(s
) : null);
1385 // TODO: never return invalid dates (like from new Date(<string>)), return null instead
1390 function parseISO8601(s
, ignoreTimezone
) { // ignoreTimezone defaults to false
1391 // derived from http://delete.me.uk/2005/03/iso8601.html
1392 // TODO: for a know glitch/feature, read tests/issue_206_parseDate_dst.html
1393 var m
= s
.match(/^([0-9]{4})(-([0-9]{2})(-([0-9]{2})([T ]([0-9]{2}):([0-9]{2})(:([0-9]{2})(\.([0-9]+))?)?(Z|(([-+])([0-9]{2}):([0-9]{2})))?)?)?)?$/);
1397 var date
= new Date(m
[1], 0, 1);
1398 if (ignoreTimezone
|| !m
[14]) {
1399 var check
= new Date(m
[1], 0, 1, 9, 0);
1401 date
.setMonth(m
[3] - 1);
1402 check
.setMonth(m
[3] - 1);
1406 check
.setDate(m
[5]);
1408 fixDate(date
, check
);
1410 date
.setHours(m
[7]);
1413 date
.setMinutes(m
[8]);
1416 date
.setSeconds(m
[10]);
1419 date
.setMilliseconds(Number("0." + m
[12]) * 1000);
1421 fixDate(date
, check
);
1423 date
.setUTCFullYear(
1425 m
[3] ? m
[3] - 1 : 0,
1432 m
[12] ? Number("0." + m
[12]) * 1000 : 0
1434 var offset
= Number(m
[16]) * 60 + Number(m
[17]);
1435 offset
*= m
[15] == '-' ? 1 : -1;
1436 date
= new Date(+date
+ (offset
* 60 * 1000));
1442 function parseTime(s
) { // returns minutes since start of day
1443 if (typeof s
== 'number') { // an hour
1446 if (typeof s
== 'object') { // a Date object
1447 return s
.getHours() * 60 + s
.getMinutes();
1449 var m
= s
.match(/(\d+)(?::(\d+))?\s*(\w+)?/);
1451 var h
= parseInt(m
[1], 10);
1454 if (m
[3].toLowerCase().charAt(0) == 'p') {
1458 return h
* 60 + (m
[2] ? parseInt(m
[2], 10) : 0);
1465 -----------------------------------------------------------------------------*/
1466 // TODO: use same function formatDate(date, [date2], format, [options])
1469 function formatDate(date
, format
, options
) {
1470 return formatDates(date
, null, format
, options
);
1474 function formatDates(date1
, date2
, format
, options
) {
1475 options
= options
|| defaults
;
1478 i
, len
= format
.length
, c
,
1481 for (i
=0; i
<len
; i
++) {
1482 c
= format
.charAt(i
);
1484 for (i2
=i
+1; i2
<len
; i2
++) {
1485 if (format
.charAt(i2
) == "'") {
1490 res
+= format
.substring(i
+1, i2
);
1498 else if (c
== '(') {
1499 for (i2
=i
+1; i2
<len
; i2
++) {
1500 if (format
.charAt(i2
) == ')') {
1501 var subres
= formatDate(date
, format
.substring(i
+1, i2
), options
);
1502 if (parseInt(subres
.replace(/\D/, ''), 10)) {
1510 else if (c
== '[') {
1511 for (i2
=i
+1; i2
<len
; i2
++) {
1512 if (format
.charAt(i2
) == ']') {
1513 var subformat
= format
.substring(i
+1, i2
);
1514 var subres
= formatDate(date
, subformat
, options
);
1515 if (subres
!= formatDate(otherDate
, subformat
, options
)) {
1523 else if (c
== '{') {
1527 else if (c
== '}') {
1532 for (i2
=len
; i2
>i
; i2
--) {
1533 if (formatter
= dateFormatters
[format
.substring(i
, i2
)]) {
1535 res
+= formatter(date
, options
);
1552 var dateFormatters
= {
1553 s : function(d
) { return d
.getSeconds() },
1554 ss : function(d
) { return zeroPad(d
.getSeconds()) },
1555 m : function(d
) { return d
.getMinutes() },
1556 mm : function(d
) { return zeroPad(d
.getMinutes()) },
1557 h : function(d
) { return d
.getHours() % 12 || 12 },
1558 hh : function(d
) { return zeroPad(d
.getHours() % 12 || 12) },
1559 H : function(d
) { return d
.getHours() },
1560 HH : function(d
) { return zeroPad(d
.getHours()) },
1561 d : function(d
) { return d
.getDate() },
1562 dd : function(d
) { return zeroPad(d
.getDate()) },
1563 ddd : function(d
,o
) { return o
.dayNamesShort
[d
.getDay()] },
1564 dddd: function(d
,o
) { return o
.dayNames
[d
.getDay()] },
1565 M : function(d
) { return d
.getMonth() + 1 },
1566 MM : function(d
) { return zeroPad(d
.getMonth() + 1) },
1567 MMM : function(d
,o
) { return o
.monthNamesShort
[d
.getMonth()] },
1568 MMMM: function(d
,o
) { return o
.monthNames
[d
.getMonth()] },
1569 yy : function(d
) { return (d
.getFullYear()+'').substring(2) },
1570 yyyy: function(d
) { return d
.getFullYear() },
1571 t : function(d
) { return d
.getHours() < 12 ? 'a' : 'p' },
1572 tt : function(d
) { return d
.getHours() < 12 ? 'am' : 'pm' },
1573 T : function(d
) { return d
.getHours() < 12 ? 'A' : 'P' },
1574 TT : function(d
) { return d
.getHours() < 12 ? 'AM' : 'PM' },
1575 u : function(d
) { return formatDate(d
, "yyyy-MM-dd'T'HH:mm:ss'Z'") },
1577 var date
= d
.getDate();
1578 if (date
> 10 && date
< 20) {
1581 return ['st', 'nd', 'rd'][date
%10-1] || 'th';
1587 fc
.applyAll
= applyAll
;
1591 -----------------------------------------------------------------------------*/
1594 function exclEndDay(event
) {
1596 return _exclEndDay(event
.end
, event
.allDay
);
1598 return addDays(cloneDate(event
.start
), 1);
1603 function _exclEndDay(end
, allDay
) {
1604 end
= cloneDate(end
);
1605 return allDay
|| end
.getHours() || end
.getMinutes() ? addDays(end
, 1) : clearTime(end
);
1609 function segCmp(a
, b
) {
1610 return (b
.msLength
- a
.msLength
) * 100 + (a
.event
.start
- b
.event
.start
);
1614 function segsCollide(seg1
, seg2
) {
1615 return seg1
.end
> seg2
.start
&& seg1
.start
< seg2
.end
;
1621 -----------------------------------------------------------------------------*/
1624 // event rendering utilities
1625 function sliceSegs(events
, visEventEnds
, start
, end
) {
1627 i
, len
=events
.length
, event
,
1628 eventStart
, eventEnd
,
1631 for (i
=0; i
<len
; i
++) {
1633 eventStart
= event
.start
;
1634 eventEnd
= visEventEnds
[i
];
1635 if (eventEnd
> start
&& eventStart
< end
) {
1636 if (eventStart
< start
) {
1637 segStart
= cloneDate(start
);
1640 segStart
= eventStart
;
1643 if (eventEnd
> end
) {
1644 segEnd
= cloneDate(end
);
1656 msLength
: segEnd
- segStart
1660 return segs
.sort(segCmp
);
1664 // event rendering calculation utilities
1665 function stackSegs(segs
) {
1667 i
, len
= segs
.length
, seg
,
1669 for (i
=0; i
<len
; i
++) {
1671 j
= 0; // the level index where seg should belong
1675 for (k
=0; k
<levels
[j
].length
; k
++) {
1676 if (segsCollide(levels
[j
][k
], seg
)) {
1689 levels
[j
].push(seg
);
1699 /* Event Element Binding
1700 -----------------------------------------------------------------------------*/
1703 function lazySegBind(container
, segs
, bindHandlers
) {
1704 container
.unbind('mouseover').mouseover(function(ev
) {
1705 var parent
=ev
.target
, e
,
1707 while (parent
!= this) {
1709 parent
= parent
.parentNode
;
1711 if ((i
= e
._fci
) !== undefined) {
1714 bindHandlers(seg
.event
, seg
.element
, seg
);
1715 $(ev
.target
).trigger(ev
);
1717 ev
.stopPropagation();
1723 /* Element Dimensions
1724 -----------------------------------------------------------------------------*/
1727 function setOuterWidth(element
, width
, includeMargins
) {
1728 for (var i
=0, e
; i
<element
.length
; i
++) {
1730 e
.width(Math
.max(0, width
- hsides(e
, includeMargins
)));
1735 function setOuterHeight(element
, height
, includeMargins
) {
1736 for (var i
=0, e
; i
<element
.length
; i
++) {
1738 e
.height(Math
.max(0, height
- vsides(e
, includeMargins
)));
1743 // TODO: curCSS has been deprecated (jQuery 1.4.3 - 10/16/2010)
1746 function hsides(element
, includeMargins
) {
1747 return hpadding(element
) + hborders(element
) + (includeMargins
? hmargins(element
) : 0);
1751 function hpadding(element
) {
1752 return (parseFloat($.curCSS(element
[0], 'paddingLeft', true)) || 0) +
1753 (parseFloat($.curCSS(element
[0], 'paddingRight', true)) || 0);
1757 function hmargins(element
) {
1758 return (parseFloat($.curCSS(element
[0], 'marginLeft', true)) || 0) +
1759 (parseFloat($.curCSS(element
[0], 'marginRight', true)) || 0);
1763 function hborders(element
) {
1764 return (parseFloat($.curCSS(element
[0], 'borderLeftWidth', true)) || 0) +
1765 (parseFloat($.curCSS(element
[0], 'borderRightWidth', true)) || 0);
1769 function vsides(element
, includeMargins
) {
1770 return vpadding(element
) + vborders(element
) + (includeMargins
? vmargins(element
) : 0);
1774 function vpadding(element
) {
1775 return (parseFloat($.curCSS(element
[0], 'paddingTop', true)) || 0) +
1776 (parseFloat($.curCSS(element
[0], 'paddingBottom', true)) || 0);
1780 function vmargins(element
) {
1781 return (parseFloat($.curCSS(element
[0], 'marginTop', true)) || 0) +
1782 (parseFloat($.curCSS(element
[0], 'marginBottom', true)) || 0);
1786 function vborders(element
) {
1787 return (parseFloat($.curCSS(element
[0], 'borderTopWidth', true)) || 0) +
1788 (parseFloat($.curCSS(element
[0], 'borderBottomWidth', true)) || 0);
1792 function setMinHeight(element
, height
) {
1793 height
= (typeof height
== 'number' ? height
+ 'px' : height
);
1794 element
.each(function(i
, _element
) {
1795 _element
.style
.cssText
+= ';min-height:' + height
+ ';_height:' + height
;
1796 // why can't we just use .css() ? i forget
1803 -----------------------------------------------------------------------------*/
1807 //TODO: isFunction, grep ?
1813 function cmp(a
, b
) {
1818 function arrayMax(a
) {
1819 return Math
.max
.apply(Math
, a
);
1823 function zeroPad(n
) {
1824 return (n
< 10 ? '0' : '') + n
;
1828 function smartProperty(obj
, name
) { // get a camel-cased/namespaced property of an object
1829 if (obj
[name
] !== undefined) {
1832 var parts
= name
.split(/(?=[A-Z])/),
1833 i
=parts
.length
-1, res
;
1835 res
= obj
[parts
[i
].toLowerCase()];
1836 if (res
!== undefined) {
1844 function htmlEscape(s
) {
1845 return s
.replace(/&/g
, '&')
1846 .replace(/</g
, '<')
1847 .replace(/>/g
, '>')
1848 .replace(/'/g, ''')
1849 .replace(/"/g, '"
;')
1850 .replace(/\n/g, '<br
/>');
1854 function cssKey(_element) {
1855 return _element.id + '/' + _element.className + '/' + _element.style.cssText.replace(/(^|;)\s*(top|left|width|height)\s*:[^;]*/ig, '');
1859 function disableTextSelection(element) {
1861 .attr('unselectable
', 'on
')
1862 .css('MozUserSelect
', 'none
')
1863 .bind('selectstart
.ui
', function() { return false; });
1868 function enableTextSelection(element) {
1870 .attr('unselectable
', 'off
')
1871 .css('MozUserSelect
', '')
1872 .unbind('selectstart
.ui
');
1877 function markFirstLast(e) {
1879 .removeClass('fc
-first fc
-last
')
1880 .filter(':first
-child
')
1881 .addClass('fc
-first
')
1883 .filter(':last
-child
')
1884 .addClass('fc
-last
');
1888 function setDayID(cell, date) {
1889 cell.each(function(i, _cell) {
1890 _cell.className = _cell.className.replace(/^fc-\w*/, 'fc
-' + dayIDs[date.getDay()]);
1891 // TODO: make a way that doesn't rely on order
of classes
1896 function getSkinCss(event
, opt
) {
1897 var source
= event
.source
|| {};
1898 var eventColor
= event
.color
;
1899 var sourceColor
= source
.color
;
1900 var optionColor
= opt('eventColor');
1901 var backgroundColor
=
1902 event
.backgroundColor
||
1904 source
.backgroundColor
||
1906 opt('eventBackgroundColor') ||
1909 event
.borderColor
||
1911 source
.borderColor
||
1913 opt('eventBorderColor') ||
1918 opt('eventTextColor');
1919 var statements
= [];
1920 if (backgroundColor
) {
1921 statements
.push('background-color:' + backgroundColor
);
1924 statements
.push('border-color:' + borderColor
);
1927 statements
.push('color:' + textColor
);
1929 return statements
.join(';');
1933 function applyAll(functions
, thisObj
, args
) {
1934 if ($.isFunction(functions
)) {
1935 functions
= [ functions
];
1940 for (i
=0; i
<functions
.length
; i
++) {
1941 ret
= functions
[i
].apply(thisObj
, args
) || ret
;
1948 function firstDefined() {
1949 for (var i
=0; i
<arguments
.length
; i
++) {
1950 if (arguments
[i
] !== undefined) {
1951 return arguments
[i
];
1958 fcViews
.month
= MonthView
;
1960 function MonthView(element
, calendar
) {
1969 BasicView
.call(t
, element
, calendar
, 'month');
1971 var renderBasic
= t
.renderBasic
;
1972 var formatDate
= calendar
.formatDate
;
1976 function render(date
, delta
) {
1978 addMonths(date
, delta
);
1981 var start
= cloneDate(date
, true);
1983 var end
= addMonths(cloneDate(start
), 1);
1984 var visStart
= cloneDate(start
);
1985 var visEnd
= cloneDate(end
);
1986 var firstDay
= opt('firstDay');
1987 var nwe
= opt('weekends') ? 0 : 1;
1989 skipWeekend(visStart
);
1990 skipWeekend(visEnd
, -1, true);
1992 addDays(visStart
, -((visStart
.getDay() - Math
.max(firstDay
, nwe
) + 7) % 7));
1993 addDays(visEnd
, (7 - visEnd
.getDay() + Math
.max(firstDay
, nwe
)) % 7);
1994 var rowCnt
= Math
.round((visEnd
- visStart
) / (DAY_MS
* 7));
1995 if (opt('weekMode') == 'fixed') {
1996 addDays(visEnd
, (6 - rowCnt
) * 7);
1999 t
.title
= formatDate(start
, opt('titleFormat'));
2002 t
.visStart
= visStart
;
2004 renderBasic(6, rowCnt
, nwe
? 5 : 7, true);
2010 fcViews
.basicWeek
= BasicWeekView
;
2012 function BasicWeekView(element
, calendar
) {
2021 BasicView
.call(t
, element
, calendar
, 'basicWeek');
2023 var renderBasic
= t
.renderBasic
;
2024 var formatDates
= calendar
.formatDates
;
2028 function render(date
, delta
) {
2030 addDays(date
, delta
* 7);
2032 var start
= addDays(cloneDate(date
), -((date
.getDay() - opt('firstDay') + 7) % 7));
2033 var end
= addDays(cloneDate(start
), 7);
2034 var visStart
= cloneDate(start
);
2035 var visEnd
= cloneDate(end
);
2036 var weekends
= opt('weekends');
2038 skipWeekend(visStart
);
2039 skipWeekend(visEnd
, -1, true);
2041 t
.title
= formatDates(
2043 addDays(cloneDate(visEnd
), -1),
2048 t
.visStart
= visStart
;
2050 renderBasic(1, 1, weekends
? 7 : 5, false);
2056 fcViews
.basicDay
= BasicDayView
;
2058 //TODO: when calendar's date starts out on a weekend, shouldn't happen
2061 function BasicDayView(element
, calendar
) {
2070 BasicView
.call(t
, element
, calendar
, 'basicDay');
2072 var renderBasic
= t
.renderBasic
;
2073 var formatDate
= calendar
.formatDate
;
2077 function render(date
, delta
) {
2079 addDays(date
, delta
);
2080 if (!opt('weekends')) {
2081 skipWeekend(date
, delta
< 0 ? -1 : 1);
2084 t
.title
= formatDate(date
, opt('titleFormat'));
2085 t
.start
= t
.visStart
= cloneDate(date
, true);
2086 t
.end
= t
.visEnd
= addDays(cloneDate(t
.start
), 1);
2087 renderBasic(1, 1, 1, false);
2098 function BasicView(element
, calendar
, viewName
) {
2103 t
.renderBasic
= renderBasic
;
2104 t
.setHeight
= setHeight
;
2105 t
.setWidth
= setWidth
;
2106 t
.renderDayOverlay
= renderDayOverlay
;
2107 t
.defaultSelectionEnd
= defaultSelectionEnd
;
2108 t
.renderSelection
= renderSelection
;
2109 t
.clearSelection
= clearSelection
;
2110 t
.reportDayClick
= reportDayClick
; // for selection (kinda hacky)
2111 t
.dragStart
= dragStart
;
2112 t
.dragStop
= dragStop
;
2113 t
.defaultEventEnd
= defaultEventEnd
;
2114 t
.getHoverListener = function() { return hoverListener
};
2115 t
.colContentLeft
= colContentLeft
;
2116 t
.colContentRight
= colContentRight
;
2117 t
.dayOfWeekCol
= dayOfWeekCol
;
2118 t
.dateCell
= dateCell
;
2119 t
.cellDate
= cellDate
;
2120 t
.cellIsAllDay = function() { return true };
2121 t
.allDayRow
= allDayRow
;
2122 t
.allDayBounds
= allDayBounds
;
2123 t
.getRowCnt = function() { return rowCnt
};
2124 t
.getColCnt = function() { return colCnt
};
2125 t
.getColWidth = function() { return colWidth
};
2126 t
.getDaySegmentContainer = function() { return daySegmentContainer
};
2130 View
.call(t
, element
, calendar
, viewName
);
2131 OverlayManager
.call(t
);
2132 SelectionManager
.call(t
);
2133 BasicEventRenderer
.call(t
);
2135 var trigger
= t
.trigger
;
2136 var clearEvents
= t
.clearEvents
;
2137 var renderOverlay
= t
.renderOverlay
;
2138 var clearOverlays
= t
.clearOverlays
;
2139 var daySelectionMousedown
= t
.daySelectionMousedown
;
2140 var formatDate
= calendar
.formatDate
;
2151 var bodyCellTopInners
;
2152 var daySegmentContainer
;
2161 var colContentPositions
;
2172 ------------------------------------------------------------*/
2175 disableTextSelection(element
.addClass('fc-grid'));
2178 function renderBasic(maxr
, r
, c
, showNumbers
) {
2182 var firstTime
= !body
;
2184 buildSkeleton(maxr
, showNumbers
);
2188 updateCells(firstTime
);
2193 function updateOptions() {
2202 firstDay
= opt('firstDay');
2203 nwe
= opt('weekends') ? 0 : 1;
2204 tm
= opt('theme') ? 'ui' : 'fc';
2205 colFormat
= opt('columnFormat');
2210 function buildSkeleton(maxRowCnt
, showNumbers
) {
2212 var headerClass
= tm
+ "-widget-header";
2213 var contentClass
= tm
+ "-widget-content";
2218 "<table class='fc-border-separate' style='width:100%' cellspacing='0'>" +
2221 for (i
=0; i
<colCnt
; i
++) {
2223 "<th class='fc- " + headerClass
+ "'/>"; // need fc- for setDayID
2229 for (i
=0; i
<maxRowCnt
; i
++) {
2231 "<tr class='fc-week" + i
+ "'>";
2232 for (j
=0; j
<colCnt
; j
++) {
2234 "<td class='fc- " + contentClass
+ " fc-day" + (i
*colCnt
+j
) + "'>" + // need fc- for setDayID
2237 "<div class='fc-day-number'/>" :
2240 "<div class='fc-day-content'>" +
2241 "<div style='position:relative'> </div>" +
2252 table
= $(s
).appendTo(element
);
2254 head
= table
.find('thead');
2255 headCells
= head
.find('th');
2256 body
= table
.find('tbody');
2257 bodyRows
= body
.find('tr');
2258 bodyCells
= body
.find('td');
2259 bodyFirstCells
= bodyCells
.filter(':first-child');
2260 bodyCellTopInners
= bodyRows
.eq(0).find('div.fc-day-content div');
2262 markFirstLast(head
.add(head
.find('tr'))); // marks first+last tr/th's
2263 markFirstLast(bodyRows
); // marks first+last td's
2264 bodyRows
.eq(0).addClass('fc-first'); // fc-last is done in updateCells
2268 daySegmentContainer
=
2269 $("<div style='position:absolute;z-index:8;top:0;left:0'/>")
2275 function updateCells(firstTime
) {
2276 var dowDirty
= firstTime
|| rowCnt
== 1; // could the cells' day-of-weeks need updating?
2277 var month
= t
.start
.getMonth();
2278 var today
= clearTime(new Date());
2284 headCells
.each(function(i
, _cell
) {
2286 date
= indexDate(i
);
2287 cell
.html(formatDate(date
, colFormat
));
2288 setDayID(cell
, date
);
2292 bodyCells
.each(function(i
, _cell
) {
2294 date
= indexDate(i
);
2295 if (date
.getMonth() == month
) {
2296 cell
.removeClass('fc-other-month');
2298 cell
.addClass('fc-other-month');
2300 if (+date
== +today
) {
2301 cell
.addClass(tm
+ '-state-highlight fc-today');
2303 cell
.removeClass(tm
+ '-state-highlight fc-today');
2305 cell
.find('div.fc-day-number').text(date
.getDate());
2307 setDayID(cell
, date
);
2311 bodyRows
.each(function(i
, _row
) {
2315 if (i
== rowCnt
-1) {
2316 row
.addClass('fc-last');
2318 row
.removeClass('fc-last');
2328 function setHeight(height
) {
2329 viewHeight
= height
;
2331 var bodyHeight
= viewHeight
- head
.height();
2336 if (opt('weekMode') == 'variable') {
2337 rowHeight
= rowHeightLast
= Math
.floor(bodyHeight
/ (rowCnt
==1 ? 2 : 6));
2339 rowHeight
= Math
.floor(bodyHeight
/ rowCnt
);
2340 rowHeightLast
= bodyHeight
- rowHeight
* (rowCnt
-1);
2343 bodyFirstCells
.each(function(i
, _cell
) {
2348 (i
==rowCnt
-1 ? rowHeightLast
: rowHeight
) - vsides(cell
)
2356 function setWidth(width
) {
2358 colContentPositions
.clear();
2359 colWidth
= Math
.floor(viewWidth
/ colCnt
);
2360 setOuterWidth(headCells
.slice(0, -1), colWidth
);
2365 /* Day clicking and binding
2366 -----------------------------------------------------------*/
2369 function dayBind(days
) {
2370 days
.click(dayClick
)
2371 .mousedown(daySelectionMousedown
);
2375 function dayClick(ev
) {
2376 if (!opt('selectable')) { // if selectable, SelectionManager will worry about dayClick
2377 var index
= parseInt(this.className
.match(/fc\-day(\d+)/)[1]); // TODO: maybe use .data
2378 var date
= indexDate(index
);
2379 trigger('dayClick', this, date
, true, ev
);
2385 /* Semi-transparent Overlay Helpers
2386 ------------------------------------------------------*/
2389 function renderDayOverlay(overlayStart
, overlayEnd
, refreshCoordinateGrid
) { // overlayEnd is exclusive
2390 if (refreshCoordinateGrid
) {
2391 coordinateGrid
.build();
2393 var rowStart
= cloneDate(t
.visStart
);
2394 var rowEnd
= addDays(cloneDate(rowStart
), colCnt
);
2395 for (var i
=0; i
<rowCnt
; i
++) {
2396 var stretchStart
= new Date(Math
.max(rowStart
, overlayStart
));
2397 var stretchEnd
= new Date(Math
.min(rowEnd
, overlayEnd
));
2398 if (stretchStart
< stretchEnd
) {
2399 var colStart
, colEnd
;
2401 colStart
= dayDiff(stretchEnd
, rowStart
)*dis
+dit
+1;
2402 colEnd
= dayDiff(stretchStart
, rowStart
)*dis
+dit
+1;
2404 colStart
= dayDiff(stretchStart
, rowStart
);
2405 colEnd
= dayDiff(stretchEnd
, rowStart
);
2408 renderCellOverlay(i
, colStart
, i
, colEnd
-1)
2411 addDays(rowStart
, 7);
2417 function renderCellOverlay(row0
, col0
, row1
, col1
) { // row1,col1 is inclusive
2418 var rect
= coordinateGrid
.rect(row0
, col0
, row1
, col1
, element
);
2419 return renderOverlay(rect
, element
);
2425 -----------------------------------------------------------------------*/
2428 function defaultSelectionEnd(startDate
, allDay
) {
2429 return cloneDate(startDate
);
2433 function renderSelection(startDate
, endDate
, allDay
) {
2434 renderDayOverlay(startDate
, addDays(cloneDate(endDate
), 1), true); // rebuild every time???
2438 function clearSelection() {
2443 function reportDayClick(date
, allDay
, ev
) {
2444 var cell
= dateCell(date
);
2445 var _element
= bodyCells
[cell
.row
*colCnt
+ cell
.col
];
2446 trigger('dayClick', _element
, date
, allDay
, ev
);
2451 /* External Dragging
2452 -----------------------------------------------------------------------*/
2455 function dragStart(_dragElement
, ev
, ui
) {
2456 hoverListener
.start(function(cell
) {
2459 renderCellOverlay(cell
.row
, cell
.col
, cell
.row
, cell
.col
);
2465 function dragStop(_dragElement
, ev
, ui
) {
2466 var cell
= hoverListener
.stop();
2469 var d
= cellDate(cell
);
2470 trigger('drop', _dragElement
, d
, true, ev
, ui
);
2477 --------------------------------------------------------*/
2480 function defaultEventEnd(event
) {
2481 return cloneDate(event
.start
);
2485 coordinateGrid
= new CoordinateGrid(function(rows
, cols
) {
2487 headCells
.each(function(i
, _e
) {
2489 n
= e
.offset().left
;
2496 p
[1] = n
+ e
.outerWidth();
2497 bodyRows
.each(function(i
, _e
) {
2508 p
[1] = n
+ e
.outerHeight();
2512 hoverListener
= new HoverListener(coordinateGrid
);
2515 colContentPositions
= new HorizontalPositionCache(function(col
) {
2516 return bodyCellTopInners
.eq(col
);
2520 function colContentLeft(col
) {
2521 return colContentPositions
.left(col
);
2525 function colContentRight(col
) {
2526 return colContentPositions
.right(col
);
2532 function dateCell(date
) {
2534 row
: Math
.floor(dayDiff(date
, t
.visStart
) / 7),
2535 col
: dayOfWeekCol(date
.getDay())
2540 function cellDate(cell
) {
2541 return _cellDate(cell
.row
, cell
.col
);
2545 function _cellDate(row
, col
) {
2546 return addDays(cloneDate(t
.visStart
), row
*7 + col
*dis
+dit
);
2547 // what about weekends in middle of week?
2551 function indexDate(index
) {
2552 return _cellDate(Math
.floor(index
/colCnt
), index
%colCnt
);
2556 function dayOfWeekCol(dayOfWeek
) {
2557 return ((dayOfWeek
- Math
.max(firstDay
, nwe
) + colCnt
) % colCnt
) * dis
+ dit
;
2563 function allDayRow(i
) {
2564 return bodyRows
.eq(i
);
2568 function allDayBounds(i
) {
2578 function BasicEventRenderer() {
2583 t
.renderEvents
= renderEvents
;
2584 t
.compileDaySegs
= compileSegs
; // for DayEventRenderer
2585 t
.clearEvents
= clearEvents
;
2586 t
.bindDaySeg
= bindDaySeg
;
2590 DayEventRenderer
.call(t
);
2592 var trigger
= t
.trigger
;
2593 //var setOverflowHidden = t.setOverflowHidden;
2594 var isEventDraggable
= t
.isEventDraggable
;
2595 var isEventResizable
= t
.isEventResizable
;
2596 var reportEvents
= t
.reportEvents
;
2597 var reportEventClear
= t
.reportEventClear
;
2598 var eventElementHandlers
= t
.eventElementHandlers
;
2599 var showEvents
= t
.showEvents
;
2600 var hideEvents
= t
.hideEvents
;
2601 var eventDrop
= t
.eventDrop
;
2602 var getDaySegmentContainer
= t
.getDaySegmentContainer
;
2603 var getHoverListener
= t
.getHoverListener
;
2604 var renderDayOverlay
= t
.renderDayOverlay
;
2605 var clearOverlays
= t
.clearOverlays
;
2606 var getRowCnt
= t
.getRowCnt
;
2607 var getColCnt
= t
.getColCnt
;
2608 var renderDaySegs
= t
.renderDaySegs
;
2609 var resizableDayEvent
= t
.resizableDayEvent
;
2614 --------------------------------------------------------------------*/
2617 function renderEvents(events
, modifiedEventId
) {
2618 reportEvents(events
);
2619 renderDaySegs(compileSegs(events
), modifiedEventId
);
2623 function clearEvents() {
2625 getDaySegmentContainer().empty();
2629 function compileSegs(events
) {
2630 var rowCnt
= getRowCnt(),
2631 colCnt
= getColCnt(),
2632 d1
= cloneDate(t
.visStart
),
2633 d2
= addDays(cloneDate(d1
), colCnt
),
2634 visEventsEnds
= $.map(events
, exclEndDay
),
2639 for (i
=0; i
<rowCnt
; i
++) {
2640 row
= stackSegs(sliceSegs(events
, visEventsEnds
, d1
, d2
));
2641 for (j
=0; j
<row
.length
; j
++) {
2643 for (k
=0; k
<level
.length
; k
++) {
2646 seg
.level
= j
; // not needed anymore
2657 function bindDaySeg(event
, eventElement
, seg
) {
2658 if (isEventDraggable(event
)) {
2659 draggableDayEvent(event
, eventElement
);
2661 if (seg
.isEnd
&& isEventResizable(event
)) {
2662 resizableDayEvent(event
, eventElement
, seg
);
2664 eventElementHandlers(event
, eventElement
);
2665 // needs to be after, because resizableDayEvent might stopImmediatePropagation on click
2671 ----------------------------------------------------------------------------*/
2674 function draggableDayEvent(event
, eventElement
) {
2675 var hoverListener
= getHoverListener();
2677 eventElement
.draggable({
2680 opacity
: opt('dragOpacity'),
2681 revertDuration
: opt('dragRevertDuration'),
2682 start: function(ev
, ui
) {
2683 trigger('eventDragStart', eventElement
, event
, ev
, ui
);
2684 hideEvents(event
, eventElement
);
2685 hoverListener
.start(function(cell
, origCell
, rowDelta
, colDelta
) {
2686 eventElement
.draggable('option', 'revert', !cell
|| !rowDelta
&& !colDelta
);
2689 //setOverflowHidden(true);
2690 dayDelta
= rowDelta
*7 + colDelta
* (opt('isRTL') ? -1 : 1);
2692 addDays(cloneDate(event
.start
), dayDelta
),
2693 addDays(exclEndDay(event
), dayDelta
)
2696 //setOverflowHidden(false);
2701 stop: function(ev
, ui
) {
2702 hoverListener
.stop();
2704 trigger('eventDragStop', eventElement
, event
, ev
, ui
);
2706 eventDrop(this, event
, dayDelta
, 0, event
.allDay
, ev
, ui
);
2708 eventElement
.css('filter', ''); // clear IE opacity side-effects
2709 showEvents(event
, eventElement
);
2711 //setOverflowHidden(false);
2719 fcViews
.agendaWeek
= AgendaWeekView
;
2721 function AgendaWeekView(element
, calendar
) {
2730 AgendaView
.call(t
, element
, calendar
, 'agendaWeek');
2732 var renderAgenda
= t
.renderAgenda
;
2733 var formatDates
= calendar
.formatDates
;
2737 function render(date
, delta
) {
2739 addDays(date
, delta
* 7);
2741 var start
= addDays(cloneDate(date
), -((date
.getDay() - opt('firstDay') + 7) % 7));
2742 var end
= addDays(cloneDate(start
), 7);
2743 var visStart
= cloneDate(start
);
2744 var visEnd
= cloneDate(end
);
2745 var weekends
= opt('weekends');
2747 skipWeekend(visStart
);
2748 skipWeekend(visEnd
, -1, true);
2750 t
.title
= formatDates(
2752 addDays(cloneDate(visEnd
), -1),
2757 t
.visStart
= visStart
;
2759 renderAgenda(weekends
? 7 : 5);
2765 fcViews
.agendaDay
= AgendaDayView
;
2767 function AgendaDayView(element
, calendar
) {
2776 AgendaView
.call(t
, element
, calendar
, 'agendaDay');
2778 var renderAgenda
= t
.renderAgenda
;
2779 var formatDate
= calendar
.formatDate
;
2783 function render(date
, delta
) {
2785 addDays(date
, delta
);
2786 if (!opt('weekends')) {
2787 skipWeekend(date
, delta
< 0 ? -1 : 1);
2790 var start
= cloneDate(date
, true);
2791 var end
= addDays(cloneDate(start
), 1);
2792 t
.title
= formatDate(date
, opt('titleFormat'));
2793 t
.start
= t
.visStart
= start
;
2794 t
.end
= t
.visEnd
= end
;
2803 allDayText
: 'all-day',
2806 defaultEventMinutes
: 120,
2807 axisFormat
: 'h(:mm)tt',
2809 agenda
: 'h:mm{ - h:mm}'
2819 // TODO: make it work in quirks mode (event corners, all-day height)
2820 // TODO: test liquid width, especially in IE6
2823 function AgendaView(element
, calendar
, viewName
) {
2828 t
.renderAgenda
= renderAgenda
;
2829 t
.setWidth
= setWidth
;
2830 t
.setHeight
= setHeight
;
2831 t
.beforeHide
= beforeHide
;
2832 t
.afterShow
= afterShow
;
2833 t
.defaultEventEnd
= defaultEventEnd
;
2834 t
.timePosition
= timePosition
;
2835 t
.dayOfWeekCol
= dayOfWeekCol
;
2836 t
.dateCell
= dateCell
;
2837 t
.cellDate
= cellDate
;
2838 t
.cellIsAllDay
= cellIsAllDay
;
2839 t
.allDayRow
= getAllDayRow
;
2840 t
.allDayBounds
= allDayBounds
;
2841 t
.getHoverListener = function() { return hoverListener
};
2842 t
.colContentLeft
= colContentLeft
;
2843 t
.colContentRight
= colContentRight
;
2844 t
.getDaySegmentContainer = function() { return daySegmentContainer
};
2845 t
.getSlotSegmentContainer = function() { return slotSegmentContainer
};
2846 t
.getMinMinute = function() { return minMinute
};
2847 t
.getMaxMinute = function() { return maxMinute
};
2848 t
.getBodyContent = function() { return slotContent
}; // !!??
2849 t
.getRowCnt = function() { return 1 };
2850 t
.getColCnt = function() { return colCnt
};
2851 t
.getColWidth = function() { return colWidth
};
2852 t
.getSlotHeight = function() { return slotHeight
};
2853 t
.defaultSelectionEnd
= defaultSelectionEnd
;
2854 t
.renderDayOverlay
= renderDayOverlay
;
2855 t
.renderSelection
= renderSelection
;
2856 t
.clearSelection
= clearSelection
;
2857 t
.reportDayClick
= reportDayClick
; // selection mousedown hack
2858 t
.dragStart
= dragStart
;
2859 t
.dragStop
= dragStop
;
2863 View
.call(t
, element
, calendar
, viewName
);
2864 OverlayManager
.call(t
);
2865 SelectionManager
.call(t
);
2866 AgendaEventRenderer
.call(t
);
2868 var trigger
= t
.trigger
;
2869 var clearEvents
= t
.clearEvents
;
2870 var renderOverlay
= t
.renderOverlay
;
2871 var clearOverlays
= t
.clearOverlays
;
2872 var reportSelection
= t
.reportSelection
;
2873 var unselect
= t
.unselect
;
2874 var daySelectionMousedown
= t
.daySelectionMousedown
;
2875 var slotSegHtml
= t
.slotSegHtml
;
2876 var formatDate
= calendar
.formatDate
;
2886 var dayBodyCellInners
;
2887 var dayBodyFirstCell
;
2888 var dayBodyFirstCellStretcher
;
2890 var daySegmentContainer
;
2895 var slotSegmentContainer
;
2897 var slotTableFirstInner
;
2900 var selectionHelper
;
2907 var slotHeight
; // TODO: what if slotHeight changes? (see issue 650)
2914 var colContentPositions
;
2915 var slotTopCache
= {};
2919 var nwe
; // no weekends (int)
2920 var rtl
, dis
, dit
; // day index sign / translate
2921 var minMinute
, maxMinute
;
2927 -----------------------------------------------------------------------------*/
2930 disableTextSelection(element
.addClass('fc-agenda'));
2933 function renderAgenda(c
) {
2946 function updateOptions() {
2947 tm
= opt('theme') ? 'ui' : 'fc';
2948 nwe
= opt('weekends') ? 0 : 1;
2949 firstDay
= opt('firstDay');
2950 if (rtl
= opt('isRTL')) {
2957 minMinute
= parseTime(opt('minTime'));
2958 maxMinute
= parseTime(opt('maxTime'));
2959 colFormat
= opt('columnFormat');
2964 function buildSkeleton() {
2965 var headerClass
= tm
+ "-widget-header";
2966 var contentClass
= tm
+ "-widget-content";
2972 var slotNormal
= opt('slotMinutes') % 15 == 0;
2975 "<table style='width:100%' class='fc-agenda-days fc-border-separate' cellspacing='0'>" +
2978 "<th class='fc-agenda-axis " + headerClass
+ "'> </th>";
2979 for (i
=0; i
<colCnt
; i
++) {
2981 "<th class='fc- fc-col" + i
+ ' ' + headerClass
+ "'/>"; // fc- needed for setDayID
2984 "<th class='fc-agenda-gutter " + headerClass
+ "'> </th>" +
2989 "<th class='fc-agenda-axis " + headerClass
+ "'> </th>";
2990 for (i
=0; i
<colCnt
; i
++) {
2992 "<td class='fc- fc-col" + i
+ ' ' + contentClass
+ "'>" + // fc- needed for setDayID
2994 "<div class='fc-day-content'>" +
2995 "<div style='position:relative'> </div>" +
3001 "<td class='fc-agenda-gutter " + contentClass
+ "'> </td>" +
3005 dayTable
= $(s
).appendTo(element
);
3006 dayHead
= dayTable
.find('thead');
3007 dayHeadCells
= dayHead
.find('th').slice(1, -1);
3008 dayBody
= dayTable
.find('tbody');
3009 dayBodyCells
= dayBody
.find('td').slice(0, -1);
3010 dayBodyCellInners
= dayBodyCells
.find('div.fc-day-content div');
3011 dayBodyFirstCell
= dayBodyCells
.eq(0);
3012 dayBodyFirstCellStretcher
= dayBodyFirstCell
.find('> div');
3014 markFirstLast(dayHead
.add(dayHead
.find('tr')));
3015 markFirstLast(dayBody
.add(dayBody
.find('tr')));
3017 axisFirstCells
= dayHead
.find('th:first');
3018 gutterCells
= dayTable
.find('.fc-agenda-gutter');
3021 $("<div style='position:absolute;z-index:2;left:0;width:100%'/>")
3024 if (opt('allDaySlot')) {
3026 daySegmentContainer
=
3027 $("<div style='position:absolute;z-index:8;top:0;left:0'/>")
3028 .appendTo(slotLayer
);
3031 "<table style='width:100%' class='fc-agenda-allday' cellspacing='0'>" +
3033 "<th class='" + headerClass
+ " fc-agenda-axis'>" + opt('allDayText') + "</th>" +
3035 "<div class='fc-day-content'><div style='position:relative'/></div>" +
3037 "<th class='" + headerClass
+ " fc-agenda-gutter'> </th>" +
3040 allDayTable
= $(s
).appendTo(slotLayer
);
3041 allDayRow
= allDayTable
.find('tr');
3043 dayBind(allDayRow
.find('td'));
3045 axisFirstCells
= axisFirstCells
.add(allDayTable
.find('th:first'));
3046 gutterCells
= gutterCells
.add(allDayTable
.find('th.fc-agenda-gutter'));
3049 "<div class='fc-agenda-divider " + headerClass
+ "'>" +
3050 "<div class='fc-agenda-divider-inner'/>" +
3056 daySegmentContainer
= $([]); // in jQuery 1.4, we can just do $()
3061 $("<div style='position:absolute;width:100%;overflow-x:hidden;overflow-y:auto'/>")
3062 .appendTo(slotLayer
);
3065 $("<div style='position:relative;width:100%;overflow:hidden'/>")
3066 .appendTo(slotScroller
);
3068 slotSegmentContainer
=
3069 $("<div style='position:absolute;z-index:8;top:0;left:0'/>")
3070 .appendTo(slotContent
);
3073 "<table class='fc-agenda-slots' style='width:100%' cellspacing='0'>" +
3076 maxd
= addMinutes(cloneDate(d
), maxMinute
);
3077 addMinutes(d
, minMinute
);
3079 for (i
=0; d
< maxd
; i
++) {
3080 minutes
= d
.getMinutes();
3082 "<tr class='fc-slot" + i
+ ' ' + (!minutes
? '' : 'fc-minor') + "'>" +
3083 "<th class='fc-agenda-axis " + headerClass
+ "'>" +
3084 ((!slotNormal
|| !minutes
) ? formatDate(d
, opt('axisFormat')) : ' ') +
3086 "<td class='" + contentClass
+ "'>" +
3087 "<div style='position:relative'> </div>" +
3090 addMinutes(d
, opt('slotMinutes'));
3096 slotTable
= $(s
).appendTo(slotContent
);
3097 slotTableFirstInner
= slotTable
.find('div:first');
3099 slotBind(slotTable
.find('td'));
3101 axisFirstCells
= axisFirstCells
.add(slotTable
.find('th:first'));
3106 function updateCells() {
3111 var today
= clearTime(new Date());
3112 for (i
=0; i
<colCnt
; i
++) {
3114 headCell
= dayHeadCells
.eq(i
);
3115 headCell
.html(formatDate(date
, colFormat
));
3116 bodyCell
= dayBodyCells
.eq(i
);
3117 if (+date
== +today
) {
3118 bodyCell
.addClass(tm
+ '-state-highlight fc-today');
3120 bodyCell
.removeClass(tm
+ '-state-highlight fc-today');
3122 setDayID(headCell
.add(bodyCell
), date
);
3128 function setHeight(height
, dateChanged
) {
3129 if (height
=== undefined) {
3130 height
= viewHeight
;
3132 viewHeight
= height
;
3135 var headHeight
= dayBody
.position().top
;
3136 var allDayHeight
= slotScroller
.position().top
; // including divider
3137 var bodyHeight
= Math
.min( // total body height, including borders
3138 height
- headHeight
, // when scrollbars
3139 slotTable
.height() + allDayHeight
+ 1 // when no scrollbars. +1 for bottom border
3142 dayBodyFirstCellStretcher
3143 .height(bodyHeight
- vsides(dayBodyFirstCell
));
3145 slotLayer
.css('top', headHeight
);
3147 slotScroller
.height(bodyHeight
- allDayHeight
- 1);
3149 slotHeight
= slotTableFirstInner
.height() + 1; // +1 for border
3158 function setWidth(width
) {
3160 colContentPositions
.clear();
3166 .each(function(i
, _cell
) {
3167 axisWidth
= Math
.max(axisWidth
, $(_cell
).outerWidth());
3172 var slotTableWidth
= slotScroller
[0].clientWidth
; // needs to be done after axisWidth (for IE7)
3173 //slotTable.width(slotTableWidth);
3175 gutterWidth
= slotScroller
.width() - slotTableWidth
;
3177 setOuterWidth(gutterCells
, gutterWidth
);
3181 .removeClass('fc-last');
3186 .addClass('fc-last');
3189 colWidth
= Math
.floor((slotTableWidth
- axisWidth
) / colCnt
);
3190 setOuterWidth(dayHeadCells
.slice(0, -1), colWidth
);
3195 function resetScroll() {
3196 var d0
= zeroDate();
3197 var scrollDate
= cloneDate(d0
);
3198 scrollDate
.setHours(opt('firstHour'));
3199 var top
= timePosition(d0
, scrollDate
) + 1; // +1 for the border
3201 slotScroller
.scrollTop(top
);
3204 setTimeout(scroll
, 0); // overrides any previous scroll state made by the browser
3208 function beforeHide() {
3209 savedScrollTop
= slotScroller
.scrollTop();
3213 function afterShow() {
3214 slotScroller
.scrollTop(savedScrollTop
);
3219 /* Slot/Day clicking and binding
3220 -----------------------------------------------------------------------*/
3223 function dayBind(cells
) {
3224 cells
.click(slotClick
)
3225 .mousedown(daySelectionMousedown
);
3229 function slotBind(cells
) {
3230 cells
.click(slotClick
)
3231 .mousedown(slotSelectionMousedown
);
3235 function slotClick(ev
) {
3236 if (!opt('selectable')) { // if selectable, SelectionManager will worry about dayClick
3237 var col
= Math
.min(colCnt
-1, Math
.floor((ev
.pageX
- dayTable
.offset().left
- axisWidth
) / colWidth
));
3238 var date
= colDate(col
);
3239 var rowMatch
= this.parentNode
.className
.match(/fc-slot(\d+)/); // TODO: maybe use data
3241 var mins
= parseInt(rowMatch
[1]) * opt('slotMinutes');
3242 var hours
= Math
.floor(mins
/60);
3243 date
.setHours(hours
);
3244 date
.setMinutes(mins
%60 + minMinute
);
3245 trigger('dayClick', dayBodyCells
[col
], date
, false, ev
);
3247 trigger('dayClick', dayBodyCells
[col
], date
, true, ev
);
3254 /* Semi-transparent Overlay Helpers
3255 -----------------------------------------------------*/
3258 function renderDayOverlay(startDate
, endDate
, refreshCoordinateGrid
) { // endDate is exclusive
3259 if (refreshCoordinateGrid
) {
3260 coordinateGrid
.build();
3262 var visStart
= cloneDate(t
.visStart
);
3263 var startCol
, endCol
;
3265 startCol
= dayDiff(endDate
, visStart
)*dis
+dit
+1;
3266 endCol
= dayDiff(startDate
, visStart
)*dis
+dit
+1;
3268 startCol
= dayDiff(startDate
, visStart
);
3269 endCol
= dayDiff(endDate
, visStart
);
3271 startCol
= Math
.max(0, startCol
);
3272 endCol
= Math
.min(colCnt
, endCol
);
3273 if (startCol
< endCol
) {
3275 renderCellOverlay(0, startCol
, 0, endCol
-1)
3281 function renderCellOverlay(row0
, col0
, row1
, col1
) { // only for all-day?
3282 var rect
= coordinateGrid
.rect(row0
, col0
, row1
, col1
, slotLayer
);
3283 return renderOverlay(rect
, slotLayer
);
3287 function renderSlotOverlay(overlayStart
, overlayEnd
) {
3288 var dayStart
= cloneDate(t
.visStart
);
3289 var dayEnd
= addDays(cloneDate(dayStart
), 1);
3290 for (var i
=0; i
<colCnt
; i
++) {
3291 var stretchStart
= new Date(Math
.max(dayStart
, overlayStart
));
3292 var stretchEnd
= new Date(Math
.min(dayEnd
, overlayEnd
));
3293 if (stretchStart
< stretchEnd
) {
3294 var col
= i
*dis
+dit
;
3295 var rect
= coordinateGrid
.rect(0, col
, 0, col
, slotContent
); // only use it for horizontal coords
3296 var top
= timePosition(dayStart
, stretchStart
);
3297 var bottom
= timePosition(dayStart
, stretchEnd
);
3299 rect
.height
= bottom
- top
;
3301 renderOverlay(rect
, slotContent
)
3304 addDays(dayStart
, 1);
3311 /* Coordinate Utilities
3312 -----------------------------------------------------------------------------*/
3315 coordinateGrid
= new CoordinateGrid(function(rows
, cols
) {
3317 dayHeadCells
.each(function(i
, _e
) {
3319 n
= e
.offset().left
;
3326 p
[1] = n
+ e
.outerWidth();
3327 if (opt('allDaySlot')) {
3330 rows
[0] = [n
, n
+e
.outerHeight()];
3332 var slotTableTop
= slotContent
.offset().top
;
3333 var slotScrollerTop
= slotScroller
.offset().top
;
3334 var slotScrollerBottom
= slotScrollerTop
+ slotScroller
.outerHeight();
3335 function constrain(n
) {
3336 return Math
.max(slotScrollerTop
, Math
.min(slotScrollerBottom
, n
));
3338 for (var i
=0; i
<slotCnt
; i
++) {
3340 constrain(slotTableTop
+ slotHeight
*i
),
3341 constrain(slotTableTop
+ slotHeight
*(i
+1))
3347 hoverListener
= new HoverListener(coordinateGrid
);
3350 colContentPositions
= new HorizontalPositionCache(function(col
) {
3351 return dayBodyCellInners
.eq(col
);
3355 function colContentLeft(col
) {
3356 return colContentPositions
.left(col
);
3360 function colContentRight(col
) {
3361 return colContentPositions
.right(col
);
3367 function dateCell(date
) { // "cell" terminology is now confusing
3369 row
: Math
.floor(dayDiff(date
, t
.visStart
) / 7),
3370 col
: dayOfWeekCol(date
.getDay())
3375 function cellDate(cell
) {
3376 var d
= colDate(cell
.col
);
3377 var slotIndex
= cell
.row
;
3378 if (opt('allDaySlot')) {
3381 if (slotIndex
>= 0) {
3382 addMinutes(d
, minMinute
+ slotIndex
* opt('slotMinutes'));
3388 function colDate(col
) { // returns dates with 00:00:00
3389 return addDays(cloneDate(t
.visStart
), col
*dis
+dit
);
3393 function cellIsAllDay(cell
) {
3394 return opt('allDaySlot') && !cell
.row
;
3398 function dayOfWeekCol(dayOfWeek
) {
3399 return ((dayOfWeek
- Math
.max(firstDay
, nwe
) + colCnt
) % colCnt
)*dis
+dit
;
3405 // get the Y coordinate of the given time on the given day (both Date objects)
3406 function timePosition(day
, time
) { // both date objects. day holds 00:00 of current day
3407 day
= cloneDate(day
, true);
3408 if (time
< addMinutes(cloneDate(day
), minMinute
)) {
3411 if (time
>= addMinutes(cloneDate(day
), maxMinute
)) {
3412 return slotTable
.height();
3414 var slotMinutes
= opt('slotMinutes'),
3415 minutes
= time
.getHours()*60 + time
.getMinutes() - minMinute
,
3416 slotI
= Math
.floor(minutes
/ slotMinutes
),
3417 slotTop
= slotTopCache
[slotI
];
3418 if (slotTop
=== undefined) {
3419 slotTop
= slotTopCache
[slotI
] = slotTable
.find('tr:eq(' + slotI
+ ') td div')[0].offsetTop
; //.position().top; // need this optimization???
3421 return Math
.max(0, Math
.round(
3422 slotTop
- 1 + slotHeight
* ((minutes
% slotMinutes
) / slotMinutes
)
3427 function allDayBounds() {
3430 right
: viewWidth
- gutterWidth
3435 function getAllDayRow(index
) {
3440 function defaultEventEnd(event
) {
3441 var start
= cloneDate(event
.start
);
3445 return addMinutes(start
, opt('defaultEventMinutes'));
3451 ---------------------------------------------------------------------------------*/
3454 function defaultSelectionEnd(startDate
, allDay
) {
3456 return cloneDate(startDate
);
3458 return addMinutes(cloneDate(startDate
), opt('slotMinutes'));
3462 function renderSelection(startDate
, endDate
, allDay
) { // only for all-day
3464 if (opt('allDaySlot')) {
3465 renderDayOverlay(startDate
, addDays(cloneDate(endDate
), 1), true);
3468 renderSlotSelection(startDate
, endDate
);
3473 function renderSlotSelection(startDate
, endDate
) {
3474 var helperOption
= opt('selectHelper');
3475 coordinateGrid
.build();
3477 var col
= dayDiff(startDate
, t
.visStart
) * dis
+ dit
;
3478 if (col
>= 0 && col
< colCnt
) { // only works when times are on same day
3479 var rect
= coordinateGrid
.rect(0, col
, 0, col
, slotContent
); // only for horizontal coords
3480 var top
= timePosition(startDate
, startDate
);
3481 var bottom
= timePosition(startDate
, endDate
);
3482 if (bottom
> top
) { // protect against selections that are entirely before or after visible range
3484 rect
.height
= bottom
- top
;
3487 if ($.isFunction(helperOption
)) {
3488 var helperRes
= helperOption(startDate
, endDate
);
3490 rect
.position
= 'absolute';
3492 selectionHelper
= $(helperRes
)
3494 .appendTo(slotContent
);
3497 rect
.isStart
= true; // conside rect a "seg" now
3498 rect
.isEnd
= true; //
3499 selectionHelper
= $(slotSegHtml(
3504 className
: ['fc-select-helper'],
3509 selectionHelper
.css('opacity', opt('dragOpacity'));
3511 if (selectionHelper
) {
3512 slotBind(selectionHelper
);
3513 slotContent
.append(selectionHelper
);
3514 setOuterWidth(selectionHelper
, rect
.width
, true); // needs to be after appended
3515 setOuterHeight(selectionHelper
, rect
.height
, true);
3520 renderSlotOverlay(startDate
, endDate
);
3525 function clearSelection() {
3527 if (selectionHelper
) {
3528 selectionHelper
.remove();
3529 selectionHelper
= null;
3534 function slotSelectionMousedown(ev
) {
3535 if (ev
.which
== 1 && opt('selectable')) { // ev.which==1 means left mouse button
3538 hoverListener
.start(function(cell
, origCell
) {
3540 if (cell
&& cell
.col
== origCell
.col
&& !cellIsAllDay(cell
)) {
3541 var d1
= cellDate(origCell
);
3542 var d2
= cellDate(cell
);
3545 addMinutes(cloneDate(d1
), opt('slotMinutes')),
3547 addMinutes(cloneDate(d2
), opt('slotMinutes'))
3549 renderSlotSelection(dates
[0], dates
[3]);
3554 $(document
).one('mouseup', function(ev
) {
3555 hoverListener
.stop();
3557 if (+dates
[0] == +dates
[1]) {
3558 reportDayClick(dates
[0], false, ev
);
3560 reportSelection(dates
[0], dates
[3], false, ev
);
3567 function reportDayClick(date
, allDay
, ev
) {
3568 trigger('dayClick', dayBodyCells
[dayOfWeekCol(date
.getDay())], date
, allDay
, ev
);
3573 /* External Dragging
3574 --------------------------------------------------------------------------------*/
3577 function dragStart(_dragElement
, ev
, ui
) {
3578 hoverListener
.start(function(cell
) {
3581 if (cellIsAllDay(cell
)) {
3582 renderCellOverlay(cell
.row
, cell
.col
, cell
.row
, cell
.col
);
3584 var d1
= cellDate(cell
);
3585 var d2
= addMinutes(cloneDate(d1
), opt('defaultEventMinutes'));
3586 renderSlotOverlay(d1
, d2
);
3593 function dragStop(_dragElement
, ev
, ui
) {
3594 var cell
= hoverListener
.stop();
3597 trigger('drop', _dragElement
, cellDate(cell
), cellIsAllDay(cell
), ev
, ui
);
3604 function AgendaEventRenderer() {
3609 t
.renderEvents
= renderEvents
;
3610 t
.compileDaySegs
= compileDaySegs
; // for DayEventRenderer
3611 t
.clearEvents
= clearEvents
;
3612 t
.slotSegHtml
= slotSegHtml
;
3613 t
.bindDaySeg
= bindDaySeg
;
3617 DayEventRenderer
.call(t
);
3619 var trigger
= t
.trigger
;
3620 //var setOverflowHidden = t.setOverflowHidden;
3621 var isEventDraggable
= t
.isEventDraggable
;
3622 var isEventResizable
= t
.isEventResizable
;
3623 var eventEnd
= t
.eventEnd
;
3624 var reportEvents
= t
.reportEvents
;
3625 var reportEventClear
= t
.reportEventClear
;
3626 var eventElementHandlers
= t
.eventElementHandlers
;
3627 var setHeight
= t
.setHeight
;
3628 var getDaySegmentContainer
= t
.getDaySegmentContainer
;
3629 var getSlotSegmentContainer
= t
.getSlotSegmentContainer
;
3630 var getHoverListener
= t
.getHoverListener
;
3631 var getMaxMinute
= t
.getMaxMinute
;
3632 var getMinMinute
= t
.getMinMinute
;
3633 var timePosition
= t
.timePosition
;
3634 var colContentLeft
= t
.colContentLeft
;
3635 var colContentRight
= t
.colContentRight
;
3636 var renderDaySegs
= t
.renderDaySegs
;
3637 var resizableDayEvent
= t
.resizableDayEvent
; // TODO: streamline binding architecture
3638 var getColCnt
= t
.getColCnt
;
3639 var getColWidth
= t
.getColWidth
;
3640 var getSlotHeight
= t
.getSlotHeight
;
3641 var getBodyContent
= t
.getBodyContent
;
3642 var reportEventElement
= t
.reportEventElement
;
3643 var showEvents
= t
.showEvents
;
3644 var hideEvents
= t
.hideEvents
;
3645 var eventDrop
= t
.eventDrop
;
3646 var eventResize
= t
.eventResize
;
3647 var renderDayOverlay
= t
.renderDayOverlay
;
3648 var clearOverlays
= t
.clearOverlays
;
3649 var calendar
= t
.calendar
;
3650 var formatDate
= calendar
.formatDate
;
3651 var formatDates
= calendar
.formatDates
;
3656 ----------------------------------------------------------------------------*/
3659 function renderEvents(events
, modifiedEventId
) {
3660 reportEvents(events
);
3661 var i
, len
=events
.length
,
3664 for (i
=0; i
<len
; i
++) {
3665 if (events
[i
].allDay
) {
3666 dayEvents
.push(events
[i
]);
3668 slotEvents
.push(events
[i
]);
3671 if (opt('allDaySlot')) {
3672 renderDaySegs(compileDaySegs(dayEvents
), modifiedEventId
);
3673 setHeight(); // no params means set to viewHeight
3675 renderSlotSegs(compileSlotSegs(slotEvents
), modifiedEventId
);
3679 function clearEvents() {
3681 getDaySegmentContainer().empty();
3682 getSlotSegmentContainer().empty();
3686 function compileDaySegs(events
) {
3687 var levels
= stackSegs(sliceSegs(events
, $.map(events
, exclEndDay
), t
.visStart
, t
.visEnd
)),
3688 i
, levelCnt
=levels
.length
, level
,
3691 for (i
=0; i
<levelCnt
; i
++) {
3693 for (j
=0; j
<level
.length
; j
++) {
3696 seg
.level
= i
; // not needed anymore
3704 function compileSlotSegs(events
) {
3705 var colCnt
= getColCnt(),
3706 minMinute
= getMinMinute(),
3707 maxMinute
= getMaxMinute(),
3708 d
= addMinutes(cloneDate(t
.visStart
), minMinute
),
3709 visEventEnds
= $.map(events
, slotEventEnd
),
3714 for (i
=0; i
<colCnt
; i
++) {
3715 col
= stackSegs(sliceSegs(events
, visEventEnds
, d
, addMinutes(cloneDate(d
), maxMinute
-minMinute
)));
3716 countForwardSegs(col
);
3717 for (j
=0; j
<col
.length
; j
++) {
3719 for (k
=0; k
<level
.length
; k
++) {
3726 addDays(d
, 1, true);
3732 function slotEventEnd(event
) {
3734 return cloneDate(event
.end
);
3736 return addMinutes(cloneDate(event
.start
), opt('defaultEventMinutes'));
3741 // renders events in the 'time slots' at the bottom
3743 function renderSlotSegs(segs
, modifiedEventId
) {
3745 var i
, segCnt
=segs
.length
, seg
,
3749 colI
, levelI
, forward
,
3763 slotSegmentContainer
= getSlotSegmentContainer(),
3765 colCnt
= getColCnt();
3767 if (rtl
= opt('isRTL')) {
3775 // calculate position/dimensions, create html
3776 for (i
=0; i
<segCnt
; i
++) {
3779 top
= timePosition(seg
.start
, seg
.start
);
3780 bottom
= timePosition(seg
.start
, seg
.end
);
3783 forward
= seg
.forward
|| 0;
3784 leftmost
= colContentLeft(colI
*dis
+ dit
);
3785 availWidth
= colContentRight(colI
*dis
+ dit
) - leftmost
;
3786 availWidth
= Math
.min(availWidth
-6, availWidth
*.95); // TODO: move this to CSS
3788 // indented and thin
3789 outerWidth
= availWidth
/ (levelI
+ forward
+ 1);
3792 // moderately wide, aligned left still
3793 outerWidth
= ((availWidth
/ (forward
+ 1)) - (12/2)) * 2; // 12 is the predicted width
of resizer
=
3795 // can be entire width, aligned left
3796 outerWidth
= availWidth
;
3799 left
= leftmost
+ // leftmost possible
3800 (availWidth
/ (levelI
+ forward
+ 1) * levelI
) // indentation
3801 * dis
+ (rtl
? availWidth
- outerWidth
: 0); // rtl
3804 seg
.outerWidth
= outerWidth
;
3805 seg
.outerHeight
= bottom
- top
;
3806 html
+= slotSegHtml(event
, seg
);
3808 slotSegmentContainer
[0].innerHTML
= html
; // faster than html()
3809 eventElements
= slotSegmentContainer
.children();
3811 // retrieve elements, run through eventRender callback, bind event handlers
3812 for (i
=0; i
<segCnt
; i
++) {
3815 eventElement
= $(eventElements
[i
]); // faster than eq()
3816 triggerRes
= trigger('eventRender', event
, event
, eventElement
);
3817 if (triggerRes
=== false) {
3818 eventElement
.remove();
3820 if (triggerRes
&& triggerRes
!== true) {
3821 eventElement
.remove();
3822 eventElement
= $(triggerRes
)
3824 position
: 'absolute',
3828 .appendTo(slotSegmentContainer
);
3830 seg
.element
= eventElement
;
3831 if (event
._id
=== modifiedEventId
) {
3832 bindSlotSeg(event
, eventElement
, seg
);
3834 eventElement
[0]._fci
= i
; // for lazySegBind
3836 reportEventElement(event
, eventElement
);
3840 lazySegBind(slotSegmentContainer
, segs
, bindSlotSeg
);
3842 // record event sides and title positions
3843 for (i
=0; i
<segCnt
; i
++) {
3845 if (eventElement
= seg
.element
) {
3846 val
= vsideCache
[key
= seg
.key
= cssKey(eventElement
[0])];
3847 seg
.vsides
= val
=== undefined ? (vsideCache
[key
] = vsides(eventElement
, true)) : val
;
3848 val
= hsideCache
[key
];
3849 seg
.hsides
= val
=== undefined ? (hsideCache
[key
] = hsides(eventElement
, true)) : val
;
3850 contentElement
= eventElement
.find('div.fc-event-content');
3851 if (contentElement
.length
) {
3852 seg
.contentTop
= contentElement
[0].offsetTop
;
3857 // set all positions/dimensions at once
3858 for (i
=0; i
<segCnt
; i
++) {
3860 if (eventElement
= seg
.element
) {
3861 eventElement
[0].style
.width
= Math
.max(0, seg
.outerWidth
- seg
.hsides
) + 'px';
3862 height
= Math
.max(0, seg
.outerHeight
- seg
.vsides
);
3863 eventElement
[0].style
.height
= height
+ 'px';
3865 if (seg
.contentTop
!== undefined && height
- seg
.contentTop
< 10) {
3866 // not enough room for title, put it in the time header
3867 eventElement
.find('div.fc-event-time')
3868 .text(formatDate(event
.start
, opt('timeFormat')) + ' - ' + event
.title
);
3869 eventElement
.find('div.fc-event-title')
3872 trigger('eventAfterRender', event
, event
, eventElement
);
3879 function slotSegHtml(event
, seg
) {
3881 var url
= event
.url
;
3882 var skinCss
= getSkinCss(event
, opt
);
3883 var skinCssAttr
= (skinCss
? " style='" + skinCss
+ "'" : '');
3884 var classes
= ['fc-event', 'fc-event-skin', 'fc-event-vert'];
3885 if (isEventDraggable(event
)) {
3886 classes
.push('fc-event-draggable');
3889 classes
.push('fc-corner-top');
3892 classes
.push('fc-corner-bottom');
3894 classes
= classes
.concat(event
.className
);
3896 classes
= classes
.concat(event
.source
.className
|| []);
3899 html
+= "a href='" + htmlEscape(event
.url
) + "'";
3904 " class='" + classes
.join(' ') + "'" +
3905 " style='position:absolute;z-index:8;top:" + seg
.top
+ "px;left:" + seg
.left
+ "px;" + skinCss
+ "'" +
3907 "<div class='fc-event-inner fc-event-skin'" + skinCssAttr
+ ">" +
3908 "<div class='fc-event-head fc-event-skin'" + skinCssAttr
+ ">" +
3909 "<div class='fc-event-time'>" +
3910 htmlEscape(formatDates(event
.start
, event
.end
, opt('timeFormat'))) +
3913 "<div class='fc-event-content'>" +
3914 "<div class='fc-event-title'>" +
3915 htmlEscape(event
.title
) +
3918 "<div class='fc-event-bg'></div>" +
3919 "</div>"; // close inner
3920 if (seg
.isEnd
&& isEventResizable(event
)) {
3922 "<div class='ui-resizable-handle ui-resizable-s'>=</div>";
3925 "</" + (url
? "a" : "div") + ">";
3930 function bindDaySeg(event
, eventElement
, seg
) {
3931 if (isEventDraggable(event
)) {
3932 draggableDayEvent(event
, eventElement
, seg
.isStart
);
3934 if (seg
.isEnd
&& isEventResizable(event
)) {
3935 resizableDayEvent(event
, eventElement
, seg
);
3937 eventElementHandlers(event
, eventElement
);
3938 // needs to be after, because resizableDayEvent might stopImmediatePropagation on click
3942 function bindSlotSeg(event
, eventElement
, seg
) {
3943 var timeElement
= eventElement
.find('div.fc-event-time');
3944 if (isEventDraggable(event
)) {
3945 draggableSlotEvent(event
, eventElement
, timeElement
);
3947 if (seg
.isEnd
&& isEventResizable(event
)) {
3948 resizableSlotEvent(event
, eventElement
, timeElement
);
3950 eventElementHandlers(event
, eventElement
);
3956 -----------------------------------------------------------------------------------*/
3959 // when event starts out FULL-DAY
3961 function draggableDayEvent(event
, eventElement
, isStart
) {
3966 var dis
= opt('isRTL') ? -1 : 1;
3967 var hoverListener
= getHoverListener();
3968 var colWidth
= getColWidth();
3969 var slotHeight
= getSlotHeight();
3970 var minMinute
= getMinMinute();
3971 eventElement
.draggable({
3973 opacity
: opt('dragOpacity', 'month'), // use whatever the month view was using
3974 revertDuration
: opt('dragRevertDuration'),
3975 start: function(ev
, ui
) {
3976 trigger('eventDragStart', eventElement
, event
, ev
, ui
);
3977 hideEvents(event
, eventElement
);
3978 origWidth
= eventElement
.width();
3979 hoverListener
.start(function(cell
, origCell
, rowDelta
, colDelta
) {
3982 //setOverflowHidden(true);
3984 dayDelta
= colDelta
* dis
;
3988 addDays(cloneDate(event
.start
), dayDelta
),
3989 addDays(exclEndDay(event
), dayDelta
)
3993 // mouse is over bottom slots
3996 // convert event to temporary slot-event
3997 eventElement
.width(colWidth
- 10); // don't use entire width
4000 slotHeight
* Math
.round(
4001 (event
.end
? ((event
.end
- event
.start
) / MINUTE_MS
) : opt('defaultEventMinutes'))
4002 / opt('slotMinutes')
4005 eventElement
.draggable('option', 'grid', [colWidth
, 1]);
4012 revert
= revert
|| (allDay
&& !dayDelta
);
4015 //setOverflowHidden(false);
4018 eventElement
.draggable('option', 'revert', revert
);
4021 stop: function(ev
, ui
) {
4022 hoverListener
.stop();
4024 trigger('eventDragStop', eventElement
, event
, ev
, ui
);
4026 // hasn't moved or is out of bounds (draggable has already reverted)
4028 eventElement
.css('filter', ''); // clear IE opacity side-effects
4029 showEvents(event
, eventElement
);
4032 var minuteDelta
= 0;
4034 minuteDelta
= Math
.round((eventElement
.offset().top
- getBodyContent().offset().top
) / slotHeight
)
4035 * opt('slotMinutes')
4037 - (event
.start
.getHours() * 60 + event
.start
.getMinutes());
4039 eventDrop(this, event
, dayDelta
, minuteDelta
, allDay
, ev
, ui
);
4041 //setOverflowHidden(false);
4044 function resetElement() {
4049 .draggable('option', 'grid', null);
4056 // when event starts out IN TIMESLOTS
4058 function draggableSlotEvent(event
, eventElement
, timeElement
) {
4063 var prevMinuteDelta
;
4064 var dis
= opt('isRTL') ? -1 : 1;
4065 var hoverListener
= getHoverListener();
4066 var colCnt
= getColCnt();
4067 var colWidth
= getColWidth();
4068 var slotHeight
= getSlotHeight();
4069 eventElement
.draggable({
4072 grid
: [colWidth
, slotHeight
],
4073 axis
: colCnt
==1 ? 'y' : false,
4074 opacity
: opt('dragOpacity'),
4075 revertDuration
: opt('dragRevertDuration'),
4076 start: function(ev
, ui
) {
4077 trigger('eventDragStart', eventElement
, event
, ev
, ui
);
4078 hideEvents(event
, eventElement
);
4079 origPosition
= eventElement
.position();
4080 minuteDelta
= prevMinuteDelta
= 0;
4081 hoverListener
.start(function(cell
, origCell
, rowDelta
, colDelta
) {
4082 eventElement
.draggable('option', 'revert', !cell
);
4085 dayDelta
= colDelta
* dis
;
4086 if (opt('allDaySlot') && !cell
.row
) {
4089 // convert to temporary all-day event
4092 eventElement
.draggable('option', 'grid', null);
4095 addDays(cloneDate(event
.start
), dayDelta
),
4096 addDays(exclEndDay(event
), dayDelta
)
4105 drag: function(ev
, ui
) {
4106 minuteDelta
= Math
.round((ui
.position
.top
- origPosition
.top
) / slotHeight
) * opt('slotMinutes');
4107 if (minuteDelta
!= prevMinuteDelta
) {
4109 updateTimeText(minuteDelta
);
4111 prevMinuteDelta
= minuteDelta
;
4114 stop: function(ev
, ui
) {
4115 var cell
= hoverListener
.stop();
4117 trigger('eventDragStop', eventElement
, event
, ev
, ui
);
4118 if (cell
&& (dayDelta
|| minuteDelta
|| allDay
)) {
4120 eventDrop(this, event
, dayDelta
, allDay
? 0 : minuteDelta
, allDay
, ev
, ui
);
4122 // either no change or out-of-bounds (draggable has already reverted)
4124 eventElement
.css('filter', ''); // clear IE opacity side-effects
4125 eventElement
.css(origPosition
); // sometimes fast drags make event revert to wrong position
4127 showEvents(event
, eventElement
);
4131 function updateTimeText(minuteDelta
) {
4132 var newStart
= addMinutes(cloneDate(event
.start
), minuteDelta
);
4135 newEnd
= addMinutes(cloneDate(event
.end
), minuteDelta
);
4137 timeElement
.text(formatDates(newStart
, newEnd
, opt('timeFormat')));
4139 function resetElement() {
4140 // convert back to original slot-event
4142 timeElement
.css('display', ''); // show() was causing display=inline
4143 eventElement
.draggable('option', 'grid', [colWidth
, slotHeight
]);
4152 --------------------------------------------------------------------------------------*/
4155 function resizableSlotEvent(event
, eventElement
, timeElement
) {
4156 var slotDelta
, prevSlotDelta
;
4157 var slotHeight
= getSlotHeight();
4158 eventElement
.resizable({
4160 s
: 'div.ui-resizable-s'
4163 start: function(ev
, ui
) {
4164 slotDelta
= prevSlotDelta
= 0;
4165 hideEvents(event
, eventElement
);
4166 eventElement
.css('z-index', 9);
4167 trigger('eventResizeStart', this, event
, ev
, ui
);
4169 resize: function(ev
, ui
) {
4170 // don't rely on ui.size.height, doesn't take grid into account
4171 slotDelta
= Math
.round((Math
.max(slotHeight
, eventElement
.height()) - ui
.originalSize
.height
) / slotHeight
);
4172 if (slotDelta
!= prevSlotDelta
) {
4176 (!slotDelta
&& !event
.end
) ? null : // no change, so don't display time range
4177 addMinutes(eventEnd(event
), opt('slotMinutes')*slotDelta
),
4181 prevSlotDelta
= slotDelta
;
4184 stop: function(ev
, ui
) {
4185 trigger('eventResizeStop', this, event
, ev
, ui
);
4187 eventResize(this, event
, 0, opt('slotMinutes')*slotDelta
, ev
, ui
);
4189 eventElement
.css('z-index', 8);
4190 showEvents(event
, eventElement
);
4191 // BUG: if event was really short, need to put title back in span
4201 function countForwardSegs(levels
) {
4202 var i
, j
, k
, level
, segForward
, segBack
;
4203 for (i
=levels
.length
-1; i
>0; i
--) {
4205 for (j
=0; j
<level
.length
; j
++) {
4206 segForward
= level
[j
];
4207 for (k
=0; k
<levels
[i
-1].length
; k
++) {
4208 segBack
= levels
[i
-1][k
];
4209 if (segsCollide(segForward
, segBack
)) {
4210 segBack
.forward
= Math
.max(segBack
.forward
||0, (segForward
.forward
||0)+1);
4220 function View(element
, calendar
, viewName
) {
4225 t
.element
= element
;
4226 t
.calendar
= calendar
;
4229 t
.trigger
= trigger
;
4230 //t.setOverflowHidden = setOverflowHidden;
4231 t
.isEventDraggable
= isEventDraggable
;
4232 t
.isEventResizable
= isEventResizable
;
4233 t
.reportEvents
= reportEvents
;
4234 t
.eventEnd
= eventEnd
;
4235 t
.reportEventElement
= reportEventElement
;
4236 t
.reportEventClear
= reportEventClear
;
4237 t
.eventElementHandlers
= eventElementHandlers
;
4238 t
.showEvents
= showEvents
;
4239 t
.hideEvents
= hideEvents
;
4240 t
.eventDrop
= eventDrop
;
4241 t
.eventResize
= eventResize
;
4244 // t.visStart, t.visEnd
4248 var defaultEventEnd
= t
.defaultEventEnd
;
4249 var normalizeEvent
= calendar
.normalizeEvent
; // in EventManager
4250 var reportEventChange
= calendar
.reportEventChange
;
4254 var eventsByID
= {};
4255 var eventElements
= [];
4256 var eventElementsByID
= {};
4257 var options
= calendar
.options
;
4261 function opt(name
, viewNameOverride
) {
4262 var v
= options
[name
];
4263 if (typeof v
== 'object') {
4264 return smartProperty(v
, viewNameOverride
|| viewName
);
4270 function trigger(name
, thisObj
) {
4271 return calendar
.trigger
.apply(
4273 [name
, thisObj
|| t
].concat(Array
.prototype.slice
.call(arguments
, 2), [t
])
4279 function setOverflowHidden(bool) {
4280 element.css('overflow', bool ? 'hidden' : '');
4285 function isEventDraggable(event
) {
4286 return isEventEditable(event
) && !opt('disableDragging');
4290 function isEventResizable(event
) { // but also need to make sure the seg.isEnd == true
4291 return isEventEditable(event
) && !opt('disableResizing');
4295 function isEventEditable(event
) {
4296 return firstDefined(event
.editable
, (event
.source
|| {}).editable
, opt('editable'));
4302 ------------------------------------------------------------------------------*/
4305 // report when view receives new events
4306 function reportEvents(events
) { // events are already normalized at this point
4308 var i
, len
=events
.length
, event
;
4309 for (i
=0; i
<len
; i
++) {
4311 if (eventsByID
[event
._id
]) {
4312 eventsByID
[event
._id
].push(event
);
4314 eventsByID
[event
._id
] = [event
];
4320 // returns a Date object for an event's end
4321 function eventEnd(event
) {
4322 return event
.end
? cloneDate(event
.end
) : defaultEventEnd(event
);
4328 ------------------------------------------------------------------------------*/
4331 // report when view creates an element for an event
4332 function reportEventElement(event
, element
) {
4333 eventElements
.push(element
);
4334 if (eventElementsByID
[event
._id
]) {
4335 eventElementsByID
[event
._id
].push(element
);
4337 eventElementsByID
[event
._id
] = [element
];
4342 function reportEventClear() {
4344 eventElementsByID
= {};
4348 // attaches eventClick, eventMouseover, eventMouseout
4349 function eventElementHandlers(event
, eventElement
) {
4351 .click(function(ev
) {
4352 if (!eventElement
.hasClass('ui-draggable-dragging') &&
4353 !eventElement
.hasClass('ui-resizable-resizing')) {
4354 return trigger('eventClick', this, event
, ev
);
4359 trigger('eventMouseover', this, event
, ev
);
4362 trigger('eventMouseout', this, event
, ev
);
4365 // TODO: don't fire eventMouseover/eventMouseout *while* dragging is occuring (on subject element)
4366 // TODO: same for resizing
4370 function showEvents(event
, exceptElement
) {
4371 eachEventElement(event
, exceptElement
, 'show');
4375 function hideEvents(event
, exceptElement
) {
4376 eachEventElement(event
, exceptElement
, 'hide');
4380 function eachEventElement(event
, exceptElement
, funcName
) {
4381 var elements
= eventElementsByID
[event
._id
],
4382 i
, len
= elements
.length
;
4383 for (i
=0; i
<len
; i
++) {
4384 if (!exceptElement
|| elements
[i
][0] != exceptElement
[0]) {
4385 elements
[i
][funcName
]();
4392 /* Event Modification Reporting
4393 ---------------------------------------------------------------------------------*/
4396 function eventDrop(e
, event
, dayDelta
, minuteDelta
, allDay
, ev
, ui
) {
4397 var oldAllDay
= event
.allDay
;
4398 var eventId
= event
._id
;
4399 moveEvents(eventsByID
[eventId
], dayDelta
, minuteDelta
, allDay
);
4408 // TODO: investigate cases where this inverse technique might not work
4409 moveEvents(eventsByID
[eventId
], -dayDelta
, -minuteDelta
, oldAllDay
);
4410 reportEventChange(eventId
);
4415 reportEventChange(eventId
);
4419 function eventResize(e
, event
, dayDelta
, minuteDelta
, ev
, ui
) {
4420 var eventId
= event
._id
;
4421 elongateEvents(eventsByID
[eventId
], dayDelta
, minuteDelta
);
4429 // TODO: investigate cases where this inverse technique might not work
4430 elongateEvents(eventsByID
[eventId
], -dayDelta
, -minuteDelta
);
4431 reportEventChange(eventId
);
4436 reportEventChange(eventId
);
4441 /* Event Modification Math
4442 ---------------------------------------------------------------------------------*/
4445 function moveEvents(events
, dayDelta
, minuteDelta
, allDay
) {
4446 minuteDelta
= minuteDelta
|| 0;
4447 for (var e
, len
=events
.length
, i
=0; i
<len
; i
++) {
4449 if (allDay
!== undefined) {
4452 addMinutes(addDays(e
.start
, dayDelta
, true), minuteDelta
);
4454 e
.end
= addMinutes(addDays(e
.end
, dayDelta
, true), minuteDelta
);
4456 normalizeEvent(e
, options
);
4461 function elongateEvents(events
, dayDelta
, minuteDelta
) {
4462 minuteDelta
= minuteDelta
|| 0;
4463 for (var e
, len
=events
.length
, i
=0; i
<len
; i
++) {
4465 e
.end
= addMinutes(addDays(eventEnd(e
), dayDelta
, true), minuteDelta
);
4466 normalizeEvent(e
, options
);
4473 function DayEventRenderer() {
4478 t
.renderDaySegs
= renderDaySegs
;
4479 t
.resizableDayEvent
= resizableDayEvent
;
4484 var trigger
= t
.trigger
;
4485 var isEventDraggable
= t
.isEventDraggable
;
4486 var isEventResizable
= t
.isEventResizable
;
4487 var eventEnd
= t
.eventEnd
;
4488 var reportEventElement
= t
.reportEventElement
;
4489 var showEvents
= t
.showEvents
;
4490 var hideEvents
= t
.hideEvents
;
4491 var eventResize
= t
.eventResize
;
4492 var getRowCnt
= t
.getRowCnt
;
4493 var getColCnt
= t
.getColCnt
;
4494 var getColWidth
= t
.getColWidth
;
4495 var allDayRow
= t
.allDayRow
;
4496 var allDayBounds
= t
.allDayBounds
;
4497 var colContentLeft
= t
.colContentLeft
;
4498 var colContentRight
= t
.colContentRight
;
4499 var dayOfWeekCol
= t
.dayOfWeekCol
;
4500 var dateCell
= t
.dateCell
;
4501 var compileDaySegs
= t
.compileDaySegs
;
4502 var getDaySegmentContainer
= t
.getDaySegmentContainer
;
4503 var bindDaySeg
= t
.bindDaySeg
; //TODO: streamline this
4504 var formatDates
= t
.calendar
.formatDates
;
4505 var renderDayOverlay
= t
.renderDayOverlay
;
4506 var clearOverlays
= t
.clearOverlays
;
4507 var clearSelection
= t
.clearSelection
;
4512 -----------------------------------------------------------------------------*/
4515 function renderDaySegs(segs
, modifiedEventId
) {
4516 var segmentContainer
= getDaySegmentContainer();
4518 var rowCnt
= getRowCnt();
4519 var colCnt
= getColCnt();
4525 var segCnt
= segs
.length
;
4529 segmentContainer
[0].innerHTML
= daySegHTML(segs
); // faster than .html()
4530 daySegElementResolve(segs
, segmentContainer
.children());
4531 daySegElementReport(segs
);
4532 daySegHandlers(segs
, segmentContainer
, modifiedEventId
);
4533 daySegCalcHSides(segs
);
4534 daySegSetWidths(segs
);
4535 daySegCalcHeights(segs
);
4536 rowDivs
= getRowDivs();
4537 // set row heights, calculate event tops (in relation to row top)
4538 for (rowI
=0; rowI
<rowCnt
; rowI
++) {
4541 for (j
=0; j
<colCnt
; j
++) {
4544 while (i
<segCnt
&& (seg
= segs
[i
]).row
== rowI
) {
4545 // loop through segs in a row
4546 top
= arrayMax(colHeights
.slice(seg
.startCol
, seg
.endCol
));
4548 top
+= seg
.outerHeight
;
4549 for (k
=seg
.startCol
; k
<seg
.endCol
; k
++) {
4550 colHeights
[k
] = top
;
4554 rowDivs
[rowI
].height(arrayMax(colHeights
));
4556 daySegSetTops(segs
, getRowTops(rowDivs
));
4560 function renderTempDaySegs(segs
, adjustRow
, adjustTop
) {
4561 var tempContainer
= $("<div/>");
4563 var segmentContainer
= getDaySegmentContainer();
4565 var segCnt
= segs
.length
;
4567 tempContainer
[0].innerHTML
= daySegHTML(segs
); // faster than .html()
4568 elements
= tempContainer
.children();
4569 segmentContainer
.append(elements
);
4570 daySegElementResolve(segs
, elements
);
4571 daySegCalcHSides(segs
);
4572 daySegSetWidths(segs
);
4573 daySegCalcHeights(segs
);
4574 daySegSetTops(segs
, getRowTops(getRowDivs()));
4576 for (i
=0; i
<segCnt
; i
++) {
4577 element
= segs
[i
].element
;
4579 if (segs
[i
].row
=== adjustRow
) {
4580 element
.css('top', adjustTop
);
4582 elements
.push(element
[0]);
4589 function daySegHTML(segs
) { // also sets seg.left and seg.outerWidth
4590 var rtl
= opt('isRTL');
4592 var segCnt
=segs
.length
;
4597 var bounds
= allDayBounds();
4598 var minLeft
= bounds
.left
;
4599 var maxLeft
= bounds
.right
;
4606 // calculate desired position/dimensions, create html
4607 for (i
=0; i
<segCnt
; i
++) {
4610 classes
= ['fc-event', 'fc-event-skin', 'fc-event-hori'];
4611 if (isEventDraggable(event
)) {
4612 classes
.push('fc-event-draggable');
4616 classes
.push('fc-corner-right');
4619 classes
.push('fc-corner-left');
4621 leftCol
= dayOfWeekCol(seg
.end
.getDay()-1);
4622 rightCol
= dayOfWeekCol(seg
.start
.getDay());
4623 left
= seg
.isEnd
? colContentLeft(leftCol
) : minLeft
;
4624 right
= seg
.isStart
? colContentRight(rightCol
) : maxLeft
;
4627 classes
.push('fc-corner-left');
4630 classes
.push('fc-corner-right');
4632 leftCol
= dayOfWeekCol(seg
.start
.getDay());
4633 rightCol
= dayOfWeekCol(seg
.end
.getDay()-1);
4634 left
= seg
.isStart
? colContentLeft(leftCol
) : minLeft
;
4635 right
= seg
.isEnd
? colContentRight(rightCol
) : maxLeft
;
4637 classes
= classes
.concat(event
.className
);
4639 classes
= classes
.concat(event
.source
.className
|| []);
4642 skinCss
= getSkinCss(event
, opt
);
4644 html
+= "<a href='" + htmlEscape(url
) + "'";
4649 " class='" + classes
.join(' ') + "'" +
4650 " style='position:absolute;z-index:8;left:"+left
+"px;" + skinCss
+ "'" +
4653 " class='fc-event-inner fc-event-skin'" +
4654 (skinCss
? " style='" + skinCss
+ "'" : '') +
4656 if (!event
.allDay
&& seg
.isStart
) {
4658 "<span class='fc-event-time'>" +
4659 htmlEscape(formatDates(event
.start
, event
.end
, opt('timeFormat'))) +
4663 "<span class='fc-event-title'>" + htmlEscape(event
.title
) + "</span>" +
4665 if (seg
.isEnd
&& isEventResizable(event
)) {
4667 "<div class='ui-resizable-handle ui-resizable-" + (rtl
? 'w' : 'e') + "'>" +
4668 " " + // makes hit area a lot better for IE6/7
4672 "</" + (url
? "a" : "div" ) + ">";
4674 seg
.outerWidth
= right
- left
;
4675 seg
.startCol
= leftCol
;
4676 seg
.endCol
= rightCol
+ 1; // needs to be exclusive
4682 function daySegElementResolve(segs
, elements
) { // sets seg.element
4684 var segCnt
= segs
.length
;
4689 for (i
=0; i
<segCnt
; i
++) {
4692 element
= $(elements
[i
]); // faster than .eq()
4693 triggerRes
= trigger('eventRender', event
, event
, element
);
4694 if (triggerRes
=== false) {
4697 if (triggerRes
&& triggerRes
!== true) {
4698 triggerRes
= $(triggerRes
)
4700 position
: 'absolute',
4703 element
.replaceWith(triggerRes
);
4704 element
= triggerRes
;
4706 seg
.element
= element
;
4712 function daySegElementReport(segs
) {
4714 var segCnt
= segs
.length
;
4717 for (i
=0; i
<segCnt
; i
++) {
4719 element
= seg
.element
;
4721 reportEventElement(seg
.event
, element
);
4727 function daySegHandlers(segs
, segmentContainer
, modifiedEventId
) {
4729 var segCnt
= segs
.length
;
4733 // retrieve elements, run through eventRender callback, bind handlers
4734 for (i
=0; i
<segCnt
; i
++) {
4736 element
= seg
.element
;
4739 if (event
._id
=== modifiedEventId
) {
4740 bindDaySeg(event
, element
, seg
);
4742 element
[0]._fci
= i
; // for lazySegBind
4746 lazySegBind(segmentContainer
, segs
, bindDaySeg
);
4750 function daySegCalcHSides(segs
) { // also sets seg.key
4752 var segCnt
= segs
.length
;
4756 var hsideCache
= {};
4757 // record event horizontal sides
4758 for (i
=0; i
<segCnt
; i
++) {
4760 element
= seg
.element
;
4762 key
= seg
.key
= cssKey(element
[0]);
4763 val
= hsideCache
[key
];
4764 if (val
=== undefined) {
4765 val
= hsideCache
[key
] = hsides(element
, true);
4773 function daySegSetWidths(segs
) {
4775 var segCnt
= segs
.length
;
4778 for (i
=0; i
<segCnt
; i
++) {
4780 element
= seg
.element
;
4782 element
[0].style
.width
= Math
.max(0, seg
.outerWidth
- seg
.hsides
) + 'px';
4788 function daySegCalcHeights(segs
) {
4790 var segCnt
= segs
.length
;
4794 var vmarginCache
= {};
4795 // record event heights
4796 for (i
=0; i
<segCnt
; i
++) {
4798 element
= seg
.element
;
4800 key
= seg
.key
; // created in daySegCalcHSides
4801 val
= vmarginCache
[key
];
4802 if (val
=== undefined) {
4803 val
= vmarginCache
[key
] = vmargins(element
);
4805 seg
.outerHeight
= element
[0].offsetHeight
+ val
;
4811 function getRowDivs() {
4813 var rowCnt
= getRowCnt();
4815 for (i
=0; i
<rowCnt
; i
++) {
4816 rowDivs
[i
] = allDayRow(i
)
4817 .find('td:first div.fc-day-content > div'); // optimal selector?
4823 function getRowTops(rowDivs
) {
4825 var rowCnt
= rowDivs
.length
;
4827 for (i
=0; i
<rowCnt
; i
++) {
4828 tops
[i
] = rowDivs
[i
][0].offsetTop
; // !!?? but this means the element needs position:relative if in a table cell!!!!
4834 function daySegSetTops(segs
, rowTops
) { // also triggers eventAfterRender
4836 var segCnt
= segs
.length
;
4840 for (i
=0; i
<segCnt
; i
++) {
4842 element
= seg
.element
;
4844 element
[0].style
.top
= rowTops
[seg
.row
] + (seg
.top
||0) + 'px';
4846 trigger('eventAfterRender', event
, event
, element
);
4854 -----------------------------------------------------------------------------------*/
4857 function resizableDayEvent(event
, element
, seg
) {
4858 var rtl
= opt('isRTL');
4859 var direction
= rtl
? 'w' : 'e';
4860 var handle
= element
.find('div.ui-resizable-' + direction
);
4861 var isResizing
= false;
4863 // TODO: look into using jquery-ui mouse widget for this stuff
4864 disableTextSelection(element
); // prevent native <a> selection for IE
4866 .mousedown(function(ev
) { // prevent native <a> selection for others
4867 ev
.preventDefault();
4869 .click(function(ev
) {
4871 ev
.preventDefault(); // prevent link from being visited (only method that worked in IE6)
4872 ev
.stopImmediatePropagation(); // prevent fullcalendar eventClick handler from being called
4873 // (eventElementHandlers needs to be bound after resizableDayEvent)
4877 handle
.mousedown(function(ev
) {
4878 if (ev
.which
!= 1) {
4879 return; // needs to be left mouse button
4882 var hoverListener
= t
.getHoverListener();
4883 var rowCnt
= getRowCnt();
4884 var colCnt
= getColCnt();
4885 var dis
= rtl
? -1 : 1;
4886 var dit
= rtl
? colCnt
-1 : 0;
4887 var elementTop
= element
.css('top');
4890 var eventCopy
= $.extend({}, event
);
4891 var minCell
= dateCell(event
.start
);
4894 .css('cursor', direction
+ '-resize')
4895 .one('mouseup', mouseup
);
4896 trigger('eventResizeStart', this, event
, ev
);
4897 hoverListener
.start(function(cell
, origCell
) {
4899 var r
= Math
.max(minCell
.row
, cell
.row
);
4902 r
= 0; // hack for all-day area in agenda views
4904 if (r
== minCell
.row
) {
4906 c
= Math
.min(minCell
.col
, c
);
4908 c
= Math
.max(minCell
.col
, c
);
4911 dayDelta
= (r
*7 + c
*dis
+dit
) - (origCell
.row
*7 + origCell
.col
*dis
+dit
);
4912 var newEnd
= addDays(eventEnd(event
), dayDelta
, true);
4914 eventCopy
.end
= newEnd
;
4915 var oldHelpers
= helpers
;
4916 helpers
= renderTempDaySegs(compileDaySegs([eventCopy
]), seg
.row
, elementTop
);
4917 helpers
.find('*').css('cursor', direction
+ '-resize');
4919 oldHelpers
.remove();
4930 renderDayOverlay(event
.start
, addDays(cloneDate(newEnd
), 1)); // coordinate grid already rebuild at hoverListener.start
4934 function mouseup(ev
) {
4935 trigger('eventResizeStop', this, event
, ev
);
4936 $('body').css('cursor', '');
4937 hoverListener
.stop();
4940 eventResize(this, event
, dayDelta
, 0, ev
);
4941 // event redraw will clear helpers
4943 // otherwise, the drag handler already restored the old events
4945 setTimeout(function() { // make this happen after the element's click event
4956 //BUG: unselect needs to be triggered when events are dragged+dropped
4958 function SelectionManager() {
4964 t
.unselect
= unselect
;
4965 t
.reportSelection
= reportSelection
;
4966 t
.daySelectionMousedown
= daySelectionMousedown
;
4971 var trigger
= t
.trigger
;
4972 var defaultSelectionEnd
= t
.defaultSelectionEnd
;
4973 var renderSelection
= t
.renderSelection
;
4974 var clearSelection
= t
.clearSelection
;
4978 var selected
= false;
4983 if (opt('selectable') && opt('unselectAuto')) {
4984 $(document
).mousedown(function(ev
) {
4985 var ignore
= opt('unselectCancel');
4987 if ($(ev
.target
).parents(ignore
).length
) { // could be optimized to stop after first match
4996 function select(startDate
, endDate
, allDay
) {
4999 endDate
= defaultSelectionEnd(startDate
, allDay
);
5001 renderSelection(startDate
, endDate
, allDay
);
5002 reportSelection(startDate
, endDate
, allDay
);
5006 function unselect(ev
) {
5010 trigger('unselect', null, ev
);
5015 function reportSelection(startDate
, endDate
, allDay
, ev
) {
5017 trigger('select', null, startDate
, endDate
, allDay
, ev
);
5021 function daySelectionMousedown(ev
) { // not really a generic manager method, oh well
5022 var cellDate
= t
.cellDate
;
5023 var cellIsAllDay
= t
.cellIsAllDay
;
5024 var hoverListener
= t
.getHoverListener();
5025 var reportDayClick
= t
.reportDayClick
; // this is hacky and sort of weird
5026 if (ev
.which
== 1 && opt('selectable')) { // which==1 means left mouse button
5028 var _mousedownElement
= this;
5030 hoverListener
.start(function(cell
, origCell
) { // TODO: maybe put cellDate/cellIsAllDay info in cell
5032 if (cell
&& cellIsAllDay(cell
)) {
5033 dates
= [ cellDate(origCell
), cellDate(cell
) ].sort(cmp
);
5034 renderSelection(dates
[0], dates
[1], true);
5039 $(document
).one('mouseup', function(ev
) {
5040 hoverListener
.stop();
5042 if (+dates
[0] == +dates
[1]) {
5043 reportDayClick(dates
[0], true, ev
);
5045 reportSelection(dates
[0], dates
[1], true, ev
);
5054 function OverlayManager() {
5059 t
.renderOverlay
= renderOverlay
;
5060 t
.clearOverlays
= clearOverlays
;
5064 var usedOverlays
= [];
5065 var unusedOverlays
= [];
5068 function renderOverlay(rect
, parent
) {
5069 var e
= unusedOverlays
.shift();
5071 e
= $("<div class='fc-cell-overlay' style='position:absolute;z-index:3'/>");
5073 if (e
[0].parentNode
!= parent
[0]) {
5076 usedOverlays
.push(e
.css(rect
).show());
5081 function clearOverlays() {
5083 while (e
= usedOverlays
.shift()) {
5084 unusedOverlays
.push(e
.hide().unbind());
5091 function CoordinateGrid(buildFunc
) {
5098 t
.build = function() {
5101 buildFunc(rows
, cols
);
5105 t
.cell = function(x
, y
) {
5106 var rowCnt
= rows
.length
;
5107 var colCnt
= cols
.length
;
5109 for (i
=0; i
<rowCnt
; i
++) {
5110 if (y
>= rows
[i
][0] && y
< rows
[i
][1]) {
5115 for (i
=0; i
<colCnt
; i
++) {
5116 if (x
>= cols
[i
][0] && x
< cols
[i
][1]) {
5121 return (r
>=0 && c
>=0) ? { row
:r
, col
:c
} : null;
5125 t
.rect = function(row0
, col0
, row1
, col1
, originElement
) { // row1,col1 is inclusive
5126 var origin
= originElement
.offset();
5128 top
: rows
[row0
][0] - origin
.top
,
5129 left
: cols
[col0
][0] - origin
.left
,
5130 width
: cols
[col1
][1] - cols
[col0
][0],
5131 height
: rows
[row1
][1] - rows
[row0
][0]
5137 function HoverListener(coordinateGrid
) {
5147 t
.start = function(_change
, ev
, _bindType
) {
5149 firstCell
= cell
= null;
5150 coordinateGrid
.build();
5152 bindType
= _bindType
|| 'mousemove';
5153 $(document
).bind(bindType
, mouse
);
5157 function mouse(ev
) {
5158 var newCell
= coordinateGrid
.cell(ev
.pageX
, ev
.pageY
);
5159 if (!newCell
!= !cell
|| newCell
&& (newCell
.row
!= cell
.row
|| newCell
.col
!= cell
.col
)) {
5162 firstCell
= newCell
;
5164 change(newCell
, firstCell
, newCell
.row
-firstCell
.row
, newCell
.col
-firstCell
.col
);
5166 change(newCell
, firstCell
);
5173 t
.stop = function() {
5174 $(document
).unbind(bindType
, mouse
);
5181 function HorizontalPositionCache(getElement
) {
5189 return elements
[i
] = elements
[i
] || getElement(i
);
5192 t
.left = function(i
) {
5193 return lefts
[i
] = lefts
[i
] === undefined ? e(i
).position().left
: lefts
[i
];
5196 t
.right = function(i
) {
5197 return rights
[i
] = rights
[i
] === undefined ? t
.left(i
) + e(i
).width() : rights
[i
];
5200 t
.clear = function() {