Explorar o código

getting ready for next version

Adam Shaw %!s(int64=16) %!d(string=hai) anos
pai
achega
c61ead1456
Modificáronse 34 ficheiros con 4490 adicións e 1473 borrados
  1. 0 184
      fullcalendar/fullcalendar.css
  2. 0 1259
      fullcalendar/fullcalendar.js
  3. 876 0
      src/agenda.js
  4. 166 0
      src/css/agenda.css
  5. 56 0
      src/css/grid.css
  6. 279 0
      src/css/main.css
  7. 18 26
      src/gcal.js
  8. 556 0
      src/grid.js
  9. 0 0
      src/jquery/jquery.js
  10. 2 2
      src/jquery/ui.core.js
  11. 2 2
      src/jquery/ui.draggable.js
  12. 800 0
      src/jquery/ui.resizable.js
  13. 577 0
      src/main.js
  14. 1 0
      src/misc/foot.txt
  15. 17 0
      src/misc/head.txt
  16. 332 0
      src/util.js
  17. 269 0
      src/view.js
  18. 133 0
      test/new.html
  19. BIN=BIN
      test/redmond/images/ui-bg_flat_0_aaaaaa_40x100.png
  20. BIN=BIN
      test/redmond/images/ui-bg_flat_55_fbec88_40x100.png
  21. BIN=BIN
      test/redmond/images/ui-bg_glass_75_d0e5f5_1x400.png
  22. BIN=BIN
      test/redmond/images/ui-bg_glass_85_dfeffc_1x400.png
  23. BIN=BIN
      test/redmond/images/ui-bg_glass_95_fef1ec_1x400.png
  24. BIN=BIN
      test/redmond/images/ui-bg_gloss-wave_55_5c9ccc_500x100.png
  25. BIN=BIN
      test/redmond/images/ui-bg_inset-hard_100_f5f8f9_1x100.png
  26. BIN=BIN
      test/redmond/images/ui-bg_inset-hard_100_fcfdfd_1x100.png
  27. BIN=BIN
      test/redmond/images/ui-icons_217bc0_256x240.png
  28. BIN=BIN
      test/redmond/images/ui-icons_2e83ff_256x240.png
  29. BIN=BIN
      test/redmond/images/ui-icons_469bdd_256x240.png
  30. BIN=BIN
      test/redmond/images/ui-icons_6da8d5_256x240.png
  31. BIN=BIN
      test/redmond/images/ui-icons_cd0a0a_256x240.png
  32. BIN=BIN
      test/redmond/images/ui-icons_d8e7f3_256x240.png
  33. BIN=BIN
      test/redmond/images/ui-icons_f9bd01_256x240.png
  34. 406 0
      test/redmond/theme.css

+ 0 - 184
fullcalendar/fullcalendar.css

@@ -1,184 +0,0 @@
-
-/* top area w/ month title and buttons */
-
-.full-calendar-title {
-	text-align: left;
-	}
-	
-.full-calendar-buttons {
-	float: right;
-	margin: 0 0 1em;
-	}
-	
-.full-calendar-buttons button {
-	vertical-align: middle;
-	margin: 0 0 0 5px;
-	font-size: 1em;
-	}
-	
-.full-calendar-buttons button span {
-	padding: 0 10px;
-	}
-	
-	/* To always display the "today" button:
-	 *
-	 * .full-calendar-buttons button.today {
-	 *    visibility: visible !important;
-	 *    }
-	 */
-	
-	
-	
-	
-/* table layout & outer border */
-
-.full-calendar-month-wrap {
-	clear: both;
-	border: 1px solid #ccc; /* outer border color & style */
-	}
-	
-.full-calendar-month {
-	width: 100%;
-	overflow: hidden;
-	}
-
-.full-calendar-month table {
-	border-collapse: collapse;
-	border-spacing: 0;
-	}
-	
-	
-	
-	
-/* cell styling */
-	
-.full-calendar-month th,
-.full-calendar-month td.day {
-	padding: 0;
-	vertical-align: top;
-	border-style: solid;    /* inner border style */
-	border-color: #ccc;     /* inner border color */
-	border-width: 1px 0 0 1px;
-	}
-	
-.full-calendar-month th {
-	border-top: 0;
-	text-align: center;
-	}
-	
-.full-calendar-month th.first,
-.full-calendar-month td.first {
-	border-left: 0;
-	}
-	
-.full-calendar-month td.today {
-	background: #FFFFCC;
-	}
-	
-.full-calendar-month .day-number {
-	text-align: right;
-	padding: 0 2px;
-	}
-	
-.full-calendar-month .other-month .day-number {
-	color: #bbb;
-	}
-	
-.full-calendar-month .day-content {
-	padding: 2px 2px 0; /* distance between events and day edges */
-	}
-	
-	/* FullCalendar automatically chooses a cell's height,
-	 * but this can be overridden:
-	 *
-	 * .full-calendar-month td.day {
-	 *    height: 100px !important;
-	 *    }
-	 */
-	 
-	
-
-
-/* event styling */
-	
-.full-calendar-month .event {
-	margin-bottom: 2px;
-	font-size: .85em;
-	cursor: pointer;
-	text-align: left;
-	}
-	
-.full-calendar-month .ui-draggable-dragging td {
-	cursor: move;
-	}
-	
-.full-calendar-month .event td {
-	background: #C1D9EC;
-	padding: 0;
-	}
-	
-.full-calendar-month .event td.ne,
-.full-calendar-month .event td.nw,
-.full-calendar-month .event td.se,
-.full-calendar-month .event td.sw {
-	background: none;
-	width: 1px;  /* <-- remove if you dont want "rounded" corners */
-	height: 1px; /* <--                                           */
-	}
-	
-.full-calendar-month .nobg td {
-	background: none;
-	}
-	
-.full-calendar-month .event td.c {
-	padding: 0 2px;
-	}
-	
-.full-calendar-month .event-time {
-	font-weight: bold;
-	}
-	
-	/* To change the color of events on a per-class basis (such as with the
-	 * "className" attribute of a CalEvent), do something like this:
-	 *
-	 * .full-calendar-month .myclass td {
-	 *    background: green;
-	 *    }
-	 */
-	
-	
-	
-	
-/* the rectangle that covers a day when dragging an event */
-	
-.full-calendar-month .over-day {
-	background: #ADDBFF;
-	opacity: .2;
-	filter: alpha(opacity=20); /* for IE */
-	}
-	
-	
-	
-	
-/* right-to-left support */
-
-.r2l .full-calendar-title {
-	text-align: right;
-	}
-	
-.r2l .full-calendar-buttons {
-	float: left;
-	}
-	
-.r2l .full-calendar-buttons button {
-	margin: 0 5px 0 0;
-	}
-	
-.r2l .full-calendar-month .day-number {
-	text-align: left;
-	}
-	
-.r2l .full-calendar-month .event {
-	text-align: right;
-	}
-	

+ 0 - 1259
fullcalendar/fullcalendar.js

@@ -1,1259 +0,0 @@
-/*!
- * FullCalendar
- * http://arshaw.com/fullcalendar/
- *
- * use fullcalendar.css for basic styling
- * requires jQuery UI core and draggables ONLY if you plan to do drag & drop
- *
- * Copyright (c) 2009 Adam Shaw
- * Dual licensed under the MIT and GPL licenses:
- *   http://www.opensource.org/licenses/mit-license.php
- *   http://www.gnu.org/licenses/gpl.html
- *
- * Date:
- * Revision:
- */
- 
-(function($) {
-
-	$.fn.fullCalendar = function(options) {
-	
-	
-		//
-		// Calls methods on a pre-existing instance
-		//
-		
-		if (typeof options == 'string') {
-			var args = Array.prototype.slice.call(arguments, 1);
-			var res;
-			this.each(function() {
-				var r = $.data(this, 'fullCalendar')[options].apply(this, args);
-				if (typeof res == 'undefined') res = r;
-			});
-			if (typeof res != 'undefined') {
-				return res;
-			}
-			return this;
-		}
-		
-		
-		
-		//
-		// Process options
-		//
-		
-		options = options || {};
-		
-		var r2l = options.rightToLeft;
-		var dis, dit; // day index sign / translate
-		if (r2l) {
-			dis = -1;
-			dit = 6;
-			this.addClass('r2l');
-		}else{
-			dis = 1;
-			dit = 0;
-		}
-		
-		var showTime = typeof options.showTime == 'undefined' ? 'guess' : options.showTime;
-		var bo = typeof options.buttons == 'undefined' ? true : options.buttons;
-		var weekStart = (options.weekStart || 0) % 7;
-		var timeFormat = options.timeFormat || 'gx';
-		var titleFormat = options.titleFormat || (r2l ? 'Y F' : 'F Y');
-		
-		
-		
-		//
-		// Rendering bug detection variables
-		//
-		
-		var tdTopBug, trTopBug, tbodyTopBug, sniffBugs = true;
-		
-		
-		
-		this.each(function() {
-		
-		
-			//
-			// Instance variables
-			//
-			
-			var date = options.year ? // holds the year & month of current month
-				new Date(options.year, options.month || 0, 1) :
-				new Date();
-			var start, end; // first & last VISIBLE dates
-			var today;
-			var numWeeks;
-			var ignoreResizes = false;
-			
-			var events = [];
-			var eventSources = options.eventSources || [];
-			if (options.events) eventSources.push(options.events);
-			
-			
-			
-			//
-			// Month navigation functions
-			//
-		
-			function refreshMonth() {
-				clearEventElements();
-				render();
-			}
-		
-			function prevMonth() {
-				addMonths(date, -1);
-				refreshMonth();
-			}
-		
-			function nextMonth() {
-				addMonths(date, 1);
-				refreshMonth();
-			}
-			
-			function gotoToday() {
-				date = new Date();
-				refreshMonth();
-			}
-		
-			function gotoMonth(year, month) {
-				date = new Date(year, month, 1);
-				refreshMonth();
-			}
-			
-			function prevYear() {
-				addYears(date, -1);
-				refreshMonth();
-			}
-			
-			function nextYear() {
-				addYears(date, 1);
-				refreshMonth();
-			}
-			
-			
-			
-			//
-			// Publicly accessible methods
-			//
-			
-			$.data(this, 'fullCalendar', {
-				refresh: refreshMonth,
-				prevMonth: prevMonth,
-				nextMonth: nextMonth,
-				today: gotoToday,
-				gotoMonth: gotoMonth,
-				prevYear: prevYear,
-				nextYear: nextYear,
-				
-				
-				//
-				// Event CRUD
-				//
-			
-				addEvent: function(event) {
-					events.push(normalizeEvent(event));
-					clearEventElements();
-					renderEvents();
-				},
-			
-				updateEvent: function(event) {
-					event.start = $.fullCalendar.parseDate(event.start);
-					event.end = $.fullCalendar.parseDate(event.end);
-					var startDelta = event.start - event._start;
-					var msLength = event.end - event.start;
-					event._start = cloneDate(event.start);
-					for (var i=0; i<events.length; i++) {
-						var e = events[i];
-						if (e.id === event.id && e !== event) {
-							e.start = new Date(e.start.getTime() + startDelta);
-							e._start = cloneDate(e.start);
-							e.end = new Date(e.start.getTime() + msLength);
-							for (var k in event) {
-								if (k && k != 'start' && k != 'end' && k.charAt(0) != '_') {
-									e[k] = event[k];
-								}
-							}
-						}
-					}
-					clearEventElements();
-					renderEvents();
-				},
-			
-				removeEvent: function(eventId) {
-					if (typeof eventId == 'object') {
-						eventId = eventId.id;
-					}
-					
-					// remove from the 'events' array
-					var newEvents = [];
-					for (var i=0; i<events.length; i++) {
-						if (events[i].id !== eventId) {
-							newEvents.push(events[i]);
-						}
-					}
-					events = newEvents;
-					
-					// remove from static event sources
-					for (var i=0; i<eventSources.length; i++) {
-						var src = eventSources[i];
-						if (typeof src != 'string' && !$.isFunction(src)) {
-							var newSrc = [];
-							for (var j=0; j<src.length; j++) {
-								if (src[j].id !== eventId) {
-									newSrc.push(src[j]);
-								}
-							}
-							eventSources[i] = newSrc;
-						}
-					}
-					
-					clearEventElements();
-					renderEvents();
-				},
-			
-				getEventsById: function(eventId) {
-					var res = [];
-					for (var i=0; i<events.length; i++) {
-						if (events[i].id === eventId) {
-							res.push(events[i]);
-						}
-					}
-					return res;
-				},
-				
-				
-			
-				//
-				// Event Source CRUD
-				//
-			
-				addEventSource: function(src) {
-					eventSources.push(src);
-					pushLoading();
-					fetchEventSource(src, function() {
-						popLoading();
-						clearEventElements();
-						renderEvents();
-					});
-				},
-			
-				removeEventSource: function(src) {
-			
-					// remove from 'eventSources' array
-					var newSources = [];
-					for (var i=0; i<eventSources.length; i++) {
-						if (src !== eventSources[i]) {
-							newSources.push(eventSources[i]);
-						}
-					}
-					eventSources = newSources;
-				
-					// remove events from 'events' array
-					var newEvents = [];
-					for (var i=0; i<events.length; i++) {
-						if (events[i].source !== src) {
-							newEvents.push(events[i]);
-						}
-					}
-					events = newEvents;
-				
-					clearEventElements();
-					renderEvents();
-				}
-				
-			});
-			
-			
-			
-			
-			
-			/*******************************************************************/
-			//
-			//		Header & Table Rendering
-			//
-			/*******************************************************************/
-			
-			
-			//
-			// Build one-time DOM elements (header, month container)
-			//
-		
-			var titleElement, todayButton, monthElement, monthElementWidth;
-			var header = $("<div class='full-calendar-header'/>").appendTo(this);
-			
-			if (bo) { // "button options"
-				var buttons = $("<div class='full-calendar-buttons'/>").appendTo(header);
-				if (bo == true || bo.today !== false) {
-					todayButton = $("<button class='today' />")
-						.append($("<span />").html(
-							typeof bo.today == 'string' ?
-								bo.today : "today"))
-						.click(gotoToday);
-					buttons.append(todayButton);
-				}
-				if (bo.prevYear) {
-					var b = $("<button class='prev-year' />")
-						.append($("<span />")
-							.html(typeof bo.prevYear == 'string' ?
-								bo.prevYear : "&laquo;"))
-						.click(prevYear);
-					if (r2l) buttons.prepend(b);
-					else buttons.append(b);
-				}
-				if (bo == true || bo.prevMonth !== false) {
-					var b = $("<button class='prev-month' />")
-						.append($("<span />")
-							.html(typeof bo.prevMonth == 'string' ?
-								bo.prevMonth : (r2l ? "&gt;" : "&lt;")))
-						.click(prevMonth);
-					if (r2l) buttons.prepend(b);
-					else buttons.append(b);
-				}
-				if (bo == true || bo.nextMonth !== false) {
-					var b = $("<button class='next-month' />")
-						.append($("<span />").html(typeof bo.nextMonth == 'string' ?
-							bo.nextMonth : (r2l ? "&lt;" : "&gt;")))
-						.click(nextMonth);
-					if (r2l) buttons.prepend(b);
-					else buttons.append(b);
-				}
-				if (bo.nextYear) {
-					var b = $("<button class='next-year' />")
-						.append($("<span />").html(typeof bo.nextYear == 'string'
-							? bo.nextYear : "&raquo;"))
-						.click(nextYear);
-					if (r2l) buttons.prepend(b);
-					else buttons.append(b);
-				}
-			}
-			
-			if (options.title !== false) {
-				titleElement = $("<h2 class='full-calendar-title'/>").appendTo(header);
-			}
-		
-			monthElement = $("<div class='full-calendar-month' style='position:relative'/>")
-				.appendTo($("<div class='full-calendar-month-wrap'/>").appendTo(this));
-			
-			
-			//
-			// Build the TABLE cells for the current month. (calls event fetching & rendering code)
-			//
-			
-			var thead, tbody, glass;
-			
-			function render() {
-		
-				ignoreResizes = true;
-				date.setDate(1);
-				clearTime(date);
-				var year = date.getFullYear();
-				var month = date.getMonth();
-				var monthTitle = $.fullCalendar.formatDate(date, titleFormat);
-				if (titleElement) titleElement.text(monthTitle);
-			
-				clearTime(date);
-				start = cloneDate(date);
-				addDays(start, -((start.getDay() - weekStart + 7) % 7));
-				end = cloneDate(date);
-				addMonths(end, 1);
-				addDays(end, (7 - end.getDay() + weekStart) % 7);
-				numWeeks = Math.round((end.getTime() - start.getTime()) / 604800000);
-				if (options.fixedWeeks != false) {
-					addDays(end, (6 - numWeeks) * 7);
-					numWeeks = 6;
-				}
-			
-				today = clearTime(new Date());
-				if (todayButton) {
-					if (today.getFullYear() == year && today.getMonth() == month) {
-						todayButton.css('visibility', 'hidden');
-					}else{
-						todayButton.css('visibility', 'visible');
-					}
-				}
-				
-				var dayNames = $.fullCalendar.dayNames;
-				var dayAbbrevs = $.fullCalendar.dayAbbrevs;
-			
-				if (!tbody) {
-				
-					// first time, build all cells from scratch
-				
-					var table = $("<table style='width:100%'/>").appendTo(monthElement);
-				
-					thead = "<thead><tr>";
-					for (var i=0; i<7; i++) {
-						var j = (i * dis + dit + weekStart) % 7;
-						thead +=
-							"<th class='" + dayAbbrevs[j].toLowerCase() +
-							(i==0 ? ' first' : '') + "'>" +
-							(options.abbrevDayHeadings!=false ? dayAbbrevs[j] : dayNames[j]) +
-							"</th>";
-					}
-					thead = $(thead + "</tr></thead>").appendTo(table);
-					
-					tbody = "<tbody>";
-					var d = cloneDate(start);
-					for (var i=0; i<numWeeks; i++) {
-						tbody += "<tr class='week"+(i+1)+"'>";
-						var tds = "";
-						for (var j=0; j<7; j++) {
-							var s =
-								"<td class='day " + dayAbbrevs[(j + weekStart) % 7].toLowerCase() +
-								(j==dit ? ' first' : '') +
-								(d.getMonth() == month ? '' : ' other-month') +
-								(d.getTime() == today.getTime() ? ' today' : '') +
-								"'><div class='day-number'>" + d.getDate() + "</div>" +
-								"<div class='day-content'><div/></div></td>";
-							if (r2l) tds = s + tds;
-							else tds += s;
-							addDays(d, 1);
-						}
-						tbody += tds + "</tr>";
-					}
-					tbody = $(tbody + "</tbody>").appendTo(table);
-						
-					// a protective coating over the TABLE
-					// intercepts mouse clicks and prevents text-selection
-					glass = $("<div style='position:absolute;top:0;left:0;z-index:1;width:100%' />")
-						.appendTo(monthElement)
-						.click(function(ev, ui) {
-							if (options.dayClick) {
-								buildDayGrid();
-								var td = dayTD(ev.pageX, ev.pageY);
-								if (td) return options.dayClick.call(td, dayDate(td));
-							}
-						});
-				
-				}else{
-				
-					// NOT first time, reuse as many cells as possible
-			
-					var diff = numWeeks - tbody.find('tr').length;
-					if (diff < 0) {
-						// remove extra rows
-						tbody.find('tr:gt(' + (numWeeks-1) + ')').remove();
-					}
-					else if (diff > 0) {
-						var trs = "";
-						for (var i=0; i<diff; i++) {
-							trs += "<tr class='week"+(numWeeks+i)+"'>";
-							for (var j=0; j<7; j++) {
-								trs +=
-									"<td class='day " +
-									dayAbbrevs[(j * dis + dit + weekStart) % 7].toLowerCase() +
-									(j==0 ? ' first' : '') + "'>" +
-									"<div class='day-number'></div>" +
-									"<div class='day-content'><div/></div>" +
-									"</td>";
-							}
-							trs += "</tr>";
-						}
-						if (trs) tbody.append(trs);
-					}
-				
-					// re-label and re-class existing cells
-					var d = cloneDate(start);
-					tbody.find('tr').each(function() {
-						for (var i=0; i<7; i++) {
-							var td = this.childNodes[i * dis + dit];
-							if (d.getMonth() == month) {
-								$(td).removeClass('other-month');
-							}else{
-								$(td).addClass('other-month');
-							}
-							if (d.getTime() == today.getTime()) {
-								$(td).addClass('today');
-							}else{
-								$(td).removeClass('today');
-							}
-							$(td.childNodes[0]).text(d.getDate());
-							addDays(d, 1);
-						}
-					});
-			
-				}
-			
-				setCellSizes();
-				
-				if (sniffBugs) {
-					// nasty bugs in opera 9.25
-					// position() returning relative to direct parent
-					var tr = tbody.find('tr');
-					var td = tr.find('td');
-					var trTop = tr.position().top;
-					var tdTop = td.position().top;
-					tdTopBug = tdTop < 0;
-					trTopBug = trTop != tdTop;
-					tbodyTopBug = tbody.position().top != trTop;
-					sniffBugs = false;
-				}
-				
-				fetchEvents(renderEvents);
-				
-				ignoreResizes = false;
-			
-				if (options.monthDisplay) {
-					options.monthDisplay(date.getFullYear(), date.getMonth(), monthTitle);
-				}
-			
-			}
-			
-			
-			//
-			// Adjust dimensions of the cells, based on container's width
-			//
-			
-			function setCellSizes() {
-				var tbodyw = tbody.width();
-				var cellw = Math.floor(tbodyw / 7);
-				var cellh = Math.round(cellw * .85);
-				thead.find('th')
-					.filter(':lt(6)').width(cellw).end()
-					.filter(':eq(6)').width(tbodyw - cellw*6);
-				tbody.find('td').height(cellh);
-				glass.height(monthElement.height());
-				monthElementWidth = monthElement.width();
-			}
-			
-			
-			
-			
-			
-			
-			/*******************************************************************/
-			//
-			//		Event Rendering
-			//
-			/*******************************************************************/
-			
-			
-			//
-			// Render the 'events' array. First, break up into segments
-			//
-			
-			var eventMatrix = [];
-		
-			function renderEvents() {
-				eventMatrix = [];
-				var i = 0;
-				var ws = cloneDate(start);
-				var we = addDays(cloneDate(ws), 7);
-				while (ws.getTime() < end.getTime()) {
-					var segs = [];
-					$.each(events, function(j, event) {
-						if (event.end.getTime() > ws.getTime() && event.start.getTime() < we.getTime()) {
-							var ss, se, isStart, isEnd;
-							if (event.start.getTime() < ws.getTime()) {
-								ss = cloneDate(ws);
-								isStart = false;
-							}else{
-								ss = cloneDate(event.start);
-								isStart = true;
-							}
-							if (event.end.getTime() > we.getTime()) {
-								se = cloneDate(we);
-								isEnd = false;
-							}else{
-								se = cloneDate(event.end);
-								isEnd = true;
-							}
-							ss = clearTime(ss);
-							se = clearTime((se.getHours()==0 && se.getMinutes()==0) ? se : addDays(se, 1));
-							segs.push({
-								event: event, start: ss, end: se,
-								isStart: isStart, isEnd: isEnd, msLength: se - ss
-							});
-						}
-					});
-					segs.sort(segCmp);
-					var levels = [];
-					$.each(segs, function(j, seg) {
-						var l = 0; // level index
-						while (true) {
-							var collide = false;
-							if (levels[l]) {
-								for (var k=0; k<levels[l].length; k++) {
-									if (seg.end.getTime() > levels[l][k].start.getTime() &&
-										seg.start.getTime() < levels[l][k].end.getTime()) {
-											collide = true;
-											break;
-										}
-								}
-							}
-							if (collide) {
-								l++;
-								continue;
-							}else{
-								break;
-							}
-						}
-						if (levels[l]) levels[l].push(seg);
-						else levels[l] = [seg];
-					});
-					eventMatrix[i] = levels;
-					addDays(ws, 7);
-					addDays(we, 7);
-					i++;
-				}
-				_renderEvents();
-			}
-		
-		
-			//
-			// Do the REAL rendering of the segments
-			//
-		
-			var eventElements = []; // [[event, element], ...]
-		
-			function _renderEvents() {
-				for (var i=0; i<eventMatrix.length; i++) {
-					var levels = eventMatrix[i];
-					var tr = tbody.find('tr:eq('+i+')');
-					var td = tr.find('td:first');
-					var innerDiv = td.find('div.day-content div').css('position', 'relative');
-					var top = innerDiv.position().top;
-					if (tdTopBug) top -= td.position().top;
-					if (trTopBug) top += tr.position().top;
-					if (tbodyTopBug) top += tbody.position().top;
-					var height = 0;
-					for (var j=0; j<levels.length; j++) {
-						var segs = levels[j];
-						var maxh = 0;
-						for (var k=0; k<segs.length; k++) {
-							var seg = segs[k];
-							var event = seg.event;
-							var left1, left2, roundW, roundE;
-							if (r2l) {
-								left2 = seg.isStart ?
-									tr.find('td:eq('+((seg.start.getDay()-weekStart+7)%7*dis+dit)+') div.day-content div') :
-									tbody;
-								left1 = seg.isEnd ?
-									tr.find('td:eq('+((seg.end.getDay()+6-weekStart)%7*dis+dit)+') div.day-content div').position().left :
-									tbody.position().left;
-								roundW = seg.isEnd;
-								roundE = seg.isStart;
-							}else{
-								left1 = seg.isStart ?
-									tr.find('td:eq('+((seg.start.getDay()-weekStart+7)%7)+') div.day-content div').position().left :
-									tbody.position().left;
-								left2 = seg.isEnd ?
-									tr.find('td:eq('+((seg.end.getDay()+6-weekStart)%7)+') div.day-content div') :
-									tbody;
-								roundW = seg.isStart;
-								roundE = seg.isEnd;
-							}
-							left2 = left2.position().left + left2.width();
-							var cl = event.className;
-							if (typeof cl == 'string') {
-								cl = ' ' + cl;
-							}
-							else if (typeof cl == 'object') {
-								cl = ' ' + cl.join(' ');
-							}
-							var element = $("<table class='event" + (cl || '') + "' />")
-								.append("<tr>" +
-									(roundW ? "<td class='nw'/>" : '') +
-									"<td class='n'/>" +
-									(roundE ? "<td class='ne'/>" : '') + "</tr>")
-								.append("<tr>" +
-									(roundW ? "<td class='w'/>" : '') +
-									"<td class='c'/>" +
-									(roundE ? "<td class='e'/>" : '') + "</tr>")
-								.append("<tr>" +
-									(roundW ? "<td class='sw'/>" : '') +
-									"<td class='s'/>" +
-									(roundE ? "<td class='se'/>" : '') + "</tr>");
-							buildEventText(event, element.find('td.c'));
-							if (options.eventRender) {
-								var res = options.eventRender(event, element);
-								if (typeof res != 'undefined') {
-									if (res === false) continue;
-									if (res !== true) element = $(res);
-								}
-							}
-							element
-								.css({
-									position: 'absolute',
-									top: top,
-									left: left1,
-									width: left2 - left1,
-									'z-index': 3
-								})
-								.appendTo(monthElement);
-							initEventElement(event, element);
-							var h = element.outerHeight({margin:true});
-							if (h > maxh) maxh = h;
-						}
-						height += maxh;
-						top += maxh;
-					}
-					innerDiv.height(height);
-				}
-			}
-			
-			
-			//
-			// create the text-contents of an event segment
-			//
-			
-			function buildEventText(event, element) {
-				$("<span class='event-title' />")
-					.text(event.title)
-					.appendTo(element);
-				var st = typeof event.showTime == 'undefined' ? showTime : event.showTime;
-				if (st != false) {
-					if (st == true || st == 'guess' &&
-						(event.start.getHours() || event.start.getMinutes() ||
-						 event.end.getHours() || event.end.getMinutes())) {
-							var timeStr = $.fullCalendar.formatDate(event.start, timeFormat);
-							var timeElement = $("<span class='event-time' />");
-							if (r2l) element.append(timeElement.text(' ' + timeStr));
-							else element.prepend(timeElement.text(timeStr + ' '));
-						}
-				}
-			}
-		
-		
-			//
-			// Attach event handlers to an event segment
-			//
-		
-			function initEventElement(event, element) {
-				element.click(function(ev) {
-					if (!element.hasClass('ui-draggable-dragging')) {
-						if (options.eventClick) {
-							var res = options.eventClick.call(this, event, ev);
-							if (res === false) return false;
-						}
-						if (event.url) window.location.href = event.url;
-					}
-				});
-				if (options.eventMouseover)
-					element.mouseover(function(ev) {
-						options.eventMouseover.call(this, event, ev);
-					});
-				if (options.eventMouseout)
-					element.mouseout(function(ev) {
-						options.eventMouseout.call(this, event, ev);
-					});
-				if (typeof event.draggable != 'undefined') {
-					if (event.draggable)
-						draggableEvent(event, element);
-				}
-				else if (options.draggable) {
-					draggableEvent(event, element);
-				}
-				eventElements.push([event, element]);
-			}
-			
-			
-			//
-			// Remove all event segments from DOM
-			//
-			
-			function clearEventElements() {
-				for (var i=0; i<eventElements.length; i++)
-					eventElements[i][1].remove();
-				eventElements = [];
-			}
-		
-		
-		
-		
-		
-		
-		
-			/*******************************************************************/
-			//
-			//		Drag & Drop		(and cell-coordinate stuff)
-			//
-			/*******************************************************************/
-			
-		
-			//
-			// Attach jQuery UI draggable
-			//
-		
-			var dragStartTD, dragTD;
-			var dayOverlay;
-		
-			function draggableEvent(event, element) {
-				element.draggable({
-					zIndex: 4,
-					delay: 50,
-					opacity: options.eventDragOpacity,
-					revertDuration: options.eventRevertDuration,
-					start: function(ev, ui) {
-						// hide other elements with same event
-						for (var i=0; i<eventElements.length; i++) {
-							var x = eventElements[i];
-							var xevent = x[0];
-							if (x[1].get(0) != this && (xevent == event ||
-								typeof xevent.id != 'undefined' && xevent.id == event.id))
-									x[1].hide();
-						}
-						if (!dayOverlay)
-							dayOverlay =
-								$("<div class='over-day' style='position:absolute;z-index:2' />")
-									.appendTo(monthElement);
-						buildDayGrid();
-						dragTD = dragStartTD = null;
-						eventDrag(this, ev, ui);
-						if (options.eventDragStart)
-							options.eventDragStart.call(this, event, ev, ui);
-					},
-					drag: function(ev, ui) {
-						eventDrag(this, ev, ui);
-					},
-					stop: function(ev, ui) {
-						if (!dragTD || dragTD == dragStartTD) {
-							// show all events
-							for (var i=0; i<eventElements.length; i++)
-								eventElements[i][1].show();
-						}else{
-							var delta = dayDelta(dragStartTD, dragTD);
-							for (var i=0; i<events.length; i++) {
-								if (event == events[i] || typeof event.id != 'undefined' && event.id == events[i].id) {
-									addDays(events[i].start, delta, true);
-									addDays(events[i].end, delta, true);
-									events[i]._start = cloneDate(events[i].start);
-								}
-							}
-							if (options.eventDrop) {
-								options.eventDrop.call(this, event, delta, ev, ui);
-							}
-							clearEventElements();
-							renderEvents();
-						}
-						dayOverlay.hide();
-						if (options.eventDragStop) {
-							options.eventDragStop.call(this, event, ev, ui);
-						}
-					}
-				});
-			}
-			
-			
-			//
-			// Called DURING dragging, on every mouse move
-			//
-		
-			function eventDrag(node, ev, ui) {
-				var oldTD = dragTD;
-				dragTD = dayTD(ev.pageX, ev.pageY);
-				if (!dragStartTD) dragStartTD = dragTD;
-				if (dragTD != oldTD) {
-					if (dragTD) {
-						$(node).draggable('option', 'revert', dragTD==dragStartTD);
-						dayOverlay.css({
-							top: currTDY,
-							left: currTDX,
-							width: currTDW,
-							height: currTDH,
-							display: 'block'
-						});
-					}else{
-						$(node).draggable('option', 'revert', true);
-						dayOverlay.hide();
-					}
-				}
-			}
-		
-		
-			//
-			// Record positions & dimensions of each TD
-			//
-		
-			var dayX, dayY, dayX0, dayY0;
-			var currTD, currR, currC;
-			var currTDX, currTDY, currTDW, currTDH;
-		
-			function buildDayGrid() {
-				var tr, td, o=monthElement.offset();
-				dayX0 = o.left;
-				dayY0 = o.top;
-				dayY = [];
-				tbody.find('tr').each(function() {
-					tr = $(this);
-					dayY.push(tr.position().top +
-						(trTopBug ? tbody.position().top : 0));
-				});
-				dayY.push(dayY[dayY.length-1] + tr.height());
-				dayX = [];
-				tr.find('td').each(function() {
-					td = $(this);
-					dayX.push(td.position().left);
-				});
-				dayX.push(dayX[dayX.length-1] + td.width());
-				currTD = null;
-			}
-			
-			//
-			// Returns TD underneath coordinate (optimized)
-			//
-		
-			function dayTD(x, y) {
-				var r=-1, c=-1;
-				var rmax=dayY.length-1, cmax=dayX.length-1;
-				while (r < rmax && y > dayY0 + dayY[r+1]) r++;
-				while (c < cmax && x > dayX0 + dayX[c+1]) c++;
-				if (r < 0 || r >= rmax || c < 0 || c >= cmax)
-					return currTD = null;
-				else if (!currTD || r != currR || c != currC) {
-					currR = r;
-					currC = c;
-					currTD = tbody.find('tr:eq('+r+') td:eq('+c+')').get(0);
-					currTDX = dayX[c];
-					currTDY = dayY[r];
-					currTDW = dayX[c+1] - currTDX;
-					currTDH = dayY[r+1] - currTDY;
-					return currTD;
-				}
-				return currTD;
-			}
-			
-			//
-			// Get a TD's date
-			//
-		
-			function dayDate(td) {
-				var i, trs = tbody.get(0).getElementsByTagName('tr');
-				for (i=0; i<trs.length; i++) {
-					var tr = trs[i];
-					for (var j=0; j<7; j++) {
-						if (tr.childNodes[j] == td) {
-							var d = cloneDate(start);
-							return addDays(d, i*7 + j*dis + dit);
-						}
-					}
-				}
-			}
-			
-			//
-			// Return the # of days between 2 TD's
-			//
-		
-			function dayDelta(td1, td2) {
-				var i1, i2, trs = tbody.get(0).getElementsByTagName('tr');
-				for (var i=0; i<trs.length; i++) {
-					var tr = trs[i];
-					for (var j=0; j<7; j++) {
-						var td = tr.childNodes[j];
-						if (td == td1) i1 = i*7 + j*dis + dit;
-						if (td == td2) i2 = i*7 + j*dis + dit;
-					}
-				}
-				return i2 - i1;
-			}
-			
-			
-			
-			
-			
-			
-			
-			/*******************************************************************/
-			//
-			//		Event Sources & Fetching
-			//
-			/*******************************************************************/
-			
-			
-			//
-			// Fetch from ALL sources. Clear 'events' array and populate
-			//
-			
-			function fetchEvents(callback) {
-				events = [];
-				if (eventSources.length > 0) {
-					var queued = eventSources.length;
-					var sourceDone = function() {
-						if (--queued == 0) {
-							popLoading();
-							if (callback) {
-								callback(events);
-							}
-						}
-					};
-					pushLoading();
-					for (var i=0; i<eventSources.length; i++) {
-						fetchEventSource(eventSources[i], sourceDone);
-					}
-				}
-			}
-			
-			
-			//
-			// Fetch from a particular source. Append to the 'events' array
-			//
-			
-			function fetchEventSource(src, callback) {
-				var y = date.getFullYear();
-				var m = date.getMonth();
-				var reportEvents = function(a) {
-					if (date.getFullYear() == y && date.getMonth() == m) {
-						for (var i=0; i<a.length; i++) {
-							normalizeEvent(a[i]);
-							a[i].source = src;
-						}
-						events = events.concat(a);
-					}
-					if (callback) {
-						callback(a);
-					}
-				};
-				if (typeof src == 'string') {
-					var params = {};
-					params[options.startParam || 'start'] = Math.round(start.getTime() / 1000);
-					params[options.endParam || 'end'] = Math.round(end.getTime() / 1000);
-					params[options.cacheParam || '_'] = (new Date()).getTime();
-					$.getJSON(src, params, reportEvents);
-				}
-				else if ($.isFunction(src)) {
-					src(start, end, reportEvents);
-				}
-				else {
-					reportEvents(src);
-				}
-			}
-			
-			
-			//
-			// methods for reporting loading state
-			//
-			
-			var loadingLevel = 0;
-			
-			function pushLoading() {
-				if (!loadingLevel++ && options.loading) {
-					options.loading(true);
-				}
-			}
-			
-			function popLoading() {
-				if (!--loadingLevel && options.loading) {
-					options.loading(false);
-				}
-			}
-			
-			
-			
-			
-			
-			
-			/*******************************************************************/
-			//
-			//		Begin "Main" Execution
-			//
-			/*******************************************************************/
-		
-			var e = $(this);
-			var _e = this;
-			var resizeID = 0;
-			$(window).resize(function() {   // re-render table on window resize
-				if (!ignoreResizes) {
-					var rid = ++resizeID;   // add a delay
-					setTimeout(function() {
-						if (rid == resizeID) {
-							// if calendar is visible
-							if (e.css('display') != 'none') {
-								// if the month width changed
-								if (monthElement.width() != monthElementWidth) {
-									clearEventElements();
-									setCellSizes();
-									_renderEvents();
-									if (options.resize) options.resize.call(_e);
-								}
-							}
-						}
-					}, 200);
-				}
-			});
-		
-			// let's begin...
-			if (e.css('display') != 'none') {
-				render();
-			}
-			
-			
-			
-			
-		});
-		
-		return this;
-	};
-	
-	
-	
-	
-	/***************************************************************************/
-	//
-	//		Utilities
-	//
-	/***************************************************************************/
-	
-	
-	//
-	// event utils
-	//
-	
-	function normalizeEvent(event) {
-		if (event.date) {
-			event.start = event.date;
-			delete event.date;
-		}
-		event.start = $.fullCalendar.parseDate(event.start);
-		event._start = cloneDate(event.start);
-		event.end = $.fullCalendar.parseDate(event.end);
-		if (!event.end || event.end <= event.start) {
-			event.end = addDays(cloneDate(event.start), 1);
-		}
-		return event;
-	}
-	
-	function segCmp(a, b) {
-		return (b.msLength - a.msLength) * 100 + (a.event.start - b.event.start);
-	}
-	
-	
-	//
-	// string utils
-	//
-	
-	function zeroPad(n) {
-		return (n < 10 ? '0' : '') + n;
-	}
-	
-	
-	//
-	// date utils
-	//
-	
-	function addMonths(d, n, keepTime) {
-		d.setMonth(d.getMonth() + n);
-		if (keepTime) return d;
-		return clearTime(d);
-	}
-	
-	function addYears(d, n, keepTime) {
-		d.setFullYear(d.getFullYear() + n);
-		if (keepTime) return d;
-		return clearTime(d);
-	}
-	
-	function addDays(d, n, keepTime) {
-		d.setDate(d.getDate() + n);
-		if (keepTime) return d;
-		return clearTime(d);
-	}
-	
-	function clearTime(d) {
-		d.setHours(0); 
-		d.setMinutes(0);
-		d.setSeconds(0); 
-		d.setMilliseconds(0);
-		return d;
-	}
-	
-	function cloneDate(d) {
-		return new Date(+d);
-	}
-	
-	
-	//
-	// globally accessible date formatting & parsing
-	//
-	
-	$.fullCalendar = {
-	
-		monthNames: ['January','February','March','April','May','June','July','August','September','October','November','December'],
-		monthAbbrevs: ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'],
-		dayNames: ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'],
-		dayAbbrevs: ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'],
-	
-		formatDate: function(d, format) {
-			var f = $.fullCalendar.dateFormatters;
-			var s = '';
-			for (var i=0; i<format.length; i++) {
-				var c = format.charAt(i);
-				if (f[c]) {
-					s += f[c](d);
-				}else{
-					s += c;
-				}
-			}
-			return s;
-		},
-		
-		dateFormatters: {
-			'a': function(d) { return d.getHours() < 12 ? 'am' : 'pm' },
-			'A': function(d) { return d.getHours() < 12 ? 'AM' : 'PM' },
-			'x': function(d) { return d.getHours() < 12 ? 'a' : 'p' },
-			'X': function(d) { return d.getHours() < 12 ? 'A' : 'P' },
-			'g': function(d) { return d.getHours() % 12 || 12 },
-			'G': function(d) { return d.getHours() },
-			'h': function(d) { return zeroPad(d.getHours() %12 || 12) },
-			'H': function(d) { return zeroPad(d.getHours()) },
-			'i': function(d) { return zeroPad(d.getMinutes()) },
-			'F': function(d) { return $.fullCalendar.monthNames[d.getMonth()] },
-			'm': function(d) { return zeroPad(d.getMonth() + 1) },
-			'M': function(d) { return $.fullCalendar.monthAbbrevs[d.getMonth()] },
-			'n': function(d) { return d.getMonth() + 1 },
-			'Y': function(d) { return d.getFullYear() },
-			'y': function(d) { return (d.getFullYear()+'').substring(2) },
-			'c': function(d) {
-				// ISO8601. derived from http://delete.me.uk/2005/03/iso8601.html
-				return d.getUTCFullYear() +
-					"-" + zeroPad(d.getUTCMonth() + 1) +
-					"-" + zeroPad(d.getUTCDate()) +
-					"T" + zeroPad(d.getUTCHours()) +
-					":" + zeroPad(d.getUTCMinutes()) +
-					":" + zeroPad(d.getUTCSeconds()) +
-					"Z";
-			}
-		},
-		
-		parseDate: function(s) {
-			if (typeof s == 'object')
-				return s; // already a Date object
-			if (typeof s == 'undefined')
-				return null;
-			if (typeof s == 'number')
-				return new Date(s * 1000);
-			return $.fullCalendar.parseISO8601(s, true) ||
-			       Date.parse(s) ||
-			       new Date(parseInt(s) * 1000);
-		},
-		
-		parseISO8601: function(s, ignoreTimezone) {
-			// derived from http://delete.me.uk/2005/03/iso8601.html
-			var regexp = "([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})))?)?)?)?";
-			var d = s.match(new RegExp(regexp));
-			if (!d) return null;
-			var offset = 0;
-			var date = new Date(d[1], 0, 1);
-			if (d[3]) { date.setMonth(d[3] - 1); }
-			if (d[5]) { date.setDate(d[5]); }
-			if (d[7]) { date.setHours(d[7]); }
-			if (d[8]) { date.setMinutes(d[8]); }
-			if (d[10]) { date.setSeconds(d[10]); }
-			if (d[12]) { date.setMilliseconds(Number("0." + d[12]) * 1000); }
-			if (!ignoreTimezone) {
-				if (d[14]) {
-					offset = (Number(d[16]) * 60) + Number(d[17]);
-					offset *= ((d[15] == '-') ? 1 : -1);
-				}
-				offset -= date.getTimezoneOffset();
-			}
-			return new Date(Number(date) + (offset * 60 * 1000));
-		}
-	
-	};
-	
-	// additional FullCalendar "extensions" should be attached to $.fullCalendar
-
-})(jQuery);

+ 876 - 0
src/agenda.js

@@ -0,0 +1,876 @@
+
+/********************************* week view ***********************************/
+
+$.fullCalendar.views.week = function(element, options) {
+
+	var agenda = new Agenda(element, options);
+	
+	safeExtend(options, {
+		weekTitleFormat: 'M j Y{ - M j Y}' // TODO: shift around
+	});
+	
+	agenda.render = function(date, delta, fetchEvents) {
+	
+		if (delta) {
+			addDays(date, delta * 7);
+		}
+		
+		this.start = addDays(cloneDate(date), -date.getDay());
+		this.end = addDays(cloneDate(this.start), 7);
+		this.title = formatDates(this.start, this.end, options.weekTitleFormat);
+		
+		this.renderAgenda(fetchEvents);
+	
+	};
+	
+	return agenda;
+};
+
+/******************************* day view *************************************/
+
+$.fullCalendar.views.day = function(element, options) {
+
+	var agenda = new Agenda(element, options);
+	
+	safeExtend(options, {
+		dayTitleFormat: 'l F j Y' // TODO: shift around
+	});
+	
+	agenda.render = function(date, delta, fetchEvents) {
+	
+		if (delta) {
+			addDays(date, delta);
+		}
+		
+		this.start = cloneDate(date, true);
+		this.end = addDays(cloneDate(date), 1);
+		this.title = formatDate(date, options.dayTitleFormat);
+		
+		this.renderAgenda(fetchEvents);
+	
+	};
+	
+	return agenda;
+};
+
+/*********************** shared by month and day views *************************/
+
+function Agenda(element, options) {
+
+	safeExtend(options, {
+		slotMinutes: 30,
+		defaultEventMinutes: 120,
+		agendaEventTimeFormat: 'g:i{ - g:i}',
+		agendaSideTimeFormat: 'ga',
+		agendaEventDragOpacity: .5
+	});
+
+	var view = this,
+		head, body, panel, bg,
+		dayCnt,
+		dayWidth, slotHeight,
+		timeWidth,
+		cachedEvents,
+		cachedSlotSegs, cachedDaySegs,
+		eventElements = [],
+		eventElementsByID = {},
+		eventsByID = {};
+	
+	element.addClass('fc-agenda').css('position', 'relative');
+	
+	
+	
+	/******************************** cell rendering ********************************/
+	
+	
+	this.renderAgenda = function(fetchEvents) { // TODO: get z-indexes sorted out
+		
+		var start = view.start,
+			end = view.end,
+			today = getToday(),
+			todayI = -1,
+			tm = options.theme ? 'ui' : 'fc',
+			slotNormal = options.slotMinutes % 15 == 0,
+			dayAbbrevs = $.fullCalendar.dayAbbrevs;
+		
+		if (!head) { // first time rendering, build from scratch TODO: need all the nbsp's?
+			
+			// head
+			var i, d, dDay, dMinutes,
+				s = "<div class='fc-agenda-head' style='position:relative;z-index:3'>" +
+					"<table style='width:100%' cellpadding='0' cellspacing='0'>" +
+						"<tr class='fc-first'>" +
+							"<th class='fc-first " + tm + "-state-default'>&nbsp;</th>";
+			dayCnt = 0;
+			for (d=cloneDate(start); d<end; addDays(d, 1)) {
+				s += "<th class='fc-" +
+					dayIDs[d.getDay()] + ' ' + // needs to be first
+					tm + '-state-default' +
+					"'>" + dayAbbrevs[d.getDay()] + "</th>";
+				if (+d == +today) {
+					todayI = dayCnt;
+				}
+				dayCnt++;
+			}
+			s += "<th class='fc-last " + tm + "-state-default'>&nbsp;</th></tr>" +
+				"<tr class='fc-last'>" +
+					"<th class='fc-first " + tm + "-state-default' style='font-weight:normal;text-align:right;padding:4px 2px'>all day</th>" +
+					"<td colspan='" + dayCnt + "' class='" + tm + "-state-default'>" +
+						"<div class='fc-day-content'><div/></div></td>" +
+					"<th class='fc-last " + tm + "-state-default'>&nbsp;</th>" +
+				"</tr></table></div>";
+			head = $(s).appendTo(element);
+			
+			// body & event panel
+			s = "<div style='position:relative;overflow:hidden'>" +
+				"<table cellpadding='0' cellspacing='0'>";
+			d = getToday();
+			dDay = d.getDay();
+			for (i=0; d.getDay()==dDay; i++, addMinutes(d, options.slotMinutes)) {
+				dMinutes = d.getMinutes();
+				s += "<tr class='" +
+					(i==0 ? 'fc-first' : (dMinutes==0 ? '' : 'fc-minor')) +
+					"'><th class='" + tm + "-state-default'>" +
+						(!slotNormal || dMinutes==0 ? formatDate(d, options.agendaSideTimeFormat) : '&nbsp;') + 
+						"</th><td class='fc-slot " + tm + "-state-default'>&nbsp;</td></tr>";
+			}
+			s += "</table></div>";
+			body = $("<div class='fc-agenda-body' style='position:relative;z-index:2'/>")
+				.append(panel = $(s))
+				.appendTo(element);
+			
+			// background stripes
+			s = "<div class='fc-agenda-bg' style='position:absolute;top:0;z-index:1'>" +
+				"<table style='width:100%;height:100%' cellpadding='0' cellspacing='0'><tr>";
+			for (i=0; i<dayCnt; i++) {
+				s += "<td class='fc-" +
+					dayIDs[i] + ' ' + // needs to be first
+					tm + '-state-default ' + 
+					(i==todayI ? tm + '-state-highlight fc-today' : 'fc-not-today') +
+					"'><div class='fc-day-content'><div>&nbsp;</div></div></td>";
+			}
+			s += "</tr></table></div>";
+			bg = $(s).appendTo(element);
+			
+		}else{ // skeleton already built, just modify it
+		
+			clearEvents();
+			
+			// change classes of background stripes
+			todayI = Math.round((today - start) / msInDay);
+			bg.find('td').each(function(i) {
+				if (i == todayI) {
+					$(this).removeClass('fc-not-today')
+						.addClass('fc-today')
+						.addClass(tm + '-state-highlight');
+				}else{
+					$(this).addClass('fc-not-today')
+						.removeClass('fc-today')
+						.removeClass(tm + '-state-highlight');
+				}
+			});
+			
+			// if 1-day view, change day-of-week class and header text
+			if (dayCnt == 1) {
+				var th = head.find('th:eq(1)').html(dayAbbrevs[start.getDay()])[0],
+					td = bg.find('td')[0];
+				th.className = th.className.replace(/^fc-\w+(?= )/, 'fc-' + dayIDs[start.getDay()]);
+				td.className = td.className.replace(/^fc-\w+(?= )/, 'fc-' + dayIDs[start.getDay()]);
+			}
+		
+		}
+		
+		updateSize();
+		fetchEvents(renderEvents);
+		
+	};
+	
+	
+	function updateSize() {
+		
+		// align first 'time' column
+		timeWidth = body.find('th:first').outerWidth();
+		head.find('th:first').width(timeWidth);
+		
+		// set table width (100% in css wasn't working in IE)
+		var panelWidth = body[0].clientWidth || body.width(); // first time, there are no scrollbars!? for IE6?
+		body.find('table').width(panelWidth);
+		
+		// align spacer column to scrollbar width
+		setOuterWidth(head.find('th:last'), body.width() - panelWidth);
+		
+		// position background stripe container
+		bg.css({
+			left: timeWidth,
+			width: panelWidth - timeWidth,
+			height: element.height()
+		});
+		
+		// align other columns
+		dayWidth = Math.floor((panelWidth - timeWidth) / dayCnt);
+		var topCells = head.find('tr:first th:gt(0)'),
+			bgCells = bg.find('td');
+		for (var i=0, len=bgCells.length-1; i<len; i++) { // TODO: use slice
+			setOuterWidth(topCells.eq(i), dayWidth);
+			setOuterWidth(bgCells.eq(i), dayWidth);
+		}
+		
+		slotHeight = body.find('tr:eq(1)').height(); // use second, first prob doesn't have a border
+		
+		// body height
+		body.height(Math.round(body.width() / contentAspectRatio) - head.height());
+		// but this will add scrollbars...
+		// TODO: bug, iE6 view heights dont match up
+		// also, no scrollbars
+		
+	}
+	
+	
+	
+	/********************************** event rendering *********************************/
+	
+	
+	function renderEvents(events) {
+		
+		var i, len=events.length, event,
+			fakeID=0, nextDay,
+			slotEvents=[], dayEvents=[];
+			
+		for (i=0; i<len; i++) {
+			event = events[i];
+			event._id = typeof event.id == 'undefined' ? '_fc' + fakeID++ : event.id + '';
+			if (eventsByID[event._id]) {
+				eventsByID[event._id].push(event);
+			}else{
+				eventsByID[event._id] = [event];
+			}
+			if (event.hasTime) {
+				event._end = event.end || addMinutes(cloneDate(event.start), options.defaultEventMinutes);
+			}else{
+				event._end = addDays(cloneDate(event.end || event.start), 1);
+			}
+			if (event.start < view.end && event._end > view.start) {
+				if (event.hasTime) {
+					event._end = event.end || addMinutes(cloneDate(event.start), options.defaultEventMinutes);
+					slotEvents.push(event);
+				}else{
+					event._end = addDays(cloneDate(event.end || event.start), 1);
+					dayEvents.push(event);
+				}
+			}
+		}
+		
+		cachedEvents = events;
+		cachedSlotSegs = compileSlotSegs(slotEvents, view.start, view.end);
+		cachedDaySegs = levelizeSegs(sliceSegs(dayEvents, view.start, view.end));
+		
+		renderSlotSegs(cachedSlotSegs);
+		renderDaySegs(cachedDaySegs);
+		
+	}
+	
+	
+	function rerenderEvents(skipCompile) {
+		clearEvents();
+		if (skipCompile) {
+			renderSlotSegs(cachedSlotSegs);
+			renderDaySegs(cachedDaySegs);
+		}else{
+			renderEvents(cachedEvents);
+		}
+	}
+	
+	
+	function clearEvents() {
+		for (var i=0; i<eventElements.length; i++) {
+			eventElements[i].remove();
+		}
+		eventElements = [];
+		eventElementsByID = {};
+		eventsByID = {};
+	}
+	
+	
+	// renders events in the 'time slots' at the bottom
+	
+	function renderSlotSegs(segCols) {
+		var colI, colLen=segCols.length, col,
+			levelI, level,
+			segI, seg,
+			event, start, end,
+			top, bottom,
+			tdInner, left, width,
+			eventElement, anchorElement, timeElement, titleElement;
+		for (colI=0; colI<colLen; colI++) {
+			col = segCols[colI];
+			for (levelI=0; levelI<col.length; levelI++) {
+				level = col[levelI];
+				for (segI=0; segI<level.length; segI++) {
+					seg = level[segI];
+					event = seg.event;
+					top = timeCoord(seg.start, seg.start);
+					bottom = timeCoord(seg.start, seg.end);
+					tdInner = bg.find('td:eq('+colI+') div div');
+					availWidth = tdInner.width();
+					left = timeWidth + tdInner.position().left + // leftmost possible
+						(availWidth / (levelI + seg.right + 1) * levelI); // indentation
+					if (levelI == 0) {
+						if (seg.right == 0) {
+							// can be entire width, aligned left
+							width = availWidth * .96;
+						}else{
+							// moderately wide, aligned left still
+							width = ((availWidth / (seg.right + 1)) - (12/2)) * 2; // 12 is the predicted width of resizer =
+						}
+					}else{
+						// indented and thinner
+						width = availWidth / (levelI + seg.right + 1);
+					}
+					eventElement = $("<div class='fc-event fc-event-vert' />")
+						.append(anchorElement = $("<a><span class='fc-event-bg'/></a>")
+							.append(titleElement = $("<span class='fc-event-title'/>")
+								.text(event.title)))
+						.css({
+							position: 'absolute',
+							zIndex: 1000,
+							top: top,
+							left: left
+						});
+					if (event.url) {
+						anchorElement.attr('href', event.url);
+					}
+					if (seg.isStart) {
+						eventElement.addClass('fc-corner-top');
+						// add the time header
+						anchorElement
+							.prepend(timeElement = $("<span class='fc-event-time'/>")
+								.text(formatDates(event.start, event.end, options.agendaEventTimeFormat)))
+					}else{
+						timeElement = null;
+					}
+					if (seg.isEnd) {
+						eventElement.addClass('fc-corner-bottom');
+						resizableSlotEvent(event, eventElement, timeElement);
+					}
+					eventElement.appendTo(panel);
+					setOuterWidth(eventElement, width, true);
+					setOuterHeight(eventElement, bottom-top, true);
+					if (timeElement && eventElement.height() - titleElement.position().top < 10) {
+						// event title doesn't have enough room, but next to the time
+						timeElement.text(formatDate(event.start, options.agendaEventTimeFormat) + ' - ' + event.title);
+						titleElement.remove();
+					}
+					draggableSlotEvent(event, eventElement, timeElement);
+					reportEventElement(event, eventElement);
+				}
+			}
+		}
+	}
+	
+	
+	// renders 'all-day' events at the top
+	
+	function renderDaySegs(segRow) {
+		var td = head.find('td');
+		var tdInner = td.find('div div');
+		var top = tdInner.position().top,
+			rowHeight = 0,
+			i, len=segRow.length, level,
+			levelHeight,
+			j, seg,
+			event, left, right,
+			eventElement, anchorElement;
+		for (i=0; i<len; i++) {
+			level = segRow[i];
+			levelHeight = 0;
+			for (j=0; j<level.length; j++) {
+				seg = level[j];
+				event = seg.event;
+				left = seg.isStart ?
+					bg.find('td:eq('+((seg.start.getDay()+dayCnt)%dayCnt)+') div div') :
+					bg.find('td:eq('+((seg.start.getDay()+dayCnt)%dayCnt)+')');
+				left = left.position().left;
+				right = seg.isEnd ?
+					bg.find('td:eq('+((seg.end.getDay()-1+dayCnt)%dayCnt)+') div div') :
+					bg.find('td:eq('+((seg.end.getDay()-1+dayCnt)%dayCnt)+')');
+				right = right.position().left + right.outerWidth();
+				eventElement = $("<div class='fc-event fc-event-hori' />")
+					.append(anchorElement = $("<a/>")
+						.append($("<span class='fc-event-title' />")
+							.text(event.title)))
+					.css({
+						position: 'absolute',
+						top: top,
+						left: timeWidth + left
+					});
+				if (seg.isStart) {
+					eventElement.addClass('fc-corner-left');
+				}
+				if (seg.isEnd) {
+					eventElement.addClass('fc-corner-right');
+				}
+				if (event.url) {
+					anchorElement.attr('href', event.url);
+				}
+				eventElement.appendTo(head);
+				setOuterWidth(eventElement, right-left, true);
+				draggableDayEvent(event, eventElement);
+				//resizableDayEvent(event, eventElement);
+				reportEventElement(event, eventElement);
+				levelHeight = Math.max(levelHeight, eventElement.outerHeight(true));
+			}
+			top += levelHeight;
+			rowHeight += levelHeight;
+		}
+		tdInner.height(rowHeight);
+		//bg.height(element.height()); // tdInner might have pushed the body down, so resize
+		//updateSize();
+	}
+	
+	
+	
+	/******************************************* draggable *****************************************/
+	
+	
+	// when event starts out IN TIMESLOTS
+	
+	function draggableSlotEvent(event, eventElement, timeElement) {
+		var origPosition, origMarginTop,
+			prevSlotDelta, slotDelta,
+			matrix;
+		eventElement.draggable({
+			zIndex: 1001,
+			scroll: false,
+			grid: [dayWidth, slotHeight],
+			axis: dayCnt==1 ? 'y' : false,
+			cancel: '.ui-resizable-handle',
+			opacity: .5,
+			start: function(ev, ui) {
+				if ($.browser.msie) {
+					eventElement.find('span.fc-event-bg').hide();
+				}
+				origPosition = eventElement.position();
+				origMarginTop = parseInt(eventElement.css('margin-top')) || 0;
+				prevSlotDelta = 0;
+				matrix = new HoverMatrix(function(cell) {
+					if (event.hasTime) {
+						// event is an original slot-event
+						if (cell && cell.row == 0) {
+							// but needs to convert to temporary full-day-event
+							var topDiff = panel.offset().top - head.offset().top;
+							eventElement.css('margin-top', origMarginTop + topDiff)
+								.appendTo(head);
+							// TODO: bug in IE8 w/ above technique, draggable ends immediately
+							event.hasTime = false;
+							if (timeElement) {
+								timeElement.hide();
+							}
+							eventElement.draggable('option', 'grid', null);
+						}
+					}else{
+						// event is a temporary full-day-event
+						if (cell && cell.row == 1) {
+							// but needs to convert to original slot-event
+							eventElement.css('margin-top', origMarginTop)
+								.appendTo(panel);
+							event.hasTime = true;
+							if (timeElement) {
+								timeElement.css('display', ''); // show() was causing display=inline
+							}
+							eventElement.draggable('option', 'grid', [dayWidth, slotHeight]);
+						}
+					}
+					if (cell && cell.row == 0) {
+						showDayOverlay(cell);
+					}else{
+						hideDayOverlay();
+					}
+				});
+				matrix.row(head.find('td'));
+				bg.find('td').each(function() {
+					matrix.col(this);
+				});
+				matrix.row(body);
+				matrix.start();
+				hideSimilarEvents(event, eventElement);
+			},
+			drag: function(ev, ui) {
+				slotDelta = Math.round((ui.position.top - origPosition.top) / slotHeight);
+				if (slotDelta != prevSlotDelta) {
+					if (timeElement && event.hasTime) {
+						// update time header
+						var newStart = addMinutes(cloneDate(event.start), slotDelta * options.slotMinutes),
+							newEnd;
+						if (event.end) {
+							newEnd = addMinutes(cloneDate(event.end), slotDelta * options.slotMinutes);
+						}
+						timeElement.text(formatDates(newStart, newEnd, options.agendaEventTimeFormat));
+					}
+					prevSlotDelta = slotDelta;
+				}
+				matrix.mouse(ev.pageX, ev.pageY);
+			},
+			stop: function(ev, ui) {
+				if (event.hasTime) {
+					if (matrix.cell) {
+						// over slots
+						var dayDelta = Math.round((ui.position.left - origPosition.left) / dayWidth);
+						reportEventMove(event, dayDelta, true, slotDelta * options.slotMinutes);
+					}
+				}else{
+					// over full-days
+					if (!matrix.cell) {
+						// was being dragged over full-days, but finished over nothing, reset
+						event.hasTime = true;
+					}else{
+						event.end = null;
+						reportEventMove(event, matrix.cell.colDelta);
+					}
+				}
+				hideDayOverlay();
+				rerenderEvents();
+			}
+		});
+		
+	}
+	
+	
+	// when event starts out FULL-DAY
+	
+	function draggableDayEvent(event, eventElement) {
+		var origWidth, matrix;
+		eventElement.draggable({
+			zIndex: 1001,
+			start: function() {
+				origWidth = eventElement.width();
+				matrix = new HoverMatrix(function(cell) {
+					if (!cell) {
+						// mouse is outside of everything
+						hideDayOverlay();
+					}else{
+						if (cell.row == 0) {
+							// on full-days
+							if (event.hasTime) {
+								// and needs to be original full-day event
+								eventElement
+									.width(origWidth)
+									.height('')
+									.draggable('option', 'grid', null);
+								event.hasTime = false;
+							}
+							showDayOverlay(cell);
+						}else{
+							// mouse is over bottom slots
+							if (!event.hasTime) {
+								// convert event to temporary slot-event
+								//if (+cloneDate(event.start, true) == +cloneDate(event._end, true)) {
+									// only change styles if a 1-day event
+									eventElement
+										.width(dayWidth - 10) // don't use entire width
+										.height(slotHeight * Math.round(options.defaultEventMinutes/options.slotMinutes) - 2);
+								//}
+								eventElement.draggable('option', 'grid', [dayWidth, 1]);
+								event.hasTime = true;
+							}
+							hideDayOverlay();
+						}
+					}
+				});
+				matrix.row(head.find('td'));
+				bg.find('td').each(function() {
+					matrix.col(this);
+				});
+				matrix.row(body);
+				matrix.start();
+				hideSimilarEvents(event, eventElement);
+			},
+			drag: function(ev, ui) {
+				matrix.mouse(ev.pageX, ev.pageY);
+			},
+			stop: function() {
+				var cell = matrix.cell;
+				if (!cell) {
+					// over nothing
+					if (event.hasTime) {
+						// event was on the slots before going out, convert back
+						event.hasTime = false;
+					}
+				}else{
+					if (!event.hasTime) {
+						// event has been dropped on a full-day
+						reportEventMove(event, cell.colDelta);
+					}else{
+						// event has been dropped on the slots
+						var slots = Math.floor((eventElement.offset().top - panel.offset().top) / slotHeight);
+						event.end = null;
+						reportEventMove(event, cell.colDelta, false, slots * options.slotMinutes);
+					}
+				}
+				hideDayOverlay();
+				rerenderEvents();
+			}
+		});
+	}
+	
+	
+	// hover effect when dragging events over top days
+	
+	var dayOverlay;
+	
+	function showDayOverlay(props) {
+		if (!dayOverlay) {
+			dayOverlay = $("<div class='fc-day-overlay' style='position:absolute;display:none'/>")
+				.appendTo(element);
+		}
+		var o = element.offset();
+		dayOverlay
+			.css({
+				top: props.top - o.top,
+				left: props.left - o.left,
+				width: props.width,
+				height: props.height
+			})
+			.show();
+	}
+	
+	function hideDayOverlay() {
+		if (dayOverlay) {
+			dayOverlay.hide();
+		}
+	}
+	
+	
+	
+	/************************************* resizable **************************************/
+	
+
+	function resizableSlotEvent(event, eventElement, timeElement) {
+		var prevSlotDelta, slotDelta, newEnd;
+		eventElement
+			.resizable({
+				handles: 's',
+				grid: [0, slotHeight],
+				start: function() {
+					prevSlotDelta = 0;
+					hideSimilarEvents(event, eventElement);
+					if ($.browser.msie && $.browser.version == '6.0') {
+						eventElement.css('overflow', 'hidden');
+					}
+				},
+				resize: function(ev, ui) {
+					slotDelta = Math.round((Math.max(slotHeight, ui.size.height) - ui.originalSize.height) / slotHeight);
+					if (slotDelta != prevSlotDelta) {
+						newEnd = addMinutes(cloneDate(event._end), options.slotMinutes * slotDelta);
+						if (timeElement) {
+							timeElement.text(formatDates(event.start, newEnd, options.agendaEventTimeFormat));
+						}
+						prevSlotDelta = slotDelta;
+					}
+				},
+				stop: function(ev, ui) {
+					reportEventResize(event, 0, true, options.slotMinutes * slotDelta);
+					rerenderEvents();
+				}
+			})
+			.find('div.ui-resizable-s').text('=');
+	}
+	
+	
+	function resizableDayEvent(event, eventElement) {
+		eventElement.resizable({
+			handles: 'e',
+			grid: [dayWidth, 0],
+			start: function() {
+				hideSimilarEvents(event, eventElement);
+			},
+			stop: function(ev, ui) {
+				var dayDelta = Math.round((Math.max(dayWidth, ui.size.width) - ui.originalSize.width) / dayWidth);
+				reportEventResize(event, dayDelta);
+				rerenderEvents();
+			}
+		});
+	}
+	
+	
+	
+	/**************************************** misc **************************************/
+	
+	
+	function reportEventElement(event, eventElement) {
+		eventElements.push(eventElement);
+		if (eventElementsByID[event._id]) {
+			eventElementsByID[event._id].push(eventElement);
+		}else{
+			eventElementsByID[event._id] = [eventElement];
+		}
+	}
+	
+	
+	function hideSimilarEvents(event, eventElement) {
+		var elements = eventElementsByID[event._id];
+		for (var i=0; i<elements.length; i++) {
+			if (elements[i] != eventElement) {
+				elements[i].hide();
+			}
+		}
+	}
+	
+	
+	function reportEventMove(event, days, keepTime, minutes) {
+		minutes = minutes || 0;
+		var events = eventsByID[event._id];
+		for (var i=0, event2; i<events.length; i++) {
+			event2 = events[i];
+			event2.hasTime = event.hasTime;
+			addMinutes(addDays(event2.start, days, keepTime), minutes);
+			if (event.end) {
+				event2.end = addMinutes(addDays(event2.end || event2._end, days, keepTime), minutes);
+			}else{
+				event2.end = event2._end = null;
+					// hopefully renderEvents() will always be called after this
+					// to reset _end.... TODO?
+			}
+		}
+	}
+	
+	
+	function reportEventResize(event, days, keepTime, minutes) {
+		minutes = minutes || 0;
+		var events = eventsByID[event._id];
+		for (var i=0, event2; i<events.length; i++) {
+			event2 = events[i];
+			event2.end = addMinutes(addDays(event2.end || event2._end, days, keepTime), minutes);
+		}
+	}
+	
+	
+	// get the Y coordinate of the given time on the given day
+	
+	function timeCoord(day, time) {
+		var nextDay = addDays(cloneDate(day), 1);
+		if (time < nextDay) {
+			var slotMinutes = options.slotMinutes;
+			var minutes = time.getHours()*60 + time.getMinutes();
+			var slotI = Math.floor(minutes / slotMinutes);
+			var td = body.find('tr:eq(' + slotI + ') td');
+			return Math.round(td.position().top + slotHeight * ((minutes % slotMinutes) / slotMinutes));
+		}else{
+			return panel.height();
+		}
+	}
+
+}
+
+
+function compileSlotSegs(events, start, end) {
+
+	// slice by day
+	var segCols = [],
+		d1 = cloneDate(start),
+		d2 = addDays(cloneDate(start), 1);
+	for (; d1<end; addDays(d1, 1), addDays(d2, 1)) {
+		segCols.push(sliceSegs(events, d1, d2));
+	}
+	
+	var segLevelCols = [],
+		segLevels,
+		segs,
+		segI, seg,
+		levelI, level,
+		collide,
+		segI2, seg2;
+		
+	for (var i=0; i<segCols.length; i++) {
+	
+		// divide segments into levels
+		segLevels = segLevelCols[i] = [];
+		segs = segCols[i];
+		for (segI=0; segI<segs.length; segI++) {
+			seg = segs[segI];
+			for (levelI=0; true; levelI++) {
+				level = segLevels[levelI];
+				if (!level) {
+					segLevels[levelI] = [seg];
+					break;
+				}else{
+					collide = false;
+					for (segI2=0; segI2<level.length; segI2++) {
+						if (segsCollide(level[segI2], seg)) {
+							collide = true;
+							break;
+						}
+					}
+					if (!collide) {
+						level.push(seg);
+						break;
+					}
+				}
+			}
+			seg.right = 0;
+		}
+		
+		// determine # of segments to the 'right' of each segment
+		for (levelI=segLevels.length-1; levelI>0; levelI--) {
+			level = segLevels[levelI];
+			for (segI=0; segI<level.length; segI++) {
+				seg = level[segI];
+				for (segI2=0; segI2<segLevels[levelI-1].length; segI2++) {
+					seg2 = segLevels[levelI-1][segI2];
+					if (segsCollide(seg, seg2)) {
+						seg2.right = Math.max(seg2.right, seg.right+1);
+					}
+				}
+			}
+		}
+		
+	}
+	
+	return segLevelCols;
+	
+}
+
+
+// TODO: move to month.js
+
+function sliceSegs(events, start, end) {
+	var segs = [],
+		i, len=events.length, event,
+		eventStart, eventEnd,
+		segStart, segEnd,
+		isStart, isEnd;
+	for (i=0; i<len; i++) {
+		event = events[i];
+		eventStart = event.start;
+		eventEnd = event._end;
+		if (eventEnd > start && eventStart < end) {
+			if (eventStart < start) {
+				segStart = cloneDate(start);
+				isStart = false;
+			}else{
+				segStart = eventStart;
+				isStart = true;
+			}
+			if (eventEnd > end) {
+				segEnd = cloneDate(end);
+				isEnd = false;
+			}else{
+				segEnd = eventEnd;
+				isEnd = true;
+			}
+			segs.push({
+				event: event,
+				start: segStart,
+				end: segEnd,
+				isStart: isStart,
+				isEnd: isEnd,
+				msLength: segEnd - segStart
+			});
+		}
+	}
+	return segs.sort(segCmp);
+}
+
+
+function segCmp(a, b) {
+	return  (b.msLength - a.msLength) * 100 + (a.event.start - b.event.start);
+}

+ 166 - 0
src/css/agenda.css

@@ -0,0 +1,166 @@
+
+	
+/* header styles */
+	
+.fc .fc-agenda-head th.fc-first {
+	border-left: 0;
+	}
+	
+.fc .fc-agenda-head th,
+.fc .fc-agenda-head td {
+	border-width: 1px 0 0 1px;
+	}
+
+.fc-agenda-head tr.fc-first th {
+	border-width: 0 0 0 1px;
+	}
+	
+.fc-agenda-head tr.fc-last th,
+.fc-agenda-head tr.fc-last td {
+	border-bottom-width: 2px;
+	}
+	
+
+	
+.fc-agenda-head tr.fc-last th {
+	/*border-width: 1px 0 1px 1px;*/
+	background-image: none;
+	}
+	
+.fc-agenda-head tr.fc-last th.fc-first {
+	/*border-width: 0 2px 1px 0;*/
+	}
+	
+.fc-agenda-head tr.fc-last th.fc-last {
+	/*border-width: 0 0 0 3px;*/
+	}
+	
+.fc .fc-agenda-head td {
+	/*border-width: 3px 0 3px 1px;*/
+	background: none;
+	}
+	
+	
+	
+	
+.fc-agenda-body {
+	/*width: 100%;*/
+	overflow: auto;
+	}
+	
+	
+	
+	
+
+	
+.fc .fc-agenda-body th {
+	border-width: 1px 0 0 0;
+	background-image: none;
+	text-align: right;
+	font-weight: normal;
+	vertical-align: middle;
+	width: 48px;
+	height: 22px;
+	padding-right: 2px;
+	}
+	
+.fc .fc-agenda-body td {
+	border-width: 1px 0 0 1px;
+	background: none;
+	}
+
+.fc .fc-agenda-body tr.fc-minor th,
+.fc .fc-agenda-body tr.fc-minor td {
+	border-top-style: dotted;
+	}
+	
+.fc .fc-agenda-body tr.fc-first th,
+.fc .fc-agenda-body tr.fc-first td {
+	border-top: 0;
+	}
+	
+	
+	
+.fc .fc-agenda-bg td {
+	border-style: double;
+	border-width: 0 0 0 3px;
+	}
+	
+.fc .fc-agenda-bg td.fc-not-today {
+	background: none;
+	}
+	
+	
+	
+.fc-agenda .fc-day-content {
+	padding: 2px 1px 14px;
+	}
+	
+	
+	
+	
+/* vertical events */
+
+.fc-event-vert {
+	border-width: 0 1px;
+	}
+	
+.fc-event-vert a {
+	border-width: 0;
+	}
+	
+.fc-content .fc-corner-top {
+	margin-top: 1px;
+	}
+	
+.fc-content .fc-corner-top a {
+	margin-top: -1px;
+	border-top-width: 1px;
+	}
+	
+.fc-content .fc-corner-bottom {
+	margin-bottom: 1px;
+	}
+	
+.fc-content .fc-corner-bottom a {
+	margin-bottom: -1px;
+	border-bottom-width: 1px;
+	}
+	
+.fc-event-vert span {
+	display: block;
+	position: relative;
+	z-index: 2;
+	}
+	
+.fc-event-vert span.fc-event-time {
+	white-space: nowrap;
+	_white-space: normal;
+	overflow: hidden;
+	font-size: 10px;
+	}
+	
+.fc-event-vert span.fc-event-title {
+	line-height: 13px;
+	}
+	
+.fc-event-vert span.fc-event-bg {
+	position: absolute;
+	z-index: 1;
+	top: 0;
+	left: 0;
+	width: 100%;
+	height: 100%;
+	background: #fff;
+	opacity: .3;
+	filter: alpha(opacity=30);
+	}
+	
+.fc-event-vert .ui-resizable-s {
+	font-family: monospace;
+	height: 8px;
+	font-size: 11px;
+	line-height: 8px;
+	bottom: 0;
+	text-align: center;
+	}

+ 56 - 0
src/css/grid.css

@@ -0,0 +1,56 @@
+
+/* Month View, Basic Week View, Basic Day View
+------------------------------------------------------------------------*/
+
+.fc-grid table {
+	width: 100%;
+	}
+	
+.fc-grid th {
+	border-width: 0 0 0 1px;
+	text-align: center;
+	}
+	
+.fc-grid td {
+	border-width: 1px 0 0 1px;
+	}
+	
+.fc-grid th.fc-left,
+.fc-grid td.fc-left {
+	border-left: 0;
+	}
+	
+.fc-grid .fc-day-number {
+	float: right;
+	padding: 0 2px;
+	}
+	
+.fc-grid .fc-other-month .fc-day-number {
+	opacity: 0.3;
+	filter: alpha(opacity=30);
+	}
+	
+.fc-grid .fc-day-content {
+	clear: both;
+	padding: 2px 2px 0; /* distance between events and day edges */
+	}
+	
+/* event styles */
+	
+.fc-grid .fc-event-time {
+	font-weight: bold;
+	}
+	
+/* right-to-left */
+
+.fc-rtl .fc-grid {
+	direction: rtl;
+	}
+	
+.fc-rtl .fc-grid .fc-day-number {
+	float: left;
+	}
+	
+.fc-rtl .fc-grid .fc-event-time {
+	float: right;
+	}

+ 279 - 0
src/css/main.css

@@ -0,0 +1,279 @@
+
+.fc,
+.fc .fc-header,
+.fc .fc-content {
+	font-size: 1em;
+	}
+	
+.fc table {
+	border-collapse: collapse;
+	border-spacing: 0;
+	}
+	
+.fc td, .fc th {
+	padding: 0;
+	vertical-align: top;
+	}
+
+
+
+/* Header
+------------------------------------------------------------------------*/
+	
+table.fc-header {
+	width: 100%;
+	}
+	
+.fc-header-left {
+	width: 25%;
+	}
+	
+.fc-header-left table {
+	float: left;
+	}
+	
+.fc-header-center {
+	width: 50%;
+	}
+	
+.fc-header-center table {
+	margin: 0 auto;
+	}
+	
+.fc-header-right {
+	width: 25%;
+	}
+	
+.fc-header-right table {
+	float: right;
+	}
+	
+.fc-header-title {
+	margin-top: 0;
+	white-space: nowrap;
+	}
+	
+.fc-header-space {
+	padding-left: 10px;
+	}
+	
+/* right-to-left */
+
+.fc-rtl .fc-header-title {
+	direction: rtl;
+	}
+	
+/* button rounded corners  */
+	
+.fc-header .fc-state-default {
+	border-width: 1px 0;
+	padding: 0 1px;
+	}
+	
+.fc-header .fc-state-default a {
+	display: block;
+	position: relative;
+	margin: 0 -1px;
+	border-width: 0 1px;
+	width: 100%;
+	}
+	
+.fc-header .fc-state-default span {
+	display: block;
+	}
+	
+.fc-header .fc-corner-left {
+	margin-left: 1px;
+	padding-left: 0;
+	}
+	
+.fc-header .fc-corner-right {
+	margin-right: 1px;
+	padding-right: 0;
+	}
+	
+.fc-header .fc-no-left {
+	padding-left: 0;
+	}
+	
+.fc-header .ui-no-left {
+	border-left: 0;
+	}
+	
+/* default button state */
+	
+.fc-header .fc-state-default,
+.fc-header .ui-state-default {
+	margin-bottom: 10px;
+	cursor: pointer;
+	}
+	
+.fc-header .fc-state-default,
+.fc-header .fc-state-default a {
+	border-style: solid;
+	border-color: #6E6E6E;
+	color: #333;
+	}
+
+.fc-header .fc-state-default span {
+	border-width: 1px 0 0 1px;
+	border-style: solid;
+	border-color: #fff;
+	background: #F0F0F0;
+	}
+	
+.fc-header .fc-state-default span,
+.fc-header .ui-state-default {
+	padding: 4px 6px;
+	}
+	
+/* active button state */
+	
+.fc-header .fc-state-active a {
+	color: #fff;
+	}
+	
+.fc-header .fc-state-active span {
+	background: #787878;
+	border-color: #777;
+	}
+	
+/* down button state */
+
+.fc-header .fc-state-down span {
+	background: #787878;
+	border-color: #777;
+	}
+	
+/* disabled button state */
+	
+.fc-header .fc-state-disabled,
+.fc-header .fc-state-disabled a {
+	border-color: #ccc;
+	}
+	
+.fc-header .fc-state-disabled a {
+	color: #999;
+	}
+	
+.fc-header .fc-state-disabled span {
+	border-color: #fff;
+	background: #F0F0F0;
+	}
+	
+	
+	
+/* Content Area & Global Cell Styles
+------------------------------------------------------------------------*/
+	
+.fc-widget-content {
+	border: 1px solid #ccc; /* outer border color */
+	}
+	
+.fc-content {
+	clear: both;
+	}
+	
+.fc-content .fc-state-default {
+	border-style: solid;
+	border-color: #ccc; /* inner border color */
+	}
+	
+.fc-content .fc-state-highlight { /* today */
+	background: #FFFFCC;
+	}
+	
+.fc-content td.fc-not-today {
+	background: none;
+	}
+	
+.fc-cell-overlay { /* semi-transparent rectangle while dragging */
+	background: #ADDBFF;
+	opacity: .2;
+	filter: alpha(opacity=20); /* for IE */
+	}
+	
+.fc-view { /* prevents dragging outside of widget */
+	width: 100%;
+	overflow: hidden;
+	}
+	
+	
+	
+/* Global Event Styles
+------------------------------------------------------------------------*/
+
+.fc-event,
+.fc-event a,
+.fc-agenda .fc-event-time {
+	color: #fff;
+	border-style: solid;
+	border-color: blue;
+	background-color: blue;
+	}
+	
+.fc-event-nobg,
+.fc-event-nobg a,
+.fc-agenda .fc-event-nobg .fc-event-time {
+	border-style: none;
+	background: none;
+	color: inherit;
+	}
+	
+.fc-event a {
+	overflow: hidden;
+	font-size: 11px;
+	text-decoration: none;
+	cursor: pointer;
+	}
+	
+.fc-event-time,
+.fc-event-title {
+	padding: 0 1px;
+	}
+
+.fc-event a { /* prep for rounded corners */
+	display: block;
+	position: relative;
+	width: 100%;
+	height: 100%;
+	}
+	
+	
+	
+/* Horizontal Events
+------------------------------------------------------------------------*/
+
+.fc-event-hori {
+	border-width: 1px 0;
+	margin-bottom: 1px;
+	}
+	
+.fc-event-hori a {
+	border-width: 0;
+	}
+	
+.fc-content .fc-corner-left {
+	margin-left: 1px;
+	}
+	
+.fc-content .fc-corner-left a {
+	margin-left: -1px;
+	border-left-width: 1px;
+	}
+	
+.fc-content .fc-corner-right {
+	margin-right: 1px;
+	}
+	
+.fc-content .fc-corner-right a {
+	margin-right: -1px;
+	border-right-width: 1px;
+	}
+	
+.fc-event-hori .ui-resizable-handle {
+	_height: 10px; /* IE6 had 0 height */
+	}
+	
+
+	

+ 18 - 26
fullcalendar/gcal.js → src/gcal.js

@@ -1,32 +1,17 @@
-/*!
- * FullCalendar Google Calendar Extension
- *
- * Visit http://arshaw.com/fullcalendar/docs/#google-calendar
- * for docs and examples.
- *
- * Copyright (c) 2009 Adam Shaw
- * Dual licensed under the MIT and GPL licenses:
- *   http://www.opensource.org/licenses/mit-license.php
- *   http://www.gnu.org/licenses/gpl.html
- *
- * Date:
- * Revision:
- */
- 
 (function($) {
 
 	$.fullCalendar.gcalFeed = function(feedUrl, options) {
 		
 		feedUrl = feedUrl.replace(/\/basic$/, '/full');
 		options = options || {};
-		var draggable = options.draggable || false;
 		
 		return function(start, end, callback) {
 			$.getJSON(feedUrl + "?alt=json-in-script&callback=?",
 				{
-					'start-min': $.fullCalendar.formatDate(start, 'c'),
-					'start-max': $.fullCalendar.formatDate(end, 'c'),
-					'singleevents': true
+					'start-min': $.fullCalendar.formatDate(start, 'u'),
+					'start-max': $.fullCalendar.formatDate(end, 'u'),
+					'singleevents': true,
+					'max-results': 1000
 				},
 				function(data) {
 					var events = [];
@@ -34,12 +19,19 @@
 						$.each(data.feed.entry, function(i, entry) {
 							var url;
 							$.each(entry['link'], function(j, link) {
-								if (link.type == 'text/html') url = link.href;
+								if (link.type == 'text/html') {
+									url = link.href;
+								}
 							});
-							var showTime = entry['gd$when'][0]['startTime'].indexOf('T') != -1;
+							var startStr = entry['gd$when'][0]['startTime'];
+							var start = $.fullCalendar.parseDate(startStr);
+							var end = $.fullCalendar.parseDate(entry['gd$when'][0]['endTime']);
+							var hasTime = startStr.indexOf('T') != -1;
 							var classNames = [];
-							if (showTime) {
-								classNames.push('nobg');
+							if (hasTime) {
+								classNames.push('fc-event-nobg');
+							}else{
+								end = new Date(end - 1);
 							}
 							if (options.className) {
 								if (typeof options.className == 'string') {
@@ -53,12 +45,12 @@
 								url: url,
 								title: entry['title']['$t'],
 								start: $.fullCalendar.parseDate(entry['gd$when'][0]['startTime']),
-								end: $.fullCalendar.parseDate(entry['gd$when'][0]['endTime']),
+								end: end,
 								location: entry['gd$where'][0]['valueString'],
 								description: entry['content']['$t'],
-								showTime: showTime,
+								hasTime: hasTime,
 								className: classNames,
-								draggable: draggable
+								editable: options.editable || false
 							});
 						});
 					callback(events);

+ 556 - 0
src/grid.js

@@ -0,0 +1,556 @@
+
+setDefaults({
+	weekMode: 'fixed'
+});
+
+views.month = function(element, options) {
+	return new Grid(element, options, {
+		render: function(date, delta, fetchEvents) {
+			if (delta) {
+				addMonths(date, delta);
+			}
+			var start = this.start = cloneDate(date, true);
+			start.setDate(1);
+			this.title = formatDates(
+				start,
+				addDays(cloneDate(this.end = addMonths(cloneDate(start), 1)), -1),
+				strProp(options.titleFormat, 'month'),
+				options
+			);
+			addDays(this.visStart = cloneDate(start), -((start.getDay() - options.weekStart + 7) % 7));
+			addDays(this.visEnd = cloneDate(this.end), (7 - this.visEnd.getDay() + options.weekStart) % 7);
+			var rowCnt = Math.round((this.visEnd - this.visStart) / (DAY_MS * 7));
+			if (options.weekMode == 'fixed') {
+				addDays(this.visEnd, (6 - rowCnt) * 7);
+				rowCnt = 6;
+			}
+			this.renderGrid(rowCnt, 7, strProp(options.columnFormat, 'month'), true, fetchEvents);
+		}
+	});
+}
+
+views.basicWeek = function(element, options) {
+	return new Grid(element, options, {
+		render: function(date, delta, fetchEvents) {
+			if (delta) {
+				addDays(date, delta * 7);
+			}
+			this.title = formatDates(
+				this.start = this.visStart = addDays(cloneDate(date), -((date.getDay() - options.weekStart + 7) % 7)),
+				addDays(cloneDate(this.end = this.visEnd = addDays(cloneDate(this.start), 7)), -1),
+				strProp(options.titleFormat, 'week'),
+				options
+			);
+			this.renderGrid(1, 7, strProp(options.columnFormat, 'week'), false, fetchEvents);
+		}
+	});
+};
+
+views.basicDay = function(element, options) {
+	return new Grid(element, options, {
+		render: function(date, delta, fetchEvents) {
+			if (delta) {
+				addDays(date, delta);
+			}
+			this.title = formatDate(date, strProp(options.titleFormat, 'day'), options);
+			this.start = this.visStart = cloneDate(date, true);
+			this.end = this.visEnd = addDays(cloneDate(this.start), 1);
+			this.renderGrid(1, 1, strProp(options.columnFormat, 'day'), false, fetchEvents);
+		}
+	});
+}
+
+
+// flags for [Opera] rendering bugs
+var tdTopBug, trTopBug, tbodyTopBug, sniffBugs = true;
+
+var tdHeightBug;
+
+var sniffedEventLeftBug, eventLeftDiff=0;
+
+
+function Grid(element, options, methods) {
+	
+	var tm, weekStart,
+		rtl, dis, dit,  // day index sign / translate
+		rowCnt, colCnt,
+		colWidth,
+		thead, tbody,
+		cachedSegs, //...
+		
+	// initialize superclass
+	view = $.extend(this, viewMethods, methods, {
+		renderGrid: renderGrid,
+		rerenderEvents: rerenderEvents,
+		updateSize: updateSize,
+		eventEnd: function(event) {
+			return event.end || cloneDate(event.start);
+		},
+		visEventEnd: function(event) {
+			if (event.end) {
+				var end = cloneDate(event.end);
+				return (!event.hasTime || end.getHours() || end.getMinutes()) ? addDays(end, 1) : end;
+			}else{
+				return addDays(cloneDate(event.start), 1);
+			}
+		}
+	});
+	view.init(element, options);
+	
+	
+	
+	/********************************* grid rendering *************************************/
+	
+	
+	element.addClass('fc-grid').css('position', 'relative');
+	if (element.disableSelection) {
+		element.disableSelection();
+	}
+
+	function renderGrid(r, c, colFormat, showNumbers, fetchEvents) {
+		//console.log('renderGrid!');
+		rowCnt = r;
+		colCnt = c;
+	
+		var month = view.start.getMonth(),
+			today = clearTime(new Date()),
+			s, s2, s3, i, j, d = cloneDate(view.visStart);
+		
+		// update option-derived variables
+		tm = options.theme ? 'ui' : 'fc'; 
+		weekStart = options.weekStart;
+		if (rtl = options.isRTL) {
+			dis = -1;
+			dit = colCnt - 1;
+		}else{
+			dis = 1;
+			dit = 0;
+		}
+		
+		if (!tbody) { // first time, build all cells from scratch
+		
+			var table = $("<table/>").appendTo(element);
+			
+			s = '';
+			for (i=0; i<colCnt; i++) {
+				s2 = "<th class='fc-" +
+					dayIDs[d.getDay()] + ' ' + // needs to be first
+					tm + '-state-default ' +
+					(i==dit ? ' fc-left' : '') +
+					"'>" + formatDate(d, colFormat, options) + "</th>"; // TODO: optionize
+				//if (rtl) {
+				//	s = s2 + s;
+				//}else{
+					s += s2;
+				//}
+				addDays(d, 1);
+			}
+			thead = $("<thead><tr>" + s + "</tr></thead>").appendTo(table);
+			
+			s = "<tbody>";
+			d = cloneDate(view.visStart);
+			for (i=0; i<rowCnt; i++) {
+				s2 = '';
+				for (j=0; j<colCnt; j++) {
+					s3 = "<td class='fc-" +
+						dayIDs[d.getDay()] + ' ' + // needs to be first
+						tm + '-state-default fc-day' + (i*colCnt+j) +
+						(j==dit ? ' fc-left' : '') +
+						(rowCnt>1 && d.getMonth() != month ? ' fc-other-month' : '') +
+						(+d == +today ?
+						' fc-today '+tm+'-state-highlight' :
+						' fc-not-today') + "'>" +
+						(showNumbers ? "<div class='fc-day-number'>" + d.getDate() + "</div>" : '') +
+						"<div class='fc-day-content'><div>&nbsp;</div></div></td>";
+					//if (rtl) {
+					//	s2 = s3 + s2;
+					//}else{
+						s2 += s3;
+					//}
+					addDays(d, 1);
+				}
+				s += "<tr class='fc-week" + i + "'>" + s2 + "</tr>";
+			}
+			tbody = $(s + "</tbody>").appendTo(table);
+			tbody.find('td').click(dayClick);
+		
+		}else{ // NOT first time, reuse as many cells as possible
+		
+			view.clearEvents();
+		
+			var prevRowCnt = tbody.find('tr').length;
+			if (rowCnt < prevRowCnt) {
+				tbody.find('tr:gt(' + (rowCnt-1) + ')').remove(); // remove extra rows
+			}
+			else if (rowCnt > prevRowCnt) {
+				s = '';
+				for (i=prevRowCnt; i<rowCnt; i++) {
+					s2 = '';
+					for (j=0; j<colCnt; j++) {
+						s3 = "<td class='fc-" +
+							dayIDs[(j * dis + dit + weekStart) % 7] + ' ' + // needs to be first
+							tm + '-state-default fc-new fc-day' + (i*colCnt + j) +
+							(j==dit ? ' fc-left' : '') + "'>" +
+							(showNumbers ? "<div class='fc-day-number'></div>" : '') +
+							"<div class='fc-day-content'><div>&nbsp;</div></div>" +
+							"</td>";
+						//if (rtl) {
+						//	s2 = s3 + s2;
+						//}else{
+							s2 += s3;
+						//}
+					}
+					s += "<tr class='fc-week" + i + "'>" + s2 + "</tr>";
+				}
+				tbody.append(s);
+			}
+			tbody.find('td.fc-new').removeClass('fc-new').click(dayClick);
+			
+			// re-label and re-class existing cells
+			tbody.find('tr').each(function() {
+				for (i=0; i<colCnt; i++) {
+					var td = $(this.childNodes[i]); // * dis + dit TODO: clean
+					if (rowCnt > 1) {
+						if (d.getMonth() == month) {
+							td.removeClass('fc-other-month');
+						}else{
+							td.addClass('fc-other-month');
+						}
+					}
+					if (+d == +today) {
+						td.removeClass('fc-not-today')
+							.addClass('fc-today')
+							.addClass(tm + '-state-highlight');
+					}else{
+						td.addClass('fc-not-today')
+							.removeClass('fc-today')
+							.removeClass(tm + '-state-highlight');
+					}
+					td.find('div.fc-day-number').text(d.getDate());
+					addDays(d, 1);
+				}
+			});
+			
+			if (colCnt == 1) {
+				var startDay = this.visStart.getDay();
+				var td = tbody.find('td')[0];
+				td.className = td.className.replace(/^fc-\w+(?= )/, 'fc-' + dayIDs[startDay]);
+				thead.find('th').text(formatDate(this.start, colFormat, options));
+			}
+		
+		}
+		
+		updateSize();
+		fetchEvents(renderEvents);
+	
+	};
+	
+	
+	function updateSize() {
+		var width = element.width();
+		var height = Math.round(width / options.aspectRatio);
+		setOuterWidth(
+			thead.find('th').slice(0, -1),
+			colWidth = Math.floor(width / colCnt)
+		);
+		var leftTDs = tbody.find('tr td:first-child');
+		var tbodyHeight = height - thead.height();
+		var rowHeight1, rowHeight2;
+		if (options.weekMode == 'variable') {
+			rowHeight1 = rowHeight2 = Math.floor(tbodyHeight / (rowCnt==1 ? 2 : 6));
+		}else{
+			rowHeight1 = Math.floor(tbodyHeight / rowCnt);
+			rowHeight2 = tbodyHeight - rowHeight1*(rowCnt-1);
+		}
+
+		if (sniffBugs) {
+			// nasty bugs in opera 9.25
+			// position() returning relative to direct parent
+			var tr = tbody.find('tr:first');
+			var td = tr.find('td:first');
+			var trTop = tr.position().top;
+			var tdTop = td.position().top;
+			tdTopBug = tdTop < 0;
+			trTopBug = trTop != tdTop;
+			tbodyTopBug = tbody.position().top != trTop;
+			sniffBugs = false;
+			//
+			td.height(rowHeight1);
+			tdHeightBug = rowHeight1 != td.height();
+		}
+		
+		if (tdHeightBug) {
+			leftTDs.slice(0, -1).height(rowHeight1);
+			leftTDs.slice(-1).height(rowHeight2);
+		}else{
+			setOuterHeight(leftTDs.slice(0, -1), rowHeight1);
+			setOuterHeight(leftTDs.slice(-1), rowHeight2);
+		}
+		
+		//alert(tbodyHeight + ' === ' + tbody.height());
+	}
+	
+	
+	
+	/******************************** event rendering *****************************/
+	
+	
+	function renderEvents(events) {
+		view.reportEvents(events);
+		renderSegs(cachedSegs = compileSegs(events)); // view.visibleEvents(
+	}
+	
+	
+	function rerenderEvents(skipCompile) {
+		//console.log('rerender events');
+		view.clearEvents();
+		if (skipCompile) {
+			renderSegs(cachedSegs);
+		}else{
+			renderEvents(view.cachedEvents);
+		}
+	}
+	
+	
+	function compileSegs(events) {
+		var d1 = cloneDate(view.visStart);
+		var d2 = addDays(cloneDate(d1), colCnt);
+		var rows = [];
+		for (var i=0; i<rowCnt; i++) {
+			rows.push(stackSegs(view.sliceSegs(events, d1, d2)));
+			addDays(d1, 7);
+			addDays(d2, 7);
+		}
+		return rows;
+	}
+	
+	
+	function renderSegs(segRows) {
+		var i, len = segRows.length, levels,
+			tr, td,
+			innerDiv,
+			top,
+			weekHeight,
+			j, segs,
+			levelHeight,
+			k, seg,
+			event,
+			eventClasses,
+			startE, endE,
+			left1, left2,
+			eventElement, eventAnchor,
+			triggerRes;
+		for (i=0; i<len; i++) {
+			levels = segRows[i];
+			tr = tbody.find('tr:eq('+i+')');
+			td = tr.find('td:first');
+			innerDiv = td.find('div.fc-day-content div').css('position', 'relative');
+			top = innerDiv.position().top;
+			if (tdTopBug) top -= td.position().top;
+			if (trTopBug) top += tr.position().top;
+			if (tbodyTopBug) top += tbody.position().top;
+			weekHeight = 0;
+			for (j=0; j<levels.length; j++) {
+				segs = levels[j];
+				levelHeight = 0;
+				for (k=0; k<segs.length; k++) {
+					seg = segs[k];
+					event = seg.event;
+					eventClasses = event.className || [];
+					if (typeof eventClasses == 'string') {
+						eventClasses = eventClasses.split(' ');
+					}
+					eventClasses.push('fc-event', 'fc-event-hori');
+					startE = seg.isStart ?
+						tr.find('td:eq('+((seg.start.getDay()-weekStart+colCnt)%colCnt)+') div.fc-day-content div') :
+						tbody;
+					endE = seg.isEnd ?
+						tr.find('td:eq('+((seg.end.getDay()-weekStart+colCnt-1)%colCnt)+') div.fc-day-content div') :
+						tbody;
+					if (rtl) {
+						left1 = endE.position().left;
+						left2 = startE.position().left + startE.width();
+						if (seg.isStart) {
+							eventClasses.push('fc-corner-right');
+						}
+						if (seg.isEnd) {
+							eventClasses.push('fc-corner-left');
+						}
+					}else{
+						left1 = startE.position().left;
+						left2 = endE.position().left + endE.width();
+						if (seg.isStart) {
+							eventClasses.push('fc-corner-left');
+						}
+						if (seg.isEnd) {
+							eventClasses.push('fc-corner-right');
+						}
+					}
+					eventElement = $("<div class='" + eventClasses.join(' ') + "'/>")
+						.append(eventAnchor = $("<a/>")
+							.append(event.hasTime ?
+								$("<span class='fc-event-time'/>")
+									.html(formatDate(event.start, options.eventTimeFormat, options)) :
+								null)
+							.append($("<span class='fc-event-title'/>")
+								.text(event.title)));
+					if (event.url) {
+						eventAnchor.attr('href', event.url);
+					}
+					triggerRes = view.trigger('eventRender', event, event, eventElement);
+					if (triggerRes !== false) {
+						if (triggerRes && typeof triggerRes != 'boolean') {
+							eventElement = $(triggerRes);
+						}
+						eventElement
+							.css({
+								position: 'absolute',
+								top: top,
+								left: left1 + eventLeftDiff,
+								zIndex: 3
+							})
+							.appendTo(element);
+						setOuterWidth(eventElement, left2-left1, true);
+						if (!sniffedEventLeftBug) {
+							if (rtl) {
+								eventLeftDiff = left1 - eventElement.position().left;
+								if (eventLeftDiff) {
+									eventElement.css('left', left1 + eventLeftDiff);
+								}
+							}
+							sniffedEventLeftBug = true;
+						}
+						eventElementHandlers(event, eventElement);
+						if (event.editable || typeof event.editable == 'undefined' && options.editable) {
+							draggableEvent(event, eventElement);
+							resizableEvent(event, eventElement);
+						}
+						view.reportEventElement(event, eventElement);
+						levelHeight = Math.max(levelHeight, eventElement.outerHeight(true));
+					}
+				}
+				weekHeight += levelHeight;
+				top += levelHeight;
+			}
+			innerDiv.height(weekHeight);
+		}
+	}
+	
+	function eventElementHandlers(event, eventElement) {
+		eventElement
+			.click(function(ev) {
+				if (!eventElement.hasClass('ui-draggable-dragging')) {
+					return view.trigger('eventClick', this, event, ev);
+				}
+			})
+			.hover(
+				function(ev) {
+					view.trigger('eventMouseover', this, event, ev);
+				},
+				function(ev) {
+					view.trigger('eventMouseover', this, event, ev);
+				}
+			);
+	}
+	
+	
+	
+	/***************************** draggable *********************************/
+	
+	
+	function draggableEvent(event, eventElement) {
+		if (!options.disableDragging && eventElement.draggable) {
+			var matrix;
+			eventElement.draggable({
+				zIndex: 4,
+				delay: 50,
+				opacity: options.dragOpacity,
+				revertDuration: options.dragRevertDuration,
+				start: function(ev, ui) {
+					matrix = new HoverMatrix(function(cell) {
+						eventElement.draggable('option', 'revert', !cell || !cell.rowDelta && !cell.colDelta);
+						if (cell) {
+							view.showOverlay(cell);
+						}else{
+							view.hideOverlay();
+						}
+					});
+					tbody.find('tr').each(function() {
+						matrix.row(this);
+					});
+					var tds = tbody.find('tr:first td');
+					if (rtl) {
+						tds = $(tds.get().reverse());
+					}
+					tds.each(function() {
+						matrix.col(this);
+					});
+					matrix.start();
+					view.hideEvents(event, eventElement);
+					view.trigger('eventDragStart', eventElement, event, ev, ui);
+				},
+				drag: function(ev) {
+					matrix.mouse(ev.pageX, ev.pageY);
+				},
+				stop: function(ev, ui) {
+					view.hideOverlay();
+					view.trigger('eventDragStop', eventElement, event, ev, ui);
+					var cell = matrix.cell;
+					if (!cell || !cell.rowDelta && !cell.colDelta) {
+						view.showEvents(event, eventElement);
+					}else{
+						var dayDelta = cell.rowDelta*7 + cell.colDelta*dis;
+						view.moveEvent(event, dayDelta);
+						view.trigger('eventDrop', this, event, dayDelta, 0, ev, ui);
+						rerenderEvents();
+					}
+				}
+			});
+		}
+	}
+	
+	
+	
+	/******************************* resizable *****************************/
+	
+	
+	function resizableEvent(event, eventElement) {
+		if (!options.disableResizing && eventElement.resizable) {
+			eventElement.resizable({
+				handles: rtl ? 'w' : 'e',
+				grid: [colWidth, 0],
+				containment: element,
+				start: function(ev, ui) {
+					eventElement.css('z-index', 4);
+					view.hideEvents(event, eventElement);
+					view.trigger('eventResizeStart', this, event, ev, ui);
+				},
+				stop: function(ev, ui) {
+					view.trigger('eventResizeStop', this, event, ev, ui);
+					var dayDelta = Math.round((Math.max(colWidth, ui.size.width) - ui.originalSize.width) / colWidth);
+					if (dayDelta) {
+						view.resizeEvent(event, dayDelta);
+						view.trigger('eventResize', this, event, dayDelta, 0, ev, ui);
+						rerenderEvents();
+					}else{
+						view.showEvents(event, eventElement);
+					}
+					eventElement.css('z-index', 3);
+				}
+			});
+		}
+	}
+	
+	
+	
+	
+	//
+	
+	function dayClick() {
+		var dayIndex = parseInt(this.className.match(/fc\-day(\d+)/)[1]);
+		var date = addDays(cloneDate(view.visStart), dayIndex);
+		view.trigger('dayClick', this, date);
+	}
+	
+
+};

+ 0 - 0
jquery/jquery.js → src/jquery/jquery.js


+ 2 - 2
jquery/ui.core.js → src/jquery/ui.core.js

@@ -1,5 +1,5 @@
 /*
- * jQuery UI 1.7
+ * jQuery UI 1.7.2
  *
  * Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about)
  * Dual licensed under the MIT (MIT-LICENSE.txt)
@@ -14,7 +14,7 @@ var _remove = $.fn.remove,
 
 //Helper functions and ui object
 $.ui = {
-	version: "1.7",
+	version: "1.7.2",
 
 	// $.ui.plugin is deprecated.  Use the proxy pattern instead.
 	plugin: {

+ 2 - 2
jquery/ui.draggable.js → src/jquery/ui.draggable.js

@@ -1,5 +1,5 @@
 /*
- * jQuery UI Draggable 1.7
+ * jQuery UI Draggable 1.7.2
  *
  * Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about)
  * Dual licensed under the MIT (MIT-LICENSE.txt)
@@ -400,7 +400,7 @@ $.widget("ui.draggable", $.extend({}, $.ui.mouse, {
 }));
 
 $.extend($.ui.draggable, {
-	version: "1.7",
+	version: "1.7.2",
 	eventPrefix: "drag",
 	defaults: {
 		addClasses: true,

+ 800 - 0
src/jquery/ui.resizable.js

@@ -0,0 +1,800 @@
+/*
+ * jQuery UI Resizable 1.7.2
+ *
+ * Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT (MIT-LICENSE.txt)
+ * and GPL (GPL-LICENSE.txt) licenses.
+ *
+ * http://docs.jquery.com/UI/Resizables
+ *
+ * Depends:
+ *	ui.core.js
+ */
+(function($) {
+
+$.widget("ui.resizable", $.extend({}, $.ui.mouse, {
+
+	_init: function() {
+
+		var self = this, o = this.options;
+		this.element.addClass("ui-resizable");
+
+		$.extend(this, {
+			_aspectRatio: !!(o.aspectRatio),
+			aspectRatio: o.aspectRatio,
+			originalElement: this.element,
+			_proportionallyResizeElements: [],
+			_helper: o.helper || o.ghost || o.animate ? o.helper || 'ui-resizable-helper' : null
+		});
+
+		//Wrap the element if it cannot hold child nodes
+		if(this.element[0].nodeName.match(/canvas|textarea|input|select|button|img/i)) {
+
+			//Opera fix for relative positioning
+			if (/relative/.test(this.element.css('position')) && $.browser.opera)
+				this.element.css({ position: 'relative', top: 'auto', left: 'auto' });
+
+			//Create a wrapper element and set the wrapper to the new current internal element
+			this.element.wrap(
+				$('<div class="ui-wrapper" style="overflow: hidden;"></div>').css({
+					position: this.element.css('position'),
+					width: this.element.outerWidth(),
+					height: this.element.outerHeight(),
+					top: this.element.css('top'),
+					left: this.element.css('left')
+				})
+			);
+
+			//Overwrite the original this.element
+			this.element = this.element.parent().data(
+				"resizable", this.element.data('resizable')
+			);
+
+			this.elementIsWrapper = true;
+
+			//Move margins to the wrapper
+			this.element.css({ marginLeft: this.originalElement.css("marginLeft"), marginTop: this.originalElement.css("marginTop"), marginRight: this.originalElement.css("marginRight"), marginBottom: this.originalElement.css("marginBottom") });
+			this.originalElement.css({ marginLeft: 0, marginTop: 0, marginRight: 0, marginBottom: 0});
+
+			//Prevent Safari textarea resize
+			this.originalResizeStyle = this.originalElement.css('resize');
+			this.originalElement.css('resize', 'none');
+
+			//Push the actual element to our proportionallyResize internal array
+			this._proportionallyResizeElements.push(this.originalElement.css({ position: 'static', zoom: 1, display: 'block' }));
+
+			// avoid IE jump (hard set the margin)
+			this.originalElement.css({ margin: this.originalElement.css('margin') });
+
+			// fix handlers offset
+			this._proportionallyResize();
+
+		}
+
+		this.handles = o.handles || (!$('.ui-resizable-handle', this.element).length ? "e,s,se" : { n: '.ui-resizable-n', e: '.ui-resizable-e', s: '.ui-resizable-s', w: '.ui-resizable-w', se: '.ui-resizable-se', sw: '.ui-resizable-sw', ne: '.ui-resizable-ne', nw: '.ui-resizable-nw' });
+		if(this.handles.constructor == String) {
+
+			if(this.handles == 'all') this.handles = 'n,e,s,w,se,sw,ne,nw';
+			var n = this.handles.split(","); this.handles = {};
+
+			for(var i = 0; i < n.length; i++) {
+
+				var handle = $.trim(n[i]), hname = 'ui-resizable-'+handle;
+				var axis = $('<div class="ui-resizable-handle ' + hname + '"></div>');
+
+				// increase zIndex of sw, se, ne, nw axis
+				//TODO : this modifies original option
+				if(/sw|se|ne|nw/.test(handle)) axis.css({ zIndex: ++o.zIndex });
+
+				//TODO : What's going on here?
+				if ('se' == handle) {
+					axis.addClass('ui-icon ui-icon-gripsmall-diagonal-se');
+				};
+
+				//Insert into internal handles object and append to element
+				this.handles[handle] = '.ui-resizable-'+handle;
+				this.element.append(axis);
+			}
+
+		}
+
+		this._renderAxis = function(target) {
+
+			target = target || this.element;
+
+			for(var i in this.handles) {
+
+				if(this.handles[i].constructor == String)
+					this.handles[i] = $(this.handles[i], this.element).show();
+
+				//Apply pad to wrapper element, needed to fix axis position (textarea, inputs, scrolls)
+				if (this.elementIsWrapper && this.originalElement[0].nodeName.match(/textarea|input|select|button/i)) {
+
+					var axis = $(this.handles[i], this.element), padWrapper = 0;
+
+					//Checking the correct pad and border
+					padWrapper = /sw|ne|nw|se|n|s/.test(i) ? axis.outerHeight() : axis.outerWidth();
+
+					//The padding type i have to apply...
+					var padPos = [ 'padding',
+						/ne|nw|n/.test(i) ? 'Top' :
+						/se|sw|s/.test(i) ? 'Bottom' :
+						/^e$/.test(i) ? 'Right' : 'Left' ].join("");
+
+					target.css(padPos, padWrapper);
+
+					this._proportionallyResize();
+
+				}
+
+				//TODO: What's that good for? There's not anything to be executed left
+				if(!$(this.handles[i]).length)
+					continue;
+
+			}
+		};
+
+		//TODO: make renderAxis a prototype function
+		this._renderAxis(this.element);
+
+		this._handles = $('.ui-resizable-handle', this.element)
+			.disableSelection();
+
+		//Matching axis name
+		this._handles.mouseover(function() {
+			if (!self.resizing) {
+				if (this.className)
+					var axis = this.className.match(/ui-resizable-(se|sw|ne|nw|n|e|s|w)/i);
+				//Axis, default = se
+				self.axis = axis && axis[1] ? axis[1] : 'se';
+			}
+		});
+
+		//If we want to auto hide the elements
+		if (o.autoHide) {
+			this._handles.hide();
+			$(this.element)
+				.addClass("ui-resizable-autohide")
+				.hover(function() {
+					$(this).removeClass("ui-resizable-autohide");
+					self._handles.show();
+				},
+				function(){
+					if (!self.resizing) {
+						$(this).addClass("ui-resizable-autohide");
+						self._handles.hide();
+					}
+				});
+		}
+
+		//Initialize the mouse interaction
+		this._mouseInit();
+
+	},
+
+	destroy: function() {
+
+		this._mouseDestroy();
+
+		var _destroy = function(exp) {
+			$(exp).removeClass("ui-resizable ui-resizable-disabled ui-resizable-resizing")
+				.removeData("resizable").unbind(".resizable").find('.ui-resizable-handle').remove();
+		};
+
+		//TODO: Unwrap at same DOM position
+		if (this.elementIsWrapper) {
+			_destroy(this.element);
+			var wrapper = this.element;
+			wrapper.parent().append(
+				this.originalElement.css({
+					position: wrapper.css('position'),
+					width: wrapper.outerWidth(),
+					height: wrapper.outerHeight(),
+					top: wrapper.css('top'),
+					left: wrapper.css('left')
+				})
+			).end().remove();
+		}
+
+		this.originalElement.css('resize', this.originalResizeStyle);
+		_destroy(this.originalElement);
+
+	},
+
+	_mouseCapture: function(event) {
+
+		var handle = false;
+		for(var i in this.handles) {
+			if($(this.handles[i])[0] == event.target) handle = true;
+		}
+
+		return this.options.disabled || !!handle;
+
+	},
+
+	_mouseStart: function(event) {
+
+		var o = this.options, iniPos = this.element.position(), el = this.element;
+
+		this.resizing = true;
+		this.documentScroll = { top: $(document).scrollTop(), left: $(document).scrollLeft() };
+
+		// bugfix for http://dev.jquery.com/ticket/1749
+		if (el.is('.ui-draggable') || (/absolute/).test(el.css('position'))) {
+			el.css({ position: 'absolute', top: iniPos.top, left: iniPos.left });
+		}
+
+		//Opera fixing relative position
+		if ($.browser.opera && (/relative/).test(el.css('position')))
+			el.css({ position: 'relative', top: 'auto', left: 'auto' });
+
+		this._renderProxy();
+
+		var curleft = num(this.helper.css('left')), curtop = num(this.helper.css('top'));
+
+		if (o.containment) {
+			curleft += $(o.containment).scrollLeft() || 0;
+			curtop += $(o.containment).scrollTop() || 0;
+		}
+
+		//Store needed variables
+		this.offset = this.helper.offset();
+		this.position = { left: curleft, top: curtop };
+		this.size = this._helper ? { width: el.outerWidth(), height: el.outerHeight() } : { width: el.width(), height: el.height() };
+		this.originalSize = this._helper ? { width: el.outerWidth(), height: el.outerHeight() } : { width: el.width(), height: el.height() };
+		this.originalPosition = { left: curleft, top: curtop };
+		this.sizeDiff = { width: el.outerWidth() - el.width(), height: el.outerHeight() - el.height() };
+		this.originalMousePosition = { left: event.pageX, top: event.pageY };
+
+		//Aspect Ratio
+		this.aspectRatio = (typeof o.aspectRatio == 'number') ? o.aspectRatio : ((this.originalSize.width / this.originalSize.height) || 1);
+
+	    var cursor = $('.ui-resizable-' + this.axis).css('cursor');
+	    $('body').css('cursor', cursor == 'auto' ? this.axis + '-resize' : cursor);
+
+		el.addClass("ui-resizable-resizing");
+		this._propagate("start", event);
+		return true;
+	},
+
+	_mouseDrag: function(event) {
+
+		//Increase performance, avoid regex
+		var el = this.helper, o = this.options, props = {},
+			self = this, smp = this.originalMousePosition, a = this.axis;
+
+		var dx = (event.pageX-smp.left)||0, dy = (event.pageY-smp.top)||0;
+		var trigger = this._change[a];
+		if (!trigger) return false;
+
+		// Calculate the attrs that will be change
+		var data = trigger.apply(this, [event, dx, dy]), ie6 = $.browser.msie && $.browser.version < 7, csdif = this.sizeDiff;
+
+		if (this._aspectRatio || event.shiftKey)
+			data = this._updateRatio(data, event);
+
+		data = this._respectSize(data, event);
+
+		// plugins callbacks need to be called first
+		this._propagate("resize", event);
+
+		el.css({
+			top: this.position.top + "px", left: this.position.left + "px",
+			width: this.size.width + "px", height: this.size.height + "px"
+		});
+
+		if (!this._helper && this._proportionallyResizeElements.length)
+			this._proportionallyResize();
+
+		this._updateCache(data);
+
+		// calling the user callback at the end
+		this._trigger('resize', event, this.ui());
+
+		return false;
+	},
+
+	_mouseStop: function(event) {
+
+		this.resizing = false;
+		var o = this.options, self = this;
+
+		if(this._helper) {
+			var pr = this._proportionallyResizeElements, ista = pr.length && (/textarea/i).test(pr[0].nodeName),
+						soffseth = ista && $.ui.hasScroll(pr[0], 'left') /* TODO - jump height */ ? 0 : self.sizeDiff.height,
+							soffsetw = ista ? 0 : self.sizeDiff.width;
+
+			var s = { width: (self.size.width - soffsetw), height: (self.size.height - soffseth) },
+				left = (parseInt(self.element.css('left'), 10) + (self.position.left - self.originalPosition.left)) || null,
+				top = (parseInt(self.element.css('top'), 10) + (self.position.top - self.originalPosition.top)) || null;
+
+			if (!o.animate)
+				this.element.css($.extend(s, { top: top, left: left }));
+
+			self.helper.height(self.size.height);
+			self.helper.width(self.size.width);
+
+			if (this._helper && !o.animate) this._proportionallyResize();
+		}
+
+		$('body').css('cursor', 'auto');
+
+		this.element.removeClass("ui-resizable-resizing");
+
+		this._propagate("stop", event);
+
+		if (this._helper) this.helper.remove();
+		return false;
+
+	},
+
+	_updateCache: function(data) {
+		var o = this.options;
+		this.offset = this.helper.offset();
+		if (isNumber(data.left)) this.position.left = data.left;
+		if (isNumber(data.top)) this.position.top = data.top;
+		if (isNumber(data.height)) this.size.height = data.height;
+		if (isNumber(data.width)) this.size.width = data.width;
+	},
+
+	_updateRatio: function(data, event) {
+
+		var o = this.options, cpos = this.position, csize = this.size, a = this.axis;
+
+		if (data.height) data.width = (csize.height * this.aspectRatio);
+		else if (data.width) data.height = (csize.width / this.aspectRatio);
+
+		if (a == 'sw') {
+			data.left = cpos.left + (csize.width - data.width);
+			data.top = null;
+		}
+		if (a == 'nw') {
+			data.top = cpos.top + (csize.height - data.height);
+			data.left = cpos.left + (csize.width - data.width);
+		}
+
+		return data;
+	},
+
+	_respectSize: function(data, event) {
+
+		var el = this.helper, o = this.options, pRatio = this._aspectRatio || event.shiftKey, a = this.axis,
+				ismaxw = isNumber(data.width) && o.maxWidth && (o.maxWidth < data.width), ismaxh = isNumber(data.height) && o.maxHeight && (o.maxHeight < data.height),
+					isminw = isNumber(data.width) && o.minWidth && (o.minWidth > data.width), isminh = isNumber(data.height) && o.minHeight && (o.minHeight > data.height);
+
+		if (isminw) data.width = o.minWidth;
+		if (isminh) data.height = o.minHeight;
+		if (ismaxw) data.width = o.maxWidth;
+		if (ismaxh) data.height = o.maxHeight;
+
+		var dw = this.originalPosition.left + this.originalSize.width, dh = this.position.top + this.size.height;
+		var cw = /sw|nw|w/.test(a), ch = /nw|ne|n/.test(a);
+
+		if (isminw && cw) data.left = dw - o.minWidth;
+		if (ismaxw && cw) data.left = dw - o.maxWidth;
+		if (isminh && ch)	data.top = dh - o.minHeight;
+		if (ismaxh && ch)	data.top = dh - o.maxHeight;
+
+		// fixing jump error on top/left - bug #2330
+		var isNotwh = !data.width && !data.height;
+		if (isNotwh && !data.left && data.top) data.top = null;
+		else if (isNotwh && !data.top && data.left) data.left = null;
+
+		return data;
+	},
+
+	_proportionallyResize: function() {
+
+		var o = this.options;
+		if (!this._proportionallyResizeElements.length) return;
+		var element = this.helper || this.element;
+
+		for (var i=0; i < this._proportionallyResizeElements.length; i++) {
+
+			var prel = this._proportionallyResizeElements[i];
+
+			if (!this.borderDif) {
+				var b = [prel.css('borderTopWidth'), prel.css('borderRightWidth'), prel.css('borderBottomWidth'), prel.css('borderLeftWidth')],
+					p = [prel.css('paddingTop'), prel.css('paddingRight'), prel.css('paddingBottom'), prel.css('paddingLeft')];
+
+				this.borderDif = $.map(b, function(v, i) {
+					var border = parseInt(v,10)||0, padding = parseInt(p[i],10)||0;
+					return border + padding;
+				});
+			}
+
+			if ($.browser.msie && !(!($(element).is(':hidden') || $(element).parents(':hidden').length)))
+				continue;
+
+			prel.css({
+				height: (element.height() - this.borderDif[0] - this.borderDif[2]) || 0,
+				width: (element.width() - this.borderDif[1] - this.borderDif[3]) || 0
+			});
+
+		};
+
+	},
+
+	_renderProxy: function() {
+
+		var el = this.element, o = this.options;
+		this.elementOffset = el.offset();
+
+		if(this._helper) {
+
+			this.helper = this.helper || $('<div style="overflow:hidden;"></div>');
+
+			// fix ie6 offset TODO: This seems broken
+			var ie6 = $.browser.msie && $.browser.version < 7, ie6offset = (ie6 ? 1 : 0),
+			pxyoffset = ( ie6 ? 2 : -1 );
+
+			this.helper.addClass(this._helper).css({
+				width: this.element.outerWidth() + pxyoffset,
+				height: this.element.outerHeight() + pxyoffset,
+				position: 'absolute',
+				left: this.elementOffset.left - ie6offset +'px',
+				top: this.elementOffset.top - ie6offset +'px',
+				zIndex: ++o.zIndex //TODO: Don't modify option
+			});
+
+			this.helper
+				.appendTo("body")
+				.disableSelection();
+
+		} else {
+			this.helper = this.element;
+		}
+
+	},
+
+	_change: {
+		e: function(event, dx, dy) {
+			return { width: this.originalSize.width + dx };
+		},
+		w: function(event, dx, dy) {
+			var o = this.options, cs = this.originalSize, sp = this.originalPosition;
+			return { left: sp.left + dx, width: cs.width - dx };
+		},
+		n: function(event, dx, dy) {
+			var o = this.options, cs = this.originalSize, sp = this.originalPosition;
+			return { top: sp.top + dy, height: cs.height - dy };
+		},
+		s: function(event, dx, dy) {
+			return { height: this.originalSize.height + dy };
+		},
+		se: function(event, dx, dy) {
+			return $.extend(this._change.s.apply(this, arguments), this._change.e.apply(this, [event, dx, dy]));
+		},
+		sw: function(event, dx, dy) {
+			return $.extend(this._change.s.apply(this, arguments), this._change.w.apply(this, [event, dx, dy]));
+		},
+		ne: function(event, dx, dy) {
+			return $.extend(this._change.n.apply(this, arguments), this._change.e.apply(this, [event, dx, dy]));
+		},
+		nw: function(event, dx, dy) {
+			return $.extend(this._change.n.apply(this, arguments), this._change.w.apply(this, [event, dx, dy]));
+		}
+	},
+
+	_propagate: function(n, event) {
+		$.ui.plugin.call(this, n, [event, this.ui()]);
+		(n != "resize" && this._trigger(n, event, this.ui()));
+	},
+
+	plugins: {},
+
+	ui: function() {
+		return {
+			originalElement: this.originalElement,
+			element: this.element,
+			helper: this.helper,
+			position: this.position,
+			size: this.size,
+			originalSize: this.originalSize,
+			originalPosition: this.originalPosition
+		};
+	}
+
+}));
+
+$.extend($.ui.resizable, {
+	version: "1.7.2",
+	eventPrefix: "resize",
+	defaults: {
+		alsoResize: false,
+		animate: false,
+		animateDuration: "slow",
+		animateEasing: "swing",
+		aspectRatio: false,
+		autoHide: false,
+		cancel: ":input,option",
+		containment: false,
+		delay: 0,
+		distance: 1,
+		ghost: false,
+		grid: false,
+		handles: "e,s,se",
+		helper: false,
+		maxHeight: null,
+		maxWidth: null,
+		minHeight: 10,
+		minWidth: 10,
+		zIndex: 1000
+	}
+});
+
+/*
+ * Resizable Extensions
+ */
+
+$.ui.plugin.add("resizable", "alsoResize", {
+
+	start: function(event, ui) {
+
+		var self = $(this).data("resizable"), o = self.options;
+
+		_store = function(exp) {
+			$(exp).each(function() {
+				$(this).data("resizable-alsoresize", {
+					width: parseInt($(this).width(), 10), height: parseInt($(this).height(), 10),
+					left: parseInt($(this).css('left'), 10), top: parseInt($(this).css('top'), 10)
+				});
+			});
+		};
+
+		if (typeof(o.alsoResize) == 'object' && !o.alsoResize.parentNode) {
+			if (o.alsoResize.length) { o.alsoResize = o.alsoResize[0];	_store(o.alsoResize); }
+			else { $.each(o.alsoResize, function(exp, c) { _store(exp); }); }
+		}else{
+			_store(o.alsoResize);
+		}
+	},
+
+	resize: function(event, ui){
+		var self = $(this).data("resizable"), o = self.options, os = self.originalSize, op = self.originalPosition;
+
+		var delta = {
+			height: (self.size.height - os.height) || 0, width: (self.size.width - os.width) || 0,
+			top: (self.position.top - op.top) || 0, left: (self.position.left - op.left) || 0
+		},
+
+		_alsoResize = function(exp, c) {
+			$(exp).each(function() {
+				var el = $(this), start = $(this).data("resizable-alsoresize"), style = {}, css = c && c.length ? c : ['width', 'height', 'top', 'left'];
+
+				$.each(css || ['width', 'height', 'top', 'left'], function(i, prop) {
+					var sum = (start[prop]||0) + (delta[prop]||0);
+					if (sum && sum >= 0)
+						style[prop] = sum || null;
+				});
+
+				//Opera fixing relative position
+				if (/relative/.test(el.css('position')) && $.browser.opera) {
+					self._revertToRelativePosition = true;
+					el.css({ position: 'absolute', top: 'auto', left: 'auto' });
+				}
+
+				el.css(style);
+			});
+		};
+
+		if (typeof(o.alsoResize) == 'object' && !o.alsoResize.nodeType) {
+			$.each(o.alsoResize, function(exp, c) { _alsoResize(exp, c); });
+		}else{
+			_alsoResize(o.alsoResize);
+		}
+	},
+
+	stop: function(event, ui){
+		var self = $(this).data("resizable");
+
+		//Opera fixing relative position
+		if (self._revertToRelativePosition && $.browser.opera) {
+			self._revertToRelativePosition = false;
+			el.css({ position: 'relative' });
+		}
+
+		$(this).removeData("resizable-alsoresize-start");
+	}
+});
+
+$.ui.plugin.add("resizable", "animate", {
+
+	stop: function(event, ui) {
+		var self = $(this).data("resizable"), o = self.options;
+
+		var pr = self._proportionallyResizeElements, ista = pr.length && (/textarea/i).test(pr[0].nodeName),
+					soffseth = ista && $.ui.hasScroll(pr[0], 'left') /* TODO - jump height */ ? 0 : self.sizeDiff.height,
+						soffsetw = ista ? 0 : self.sizeDiff.width;
+
+		var style = { width: (self.size.width - soffsetw), height: (self.size.height - soffseth) },
+					left = (parseInt(self.element.css('left'), 10) + (self.position.left - self.originalPosition.left)) || null,
+						top = (parseInt(self.element.css('top'), 10) + (self.position.top - self.originalPosition.top)) || null;
+
+		self.element.animate(
+			$.extend(style, top && left ? { top: top, left: left } : {}), {
+				duration: o.animateDuration,
+				easing: o.animateEasing,
+				step: function() {
+
+					var data = {
+						width: parseInt(self.element.css('width'), 10),
+						height: parseInt(self.element.css('height'), 10),
+						top: parseInt(self.element.css('top'), 10),
+						left: parseInt(self.element.css('left'), 10)
+					};
+
+					if (pr && pr.length) $(pr[0]).css({ width: data.width, height: data.height });
+
+					// propagating resize, and updating values for each animation step
+					self._updateCache(data);
+					self._propagate("resize", event);
+
+				}
+			}
+		);
+	}
+
+});
+
+$.ui.plugin.add("resizable", "containment", {
+
+	start: function(event, ui) {
+		var self = $(this).data("resizable"), o = self.options, el = self.element;
+		var oc = o.containment,	ce = (oc instanceof $) ? oc.get(0) : (/parent/.test(oc)) ? el.parent().get(0) : oc;
+		if (!ce) return;
+
+		self.containerElement = $(ce);
+
+		if (/document/.test(oc) || oc == document) {
+			self.containerOffset = { left: 0, top: 0 };
+			self.containerPosition = { left: 0, top: 0 };
+
+			self.parentData = {
+				element: $(document), left: 0, top: 0,
+				width: $(document).width(), height: $(document).height() || document.body.parentNode.scrollHeight
+			};
+		}
+
+		// i'm a node, so compute top, left, right, bottom
+		else {
+			var element = $(ce), p = [];
+			$([ "Top", "Right", "Left", "Bottom" ]).each(function(i, name) { p[i] = num(element.css("padding" + name)); });
+
+			self.containerOffset = element.offset();
+			self.containerPosition = element.position();
+			self.containerSize = { height: (element.innerHeight() - p[3]), width: (element.innerWidth() - p[1]) };
+
+			var co = self.containerOffset, ch = self.containerSize.height,	cw = self.containerSize.width,
+						width = ($.ui.hasScroll(ce, "left") ? ce.scrollWidth : cw ), height = ($.ui.hasScroll(ce) ? ce.scrollHeight : ch);
+
+			self.parentData = {
+				element: ce, left: co.left, top: co.top, width: width, height: height
+			};
+		}
+	},
+
+	resize: function(event, ui) {
+		var self = $(this).data("resizable"), o = self.options,
+				ps = self.containerSize, co = self.containerOffset, cs = self.size, cp = self.position,
+				pRatio = self._aspectRatio || event.shiftKey, cop = { top:0, left:0 }, ce = self.containerElement;
+
+		if (ce[0] != document && (/static/).test(ce.css('position'))) cop = co;
+
+		if (cp.left < (self._helper ? co.left : 0)) {
+			self.size.width = self.size.width + (self._helper ? (self.position.left - co.left) : (self.position.left - cop.left));
+			if (pRatio) self.size.height = self.size.width / o.aspectRatio;
+			self.position.left = o.helper ? co.left : 0;
+		}
+
+		if (cp.top < (self._helper ? co.top : 0)) {
+			self.size.height = self.size.height + (self._helper ? (self.position.top - co.top) : self.position.top);
+			if (pRatio) self.size.width = self.size.height * o.aspectRatio;
+			self.position.top = self._helper ? co.top : 0;
+		}
+
+		self.offset.left = self.parentData.left+self.position.left;
+		self.offset.top = self.parentData.top+self.position.top;
+
+		var woset = Math.abs( (self._helper ? self.offset.left - cop.left : (self.offset.left - cop.left)) + self.sizeDiff.width ),
+					hoset = Math.abs( (self._helper ? self.offset.top - cop.top : (self.offset.top - co.top)) + self.sizeDiff.height );
+
+		var isParent = self.containerElement.get(0) == self.element.parent().get(0),
+		    isOffsetRelative = /relative|absolute/.test(self.containerElement.css('position'));
+
+		if(isParent && isOffsetRelative) woset -= self.parentData.left;
+
+		if (woset + self.size.width >= self.parentData.width) {
+			self.size.width = self.parentData.width - woset;
+			if (pRatio) self.size.height = self.size.width / self.aspectRatio;
+		}
+
+		if (hoset + self.size.height >= self.parentData.height) {
+			self.size.height = self.parentData.height - hoset;
+			if (pRatio) self.size.width = self.size.height * self.aspectRatio;
+		}
+	},
+
+	stop: function(event, ui){
+		var self = $(this).data("resizable"), o = self.options, cp = self.position,
+				co = self.containerOffset, cop = self.containerPosition, ce = self.containerElement;
+
+		var helper = $(self.helper), ho = helper.offset(), w = helper.outerWidth() - self.sizeDiff.width, h = helper.outerHeight() - self.sizeDiff.height;
+
+		if (self._helper && !o.animate && (/relative/).test(ce.css('position')))
+			$(this).css({ left: ho.left - cop.left - co.left, width: w, height: h });
+
+		if (self._helper && !o.animate && (/static/).test(ce.css('position')))
+			$(this).css({ left: ho.left - cop.left - co.left, width: w, height: h });
+
+	}
+});
+
+$.ui.plugin.add("resizable", "ghost", {
+
+	start: function(event, ui) {
+
+		var self = $(this).data("resizable"), o = self.options, cs = self.size;
+
+		self.ghost = self.originalElement.clone();
+		self.ghost
+			.css({ opacity: .25, display: 'block', position: 'relative', height: cs.height, width: cs.width, margin: 0, left: 0, top: 0 })
+			.addClass('ui-resizable-ghost')
+			.addClass(typeof o.ghost == 'string' ? o.ghost : '');
+
+		self.ghost.appendTo(self.helper);
+
+	},
+
+	resize: function(event, ui){
+		var self = $(this).data("resizable"), o = self.options;
+		if (self.ghost) self.ghost.css({ position: 'relative', height: self.size.height, width: self.size.width });
+	},
+
+	stop: function(event, ui){
+		var self = $(this).data("resizable"), o = self.options;
+		if (self.ghost && self.helper) self.helper.get(0).removeChild(self.ghost.get(0));
+	}
+
+});
+
+$.ui.plugin.add("resizable", "grid", {
+
+	resize: function(event, ui) {
+		var self = $(this).data("resizable"), o = self.options, cs = self.size, os = self.originalSize, op = self.originalPosition, a = self.axis, ratio = o._aspectRatio || event.shiftKey;
+		o.grid = typeof o.grid == "number" ? [o.grid, o.grid] : o.grid;
+		var ox = Math.round((cs.width - os.width) / (o.grid[0]||1)) * (o.grid[0]||1), oy = Math.round((cs.height - os.height) / (o.grid[1]||1)) * (o.grid[1]||1);
+
+		if (/^(se|s|e)$/.test(a)) {
+			self.size.width = os.width + ox;
+			self.size.height = os.height + oy;
+		}
+		else if (/^(ne)$/.test(a)) {
+			self.size.width = os.width + ox;
+			self.size.height = os.height + oy;
+			self.position.top = op.top - oy;
+		}
+		else if (/^(sw)$/.test(a)) {
+			self.size.width = os.width + ox;
+			self.size.height = os.height + oy;
+			self.position.left = op.left - ox;
+		}
+		else {
+			self.size.width = os.width + ox;
+			self.size.height = os.height + oy;
+			self.position.top = op.top - oy;
+			self.position.left = op.left - ox;
+		}
+	}
+
+});
+
+var num = function(v) {
+	return parseInt(v, 10) || 0;
+};
+
+var isNumber = function(value) {
+	return !isNaN(parseInt(value, 10));
+};
+
+})(jQuery);

+ 577 - 0
src/main.js

@@ -0,0 +1,577 @@
+
+var fc = $.fullCalendar = {};
+var views = fc.views = {};
+
+
+/* Defaults
+-----------------------------------------------------------------------------*/
+
+var defaults = {
+
+	// display
+	defaultView: 'month',
+	aspectRatio: 1.35,
+	header: {
+		left: 'prev,next today',
+		center: 'title',
+		right: 'month,basicWeek,basicDay'
+	},
+	
+	// event ajax
+	startParam: 'start',
+	endParam: 'end',
+	cacheParam: '_',
+	
+	// time formats
+	eventTimeFormat: 'h(:mm)t',
+	titleFormat: {
+		month: 'MMMM yyyy',
+		week: "MMM d[ yyyy]{ '&#8212;' [MMM ]d yyyy}",
+		day: 'dddd, MMM d, yyyy'
+	},
+	columnFormat: {
+		month: 'ddd',
+		week: 'ddd M/d',
+		day: 'dddd M/d'
+	},
+	
+	// regional
+	isRTL: false,
+	weekStart: 0,
+	monthNames: ['January','February','March','April','May','June','July','August','September','October','November','December'],
+	monthNamesShort: ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'],
+	dayNames: ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'],
+	dayNamesShort: ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'],
+	buttonText: {
+		prev: '&#9668;',
+		next: '&#9658;',
+		today: 'today',
+		month: 'month',
+		week: 'week',
+		day: 'day'
+	},
+	
+	// jquery-ui theming
+	theme: false,
+	buttonIcons: {
+		prev: 'circle-triangle-w',
+		next: 'circle-triangle-e'
+	}
+};
+
+// right-to-left defaults
+var rtlDefaults = {
+	header: {
+		left: 'basicDay,basicWeek,month',
+		center: 'title',
+		right: 'today next,prev'
+	},
+	buttonText: {
+		prev: '&#9658;',
+		next: '&#9668;'
+	}
+};
+
+// function for adding/overriding defaults
+var setDefaults = fc.setDefaults = function(d) {
+	$.extend(true, defaults, d);
+}
+
+
+
+/* .fullCalendar jQuery function
+-----------------------------------------------------------------------------*/
+
+$.fn.fullCalendar = function(options) {
+
+	// method calling
+	if (typeof options == 'string') {
+		var args = Array.prototype.slice.call(arguments, 1), res;
+		this.each(function() {
+			var r = $.data(this, 'fullCalendar')[options].apply(this, args);
+			if (typeof res == 'undefined') res = r;
+		});
+		if (typeof res != 'undefined') {
+			return res;
+		}
+		return this;
+	}
+
+	// pluck the 'events' and 'eventSources' options
+	var eventSources = options.eventSources || [];
+	delete options.eventSources;
+	if (options.events) {
+		eventSources.push(options.events);
+		delete options.event;
+	}
+	
+	// first event source reserved for 'sticky' events
+	eventSources.unshift([]);
+	
+	// initialize options
+	options = $.extend(true, {},
+		defaults,
+		(options.isRTL || typeof options.isRTL == 'undefined' && defaults.isRTL) ? rtlDefaults : {},
+		options
+	);
+	var tm = options.theme ? 'ui' : 'fc';
+	
+	
+	this.each(function() {
+	
+	
+		/* Instance Initialization
+		-----------------------------------------------------------------------------*/
+		
+		// element
+		var _element = this,
+			element = $(this).addClass('fc'),
+			content = $("<div class='fc-content " + tm + "-widget-content'/>").appendTo(this);
+		if (options.isRTL) {
+			element.addClass('fc-rtl');
+		}
+		if (options.theme) {
+			element.addClass('ui-widget');
+		}
+		
+		// view managing
+		var date = new Date(),
+			viewName, view, // the current view
+			prevView,
+			viewInstances = {};
+		if (options.year) {
+			date.setYear(options.year);
+		}
+		if (options.month) {
+			date.setMonth(options.month);
+		}
+		if (options.date) {
+			date.setDate(options.date);
+		}
+		
+		
+		
+		/* View Rendering
+		-----------------------------------------------------------------------------*/
+		
+		function switchView(v) {
+			if (v != viewName) {
+				prevView = view;
+				if (viewInstances[v]) {
+					(view = viewInstances[v]).element.show();
+				}else{
+					view = viewInstances[v] = $.fullCalendar.views[v](
+						$("<div class='fc-view fc-view-" + v + "'/>").appendTo(content),
+						options);
+				}
+				if (prevView) {
+					prevView.element.hide();
+					if (prevView.eventsChanged) {
+						eventsDirtyExcept(prevView);
+						prevView.eventsChanged = false;
+					}
+				}
+				if (header) {
+					header.find('div.fc-button-' + viewName).removeClass(tm + '-state-active');
+					header.find('div.fc-button-' + v).addClass(tm + '-state-active');
+				}
+				view.name = viewName = v;
+				render();
+			}
+		}
+		
+		function render(inc) {
+			if (inc || !view.date || +view.date != +date) {
+				ignoreResizes = true;
+				view.render(date, inc || 0, function(callback) {
+					if (!eventStart || view.visStart < eventStart || view.visEnd > eventEnd) {
+						fetchEvents(callback);
+					}else{
+						callback(events);
+					}
+				});
+				ignoreResizes = false;
+				view.date = cloneDate(date);
+				if (header) {
+					var today = new Date();
+					if (today >= view.start && today < view.end) {
+						header.find('div.fc-button-today').addClass(tm + '-state-disabled');
+					}else{
+						header.find('div.fc-button-today').removeClass(tm + '-state-disabled');
+					}
+				}
+			}
+			else if (view.eventsDirty) {
+				view.rerenderEvents();
+			}
+			if (header) {
+				header.find('h2.fc-header-title').html(view.title);
+			}
+			view.eventsDirty = false;
+			view.trigger('viewDisplay', _element, date);
+		}
+		
+		function eventsDirtyExcept(exceptView) {
+			$.each(viewInstances, function() {
+				if (this != exceptView) {
+					this.eventsDirty = true;
+				}
+			});
+		}
+		
+		function eventsChanged() {
+			view.clearEvents();
+			view.renderEvents(events);
+			eventsDirtyExcept(view);
+		}
+		
+		
+		
+		/* Event Sources and Fetching
+		-----------------------------------------------------------------------------*/
+		
+		var events = [],
+			eventStart, eventEnd;
+		
+		// Fetch from ALL sources. Clear 'events' array and populate
+		function fetchEvents(callback) {
+			events = [];
+			eventStart = cloneDate(view.visStart);
+			eventEnd = cloneDate(view.visEnd);
+			var queued = eventSources.length,
+				sourceDone = function() {
+					if (--queued == 0) {
+						if (callback) {
+							callback(events);
+						}
+					}
+				}, i=0;
+			for (; i<eventSources.length; i++) {
+				fetchEventSource(eventSources[i], sourceDone);
+			}
+		}
+		
+		// Fetch from a particular source. Append to the 'events' array
+		function fetchEventSource(src, callback) {
+			var prevDate = cloneDate(date),
+				reportEvents = function(a, dontPopLoading) {
+					if (+date == +prevDate) {
+						for (var i=0; i<a.length; i++) {
+							normalizeEvent(a[i]);
+							a[i].source = src;
+						}
+						events = events.concat(a);
+					}
+					if (!dontPopLoading) {
+						popLoading();
+					}
+					if (callback) {
+						callback(a);
+					}
+				};
+			if (typeof src == 'string') {
+				var params = {};
+				params[options.startParam] = Math.round(eventStart.getTime() / 1000);
+				params[options.endParam] = Math.round(eventEnd.getTime() / 1000);
+				params[options.cacheParam] = (new Date()).getTime();
+				pushLoading();
+				$.getJSON(src, params, reportEvents);
+			}
+			else if ($.isFunction(src)) {
+				pushLoading();
+				src(cloneDate(eventStart), cloneDate(eventEnd), reportEvents);
+			}
+			else {
+				reportEvents(src, true); // src is an array
+			}
+		}
+		
+		
+		
+		/* Loading State
+		-----------------------------------------------------------------------------*/
+		
+		var loadingLevel = 0;
+		
+		function pushLoading() {
+			if (!loadingLevel++ && options.loading) {
+				options.loading(true);
+			}
+		}
+		
+		function popLoading() {
+			if (!--loadingLevel && options.loading) {
+				options.loading(false);
+			}
+		}
+		
+		
+		
+		/* Public Methods
+		-----------------------------------------------------------------------------*/
+		
+		var publicMethods = {
+		
+			//
+			// Navigation
+			//
+			
+			prev: function() {
+				render(-1);
+			},
+			
+			next: function() {
+				render(1);
+			},
+			
+			today: function() {
+				date = new Date();
+				render();
+			},
+			
+			//
+			// Event Rendering
+			//
+			
+			renderEvent: function(event, stick) {
+				if (typeof event != 'object') {
+					event = eventsByID(event)[0]; // assumed to be ID
+					if (!event) return;
+				}else{
+					normalizeEvent(event);
+				}
+				var startDelta = event.start - event._start,
+					msDuration = event.end - event.start,
+					i, len = events.length, e,
+					found = false;
+				for (i=0; i<len; i++) {
+					e = events[i];
+					if (e._id == event._id) {
+						if (e != event) {
+							e._start = cloneDate(e.start = new Date(+e.start + startDelta));
+							e.end = new Date(+e.start + msDuration);
+							e.title = event.title;
+							e.hasTime = event.hasTime;
+							if (stick && !event.source) {
+								(event.source = eventSources[0]).push(event);
+							}
+						}
+						found = true;
+					}
+				}
+				if (!found) {
+					events.push(event);
+				}
+				eventsChanged();
+			},
+			
+			removeEvent: function(id) {
+				if (typeof id == 'object') {
+					id = id._id;
+				}else{
+					id += '';
+				}
+				removeEvents(function(e) {
+					return e._id != id;
+				});
+			},
+			
+			clientEvents: function(filter) {
+				if (filter) {
+					return filterArray(events, filter);
+				}else{
+					return events;
+				}
+			},
+			
+			clientEventsByID: eventsByID,
+			removeEvents: removeEvents,
+			
+			//
+			// Event Source
+			//
+		
+			addEventSource: function(src) {
+				eventSources.push(src);
+				fetchEventSource(src, function() {
+					eventsChanged();
+				});
+			},
+		
+			removeEventSource: function(source) {
+				eventSources = filterArray(eventSources, function(src) {
+					return src != source;
+				});
+				// remove all client events from that source
+				events = filterArray(events, function(e) {
+					return e.source != source;
+				});
+				eventsChanged();
+			}
+			
+		};
+		
+		$.data(this, 'fullCalendar', publicMethods);
+		
+		function eventsByID(id) {
+			id += '';
+			return filterArray(events, function(e) {
+				e._id == id;
+			});
+		}
+		
+		function removeEvents(filter) {
+			var i, len = eventSources.length;
+			if (filter) {
+				events = filterArray(events, function(e) {
+					return !filter(e);
+				});
+				// remove events from array sources
+				for (i=0; i<len; i++) {
+					if (typeof eventSources[i] == 'object') {
+						eventSources[i] = filterArray(eventSources[i], function(e) {
+							return !filter(e);
+						});
+					}
+				}
+			}else{
+				events = [];
+				// clear all array sources
+				for (i=0; i<len; i++) {
+					if (typeof eventSources[i] == 'object') {
+						eventSources[i] = [];
+					}
+				}
+			}
+			eventsChanged();
+		}
+		
+		
+		
+		/* Header
+		-----------------------------------------------------------------------------*/
+		
+		var header,
+			sections = options.header;
+		if (sections) {
+			header = $("<table class='fc-header'/>")
+				.append($("<tr/>")
+					.append($("<td class='fc-header-left'/>").append(buildSection(sections.left)))
+					.append($("<td class='fc-header-center'/>").append(buildSection(sections.center)))
+					.append($("<td class='fc-header-right'/>").append(buildSection(sections.right))))
+				.prependTo(element);
+		}
+		function buildSection(buttonStr) {
+			if (buttonStr) {
+				var tr = $("<tr/>"),
+					prevTitle = false;
+				$.each(buttonStr.split(' '), function(i) {
+					if (i > 0) {
+						tr.append("<td><span class='fc-header-space'/></td>");
+					}
+					$.each(this.split(','), function(j) {
+						var buttonName = this,
+							buttonNameShort = this.replace(/^(basic|agenda)/, '').toLowerCase();
+						if (buttonName == 'title') {
+							tr.find('> :last div').addClass(tm + '-corner-right');
+							tr.append("<td><h2 class='fc-header-title'/></td>");
+							prevTitle = true;
+						}else{
+							var button,
+								icon = options.theme ? options.buttonIcons[buttonNameShort] : null,
+								text = options.buttonText[buttonNameShort];
+							if (icon) {
+								button = $("<div class='fc-button-" + buttonName + " ui-state-default'>" +
+									"<a><span class='ui-icon ui-icon-" + icon + "'/></a></div>");
+							}
+							else if (text) {
+								button = $("<div class='fc-button-" + buttonName + " " + tm + "-state-default'>" +
+									"<a><span>" + text + "</span></a></div>");
+							}
+							if (button) {
+								button
+									.mousedown(function() {
+										button.addClass(tm + '-state-down');
+									})
+									.mouseup(function() {
+										button.removeClass(tm + '-state-down');
+									})
+									.hover(function() {
+										button.addClass(tm + '-state-hover');
+									},
+									function() {
+										button.removeClass(tm + '-state-hover')
+											.removeClass(tm + '-state-down');
+									})
+									.appendTo($("<td/>").appendTo(tr));
+								if (publicMethods[buttonNameShort]) {
+									button.click(publicMethods[buttonNameShort]);
+								}
+								else if (views[buttonName]) {
+									button.click(function() {
+										switchView(buttonName);
+									});
+								}
+								if (j == 0 || prevTitle) {
+									button.addClass(tm + '-corner-left');
+								}else{
+									button.addClass(tm + '-no-left');
+								}
+								prevTitle = false;
+							}
+						}
+					});
+					tr.find('> :last div').addClass(tm + '-corner-right');
+				});
+				return $("<table/>").append(tr);
+			}
+		}
+		
+		
+		
+		/* Resizing
+		-----------------------------------------------------------------------------*/
+		
+		var elementWidth,
+			ignoreResizes = false,
+			resizeCnt = 0;
+		
+		$(window).resize(function() {
+			if (!ignoreResizes) {
+				var rcnt = ++resizeCnt; // add a delay
+				setTimeout(function() {
+					if (rcnt == resizeCnt) {
+						var newWidth = element.width();
+						if (newWidth != elementWidth) {
+							elementWidth = newWidth;
+							view.updateSize();
+							view.rerenderEvents(true);
+							view.trigger('windowResize', _element);
+						}
+					}
+				}, 200);
+			}
+		});
+		
+		
+		// let's begin...
+		switchView(options.defaultView);
+		elementWidth = element.width();
+	
+	});
+	
+};
+
+
+
+/* Important Event Utilities
+-----------------------------------------------------------------------------*/
+
+var fakeID = 0;
+
+function normalizeEvent(event) {
+	event._id = event._id || (typeof event.id == 'undefined' ? '_fc' + fakeID++ : event.id + '');
+	event._start = cloneDate(event.start = parseDate(event.start));
+	event.end = parseDate(event.end);
+}
+

+ 1 - 0
src/misc/foot.txt

@@ -0,0 +1 @@
+})(jQuery);

+ 17 - 0
src/misc/head.txt

@@ -0,0 +1,17 @@
+/*!
+ * FullCalendar
+ * http://arshaw.com/fullcalendar/
+ *
+ * use fullcalendar.css for basic styling
+ * requires jQuery UI core and draggables ONLY if you plan to do drag & drop
+ *
+ * Copyright (c) 2009 Adam Shaw
+ * Dual licensed under the MIT and GPL licenses:
+ *   http://www.opensource.org/licenses/mit-license.php
+ *   http://www.gnu.org/licenses/gpl.html
+ *
+ * Date:
+ * Revision:
+ */
+ 
+(function($) {

+ 332 - 0
src/util.js

@@ -0,0 +1,332 @@
+
+/* Date Math
+-----------------------------------------------------------------------------*/
+
+var DAY_MS = 86400000;
+
+function addMonths(d, n, keepTime) {
+	d.setMonth(d.getMonth() + n);
+	if (keepTime) return d;
+	return clearTime(d);
+}
+
+function addYears(d, n, keepTime) {
+	d.setFullYear(d.getFullYear() + n);
+	if (keepTime) return d;
+	return clearTime(d);
+}
+
+function addDays(d, n, keepTime) {
+	d.setDate(d.getDate() + n);
+	if (keepTime) return d;
+	return clearTime(d);
+}
+
+function addMinutes(d, n) {
+	d.setMinutes(d.getMinutes() + n);
+	return d;
+}
+
+function clearTime(d) {
+	d.setHours(0);
+	d.setMinutes(0);
+	d.setSeconds(0); 
+	d.setMilliseconds(0);
+	return d;
+}
+
+function cloneDate(d, dontKeepTime) {
+	if (dontKeepTime) {
+		return clearTime(new Date(+d));
+	}else{
+		return new Date(+d);
+	}
+}
+
+
+
+/* Date Parsing
+-----------------------------------------------------------------------------*/
+
+var parseDate = fc.parseDate = function(s) {
+	if (typeof s == 'object')
+		return s; // already a Date object
+	if (typeof s == 'undefined')
+		return null;
+	if (typeof s == 'number')
+		return new Date(s * 1000);
+	return parseISO8601(s, true) ||
+		   Date.parse(s) ||
+		   new Date(parseInt(s) * 1000);
+}
+
+var parseISO8601 = fc.parseISO8601 = function(s, ignoreTimezone) {
+	// derived from http://delete.me.uk/2005/03/iso8601.html
+	var regexp = "([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})))?)?)?)?";
+	var d = s.match(new RegExp(regexp));
+	if (!d) return null;
+	var offset = 0;
+	var date = new Date(d[1], 0, 1);
+	if (d[3]) { date.setMonth(d[3] - 1); }
+	if (d[5]) { date.setDate(d[5]); }
+	if (d[7]) { date.setHours(d[7]); }
+	if (d[8]) { date.setMinutes(d[8]); }
+	if (d[10]) { date.setSeconds(d[10]); }
+	if (d[12]) { date.setMilliseconds(Number("0." + d[12]) * 1000); }
+	if (!ignoreTimezone) {
+		if (d[14]) {
+			offset = (Number(d[16]) * 60) + Number(d[17]);
+			offset *= ((d[15] == '-') ? 1 : -1);
+		}
+		offset -= date.getTimezoneOffset();
+	}
+	return new Date(Number(date) + (offset * 60 * 1000));
+}
+
+
+
+/* Date Formatting
+-----------------------------------------------------------------------------*/
+
+var formatDate = fc.formatDate = function(date, format, options) {
+	return formatDates(date, null, format, options);
+}
+
+var formatDates = fc.formatDates = function(date1, date2, format, options) {
+	options = options || defaults;
+	var date = date1,
+		otherDate = date2,
+		i, len = format.length, c,
+		i2, formatter,
+		res = '';
+	for (i=0; i<len; i++) {
+		c = format.charAt(i);
+		if (c == "'") {
+			for (i2=i+1; i2<len; i2++) {
+				if (format.charAt(i2) == "'") {
+					if (date) {
+						if (i2 == i+1) {
+							res += "'";
+						}else{
+							res += format.substring(i+1, i2);
+						}
+						i = i2;
+					}
+					break;
+				}
+			}
+		}
+		else if (c == '(') {
+			for (i2=i+1; i2<len; i2++) {
+				if (format.charAt(i2) == ')') {
+					var subres = formatDate(date, format.substring(i+1, i2), options);
+					if (parseInt(subres.replace(/\D/, ''))) {
+						res += subres;
+					}
+					i = i2;
+					break;
+				}
+			}
+		}
+		else if (c == '[') {
+			for (i2=i+1; i2<len; i2++) {
+				if (format.charAt(i2) == ']') {
+					var subformat = format.substring(i+1, i2);
+					var subres = formatDate(date, subformat, options);
+					if (subres != formatDate(otherDate, subformat, options)) {
+						res += subres;
+					}
+					i = i2;
+					break;
+				}
+			}
+		}
+		else if (c == '{') {
+			date = date2;
+			otherDate = date1;
+		}
+		else if (c == '}') {
+			date = date1;
+			otherDate = date2;
+		}
+		else {
+			for (i2=len; i2>i; i2--) {
+				if (formatter = dateFormatters[format.substring(i, i2)]) {
+					if (date) {
+						res += formatter(date, options);
+					}
+					i = i2 - 1;
+					break;
+				}
+			}
+			if (i2 == i) {
+				if (date) {
+					res += c;
+				}
+			}
+		}
+	}
+	return res;
+}
+
+var dateFormatters = {
+	s	: function(d)	{ return d.getSeconds() },
+	ss	: function(d)	{ return zeroPad(d.getSeconds()) },
+	m	: function(d)	{ return d.getMinutes() },
+	mm	: function(d)	{ return zeroPad(d.getMinutes()) },
+	h	: function(d)	{ return d.getHours() % 12 || 12 },
+	hh	: function(d)	{ return zeroPad(d.getHours() % 12 || 12) },
+	H	: function(d)	{ return d.getHours() },
+	HH	: function(d)	{ return zeroPad(d.getHours()) },
+	d	: function(d)	{ return d.getDate() },
+	dd	: function(d)	{ return zeroPad(d.getDate()) },
+	ddd	: function(d,o)	{ return o.dayNamesShort[d.getDay()] },
+	dddd: function(d,o)	{ return o.dayNames[d.getDay()] },
+	M	: function(d)	{ return d.getMonth() + 1 },
+	MM	: function(d)	{ return zeroPad(d.getMonth() + 1) },
+	MMM	: function(d,o)	{ return o.monthNamesShort[d.getMonth()] },
+	MMMM: function(d,o)	{ return o.monthNames[d.getMonth()] },
+	yy	: function(d)	{ return (d.getFullYear()+'').substring(2) },
+	yyyy: function(d)	{ return d.getFullYear() },
+	t	: function(d)	{ return d.getHours() < 12 ? 'a' : 'p' },
+	tt	: function(d)	{ return d.getHours() < 12 ? 'am' : 'pm' },
+	T	: function(d)	{ return d.getHours() < 12 ? 'A' : 'P' },
+	TT	: function(d)	{ return d.getHours() < 12 ? 'AM' : 'PM' },
+	u	: function(d)	{ return formatDate(d, "yyyy-MM-dd'T'HH:mm:ss'Z'") },
+	S	: function(d)	{
+		var date = d.getDate();
+		if (date > 10 && date < 20) return 'th';
+		return ['st', 'nd', 'rd'][date%10-1] || 'th';
+	},
+};
+
+
+
+/* Element Dimensions
+-----------------------------------------------------------------------------*/
+
+function setOuterWidth(element, width, includeMargins) {
+	element.each(function() {
+		var e = $(this);
+		var w = width - (
+			(parseInt(e.css('border-left-width')) || 0) +
+			(parseInt(e.css('padding-left')) || 0) +
+			(parseInt(e.css('padding-right')) || 0) +
+			(parseInt(e.css('border-right-width')) || 0));
+		if (includeMargins) {
+			w -=
+				(parseInt(e.css('margin-left')) || 0) +
+				(parseInt(e.css('margin-right')) || 0);
+		}
+		e.width(w);
+	});
+}
+
+function setOuterHeight(element, height, includeMargins) {
+	element.each(function() {
+		var e = $(this);
+		var h = height - (
+			(parseInt(e.css('border-top-width')) || 0) +
+			(parseInt(e.css('padding-top')) || 0) +
+			(parseInt(e.css('padding-bottom')) || 0) +
+			(parseInt(e.css('border-bottom-width')) || 0));
+		if (includeMargins) {
+			h -=
+				(parseInt(e.css('margin-top')) || 0) +
+				(parseInt(e.css('margin-bottom')) || 0);
+		}
+		e.height(h);
+	});
+}
+
+
+
+/* Hover Matrix
+-----------------------------------------------------------------------------*/
+
+function HoverMatrix(changeCallback) {
+
+	var tops=[], lefts=[],
+		prevRowE, prevColE,
+		origRow, origCol,
+		currRow, currCol;
+		
+	// this.cell = null;
+	
+	this.row = function(e) {
+		prevRowE = $(e);
+		tops.push(prevRowE.offset().top);
+	};
+	
+	this.col = function(e) {
+		prevColE = $(e);
+		lefts.push(prevColE.offset().left);
+	};
+	
+	this.start = function() {
+		tops.push(tops[tops.length-1] + prevRowE.outerHeight());
+		lefts.push(lefts[lefts.length-1] + prevColE.outerWidth());
+		origRow = currRow = currCol = -1;
+	};
+
+	this.mouse = function(x, y) {
+		var r, c;
+		for (r=0; r<tops.length && y>=tops[r]; r++) ;
+		for (c=0; c<lefts.length && x>=lefts[c]; c++) ;
+		r = r >= tops.length ? -1 : r - 1;
+		c = c >= lefts.length ? -1 : c - 1;
+		if (r != currRow || c != currCol) {
+			currRow = r;
+			currCol = c;
+			if (r == -1 || c == -1) {
+				this.cell = null;
+			}else{
+				if (origRow == -1) {
+					origRow = r;
+					origCol = c;
+				}
+				this.cell = {
+					row: r,
+					col: c,
+					top: tops[r],
+					left: lefts[c],
+					width: lefts[c+1] - lefts[c],
+					height: tops[r+1] - tops[r],
+					isOrig: r==origRow && c==origCol,
+					rowDelta: r-origRow,
+					colDelta: c-origCol
+				};
+			}
+			changeCallback(this.cell);
+		}
+	};
+
+}
+
+
+
+/* Misc Utils
+-----------------------------------------------------------------------------*/
+
+var dayIDs = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'];
+
+function zeroPad(n) {
+	return (n < 10 ? '0' : '') + n;
+}
+
+function strProp(s, prop) {
+	return typeof s == 'string' ? s : s[prop];
+}
+
+function filterArray(a, test) {
+	var res = [],
+		i, len = a.length;
+	for (i=0; i<len; i++) {
+		if (test(a[i])) {
+			res.push(a[i]);
+		}
+	}
+	return res;
+}

+ 269 - 0
src/view.js

@@ -0,0 +1,269 @@
+
+var viewMethods = {
+
+	//
+	// Objects inheriting these methods must implement the following properties/methods:
+	// - title
+	// - start
+	// - end
+	// - visStart
+	// - visEnd
+	// - eventEnd(event)
+	// - visEventEnd(event)
+	//
+	// - render
+	// - rerenderEvents
+	//
+
+	init: function(element, options) {
+		this.element = element;
+		this.options = options;
+		this.cachedEvents = [];
+		this.eventsByID = {};
+		this.eventElements = [];
+		this.eventElementsByID = {};
+	},
+	
+	
+	
+	trigger: function(name, thisObj) {
+		if (this.options[name]) {
+			return this.options[name].apply(thisObj, Array.prototype.slice.call(arguments, 2).concat([this]));
+		}
+	},
+	
+	
+	
+	// event/element creation reporting
+	
+	reportEvents: function(events) {
+		var i, len=events.length, event,
+			fakeID = 0,
+			eventsByID = this.eventsByID = {},
+			cachedEvents = this.cachedEvents = [];
+		for (i=0; i<len; i++) { // TODO: move _id creation to more global 'cleanEvents'
+			event = events[i];
+			if (eventsByID[event._id]) {
+				eventsByID[event._id].push(event);
+			}else{
+				eventsByID[event._id] = [event];
+			}
+			cachedEvents.push(event);
+		}
+	},
+
+	reportEventElement: function(event, element) {
+		this.eventElements.push(element);
+		var eventElementsByID = this.eventElementsByID;
+		if (eventElementsByID[event._id]) {
+			eventElementsByID[event._id].push(element);
+		}else{
+			eventElementsByID[event._id] = [element];
+		}
+	},
+	
+	
+	
+	// get events within visStart and visEnd TODO: need this? move it somewhere else?
+	
+	visibleEvents: function(events) {
+		var res=[], i, len=events.length, event;
+		for (i=0; i<len; i++) {
+			event = events[i];
+			if (this.visEventEnd(event) > this.visStart && event.start < this.visEnd) {
+				res.push(event);
+			}
+		}
+		return res;
+	},
+	
+	
+	
+	// event element manipulation
+	
+	clearEvents: function() { // just remove ELEMENTS
+		$.each(this.eventElements, function() {
+			this.remove();
+		});
+		this.eventElements = [];
+		this.eventElementsByID = {};
+	},
+	
+	showEvents: function(event, exceptElement) {
+		this._eee(event, exceptElement, 'show');
+	},
+	
+	hideEvents: function(event, exceptElement) {
+		this._eee(event, exceptElement, 'hide'); // fadeOut
+	},
+	
+	_eee: function(event, exceptElement, funcName) { // event-element-each
+		var elements = this.eventElementsByID[event._id];
+		for (var i=0; i<elements.length; i++) {
+			if (elements[i] != exceptElement) {
+				elements[i][funcName]();
+			}
+		}
+	},
+	
+	
+	
+	// event modification reporting
+	
+	moveEvent: function(event, days, minutes) { // and actually DO the date change too
+		minutes = minutes || 0;
+		var i, event2, events = this.eventsByID[event._id];
+		for (i=0; i<events.length; i++) {
+			event2 = events[i];
+			event2.hasTime = event.hasTime;
+			addMinutes(addDays(event2.start, days, true), minutes);
+			if (event.end) {
+				event2.end = addMinutes(addDays(this.eventEnd(event2), days, true), minutes);
+			}else{
+				event2.end = null;
+			}
+			normalizeEvent(event2);
+		}
+		this.eventsChanged = true;
+	},
+	
+	resizeEvent: function(event, days, minutes) { // and actually DO the date change too
+		minutes = minutes || 0;
+		var i, event2, events = this.eventsByID[event._id];
+		for (i=0; i<events.length; i++) {
+			event2 = events[i];
+			event2.end = addMinutes(addDays(this.eventEnd(event2), days, true), minutes);
+			normalizeEvent(event2);
+		}
+		this.eventsChanged = true;
+	},
+	
+	
+	
+	// semi-transparent overlay (for days while dragging)
+	
+	showOverlay: function(props) {
+		if (!this.dayOverlay) {
+			this.dayOverlay = $("<div class='fc-cell-overlay' style='position:absolute;display:none'/>")
+				.appendTo(this.element);
+		}
+		var o = this.element.offset();
+		this.dayOverlay
+			.css({
+				top: props.top - o.top,
+				left: props.left - o.left,
+				width: props.width,
+				height: props.height
+			})
+			.show();
+	},
+	
+	hideOverlay: function() {
+		if (this.dayOverlay) {
+			this.dayOverlay.hide();
+		}
+	},
+	
+	
+	
+	// event rendering utilities
+	
+	sliceSegs: function(events, start, end) {
+		var segs = [],
+			i, len=events.length, event,
+			eventStart, eventEnd,
+			segStart, segEnd,
+			isStart, isEnd;
+		for (i=0; i<len; i++) {
+			event = events[i];
+			eventStart = event.start;
+			eventEnd = this.visEventEnd(event);
+			if (eventEnd > start && eventStart < end) {
+				if (eventStart < start) {
+					segStart = cloneDate(start);
+					isStart = false;
+				}else{
+					segStart = eventStart;
+					isStart = true;
+				}
+				if (eventEnd > end) {
+					segEnd = cloneDate(end);
+					isEnd = false;
+				}else{
+					segEnd = eventEnd;
+					isEnd = true;
+				}
+				segs.push({
+					event: event,
+					start: segStart,
+					end: segEnd,
+					isStart: isStart,
+					isEnd: isEnd,
+					msLength: segEnd - segStart
+				});
+			}
+		}
+		return segs.sort(segCmp);
+	}
+
+};
+
+
+// more event rendering utilities
+
+function stackSegs(segs) {
+	var levels = [],
+		i, len = segs.length, seg,
+		j, collide, k;
+	for (i=0; i<len; i++) {
+		seg = segs[i];
+		j = 0; // the level index where seg should belong
+		while (true) {
+			collide = false;
+			if (levels[j]) {
+				for (k=0; k<levels[j].length; k++) {
+					if (seg.end > levels[j][k].start && seg.start < levels[j][k].end) {
+						collide = true;
+						break;
+					}
+				}
+			}
+			if (collide) {
+				j++;
+			}else{
+				break;
+			}
+		}
+		if (levels[j]) {
+			levels[j].push(seg);
+		}else{
+			levels[j] = [seg];
+		}
+		seg.after = 0;
+	}
+	return levels;
+}
+
+function segAfters(levels) { // TODO: put in agenda.js
+	var i, j, k, level, seg, seg2;
+	for (i=levels.length-1; i>0; i--) {
+		level = levels[i];
+		for (j=0; j<level.length; j++) {
+			seg = level[j];
+			for (k=0; k<segLevels[i-1].length; k++) {
+				seg2 = segLevels[i-1][k];
+				if (segsCollide(seg, seg2)) {
+					seg2.after = Math.max(seg2.after, seg.after+1);
+				}
+			}
+		}
+	}
+}
+
+function segCmp(a, b) {
+	return  (b.msLength - a.msLength) * 100 + (a.event.start - b.event.start);
+}
+
+function segsCollide(seg1, seg2) {
+	return seg1.end > seg2.start && seg1.start < seg2.end;
+}

+ 133 - 0
test/new.html

@@ -0,0 +1,133 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html>
+<head>
+<link rel='stylesheet' type='text/css' href='redmond/theme.css' />
+<link rel='stylesheet' type='text/css' href='../src/css/main.css' />
+<link rel='stylesheet' type='text/css' href='../src/css/grid.css' />
+<link rel='stylesheet' type='text/css' href='../src/css/agenda.css' />
+
+<script type='text/javascript' src='../src/jquery/jquery.js'></script>
+<script type='text/javascript' src='../src/jquery/ui.core.js'></script>
+<script type='text/javascript' src='../src/jquery/ui.draggable.js'></script>
+<script type='text/javascript' src='../src/jquery/ui.resizable.js'></script>
+
+<script type='text/javascript' src='../src/main.js'></script>
+<script type='text/javascript' src='../src/grid.js'></script>
+<script type='text/javascript' src='../src/view.js'></script>
+<script type='text/javascript' src='../src/util.js'></script>
+<script type='text/javascript' src='../src/gcal.js'></script>
+<script type='text/javascript'>
+
+	var d = new Date();
+	var y = d.getFullYear();
+	var m = d.getMonth();
+
+	$(document).ready(function() {
+		$('#calendar').fullCalendar({
+			windowResize: function() {
+				//alert('resize');
+			},
+			theme: false,
+			isRTL: false,
+			weekStart: 1,
+			weekMode: 'fixed',
+			//defaultView: 'dayBasic',
+			viewDisplay: function(date, view) {
+				//console.log(date + ', ' + view.name);
+			},
+			dayClick: function(date, view) {
+				//console.log(date + ', ' + view.name);
+			},
+			eventRender: function(event, element) {
+				//console.log(event.title + ' RENDER');
+			},
+			eventMouseover: function(event) {
+				//console.log('OVER ' + event.title);
+			},
+			eventMouseout: function(event) {
+				//console.log('OUT ' + event.title);
+			},
+			eventClick: function(event) {
+				//console.log('CLICK ' + event.title + ' /// ' + this.className);
+				//return false;
+			},
+			eventDragStart: function(event) {
+				//console.log('DRAG START ' + event.title);
+			},
+			eventDragStop: function(event) {
+				//console.log('DRAG STOP ' + event.title);
+			},
+			eventDrop: function(event, dayDelta, minuteDelta) {
+				//console.log(dayDelta + ' ' + minuteDelta + ' --- ' + event.title);
+			},
+			eventResizeStart: function(event) {
+				//console.log('resize START');
+			},
+			eventResizeStop: function(event) {
+				//console.log('resize STOP');
+			},
+			eventResize: function(event, dayDelta, minuteDelta) {
+				//console.log(dayDelta + ' ' + minuteDelta + ' --- ' + event.title);
+			},
+			editable: true,
+			eventSources: [
+				$.fullCalendar.gcalFeed('http://www.google.com/calendar/feeds/usa__en%40holiday.calendar.google.com/public/basic')
+			],
+			events: [
+				{
+					id: 1,
+					title: "Long Event",
+					start: new Date(y, m, 6),
+					end: new Date(y, m, 10)
+				},
+				{
+					id: 2,
+					title: "Repeating",
+					start: new Date(y, m, 2)
+				},
+				{
+					id: 2,
+					title: "Repeating",
+					start: new Date(y, m, 9)
+				},
+				{
+					id: 3,
+					title: "Meeting",
+					start: new Date(y, m, 20, 9, 0),
+					hasTime: true
+				},
+				{
+					id: 4,
+					title: "Click for Facebook",
+					start: new Date(y, m, 27),
+					end: new Date(y, m, 28),
+					url: "http://facebook.com/"
+				},
+				{
+					id: 5,
+					title: "timed event1",
+					start: new Date (y, m, 31, 17, 30),
+					hasTime: true
+				},
+				{
+					id: 6,
+					title: "timed event1",
+					start: new Date (y, m+1, 2, 14, 15),
+					hasTime: true
+				},
+				{
+					id: 7,
+					title: "timed event1",
+					start: new Date (y, m+1, 4, 15, 00),
+					end: new Date(y, m+1, 4, 17, 00),
+					hasTime: true
+				}
+			]
+		});
+	});
+
+</script>
+</head>
+<body style='font-size:12px'>
+<div id='calendar' style='width:900px;margin:20px auto 0;font-family:arial'></div>
+</body>

BIN=BIN
test/redmond/images/ui-bg_flat_0_aaaaaa_40x100.png


BIN=BIN
test/redmond/images/ui-bg_flat_55_fbec88_40x100.png


BIN=BIN
test/redmond/images/ui-bg_glass_75_d0e5f5_1x400.png


BIN=BIN
test/redmond/images/ui-bg_glass_85_dfeffc_1x400.png


BIN=BIN
test/redmond/images/ui-bg_glass_95_fef1ec_1x400.png


BIN=BIN
test/redmond/images/ui-bg_gloss-wave_55_5c9ccc_500x100.png


BIN=BIN
test/redmond/images/ui-bg_inset-hard_100_f5f8f9_1x100.png


BIN=BIN
test/redmond/images/ui-bg_inset-hard_100_fcfdfd_1x100.png


BIN=BIN
test/redmond/images/ui-icons_217bc0_256x240.png


BIN=BIN
test/redmond/images/ui-icons_2e83ff_256x240.png


BIN=BIN
test/redmond/images/ui-icons_469bdd_256x240.png


BIN=BIN
test/redmond/images/ui-icons_6da8d5_256x240.png


BIN=BIN
test/redmond/images/ui-icons_cd0a0a_256x240.png


BIN=BIN
test/redmond/images/ui-icons_d8e7f3_256x240.png


BIN=BIN
test/redmond/images/ui-icons_f9bd01_256x240.png


+ 406 - 0
test/redmond/theme.css

@@ -0,0 +1,406 @@
+/*
+* jQuery UI CSS Framework
+* Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about)
+* Dual licensed under the MIT (MIT-LICENSE.txt) and GPL (GPL-LICENSE.txt) licenses.
+*/
+
+/* Layout helpers
+----------------------------------*/
+.ui-helper-hidden { display: none; }
+.ui-helper-hidden-accessible { position: absolute; left: -99999999px; }
+.ui-helper-reset { margin: 0; padding: 0; border: 0; outline: 0; line-height: 1.3; text-decoration: none; font-size: 100%; list-style: none; }
+.ui-helper-clearfix:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; }
+.ui-helper-clearfix { display: inline-block; }
+/* required comment for clearfix to work in Opera \*/
+* html .ui-helper-clearfix { height:1%; }
+.ui-helper-clearfix { display:block; }
+/* end clearfix */
+.ui-helper-zfix { width: 100%; height: 100%; top: 0; left: 0; position: absolute; opacity: 0; filter:Alpha(Opacity=0); }
+
+
+/* Interaction Cues
+----------------------------------*/
+.ui-state-disabled { cursor: default !important; }
+
+
+/* Icons
+----------------------------------*/
+
+/* states and images */
+.ui-icon { display: block; text-indent: -99999px; overflow: hidden; background-repeat: no-repeat; }
+
+
+/* Misc visuals
+----------------------------------*/
+
+/* Overlays */
+.ui-widget-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }
+
+
+
+/*
+* jQuery UI CSS Framework
+* Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about)
+* Dual licensed under the MIT (MIT-LICENSE.txt) and GPL (GPL-LICENSE.txt) licenses.
+* To view and modify this theme, visit http://jqueryui.com/themeroller/?ffDefault=Lucida%20Grande,%20Lucida%20Sans,%20Arial,%20sans-serif&fwDefault=bold&fsDefault=1.1em&cornerRadius=5px&bgColorHeader=5c9ccc&bgTextureHeader=12_gloss_wave.png&bgImgOpacityHeader=55&borderColorHeader=4297d7&fcHeader=ffffff&iconColorHeader=d8e7f3&bgColorContent=fcfdfd&bgTextureContent=06_inset_hard.png&bgImgOpacityContent=100&borderColorContent=a6c9e2&fcContent=222222&iconColorContent=469bdd&bgColorDefault=dfeffc&bgTextureDefault=02_glass.png&bgImgOpacityDefault=85&borderColorDefault=c5dbec&fcDefault=2e6e9e&iconColorDefault=6da8d5&bgColorHover=d0e5f5&bgTextureHover=02_glass.png&bgImgOpacityHover=75&borderColorHover=79b7e7&fcHover=1d5987&iconColorHover=217bc0&bgColorActive=f5f8f9&bgTextureActive=06_inset_hard.png&bgImgOpacityActive=100&borderColorActive=79b7e7&fcActive=e17009&iconColorActive=f9bd01&bgColorHighlight=fbec88&bgTextureHighlight=01_flat.png&bgImgOpacityHighlight=55&borderColorHighlight=fad42e&fcHighlight=363636&iconColorHighlight=2e83ff&bgColorError=fef1ec&bgTextureError=02_glass.png&bgImgOpacityError=95&borderColorError=cd0a0a&fcError=cd0a0a&iconColorError=cd0a0a&bgColorOverlay=aaaaaa&bgTextureOverlay=01_flat.png&bgImgOpacityOverlay=0&opacityOverlay=30&bgColorShadow=aaaaaa&bgTextureShadow=01_flat.png&bgImgOpacityShadow=0&opacityShadow=30&thicknessShadow=8px&offsetTopShadow=-8px&offsetLeftShadow=-8px&cornerRadiusShadow=8px
+*/
+
+
+/* Component containers
+----------------------------------*/
+.ui-widget { font-family: Lucida Grande, Lucida Sans, Arial, sans-serif; font-size: 1.1em; }
+.ui-widget input, .ui-widget select, .ui-widget textarea, .ui-widget button { font-family: Lucida Grande, Lucida Sans, Arial, sans-serif; font-size: 1em; }
+.ui-widget-content { border: 1px solid #a6c9e2; background: #fcfdfd url(images/ui-bg_inset-hard_100_fcfdfd_1x100.png) 50% bottom repeat-x; color: #222222; }
+.ui-widget-content a { color: #222222; }
+.ui-widget-header { border: 1px solid #4297d7; background: #5c9ccc url(images/ui-bg_gloss-wave_55_5c9ccc_500x100.png) 50% 50% repeat-x; color: #ffffff; font-weight: bold; }
+.ui-widget-header a { color: #ffffff; }
+
+/* Interaction states
+----------------------------------*/
+.ui-state-default, .ui-widget-content .ui-state-default { border: 1px solid #c5dbec; background: #dfeffc url(images/ui-bg_glass_85_dfeffc_1x400.png) 50% 50% repeat-x; font-weight: bold; color: #2e6e9e; outline: none; }
+.ui-state-default a, .ui-state-default a:link, .ui-state-default a:visited { color: #2e6e9e; text-decoration: none; outline: none; }
+.ui-state-hover, .ui-widget-content .ui-state-hover, .ui-state-focus, .ui-widget-content .ui-state-focus { border: 1px solid #79b7e7; background: #d0e5f5 url(images/ui-bg_glass_75_d0e5f5_1x400.png) 50% 50% repeat-x; font-weight: bold; color: #1d5987; outline: none; }
+.ui-state-hover a, .ui-state-hover a:hover { color: #1d5987; text-decoration: none; outline: none; }
+.ui-state-active, .ui-widget-content .ui-state-active { border: 1px solid #79b7e7; background: #f5f8f9 url(images/ui-bg_inset-hard_100_f5f8f9_1x100.png) 50% 50% repeat-x; font-weight: bold; color: #e17009; outline: none; }
+.ui-state-active a, .ui-state-active a:link, .ui-state-active a:visited { color: #e17009; outline: none; text-decoration: none; }
+
+/* Interaction Cues
+----------------------------------*/
+.ui-state-highlight, .ui-widget-content .ui-state-highlight {border: 1px solid #fad42e; background: #fbec88 url(images/ui-bg_flat_55_fbec88_40x100.png) 50% 50% repeat-x; color: #363636; }
+.ui-state-highlight a, .ui-widget-content .ui-state-highlight a { color: #363636; }
+.ui-state-error, .ui-widget-content .ui-state-error {border: 1px solid #cd0a0a; background: #fef1ec url(images/ui-bg_glass_95_fef1ec_1x400.png) 50% 50% repeat-x; color: #cd0a0a; }
+.ui-state-error a, .ui-widget-content .ui-state-error a { color: #cd0a0a; }
+.ui-state-error-text, .ui-widget-content .ui-state-error-text { color: #cd0a0a; }
+.ui-state-disabled, .ui-widget-content .ui-state-disabled { opacity: .35; filter:Alpha(Opacity=35); background-image: none; }
+.ui-priority-primary, .ui-widget-content .ui-priority-primary { font-weight: bold; }
+.ui-priority-secondary, .ui-widget-content .ui-priority-secondary { opacity: .7; filter:Alpha(Opacity=70); font-weight: normal; }
+
+/* Icons
+----------------------------------*/
+
+/* states and images */
+.ui-icon { width: 16px; height: 16px; background-image: url(images/ui-icons_469bdd_256x240.png); }
+.ui-widget-content .ui-icon {background-image: url(images/ui-icons_469bdd_256x240.png); }
+.ui-widget-header .ui-icon {background-image: url(images/ui-icons_d8e7f3_256x240.png); }
+.ui-state-default .ui-icon { background-image: url(images/ui-icons_6da8d5_256x240.png); }
+.ui-state-hover .ui-icon, .ui-state-focus .ui-icon {background-image: url(images/ui-icons_217bc0_256x240.png); }
+.ui-state-active .ui-icon {background-image: url(images/ui-icons_f9bd01_256x240.png); }
+.ui-state-highlight .ui-icon {background-image: url(images/ui-icons_2e83ff_256x240.png); }
+.ui-state-error .ui-icon, .ui-state-error-text .ui-icon {background-image: url(images/ui-icons_cd0a0a_256x240.png); }
+
+/* positioning */
+.ui-icon-carat-1-n { background-position: 0 0; }
+.ui-icon-carat-1-ne { background-position: -16px 0; }
+.ui-icon-carat-1-e { background-position: -32px 0; }
+.ui-icon-carat-1-se { background-position: -48px 0; }
+.ui-icon-carat-1-s { background-position: -64px 0; }
+.ui-icon-carat-1-sw { background-position: -80px 0; }
+.ui-icon-carat-1-w { background-position: -96px 0; }
+.ui-icon-carat-1-nw { background-position: -112px 0; }
+.ui-icon-carat-2-n-s { background-position: -128px 0; }
+.ui-icon-carat-2-e-w { background-position: -144px 0; }
+.ui-icon-triangle-1-n { background-position: 0 -16px; }
+.ui-icon-triangle-1-ne { background-position: -16px -16px; }
+.ui-icon-triangle-1-e { background-position: -32px -16px; }
+.ui-icon-triangle-1-se { background-position: -48px -16px; }
+.ui-icon-triangle-1-s { background-position: -64px -16px; }
+.ui-icon-triangle-1-sw { background-position: -80px -16px; }
+.ui-icon-triangle-1-w { background-position: -96px -16px; }
+.ui-icon-triangle-1-nw { background-position: -112px -16px; }
+.ui-icon-triangle-2-n-s { background-position: -128px -16px; }
+.ui-icon-triangle-2-e-w { background-position: -144px -16px; }
+.ui-icon-arrow-1-n { background-position: 0 -32px; }
+.ui-icon-arrow-1-ne { background-position: -16px -32px; }
+.ui-icon-arrow-1-e { background-position: -32px -32px; }
+.ui-icon-arrow-1-se { background-position: -48px -32px; }
+.ui-icon-arrow-1-s { background-position: -64px -32px; }
+.ui-icon-arrow-1-sw { background-position: -80px -32px; }
+.ui-icon-arrow-1-w { background-position: -96px -32px; }
+.ui-icon-arrow-1-nw { background-position: -112px -32px; }
+.ui-icon-arrow-2-n-s { background-position: -128px -32px; }
+.ui-icon-arrow-2-ne-sw { background-position: -144px -32px; }
+.ui-icon-arrow-2-e-w { background-position: -160px -32px; }
+.ui-icon-arrow-2-se-nw { background-position: -176px -32px; }
+.ui-icon-arrowstop-1-n { background-position: -192px -32px; }
+.ui-icon-arrowstop-1-e { background-position: -208px -32px; }
+.ui-icon-arrowstop-1-s { background-position: -224px -32px; }
+.ui-icon-arrowstop-1-w { background-position: -240px -32px; }
+.ui-icon-arrowthick-1-n { background-position: 0 -48px; }
+.ui-icon-arrowthick-1-ne { background-position: -16px -48px; }
+.ui-icon-arrowthick-1-e { background-position: -32px -48px; }
+.ui-icon-arrowthick-1-se { background-position: -48px -48px; }
+.ui-icon-arrowthick-1-s { background-position: -64px -48px; }
+.ui-icon-arrowthick-1-sw { background-position: -80px -48px; }
+.ui-icon-arrowthick-1-w { background-position: -96px -48px; }
+.ui-icon-arrowthick-1-nw { background-position: -112px -48px; }
+.ui-icon-arrowthick-2-n-s { background-position: -128px -48px; }
+.ui-icon-arrowthick-2-ne-sw { background-position: -144px -48px; }
+.ui-icon-arrowthick-2-e-w { background-position: -160px -48px; }
+.ui-icon-arrowthick-2-se-nw { background-position: -176px -48px; }
+.ui-icon-arrowthickstop-1-n { background-position: -192px -48px; }
+.ui-icon-arrowthickstop-1-e { background-position: -208px -48px; }
+.ui-icon-arrowthickstop-1-s { background-position: -224px -48px; }
+.ui-icon-arrowthickstop-1-w { background-position: -240px -48px; }
+.ui-icon-arrowreturnthick-1-w { background-position: 0 -64px; }
+.ui-icon-arrowreturnthick-1-n { background-position: -16px -64px; }
+.ui-icon-arrowreturnthick-1-e { background-position: -32px -64px; }
+.ui-icon-arrowreturnthick-1-s { background-position: -48px -64px; }
+.ui-icon-arrowreturn-1-w { background-position: -64px -64px; }
+.ui-icon-arrowreturn-1-n { background-position: -80px -64px; }
+.ui-icon-arrowreturn-1-e { background-position: -96px -64px; }
+.ui-icon-arrowreturn-1-s { background-position: -112px -64px; }
+.ui-icon-arrowrefresh-1-w { background-position: -128px -64px; }
+.ui-icon-arrowrefresh-1-n { background-position: -144px -64px; }
+.ui-icon-arrowrefresh-1-e { background-position: -160px -64px; }
+.ui-icon-arrowrefresh-1-s { background-position: -176px -64px; }
+.ui-icon-arrow-4 { background-position: 0 -80px; }
+.ui-icon-arrow-4-diag { background-position: -16px -80px; }
+.ui-icon-extlink { background-position: -32px -80px; }
+.ui-icon-newwin { background-position: -48px -80px; }
+.ui-icon-refresh { background-position: -64px -80px; }
+.ui-icon-shuffle { background-position: -80px -80px; }
+.ui-icon-transfer-e-w { background-position: -96px -80px; }
+.ui-icon-transferthick-e-w { background-position: -112px -80px; }
+.ui-icon-folder-collapsed { background-position: 0 -96px; }
+.ui-icon-folder-open { background-position: -16px -96px; }
+.ui-icon-document { background-position: -32px -96px; }
+.ui-icon-document-b { background-position: -48px -96px; }
+.ui-icon-note { background-position: -64px -96px; }
+.ui-icon-mail-closed { background-position: -80px -96px; }
+.ui-icon-mail-open { background-position: -96px -96px; }
+.ui-icon-suitcase { background-position: -112px -96px; }
+.ui-icon-comment { background-position: -128px -96px; }
+.ui-icon-person { background-position: -144px -96px; }
+.ui-icon-print { background-position: -160px -96px; }
+.ui-icon-trash { background-position: -176px -96px; }
+.ui-icon-locked { background-position: -192px -96px; }
+.ui-icon-unlocked { background-position: -208px -96px; }
+.ui-icon-bookmark { background-position: -224px -96px; }
+.ui-icon-tag { background-position: -240px -96px; }
+.ui-icon-home { background-position: 0 -112px; }
+.ui-icon-flag { background-position: -16px -112px; }
+.ui-icon-calendar { background-position: -32px -112px; }
+.ui-icon-cart { background-position: -48px -112px; }
+.ui-icon-pencil { background-position: -64px -112px; }
+.ui-icon-clock { background-position: -80px -112px; }
+.ui-icon-disk { background-position: -96px -112px; }
+.ui-icon-calculator { background-position: -112px -112px; }
+.ui-icon-zoomin { background-position: -128px -112px; }
+.ui-icon-zoomout { background-position: -144px -112px; }
+.ui-icon-search { background-position: -160px -112px; }
+.ui-icon-wrench { background-position: -176px -112px; }
+.ui-icon-gear { background-position: -192px -112px; }
+.ui-icon-heart { background-position: -208px -112px; }
+.ui-icon-star { background-position: -224px -112px; }
+.ui-icon-link { background-position: -240px -112px; }
+.ui-icon-cancel { background-position: 0 -128px; }
+.ui-icon-plus { background-position: -16px -128px; }
+.ui-icon-plusthick { background-position: -32px -128px; }
+.ui-icon-minus { background-position: -48px -128px; }
+.ui-icon-minusthick { background-position: -64px -128px; }
+.ui-icon-close { background-position: -80px -128px; }
+.ui-icon-closethick { background-position: -96px -128px; }
+.ui-icon-key { background-position: -112px -128px; }
+.ui-icon-lightbulb { background-position: -128px -128px; }
+.ui-icon-scissors { background-position: -144px -128px; }
+.ui-icon-clipboard { background-position: -160px -128px; }
+.ui-icon-copy { background-position: -176px -128px; }
+.ui-icon-contact { background-position: -192px -128px; }
+.ui-icon-image { background-position: -208px -128px; }
+.ui-icon-video { background-position: -224px -128px; }
+.ui-icon-script { background-position: -240px -128px; }
+.ui-icon-alert { background-position: 0 -144px; }
+.ui-icon-info { background-position: -16px -144px; }
+.ui-icon-notice { background-position: -32px -144px; }
+.ui-icon-help { background-position: -48px -144px; }
+.ui-icon-check { background-position: -64px -144px; }
+.ui-icon-bullet { background-position: -80px -144px; }
+.ui-icon-radio-off { background-position: -96px -144px; }
+.ui-icon-radio-on { background-position: -112px -144px; }
+.ui-icon-pin-w { background-position: -128px -144px; }
+.ui-icon-pin-s { background-position: -144px -144px; }
+.ui-icon-play { background-position: 0 -160px; }
+.ui-icon-pause { background-position: -16px -160px; }
+.ui-icon-seek-next { background-position: -32px -160px; }
+.ui-icon-seek-prev { background-position: -48px -160px; }
+.ui-icon-seek-end { background-position: -64px -160px; }
+.ui-icon-seek-first { background-position: -80px -160px; }
+.ui-icon-stop { background-position: -96px -160px; }
+.ui-icon-eject { background-position: -112px -160px; }
+.ui-icon-volume-off { background-position: -128px -160px; }
+.ui-icon-volume-on { background-position: -144px -160px; }
+.ui-icon-power { background-position: 0 -176px; }
+.ui-icon-signal-diag { background-position: -16px -176px; }
+.ui-icon-signal { background-position: -32px -176px; }
+.ui-icon-battery-0 { background-position: -48px -176px; }
+.ui-icon-battery-1 { background-position: -64px -176px; }
+.ui-icon-battery-2 { background-position: -80px -176px; }
+.ui-icon-battery-3 { background-position: -96px -176px; }
+.ui-icon-circle-plus { background-position: 0 -192px; }
+.ui-icon-circle-minus { background-position: -16px -192px; }
+.ui-icon-circle-close { background-position: -32px -192px; }
+.ui-icon-circle-triangle-e { background-position: -48px -192px; }
+.ui-icon-circle-triangle-s { background-position: -64px -192px; }
+.ui-icon-circle-triangle-w { background-position: -80px -192px; }
+.ui-icon-circle-triangle-n { background-position: -96px -192px; }
+.ui-icon-circle-arrow-e { background-position: -112px -192px; }
+.ui-icon-circle-arrow-s { background-position: -128px -192px; }
+.ui-icon-circle-arrow-w { background-position: -144px -192px; }
+.ui-icon-circle-arrow-n { background-position: -160px -192px; }
+.ui-icon-circle-zoomin { background-position: -176px -192px; }
+.ui-icon-circle-zoomout { background-position: -192px -192px; }
+.ui-icon-circle-check { background-position: -208px -192px; }
+.ui-icon-circlesmall-plus { background-position: 0 -208px; }
+.ui-icon-circlesmall-minus { background-position: -16px -208px; }
+.ui-icon-circlesmall-close { background-position: -32px -208px; }
+.ui-icon-squaresmall-plus { background-position: -48px -208px; }
+.ui-icon-squaresmall-minus { background-position: -64px -208px; }
+.ui-icon-squaresmall-close { background-position: -80px -208px; }
+.ui-icon-grip-dotted-vertical { background-position: 0 -224px; }
+.ui-icon-grip-dotted-horizontal { background-position: -16px -224px; }
+.ui-icon-grip-solid-vertical { background-position: -32px -224px; }
+.ui-icon-grip-solid-horizontal { background-position: -48px -224px; }
+.ui-icon-gripsmall-diagonal-se { background-position: -64px -224px; }
+.ui-icon-grip-diagonal-se { background-position: -80px -224px; }
+
+
+/* Misc visuals
+----------------------------------*/
+
+/* Corner radius */
+.ui-corner-tl { -moz-border-radius-topleft: 5px; -webkit-border-top-left-radius: 5px; }
+.ui-corner-tr { -moz-border-radius-topright: 5px; -webkit-border-top-right-radius: 5px; }
+.ui-corner-bl { -moz-border-radius-bottomleft: 5px; -webkit-border-bottom-left-radius: 5px; }
+.ui-corner-br { -moz-border-radius-bottomright: 5px; -webkit-border-bottom-right-radius: 5px; }
+.ui-corner-top { -moz-border-radius-topleft: 5px; -webkit-border-top-left-radius: 5px; -moz-border-radius-topright: 5px; -webkit-border-top-right-radius: 5px; }
+.ui-corner-bottom { -moz-border-radius-bottomleft: 5px; -webkit-border-bottom-left-radius: 5px; -moz-border-radius-bottomright: 5px; -webkit-border-bottom-right-radius: 5px; }
+.ui-corner-right {  -moz-border-radius-topright: 5px; -webkit-border-top-right-radius: 5px; -moz-border-radius-bottomright: 5px; -webkit-border-bottom-right-radius: 5px; }
+.ui-corner-left { -moz-border-radius-topleft: 5px; -webkit-border-top-left-radius: 5px; -moz-border-radius-bottomleft: 5px; -webkit-border-bottom-left-radius: 5px; }
+.ui-corner-all { -moz-border-radius: 5px; -webkit-border-radius: 5px; }
+
+/* Overlays */
+.ui-widget-overlay { background: #aaaaaa url(images/ui-bg_flat_0_aaaaaa_40x100.png) 50% 50% repeat-x; opacity: .30;filter:Alpha(Opacity=30); }
+.ui-widget-shadow { margin: -8px 0 0 -8px; padding: 8px; background: #aaaaaa url(images/ui-bg_flat_0_aaaaaa_40x100.png) 50% 50% repeat-x; opacity: .30;filter:Alpha(Opacity=30); -moz-border-radius: 8px; -webkit-border-radius: 8px; }/* Accordion
+----------------------------------*/
+.ui-accordion .ui-accordion-header { cursor: pointer; position: relative; margin-top: 1px; zoom: 1; }
+.ui-accordion .ui-accordion-li-fix { display: inline; }
+.ui-accordion .ui-accordion-header-active { border-bottom: 0 !important; }
+.ui-accordion .ui-accordion-header a { display: block; font-size: 1em; padding: .5em .5em .5em 2.2em; }
+.ui-accordion .ui-accordion-header .ui-icon { position: absolute; left: .5em; top: 50%; margin-top: -8px; }
+.ui-accordion .ui-accordion-content { padding: 1em 2.2em; border-top: 0; margin-top: -2px; position: relative; top: 1px; margin-bottom: 2px; overflow: auto; display: none; }
+.ui-accordion .ui-accordion-content-active { display: block; }/* Datepicker
+----------------------------------*/
+.ui-datepicker { width: 17em; padding: .2em .2em 0; }
+.ui-datepicker .ui-datepicker-header { position:relative; padding:.2em 0; }
+.ui-datepicker .ui-datepicker-prev, .ui-datepicker .ui-datepicker-next { position:absolute; top: 2px; width: 1.8em; height: 1.8em; }
+.ui-datepicker .ui-datepicker-prev-hover, .ui-datepicker .ui-datepicker-next-hover { top: 1px; }
+.ui-datepicker .ui-datepicker-prev { left:2px; }
+.ui-datepicker .ui-datepicker-next { right:2px; }
+.ui-datepicker .ui-datepicker-prev-hover { left:1px; }
+.ui-datepicker .ui-datepicker-next-hover { right:1px; }
+.ui-datepicker .ui-datepicker-prev span, .ui-datepicker .ui-datepicker-next span { display: block; position: absolute; left: 50%; margin-left: -8px; top: 50%; margin-top: -8px;  }
+.ui-datepicker .ui-datepicker-title { margin: 0 2.3em; line-height: 1.8em; text-align: center; }
+.ui-datepicker .ui-datepicker-title select { float:left; font-size:1em; margin:1px 0; }
+.ui-datepicker select.ui-datepicker-month-year {width: 100%;}
+.ui-datepicker select.ui-datepicker-month, 
+.ui-datepicker select.ui-datepicker-year { width: 49%;}
+.ui-datepicker .ui-datepicker-title select.ui-datepicker-year { float: right; }
+.ui-datepicker table {width: 100%; font-size: .9em; border-collapse: collapse; margin:0 0 .4em; }
+.ui-datepicker th { padding: .7em .3em; text-align: center; font-weight: bold; border: 0;  }
+.ui-datepicker td { border: 0; padding: 1px; }
+.ui-datepicker td span, .ui-datepicker td a { display: block; padding: .2em; text-align: right; text-decoration: none; }
+.ui-datepicker .ui-datepicker-buttonpane { background-image: none; margin: .7em 0 0 0; padding:0 .2em; border-left: 0; border-right: 0; border-bottom: 0; }
+.ui-datepicker .ui-datepicker-buttonpane button { float: right; margin: .5em .2em .4em; cursor: pointer; padding: .2em .6em .3em .6em; width:auto; overflow:visible; }
+.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current { float:left; }
+
+/* with multiple calendars */
+.ui-datepicker.ui-datepicker-multi { width:auto; }
+.ui-datepicker-multi .ui-datepicker-group { float:left; }
+.ui-datepicker-multi .ui-datepicker-group table { width:95%; margin:0 auto .4em; }
+.ui-datepicker-multi-2 .ui-datepicker-group { width:50%; }
+.ui-datepicker-multi-3 .ui-datepicker-group { width:33.3%; }
+.ui-datepicker-multi-4 .ui-datepicker-group { width:25%; }
+.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header { border-left-width:0; }
+.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header { border-left-width:0; }
+.ui-datepicker-multi .ui-datepicker-buttonpane { clear:left; }
+.ui-datepicker-row-break { clear:both; width:100%; }
+
+/* RTL support */
+.ui-datepicker-rtl { direction: rtl; }
+.ui-datepicker-rtl .ui-datepicker-prev { right: 2px; left: auto; }
+.ui-datepicker-rtl .ui-datepicker-next { left: 2px; right: auto; }
+.ui-datepicker-rtl .ui-datepicker-prev:hover { right: 1px; left: auto; }
+.ui-datepicker-rtl .ui-datepicker-next:hover { left: 1px; right: auto; }
+.ui-datepicker-rtl .ui-datepicker-buttonpane { clear:right; }
+.ui-datepicker-rtl .ui-datepicker-buttonpane button { float: left; }
+.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current { float:right; }
+.ui-datepicker-rtl .ui-datepicker-group { float:right; }
+.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header { border-right-width:0; border-left-width:1px; }
+.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header { border-right-width:0; border-left-width:1px; }
+
+/* IE6 IFRAME FIX (taken from datepicker 1.5.3 */
+.ui-datepicker-cover {
+    display: none; /*sorry for IE5*/
+    display/**/: block; /*sorry for IE5*/
+    position: absolute; /*must have*/
+    z-index: -1; /*must have*/
+    filter: mask(); /*must have*/
+    top: -4px; /*must have*/
+    left: -4px; /*must have*/
+    width: 200px; /*must have*/
+    height: 200px; /*must have*/
+}/* Dialog
+----------------------------------*/
+.ui-dialog { position: relative; padding: .2em; width: 300px; }
+.ui-dialog .ui-dialog-titlebar { padding: .5em .3em .3em 1em; position: relative;  }
+.ui-dialog .ui-dialog-title { float: left; margin: .1em 0 .2em; } 
+.ui-dialog .ui-dialog-titlebar-close { position: absolute; right: .3em; top: 50%; width: 19px; margin: -10px 0 0 0; padding: 1px; height: 18px; }
+.ui-dialog .ui-dialog-titlebar-close span { display: block; margin: 1px; }
+.ui-dialog .ui-dialog-titlebar-close:hover, .ui-dialog .ui-dialog-titlebar-close:focus { padding: 0; }
+.ui-dialog .ui-dialog-content { border: 0; padding: .5em 1em; background: none; overflow: auto; zoom: 1; }
+.ui-dialog .ui-dialog-buttonpane { text-align: left; border-width: 1px 0 0 0; background-image: none; margin: .5em 0 0 0; padding: .3em 1em .5em .4em; }
+.ui-dialog .ui-dialog-buttonpane button { float: right; margin: .5em .4em .5em 0; cursor: pointer; padding: .2em .6em .3em .6em; line-height: 1.4em; width:auto; overflow:visible; }
+.ui-dialog .ui-resizable-se { width: 14px; height: 14px; right: 3px; bottom: 3px; }
+.ui-draggable .ui-dialog-titlebar { cursor: move; }
+/* Progressbar
+----------------------------------*/
+.ui-progressbar { height:2em; text-align: left; }
+.ui-progressbar .ui-progressbar-value {margin: -1px; height:100%; }/* Resizable
+----------------------------------*/
+.ui-resizable { position: relative;}
+.ui-resizable-handle { position: absolute;font-size: 0.1px;z-index: 99999; display: block;}
+.ui-resizable-disabled .ui-resizable-handle, .ui-resizable-autohide .ui-resizable-handle { display: none; }
+.ui-resizable-n { cursor: n-resize; height: 7px; width: 100%; top: -5px; left: 0px; }
+.ui-resizable-s { cursor: s-resize; height: 7px; width: 100%; bottom: -5px; left: 0px; }
+.ui-resizable-e { cursor: e-resize; width: 7px; right: -5px; top: 0px; height: 100%; }
+.ui-resizable-w { cursor: w-resize; width: 7px; left: -5px; top: 0px; height: 100%; }
+.ui-resizable-se { cursor: se-resize; width: 12px; height: 12px; right: 1px; bottom: 1px; }
+.ui-resizable-sw { cursor: sw-resize; width: 9px; height: 9px; left: -5px; bottom: -5px; }
+.ui-resizable-nw { cursor: nw-resize; width: 9px; height: 9px; left: -5px; top: -5px; }
+.ui-resizable-ne { cursor: ne-resize; width: 9px; height: 9px; right: -5px; top: -5px;}/* Slider
+----------------------------------*/
+.ui-slider { position: relative; text-align: left; }
+.ui-slider .ui-slider-handle { position: absolute; z-index: 2; width: 1.2em; height: 1.2em; cursor: default; }
+.ui-slider .ui-slider-range { position: absolute; z-index: 1; font-size: .7em; display: block; border: 0; }
+
+.ui-slider-horizontal { height: .8em; }
+.ui-slider-horizontal .ui-slider-handle { top: -.3em; margin-left: -.6em; }
+.ui-slider-horizontal .ui-slider-range { top: 0; height: 100%; }
+.ui-slider-horizontal .ui-slider-range-min { left: 0; }
+.ui-slider-horizontal .ui-slider-range-max { right: 0; }
+
+.ui-slider-vertical { width: .8em; height: 100px; }
+.ui-slider-vertical .ui-slider-handle { left: -.3em; margin-left: 0; margin-bottom: -.6em; }
+.ui-slider-vertical .ui-slider-range { left: 0; width: 100%; }
+.ui-slider-vertical .ui-slider-range-min { bottom: 0; }
+.ui-slider-vertical .ui-slider-range-max { top: 0; }/* Tabs
+----------------------------------*/
+.ui-tabs { padding: .2em; zoom: 1; }
+.ui-tabs .ui-tabs-nav { list-style: none; position: relative; padding: .2em .2em 0; }
+.ui-tabs .ui-tabs-nav li { position: relative; float: left; border-bottom-width: 0 !important; margin: 0 .2em -1px 0; padding: 0; }
+.ui-tabs .ui-tabs-nav li a { float: left; text-decoration: none; padding: .5em 1em; }
+.ui-tabs .ui-tabs-nav li.ui-tabs-selected { padding-bottom: 1px; border-bottom-width: 0; }
+.ui-tabs .ui-tabs-nav li.ui-tabs-selected a, .ui-tabs .ui-tabs-nav li.ui-state-disabled a, .ui-tabs .ui-tabs-nav li.ui-state-processing a { cursor: text; }
+.ui-tabs .ui-tabs-nav li a, .ui-tabs.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-selected a { cursor: pointer; } /* first selector in group seems obsolete, but required to overcome bug in Opera applying cursor: text overall if defined elsewhere... */
+.ui-tabs .ui-tabs-panel { padding: 1em 1.4em; display: block; border-width: 0; background: none; }
+.ui-tabs .ui-tabs-hide { display: none !important; }