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: Sun Aug 21 22:06:09 2011 -0700
18 (function($, undefined) {
29 right
: 'today prev,next'
35 //disableDragging: false,
36 //disableResizing: false,
49 week
: "d MMMM [ yyyy]{ '—'d [ MMMM] yyyy}",
50 day
: 'dddd d MMMM yyyy'
57 timeFormat
: { // for event elements
58 '': 'H(:mm)' // default
65 monthNames
: ['Janvier','F\351vrier','Mars','Avril','Mai','Juin','Juillet','Ao\373t','Septembre','Octobre','Novembre','D\351cembre'],
66 monthNamesShort
: ['Jan','F\351v','Mar','Avr','Mai','Jun','Jui','Ao\373','Sep','Oct','Nov','D\351c'],
67 dayNames
: ['Dimanche','Lundi','Mardi','Mercredi','Jeudi','Vendredi','Samedi'],
68 dayNamesShort
: ['Dim','Lun','Mar','Mer','Jeu','Ven','Sam'],
70 prev
: ' ◄ ',
71 next
: ' ► ',
72 prevYear
: ' << ',
73 nextYear
: ' >> ',
74 today
: 'Aujourd’hui',
83 prev
: 'circle-triangle-w',
84 next
: 'circle-triangle-e'
94 // right-to-left defaults
97 left
: 'next,prev today',
102 prev
: ' ► ',
103 next
: ' ◄ ',
104 prevYear
: ' >> ',
105 nextYear
: ' << '
108 prev
: 'circle-triangle-e',
109 next
: 'circle-triangle-w'
115 var fc
= $.fullCalendar
= { version
: "1.5.2" };
116 var fcViews
= fc
.views
= {};
119 $.fn
.fullCalendar = function(options
) {
123 if (typeof options
== 'string') {
124 var args
= Array
.prototype.slice
.call(arguments
, 1);
126 this.each(function() {
127 var calendar
= $.data(this, 'fullCalendar');
128 if (calendar
&& $.isFunction(calendar
[options
])) {
129 var r
= calendar
[options
].apply(calendar
, args
);
130 if (res
=== undefined) {
133 if (options
== 'destroy') {
134 $.removeData(this, 'fullCalendar');
138 if (res
!== undefined) {
145 // would like to have this logic in EventManager, but needs to happen before options are recursively extended
146 var eventSources
= options
.eventSources
|| [];
147 delete options
.eventSources
;
148 if (options
.events
) {
149 eventSources
.push(options
.events
);
150 delete options
.events
;
154 options
= $.extend(true, {},
156 (options
.isRTL
|| options
.isRTL
===undefined && defaults
.isRTL
) ? rtlDefaults
: {},
161 this.each(function(i
, _element
) {
162 var element
= $(_element
);
163 var calendar
= new Calendar(element
, options
, eventSources
);
164 element
.data('fullCalendar', calendar
); // TODO: look into memory leak implications
174 // function for adding/overriding defaults
175 function setDefaults(d
) {
176 $.extend(true, defaults
, d
);
182 function Calendar(element
, options
, eventSources
) {
190 t
.refetchEvents
= refetchEvents
;
191 t
.reportEvents
= reportEvents
;
192 t
.reportEventChange
= reportEventChange
;
193 t
.rerenderEvents
= rerenderEvents
;
194 t
.changeView
= changeView
;
196 t
.unselect
= unselect
;
199 t
.prevYear
= prevYear
;
200 t
.nextYear
= nextYear
;
202 t
.gotoDate
= gotoDate
;
203 t
.incrementDate
= incrementDate
;
204 t
.formatDate = function(format
, date
) { return formatDate(format
, date
, options
) };
205 t
.formatDates = function(format
, date1
, date2
) { return formatDates(format
, date1
, date2
, options
) };
213 EventManager
.call(t
, options
, eventSources
);
214 var isFetchNeeded
= t
.isFetchNeeded
;
215 var fetchEvents
= t
.fetchEvents
;
219 var _element
= element
[0];
223 var tm
; // for making theme classes
225 var viewInstances
= {};
226 var elementOuterWidth
;
227 var suggestedViewHeight
;
228 var absoluteViewElement
;
230 var ignoreWindowResize
= 0;
231 var date
= new Date();
238 -----------------------------------------------------------------------------*/
241 setYMD(date
, options
.year
, options
.month
, options
.date
);
244 function render(inc
) {
256 function initialRender() {
257 tm
= options
.theme
? 'ui' : 'fc';
258 element
.addClass('fc');
260 element
.addClass('fc-rtl');
263 element
.addClass('ui-widget');
265 content
= $("<div class='fc-content' style='position:relative'/>")
267 header
= new Header(t
, options
);
268 headerElement
= header
.render();
270 element
.prepend(headerElement
);
272 changeView(options
.defaultView
);
273 $(window
).resize(windowResize
);
274 // needed for IE in a 0x0 iframe, b/c when it is resized, never triggers a windowResize
275 if (!bodyVisible()) {
281 // called when we know the calendar couldn't be rendered when it was initialized,
282 // but we think it's ready now
283 function lateRender() {
284 setTimeout(function() { // IE7 needs this so dimensions are calculated correctly
285 if (!currentView
.start
&& bodyVisible()) { // !currentView.start makes sure this never happens more than once
293 $(window
).unbind('resize', windowResize
);
296 element
.removeClass('fc fc-rtl ui-widget');
301 function elementVisible() {
302 return _element
.offsetWidth
!== 0;
306 function bodyVisible() {
307 return $('body')[0].offsetWidth
!== 0;
313 -----------------------------------------------------------------------------*/
315 // TODO: improve view switching (still weird transition in IE, and FF has whiteout problem)
317 function changeView(newViewName
) {
318 if (!currentView
|| newViewName
!= currentView
.name
) {
319 ignoreWindowResize
++; // because setMinHeight might change the height before render (and subsequently setSize) is reached
323 var oldView
= currentView
;
327 (oldView
.beforeHide
|| noop
)(); // called before changing min-height. if called after, scroll state is reset (in Opera)
328 setMinHeight(content
, content
.height());
329 oldView
.element
.hide();
331 setMinHeight(content
, 1); // needs to be 1 (not 0) for IE7, or else view dimensions miscalculated
333 content
.css('overflow', 'hidden');
335 currentView
= viewInstances
[newViewName
];
337 currentView
.element
.show();
339 currentView
= viewInstances
[newViewName
] = new fcViews
[newViewName
](
340 newViewElement
= absoluteViewElement
=
341 $("<div class='fc-view fc-view-" + newViewName
+ "' style='position:absolute'/>")
343 t
// the calendar object
348 header
.deactivateButton(oldView
.name
);
350 header
.activateButton(newViewName
);
352 renderView(); // after height has been set, will make absoluteViewElement's position=relative, then set to null
354 content
.css('overflow', '');
356 setMinHeight(content
, 1);
359 if (!newViewElement
) {
360 (currentView
.afterShow
|| noop
)(); // called after setting min-height/overflow, so in final scroll state (for Opera)
363 ignoreWindowResize
--;
369 function renderView(inc
) {
370 if (elementVisible()) {
371 ignoreWindowResize
++; // because renderEvents might temporarily change the height before setSize is reached
375 if (suggestedViewHeight
=== undefined) {
379 var forceEventRender
= false;
380 if (!currentView
.start
|| inc
|| date
< currentView
.start
|| date
>= currentView
.end
) {
381 // view must render an entire new date range (and refetch/render events)
382 currentView
.render(date
, inc
|| 0); // responsible for clearing events
384 forceEventRender
= true;
386 else if (currentView
.sizeDirty
) {
387 // view must resize (and rerender events)
388 currentView
.clearEvents();
390 forceEventRender
= true;
392 else if (currentView
.eventsDirty
) {
393 currentView
.clearEvents();
394 forceEventRender
= true;
396 currentView
.sizeDirty
= false;
397 currentView
.eventsDirty
= false;
398 updateEvents(forceEventRender
);
400 elementOuterWidth
= element
.outerWidth();
402 header
.updateTitle(currentView
.title
);
403 var today
= new Date();
404 if (today
>= currentView
.start
&& today
< currentView
.end
) {
405 header
.disableButton('today');
407 header
.enableButton('today');
410 ignoreWindowResize
--;
411 currentView
.trigger('viewDisplay', _element
);
418 -----------------------------------------------------------------------------*/
421 function updateSize() {
423 if (elementVisible()) {
427 currentView
.clearEvents();
428 currentView
.renderEvents(events
);
429 currentView
.sizeDirty
= false;
434 function markSizesDirty() {
435 $.each(viewInstances
, function(i
, inst
) {
436 inst
.sizeDirty
= true;
441 function calcSize() {
442 if (options
.contentHeight
) {
443 suggestedViewHeight
= options
.contentHeight
;
445 else if (options
.height
) {
446 suggestedViewHeight
= options
.height
- (headerElement
? headerElement
.height() : 0) - vsides(content
);
449 suggestedViewHeight
= Math
.round(content
.width() / Math
.max(options
.aspectRatio
, .5));
454 function setSize(dateChanged
) { // todo: dateChanged?
455 ignoreWindowResize
++;
456 currentView
.setHeight(suggestedViewHeight
, dateChanged
);
457 if (absoluteViewElement
) {
458 absoluteViewElement
.css('position', 'relative');
459 absoluteViewElement
= null;
461 currentView
.setWidth(content
.width(), dateChanged
);
462 ignoreWindowResize
--;
466 function windowResize() {
467 if (!ignoreWindowResize
) {
468 if (currentView
.start
) { // view has already been rendered
469 var uid
= ++resizeUID
;
470 setTimeout(function() { // add a delay
471 if (uid
== resizeUID
&& !ignoreWindowResize
&& elementVisible()) {
472 if (elementOuterWidth
!= (elementOuterWidth
= element
.outerWidth())) {
473 ignoreWindowResize
++; // in case the windowResize callback changes the height
475 currentView
.trigger('windowResize', _element
);
476 ignoreWindowResize
--;
481 // calendar must have been initialized in a 0x0 iframe that has just been resized
489 /* Event Fetching/Rendering
490 -----------------------------------------------------------------------------*/
493 // fetches events if necessary, rerenders events if necessary (or if forced)
494 function updateEvents(forceRender
) {
495 if (!options
.lazyFetching
|| isFetchNeeded(currentView
.visStart
, currentView
.visEnd
)) {
498 else if (forceRender
) {
504 function refetchEvents() {
505 fetchEvents(currentView
.visStart
, currentView
.visEnd
); // will call reportEvents
509 // called when event data arrives
510 function reportEvents(_events
) {
516 // called when a single event's data has been changed
517 function reportEventChange(eventID
) {
518 rerenderEvents(eventID
);
522 // attempts to rerenderEvents
523 function rerenderEvents(modifiedEventID
) {
525 if (elementVisible()) {
526 currentView
.clearEvents();
527 currentView
.renderEvents(events
, modifiedEventID
);
528 currentView
.eventsDirty
= false;
533 function markEventsDirty() {
534 $.each(viewInstances
, function(i
, inst
) {
535 inst
.eventsDirty
= true;
542 -----------------------------------------------------------------------------*/
545 function select(start
, end
, allDay
) {
546 currentView
.select(start
, end
, allDay
===undefined ? true : allDay
);
550 function unselect() { // safe to be called before renderView
552 currentView
.unselect();
559 -----------------------------------------------------------------------------*/
572 function prevYear() {
578 function nextYear() {
590 function gotoDate(year
, month
, dateOfMonth
) {
591 if (year
instanceof Date
) {
592 date
= cloneDate(year
); // provided 1 argument, a Date
594 setYMD(date
, year
, month
, dateOfMonth
);
600 function incrementDate(years
, months
, days
) {
601 if (years
!== undefined) {
602 addYears(date
, years
);
604 if (months
!== undefined) {
605 addMonths(date
, months
);
607 if (days
!== undefined) {
615 return cloneDate(date
);
621 -----------------------------------------------------------------------------*/
629 function option(name
, value
) {
630 if (value
=== undefined) {
631 return options
[name
];
633 if (name
== 'height' || name
== 'contentHeight' || name
== 'aspectRatio') {
634 options
[name
] = value
;
640 function trigger(name
, thisObj
) {
642 return options
[name
].apply(
644 Array
.prototype.slice
.call(arguments
, 2)
652 ------------------------------------------------------------------------*/
654 if (options
.droppable
) {
656 .bind('dragstart', function(ev
, ui
) {
659 if (!e
.parents('.fc').length
) { // not already inside a calendar
660 var accept
= options
.dropAccept
;
661 if ($.isFunction(accept
) ? accept
.call(_e
, e
) : e
.is(accept
)) {
663 currentView
.dragStart(_dragElement
, ev
, ui
);
667 .bind('dragstop', function(ev
, ui
) {
669 currentView
.dragStop(_dragElement
, ev
, ui
);
678 function Header(calendar
, options
) {
685 t
.updateTitle
= updateTitle
;
686 t
.activateButton
= activateButton
;
687 t
.deactivateButton
= deactivateButton
;
688 t
.disableButton
= disableButton
;
689 t
.enableButton
= enableButton
;
699 tm
= options
.theme
? 'ui' : 'fc';
700 var sections
= options
.header
;
702 element
= $("<table class='fc-header' style='width:100%'/>")
705 .append(renderSection('left'))
706 .append(renderSection('center'))
707 .append(renderSection('right'))
719 function renderSection(position
) {
720 var e
= $("<td class='fc-header-" + position
+ "'/>");
721 var buttonStr
= options
.header
[position
];
723 $.each(buttonStr
.split(' '), function(i
) {
725 e
.append("<span class='fc-header-space'/>");
728 $.each(this.split(','), function(j
, buttonName
) {
729 if (buttonName
== 'title') {
730 e
.append("<span class='fc-header-title'><h2> </h2></span>");
732 prevButton
.addClass(tm
+ '-corner-right');
737 if (calendar
[buttonName
]) {
738 buttonClick
= calendar
[buttonName
]; // calendar method
740 else if (fcViews
[buttonName
]) {
741 buttonClick = function() {
742 button
.removeClass(tm
+ '-state-hover'); // forget why
743 calendar
.changeView(buttonName
);
747 var icon
= options
.theme
? smartProperty(options
.buttonIcons
, buttonName
) : null; // why are we using smartProperty here?
748 var text
= smartProperty(options
.buttonText
, buttonName
); // why are we using smartProperty here?
750 "<span class='fc-button fc-button-" + buttonName
+ " " + tm
+ "-state-default'>" +
751 "<span class='fc-button-inner'>" +
752 "<span class='fc-button-content'>" +
754 "<span class='fc-icon-wrap'>" +
755 "<span class='ui-icon ui-icon-" + icon
+ "'/>" +
760 "<span class='fc-button-effect'><span></span></span>" +
767 if (!button
.hasClass(tm
+ '-state-disabled')) {
771 .mousedown(function() {
773 .not('.' + tm
+ '-state-active')
774 .not('.' + tm
+ '-state-disabled')
775 .addClass(tm
+ '-state-down');
777 .mouseup(function() {
778 button
.removeClass(tm
+ '-state-down');
783 .not('.' + tm
+ '-state-active')
784 .not('.' + tm
+ '-state-disabled')
785 .addClass(tm
+ '-state-hover');
789 .removeClass(tm
+ '-state-hover')
790 .removeClass(tm
+ '-state-down');
795 button
.addClass(tm
+ '-corner-left');
803 prevButton
.addClass(tm
+ '-corner-right');
811 function updateTitle(html
) {
817 function activateButton(buttonName
) {
818 element
.find('span.fc-button-' + buttonName
)
819 .addClass(tm
+ '-state-active');
823 function deactivateButton(buttonName
) {
824 element
.find('span.fc-button-' + buttonName
)
825 .removeClass(tm
+ '-state-active');
829 function disableButton(buttonName
) {
830 element
.find('span.fc-button-' + buttonName
)
831 .addClass(tm
+ '-state-disabled');
835 function enableButton(buttonName
) {
836 element
.find('span.fc-button-' + buttonName
)
837 .removeClass(tm
+ '-state-disabled');
843 fc
.sourceNormalizers
= [];
844 fc
.sourceFetchers
= [];
854 function EventManager(options
, _sources
) {
859 t
.isFetchNeeded
= isFetchNeeded
;
860 t
.fetchEvents
= fetchEvents
;
861 t
.addEventSource
= addEventSource
;
862 t
.removeEventSource
= removeEventSource
;
863 t
.updateEvent
= updateEvent
;
864 t
.renderEvent
= renderEvent
;
865 t
.removeEvents
= removeEvents
;
866 t
.clientEvents
= clientEvents
;
867 t
.normalizeEvent
= normalizeEvent
;
871 var trigger
= t
.trigger
;
872 var getView
= t
.getView
;
873 var reportEvents
= t
.reportEvents
;
877 var stickySource
= { events
: [] };
878 var sources
= [ stickySource
];
879 var rangeStart
, rangeEnd
;
880 var currentFetchID
= 0;
881 var pendingSourceCnt
= 0;
882 var loadingLevel
= 0;
886 for (var i
=0; i
<_sources
.length
; i
++) {
887 _addEventSource(_sources
[i
]);
893 -----------------------------------------------------------------------------*/
896 function isFetchNeeded(start
, end
) {
897 return !rangeStart
|| start
< rangeStart
|| end
> rangeEnd
;
901 function fetchEvents(start
, end
) {
905 var fetchID
= ++currentFetchID
;
906 var len
= sources
.length
;
907 pendingSourceCnt
= len
;
908 for (var i
=0; i
<len
; i
++) {
909 fetchEventSource(sources
[i
], fetchID
);
914 function fetchEventSource(source
, fetchID
) {
915 _fetchEventSource(source
, function(events
) {
916 if (fetchID
== currentFetchID
) {
918 for (var i
=0; i
<events
.length
; i
++) {
919 events
[i
].source
= source
;
920 normalizeEvent(events
[i
]);
922 cache
= cache
.concat(events
);
925 if (!pendingSourceCnt
) {
933 function _fetchEventSource(source
, callback
) {
935 var fetchers
= fc
.sourceFetchers
;
937 for (i
=0; i
<fetchers
.length
; i
++) {
938 res
= fetchers
[i
](source
, rangeStart
, rangeEnd
, callback
);
940 // the fetcher is in charge. made its own async request
943 else if (typeof res
== 'object') {
944 // the fetcher returned a new source. process it
945 _fetchEventSource(res
, callback
);
949 var events
= source
.events
;
951 if ($.isFunction(events
)) {
953 events(cloneDate(rangeStart
), cloneDate(rangeEnd
), function(events
) {
958 else if ($.isArray(events
)) {
965 var url
= source
.url
;
967 var success
= source
.success
;
968 var error
= source
.error
;
969 var complete
= source
.complete
;
970 var data
= $.extend({}, source
.data
|| {});
971 var startParam
= firstDefined(source
.startParam
, options
.startParam
);
972 var endParam
= firstDefined(source
.endParam
, options
.endParam
);
974 data
[startParam
] = Math
.round(+rangeStart
/ 1000);
977 data
[endParam
] = Math
.round(+rangeEnd
/ 1000);
980 $.ajax($.extend({}, ajaxDefaults
, source
, {
982 success: function(events
) {
983 events
= events
|| [];
984 var res
= applyAll(success
, this, arguments
);
985 if ($.isArray(res
)) {
991 applyAll(error
, this, arguments
);
994 complete: function() {
995 applyAll(complete
, this, arguments
);
1008 -----------------------------------------------------------------------------*/
1011 function addEventSource(source
) {
1012 source
= _addEventSource(source
);
1015 fetchEventSource(source
, currentFetchID
); // will eventually call reportEvents
1020 function _addEventSource(source
) {
1021 if ($.isFunction(source
) || $.isArray(source
)) {
1022 source
= { events
: source
};
1024 else if (typeof source
== 'string') {
1025 source
= { url
: source
};
1027 if (typeof source
== 'object') {
1028 normalizeSource(source
);
1029 sources
.push(source
);
1035 function removeEventSource(source
) {
1036 sources
= $.grep(sources
, function(src
) {
1037 return !isSourcesEqual(src
, source
);
1039 // remove all client events from that source
1040 cache
= $.grep(cache
, function(e
) {
1041 return !isSourcesEqual(e
.source
, source
);
1043 reportEvents(cache
);
1049 -----------------------------------------------------------------------------*/
1052 function updateEvent(event
) { // update an existing event
1053 var i
, len
= cache
.length
, e
,
1054 defaultEventEnd
= getView().defaultEventEnd
, // getView???
1055 startDelta
= event
.start
- event
._start
,
1056 endDelta
= event
.end
?
1057 (event
.end
- (event
._end
|| defaultEventEnd(event
))) // event._end would be null if event.end
1058 : 0; // was null and event was just resized
1059 for (i
=0; i
<len
; i
++) {
1061 if (e
._id
== event
._id
&& e
!= event
) {
1062 e
.start
= new Date(+e
.start
+ startDelta
);
1065 e
.end
= new Date(+e
.end
+ endDelta
);
1067 e
.end
= new Date(+defaultEventEnd(e
) + endDelta
);
1072 e
.title
= event
.title
;
1074 e
.allDay
= event
.allDay
;
1075 e
.className
= event
.className
;
1076 e
.editable
= event
.editable
;
1077 e
.color
= event
.color
;
1078 e
.backgroudColor
= event
.backgroudColor
;
1079 e
.borderColor
= event
.borderColor
;
1080 e
.textColor
= event
.textColor
;
1084 normalizeEvent(event
);
1085 reportEvents(cache
);
1089 function renderEvent(event
, stick
) {
1090 normalizeEvent(event
);
1091 if (!event
.source
) {
1093 stickySource
.events
.push(event
);
1094 event
.source
= stickySource
;
1098 reportEvents(cache
);
1102 function removeEvents(filter
) {
1103 if (!filter
) { // remove all
1105 // clear all array sources
1106 for (var i
=0; i
<sources
.length
; i
++) {
1107 if ($.isArray(sources
[i
].events
)) {
1108 sources
[i
].events
= [];
1112 if (!$.isFunction(filter
)) { // an event ID
1113 var id
= filter
+ '';
1114 filter = function(e
) {
1118 cache
= $.grep(cache
, filter
, true);
1119 // remove events from array sources
1120 for (var i
=0; i
<sources
.length
; i
++) {
1121 if ($.isArray(sources
[i
].events
)) {
1122 sources
[i
].events
= $.grep(sources
[i
].events
, filter
, true);
1126 reportEvents(cache
);
1130 function clientEvents(filter
) {
1131 if ($.isFunction(filter
)) {
1132 return $.grep(cache
, filter
);
1134 else if (filter
) { // an event ID
1136 return $.grep(cache
, function(e
) {
1137 return e
._id
== filter
;
1140 return cache
; // else, return all
1146 -----------------------------------------------------------------------------*/
1149 function pushLoading() {
1150 if (!loadingLevel
++) {
1151 trigger('loading', null, true);
1156 function popLoading() {
1157 if (!--loadingLevel
) {
1158 trigger('loading', null, false);
1164 /* Event Normalization
1165 -----------------------------------------------------------------------------*/
1168 function normalizeEvent(event
) {
1169 var source
= event
.source
|| {};
1170 var ignoreTimezone
= firstDefined(source
.ignoreTimezone
, options
.ignoreTimezone
);
1171 event
._id
= event
._id
|| (event
.id
=== undefined ? '_fc' + eventGUID
++ : event
.id
+ '');
1174 event
.start
= event
.date
;
1178 event
._start
= cloneDate(event
.start
= parseDate(event
.start
, ignoreTimezone
));
1179 event
.end
= parseDate(event
.end
, ignoreTimezone
);
1180 if (event
.end
&& event
.end
<= event
.start
) {
1183 event
._end
= event
.end
? cloneDate(event
.end
) : null;
1184 if (event
.allDay
=== undefined) {
1185 event
.allDay
= firstDefined(source
.allDayDefault
, options
.allDayDefault
);
1187 if (event
.className
) {
1188 if (typeof event
.className
== 'string') {
1189 event
.className
= event
.className
.split(/\s+/);
1192 event
.className
= [];
1194 // TODO: if there is no start date, return false to indicate an invalid event
1200 ------------------------------------------------------------------------------*/
1203 function normalizeSource(source
) {
1204 if (source
.className
) {
1205 // TODO: repeat code, same code for event classNames
1206 if (typeof source
.className
== 'string') {
1207 source
.className
= source
.className
.split(/\s+/);
1210 source
.className
= [];
1212 var normalizers
= fc
.sourceNormalizers
;
1213 for (var i
=0; i
<normalizers
.length
; i
++) {
1214 normalizers
[i
](source
);
1219 function isSourcesEqual(source1
, source2
) {
1220 return source1
&& source2
&& getSourcePrimitive(source1
) == getSourcePrimitive(source2
);
1224 function getSourcePrimitive(source
) {
1225 return ((typeof source
== 'object') ? (source
.events
|| source
.url
) : '') || source
;
1232 fc
.addDays
= addDays
;
1233 fc
.cloneDate
= cloneDate
;
1234 fc
.parseDate
= parseDate
;
1235 fc
.parseISO8601
= parseISO8601
;
1236 fc
.parseTime
= parseTime
;
1237 fc
.formatDate
= formatDate
;
1238 fc
.formatDates
= formatDates
;
1243 -----------------------------------------------------------------------------*/
1245 var dayIDs
= ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'],
1251 function addYears(d
, n
, keepTime
) {
1252 d
.setFullYear(d
.getFullYear() + n
);
1260 function addMonths(d
, n
, keepTime
) { // prevents day overflow/underflow
1261 if (+d
) { // prevent infinite looping on invalid dates
1262 var m
= d
.getMonth() + n
,
1263 check
= cloneDate(d
);
1270 while (d
.getMonth() != check
.getMonth()) {
1271 d
.setDate(d
.getDate() + (d
< check
? 1 : -1));
1278 function addDays(d
, n
, keepTime
) { // deals with daylight savings
1280 var dd
= d
.getDate() + n
,
1281 check
= cloneDate(d
);
1282 check
.setHours(9); // set to middle of day
1294 function fixDate(d
, check
) { // force d to be on check's YMD, for daylight savings purposes
1295 if (+d
) { // prevent infinite looping on invalid dates
1296 while (d
.getDate() != check
.getDate()) {
1297 d
.setTime(+d
+ (d
< check
? 1 : -1) * HOUR_MS
);
1303 function addMinutes(d
, n
) {
1304 d
.setMinutes(d
.getMinutes() + n
);
1309 function clearTime(d
) {
1313 d
.setMilliseconds(0);
1318 function cloneDate(d
, dontKeepTime
) {
1320 return clearTime(new Date(+d
));
1322 return new Date(+d
);
1326 function zeroDate() { // returns a Date with time 00:00:00 and dateOfMonth=1
1329 d
= new Date(1970, i
++, 1);
1330 } while (d
.getHours()); // != 0
1335 function skipWeekend(date
, inc
, excl
) {
1337 while (!date
.getDay() || (excl
&& date
.getDay()==1 || !excl
&& date
.getDay()==6)) {
1344 function dayDiff(d1
, d2
) { // d1 - d2
1345 return Math
.round((cloneDate(d1
, true) - cloneDate(d2
, true)) / DAY_MS
);
1349 function setYMD(date
, y
, m
, d
) {
1350 if (y
!== undefined && y
!= date
.getFullYear()) {
1353 date
.setFullYear(y
);
1355 if (m
!== undefined && m
!= date
.getMonth()) {
1359 if (d
!== undefined) {
1367 -----------------------------------------------------------------------------*/
1370 function parseDate(s
, ignoreTimezone
) { // ignoreTimezone defaults to true
1371 if (typeof s
== 'object') { // already a Date object
1374 if (typeof s
== 'number') { // a UNIX timestamp
1375 return new Date(s
* 1000);
1377 if (typeof s
== 'string') {
1378 if (s
.match(/^\d+(\.\d+)?$/)) { // a UNIX timestamp
1379 return new Date(parseFloat(s
) * 1000);
1381 if (ignoreTimezone
=== undefined) {
1382 ignoreTimezone
= true;
1384 return parseISO8601(s
, ignoreTimezone
) || (s
? new Date(s
) : null);
1386 // TODO: never return invalid dates (like from new Date(<string>)), return null instead
1391 function parseISO8601(s
, ignoreTimezone
) { // ignoreTimezone defaults to false
1392 // derived from http://delete.me.uk/2005/03/iso8601.html
1393 // TODO: for a know glitch/feature, read tests/issue_206_parseDate_dst.html
1394 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}))?))?)?)?)?$/);
1398 var date
= new Date(m
[1], 0, 1);
1399 if (ignoreTimezone
|| !m
[13]) {
1400 var check
= new Date(m
[1], 0, 1, 9, 0);
1402 date
.setMonth(m
[3] - 1);
1403 check
.setMonth(m
[3] - 1);
1407 check
.setDate(m
[5]);
1409 fixDate(date
, check
);
1411 date
.setHours(m
[7]);
1414 date
.setMinutes(m
[8]);
1417 date
.setSeconds(m
[10]);
1420 date
.setMilliseconds(Number("0." + m
[12]) * 1000);
1422 fixDate(date
, check
);
1424 date
.setUTCFullYear(
1426 m
[3] ? m
[3] - 1 : 0,
1433 m
[12] ? Number("0." + m
[12]) * 1000 : 0
1436 var offset
= Number(m
[16]) * 60 + (m
[18] ? Number(m
[18]) : 0);
1437 offset
*= m
[15] == '-' ? 1 : -1;
1438 date
= new Date(+date
+ (offset
* 60 * 1000));
1445 function parseTime(s
) { // returns minutes since start of day
1446 if (typeof s
== 'number') { // an hour
1449 if (typeof s
== 'object') { // a Date object
1450 return s
.getHours() * 60 + s
.getMinutes();
1452 var m
= s
.match(/(\d+)(?::(\d+))?\s*(\w+)?/);
1454 var h
= parseInt(m
[1], 10);
1457 if (m
[3].toLowerCase().charAt(0) == 'p') {
1461 return h
* 60 + (m
[2] ? parseInt(m
[2], 10) : 0);
1468 -----------------------------------------------------------------------------*/
1469 // TODO: use same function formatDate(date, [date2], format, [options])
1472 function formatDate(date
, format
, options
) {
1473 return formatDates(date
, null, format
, options
);
1477 function formatDates(date1
, date2
, format
, options
) {
1478 options
= options
|| defaults
;
1481 i
, len
= format
.length
, c
,
1484 for (i
=0; i
<len
; i
++) {
1485 c
= format
.charAt(i
);
1487 for (i2
=i
+1; i2
<len
; i2
++) {
1488 if (format
.charAt(i2
) == "'") {
1493 res
+= format
.substring(i
+1, i2
);
1501 else if (c
== '(') {
1502 for (i2
=i
+1; i2
<len
; i2
++) {
1503 if (format
.charAt(i2
) == ')') {
1504 var subres
= formatDate(date
, format
.substring(i
+1, i2
), options
);
1505 if (parseInt(subres
.replace(/\D/, ''), 10)) {
1513 else if (c
== '[') {
1514 for (i2
=i
+1; i2
<len
; i2
++) {
1515 if (format
.charAt(i2
) == ']') {
1516 var subformat
= format
.substring(i
+1, i2
);
1517 var subres
= formatDate(date
, subformat
, options
);
1518 if (subres
!= formatDate(otherDate
, subformat
, options
)) {
1526 else if (c
== '{') {
1530 else if (c
== '}') {
1535 for (i2
=len
; i2
>i
; i2
--) {
1536 if (formatter
= dateFormatters
[format
.substring(i
, i2
)]) {
1538 res
+= formatter(date
, options
);
1555 var dateFormatters
= {
1556 s : function(d
) { return d
.getSeconds() },
1557 ss : function(d
) { return zeroPad(d
.getSeconds()) },
1558 m : function(d
) { return d
.getMinutes() },
1559 mm : function(d
) { return zeroPad(d
.getMinutes()) },
1560 h : function(d
) { return d
.getHours() % 12 || 12 },
1561 hh : function(d
) { return zeroPad(d
.getHours() % 12 || 12) },
1562 H : function(d
) { return d
.getHours() },
1563 HH : function(d
) { return zeroPad(d
.getHours()) },
1564 d : function(d
) { return d
.getDate() },
1565 dd : function(d
) { return zeroPad(d
.getDate()) },
1566 ddd : function(d
,o
) { return o
.dayNamesShort
[d
.getDay()] },
1567 dddd: function(d
,o
) { return o
.dayNames
[d
.getDay()] },
1568 M : function(d
) { return d
.getMonth() + 1 },
1569 MM : function(d
) { return zeroPad(d
.getMonth() + 1) },
1570 MMM : function(d
,o
) { return o
.monthNamesShort
[d
.getMonth()] },
1571 MMMM: function(d
,o
) { return o
.monthNames
[d
.getMonth()] },
1572 yy : function(d
) { return (d
.getFullYear()+'').substring(2) },
1573 yyyy: function(d
) { return d
.getFullYear() },
1574 t : function(d
) { return d
.getHours() < 12 ? 'a' : 'p' },
1575 tt : function(d
) { return d
.getHours() < 12 ? 'am' : 'pm' },
1576 T : function(d
) { return d
.getHours() < 12 ? 'A' : 'P' },
1577 TT : function(d
) { return d
.getHours() < 12 ? 'AM' : 'PM' },
1578 u : function(d
) { return formatDate(d
, "yyyy-MM-dd'T'HH:mm:ss'Z'") },
1580 var date
= d
.getDate();
1581 if (date
> 10 && date
< 20) {
1584 return ['st', 'nd', 'rd'][date
%10-1] || 'th';
1590 fc
.applyAll
= applyAll
;
1594 -----------------------------------------------------------------------------*/
1597 function exclEndDay(event
) {
1599 return _exclEndDay(event
.end
, event
.allDay
);
1601 return addDays(cloneDate(event
.start
), 1);
1606 function _exclEndDay(end
, allDay
) {
1607 end
= cloneDate(end
);
1608 return allDay
|| end
.getHours() || end
.getMinutes() ? addDays(end
, 1) : clearTime(end
);
1612 function segCmp(a
, b
) {
1613 return (b
.msLength
- a
.msLength
) * 100 + (a
.event
.start
- b
.event
.start
);
1617 function segsCollide(seg1
, seg2
) {
1618 return seg1
.end
> seg2
.start
&& seg1
.start
< seg2
.end
;
1624 -----------------------------------------------------------------------------*/
1627 // event rendering utilities
1628 function sliceSegs(events
, visEventEnds
, start
, end
) {
1630 i
, len
=events
.length
, event
,
1631 eventStart
, eventEnd
,
1634 for (i
=0; i
<len
; i
++) {
1636 eventStart
= event
.start
;
1637 eventEnd
= visEventEnds
[i
];
1638 if (eventEnd
> start
&& eventStart
< end
) {
1639 if (eventStart
< start
) {
1640 segStart
= cloneDate(start
);
1643 segStart
= eventStart
;
1646 if (eventEnd
> end
) {
1647 segEnd
= cloneDate(end
);
1659 msLength
: segEnd
- segStart
1663 return segs
.sort(segCmp
);
1667 // event rendering calculation utilities
1668 function stackSegs(segs
) {
1670 i
, len
= segs
.length
, seg
,
1672 for (i
=0; i
<len
; i
++) {
1674 j
= 0; // the level index where seg should belong
1678 for (k
=0; k
<levels
[j
].length
; k
++) {
1679 if (segsCollide(levels
[j
][k
], seg
)) {
1692 levels
[j
].push(seg
);
1702 /* Event Element Binding
1703 -----------------------------------------------------------------------------*/
1706 function lazySegBind(container
, segs
, bindHandlers
) {
1707 container
.unbind('mouseover').mouseover(function(ev
) {
1708 var parent
=ev
.target
, e
,
1710 while (parent
!= this) {
1712 parent
= parent
.parentNode
;
1714 if ((i
= e
._fci
) !== undefined) {
1717 bindHandlers(seg
.event
, seg
.element
, seg
);
1718 $(ev
.target
).trigger(ev
);
1720 ev
.stopPropagation();
1726 /* Element Dimensions
1727 -----------------------------------------------------------------------------*/
1730 function setOuterWidth(element
, width
, includeMargins
) {
1731 for (var i
=0, e
; i
<element
.length
; i
++) {
1733 e
.width(Math
.max(0, width
- hsides(e
, includeMargins
)));
1738 function setOuterHeight(element
, height
, includeMargins
) {
1739 for (var i
=0, e
; i
<element
.length
; i
++) {
1741 e
.height(Math
.max(0, height
- vsides(e
, includeMargins
)));
1746 // TODO: curCSS has been deprecated (jQuery 1.4.3 - 10/16/2010)
1749 function hsides(element
, includeMargins
) {
1750 return hpadding(element
) + hborders(element
) + (includeMargins
? hmargins(element
) : 0);
1754 function hpadding(element
) {
1755 return (parseFloat($.curCSS(element
[0], 'paddingLeft', true)) || 0) +
1756 (parseFloat($.curCSS(element
[0], 'paddingRight', true)) || 0);
1760 function hmargins(element
) {
1761 return (parseFloat($.curCSS(element
[0], 'marginLeft', true)) || 0) +
1762 (parseFloat($.curCSS(element
[0], 'marginRight', true)) || 0);
1766 function hborders(element
) {
1767 return (parseFloat($.curCSS(element
[0], 'borderLeftWidth', true)) || 0) +
1768 (parseFloat($.curCSS(element
[0], 'borderRightWidth', true)) || 0);
1772 function vsides(element
, includeMargins
) {
1773 return vpadding(element
) + vborders(element
) + (includeMargins
? vmargins(element
) : 0);
1777 function vpadding(element
) {
1778 return (parseFloat($.curCSS(element
[0], 'paddingTop', true)) || 0) +
1779 (parseFloat($.curCSS(element
[0], 'paddingBottom', true)) || 0);
1783 function vmargins(element
) {
1784 return (parseFloat($.curCSS(element
[0], 'marginTop', true)) || 0) +
1785 (parseFloat($.curCSS(element
[0], 'marginBottom', true)) || 0);
1789 function vborders(element
) {
1790 return (parseFloat($.curCSS(element
[0], 'borderTopWidth', true)) || 0) +
1791 (parseFloat($.curCSS(element
[0], 'borderBottomWidth', true)) || 0);
1795 function setMinHeight(element
, height
) {
1796 height
= (typeof height
== 'number' ? height
+ 'px' : height
);
1797 element
.each(function(i
, _element
) {
1798 _element
.style
.cssText
+= ';min-height:' + height
+ ';_height:' + height
;
1799 // why can't we just use .css() ? i forget
1806 -----------------------------------------------------------------------------*/
1810 //TODO: isFunction, grep ?
1816 function cmp(a
, b
) {
1821 function arrayMax(a
) {
1822 return Math
.max
.apply(Math
, a
);
1826 function zeroPad(n
) {
1827 return (n
< 10 ? '0' : '') + n
;
1831 function smartProperty(obj
, name
) { // get a camel-cased/namespaced property of an object
1832 if (obj
[name
] !== undefined) {
1835 var parts
= name
.split(/(?=[A-Z])/),
1836 i
=parts
.length
-1, res
;
1838 res
= obj
[parts
[i
].toLowerCase()];
1839 if (res
!== undefined) {
1847 function htmlEscape(s
) {
1848 return s
.replace(/&/g
, '&')
1849 .replace(/</g
, '<')
1850 .replace(/>/g
, '>')
1851 .replace(/'/g, ''')
1852 .replace(/"/g, '"
;')
1853 .replace(/\n/g, '<br
/>');
1857 function cssKey(_element) {
1858 return _element.id + '/' + _element.className + '/' + _element.style.cssText.replace(/(^|;)\s*(top|left|width|height)\s*:[^;]*/ig, '');
1862 function disableTextSelection(element) {
1864 .attr('unselectable
', 'on
')
1865 .css('MozUserSelect
', 'none
')
1866 .bind('selectstart
.ui
', function() { return false; });
1871 function enableTextSelection(element) {
1873 .attr('unselectable
', 'off
')
1874 .css('MozUserSelect
', '')
1875 .unbind('selectstart
.ui
');
1880 function markFirstLast(e) {
1882 .removeClass('fc
-first fc
-last
')
1883 .filter(':first
-child
')
1884 .addClass('fc
-first
')
1886 .filter(':last
-child
')
1887 .addClass('fc
-last
');
1891 function setDayID(cell, date) {
1892 cell.each(function(i, _cell) {
1893 _cell.className = _cell.className.replace(/^fc-\w*/, 'fc
-' + dayIDs[date.getDay()]);
1894 // TODO: make a way that doesn't rely on order
of classes
1899 function getSkinCss(event
, opt
) {
1900 var source
= event
.source
|| {};
1901 var eventColor
= event
.color
;
1902 var sourceColor
= source
.color
;
1903 var optionColor
= opt('eventColor');
1904 var backgroundColor
=
1905 event
.backgroundColor
||
1907 source
.backgroundColor
||
1909 opt('eventBackgroundColor') ||
1912 event
.borderColor
||
1914 source
.borderColor
||
1916 opt('eventBorderColor') ||
1921 opt('eventTextColor');
1922 var statements
= [];
1923 if (backgroundColor
) {
1924 statements
.push('background-color:' + backgroundColor
);
1927 statements
.push('border-color:' + borderColor
);
1930 statements
.push('color:' + textColor
);
1932 return statements
.join(';');
1936 function applyAll(functions
, thisObj
, args
) {
1937 if ($.isFunction(functions
)) {
1938 functions
= [ functions
];
1943 for (i
=0; i
<functions
.length
; i
++) {
1944 ret
= functions
[i
].apply(thisObj
, args
) || ret
;
1951 function firstDefined() {
1952 for (var i
=0; i
<arguments
.length
; i
++) {
1953 if (arguments
[i
] !== undefined) {
1954 return arguments
[i
];
1961 fcViews
.month
= MonthView
;
1963 function MonthView(element
, calendar
) {
1972 BasicView
.call(t
, element
, calendar
, 'month');
1974 var renderBasic
= t
.renderBasic
;
1975 var formatDate
= calendar
.formatDate
;
1979 function render(date
, delta
) {
1981 addMonths(date
, delta
);
1984 var start
= cloneDate(date
, true);
1986 var end
= addMonths(cloneDate(start
), 1);
1987 var visStart
= cloneDate(start
);
1988 var visEnd
= cloneDate(end
);
1989 var firstDay
= opt('firstDay');
1990 var nwe
= opt('weekends') ? 0 : 1;
1992 skipWeekend(visStart
);
1993 skipWeekend(visEnd
, -1, true);
1995 addDays(visStart
, -((visStart
.getDay() - Math
.max(firstDay
, nwe
) + 7) % 7));
1996 addDays(visEnd
, (7 - visEnd
.getDay() + Math
.max(firstDay
, nwe
)) % 7);
1997 var rowCnt
= Math
.round((visEnd
- visStart
) / (DAY_MS
* 7));
1998 if (opt('weekMode') == 'fixed') {
1999 addDays(visEnd
, (6 - rowCnt
) * 7);
2002 t
.title
= formatDate(start
, opt('titleFormat'));
2005 t
.visStart
= visStart
;
2007 renderBasic(6, rowCnt
, nwe
? 5 : 7, true);
2013 fcViews
.basicWeek
= BasicWeekView
;
2015 function BasicWeekView(element
, calendar
) {
2024 BasicView
.call(t
, element
, calendar
, 'basicWeek');
2026 var renderBasic
= t
.renderBasic
;
2027 var formatDates
= calendar
.formatDates
;
2031 function render(date
, delta
) {
2033 addDays(date
, delta
* 7);
2035 var start
= addDays(cloneDate(date
), -((date
.getDay() - opt('firstDay') + 7) % 7));
2036 var end
= addDays(cloneDate(start
), 7);
2037 var visStart
= cloneDate(start
);
2038 var visEnd
= cloneDate(end
);
2039 var weekends
= opt('weekends');
2041 skipWeekend(visStart
);
2042 skipWeekend(visEnd
, -1, true);
2044 t
.title
= formatDates(
2046 addDays(cloneDate(visEnd
), -1),
2051 t
.visStart
= visStart
;
2053 renderBasic(1, 1, weekends
? 7 : 5, false);
2059 fcViews
.basicDay
= BasicDayView
;
2061 //TODO: when calendar's date starts out on a weekend, shouldn't happen
2064 function BasicDayView(element
, calendar
) {
2073 BasicView
.call(t
, element
, calendar
, 'basicDay');
2075 var renderBasic
= t
.renderBasic
;
2076 var formatDate
= calendar
.formatDate
;
2080 function render(date
, delta
) {
2082 addDays(date
, delta
);
2083 if (!opt('weekends')) {
2084 skipWeekend(date
, delta
< 0 ? -1 : 1);
2087 t
.title
= formatDate(date
, opt('titleFormat'));
2088 t
.start
= t
.visStart
= cloneDate(date
, true);
2089 t
.end
= t
.visEnd
= addDays(cloneDate(t
.start
), 1);
2090 renderBasic(1, 1, 1, false);
2101 function BasicView(element
, calendar
, viewName
) {
2106 t
.renderBasic
= renderBasic
;
2107 t
.setHeight
= setHeight
;
2108 t
.setWidth
= setWidth
;
2109 t
.renderDayOverlay
= renderDayOverlay
;
2110 t
.defaultSelectionEnd
= defaultSelectionEnd
;
2111 t
.renderSelection
= renderSelection
;
2112 t
.clearSelection
= clearSelection
;
2113 t
.reportDayClick
= reportDayClick
; // for selection (kinda hacky)
2114 t
.dragStart
= dragStart
;
2115 t
.dragStop
= dragStop
;
2116 t
.defaultEventEnd
= defaultEventEnd
;
2117 t
.getHoverListener = function() { return hoverListener
};
2118 t
.colContentLeft
= colContentLeft
;
2119 t
.colContentRight
= colContentRight
;
2120 t
.dayOfWeekCol
= dayOfWeekCol
;
2121 t
.dateCell
= dateCell
;
2122 t
.cellDate
= cellDate
;
2123 t
.cellIsAllDay = function() { return true };
2124 t
.allDayRow
= allDayRow
;
2125 t
.allDayBounds
= allDayBounds
;
2126 t
.getRowCnt = function() { return rowCnt
};
2127 t
.getColCnt = function() { return colCnt
};
2128 t
.getColWidth = function() { return colWidth
};
2129 t
.getDaySegmentContainer = function() { return daySegmentContainer
};
2133 View
.call(t
, element
, calendar
, viewName
);
2134 OverlayManager
.call(t
);
2135 SelectionManager
.call(t
);
2136 BasicEventRenderer
.call(t
);
2138 var trigger
= t
.trigger
;
2139 var clearEvents
= t
.clearEvents
;
2140 var renderOverlay
= t
.renderOverlay
;
2141 var clearOverlays
= t
.clearOverlays
;
2142 var daySelectionMousedown
= t
.daySelectionMousedown
;
2143 var formatDate
= calendar
.formatDate
;
2154 var bodyCellTopInners
;
2155 var daySegmentContainer
;
2164 var colContentPositions
;
2175 ------------------------------------------------------------*/
2178 disableTextSelection(element
.addClass('fc-grid'));
2181 function renderBasic(maxr
, r
, c
, showNumbers
) {
2185 var firstTime
= !body
;
2187 buildSkeleton(maxr
, showNumbers
);
2191 updateCells(firstTime
);
2196 function updateOptions() {
2205 firstDay
= opt('firstDay');
2206 nwe
= opt('weekends') ? 0 : 1;
2207 tm
= opt('theme') ? 'ui' : 'fc';
2208 colFormat
= opt('columnFormat');
2213 function buildSkeleton(maxRowCnt
, showNumbers
) {
2215 var headerClass
= tm
+ "-widget-header";
2216 var contentClass
= tm
+ "-widget-content";
2221 "<table class='fc-border-separate' style='width:100%' cellspacing='0'>" +
2224 for (i
=0; i
<colCnt
; i
++) {
2226 "<th class='fc- " + headerClass
+ "'/>"; // need fc- for setDayID
2232 for (i
=0; i
<maxRowCnt
; i
++) {
2234 "<tr class='fc-week" + i
+ "'>";
2235 for (j
=0; j
<colCnt
; j
++) {
2237 "<td class='fc- " + contentClass
+ " fc-day" + (i
*colCnt
+j
) + "'>" + // need fc- for setDayID
2240 "<div class='fc-day-number'/>" :
2243 "<div class='fc-day-content'>" +
2244 "<div style='position:relative'> </div>" +
2255 table
= $(s
).appendTo(element
);
2257 head
= table
.find('thead');
2258 headCells
= head
.find('th');
2259 body
= table
.find('tbody');
2260 bodyRows
= body
.find('tr');
2261 bodyCells
= body
.find('td');
2262 bodyFirstCells
= bodyCells
.filter(':first-child');
2263 bodyCellTopInners
= bodyRows
.eq(0).find('div.fc-day-content div');
2265 markFirstLast(head
.add(head
.find('tr'))); // marks first+last tr/th's
2266 markFirstLast(bodyRows
); // marks first+last td's
2267 bodyRows
.eq(0).addClass('fc-first'); // fc-last is done in updateCells
2271 daySegmentContainer
=
2272 $("<div style='position:absolute;z-index:8;top:0;left:0'/>")
2278 function updateCells(firstTime
) {
2279 var dowDirty
= firstTime
|| rowCnt
== 1; // could the cells' day-of-weeks need updating?
2280 var month
= t
.start
.getMonth();
2281 var today
= clearTime(new Date());
2287 headCells
.each(function(i
, _cell
) {
2289 date
= indexDate(i
);
2290 cell
.html(formatDate(date
, colFormat
));
2291 setDayID(cell
, date
);
2295 bodyCells
.each(function(i
, _cell
) {
2297 date
= indexDate(i
);
2298 if (date
.getMonth() == month
) {
2299 cell
.removeClass('fc-other-month');
2301 cell
.addClass('fc-other-month');
2303 if (+date
== +today
) {
2304 cell
.addClass(tm
+ '-state-highlight fc-today');
2306 cell
.removeClass(tm
+ '-state-highlight fc-today');
2308 cell
.find('div.fc-day-number').text(date
.getDate());
2310 setDayID(cell
, date
);
2314 bodyRows
.each(function(i
, _row
) {
2318 if (i
== rowCnt
-1) {
2319 row
.addClass('fc-last');
2321 row
.removeClass('fc-last');
2331 function setHeight(height
) {
2332 viewHeight
= height
;
2334 var bodyHeight
= viewHeight
- head
.height();
2339 if (opt('weekMode') == 'variable') {
2340 rowHeight
= rowHeightLast
= Math
.floor(bodyHeight
/ (rowCnt
==1 ? 2 : 6));
2342 rowHeight
= Math
.floor(bodyHeight
/ rowCnt
);
2343 rowHeightLast
= bodyHeight
- rowHeight
* (rowCnt
-1);
2346 bodyFirstCells
.each(function(i
, _cell
) {
2351 (i
==rowCnt
-1 ? rowHeightLast
: rowHeight
) - vsides(cell
)
2359 function setWidth(width
) {
2361 colContentPositions
.clear();
2362 colWidth
= Math
.floor(viewWidth
/ colCnt
);
2363 setOuterWidth(headCells
.slice(0, -1), colWidth
);
2368 /* Day clicking and binding
2369 -----------------------------------------------------------*/
2372 function dayBind(days
) {
2373 days
.click(dayClick
)
2374 .mousedown(daySelectionMousedown
);
2378 function dayClick(ev
) {
2379 if (!opt('selectable')) { // if selectable, SelectionManager will worry about dayClick
2380 var index
= parseInt(this.className
.match(/fc\-day(\d+)/)[1]); // TODO: maybe use .data
2381 var date
= indexDate(index
);
2382 trigger('dayClick', this, date
, true, ev
);
2388 /* Semi-transparent Overlay Helpers
2389 ------------------------------------------------------*/
2392 function renderDayOverlay(overlayStart
, overlayEnd
, refreshCoordinateGrid
) { // overlayEnd is exclusive
2393 if (refreshCoordinateGrid
) {
2394 coordinateGrid
.build();
2396 var rowStart
= cloneDate(t
.visStart
);
2397 var rowEnd
= addDays(cloneDate(rowStart
), colCnt
);
2398 for (var i
=0; i
<rowCnt
; i
++) {
2399 var stretchStart
= new Date(Math
.max(rowStart
, overlayStart
));
2400 var stretchEnd
= new Date(Math
.min(rowEnd
, overlayEnd
));
2401 if (stretchStart
< stretchEnd
) {
2402 var colStart
, colEnd
;
2404 colStart
= dayDiff(stretchEnd
, rowStart
)*dis
+dit
+1;
2405 colEnd
= dayDiff(stretchStart
, rowStart
)*dis
+dit
+1;
2407 colStart
= dayDiff(stretchStart
, rowStart
);
2408 colEnd
= dayDiff(stretchEnd
, rowStart
);
2411 renderCellOverlay(i
, colStart
, i
, colEnd
-1)
2414 addDays(rowStart
, 7);
2420 function renderCellOverlay(row0
, col0
, row1
, col1
) { // row1,col1 is inclusive
2421 var rect
= coordinateGrid
.rect(row0
, col0
, row1
, col1
, element
);
2422 return renderOverlay(rect
, element
);
2428 -----------------------------------------------------------------------*/
2431 function defaultSelectionEnd(startDate
, allDay
) {
2432 return cloneDate(startDate
);
2436 function renderSelection(startDate
, endDate
, allDay
) {
2437 renderDayOverlay(startDate
, addDays(cloneDate(endDate
), 1), true); // rebuild every time???
2441 function clearSelection() {
2446 function reportDayClick(date
, allDay
, ev
) {
2447 var cell
= dateCell(date
);
2448 var _element
= bodyCells
[cell
.row
*colCnt
+ cell
.col
];
2449 trigger('dayClick', _element
, date
, allDay
, ev
);
2454 /* External Dragging
2455 -----------------------------------------------------------------------*/
2458 function dragStart(_dragElement
, ev
, ui
) {
2459 hoverListener
.start(function(cell
) {
2462 renderCellOverlay(cell
.row
, cell
.col
, cell
.row
, cell
.col
);
2468 function dragStop(_dragElement
, ev
, ui
) {
2469 var cell
= hoverListener
.stop();
2472 var d
= cellDate(cell
);
2473 trigger('drop', _dragElement
, d
, true, ev
, ui
);
2480 --------------------------------------------------------*/
2483 function defaultEventEnd(event
) {
2484 return cloneDate(event
.start
);
2488 coordinateGrid
= new CoordinateGrid(function(rows
, cols
) {
2490 headCells
.each(function(i
, _e
) {
2492 n
= e
.offset().left
;
2499 p
[1] = n
+ e
.outerWidth();
2500 bodyRows
.each(function(i
, _e
) {
2511 p
[1] = n
+ e
.outerHeight();
2515 hoverListener
= new HoverListener(coordinateGrid
);
2518 colContentPositions
= new HorizontalPositionCache(function(col
) {
2519 return bodyCellTopInners
.eq(col
);
2523 function colContentLeft(col
) {
2524 return colContentPositions
.left(col
);
2528 function colContentRight(col
) {
2529 return colContentPositions
.right(col
);
2535 function dateCell(date
) {
2537 row
: Math
.floor(dayDiff(date
, t
.visStart
) / 7),
2538 col
: dayOfWeekCol(date
.getDay())
2543 function cellDate(cell
) {
2544 return _cellDate(cell
.row
, cell
.col
);
2548 function _cellDate(row
, col
) {
2549 return addDays(cloneDate(t
.visStart
), row
*7 + col
*dis
+dit
);
2550 // what about weekends in middle of week?
2554 function indexDate(index
) {
2555 return _cellDate(Math
.floor(index
/colCnt
), index
%colCnt
);
2559 function dayOfWeekCol(dayOfWeek
) {
2560 return ((dayOfWeek
- Math
.max(firstDay
, nwe
) + colCnt
) % colCnt
) * dis
+ dit
;
2566 function allDayRow(i
) {
2567 return bodyRows
.eq(i
);
2571 function allDayBounds(i
) {
2581 function BasicEventRenderer() {
2586 t
.renderEvents
= renderEvents
;
2587 t
.compileDaySegs
= compileSegs
; // for DayEventRenderer
2588 t
.clearEvents
= clearEvents
;
2589 t
.bindDaySeg
= bindDaySeg
;
2593 DayEventRenderer
.call(t
);
2595 var trigger
= t
.trigger
;
2596 //var setOverflowHidden = t.setOverflowHidden;
2597 var isEventDraggable
= t
.isEventDraggable
;
2598 var isEventResizable
= t
.isEventResizable
;
2599 var reportEvents
= t
.reportEvents
;
2600 var reportEventClear
= t
.reportEventClear
;
2601 var eventElementHandlers
= t
.eventElementHandlers
;
2602 var showEvents
= t
.showEvents
;
2603 var hideEvents
= t
.hideEvents
;
2604 var eventDrop
= t
.eventDrop
;
2605 var getDaySegmentContainer
= t
.getDaySegmentContainer
;
2606 var getHoverListener
= t
.getHoverListener
;
2607 var renderDayOverlay
= t
.renderDayOverlay
;
2608 var clearOverlays
= t
.clearOverlays
;
2609 var getRowCnt
= t
.getRowCnt
;
2610 var getColCnt
= t
.getColCnt
;
2611 var renderDaySegs
= t
.renderDaySegs
;
2612 var resizableDayEvent
= t
.resizableDayEvent
;
2617 --------------------------------------------------------------------*/
2620 function renderEvents(events
, modifiedEventId
) {
2621 reportEvents(events
);
2622 renderDaySegs(compileSegs(events
), modifiedEventId
);
2626 function clearEvents() {
2628 getDaySegmentContainer().empty();
2632 function compileSegs(events
) {
2633 var rowCnt
= getRowCnt(),
2634 colCnt
= getColCnt(),
2635 d1
= cloneDate(t
.visStart
),
2636 d2
= addDays(cloneDate(d1
), colCnt
),
2637 visEventsEnds
= $.map(events
, exclEndDay
),
2642 for (i
=0; i
<rowCnt
; i
++) {
2643 row
= stackSegs(sliceSegs(events
, visEventsEnds
, d1
, d2
));
2644 for (j
=0; j
<row
.length
; j
++) {
2646 for (k
=0; k
<level
.length
; k
++) {
2649 seg
.level
= j
; // not needed anymore
2660 function bindDaySeg(event
, eventElement
, seg
) {
2661 if (isEventDraggable(event
)) {
2662 draggableDayEvent(event
, eventElement
);
2664 if (seg
.isEnd
&& isEventResizable(event
)) {
2665 resizableDayEvent(event
, eventElement
, seg
);
2667 eventElementHandlers(event
, eventElement
);
2668 // needs to be after, because resizableDayEvent might stopImmediatePropagation on click
2674 ----------------------------------------------------------------------------*/
2677 function draggableDayEvent(event
, eventElement
) {
2678 var hoverListener
= getHoverListener();
2680 eventElement
.draggable({
2683 opacity
: opt('dragOpacity'),
2684 revertDuration
: opt('dragRevertDuration'),
2685 start: function(ev
, ui
) {
2686 trigger('eventDragStart', eventElement
, event
, ev
, ui
);
2687 hideEvents(event
, eventElement
);
2688 hoverListener
.start(function(cell
, origCell
, rowDelta
, colDelta
) {
2689 eventElement
.draggable('option', 'revert', !cell
|| !rowDelta
&& !colDelta
);
2692 //setOverflowHidden(true);
2693 dayDelta
= rowDelta
*7 + colDelta
* (opt('isRTL') ? -1 : 1);
2695 addDays(cloneDate(event
.start
), dayDelta
),
2696 addDays(exclEndDay(event
), dayDelta
)
2699 //setOverflowHidden(false);
2704 stop: function(ev
, ui
) {
2705 hoverListener
.stop();
2707 trigger('eventDragStop', eventElement
, event
, ev
, ui
);
2709 eventDrop(this, event
, dayDelta
, 0, event
.allDay
, ev
, ui
);
2711 eventElement
.css('filter', ''); // clear IE opacity side-effects
2712 showEvents(event
, eventElement
);
2714 //setOverflowHidden(false);
2722 fcViews
.agendaWeek
= AgendaWeekView
;
2724 function AgendaWeekView(element
, calendar
) {
2733 AgendaView
.call(t
, element
, calendar
, 'agendaWeek');
2735 var renderAgenda
= t
.renderAgenda
;
2736 var formatDates
= calendar
.formatDates
;
2740 function render(date
, delta
) {
2742 addDays(date
, delta
* 7);
2744 var start
= addDays(cloneDate(date
), -((date
.getDay() - opt('firstDay') + 7) % 7));
2745 var end
= addDays(cloneDate(start
), 7);
2746 var visStart
= cloneDate(start
);
2747 var visEnd
= cloneDate(end
);
2748 var weekends
= opt('weekends');
2750 skipWeekend(visStart
);
2751 skipWeekend(visEnd
, -1, true);
2753 t
.title
= formatDates(
2755 addDays(cloneDate(visEnd
), -1),
2760 t
.visStart
= visStart
;
2762 renderAgenda(weekends
? 7 : 5);
2768 fcViews
.agendaDay
= AgendaDayView
;
2770 function AgendaDayView(element
, calendar
) {
2779 AgendaView
.call(t
, element
, calendar
, 'agendaDay');
2781 var renderAgenda
= t
.renderAgenda
;
2782 var formatDate
= calendar
.formatDate
;
2786 function render(date
, delta
) {
2788 addDays(date
, delta
);
2789 if (!opt('weekends')) {
2790 skipWeekend(date
, delta
< 0 ? -1 : 1);
2793 var start
= cloneDate(date
, true);
2794 var end
= addDays(cloneDate(start
), 1);
2795 t
.title
= formatDate(date
, opt('titleFormat'));
2796 t
.start
= t
.visStart
= start
;
2797 t
.end
= t
.visEnd
= end
;
2806 allDayText
: 'all-day',
2809 defaultEventMinutes
: 120,
2810 axisFormat
: 'h(:mm)tt',
2812 agenda
: 'h:mm{ - h:mm}'
2822 // TODO: make it work in quirks mode (event corners, all-day height)
2823 // TODO: test liquid width, especially in IE6
2826 function AgendaView(element
, calendar
, viewName
) {
2831 t
.renderAgenda
= renderAgenda
;
2832 t
.setWidth
= setWidth
;
2833 t
.setHeight
= setHeight
;
2834 t
.beforeHide
= beforeHide
;
2835 t
.afterShow
= afterShow
;
2836 t
.defaultEventEnd
= defaultEventEnd
;
2837 t
.timePosition
= timePosition
;
2838 t
.dayOfWeekCol
= dayOfWeekCol
;
2839 t
.dateCell
= dateCell
;
2840 t
.cellDate
= cellDate
;
2841 t
.cellIsAllDay
= cellIsAllDay
;
2842 t
.allDayRow
= getAllDayRow
;
2843 t
.allDayBounds
= allDayBounds
;
2844 t
.getHoverListener = function() { return hoverListener
};
2845 t
.colContentLeft
= colContentLeft
;
2846 t
.colContentRight
= colContentRight
;
2847 t
.getDaySegmentContainer = function() { return daySegmentContainer
};
2848 t
.getSlotSegmentContainer = function() { return slotSegmentContainer
};
2849 t
.getMinMinute = function() { return minMinute
};
2850 t
.getMaxMinute = function() { return maxMinute
};
2851 t
.getBodyContent = function() { return slotContent
}; // !!??
2852 t
.getRowCnt = function() { return 1 };
2853 t
.getColCnt = function() { return colCnt
};
2854 t
.getColWidth = function() { return colWidth
};
2855 t
.getSlotHeight = function() { return slotHeight
};
2856 t
.defaultSelectionEnd
= defaultSelectionEnd
;
2857 t
.renderDayOverlay
= renderDayOverlay
;
2858 t
.renderSelection
= renderSelection
;
2859 t
.clearSelection
= clearSelection
;
2860 t
.reportDayClick
= reportDayClick
; // selection mousedown hack
2861 t
.dragStart
= dragStart
;
2862 t
.dragStop
= dragStop
;
2866 View
.call(t
, element
, calendar
, viewName
);
2867 OverlayManager
.call(t
);
2868 SelectionManager
.call(t
);
2869 AgendaEventRenderer
.call(t
);
2871 var trigger
= t
.trigger
;
2872 var clearEvents
= t
.clearEvents
;
2873 var renderOverlay
= t
.renderOverlay
;
2874 var clearOverlays
= t
.clearOverlays
;
2875 var reportSelection
= t
.reportSelection
;
2876 var unselect
= t
.unselect
;
2877 var daySelectionMousedown
= t
.daySelectionMousedown
;
2878 var slotSegHtml
= t
.slotSegHtml
;
2879 var formatDate
= calendar
.formatDate
;
2889 var dayBodyCellInners
;
2890 var dayBodyFirstCell
;
2891 var dayBodyFirstCellStretcher
;
2893 var daySegmentContainer
;
2898 var slotSegmentContainer
;
2900 var slotTableFirstInner
;
2903 var selectionHelper
;
2910 var slotHeight
; // TODO: what if slotHeight changes? (see issue 650)
2917 var colContentPositions
;
2918 var slotTopCache
= {};
2922 var nwe
; // no weekends (int)
2923 var rtl
, dis
, dit
; // day index sign / translate
2924 var minMinute
, maxMinute
;
2930 -----------------------------------------------------------------------------*/
2933 disableTextSelection(element
.addClass('fc-agenda'));
2936 function renderAgenda(c
) {
2949 function updateOptions() {
2950 tm
= opt('theme') ? 'ui' : 'fc';
2951 nwe
= opt('weekends') ? 0 : 1;
2952 firstDay
= opt('firstDay');
2953 if (rtl
= opt('isRTL')) {
2960 minMinute
= parseTime(opt('minTime'));
2961 maxMinute
= parseTime(opt('maxTime'));
2962 colFormat
= opt('columnFormat');
2967 function buildSkeleton() {
2968 var headerClass
= tm
+ "-widget-header";
2969 var contentClass
= tm
+ "-widget-content";
2975 var slotNormal
= opt('slotMinutes') % 15 == 0;
2978 "<table style='width:100%' class='fc-agenda-days fc-border-separate' cellspacing='0'>" +
2981 "<th class='fc-agenda-axis " + headerClass
+ "'> </th>";
2982 for (i
=0; i
<colCnt
; i
++) {
2984 "<th class='fc- fc-col" + i
+ ' ' + headerClass
+ "'/>"; // fc- needed for setDayID
2987 "<th class='fc-agenda-gutter " + headerClass
+ "'> </th>" +
2992 "<th class='fc-agenda-axis " + headerClass
+ "'> </th>";
2993 for (i
=0; i
<colCnt
; i
++) {
2995 "<td class='fc- fc-col" + i
+ ' ' + contentClass
+ "'>" + // fc- needed for setDayID
2997 "<div class='fc-day-content'>" +
2998 "<div style='position:relative'> </div>" +
3004 "<td class='fc-agenda-gutter " + contentClass
+ "'> </td>" +
3008 dayTable
= $(s
).appendTo(element
);
3009 dayHead
= dayTable
.find('thead');
3010 dayHeadCells
= dayHead
.find('th').slice(1, -1);
3011 dayBody
= dayTable
.find('tbody');
3012 dayBodyCells
= dayBody
.find('td').slice(0, -1);
3013 dayBodyCellInners
= dayBodyCells
.find('div.fc-day-content div');
3014 dayBodyFirstCell
= dayBodyCells
.eq(0);
3015 dayBodyFirstCellStretcher
= dayBodyFirstCell
.find('> div');
3017 markFirstLast(dayHead
.add(dayHead
.find('tr')));
3018 markFirstLast(dayBody
.add(dayBody
.find('tr')));
3020 axisFirstCells
= dayHead
.find('th:first');
3021 gutterCells
= dayTable
.find('.fc-agenda-gutter');
3024 $("<div style='position:absolute;z-index:2;left:0;width:100%'/>")
3027 if (opt('allDaySlot')) {
3029 daySegmentContainer
=
3030 $("<div style='position:absolute;z-index:8;top:0;left:0'/>")
3031 .appendTo(slotLayer
);
3034 "<table style='width:100%' class='fc-agenda-allday' cellspacing='0'>" +
3036 "<th class='" + headerClass
+ " fc-agenda-axis'>" + opt('allDayText') + "</th>" +
3038 "<div class='fc-day-content'><div style='position:relative'/></div>" +
3040 "<th class='" + headerClass
+ " fc-agenda-gutter'> </th>" +
3043 allDayTable
= $(s
).appendTo(slotLayer
);
3044 allDayRow
= allDayTable
.find('tr');
3046 dayBind(allDayRow
.find('td'));
3048 axisFirstCells
= axisFirstCells
.add(allDayTable
.find('th:first'));
3049 gutterCells
= gutterCells
.add(allDayTable
.find('th.fc-agenda-gutter'));
3052 "<div class='fc-agenda-divider " + headerClass
+ "'>" +
3053 "<div class='fc-agenda-divider-inner'/>" +
3059 daySegmentContainer
= $([]); // in jQuery 1.4, we can just do $()
3064 $("<div style='position:absolute;width:100%;overflow-x:hidden;overflow-y:auto'/>")
3065 .appendTo(slotLayer
);
3068 $("<div style='position:relative;width:100%;overflow:hidden'/>")
3069 .appendTo(slotScroller
);
3071 slotSegmentContainer
=
3072 $("<div style='position:absolute;z-index:8;top:0;left:0'/>")
3073 .appendTo(slotContent
);
3076 "<table class='fc-agenda-slots' style='width:100%' cellspacing='0'>" +
3079 maxd
= addMinutes(cloneDate(d
), maxMinute
);
3080 addMinutes(d
, minMinute
);
3082 for (i
=0; d
< maxd
; i
++) {
3083 minutes
= d
.getMinutes();
3085 "<tr class='fc-slot" + i
+ ' ' + (!minutes
? '' : 'fc-minor') + "'>" +
3086 "<th class='fc-agenda-axis " + headerClass
+ "'>" +
3087 ((!slotNormal
|| !minutes
) ? formatDate(d
, opt('axisFormat')) : ' ') +
3089 "<td class='" + contentClass
+ "'>" +
3090 "<div style='position:relative'> </div>" +
3093 addMinutes(d
, opt('slotMinutes'));
3099 slotTable
= $(s
).appendTo(slotContent
);
3100 slotTableFirstInner
= slotTable
.find('div:first');
3102 slotBind(slotTable
.find('td'));
3104 axisFirstCells
= axisFirstCells
.add(slotTable
.find('th:first'));
3109 function updateCells() {
3114 var today
= clearTime(new Date());
3115 for (i
=0; i
<colCnt
; i
++) {
3117 headCell
= dayHeadCells
.eq(i
);
3118 headCell
.html(formatDate(date
, colFormat
));
3119 bodyCell
= dayBodyCells
.eq(i
);
3120 if (+date
== +today
) {
3121 bodyCell
.addClass(tm
+ '-state-highlight fc-today');
3123 bodyCell
.removeClass(tm
+ '-state-highlight fc-today');
3125 setDayID(headCell
.add(bodyCell
), date
);
3131 function setHeight(height
, dateChanged
) {
3132 if (height
=== undefined) {
3133 height
= viewHeight
;
3135 viewHeight
= height
;
3138 var headHeight
= dayBody
.position().top
;
3139 var allDayHeight
= slotScroller
.position().top
; // including divider
3140 var bodyHeight
= Math
.min( // total body height, including borders
3141 height
- headHeight
, // when scrollbars
3142 slotTable
.height() + allDayHeight
+ 1 // when no scrollbars. +1 for bottom border
3145 dayBodyFirstCellStretcher
3146 .height(bodyHeight
- vsides(dayBodyFirstCell
));
3148 slotLayer
.css('top', headHeight
);
3150 slotScroller
.height(bodyHeight
- allDayHeight
- 1);
3152 slotHeight
= slotTableFirstInner
.height() + 1; // +1 for border
3161 function setWidth(width
) {
3163 colContentPositions
.clear();
3169 .each(function(i
, _cell
) {
3170 axisWidth
= Math
.max(axisWidth
, $(_cell
).outerWidth());
3175 var slotTableWidth
= slotScroller
[0].clientWidth
; // needs to be done after axisWidth (for IE7)
3176 //slotTable.width(slotTableWidth);
3178 gutterWidth
= slotScroller
.width() - slotTableWidth
;
3180 setOuterWidth(gutterCells
, gutterWidth
);
3184 .removeClass('fc-last');
3189 .addClass('fc-last');
3192 colWidth
= Math
.floor((slotTableWidth
- axisWidth
) / colCnt
);
3193 setOuterWidth(dayHeadCells
.slice(0, -1), colWidth
);
3198 function resetScroll() {
3199 var d0
= zeroDate();
3200 var scrollDate
= cloneDate(d0
);
3201 scrollDate
.setHours(opt('firstHour'));
3202 var top
= timePosition(d0
, scrollDate
) + 1; // +1 for the border
3204 slotScroller
.scrollTop(top
);
3207 setTimeout(scroll
, 0); // overrides any previous scroll state made by the browser
3211 function beforeHide() {
3212 savedScrollTop
= slotScroller
.scrollTop();
3216 function afterShow() {
3217 slotScroller
.scrollTop(savedScrollTop
);
3222 /* Slot/Day clicking and binding
3223 -----------------------------------------------------------------------*/
3226 function dayBind(cells
) {
3227 cells
.click(slotClick
)
3228 .mousedown(daySelectionMousedown
);
3232 function slotBind(cells
) {
3233 cells
.click(slotClick
)
3234 .mousedown(slotSelectionMousedown
);
3238 function slotClick(ev
) {
3239 if (!opt('selectable')) { // if selectable, SelectionManager will worry about dayClick
3240 var col
= Math
.min(colCnt
-1, Math
.floor((ev
.pageX
- dayTable
.offset().left
- axisWidth
) / colWidth
));
3241 var date
= colDate(col
);
3242 var rowMatch
= this.parentNode
.className
.match(/fc-slot(\d+)/); // TODO: maybe use data
3244 var mins
= parseInt(rowMatch
[1]) * opt('slotMinutes');
3245 var hours
= Math
.floor(mins
/60);
3246 date
.setHours(hours
);
3247 date
.setMinutes(mins
%60 + minMinute
);
3248 trigger('dayClick', dayBodyCells
[col
], date
, false, ev
);
3250 trigger('dayClick', dayBodyCells
[col
], date
, true, ev
);
3257 /* Semi-transparent Overlay Helpers
3258 -----------------------------------------------------*/
3261 function renderDayOverlay(startDate
, endDate
, refreshCoordinateGrid
) { // endDate is exclusive
3262 if (refreshCoordinateGrid
) {
3263 coordinateGrid
.build();
3265 var visStart
= cloneDate(t
.visStart
);
3266 var startCol
, endCol
;
3268 startCol
= dayDiff(endDate
, visStart
)*dis
+dit
+1;
3269 endCol
= dayDiff(startDate
, visStart
)*dis
+dit
+1;
3271 startCol
= dayDiff(startDate
, visStart
);
3272 endCol
= dayDiff(endDate
, visStart
);
3274 startCol
= Math
.max(0, startCol
);
3275 endCol
= Math
.min(colCnt
, endCol
);
3276 if (startCol
< endCol
) {
3278 renderCellOverlay(0, startCol
, 0, endCol
-1)
3284 function renderCellOverlay(row0
, col0
, row1
, col1
) { // only for all-day?
3285 var rect
= coordinateGrid
.rect(row0
, col0
, row1
, col1
, slotLayer
);
3286 return renderOverlay(rect
, slotLayer
);
3290 function renderSlotOverlay(overlayStart
, overlayEnd
) {
3291 var dayStart
= cloneDate(t
.visStart
);
3292 var dayEnd
= addDays(cloneDate(dayStart
), 1);
3293 for (var i
=0; i
<colCnt
; i
++) {
3294 var stretchStart
= new Date(Math
.max(dayStart
, overlayStart
));
3295 var stretchEnd
= new Date(Math
.min(dayEnd
, overlayEnd
));
3296 if (stretchStart
< stretchEnd
) {
3297 var col
= i
*dis
+dit
;
3298 var rect
= coordinateGrid
.rect(0, col
, 0, col
, slotContent
); // only use it for horizontal coords
3299 var top
= timePosition(dayStart
, stretchStart
);
3300 var bottom
= timePosition(dayStart
, stretchEnd
);
3302 rect
.height
= bottom
- top
;
3304 renderOverlay(rect
, slotContent
)
3307 addDays(dayStart
, 1);
3314 /* Coordinate Utilities
3315 -----------------------------------------------------------------------------*/
3318 coordinateGrid
= new CoordinateGrid(function(rows
, cols
) {
3320 dayHeadCells
.each(function(i
, _e
) {
3322 n
= e
.offset().left
;
3329 p
[1] = n
+ e
.outerWidth();
3330 if (opt('allDaySlot')) {
3333 rows
[0] = [n
, n
+e
.outerHeight()];
3335 var slotTableTop
= slotContent
.offset().top
;
3336 var slotScrollerTop
= slotScroller
.offset().top
;
3337 var slotScrollerBottom
= slotScrollerTop
+ slotScroller
.outerHeight();
3338 function constrain(n
) {
3339 return Math
.max(slotScrollerTop
, Math
.min(slotScrollerBottom
, n
));
3341 for (var i
=0; i
<slotCnt
; i
++) {
3343 constrain(slotTableTop
+ slotHeight
*i
),
3344 constrain(slotTableTop
+ slotHeight
*(i
+1))
3350 hoverListener
= new HoverListener(coordinateGrid
);
3353 colContentPositions
= new HorizontalPositionCache(function(col
) {
3354 return dayBodyCellInners
.eq(col
);
3358 function colContentLeft(col
) {
3359 return colContentPositions
.left(col
);
3363 function colContentRight(col
) {
3364 return colContentPositions
.right(col
);
3370 function dateCell(date
) { // "cell" terminology is now confusing
3372 row
: Math
.floor(dayDiff(date
, t
.visStart
) / 7),
3373 col
: dayOfWeekCol(date
.getDay())
3378 function cellDate(cell
) {
3379 var d
= colDate(cell
.col
);
3380 var slotIndex
= cell
.row
;
3381 if (opt('allDaySlot')) {
3384 if (slotIndex
>= 0) {
3385 addMinutes(d
, minMinute
+ slotIndex
* opt('slotMinutes'));
3391 function colDate(col
) { // returns dates with 00:00:00
3392 return addDays(cloneDate(t
.visStart
), col
*dis
+dit
);
3396 function cellIsAllDay(cell
) {
3397 return opt('allDaySlot') && !cell
.row
;
3401 function dayOfWeekCol(dayOfWeek
) {
3402 return ((dayOfWeek
- Math
.max(firstDay
, nwe
) + colCnt
) % colCnt
)*dis
+dit
;
3408 // get the Y coordinate of the given time on the given day (both Date objects)
3409 function timePosition(day
, time
) { // both date objects. day holds 00:00 of current day
3410 day
= cloneDate(day
, true);
3411 if (time
< addMinutes(cloneDate(day
), minMinute
)) {
3414 if (time
>= addMinutes(cloneDate(day
), maxMinute
)) {
3415 return slotTable
.height();
3417 var slotMinutes
= opt('slotMinutes'),
3418 minutes
= time
.getHours()*60 + time
.getMinutes() - minMinute
,
3419 slotI
= Math
.floor(minutes
/ slotMinutes
),
3420 slotTop
= slotTopCache
[slotI
];
3421 if (slotTop
=== undefined) {
3422 slotTop
= slotTopCache
[slotI
] = slotTable
.find('tr:eq(' + slotI
+ ') td div')[0].offsetTop
; //.position().top; // need this optimization???
3424 return Math
.max(0, Math
.round(
3425 slotTop
- 1 + slotHeight
* ((minutes
% slotMinutes
) / slotMinutes
)
3430 function allDayBounds() {
3433 right
: viewWidth
- gutterWidth
3438 function getAllDayRow(index
) {
3443 function defaultEventEnd(event
) {
3444 var start
= cloneDate(event
.start
);
3448 return addMinutes(start
, opt('defaultEventMinutes'));
3454 ---------------------------------------------------------------------------------*/
3457 function defaultSelectionEnd(startDate
, allDay
) {
3459 return cloneDate(startDate
);
3461 return addMinutes(cloneDate(startDate
), opt('slotMinutes'));
3465 function renderSelection(startDate
, endDate
, allDay
) { // only for all-day
3467 if (opt('allDaySlot')) {
3468 renderDayOverlay(startDate
, addDays(cloneDate(endDate
), 1), true);
3471 renderSlotSelection(startDate
, endDate
);
3476 function renderSlotSelection(startDate
, endDate
) {
3477 var helperOption
= opt('selectHelper');
3478 coordinateGrid
.build();
3480 var col
= dayDiff(startDate
, t
.visStart
) * dis
+ dit
;
3481 if (col
>= 0 && col
< colCnt
) { // only works when times are on same day
3482 var rect
= coordinateGrid
.rect(0, col
, 0, col
, slotContent
); // only for horizontal coords
3483 var top
= timePosition(startDate
, startDate
);
3484 var bottom
= timePosition(startDate
, endDate
);
3485 if (bottom
> top
) { // protect against selections that are entirely before or after visible range
3487 rect
.height
= bottom
- top
;
3490 if ($.isFunction(helperOption
)) {
3491 var helperRes
= helperOption(startDate
, endDate
);
3493 rect
.position
= 'absolute';
3495 selectionHelper
= $(helperRes
)
3497 .appendTo(slotContent
);
3500 rect
.isStart
= true; // conside rect a "seg" now
3501 rect
.isEnd
= true; //
3502 selectionHelper
= $(slotSegHtml(
3507 className
: ['fc-select-helper'],
3512 selectionHelper
.css('opacity', opt('dragOpacity'));
3514 if (selectionHelper
) {
3515 slotBind(selectionHelper
);
3516 slotContent
.append(selectionHelper
);
3517 setOuterWidth(selectionHelper
, rect
.width
, true); // needs to be after appended
3518 setOuterHeight(selectionHelper
, rect
.height
, true);
3523 renderSlotOverlay(startDate
, endDate
);
3528 function clearSelection() {
3530 if (selectionHelper
) {
3531 selectionHelper
.remove();
3532 selectionHelper
= null;
3537 function slotSelectionMousedown(ev
) {
3538 if (ev
.which
== 1 && opt('selectable')) { // ev.which==1 means left mouse button
3541 hoverListener
.start(function(cell
, origCell
) {
3543 if (cell
&& cell
.col
== origCell
.col
&& !cellIsAllDay(cell
)) {
3544 var d1
= cellDate(origCell
);
3545 var d2
= cellDate(cell
);
3548 addMinutes(cloneDate(d1
), opt('slotMinutes')),
3550 addMinutes(cloneDate(d2
), opt('slotMinutes'))
3552 renderSlotSelection(dates
[0], dates
[3]);
3557 $(document
).one('mouseup', function(ev
) {
3558 hoverListener
.stop();
3560 if (+dates
[0] == +dates
[1]) {
3561 reportDayClick(dates
[0], false, ev
);
3563 reportSelection(dates
[0], dates
[3], false, ev
);
3570 function reportDayClick(date
, allDay
, ev
) {
3571 trigger('dayClick', dayBodyCells
[dayOfWeekCol(date
.getDay())], date
, allDay
, ev
);
3576 /* External Dragging
3577 --------------------------------------------------------------------------------*/
3580 function dragStart(_dragElement
, ev
, ui
) {
3581 hoverListener
.start(function(cell
) {
3584 if (cellIsAllDay(cell
)) {
3585 renderCellOverlay(cell
.row
, cell
.col
, cell
.row
, cell
.col
);
3587 var d1
= cellDate(cell
);
3588 var d2
= addMinutes(cloneDate(d1
), opt('defaultEventMinutes'));
3589 renderSlotOverlay(d1
, d2
);
3596 function dragStop(_dragElement
, ev
, ui
) {
3597 var cell
= hoverListener
.stop();
3600 trigger('drop', _dragElement
, cellDate(cell
), cellIsAllDay(cell
), ev
, ui
);
3607 function AgendaEventRenderer() {
3612 t
.renderEvents
= renderEvents
;
3613 t
.compileDaySegs
= compileDaySegs
; // for DayEventRenderer
3614 t
.clearEvents
= clearEvents
;
3615 t
.slotSegHtml
= slotSegHtml
;
3616 t
.bindDaySeg
= bindDaySeg
;
3620 DayEventRenderer
.call(t
);
3622 var trigger
= t
.trigger
;
3623 //var setOverflowHidden = t.setOverflowHidden;
3624 var isEventDraggable
= t
.isEventDraggable
;
3625 var isEventResizable
= t
.isEventResizable
;
3626 var eventEnd
= t
.eventEnd
;
3627 var reportEvents
= t
.reportEvents
;
3628 var reportEventClear
= t
.reportEventClear
;
3629 var eventElementHandlers
= t
.eventElementHandlers
;
3630 var setHeight
= t
.setHeight
;
3631 var getDaySegmentContainer
= t
.getDaySegmentContainer
;
3632 var getSlotSegmentContainer
= t
.getSlotSegmentContainer
;
3633 var getHoverListener
= t
.getHoverListener
;
3634 var getMaxMinute
= t
.getMaxMinute
;
3635 var getMinMinute
= t
.getMinMinute
;
3636 var timePosition
= t
.timePosition
;
3637 var colContentLeft
= t
.colContentLeft
;
3638 var colContentRight
= t
.colContentRight
;
3639 var renderDaySegs
= t
.renderDaySegs
;
3640 var resizableDayEvent
= t
.resizableDayEvent
; // TODO: streamline binding architecture
3641 var getColCnt
= t
.getColCnt
;
3642 var getColWidth
= t
.getColWidth
;
3643 var getSlotHeight
= t
.getSlotHeight
;
3644 var getBodyContent
= t
.getBodyContent
;
3645 var reportEventElement
= t
.reportEventElement
;
3646 var showEvents
= t
.showEvents
;
3647 var hideEvents
= t
.hideEvents
;
3648 var eventDrop
= t
.eventDrop
;
3649 var eventResize
= t
.eventResize
;
3650 var renderDayOverlay
= t
.renderDayOverlay
;
3651 var clearOverlays
= t
.clearOverlays
;
3652 var calendar
= t
.calendar
;
3653 var formatDate
= calendar
.formatDate
;
3654 var formatDates
= calendar
.formatDates
;
3659 ----------------------------------------------------------------------------*/
3662 function renderEvents(events
, modifiedEventId
) {
3663 reportEvents(events
);
3664 var i
, len
=events
.length
,
3667 for (i
=0; i
<len
; i
++) {
3668 if (events
[i
].allDay
) {
3669 dayEvents
.push(events
[i
]);
3671 slotEvents
.push(events
[i
]);
3674 if (opt('allDaySlot')) {
3675 renderDaySegs(compileDaySegs(dayEvents
), modifiedEventId
);
3676 setHeight(); // no params means set to viewHeight
3678 renderSlotSegs(compileSlotSegs(slotEvents
), modifiedEventId
);
3682 function clearEvents() {
3684 getDaySegmentContainer().empty();
3685 getSlotSegmentContainer().empty();
3689 function compileDaySegs(events
) {
3690 var levels
= stackSegs(sliceSegs(events
, $.map(events
, exclEndDay
), t
.visStart
, t
.visEnd
)),
3691 i
, levelCnt
=levels
.length
, level
,
3694 for (i
=0; i
<levelCnt
; i
++) {
3696 for (j
=0; j
<level
.length
; j
++) {
3699 seg
.level
= i
; // not needed anymore
3707 function compileSlotSegs(events
) {
3708 var colCnt
= getColCnt(),
3709 minMinute
= getMinMinute(),
3710 maxMinute
= getMaxMinute(),
3711 d
= addMinutes(cloneDate(t
.visStart
), minMinute
),
3712 visEventEnds
= $.map(events
, slotEventEnd
),
3717 for (i
=0; i
<colCnt
; i
++) {
3718 col
= stackSegs(sliceSegs(events
, visEventEnds
, d
, addMinutes(cloneDate(d
), maxMinute
-minMinute
)));
3719 countForwardSegs(col
);
3720 for (j
=0; j
<col
.length
; j
++) {
3722 for (k
=0; k
<level
.length
; k
++) {
3729 addDays(d
, 1, true);
3735 function slotEventEnd(event
) {
3737 return cloneDate(event
.end
);
3739 return addMinutes(cloneDate(event
.start
), opt('defaultEventMinutes'));
3744 // renders events in the 'time slots' at the bottom
3746 function renderSlotSegs(segs
, modifiedEventId
) {
3748 var i
, segCnt
=segs
.length
, seg
,
3752 colI
, levelI
, forward
,
3766 slotSegmentContainer
= getSlotSegmentContainer(),
3768 colCnt
= getColCnt();
3770 if (rtl
= opt('isRTL')) {
3778 // calculate position/dimensions, create html
3779 for (i
=0; i
<segCnt
; i
++) {
3782 top
= timePosition(seg
.start
, seg
.start
);
3783 bottom
= timePosition(seg
.start
, seg
.end
);
3786 forward
= seg
.forward
|| 0;
3787 leftmost
= colContentLeft(colI
*dis
+ dit
);
3788 availWidth
= colContentRight(colI
*dis
+ dit
) - leftmost
;
3789 availWidth
= Math
.min(availWidth
-6, availWidth
*.95); // TODO: move this to CSS
3791 // indented and thin
3792 outerWidth
= availWidth
/ (levelI
+ forward
+ 1);
3795 // moderately wide, aligned left still
3796 outerWidth
= ((availWidth
/ (forward
+ 1)) - (12/2)) * 2; // 12 is the predicted width
of resizer
=
3798 // can be entire width, aligned left
3799 outerWidth
= availWidth
;
3802 left
= leftmost
+ // leftmost possible
3803 (availWidth
/ (levelI
+ forward
+ 1) * levelI
) // indentation
3804 * dis
+ (rtl
? availWidth
- outerWidth
: 0); // rtl
3807 seg
.outerWidth
= outerWidth
;
3808 seg
.outerHeight
= bottom
- top
;
3809 html
+= slotSegHtml(event
, seg
);
3811 slotSegmentContainer
[0].innerHTML
= html
; // faster than html()
3812 eventElements
= slotSegmentContainer
.children();
3814 // retrieve elements, run through eventRender callback, bind event handlers
3815 for (i
=0; i
<segCnt
; i
++) {
3818 eventElement
= $(eventElements
[i
]); // faster than eq()
3819 triggerRes
= trigger('eventRender', event
, event
, eventElement
);
3820 if (triggerRes
=== false) {
3821 eventElement
.remove();
3823 if (triggerRes
&& triggerRes
!== true) {
3824 eventElement
.remove();
3825 eventElement
= $(triggerRes
)
3827 position
: 'absolute',
3831 .appendTo(slotSegmentContainer
);
3833 seg
.element
= eventElement
;
3834 if (event
._id
=== modifiedEventId
) {
3835 bindSlotSeg(event
, eventElement
, seg
);
3837 eventElement
[0]._fci
= i
; // for lazySegBind
3839 reportEventElement(event
, eventElement
);
3843 lazySegBind(slotSegmentContainer
, segs
, bindSlotSeg
);
3845 // record event sides and title positions
3846 for (i
=0; i
<segCnt
; i
++) {
3848 if (eventElement
= seg
.element
) {
3849 val
= vsideCache
[key
= seg
.key
= cssKey(eventElement
[0])];
3850 seg
.vsides
= val
=== undefined ? (vsideCache
[key
] = vsides(eventElement
, true)) : val
;
3851 val
= hsideCache
[key
];
3852 seg
.hsides
= val
=== undefined ? (hsideCache
[key
] = hsides(eventElement
, true)) : val
;
3853 contentElement
= eventElement
.find('div.fc-event-content');
3854 if (contentElement
.length
) {
3855 seg
.contentTop
= contentElement
[0].offsetTop
;
3860 // set all positions/dimensions at once
3861 for (i
=0; i
<segCnt
; i
++) {
3863 if (eventElement
= seg
.element
) {
3864 eventElement
[0].style
.width
= Math
.max(0, seg
.outerWidth
- seg
.hsides
) + 'px';
3865 height
= Math
.max(0, seg
.outerHeight
- seg
.vsides
);
3866 eventElement
[0].style
.height
= height
+ 'px';
3868 if (seg
.contentTop
!== undefined && height
- seg
.contentTop
< 10) {
3869 // not enough room for title, put it in the time header
3870 eventElement
.find('div.fc-event-time')
3871 .text(formatDate(event
.start
, opt('timeFormat')) + ' - ' + event
.title
);
3872 eventElement
.find('div.fc-event-title')
3875 trigger('eventAfterRender', event
, event
, eventElement
);
3882 function slotSegHtml(event
, seg
) {
3884 var url
= event
.url
;
3885 var skinCss
= getSkinCss(event
, opt
);
3886 var skinCssAttr
= (skinCss
? " style='" + skinCss
+ "'" : '');
3887 var classes
= ['fc-event', 'fc-event-skin', 'fc-event-vert'];
3888 if (isEventDraggable(event
)) {
3889 classes
.push('fc-event-draggable');
3892 classes
.push('fc-corner-top');
3895 classes
.push('fc-corner-bottom');
3897 classes
= classes
.concat(event
.className
);
3899 classes
= classes
.concat(event
.source
.className
|| []);
3902 html
+= "a href='" + htmlEscape(event
.url
) + "'";
3907 " class='" + classes
.join(' ') + "'" +
3908 " style='position:absolute;z-index:8;top:" + seg
.top
+ "px;left:" + seg
.left
+ "px;" + skinCss
+ "'" +
3910 "<div class='fc-event-inner fc-event-skin'" + skinCssAttr
+ ">" +
3911 "<div class='fc-event-head fc-event-skin'" + skinCssAttr
+ ">" +
3912 "<div class='fc-event-time'>" +
3913 htmlEscape(formatDates(event
.start
, event
.end
, opt('timeFormat'))) +
3916 "<div class='fc-event-content'>" +
3917 "<div class='fc-event-title'>" +
3918 htmlEscape(event
.title
) +
3921 "<div class='fc-event-bg'></div>" +
3922 "</div>"; // close inner
3923 if (seg
.isEnd
&& isEventResizable(event
)) {
3925 "<div class='ui-resizable-handle ui-resizable-s'>=</div>";
3928 "</" + (url
? "a" : "div") + ">";
3933 function bindDaySeg(event
, eventElement
, seg
) {
3934 if (isEventDraggable(event
)) {
3935 draggableDayEvent(event
, eventElement
, seg
.isStart
);
3937 if (seg
.isEnd
&& isEventResizable(event
)) {
3938 resizableDayEvent(event
, eventElement
, seg
);
3940 eventElementHandlers(event
, eventElement
);
3941 // needs to be after, because resizableDayEvent might stopImmediatePropagation on click
3945 function bindSlotSeg(event
, eventElement
, seg
) {
3946 var timeElement
= eventElement
.find('div.fc-event-time');
3947 if (isEventDraggable(event
)) {
3948 draggableSlotEvent(event
, eventElement
, timeElement
);
3950 if (seg
.isEnd
&& isEventResizable(event
)) {
3951 resizableSlotEvent(event
, eventElement
, timeElement
);
3953 eventElementHandlers(event
, eventElement
);
3959 -----------------------------------------------------------------------------------*/
3962 // when event starts out FULL-DAY
3964 function draggableDayEvent(event
, eventElement
, isStart
) {
3969 var dis
= opt('isRTL') ? -1 : 1;
3970 var hoverListener
= getHoverListener();
3971 var colWidth
= getColWidth();
3972 var slotHeight
= getSlotHeight();
3973 var minMinute
= getMinMinute();
3974 eventElement
.draggable({
3976 opacity
: opt('dragOpacity', 'month'), // use whatever the month view was using
3977 revertDuration
: opt('dragRevertDuration'),
3978 start: function(ev
, ui
) {
3979 trigger('eventDragStart', eventElement
, event
, ev
, ui
);
3980 hideEvents(event
, eventElement
);
3981 origWidth
= eventElement
.width();
3982 hoverListener
.start(function(cell
, origCell
, rowDelta
, colDelta
) {
3985 //setOverflowHidden(true);
3987 dayDelta
= colDelta
* dis
;
3991 addDays(cloneDate(event
.start
), dayDelta
),
3992 addDays(exclEndDay(event
), dayDelta
)
3996 // mouse is over bottom slots
3999 // convert event to temporary slot-event
4000 eventElement
.width(colWidth
- 10); // don't use entire width
4003 slotHeight
* Math
.round(
4004 (event
.end
? ((event
.end
- event
.start
) / MINUTE_MS
) : opt('defaultEventMinutes'))
4005 / opt('slotMinutes')
4008 eventElement
.draggable('option', 'grid', [colWidth
, 1]);
4015 revert
= revert
|| (allDay
&& !dayDelta
);
4018 //setOverflowHidden(false);
4021 eventElement
.draggable('option', 'revert', revert
);
4024 stop: function(ev
, ui
) {
4025 hoverListener
.stop();
4027 trigger('eventDragStop', eventElement
, event
, ev
, ui
);
4029 // hasn't moved or is out of bounds (draggable has already reverted)
4031 eventElement
.css('filter', ''); // clear IE opacity side-effects
4032 showEvents(event
, eventElement
);
4035 var minuteDelta
= 0;
4037 minuteDelta
= Math
.round((eventElement
.offset().top
- getBodyContent().offset().top
) / slotHeight
)
4038 * opt('slotMinutes')
4040 - (event
.start
.getHours() * 60 + event
.start
.getMinutes());
4042 eventDrop(this, event
, dayDelta
, minuteDelta
, allDay
, ev
, ui
);
4044 //setOverflowHidden(false);
4047 function resetElement() {
4052 .draggable('option', 'grid', null);
4059 // when event starts out IN TIMESLOTS
4061 function draggableSlotEvent(event
, eventElement
, timeElement
) {
4066 var prevMinuteDelta
;
4067 var dis
= opt('isRTL') ? -1 : 1;
4068 var hoverListener
= getHoverListener();
4069 var colCnt
= getColCnt();
4070 var colWidth
= getColWidth();
4071 var slotHeight
= getSlotHeight();
4072 eventElement
.draggable({
4075 grid
: [colWidth
, slotHeight
],
4076 axis
: colCnt
==1 ? 'y' : false,
4077 opacity
: opt('dragOpacity'),
4078 revertDuration
: opt('dragRevertDuration'),
4079 start: function(ev
, ui
) {
4080 trigger('eventDragStart', eventElement
, event
, ev
, ui
);
4081 hideEvents(event
, eventElement
);
4082 origPosition
= eventElement
.position();
4083 minuteDelta
= prevMinuteDelta
= 0;
4084 hoverListener
.start(function(cell
, origCell
, rowDelta
, colDelta
) {
4085 eventElement
.draggable('option', 'revert', !cell
);
4088 dayDelta
= colDelta
* dis
;
4089 if (opt('allDaySlot') && !cell
.row
) {
4092 // convert to temporary all-day event
4095 eventElement
.draggable('option', 'grid', null);
4098 addDays(cloneDate(event
.start
), dayDelta
),
4099 addDays(exclEndDay(event
), dayDelta
)
4108 drag: function(ev
, ui
) {
4109 minuteDelta
= Math
.round((ui
.position
.top
- origPosition
.top
) / slotHeight
) * opt('slotMinutes');
4110 if (minuteDelta
!= prevMinuteDelta
) {
4112 updateTimeText(minuteDelta
);
4114 prevMinuteDelta
= minuteDelta
;
4117 stop: function(ev
, ui
) {
4118 var cell
= hoverListener
.stop();
4120 trigger('eventDragStop', eventElement
, event
, ev
, ui
);
4121 if (cell
&& (dayDelta
|| minuteDelta
|| allDay
)) {
4123 eventDrop(this, event
, dayDelta
, allDay
? 0 : minuteDelta
, allDay
, ev
, ui
);
4125 // either no change or out-of-bounds (draggable has already reverted)
4127 eventElement
.css('filter', ''); // clear IE opacity side-effects
4128 eventElement
.css(origPosition
); // sometimes fast drags make event revert to wrong position
4130 showEvents(event
, eventElement
);
4134 function updateTimeText(minuteDelta
) {
4135 var newStart
= addMinutes(cloneDate(event
.start
), minuteDelta
);
4138 newEnd
= addMinutes(cloneDate(event
.end
), minuteDelta
);
4140 timeElement
.text(formatDates(newStart
, newEnd
, opt('timeFormat')));
4142 function resetElement() {
4143 // convert back to original slot-event
4145 timeElement
.css('display', ''); // show() was causing display=inline
4146 eventElement
.draggable('option', 'grid', [colWidth
, slotHeight
]);
4155 --------------------------------------------------------------------------------------*/
4158 function resizableSlotEvent(event
, eventElement
, timeElement
) {
4159 var slotDelta
, prevSlotDelta
;
4160 var slotHeight
= getSlotHeight();
4161 eventElement
.resizable({
4163 s
: 'div.ui-resizable-s'
4166 start: function(ev
, ui
) {
4167 slotDelta
= prevSlotDelta
= 0;
4168 hideEvents(event
, eventElement
);
4169 eventElement
.css('z-index', 9);
4170 trigger('eventResizeStart', this, event
, ev
, ui
);
4172 resize: function(ev
, ui
) {
4173 // don't rely on ui.size.height, doesn't take grid into account
4174 slotDelta
= Math
.round((Math
.max(slotHeight
, eventElement
.height()) - ui
.originalSize
.height
) / slotHeight
);
4175 if (slotDelta
!= prevSlotDelta
) {
4179 (!slotDelta
&& !event
.end
) ? null : // no change, so don't display time range
4180 addMinutes(eventEnd(event
), opt('slotMinutes')*slotDelta
),
4184 prevSlotDelta
= slotDelta
;
4187 stop: function(ev
, ui
) {
4188 trigger('eventResizeStop', this, event
, ev
, ui
);
4190 eventResize(this, event
, 0, opt('slotMinutes')*slotDelta
, ev
, ui
);
4192 eventElement
.css('z-index', 8);
4193 showEvents(event
, eventElement
);
4194 // BUG: if event was really short, need to put title back in span
4204 function countForwardSegs(levels
) {
4205 var i
, j
, k
, level
, segForward
, segBack
;
4206 for (i
=levels
.length
-1; i
>0; i
--) {
4208 for (j
=0; j
<level
.length
; j
++) {
4209 segForward
= level
[j
];
4210 for (k
=0; k
<levels
[i
-1].length
; k
++) {
4211 segBack
= levels
[i
-1][k
];
4212 if (segsCollide(segForward
, segBack
)) {
4213 segBack
.forward
= Math
.max(segBack
.forward
||0, (segForward
.forward
||0)+1);
4223 function View(element
, calendar
, viewName
) {
4228 t
.element
= element
;
4229 t
.calendar
= calendar
;
4232 t
.trigger
= trigger
;
4233 //t.setOverflowHidden = setOverflowHidden;
4234 t
.isEventDraggable
= isEventDraggable
;
4235 t
.isEventResizable
= isEventResizable
;
4236 t
.reportEvents
= reportEvents
;
4237 t
.eventEnd
= eventEnd
;
4238 t
.reportEventElement
= reportEventElement
;
4239 t
.reportEventClear
= reportEventClear
;
4240 t
.eventElementHandlers
= eventElementHandlers
;
4241 t
.showEvents
= showEvents
;
4242 t
.hideEvents
= hideEvents
;
4243 t
.eventDrop
= eventDrop
;
4244 t
.eventResize
= eventResize
;
4247 // t.visStart, t.visEnd
4251 var defaultEventEnd
= t
.defaultEventEnd
;
4252 var normalizeEvent
= calendar
.normalizeEvent
; // in EventManager
4253 var reportEventChange
= calendar
.reportEventChange
;
4257 var eventsByID
= {};
4258 var eventElements
= [];
4259 var eventElementsByID
= {};
4260 var options
= calendar
.options
;
4264 function opt(name
, viewNameOverride
) {
4265 var v
= options
[name
];
4266 if (typeof v
== 'object') {
4267 return smartProperty(v
, viewNameOverride
|| viewName
);
4273 function trigger(name
, thisObj
) {
4274 return calendar
.trigger
.apply(
4276 [name
, thisObj
|| t
].concat(Array
.prototype.slice
.call(arguments
, 2), [t
])
4282 function setOverflowHidden(bool) {
4283 element.css('overflow', bool ? 'hidden' : '');
4288 function isEventDraggable(event
) {
4289 return isEventEditable(event
) && !opt('disableDragging');
4293 function isEventResizable(event
) { // but also need to make sure the seg.isEnd == true
4294 return isEventEditable(event
) && !opt('disableResizing');
4298 function isEventEditable(event
) {
4299 return firstDefined(event
.editable
, (event
.source
|| {}).editable
, opt('editable'));
4305 ------------------------------------------------------------------------------*/
4308 // report when view receives new events
4309 function reportEvents(events
) { // events are already normalized at this point
4311 var i
, len
=events
.length
, event
;
4312 for (i
=0; i
<len
; i
++) {
4314 if (eventsByID
[event
._id
]) {
4315 eventsByID
[event
._id
].push(event
);
4317 eventsByID
[event
._id
] = [event
];
4323 // returns a Date object for an event's end
4324 function eventEnd(event
) {
4325 return event
.end
? cloneDate(event
.end
) : defaultEventEnd(event
);
4331 ------------------------------------------------------------------------------*/
4334 // report when view creates an element for an event
4335 function reportEventElement(event
, element
) {
4336 eventElements
.push(element
);
4337 if (eventElementsByID
[event
._id
]) {
4338 eventElementsByID
[event
._id
].push(element
);
4340 eventElementsByID
[event
._id
] = [element
];
4345 function reportEventClear() {
4347 eventElementsByID
= {};
4351 // attaches eventClick, eventMouseover, eventMouseout
4352 function eventElementHandlers(event
, eventElement
) {
4354 .click(function(ev
) {
4355 if (!eventElement
.hasClass('ui-draggable-dragging') &&
4356 !eventElement
.hasClass('ui-resizable-resizing')) {
4357 return trigger('eventClick', this, event
, ev
);
4362 trigger('eventMouseover', this, event
, ev
);
4365 trigger('eventMouseout', this, event
, ev
);
4368 // TODO: don't fire eventMouseover/eventMouseout *while* dragging is occuring (on subject element)
4369 // TODO: same for resizing
4373 function showEvents(event
, exceptElement
) {
4374 eachEventElement(event
, exceptElement
, 'show');
4378 function hideEvents(event
, exceptElement
) {
4379 eachEventElement(event
, exceptElement
, 'hide');
4383 function eachEventElement(event
, exceptElement
, funcName
) {
4384 var elements
= eventElementsByID
[event
._id
],
4385 i
, len
= elements
.length
;
4386 for (i
=0; i
<len
; i
++) {
4387 if (!exceptElement
|| elements
[i
][0] != exceptElement
[0]) {
4388 elements
[i
][funcName
]();
4395 /* Event Modification Reporting
4396 ---------------------------------------------------------------------------------*/
4399 function eventDrop(e
, event
, dayDelta
, minuteDelta
, allDay
, ev
, ui
) {
4400 var oldAllDay
= event
.allDay
;
4401 var eventId
= event
._id
;
4402 moveEvents(eventsByID
[eventId
], dayDelta
, minuteDelta
, allDay
);
4411 // TODO: investigate cases where this inverse technique might not work
4412 moveEvents(eventsByID
[eventId
], -dayDelta
, -minuteDelta
, oldAllDay
);
4413 reportEventChange(eventId
);
4418 reportEventChange(eventId
);
4422 function eventResize(e
, event
, dayDelta
, minuteDelta
, ev
, ui
) {
4423 var eventId
= event
._id
;
4424 elongateEvents(eventsByID
[eventId
], dayDelta
, minuteDelta
);
4432 // TODO: investigate cases where this inverse technique might not work
4433 elongateEvents(eventsByID
[eventId
], -dayDelta
, -minuteDelta
);
4434 reportEventChange(eventId
);
4439 reportEventChange(eventId
);
4444 /* Event Modification Math
4445 ---------------------------------------------------------------------------------*/
4448 function moveEvents(events
, dayDelta
, minuteDelta
, allDay
) {
4449 minuteDelta
= minuteDelta
|| 0;
4450 for (var e
, len
=events
.length
, i
=0; i
<len
; i
++) {
4452 if (allDay
!== undefined) {
4455 addMinutes(addDays(e
.start
, dayDelta
, true), minuteDelta
);
4457 e
.end
= addMinutes(addDays(e
.end
, dayDelta
, true), minuteDelta
);
4459 normalizeEvent(e
, options
);
4464 function elongateEvents(events
, dayDelta
, minuteDelta
) {
4465 minuteDelta
= minuteDelta
|| 0;
4466 for (var e
, len
=events
.length
, i
=0; i
<len
; i
++) {
4468 e
.end
= addMinutes(addDays(eventEnd(e
), dayDelta
, true), minuteDelta
);
4469 normalizeEvent(e
, options
);
4476 function DayEventRenderer() {
4481 t
.renderDaySegs
= renderDaySegs
;
4482 t
.resizableDayEvent
= resizableDayEvent
;
4487 var trigger
= t
.trigger
;
4488 var isEventDraggable
= t
.isEventDraggable
;
4489 var isEventResizable
= t
.isEventResizable
;
4490 var eventEnd
= t
.eventEnd
;
4491 var reportEventElement
= t
.reportEventElement
;
4492 var showEvents
= t
.showEvents
;
4493 var hideEvents
= t
.hideEvents
;
4494 var eventResize
= t
.eventResize
;
4495 var getRowCnt
= t
.getRowCnt
;
4496 var getColCnt
= t
.getColCnt
;
4497 var getColWidth
= t
.getColWidth
;
4498 var allDayRow
= t
.allDayRow
;
4499 var allDayBounds
= t
.allDayBounds
;
4500 var colContentLeft
= t
.colContentLeft
;
4501 var colContentRight
= t
.colContentRight
;
4502 var dayOfWeekCol
= t
.dayOfWeekCol
;
4503 var dateCell
= t
.dateCell
;
4504 var compileDaySegs
= t
.compileDaySegs
;
4505 var getDaySegmentContainer
= t
.getDaySegmentContainer
;
4506 var bindDaySeg
= t
.bindDaySeg
; //TODO: streamline this
4507 var formatDates
= t
.calendar
.formatDates
;
4508 var renderDayOverlay
= t
.renderDayOverlay
;
4509 var clearOverlays
= t
.clearOverlays
;
4510 var clearSelection
= t
.clearSelection
;
4515 -----------------------------------------------------------------------------*/
4518 function renderDaySegs(segs
, modifiedEventId
) {
4519 var segmentContainer
= getDaySegmentContainer();
4521 var rowCnt
= getRowCnt();
4522 var colCnt
= getColCnt();
4528 var segCnt
= segs
.length
;
4532 segmentContainer
[0].innerHTML
= daySegHTML(segs
); // faster than .html()
4533 daySegElementResolve(segs
, segmentContainer
.children());
4534 daySegElementReport(segs
);
4535 daySegHandlers(segs
, segmentContainer
, modifiedEventId
);
4536 daySegCalcHSides(segs
);
4537 daySegSetWidths(segs
);
4538 daySegCalcHeights(segs
);
4539 rowDivs
= getRowDivs();
4540 // set row heights, calculate event tops (in relation to row top)
4541 for (rowI
=0; rowI
<rowCnt
; rowI
++) {
4544 for (j
=0; j
<colCnt
; j
++) {
4547 while (i
<segCnt
&& (seg
= segs
[i
]).row
== rowI
) {
4548 // loop through segs in a row
4549 top
= arrayMax(colHeights
.slice(seg
.startCol
, seg
.endCol
));
4551 top
+= seg
.outerHeight
;
4552 for (k
=seg
.startCol
; k
<seg
.endCol
; k
++) {
4553 colHeights
[k
] = top
;
4557 rowDivs
[rowI
].height(arrayMax(colHeights
));
4559 daySegSetTops(segs
, getRowTops(rowDivs
));
4563 function renderTempDaySegs(segs
, adjustRow
, adjustTop
) {
4564 var tempContainer
= $("<div/>");
4566 var segmentContainer
= getDaySegmentContainer();
4568 var segCnt
= segs
.length
;
4570 tempContainer
[0].innerHTML
= daySegHTML(segs
); // faster than .html()
4571 elements
= tempContainer
.children();
4572 segmentContainer
.append(elements
);
4573 daySegElementResolve(segs
, elements
);
4574 daySegCalcHSides(segs
);
4575 daySegSetWidths(segs
);
4576 daySegCalcHeights(segs
);
4577 daySegSetTops(segs
, getRowTops(getRowDivs()));
4579 for (i
=0; i
<segCnt
; i
++) {
4580 element
= segs
[i
].element
;
4582 if (segs
[i
].row
=== adjustRow
) {
4583 element
.css('top', adjustTop
);
4585 elements
.push(element
[0]);
4592 function daySegHTML(segs
) { // also sets seg.left and seg.outerWidth
4593 var rtl
= opt('isRTL');
4595 var segCnt
=segs
.length
;
4600 var bounds
= allDayBounds();
4601 var minLeft
= bounds
.left
;
4602 var maxLeft
= bounds
.right
;
4609 // calculate desired position/dimensions, create html
4610 for (i
=0; i
<segCnt
; i
++) {
4613 classes
= ['fc-event', 'fc-event-skin', 'fc-event-hori'];
4614 if (isEventDraggable(event
)) {
4615 classes
.push('fc-event-draggable');
4619 classes
.push('fc-corner-right');
4622 classes
.push('fc-corner-left');
4624 leftCol
= dayOfWeekCol(seg
.end
.getDay()-1);
4625 rightCol
= dayOfWeekCol(seg
.start
.getDay());
4626 left
= seg
.isEnd
? colContentLeft(leftCol
) : minLeft
;
4627 right
= seg
.isStart
? colContentRight(rightCol
) : maxLeft
;
4630 classes
.push('fc-corner-left');
4633 classes
.push('fc-corner-right');
4635 leftCol
= dayOfWeekCol(seg
.start
.getDay());
4636 rightCol
= dayOfWeekCol(seg
.end
.getDay()-1);
4637 left
= seg
.isStart
? colContentLeft(leftCol
) : minLeft
;
4638 right
= seg
.isEnd
? colContentRight(rightCol
) : maxLeft
;
4640 classes
= classes
.concat(event
.className
);
4642 classes
= classes
.concat(event
.source
.className
|| []);
4645 skinCss
= getSkinCss(event
, opt
);
4647 html
+= "<a href='" + htmlEscape(url
) + "'";
4652 " class='" + classes
.join(' ') + "'" +
4653 " style='position:absolute;z-index:8;left:"+left
+"px;" + skinCss
+ "'" +
4656 " class='fc-event-inner fc-event-skin'" +
4657 (skinCss
? " style='" + skinCss
+ "'" : '') +
4659 if (!event
.allDay
&& seg
.isStart
) {
4661 "<span class='fc-event-time'>" +
4662 htmlEscape(formatDates(event
.start
, event
.end
, opt('timeFormat'))) +
4666 "<span class='fc-event-title'>" + htmlEscape(event
.title
) + "</span>" +
4668 if (seg
.isEnd
&& isEventResizable(event
)) {
4670 "<div class='ui-resizable-handle ui-resizable-" + (rtl
? 'w' : 'e') + "'>" +
4671 " " + // makes hit area a lot better for IE6/7
4675 "</" + (url
? "a" : "div" ) + ">";
4677 seg
.outerWidth
= right
- left
;
4678 seg
.startCol
= leftCol
;
4679 seg
.endCol
= rightCol
+ 1; // needs to be exclusive
4685 function daySegElementResolve(segs
, elements
) { // sets seg.element
4687 var segCnt
= segs
.length
;
4692 for (i
=0; i
<segCnt
; i
++) {
4695 element
= $(elements
[i
]); // faster than .eq()
4696 triggerRes
= trigger('eventRender', event
, event
, element
);
4697 if (triggerRes
=== false) {
4700 if (triggerRes
&& triggerRes
!== true) {
4701 triggerRes
= $(triggerRes
)
4703 position
: 'absolute',
4706 element
.replaceWith(triggerRes
);
4707 element
= triggerRes
;
4709 seg
.element
= element
;
4715 function daySegElementReport(segs
) {
4717 var segCnt
= segs
.length
;
4720 for (i
=0; i
<segCnt
; i
++) {
4722 element
= seg
.element
;
4724 reportEventElement(seg
.event
, element
);
4730 function daySegHandlers(segs
, segmentContainer
, modifiedEventId
) {
4732 var segCnt
= segs
.length
;
4736 // retrieve elements, run through eventRender callback, bind handlers
4737 for (i
=0; i
<segCnt
; i
++) {
4739 element
= seg
.element
;
4742 if (event
._id
=== modifiedEventId
) {
4743 bindDaySeg(event
, element
, seg
);
4745 element
[0]._fci
= i
; // for lazySegBind
4749 lazySegBind(segmentContainer
, segs
, bindDaySeg
);
4753 function daySegCalcHSides(segs
) { // also sets seg.key
4755 var segCnt
= segs
.length
;
4759 var hsideCache
= {};
4760 // record event horizontal sides
4761 for (i
=0; i
<segCnt
; i
++) {
4763 element
= seg
.element
;
4765 key
= seg
.key
= cssKey(element
[0]);
4766 val
= hsideCache
[key
];
4767 if (val
=== undefined) {
4768 val
= hsideCache
[key
] = hsides(element
, true);
4776 function daySegSetWidths(segs
) {
4778 var segCnt
= segs
.length
;
4781 for (i
=0; i
<segCnt
; i
++) {
4783 element
= seg
.element
;
4785 element
[0].style
.width
= Math
.max(0, seg
.outerWidth
- seg
.hsides
) + 'px';
4791 function daySegCalcHeights(segs
) {
4793 var segCnt
= segs
.length
;
4797 var vmarginCache
= {};
4798 // record event heights
4799 for (i
=0; i
<segCnt
; i
++) {
4801 element
= seg
.element
;
4803 key
= seg
.key
; // created in daySegCalcHSides
4804 val
= vmarginCache
[key
];
4805 if (val
=== undefined) {
4806 val
= vmarginCache
[key
] = vmargins(element
);
4808 seg
.outerHeight
= element
[0].offsetHeight
+ val
;
4814 function getRowDivs() {
4816 var rowCnt
= getRowCnt();
4818 for (i
=0; i
<rowCnt
; i
++) {
4819 rowDivs
[i
] = allDayRow(i
)
4820 .find('td:first div.fc-day-content > div'); // optimal selector?
4826 function getRowTops(rowDivs
) {
4828 var rowCnt
= rowDivs
.length
;
4830 for (i
=0; i
<rowCnt
; i
++) {
4831 tops
[i
] = rowDivs
[i
][0].offsetTop
; // !!?? but this means the element needs position:relative if in a table cell!!!!
4837 function daySegSetTops(segs
, rowTops
) { // also triggers eventAfterRender
4839 var segCnt
= segs
.length
;
4843 for (i
=0; i
<segCnt
; i
++) {
4845 element
= seg
.element
;
4847 element
[0].style
.top
= rowTops
[seg
.row
] + (seg
.top
||0) + 'px';
4849 trigger('eventAfterRender', event
, event
, element
);
4857 -----------------------------------------------------------------------------------*/
4860 function resizableDayEvent(event
, element
, seg
) {
4861 var rtl
= opt('isRTL');
4862 var direction
= rtl
? 'w' : 'e';
4863 var handle
= element
.find('div.ui-resizable-' + direction
);
4864 var isResizing
= false;
4866 // TODO: look into using jquery-ui mouse widget for this stuff
4867 disableTextSelection(element
); // prevent native <a> selection for IE
4869 .mousedown(function(ev
) { // prevent native <a> selection for others
4870 ev
.preventDefault();
4872 .click(function(ev
) {
4874 ev
.preventDefault(); // prevent link from being visited (only method that worked in IE6)
4875 ev
.stopImmediatePropagation(); // prevent fullcalendar eventClick handler from being called
4876 // (eventElementHandlers needs to be bound after resizableDayEvent)
4880 handle
.mousedown(function(ev
) {
4881 if (ev
.which
!= 1) {
4882 return; // needs to be left mouse button
4885 var hoverListener
= t
.getHoverListener();
4886 var rowCnt
= getRowCnt();
4887 var colCnt
= getColCnt();
4888 var dis
= rtl
? -1 : 1;
4889 var dit
= rtl
? colCnt
-1 : 0;
4890 var elementTop
= element
.css('top');
4893 var eventCopy
= $.extend({}, event
);
4894 var minCell
= dateCell(event
.start
);
4897 .css('cursor', direction
+ '-resize')
4898 .one('mouseup', mouseup
);
4899 trigger('eventResizeStart', this, event
, ev
);
4900 hoverListener
.start(function(cell
, origCell
) {
4902 var r
= Math
.max(minCell
.row
, cell
.row
);
4905 r
= 0; // hack for all-day area in agenda views
4907 if (r
== minCell
.row
) {
4909 c
= Math
.min(minCell
.col
, c
);
4911 c
= Math
.max(minCell
.col
, c
);
4914 dayDelta
= (r
*7 + c
*dis
+dit
) - (origCell
.row
*7 + origCell
.col
*dis
+dit
);
4915 var newEnd
= addDays(eventEnd(event
), dayDelta
, true);
4917 eventCopy
.end
= newEnd
;
4918 var oldHelpers
= helpers
;
4919 helpers
= renderTempDaySegs(compileDaySegs([eventCopy
]), seg
.row
, elementTop
);
4920 helpers
.find('*').css('cursor', direction
+ '-resize');
4922 oldHelpers
.remove();
4933 renderDayOverlay(event
.start
, addDays(cloneDate(newEnd
), 1)); // coordinate grid already rebuild at hoverListener.start
4937 function mouseup(ev
) {
4938 trigger('eventResizeStop', this, event
, ev
);
4939 $('body').css('cursor', '');
4940 hoverListener
.stop();
4943 eventResize(this, event
, dayDelta
, 0, ev
);
4944 // event redraw will clear helpers
4946 // otherwise, the drag handler already restored the old events
4948 setTimeout(function() { // make this happen after the element's click event
4959 //BUG: unselect needs to be triggered when events are dragged+dropped
4961 function SelectionManager() {
4967 t
.unselect
= unselect
;
4968 t
.reportSelection
= reportSelection
;
4969 t
.daySelectionMousedown
= daySelectionMousedown
;
4974 var trigger
= t
.trigger
;
4975 var defaultSelectionEnd
= t
.defaultSelectionEnd
;
4976 var renderSelection
= t
.renderSelection
;
4977 var clearSelection
= t
.clearSelection
;
4981 var selected
= false;
4986 if (opt('selectable') && opt('unselectAuto')) {
4987 $(document
).mousedown(function(ev
) {
4988 var ignore
= opt('unselectCancel');
4990 if ($(ev
.target
).parents(ignore
).length
) { // could be optimized to stop after first match
4999 function select(startDate
, endDate
, allDay
) {
5002 endDate
= defaultSelectionEnd(startDate
, allDay
);
5004 renderSelection(startDate
, endDate
, allDay
);
5005 reportSelection(startDate
, endDate
, allDay
);
5009 function unselect(ev
) {
5013 trigger('unselect', null, ev
);
5018 function reportSelection(startDate
, endDate
, allDay
, ev
) {
5020 trigger('select', null, startDate
, endDate
, allDay
, ev
);
5024 function daySelectionMousedown(ev
) { // not really a generic manager method, oh well
5025 var cellDate
= t
.cellDate
;
5026 var cellIsAllDay
= t
.cellIsAllDay
;
5027 var hoverListener
= t
.getHoverListener();
5028 var reportDayClick
= t
.reportDayClick
; // this is hacky and sort of weird
5029 if (ev
.which
== 1 && opt('selectable')) { // which==1 means left mouse button
5031 var _mousedownElement
= this;
5033 hoverListener
.start(function(cell
, origCell
) { // TODO: maybe put cellDate/cellIsAllDay info in cell
5035 if (cell
&& cellIsAllDay(cell
)) {
5036 dates
= [ cellDate(origCell
), cellDate(cell
) ].sort(cmp
);
5037 renderSelection(dates
[0], dates
[1], true);
5042 $(document
).one('mouseup', function(ev
) {
5043 hoverListener
.stop();
5045 if (+dates
[0] == +dates
[1]) {
5046 reportDayClick(dates
[0], true, ev
);
5048 reportSelection(dates
[0], dates
[1], true, ev
);
5057 function OverlayManager() {
5062 t
.renderOverlay
= renderOverlay
;
5063 t
.clearOverlays
= clearOverlays
;
5067 var usedOverlays
= [];
5068 var unusedOverlays
= [];
5071 function renderOverlay(rect
, parent
) {
5072 var e
= unusedOverlays
.shift();
5074 e
= $("<div class='fc-cell-overlay' style='position:absolute;z-index:3'/>");
5076 if (e
[0].parentNode
!= parent
[0]) {
5079 usedOverlays
.push(e
.css(rect
).show());
5084 function clearOverlays() {
5086 while (e
= usedOverlays
.shift()) {
5087 unusedOverlays
.push(e
.hide().unbind());
5094 function CoordinateGrid(buildFunc
) {
5101 t
.build = function() {
5104 buildFunc(rows
, cols
);
5108 t
.cell = function(x
, y
) {
5109 var rowCnt
= rows
.length
;
5110 var colCnt
= cols
.length
;
5112 for (i
=0; i
<rowCnt
; i
++) {
5113 if (y
>= rows
[i
][0] && y
< rows
[i
][1]) {
5118 for (i
=0; i
<colCnt
; i
++) {
5119 if (x
>= cols
[i
][0] && x
< cols
[i
][1]) {
5124 return (r
>=0 && c
>=0) ? { row
:r
, col
:c
} : null;
5128 t
.rect = function(row0
, col0
, row1
, col1
, originElement
) { // row1,col1 is inclusive
5129 var origin
= originElement
.offset();
5131 top
: rows
[row0
][0] - origin
.top
,
5132 left
: cols
[col0
][0] - origin
.left
,
5133 width
: cols
[col1
][1] - cols
[col0
][0],
5134 height
: rows
[row1
][1] - rows
[row0
][0]
5140 function HoverListener(coordinateGrid
) {
5150 t
.start = function(_change
, ev
, _bindType
) {
5152 firstCell
= cell
= null;
5153 coordinateGrid
.build();
5155 bindType
= _bindType
|| 'mousemove';
5156 $(document
).bind(bindType
, mouse
);
5160 function mouse(ev
) {
5161 var newCell
= coordinateGrid
.cell(ev
.pageX
, ev
.pageY
);
5162 if (!newCell
!= !cell
|| newCell
&& (newCell
.row
!= cell
.row
|| newCell
.col
!= cell
.col
)) {
5165 firstCell
= newCell
;
5167 change(newCell
, firstCell
, newCell
.row
-firstCell
.row
, newCell
.col
-firstCell
.col
);
5169 change(newCell
, firstCell
);
5176 t
.stop = function() {
5177 $(document
).unbind(bindType
, mouse
);
5184 function HorizontalPositionCache(getElement
) {
5192 return elements
[i
] = elements
[i
] || getElement(i
);
5195 t
.left = function(i
) {
5196 return lefts
[i
] = lefts
[i
] === undefined ? e(i
).position().left
: lefts
[i
];
5199 t
.right = function(i
) {
5200 return rights
[i
] = rights
[i
] === undefined ? t
.left(i
) + e(i
).width() : rights
[i
];
5203 t
.clear = function() {