PageSourceSearch

https://www.socialmotus.com/js/calendar.js

js socialmotus.com collected 2026-10-04 21:14:23 UTC 132,257 bytes, 5,330 lines download raw bytes

1$(document).ready(function () {
2	/**
3	 * @preserve
4	 * FullCalendar v1.5.1
5	 * http://arshaw.com/fullcalendar/
6	 *
7	 * Use fullcalendar.css for basic styling.
8	 * For event drag & drop, requires jQuery UI draggable.
9	 * For event resizing, requires jQuery UI resizable.
10	 *
11	 * Copyright (c) 2011 Adam Shaw
12	 * Dual licensed under the MIT and GPL licenses, located in
13	 * MIT-LICENSE.txt and GPL-LICENSE.txt respectively.
14	 *
15	 * Date: Sat Apr 9 14:09:51 2011 -0700
16	 *
17	 */
18	 
19	(function($, undefined) {
20	
21	
22	var defaults = {
23	
24		// display
25		defaultView: 'month',
26		aspectRatio: 1.35,
27		header: {
28			left: 'title',
29			center: '',
30			right: 'today prev,next'
31		},
32		weekends: true,
33		
34		// editing
35		//editable: false,
36		//disableDragging: false,
37		//disableResizing: false,
38		
39		allDayDefault: true,
40		ignoreTimezone: true,
41		
42		// event ajax
43		lazyFetching: true,
44		startParam: 'start',
45		endParam: 'end',
46		
47		// time formats
48		titleFormat: {
49			month: 'MMMM yyyy',
50			week: "MMM d[ yyyy]{ '—'[ MMM] d yyyy}",
51			day: 'dddd, MMM d, yyyy'
52		},
53		columnFormat: {
54			month: 'ddd',
55			week: 'ddd M/d',
56			day: 'dddd M/d'
57		},
58		timeFormat: { // for event elements
59			'': 'h(:mm)t' // default
60		},
61		
62		// locale
63		isRTL: false,
64		firstDay: 0,
65		monthNames: ['January','February','March','April','May','June','July','August','September','October','November','December'],
66		monthNamesShort: ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'],
67		dayNames: ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'],
68		dayNamesShort: ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'],
69		buttonText: {
70			prev: ' ◄ ',
71			next: ' ► ',
72			prevYear: ' << ',
73			nextYear: ' >> ',
74			today: 'today',
75			month: 'month',
76			week: 'week',
77			day: 'day'
78		},
79		
80		// jquery-ui theming
81		theme: false,
82		buttonIcons: {
83			prev: 'circle-triangle-w',
84			next: 'circle-triangle-e'
85		},
86		
87		//selectable: false,
88		unselectAuto: true,
89		
90		dropAccept: '*'
91		
92	};
93	
94	// right-to-left defaults
95	var rtlDefaults = {
96		header: {
97			left: 'next,prev today',
98			center: '',
99			right: 'title'
100		},
101		buttonText: {
102			prev: ' ► ',
103			next: ' ◄ ',
104			prevYear: ' >> ',
105			nextYear: ' << '
106		},
107		buttonIcons: {
108			prev: 'circle-triangle-e',
109			next: 'circle-triangle-w'
110		}
111	};
112	
113	
114	
115	var fc = $.fullCalendar = { version: "1.5.1" };
116	var fcViews = fc.views = {};
117	
118	
119	$.fn.fullCalendar = function(options) {
120	
121	
122		// method calling
123		if (typeof options == 'string') {
124			var args = Array.prototype.slice.call(arguments, 1);
125			var res;
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) {
131						res = r;
132					}
133					if (options == 'destroy') {
134						$.removeData(this, 'fullCalendar');
135					}
136				}
137			});
138			if (res !== undefined) {
139				return res;
140			}
141			return this;
142		}
143		
144		
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;
151		}
152		
153	
154		options = $.extend(true, {},
155			defaults,
156			(options.isRTL || options.isRTL===undefined && defaults.isRTL) ? rtlDefaults : {},
157			options
158		);
159		
160		
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
165			calendar.render();
166		});
167		
168		
169		return this;
170		
171	};
172	
173	
174	// function for adding/overriding defaults
175	function setDefaults(d) {
176		$.extend(true, defaults, d);
177	}
178	
179	
180	
181	 
182	function Calendar(element, options, eventSources) {
183		var t = this;
184		
185		
186		// exports
187		t.options = options;
188		t.render = render;
189		t.destroy = destroy;
190		t.refetchEvents = refetchEvents;
191		t.reportEvents = reportEvents;
192		t.reportEventChange = reportEventChange;
193		t.rerenderEvents = rerenderEvents;
194		t.changeView = changeView;
195		t.select = select;
196		t.unselect = unselect;
197		t.prev = prev;
198		t.next = next;
199		t.prevYear = prevYear;
200		t.nextYear = nextYear;
201		t.today = today;
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) };
206		t.getDate = getDate;
207		t.getView = getView;
208		t.option = option;
209		t.trigger = trigger;
210		
211		
212		// imports
213		EventManager.call(t, options, eventSources);
214		var isFetchNeeded = t.isFetchNeeded;
215		var fetchEvents = t.fetchEvents;
216		
217		
218		// locals
219		var _element = element[0];
220		var header;
221		var headerElement;
222		var content;
223		var tm; // for making theme classes
224		var currentView;
225		var viewInstances = {};
226		var elementOuterWidth;
227		var suggestedViewHeight;
228		var absoluteViewElement;
229		var resizeUID = 0;
230		var ignoreWindowResize = 0;
231		var date = new Date();
232		var events = [];
233		var _dragElement;
234		
235		
236		
237		/* Main Rendering
238		-----------------------------------------------------------------------------*/
239		
240		
241		setYMD(date, options.year, options.month, options.date);
242		
243		
244		function render(inc) {
245			if (!content) {
246				initialRender();
247			}else{
248				calcSize();
249				markSizesDirty();
250				markEventsDirty();
251				renderView(inc);
252			}
253		}
254		
255		
256		function initialRender() {
257			tm = options.theme ? 'ui' : 'fc';
258			element.addClass('fc');
259			if (options.isRTL) {
260				element.addClass('fc-rtl');
261			}
262			if (options.theme) {
263				element.addClass('ui-widget');
264			}
265			content = $("<div class='fc-content' style='position:relative'/>")
266				.prependTo(element);
267			header = new Header(t, options);
268			headerElement = header.render();
269			if (headerElement) {
270				element.prepend(headerElement);
271			}
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()) {
276				lateRender();
277			}
278		}
279		
280		
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
286					renderView();
287				}
288			},0);
289		}
290		
291		
292		function destroy() {
293			$(window).unbind('resize', windowResize);
294			header.destroy();
295			content.remove();
296			element.removeClass('fc fc-rtl ui-widget');
297		}
298		
299		
300		
301		function elementVisible() {
302			return _element.offsetWidth !== 0;
303		}
304		
305		
306		function bodyVisible() {
307			return $('body')[0].offsetWidth !== 0;
308		}
309		
310		
311		
312		/* View Rendering
313		-----------------------------------------------------------------------------*/
314		
315		// TODO: improve view switching (still weird transition in IE, and FF has whiteout problem)
316		
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
320	
321				unselect();
322				
323				var oldView = currentView;
324				var newViewElement;
325					
326				if (oldView) {
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();
330				}else{
331					setMinHeight(content, 1); // needs to be 1 (not 0) for IE7, or else view dimensions miscalculated
332				}
333				content.css('overflow', 'hidden');
334				
335				currentView = viewInstances[newViewName];
336				if (currentView) {
337					currentView.element.show();
338				}else{
339					currentView = viewInstances[newViewName] = new fcViews[newViewName](
340						newViewElement = absoluteViewElement =
341							$("<div class='fc-view fc-view-" + newViewName + "' style='position:absolute'/>")
342								.appendTo(content),
343						t // the calendar object
344					);
345				}
346				
347				if (oldView) {
348					header.deactivateButton(oldView.name);
349				}
350				header.activateButton(newViewName);
351				
352				renderView(); // after height has been set, will make absoluteViewElement's position=relative, then set to null
353				
354				content.css('overflow', '');
355				if (oldView) {
356					setMinHeight(content, 1);
357				}
358				
359				if (!newViewElement) {
360					(currentView.afterShow || noop)(); // called after setting min-height/overflow, so in final scroll state (for Opera)
361				}
362				
363				ignoreWindowResize--;
364			}
365		}
366		
367		
368		
369		function renderView(inc) {
370			if (elementVisible()) {
371				ignoreWindowResize++; // because renderEvents might temporarily change the height before setSize is reached
372	
373				unselect();
374				
375				if (suggestedViewHeight === undefined) {
376					calcSize();
377				}
378				
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
383					setSize(true);
384					forceEventRender = true;
385				}
386				else if (currentView.sizeDirty) {
387					// view must resize (and rerender events)
388					currentView.clearEvents();
389					setSize();
390					forceEventRender = true;
391				}
392				else if (currentView.eventsDirty) {
393					currentView.clearEvents();
394					forceEventRender = true;
395				}
396				currentView.sizeDirty = false;
397				currentView.eventsDirty = false;
398				updateEvents(forceEventRender);
399				
400				elementOuterWidth = element.outerWidth();
401				
402				header.updateTitle(currentView.title);
403				var today = new Date();
404				if (today >= currentView.start && today < currentView.end) {
405					header.disableButton('today');
406				}else{
407					header.enableButton('today');
408				}
409				
410				ignoreWindowResize--;
411				currentView.trigger('viewDisplay', _element);
412			}
413		}
414		
415		
416		
417		/* Resizing
418		-----------------------------------------------------------------------------*/
419		
420		
421		function updateSize() {
422			markSizesDirty();
423			if (elementVisible()) {
424				calcSize();
425				setSize();
426				unselect();
427				currentView.clearEvents();
428				currentView.renderEvents(events);
429				currentView.sizeDirty = false;
430			}
431		}
432		
433		
434		function markSizesDirty() {
435			$.each(viewInstances, function(i, inst) {
436				inst.sizeDirty = true;
437			});
438		}
439		
440		
441		function calcSize() {
442			if (options.contentHeight) {
443				suggestedViewHeight = options.contentHeight;
444			}
445			else if (options.height) {
446				suggestedViewHeight = options.height - (headerElement ? headerElement.height() : 0) - vsides(content);
447			}
448			else {
449				suggestedViewHeight = Math.round(content.width() / Math.max(options.aspectRatio, .5));
450			}
451		}
452		
453		
454		function setSize(dateChanged) { // todo: dateChanged?
455			ignoreWindowResize++;
456			currentView.setHeight(suggestedViewHeight, dateChanged);
457			if (absoluteViewElement) {
458				absoluteViewElement.css('position', 'relative');
459				absoluteViewElement = null;
460			}
461			currentView.setWidth(content.width(), dateChanged);
462			ignoreWindowResize--;
463		}
464		
465		
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
474								updateSize();
475								currentView.trigger('windowResize', _element);
476								ignoreWindowResize--;
477							}
478						}
479					}, 200);
480				}else{
481					// calendar must have been initialized in a 0x0 iframe that has just been resized
482					lateRender();
483				}
484			}
485		}
486		
487		
488		
489		/* Event Fetching/Rendering
490		-----------------------------------------------------------------------------*/
491		
492		
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)) {
496				refetchEvents();
497			}
498			else if (forceRender) {
499				rerenderEvents();
500			}
501		}
502		
503		
504		function refetchEvents() {
505			fetchEvents(currentView.visStart, currentView.visEnd); // will call reportEvents
506		}
507		
508		
509		// called when event data arrives
510		function reportEvents(_events) {
511			events = _events;
512			rerenderEvents();
513		}
514		
515		
516		// called when a single event's data has been changed
517		function reportEventChange(eventID) {
518			rerenderEvents(eventID);
519		}
520		
521		
522		// attempts to rerenderEvents
523		function rerenderEvents(modifiedEventID) {
524			markEventsDirty();
525			if (elementVisible()) {
526				currentView.clearEvents();
527				currentView.renderEvents(events, modifiedEventID);
528				currentView.eventsDirty = false;
529			}
530		}
531		
532		
533		function markEventsDirty() {
534			$.each(viewInstances, function(i, inst) {
535				inst.eventsDirty = true;
536			});
537		}
538		
539	
540	
541		/* Selection
542		-----------------------------------------------------------------------------*/
543		
544	
545		function select(start, end, allDay) {
546			currentView.select(start, end, allDay===undefined ? true : allDay);
547		}
548		
549	
550		function unselect() { // safe to be called before renderView
551			if (currentView) {
552				currentView.unselect();
553			}
554		}
555		
556		
557		
558		/* Date
559		-----------------------------------------------------------------------------*/
560		
561		
562		function prev() {
563			renderView(-1);
564		}
565		
566		
567		function next() {
568			renderView(1);
569		}
570		
571		
572		function prevYear() {
573			addYears(date, -1);
574			renderView();
575		}
576		
577		
578		function nextYear() {
579			addYears(date, 1);
580			renderView();
581		}
582		
583		
584		function today() {
585			date = new Date();
586			renderView();
587		}
588		
589		
590		function gotoDate(year, month, dateOfMonth) {
591			if (year instanceof Date) {
592				date = cloneDate(year); // provided 1 argument, a Date
593			}else{
594				setYMD(date, year, month, dateOfMonth);
595			}
596			renderView();
597		}
598		
599		
600		function incrementDate(years, months, days) {
601			if (years !== undefined) {
602				addYears(date, years);
603			}
604			if (months !== undefined) {
605				addMonths(date, months);
606			}
607			if (days !== undefined) {
608				addDays(date, days);
609			}
610			renderView();
611		}
612		
613		
614		function getDate() {
615			return cloneDate(date);
616		}
617		
618		
619		
620		/* Misc
621		-----------------------------------------------------------------------------*/
622		
623		
624		function getView() {
625			return currentView;
626		}
627		
628		
629		function option(name, value) {
630			if (value === undefined) {
631				return options[name];
632			}
633			if (name == 'height' || name == 'contentHeight' || name == 'aspectRatio') {
634				options[name] = value;
635				updateSize();
636			}
637		}
638		
639		
640		function trigger(name, thisObj) {
641			if (options[name]) {
642				return options[name].apply(
643					thisObj || _element,
644					Array.prototype.slice.call(arguments, 2)
645				);
646			}
647		}
648		
649		
650		
651		/* External Dragging
652		------------------------------------------------------------------------*/
653		
654		if (options.droppable) {
655			$(document)
656				.bind('dragstart', function(ev, ui) {
657					var _e = ev.target;
658					var e = $(_e);
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)) {
662							_dragElement = _e;
663							currentView.dragStart(_dragElement, ev, ui);
664						}
665					}
666				})
667				.bind('dragstop', function(ev, ui) {
668					if (_dragElement) {
669						currentView.dragStop(_dragElement, ev, ui);
670						_dragElement = null;
671					}
672				});
673		}
674		
675	
676	}
677	
678	function Header(calendar, options) {
679		var t = this;
680		
681		
682		// exports
683		t.render = render;
684		t.destroy = destroy;
685		t.updateTitle = updateTitle;
686		t.activateButton = activateButton;
687		t.deactivateButton = deactivateButton;
688		t.disableButton = disableButton;
689		t.enableButton = enableButton;
690		
691		
692		// locals
693		var element = $([]);
694		var tm;
695		
696	
697	
698		function render() {
699			tm = options.theme ? 'ui' : 'fc';
700			var sections = options.header;
701			if (sections) {
702				element = $("<table class='fc-header' style='width:100%'/>")
703					.append(
704						$("<tr/>")
705							.append(renderSection('left'))
706							.append(renderSection('center'))
707							.append(renderSection('right'))
708					);
709				return element;
710			}
711		}
712		
713		
714		function destroy() {
715			element.remove();
716		}
717		
718		
719		function renderSection(position) {
720			var e = $("<td class='fc-header-" + position + "'/>");
721			var buttonStr = options.header[position];
722			if (buttonStr) {
723				$.each(buttonStr.split(' '), function(i) {
724					if (i > 0) {
725						e.append("<span class='fc-header-space'/>");
726					}
727					var prevButton;
728					$.each(this.split(','), function(j, buttonName) {
729						if (buttonName == 'title') {
730							e.append("<span class='fc-header-title'><h2>&nbsp;</h2></span>");
731							if (prevButton) {
732								prevButton.addClass(tm + '-corner-right');
733							}
734							prevButton = null;
735						}else{
736							var buttonClick;
737							if (calendar[buttonName]) {
738								buttonClick = calendar[buttonName]; // calendar method
739							}
740							else if (fcViews[buttonName]) {
741								buttonClick = function() {
742									button.removeClass(tm + '-state-hover'); // forget why
743									calendar.changeView(buttonName);
744								};
745							}
746							if (buttonClick) {
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?
749								var button = $(
750									"<span class='fc-button fc-button-" + buttonName + " " + tm + "-state-default'>" +
751										"<span class='fc-button-inner'>" +
752											"<span class='fc-button-content'>" +
753												(icon ?
754													"<span class='fc-icon-wrap'>" +
755														"<span class='ui-icon ui-icon-" + icon + "'/>" +
756													"</span>" :
757													text
758													) +
759											"</span>" +
760											"<span class='fc-button-effect'><span></span></span>" +
761										"</span>" +
762									"</span>"
763								);
764								if (button) {
765									button
766										.click(function() {
767											if (!button.hasClass(tm + '-state-disabled')) {
768												buttonClick();
769											}
770										})
771										.mousedown(function() {
772											button
773												.not('.' + tm + '-state-active')
774												.not('.' + tm + '-state-disabled')
775												.addClass(tm + '-state-down');
776										})
777										.mouseup(function() {
778											button.removeClass(tm + '-state-down');
779										})
780										.hover(
781											function() {
782												button
783													.not('.' + tm + '-state-active')
784													.not('.' + tm + '-state-disabled')
785													.addClass(tm + '-state-hover');
786											},
787											function() {
788												button
789													.removeClass(tm + '-state-hover')
790													.removeClass(tm + '-state-down');
791											}
792										)
793										.appendTo(e);
794									if (!prevButton) {
795										button.addClass(tm + '-corner-left');
796									}
797									prevButton = button;
798								}
799							}
800						}
801					});
802					if (prevButton) {
803						prevButton.addClass(tm + '-corner-right');
804					}
805				});
806			}
807			return e;
808		}
809		
810		
811		function updateTitle(html) {
812			element.find('h2')
813				.html(html);
814		}
815		
816		
817		function activateButton(buttonName) {
818			element.find('span.fc-button-' + buttonName)
819				.addClass(tm + '-state-active');
820		}
821		
822		
823		function deactivateButton(buttonName) {
824			element.find('span.fc-button-' + buttonName)
825				.removeClass(tm + '-state-active');
826		}
827		
828		
829		function disableButton(buttonName) {
830			element.find('span.fc-button-' + buttonName)
831				.addClass(tm + '-state-disabled');
832		}
833		
834		
835		function enableButton(buttonName) {
836			element.find('span.fc-button-' + buttonName)
837				.removeClass(tm + '-state-disabled');
838		}
839	
840	
841	}
842	
843	fc.sourceNormalizers = [];
844	fc.sourceFetchers = [];
845	
846	var ajaxDefaults = {
847		dataType: 'json',
848		cache: false
849	};
850	
851	var eventGUID = 1;
852	
853	
854	function EventManager(options, _sources) {
855		var t = this;
856		
857		
858		// exports
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;
868		
869		
870		// imports
871		var trigger = t.trigger;
872		var getView = t.getView;
873		var reportEvents = t.reportEvents;
874		
875		
876		// locals
877		var stickySource = { events: [] };
878		var sources = [ stickySource ];
879		var rangeStart, rangeEnd;
880		var currentFetchID = 0;
881		var pendingSourceCnt = 0;
882		var loadingLevel = 0;
883		var cache = [];
884		
885		
886		for (var i=0; i<_sources.length; i++) {
887			_addEventSource(_sources[i]);
888		}
889		
890		
891		
892		/* Fetching
893		-----------------------------------------------------------------------------*/
894		
895		
896		function isFetchNeeded(start, end) {
897			return !rangeStart || start < rangeStart || end > rangeEnd;
898		}
899		
900		
901		function fetchEvents(start, end) {
902			rangeStart = start;
903			rangeEnd = end;
904			cache = [];
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);
910			}
911		}
912		
913		
914		function fetchEventSource(source, fetchID) {
915			_fetchEventSource(source, function(events) {
916				if (fetchID == currentFetchID) {
917					if (events) {
918						for (var i=0; i<events.length; i++) {
919							events[i].source = source;
920							normalizeEvent(events[i]);
921						}
922						cache = cache.concat(events);
923					}
924					pendingSourceCnt--;
925					if (!pendingSourceCnt) {
926						reportEvents(cache);
927					}
928				}
929			});
930		}
931		
932		
933		function _fetchEventSource(source, callback) {
934			var i;
935			var fetchers = fc.sourceFetchers;
936			var res;
937			for (i=0; i<fetchers.length; i++) {
938				res = fetchers[i](source, rangeStart, rangeEnd, callback);
939				if (res === true) {
940					// the fetcher is in charge. made its own async request
941					return;
942				}
943				else if (typeof res == 'object') {
944					// the fetcher returned a new source. process it
945					_fetchEventSource(res, callback);
946					return;
947				}
948			}
949			var events = source.events;
950			if (events) {
951				if ($.isFunction(events)) {
952					pushLoading();
953					events(cloneDate(rangeStart), cloneDate(rangeEnd), function(events) {
954						callback(events);
955						popLoading();
956					});
957				}
958				else if ($.isArray(events)) {
959					callback(events);
960				}
961				else {
962					callback();
963				}
964			}else{
965				var url = source.url;
966				if (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);
973					if (startParam) {
974						data[startParam] = Math.round(+rangeStart / 1000);
975					}
976					if (endParam) {
977						data[endParam] = Math.round(+rangeEnd / 1000);
978					}
979					pushLoading();
980					$.ajax($.extend({}, ajaxDefaults, source, {
981						data: data,
982						success: function(events) {
983							events = events || [];
984							var res = applyAll(success, this, arguments);
985							if ($.isArray(res)) {
986								events = res;
987							}
988							callback(events);
989						},
990						error: function() {
991							applyAll(error, this, arguments);
992							callback();
993						},
994						complete: function() {
995							applyAll(complete, this, arguments);
996							popLoading();
997						}
998					}));
999				}else{
1000					callback();
1001				}
1002			}
1003		}
1004		
1005		
1006		
1007		/* Sources
1008		-----------------------------------------------------------------------------*/
1009		
1010	
1011		function addEventSource(source) {
1012			source = _addEventSource(source);
1013			if (source) {
1014				pendingSourceCnt++;
1015				fetchEventSource(source, currentFetchID); // will eventually call reportEvents
1016			}
1017		}
1018		
1019		
1020		function _addEventSource(source) {
1021			if ($.isFunction(source) || $.isArray(source)) {
1022				source = { events: source };
1023			}
1024			else if (typeof source == 'string') {
1025				source = { url: source };
1026			}
1027			if (typeof source == 'object') {
1028				normalizeSource(source);
1029				sources.push(source);
1030				return source;
1031			}
1032		}
1033		
1034	
1035		function removeEventSource(source) {
1036			sources = $.grep(sources, function(src) {
1037				return !isSourcesEqual(src, source);
1038			});
1039			// remove all client events from that source
1040			cache = $.grep(cache, function(e) {
1041				return !isSourcesEqual(e.source, source);
1042			});
1043			reportEvents(cache);
1044		}
1045		
1046		
1047		
1048		/* Manipulation
1049		-----------------------------------------------------------------------------*/
1050		
1051		
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++) {
1060				e = cache[i];
1061				if (e._id == event._id && e != event) {
1062					e.start = new Date(+e.start + startDelta);
1063					if (event.end) {
1064						if (e.end) {
1065							e.end = new Date(+e.end + endDelta);
1066						}else{
1067							e.end = new Date(+defaultEventEnd(e) + endDelta);
1068						}
1069					}else{
1070						e.end = null;
1071					}
1072					e.title = event.title;
1073					e.url = event.url;
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;
1081					normalizeEvent(e);
1082				}
1083			}
1084			normalizeEvent(event);
1085			reportEvents(cache);
1086		}
1087		
1088		
1089		function renderEvent(event, stick) {
1090			normalizeEvent(event);
1091			if (!event.source) {
1092				if (stick) {
1093					stickySource.events.push(event);
1094					event.source = stickySource;
1095				}
1096				cache.push(event);
1097			}
1098			reportEvents(cache);
1099		}
1100		
1101		
1102		function removeEvents(filter) {
1103			if (!filter) { // remove all
1104				cache = [];
1105				// clear all array sources
1106				for (var i=0; i<sources.length; i++) {
1107					if ($.isArray(sources[i].events)) {
1108						sources[i].events = [];
1109					}
1110				}
1111			}else{
1112				if (!$.isFunction(filter)) { // an event ID
1113					var id = filter + '';
1114					filter = function(e) {
1115						return e._id == id;
1116					};
1117				}
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);
1123					}
1124				}
1125			}
1126			reportEvents(cache);
1127		}
1128		
1129		
1130		function clientEvents(filter) {
1131			if ($.isFunction(filter)) {
1132				return $.grep(cache, filter);
1133			}
1134			else if (filter) { // an event ID
1135				filter += '';
1136				return $.grep(cache, function(e) {
1137					return e._id == filter;
1138				});
1139			}
1140			return cache; // else, return all
1141		}
1142		
1143		
1144		
1145		/* Loading State
1146		-----------------------------------------------------------------------------*/
1147		
1148		
1149		function pushLoading() {
1150			if (!loadingLevel++) {
1151				trigger('loading', null, true);
1152			}
1153		}
1154		
1155		
1156		function popLoading() {
1157			if (!--loadingLevel) {
1158				trigger('loading', null, false);
1159			}
1160		}
1161		
1162		
1163		
1164		/* Event Normalization
1165		-----------------------------------------------------------------------------*/
1166		
1167		
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 + '');
1172			if (event.date) {
1173				if (!event.start) {
1174					event.start = event.date;
1175				}
1176				delete event.date;
1177			}
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) {
1181				event.end = null;
1182			}
1183			event._end = event.end ? cloneDate(event.end) : null;
1184			if (event.allDay === undefined) {
1185				event.allDay = firstDefined(source.allDayDefault, options.allDayDefault);
1186			}
1187			if (event.className) {
1188				if (typeof event.className == 'string') {
1189					event.className = event.className.split(/\s+/);
1190				}
1191			}else{
1192				event.className = [];
1193			}
1194			// TODO: if there is no start date, return false to indicate an invalid event
1195		}
1196		
1197		
1198		
1199		/* Utils
1200		------------------------------------------------------------------------------*/
1201		
1202		
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+/);
1208				}
1209			}else{
1210				source.className = [];
1211			}
1212			var normalizers = fc.sourceNormalizers;
1213			for (var i=0; i<normalizers.length; i++) {
1214				normalizers[i](source);
1215			}
1216		}
1217		
1218		
1219		function isSourcesEqual(source1, source2) {
1220			return source1 && source2 && getSourcePrimitive(source1) == getSourcePrimitive(source2);
1221		}
1222		
1223		
1224		function getSourcePrimitive(source) {
1225			return ((typeof source == 'object') ? (source.events || source.url) : '') || source;
1226		}
1227	
1228	
1229	}
1230	
1231	
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;
1239	
1240	
1241	
1242	/* Date Math
1243	-----------------------------------------------------------------------------*/
1244	
1245	var dayIDs = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'],
1246		DAY_MS = 86400000,
1247		HOUR_MS = 3600000,
1248		MINUTE_MS = 60000;
1249		
1250	
1251	function addYears(d, n, keepTime) {
1252		d.setFullYear(d.getFullYear() + n);
1253		if (!keepTime) {
1254			clearTime(d);
1255		}
1256		return d;
1257	}
1258	
1259	
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);
1264			check.setDate(1);
1265			check.setMonth(m);
1266			d.setMonth(m);
1267			if (!keepTime) {
1268				clearTime(d);
1269			}
1270			while (d.getMonth() != check.getMonth()) {
1271				d.setDate(d.getDate() + (d < check ? 1 : -1));
1272			}
1273		}
1274		return d;
1275	}
1276	
1277	
1278	function addDays(d, n, keepTime) { // deals with daylight savings
1279		if (+d) {
1280			var dd = d.getDate() + n,
1281				check = cloneDate(d);
1282			check.setHours(9); // set to middle of day
1283			check.setDate(dd);
1284			d.setDate(dd);
1285			if (!keepTime) {
1286				clearTime(d);
1287			}
1288			fixDate(d, check);
1289		}
1290		return d;
1291	}
1292	
1293	
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);
1298			}
1299		}
1300	}
1301	
1302	
1303	function addMinutes(d, n) {
1304		d.setMinutes(d.getMinutes() + n);
1305		return d;
1306	}
1307	
1308	
1309	function clearTime(d) {
1310		d.setHours(0);
1311		d.setMinutes(0);
1312		d.setSeconds(0); 
1313		d.setMilliseconds(0);
1314		return d;
1315	}
1316	
1317	
1318	function cloneDate(d, dontKeepTime) {
1319		if (dontKeepTime) {
1320			return clearTime(new Date(+d));
1321		}
1322		return new Date(+d);
1323	}
1324	
1325	
1326	function zeroDate() { // returns a Date with time 00:00:00 and dateOfMonth=1
1327		var i=0, d;
1328		do {
1329			d = new Date(1970, i++, 1);
1330		} while (d.getHours()); // != 0
1331		return d;
1332	}
1333	
1334	
1335	function skipWeekend(date, inc, excl) {
1336		inc = inc || 1;
1337		while (!date.getDay() || (excl && date.getDay()==1 || !excl && date.getDay()==6)) {
1338			addDays(date, inc);
1339		}
1340		return date;
1341	}
1342	
1343	
1344	function dayDiff(d1, d2) { // d1 - d2
1345		return Math.round((cloneDate(d1, true) - cloneDate(d2, true)) / DAY_MS);
1346	}
1347	
1348	
1349	function setYMD(date, y, m, d) {
1350		if (y !== undefined && y != date.getFullYear()) {
1351			date.setDate(1);
1352			date.setMonth(0);
1353			date.setFullYear(y);
1354		}
1355		if (m !== undefined && m != date.getMonth()) {
1356			date.setDate(1);
1357			date.setMonth(m);
1358		}
1359		if (d !== undefined) {
1360			date.setDate(d);
1361		}
1362	}
1363	
1364	
1365	
1366	/* Date Parsing
1367	-----------------------------------------------------------------------------*/
1368	
1369	
1370	function parseDate(s, ignoreTimezone) { // ignoreTimezone defaults to true
1371		if (typeof s == 'object') { // already a Date object
1372			return s;
1373		}
1374		if (typeof s == 'number') { // a UNIX timestamp
1375			return new Date(s * 1000);
1376		}
1377		if (typeof s == 'string') {
1378			if (s.match(/^\d+(\.\d+)?$/)) { // a UNIX timestamp
1379				return new Date(parseFloat(s) * 1000);
1380			}
1381			if (ignoreTimezone === undefined) {
1382				ignoreTimezone = true;
1383			}
1384			return parseISO8601(s, ignoreTimezone) || (s ? new Date(s) : null);
1385		}
1386	
1387		// TODO: never return invalid dates (like from new Date(<string>)), return null instead
1388		return null;
1389	}
1390	
1391	
1392	function parseISO8601(s, ignoreTimezone) { // ignoreTimezone defaults to false
1393		// derived from http://delete.me.uk/2005/03/iso8601.html
1394		// TODO: for a know glitch/feature, read tests/issue_206_parseDate_dst.html
1395		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}))?))?)?)?)?$/);
1396		if (!m) {
1397			return null;
1398		}
1399		var date = new Date(m[1], 0, 1);
1400		if (ignoreTimezone || !m[14]) {
1401			var check = new Date(m[1], 0, 1, 9, 0);
1402			if (m[3]) {
1403				date.setMonth(m[3] - 1);
1404				check.setMonth(m[3] - 1);
1405			}
1406			if (m[5]) {
1407				date.setDate(m[5]);
1408				check.setDate(m[5]);
1409			}
1410			fixDate(date, check);
1411			if (m[7]) {
1412				date.setHours(m[7]);
1413			}
1414			if (m[8]) {
1415				date.setMinutes(m[8]);
1416			}
1417			if (m[10]) {
1418				date.setSeconds(m[10]);
1419			}
1420			if (m[12]) {
1421				date.setMilliseconds(Number("0." + m[12]) * 1000);
1422			}
1423			fixDate(date, check);
1424		}else{
1425			date.setUTCFullYear(
1426				m[1],
1427				m[3] ? m[3] - 1 : 0,
1428				m[5] || 1
1429			);
1430			date.setUTCHours(
1431				m[7] || 0,
1432				m[8] || 0,
1433				m[10] || 0,
1434				m[12] ? Number("0." + m[12]) * 1000 : 0
1435			);
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));
1439		}
1440		return date;
1441	}
1442	
1443	
1444	function parseTime(s) { // returns minutes since start of day
1445		if (typeof s == 'number') { // an hour
1446			return s * 60;
1447		}
1448		if (typeof s == 'object') { // a Date object
1449			return s.getHours() * 60 + s.getMinutes();
1450		}
1451		var m = s.match(/(\d+)(?::(\d+))?\s*(\w+)?/);
1452		if (m) {
1453			var h = parseInt(m[1], 10);
1454			if (m[3]) {
1455				h %= 12;
1456				if (m[3].toLowerCase().charAt(0) == 'p') {
1457					h += 12;
1458				}
1459			}
1460			return h * 60 + (m[2] ? parseInt(m[2], 10) : 0);
1461		}
1462	}
1463	
1464	
1465	
1466	/* Date Formatting
1467	-----------------------------------------------------------------------------*/
1468	// TODO: use same function formatDate(date, [date2], format, [options])
1469	
1470	
1471	function formatDate(date, format, options) {
1472		return formatDates(date, null, format, options);
1473	}
1474	
1475	
1476	function formatDates(date1, date2, format, options) {
1477		options = options || defaults;
1478		var date = date1,
1479			otherDate = date2,
1480			i, len = format.length, c,
1481			i2, formatter,
1482			res = '';
1483		for (i=0; i<len; i++) {
1484			c = format.charAt(i);
1485			if (c == "'") {
1486				for (i2=i+1; i2<len; i2++) {
1487					if (format.charAt(i2) == "'") {
1488						if (date) {
1489							if (i2 == i+1) {
1490								res += "'";
1491							}else{
1492								res += format.substring(i+1, i2);
1493							}
1494							i = i2;
1495						}
1496						break;
1497					}
1498				}
1499			}
1500			else if (c == '(') {
1501				for (i2=i+1; i2<len; i2++) {
1502					if (format.charAt(i2) == ')') {
1503						var subres = formatDate(date, format.substring(i+1, i2), options);
1504						if (parseInt(subres.replace(/\D/, ''), 10)) {
1505							res += subres;
1506						}
1507						i = i2;
1508						break;
1509					}
1510				}
1511			}
1512			else if (c == '[') {
1513				for (i2=i+1; i2<len; i2++) {
1514					if (format.charAt(i2) == ']') {
1515						var subformat = format.substring(i+1, i2);
1516						var subres = formatDate(date, subformat, options);
1517						if (subres != formatDate(otherDate, subformat, options)) {
1518							res += subres;
1519						}
1520						i = i2;
1521						break;
1522					}
1523				}
1524			}
1525			else if (c == '{') {
1526				date = date2;
1527				otherDate = date1;
1528			}
1529			else if (c == '}') {
1530				date = date1;
1531				otherDate = date2;
1532			}
1533			else {
1534				for (i2=len; i2>i; i2--) {
1535					if (formatter = dateFormatters[format.substring(i, i2)]) {
1536						if (date) {
1537							res += formatter(date, options);
1538						}
1539						i = i2 - 1;
1540						break;
1541					}
1542				}
1543				if (i2 == i) {
1544					if (date) {
1545						res += c;
1546					}
1547				}
1548			}
1549		}
1550		return res;
1551	};
1552	
1553	
1554	var dateFormatters = {
1555		s	: function(d)	{ return d.getSeconds() },
1556		ss	: function(d)	{ return zeroPad(d.getSeconds()) },
1557		m	: function(d)	{ return d.getMinutes() },
1558		mm	: function(d)	{ return zeroPad(d.getMinutes()) },
1559		h	: function(d)	{ return d.getHours() % 12 || 12 },
1560		hh	: function(d)	{ return zeroPad(d.getHours() % 12 || 12) },
1561		H	: function(d)	{ return d.getHours() },
1562		HH	: function(d)	{ return zeroPad(d.getHours()) },
1563		d	: function(d)	{ return d.getDate() },
1564		dd	: function(d)	{ return zeroPad(d.getDate()) },
1565		ddd	: function(d,o)	{ return o.dayNamesShort[d.getDay()] },
1566		dddd: function(d,o)	{ return o.dayNames[d.getDay()] },
1567		M	: function(d)	{ return d.getMonth() + 1 },
1568		MM	: function(d)	{ return zeroPad(d.getMonth() + 1) },
1569		MMM	: function(d,o)	{ return o.monthNamesShort[d.getMonth()] },
1570		MMMM: function(d,o)	{ return o.monthNames[d.getMonth()] },
1571		yy	: function(d)	{ return (d.getFullYear()+'').substring(2) },
1572		yyyy: function(d)	{ return d.getFullYear() },
1573		t	: function(d)	{ return d.getHours() < 12 ? 'a' : 'p' },
1574		tt	: function(d)	{ return d.getHours() < 12 ? 'am' : 'pm' },
1575		T	: function(d)	{ return d.getHours() < 12 ? 'A' : 'P' },
1576		TT	: function(d)	{ return d.getHours() < 12 ? 'AM' : 'PM' },
1577		u	: function(d)	{ return formatDate(d, "yyyy-MM-dd'T'HH:mm:ss'Z'") },
1578		S	: function(d)	{
1579			var date = d.getDate();
1580			if (date > 10 && date < 20) {
1581				return 'th';
1582			}
1583			return ['st', 'nd', 'rd'][date%10-1] || 'th';
1584		}
1585	};
1586	
1587	
1588	
1589	fc.applyAll = applyAll;
1590	
1591	
1592	/* Event Date Math
1593	-----------------------------------------------------------------------------*/
1594	
1595	
1596	function exclEndDay(event) {
1597		if (event.end) {
1598			return _exclEndDay(event.end, event.allDay);
1599		}else{
1600			return addDays(cloneDate(event.start), 1);
1601		}
1602	}
1603	
1604	
1605	function _exclEndDay(end, allDay) {
1606		end = cloneDate(end);
1607		return allDay || end.getHours() || end.getMinutes() ? addDays(end, 1) : clearTime(end);
1608	}
1609	
1610	
1611	function segCmp(a, b) {
1612		return (b.msLength - a.msLength) * 100 + (a.event.start - b.event.start);
1613	}
1614	
1615	
1616	function segsCollide(seg1, seg2) {
1617		return seg1.end > seg2.start && seg1.start < seg2.end;
1618	}
1619	
1620	
1621	
1622	/* Event Sorting
1623	-----------------------------------------------------------------------------*/
1624	
1625	
1626	// event rendering utilities
1627	function sliceSegs(events, visEventEnds, start, end) {
1628		var segs = [],
1629			i, len=events.length, event,
1630			eventStart, eventEnd,
1631			segStart, segEnd,
1632			isStart, isEnd;
1633		for (i=0; i<len; i++) {
1634			event = events[i];
1635			eventStart = event.start;
1636			eventEnd = visEventEnds[i];
1637			if (eventEnd > start && eventStart < end) {
1638				if (eventStart < start) {
1639					segStart = cloneDate(start);
1640					isStart = false;
1641				}else{
1642					segStart = eventStart;
1643					isStart = true;
1644				}
1645				if (eventEnd > end) {
1646					segEnd = cloneDate(end);
1647					isEnd = false;
1648				}else{
1649					segEnd = eventEnd;
1650					isEnd = true;
1651				}
1652				segs.push({
1653					event: event,
1654					start: segStart,
1655					end: segEnd,
1656					isStart: isStart,
1657					isEnd: isEnd,
1658					msLength: segEnd - segStart
1659				});
1660			}
1661		} 
1662		return segs.sort(segCmp);
1663	}
1664	
1665	
1666	// event rendering calculation utilities
1667	function stackSegs(segs) {
1668		var levels = [],
1669			i, len = segs.length, seg,
1670			j, collide, k;
1671		for (i=0; i<len; i++) {
1672			seg = segs[i];
1673			j = 0; // the level index where seg should belong
1674			while (true) {
1675				collide = false;
1676				if (levels[j]) {
1677					for (k=0; k<levels[j].length; k++) {
1678						if (segsCollide(levels[j][k], seg)) {
1679							collide = true;
1680							break;
1681						}
1682					}
1683				}
1684				if (collide) {
1685					j++;
1686				}else{
1687					break;
1688				}
1689			}
1690			if (levels[j]) {
1691				levels[j].push(seg);
1692			}else{
1693				levels[j] = [seg];
1694			}
1695		}
1696		return levels;
1697	}
1698	
1699	
1700	
1701	/* Event Element Binding
1702	-----------------------------------------------------------------------------*/
1703	
1704	
1705	function lazySegBind(container, segs, bindHandlers) {
1706		container.unbind('mouseover').mouseover(function(ev) {
1707			var parent=ev.target, e,
1708				i, seg;
1709			while (parent != this) {
1710				e = parent;
1711				parent = parent.parentNode;
1712			}
1713			if ((i = e._fci) !== undefined) {
1714				e._fci = undefined;
1715				seg = segs[i];
1716				bindHandlers(seg.event, seg.element, seg);
1717				$(ev.target).trigger(ev);
1718			}
1719			ev.stopPropagation();
1720		});
1721	}
1722	
1723	
1724	
1725	/* Element Dimensions
1726	-----------------------------------------------------------------------------*/
1727	
1728	
1729	function setOuterWidth(element, width, includeMargins) {
1730		for (var i=0, e; i<element.length; i++) {
1731			e = $(element[i]);
1732			e.width(Math.max(0, width - hsides(e, includeMargins)));
1733		}
1734	}
1735	
1736	
1737	function setOuterHeight(element, height, includeMargins) {
1738		for (var i=0, e; i<element.length; i++) {
1739			e = $(element[i]);
1740			e.height(Math.max(0, height - vsides(e, includeMargins)));
1741		}
1742	}
1743	
1744	
1745	// TODO: curCSS has been deprecated (jQuery 1.4.3 - 10/16/2010)
1746	
1747	
1748	function hsides(element, includeMargins) {
1749		return hpadding(element) + hborders(element) + (includeMargins ? hmargins(element) : 0);
1750	}
1751	
1752	
1753	function hpadding(element) {
1754		return (parseFloat($.curCSS(element[0], 'paddingLeft', true)) || 0) +
1755			   (parseFloat($.curCSS(element[0], 'paddingRight', true)) || 0);
1756	}
1757	
1758	
1759	function hmargins(element) {
1760		return (parseFloat($.curCSS(element[0], 'marginLeft', true)) || 0) +
1761			   (parseFloat($.curCSS(element[0], 'marginRight', true)) || 0);
1762	}
1763	
1764	
1765	function hborders(element) {
1766		return (parseFloat($.curCSS(element[0], 'borderLeftWidth', true)) || 0) +
1767			   (parseFloat($.curCSS(element[0], 'borderRightWidth', true)) || 0);
1768	}
1769	
1770	
1771	function vsides(element, includeMargins) {
1772		return vpadding(element) +  vborders(element) + (includeMargins ? vmargins(element) : 0);
1773	}
1774	
1775	
1776	function vpadding(element) {
1777		return (parseFloat($.curCSS(element[0], 'paddingTop', true)) || 0) +
1778			   (parseFloat($.curCSS(element[0], 'paddingBottom', true)) || 0);
1779	}
1780	
1781	
1782	function vmargins(element) {
1783		return (parseFloat($.curCSS(element[0], 'marginTop', true)) || 0) +
1784			   (parseFloat($.curCSS(element[0], 'marginBottom', true)) || 0);
1785	}
1786	
1787	
1788	function vborders(element) {
1789		return (parseFloat($.curCSS(element[0], 'borderTopWidth', true)) || 0) +
1790			   (parseFloat($.curCSS(element[0], 'borderBottomWidth', true)) || 0);
1791	}
1792	
1793	
1794	function setMinHeight(element, height) {
1795		height = (typeof height == 'number' ? height + 'px' : height);
1796		element.each(function(i, _element) {
1797			_element.style.cssText += ';min-height:' + height + ';_height:' + height;
1798			// why can't we just use .css() ? i forget
1799		});
1800	}
1801	
1802	
1803	
1804	/* Misc Utils
1805	-----------------------------------------------------------------------------*/
1806	
1807	
1808	//TODO: arraySlice
1809	//TODO: isFunction, grep ?
1810	
1811	
1812	function noop() { }
1813	
1814	
1815	function cmp(a, b) {
1816		return a - b;
1817	}
1818	
1819	
1820	function arrayMax(a) {
1821		return Math.max.apply(Math, a);
1822	}
1823	
1824	
1825	function zeroPad(n) {
1826		return (n < 10 ? '0' : '') + n;
1827	}
1828	
1829	
1830	function smartProperty(obj, name) { // get a camel-cased/namespaced property of an object
1831		if (obj[name] !== undefined) {
1832			return obj[name];
1833		}
1834		var parts = name.split(/(?=[A-Z])/),
1835			i=parts.length-1, res;
1836		for (; i>=0; i--) {
1837			res = obj[parts[i].toLowerCase()];
1838			if (res !== undefined) {
1839				return res;
1840			}
1841		}
1842		return obj[''];
1843	}
1844	
1845	
1846	function htmlEscape(s) {
1847		return s.replace(/&/g, '&amp;')
1848			.replace(/</g, '&lt;')
1849			.replace(/>/g, '&gt;')
1850			.replace(/'/g, '&#039;')
1851			.replace(/"/g, '&quot;')
1852			.replace(/\n/g, '<br />');
1853	}
1854	
1855	
1856	function cssKey(_element) {
1857		return _element.id + '/' + _element.className + '/' + _element.style.cssText.replace(/(^|;)\s*(top|left|width|height)\s*:[^;]*/ig, '');
1858	}
1859	
1860	
1861	function disableTextSelection(element) {
1862		element
1863			.attr('unselectable', 'on')
1864			.css('MozUserSelect', 'none')
1865			.bind('selectstart.ui', function() { return false; });
1866	}
1867	
1868	
1869	/*
1870	function enableTextSelection(element) {
1871		element
1872			.attr('unselectable', 'off')
1873			.css('MozUserSelect', '')
1874			.unbind('selectstart.ui');
1875	}
1876	*/
1877	
1878	
1879	function markFirstLast(e) {
1880		e.children()
1881			.removeClass('fc-first fc-last')
1882			.filter(':first-child')
1883				.addClass('fc-first')
1884			.end()
1885			.filter(':last-child')
1886				.addClass('fc-last');
1887	}
1888	
1889	
1890	function setDayID(cell, date) {
1891		cell.each(function(i, _cell) {
1892			_cell.className = _cell.className.replace(/^fc-\w*/, 'fc-' + dayIDs[date.getDay()]);
1893			// TODO: make a way that doesn't rely on order of classes
1894		});
1895	}
1896	
1897	
1898	function getSkinCss(event, opt) {
1899		var source = event.source || {};
1900		var eventColor = event.color;
1901		var sourceColor = source.color;
1902		var optionColor = opt('eventColor');
1903		var backgroundColor =
1904			event.backgroundColor ||
1905			eventColor ||
1906			source.backgroundColor ||
1907			sourceColor ||
1908			opt('eventBackgroundColor') ||
1909			optionColor;
1910		var borderColor =
1911			event.borderColor ||
1912			eventColor ||
1913			source.borderColor ||
1914			sourceColor ||
1915			opt('eventBorderColor') ||
1916			optionColor;
1917		var textColor =
1918			event.textColor ||
1919			source.textColor ||
1920			opt('eventTextColor');
1921		var statements = [];
1922		if (backgroundColor) {
1923			statements.push('background-color:' + backgroundColor);
1924		}
1925		if (borderColor) {
1926			statements.push('border-color:' + borderColor);
1927		}
1928		if (textColor) {
1929			statements.push('color:' + textColor);
1930		}
1931		return statements.join(';');
1932	}
1933	
1934	
1935	function applyAll(functions, thisObj, args) {
1936		if ($.isFunction(functions)) {
1937			functions = [ functions ];
1938		}
1939		if (functions) {
1940			var i;
1941			var ret;
1942			for (i=0; i<functions.length; i++) {
1943				ret = functions[i].apply(thisObj, args) || ret;
1944			}
1945			return ret;
1946		}
1947	}
1948	
1949	
1950	function firstDefined() {
1951		for (var i=0; i<arguments.length; i++) {
1952			if (arguments[i] !== undefined) {
1953				return arguments[i];
1954			}
1955		}
1956	}
1957	
1958	
1959	
1960	fcViews.month = MonthView;
1961	
1962	function MonthView(element, calendar) {
1963		var t = this;
1964		
1965		
1966		// exports
1967		t.render = render;
1968		
1969		
1970		// imports
1971		BasicView.call(t, element, calendar, 'month');
1972		var opt = t.opt;
1973		var renderBasic = t.renderBasic;
1974		var formatDate = calendar.formatDate;
1975		
1976		
1977		
1978		function render(date, delta) {
1979			if (delta) {
1980				addMonths(date, delta);
1981				date.setDate(1);
1982			}
1983			var start = cloneDate(date, true);
1984			start.setDate(1);
1985			var end = addMonths(cloneDate(start), 1);
1986			var visStart = cloneDate(start);
1987			var visEnd = cloneDate(end);
1988			var firstDay = opt('firstDay');
1989			var nwe = opt('weekends') ? 0 : 1;
1990			if (nwe) {
1991				skipWeekend(visStart);
1992				skipWeekend(visEnd, -1, true);
1993			}
1994			addDays(visStart, -((visStart.getDay() - Math.max(firstDay, nwe) + 7) % 7));
1995			addDays(visEnd, (7 - visEnd.getDay() + Math.max(firstDay, nwe)) % 7);
1996			var rowCnt = Math.round((visEnd - visStart) / (DAY_MS * 7));
1997			if (opt('weekMode') == 'fixed') {
1998				addDays(visEnd, (6 - rowCnt) * 7);
1999				rowCnt = 6;
2000			}
2001			t.title = formatDate(start, opt('titleFormat'));
2002			t.start = start;
2003			t.end = end;
2004			t.visStart = visStart;
2005			t.visEnd = visEnd;
2006			renderBasic(6, rowCnt, nwe ? 5 : 7, true);
2007		}
2008		
2009		
2010	}
2011	
2012	fcViews.basicWeek = BasicWeekView;
2013	
2014	function BasicWeekView(element, calendar) {
2015		var t = this;
2016		
2017		
2018		// exports
2019		t.render = render;
2020		
2021		
2022		// imports
2023		BasicView.call(t, element, calendar, 'basicWeek');
2024		var opt = t.opt;
2025		var renderBasic = t.renderBasic;
2026		var formatDates = calendar.formatDates;
2027		
2028		
2029		
2030		function render(date, delta) {
2031			if (delta) {
2032				addDays(date, delta * 7);
2033			}
2034			var start = addDays(cloneDate(date), -((date.getDay() - opt('firstDay') + 7) % 7));
2035			var end = addDays(cloneDate(start), 7);
2036			var visStart = cloneDate(start);
2037			var visEnd = cloneDate(end);
2038			var weekends = opt('weekends');
2039			if (!weekends) {
2040				skipWeekend(visStart);
2041				skipWeekend(visEnd, -1, true);
2042			}
2043			t.title = formatDates(
2044				visStart,
2045				addDays(cloneDate(visEnd), -1),
2046				opt('titleFormat')
2047			);
2048			t.start = start;
2049			t.end = end;
2050			t.visStart = visStart;
2051			t.visEnd = visEnd;
2052			renderBasic(1, 1, weekends ? 7 : 5, false);
2053		}
2054		
2055		
2056	}
2057	
2058	fcViews.basicDay = BasicDayView;
2059	
2060	//TODO: when calendar's date starts out on a weekend, shouldn't happen
2061	
2062	
2063	function BasicDayView(element, calendar) {
2064		var t = this;
2065		
2066		
2067		// exports
2068		t.render = render;
2069		
2070		
2071		// imports
2072		BasicView.call(t, element, calendar, 'basicDay');
2073		var opt = t.opt;
2074		var renderBasic = t.renderBasic;
2075		var formatDate = calendar.formatDate;
2076		
2077		
2078		
2079		function render(date, delta) {
2080			if (delta) {
2081				addDays(date, delta);
2082				if (!opt('weekends')) {
2083					skipWeekend(date, delta < 0 ? -1 : 1);
2084				}
2085			}
2086			t.title = formatDate(date, opt('titleFormat'));
2087			t.start = t.visStart = cloneDate(date, true);
2088			t.end = t.visEnd = addDays(cloneDate(t.start), 1);
2089			renderBasic(1, 1, 1, false);
2090		}
2091		
2092		
2093	}
2094	
2095	setDefaults({
2096		weekMode: 'fixed'
2097	});
2098	
2099	
2100	function BasicView(element, calendar, viewName) {
2101		var t = this;
2102		
2103		
2104		// exports
2105		t.renderBasic = renderBasic;
2106		t.setHeight = setHeight;
2107		t.setWidth = setWidth;
2108		t.renderDayOverlay = renderDayOverlay;
2109		t.defaultSelectionEnd = defaultSelectionEnd;
2110		t.renderSelection = renderSelection;
2111		t.clearSelection = clearSelection;
2112		t.reportDayClick = reportDayClick; // for selection (kinda hacky)
2113		t.dragStart = dragStart;
2114		t.dragStop = dragStop;
2115		t.defaultEventEnd = defaultEventEnd;
2116		t.getHoverListener = function() { return hoverListener };
2117		t.colContentLeft = colContentLeft;
2118		t.colContentRight = colContentRight;
2119		t.dayOfWeekCol = dayOfWeekCol;
2120		t.dateCell = dateCell;
2121		t.cellDate = cellDate;
2122		t.cellIsAllDay = function() { return true };
2123		t.allDayRow = allDayRow;
2124		t.allDayBounds = allDayBounds;
2125		t.getRowCnt = function() { return rowCnt };
2126		t.getColCnt = function() { return colCnt };
2127		t.getColWidth = function() { return colWidth };
2128		t.getDaySegmentContainer = function() { return daySegmentContainer };
2129		
2130		
2131		// imports
2132		View.call(t, element, calendar, viewName);
2133		OverlayManager.call(t);
2134		SelectionManager.call(t);
2135		BasicEventRenderer.call(t);
2136		var opt = t.opt;
2137		var trigger = t.trigger;
2138		var clearEvents = t.clearEvents;
2139		var renderOverlay = t.renderOverlay;
2140		var clearOverlays = t.clearOverlays;
2141		var daySelectionMousedown = t.daySelectionMousedown;
2142		var formatDate = calendar.formatDate;
2143		
2144		
2145		// locals
2146		
2147		var head;
2148		var headCells;
2149		var body;
2150		var bodyRows;
2151		var bodyCells;
2152		var bodyFirstCells;
2153		var bodyCellTopInners;
2154		var daySegmentContainer;
2155		
2156		var viewWidth;
2157		var viewHeight;
2158		var colWidth;
2159		
2160		var rowCnt, colCnt;
2161		var coordinateGrid;
2162		var hoverListener;
2163		var colContentPositions;
2164		
2165		var rtl, dis, dit;
2166		var firstDay;
2167		var nwe;
2168		var tm;
2169		var colFormat;
2170		
2171		
2172		
2173		/* Rendering
2174		------------------------------------------------------------*/
2175		
2176		
2177		disableTextSelection(element.addClass('fc-grid'));
2178		
2179		
2180		function renderBasic(maxr, r, c, showNumbers) {
2181			rowCnt = r;
2182			colCnt = c;
2183			updateOptions();
2184			var firstTime = !body;
2185			if (firstTime) {
2186				buildSkeleton(maxr, showNumbers);
2187			}else{
2188				clearEvents();
2189			}
2190			updateCells(firstTime);
2191		}
2192		
2193		
2194		
2195		function updateOptions() {
2196			rtl = opt('isRTL');
2197			if (rtl) {
2198				dis = -1;
2199				dit = colCnt - 1;
2200			}else{
2201				dis = 1;
2202				dit = 0;
2203			}
2204			firstDay = opt('firstDay');
2205			nwe = opt('weekends') ? 0 : 1;
2206			tm = opt('theme') ? 'ui' : 'fc';
2207			colFormat = opt('columnFormat');
2208		}
2209		
2210		
2211		
2212		function buildSkeleton(maxRowCnt, showNumbers) {
2213			var s;
2214			var headerClass = tm + "-widget-header";
2215			var contentClass = tm + "-widget-content";
2216			var i, j;
2217			var table;
2218			
2219			s =
2220				"<table class='fc-border-separate' style='width:100%' cellspacing='0'>" +
2221				"<thead>" +
2222				"<tr>";
2223			for (i=0; i<colCnt; i++) {
2224				s +=
2225					"<th class='fc- " + headerClass + "'/>"; // need fc- for setDayID
2226			}
2227			s +=
2228				"</tr>" +
2229				"</thead>" +
2230				"<tbody>";
2231			for (i=0; i<maxRowCnt; i++) {
2232				s +=
2233					"<tr class='fc-week" + i + "'>";
2234				for (j=0; j<colCnt; j++) {
2235					s +=
2236						"<td class='fc- " + contentClass + " fc-day" + (i*colCnt+j) + "'>" + // need fc- for setDayID
2237						"<div>" +
2238						(showNumbers ?
2239							"<div class='fc-day-number'/>" :
2240							''
2241							) +
2242						"<div class='fc-day-content'>" +
2243						"<div style='position:relative'>&nbsp;</div>" +
2244						"</div>" +
2245						"</div>" +
2246						"</td>";
2247				}
2248				s +=
2249					"</tr>";
2250			}
2251			s +=
2252				"</tbody>" +
2253				"</table>";
2254			table = $(s).appendTo(element);
2255			
2256			head = table.find('thead');
2257			headCells = head.find('th');
2258			body = table.find('tbody');
2259			bodyRows = body.find('tr');
2260			bodyCells = body.find('td');
2261			bodyFirstCells = bodyCells.filter(':first-child');
2262			bodyCellTopInners = bodyRows.eq(0).find('div.fc-day-content div');
2263			
2264			markFirstLast(head.add(head.find('tr'))); // marks first+last tr/th's
2265			markFirstLast(bodyRows); // marks first+last td's
2266			bodyRows.eq(0).addClass('fc-first'); // fc-last is done in updateCells
2267			
2268			dayBind(bodyCells);
2269			
2270			daySegmentContainer =
2271				$("<div style='position:absolute;z-index:8;top:0;left:0'/>")
2272					.appendTo(element);
2273		}
2274		
2275		
2276		
2277		function updateCells(firstTime) {
2278			var dowDirty = firstTime || rowCnt == 1; // could the cells' day-of-weeks need updating?
2279			var month = t.start.getMonth();
2280			var today = clearTime(new Date());
2281			var cell;
2282			var date;
2283			var row;
2284		
2285			if (dowDirty) {
2286				headCells.each(function(i, _cell) {
2287					cell = $(_cell);
2288					date = indexDate(i);
2289					cell.html(formatDate(date, colFormat));
2290					setDayID(cell, date);
2291				});
2292			}
2293			
2294			bodyCells.each(function(i, _cell) {
2295				cell = $(_cell);
2296				date = indexDate(i);
2297				if (date.getMonth() == month) {
2298					cell.removeClass('fc-other-month');
2299				}else{
2300					cell.addClass('fc-other-month');
2301				}
2302				if (+date == +today) {
2303					cell.addClass(tm + '-state-highlight fc-today');
2304				}else{
2305					cell.removeClass(tm + '-state-highlight fc-today');
2306				}
2307				cell.find('div.fc-day-number').text(date.getDate());
2308				if (dowDirty) {
2309					setDayID(cell, date);
2310				}
2311			});
2312			
2313			bodyRows.each(function(i, _row) {
2314				row = $(_row);
2315				if (i < rowCnt) {
2316					row.show();
2317					if (i == rowCnt-1) {
2318						row.addClass('fc-last');
2319					}else{
2320						row.removeClass('fc-last');
2321					}
2322				}else{
2323					row.hide();
2324				}
2325			});
2326		}
2327		
2328		
2329		
2330		function setHeight(height) {
2331			viewHeight = height;
2332			
2333			var bodyHeight = viewHeight - head.height();
2334			var rowHeight;
2335			var rowHeightLast;
2336			var cell;
2337				
2338			if (opt('weekMode') == 'variable') {
2339				rowHeight = rowHeightLast = Math.floor(bodyHeight / (rowCnt==1 ? 2 : 6));
2340			}else{
2341				rowHeight = Math.floor(bodyHeight / rowCnt);
2342				rowHeightLast = bodyHeight - rowHeight * (rowCnt-1);
2343			}
2344			
2345			bodyFirstCells.each(function(i, _cell) {
2346				if (i < rowCnt) {
2347					cell = $(_cell);
2348					setMinHeight(
2349						cell.find('> div'),
2350						(i==rowCnt-1 ? rowHeightLast : rowHeight) - vsides(cell)
2351					);
2352				}
2353			});
2354			
2355		}
2356		
2357		
2358		function setWidth(width) {
2359			viewWidth = width;
2360			colContentPositions.clear();
2361			colWidth = Math.floor(viewWidth / colCnt);
2362			setOuterWidth(headCells.slice(0, -1), colWidth);
2363		}
2364		
2365		
2366		
2367		/* Day clicking and binding
2368		-----------------------------------------------------------*/
2369		
2370		
2371		function dayBind(days) {
2372			days.click(dayClick)
2373				.mousedown(daySelectionMousedown);
2374		}
2375		
2376		
2377		function dayClick(ev) {
2378			if (!opt('selectable')) { // if selectable, SelectionManager will worry about dayClick
2379				var index = parseInt(this.className.match(/fc\-day(\d+)/)[1]); // TODO: maybe use .data
2380				var date = indexDate(index);
2381				trigger('dayClick', this, date, true, ev);
2382			}
2383		}
2384		
2385		
2386		
2387		/* Semi-transparent Overlay Helpers
2388		------------------------------------------------------*/
2389		
2390		
2391		function renderDayOverlay(overlayStart, overlayEnd, refreshCoordinateGrid) { // overlayEnd is exclusive
2392			if (refreshCoordinateGrid) {
2393				coordinateGrid.build();
2394			}
2395			var rowStart = cloneDate(t.visStart);
2396			var rowEnd = addDays(cloneDate(rowStart), colCnt);
2397			for (var i=0; i<rowCnt; i++) {
2398				var stretchStart = new Date(Math.max(rowStart, overlayStart));
2399				var stretchEnd = new Date(Math.min(rowEnd, overlayEnd));
2400				if (stretchStart < stretchEnd) {
2401					var colStart, colEnd;
2402					if (rtl) {
2403						colStart = dayDiff(stretchEnd, rowStart)*dis+dit+1;
2404						colEnd = dayDiff(stretchStart, rowStart)*dis+dit+1;
2405					}else{
2406						colStart = dayDiff(stretchStart, rowStart);
2407						colEnd = dayDiff(stretchEnd, rowStart);
2408					}
2409					dayBind(
2410						renderCellOverlay(i, colStart, i, colEnd-1)
2411					);
2412				}
2413				addDays(rowStart, 7);
2414				addDays(rowEnd, 7);
2415			}
2416		}
2417		
2418		
2419		function renderCellOverlay(row0, col0, row1, col1) { // row1,col1 is inclusive
2420			var rect = coordinateGrid.rect(row0, col0, row1, col1, element);
2421			return renderOverlay(rect, element);
2422		}
2423		
2424		
2425		
2426		/* Selection
2427		-----------------------------------------------------------------------*/
2428		
2429		
2430		function defaultSelectionEnd(startDate, allDay) {
2431			return cloneDate(startDate);
2432		}
2433		
2434		
2435		function renderSelection(startDate, endDate, allDay) {
2436			renderDayOverlay(startDate, addDays(cloneDate(endDate), 1), true); // rebuild every time???
2437		}
2438		
2439		
2440		function clearSelection() {
2441			clearOverlays();
2442		}
2443		
2444		
2445		function reportDayClick(date, allDay, ev) {
2446			var cell = dateCell(date);
2447			var _element = bodyCells[cell.row*colCnt + cell.col];
2448			trigger('dayClick', _element, date, allDay, ev);
2449		}
2450		
2451		
2452		
2453		/* External Dragging
2454		-----------------------------------------------------------------------*/
2455		
2456		
2457		function dragStart(_dragElement, ev, ui) {
2458			hoverListener.start(function(cell) {
2459				clearOverlays();
2460				if (cell) {
2461					renderCellOverlay(cell.row, cell.col, cell.row, cell.col);
2462				}
2463			}, ev);
2464		}
2465		
2466		
2467		function dragStop(_dragElement, ev, ui) {
2468			var cell = hoverListener.stop();
2469			clearOverlays();
2470			if (cell) {
2471				var d = cellDate(cell);
2472				trigger('drop', _dragElement, d, true, ev, ui);
2473			}
2474		}
2475		
2476		
2477		
2478		/* Utilities
2479		--------------------------------------------------------*/
2480		
2481		
2482		function defaultEventEnd(event) {
2483			return cloneDate(event.start);
2484		}
2485		
2486		
2487		coordinateGrid = new CoordinateGrid(function(rows, cols) {
2488			var e, n, p;
2489			headCells.each(function(i, _e) {
2490				e = $(_e);
2491				n = e.offset().left;
2492				if (i) {
2493					p[1] = n;
2494				}
2495				p = [n];
2496				cols[i] = p;
2497			});
2498			p[1] = n + e.outerWidth();
2499			bodyRows.each(function(i, _e) {
2500				if (i < rowCnt) {
2501					e = $(_e);
2502					n = e.offset().top;
2503					if (i) {
2504						p[1] = n;
2505					}
2506					p = [n];
2507					rows[i] = p;
2508				}
2509			});
2510			p[1] = n + e.outerHeight();
2511		});
2512		
2513		
2514		hoverListener = new HoverListener(coordinateGrid);
2515		
2516		
2517		colContentPositions = new HorizontalPositionCache(function(col) {
2518			return bodyCellTopInners.eq(col);
2519		});
2520		
2521		
2522		function colContentLeft(col) {
2523			return colContentPositions.left(col);
2524		}
2525		
2526		
2527		function colContentRight(col) {
2528			return colContentPositions.right(col);
2529		}
2530		
2531		
2532		
2533		
2534		function dateCell(date) {
2535			return {
2536				row: Math.floor(dayDiff(date, t.visStart) / 7),
2537				col: dayOfWeekCol(date.getDay())
2538			};
2539		}
2540		
2541		
2542		function cellDate(cell) {
2543			return _cellDate(cell.row, cell.col);
2544		}
2545		
2546		
2547		function _cellDate(row, col) {
2548			return addDays(cloneDate(t.visStart), row*7 + col*dis+dit);
2549			// what about weekends in middle of week?
2550		}
2551		
2552		
2553		function indexDate(index) {
2554			return _cellDate(Math.floor(index/colCnt), index%colCnt);
2555		}
2556		
2557		
2558		function dayOfWeekCol(dayOfWeek) {
2559			return ((dayOfWeek - Math.max(firstDay, nwe) + colCnt) % colCnt) * dis + dit;
2560		}
2561		
2562		
2563		
2564		
2565		function allDayRow(i) {
2566			return bodyRows.eq(i);
2567		}
2568		
2569		
2570		function allDayBounds(i) {
2571			return {
2572				left: 0,
2573				right: viewWidth
2574			};
2575		}
2576		
2577		
2578	}
2579	
2580	function BasicEventRenderer() {
2581		var t = this;
2582		
2583		
2584		// exports
2585		t.renderEvents = renderEvents;
2586		t.compileDaySegs = compileSegs; // for DayEventRenderer
2587		t.clearEvents = clearEvents;
2588		t.bindDaySeg = bindDaySeg;
2589		
2590		
2591		// imports
2592		DayEventRenderer.call(t);
2593		var opt = t.opt;
2594		var trigger = t.trigger;
2595		//var setOverflowHidden = t.setOverflowHidden;
2596		var isEventDraggable = t.isEventDraggable;
2597		var isEventResizable = t.isEventResizable;
2598		var reportEvents = t.reportEvents;
2599		var reportEventClear = t.reportEventClear;
2600		var eventElementHandlers = t.eventElementHandlers;
2601		var showEvents = t.showEvents;
2602		var hideEvents = t.hideEvents;
2603		var eventDrop = t.eventDrop;
2604		var getDaySegmentContainer = t.getDaySegmentContainer;
2605		var getHoverListener = t.getHoverListener;
2606		var renderDayOverlay = t.renderDayOverlay;
2607		var clearOverlays = t.clearOverlays;
2608		var getRowCnt = t.getRowCnt;
2609		var getColCnt = t.getColCnt;
2610		var renderDaySegs = t.renderDaySegs;
2611		var resizableDayEvent = t.resizableDayEvent;
2612		
2613		
2614		
2615		/* Rendering
2616		--------------------------------------------------------------------*/
2617		
2618		
2619		function renderEvents(events, modifiedEventId) {
2620			reportEvents(events);
2621			renderDaySegs(compileSegs(events), modifiedEventId);
2622		}
2623		
2624		
2625		function clearEvents() {
2626			reportEventClear();
2627			getDaySegmentContainer().empty();
2628		}
2629		
2630		
2631		function compileSegs(events) {
2632			var rowCnt = getRowCnt(),
2633				colCnt = getColCnt(),
2634				d1 = cloneDate(t.visStart),
2635				d2 = addDays(cloneDate(d1), colCnt),
2636				visEventsEnds = $.map(events, exclEndDay),
2637				i, row,
2638				j, level,
2639				k, seg,
2640				segs=[];
2641			for (i=0; i<rowCnt; i++) {
2642				row = stackSegs(sliceSegs(events, visEventsEnds, d1, d2));
2643				for (j=0; j<row.length; j++) {
2644					level = row[j];
2645					for (k=0; k<level.length; k++) {
2646						seg = level[k];
2647						seg.row = i;
2648						seg.level = j; // not needed anymore
2649						segs.push(seg);
2650					}
2651				}
2652				addDays(d1, 7);
2653				addDays(d2, 7);
2654			}
2655			return segs;
2656		}
2657		
2658		
2659		function bindDaySeg(event, eventElement, seg) {
2660			if (isEventDraggable(event)) {
2661				draggableDayEvent(event, eventElement);
2662			}
2663			if (seg.isEnd && isEventResizable(event)) {
2664				resizableDayEvent(event, eventElement, seg);
2665			}
2666			eventElementHandlers(event, eventElement);
2667				// needs to be after, because resizableDayEvent might stopImmediatePropagation on click
2668		}
2669		
2670		
2671		
2672		/* Dragging
2673		----------------------------------------------------------------------------*/
2674		
2675		
2676		function draggableDayEvent(event, eventElement) {
2677			var hoverListener = getHoverListener();
2678			var dayDelta;
2679			eventElement.draggable({
2680				zIndex: 9,
2681				delay: 50,
2682				opacity: opt('dragOpacity'),
2683				revertDuration: opt('dragRevertDuration'),
2684				start: function(ev, ui) {
2685					trigger('eventDragStart', eventElement, event, ev, ui);
2686					hideEvents(event, eventElement);
2687					hoverListener.start(function(cell, origCell, rowDelta, colDelta) {
2688						eventElement.draggable('option', 'revert', !cell || !rowDelta && !colDelta);
2689						clearOverlays();
2690						if (cell) {
2691							//setOverflowHidden(true);
2692							dayDelta = rowDelta*7 + colDelta * (opt('isRTL') ? -1 : 1);
2693							renderDayOverlay(
2694								addDays(cloneDate(event.start), dayDelta),
2695								addDays(exclEndDay(event), dayDelta)
2696							);
2697						}else{
2698							//setOverflowHidden(false);
2699							dayDelta = 0;
2700						}
2701					}, ev, 'drag');
2702				},
2703				stop: function(ev, ui) {
2704					hoverListener.stop();
2705					clearOverlays();
2706					trigger('eventDragStop', eventElement, event, ev, ui);
2707					if (dayDelta) {
2708						eventDrop(this, event, dayDelta, 0, event.allDay, ev, ui);
2709					}else{
2710						eventElement.css('filter', ''); // clear IE opacity side-effects
2711						showEvents(event, eventElement);
2712					}
2713					//setOverflowHidden(false);
2714				}
2715			});
2716		}
2717	
2718	
2719	}
2720	
2721	fcViews.agendaWeek = AgendaWeekView;
2722	
2723	function AgendaWeekView(element, calendar) {
2724		var t = this;
2725		
2726		
2727		// exports
2728		t.render = render;
2729		
2730		
2731		// imports
2732		AgendaView.call(t, element, calendar, 'agendaWeek');
2733		var opt = t.opt;
2734		var renderAgenda = t.renderAgenda;
2735		var formatDates = calendar.formatDates;
2736		
2737		
2738		
2739		function render(date, delta) {
2740			if (delta) {
2741				addDays(date, delta * 7);
2742			}
2743			var start = addDays(cloneDate(date), -((date.getDay() - opt('firstDay') + 7) % 7));
2744			var end = addDays(cloneDate(start), 7);
2745			var visStart = cloneDate(start);
2746			var visEnd = cloneDate(end);
2747			var weekends = opt('weekends');
2748			if (!weekends) {
2749				skipWeekend(visStart);
2750				skipWeekend(visEnd, -1, true);
2751			}
2752			t.title = formatDates(
2753				visStart,
2754				addDays(cloneDate(visEnd), -1),
2755				opt('titleFormat')
2756			);
2757			t.start = start;
2758			t.end = end;
2759			t.visStart = visStart;
2760			t.visEnd = visEnd;
2761			renderAgenda(weekends ? 7 : 5);
2762		}
2763		
2764	
2765	}
2766	
2767	fcViews.agendaDay = AgendaDayView;
2768	
2769	function AgendaDayView(element, calendar) {
2770		var t = this;
2771		
2772		
2773		// exports
2774		t.render = render;
2775		
2776		
2777		// imports
2778		AgendaView.call(t, element, calendar, 'agendaDay');
2779		var opt = t.opt;
2780		var renderAgenda = t.renderAgenda;
2781		var formatDate = calendar.formatDate;
2782		
2783		
2784		
2785		function render(date, delta) {
2786			if (delta) {
2787				addDays(date, delta);
2788				if (!opt('weekends')) {
2789					skipWeekend(date, delta < 0 ? -1 : 1);
2790				}
2791			}
2792			var start = cloneDate(date, true);
2793			var end = addDays(cloneDate(start), 1);
2794			t.title = formatDate(date, opt('titleFormat'));
2795			t.start = t.visStart = start;
2796			t.end = t.visEnd = end;
2797			renderAgenda(1);
2798		}
2799		
2800	
2801	}
2802	
2803	setDefaults({
2804		allDaySlot: true,
2805		allDayText: 'all-day',
2806		firstHour: 6,
2807		slotMinutes: 30,
2808		defaultEventMinutes: 120,
2809		axisFormat: 'h(:mm)tt',
2810		timeFormat: {
2811			agenda: 'h:mm{ - h:mm}'
2812		},
2813		dragOpacity: {
2814			agenda: .5
2815		},
2816		minTime: 0,
2817		maxTime: 24
2818	});
2819	
2820	
2821	// TODO: make it work in quirks mode (event corners, all-day height)
2822	// TODO: test liquid width, especially in IE6
2823	
2824	
2825	function AgendaView(element, calendar, viewName) {
2826		var t = this;
2827		
2828		
2829		// exports
2830		t.renderAgenda = renderAgenda;
2831		t.setWidth = setWidth;
2832		t.setHeight = setHeight;
2833		t.beforeHide = beforeHide;
2834		t.afterShow = afterShow;
2835		t.defaultEventEnd = defaultEventEnd;
2836		t.timePosition = timePosition;
2837		t.dayOfWeekCol = dayOfWeekCol;
2838		t.dateCell = dateCell;
2839		t.cellDate = cellDate;
2840		t.cellIsAllDay = cellIsAllDay;
2841		t.allDayRow = getAllDayRow;
2842		t.allDayBounds = allDayBounds;
2843		t.getHoverListener = function() { return hoverListener };
2844		t.colContentLeft = colContentLeft;
2845		t.colContentRight = colContentRight;
2846		t.getDaySegmentContainer = function() { return daySegmentContainer };
2847		t.getSlotSegmentContainer = function() { return slotSegmentContainer };
2848		t.getMinMinute = function() { return minMinute };
2849		t.getMaxMinute = function() { return maxMinute };
2850		t.getBodyContent = function() { return slotContent }; // !!??
2851		t.getRowCnt = function() { return 1 };
2852		t.getColCnt = function() { return colCnt };
2853		t.getColWidth = function() { return colWidth };
2854		t.getSlotHeight = function() { return slotHeight };
2855		t.defaultSelectionEnd = defaultSelectionEnd;
2856		t.renderDayOverlay = renderDayOverlay;
2857		t.renderSelection = renderSelection;
2858		t.clearSelection = clearSelection;
2859		t.reportDayClick = reportDayClick; // selection mousedown hack
2860		t.dragStart = dragStart;
2861		t.dragStop = dragStop;
2862		
2863		
2864		// imports
2865		View.call(t, element, calendar, viewName);
2866		OverlayManager.call(t);
2867		SelectionManager.call(t);
2868		AgendaEventRenderer.call(t);
2869		var opt = t.opt;
2870		var trigger = t.trigger;
2871		var clearEvents = t.clearEvents;
2872		var renderOverlay = t.renderOverlay;
2873		var clearOverlays = t.clearOverlays;
2874		var reportSelection = t.reportSelection;
2875		var unselect = t.unselect;
2876		var daySelectionMousedown = t.daySelectionMousedown;
2877		var slotSegHtml = t.slotSegHtml;
2878		var formatDate = calendar.formatDate;
2879		
2880		
2881		// locals
2882		
2883		var dayTable;
2884		var dayHead;
2885		var dayHeadCells;
2886		var dayBody;
2887		var dayBodyCells;
2888		var dayBodyCellInners;
2889		var dayBodyFirstCell;
2890		var dayBodyFirstCellStretcher;
2891		var slotLayer;
2892		var daySegmentContainer;
2893		var allDayTable;
2894		var allDayRow;
2895		var slotScroller;
2896		var slotContent;
2897		var slotSegmentContainer;
2898		var slotTable;
2899		var slotTableFirstInner;
2900		var axisFirstCells;
2901		var gutterCells;
2902		var selectionHelper;
2903		
2904		var viewWidth;
2905		var viewHeight;
2906		var axisWidth;
2907		var colWidth;
2908		var gutterWidth;
2909		var slotHeight; // TODO: what if slotHeight changes? (see issue 650)
2910		var savedScrollTop;
2911		
2912		var colCnt;
2913		var slotCnt;
2914		var coordinateGrid;
2915		var hoverListener;
2916		var colContentPositions;
2917		var slotTopCache = {};
2918		
2919		var tm;
2920		var firstDay;
2921		var nwe;            // no weekends (int)
2922		var rtl, dis, dit;  // day index sign / translate
2923		var minMinute, maxMinute;
2924		var colFormat;
2925		
2926	
2927		
2928		/* Rendering
2929		-----------------------------------------------------------------------------*/
2930		
2931		
2932		disableTextSelection(element.addClass('fc-agenda'));
2933		
2934		
2935		function renderAgenda(c) {
2936			colCnt = c;
2937			updateOptions();
2938			if (!dayTable) {
2939				buildSkeleton();
2940			}else{
2941				clearEvents();
2942			}
2943			updateCells();
2944		}
2945		
2946		
2947		
2948		function updateOptions() {
2949			tm = opt('theme') ? 'ui' : 'fc';
2950			nwe = opt('weekends') ? 0 : 1;
2951			firstDay = opt('firstDay');
2952			if (rtl = opt('isRTL')) {
2953				dis = -1;
2954				dit = colCnt - 1;
2955			}else{
2956				dis = 1;
2957				dit = 0;
2958			}
2959			minMinute = parseTime(opt('minTime'));
2960			maxMinute = parseTime(opt('maxTime'));
2961			colFormat = opt('columnFormat');
2962		}
2963		
2964		
2965		
2966		function buildSkeleton() {
2967			var headerClass = tm + "-widget-header";
2968			var contentClass = tm + "-widget-content";
2969			var s;
2970			var i;
2971			var d;
2972			var maxd;
2973			var minutes;
2974			var slotNormal = opt('slotMinutes') % 15 == 0;
2975			
2976			s =
2977				"<table style='width:100%' class='fc-agenda-days fc-border-separate' cellspacing='0'>" +
2978				"<thead>" +
2979				"<tr>" +
2980				"<th class='fc-agenda-axis " + headerClass + "'>&nbsp;</th>";
2981			for (i=0; i<colCnt; i++) {
2982				s +=
2983					"<th class='fc- fc-col" + i + ' ' + headerClass + "'/>"; // fc- needed for setDayID
2984			}
2985			s +=
2986				"<th class='fc-agenda-gutter " + headerClass + "'>&nbsp;</th>" +
2987				"</tr>" +
2988				"</thead>" +
2989				"<tbody>" +
2990				"<tr>" +
2991				"<th class='fc-agenda-axis " + headerClass + "'>&nbsp;</th>";
2992			for (i=0; i<colCnt; i++) {
2993				s +=
2994					"<td class='fc- fc-col" + i + ' ' + contentClass + "'>" + // fc- needed for setDayID
2995					"<div>" +
2996					"<div class='fc-day-content'>" +
2997					"<div style='position:relative'>
2997&nbsp;</div>" +
2998					"</div>" +
2999					"</div>" +
3000					"</td>";
3001			}
3002			s +=
3003				"<td class='fc-agenda-gutter " + contentClass + "'>&nbsp;</td>" +
3004				"</tr>" +
3005				"</tbody>" +
3006				"</table>";
3007			dayTable = $(s).appendTo(element);
3008			dayHead = dayTable.find('thead');
3009			dayHeadCells = dayHead.find('th').slice(1, -1);
3010			dayBody = dayTable.find('tbody');
3011			dayBodyCells = dayBody.find('td').slice(0, -1);
3012			dayBodyCellInners = dayBodyCells.find('div.fc-day-content div');
3013			dayBodyFirstCell = dayBodyCells.eq(0);
3014			dayBodyFirstCellStretcher = dayBodyFirstCell.find('> div');
3015			
3016			markFirstLast(dayHead.add(dayHead.find('tr')));
3017			markFirstLast(dayBody.add(dayBody.find('tr')));
3018			
3019			axisFirstCells = dayHead.find('th:first');
3020			gutterCells = dayTable.find('.fc-agenda-gutter');
3021			
3022			slotLayer =
3023				$("<div style='position:absolute;z-index:2;left:0;width:100%'/>")
3024					.appendTo(element);
3025					
3026			if (opt('allDaySlot')) {
3027			
3028				daySegmentContainer =
3029					$("<div style='position:absolute;z-index:8;top:0;left:0'/>")
3030						.appendTo(slotLayer);
3031			
3032				s =
3033					"<table style='width:100%' class='fc-agenda-allday' cellspacing='0'>" +
3034					"<tr>" +
3035					"<th class='" + headerClass + " fc-agenda-axis'>" + opt('allDayText') + "</th>" +
3036					"<td>" +
3037					"<div class='fc-day-content'><div style='position:relative'/></div>" +
3038					"</td>" +
3039					"<th class='" + headerClass + " fc-agenda-gutter'>&nbsp;</th>" +
3040					"</tr>" +
3041					"</table>";
3042				allDayTable = $(s).appendTo(slotLayer);
3043				allDayRow = allDayTable.find('tr');
3044				
3045				dayBind(allDayRow.find('td'));
3046				
3047				axisFirstCells = axisFirstCells.add(allDayTable.find('th:first'));
3048				gutterCells = gutterCells.add(allDayTable.find('th.fc-agenda-gutter'));
3049				
3050				slotLayer.append(
3051					"<div class='fc-agenda-divider " + headerClass + "'>" +
3052					"<div class='fc-agenda-divider-inner'/>" +
3053					"</div>"
3054				);
3055				
3056			}else{
3057			
3058				daySegmentContainer = $([]); // in jQuery 1.4, we can just do $()
3059			
3060			}
3061			
3062			slotScroller =
3063				$("<div style='position:absolute;width:100%;overflow-x:hidden;overflow-y:auto'/>")
3064					.appendTo(slotLayer);
3065					
3066			slotContent =
3067				$("<div style='position:relative;width:100%;overflow:hidden'/>")
3068					.appendTo(slotScroller);
3069					
3070			slotSegmentContainer =
3071				$("<div style='position:absolute;z-index:8;top:0;left:0'/>")
3072					.appendTo(slotContent);
3073			
3074			s =
3075				"<table class='fc-agenda-slots' style='width:100%' cellspacing='0'>" +
3076				"<tbody>";
3077			d = zeroDate();
3078			maxd = addMinutes(cloneDate(d), maxMinute);
3079			addMinutes(d, minMinute);
3080			slotCnt = 0;
3081			for (i=0; d < maxd; i++) {
3082				minutes = d.getMinutes();
3083				s +=
3084					"<tr class='fc-slot" + i + ' ' + (!minutes ? '' : 'fc-minor') + "'>" +
3085					"<th class='fc-agenda-axis " + headerClass + "'>" +
3086					((!slotNormal || !minutes) ? formatDate(d, opt('axisFormat')) : '&nbsp;') +
3087					"</th>" +
3088					"<td class='" + contentClass + "'>" +
3089					"<div style='position:relative'>&nbsp;</div>" +
3090					"</td>" +
3091					"</tr>";
3092				addMinutes(d, opt('slotMinutes'));
3093				slotCnt++;
3094			}
3095			s +=
3096				"</tbody>" +
3097				"</table>";
3098			slotTable = $(s).appendTo(slotContent);
3099			slotTableFirstInner = slotTable.find('div:first');
3100			
3101			slotBind(slotTable.find('td'));
3102			
3103			axisFirstCells = axisFirstCells.add(slotTable.find('th:first'));
3104		}
3105		
3106		
3107		
3108		function updateCells() {
3109			var i;
3110			var headCell;
3111			var bodyCell;
3112			var date;
3113			var today = clearTime(new Date());
3114			for (i=0; i<colCnt; i++) {
3115				date = colDate(i);
3116				headCell = dayHeadCells.eq(i);
3117				headCell.html(formatDate(date, colFormat));
3118				bodyCell = dayBodyCells.eq(i);
3119				if (+date == +today) {
3120					bodyCell.addClass(tm + '-state-highlight fc-today');
3121				}else{
3122					bodyCell.removeClass(tm + '-state-highlight fc-today');
3123				}
3124				setDayID(headCell.add(bodyCell), date);
3125			}
3126		}
3127		
3128		
3129		
3130		function setHeight(height, dateChanged) {
3131			if (height === undefined) {
3132				height = viewHeight;
3133			}
3134			viewHeight = height;
3135			slotTopCache = {};
3136		
3137			var headHeight = dayBody.position().top;
3138			var allDayHeight = slotScroller.position().top; // including divider
3139			var bodyHeight = Math.min( // total body height, including borders
3140				height - headHeight,   // when scrollbars
3141				slotTable.height() + allDayHeight + 1 // when no scrollbars. +1 for bottom border
3142			);
3143			
3144			dayBodyFirstCellStretcher
3145				.height(bodyHeight - vsides(dayBodyFirstCell));
3146			
3147			slotLayer.css('top', headHeight);
3148			
3149			slotScroller.height(bodyHeight - allDayHeight - 1);
3150			
3151			slotHeight = slotTableFirstInner.height() + 1; // +1 for border
3152			
3153			if (dateChanged) {
3154				resetScroll();
3155			}
3156		}
3157		
3158		
3159		
3160		function setWidth(width) {
3161			viewWidth = width;
3162			colContentPositions.clear();
3163			
3164			axisWidth = 0;
3165			setOuterWidth(
3166				axisFirstCells
3167					.width('')
3168					.each(function(i, _cell) {
3169						axisWidth = Math.max(axisWidth, $(_cell).outerWidth());
3170					}),
3171				axisWidth
3172			);
3173			
3174			var slotTableWidth = slotScroller[0].clientWidth; // needs to be done after axisWidth (for IE7)
3175			//slotTable.width(slotTableWidth);
3176			
3177			gutterWidth = slotScroller.width() - slotTableWidth;
3178			if (gutterWidth) {
3179				setOuterWidth(gutterCells, gutterWidth);
3180				gutterCells
3181					.show()
3182					.prev()
3183					.removeClass('fc-last');
3184			}else{
3185				gutterCells
3186					.hide()
3187					.prev()
3188					.addClass('fc-last');
3189			}
3190			
3191			colWidth = Math.floor((slotTableWidth - axisWidth) / colCnt);
3192			setOuterWidth(dayHeadCells.slice(0, -1), colWidth);
3193		}
3194		
3195	
3196	
3197		function resetScroll() {
3198			var d0 = zeroDate();
3199			var scrollDate = cloneDate(d0);
3200			scrollDate.setHours(opt('firstHour'));
3201			var top = timePosition(d0, scrollDate) + 1; // +1 for the border
3202			function scroll() {
3203				slotScroller.scrollTop(top);
3204			}
3205			scroll();
3206			setTimeout(scroll, 0); // overrides any previous scroll state made by the browser
3207		}
3208		
3209		
3210		function beforeHide() {
3211			savedScrollTop = slotScroller.scrollTop();
3212		}
3213		
3214		
3215		function afterShow() {
3216			slotScroller.scrollTop(savedScrollTop);
3217		}
3218		
3219		
3220		
3221		/* Slot/Day clicking and binding
3222		-----------------------------------------------------------------------*/
3223		
3224	
3225		function dayBind(cells) {
3226			cells.click(slotClick)
3227				.mousedown(daySelectionMousedown);
3228		}
3229	
3230	
3231		function slotBind(cells) {
3232			cells.click(slotClick)
3233				.mousedown(slotSelectionMousedown);
3234		}
3235		
3236		
3237		function slotClick(ev) {
3238			if (!opt('selectable')) { // if selectable, SelectionManager will worry about dayClick
3239				var col = Math.min(colCnt-1, Math.floor((ev.pageX - dayTable.offset().left - axisWidth) / colWidth));
3240				var date = colDate(col);
3241				var rowMatch = this.parentNode.className.match(/fc-slot(\d+)/); // TODO: maybe use data
3242				if (rowMatch) {
3243					var mins = parseInt(rowMatch[1]) * opt('slotMinutes');
3244					var hours = Math.floor(mins/60);
3245					date.setHours(hours);
3246					date.setMinutes(mins%60 + minMinute);
3247					trigger('dayClick', dayBodyCells[col], date, false, ev);
3248				}else{
3249					trigger('dayClick', dayBodyCells[col], date, true, ev);
3250				}
3251			}
3252		}
3253		
3254		
3255		
3256		/* Semi-transparent Overlay Helpers
3257		-----------------------------------------------------*/
3258		
3259	
3260		function renderDayOverlay(startDate, endDate, refreshCoordinateGrid) { // endDate is exclusive
3261			if (refreshCoordinateGrid) {
3262				coordinateGrid.build();
3263			}
3264			var visStart = cloneDate(t.visStart);
3265			var startCol, endCol;
3266			if (rtl) {
3267				startCol = dayDiff(endDate, visStart)*dis+dit+1;
3268				endCol = dayDiff(startDate, visStart)*dis+dit+1;
3269			}else{
3270				startCol = dayDiff(startDate, visStart);
3271				endCol = dayDiff(endDate, visStart);
3272			}
3273			startCol = Math.max(0, startCol);
3274			endCol = Math.min(colCnt, endCol);
3275			if (startCol < endCol) {
3276				dayBind(
3277					renderCellOverlay(0, startCol, 0, endCol-1)
3278				);
3279			}
3280		}
3281		
3282		
3283		function renderCellOverlay(row0, col0, row1, col1) { // only for all-day?
3284			var rect = coordinateGrid.rect(row0, col0, row1, col1, slotLayer);
3285			return renderOverlay(rect, slotLayer);
3286		}
3287		
3288	
3289		function renderSlotOverlay(overlayStart, overlayEnd) {
3290			var dayStart = cloneDate(t.visStart);
3291			var dayEnd = addDays(cloneDate(dayStart), 1);
3292			for (var i=0; i<colCnt; i++) {
3293				var stretchStart = new Date(Math.max(dayStart, overlayStart));
3294				var stretchEnd = new Date(Math.min(dayEnd, overlayEnd));
3295				if (stretchStart < stretchEnd) {
3296					var col = i*dis+dit;
3297					var rect = coordinateGrid.rect(0, col, 0, col, slotContent); // only use it for horizontal coords
3298					var top = timePosition(dayStart, stretchStart);
3299					var bottom = timePosition(dayStart, stretchEnd);
3300					rect.top = top;
3301					rect.height = bottom - top;
3302					slotBind(
3303						renderOverlay(rect, slotContent)
3304					);
3305				}
3306				addDays(dayStart, 1);
3307				addDays(dayEnd, 1);
3308			}
3309		}
3310		
3311		
3312		
3313		/* Coordinate Utilities
3314		-----------------------------------------------------------------------------*/
3315		
3316		
3317		coordinateGrid = new CoordinateGrid(function(rows, cols) {
3318			var e, n, p;
3319			dayHeadCells.each(function(i, _e) {
3320				e = $(_e);
3321				n = e.offset().left;
3322				if (i) {
3323					p[1] = n;
3324				}
3325				p = [n];
3326				cols[i] = p;
3327			});
3328			p[1] = n + e.outerWidth();
3329			if (opt('allDaySlot')) {
3330				e = allDayRow;
3331				n = e.offset().top;
3332				rows[0] = [n, n+e.outerHeight()];
3333			}
3334			var slotTableTop = slotContent.offset().top;
3335			var slotScrollerTop = slotScroller.offset().top;
3336			var slotScrollerBottom = slotScrollerTop + slotScroller.outerHeight();
3337			function constrain(n) {
3338				return Math.max(slotScrollerTop, Math.min(slotScrollerBottom, n));
3339			}
3340			for (var i=0; i<slotCnt; i++) {
3341				rows.push([
3342					constrain(slotTableTop + slotHeight*i),
3343					constrain(slotTableTop + slotHeight*(i+1))
3344				]);
3345			}
3346		});
3347		
3348		
3349		hoverListener = new HoverListener(coordinateGrid);
3350		
3351		
3352		colContentPositions = new HorizontalPositionCache(function(col) {
3353			return dayBodyCellInners.eq(col);
3354		});
3355		
3356		
3357		function colContentLeft(col) {
3358			return colContentPositions.left(col);
3359		}
3360		
3361		
3362		function colContentRight(col) {
3363			return colContentPositions.right(col);
3364		}
3365		
3366		
3367		
3368		
3369		function dateCell(date) { // "cell" terminology is now confusing
3370			return {
3371				row: Math.floor(dayDiff(date, t.visStart) / 7),
3372				col: dayOfWeekCol(date.getDay())
3373			};
3374		}
3375		
3376		
3377		function cellDate(cell) {
3378			var d = colDate(cell.col);
3379			var slotIndex = cell.row;
3380			if (opt('allDaySlot')) {
3381				slotIndex--;
3382			}
3383			if (slotIndex >= 0) {
3384				addMinutes(d, minMinute + slotIndex * opt('slotMinutes'));
3385			}
3386			return d;
3387		}
3388		
3389		
3390		function colDate(col) { // returns dates with 00:00:00
3391			return addDays(cloneDate(t.visStart), col*dis+dit);
3392		}
3393		
3394		
3395		function cellIsAllDay(cell) {
3396			return opt('allDaySlot') && !cell.row;
3397		}
3398		
3399		
3400		function dayOfWeekCol(dayOfWeek) {
3401			return ((dayOfWeek - Math.max(firstDay, nwe) + colCnt) % colCnt)*dis+dit;
3402		}
3403		
3404		
3405		
3406		
3407		// get the Y coordinate of the given time on the given day (both Date objects)
3408		function timePosition(day, time) { // both date objects. day holds 00:00 of current day
3409			day = cloneDate(day, true);
3410			if (time < addMinutes(cloneDate(day), minMinute)) {
3411				return 0;
3412			}
3413			if (time >= addMinutes(cloneDate(day), maxMinute)) {
3414				return slotTable.height();
3415			}
3416			var slotMinutes = opt('slotMinutes'),
3417				minutes = time.getHours()*60 + time.getMinutes() - minMinute,
3418				slotI = Math.floor(minutes / slotMinutes),
3419				slotTop = slotTopCache[slotI];
3420			if (slotTop === undefined) {
3421				slotTop = slotTopCache[slotI] = slotTable.find('tr:eq(' + slotI + ') td div')[0].offsetTop; //.position().top; // need this optimization???
3422			}
3423			return Math.max(0, Math.round(
3424				slotTop - 1 + slotHeight * ((minutes % slotMinutes) / slotMinutes)
3425			));
3426		}
3427		
3428		
3429		function allDayBounds() {
3430			return {
3431				left: axisWidth,
3432				right: viewWidth - gutterWidth
3433			}
3434		}
3435		
3436		
3437		function getAllDayRow(index) {
3438			return allDayRow;
3439		}
3440		
3441		
3442		function defaultEventEnd(event) {
3443			var start = cloneDate(event.start);
3444			if (event.allDay) {
3445				return start;
3446			}
3447			return addMinutes(start, opt('defaultEventMinutes'));
3448		}
3449		
3450		
3451		
3452		/* Selection
3453		---------------------------------------------------------------------------------*/
3454		
3455		
3456		function defaultSelectionEnd(startDate, allDay) {
3457			if (allDay) {
3458				return cloneDate(startDate);
3459			}
3460			return addMinutes(cloneDate(startDate), opt('slotMinutes'));
3461		}
3462		
3463		
3464		function renderSelection(startDate, endDate, allDay) { // only for all-day
3465			if (allDay) {
3466				if (opt('allDaySlot')) {
3467					renderDayOverlay(startDate, addDays(cloneDate(endDate), 1), true);
3468				}
3469			}else{
3470				renderSlotSelection(startDate, endDate);
3471			}
3472		}
3473		
3474		
3475		function renderSlotSelection(startDate, endDate) {
3476			var helperOption = opt('selectHelper');
3477			coordinateGrid.build();
3478			if (helperOption) {
3479				var col = dayDiff(startDate, t.visStart) * dis + dit;
3480				if (col >= 0 && col < colCnt) { // only works when times are on same day
3481					var rect = coordinateGrid.rect(0, col, 0, col, slotContent); // only for horizontal coords
3482					var top = timePosition(startDate, startDate);
3483					var bottom = timePosition(startDate, endDate);
3484					if (bottom > top) { // protect against selections that are entirely before or after visible range
3485						rect.top = top;
3486						rect.height = bottom - top;
3487						rect.left += 2;
3488						rect.width -= 5;
3489						if ($.isFunction(helperOption)) {
3490							var helperRes = helperOption(startDate, endDate);
3491							if (helperRes) {
3492								rect.position = 'absolute';
3493								rect.zIndex = 8;
3494								selectionHelper = $(helperRes)
3495									.css(rect)
3496									.appendTo(slotContent);
3497							}
3498						}else{
3499							rect.isStart = true; // conside rect a "seg" now
3500							rect.isEnd = true;   //
3501							selectionHelper = $(slotSegHtml(
3502								{
3503									title: '',
3504									start: startDate,
3505									end: endDate,
3506									className: ['fc-select-helper'],
3507									editable: false
3508								},
3509								rect
3510							));
3511							selectionHelper.css('opacity', opt('dragOpacity'));
3512						}
3513						if (selectionHelper) {
3514							slotBind(selectionHelper);
3515							slotContent.append(selectionHelper);
3516							setOuterWidth(selectionHelper, rect.width, true); // needs to be after appended
3517							setOuterHeight(selectionHelper, rect.height, true);
3518						}
3519					}
3520				}
3521			}else{
3522				renderSlotOverlay(startDate, endDate);
3523			}
3524		}
3525		
3526		
3527		function clearSelection() {
3528			clearOverlays();
3529			if (selectionHelper) {
3530				selectionHelper.remove();
3531				selectionHelper = null;
3532			}
3533		}
3534		
3535		
3536		function slotSelectionMousedown(ev) {
3537			if (ev.which == 1 && opt('selectable')) { // ev.which==1 means left mouse button
3538				unselect(ev);
3539				var dates;
3540				hoverListener.start(function(cell, origCell) {
3541					clearSelection();
3542					if (cell && cell.col == origCell.col && !cellIsAllDay(cell)) {
3543						var d1 = cellDate(origCell);
3544						var d2 = cellDate(cell);
3545						dates = [
3546							d1,
3547							addMinutes(cloneDate(d1), opt('slotMinutes')),
3548							d2,
3549							addMinutes(cloneDate(d2), opt('slotMinutes'))
3550						].sort(cmp);
3551						renderSlotSelection(dates[0], dates[3]);
3552					}else{
3553						dates = null;
3554					}
3555				}, ev);
3556				$(document).one('mouseup', function(ev) {
3557					hoverListener.stop();
3558					if (dates) {
3559						if (+dates[0] == +dates[1]) {
3560							reportDayClick(dates[0], false, ev);
3561						}
3562						reportSelection(dates[0], dates[3], false, ev);
3563					}
3564				});
3565			}
3566		}
3567		
3568		
3569		function reportDayClick(date, allDay, ev) {
3570			trigger('dayClick', dayBodyCells[dayOfWeekCol(date.getDay())], date, allDay, ev);
3571		}
3572		
3573		
3574		
3575		/* External Dragging
3576		--------------------------------------------------------------------------------*/
3577		
3578		
3579		function dragStart(_dragElement, ev, ui) {
3580			hoverListener.start(function(cell) {
3581				clearOverlays();
3582				if (cell) {
3583					if (cellIsAllDay(cell)) {
3584						renderCellOverlay(cell.row, cell.col, cell.row, cell.col);
3585					}else{
3586						var d1 = cellDate(cell);
3587						var d2 = addMinutes(cloneDate(d1), opt('defaultEventMinutes'));
3588						renderSlotOverlay(d1, d2);
3589					}
3590				}
3591			}, ev);
3592		}
3593		
3594		
3595		function dragStop(_dragElement, ev, ui) {
3596			var cell = hoverListener.stop();
3597			clearOverlays();
3598			if (cell) {
3599				trigger('drop', _dragElement, cellDate(cell), cellIsAllDay(cell), ev, ui);
3600			}
3601		}
3602	
3603	
3604	}
3605	
3606	function AgendaEventRenderer() {
3607		var t = this;
3608		
3609		
3610		// exports
3611		t.renderEvents = renderEvents;
3612		t.compileDaySegs = compileDaySegs; // for DayEventRenderer
3613		t.clearEvents = clearEvents;
3614		t.slotSegHtml = slotSegHtml;
3615		t.bindDaySeg = bindDaySeg;
3616		
3617		
3618		// imports
3619		DayEventRenderer.call(t);
3620		var opt = t.opt;
3621		var trigger = t.trigger;
3622		//var setOverflowHidden = t.setOverflowHidden;
3623		var isEventDraggable = t.isEventDraggable;
3624		var isEventResizable = t.isEventResizable;
3625		var eventEnd = t.eventEnd;
3626		var reportEvents = t.reportEvents;
3627		var reportEventClear = t.reportEventClear;
3628		var eventElementHandlers = t.eventElementHandlers;
3629		var setHeight = t.setHeight;
3630		var getDaySegmentContainer = t.getDaySegmentContainer;
3631		var getSlotSegmentContainer = t.getSlotSegmentContainer;
3632		var getHoverListener = t.getHoverListener;
3633		var getMaxMinute = t.getMaxMinute;
3634		var getMinMinute = t.getMinMinute;
3635		var timePosition = t.timePosition;
3636		var colContentLeft = t.colContentLeft;
3637		var colContentRight = t.colContentRight;
3638		var renderDaySegs = t.renderDaySegs;
3639		var resizableDayEvent = t.resizableDayEvent; // TODO: streamline binding architecture
3640		var getColCnt = t.getColCnt;
3641		var getColWidth = t.getColWidth;
3642		var getSlotHeight = t.getSlotHeight;
3643		var getBodyContent = t.getBodyContent;
3644		var reportEventElement = t.reportEventElement;
3645		var showEvents = t.showEvents;
3646		var hideEvents = t.hideEvents;
3647		var eventDrop = t.eventDrop;
3648		var eventResize = t.eventResize;
3649		var renderDayOverlay = t.renderDayOverlay;
3650		var clearOverlays = t.clearOverlays;
3651		var calendar = t.calendar;
3652		var formatDate = calendar.formatDate;
3653		var formatDates = calendar.formatDates;
3654		
3655		
3656		
3657		/* Rendering
3658		----------------------------------------------------------------------------*/
3659		
3660	
3661		function renderEvents(events, modifiedEventId) {
3662			reportEvents(events);
3663			var i, len=events.length,
3664				dayEvents=[],
3665				slotEvents=[];
3666			for (i=0; i<len; i++) {
3667				if (events[i].allDay) {
3668					dayEvents.push(events[i]);
3669				}else{
3670					slotEvents.push(events[i]);
3671				}
3672			}
3673			if (opt('allDaySlot')) {
3674				renderDaySegs(compileDaySegs(dayEvents), modifiedEventId);
3675				setHeight(); // no params means set to viewHeight
3676			}
3677			renderSlotSegs(compileSlotSegs(slotEvents), modifiedEventId);
3678		}
3679		
3680		
3681		function clearEvents() {
3682			reportEventClear();
3683			getDaySegmentContainer().empty();
3684			getSlotSegmentContainer().empty();
3685		}
3686		
3687		
3688		function compileDaySegs(events) {
3689			var levels = stackSegs(sliceSegs(events, $.map(events, exclEndDay), t.visStart, t.visEnd)),
3690				i, levelCnt=levels.length, level,
3691				j, seg,
3692				segs=[];
3693			for (i=0; i<levelCnt; i++) {
3694				level = levels[i];
3695				for (j=0; j<level.length; j++) {
3696					seg = level[j];
3697					seg.row = 0;
3698					seg.level = i; // not needed anymore
3699					segs.push(seg);
3700				}
3701			}
3702			return segs;
3703		}
3704		
3705		
3706		function compileSlotSegs(events) {
3707			var colCnt = getColCnt(),
3708				minMinute = getMinMinute(),
3709				maxMinute = getMaxMinute(),
3710				d = addMinutes(cloneDate(t.visStart), minMinute),
3711				visEventEnds = $.map(events, slotEventEnd),
3712				i, col,
3713				j, level,
3714				k, seg,
3715				segs=[];
3716			for (i=0; i<colCnt; i++) {
3717				col = stackSegs(sliceSegs(events, visEventEnds, d, addMinutes(cloneDate(d), maxMinute-minMinute)));
3718				countForwardSegs(col);
3719				for (j=0; j<col.length; j++) {
3720					level = col[j];
3721					for (k=0; k<level.length; k++) {
3722						seg = level[k];
3723						seg.col = i;
3724						seg.level = j;
3725						segs.push(seg);
3726					}
3727				}
3728				addDays(d, 1, true);
3729	
3730			}
3731			return segs;
3732		}
3733		
3734		
3735		function slotEventEnd(event) {
3736			if (event.end) {
3737				return cloneDate(event.end);
3738			}else{
3739				return addMinutes(cloneDate(event.start), opt('defaultEventMinutes'));
3740			}
3741		}
3742		
3743		
3744		// renders events in the 'time slots' at the bottom
3745		
3746		function renderSlotSegs(segs, modifiedEventId) {
3747		
3748			var i, segCnt=segs.length, seg,
3749				event,
3750				classes,
3751				top, bottom,
3752				colI, levelI, forward,
3753				leftmost,
3754				availWidth,
3755				outerWidth,
3756				left,
3757				html='',
3758				eventElements,
3759				eventElement,
3760				triggerRes,
3761				vsideCache={},
3762				hsideCache={},
3763				key, val,
3764				contentElement,
3765				height,
3766				slotSegmentContainer = getSlotSegmentContainer(),
3767				rtl, dis, dit,
3768				colCnt = getColCnt();
3769				
3770			if (rtl = opt('isRTL')) {
3771				dis = -1;
3772				dit = colCnt - 1;
3773			}else{
3774				dis = 1;
3775				dit = 0;
3776			}
3777				
3778			// calculate position/dimensions, create html
3779			for (i=0; i<segCnt; i++) {
3780				seg = segs[i];
3781				event = seg.event;
3782				top = timePosition(seg.start, seg.start);
3783				bottom = timePosition(seg.start, seg.end);
3784				colI = seg.col;
3785				levelI = seg.level;
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
3790				if (levelI) {
3791					// indented and thin
3792					outerWidth = availWidth / (levelI + forward + 1);
3793				}else{
3794					if (forward) {
3795						// moderately wide, aligned left still
3796						outerWidth = ((availWidth / (forward + 1)) - (12/2)) * 2; // 12 is the predicted width of resizer =
3797					}else{
3798						// can be entire width, aligned left
3799						outerWidth = availWidth;
3800					}
3801				}
3802				left = leftmost +                                  // leftmost possible
3803					(availWidth / (levelI + forward + 1) * levelI) // indentation
3804					* dis + (rtl ? availWidth - outerWidth : 0);   // rtl
3805				seg.top = top;
3806				seg.left = left;
3807				seg.outerWidth = outerWidth;
3808				seg.outerHeight = bottom - top;
3809				html += slotSegHtml(event, seg);
3810			}
3811			slotSegmentContainer[0].innerHTML = html; // faster than html()
3812			eventElements = slotSegmentContainer.children();
3813			
3814			// retrieve elements, run through eventRender callback, bind event handlers
3815			for (i=0; i<segCnt; i++) {
3816				seg = segs[i];
3817				event = seg.event;
3818				eventElement = $(eventElements[i]); // faster than eq()
3819				triggerRes = trigger('eventRender', event, event, eventElement);
3820				if (triggerRes === false) {
3821					eventElement.remove();
3822				}else{
3823					if (triggerRes && triggerRes !== true) {
3824						eventElement.remove();
3825						eventElement = $(triggerRes)
3826							.css({
3827								position: 'absolute',
3828								top: seg.top,
3829								left: seg.left
3830							})
3831							.appendTo(slotSegmentContainer);
3832					}
3833					seg.element = eventElement;
3834					if (event._id === modifiedEventId) {
3835						bindSlotSeg(event, eventElement, seg);
3836					}else{
3837						eventElement[0]._fci = i; // for lazySegBind
3838					}
3839					reportEventElement(event, eventElement);
3840				}
3841			}
3842			
3843			lazySegBind(slotSegmentContainer, segs, bindSlotSeg);
3844			
3845			// record event sides and title positions
3846			for (i=0; i<segCnt; i++) {
3847				seg = segs[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;
3856					}
3857				}
3858			}
3859			
3860			// set all positions/dimensions at once
3861			for (i=0; i<segCnt; i++) {
3862				seg = segs[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';
3867					event = seg.event;
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')
3873							.remove();
3874					}
3875					trigger('eventAfterRender', event, event, eventElement);
3876				}
3877			}
3878						
3879		}
3880		
3881		
3882		function slotSegHtml(event, seg) {
3883			var html = "<";
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');
3890			}
3891			if (seg.isStart) {
3892				classes.push('fc-corner-top');
3893			}
3894			if (seg.isEnd) {
3895				classes.push('fc-corner-bottom');
3896			}
3897			classes = classes.concat(event.className);
3898			if (event.source) {
3899				classes = classes.concat(event.source.className || []);
3900			}
3901			if (url) {
3902				html += "a href='" + htmlEscape(event.url) + "'";
3903			}else{
3904				html += "div";
3905			}
3906			html +=
3907				" class='" + classes.join(' ') + "'" +
3908				" style='position:absolute;z-index:8;top:" + seg.top + "px;left:" + seg.left + "px;" + skinCss + "'" +
3909				">" +
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'))) +
3914				"</div>" +
3915				"</div>" +
3916				"<div class='fc-event-content'>" +
3917				"<div class='fc-event-title'>" +
3918				htmlEscape(event.title) +
3919				"</div>" +
3920				"</div>" +
3921				"<div class='fc-event-bg'></div>" +
3922				"</div>"; // close inner
3923			if (seg.isEnd && isEventResizable(event)) {
3924				html +=
3925					"<div class='ui-resizable-handle ui-resizable-s'>=</div>";
3926			}
3927			html +=
3928				"</" + (url ? "a" : "div") + ">";
3929			return html;
3930		}
3931		
3932		
3933		function bindDaySeg(event, eventElement, seg) {
3934			if (isEventDraggable(event)) {
3935				draggableDayEvent(event, eventElement, seg.isStart);
3936			}
3937			if (seg.isEnd && isEventResizable(event)) {
3938				resizableDayEvent(event, eventElement, seg);
3939			}
3940			eventElementHandlers(event, eventElement);
3941				// needs to be after, because resizableDayEvent might stopImmediatePropagation on click
3942		}
3943		
3944		
3945		function bindSlotSeg(event, eventElement, seg) {
3946			var timeElement = eventElement.find('div.fc-event-time');
3947			if (isEventDraggable(event)) {
3948				draggableSlotEvent(event, eventElement, timeElement);
3949			}
3950			if (seg.isEnd && isEventResizable(event)) {
3951				resizableSlotEvent(event, eventElement, timeElement);
3952			}
3953			eventElementHandlers(event, eventElement);
3954		}
3955		
3956		
3957		
3958		/* Dragging
3959		-----------------------------------------------------------------------------------*/
3960		
3961		
3962		// when event starts out FULL-DAY
3963		
3964		function draggableDayEvent(event, eventElement, isStart) {
3965			var origWidth;
3966			var revert;
3967			var allDay=true;
3968			var dayDelta;
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({
3975				zIndex: 9,
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) {
3983						clearOverlays();
3984						if (cell) {
3985							//setOverflowHidden(true);
3986							revert = false;
3987							dayDelta = colDelta * dis;
3988							if (!cell.row) {
3989								// on full-days
3990								renderDayOverlay(
3991									addDays(cloneDate(event.start), dayDelta),
3992									addDays(exclEndDay(event), dayDelta)
3993								);
3994								resetElement();
3995							}else{
3996								// mouse is over bottom slots
3997								if (isStart) {
3998									if (allDay) {
3999										// convert event to temporary slot-event
4000										eventElement.width(colWidth - 10); // don't use entire width
4001										setOuterHeight(
4002											eventElement,
4003											slotHeight * Math.round(
4004												(event.end ? ((event.end - event.start) / MINUTE_MS) : opt('defaultEventMinutes'))
4005												/ opt('slotMinutes')
4006											)
4007										);
4008										eventElement.draggable('option', 'grid', [colWidth, 1]);
4009										allDay = false;
4010									}
4011								}else{
4012									revert = true;
4013								}
4014							}
4015							revert = revert || (allDay && !dayDelta);
4016						}else{
4017							resetElement();
4018							//setOverflowHidden(false);
4019							revert = true;
4020						}
4021						eventElement.draggable('option', 'revert', revert);
4022					}, ev, 'drag');
4023				},
4024				stop: function(ev, ui) {
4025					hoverListener.stop();
4026					clearOverlays();
4027					trigger('eventDragStop', eventElement, event, ev, ui);
4028					if (revert) {
4029						// hasn't moved or is out of bounds (draggable has already reverted)
4030						resetElement();
4031						eventElement.css('filter', ''); // clear IE opacity side-effects
4032						showEvents(event, eventElement);
4033					}else{
4034						// changed!
4035						var minuteDelta = 0;
4036						if (!allDay) {
4037							minuteDelta = Math.round((eventElement.offset().top - getBodyContent().offset().top) / slotHeight)
4038								* opt('slotMinutes')
4039								+ minMinute
4040								- (event.start.getHours() * 60 + event.start.getMinutes());
4041						}
4042						eventDrop(this, event, dayDelta, minuteDelta, allDay, ev, ui);
4043					}
4044					//setOverflowHidden(false);
4045				}
4046			});
4047			function resetElement() {
4048				if (!allDay) {
4049					eventElement
4050						.width(origWidth)
4051						.height('')
4052						.draggable('option', 'grid', null);
4053					allDay = true;
4054				}
4055			}
4056		}
4057		
4058		
4059		// when event starts out IN TIMESLOTS
4060		
4061		function draggableSlotEvent(event, eventElement, timeElement) {
4062			var origPosition;
4063			var allDay=false;
4064			var dayDelta;
4065			var minuteDelta;
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({
4073				zIndex: 9,
4074				scroll: false,
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);
4086						clearOverlays();
4087						if (cell) {
4088							dayDelta = colDelta * dis;
4089							if (opt('allDaySlot') && !cell.row) {
4090								// over full days
4091								if (!allDay) {
4092									// convert to temporary all-day event
4093									allDay = true;
4094									timeElement.hide();
4095									eventElement.draggable('option', 'grid', null);
4096								}
4097								renderDayOverlay(
4098									addDays(cloneDate(event.start), dayDelta),
4099									addDays(exclEndDay(event), dayDelta)
4100								);
4101							}else{
4102								// on slots
4103								resetElement();
4104							}
4105						}
4106					}, ev, 'drag');
4107				},
4108				drag: function(ev, ui) {
4109					minuteDelta = Math.round((ui.position.top - origPosition.top) / slotHeight) * opt('slotMinutes');
4110					if (minuteDelta != prevMinuteDelta) {
4111						if (!allDay) {
4112							updateTimeText(minuteDelta);
4113						}
4114						prevMinuteDelta = minuteDelta;
4115					}
4116				},
4117				stop: function(ev, ui) {
4118					var cell = hoverListener.stop();
4119					clearOverlays();
4120					trigger('eventDragStop', eventElement, event, ev, ui);
4121					if (cell && (dayDelta || minuteDelta || allDay)) {
4122						// changed!
4123						eventDrop(this, event, dayDelta, allDay ? 0 : minuteDelta, allDay, ev, ui);
4124					}else{
4125						// either no change or out-of-bounds (draggable has already reverted)
4126						resetElement();
4127						eventElement.css('filter', ''); // clear IE opacity side-effects
4128						eventElement.css(origPosition); // sometimes fast drags make event revert to wrong position
4129						updateTimeText(0);
4130						showEvents(event, eventElement);
4131					}
4132				}
4133			});
4134			function updateTimeText(minuteDelta) {
4135				var newStart = addMinutes(cloneDate(event.start), minuteDelta);
4136				var newEnd;
4137				if (event.end) {
4138					newEnd = addMinutes(cloneDate(event.end), minuteDelta);
4139				}
4140				timeElement.text(formatDates(newStart, newEnd, opt('timeFormat')));
4141			}
4142			function resetElement() {
4143				// convert back to original slot-event
4144				if (allDay) {
4145					timeElement.css('display', ''); // show() was causing display=inline
4146					eventElement.draggable('option', 'grid', [colWidth, slotHeight]);
4147					allDay = false;
4148				}
4149			}
4150		}
4151		
4152		
4153		
4154		/* Resizing
4155		--------------------------------------------------------------------------------------*/
4156		
4157		
4158		function resizableSlotEvent(event, eventElement, timeElement) {
4159			var slotDelta, prevSlotDelta;
4160			var slotHeight = getSlotHeight();
4161			eventElement.resizable({
4162				handles: {
4163					s: 'div.ui-resizable-s'
4164				},
4165				grid: slotHeight,
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);
4171				},
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) {
4176						timeElement.text(
4177							formatDates(
4178								event.start,
4179								(!slotDelta && !event.end) ? null : // no change, so don't display time range
4180									addMinutes(eventEnd(event), opt('slotMinutes')*slotDelta),
4181								opt('timeFormat')
4182							)
4183						);
4184						prevSlotDelta = slotDelta;
4185					}
4186				},
4187				stop: function(ev, ui) {
4188					trigger('eventResizeStop', this, event, ev, ui);
4189					if (slotDelta) {
4190						eventResize(this, event, 0, opt('slotMinutes')*slotDelta, ev, ui);
4191					}else{
4192						eventElement.css('z-index', 8);
4193						showEvents(event, eventElement);
4194						// BUG: if event was really short, need to put title back in span
4195					}
4196				}
4197			});
4198		}
4199		
4200	
4201	}
4202	
4203	
4204	function countForwardSegs(levels) {
4205		var i, j, k, level, segForward, segBack;
4206		for (i=levels.length-1; i>0; i--) {
4207			level = levels[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);
4214					}
4215				}
4216			}
4217		}
4218	}
4219	
4220	
4221	
4222	
4223	function View(element, calendar, viewName) {
4224		var t = this;
4225		
4226		
4227		// exports
4228		t.element = element;
4229		t.calendar = calendar;
4230		t.name = viewName;
4231		t.opt = opt;
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;
4245		// t.title
4246		// t.start, t.end
4247		// t.visStart, t.visEnd
4248		
4249		
4250		// imports
4251		var defaultEventEnd = t.defaultEventEnd;
4252		var normalizeEvent = calendar.normalizeEvent; // in EventManager
4253		var reportEventChange = calendar.reportEventChange;
4254		
4255		
4256		// locals
4257		var eventsByID = {};
4258		var eventElements = [];
4259		var eventElementsByID = {};
4260		var options = calendar.options;
4261		
4262		
4263		
4264		function opt(name, viewNameOverride) {
4265			var v = options[name];
4266			if (typeof v == 'object') {
4267				return smartProperty(v, viewNameOverride || viewName);
4268			}
4269			return v;
4270		}
4271	
4272		
4273		function trigger(name, thisObj) {
4274			return calendar.trigger.apply(
4275				calendar,
4276				[name, thisObj || t].concat(Array.prototype.slice.call(arguments, 2), [t])
4277			);
4278		}
4279		
4280		
4281		/*
4282		function setOverflowHidden(bool) {
4283			element.css('overflow', bool ? 'hidden' : '');
4284		}
4285		*/
4286		
4287		
4288		function isEventDraggable(event) {
4289			return isEventEditable(event) && !opt('disableDragging');
4290	
4291		}
4292		
4293		
4294		function isEventResizable(event) { // but also need to make sure the seg.isEnd == true
4295			return isEventEditable(event) && !opt('disableResizing');
4296		}
4297		
4298		
4299		function isEventEditable(event) {
4300			return firstDefined(event.editable, (event.source || {}).editable, opt('editable'));
4301		}
4302		
4303		
4304		
4305		/* Event Data
4306		------------------------------------------------------------------------------*/
4307		
4308		
4309		// report when view receives new events
4310		function reportEvents(events) { // events are already normalized at this point
4311			eventsByID = {};
4312			var i, len=events.length, event;
4313			for (i=0; i<len; i++) {
4314				event = events[i];
4315				if (eventsByID[event._id]) {
4316					eventsByID[event._id].push(event);
4317				}else{
4318					eventsByID[event._id] = [event];
4319				}
4320			}
4321		}
4322		
4323		
4324		// returns a Date object for an event's end
4325		function eventEnd(event) {
4326			return event.end ? cloneDate(event.end) : defaultEventEnd(event);
4327		}
4328		
4329		
4330		
4331		/* Event Elements
4332		------------------------------------------------------------------------------*/
4333		
4334		
4335		// report when view creates an element for an event
4336		function reportEventElement(event, element) {
4337			eventElements.push(element);
4338			if (eventElementsByID[event._id]) {
4339				eventElementsByID[event._id].push(element);
4340			}else{
4341				eventElementsByID[event._id] = [element];
4342			}
4343		}
4344		
4345		
4346		function reportEventClear() {
4347			eventElements = [];
4348			eventElementsByID = {};
4349		}
4350		
4351		
4352		// attaches eventClick, eventMouseover, eventMouseout
4353		function eventElementHandlers(event, eventElement) {
4354			eventElement
4355				.click(function(ev) {
4356					if (!eventElement.hasClass('ui-draggable-dragging') &&
4357						!eventElement.hasClass('ui-resizable-resizing')) {
4358							return trigger('eventClick', this, event, ev);
4359						}
4360				})
4361				.hover(
4362					function(ev) {
4363						trigger('eventMouseover', this, event, ev);
4364					},
4365					function(ev) {
4366						trigger('eventMouseout', this, event, ev);
4367					}
4368				);
4369			// TODO: don't fire eventMouseover/eventMouseout *while* dragging is occuring (on subject element)
4370			// TODO: same for resizing
4371		}
4372		
4373		
4374		function showEvents(event, exceptElement) {
4375			eachEventElement(event, exceptElement, 'show');
4376		}
4377		
4378		
4379		function hideEvents(event, exceptElement) {
4380			eachEventElement(event, exceptElement, 'hide');
4381		}
4382		
4383		
4384		function eachEventElement(event, exceptElement, funcName) {
4385			var elements = eventElementsByID[event._id],
4386				i, len = elements.length;
4387			for (i=0; i<len; i++) {
4388				if (!exceptElement || elements[i][0] != exceptElement[0]) {
4389					elements[i][funcName]();
4390				}
4391			}
4392		}
4393		
4394		
4395		
4396		/* Event Modification Reporting
4397		---------------------------------------------------------------------------------*/
4398		
4399		
4400		function eventDrop(e, event, dayDelta, minuteDelta, allDay, ev, ui) {
4401			var oldAllDay = event.allDay;
4402			var eventId = event._id;
4403			moveEvents(eventsByID[eventId], dayDelta, minuteDelta, allDay);
4404			trigger(
4405				'eventDrop',
4406				e,
4407				event,
4408				dayDelta,
4409				minuteDelta,
4410				allDay,
4411				function() {
4412					// TODO: investigate cases where this inverse technique might not work
4413					moveEvents(eventsByID[eventId], -dayDelta, -minuteDelta, oldAllDay);
4414					reportEventChange(eventId);
4415				},
4416				ev,
4417				ui
4418			);
4419			reportEventChange(eventId);
4420		}
4421		
4422		
4423		function eventResize(e, event, dayDelta, minuteDelta, ev, ui) {
4424			var eventId = event._id;
4425			elongateEvents(eventsByID[eventId], dayDelta, minuteDelta);
4426			trigger(
4427				'eventResize',
4428				e,
4429				event,
4430				dayDelta,
4431				minuteDelta,
4432				function() {
4433					// TODO: investigate cases where this inverse technique might not work
4434					elongateEvents(eventsByID[eventId], -dayDelta, -minuteDelta);
4435					reportEventChange(eventId);
4436				},
4437				ev,
4438				ui
4439			);
4440			reportEventChange(eventId);
4441		}
4442		
4443		
4444		
4445		/* Event Modification Math
4446		---------------------------------------------------------------------------------*/
4447		
4448		
4449		function moveEvents(events, dayDelta, minuteDelta, allDay) {
4450			minuteDelta = minuteDelta || 0;
4451			for (var e, len=events.length, i=0; i<len; i++) {
4452				e = events[i];
4453				if (allDay !== undefined) {
4454					e.allDay = allDay;
4455				}
4456				addMinutes(addDays(e.start, dayDelta, true), minuteDelta);
4457				if (e.end) {
4458					e.end = addMinutes(addDays(e.end, dayDelta, true), minuteDelta);
4459				}
4460				normalizeEvent(e, options);
4461			}
4462		}
4463		
4464		
4465		function elongateEvents(events, dayDelta, minuteDelta) {
4466			minuteDelta = minuteDelta || 0;
4467			for (var e, len=events.length, i=0; i<len; i++) {
4468				e = events[i];
4469				e.end = addMinutes(addDays(eventEnd(e), dayDelta, true), minuteDelta);
4470				normalizeEvent(e, options);
4471			}
4472		}
4473		
4474	
4475	}
4476	
4477	function DayEventRenderer() {
4478		var t = this;
4479	
4480		
4481		// exports
4482		t.renderDaySegs = renderDaySegs;
4483		t.resizableDayEvent = resizableDayEvent;
4484		
4485		
4486		// imports
4487		var opt = t.opt;
4488		var trigger = t.trigger;
4489		var isEventDraggable = t.isEventDraggable;
4490		var isEventResizable = t.isEventResizable;
4491		var eventEnd = t.eventEnd;
4492		var reportEventElement = t.reportEventElement;
4493		var showEvents = t.showEvents;
4494		var hideEvents = t.hideEvents;
4495		var eventResize = t.eventResize;
4496		var getRowCnt = t.getRowCnt;
4497		var getColCnt = t.getColCnt;
4498		var getColWidth = t.getColWidth;
4499		var allDayRow = t.allDayRow;
4500		var allDayBounds = t.allDayBounds;
4501		var colContentLeft = t.colContentLeft;
4502		var colContentRight = t.colContentRight;
4503		var dayOfWeekCol = t.dayOfWeekCol;
4504		var dateCell = t.dateCell;
4505		var compileDaySegs = t.compileDaySegs;
4506		var getDaySegmentContainer = t.getDaySegmentContainer;
4507		var bindDaySeg = t.bindDaySeg; //TODO: streamline this
4508		var formatDates = t.calendar.formatDates;
4509		var renderDayOverlay = t.renderDayOverlay;
4510		var clearOverlays = t.clearOverlays;
4511		var clearSelection = t.clearSelection;
4512		
4513		
4514		
4515		/* Rendering
4516		-----------------------------------------------------------------------------*/
4517		
4518		
4519		function renderDaySegs(segs, modifiedEventId) {
4520			var segmentContainer = getDaySegmentContainer();
4521			var rowDivs;
4522			var rowCnt = getRowCnt();
4523			var colCnt = getColCnt();
4524			var i = 0;
4525			var rowI;
4526			var levelI;
4527			var colHeights;
4528			var j;
4529			var segCnt = segs.length;
4530			var seg;
4531			var top;
4532			var k;
4533			segmentContainer[0].innerHTML = daySegHTML(segs); // faster than .html()
4534			daySegElementResolve(segs, segmentContainer.children());
4535			daySegElementReport(segs);
4536			daySegHandlers(segs, segmentContainer, modifiedEventId);
4537			daySegCalcHSides(segs);
4538			daySegSetWidths(segs);
4539			daySegCalcHeights(segs);
4540			rowDivs = getRowDivs();
4541			// set row heights, calculate event tops (in relation to row top)
4542			for (rowI=0; rowI<rowCnt; rowI++) {
4543				levelI = 0;
4544				colHeights = [];
4545				for (j=0; j<colCnt; j++) {
4546					colHeights[j] = 0;
4547				}
4548				while (i<segCnt && (seg = segs[i]).row == rowI) {
4549					// loop through segs in a row
4550					top = arrayMax(colHeights.slice(seg.startCol, seg.endCol));
4551					seg.top = top;
4552					top += seg.outerHeight;
4553					for (k=seg.startCol; k<seg.endCol; k++) {
4554						colHeights[k] = top;
4555					}
4556					i++;
4557				}
4558				rowDivs[rowI].height(arrayMax(colHeights));
4559			}
4560			daySegSetTops(segs, getRowTops(rowDivs));
4561		}
4562		
4563		
4564		function renderTempDaySegs(segs, adjustRow, adjustTop) {
4565			var tempContainer = $("<div/>");
4566			var elements;
4567			var segmentContainer = getDaySegmentContainer();
4568			var i;
4569			var segCnt = segs.length;
4570			var element;
4571			tempContainer[0].innerHTML = daySegHTML(segs); // faster than .html()
4572			elements = tempContainer.children();
4573			segmentContainer.append(elements);
4574			daySegElementResolve(segs, elements);
4575			daySegCalcHSides(segs);
4576			daySegSetWidths(segs);
4577			daySegCalcHeights(segs);
4578			daySegSetTops(segs, getRowTops(getRowDivs()));
4579			elements = [];
4580			for (i=0; i<segCnt; i++) {
4581				element = segs[i].element;
4582				if (element) {
4583					if (segs[i].row === adjustRow) {
4584						element.css('top', adjustTop);
4585					}
4586					elements.push(element[0]);
4587				}
4588			}
4589			return $(elements);
4590		}
4591		
4592		
4593		function daySegHTML(segs) { // also sets seg.left and seg.outerWidth
4594			var rtl = opt('isRTL');
4595			var i;
4596			var segCnt=segs.length;
4597			var seg;
4598			var event;
4599			var url;
4600			var classes;
4601			var bounds = allDayBounds();
4602			var minLeft = bounds.left;
4603			var maxLeft = bounds.right;
4604			var leftCol;
4605			var rightCol;
4606			var left;
4607			var right;
4608			var skinCss;
4609			var html = '';
4610			// calculate desired position/dimensions, create html
4611			for (i=0; i<segCnt; i++) {
4612				seg = segs[i];
4613				event = seg.event;
4614				classes = ['fc-event', 'fc-event-skin', 'fc-event-hori'];
4615				if (isEventDraggable(event)) {
4616					classes.push('fc-event-draggable');
4617				}
4618				if (rtl) {
4619					if (seg.isStart) {
4620						classes.push('fc-corner-right');
4621					}
4622					if (seg.isEnd) {
4623						classes.push('fc-corner-left');
4624					}
4625					leftCol = dayOfWeekCol(seg.end.getDay()-1);
4626					rightCol = dayOfWeekCol(seg.start.getDay());
4627					left = seg.isEnd ? colContentLeft(leftCol) : minLeft;
4628					right = seg.isStart ? colContentRight(rightCol) : maxLeft;
4629				}else{
4630					if (seg.isStart) {
4631						classes.push('fc-corner-left');
4632					}
4633					if (seg.isEnd) {
4634						classes.push('fc-corner-right');
4635					}
4636					leftCol = dayOfWeekCol(seg.start.getDay());
4637					rightCol = dayOfWeekCol(seg.end.getDay()-1);
4638					left = seg.isStart ? colContentLeft(leftCol) : minLeft;
4639					right = seg.isEnd ? colContentRight(rightCol) : maxLeft;
4640				}
4641				classes = classes.concat(event.className);
4642				if (event.source) {
4643					classes = classes.concat(event.source.className || []);
4644				}
4645				url = event.url;
4646				skinCss = getSkinCss(event, opt);
4647				if (url) {
4648					html += "<a href='" + htmlEscape(url) + "'";
4649				}else{
4650					html += "<div";
4651				}
4652				html +=
4653					" class='" + classes.join(' ') + "'" +
4654					" style='position:absolute;z-index:8;left:"+left+"px;" + skinCss + "'" +
4655					">" +
4656					"<div" +
4657					" class='fc-event-inner fc-event-skin'" +
4658					(skinCss ? " style='" + skinCss + "'" : '') +
4659					">";
4660				if (!event.allDay && seg.isStart) {
4661					html +=
4662						"<span class='fc-event-time'>" +
4663						htmlEscape(formatDates(event.start, event.end, opt('timeFormat'))) +
4664						"</span>";
4665				}
4666				html +=
4667					"<span class='fc-event-title'>" + htmlEscape(event.title) + "</span>" +
4668					"</div>";
4669				if (seg.isEnd && isEventResizable(event)) {
4670					html +=
4671						"<div class='ui-resizable-handle ui-resizable-" + (rtl ? 'w' : 'e') + "'>" +
4672						"&nbsp;&nbsp;&nbsp;" + // makes hit area a lot better for IE6/7
4673						"</div>";
4674				}
4675				html +=
4676					"</" + (url ? "a" : "div" ) + ">";
4677				seg.left = left;
4678				seg.outerWidth = right - left;
4679				seg.startCol = leftCol;
4680				seg.endCol = rightCol + 1; // needs to be exclusive
4681			}
4682			return html;
4683		}
4684		
4685		
4686		function daySegElementResolve(segs, elements) { // sets seg.element
4687			var i;
4688			var segCnt = segs.length;
4689			var seg;
4690			var event;
4691			var element;
4692			var triggerRes;
4693			for (i=0; i<segCnt; i++) {
4694				seg = segs[i];
4695				event = seg.event;
4696				element = $(elements[i]); // faster than .eq()
4697				triggerRes = trigger('eventRender', event, event, element);
4698				if (triggerRes === false) {
4699					element.remove();
4700				}else{
4701					if (triggerRes && triggerRes !== true) {
4702						triggerRes = $(triggerRes)
4703							.css({
4704								position: 'absolute',
4705								left: seg.left
4706							});
4707						element.replaceWith(triggerRes);
4708						element = triggerRes;
4709					}
4710					seg.element = element;
4711				}
4712			}
4713		}
4714		
4715		
4716		function daySegElementReport(segs) {
4717			var i;
4718			var segCnt = segs.length;
4719			var seg;
4720			var element;
4721			for (i=0; i<segCnt; i++) {
4722				seg = segs[i];
4723				element = seg.element;
4724				if (element) {
4725					reportEventElement(seg.event, element);
4726				}
4727			}
4728		}
4729		
4730		
4731		function daySegHandlers(segs, segmentContainer, modifiedEventId) {
4732			var i;
4733			var segCnt = segs.length;
4734			var seg;
4735			var element;
4736			var event;
4737			// retrieve elements, run through eventRender callback, bind handlers
4738			for (i=0; i<segCnt; i++) {
4739				seg = segs[i];
4740				element = seg.element;
4741				if (element) {
4742					event = seg.event;
4743					if (event._id === modifiedEventId) {
4744						bindDaySeg(event, element, seg);
4745					}else{
4746						element[0]._fci = i; // for lazySegBind
4747					}
4748				}
4749			}
4750			lazySegBind(segmentContainer, segs, bindDaySeg);
4751		}
4752		
4753		
4754		function daySegCalcHSides(segs) { // also sets seg.key
4755			var i;
4756			var segCnt = segs.length;
4757			var seg;
4758			var element;
4759			var key, val;
4760			var hsideCache = {};
4761			// record event horizontal sides
4762			for (i=0; i<segCnt; i++) {
4763				seg = segs[i];
4764				element = seg.element;
4765				if (element) {
4766					key = seg.key = cssKey(element[0]);
4767					val = hsideCache[key];
4768					if (val === undefined) {
4769						val = hsideCache[key] = hsides(element, true);
4770					}
4771					seg.hsides = val;
4772				}
4773			}
4774		}
4775		
4776		
4777		function daySegSetWidths(segs) {
4778			var i;
4779			var segCnt = segs.length;
4780			var seg;
4781			var element;
4782			for (i=0; i<segCnt; i++) {
4783				seg = segs[i];
4784				element = seg.element;
4785				if (element) {
4786					element[0].style.width = Math.max(0, seg.outerWidth - seg.hsides) + 'px';
4787				}
4788			}
4789		}
4790		
4791		
4792		function daySegCalcHeights(segs) {
4793			var i;
4794			var segCnt = segs.length;
4795			var seg;
4796			var element;
4797			var key, val;
4798			var vmarginCache = {};
4799			// record event heights
4800			for (i=0; i<segCnt; i++) {
4801				seg = segs[i];
4802				element = seg.element;
4803				if (element) {
4804					key = seg.key; // created in daySegCalcHSides
4805					val = vmarginCache[key];
4806					if (val === undefined) {
4807						val = vmarginCache[key] = vmargins(element);
4808					}
4809					seg.outerHeight = element[0].offsetHeight + val;
4810				}
4811			}
4812		}
4813		
4814		
4815		function getRowDivs() {
4816			var i;
4817			var rowCnt = getRowCnt();
4818			var rowDivs = [];
4819			for (i=0; i<rowCnt; i++) {
4820				rowDivs[i] = allDayRow(i)
4821					.find('td:first div.fc-day-content > div'); // optimal selector?
4822			}
4823			return rowDivs;
4824		}
4825		
4826		
4827		function getRowTops(rowDivs) {
4828			var i;
4829			var rowCnt = rowDivs.length;
4830			var tops = [];
4831			for (i=0; i<rowCnt; i++) {
4832				tops[i] = rowDivs[i][0].offsetTop; // !!?? but this means the element needs position:relative if in a table cell!!!!
4833			}
4834			return tops;
4835		}
4836		
4837		
4838		function daySegSetTops(segs, rowTops) { // also triggers eventAfterRender
4839			var i;
4840			var segCnt = segs.length;
4841			var seg;
4842			var element;
4843			var event;
4844			for (i=0; i<segCnt; i++) {
4845				seg = segs[i];
4846				element = seg.element;
4847				if (element) {
4848					element[0].style.top = rowTops[seg.row] + (seg.top||0) + 'px';
4849					event = seg.event;
4850					trigger('eventAfterRender', event, event, element);
4851				}
4852			}
4853		}
4854		
4855		
4856		
4857		/* Resizing
4858		-----------------------------------------------------------------------------------*/
4859		
4860		
4861		function resizableDayEvent(event, element, seg) {
4862			var rtl = opt('isRTL');
4863			var direction = rtl ? 'w' : 'e';
4864			var handle = element.find('div.ui-resizable-' + direction);
4865			var isResizing = false;
4866			
4867			// TODO: look into using jquery-ui mouse widget for this stuff
4868			disableTextSelection(element); // prevent native <a> selection for IE
4869			element
4870				.mousedown(function(ev) { // prevent native <a> selection for others
4871					ev.preventDefault();
4872				})
4873				.click(function(ev) {
4874					if (isResizing) {
4875						ev.preventDefault(); // prevent link from being visited (only method that worked in IE6)
4876						ev.stopImmediatePropagation(); // prevent fullcalendar eventClick handler from being called
4877													   // (eventElementHandlers needs to be bound after resizableDayEvent)
4878					}
4879				});
4880			
4881			handle.mousedown(function(ev) {
4882				if (ev.which != 1) {
4883					return; // needs to be left mouse button
4884				}
4885				isResizing = true;
4886				var hoverListener = t.getHoverListener();
4887				var rowCnt = getRowCnt();
4888				var colCnt = getColCnt();
4889				var dis = rtl ? -1 : 1;
4890				var dit = rtl ? colCnt-1 : 0;
4891				var elementTop = element.css('top');
4892				var dayDelta;
4893				var helpers;
4894				var eventCopy = $.extend({}, event);
4895				var minCell = dateCell(event.start);
4896				clearSelection();
4897				$('body')
4898					.css('cursor', direction + '-resize')
4899					.one('mouseup', mouseup);
4900				trigger('eventResizeStart', this, event, ev);
4901				hoverListener.start(function(cell, origCell) {
4902					if (cell) {
4903						var r = Math.max(minCell.row, cell.row);
4904						var c = cell.col;
4905						if (rowCnt == 1) {
4906							r = 0; // hack for all-day area in agenda views
4907						}
4908						if (r == minCell.row) {
4909							if (rtl) {
4910								c = Math.min(minCell.col, c);
4911							}else{
4912								c = Math.max(minCell.col, c);
4913							}
4914						}
4915						dayDelta = (r*7 + c*dis+dit) - (origCell.row*7 + origCell.col*dis+dit);
4916						var newEnd = addDays(eventEnd(event), dayDelta, true);
4917						if (dayDelta) {
4918							eventCopy.end = newEnd;
4919							var oldHelpers = helpers;
4920							helpers = renderTempDaySegs(compileDaySegs([eventCopy]), seg.row, elementTop);
4921							helpers.find('*').css('cursor', direction + '-resize');
4922							if (oldHelpers) {
4923								oldHelpers.remove();
4924							}
4925							hideEvents(event);
4926						}else{
4927							if (helpers) {
4928								showEvents(event);
4929								helpers.remove();
4930								helpers = null;
4931							}
4932						}
4933						clearOverlays();
4934						renderDayOverlay(event.start, addDays(cloneDate(newEnd), 1)); // coordinate grid already rebuild at hoverListener.start
4935					}
4936				}, ev);
4937				
4938				function mouseup(ev) {
4939					trigger('eventResizeStop', this, event, ev);
4940					$('body').css('cursor', '');
4941					hoverListener.stop();
4942					clearOverlays();
4943					if (dayDelta) {
4944						eventResize(this, event, dayDelta, 0, ev);
4945						// event redraw will clear helpers
4946					}
4947					// otherwise, the drag handler already restored the old events
4948					
4949					setTimeout(function() { // make this happen after the element's click event
4950						isResizing = false;
4951					},0);
4952				}
4953				
4954			});
4955		}
4956		
4957	
4958	}
4959	
4960	//BUG: unselect needs to be triggered when events are dragged+dropped
4961	
4962	function SelectionManager() {
4963		var t = this;
4964		
4965		
4966		// exports
4967		t.select = select;
4968		t.unselect = unselect;
4969		t.reportSelection = reportSelection;
4970		t.daySelectionMousedown = daySelectionMousedown;
4971		
4972		
4973		// imports
4974		var opt = t.opt;
4975		var trigger = t.trigger;
4976		var defaultSelectionEnd = t.defaultSelectionEnd;
4977		var renderSelection = t.renderSelection;
4978		var clearSelection = t.clearSelection;
4979		
4980		
4981		// locals
4982		var selected = false;
4983	
4984	
4985	
4986		// unselectAuto
4987		if (opt('selectable') && opt('unselectAuto')) {
4988			$(document).mousedown(function(ev) {
4989				var ignore = opt('unselectCancel');
4990				if (ignore) {
4991					if ($(ev.target).parents(ignore).length) { // could be optimized to stop after first match
4992						return;
4993					}
4994				}
4995				unselect(ev);
4996			});
4997		}
4998		
4999	
5000		function select(startDate, endDate, allDay) {
5001			unselect();
5002			if (!endDate) {
5003				endDate = defaultSelectionEnd(startDate, allDay);
5004			}
5005			renderSelection(startDate, endDate, allDay);
5006			reportSelection(startDate, endDate, allDay);
5007		}
5008		
5009		
5010		function unselect(ev) {
5011			if (selected) {
5012				selected = false;
5013				clearSelection();
5014				trigger('unselect', null, ev);
5015			}
5016		}
5017		
5018		
5019		function reportSelection(startDate, endDate, allDay, ev) {
5020			selected = true;
5021			trigger('select', null, startDate, endDate, allDay, ev);
5022		}
5023		
5024		
5025		function daySelectionMousedown(ev) { // not really a generic manager method, oh well
5026			var cellDate = t.cellDate;
5027			var cellIsAllDay = t.cellIsAllDay;
5028			var hoverListener = t.getHoverListener();
5029			var reportDayClick = t.reportDayClick; // this is hacky and sort of weird
5030			if (ev.which == 1 && opt('selectable')) { // which==1 means left mouse button
5031				unselect(ev);
5032				var _mousedownElement = this;
5033				var dates;
5034				hoverListener.start(function(cell, origCell) { // TODO: maybe put cellDate/cellIsAllDay info in cell
5035					clearSelection();
5036					if (cell && cellIsAllDay(cell)) {
5037						dates = [ cellDate(origCell), cellDate(cell) ].sort(cmp);
5038						renderSelection(dates[0], dates[1], true);
5039					}else{
5040						dates = null;
5041					}
5042				}, ev);
5043				$(document).one('mouseup', function(ev) {
5044					hoverListener.stop();
5045					if (dates) {
5046						if (+dates[0] == +dates[1]) {
5047							reportDayClick(dates[0], true, ev);
5048						}
5049						reportSelection(dates[0], dates[1], true, ev);
5050					}
5051				});
5052			}
5053		}
5054	
5055	
5056	}
5057	 
5058	function OverlayManager() {
5059		var t = this;
5060		
5061		
5062		// exports
5063		t.renderOverlay = renderOverlay;
5064		t.clearOverlays = clearOverlays;
5065		
5066		
5067		// locals
5068		var usedOverlays = [];
5069		var unusedOverlays = [];
5070		
5071		
5072		function renderOverlay(rect, parent) {
5073			var e = unusedOverlays.shift();
5074			if (!e) {
5075				e = $("<div class='fc-cell-overlay' style='position:absolute;z-index:3'/>");
5076			}
5077			if (e[0].parentNode != parent[0]) {
5078				e.appendTo(parent);
5079			}
5080			usedOverlays.push(e.css(rect).show());
5081			return e;
5082		}
5083		
5084	
5085		function clearOverlays() {
5086			var e;
5087			while (e = usedOverlays.shift()) {
5088				unusedOverlays.push(e.hide().unbind());
5089			}
5090		}
5091	
5092	
5093	}
5094	
5095	function CoordinateGrid(buildFunc) {
5096	
5097		var t = this;
5098		var rows;
5099		var cols;
5100		
5101		
5102		t.build = function() {
5103			rows = [];
5104			cols = [];
5105			buildFunc(rows, cols);
5106		};
5107		
5108		
5109		t.cell = function(x, y) {
5110			var rowCnt = rows.length;
5111			var colCnt = cols.length;
5112			var i, r=-1, c=-1;
5113			for (i=0; i<rowCnt; i++) {
5114				if (y >= rows[i][0] && y < rows[i][1]) {
5115					r = i;
5116					break;
5117				}
5118			}
5119			for (i=0; i<colCnt; i++) {
5120				if (x >= cols[i][0] && x < cols[i][1]) {
5121					c = i;
5122					break;
5123				}
5124			}
5125			return (r>=0 && c>=0) ? { row:r, col:c } : null;
5126		};
5127		
5128		
5129		t.rect = function(row0, col0, row1, col1, originElement) { // row1,col1 is inclusive
5130			var origin = originElement.offset();
5131			return {
5132				top: rows[row0][0] - origin.top,
5133				left: cols[col0][0] - origin.left,
5134				width: cols[col1][1] - cols[col0][0],
5135				height: rows[row1][1] - rows[row0][0]
5136			};
5137		};
5138	
5139	}
5140	
5141	function HoverListener(coordinateGrid) {
5142	
5143	
5144		var t = this;
5145		var bindType;
5146		var change;
5147		var firstCell;
5148		var cell;
5149		
5150		
5151		t.start = function(_change, ev, _bindType) {
5152			change = _change;
5153			firstCell = cell = null;
5154			coordinateGrid.build();
5155			mouse(ev);
5156			bindType = _bindType || 'mousemove';
5157			$(document).bind(bindType, mouse);
5158		};
5159		
5160		
5161		function mouse(ev) {
5162			var newCell = coordinateGrid.cell(ev.pageX, ev.pageY);
5163			if (!newCell != !cell || newCell && (newCell.row != cell.row || newCell.col != cell.col)) {
5164				if (newCell) {
5165					if (!firstCell) {
5166						firstCell = newCell;
5167					}
5168					change(newCell, firstCell, newCell.row-firstCell.row, newCell.col-firstCell.col);
5169				}else{
5170					change(newCell, firstCell);
5171				}
5172				cell = newCell;
5173			}
5174		}
5175		
5176		
5177		t.stop = function() {
5178			$(document).unbind(bindType, mouse);
5179			return cell;
5180		};
5181		
5182		
5183	}
5184	
5185	function HorizontalPositionCache(getElement) {
5186	
5187		var t = this,
5188			elements = {},
5189			lefts = {},
5190			rights = {};
5191			
5192		function e(i) {
5193			return elements[i] = elements[i] || getElement(i);
5194		}
5195		
5196		t.left = function(i) {
5197			return lefts[i] = lefts[i] === undefined ? e(i).position().left : lefts[i];
5198		};
5199		
5200		t.right = function(i) {
5201			return rights[i] = rights[i] === undefined ? t.left(i) + e(i).width() : rights[i];
5202		};
5203		
5204		t.clear = function() {
5205			elements = {};
5206			lefts = {};
5207			rights = {};
5208		};
5209		
5210	}
5211	
5212	})(jQuery);
5213	
5214	
5215/*
5216 * FullCalendar v1.5.1 Google Calendar Plugin
5217 *
5218 * Copyright (c) 2011 Adam Shaw
5219 * Dual licensed under the MIT and GPL licenses, located in
5220 * MIT-LICENSE.txt and GPL-LICENSE.txt respectively.
5221 *
5222 * Date: Sat Apr 9 14:09:51 2011 -0700
5223 *
5224 */
5225 
5226(function($) {
5227
5228
5229var fc = $.fullCalendar;
5230var formatDate = fc.formatDate;
5231var parseISO8601 = fc.parseISO8601;
5232var addDays = fc.addDays;
5233var applyAll = fc.applyAll;
5234
5235
5236fc.sourceNormalizers.push(function(sourceOptions) {
5237	if (sourceOptions.dataType == 'gcal' ||
5238		sourceOptions.dataType === undefined &&
5239		(sourceOptions.url || '').match(/^(http|https):\/\/www.google.com\/calendar\/feeds\//)) {
5240			sourceOptions.dataType = 'gcal';
5241			if (sourceOptions.editable === undefined) {
5242				sourceOptions.editable = false;
5243			}
5244		}
5245});
5246
5247
5248fc.sourceFetchers.push(function(sourceOptions, start, end) {
5249	if (sourceOptions.dataType == 'gcal') {
5250		return transformOptions(sourceOptions, start, end);
5251	}
5252});
5253
5254
5255function transformOptions(sourceOptions, start, end) {
5256
5257	var success = sourceOptions.success;
5258	var data = $.extend({}, sourceOptions.data || {}, {
5259		'start-min': formatDate(start, 'u'),
5260		'start-max': formatDate(end, 'u'),
5261		'singleevents': true,
5262		'max-results': 9999
5263	});
5264	
5265	var ctz = sourceOptions.currentTimezone;
5266	if (ctz) {
5267		data.ctz = ctz = ctz.replace(' ', '_');
5268	}
5269
5270	return $.extend({}, sourceOptions, {
5271		url: sourceOptions.url.replace(/\/basic$/, '/full') + '?alt=json-in-script&callback=?',
5272		dataType: 'jsonp',
5273		data: data,
5274		startParam: false,
5275		endParam: false,
5276		success: function(data) {
5277			var events = [];
5278			if (data.feed.entry) {
5279				$.each(data.feed.entry, function(i, entry) {
5280					var startStr = entry['gd$when'][0]['startTime'];
5281					var start = parseISO8601(startStr, true);
5282					var end = parseISO8601(entry['gd$when'][0]['endTime'], true);
5283					var allDay = startStr.indexOf('T') == -1;
5284					var url;
5285					$.each(entry.link, function(i, link) {
5286						if (link.type == 'text/html') {
5287							url = link.href;
5288							if (ctz) {
5289								url += (url.indexOf('?') == -1 ? '?' : '&') + 'ctz=' + ctz;
5290							}
5291						}
5292					});
5293					if (allDay) {
5294						addDays(end, -1); // make inclusive
5295					}
5296					events.push({
5297						id: entry['gCal$uid']['value'],
5298						title: entry['title']['$t'],
5299						url: url,
5300						start: start,
5301						end: end,
5302						allDay: allDay,
5303						location: entry['gd$where'][0]['valueString'],
5304						description: entry['content']['$t']
5305					});
5306				});
5307			}
5308			var args = [events].concat(Array.prototype.slice.call(arguments, 1));
5309			var res = applyAll(success, this, args);
5310			if ($.isArray(res)) {
5311				return res;
5312			}
5313			return events;
5314		}
5315	});
5316	
5317}
5318
5319
5320// legacy
5321fc.gcalFeed = function(url, sourceOptions) {
5322	return $.extend({}, sourceOptions, { url: url, dataType: 'gcal' });
5323};
5324
5325
5326})(jQuery);
5327	
5328	
5329	
5330});

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.