2
0
Эх сурвалжийг харах

Merge branch 'v3.5' into v3.6-demolish

Adam Shaw 8 жил өмнө
parent
commit
81f1c53ea0

+ 22 - 6
CHANGELOG.md

@@ -1,11 +1,23 @@
 
-v3.5.0
-------
+v3.5.1 (2017-09-06)
+-------------------
+
+- fixed loading trigger not firing (#3810)
+- fixed overaggressively fetching events, on option changes (#3820)
+- fixed event object `date` property being discarded (tho still parsed) (#3819)
+- fixed event object `_id` property being discarded (#3811)
+
+
+v3.5.0 (2017-08-30)
+-------------------
 
 Features:
-- Bootstrap support (#2334, #3566)
-- jQuery UI (and Bootstrap) theme switcher on demo page (#1436)
-  (jQuery UI "Cupertino" theme no longer included in zip archive)
+- Bootstrap 3 theme support (#2334, #3566)
+	- via `themeSystem: 'bootstrap3'` (the `theme` option is deprecated)
+	- new `bootstrapGlyphicons` option
+	- jQuery UI "Cupertino" theme no longer included in zip archive
+	- improved theme switcher on demo page (#1436)
+	(big thanks to @joankaradimov)
 - 25% event rendering performance improvement across the board (#2524)
 - console message for unknown method/calendar (#3253)
 - Serbian cyrilic/latin (#3656)
@@ -20,11 +32,15 @@ Bugfixes:
 - Promise then method doesn't forward result (#3744)
 - Korean typo (#3693)
 
+Incompatibilities:
+- Event Objects obtained from clientEvents or various callbacks are no longer
+  references to internally used objects. Rather, they are static object copies.
+
 
 v3.4.0 (2017-04-27)
 -------------------
 
-- composer.js for Composer (PHP package manager) (#3617)
+- composer.json for Composer (PHP package manager) (#3617)
 - fix toISOString for locales with non-trivial postformatting (#3619)
 - fix for nested inverse-background events (#3609)
 - Estonian locale (#3600)

+ 16 - 16
demos/agenda-views.html

@@ -17,64 +17,64 @@
 				center: 'title',
 				right: 'month,agendaWeek,agendaDay,listWeek'
 			},
-			defaultDate: '2017-07-12',
+			defaultDate: '2017-09-12',
 			navLinks: true, // can click day/week names to navigate views
 			editable: true,
 			eventLimit: true, // allow "more" link when too many events
 			events: [
 				{
 					title: 'All Day Event',
-					start: '2017-07-01',
+					start: '2017-09-01',
 				},
 				{
 					title: 'Long Event',
-					start: '2017-07-07',
-					end: '2017-07-10'
+					start: '2017-09-07',
+					end: '2017-09-10'
 				},
 				{
 					id: 999,
 					title: 'Repeating Event',
-					start: '2017-07-09T16:00:00'
+					start: '2017-09-09T16:00:00'
 				},
 				{
 					id: 999,
 					title: 'Repeating Event',
-					start: '2017-07-16T16:00:00'
+					start: '2017-09-16T16:00:00'
 				},
 				{
 					title: 'Conference',
-					start: '2017-07-11',
-					end: '2017-07-13'
+					start: '2017-09-11',
+					end: '2017-09-13'
 				},
 				{
 					title: 'Meeting',
-					start: '2017-07-12T10:30:00',
-					end: '2017-07-12T12:30:00'
+					start: '2017-09-12T10:30:00',
+					end: '2017-09-12T12:30:00'
 				},
 				{
 					title: 'Lunch',
-					start: '2017-07-12T12:00:00'
+					start: '2017-09-12T12:00:00'
 				},
 				{
 					title: 'Meeting',
-					start: '2017-07-12T14:30:00'
+					start: '2017-09-12T14:30:00'
 				},
 				{
 					title: 'Happy Hour',
-					start: '2017-07-12T17:30:00'
+					start: '2017-09-12T17:30:00'
 				},
 				{
 					title: 'Dinner',
-					start: '2017-07-12T20:00:00'
+					start: '2017-09-12T20:00:00'
 				},
 				{
 					title: 'Birthday Party',
-					start: '2017-07-13T07:00:00'
+					start: '2017-09-13T07:00:00'
 				},
 				{
 					title: 'Click for Google',
 					url: 'http://google.com/',
-					start: '2017-07-28'
+					start: '2017-09-28'
 				}
 			]
 		});

+ 14 - 14
demos/background-events.html

@@ -17,57 +17,57 @@
 				center: 'title',
 				right: 'month,agendaWeek,agendaDay,listMonth'
 			},
-			defaultDate: '2017-07-12',
+			defaultDate: '2017-09-12',
 			navLinks: true, // can click day/week names to navigate views
 			businessHours: true, // display business hours
 			editable: true,
 			events: [
 				{
 					title: 'Business Lunch',
-					start: '2017-07-03T13:00:00',
+					start: '2017-09-03T13:00:00',
 					constraint: 'businessHours'
 				},
 				{
 					title: 'Meeting',
-					start: '2017-07-13T11:00:00',
+					start: '2017-09-13T11:00:00',
 					constraint: 'availableForMeeting', // defined below
 					color: '#257e4a'
 				},
 				{
 					title: 'Conference',
-					start: '2017-07-18',
-					end: '2017-07-20'
+					start: '2017-09-18',
+					end: '2017-09-20'
 				},
 				{
 					title: 'Party',
-					start: '2017-07-29T20:00:00'
+					start: '2017-09-29T20:00:00'
 				},
 
 				// areas where "Meeting" must be dropped
 				{
 					id: 'availableForMeeting',
-					start: '2017-07-11T10:00:00',
-					end: '2017-07-11T16:00:00',
+					start: '2017-09-11T10:00:00',
+					end: '2017-09-11T16:00:00',
 					rendering: 'background'
 				},
 				{
 					id: 'availableForMeeting',
-					start: '2017-07-13T10:00:00',
-					end: '2017-07-13T16:00:00',
+					start: '2017-09-13T10:00:00',
+					end: '2017-09-13T16:00:00',
 					rendering: 'background'
 				},
 
 				// red areas where no events can be dropped
 				{
-					start: '2017-07-24',
-					end: '2017-07-28',
+					start: '2017-09-24',
+					end: '2017-09-28',
 					overlap: false,
 					rendering: 'background',
 					color: '#ff9f89'
 				},
 				{
-					start: '2017-07-06',
-					end: '2017-07-08',
+					start: '2017-09-06',
+					end: '2017-09-08',
 					overlap: false,
 					rendering: 'background',
 					color: '#ff9f89'

+ 16 - 16
demos/basic-views.html

@@ -17,64 +17,64 @@
 				center: 'title',
 				right: 'month,basicWeek,basicDay'
 			},
-			defaultDate: '2017-07-12',
+			defaultDate: '2017-09-12',
 			navLinks: true, // can click day/week names to navigate views
 			editable: true,
 			eventLimit: true, // allow "more" link when too many events
 			events: [
 				{
 					title: 'All Day Event',
-					start: '2017-07-01'
+					start: '2017-09-01'
 				},
 				{
 					title: 'Long Event',
-					start: '2017-07-07',
-					end: '2017-07-10'
+					start: '2017-09-07',
+					end: '2017-09-10'
 				},
 				{
 					id: 999,
 					title: 'Repeating Event',
-					start: '2017-07-09T16:00:00'
+					start: '2017-09-09T16:00:00'
 				},
 				{
 					id: 999,
 					title: 'Repeating Event',
-					start: '2017-07-16T16:00:00'
+					start: '2017-09-16T16:00:00'
 				},
 				{
 					title: 'Conference',
-					start: '2017-07-11',
-					end: '2017-07-13'
+					start: '2017-09-11',
+					end: '2017-09-13'
 				},
 				{
 					title: 'Meeting',
-					start: '2017-07-12T10:30:00',
-					end: '2017-07-12T12:30:00'
+					start: '2017-09-12T10:30:00',
+					end: '2017-09-12T12:30:00'
 				},
 				{
 					title: 'Lunch',
-					start: '2017-07-12T12:00:00'
+					start: '2017-09-12T12:00:00'
 				},
 				{
 					title: 'Meeting',
-					start: '2017-07-12T14:30:00'
+					start: '2017-09-12T14:30:00'
 				},
 				{
 					title: 'Happy Hour',
-					start: '2017-07-12T17:30:00'
+					start: '2017-09-12T17:30:00'
 				},
 				{
 					title: 'Dinner',
-					start: '2017-07-12T20:00:00'
+					start: '2017-09-12T20:00:00'
 				},
 				{
 					title: 'Birthday Party',
-					start: '2017-07-13T07:00:00'
+					start: '2017-09-13T07:00:00'
 				},
 				{
 					title: 'Click for Google',
 					url: 'http://google.com/',
-					start: '2017-07-28'
+					start: '2017-09-28'
 				}
 			]
 		});

+ 16 - 16
demos/default.html

@@ -12,63 +12,63 @@
 	$(document).ready(function() {
 
 		$('#calendar').fullCalendar({
-			defaultDate: '2017-07-12',
+			defaultDate: '2017-09-12',
 			editable: true,
 			eventLimit: true, // allow "more" link when too many events
 			events: [
 				{
 					title: 'All Day Event',
-					start: '2017-07-01'
+					start: '2017-09-01'
 				},
 				{
 					title: 'Long Event',
-					start: '2017-07-07',
-					end: '2017-07-10'
+					start: '2017-09-07',
+					end: '2017-09-10'
 				},
 				{
 					id: 999,
 					title: 'Repeating Event',
-					start: '2017-07-09T16:00:00'
+					start: '2017-09-09T16:00:00'
 				},
 				{
 					id: 999,
 					title: 'Repeating Event',
-					start: '2017-07-16T16:00:00'
+					start: '2017-09-16T16:00:00'
 				},
 				{
 					title: 'Conference',
-					start: '2017-07-11',
-					end: '2017-07-13'
+					start: '2017-09-11',
+					end: '2017-09-13'
 				},
 				{
 					title: 'Meeting',
-					start: '2017-07-12T10:30:00',
-					end: '2017-07-12T12:30:00'
+					start: '2017-09-12T10:30:00',
+					end: '2017-09-12T12:30:00'
 				},
 				{
 					title: 'Lunch',
-					start: '2017-07-12T12:00:00'
+					start: '2017-09-12T12:00:00'
 				},
 				{
 					title: 'Meeting',
-					start: '2017-07-12T14:30:00'
+					start: '2017-09-12T14:30:00'
 				},
 				{
 					title: 'Happy Hour',
-					start: '2017-07-12T17:30:00'
+					start: '2017-09-12T17:30:00'
 				},
 				{
 					title: 'Dinner',
-					start: '2017-07-12T20:00:00'
+					start: '2017-09-12T20:00:00'
 				},
 				{
 					title: 'Birthday Party',
-					start: '2017-07-13T07:00:00'
+					start: '2017-09-13T07:00:00'
 				},
 				{
 					title: 'Click for Google',
 					url: 'http://google.com/',
-					start: '2017-07-28'
+					start: '2017-09-28'
 				}
 			]
 		});

+ 109 - 0
demos/js/theme-chooser.js

@@ -0,0 +1,109 @@
+
+function initThemeChooser(settings) {
+	var isInitialized = false;
+	var $currentStylesheet = $();
+	var $loading = $('#loading');
+	var $systemSelect = $('#theme-system-selector select')
+		.on('change', function() {
+			setThemeSystem(this.value);
+		});
+
+	setThemeSystem($systemSelect.val());
+
+
+	function setThemeSystem(themeSystem) {
+		var $allSelectWraps = $('.selector[data-theme-system]').hide();
+		var $selectWrap = $allSelectWraps.filter('[data-theme-system="' + themeSystem +'"]').show();
+		var $select = $selectWrap.find('select')
+			.off('change') // avoid duplicate handlers :(
+			.on('change', function() {
+				setTheme(themeSystem, this.value);
+			});
+
+		setTheme(themeSystem, $select.val());
+	}
+
+
+	function setTheme(themeSystem, themeName) {
+		var stylesheetUrl = generateStylesheetUrl(themeSystem, themeName);
+		var $stylesheet;
+
+		function done() {
+			if (!isInitialized) {
+				isInitialized = true;
+				settings.init(themeSystem);
+			}
+			else {
+				settings.change(themeSystem);
+			}
+
+			showCredits(themeSystem, themeName);
+		}
+
+		if (stylesheetUrl) {
+			$stylesheet = $('<link rel="stylesheet" type="text/css" href="' + stylesheetUrl + '"/>').appendTo('head');
+			$loading.show();
+
+			whenStylesheetLoaded($stylesheet[0], function() {
+				$currentStylesheet.remove();
+				$currentStylesheet = $stylesheet;
+				$loading.hide();
+				done();
+			});
+		} else {
+			$currentStylesheet.remove();
+			$currentStylesheet = $();
+			done();
+		}
+	}
+
+
+	function generateStylesheetUrl(themeSystem, themeName) {
+		if (themeSystem === 'jquery-ui') {
+			return 'https://code.jquery.com/ui/1.12.1/themes/' + themeName + '/jquery-ui.css';
+		}
+		else if (themeSystem === 'bootstrap3') {
+			if (themeName) {
+				return 'https://bootswatch.com/' + themeName + '/bootstrap.min.css';
+			}
+			else { // the default bootstrap theme
+				return 'https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css';
+			}
+		}
+	}
+
+
+	function showCredits(themeSystem, themeName) {
+		var creditId;
+
+		if (themeSystem === 'jquery-ui') {
+			creditId = 'jquery-ui';
+		}
+		else if (themeSystem === 'bootstrap3') {
+			if (themeName) {
+				creditId = 'bootstrap-custom';
+			}
+			else {
+				creditId = 'bootstrap-standard';
+			}
+		}
+
+		$('.credits').hide()
+			.filter('[data-credit-id="' + creditId + '"]').show();
+	}
+
+
+	function whenStylesheetLoaded(linkNode, callback) {
+		var isReady = false;
+
+		function ready() {
+			if (!isReady) { // avoid double-call
+				isReady = true;
+				callback();
+			}
+		}
+
+		linkNode.onload = ready; // does not work cross-browser
+		setTimeout(ready, 2000); // max wait. also handles browsers that don't support onload
+	}
+}

+ 1 - 1
demos/json.html

@@ -17,7 +17,7 @@
 				center: 'title',
 				right: 'month,agendaWeek,agendaDay,listWeek'
 			},
-			defaultDate: '2017-07-12',
+			defaultDate: '2017-09-12',
 			editable: true,
 			navLinks: true, // can click day/week names to navigate views
 			eventLimit: true, // allow "more" link when too many events

+ 15 - 15
demos/json/events.json

@@ -1,56 +1,56 @@
 [
   {
     "title": "All Day Event",
-    "start": "2017-07-01"
+    "start": "2017-09-01"
   },
   {
     "title": "Long Event",
-    "start": "2017-07-07",
-    "end": "2017-07-10"
+    "start": "2017-09-07",
+    "end": "2017-09-10"
   },
   {
     "id": "999",
     "title": "Repeating Event",
-    "start": "2017-07-09T16:00:00-05:00"
+    "start": "2017-09-09T16:00:00-05:00"
   },
   {
     "id": "999",
     "title": "Repeating Event",
-    "start": "2017-07-16T16:00:00-05:00"
+    "start": "2017-09-16T16:00:00-05:00"
   },
   {
     "title": "Conference",
-    "start": "2017-07-11",
-    "end": "2017-07-13"
+    "start": "2017-09-11",
+    "end": "2017-09-13"
   },
   {
     "title": "Meeting",
-    "start": "2017-07-12T10:30:00-05:00",
-    "end": "2017-07-12T12:30:00-05:00"
+    "start": "2017-09-12T10:30:00-05:00",
+    "end": "2017-09-12T12:30:00-05:00"
   },
   {
     "title": "Lunch",
-    "start": "2017-07-12T12:00:00-05:00"
+    "start": "2017-09-12T12:00:00-05:00"
   },
   {
     "title": "Meeting",
-    "start": "2017-07-12T14:30:00-05:00"
+    "start": "2017-09-12T14:30:00-05:00"
   },
   {
     "title": "Happy Hour",
-    "start": "2017-07-12T17:30:00-05:00"
+    "start": "2017-09-12T17:30:00-05:00"
   },
   {
     "title": "Dinner",
-    "start": "2017-07-12T20:00:00"
+    "start": "2017-09-12T20:00:00"
   },
   {
     "title": "Birthday Party",
-    "start": "2017-07-13T07:00:00-05:00"
+    "start": "2017-09-13T07:00:00-05:00"
   },
   {
     "title": "Click for Google",
     "url": "http://google.com/",
-    "start": "2017-07-28"
+    "start": "2017-09-28"
   }
 ]

+ 16 - 16
demos/list-views.html

@@ -26,64 +26,64 @@
 			},
 
 			defaultView: 'listWeek',
-			defaultDate: '2017-07-12',
+			defaultDate: '2017-09-12',
 			navLinks: true, // can click day/week names to navigate views
 			editable: true,
 			eventLimit: true, // allow "more" link when too many events
 			events: [
 				{
 					title: 'All Day Event',
-					start: '2017-07-01'
+					start: '2017-09-01'
 				},
 				{
 					title: 'Long Event',
-					start: '2017-07-07',
-					end: '2017-07-10'
+					start: '2017-09-07',
+					end: '2017-09-10'
 				},
 				{
 					id: 999,
 					title: 'Repeating Event',
-					start: '2017-07-09T16:00:00'
+					start: '2017-09-09T16:00:00'
 				},
 				{
 					id: 999,
 					title: 'Repeating Event',
-					start: '2017-07-16T16:00:00'
+					start: '2017-09-16T16:00:00'
 				},
 				{
 					title: 'Conference',
-					start: '2017-07-11',
-					end: '2017-07-13'
+					start: '2017-09-11',
+					end: '2017-09-13'
 				},
 				{
 					title: 'Meeting',
-					start: '2017-07-12T10:30:00',
-					end: '2017-07-12T12:30:00'
+					start: '2017-09-12T10:30:00',
+					end: '2017-09-12T12:30:00'
 				},
 				{
 					title: 'Lunch',
-					start: '2017-07-12T12:00:00'
+					start: '2017-09-12T12:00:00'
 				},
 				{
 					title: 'Meeting',
-					start: '2017-07-12T14:30:00'
+					start: '2017-09-12T14:30:00'
 				},
 				{
 					title: 'Happy Hour',
-					start: '2017-07-12T17:30:00'
+					start: '2017-09-12T17:30:00'
 				},
 				{
 					title: 'Dinner',
-					start: '2017-07-12T20:00:00'
+					start: '2017-09-12T20:00:00'
 				},
 				{
 					title: 'Birthday Party',
-					start: '2017-07-13T07:00:00'
+					start: '2017-09-13T07:00:00'
 				},
 				{
 					title: 'Click for Google',
 					url: 'http://google.com/',
-					start: '2017-07-28'
+					start: '2017-09-28'
 				}
 			]
 		});

+ 16 - 16
demos/locales.html

@@ -19,7 +19,7 @@
 				center: 'title',
 				right: 'month,agendaWeek,agendaDay,listMonth'
 			},
-			defaultDate: '2017-07-12',
+			defaultDate: '2017-09-12',
 			locale: initialLocaleCode,
 			buttonIcons: false, // show the prev/next text
 			weekNumbers: true,
@@ -29,57 +29,57 @@
 			events: [
 				{
 					title: 'All Day Event',
-					start: '2017-07-01'
+					start: '2017-09-01'
 				},
 				{
 					title: 'Long Event',
-					start: '2017-07-07',
-					end: '2017-07-10'
+					start: '2017-09-07',
+					end: '2017-09-10'
 				},
 				{
 					id: 999,
 					title: 'Repeating Event',
-					start: '2017-07-09T16:00:00'
+					start: '2017-09-09T16:00:00'
 				},
 				{
 					id: 999,
 					title: 'Repeating Event',
-					start: '2017-07-16T16:00:00'
+					start: '2017-09-16T16:00:00'
 				},
 				{
 					title: 'Conference',
-					start: '2017-07-11',
-					end: '2017-07-13'
+					start: '2017-09-11',
+					end: '2017-09-13'
 				},
 				{
 					title: 'Meeting',
-					start: '2017-07-12T10:30:00',
-					end: '2017-07-12T12:30:00'
+					start: '2017-09-12T10:30:00',
+					end: '2017-09-12T12:30:00'
 				},
 				{
 					title: 'Lunch',
-					start: '2017-07-12T12:00:00'
+					start: '2017-09-12T12:00:00'
 				},
 				{
 					title: 'Meeting',
-					start: '2017-07-12T14:30:00'
+					start: '2017-09-12T14:30:00'
 				},
 				{
 					title: 'Happy Hour',
-					start: '2017-07-12T17:30:00'
+					start: '2017-09-12T17:30:00'
 				},
 				{
 					title: 'Dinner',
-					start: '2017-07-12T20:00:00'
+					start: '2017-09-12T20:00:00'
 				},
 				{
 					title: 'Birthday Party',
-					start: '2017-07-13T07:00:00'
+					start: '2017-09-13T07:00:00'
 				},
 				{
 					title: 'Click for Google',
 					url: 'http://google.com/',
-					start: '2017-07-28'
+					start: '2017-09-28'
 				}
 			]
 		});

+ 16 - 16
demos/selectable.html

@@ -17,7 +17,7 @@
 				center: 'title',
 				right: 'month,agendaWeek,agendaDay'
 			},
-			defaultDate: '2017-07-12',
+			defaultDate: '2017-09-12',
 			navLinks: true, // can click day/week names to navigate views
 			selectable: true,
 			selectHelper: true,
@@ -39,57 +39,57 @@
 			events: [
 				{
 					title: 'All Day Event',
-					start: '2017-07-01'
+					start: '2017-09-01'
 				},
 				{
 					title: 'Long Event',
-					start: '2017-07-07',
-					end: '2017-07-10'
+					start: '2017-09-07',
+					end: '2017-09-10'
 				},
 				{
 					id: 999,
 					title: 'Repeating Event',
-					start: '2017-07-09T16:00:00'
+					start: '2017-09-09T16:00:00'
 				},
 				{
 					id: 999,
 					title: 'Repeating Event',
-					start: '2017-07-16T16:00:00'
+					start: '2017-09-16T16:00:00'
 				},
 				{
 					title: 'Conference',
-					start: '2017-07-11',
-					end: '2017-07-13'
+					start: '2017-09-11',
+					end: '2017-09-13'
 				},
 				{
 					title: 'Meeting',
-					start: '2017-07-12T10:30:00',
-					end: '2017-07-12T12:30:00'
+					start: '2017-09-12T10:30:00',
+					end: '2017-09-12T12:30:00'
 				},
 				{
 					title: 'Lunch',
-					start: '2017-07-12T12:00:00'
+					start: '2017-09-12T12:00:00'
 				},
 				{
 					title: 'Meeting',
-					start: '2017-07-12T14:30:00'
+					start: '2017-09-12T14:30:00'
 				},
 				{
 					title: 'Happy Hour',
-					start: '2017-07-12T17:30:00'
+					start: '2017-09-12T17:30:00'
 				},
 				{
 					title: 'Dinner',
-					start: '2017-07-12T20:00:00'
+					start: '2017-09-12T20:00:00'
 				},
 				{
 					title: 'Birthday Party',
-					start: '2017-07-13T07:00:00'
+					start: '2017-09-13T07:00:00'
 				},
 				{
 					title: 'Click for Google',
 					url: 'http://google.com/',
-					start: '2017-07-28'
+					start: '2017-09-28'
 				}
 			]
 		});

+ 159 - 192
demos/themes.html

@@ -2,154 +2,95 @@
 <html>
 <head>
 <meta charset='utf-8' />
-
-<!-- preload the first theme's stylesheet -->
-<link href='https://code.jquery.com/ui/1.12.1/themes/cupertino/jquery-ui.css' rel='stylesheet' data-theme-stylesheet />
-
 <link href='../dist/fullcalendar.css' rel='stylesheet' />
 <link href='../dist/fullcalendar.print.css' rel='stylesheet' media='print' />
 <script src='../node_modules/moment/moment.js'></script>
 <script src='../node_modules/jquery/dist/jquery.js'></script>
 <script src='../dist/fullcalendar.js'></script>
+<script src='js/theme-chooser.js'></script>
 <script>
 
 	$(document).ready(function() {
-		var $themeStylesheet = $('link[data-theme-stylesheet]');
-		var $themeSystemSelect = $('#theme-system-selector select');
-		var $themeNameSelects = $('#theme-name-selector select');
-		var currentThemeSystem;
-		var calendarObj;
-
-
-		setThemeSystem($themeSystemSelect.val()); // whatever is selected
 
-
-		$('#calendar').fullCalendar({
-			theme: currentThemeSystem, // set by setThemeSystem
-			header: {
-				left: 'prev,next today',
-				center: 'title',
-				right: 'month,agendaWeek,agendaDay,listMonth'
+		initThemeChooser({
+
+			init: function(themeSystem) {
+				$('#calendar').fullCalendar({
+					themeSystem: themeSystem,
+					header: {
+						left: 'prev,next today',
+						center: 'title',
+						right: 'month,agendaWeek,agendaDay,listMonth'
+					},
+					defaultDate: '2017-09-12',
+					weekNumbers: true,
+					navLinks: true, // can click day/week names to navigate views
+					editable: true,
+					eventLimit: true, // allow "more" link when too many events
+					events: [
+						{
+							title: 'All Day Event',
+							start: '2017-09-01'
+						},
+						{
+							title: 'Long Event',
+							start: '2017-09-07',
+							end: '2017-09-10'
+						},
+						{
+							id: 999,
+							title: 'Repeating Event',
+							start: '2017-09-09T16:00:00'
+						},
+						{
+							id: 999,
+							title: 'Repeating Event',
+							start: '2017-09-16T16:00:00'
+						},
+						{
+							title: 'Conference',
+							start: '2017-09-11',
+							end: '2017-09-13'
+						},
+						{
+							title: 'Meeting',
+							start: '2017-09-12T10:30:00',
+							end: '2017-09-12T12:30:00'
+						},
+						{
+							title: 'Lunch',
+							start: '2017-09-12T12:00:00'
+						},
+						{
+							title: 'Meeting',
+							start: '2017-09-12T14:30:00'
+						},
+						{
+							title: 'Happy Hour',
+							start: '2017-09-12T17:30:00'
+						},
+						{
+							title: 'Dinner',
+							start: '2017-09-12T20:00:00'
+						},
+						{
+							title: 'Birthday Party',
+							start: '2017-09-13T07:00:00'
+						},
+						{
+							title: 'Click for Google',
+							url: 'http://google.com/',
+							start: '2017-09-28'
+						}
+					]
+				});
 			},
-			defaultDate: '2017-07-12',
-			weekNumbers: true,
-			navLinks: true, // can click day/week names to navigate views
-			editable: true,
-			eventLimit: true, // allow "more" link when too many events
-			events: [
-				{
-					title: 'All Day Event',
-					start: '2017-07-01'
-				},
-				{
-					title: 'Long Event',
-					start: '2017-07-07',
-					end: '2017-07-10'
-				},
-				{
-					id: 999,
-					title: 'Repeating Event',
-					start: '2017-07-09T16:00:00'
-				},
-				{
-					id: 999,
-					title: 'Repeating Event',
-					start: '2017-07-16T16:00:00'
-				},
-				{
-					title: 'Conference',
-					start: '2017-07-11',
-					end: '2017-07-13'
-				},
-				{
-					title: 'Meeting',
-					start: '2017-07-12T10:30:00',
-					end: '2017-07-12T12:30:00'
-				},
-				{
-					title: 'Lunch',
-					start: '2017-07-12T12:00:00'
-				},
-				{
-					title: 'Meeting',
-					start: '2017-07-12T14:30:00'
-				},
-				{
-					title: 'Happy Hour',
-					start: '2017-07-12T17:30:00'
-				},
-				{
-					title: 'Dinner',
-					start: '2017-07-12T20:00:00'
-				},
-				{
-					title: 'Birthday Party',
-					start: '2017-07-13T07:00:00'
-				},
-				{
-					title: 'Click for Google',
-					url: 'http://google.com/',
-					start: '2017-07-28'
-				}
-			]
-		});
-
-		calendarObj = $('#calendar').fullCalendar('getCalendar'); // for cleaner API access
-
-
-		$themeSystemSelect.on('change', function() {
-			setThemeSystem(this.value);
-		});
-
-		$themeNameSelects.on('change', function() {
-			setThemeStylesheet(this.value);
-		});
-
-
-		function setThemeSystem(newThemeSystem) {
-			var $nameSelector = $('#theme-name-selector select').hide() // hide all
-				.filter('[data-theme-type="' + newThemeSystem + '"]').show(); // then show new select
-
-			currentThemeSystem = newThemeSystem;
 
-			if ($nameSelector.length) {
-				$('#theme-name-selector').show();
-				setThemeStylesheet($nameSelector.val());
+			change: function(themeSystem) {
+				$('#calendar').fullCalendar('option', 'themeSystem', themeSystem);
 			}
-			else {
-				$('#theme-name-selector').hide();
-				clearThemeStylesheet();
-			}
-
-			if (calendarObj) { // if already initialized
-				calendarObj.option('theme', newThemeSystem);
-			}
-		}
-
-
-		function setThemeStylesheet(themeName) {
-			$themeStylesheet.attr('href', buildThemeStylesheetUrl(themeName));
-		}
-
-
-		function clearThemeStylesheet() {
-			$themeStylesheet.attr('href', '');
-		}
 
-
-		function buildThemeStylesheetUrl(themeName) {
-			if (currentThemeSystem === 'jquery-ui') {
-				return 'https://code.jquery.com/ui/1.12.1/themes/' + themeName + '/jquery-ui.css';
-			}
-			else if (currentThemeSystem === 'bootstrap3') {
-				if (themeName) {
-					return 'http://bootswatch.com/' + themeName + '/bootstrap.min.css';
-				}
-				else { // the default bootstrap theme
-					return 'https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css';
-				}
-			}
-		}
+		});
 
 	});
 
@@ -185,6 +126,10 @@
 		font: inherit; /* mock what Boostrap does, don't compete  */
 	}
 
+	.left { float: left }
+	.right { float: right }
+	.clear { clear: both }
+
 	#calendar {
 		max-width: 900px;
 		margin: 40px auto;
@@ -197,68 +142,90 @@
 
 	<div id='top'>
 
-		<div id='theme-system-selector' class='selector'>
-			Theme System:
+		<div class='left'>
+
+			<div id='theme-system-selector' class='selector'>
+				Theme System:
+
+				<select>
+					<option value='bootstrap3' selected>Bootstrap 3</option>
+					<option value='jquery-ui'>jQuery UI</option>
+					<option value='standard'>unthemed</option>
+				</select>
+			</div>
+
+			<div data-theme-system="bootstrap3" class='selector' style='display:none'>
+				Theme Name:
+
+				<select>
+					<option value='' selected>Default</option>
+					<option value='cosmo'>Cosmo</option>
+					<option value='cyborg'>Cyborg</option>
+					<option value='darkly'>Darkly</option>
+					<option value='flatly'>Flatly</option>
+					<option value='journal'>Journal</option>
+					<option value='lumen'>Lumen</option>
+					<option value='paper'>Paper</option>
+					<option value='readable'>Readable</option>
+					<option value='sandstone'>Sandstone</option>
+					<option value='simplex'>Simplex</option>
+					<option value='slate'>Slate</option>
+					<option value='solar'>Solar</option>
+					<option value='spacelab'>Spacelab</option>
+					<option value='superhero'>Superhero</option>
+					<option value='united'>United</option>
+					<option value='yeti'>Yeti</option>
+				</select>
+			</div>
+
+			<div data-theme-system="jquery-ui" class='selector' style='display:none'>
+				Theme Name:
+
+				<select>
+					<option value='black-tie'>Black Tie</option>
+					<option value='blitzer'>Blitzer</option>
+					<option value='cupertino' selected>Cupertino</option>
+					<option value='dark-hive'>Dark Hive</option>
+					<option value='dot-luv'>Dot Luv</option>
+					<option value='eggplant'>Eggplant</option>
+					<option value='excite-bike'>Excite Bike</option>
+					<option value='flick'>Flick</option>
+					<option value='hot-sneaks'>Hot Sneaks</option>
+					<option value='humanity'>Humanity</option>
+					<option value='le-frog'>Le Frog</option>
+					<option value='mint-choc'>Mint Choc</option>
+					<option value='overcast'>Overcast</option>
+					<option value='pepper-grinder'>Pepper Grinder</option>
+					<option value='redmond'>Redmond</option>
+					<option value='smoothness'>Smoothness</option>
+					<option value='south-street'>South Street</option>
+					<option value='start'>Start</option>
+					<option value='sunny'>Sunny</option>
+					<option value='swanky-purse'>Swanky Purse</option>
+					<option value='trontastic'>Trontastic</option>
+					<option value='ui-darkness'>UI Darkness</option>
+					<option value='ui-lightness'>UI Lightness</option>
+					<option value='vader'>Vader</option>
+				</select>
+			</div>
+
+			<span id='loading' style='display:none'>loading theme...</span>
 
-			<select>
-				<option value='jquery-ui' selected>jQuery UI</option>
-				<option value='bootstrap3'>Bootstrap 3</option>
-				<option value='standard'>unthemed</option>
-			</select>
 		</div>
 
-		<div id='theme-name-selector' class='selector'>
-			Theme Name:
-
-			<select data-theme-type='jquery-ui'>
-				<option value='black-tie'>Black Tie</option>
-				<option value='blitzer'>Blitzer</option>
-				<option value='cupertino' selected>Cupertino</option>
-				<option value='dark-hive'>Dark Hive</option>
-				<option value='dot-luv'>Dot Luv</option>
-				<option value='eggplant'>Eggplant</option>
-				<option value='excite-bike'>Excite Bike</option>
-				<option value='flick'>Flick</option>
-				<option value='hot-sneaks'>Hot Sneaks</option>
-				<option value='humanity'>Humanity</option>
-				<option value='le-frog'>Le Frog</option>
-				<option value='mint-choc'>Mint Choc</option>
-				<option value='overcast'>Overcast</option>
-				<option value='pepper-grinder'>Pepper Grinder</option>
-				<option value='redmond'>Redmond</option>
-				<option value='smoothness'>Smoothness</option>
-				<option value='south-street'>South Street</option>
-				<option value='start'>Start</option>
-				<option value='sunny'>Sunny</option>
-				<option value='swanky-purse'>Swanky Purse</option>
-				<option value='trontastic'>Trontastic</option>
-				<option value='ui-darkness'>UI Darkness</option>
-				<option value='ui-lightness'>UI Lightness</option>
-				<option value='vader'>Vader</option>
-			</select>
-
-			<select data-theme-type='bootstrap3'>
-				<option value=''>Default</option>
-				<option value='cerulean'>Cerulean</option>
-				<option value='cosmo'>Cosmo</option>
-				<option value='cyborg'>Cyborg</option>
-				<option value='darkly'>Darkly</option>
-				<option value='flatly'>Flatly</option>
-				<option value='journal'>Journal</option>
-				<option value='lumen'>Lumen</option>
-				<option value='paper'>Paper</option>
-				<option value='readable'>Readable</option>
-				<option value='sandstone'>Sandstone</option>
-				<option value='simplex'>Simplex</option>
-				<option value='slate'>Slate</option>
-				<option value='solar'>Solar</option>
-				<option value='spacelab'>Spacelab</option>
-				<option value='superhero'>Superhero</option>
-				<option value='united'>United</option>
-				<option value='yeti'>Yeti</option>
-			</select>
+		<div class='right'>
+			<span class='credits' data-credit-id='bootstrap-standard' style='display:none'>
+				<a href='https://getbootstrap.com/docs/3.3/' target='_blank'>Theme by Bootstrap</a>
+			</span>
+			<span class='credits' data-credit-id='bootstrap-custom' style='display:none'>
+				<a href='https://bootswatch.com/' target='_blank'>Theme by Bootswatch</a>
+			</span>
+			<span class='credits' data-credit-id='jquery-ui' style='display:none'>
+				<a href='http://jqueryui.com/themeroller/' target='_blank'>Theme by jQuery UI</a>
+			</span>
 		</div>
 
+		<div class='clear'></div>
 	</div>
 
 	<div id='calendar'></div>

+ 1 - 1
demos/timezones.html

@@ -17,7 +17,7 @@
 				center: 'title',
 				right: 'month,agendaWeek,agendaDay,listWeek'
 			},
-			defaultDate: '2017-07-12',
+			defaultDate: '2017-09-12',
 			navLinks: true, // can click day/week names to navigate views
 			editable: true,
 			selectable: true,

+ 16 - 16
demos/week-numbers.html

@@ -17,7 +17,7 @@
 				center: 'title',
 				right: 'month,agendaWeek,agendaDay,listWeek'
 			},
-			defaultDate: '2017-07-12',
+			defaultDate: '2017-09-12',
 			navLinks: true, // can click day/week names to navigate views
 
 			weekNumbers: true,
@@ -29,57 +29,57 @@
 			events: [
 				{
 					title: 'All Day Event',
-					start: '2017-07-01'
+					start: '2017-09-01'
 				},
 				{
 					title: 'Long Event',
-					start: '2017-07-07',
-					end: '2017-07-10'
+					start: '2017-09-07',
+					end: '2017-09-10'
 				},
 				{
 					id: 999,
 					title: 'Repeating Event',
-					start: '2017-07-09T16:00:00'
+					start: '2017-09-09T16:00:00'
 				},
 				{
 					id: 999,
 					title: 'Repeating Event',
-					start: '2017-07-16T16:00:00'
+					start: '2017-09-16T16:00:00'
 				},
 				{
 					title: 'Conference',
-					start: '2017-07-11',
-					end: '2017-07-13'
+					start: '2017-09-11',
+					end: '2017-09-13'
 				},
 				{
 					title: 'Meeting',
-					start: '2017-07-12T10:30:00',
-					end: '2017-07-12T12:30:00'
+					start: '2017-09-12T10:30:00',
+					end: '2017-09-12T12:30:00'
 				},
 				{
 					title: 'Lunch',
-					start: '2017-07-12T12:00:00'
+					start: '2017-09-12T12:00:00'
 				},
 				{
 					title: 'Meeting',
-					start: '2017-07-12T14:30:00'
+					start: '2017-09-12T14:30:00'
 				},
 				{
 					title: 'Happy Hour',
-					start: '2017-07-12T17:30:00'
+					start: '2017-09-12T17:30:00'
 				},
 				{
 					title: 'Dinner',
-					start: '2017-07-12T20:00:00'
+					start: '2017-09-12T20:00:00'
 				},
 				{
 					title: 'Birthday Party',
-					start: '2017-07-13T07:00:00'
+					start: '2017-09-13T07:00:00'
 				},
 				{
 					title: 'Click for Google',
 					url: 'http://google.com/',
-					start: '2017-07-28'
+					start: '2017-09-28'
 				}
 			]
 		});

+ 2 - 2
src/Calendar.render.js

@@ -49,8 +49,8 @@ Calendar.mixin({
 		});
 
 		// called immediately, and upon option change
-		this.optionsModel.watch('settingTheme', [ '?theme' ], function(opts) {
-			var themeClass = ThemeRegistry.getThemeClass(opts.theme);
+		this.optionsModel.watch('settingTheme', [ '?theme', '?themeSystem' ], function(opts) {
+			var themeClass = ThemeRegistry.getThemeClass(opts.themeSystem || opts.theme);
 			var theme = new themeClass(_this.optionsModel);
 			var widgetClass = theme.getClass('widget');
 

+ 1 - 1
src/common/common.bootstrap3.css

@@ -7,7 +7,7 @@
 	text-decoration: underline;
 }
 
-.fc-bootstrap3 .fc-divider {
+.fc-bootstrap3 hr.fc-divider {
 	border-color: inherit;
 }
 

+ 1 - 1
src/main.js

@@ -4,7 +4,7 @@ var FC = $.fullCalendar = {
 	// When introducing internal API incompatibilities (where fullcalendar plugins would break),
 	// the minor version of the calendar should be upped (ex: 2.7.2 -> 2.8.0)
 	// and the below integer should be incremented.
-	internalApiVersion: 9
+	internalApiVersion: 10
 };
 var fcViews = FC.views = {};
 

+ 4 - 0
src/models/event-source/FuncEventSource.js

@@ -7,6 +7,8 @@ var FuncEventSource = EventSource.extend({
 	fetch: function(start, end, timezone) {
 		var _this = this;
 
+		this.calendar.pushLoading();
+
 		return Promise.construct(function(onResolve) {
 			_this.func.call(
 				this.calendar,
@@ -14,6 +16,8 @@ var FuncEventSource = EventSource.extend({
 				end.clone(),
 				timezone,
 				function(rawEventDefs) {
+					_this.calendar.popLoading();
+
 					onResolve(_this.parseEventDefs(rawEventDefs));
 				}
 			);

+ 6 - 0
src/models/event-source/JsonFeedEventSource.js

@@ -19,6 +19,8 @@ var JsonFeedEventSource = EventSource.extend({
 		// don't intercept success/error
 		// tho will be a breaking API change
 
+		this.calendar.pushLoading();
+
 		return Promise.construct(function(onResolve, onReject) {
 			$.ajax($.extend(
 				{}, // avoid mutation
@@ -29,6 +31,8 @@ var JsonFeedEventSource = EventSource.extend({
 					success: function(rawEventDefs) {
 						var callbackRes;
 
+						_this.calendar.popLoading();
+
 						if (rawEventDefs) {
 							callbackRes = applyAll(onSuccess, this, arguments); // redirect `this`
 
@@ -43,6 +47,8 @@ var JsonFeedEventSource = EventSource.extend({
 						}
 					},
 					error: function() {
+						_this.calendar.popLoading();
+
 						applyAll(onError, this, arguments); // redirect `this`
 						onReject();
 					}

+ 8 - 1
src/models/event/EventDef.js

@@ -26,7 +26,6 @@ var EventDef = FC.EventDef = Class.extend(ParsableModelMixin, {
 
 
 	constructor: function(source) {
-		this.uid = String(EventDef.uuid++);
 		this.source = source;
 		this.className = [];
 		this.miscProps = {};
@@ -160,6 +159,13 @@ var EventDef = FC.EventDef = Class.extend(ParsableModelMixin, {
 			this.id = EventDef.generateId();
 		}
 
+		if (rawProps._id != null) { // accept this prop, even tho somewhat internal
+			this.uid = String(rawProps._id);
+		}
+		else {
+			this.uid = EventDef.generateId();
+		}
+
 		// TODO: converge with EventSource
 		if ($.isArray(rawProps.className)) {
 			this.className = rawProps.className;
@@ -207,6 +213,7 @@ EventDef.generateId = function() {
 
 EventDef.allowRawProps({
 	// not automatically assigned (`false`)
+	_id: false,
 	id: false,
 	className: false,
 	source: false, // will ignored

+ 5 - 0
src/models/event/SingleEventDef.js

@@ -56,6 +56,11 @@ var SingleEventDef = EventDef.extend({
 		if (dateProfile) {
 			this.dateProfile = dateProfile;
 
+			// make sure `date` shows up in the legacy event objects as-is
+			if (rawProps.date != null) {
+				this.miscProps.date = rawProps.date;
+			}
+
 			return superSuccess;
 		}
 		else {

+ 1 - 0
tasks/archive.js

@@ -91,6 +91,7 @@ function transformDemoPath(path) {
 
 	if (
 		!/\.min\.(js|css)$/.test(path) && // not already minified
+		!/^\w/.test(path) && // reference to demo util js/css file
 		path !== '../locale-all.js' // this file is already minified
 	) {
 		// use minified

+ 30 - 0
tests/event-data/EventObject-parsing.js

@@ -0,0 +1,30 @@
+
+describe('Event Object parsing', function() {
+
+	it('leaves an existing _id prop untouched', function() {
+		initCalendar({
+			currentDate: '2017-09-05',
+			defaultView: 'month',
+			events: [
+				{ _id: 'a', start: '2017-09-05' }
+			]
+		});
+
+		var events = currentCalendar.clientEvents();
+		expect(events[0]._id).toBe('a');
+	});
+
+	it('leaves an existing date prop unparsed and untouched', function() {
+		initCalendar({
+			currentDate: '2017-09-05',
+			defaultView: 'month',
+			events: [
+				{ date: '2017-09-05' }
+			]
+		});
+
+		var events = currentCalendar.clientEvents();
+		expect(events[0].date).toBe('2017-09-05');
+	});
+
+});

+ 24 - 0
tests/event-data/dynamic-options.js

@@ -0,0 +1,24 @@
+
+describe('setting option dynamically', function() {
+
+	it('does not cause refetch of events', function(done) {
+		var fetchCnt = 0;
+
+		initCalendar({
+			defaultView: 'month',
+			events: function(start, end, timezone, callback) {
+				fetchCnt++;
+				callback([]);
+			}
+		});
+
+		expect(fetchCnt).toBe(1);
+
+		currentCalendar.option('selectable', true);
+
+		setTimeout(function() { // in case async
+			expect(fetchCnt).toBe(1);
+			done();
+		}, 0);
+	});
+});

+ 47 - 0
tests/event-data/events-function.js

@@ -53,4 +53,51 @@ describe('events as a function', function() {
 			}
 		});
 	});
+
+	it('calls loading callback', function(done) {
+		var loadingCallArgs = [];
+
+		initCalendar({
+			loading: function(bool) {
+				loadingCallArgs.push(bool);
+			},
+			events: function(start, end, timezone, callback) {
+				setTimeout(function() {
+					expect(loadingCallArgs).toEqual([ true ]);
+					callback([]);
+					setTimeout(function() {
+						expect(loadingCallArgs).toEqual([ true, false ]);
+						done();
+					}, 0);
+				}, 0);
+			}
+		});
+	});
+
+	it('calls loading callback only once for multiple sources', function(done) {
+		var loadingCallArgs = [];
+
+		initCalendar({
+			loading: function(bool) {
+				loadingCallArgs.push(bool);
+			},
+			eventSources: [
+				function(start, end, timezone, callback) {
+					setTimeout(function() {
+						callback([]);
+					}, 0);
+				},
+				function(start, end, timezone, callback) {
+					setTimeout(function() {
+						callback([]);
+					}, 10);
+				}
+			]
+		});
+
+		setTimeout(function() {
+			expect(loadingCallArgs).toEqual([ true, false ]);
+			done();
+		}, 20);
+	});
 });

+ 15 - 0
tests/legacy/events-json-feed.js

@@ -119,4 +119,19 @@ describe('events as a json feed', function() {
 		$('#cal').fullCalendar(options);
 	});
 
+	it('calls loading callback', function(done) {
+		var loadingCallArgs = [];
+
+		initCalendar({
+			events: { url: 'my-feed.php' },
+			loading: function(bool) {
+				loadingCallArgs.push(bool);
+			},
+			eventAfterAllRender: function() {
+				expect(loadingCallArgs).toEqual([ true, false ]);
+				done();
+			}
+		});
+	});
+
 });

+ 242 - 0
tests/manual/profiling.html

@@ -0,0 +1,242 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset='utf-8' />
+<link href='../../dist/fullcalendar.css' rel='stylesheet' />
+<link href='../../dist/fullcalendar.print.css' rel='stylesheet' media='print' />
+<script src='../../node_modules/moment/moment.js'></script>
+<script src='../../node_modules/jquery/dist/jquery.js'></script>
+<script src='../../dist/fullcalendar.js'></script>
+<script>
+
+	$(document).ready(function() {
+		var isOneTime = false;
+
+		function initProfilingAction(containerEl, execFunc, teardownFunc) {
+			containerEl.find('.profiling-action__button').on('click', function() {
+				if (isOneTime) {
+					containerEl.find('.profiling-action__result')
+						.text(executeOneTime(execFunc) + 'ms');
+				}
+				else {
+					executeTimes(execFunc, teardownFunc, 100).then(function(res) {
+						containerEl.find('.profiling-action__result')
+							.text(res + 'ms ave');
+					});
+				}
+			});
+		}
+
+		function executeOneTime(execFunc) {
+			var startMs;
+			var totalMs;
+
+			execFunc(function() {
+				startMs = new Date().valueOf();
+			}, function() {
+				totalMs = new Date().valueOf() - startMs;
+			});
+
+			return totalMs;
+		}
+
+		function executeTimes(execFunc, teardownFunc, times) {
+			var deferred = $.Deferred();
+			var totalTotalMs = 0;
+			var i = 0;
+
+			function next() {
+				if (i < times) {
+					setTimeout(function() {
+						var startMs;
+						var totalMs;
+
+						if (i && teardownFunc) {
+							teardownFunc();
+						}
+						execFunc(function() {
+							startMs = new Date().valueOf();
+						}, function() {
+							totalMs = new Date().valueOf() - startMs;
+						});
+						totalTotalMs += totalMs;
+
+						i++;
+						next();
+					}, 0);
+				}
+				else {
+					deferred.resolve(totalTotalMs / times);
+				}
+			}
+
+			next();
+
+			return deferred.promise();
+		}
+
+		function initCalendar() {
+			$('#calendar').fullCalendar({
+				header: {
+					left: 'prev,next today',
+					center: 'title',
+					right: 'month,agendaWeek,agendaDay,listWeek'
+				},
+				defaultDate: '2017-07-01',
+				navLinks: true, // can click day/week names to navigate views
+				editable: true
+			});
+		}
+
+		function destroyCalendar() {
+			$('#calendar').fullCalendar('destroy');
+		}
+
+		initProfilingAction($('#init-calendar'), function(start, stop) {
+			start();
+			initCalendar();
+			stop();
+		}, destroyCalendar);
+
+		initProfilingAction($('#render-month-events'), function(start, stop) {
+			initCalendar();
+
+			var calendar = $('#calendar').fullCalendar('getCalendar');
+			calendar.changeView('month');
+
+			var date = calendar.getView().start.clone();
+			var end = calendar.getView().end.clone();
+			var events = [];
+
+			while (date.isBefore(end)) {
+				events.push({
+					title: '3 day event',
+					start: date.clone(),
+					end: date.clone().add(3, 'days')
+				}, {
+					title: '2 day timed event',
+					start: date.clone().time('03:00'),
+					end: date.clone().add(1, 'day').time('20:00').format()
+				}, {
+					title: 'timed event',
+					start: date.clone().time('16:00')
+				}, {
+					title: 'timed event',
+					start: date.clone().time('16:00')
+				}, {
+					title: 'timed event',
+					start: date.clone().time('16:00')
+				}, {
+					title: 'timed event',
+					start: date.clone().time('16:00')
+				}, {
+					title: 'timed event',
+					start: date.clone().time('16:00')
+				});
+				date.add(1, 'day');
+			}
+
+			start();
+			calendar.renderEvents(events);
+			stop();
+			//console.log('rendered ' + events.length + ' events');
+		}, destroyCalendar);
+
+		initProfilingAction($('#render-agenda-events'), function(start, stop) {
+			initCalendar();
+
+			var calendar = $('#calendar').fullCalendar('getCalendar');
+			calendar.changeView('agendaWeek');
+
+			var date = calendar.getView().start.clone();
+			var end = calendar.getView().end.clone();
+			var events = [];
+			var time;
+			var calendar;
+
+			while (date.isBefore(end)) {
+				time = moment.duration(0);
+
+				while (time < moment.duration('24:00')) {
+					events.push({
+						title: 'event',
+						start: date.clone().time(time)
+					});
+
+					time.add(30, 'minutes');
+				}
+
+				date.add(1, 'day');
+			}
+
+			start();
+			calendar.renderEvents(events);
+			stop();
+			//console.log('rendered ' + events.length + ' events');
+		}, destroyCalendar);
+
+		initProfilingAction($('#clear-events'), function(start, stop) {
+			var calendar = $('#calendar').fullCalendar('getCalendar');
+
+			start();
+			calendar.removeEvents();
+			stop();
+		});
+		
+	});
+
+</script>
+<style>
+
+	body {
+		margin: 10px 10px;
+		padding: 0;
+		font-family: "Lucida Grande",Helvetica,Arial,Verdana,sans-serif;
+		font-size: 14px;
+		overflow: scroll;
+	}
+
+	#profiling-area {
+		float: right;
+		text-align: right;
+	}
+
+	#calendar {
+		max-width: 900px;
+		float: left;
+	}
+
+</style>
+</head>
+<body>
+
+	<div id='profiling-area'>
+
+		<div class='profiling-action' id='init-calendar'>
+			<span class='profiling-action__result'></span>
+			<button class='profiling-action__button'>init calendar</button>
+		</div>
+
+		<div class='profiling-action' id='render-month-events'>
+			<span class='profiling-action__result'></span>
+			<button class='profiling-action__button'>render events for month</button>
+		</div>
+
+		<div class='profiling-action' id='render-agenda-events'>
+			<span class='profiling-action__result'></span>
+			<button class='profiling-action__button'>render events for agenda</button>
+		</div>
+
+		<div class='profiling-action' id='clear-events'>
+			<span class='profiling-action__result'></span>
+			<button class='profiling-action__button'>clear events</button>
+		</div>
+
+	</div>
+
+	<div id='calendar'></div>
+
+	<div style='clear:both'></div>
+
+</body>
+</html>

+ 30 - 0
tests/manual/profiling_results.txt

@@ -0,0 +1,30 @@
+
+# v3.5.0
+
+## Chrome
+
+27.71ms ave   init calendar             (4% improvement)
+102.29ms ave  render events for month   (22% improvement)
+112.77ms ave  render events for agenda  (29% improvement)
+
+## IE11
+
+72.79ms ave   init calendar             (10% improvement)
+418.35ms ave  render events for month   (10% improvement)
+393.8ms ave   render events for agenda  (28% improvement)
+
+
+
+# v3.4.0
+
+## Chrome
+
+28.9ms ave    init calendar
+131.7ms ave   render events for month
+157.91ms ave  render events for agenda
+
+## IE11
+
+80.49ms ave   init calendar 
+464.73ms ave  render events for month 
+550.13ms ave  render events for agenda

+ 1 - 1
tests/theme/bootstrap3.js

@@ -1,6 +1,6 @@
 
 describe('bootstrap3 theme', function() {
-	pushOptions({ theme: 'bootstrap3' });
+	pushOptions({ themeSystem: 'bootstrap3' });
 
 	describe('glyphicons', function() {
 		pushOptions({

+ 3 - 3
tests/theme/switching.js

@@ -4,14 +4,14 @@ describe('theme switching', function() {
 	it('can switch from standard to jquery-ui', function() {
 		initCalendar();
 		verifyStandardTheme();
-		currentCalendar.option('theme', 'jquery-ui');
+		currentCalendar.option('themeSystem', 'jquery-ui');
 		verifyJqueryUiTheme();
 	});
 
 	it('can switch from jquery-ui to boostrap3', function() {
-		initCalendar({ theme: 'jquery-ui' });
+		initCalendar({ themeSystem: 'jquery-ui' });
 		verifyJqueryUiTheme();
-		currentCalendar.option('theme', 'bootstrap3');
+		currentCalendar.option('themeSystem', 'bootstrap3');
 		verifyBootstrapTheme();
 	});
 

+ 2 - 2
tests/toolbar/customButtons.js

@@ -25,7 +25,7 @@ describe('customButtons', function() {
 
 	it('can specify a jquery-ui icon', function() {
 		initCalendar({
-			theme: 'jquery-ui',
+			themeSystem: 'jquery-ui',
 			customButtons: {
 				mybutton: { themeIcon: 'asdf' }
 			},
@@ -37,7 +37,7 @@ describe('customButtons', function() {
 
 	it('can specify a bootstrap glyphicon', function() {
 		initCalendar({
-			theme: 'bootstrap3',
+			themeSystem: 'bootstrap3',
 			customButtons: {
 				mybutton: { bootstrapGlyphicon: 'asdf' }
 			},