diff --git a/README.md b/README.md
index 8a8722e0..65d9c535 100644
--- a/README.md
+++ b/README.md
@@ -3,7 +3,7 @@
[![Circle CI](https://img.shields.io/circleci/project/timekit-io/booking-js.svg)](https://circleci.com/gh/timekit-io/booking-js)
[![Codacy Badge](https://api.codacy.com/project/badge/grade/feb445801acf454a95b1690a75959893)](https://www.codacy.com/app/laander/booking-js)
-**Latest release:** [v1.5.0](https://github.com/timekit-io/booking-js/releases)
+**Latest release:** [v1.5.1](https://github.com/timekit-io/booking-js/releases)
> Make a beautiful embeddable booking widget in minutes.
diff --git a/dist/booking.js b/dist/booking.js
index 34fd4efa..461cc5a9 100644
--- a/dist/booking.js
+++ b/dist/booking.js
@@ -58,7 +58,7 @@ return /******/ (function(modules) { // webpackBootstrap
/*!
* Booking.js
- * Version: 1.5.0
+ * Version: 1.5.1
* http://booking.timekit.io
*
* Copyright 2015 Timekit, Inc.
@@ -276,12 +276,12 @@ return /******/ (function(modules) { // webpackBootstrap
var decideCalendarSize = function() {
var view = 'agendaWeek';
- var height = 470;
+ var height = 420;
var rootWidth = rootTarget.width();
if (rootWidth < 480) {
view = 'basicDay';
- height = 346;
+ height = 335;
rootTarget.addClass('is-small');
} else {
rootTarget.removeClass('is-small');
@@ -355,6 +355,7 @@ return /******/ (function(modules) { // webpackBootstrap
closeIcon: __webpack_require__(50),
checkmarkIcon: __webpack_require__(51),
loadingIcon: __webpack_require__(52),
+ errorIcon: __webpack_require__(53),
submitText: 'Book it',
successMessageTitle: 'Thanks!',
successMessagePart1: 'An invitation has been sent to:',
@@ -416,7 +417,7 @@ return /******/ (function(modules) { // webpackBootstrap
var formElement = $(form);
// Abort if form is submitting, have submitted or does not validate
- if(formElement.hasClass('loading') || formElement.hasClass('success') || !e.target.checkValidity()) {
+ if(formElement.hasClass('loading') || formElement.hasClass('success') || formElement.hasClass('error') || !e.target.checkValidity()) {
var submitButton = formElement.find('.bookingjs-form-button');
submitButton.addClass('button-shake');
setTimeout(function() {
@@ -443,7 +444,20 @@ return /******/ (function(modules) { // webpackBootstrap
formElement.removeClass('loading').addClass('success');
}).catch(function(response){
+
utils.doCallback('createEventFailed', config, response);
+
+ var submitButton = formElement.find('.bookingjs-form-button');
+ submitButton.addClass('button-shake');
+ setTimeout(function() {
+ submitButton.removeClass('button-shake');
+ }, 500);
+
+ formElement.removeClass('loading').addClass('error');
+ setTimeout(function() {
+ formElement.removeClass('error');
+ }, 2000);
+
throw new Error('TimekitBooking - An error with Timekit createEvent occured, context: ' + response);
});
@@ -478,8 +492,8 @@ return /******/ (function(modules) { // webpackBootstrap
// Render the powered by Timekit message
var renderPoweredByMessage = function(pageTarget) {
- var template = __webpack_require__(53);
- var timekitIcon = __webpack_require__(54);
+ var template = __webpack_require__(54);
+ var timekitIcon = __webpack_require__(55);
var poweredTarget = $(template.render({
timekitIcon: timekitIcon
}));
@@ -18895,7 +18909,7 @@ return /******/ (function(modules) { // webpackBootstrap
exports.push([module.id, "@import url(https://fonts.googleapis.com/css?family=Open+Sans:400,600);", ""]);
// module
- exports.push([module.id, "/*!\n * Booking.js\n * http://booking.timekit.io\n * (c) 2015 Timekit Inc.\n */.bookingjs{position:relative;font-family:Open Sans,Helvetica,Tahoma,Arial,sans-serif;font-size:13px;border-radius:4px;background-color:#fff;box-shadow:rgba(0,0,0,.2) 0 2px 4px 0;margin:60px auto 20px;z-index:10;opacity:0;color:#333}.bookingjs.show{-webkit-transition:opacity .3s ease;transition:opacity .3s ease;opacity:1}.is-small.has-avatar.has-displayname .bookingjs-calendar .fc-toolbar{padding-bottom:24px}.is-small .bookingjs-calendar .fc-toolbar>.fc-right>button.fc-today-button{position:absolute;left:15px}.bookingjs-timezonehelper{color:#aeaeae;text-align:center;padding:7px 10px;background-color:#fbfbfb;border-top:1px solid #ececec;min-height:15px;z-index:20;border-radius:0 0 4px 4px}.bookingjs-timezoneicon{width:10px;margin-right:5px}.bookingjs-avatar{position:absolute;top:-50px;left:50%;-webkit-transform:translateX(-50%);-ms-transform:translateX(-50%);transform:translateX(-50%);border-radius:150px;border:3px solid #fff;box-shadow:0 1px 3px 0 rgba(0,0,0,.13);overflow:hidden;z-index:40;background-color:#fff}.is-small .bookingjs-avatar{top:-40px}.bookingjs-avatar img{max-width:100%;vertical-align:middle;display:inline-block;width:80px;height:80px}.is-small .bookingjs-avatar img{width:70px;height:70px}.bookingjs-displayname{position:absolute;top:0;left:0;padding:15px 20px;color:#333;font-weight:600}.is-small .bookingjs-displayname{text-align:center;width:100%;box-sizing:border-box}.is-small.has-avatar .bookingjs-displayname{top:28px}.bookingjs-bookpage{position:absolute;height:100%;width:100%;top:0;left:0;background-color:#fbfbfb;z-index:30;opacity:0;-webkit-transition:opacity .2s ease;transition:opacity .2s ease;border-radius:4px}.bookingjs-bookpage.show{opacity:1}.bookingjs-bookpage-close{position:absolute;top:0;right:0;padding:18px;-webkit-transition:opacity .2s ease;transition:opacity .2s ease;opacity:.3}.bookingjs-bookpage-close:hover{opacity:1}.bookingjs-bookpage-date{text-align:center;font-size:34px;font-weight:400;margin-top:90px;margin-bottom:20px}.is-small .bookingjs-bookpage-date{font-size:24px;margin-top:60px;margin-bottom:15px}.bookingjs-bookpage-time{text-align:center;font-size:17px;font-weight:400;margin-bottom:70px}.is-small .bookingjs-bookpage-time{font-size:15px;margin-bottom:30px}.bookingjs-closeicon{width:15px}.bookingjs-form{width:350px;position:relative;margin:0 auto;text-align:center}.is-small .bookingjs-form{width:90%}.bookingjs-form-box{position:relative;box-shadow:0 1px 3px 0 rgba(0,0,0,.1);border-radius:4px;overflow:hidden;background-color:#fff;line-height:0}.bookingjs-form-success-message{position:absolute;top:-999px;left:0;right:0;padding:30px;background-color:#fff;opacity:0;-webkit-transition:opacity .3s ease;transition:opacity .3s ease;line-height:normal}.is-small .bookingjs-form-success-message{padding:22px 10px}.bookingjs-form-success-message .title{font-weight:600}.bookingjs-form-success-message .booked-email{color:#aeaeae}.bookingjs-form.success .bookingjs-form-success-message{opacity:1;top:0;bottom:0}.bookingjs-form-input{-webkit-transition:box-shadow .2s ease;transition:box-shadow .2s ease;width:100%;padding:15px 25px;border:0 solid #ececec;font-size:1em;box-shadow:inset 0 0 1px 1px hsla(0,0%,100%,0);text-align:left;box-sizing:border-box;line-height:normal;font-family:Open Sans,Helvetica,Tahoma,Arial,sans-serif}.bookingjs-form-input:focus{outline:0;box-shadow:inset 0 0 1px 1px #689ad8}.bookingjs-form-input.hidden{display:none}.bookingjs-form-button{position:relative;-webkit-transition:background-color .2s,max-width .3s;transition:background-color .2s,max-width .3s;display:inline-block;padding:13px 25px;background-color:#689ad8;text-transform:uppercase;box-shadow:0 1px 3px 0 rgba(0,0,0,.15);color:#fff;border:0;border-radius:3px;font-size:1.1em;font-weight:600;margin-top:30px;cursor:pointer;height:44px;outline:0;text-align:center;max-width:200px}.bookingjs-form-button .loading-text,.bookingjs-form-button .success-text{-webkit-transition:opacity .3s ease;transition:opacity .3s ease;position:absolute;top:13px;left:50%;-webkit-transform:translateX(-50%);-ms-transform:translateX(-50%);transform:translateX(-50%)}.bookingjs-form-button .inactive-text{opacity:1}.bookingjs-form-button .loading-text,.bookingjs-form-button .success-text{opacity:0}.bookingjs-form-button .loading-text svg{height:19px;width:19px;-webkit-animation:spin .6s infinite linear;animation:spin .6s infinite linear}.bookingjs-form-button .success-text svg{height:15px;margin-top:2px;-webkit-transform:scale(0);-ms-transform:scale(0);transform:scale(0);-webkit-transition:-webkit-transform .6s ease;transition:-webkit-transform .6s ease;transition:transform .6s ease;transition:transform .6s ease,-webkit-transform .6s ease}.bookingjs-form-button:hover{background-color:#3f7fce}.bookingjs-form-button.button-shake{-webkit-animation:shake .5s 1 ease;animation:shake .5s 1 ease}.bookingjs-form.loading .bookingjs-form-button,.bookingjs-form.loading .bookingjs-form-button:hover{max-width:80px;background-color:#b1b1b1}.bookingjs-form.loading .bookingjs-form-button .inactive-text,.bookingjs-form.loading .bookingjs-form-button:hover .inactive-text{opacity:0}.bookingjs-form.loading .bookingjs-form-button .loading-text,.bookingjs-form.loading .bookingjs-form-button:hover .loading-text{opacity:1}.bookingjs-form.success .bookingjs-form-button,.bookingjs-form.success .bookingjs-form-button:hover{max-width:80px;background-color:#5baf56}.bookingjs-form.success .bookingjs-form-button .inactive-text,.bookingjs-form.success .bookingjs-form-button .loading-text,.bookingjs-form.success .bookingjs-form-button:hover .inactive-text,.bookingjs-form.success .bookingjs-form-button:hover .loading-text{opacity:0}.bookingjs-form.success .bookingjs-form-button .success-text,.bookingjs-form.success .bookingjs-form-button:hover .success-text{opacity:1}.bookingjs-form.success .bookingjs-form-button .success-text svg,.bookingjs-form.success .bookingjs-form-button:hover .success-text svg{-webkit-transform:scale(1);-ms-transform:scale(1);transform:scale(1)}.bookingjs-poweredby{position:absolute;bottom:0;left:0;right:0;text-align:center;padding:7px 10px}.bookingjs-poweredby a{-webkit-transition:color .2s ease;transition:color .2s ease;color:#aeaeae;text-decoration:none}.bookingjs-poweredby a svg path{-webkit-transition:fill .2s ease;transition:fill .2s ease;fill:#aeaeae}.bookingjs-poweredby a:hover{color:#333}.bookingjs-poweredby a:hover svg path{fill:#333}.bookingjs-timekiticon{width:13px;margin-right:5px;vertical-align:sub}", ""]);
+ exports.push([module.id, "/*!\n * Booking.js\n * http://booking.timekit.io\n * (c) 2015 Timekit Inc.\n */.bookingjs{position:relative;font-family:Open Sans,Helvetica,Tahoma,Arial,sans-serif;font-size:13px;border-radius:4px;background-color:#fff;box-shadow:rgba(0,0,0,.2) 0 2px 4px 0;margin:60px auto 20px;z-index:10;opacity:0;color:#333}.bookingjs.show{-webkit-transition:opacity .3s ease;transition:opacity .3s ease;opacity:1}.is-small.has-avatar.has-displayname .bookingjs-calendar .fc-toolbar{padding-bottom:24px}.is-small .bookingjs-calendar .fc-toolbar>.fc-right>button.fc-today-button{position:absolute;left:15px}.bookingjs-timezonehelper{color:#aeaeae;text-align:center;padding:7px 10px;background-color:#fbfbfb;border-top:1px solid #ececec;min-height:15px;z-index:20;border-radius:0 0 4px 4px}.bookingjs-timezoneicon{width:10px;margin-right:5px}.bookingjs-avatar{position:absolute;top:-50px;left:50%;-webkit-transform:translateX(-50%);-ms-transform:translateX(-50%);transform:translateX(-50%);border-radius:150px;border:3px solid #fff;box-shadow:0 1px 3px 0 rgba(0,0,0,.13);overflow:hidden;z-index:40;background-color:#fff}.is-small .bookingjs-avatar{top:-40px}.bookingjs-avatar img{max-width:100%;vertical-align:middle;display:inline-block;width:80px;height:80px}.is-small .bookingjs-avatar img{width:70px;height:70px}.bookingjs-displayname{position:absolute;top:0;left:0;padding:15px 20px;color:#333;font-weight:600}.is-small .bookingjs-displayname{text-align:center;width:100%;box-sizing:border-box}.is-small.has-avatar .bookingjs-displayname{top:28px}.bookingjs-bookpage{position:absolute;height:100%;width:100%;top:0;left:0;background-color:#fbfbfb;z-index:30;opacity:0;-webkit-transition:opacity .2s ease;transition:opacity .2s ease;border-radius:4px}.bookingjs-bookpage.show{opacity:1}.bookingjs-bookpage-close{position:absolute;top:0;right:0;padding:18px;-webkit-transition:opacity .2s ease;transition:opacity .2s ease;opacity:.3}.bookingjs-bookpage-close:hover{opacity:1}.bookingjs-bookpage-date{text-align:center;font-size:34px;font-weight:400;margin-top:70px;margin-bottom:10px}.is-small .bookingjs-bookpage-date{font-size:27px;margin-top:60px}.bookingjs-bookpage-time{text-align:center;font-size:17px;font-weight:400;margin-bottom:50px;margin-top:10px}.is-small .bookingjs-bookpage-time{font-size:15px;margin-bottom:35px}.bookingjs-closeicon{width:15px}.bookingjs-form{width:350px;position:relative;margin:0 auto;text-align:center}.is-small .bookingjs-form{width:90%}.bookingjs-form-box{position:relative;box-shadow:0 1px 3px 0 rgba(0,0,0,.1);border-radius:4px;overflow:hidden;background-color:#fff;line-height:0}.bookingjs-form-success-message{position:absolute;top:-999px;left:0;right:0;padding:30px;background-color:#fff;opacity:0;-webkit-transition:opacity .3s ease;transition:opacity .3s ease;line-height:normal}.is-small .bookingjs-form-success-message{padding:22px 10px}.bookingjs-form-success-message .title{font-weight:600}.bookingjs-form-success-message .booked-email{color:#aeaeae}.bookingjs-form.success .bookingjs-form-success-message{opacity:1;top:0;bottom:0}.bookingjs-form-input{-webkit-transition:box-shadow .2s ease;transition:box-shadow .2s ease;width:100%;padding:15px 25px;border:0 solid #ececec;font-size:1em;box-shadow:inset 0 0 1px 1px hsla(0,0%,100%,0);text-align:left;box-sizing:border-box;line-height:normal;font-family:Open Sans,Helvetica,Tahoma,Arial,sans-serif}.bookingjs-form-input:focus{outline:0;box-shadow:inset 0 0 1px 1px #689ad8}.bookingjs-form-input.hidden{display:none}.bookingjs-form-button{position:relative;-webkit-transition:background-color .2s,max-width .3s;transition:background-color .2s,max-width .3s;display:inline-block;padding:13px 25px;background-color:#689ad8;text-transform:uppercase;box-shadow:0 1px 3px 0 rgba(0,0,0,.15);color:#fff;border:0;border-radius:3px;font-size:1.1em;font-weight:600;margin-top:30px;cursor:pointer;height:44px;outline:0;text-align:center;max-width:200px}.bookingjs-form-button .error-text,.bookingjs-form-button .loading-text,.bookingjs-form-button .success-text{-webkit-transition:opacity .3s ease;transition:opacity .3s ease;position:absolute;top:13px;left:50%;-webkit-transform:translateX(-50%);-ms-transform:translateX(-50%);transform:translateX(-50%);opacity:0}.bookingjs-form-button .inactive-text{white-space:nowrap;opacity:1}.bookingjs-form-button .loading-text svg{height:19px;width:19px;-webkit-animation:spin .6s infinite linear;animation:spin .6s infinite linear}.bookingjs-form-button .error-text svg{height:15px;width:15px;margin-top:2px}.bookingjs-form-button .success-text svg{height:15px;margin-top:2px;-webkit-transform:scale(0);-ms-transform:scale(0);transform:scale(0);-webkit-transition:-webkit-transform .6s ease;transition:-webkit-transform .6s ease;transition:transform .6s ease;transition:transform .6s ease,-webkit-transform .6s ease}.bookingjs-form-button:hover{background-color:#3f7fce}.bookingjs-form-button.button-shake{-webkit-animation:shake .5s 1 ease;animation:shake .5s 1 ease}.bookingjs-form.loading .bookingjs-form-button,.bookingjs-form.loading .bookingjs-form-button:hover{max-width:80px;background-color:#b1b1b1;cursor:not-allowed}.bookingjs-form.loading .bookingjs-form-button .inactive-text,.bookingjs-form.loading .bookingjs-form-button:hover .inactive-text{opacity:0}.bookingjs-form.loading .bookingjs-form-button .loading-text,.bookingjs-form.loading .bookingjs-form-button:hover .loading-text{opacity:1}.bookingjs-form.error .bookingjs-form-button,.bookingjs-form.error .bookingjs-form-button:hover{max-width:80px;background-color:#d83b46;cursor:not-allowed}.bookingjs-form.error .bookingjs-form-button .inactive-text,.bookingjs-form.error .bookingjs-form-button:hover .inactive-text{opacity:0}.bookingjs-form.error .bookingjs-form-button .error-text,.bookingjs-form.error .bookingjs-form-button:hover .error-text{opacity:1}.bookingjs-form.success .bookingjs-form-button,.bookingjs-form.success .bookingjs-form-button:hover{max-width:80px;background-color:#5baf56;cursor:not-allowed}.bookingjs-form.success .bookingjs-form-button .inactive-text,.bookingjs-form.success .bookingjs-form-button:hover .inactive-text{opacity:0}.bookingjs-form.success .bookingjs-form-button .success-text,.bookingjs-form.success .bookingjs-form-button:hover .success-text{opacity:1}.bookingjs-form.success .bookingjs-form-button .success-text svg,.bookingjs-form.success .bookingjs-form-button:hover .success-text svg{-webkit-transform:scale(1);-ms-transform:scale(1);transform:scale(1)}.bookingjs-poweredby{position:absolute;bottom:0;left:0;right:0;text-align:center;padding:7px 10px}.bookingjs-poweredby a{-webkit-transition:color .2s ease;transition:color .2s ease;color:#aeaeae;text-decoration:none}.bookingjs-poweredby a svg path{-webkit-transition:fill .2s ease;transition:fill .2s ease;fill:#aeaeae}.bookingjs-poweredby a:hover{color:#333}.bookingjs-poweredby a:hover svg path{fill:#333}.bookingjs-timekiticon{width:13px;margin-right:5px;vertical-align:sub}", ""]);
// exports
@@ -19742,7 +19756,7 @@ return /******/ (function(modules) { // webpackBootstrap
/***/ function(module, exports, __webpack_require__) {
var H = __webpack_require__(43);
- module.exports = function() { var T = new H.Template({code: function (c,p,i) { var t=this;t.b(i=i||"");t.b("
');\n rootTarget.append(calendarTarget);\n\n calendarTarget.fullCalendar(args);\n rootTarget.addClass('show');\n\n utils.doCallback('fullCalendarInitialized', config);\n\n };\n\n // Fires when window is resized and calendar must adhere\n var decideCalendarSize = function() {\n\n var view = 'agendaWeek';\n var height = 470;\n var rootWidth = rootTarget.width();\n\n if (rootWidth < 480) {\n view = 'basicDay';\n height = 346;\n rootTarget.addClass('is-small');\n } else {\n rootTarget.removeClass('is-small');\n }\n\n if (config.bookingFields.comment.enabled) { height += 84; }\n if (config.bookingFields.phone.enabled) { height += 48; }\n if (config.bookingFields.voip.enabled) { height += 48; }\n if (config.bookingFields.location.enabled) { height += 48; }\n\n return {\n height: height,\n view: view\n };\n\n };\n\n // Render the supplied calendar events in FullCalendar\n var renderCalendarEvents = function(eventData) {\n\n calendarTarget.fullCalendar('addEventSource', {\n events: eventData\n });\n\n calendarTarget.removeClass('empty-calendar');\n\n };\n\n // Render the avatar image\n var renderAvatarImage = function() {\n\n var template = require('./templates/user-avatar.html');\n var avatarTarget = $(template.render({\n image: config.avatar\n }));\n\n rootTarget.addClass('has-avatar');\n rootTarget.append(avatarTarget);\n\n };\n\n // Render the avatar image\n var renderDisplayName = function() {\n\n var template = require('./templates/user-displayname.html');\n var displayNameTarget = $(template.render({\n name: config.name\n }));\n\n rootTarget.addClass('has-displayname');\n rootTarget.append(displayNameTarget);\n\n };\n\n // Event handler when a timeslot is clicked in FullCalendar\n var showBookingPage = function(eventData) {\n\n utils.doCallback('showBookingPage', config, eventData);\n\n var fieldsTemplate = require('./templates/booking-fields.html');\n var template = require('./templates/booking-page.html');\n\n var dateFormat = config.localization.bookingDateFormat || moment.localeData().longDateFormat('LL');\n var timeFormat = config.localization.bookingTimeFormat || moment.localeData().longDateFormat('LT');\n\n bookingPageTarget = $(template.render({\n chosenDate: moment(eventData.start).format(dateFormat),\n chosenTime: moment(eventData.start).format(timeFormat) + ' - ' + moment(eventData.end).format(timeFormat),\n start: moment(eventData.start).format(),\n end: moment(eventData.end).format(),\n closeIcon: require('!svg-inline!./assets/close-icon.svg'),\n checkmarkIcon: require('!svg-inline!./assets/checkmark-icon.svg'),\n loadingIcon: require('!svg-inline!./assets/loading-spinner.svg'),\n submitText: 'Book it',\n successMessageTitle: 'Thanks!',\n successMessagePart1: 'An invitation has been sent to:',\n successMessagePart2: 'Accept the invitation to confirm the booking.',\n fields: config.bookingFields\n }, {\n formFields: fieldsTemplate\n }));\n\n bookingPageTarget.children('.bookingjs-bookpage-close').click(function(e) {\n e.preventDefault();\n hideBookingPage();\n });\n\n var form = bookingPageTarget.children('.bookingjs-form');\n\n form.submit(function(e) {\n submitBookingForm(this, e);\n });\n\n // Show powered by Timekit message\n if (config.showCredits) {\n renderPoweredByMessage(bookingPageTarget);\n }\n\n $(document).on('keyup', function(e) {\n // escape key maps to keycode `27`\n if (e.keyCode === 27) { hideBookingPage(); }\n });\n\n rootTarget.append(bookingPageTarget);\n\n setTimeout(function(){\n bookingPageTarget.addClass('show');\n }, 100);\n\n };\n\n // Remove the booking page DOM node\n var hideBookingPage = function() {\n\n utils.doCallback('closeBookingPage', config);\n\n bookingPageTarget.removeClass('show');\n\n setTimeout(function(){\n bookingPageTarget.remove();\n }, 200);\n\n $(document).off('keyup');\n\n };\n\n // Event handler on form submit\n var submitBookingForm = function(form, e) {\n\n e.preventDefault();\n\n var formElement = $(form);\n\n // Abort if form is submitting, have submitted or does not validate\n if(formElement.hasClass('loading') || formElement.hasClass('success') || !e.target.checkValidity()) {\n var submitButton = formElement.find('.bookingjs-form-button');\n submitButton.addClass('button-shake');\n setTimeout(function() {\n submitButton.removeClass('button-shake');\n }, 500);\n return;\n }\n\n var values = {};\n $.each(formElement.serializeArray(), function(i, field) {\n values[field.name] = field.value;\n });\n\n formElement.addClass('loading');\n\n utils.doCallback('submitBookingForm', config, values);\n\n // Call create event endpoint\n timekitCreateEvent(values).then(function(response){\n\n utils.doCallback('createEventSuccessful', config, response);\n\n formElement.find('.booked-email').html(values.email);\n formElement.removeClass('loading').addClass('success');\n\n }).catch(function(response){\n utils.doCallback('createEventFailed', config, response);\n throw new Error('TimekitBooking - An error with Timekit createEvent occured, context: ' + response);\n });\n\n };\n\n // Create new event through Timekit SDK\n var timekitCreateEvent = function(data) {\n\n var args = {\n start: data.start,\n end: data.end,\n what: config.name + ' x ' + data.name,\n where: 'TBD',\n description: '',\n calendar_id: config.calendar,\n participants: [config.email, data.email]\n };\n\n if (config.bookingFields.location.enabled) { args.where = data.location; }\n if (config.bookingFields.phone.enabled) { args.description += config.bookingFields.phone.placeholder + ': ' + data.phone + '\\n'; }\n if (config.bookingFields.voip.enabled) { args.description += config.bookingFields.voip.placeholder + ': ' + data.voip + '\\n'; }\n if (config.bookingFields.comment.enabled) { args.description += config.bookingFields.comment.placeholder + ': ' + data.comment + '\\n'; }\n\n $.extend(true, args, config.timekitCreateEvent);\n\n utils.doCallback('createEventStarted', config, args);\n\n return timekit.createEvent(args);\n\n };\n\n // Render the powered by Timekit message\n var renderPoweredByMessage = function(pageTarget) {\n\n var template = require('./templates/poweredby.html');\n var timekitIcon = require('!svg-inline!./assets/timekit-icon.svg');\n var poweredTarget = $(template.render({\n timekitIcon: timekitIcon\n }));\n\n pageTarget.append(poweredTarget);\n\n };\n\n // Set configs and defaults\n var setConfig = function(suppliedConfig) {\n\n // Check whether a config is supplied\n if(suppliedConfig === undefined || typeof suppliedConfig !== 'object' || $.isEmptyObject(suppliedConfig)) {\n if (window.timekitBookingConfig !== undefined) {\n suppliedConfig = window.timekitBookingConfig;\n } else {\n throw new Error('TimekitBooking - No configuration was supplied or found. Please supply a config object upon library initialization');\n }\n }\n\n // Extend the default config with supplied settings\n var newConfig = $.extend(true, {}, defaultConfig.primary, suppliedConfig);\n\n // Apply any presets if applicable (supplied config have presedence over preset)\n var presetsConfig = {};\n if(newConfig.localization.timeDateFormat === '24h-dmy-mon') {\n presetsConfig = defaultConfig.presets.timeDateFormat24hdmymon;\n }\n if(newConfig.localization.timeDateFormat === '12h-mdy-sun') {\n presetsConfig = defaultConfig.presets.timeDateFormat12hmdysun;\n }\n\n // Extend the config with the presets\n var finalConfig = $.extend(true, {}, presetsConfig, newConfig);\n\n // Check for required settings\n if(!finalConfig.email || !finalConfig.apiToken || !finalConfig.calendar) {\n throw new Error('TimekitBooking - A required config setting was missing (\"email\", \"apiToken\" or \"calendar\")');\n }\n\n // Set new config to instance config\n config = finalConfig;\n\n return config;\n\n };\n\n // Get library config\n var getConfig = function() {\n\n return config;\n\n };\n\n // Render method\n var render = function() {\n\n // Set rootTarget to the target element and clean before child nodes before continuing\n prepareDOM();\n\n // Setup Timekit SDK config\n timekitSetup();\n\n // Initialize FullCalendar\n initializeCalendar();\n\n // Get availability through Timekit SDK\n timekitFindTime();\n\n // Show timezone helper if enabled\n if (config.localization.showTimezoneHelper) {\n renderTimezoneHelper();\n }\n\n // Show image avatar if set\n if (config.avatar) {\n renderAvatarImage();\n }\n\n // Print out display name\n if (config.name) {\n renderDisplayName();\n }\n\n utils.doCallback('renderCompleted', config);\n\n return this;\n\n };\n\n // Initilization method\n var init = function(suppliedConfig) {\n\n // Handle config and defaults\n setConfig(suppliedConfig);\n\n // Include library styles if enabled\n if (config.includeStyles) {\n includeStyles();\n }\n\n return render();\n\n };\n\n var destroy = function() {\n\n prepareDOM();\n config = {};\n return this;\n\n };\n\n // The fullCalendar object for advanced puppeting\n var fullCalendar = function() {\n\n if (calendarTarget.fullCalendar === undefined) { return undefined; }\n return calendarTarget.fullCalendar.apply(calendarTarget, arguments);\n\n };\n\n // Expose methods\n return {\n setConfig: setConfig,\n getConfig: getConfig,\n render: render,\n init: init,\n destroy: destroy,\n fullCalendar: fullCalendar\n };\n\n}\n\n// Autoload if config is available on window, else export function\nif (window && window.timekitBookingConfig && window.timekitBookingConfig.autoload !== false) {\n $(window).load(function(){\n var instance = new TimekitBooking();\n instance.init(window.timekitBookingConfig);\n module.exports = instance;\n });\n} else {\n module.exports = TimekitBooking;\n}\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./src/main.js\n ** module id = 0\n ** module chunks = 0\n **/","module.exports = __WEBPACK_EXTERNAL_MODULE_1__;\n\n\n/*****************\n ** WEBPACK FOOTER\n ** external \"jQuery\"\n ** module id = 1\n ** module chunks = 0\n **/","/*!\n * FullCalendar v2.4.0\n * Docs & License: http://fullcalendar.io/\n * (c) 2015 Adam Shaw\n */\n\n(function(factory) {\n\tif (typeof define === 'function' && define.amd) {\n\t\tdefine([ 'jquery', 'moment' ], factory);\n\t}\n\telse if (typeof exports === 'object') { // Node/CommonJS\n\t\tmodule.exports = factory(require('jquery'), require('moment'));\n\t}\n\telse {\n\t\tfactory(jQuery, moment);\n\t}\n})(function($, moment) {\n\n;;\n\nvar fc = $.fullCalendar = { version: \"2.4.0\" };\nvar fcViews = fc.views = {};\n\n\n$.fn.fullCalendar = function(options) {\n\tvar args = Array.prototype.slice.call(arguments, 1); // for a possible method call\n\tvar res = this; // what this function will return (this jQuery object by default)\n\n\tthis.each(function(i, _element) { // loop each DOM element involved\n\t\tvar element = $(_element);\n\t\tvar calendar = element.data('fullCalendar'); // get the existing calendar object (if any)\n\t\tvar singleRes; // the returned value of this single method call\n\n\t\t// a method call\n\t\tif (typeof options === 'string') {\n\t\t\tif (calendar && $.isFunction(calendar[options])) {\n\t\t\t\tsingleRes = calendar[options].apply(calendar, args);\n\t\t\t\tif (!i) {\n\t\t\t\t\tres = singleRes; // record the first method call result\n\t\t\t\t}\n\t\t\t\tif (options === 'destroy') { // for the destroy method, must remove Calendar object data\n\t\t\t\t\telement.removeData('fullCalendar');\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// a new calendar initialization\n\t\telse if (!calendar) { // don't initialize twice\n\t\t\tcalendar = new Calendar(element, options);\n\t\t\telement.data('fullCalendar', calendar);\n\t\t\tcalendar.render();\n\t\t}\n\t});\n\t\n\treturn res;\n};\n\n\nvar complexOptions = [ // names of options that are objects whose properties should be combined\n\t'header',\n\t'buttonText',\n\t'buttonIcons',\n\t'themeButtonIcons'\n];\n\n\n// Merges an array of option objects into a single object\nfunction mergeOptions(optionObjs) {\n\treturn mergeProps(optionObjs, complexOptions);\n}\n\n\n// Given options specified for the calendar's constructor, massages any legacy options into a non-legacy form.\n// Converts View-Option-Hashes into the View-Specific-Options format.\nfunction massageOverrides(input) {\n\tvar overrides = { views: input.views || {} }; // the output. ensure a `views` hash\n\tvar subObj;\n\n\t// iterate through all option override properties (except `views`)\n\t$.each(input, function(name, val) {\n\t\tif (name != 'views') {\n\n\t\t\t// could the value be a legacy View-Option-Hash?\n\t\t\tif (\n\t\t\t\t$.isPlainObject(val) &&\n\t\t\t\t!/(time|duration|interval)$/i.test(name) && // exclude duration options. might be given as objects\n\t\t\t\t$.inArray(name, complexOptions) == -1 // complex options aren't allowed to be View-Option-Hashes\n\t\t\t) {\n\t\t\t\tsubObj = null;\n\n\t\t\t\t// iterate through the properties of this possible View-Option-Hash value\n\t\t\t\t$.each(val, function(subName, subVal) {\n\n\t\t\t\t\t// is the property targeting a view?\n\t\t\t\t\tif (/^(month|week|day|default|basic(Week|Day)?|agenda(Week|Day)?)$/.test(subName)) {\n\t\t\t\t\t\tif (!overrides.views[subName]) { // ensure the view-target entry exists\n\t\t\t\t\t\t\toverrides.views[subName] = {};\n\t\t\t\t\t\t}\n\t\t\t\t\t\toverrides.views[subName][name] = subVal; // record the value in the `views` object\n\t\t\t\t\t}\n\t\t\t\t\telse { // a non-View-Option-Hash property\n\t\t\t\t\t\tif (!subObj) {\n\t\t\t\t\t\t\tsubObj = {};\n\t\t\t\t\t\t}\n\t\t\t\t\t\tsubObj[subName] = subVal; // accumulate these unrelated values for later\n\t\t\t\t\t}\n\t\t\t\t});\n\n\t\t\t\tif (subObj) { // non-View-Option-Hash properties? transfer them as-is\n\t\t\t\t\toverrides[name] = subObj;\n\t\t\t\t}\n\t\t\t}\n\t\t\telse {\n\t\t\t\toverrides[name] = val; // transfer normal options as-is\n\t\t\t}\n\t\t}\n\t});\n\n\treturn overrides;\n}\n\n;;\n\n// exports\nfc.intersectionToSeg = intersectionToSeg;\nfc.applyAll = applyAll;\nfc.debounce = debounce;\nfc.isInt = isInt;\nfc.htmlEscape = htmlEscape;\nfc.cssToStr = cssToStr;\nfc.proxy = proxy;\nfc.capitaliseFirstLetter = capitaliseFirstLetter;\n\n\n/* FullCalendar-specific DOM Utilities\n----------------------------------------------------------------------------------------------------------------------*/\n\n\n// Given the scrollbar widths of some other container, create borders/margins on rowEls in order to match the left\n// and right space that was offset by the scrollbars. A 1-pixel border first, then margin beyond that.\nfunction compensateScroll(rowEls, scrollbarWidths) {\n\tif (scrollbarWidths.left) {\n\t\trowEls.css({\n\t\t\t'border-left-width': 1,\n\t\t\t'margin-left': scrollbarWidths.left - 1\n\t\t});\n\t}\n\tif (scrollbarWidths.right) {\n\t\trowEls.css({\n\t\t\t'border-right-width': 1,\n\t\t\t'margin-right': scrollbarWidths.right - 1\n\t\t});\n\t}\n}\n\n\n// Undoes compensateScroll and restores all borders/margins\nfunction uncompensateScroll(rowEls) {\n\trowEls.css({\n\t\t'margin-left': '',\n\t\t'margin-right': '',\n\t\t'border-left-width': '',\n\t\t'border-right-width': ''\n\t});\n}\n\n\n// Make the mouse cursor express that an event is not allowed in the current area\nfunction disableCursor() {\n\t$('body').addClass('fc-not-allowed');\n}\n\n\n// Returns the mouse cursor to its original look\nfunction enableCursor() {\n\t$('body').removeClass('fc-not-allowed');\n}\n\n\n// Given a total available height to fill, have `els` (essentially child rows) expand to accomodate.\n// By default, all elements that are shorter than the recommended height are expanded uniformly, not considering\n// any other els that are already too tall. if `shouldRedistribute` is on, it considers these tall rows and \n// reduces the available height.\nfunction distributeHeight(els, availableHeight, shouldRedistribute) {\n\n\t// *FLOORING NOTE*: we floor in certain places because zoom can give inaccurate floating-point dimensions,\n\t// and it is better to be shorter than taller, to avoid creating unnecessary scrollbars.\n\n\tvar minOffset1 = Math.floor(availableHeight / els.length); // for non-last element\n\tvar minOffset2 = Math.floor(availableHeight - minOffset1 * (els.length - 1)); // for last element *FLOORING NOTE*\n\tvar flexEls = []; // elements that are allowed to expand. array of DOM nodes\n\tvar flexOffsets = []; // amount of vertical space it takes up\n\tvar flexHeights = []; // actual css height\n\tvar usedHeight = 0;\n\n\tundistributeHeight(els); // give all elements their natural height\n\n\t// find elements that are below the recommended height (expandable).\n\t// important to query for heights in a single first pass (to avoid reflow oscillation).\n\tels.each(function(i, el) {\n\t\tvar minOffset = i === els.length - 1 ? minOffset2 : minOffset1;\n\t\tvar naturalOffset = $(el).outerHeight(true);\n\n\t\tif (naturalOffset < minOffset) {\n\t\t\tflexEls.push(el);\n\t\t\tflexOffsets.push(naturalOffset);\n\t\t\tflexHeights.push($(el).height());\n\t\t}\n\t\telse {\n\t\t\t// this element stretches past recommended height (non-expandable). mark the space as occupied.\n\t\t\tusedHeight += naturalOffset;\n\t\t}\n\t});\n\n\t// readjust the recommended height to only consider the height available to non-maxed-out rows.\n\tif (shouldRedistribute) {\n\t\tavailableHeight -= usedHeight;\n\t\tminOffset1 = Math.floor(availableHeight / flexEls.length);\n\t\tminOffset2 = Math.floor(availableHeight - minOffset1 * (flexEls.length - 1)); // *FLOORING NOTE*\n\t}\n\n\t// assign heights to all expandable elements\n\t$(flexEls).each(function(i, el) {\n\t\tvar minOffset = i === flexEls.length - 1 ? minOffset2 : minOffset1;\n\t\tvar naturalOffset = flexOffsets[i];\n\t\tvar naturalHeight = flexHeights[i];\n\t\tvar newHeight = minOffset - (naturalOffset - naturalHeight); // subtract the margin/padding\n\n\t\tif (naturalOffset < minOffset) { // we check this again because redistribution might have changed things\n\t\t\t$(el).height(newHeight);\n\t\t}\n\t});\n}\n\n\n// Undoes distrubuteHeight, restoring all els to their natural height\nfunction undistributeHeight(els) {\n\tels.height('');\n}\n\n\n// Given `els`, a jQuery set of
cells, find the cell with the largest natural width and set the widths of all the\n// cells to be that width.\n// PREREQUISITE: if you want a cell to take up width, it needs to have a single inner element w/ display:inline\nfunction matchCellWidths(els) {\n\tvar maxInnerWidth = 0;\n\n\tels.find('> *').each(function(i, innerEl) {\n\t\tvar innerWidth = $(innerEl).outerWidth();\n\t\tif (innerWidth > maxInnerWidth) {\n\t\t\tmaxInnerWidth = innerWidth;\n\t\t}\n\t});\n\n\tmaxInnerWidth++; // sometimes not accurate of width the text needs to stay on one line. insurance\n\n\tels.width(maxInnerWidth);\n\n\treturn maxInnerWidth;\n}\n\n\n// Turns a container element into a scroller if its contents is taller than the allotted height.\n// Returns true if the element is now a scroller, false otherwise.\n// NOTE: this method is best because it takes weird zooming dimensions into account\nfunction setPotentialScroller(containerEl, height) {\n\tcontainerEl.height(height).addClass('fc-scroller');\n\n\t// are scrollbars needed?\n\tif (containerEl[0].scrollHeight - 1 > containerEl[0].clientHeight) { // !!! -1 because IE is often off-by-one :(\n\t\treturn true;\n\t}\n\n\tunsetScroller(containerEl); // undo\n\treturn false;\n}\n\n\n// Takes an element that might have been a scroller, and turns it back into a normal element.\nfunction unsetScroller(containerEl) {\n\tcontainerEl.height('').removeClass('fc-scroller');\n}\n\n\n/* General DOM Utilities\n----------------------------------------------------------------------------------------------------------------------*/\n\nfc.getClientRect = getClientRect;\nfc.getContentRect = getContentRect;\nfc.getScrollbarWidths = getScrollbarWidths;\n\n\n// borrowed from https://github.com/jquery/jquery-ui/blob/1.11.0/ui/core.js#L51\nfunction getScrollParent(el) {\n\tvar position = el.css('position'),\n\t\tscrollParent = el.parents().filter(function() {\n\t\t\tvar parent = $(this);\n\t\t\treturn (/(auto|scroll)/).test(\n\t\t\t\tparent.css('overflow') + parent.css('overflow-y') + parent.css('overflow-x')\n\t\t\t);\n\t\t}).eq(0);\n\n\treturn position === 'fixed' || !scrollParent.length ? $(el[0].ownerDocument || document) : scrollParent;\n}\n\n\n// Queries the outer bounding area of a jQuery element.\n// Returns a rectangle with absolute coordinates: left, right (exclusive), top, bottom (exclusive).\nfunction getOuterRect(el) {\n\tvar offset = el.offset();\n\n\treturn {\n\t\tleft: offset.left,\n\t\tright: offset.left + el.outerWidth(),\n\t\ttop: offset.top,\n\t\tbottom: offset.top + el.outerHeight()\n\t};\n}\n\n\n// Queries the area within the margin/border/scrollbars of a jQuery element. Does not go within the padding.\n// Returns a rectangle with absolute coordinates: left, right (exclusive), top, bottom (exclusive).\n// NOTE: should use clientLeft/clientTop, but very unreliable cross-browser.\nfunction getClientRect(el) {\n\tvar offset = el.offset();\n\tvar scrollbarWidths = getScrollbarWidths(el);\n\tvar left = offset.left + getCssFloat(el, 'border-left-width') + scrollbarWidths.left;\n\tvar top = offset.top + getCssFloat(el, 'border-top-width') + scrollbarWidths.top;\n\n\treturn {\n\t\tleft: left,\n\t\tright: left + el[0].clientWidth, // clientWidth includes padding but NOT scrollbars\n\t\ttop: top,\n\t\tbottom: top + el[0].clientHeight // clientHeight includes padding but NOT scrollbars\n\t};\n}\n\n\n// Queries the area within the margin/border/padding of a jQuery element. Assumed not to have scrollbars.\n// Returns a rectangle with absolute coordinates: left, right (exclusive), top, bottom (exclusive).\nfunction getContentRect(el) {\n\tvar offset = el.offset(); // just outside of border, margin not included\n\tvar left = offset.left + getCssFloat(el, 'border-left-width') + getCssFloat(el, 'padding-left');\n\tvar top = offset.top + getCssFloat(el, 'border-top-width') + getCssFloat(el, 'padding-top');\n\n\treturn {\n\t\tleft: left,\n\t\tright: left + el.width(),\n\t\ttop: top,\n\t\tbottom: top + el.height()\n\t};\n}\n\n\n// Returns the computed left/right/top/bottom scrollbar widths for the given jQuery element.\n// NOTE: should use clientLeft/clientTop, but very unreliable cross-browser.\nfunction getScrollbarWidths(el) {\n\tvar leftRightWidth = el.innerWidth() - el[0].clientWidth; // the paddings cancel out, leaving the scrollbars\n\tvar widths = {\n\t\tleft: 0,\n\t\tright: 0,\n\t\ttop: 0,\n\t\tbottom: el.innerHeight() - el[0].clientHeight // the paddings cancel out, leaving the bottom scrollbar\n\t};\n\n\tif (getIsLeftRtlScrollbars() && el.css('direction') == 'rtl') { // is the scrollbar on the left side?\n\t\twidths.left = leftRightWidth;\n\t}\n\telse {\n\t\twidths.right = leftRightWidth;\n\t}\n\n\treturn widths;\n}\n\n\n// Logic for determining if, when the element is right-to-left, the scrollbar appears on the left side\n\nvar _isLeftRtlScrollbars = null;\n\nfunction getIsLeftRtlScrollbars() { // responsible for caching the computation\n\tif (_isLeftRtlScrollbars === null) {\n\t\t_isLeftRtlScrollbars = computeIsLeftRtlScrollbars();\n\t}\n\treturn _isLeftRtlScrollbars;\n}\n\nfunction computeIsLeftRtlScrollbars() { // creates an offscreen test element, then removes it\n\tvar el = $('')\n\t\t.css({\n\t\t\tposition: 'absolute',\n\t\t\ttop: -1000,\n\t\t\tleft: 0,\n\t\t\tborder: 0,\n\t\t\tpadding: 0,\n\t\t\toverflow: 'scroll',\n\t\t\tdirection: 'rtl'\n\t\t})\n\t\t.appendTo('body');\n\tvar innerEl = el.children();\n\tvar res = innerEl.offset().left > el.offset().left; // is the inner div shifted to accommodate a left scrollbar?\n\tel.remove();\n\treturn res;\n}\n\n\n// Retrieves a jQuery element's computed CSS value as a floating-point number.\n// If the queried value is non-numeric (ex: IE can return \"medium\" for border width), will just return zero.\nfunction getCssFloat(el, prop) {\n\treturn parseFloat(el.css(prop)) || 0;\n}\n\n\n// Returns a boolean whether this was a left mouse click and no ctrl key (which means right click on Mac)\nfunction isPrimaryMouseButton(ev) {\n\treturn ev.which == 1 && !ev.ctrlKey;\n}\n\n\n/* Geometry\n----------------------------------------------------------------------------------------------------------------------*/\n\nfc.intersectRects = intersectRects;\n\n// Returns a new rectangle that is the intersection of the two rectangles. If they don't intersect, returns false\nfunction intersectRects(rect1, rect2) {\n\tvar res = {\n\t\tleft: Math.max(rect1.left, rect2.left),\n\t\tright: Math.min(rect1.right, rect2.right),\n\t\ttop: Math.max(rect1.top, rect2.top),\n\t\tbottom: Math.min(rect1.bottom, rect2.bottom)\n\t};\n\n\tif (res.left < res.right && res.top < res.bottom) {\n\t\treturn res;\n\t}\n\treturn false;\n}\n\n\n// Returns a new point that will have been moved to reside within the given rectangle\nfunction constrainPoint(point, rect) {\n\treturn {\n\t\tleft: Math.min(Math.max(point.left, rect.left), rect.right),\n\t\ttop: Math.min(Math.max(point.top, rect.top), rect.bottom)\n\t};\n}\n\n\n// Returns a point that is the center of the given rectangle\nfunction getRectCenter(rect) {\n\treturn {\n\t\tleft: (rect.left + rect.right) / 2,\n\t\ttop: (rect.top + rect.bottom) / 2\n\t};\n}\n\n\n// Subtracts point2's coordinates from point1's coordinates, returning a delta\nfunction diffPoints(point1, point2) {\n\treturn {\n\t\tleft: point1.left - point2.left,\n\t\ttop: point1.top - point2.top\n\t};\n}\n\n\n/* Object Ordering by Field\n----------------------------------------------------------------------------------------------------------------------*/\n\nfc.parseFieldSpecs = parseFieldSpecs;\nfc.compareByFieldSpecs = compareByFieldSpecs;\nfc.compareByFieldSpec = compareByFieldSpec;\nfc.flexibleCompare = flexibleCompare;\n\n\nfunction parseFieldSpecs(input) {\n\tvar specs = [];\n\tvar tokens = [];\n\tvar i, token;\n\n\tif (typeof input === 'string') {\n\t\ttokens = input.split(/\\s*,\\s*/);\n\t}\n\telse if (typeof input === 'function') {\n\t\ttokens = [ input ];\n\t}\n\telse if ($.isArray(input)) {\n\t\ttokens = input;\n\t}\n\n\tfor (i = 0; i < tokens.length; i++) {\n\t\ttoken = tokens[i];\n\n\t\tif (typeof token === 'string') {\n\t\t\tspecs.push(\n\t\t\t\ttoken.charAt(0) == '-' ?\n\t\t\t\t\t{ field: token.substring(1), order: -1 } :\n\t\t\t\t\t{ field: token, order: 1 }\n\t\t\t);\n\t\t}\n\t\telse if (typeof token === 'function') {\n\t\t\tspecs.push({ func: token });\n\t\t}\n\t}\n\n\treturn specs;\n}\n\n\nfunction compareByFieldSpecs(obj1, obj2, fieldSpecs) {\n\tvar i;\n\tvar cmp;\n\n\tfor (i = 0; i < fieldSpecs.length; i++) {\n\t\tcmp = compareByFieldSpec(obj1, obj2, fieldSpecs[i]);\n\t\tif (cmp) {\n\t\t\treturn cmp;\n\t\t}\n\t}\n\n\treturn 0;\n}\n\n\nfunction compareByFieldSpec(obj1, obj2, fieldSpec) {\n\tif (fieldSpec.func) {\n\t\treturn fieldSpec.func(obj1, obj2);\n\t}\n\treturn flexibleCompare(obj1[fieldSpec.field], obj2[fieldSpec.field]) *\n\t\t(fieldSpec.order || 1);\n}\n\n\nfunction flexibleCompare(a, b) {\n\tif (!a && !b) {\n\t\treturn 0;\n\t}\n\tif (b == null) {\n\t\treturn -1;\n\t}\n\tif (a == null) {\n\t\treturn 1;\n\t}\n\tif ($.type(a) === 'string' || $.type(b) === 'string') {\n\t\treturn String(a).localeCompare(String(b));\n\t}\n\treturn a - b;\n}\n\n\n/* FullCalendar-specific Misc Utilities\n----------------------------------------------------------------------------------------------------------------------*/\n\n\n// Creates a basic segment with the intersection of the two ranges. Returns undefined if no intersection.\n// Expects all dates to be normalized to the same timezone beforehand.\n// TODO: move to date section?\nfunction intersectionToSeg(subjectRange, constraintRange) {\n\tvar subjectStart = subjectRange.start;\n\tvar subjectEnd = subjectRange.end;\n\tvar constraintStart = constraintRange.start;\n\tvar constraintEnd = constraintRange.end;\n\tvar segStart, segEnd;\n\tvar isStart, isEnd;\n\n\tif (subjectEnd > constraintStart && subjectStart < constraintEnd) { // in bounds at all?\n\n\t\tif (subjectStart >= constraintStart) {\n\t\t\tsegStart = subjectStart.clone();\n\t\t\tisStart = true;\n\t\t}\n\t\telse {\n\t\t\tsegStart = constraintStart.clone();\n\t\t\tisStart = false;\n\t\t}\n\n\t\tif (subjectEnd <= constraintEnd) {\n\t\t\tsegEnd = subjectEnd.clone();\n\t\t\tisEnd = true;\n\t\t}\n\t\telse {\n\t\t\tsegEnd = constraintEnd.clone();\n\t\t\tisEnd = false;\n\t\t}\n\n\t\treturn {\n\t\t\tstart: segStart,\n\t\t\tend: segEnd,\n\t\t\tisStart: isStart,\n\t\t\tisEnd: isEnd\n\t\t};\n\t}\n}\n\n\n/* Date Utilities\n----------------------------------------------------------------------------------------------------------------------*/\n\nfc.computeIntervalUnit = computeIntervalUnit;\nfc.divideRangeByDuration = divideRangeByDuration;\nfc.divideDurationByDuration = divideDurationByDuration;\nfc.multiplyDuration = multiplyDuration;\nfc.durationHasTime = durationHasTime;\n\nvar dayIDs = [ 'sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat' ];\nvar intervalUnits = [ 'year', 'month', 'week', 'day', 'hour', 'minute', 'second', 'millisecond' ];\n\n\n// Diffs the two moments into a Duration where full-days are recorded first, then the remaining time.\n// Moments will have their timezones normalized.\nfunction diffDayTime(a, b) {\n\treturn moment.duration({\n\t\tdays: a.clone().stripTime().diff(b.clone().stripTime(), 'days'),\n\t\tms: a.time() - b.time() // time-of-day from day start. disregards timezone\n\t});\n}\n\n\n// Diffs the two moments via their start-of-day (regardless of timezone). Produces whole-day durations.\nfunction diffDay(a, b) {\n\treturn moment.duration({\n\t\tdays: a.clone().stripTime().diff(b.clone().stripTime(), 'days')\n\t});\n}\n\n\n// Diffs two moments, producing a duration, made of a whole-unit-increment of the given unit. Uses rounding.\nfunction diffByUnit(a, b, unit) {\n\treturn moment.duration(\n\t\tMath.round(a.diff(b, unit, true)), // returnFloat=true\n\t\tunit\n\t);\n}\n\n\n// Computes the unit name of the largest whole-unit period of time.\n// For example, 48 hours will be \"days\" whereas 49 hours will be \"hours\".\n// Accepts start/end, a range object, or an original duration object.\nfunction computeIntervalUnit(start, end) {\n\tvar i, unit;\n\tvar val;\n\n\tfor (i = 0; i < intervalUnits.length; i++) {\n\t\tunit = intervalUnits[i];\n\t\tval = computeRangeAs(unit, start, end);\n\n\t\tif (val >= 1 && isInt(val)) {\n\t\t\tbreak;\n\t\t}\n\t}\n\n\treturn unit; // will be \"milliseconds\" if nothing else matches\n}\n\n\n// Computes the number of units (like \"hours\") in the given range.\n// Range can be a {start,end} object, separate start/end args, or a Duration.\n// Results are based on Moment's .as() and .diff() methods, so results can depend on internal handling\n// of month-diffing logic (which tends to vary from version to version).\nfunction computeRangeAs(unit, start, end) {\n\n\tif (end != null) { // given start, end\n\t\treturn end.diff(start, unit, true);\n\t}\n\telse if (moment.isDuration(start)) { // given duration\n\t\treturn start.as(unit);\n\t}\n\telse { // given { start, end } range object\n\t\treturn start.end.diff(start.start, unit, true);\n\t}\n}\n\n\n// Intelligently divides a range (specified by a start/end params) by a duration\nfunction divideRangeByDuration(start, end, dur) {\n\tvar months;\n\n\tif (durationHasTime(dur)) {\n\t\treturn (end - start) / dur;\n\t}\n\tmonths = dur.asMonths();\n\tif (Math.abs(months) >= 1 && isInt(months)) {\n\t\treturn end.diff(start, 'months', true) / months;\n\t}\n\treturn end.diff(start, 'days', true) / dur.asDays();\n}\n\n\n// Intelligently divides one duration by another\nfunction divideDurationByDuration(dur1, dur2) {\n\tvar months1, months2;\n\n\tif (durationHasTime(dur1) || durationHasTime(dur2)) {\n\t\treturn dur1 / dur2;\n\t}\n\tmonths1 = dur1.asMonths();\n\tmonths2 = dur2.asMonths();\n\tif (\n\t\tMath.abs(months1) >= 1 && isInt(months1) &&\n\t\tMath.abs(months2) >= 1 && isInt(months2)\n\t) {\n\t\treturn months1 / months2;\n\t}\n\treturn dur1.asDays() / dur2.asDays();\n}\n\n\n// Intelligently multiplies a duration by a number\nfunction multiplyDuration(dur, n) {\n\tvar months;\n\n\tif (durationHasTime(dur)) {\n\t\treturn moment.duration(dur * n);\n\t}\n\tmonths = dur.asMonths();\n\tif (Math.abs(months) >= 1 && isInt(months)) {\n\t\treturn moment.duration({ months: months * n });\n\t}\n\treturn moment.duration({ days: dur.asDays() * n });\n}\n\n\n// Returns a boolean about whether the given duration has any time parts (hours/minutes/seconds/ms)\nfunction durationHasTime(dur) {\n\treturn Boolean(dur.hours() || dur.minutes() || dur.seconds() || dur.milliseconds());\n}\n\n\nfunction isNativeDate(input) {\n\treturn Object.prototype.toString.call(input) === '[object Date]' || input instanceof Date;\n}\n\n\n// Returns a boolean about whether the given input is a time string, like \"06:40:00\" or \"06:00\"\nfunction isTimeString(str) {\n\treturn /^\\d+\\:\\d+(?:\\:\\d+\\.?(?:\\d{3})?)?$/.test(str);\n}\n\n\n/* Logging and Debug\n----------------------------------------------------------------------------------------------------------------------*/\n\nfc.log = function() {\n\tvar console = window.console;\n\n\tif (console && console.log) {\n\t\treturn console.log.apply(console, arguments);\n\t}\n};\n\nfc.warn = function() {\n\tvar console = window.console;\n\n\tif (console && console.warn) {\n\t\treturn console.warn.apply(console, arguments);\n\t}\n\telse {\n\t\treturn fc.log.apply(fc, arguments);\n\t}\n};\n\n\n/* General Utilities\n----------------------------------------------------------------------------------------------------------------------*/\n\nvar hasOwnPropMethod = {}.hasOwnProperty;\n\n\n// Merges an array of objects into a single object.\n// The second argument allows for an array of property names who's object values will be merged together.\nfunction mergeProps(propObjs, complexProps) {\n\tvar dest = {};\n\tvar i, name;\n\tvar complexObjs;\n\tvar j, val;\n\tvar props;\n\n\tif (complexProps) {\n\t\tfor (i = 0; i < complexProps.length; i++) {\n\t\t\tname = complexProps[i];\n\t\t\tcomplexObjs = [];\n\n\t\t\t// collect the trailing object values, stopping when a non-object is discovered\n\t\t\tfor (j = propObjs.length - 1; j >= 0; j--) {\n\t\t\t\tval = propObjs[j][name];\n\n\t\t\t\tif (typeof val === 'object') {\n\t\t\t\t\tcomplexObjs.unshift(val);\n\t\t\t\t}\n\t\t\t\telse if (val !== undefined) {\n\t\t\t\t\tdest[name] = val; // if there were no objects, this value will be used\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// if the trailing values were objects, use the merged value\n\t\t\tif (complexObjs.length) {\n\t\t\t\tdest[name] = mergeProps(complexObjs);\n\t\t\t}\n\t\t}\n\t}\n\n\t// copy values into the destination, going from last to first\n\tfor (i = propObjs.length - 1; i >= 0; i--) {\n\t\tprops = propObjs[i];\n\n\t\tfor (name in props) {\n\t\t\tif (!(name in dest)) { // if already assigned by previous props or complex props, don't reassign\n\t\t\t\tdest[name] = props[name];\n\t\t\t}\n\t\t}\n\t}\n\n\treturn dest;\n}\n\n\n// Create an object that has the given prototype. Just like Object.create\nfunction createObject(proto) {\n\tvar f = function() {};\n\tf.prototype = proto;\n\treturn new f();\n}\n\n\nfunction copyOwnProps(src, dest) {\n\tfor (var name in src) {\n\t\tif (hasOwnProp(src, name)) {\n\t\t\tdest[name] = src[name];\n\t\t}\n\t}\n}\n\n\n// Copies over certain methods with the same names as Object.prototype methods. Overcomes an IE<=8 bug:\n// https://developer.mozilla.org/en-US/docs/ECMAScript_DontEnum_attribute#JScript_DontEnum_Bug\nfunction copyNativeMethods(src, dest) {\n\tvar names = [ 'constructor', 'toString', 'valueOf' ];\n\tvar i, name;\n\n\tfor (i = 0; i < names.length; i++) {\n\t\tname = names[i];\n\n\t\tif (src[name] !== Object.prototype[name]) {\n\t\t\tdest[name] = src[name];\n\t\t}\n\t}\n}\n\n\nfunction hasOwnProp(obj, name) {\n\treturn hasOwnPropMethod.call(obj, name);\n}\n\n\n// Is the given value a non-object non-function value?\nfunction isAtomic(val) {\n\treturn /undefined|null|boolean|number|string/.test($.type(val));\n}\n\n\nfunction applyAll(functions, thisObj, args) {\n\tif ($.isFunction(functions)) {\n\t\tfunctions = [ functions ];\n\t}\n\tif (functions) {\n\t\tvar i;\n\t\tvar ret;\n\t\tfor (i=0; i/g, '>')\n\t\t.replace(/'/g, ''')\n\t\t.replace(/\"/g, '"')\n\t\t.replace(/\\n/g, ' ');\n}\n\n\nfunction stripHtmlEntities(text) {\n\treturn text.replace(/&.*?;/g, '');\n}\n\n\n// Given a hash of CSS properties, returns a string of CSS.\n// Uses property names as-is (no camel-case conversion). Will not make statements for null/undefined values.\nfunction cssToStr(cssProps) {\n\tvar statements = [];\n\n\t$.each(cssProps, function(name, val) {\n\t\tif (val != null) {\n\t\t\tstatements.push(name + ':' + val);\n\t\t}\n\t});\n\n\treturn statements.join(';');\n}\n\n\nfunction capitaliseFirstLetter(str) {\n\treturn str.charAt(0).toUpperCase() + str.slice(1);\n}\n\n\nfunction compareNumbers(a, b) { // for .sort()\n\treturn a - b;\n}\n\n\nfunction isInt(n) {\n\treturn n % 1 === 0;\n}\n\n\n// Returns a method bound to the given object context.\n// Just like one of the jQuery.proxy signatures, but without the undesired behavior of treating the same method with\n// different contexts as identical when binding/unbinding events.\nfunction proxy(obj, methodName) {\n\tvar method = obj[methodName];\n\n\treturn function() {\n\t\treturn method.apply(obj, arguments);\n\t};\n}\n\n\n// Returns a function, that, as long as it continues to be invoked, will not\n// be triggered. The function will be called after it stops being called for\n// N milliseconds.\n// https://github.com/jashkenas/underscore/blob/1.6.0/underscore.js#L714\nfunction debounce(func, wait) {\n\tvar timeoutId;\n\tvar args;\n\tvar context;\n\tvar timestamp; // of most recent call\n\tvar later = function() {\n\t\tvar last = +new Date() - timestamp;\n\t\tif (last < wait && last > 0) {\n\t\t\ttimeoutId = setTimeout(later, wait - last);\n\t\t}\n\t\telse {\n\t\t\ttimeoutId = null;\n\t\t\tfunc.apply(context, args);\n\t\t\tif (!timeoutId) {\n\t\t\t\tcontext = args = null;\n\t\t\t}\n\t\t}\n\t};\n\n\treturn function() {\n\t\tcontext = this;\n\t\targs = arguments;\n\t\ttimestamp = +new Date();\n\t\tif (!timeoutId) {\n\t\t\ttimeoutId = setTimeout(later, wait);\n\t\t}\n\t};\n}\n\n;;\n\nvar ambigDateOfMonthRegex = /^\\s*\\d{4}-\\d\\d$/;\nvar ambigTimeOrZoneRegex =\n\t/^\\s*\\d{4}-(?:(\\d\\d-\\d\\d)|(W\\d\\d$)|(W\\d\\d-\\d)|(\\d\\d\\d))((T| )(\\d\\d(:\\d\\d(:\\d\\d(\\.\\d+)?)?)?)?)?$/;\nvar newMomentProto = moment.fn; // where we will attach our new methods\nvar oldMomentProto = $.extend({}, newMomentProto); // copy of original moment methods\nvar allowValueOptimization;\nvar setUTCValues; // function defined below\nvar setLocalValues; // function defined below\n\n\n// Creating\n// -------------------------------------------------------------------------------------------------\n\n// Creates a new moment, similar to the vanilla moment(...) constructor, but with\n// extra features (ambiguous time, enhanced formatting). When given an existing moment,\n// it will function as a clone (and retain the zone of the moment). Anything else will\n// result in a moment in the local zone.\nfc.moment = function() {\n\treturn makeMoment(arguments);\n};\n\n// Sames as fc.moment, but forces the resulting moment to be in the UTC timezone.\nfc.moment.utc = function() {\n\tvar mom = makeMoment(arguments, true);\n\n\t// Force it into UTC because makeMoment doesn't guarantee it\n\t// (if given a pre-existing moment for example)\n\tif (mom.hasTime()) { // don't give ambiguously-timed moments a UTC zone\n\t\tmom.utc();\n\t}\n\n\treturn mom;\n};\n\n// Same as fc.moment, but when given an ISO8601 string, the timezone offset is preserved.\n// ISO8601 strings with no timezone offset will become ambiguously zoned.\nfc.moment.parseZone = function() {\n\treturn makeMoment(arguments, true, true);\n};\n\n// Builds an enhanced moment from args. When given an existing moment, it clones. When given a\n// native Date, or called with no arguments (the current time), the resulting moment will be local.\n// Anything else needs to be \"parsed\" (a string or an array), and will be affected by:\n// parseAsUTC - if there is no zone information, should we parse the input in UTC?\n// parseZone - if there is zone information, should we force the zone of the moment?\nfunction makeMoment(args, parseAsUTC, parseZone) {\n\tvar input = args[0];\n\tvar isSingleString = args.length == 1 && typeof input === 'string';\n\tvar isAmbigTime;\n\tvar isAmbigZone;\n\tvar ambigMatch;\n\tvar mom;\n\n\tif (moment.isMoment(input)) {\n\t\tmom = moment.apply(null, args); // clone it\n\t\ttransferAmbigs(input, mom); // the ambig flags weren't transfered with the clone\n\t}\n\telse if (isNativeDate(input) || input === undefined) {\n\t\tmom = moment.apply(null, args); // will be local\n\t}\n\telse { // \"parsing\" is required\n\t\tisAmbigTime = false;\n\t\tisAmbigZone = false;\n\n\t\tif (isSingleString) {\n\t\t\tif (ambigDateOfMonthRegex.test(input)) {\n\t\t\t\t// accept strings like '2014-05', but convert to the first of the month\n\t\t\t\tinput += '-01';\n\t\t\t\targs = [ input ]; // for when we pass it on to moment's constructor\n\t\t\t\tisAmbigTime = true;\n\t\t\t\tisAmbigZone = true;\n\t\t\t}\n\t\t\telse if ((ambigMatch = ambigTimeOrZoneRegex.exec(input))) {\n\t\t\t\tisAmbigTime = !ambigMatch[5]; // no time part?\n\t\t\t\tisAmbigZone = true;\n\t\t\t}\n\t\t}\n\t\telse if ($.isArray(input)) {\n\t\t\t// arrays have no timezone information, so assume ambiguous zone\n\t\t\tisAmbigZone = true;\n\t\t}\n\t\t// otherwise, probably a string with a format\n\n\t\tif (parseAsUTC || isAmbigTime) {\n\t\t\tmom = moment.utc.apply(moment, args);\n\t\t}\n\t\telse {\n\t\t\tmom = moment.apply(null, args);\n\t\t}\n\n\t\tif (isAmbigTime) {\n\t\t\tmom._ambigTime = true;\n\t\t\tmom._ambigZone = true; // ambiguous time always means ambiguous zone\n\t\t}\n\t\telse if (parseZone) { // let's record the inputted zone somehow\n\t\t\tif (isAmbigZone) {\n\t\t\t\tmom._ambigZone = true;\n\t\t\t}\n\t\t\telse if (isSingleString) {\n\t\t\t\tif (mom.utcOffset) {\n\t\t\t\t\tmom.utcOffset(input); // if not a valid zone, will assign UTC\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tmom.zone(input); // for moment-pre-2.9\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tmom._fullCalendar = true; // flag for extended functionality\n\n\treturn mom;\n}\n\n\n// A clone method that works with the flags related to our enhanced functionality.\n// In the future, use moment.momentProperties\nnewMomentProto.clone = function() {\n\tvar mom = oldMomentProto.clone.apply(this, arguments);\n\n\t// these flags weren't transfered with the clone\n\ttransferAmbigs(this, mom);\n\tif (this._fullCalendar) {\n\t\tmom._fullCalendar = true;\n\t}\n\n\treturn mom;\n};\n\n\n// Week Number\n// -------------------------------------------------------------------------------------------------\n\n\n// Returns the week number, considering the locale's custom week number calcuation\n// `weeks` is an alias for `week`\nnewMomentProto.week = newMomentProto.weeks = function(input) {\n\tvar weekCalc = (this._locale || this._lang) // works pre-moment-2.8\n\t\t._fullCalendar_weekCalc;\n\n\tif (input == null && typeof weekCalc === 'function') { // custom function only works for getter\n\t\treturn weekCalc(this);\n\t}\n\telse if (weekCalc === 'ISO') {\n\t\treturn oldMomentProto.isoWeek.apply(this, arguments); // ISO getter/setter\n\t}\n\n\treturn oldMomentProto.week.apply(this, arguments); // local getter/setter\n};\n\n\n// Time-of-day\n// -------------------------------------------------------------------------------------------------\n\n// GETTER\n// Returns a Duration with the hours/minutes/seconds/ms values of the moment.\n// If the moment has an ambiguous time, a duration of 00:00 will be returned.\n//\n// SETTER\n// You can supply a Duration, a Moment, or a Duration-like argument.\n// When setting the time, and the moment has an ambiguous time, it then becomes unambiguous.\nnewMomentProto.time = function(time) {\n\n\t// Fallback to the original method (if there is one) if this moment wasn't created via FullCalendar.\n\t// `time` is a generic enough method name where this precaution is necessary to avoid collisions w/ other plugins.\n\tif (!this._fullCalendar) {\n\t\treturn oldMomentProto.time.apply(this, arguments);\n\t}\n\n\tif (time == null) { // getter\n\t\treturn moment.duration({\n\t\t\thours: this.hours(),\n\t\t\tminutes: this.minutes(),\n\t\t\tseconds: this.seconds(),\n\t\t\tmilliseconds: this.milliseconds()\n\t\t});\n\t}\n\telse { // setter\n\n\t\tthis._ambigTime = false; // mark that the moment now has a time\n\n\t\tif (!moment.isDuration(time) && !moment.isMoment(time)) {\n\t\t\ttime = moment.duration(time);\n\t\t}\n\n\t\t// The day value should cause overflow (so 24 hours becomes 00:00:00 of next day).\n\t\t// Only for Duration times, not Moment times.\n\t\tvar dayHours = 0;\n\t\tif (moment.isDuration(time)) {\n\t\t\tdayHours = Math.floor(time.asDays()) * 24;\n\t\t}\n\n\t\t// We need to set the individual fields.\n\t\t// Can't use startOf('day') then add duration. In case of DST at start of day.\n\t\treturn this.hours(dayHours + time.hours())\n\t\t\t.minutes(time.minutes())\n\t\t\t.seconds(time.seconds())\n\t\t\t.milliseconds(time.milliseconds());\n\t}\n};\n\n// Converts the moment to UTC, stripping out its time-of-day and timezone offset,\n// but preserving its YMD. A moment with a stripped time will display no time\n// nor timezone offset when .format() is called.\nnewMomentProto.stripTime = function() {\n\tvar a;\n\n\tif (!this._ambigTime) {\n\n\t\t// get the values before any conversion happens\n\t\ta = this.toArray(); // array of y/m/d/h/m/s/ms\n\n\t\t// TODO: use keepLocalTime in the future\n\t\tthis.utc(); // set the internal UTC flag (will clear the ambig flags)\n\t\tsetUTCValues(this, a.slice(0, 3)); // set the year/month/date. time will be zero\n\n\t\t// Mark the time as ambiguous. This needs to happen after the .utc() call, which might call .utcOffset(),\n\t\t// which clears all ambig flags. Same with setUTCValues with moment-timezone.\n\t\tthis._ambigTime = true;\n\t\tthis._ambigZone = true; // if ambiguous time, also ambiguous timezone offset\n\t}\n\n\treturn this; // for chaining\n};\n\n// Returns if the moment has a non-ambiguous time (boolean)\nnewMomentProto.hasTime = function() {\n\treturn !this._ambigTime;\n};\n\n\n// Timezone\n// -------------------------------------------------------------------------------------------------\n\n// Converts the moment to UTC, stripping out its timezone offset, but preserving its\n// YMD and time-of-day. A moment with a stripped timezone offset will display no\n// timezone offset when .format() is called.\n// TODO: look into Moment's keepLocalTime functionality\nnewMomentProto.stripZone = function() {\n\tvar a, wasAmbigTime;\n\n\tif (!this._ambigZone) {\n\n\t\t// get the values before any conversion happens\n\t\ta = this.toArray(); // array of y/m/d/h/m/s/ms\n\t\twasAmbigTime = this._ambigTime;\n\n\t\tthis.utc(); // set the internal UTC flag (might clear the ambig flags, depending on Moment internals)\n\t\tsetUTCValues(this, a); // will set the year/month/date/hours/minutes/seconds/ms\n\n\t\t// the above call to .utc()/.utcOffset() unfortunately might clear the ambig flags, so restore\n\t\tthis._ambigTime = wasAmbigTime || false;\n\n\t\t// Mark the zone as ambiguous. This needs to happen after the .utc() call, which might call .utcOffset(),\n\t\t// which clears the ambig flags. Same with setUTCValues with moment-timezone.\n\t\tthis._ambigZone = true;\n\t}\n\n\treturn this; // for chaining\n};\n\n// Returns of the moment has a non-ambiguous timezone offset (boolean)\nnewMomentProto.hasZone = function() {\n\treturn !this._ambigZone;\n};\n\n\n// this method implicitly marks a zone\nnewMomentProto.local = function() {\n\tvar a = this.toArray(); // year,month,date,hours,minutes,seconds,ms as an array\n\tvar wasAmbigZone = this._ambigZone;\n\n\toldMomentProto.local.apply(this, arguments);\n\n\t// ensure non-ambiguous\n\t// this probably already happened via local() -> utcOffset(), but don't rely on Moment's internals\n\tthis._ambigTime = false;\n\tthis._ambigZone = false;\n\n\tif (wasAmbigZone) {\n\t\t// If the moment was ambiguously zoned, the date fields were stored as UTC.\n\t\t// We want to preserve these, but in local time.\n\t\t// TODO: look into Moment's keepLocalTime functionality\n\t\tsetLocalValues(this, a);\n\t}\n\n\treturn this; // for chaining\n};\n\n\n// implicitly marks a zone\nnewMomentProto.utc = function() {\n\toldMomentProto.utc.apply(this, arguments);\n\n\t// ensure non-ambiguous\n\t// this probably already happened via utc() -> utcOffset(), but don't rely on Moment's internals\n\tthis._ambigTime = false;\n\tthis._ambigZone = false;\n\n\treturn this;\n};\n\n\n// methods for arbitrarily manipulating timezone offset.\n// should clear time/zone ambiguity when called.\n$.each([\n\t'zone', // only in moment-pre-2.9. deprecated afterwards\n\t'utcOffset'\n], function(i, name) {\n\tif (oldMomentProto[name]) { // original method exists?\n\n\t\t// this method implicitly marks a zone (will probably get called upon .utc() and .local())\n\t\tnewMomentProto[name] = function(tzo) {\n\n\t\t\tif (tzo != null) { // setter\n\t\t\t\t// these assignments needs to happen before the original zone method is called.\n\t\t\t\t// I forget why, something to do with a browser crash.\n\t\t\t\tthis._ambigTime = false;\n\t\t\t\tthis._ambigZone = false;\n\t\t\t}\n\n\t\t\treturn oldMomentProto[name].apply(this, arguments);\n\t\t};\n\t}\n});\n\n\n// Formatting\n// -------------------------------------------------------------------------------------------------\n\nnewMomentProto.format = function() {\n\tif (this._fullCalendar && arguments[0]) { // an enhanced moment? and a format string provided?\n\t\treturn formatDate(this, arguments[0]); // our extended formatting\n\t}\n\tif (this._ambigTime) {\n\t\treturn oldMomentFormat(this, 'YYYY-MM-DD');\n\t}\n\tif (this._ambigZone) {\n\t\treturn oldMomentFormat(this, 'YYYY-MM-DD[T]HH:mm:ss');\n\t}\n\treturn oldMomentProto.format.apply(this, arguments);\n};\n\nnewMomentProto.toISOString = function() {\n\tif (this._ambigTime) {\n\t\treturn oldMomentFormat(this, 'YYYY-MM-DD');\n\t}\n\tif (this._ambigZone) {\n\t\treturn oldMomentFormat(this, 'YYYY-MM-DD[T]HH:mm:ss');\n\t}\n\treturn oldMomentProto.toISOString.apply(this, arguments);\n};\n\n\n// Querying\n// -------------------------------------------------------------------------------------------------\n\n// Is the moment within the specified range? `end` is exclusive.\n// FYI, this method is not a standard Moment method, so always do our enhanced logic.\nnewMomentProto.isWithin = function(start, end) {\n\tvar a = commonlyAmbiguate([ this, start, end ]);\n\treturn a[0] >= a[1] && a[0] < a[2];\n};\n\n// When isSame is called with units, timezone ambiguity is normalized before the comparison happens.\n// If no units specified, the two moments must be identically the same, with matching ambig flags.\nnewMomentProto.isSame = function(input, units) {\n\tvar a;\n\n\t// only do custom logic if this is an enhanced moment\n\tif (!this._fullCalendar) {\n\t\treturn oldMomentProto.isSame.apply(this, arguments);\n\t}\n\n\tif (units) {\n\t\ta = commonlyAmbiguate([ this, input ], true); // normalize timezones but don't erase times\n\t\treturn oldMomentProto.isSame.call(a[0], a[1], units);\n\t}\n\telse {\n\t\tinput = fc.moment.parseZone(input); // normalize input\n\t\treturn oldMomentProto.isSame.call(this, input) &&\n\t\t\tBoolean(this._ambigTime) === Boolean(input._ambigTime) &&\n\t\t\tBoolean(this._ambigZone) === Boolean(input._ambigZone);\n\t}\n};\n\n// Make these query methods work with ambiguous moments\n$.each([\n\t'isBefore',\n\t'isAfter'\n], function(i, methodName) {\n\tnewMomentProto[methodName] = function(input, units) {\n\t\tvar a;\n\n\t\t// only do custom logic if this is an enhanced moment\n\t\tif (!this._fullCalendar) {\n\t\t\treturn oldMomentProto[methodName].apply(this, arguments);\n\t\t}\n\n\t\ta = commonlyAmbiguate([ this, input ]);\n\t\treturn oldMomentProto[methodName].call(a[0], a[1], units);\n\t};\n});\n\n\n// Misc Internals\n// -------------------------------------------------------------------------------------------------\n\n// given an array of moment-like inputs, return a parallel array w/ moments similarly ambiguated.\n// for example, of one moment has ambig time, but not others, all moments will have their time stripped.\n// set `preserveTime` to `true` to keep times, but only normalize zone ambiguity.\n// returns the original moments if no modifications are necessary.\nfunction commonlyAmbiguate(inputs, preserveTime) {\n\tvar anyAmbigTime = false;\n\tvar anyAmbigZone = false;\n\tvar len = inputs.length;\n\tvar moms = [];\n\tvar i, mom;\n\n\t// parse inputs into real moments and query their ambig flags\n\tfor (i = 0; i < len; i++) {\n\t\tmom = inputs[i];\n\t\tif (!moment.isMoment(mom)) {\n\t\t\tmom = fc.moment.parseZone(mom);\n\t\t}\n\t\tanyAmbigTime = anyAmbigTime || mom._ambigTime;\n\t\tanyAmbigZone = anyAmbigZone || mom._ambigZone;\n\t\tmoms.push(mom);\n\t}\n\n\t// strip each moment down to lowest common ambiguity\n\t// use clones to avoid modifying the original moments\n\tfor (i = 0; i < len; i++) {\n\t\tmom = moms[i];\n\t\tif (!preserveTime && anyAmbigTime && !mom._ambigTime) {\n\t\t\tmoms[i] = mom.clone().stripTime();\n\t\t}\n\t\telse if (anyAmbigZone && !mom._ambigZone) {\n\t\t\tmoms[i] = mom.clone().stripZone();\n\t\t}\n\t}\n\n\treturn moms;\n}\n\n// Transfers all the flags related to ambiguous time/zone from the `src` moment to the `dest` moment\n// TODO: look into moment.momentProperties for this.\nfunction transferAmbigs(src, dest) {\n\tif (src._ambigTime) {\n\t\tdest._ambigTime = true;\n\t}\n\telse if (dest._ambigTime) {\n\t\tdest._ambigTime = false;\n\t}\n\n\tif (src._ambigZone) {\n\t\tdest._ambigZone = true;\n\t}\n\telse if (dest._ambigZone) {\n\t\tdest._ambigZone = false;\n\t}\n}\n\n\n// Sets the year/month/date/etc values of the moment from the given array.\n// Inefficient because it calls each individual setter.\nfunction setMomentValues(mom, a) {\n\tmom.year(a[0] || 0)\n\t\t.month(a[1] || 0)\n\t\t.date(a[2] || 0)\n\t\t.hours(a[3] || 0)\n\t\t.minutes(a[4] || 0)\n\t\t.seconds(a[5] || 0)\n\t\t.milliseconds(a[6] || 0);\n}\n\n// Can we set the moment's internal date directly?\nallowValueOptimization = '_d' in moment() && 'updateOffset' in moment;\n\n// Utility function. Accepts a moment and an array of the UTC year/month/date/etc values to set.\n// Assumes the given moment is already in UTC mode.\nsetUTCValues = allowValueOptimization ? function(mom, a) {\n\t// simlate what moment's accessors do\n\tmom._d.setTime(Date.UTC.apply(Date, a));\n\tmoment.updateOffset(mom, false); // keepTime=false\n} : setMomentValues;\n\n// Utility function. Accepts a moment and an array of the local year/month/date/etc values to set.\n// Assumes the given moment is already in local mode.\nsetLocalValues = allowValueOptimization ? function(mom, a) {\n\t// simlate what moment's accessors do\n\tmom._d.setTime(+new Date( // FYI, there is now way to apply an array of args to a constructor\n\t\ta[0] || 0,\n\t\ta[1] || 0,\n\t\ta[2] || 0,\n\t\ta[3] || 0,\n\t\ta[4] || 0,\n\t\ta[5] || 0,\n\t\ta[6] || 0\n\t));\n\tmoment.updateOffset(mom, false); // keepTime=false\n} : setMomentValues;\n\n;;\n\n// Single Date Formatting\n// -------------------------------------------------------------------------------------------------\n\n\n// call this if you want Moment's original format method to be used\nfunction oldMomentFormat(mom, formatStr) {\n\treturn oldMomentProto.format.call(mom, formatStr); // oldMomentProto defined in moment-ext.js\n}\n\n\n// Formats `date` with a Moment formatting string, but allow our non-zero areas and\n// additional token.\nfunction formatDate(date, formatStr) {\n\treturn formatDateWithChunks(date, getFormatStringChunks(formatStr));\n}\n\n\nfunction formatDateWithChunks(date, chunks) {\n\tvar s = '';\n\tvar i;\n\n\tfor (i=0; i \"MMMM D YYYY\"\n\tformatStr = localeData.longDateFormat(formatStr) || formatStr;\n\t// BTW, this is not important for `formatDate` because it is impossible to put custom tokens\n\t// or non-zero areas in Moment's localized format strings.\n\n\tseparator = separator || ' - ';\n\n\treturn formatRangeWithChunks(\n\t\tdate1,\n\t\tdate2,\n\t\tgetFormatStringChunks(formatStr),\n\t\tseparator,\n\t\tisRTL\n\t);\n}\nfc.formatRange = formatRange; // expose\n\n\nfunction formatRangeWithChunks(date1, date2, chunks, separator, isRTL) {\n\tvar chunkStr; // the rendering of the chunk\n\tvar leftI;\n\tvar leftStr = '';\n\tvar rightI;\n\tvar rightStr = '';\n\tvar middleI;\n\tvar middleStr1 = '';\n\tvar middleStr2 = '';\n\tvar middleStr = '';\n\n\t// Start at the leftmost side of the formatting string and continue until you hit a token\n\t// that is not the same between dates.\n\tfor (leftI=0; leftIleftI; rightI--) {\n\t\tchunkStr = formatSimilarChunk(date1, date2, chunks[rightI]);\n\t\tif (chunkStr === false) {\n\t\t\tbreak;\n\t\t}\n\t\trightStr = chunkStr + rightStr;\n\t}\n\n\t// The area in the middle is different for both of the dates.\n\t// Collect them distinctly so we can jam them together later.\n\tfor (middleI=leftI; middleI<=rightI; middleI++) {\n\t\tmiddleStr1 += formatDateWithChunk(date1, chunks[middleI]);\n\t\tmiddleStr2 += formatDateWithChunk(date2, chunks[middleI]);\n\t}\n\n\tif (middleStr1 || middleStr2) {\n\t\tif (isRTL) {\n\t\t\tmiddleStr = middleStr2 + separator + middleStr1;\n\t\t}\n\t\telse {\n\t\t\tmiddleStr = middleStr1 + separator + middleStr2;\n\t\t}\n\t}\n\n\treturn leftStr + middleStr + rightStr;\n}\n\n\nvar similarUnitMap = {\n\tY: 'year',\n\tM: 'month',\n\tD: 'day', // day of month\n\td: 'day', // day of week\n\t// prevents a separator between anything time-related...\n\tA: 'second', // AM/PM\n\ta: 'second', // am/pm\n\tT: 'second', // A/P\n\tt: 'second', // a/p\n\tH: 'second', // hour (24)\n\th: 'second', // hour (12)\n\tm: 'second', // minute\n\ts: 'second' // second\n};\n// TODO: week maybe?\n\n\n// Given a formatting chunk, and given that both dates are similar in the regard the\n// formatting chunk is concerned, format date1 against `chunk`. Otherwise, return `false`.\nfunction formatSimilarChunk(date1, date2, chunk) {\n\tvar token;\n\tvar unit;\n\n\tif (typeof chunk === 'string') { // a literal string\n\t\treturn chunk;\n\t}\n\telse if ((token = chunk.token)) {\n\t\tunit = similarUnitMap[token.charAt(0)];\n\t\t// are the dates the same for this unit of measurement?\n\t\tif (unit && date1.isSame(date2, unit)) {\n\t\t\treturn oldMomentFormat(date1, token); // would be the same if we used `date2`\n\t\t\t// BTW, don't support custom tokens\n\t\t}\n\t}\n\n\treturn false; // the chunk is NOT the same for the two dates\n\t// BTW, don't support splitting on non-zero areas\n}\n\n\n// Chunking Utils\n// -------------------------------------------------------------------------------------------------\n\n\nvar formatStringChunkCache = {};\n\n\nfunction getFormatStringChunks(formatStr) {\n\tif (formatStr in formatStringChunkCache) {\n\t\treturn formatStringChunkCache[formatStr];\n\t}\n\treturn (formatStringChunkCache[formatStr] = chunkFormatString(formatStr));\n}\n\n\n// Break the formatting string into an array of chunks\nfunction chunkFormatString(formatStr) {\n\tvar chunks = [];\n\tvar chunker = /\\[([^\\]]*)\\]|\\(([^\\)]*)\\)|(LTS|LT|(\\w)\\4*o?)|([^\\w\\[\\(]+)/g; // TODO: more descrimination\n\tvar match;\n\n\twhile ((match = chunker.exec(formatStr))) {\n\t\tif (match[1]) { // a literal string inside [ ... ]\n\t\t\tchunks.push(match[1]);\n\t\t}\n\t\telse if (match[2]) { // non-zero formatting inside ( ... )\n\t\t\tchunks.push({ maybe: chunkFormatString(match[2]) });\n\t\t}\n\t\telse if (match[3]) { // a formatting token\n\t\t\tchunks.push({ token: match[3] });\n\t\t}\n\t\telse if (match[5]) { // an unenclosed literal string\n\t\t\tchunks.push(match[5]);\n\t\t}\n\t}\n\n\treturn chunks;\n}\n\n;;\n\nfc.Class = Class; // export\n\n// class that all other classes will inherit from\nfunction Class() { }\n\n// called upon a class to create a subclass\nClass.extend = function(members) {\n\tvar superClass = this;\n\tvar subClass;\n\n\tmembers = members || {};\n\n\t// ensure a constructor for the subclass, forwarding all arguments to the super-constructor if it doesn't exist\n\tif (hasOwnProp(members, 'constructor')) {\n\t\tsubClass = members.constructor;\n\t}\n\tif (typeof subClass !== 'function') {\n\t\tsubClass = members.constructor = function() {\n\t\t\tsuperClass.apply(this, arguments);\n\t\t};\n\t}\n\n\t// build the base prototype for the subclass, which is an new object chained to the superclass's prototype\n\tsubClass.prototype = createObject(superClass.prototype);\n\n\t// copy each member variable/method onto the the subclass's prototype\n\tcopyOwnProps(members, subClass.prototype);\n\tcopyNativeMethods(members, subClass.prototype); // hack for IE8\n\n\t// copy over all class variables/methods to the subclass, such as `extend` and `mixin`\n\tcopyOwnProps(superClass, subClass);\n\n\treturn subClass;\n};\n\n// adds new member variables/methods to the class's prototype.\n// can be called with another class, or a plain object hash containing new members.\nClass.mixin = function(members) {\n\tcopyOwnProps(members.prototype || members, this.prototype); // TODO: copyNativeMethods?\n};\n;;\n\nvar Emitter = fc.Emitter = Class.extend({\n\n\tcallbackHash: null,\n\n\n\ton: function(name, callback) {\n\t\tthis.getCallbacks(name).add(callback);\n\t\treturn this; // for chaining\n\t},\n\n\n\toff: function(name, callback) {\n\t\tthis.getCallbacks(name).remove(callback);\n\t\treturn this; // for chaining\n\t},\n\n\n\ttrigger: function(name) { // args...\n\t\tvar args = Array.prototype.slice.call(arguments, 1);\n\n\t\tthis.triggerWith(name, this, args);\n\n\t\treturn this; // for chaining\n\t},\n\n\n\ttriggerWith: function(name, context, args) {\n\t\tvar callbacks = this.getCallbacks(name);\n\n\t\tcallbacks.fireWith(context, args);\n\n\t\treturn this; // for chaining\n\t},\n\n\n\tgetCallbacks: function(name) {\n\t\tvar callbacks;\n\n\t\tif (!this.callbackHash) {\n\t\t\tthis.callbackHash = {};\n\t\t}\n\n\t\tcallbacks = this.callbackHash[name];\n\t\tif (!callbacks) {\n\t\t\tcallbacks = this.callbackHash[name] = $.Callbacks();\n\t\t}\n\n\t\treturn callbacks;\n\t}\n\n});\n;;\n\n/* A rectangular panel that is absolutely positioned over other content\n------------------------------------------------------------------------------------------------------------------------\nOptions:\n\t- className (string)\n\t- content (HTML string or jQuery element set)\n\t- parentEl\n\t- top\n\t- left\n\t- right (the x coord of where the right edge should be. not a \"CSS\" right)\n\t- autoHide (boolean)\n\t- show (callback)\n\t- hide (callback)\n*/\n\nvar Popover = Class.extend({\n\n\tisHidden: true,\n\toptions: null,\n\tel: null, // the container element for the popover. generated by this object\n\tdocumentMousedownProxy: null, // document mousedown handler bound to `this`\n\tmargin: 10, // the space required between the popover and the edges of the scroll container\n\n\n\tconstructor: function(options) {\n\t\tthis.options = options || {};\n\t},\n\n\n\t// Shows the popover on the specified position. Renders it if not already\n\tshow: function() {\n\t\tif (this.isHidden) {\n\t\t\tif (!this.el) {\n\t\t\t\tthis.render();\n\t\t\t}\n\t\t\tthis.el.show();\n\t\t\tthis.position();\n\t\t\tthis.isHidden = false;\n\t\t\tthis.trigger('show');\n\t\t}\n\t},\n\n\n\t// Hides the popover, through CSS, but does not remove it from the DOM\n\thide: function() {\n\t\tif (!this.isHidden) {\n\t\t\tthis.el.hide();\n\t\t\tthis.isHidden = true;\n\t\t\tthis.trigger('hide');\n\t\t}\n\t},\n\n\n\t// Creates `this.el` and renders content inside of it\n\trender: function() {\n\t\tvar _this = this;\n\t\tvar options = this.options;\n\n\t\tthis.el = $('')\n\t\t\t.addClass(options.className || '')\n\t\t\t.css({\n\t\t\t\t// position initially to the top left to avoid creating scrollbars\n\t\t\t\ttop: 0,\n\t\t\t\tleft: 0\n\t\t\t})\n\t\t\t.append(options.content)\n\t\t\t.appendTo(options.parentEl);\n\n\t\t// when a click happens on anything inside with a 'fc-close' className, hide the popover\n\t\tthis.el.on('click', '.fc-close', function() {\n\t\t\t_this.hide();\n\t\t});\n\n\t\tif (options.autoHide) {\n\t\t\t$(document).on('mousedown', this.documentMousedownProxy = proxy(this, 'documentMousedown'));\n\t\t}\n\t},\n\n\n\t// Triggered when the user clicks *anywhere* in the document, for the autoHide feature\n\tdocumentMousedown: function(ev) {\n\t\t// only hide the popover if the click happened outside the popover\n\t\tif (this.el && !$(ev.target).closest(this.el).length) {\n\t\t\tthis.hide();\n\t\t}\n\t},\n\n\n\t// Hides and unregisters any handlers\n\tremoveElement: function() {\n\t\tthis.hide();\n\n\t\tif (this.el) {\n\t\t\tthis.el.remove();\n\t\t\tthis.el = null;\n\t\t}\n\n\t\t$(document).off('mousedown', this.documentMousedownProxy);\n\t},\n\n\n\t// Positions the popover optimally, using the top/left/right options\n\tposition: function() {\n\t\tvar options = this.options;\n\t\tvar origin = this.el.offsetParent().offset();\n\t\tvar width = this.el.outerWidth();\n\t\tvar height = this.el.outerHeight();\n\t\tvar windowEl = $(window);\n\t\tvar viewportEl = getScrollParent(this.el);\n\t\tvar viewportTop;\n\t\tvar viewportLeft;\n\t\tvar viewportOffset;\n\t\tvar top; // the \"position\" (not \"offset\") values for the popover\n\t\tvar left; //\n\n\t\t// compute top and left\n\t\ttop = options.top || 0;\n\t\tif (options.left !== undefined) {\n\t\t\tleft = options.left;\n\t\t}\n\t\telse if (options.right !== undefined) {\n\t\t\tleft = options.right - width; // derive the left value from the right value\n\t\t}\n\t\telse {\n\t\t\tleft = 0;\n\t\t}\n\n\t\tif (viewportEl.is(window) || viewportEl.is(document)) { // normalize getScrollParent's result\n\t\t\tviewportEl = windowEl;\n\t\t\tviewportTop = 0; // the window is always at the top left\n\t\t\tviewportLeft = 0; // (and .offset() won't work if called here)\n\t\t}\n\t\telse {\n\t\t\tviewportOffset = viewportEl.offset();\n\t\t\tviewportTop = viewportOffset.top;\n\t\t\tviewportLeft = viewportOffset.left;\n\t\t}\n\n\t\t// if the window is scrolled, it causes the visible area to be further down\n\t\tviewportTop += windowEl.scrollTop();\n\t\tviewportLeft += windowEl.scrollLeft();\n\n\t\t// constrain to the view port. if constrained by two edges, give precedence to top/left\n\t\tif (options.viewportConstrain !== false) {\n\t\t\ttop = Math.min(top, viewportTop + viewportEl.outerHeight() - height - this.margin);\n\t\t\ttop = Math.max(top, viewportTop + this.margin);\n\t\t\tleft = Math.min(left, viewportLeft + viewportEl.outerWidth() - width - this.margin);\n\t\t\tleft = Math.max(left, viewportLeft + this.margin);\n\t\t}\n\n\t\tthis.el.css({\n\t\t\ttop: top - origin.top,\n\t\t\tleft: left - origin.left\n\t\t});\n\t},\n\n\n\t// Triggers a callback. Calls a function in the option hash of the same name.\n\t// Arguments beyond the first `name` are forwarded on.\n\t// TODO: better code reuse for this. Repeat code\n\ttrigger: function(name) {\n\t\tif (this.options[name]) {\n\t\t\tthis.options[name].apply(this, Array.prototype.slice.call(arguments, 1));\n\t\t}\n\t}\n\n});\n\n;;\n\n/* A \"coordinate map\" converts pixel coordinates into an associated cell, which has an associated date\n------------------------------------------------------------------------------------------------------------------------\nCommon interface:\n\n\tCoordMap.prototype = {\n\t\tbuild: function() {},\n\t\tgetCell: function(x, y) {}\n\t};\n\n*/\n\n/* Coordinate map for a grid component\n----------------------------------------------------------------------------------------------------------------------*/\n\nvar GridCoordMap = Class.extend({\n\n\tgrid: null, // reference to the Grid\n\trowCoords: null, // array of {top,bottom} objects\n\tcolCoords: null, // array of {left,right} objects\n\n\tcontainerEl: null, // container element that all coordinates are constrained to. optionally assigned\n\tbounds: null,\n\n\n\tconstructor: function(grid) {\n\t\tthis.grid = grid;\n\t},\n\n\n\t// Queries the grid for the coordinates of all the cells\n\tbuild: function() {\n\t\tthis.grid.build();\n\t\tthis.rowCoords = this.grid.computeRowCoords();\n\t\tthis.colCoords = this.grid.computeColCoords();\n\t\tthis.computeBounds();\n\t},\n\n\n\t// Clears the coordinates data to free up memory\n\tclear: function() {\n\t\tthis.grid.clear();\n\t\tthis.rowCoords = null;\n\t\tthis.colCoords = null;\n\t},\n\n\n\t// Given a coordinate of the document, gets the associated cell. If no cell is underneath, returns null\n\tgetCell: function(x, y) {\n\t\tvar rowCoords = this.rowCoords;\n\t\tvar rowCnt = rowCoords.length;\n\t\tvar colCoords = this.colCoords;\n\t\tvar colCnt = colCoords.length;\n\t\tvar hitRow = null;\n\t\tvar hitCol = null;\n\t\tvar i, coords;\n\t\tvar cell;\n\n\t\tif (this.inBounds(x, y)) {\n\n\t\t\tfor (i = 0; i < rowCnt; i++) {\n\t\t\t\tcoords = rowCoords[i];\n\t\t\t\tif (y >= coords.top && y < coords.bottom) {\n\t\t\t\t\thitRow = i;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfor (i = 0; i < colCnt; i++) {\n\t\t\t\tcoords = colCoords[i];\n\t\t\t\tif (x >= coords.left && x < coords.right) {\n\t\t\t\t\thitCol = i;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (hitRow !== null && hitCol !== null) {\n\n\t\t\t\tcell = this.grid.getCell(hitRow, hitCol); // expected to return a fresh object we can modify\n\t\t\t\tcell.grid = this.grid; // for CellDragListener's isCellsEqual. dragging between grids\n\n\t\t\t\t// make the coordinates available on the cell object\n\t\t\t\t$.extend(cell, rowCoords[hitRow], colCoords[hitCol]);\n\n\t\t\t\treturn cell;\n\t\t\t}\n\t\t}\n\n\t\treturn null;\n\t},\n\n\n\t// If there is a containerEl, compute the bounds into min/max values\n\tcomputeBounds: function() {\n\t\tthis.bounds = this.containerEl ?\n\t\t\tgetClientRect(this.containerEl) : // area within scrollbars\n\t\t\tnull;\n\t},\n\n\n\t// Determines if the given coordinates are in bounds. If no `containerEl`, always true\n\tinBounds: function(x, y) {\n\t\tvar bounds = this.bounds;\n\n\t\tif (bounds) {\n\t\t\treturn x >= bounds.left && x < bounds.right && y >= bounds.top && y < bounds.bottom;\n\t\t}\n\n\t\treturn true;\n\t}\n\n});\n\n\n/* Coordinate map that is a combination of multiple other coordinate maps\n----------------------------------------------------------------------------------------------------------------------*/\n\nvar ComboCoordMap = Class.extend({\n\n\tcoordMaps: null, // an array of CoordMaps\n\n\n\tconstructor: function(coordMaps) {\n\t\tthis.coordMaps = coordMaps;\n\t},\n\n\n\t// Builds all coordMaps\n\tbuild: function() {\n\t\tvar coordMaps = this.coordMaps;\n\t\tvar i;\n\n\t\tfor (i = 0; i < coordMaps.length; i++) {\n\t\t\tcoordMaps[i].build();\n\t\t}\n\t},\n\n\n\t// Queries all coordMaps for the cell underneath the given coordinates, returning the first result\n\tgetCell: function(x, y) {\n\t\tvar coordMaps = this.coordMaps;\n\t\tvar cell = null;\n\t\tvar i;\n\n\t\tfor (i = 0; i < coordMaps.length && !cell; i++) {\n\t\t\tcell = coordMaps[i].getCell(x, y);\n\t\t}\n\n\t\treturn cell;\n\t},\n\n\n\t// Clears all coordMaps\n\tclear: function() {\n\t\tvar coordMaps = this.coordMaps;\n\t\tvar i;\n\n\t\tfor (i = 0; i < coordMaps.length; i++) {\n\t\t\tcoordMaps[i].clear();\n\t\t}\n\t}\n\n});\n\n;;\n\n/* Tracks a drag's mouse movement, firing various handlers\n----------------------------------------------------------------------------------------------------------------------*/\n\nvar DragListener = fc.DragListener = Class.extend({\n\n\toptions: null,\n\n\tisListening: false,\n\tisDragging: false,\n\n\t// coordinates of the initial mousedown\n\toriginX: null,\n\toriginY: null,\n\n\t// handler attached to the document, bound to the DragListener's `this`\n\tmousemoveProxy: null,\n\tmouseupProxy: null,\n\n\t// for IE8 bug-fighting behavior, for now\n\tsubjectEl: null, // the element being draged. optional\n\tsubjectHref: null,\n\n\tscrollEl: null,\n\tscrollBounds: null, // { top, bottom, left, right }\n\tscrollTopVel: null, // pixels per second\n\tscrollLeftVel: null, // pixels per second\n\tscrollIntervalId: null, // ID of setTimeout for scrolling animation loop\n\tscrollHandlerProxy: null, // this-scoped function for handling when scrollEl is scrolled\n\n\tscrollSensitivity: 30, // pixels from edge for scrolling to start\n\tscrollSpeed: 200, // pixels per second, at maximum speed\n\tscrollIntervalMs: 50, // millisecond wait between scroll increment\n\n\n\tconstructor: function(options) {\n\t\toptions = options || {};\n\t\tthis.options = options;\n\t\tthis.subjectEl = options.subjectEl;\n\t},\n\n\n\t// Call this when the user does a mousedown. Will probably lead to startListening\n\tmousedown: function(ev) {\n\t\tif (isPrimaryMouseButton(ev)) {\n\n\t\t\tev.preventDefault(); // prevents native selection in most browsers\n\n\t\t\tthis.startListening(ev);\n\n\t\t\t// start the drag immediately if there is no minimum distance for a drag start\n\t\t\tif (!this.options.distance) {\n\t\t\t\tthis.startDrag(ev);\n\t\t\t}\n\t\t}\n\t},\n\n\n\t// Call this to start tracking mouse movements\n\tstartListening: function(ev) {\n\t\tvar scrollParent;\n\n\t\tif (!this.isListening) {\n\n\t\t\t// grab scroll container and attach handler\n\t\t\tif (ev && this.options.scroll) {\n\t\t\t\tscrollParent = getScrollParent($(ev.target));\n\t\t\t\tif (!scrollParent.is(window) && !scrollParent.is(document)) {\n\t\t\t\t\tthis.scrollEl = scrollParent;\n\n\t\t\t\t\t// scope to `this`, and use `debounce` to make sure rapid calls don't happen\n\t\t\t\t\tthis.scrollHandlerProxy = debounce(proxy(this, 'scrollHandler'), 100);\n\t\t\t\t\tthis.scrollEl.on('scroll', this.scrollHandlerProxy);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t$(document)\n\t\t\t\t.on('mousemove', this.mousemoveProxy = proxy(this, 'mousemove'))\n\t\t\t\t.on('mouseup', this.mouseupProxy = proxy(this, 'mouseup'))\n\t\t\t\t.on('selectstart', this.preventDefault); // prevents native selection in IE<=8\n\n\t\t\tif (ev) {\n\t\t\t\tthis.originX = ev.pageX;\n\t\t\t\tthis.originY = ev.pageY;\n\t\t\t}\n\t\t\telse {\n\t\t\t\t// if no starting information was given, origin will be the topleft corner of the screen.\n\t\t\t\t// if so, dx/dy in the future will be the absolute coordinates.\n\t\t\t\tthis.originX = 0;\n\t\t\t\tthis.originY = 0;\n\t\t\t}\n\n\t\t\tthis.isListening = true;\n\t\t\tthis.listenStart(ev);\n\t\t}\n\t},\n\n\n\t// Called when drag listening has started (but a real drag has not necessarily began)\n\tlistenStart: function(ev) {\n\t\tthis.trigger('listenStart', ev);\n\t},\n\n\n\t// Called when the user moves the mouse\n\tmousemove: function(ev) {\n\t\tvar dx = ev.pageX - this.originX;\n\t\tvar dy = ev.pageY - this.originY;\n\t\tvar minDistance;\n\t\tvar distanceSq; // current distance from the origin, squared\n\n\t\tif (!this.isDragging) { // if not already dragging...\n\t\t\t// then start the drag if the minimum distance criteria is met\n\t\t\tminDistance = this.options.distance || 1;\n\t\t\tdistanceSq = dx * dx + dy * dy;\n\t\t\tif (distanceSq >= minDistance * minDistance) { // use pythagorean theorem\n\t\t\t\tthis.startDrag(ev);\n\t\t\t}\n\t\t}\n\n\t\tif (this.isDragging) {\n\t\t\tthis.drag(dx, dy, ev); // report a drag, even if this mousemove initiated the drag\n\t\t}\n\t},\n\n\n\t// Call this to initiate a legitimate drag.\n\t// This function is called internally from this class, but can also be called explicitly from outside\n\tstartDrag: function(ev) {\n\n\t\tif (!this.isListening) { // startDrag must have manually initiated\n\t\t\tthis.startListening();\n\t\t}\n\n\t\tif (!this.isDragging) {\n\t\t\tthis.isDragging = true;\n\t\t\tthis.dragStart(ev);\n\t\t}\n\t},\n\n\n\t// Called when the actual drag has started (went beyond minDistance)\n\tdragStart: function(ev) {\n\t\tvar subjectEl = this.subjectEl;\n\n\t\tthis.trigger('dragStart', ev);\n\n\t\t// remove a mousedown'd 's href so it is not visited (IE8 bug)\n\t\tif ((this.subjectHref = subjectEl ? subjectEl.attr('href') : null)) {\n\t\t\tsubjectEl.removeAttr('href');\n\t\t}\n\t},\n\n\n\t// Called while the mouse is being moved and when we know a legitimate drag is taking place\n\tdrag: function(dx, dy, ev) {\n\t\tthis.trigger('drag', dx, dy, ev);\n\t\tthis.updateScroll(ev); // will possibly cause scrolling\n\t},\n\n\n\t// Called when the user does a mouseup\n\tmouseup: function(ev) {\n\t\tthis.stopListening(ev);\n\t},\n\n\n\t// Called when the drag is over. Will not cause listening to stop however.\n\t// A concluding 'cellOut' event will NOT be triggered.\n\tstopDrag: function(ev) {\n\t\tif (this.isDragging) {\n\t\t\tthis.stopScrolling();\n\t\t\tthis.dragStop(ev);\n\t\t\tthis.isDragging = false;\n\t\t}\n\t},\n\n\n\t// Called when dragging has been stopped\n\tdragStop: function(ev) {\n\t\tvar _this = this;\n\n\t\tthis.trigger('dragStop', ev);\n\n\t\t// restore a mousedown'd 's href (for IE8 bug)\n\t\tsetTimeout(function() { // must be outside of the click's execution\n\t\t\tif (_this.subjectHref) {\n\t\t\t\t_this.subjectEl.attr('href', _this.subjectHref);\n\t\t\t}\n\t\t}, 0);\n\t},\n\n\n\t// Call this to stop listening to the user's mouse events\n\tstopListening: function(ev) {\n\t\tthis.stopDrag(ev); // if there's a current drag, kill it\n\n\t\tif (this.isListening) {\n\n\t\t\t// remove the scroll handler if there is a scrollEl\n\t\t\tif (this.scrollEl) {\n\t\t\t\tthis.scrollEl.off('scroll', this.scrollHandlerProxy);\n\t\t\t\tthis.scrollHandlerProxy = null;\n\t\t\t}\n\n\t\t\t$(document)\n\t\t\t\t.off('mousemove', this.mousemoveProxy)\n\t\t\t\t.off('mouseup', this.mouseupProxy)\n\t\t\t\t.off('selectstart', this.preventDefault);\n\n\t\t\tthis.mousemoveProxy = null;\n\t\t\tthis.mouseupProxy = null;\n\n\t\t\tthis.isListening = false;\n\t\t\tthis.listenStop(ev);\n\t\t}\n\t},\n\n\n\t// Called when drag listening has stopped\n\tlistenStop: function(ev) {\n\t\tthis.trigger('listenStop', ev);\n\t},\n\n\n\t// Triggers a callback. Calls a function in the option hash of the same name.\n\t// Arguments beyond the first `name` are forwarded on.\n\ttrigger: function(name) {\n\t\tif (this.options[name]) {\n\t\t\tthis.options[name].apply(this, Array.prototype.slice.call(arguments, 1));\n\t\t}\n\t},\n\n\n\t// Stops a given mouse event from doing it's native browser action. In our case, text selection.\n\tpreventDefault: function(ev) {\n\t\tev.preventDefault();\n\t},\n\n\n\t/* Scrolling\n\t------------------------------------------------------------------------------------------------------------------*/\n\n\n\t// Computes and stores the bounding rectangle of scrollEl\n\tcomputeScrollBounds: function() {\n\t\tvar el = this.scrollEl;\n\n\t\tthis.scrollBounds = el ? getOuterRect(el) : null;\n\t\t\t// TODO: use getClientRect in future. but prevents auto scrolling when on top of scrollbars\n\t},\n\n\n\t// Called when the dragging is in progress and scrolling should be updated\n\tupdateScroll: function(ev) {\n\t\tvar sensitivity = this.scrollSensitivity;\n\t\tvar bounds = this.scrollBounds;\n\t\tvar topCloseness, bottomCloseness;\n\t\tvar leftCloseness, rightCloseness;\n\t\tvar topVel = 0;\n\t\tvar leftVel = 0;\n\n\t\tif (bounds) { // only scroll if scrollEl exists\n\n\t\t\t// compute closeness to edges. valid range is from 0.0 - 1.0\n\t\t\ttopCloseness = (sensitivity - (ev.pageY - bounds.top)) / sensitivity;\n\t\t\tbottomCloseness = (sensitivity - (bounds.bottom - ev.pageY)) / sensitivity;\n\t\t\tleftCloseness = (sensitivity - (ev.pageX - bounds.left)) / sensitivity;\n\t\t\trightCloseness = (sensitivity - (bounds.right - ev.pageX)) / sensitivity;\n\n\t\t\t// translate vertical closeness into velocity.\n\t\t\t// mouse must be completely in bounds for velocity to happen.\n\t\t\tif (topCloseness >= 0 && topCloseness <= 1) {\n\t\t\t\ttopVel = topCloseness * this.scrollSpeed * -1; // negative. for scrolling up\n\t\t\t}\n\t\t\telse if (bottomCloseness >= 0 && bottomCloseness <= 1) {\n\t\t\t\ttopVel = bottomCloseness * this.scrollSpeed;\n\t\t\t}\n\n\t\t\t// translate horizontal closeness into velocity\n\t\t\tif (leftCloseness >= 0 && leftCloseness <= 1) {\n\t\t\t\tleftVel = leftCloseness * this.scrollSpeed * -1; // negative. for scrolling left\n\t\t\t}\n\t\t\telse if (rightCloseness >= 0 && rightCloseness <= 1) {\n\t\t\t\tleftVel = rightCloseness * this.scrollSpeed;\n\t\t\t}\n\t\t}\n\n\t\tthis.setScrollVel(topVel, leftVel);\n\t},\n\n\n\t// Sets the speed-of-scrolling for the scrollEl\n\tsetScrollVel: function(topVel, leftVel) {\n\n\t\tthis.scrollTopVel = topVel;\n\t\tthis.scrollLeftVel = leftVel;\n\n\t\tthis.constrainScrollVel(); // massages into realistic values\n\n\t\t// if there is non-zero velocity, and an animation loop hasn't already started, then START\n\t\tif ((this.scrollTopVel || this.scrollLeftVel) && !this.scrollIntervalId) {\n\t\t\tthis.scrollIntervalId = setInterval(\n\t\t\t\tproxy(this, 'scrollIntervalFunc'), // scope to `this`\n\t\t\t\tthis.scrollIntervalMs\n\t\t\t);\n\t\t}\n\t},\n\n\n\t// Forces scrollTopVel and scrollLeftVel to be zero if scrolling has already gone all the way\n\tconstrainScrollVel: function() {\n\t\tvar el = this.scrollEl;\n\n\t\tif (this.scrollTopVel < 0) { // scrolling up?\n\t\t\tif (el.scrollTop() <= 0) { // already scrolled all the way up?\n\t\t\t\tthis.scrollTopVel = 0;\n\t\t\t}\n\t\t}\n\t\telse if (this.scrollTopVel > 0) { // scrolling down?\n\t\t\tif (el.scrollTop() + el[0].clientHeight >= el[0].scrollHeight) { // already scrolled all the way down?\n\t\t\t\tthis.scrollTopVel = 0;\n\t\t\t}\n\t\t}\n\n\t\tif (this.scrollLeftVel < 0) { // scrolling left?\n\t\t\tif (el.scrollLeft() <= 0) { // already scrolled all the left?\n\t\t\t\tthis.scrollLeftVel = 0;\n\t\t\t}\n\t\t}\n\t\telse if (this.scrollLeftVel > 0) { // scrolling right?\n\t\t\tif (el.scrollLeft() + el[0].clientWidth >= el[0].scrollWidth) { // already scrolled all the way right?\n\t\t\t\tthis.scrollLeftVel = 0;\n\t\t\t}\n\t\t}\n\t},\n\n\n\t// This function gets called during every iteration of the scrolling animation loop\n\tscrollIntervalFunc: function() {\n\t\tvar el = this.scrollEl;\n\t\tvar frac = this.scrollIntervalMs / 1000; // considering animation frequency, what the vel should be mult'd by\n\n\t\t// change the value of scrollEl's scroll\n\t\tif (this.scrollTopVel) {\n\t\t\tel.scrollTop(el.scrollTop() + this.scrollTopVel * frac);\n\t\t}\n\t\tif (this.scrollLeftVel) {\n\t\t\tel.scrollLeft(el.scrollLeft() + this.scrollLeftVel * frac);\n\t\t}\n\n\t\tthis.constrainScrollVel(); // since the scroll values changed, recompute the velocities\n\n\t\t// if scrolled all the way, which causes the vels to be zero, stop the animation loop\n\t\tif (!this.scrollTopVel && !this.scrollLeftVel) {\n\t\t\tthis.stopScrolling();\n\t\t}\n\t},\n\n\n\t// Kills any existing scrolling animation loop\n\tstopScrolling: function() {\n\t\tif (this.scrollIntervalId) {\n\t\t\tclearInterval(this.scrollIntervalId);\n\t\t\tthis.scrollIntervalId = null;\n\n\t\t\t// when all done with scrolling, recompute positions since they probably changed\n\t\t\tthis.scrollStop();\n\t\t}\n\t},\n\n\n\t// Get called when the scrollEl is scrolled (NOTE: this is delayed via debounce)\n\tscrollHandler: function() {\n\t\t// recompute all coordinates, but *only* if this is *not* part of our scrolling animation\n\t\tif (!this.scrollIntervalId) {\n\t\t\tthis.scrollStop();\n\t\t}\n\t},\n\n\n\t// Called when scrolling has stopped, whether through auto scroll, or the user scrolling\n\tscrollStop: function() {\n\t}\n\n});\n\n;;\n\n/* Tracks mouse movements over a CoordMap and raises events about which cell the mouse is over.\n------------------------------------------------------------------------------------------------------------------------\noptions:\n- subjectEl\n- subjectCenter\n*/\n\nvar CellDragListener = DragListener.extend({\n\n\tcoordMap: null, // converts coordinates to date cells\n\torigCell: null, // the cell the mouse was over when listening started\n\tcell: null, // the cell the mouse is over\n\tcoordAdjust: null, // delta that will be added to the mouse coordinates when computing collisions\n\n\n\tconstructor: function(coordMap, options) {\n\t\tDragListener.prototype.constructor.call(this, options); // call the super-constructor\n\n\t\tthis.coordMap = coordMap;\n\t},\n\n\n\t// Called when drag listening starts (but a real drag has not necessarily began).\n\t// ev might be undefined if dragging was started manually.\n\tlistenStart: function(ev) {\n\t\tvar subjectEl = this.subjectEl;\n\t\tvar subjectRect;\n\t\tvar origPoint;\n\t\tvar point;\n\n\t\tDragListener.prototype.listenStart.apply(this, arguments); // call the super-method\n\n\t\tthis.computeCoords();\n\n\t\tif (ev) {\n\t\t\torigPoint = { left: ev.pageX, top: ev.pageY };\n\t\t\tpoint = origPoint;\n\n\t\t\t// constrain the point to bounds of the element being dragged\n\t\t\tif (subjectEl) {\n\t\t\t\tsubjectRect = getOuterRect(subjectEl); // used for centering as well\n\t\t\t\tpoint = constrainPoint(point, subjectRect);\n\t\t\t}\n\n\t\t\tthis.origCell = this.getCell(point.left, point.top);\n\n\t\t\t// treat the center of the subject as the collision point?\n\t\t\tif (subjectEl && this.options.subjectCenter) {\n\n\t\t\t\t// only consider the area the subject overlaps the cell. best for large subjects\n\t\t\t\tif (this.origCell) {\n\t\t\t\t\tsubjectRect = intersectRects(this.origCell, subjectRect) ||\n\t\t\t\t\t\tsubjectRect; // in case there is no intersection\n\t\t\t\t}\n\n\t\t\t\tpoint = getRectCenter(subjectRect);\n\t\t\t}\n\n\t\t\tthis.coordAdjust = diffPoints(point, origPoint); // point - origPoint\n\t\t}\n\t\telse {\n\t\t\tthis.origCell = null;\n\t\t\tthis.coordAdjust = null;\n\t\t}\n\t},\n\n\n\t// Recomputes the drag-critical positions of elements\n\tcomputeCoords: function() {\n\t\tthis.coordMap.build();\n\t\tthis.computeScrollBounds();\n\t},\n\n\n\t// Called when the actual drag has started\n\tdragStart: function(ev) {\n\t\tvar cell;\n\n\t\tDragListener.prototype.dragStart.apply(this, arguments); // call the super-method\n\n\t\tcell = this.getCell(ev.pageX, ev.pageY); // might be different from this.origCell if the min-distance is large\n\n\t\t// report the initial cell the mouse is over\n\t\t// especially important if no min-distance and drag starts immediately\n\t\tif (cell) {\n\t\t\tthis.cellOver(cell);\n\t\t}\n\t},\n\n\n\t// Called when the drag moves\n\tdrag: function(dx, dy, ev) {\n\t\tvar cell;\n\n\t\tDragListener.prototype.drag.apply(this, arguments); // call the super-method\n\n\t\tcell = this.getCell(ev.pageX, ev.pageY);\n\n\t\tif (!isCellsEqual(cell, this.cell)) { // a different cell than before?\n\t\t\tif (this.cell) {\n\t\t\t\tthis.cellOut();\n\t\t\t}\n\t\t\tif (cell) {\n\t\t\t\tthis.cellOver(cell);\n\t\t\t}\n\t\t}\n\t},\n\n\n\t// Called when dragging has been stopped\n\tdragStop: function() {\n\t\tthis.cellDone();\n\t\tDragListener.prototype.dragStop.apply(this, arguments); // call the super-method\n\t},\n\n\n\t// Called when a the mouse has just moved over a new cell\n\tcellOver: function(cell) {\n\t\tthis.cell = cell;\n\t\tthis.trigger('cellOver', cell, isCellsEqual(cell, this.origCell), this.origCell);\n\t},\n\n\n\t// Called when the mouse has just moved out of a cell\n\tcellOut: function() {\n\t\tif (this.cell) {\n\t\t\tthis.trigger('cellOut', this.cell);\n\t\t\tthis.cellDone();\n\t\t\tthis.cell = null;\n\t\t}\n\t},\n\n\n\t// Called after a cellOut. Also called before a dragStop\n\tcellDone: function() {\n\t\tif (this.cell) {\n\t\t\tthis.trigger('cellDone', this.cell);\n\t\t}\n\t},\n\n\n\t// Called when drag listening has stopped\n\tlistenStop: function() {\n\t\tDragListener.prototype.listenStop.apply(this, arguments); // call the super-method\n\n\t\tthis.origCell = this.cell = null;\n\t\tthis.coordMap.clear();\n\t},\n\n\n\t// Called when scrolling has stopped, whether through auto scroll, or the user scrolling\n\tscrollStop: function() {\n\t\tDragListener.prototype.scrollStop.apply(this, arguments); // call the super-method\n\n\t\tthis.computeCoords(); // cells' absolute positions will be in new places. recompute\n\t},\n\n\n\t// Gets the cell underneath the coordinates for the given mouse event\n\tgetCell: function(left, top) {\n\n\t\tif (this.coordAdjust) {\n\t\t\tleft += this.coordAdjust.left;\n\t\t\ttop += this.coordAdjust.top;\n\t\t}\n\n\t\treturn this.coordMap.getCell(left, top);\n\t}\n\n});\n\n\n// Returns `true` if the cells are identically equal. `false` otherwise.\n// They must have the same row, col, and be from the same grid.\n// Two null values will be considered equal, as two \"out of the grid\" states are the same.\nfunction isCellsEqual(cell1, cell2) {\n\n\tif (!cell1 && !cell2) {\n\t\treturn true;\n\t}\n\n\tif (cell1 && cell2) {\n\t\treturn cell1.grid === cell2.grid &&\n\t\t\tcell1.row === cell2.row &&\n\t\t\tcell1.col === cell2.col;\n\t}\n\n\treturn false;\n}\n\n;;\n\n/* Creates a clone of an element and lets it track the mouse as it moves\n----------------------------------------------------------------------------------------------------------------------*/\n\nvar MouseFollower = Class.extend({\n\n\toptions: null,\n\n\tsourceEl: null, // the element that will be cloned and made to look like it is dragging\n\tel: null, // the clone of `sourceEl` that will track the mouse\n\tparentEl: null, // the element that `el` (the clone) will be attached to\n\n\t// the initial position of el, relative to the offset parent. made to match the initial offset of sourceEl\n\ttop0: null,\n\tleft0: null,\n\n\t// the initial position of the mouse\n\tmouseY0: null,\n\tmouseX0: null,\n\n\t// the number of pixels the mouse has moved from its initial position\n\ttopDelta: null,\n\tleftDelta: null,\n\n\tmousemoveProxy: null, // document mousemove handler, bound to the MouseFollower's `this`\n\n\tisFollowing: false,\n\tisHidden: false,\n\tisAnimating: false, // doing the revert animation?\n\n\tconstructor: function(sourceEl, options) {\n\t\tthis.options = options = options || {};\n\t\tthis.sourceEl = sourceEl;\n\t\tthis.parentEl = options.parentEl ? $(options.parentEl) : sourceEl.parent(); // default to sourceEl's parent\n\t},\n\n\n\t// Causes the element to start following the mouse\n\tstart: function(ev) {\n\t\tif (!this.isFollowing) {\n\t\t\tthis.isFollowing = true;\n\n\t\t\tthis.mouseY0 = ev.pageY;\n\t\t\tthis.mouseX0 = ev.pageX;\n\t\t\tthis.topDelta = 0;\n\t\t\tthis.leftDelta = 0;\n\n\t\t\tif (!this.isHidden) {\n\t\t\t\tthis.updatePosition();\n\t\t\t}\n\n\t\t\t$(document).on('mousemove', this.mousemoveProxy = proxy(this, 'mousemove'));\n\t\t}\n\t},\n\n\n\t// Causes the element to stop following the mouse. If shouldRevert is true, will animate back to original position.\n\t// `callback` gets invoked when the animation is complete. If no animation, it is invoked immediately.\n\tstop: function(shouldRevert, callback) {\n\t\tvar _this = this;\n\t\tvar revertDuration = this.options.revertDuration;\n\n\t\tfunction complete() {\n\t\t\tthis.isAnimating = false;\n\t\t\t_this.removeElement();\n\n\t\t\tthis.top0 = this.left0 = null; // reset state for future updatePosition calls\n\n\t\t\tif (callback) {\n\t\t\t\tcallback();\n\t\t\t}\n\t\t}\n\n\t\tif (this.isFollowing && !this.isAnimating) { // disallow more than one stop animation at a time\n\t\t\tthis.isFollowing = false;\n\n\t\t\t$(document).off('mousemove', this.mousemoveProxy);\n\n\t\t\tif (shouldRevert && revertDuration && !this.isHidden) { // do a revert animation?\n\t\t\t\tthis.isAnimating = true;\n\t\t\t\tthis.el.animate({\n\t\t\t\t\ttop: this.top0,\n\t\t\t\t\tleft: this.left0\n\t\t\t\t}, {\n\t\t\t\t\tduration: revertDuration,\n\t\t\t\t\tcomplete: complete\n\t\t\t\t});\n\t\t\t}\n\t\t\telse {\n\t\t\t\tcomplete();\n\t\t\t}\n\t\t}\n\t},\n\n\n\t// Gets the tracking element. Create it if necessary\n\tgetEl: function() {\n\t\tvar el = this.el;\n\n\t\tif (!el) {\n\t\t\tthis.sourceEl.width(); // hack to force IE8 to compute correct bounding box\n\t\t\tel = this.el = this.sourceEl.clone()\n\t\t\t\t.css({\n\t\t\t\t\tposition: 'absolute',\n\t\t\t\t\tvisibility: '', // in case original element was hidden (commonly through hideEvents())\n\t\t\t\t\tdisplay: this.isHidden ? 'none' : '', // for when initially hidden\n\t\t\t\t\tmargin: 0,\n\t\t\t\t\tright: 'auto', // erase and set width instead\n\t\t\t\t\tbottom: 'auto', // erase and set height instead\n\t\t\t\t\twidth: this.sourceEl.width(), // explicit height in case there was a 'right' value\n\t\t\t\t\theight: this.sourceEl.height(), // explicit width in case there was a 'bottom' value\n\t\t\t\t\topacity: this.options.opacity || '',\n\t\t\t\t\tzIndex: this.options.zIndex\n\t\t\t\t})\n\t\t\t\t.appendTo(this.parentEl);\n\t\t}\n\n\t\treturn el;\n\t},\n\n\n\t// Removes the tracking element if it has already been created\n\tremoveElement: function() {\n\t\tif (this.el) {\n\t\t\tthis.el.remove();\n\t\t\tthis.el = null;\n\t\t}\n\t},\n\n\n\t// Update the CSS position of the tracking element\n\tupdatePosition: function() {\n\t\tvar sourceOffset;\n\t\tvar origin;\n\n\t\tthis.getEl(); // ensure this.el\n\n\t\t// make sure origin info was computed\n\t\tif (this.top0 === null) {\n\t\t\tthis.sourceEl.width(); // hack to force IE8 to compute correct bounding box\n\t\t\tsourceOffset = this.sourceEl.offset();\n\t\t\torigin = this.el.offsetParent().offset();\n\t\t\tthis.top0 = sourceOffset.top - origin.top;\n\t\t\tthis.left0 = sourceOffset.left - origin.left;\n\t\t}\n\n\t\tthis.el.css({\n\t\t\ttop: this.top0 + this.topDelta,\n\t\t\tleft: this.left0 + this.leftDelta\n\t\t});\n\t},\n\n\n\t// Gets called when the user moves the mouse\n\tmousemove: function(ev) {\n\t\tthis.topDelta = ev.pageY - this.mouseY0;\n\t\tthis.leftDelta = ev.pageX - this.mouseX0;\n\n\t\tif (!this.isHidden) {\n\t\t\tthis.updatePosition();\n\t\t}\n\t},\n\n\n\t// Temporarily makes the tracking element invisible. Can be called before following starts\n\thide: function() {\n\t\tif (!this.isHidden) {\n\t\t\tthis.isHidden = true;\n\t\t\tif (this.el) {\n\t\t\t\tthis.el.hide();\n\t\t\t}\n\t\t}\n\t},\n\n\n\t// Show the tracking element after it has been temporarily hidden\n\tshow: function() {\n\t\tif (this.isHidden) {\n\t\t\tthis.isHidden = false;\n\t\t\tthis.updatePosition();\n\t\t\tthis.getEl().show();\n\t\t}\n\t}\n\n});\n\n;;\n\n/* A utility class for rendering rows.\n----------------------------------------------------------------------------------------------------------------------*/\n// It leverages methods of the subclass and the View to determine custom rendering behavior for each row \"type\"\n// (such as highlight rows, day rows, helper rows, etc).\n\nvar RowRenderer = Class.extend({\n\n\tview: null, // a View object\n\tisRTL: null, // shortcut to the view's isRTL option\n\tcellHtml: ' | ', // plain default HTML used for a cell when no other is available\n\n\n\tconstructor: function(view) {\n\t\tthis.view = view;\n\t\tthis.isRTL = view.opt('isRTL');\n\t},\n\n\n\t// Renders the HTML for a row, leveraging custom cell-HTML-renderers based on the `rowType`.\n\t// Also applies the \"intro\" and \"outro\" cells, which are specified by the subclass and views.\n\t// `row` is an optional row number.\n\trowHtml: function(rowType, row) {\n\t\tvar renderCell = this.getHtmlRenderer('cell', rowType);\n\t\tvar rowCellHtml = '';\n\t\tvar col;\n\t\tvar cell;\n\n\t\trow = row || 0;\n\n\t\tfor (col = 0; col < this.colCnt; col++) {\n\t\t\tcell = this.getCell(row, col);\n\t\t\trowCellHtml += renderCell(cell);\n\t\t}\n\n\t\trowCellHtml = this.bookendCells(rowCellHtml, rowType, row); // apply intro and outro\n\n\t\treturn ' ' + rowCellHtml + ' ';\n\t},\n\n\n\t// Applies the \"intro\" and \"outro\" HTML to the given cells.\n\t// Intro means the leftmost cell when the calendar is LTR and the rightmost cell when RTL. Vice-versa for outro.\n\t// `cells` can be an HTML string of 's or a jQuery | element\n\t// `row` is an optional row number.\n\tbookendCells: function(cells, rowType, row) {\n\t\tvar intro = this.getHtmlRenderer('intro', rowType)(row || 0);\n\t\tvar outro = this.getHtmlRenderer('outro', rowType)(row || 0);\n\t\tvar prependHtml = this.isRTL ? outro : intro;\n\t\tvar appendHtml = this.isRTL ? intro : outro;\n\n\t\tif (typeof cells === 'string') {\n\t\t\treturn prependHtml + cells + appendHtml;\n\t\t}\n\t\telse { // a jQuery element\n\t\t\treturn cells.prepend(prependHtml).append(appendHtml);\n\t\t}\n\t},\n\n\n\t// Returns an HTML-rendering function given a specific `rendererName` (like cell, intro, or outro) and a specific\n\t// `rowType` (like day, eventSkeleton, helperSkeleton), which is optional.\n\t// If a renderer for the specific rowType doesn't exist, it will fall back to a generic renderer.\n\t// We will query the View object first for any custom rendering functions, then the methods of the subclass.\n\tgetHtmlRenderer: function(rendererName, rowType) {\n\t\tvar view = this.view;\n\t\tvar generalName; // like \"cellHtml\"\n\t\tvar specificName; // like \"dayCellHtml\". based on rowType\n\t\tvar provider; // either the View or the RowRenderer subclass, whichever provided the method\n\t\tvar renderer;\n\n\t\tgeneralName = rendererName + 'Html';\n\t\tif (rowType) {\n\t\t\tspecificName = rowType + capitaliseFirstLetter(rendererName) + 'Html';\n\t\t}\n\n\t\tif (specificName && (renderer = view[specificName])) {\n\t\t\tprovider = view;\n\t\t}\n\t\telse if (specificName && (renderer = this[specificName])) {\n\t\t\tprovider = this;\n\t\t}\n\t\telse if ((renderer = view[generalName])) {\n\t\t\tprovider = view;\n\t\t}\n\t\telse if ((renderer = this[generalName])) {\n\t\t\tprovider = this;\n\t\t}\n\n\t\tif (typeof renderer === 'function') {\n\t\t\treturn function() {\n\t\t\t\treturn renderer.apply(provider, arguments) || ''; // use correct `this` and always return a string\n\t\t\t};\n\t\t}\n\n\t\t// the rendered can be a plain string as well. if not specified, always an empty string.\n\t\treturn function() {\n\t\t\treturn renderer || '';\n\t\t};\n\t}\n\n});\n\n;;\n\n/* An abstract class comprised of a \"grid\" of cells that each represent a specific datetime\n----------------------------------------------------------------------------------------------------------------------*/\n\nvar Grid = fc.Grid = RowRenderer.extend({\n\n\tstart: null, // the date of the first cell\n\tend: null, // the date after the last cell\n\n\trowCnt: 0, // number of rows\n\tcolCnt: 0, // number of cols\n\n\tel: null, // the containing element\n\tcoordMap: null, // a GridCoordMap that converts pixel values to datetimes\n\telsByFill: null, // a hash of jQuery element sets used for rendering each fill. Keyed by fill name.\n\n\texternalDragStartProxy: null, // binds the Grid's scope to externalDragStart (in DayGrid.events)\n\n\t// derived from options\n\tcolHeadFormat: null, // TODO: move to another class. not applicable to all Grids\n\teventTimeFormat: null,\n\tdisplayEventTime: null,\n\tdisplayEventEnd: null,\n\n\t// if all cells are the same length of time, the duration they all share. optional.\n\t// when defined, allows the computeCellRange shortcut, as well as improved resizing behavior.\n\tcellDuration: null,\n\n\t// if defined, holds the unit identified (ex: \"year\" or \"month\") that determines the level of granularity\n\t// of the date cells. if not defined, assumes to be day and time granularity.\n\tlargeUnit: null,\n\n\n\tconstructor: function() {\n\t\tRowRenderer.apply(this, arguments); // call the super-constructor\n\n\t\tthis.coordMap = new GridCoordMap(this);\n\t\tthis.elsByFill = {};\n\t\tthis.externalDragStartProxy = proxy(this, 'externalDragStart');\n\t},\n\n\n\t/* Options\n\t------------------------------------------------------------------------------------------------------------------*/\n\n\n\t// Generates the format string used for the text in column headers, if not explicitly defined by 'columnFormat'\n\t// TODO: move to another class. not applicable to all Grids\n\tcomputeColHeadFormat: function() {\n\t\t// subclasses must implement if they want to use headHtml()\n\t},\n\n\n\t// Generates the format string used for event time text, if not explicitly defined by 'timeFormat'\n\tcomputeEventTimeFormat: function() {\n\t\treturn this.view.opt('smallTimeFormat');\n\t},\n\n\n\t// Determines whether events should have their end times displayed, if not explicitly defined by 'displayEventTime'.\n\t// Only applies to non-all-day events.\n\tcomputeDisplayEventTime: function() {\n\t\treturn true;\n\t},\n\n\n\t// Determines whether events should have their end times displayed, if not explicitly defined by 'displayEventEnd'\n\tcomputeDisplayEventEnd: function() {\n\t\treturn true;\n\t},\n\n\n\t/* Dates\n\t------------------------------------------------------------------------------------------------------------------*/\n\n\n\t// Tells the grid about what period of time to display.\n\t// Any date-related cell system internal data should be generated.\n\tsetRange: function(range) {\n\t\tthis.start = range.start.clone();\n\t\tthis.end = range.end.clone();\n\n\t\tthis.rangeUpdated();\n\t\tthis.processRangeOptions();\n\t},\n\n\n\t// Called when internal variables that rely on the range should be updated\n\trangeUpdated: function() {\n\t},\n\n\n\t// Updates values that rely on options and also relate to range\n\tprocessRangeOptions: function() {\n\t\tvar view = this.view;\n\t\tvar displayEventTime;\n\t\tvar displayEventEnd;\n\n\t\t// Populate option-derived settings. Look for override first, then compute if necessary.\n\t\tthis.colHeadFormat = view.opt('columnFormat') || this.computeColHeadFormat();\n\n\t\tthis.eventTimeFormat =\n\t\t\tview.opt('eventTimeFormat') ||\n\t\t\tview.opt('timeFormat') || // deprecated\n\t\t\tthis.computeEventTimeFormat();\n\n\t\tdisplayEventTime = view.opt('displayEventTime');\n\t\tif (displayEventTime == null) {\n\t\t\tdisplayEventTime = this.computeDisplayEventTime(); // might be based off of range\n\t\t}\n\n\t\tdisplayEventEnd = view.opt('displayEventEnd');\n\t\tif (displayEventEnd == null) {\n\t\t\tdisplayEventEnd = this.computeDisplayEventEnd(); // might be based off of range\n\t\t}\n\n\t\tthis.displayEventTime = displayEventTime;\n\t\tthis.displayEventEnd = displayEventEnd;\n\t},\n\n\n\t// Called before the grid's coordinates will need to be queried for cells.\n\t// Any non-date-related cell system internal data should be built.\n\tbuild: function() {\n\t},\n\n\n\t// Called after the grid's coordinates are done being relied upon.\n\t// Any non-date-related cell system internal data should be cleared.\n\tclear: function() {\n\t},\n\n\n\t// Converts a range with an inclusive `start` and an exclusive `end` into an array of segment objects\n\trangeToSegs: function(range) {\n\t\t// subclasses must implement\n\t},\n\n\n\t// Diffs the two dates, returning a duration, based on granularity of the grid\n\tdiffDates: function(a, b) {\n\t\tif (this.largeUnit) {\n\t\t\treturn diffByUnit(a, b, this.largeUnit);\n\t\t}\n\t\telse {\n\t\t\treturn diffDayTime(a, b);\n\t\t}\n\t},\n\n\n\t/* Cells\n\t------------------------------------------------------------------------------------------------------------------*/\n\t// NOTE: columns are ordered left-to-right\n\n\n\t// Gets an object containing row/col number, misc data, and range information about the cell.\n\t// Accepts row/col values, an object with row/col properties, or a single-number offset from the first cell.\n\tgetCell: function(row, col) {\n\t\tvar cell;\n\n\t\tif (col == null) {\n\t\t\tif (typeof row === 'number') { // a single-number offset\n\t\t\t\tcol = row % this.colCnt;\n\t\t\t\trow = Math.floor(row / this.colCnt);\n\t\t\t}\n\t\t\telse { // an object with row/col properties\n\t\t\t\tcol = row.col;\n\t\t\t\trow = row.row;\n\t\t\t}\n\t\t}\n\n\t\tcell = { row: row, col: col };\n\n\t\t$.extend(cell, this.getRowData(row), this.getColData(col));\n\t\t$.extend(cell, this.computeCellRange(cell));\n\n\t\treturn cell;\n\t},\n\n\n\t// Given a cell object with index and misc data, generates a range object\n\t// If the grid is leveraging cellDuration, this doesn't need to be defined. Only computeCellDate does.\n\t// If being overridden, should return a range with reference-free date copies.\n\tcomputeCellRange: function(cell) {\n\t\tvar date = this.computeCellDate(cell);\n\n\t\treturn {\n\t\t\tstart: date,\n\t\t\tend: date.clone().add(this.cellDuration)\n\t\t};\n\t},\n\n\n\t// Given a cell, returns its start date. Should return a reference-free date copy.\n\tcomputeCellDate: function(cell) {\n\t\t// subclasses can implement\n\t},\n\n\n\t// Retrieves misc data about the given row\n\tgetRowData: function(row) {\n\t\treturn {};\n\t},\n\n\n\t// Retrieves misc data baout the given column\n\tgetColData: function(col) {\n\t\treturn {};\n\t},\n\n\n\t// Retrieves the element representing the given row\n\tgetRowEl: function(row) {\n\t\t// subclasses should implement if leveraging the default getCellDayEl() or computeRowCoords()\n\t},\n\n\n\t// Retrieves the element representing the given column\n\tgetColEl: function(col) {\n\t\t// subclasses should implement if leveraging the default getCellDayEl() or computeColCoords()\n\t},\n\n\n\t// Given a cell object, returns the element that represents the cell's whole-day\n\tgetCellDayEl: function(cell) {\n\t\treturn this.getColEl(cell.col) || this.getRowEl(cell.row);\n\t},\n\n\n\t/* Cell Coordinates\n\t------------------------------------------------------------------------------------------------------------------*/\n\n\n\t// Computes the top/bottom coordinates of all rows.\n\t// By default, queries the dimensions of the element provided by getRowEl().\n\tcomputeRowCoords: function() {\n\t\tvar items = [];\n\t\tvar i, el;\n\t\tvar top;\n\n\t\tfor (i = 0; i < this.rowCnt; i++) {\n\t\t\tel = this.getRowEl(i);\n\t\t\ttop = el.offset().top;\n\t\t\titems.push({\n\t\t\t\ttop: top,\n\t\t\t\tbottom: top + el.outerHeight()\n\t\t\t});\n\t\t}\n\n\t\treturn items;\n\t},\n\n\n\t// Computes the left/right coordinates of all rows.\n\t// By default, queries the dimensions of the element provided by getColEl(). Columns can be LTR or RTL.\n\tcomputeColCoords: function() {\n\t\tvar items = [];\n\t\tvar i, el;\n\t\tvar left;\n\n\t\tfor (i = 0; i < this.colCnt; i++) {\n\t\t\tel = this.getColEl(i);\n\t\t\tleft = el.offset().left;\n\t\t\titems.push({\n\t\t\t\tleft: left,\n\t\t\t\tright: left + el.outerWidth()\n\t\t\t});\n\t\t}\n\n\t\treturn items;\n\t},\n\n\n\t/* Rendering\n\t------------------------------------------------------------------------------------------------------------------*/\n\n\n\t// Sets the container element that the grid should render inside of.\n\t// Does other DOM-related initializations.\n\tsetElement: function(el) {\n\t\tvar _this = this;\n\n\t\tthis.el = el;\n\n\t\t// attach a handler to the grid's root element.\n\t\t// jQuery will take care of unregistering them when removeElement gets called.\n\t\tel.on('mousedown', function(ev) {\n\t\t\tif (\n\t\t\t\t!$(ev.target).is('.fc-event-container *, .fc-more') && // not an an event element, or \"more..\" link\n\t\t\t\t!$(ev.target).closest('.fc-popover').length // not on a popover (like the \"more..\" events one)\n\t\t\t) {\n\t\t\t\t_this.dayMousedown(ev);\n\t\t\t}\n\t\t});\n\n\t\t// attach event-element-related handlers. in Grid.events\n\t\t// same garbage collection note as above.\n\t\tthis.bindSegHandlers();\n\n\t\tthis.bindGlobalHandlers();\n\t},\n\n\n\t// Removes the grid's container element from the DOM. Undoes any other DOM-related attachments.\n\t// DOES NOT remove any content beforehand (doesn't clear events or call unrenderDates), unlike View\n\tremoveElement: function() {\n\t\tthis.unbindGlobalHandlers();\n\n\t\tthis.el.remove();\n\n\t\t// NOTE: we don't null-out this.el for the same reasons we don't do it within View::removeElement\n\t},\n\n\n\t// Renders the basic structure of grid view before any content is rendered\n\trenderSkeleton: function() {\n\t\t// subclasses should implement\n\t},\n\n\n\t// Renders the grid's date-related content (like cells that represent days/times).\n\t// Assumes setRange has already been called and the skeleton has already been rendered.\n\trenderDates: function() {\n\t\t// subclasses should implement\n\t},\n\n\n\t// Unrenders the grid's date-related content\n\tunrenderDates: function() {\n\t\t// subclasses should implement\n\t},\n\n\n\t/* Handlers\n\t------------------------------------------------------------------------------------------------------------------*/\n\n\n\t// Binds DOM handlers to elements that reside outside the grid, such as the document\n\tbindGlobalHandlers: function() {\n\t\t$(document).on('dragstart sortstart', this.externalDragStartProxy); // jqui\n\t},\n\n\n\t// Unbinds DOM handlers from elements that reside outside the grid\n\tunbindGlobalHandlers: function() {\n\t\t$(document).off('dragstart sortstart', this.externalDragStartProxy); // jqui\n\t},\n\n\n\t// Process a mousedown on an element that represents a day. For day clicking and selecting.\n\tdayMousedown: function(ev) {\n\t\tvar _this = this;\n\t\tvar view = this.view;\n\t\tvar isSelectable = view.opt('selectable');\n\t\tvar dayClickCell; // null if invalid dayClick\n\t\tvar selectionRange; // null if invalid selection\n\n\t\t// this listener tracks a mousedown on a day element, and a subsequent drag.\n\t\t// if the drag ends on the same day, it is a 'dayClick'.\n\t\t// if 'selectable' is enabled, this listener also detects selections.\n\t\tvar dragListener = new CellDragListener(this.coordMap, {\n\t\t\t//distance: 5, // needs more work if we want dayClick to fire correctly\n\t\t\tscroll: view.opt('dragScroll'),\n\t\t\tdragStart: function() {\n\t\t\t\tview.unselect(); // since we could be rendering a new selection, we want to clear any old one\n\t\t\t},\n\t\t\tcellOver: function(cell, isOrig, origCell) {\n\t\t\t\tif (origCell) { // click needs to have started on a cell\n\t\t\t\t\tdayClickCell = isOrig ? cell : null; // single-cell selection is a day click\n\t\t\t\t\tif (isSelectable) {\n\t\t\t\t\t\tselectionRange = _this.computeSelection(origCell, cell);\n\t\t\t\t\t\tif (selectionRange) {\n\t\t\t\t\t\t\t_this.renderSelection(selectionRange);\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse {\n\t\t\t\t\t\t\tdisableCursor();\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t},\n\t\t\tcellOut: function(cell) {\n\t\t\t\tdayClickCell = null;\n\t\t\t\tselectionRange = null;\n\t\t\t\t_this.unrenderSelection();\n\t\t\t\tenableCursor();\n\t\t\t},\n\t\t\tlistenStop: function(ev) {\n\t\t\t\tif (dayClickCell) {\n\t\t\t\t\tview.triggerDayClick(dayClickCell, _this.getCellDayEl(dayClickCell), ev);\n\t\t\t\t}\n\t\t\t\tif (selectionRange) {\n\t\t\t\t\t// the selection will already have been rendered. just report it\n\t\t\t\t\tview.reportSelection(selectionRange, ev);\n\t\t\t\t}\n\t\t\t\tenableCursor();\n\t\t\t}\n\t\t});\n\n\t\tdragListener.mousedown(ev); // start listening, which will eventually initiate a dragStart\n\t},\n\n\n\t/* Event Helper\n\t------------------------------------------------------------------------------------------------------------------*/\n\t// TODO: should probably move this to Grid.events, like we did event dragging / resizing\n\n\n\t// Renders a mock event over the given range\n\trenderRangeHelper: function(range, sourceSeg) {\n\t\tvar fakeEvent = this.fabricateHelperEvent(range, sourceSeg);\n\n\t\tthis.renderHelper(fakeEvent, sourceSeg); // do the actual rendering\n\t},\n\n\n\t// Builds a fake event given a date range it should cover, and a segment is should be inspired from.\n\t// The range's end can be null, in which case the mock event that is rendered will have a null end time.\n\t// `sourceSeg` is the internal segment object involved in the drag. If null, something external is dragging.\n\tfabricateHelperEvent: function(range, sourceSeg) {\n\t\tvar fakeEvent = sourceSeg ? createObject(sourceSeg.event) : {}; // mask the original event object if possible\n\n\t\tfakeEvent.start = range.start.clone();\n\t\tfakeEvent.end = range.end ? range.end.clone() : null;\n\t\tfakeEvent.allDay = null; // force it to be freshly computed by normalizeEventRange\n\t\tthis.view.calendar.normalizeEventRange(fakeEvent);\n\n\t\t// this extra className will be useful for differentiating real events from mock events in CSS\n\t\tfakeEvent.className = (fakeEvent.className || []).concat('fc-helper');\n\n\t\t// if something external is being dragged in, don't render a resizer\n\t\tif (!sourceSeg) {\n\t\t\tfakeEvent.editable = false;\n\t\t}\n\n\t\treturn fakeEvent;\n\t},\n\n\n\t// Renders a mock event\n\trenderHelper: function(event, sourceSeg) {\n\t\t// subclasses must implement\n\t},\n\n\n\t// Unrenders a mock event\n\tunrenderHelper: function() {\n\t\t// subclasses must implement\n\t},\n\n\n\t/* Selection\n\t------------------------------------------------------------------------------------------------------------------*/\n\n\n\t// Renders a visual indication of a selection. Will highlight by default but can be overridden by subclasses.\n\trenderSelection: function(range) {\n\t\tthis.renderHighlight(this.selectionRangeToSegs(range));\n\t},\n\n\n\t// Unrenders any visual indications of a selection. Will unrender a highlight by default.\n\tunrenderSelection: function() {\n\t\tthis.unrenderHighlight();\n\t},\n\n\n\t// Given the first and last cells of a selection, returns a range object.\n\t// Will return something falsy if the selection is invalid (when outside of selectionConstraint for example).\n\t// Subclasses can override and provide additional data in the range object. Will be passed to renderSelection().\n\tcomputeSelection: function(firstCell, lastCell) {\n\t\tvar dates = [\n\t\t\tfirstCell.start,\n\t\t\tfirstCell.end,\n\t\t\tlastCell.start,\n\t\t\tlastCell.end\n\t\t];\n\t\tvar range;\n\n\t\tdates.sort(compareNumbers); // sorts chronologically. works with Moments\n\n\t\trange = {\n\t\t\tstart: dates[0].clone(),\n\t\t\tend: dates[3].clone()\n\t\t};\n\n\t\tif (!this.view.calendar.isSelectionRangeAllowed(range)) {\n\t\t\treturn null;\n\t\t}\n\n\t\treturn range;\n\t},\n\n\n\tselectionRangeToSegs: function(range) {\n\t\treturn this.rangeToSegs(range);\n\t},\n\n\n\t/* Highlight\n\t------------------------------------------------------------------------------------------------------------------*/\n\n\n\t// Renders an emphasis on the given date range. Given an array of segments.\n\trenderHighlight: function(segs) {\n\t\tthis.renderFill('highlight', segs);\n\t},\n\n\n\t// Unrenders the emphasis on a date range\n\tunrenderHighlight: function() {\n\t\tthis.unrenderFill('highlight');\n\t},\n\n\n\t// Generates an array of classNames for rendering the highlight. Used by the fill system.\n\thighlightSegClasses: function() {\n\t\treturn [ 'fc-highlight' ];\n\t},\n\n\n\t/* Fill System (highlight, background events, business hours)\n\t------------------------------------------------------------------------------------------------------------------*/\n\n\n\t// Renders a set of rectangles over the given segments of time.\n\t// MUST RETURN a subset of segs, the segs that were actually rendered.\n\t// Responsible for populating this.elsByFill. TODO: better API for expressing this requirement\n\trenderFill: function(type, segs) {\n\t\t// subclasses must implement\n\t},\n\n\n\t// Unrenders a specific type of fill that is currently rendered on the grid\n\tunrenderFill: function(type) {\n\t\tvar el = this.elsByFill[type];\n\n\t\tif (el) {\n\t\t\tel.remove();\n\t\t\tdelete this.elsByFill[type];\n\t\t}\n\t},\n\n\n\t// Renders and assigns an `el` property for each fill segment. Generic enough to work with different types.\n\t// Only returns segments that successfully rendered.\n\t// To be harnessed by renderFill (implemented by subclasses).\n\t// Analagous to renderFgSegEls.\n\trenderFillSegEls: function(type, segs) {\n\t\tvar _this = this;\n\t\tvar segElMethod = this[type + 'SegEl'];\n\t\tvar html = '';\n\t\tvar renderedSegs = [];\n\t\tvar i;\n\n\t\tif (segs.length) {\n\n\t\t\t// build a large concatenation of segment HTML\n\t\t\tfor (i = 0; i < segs.length; i++) {\n\t\t\t\thtml += this.fillSegHtml(type, segs[i]);\n\t\t\t}\n\n\t\t\t// Grab individual elements from the combined HTML string. Use each as the default rendering.\n\t\t\t// Then, compute the 'el' for each segment.\n\t\t\t$(html).each(function(i, node) {\n\t\t\t\tvar seg = segs[i];\n\t\t\t\tvar el = $(node);\n\n\t\t\t\t// allow custom filter methods per-type\n\t\t\t\tif (segElMethod) {\n\t\t\t\t\tel = segElMethod.call(_this, seg, el);\n\t\t\t\t}\n\n\t\t\t\tif (el) { // custom filters did not cancel the render\n\t\t\t\t\tel = $(el); // allow custom filter to return raw DOM node\n\n\t\t\t\t\t// correct element type? (would be bad if a non-TD were inserted into a table for example)\n\t\t\t\t\tif (el.is(_this.fillSegTag)) {\n\t\t\t\t\t\tseg.el = el;\n\t\t\t\t\t\trenderedSegs.push(seg);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\n\t\treturn renderedSegs;\n\t},\n\n\n\tfillSegTag: 'div', // subclasses can override\n\n\n\t// Builds the HTML needed for one fill segment. Generic enought o work with different types.\n\tfillSegHtml: function(type, seg) {\n\n\t\t// custom hooks per-type\n\t\tvar classesMethod = this[type + 'SegClasses'];\n\t\tvar cssMethod = this[type + 'SegCss'];\n\n\t\tvar classes = classesMethod ? classesMethod.call(this, seg) : [];\n\t\tvar css = cssToStr(cssMethod ? cssMethod.call(this, seg) : {});\n\n\t\treturn '<' + this.fillSegTag +\n\t\t\t(classes.length ? ' class=\"' + classes.join(' ') + '\"' : '') +\n\t\t\t(css ? ' style=\"' + css + '\"' : '') +\n\t\t\t' />';\n\t},\n\n\n\t/* Generic rendering utilities for subclasses\n\t------------------------------------------------------------------------------------------------------------------*/\n\n\n\t// Renders a day-of-week header row.\n\t// TODO: move to another class. not applicable to all Grids\n\theadHtml: function() {\n\t\treturn '' +\n\t\t\t'' +\n\t\t\t\t' ' +\n\t\t\t\t\t'' +\n\t\t\t\t\t\tthis.rowHtml('head') + // leverages RowRenderer\n\t\t\t\t\t'' +\n\t\t\t\t' ' +\n\t\t\t' ';\n\t},\n\n\n\t// Used by the `headHtml` method, via RowRenderer, for rendering the HTML of a day-of-week header cell\n\t// TODO: move to another class. not applicable to all Grids\n\theadCellHtml: function(cell) {\n\t\tvar view = this.view;\n\t\tvar date = cell.start;\n\n\t\treturn '' +\n\t\t\t'';\n\t},\n\n\n\t// Renders the HTML for a single-day background cell\n\tbgCellHtml: function(cell) {\n\t\tvar view = this.view;\n\t\tvar date = cell.start;\n\t\tvar classes = this.getDayClasses(date);\n\n\t\tclasses.unshift('fc-day', view.widgetContentClass);\n\n\t\treturn ' | ';\n\t},\n\n\n\t// Computes HTML classNames for a single-day cell\n\tgetDayClasses: function(date) {\n\t\tvar view = this.view;\n\t\tvar today = view.calendar.getNow().stripTime();\n\t\tvar classes = [ 'fc-' + dayIDs[date.day()] ];\n\n\t\tif (\n\t\t\tview.intervalDuration.as('months') == 1 &&\n\t\t\tdate.month() != view.intervalStart.month()\n\t\t) {\n\t\t\tclasses.push('fc-other-month');\n\t\t}\n\n\t\tif (date.isSame(today, 'day')) {\n\t\t\tclasses.push(\n\t\t\t\t'fc-today',\n\t\t\t\tview.highlightStateClass\n\t\t\t);\n\t\t}\n\t\telse if (date < today) {\n\t\t\tclasses.push('fc-past');\n\t\t}\n\t\telse {\n\t\t\tclasses.push('fc-future');\n\t\t}\n\n\t\treturn classes;\n\t}\n\n});\n\n;;\n\n/* Event-rendering and event-interaction methods for the abstract Grid class\n----------------------------------------------------------------------------------------------------------------------*/\n\nGrid.mixin({\n\n\tmousedOverSeg: null, // the segment object the user's mouse is over. null if over nothing\n\tisDraggingSeg: false, // is a segment being dragged? boolean\n\tisResizingSeg: false, // is a segment being resized? boolean\n\tisDraggingExternal: false, // jqui-dragging an external element? boolean\n\tsegs: null, // the event segments currently rendered in the grid\n\n\n\t// Renders the given events onto the grid\n\trenderEvents: function(events) {\n\t\tvar segs = this.eventsToSegs(events);\n\t\tvar bgSegs = [];\n\t\tvar fgSegs = [];\n\t\tvar i, seg;\n\n\t\tfor (i = 0; i < segs.length; i++) {\n\t\t\tseg = segs[i];\n\n\t\t\tif (isBgEvent(seg.event)) {\n\t\t\t\tbgSegs.push(seg);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tfgSegs.push(seg);\n\t\t\t}\n\t\t}\n\n\t\t// Render each different type of segment.\n\t\t// Each function may return a subset of the segs, segs that were actually rendered.\n\t\tbgSegs = this.renderBgSegs(bgSegs) || bgSegs;\n\t\tfgSegs = this.renderFgSegs(fgSegs) || fgSegs;\n\n\t\tthis.segs = bgSegs.concat(fgSegs);\n\t},\n\n\n\t// Unrenders all events currently rendered on the grid\n\tunrenderEvents: function() {\n\t\tthis.triggerSegMouseout(); // trigger an eventMouseout if user's mouse is over an event\n\n\t\tthis.unrenderFgSegs();\n\t\tthis.unrenderBgSegs();\n\n\t\tthis.segs = null;\n\t},\n\n\n\t// Retrieves all rendered segment objects currently rendered on the grid\n\tgetEventSegs: function() {\n\t\treturn this.segs || [];\n\t},\n\n\n\t/* Foreground Segment Rendering\n\t------------------------------------------------------------------------------------------------------------------*/\n\n\n\t// Renders foreground event segments onto the grid. May return a subset of segs that were rendered.\n\trenderFgSegs: function(segs) {\n\t\t// subclasses must implement\n\t},\n\n\n\t// Unrenders all currently rendered foreground segments\n\tunrenderFgSegs: function() {\n\t\t// subclasses must implement\n\t},\n\n\n\t// Renders and assigns an `el` property for each foreground event segment.\n\t// Only returns segments that successfully rendered.\n\t// A utility that subclasses may use.\n\trenderFgSegEls: function(segs, disableResizing) {\n\t\tvar view = this.view;\n\t\tvar html = '';\n\t\tvar renderedSegs = [];\n\t\tvar i;\n\n\t\tif (segs.length) { // don't build an empty html string\n\n\t\t\t// build a large concatenation of event segment HTML\n\t\t\tfor (i = 0; i < segs.length; i++) {\n\t\t\t\thtml += this.fgSegHtml(segs[i], disableResizing);\n\t\t\t}\n\n\t\t\t// Grab individual elements from the combined HTML string. Use each as the default rendering.\n\t\t\t// Then, compute the 'el' for each segment. An el might be null if the eventRender callback returned false.\n\t\t\t$(html).each(function(i, node) {\n\t\t\t\tvar seg = segs[i];\n\t\t\t\tvar el = view.resolveEventEl(seg.event, $(node));\n\n\t\t\t\tif (el) {\n\t\t\t\t\tel.data('fc-seg', seg); // used by handlers\n\t\t\t\t\tseg.el = el;\n\t\t\t\t\trenderedSegs.push(seg);\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\n\t\treturn renderedSegs;\n\t},\n\n\n\t// Generates the HTML for the default rendering of a foreground event segment. Used by renderFgSegEls()\n\tfgSegHtml: function(seg, disableResizing) {\n\t\t// subclasses should implement\n\t},\n\n\n\t/* Background Segment Rendering\n\t------------------------------------------------------------------------------------------------------------------*/\n\n\n\t// Renders the given background event segments onto the grid.\n\t// Returns a subset of the segs that were actually rendered.\n\trenderBgSegs: function(segs) {\n\t\treturn this.renderFill('bgEvent', segs);\n\t},\n\n\n\t// Unrenders all the currently rendered background event segments\n\tunrenderBgSegs: function() {\n\t\tthis.unrenderFill('bgEvent');\n\t},\n\n\n\t// Renders a background event element, given the default rendering. Called by the fill system.\n\tbgEventSegEl: function(seg, el) {\n\t\treturn this.view.resolveEventEl(seg.event, el); // will filter through eventRender\n\t},\n\n\n\t// Generates an array of classNames to be used for the default rendering of a background event.\n\t// Called by the fill system.\n\tbgEventSegClasses: function(seg) {\n\t\tvar event = seg.event;\n\t\tvar source = event.source || {};\n\n\t\treturn [ 'fc-bgevent' ].concat(\n\t\t\tevent.className,\n\t\t\tsource.className || []\n\t\t);\n\t},\n\n\n\t// Generates a semicolon-separated CSS string to be used for the default rendering of a background event.\n\t// Called by the fill system.\n\t// TODO: consolidate with getEventSkinCss?\n\tbgEventSegCss: function(seg) {\n\t\tvar view = this.view;\n\t\tvar event = seg.event;\n\t\tvar source = event.source || {};\n\n\t\treturn {\n\t\t\t'background-color':\n\t\t\t\tevent.backgroundColor ||\n\t\t\t\tevent.color ||\n\t\t\t\tsource.backgroundColor ||\n\t\t\t\tsource.color ||\n\t\t\t\tview.opt('eventBackgroundColor') ||\n\t\t\t\tview.opt('eventColor')\n\t\t};\n\t},\n\n\n\t// Generates an array of classNames to be used for the rendering business hours overlay. Called by the fill system.\n\tbusinessHoursSegClasses: function(seg) {\n\t\treturn [ 'fc-nonbusiness', 'fc-bgevent' ];\n\t},\n\n\n\t/* Handlers\n\t------------------------------------------------------------------------------------------------------------------*/\n\n\n\t// Attaches event-element-related handlers to the container element and leverage bubbling\n\tbindSegHandlers: function() {\n\t\tvar _this = this;\n\t\tvar view = this.view;\n\n\t\t$.each(\n\t\t\t{\n\t\t\t\tmouseenter: function(seg, ev) {\n\t\t\t\t\t_this.triggerSegMouseover(seg, ev);\n\t\t\t\t},\n\t\t\t\tmouseleave: function(seg, ev) {\n\t\t\t\t\t_this.triggerSegMouseout(seg, ev);\n\t\t\t\t},\n\t\t\t\tclick: function(seg, ev) {\n\t\t\t\t\treturn view.trigger('eventClick', this, seg.event, ev); // can return `false` to cancel\n\t\t\t\t},\n\t\t\t\tmousedown: function(seg, ev) {\n\t\t\t\t\tif ($(ev.target).is('.fc-resizer') && view.isEventResizable(seg.event)) {\n\t\t\t\t\t\t_this.segResizeMousedown(seg, ev, $(ev.target).is('.fc-start-resizer'));\n\t\t\t\t\t}\n\t\t\t\t\telse if (view.isEventDraggable(seg.event)) {\n\t\t\t\t\t\t_this.segDragMousedown(seg, ev);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t},\n\t\t\tfunction(name, func) {\n\t\t\t\t// attach the handler to the container element and only listen for real event elements via bubbling\n\t\t\t\t_this.el.on(name, '.fc-event-container > *', function(ev) {\n\t\t\t\t\tvar seg = $(this).data('fc-seg'); // grab segment data. put there by View::renderEvents\n\n\t\t\t\t\t// only call the handlers if there is not a drag/resize in progress\n\t\t\t\t\tif (seg && !_this.isDraggingSeg && !_this.isResizingSeg) {\n\t\t\t\t\t\treturn func.call(this, seg, ev); // `this` will be the event element\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\t\t);\n\t},\n\n\n\t// Updates internal state and triggers handlers for when an event element is moused over\n\ttriggerSegMouseover: function(seg, ev) {\n\t\tif (!this.mousedOverSeg) {\n\t\t\tthis.mousedOverSeg = seg;\n\t\t\tthis.view.trigger('eventMouseover', seg.el[0], seg.event, ev);\n\t\t}\n\t},\n\n\n\t// Updates internal state and triggers handlers for when an event element is moused out.\n\t// Can be given no arguments, in which case it will mouseout the segment that was previously moused over.\n\ttriggerSegMouseout: function(seg, ev) {\n\t\tev = ev || {}; // if given no args, make a mock mouse event\n\n\t\tif (this.mousedOverSeg) {\n\t\t\tseg = seg || this.mousedOverSeg; // if given no args, use the currently moused-over segment\n\t\t\tthis.mousedOverSeg = null;\n\t\t\tthis.view.trigger('eventMouseout', seg.el[0], seg.event, ev);\n\t\t}\n\t},\n\n\n\t/* Event Dragging\n\t------------------------------------------------------------------------------------------------------------------*/\n\n\n\t// Called when the user does a mousedown on an event, which might lead to dragging.\n\t// Generic enough to work with any type of Grid.\n\tsegDragMousedown: function(seg, ev) {\n\t\tvar _this = this;\n\t\tvar view = this.view;\n\t\tvar calendar = view.calendar;\n\t\tvar el = seg.el;\n\t\tvar event = seg.event;\n\t\tvar dropLocation;\n\n\t\t// A clone of the original element that will move with the mouse\n\t\tvar mouseFollower = new MouseFollower(seg.el, {\n\t\t\tparentEl: view.el,\n\t\t\topacity: view.opt('dragOpacity'),\n\t\t\trevertDuration: view.opt('dragRevertDuration'),\n\t\t\tzIndex: 2 // one above the .fc-view\n\t\t});\n\n\t\t// Tracks mouse movement over the *view's* coordinate map. Allows dragging and dropping between subcomponents\n\t\t// of the view.\n\t\tvar dragListener = new CellDragListener(view.coordMap, {\n\t\t\tdistance: 5,\n\t\t\tscroll: view.opt('dragScroll'),\n\t\t\tsubjectEl: el,\n\t\t\tsubjectCenter: true,\n\t\t\tlistenStart: function(ev) {\n\t\t\t\tmouseFollower.hide(); // don't show until we know this is a real drag\n\t\t\t\tmouseFollower.start(ev);\n\t\t\t},\n\t\t\tdragStart: function(ev) {\n\t\t\t\t_this.triggerSegMouseout(seg, ev); // ensure a mouseout on the manipulated event has been reported\n\t\t\t\t_this.segDragStart(seg, ev);\n\t\t\t\tview.hideEvent(event); // hide all event segments. our mouseFollower will take over\n\t\t\t},\n\t\t\tcellOver: function(cell, isOrig, origCell) {\n\n\t\t\t\t// starting cell could be forced (DayGrid.limit)\n\t\t\t\tif (seg.cell) {\n\t\t\t\t\torigCell = seg.cell;\n\t\t\t\t}\n\n\t\t\t\tdropLocation = _this.computeEventDrop(origCell, cell, event);\n\n\t\t\t\tif (dropLocation && !calendar.isEventRangeAllowed(dropLocation, event)) {\n\t\t\t\t\tdisableCursor();\n\t\t\t\t\tdropLocation = null;\n\t\t\t\t}\n\n\t\t\t\t// if a valid drop location, have the subclass render a visual indication\n\t\t\t\tif (dropLocation && view.renderDrag(dropLocation, seg)) {\n\t\t\t\t\tmouseFollower.hide(); // if the subclass is already using a mock event \"helper\", hide our own\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tmouseFollower.show(); // otherwise, have the helper follow the mouse (no snapping)\n\t\t\t\t}\n\n\t\t\t\tif (isOrig) {\n\t\t\t\t\tdropLocation = null; // needs to have moved cells to be a valid drop\n\t\t\t\t}\n\t\t\t},\n\t\t\tcellOut: function() { // called before mouse moves to a different cell OR moved out of all cells\n\t\t\t\tview.unrenderDrag(); // unrender whatever was done in renderDrag\n\t\t\t\tmouseFollower.show(); // show in case we are moving out of all cells\n\t\t\t\tdropLocation = null;\n\t\t\t},\n\t\t\tcellDone: function() { // Called after a cellOut OR before a dragStop\n\t\t\t\tenableCursor();\n\t\t\t},\n\t\t\tdragStop: function(ev) {\n\t\t\t\t// do revert animation if hasn't changed. calls a callback when finished (whether animation or not)\n\t\t\t\tmouseFollower.stop(!dropLocation, function() {\n\t\t\t\t\tview.unrenderDrag();\n\t\t\t\t\tview.showEvent(event);\n\t\t\t\t\t_this.segDragStop(seg, ev);\n\n\t\t\t\t\tif (dropLocation) {\n\t\t\t\t\t\tview.reportEventDrop(event, dropLocation, this.largeUnit, el, ev);\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t},\n\t\t\tlistenStop: function() {\n\t\t\t\tmouseFollower.stop(); // put in listenStop in case there was a mousedown but the drag never started\n\t\t\t}\n\t\t});\n\n\t\tdragListener.mousedown(ev); // start listening, which will eventually lead to a dragStart\n\t},\n\n\n\t// Called before event segment dragging starts\n\tsegDragStart: function(seg, ev) {\n\t\tthis.isDraggingSeg = true;\n\t\tthis.view.trigger('eventDragStart', seg.el[0], seg.event, ev, {}); // last argument is jqui dummy\n\t},\n\n\n\t// Called after event segment dragging stops\n\tsegDragStop: function(seg, ev) {\n\t\tthis.isDraggingSeg = false;\n\t\tthis.view.trigger('eventDragStop', seg.el[0], seg.event, ev, {}); // last argument is jqui dummy\n\t},\n\n\n\t// Given the cell an event drag began, and the cell event was dropped, calculates the new start/end/allDay\n\t// values for the event. Subclasses may override and set additional properties to be used by renderDrag.\n\t// A falsy returned value indicates an invalid drop.\n\tcomputeEventDrop: function(startCell, endCell, event) {\n\t\tvar calendar = this.view.calendar;\n\t\tvar dragStart = startCell.start;\n\t\tvar dragEnd = endCell.start;\n\t\tvar delta;\n\t\tvar dropLocation;\n\n\t\tif (dragStart.hasTime() === dragEnd.hasTime()) {\n\t\t\tdelta = this.diffDates(dragEnd, dragStart);\n\n\t\t\t// if an all-day event was in a timed area and it was dragged to a different time,\n\t\t\t// guarantee an end and adjust start/end to have times\n\t\t\tif (event.allDay && durationHasTime(delta)) {\n\t\t\t\tdropLocation = {\n\t\t\t\t\tstart: event.start.clone(),\n\t\t\t\t\tend: calendar.getEventEnd(event), // will be an ambig day\n\t\t\t\t\tallDay: false // for normalizeEventRangeTimes\n\t\t\t\t};\n\t\t\t\tcalendar.normalizeEventRangeTimes(dropLocation);\n\t\t\t}\n\t\t\t// othewise, work off existing values\n\t\t\telse {\n\t\t\t\tdropLocation = {\n\t\t\t\t\tstart: event.start.clone(),\n\t\t\t\t\tend: event.end ? event.end.clone() : null,\n\t\t\t\t\tallDay: event.allDay // keep it the same\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tdropLocation.start.add(delta);\n\t\t\tif (dropLocation.end) {\n\t\t\t\tdropLocation.end.add(delta);\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\t// if switching from day <-> timed, start should be reset to the dropped date, and the end cleared\n\t\t\tdropLocation = {\n\t\t\t\tstart: dragEnd.clone(),\n\t\t\t\tend: null, // end should be cleared\n\t\t\t\tallDay: !dragEnd.hasTime()\n\t\t\t};\n\t\t}\n\n\t\treturn dropLocation;\n\t},\n\n\n\t// Utility for apply dragOpacity to a jQuery set\n\tapplyDragOpacity: function(els) {\n\t\tvar opacity = this.view.opt('dragOpacity');\n\n\t\tif (opacity != null) {\n\t\t\tels.each(function(i, node) {\n\t\t\t\t// Don't use jQuery (will set an IE filter), do it the old fashioned way.\n\t\t\t\t// In IE8, a helper element will disappears if there's a filter.\n\t\t\t\tnode.style.opacity = opacity;\n\t\t\t});\n\t\t}\n\t},\n\n\n\t/* External Element Dragging\n\t------------------------------------------------------------------------------------------------------------------*/\n\n\n\t// Called when a jQuery UI drag is initiated anywhere in the DOM\n\texternalDragStart: function(ev, ui) {\n\t\tvar view = this.view;\n\t\tvar el;\n\t\tvar accept;\n\n\t\tif (view.opt('droppable')) { // only listen if this setting is on\n\t\t\tel = $((ui ? ui.item : null) || ev.target);\n\n\t\t\t// Test that the dragged element passes the dropAccept selector or filter function.\n\t\t\t// FYI, the default is \"*\" (matches all)\n\t\t\taccept = view.opt('dropAccept');\n\t\t\tif ($.isFunction(accept) ? accept.call(el[0], el) : el.is(accept)) {\n\t\t\t\tif (!this.isDraggingExternal) { // prevent double-listening if fired twice\n\t\t\t\t\tthis.listenToExternalDrag(el, ev, ui);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t},\n\n\n\t// Called when a jQuery UI drag starts and it needs to be monitored for cell dropping\n\tlistenToExternalDrag: function(el, ev, ui) {\n\t\tvar _this = this;\n\t\tvar meta = getDraggedElMeta(el); // extra data about event drop, including possible event to create\n\t\tvar dragListener;\n\t\tvar dropLocation; // a null value signals an unsuccessful drag\n\n\t\t// listener that tracks mouse movement over date-associated pixel regions\n\t\tdragListener = new CellDragListener(this.coordMap, {\n\t\t\tlistenStart: function() {\n\t\t\t\t_this.isDraggingExternal = true;\n\t\t\t},\n\t\t\tcellOver: function(cell) {\n\t\t\t\tdropLocation = _this.computeExternalDrop(cell, meta);\n\t\t\t\tif (dropLocation) {\n\t\t\t\t\t_this.renderDrag(dropLocation); // called without a seg parameter\n\t\t\t\t}\n\t\t\t\telse { // invalid drop cell\n\t\t\t\t\tdisableCursor();\n\t\t\t\t}\n\t\t\t},\n\t\t\tcellOut: function() {\n\t\t\t\tdropLocation = null; // signal unsuccessful\n\t\t\t\t_this.unrenderDrag();\n\t\t\t\tenableCursor();\n\t\t\t},\n\t\t\tdragStop: function() {\n\t\t\t\t_this.unrenderDrag();\n\t\t\t\tenableCursor();\n\n\t\t\t\tif (dropLocation) { // element was dropped on a valid date/time cell\n\t\t\t\t\t_this.view.reportExternalDrop(meta, dropLocation, el, ev, ui);\n\t\t\t\t}\n\t\t\t},\n\t\t\tlistenStop: function() {\n\t\t\t\t_this.isDraggingExternal = false;\n\t\t\t}\n\t\t});\n\n\t\tdragListener.startDrag(ev); // start listening immediately\n\t},\n\n\n\t// Given a cell to be dropped upon, and misc data associated with the jqui drag (guaranteed to be a plain object),\n\t// returns start/end dates for the event that would result from the hypothetical drop. end might be null.\n\t// Returning a null value signals an invalid drop cell.\n\tcomputeExternalDrop: function(cell, meta) {\n\t\tvar dropLocation = {\n\t\t\tstart: cell.start.clone(),\n\t\t\tend: null\n\t\t};\n\n\t\t// if dropped on an all-day cell, and element's metadata specified a time, set it\n\t\tif (meta.startTime && !dropLocation.start.hasTime()) {\n\t\t\tdropLocation.start.time(meta.startTime);\n\t\t}\n\n\t\tif (meta.duration) {\n\t\t\tdropLocation.end = dropLocation.start.clone().add(meta.duration);\n\t\t}\n\n\t\tif (!this.view.calendar.isExternalDropRangeAllowed(dropLocation, meta.eventProps)) {\n\t\t\treturn null;\n\t\t}\n\n\t\treturn dropLocation;\n\t},\n\n\n\n\t/* Drag Rendering (for both events and an external elements)\n\t------------------------------------------------------------------------------------------------------------------*/\n\n\n\t// Renders a visual indication of an event or external element being dragged.\n\t// `dropLocation` contains hypothetical start/end/allDay values the event would have if dropped. end can be null.\n\t// `seg` is the internal segment object that is being dragged. If dragging an external element, `seg` is null.\n\t// A truthy returned value indicates this method has rendered a helper element.\n\trenderDrag: function(dropLocation, seg) {\n\t\t// subclasses must implement\n\t},\n\n\n\t// Unrenders a visual indication of an event or external element being dragged\n\tunrenderDrag: function() {\n\t\t// subclasses must implement\n\t},\n\n\n\t/* Resizing\n\t------------------------------------------------------------------------------------------------------------------*/\n\n\n\t// Called when the user does a mousedown on an event's resizer, which might lead to resizing.\n\t// Generic enough to work with any type of Grid.\n\tsegResizeMousedown: function(seg, ev, isStart) {\n\t\tvar _this = this;\n\t\tvar view = this.view;\n\t\tvar calendar = view.calendar;\n\t\tvar el = seg.el;\n\t\tvar event = seg.event;\n\t\tvar eventEnd = calendar.getEventEnd(event);\n\t\tvar dragListener;\n\t\tvar resizeLocation; // falsy if invalid resize\n\n\t\t// Tracks mouse movement over the *grid's* coordinate map\n\t\tdragListener = new CellDragListener(this.coordMap, {\n\t\t\tdistance: 5,\n\t\t\tscroll: view.opt('dragScroll'),\n\t\t\tsubjectEl: el,\n\t\t\tdragStart: function(ev) {\n\t\t\t\t_this.triggerSegMouseout(seg, ev); // ensure a mouseout on the manipulated event has been reported\n\t\t\t\t_this.segResizeStart(seg, ev);\n\t\t\t},\n\t\t\tcellOver: function(cell, isOrig, origCell) {\n\t\t\t\tresizeLocation = isStart ?\n\t\t\t\t\t_this.computeEventStartResize(origCell, cell, event) :\n\t\t\t\t\t_this.computeEventEndResize(origCell, cell, event);\n\n\t\t\t\tif (resizeLocation) {\n\t\t\t\t\tif (!calendar.isEventRangeAllowed(resizeLocation, event)) {\n\t\t\t\t\t\tdisableCursor();\n\t\t\t\t\t\tresizeLocation = null;\n\t\t\t\t\t}\n\t\t\t\t\t// no change? (TODO: how does this work with timezones?)\n\t\t\t\t\telse if (resizeLocation.start.isSame(event.start) && resizeLocation.end.isSame(eventEnd)) {\n\t\t\t\t\t\tresizeLocation = null;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (resizeLocation) {\n\t\t\t\t\tview.hideEvent(event);\n\t\t\t\t\t_this.renderEventResize(resizeLocation, seg);\n\t\t\t\t}\n\t\t\t},\n\t\t\tcellOut: function() { // called before mouse moves to a different cell OR moved out of all cells\n\t\t\t\tresizeLocation = null;\n\t\t\t},\n\t\t\tcellDone: function() { // resets the rendering to show the original event\n\t\t\t\t_this.unrenderEventResize();\n\t\t\t\tview.showEvent(event);\n\t\t\t\tenableCursor();\n\t\t\t},\n\t\t\tdragStop: function(ev) {\n\t\t\t\t_this.segResizeStop(seg, ev);\n\n\t\t\t\tif (resizeLocation) { // valid date to resize to?\n\t\t\t\t\tview.reportEventResize(event, resizeLocation, this.largeUnit, el, ev);\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\n\t\tdragListener.mousedown(ev); // start listening, which will eventually lead to a dragStart\n\t},\n\n\n\t// Called before event segment resizing starts\n\tsegResizeStart: function(seg, ev) {\n\t\tthis.isResizingSeg = true;\n\t\tthis.view.trigger('eventResizeStart', seg.el[0], seg.event, ev, {}); // last argument is jqui dummy\n\t},\n\n\n\t// Called after event segment resizing stops\n\tsegResizeStop: function(seg, ev) {\n\t\tthis.isResizingSeg = false;\n\t\tthis.view.trigger('eventResizeStop', seg.el[0], seg.event, ev, {}); // last argument is jqui dummy\n\t},\n\n\n\t// Returns new date-information for an event segment being resized from its start\n\tcomputeEventStartResize: function(startCell, endCell, event) {\n\t\treturn this.computeEventResize('start', startCell, endCell, event);\n\t},\n\n\n\t// Returns new date-information for an event segment being resized from its end\n\tcomputeEventEndResize: function(startCell, endCell, event) {\n\t\treturn this.computeEventResize('end', startCell, endCell, event);\n\t},\n\n\n\t// Returns new date-information for an event segment being resized from its start OR end\n\t// `type` is either 'start' or 'end'\n\tcomputeEventResize: function(type, startCell, endCell, event) {\n\t\tvar calendar = this.view.calendar;\n\t\tvar delta = this.diffDates(endCell[type], startCell[type]);\n\t\tvar range;\n\t\tvar defaultDuration;\n\n\t\t// build original values to work from, guaranteeing a start and end\n\t\trange = {\n\t\t\tstart: event.start.clone(),\n\t\t\tend: calendar.getEventEnd(event),\n\t\t\tallDay: event.allDay\n\t\t};\n\n\t\t// if an all-day event was in a timed area and was resized to a time, adjust start/end to have times\n\t\tif (range.allDay && durationHasTime(delta)) {\n\t\t\trange.allDay = false;\n\t\t\tcalendar.normalizeEventRangeTimes(range);\n\t\t}\n\n\t\trange[type].add(delta); // apply delta to start or end\n\n\t\t// if the event was compressed too small, find a new reasonable duration for it\n\t\tif (!range.start.isBefore(range.end)) {\n\n\t\t\tdefaultDuration = event.allDay ?\n\t\t\t\tcalendar.defaultAllDayEventDuration :\n\t\t\t\tcalendar.defaultTimedEventDuration;\n\n\t\t\t// between the cell's duration and the event's default duration, use the smaller of the two.\n\t\t\t// example: if year-length slots, and compressed to one slot, we don't want the event to be a year long\n\t\t\tif (this.cellDuration && this.cellDuration < defaultDuration) {\n\t\t\t\tdefaultDuration = this.cellDuration;\n\t\t\t}\n\n\t\t\tif (type == 'start') { // resizing the start?\n\t\t\t\trange.start = range.end.clone().subtract(defaultDuration);\n\t\t\t}\n\t\t\telse { // resizing the end?\n\t\t\t\trange.end = range.start.clone().add(defaultDuration);\n\t\t\t}\n\t\t}\n\n\t\treturn range;\n\t},\n\n\n\t// Renders a visual indication of an event being resized.\n\t// `range` has the updated dates of the event. `seg` is the original segment object involved in the drag.\n\trenderEventResize: function(range, seg) {\n\t\t// subclasses must implement\n\t},\n\n\n\t// Unrenders a visual indication of an event being resized.\n\tunrenderEventResize: function() {\n\t\t// subclasses must implement\n\t},\n\n\n\t/* Rendering Utils\n\t------------------------------------------------------------------------------------------------------------------*/\n\n\n\t// Compute the text that should be displayed on an event's element.\n\t// `range` can be the Event object itself, or something range-like, with at least a `start`.\n\t// If event times are disabled, or the event has no time, will return a blank string.\n\t// If not specified, formatStr will default to the eventTimeFormat setting,\n\t// and displayEnd will default to the displayEventEnd setting.\n\tgetEventTimeText: function(range, formatStr, displayEnd) {\n\n\t\tif (formatStr == null) {\n\t\t\tformatStr = this.eventTimeFormat;\n\t\t}\n\n\t\tif (displayEnd == null) {\n\t\t\tdisplayEnd = this.displayEventEnd;\n\t\t}\n\n\t\tif (this.displayEventTime && range.start.hasTime()) {\n\t\t\tif (displayEnd && range.end) {\n\t\t\t\treturn this.view.formatRange(range, formatStr);\n\t\t\t}\n\t\t\telse {\n\t\t\t\treturn range.start.format(formatStr);\n\t\t\t}\n\t\t}\n\n\t\treturn '';\n\t},\n\n\n\t// Generic utility for generating the HTML classNames for an event segment's element\n\tgetSegClasses: function(seg, isDraggable, isResizable) {\n\t\tvar event = seg.event;\n\t\tvar classes = [\n\t\t\t'fc-event',\n\t\t\tseg.isStart ? 'fc-start' : 'fc-not-start',\n\t\t\tseg.isEnd ? 'fc-end' : 'fc-not-end'\n\t\t].concat(\n\t\t\tevent.className,\n\t\t\tevent.source ? event.source.className : []\n\t\t);\n\n\t\tif (isDraggable) {\n\t\t\tclasses.push('fc-draggable');\n\t\t}\n\t\tif (isResizable) {\n\t\t\tclasses.push('fc-resizable');\n\t\t}\n\n\t\treturn classes;\n\t},\n\n\n\t// Utility for generating event skin-related CSS properties\n\tgetEventSkinCss: function(event) {\n\t\tvar view = this.view;\n\t\tvar source = event.source || {};\n\t\tvar eventColor = event.color;\n\t\tvar sourceColor = source.color;\n\t\tvar optionColor = view.opt('eventColor');\n\n\t\treturn {\n\t\t\t'background-color':\n\t\t\t\tevent.backgroundColor ||\n\t\t\t\teventColor ||\n\t\t\t\tsource.backgroundColor ||\n\t\t\t\tsourceColor ||\n\t\t\t\tview.opt('eventBackgroundColor') ||\n\t\t\t\toptionColor,\n\t\t\t'border-color':\n\t\t\t\tevent.borderColor ||\n\t\t\t\teventColor ||\n\t\t\t\tsource.borderColor ||\n\t\t\t\tsourceColor ||\n\t\t\t\tview.opt('eventBorderColor') ||\n\t\t\t\toptionColor,\n\t\t\tcolor:\n\t\t\t\tevent.textColor ||\n\t\t\t\tsource.textColor ||\n\t\t\t\tview.opt('eventTextColor')\n\t\t};\n\t},\n\n\n\t/* Converting events -> ranges -> segs\n\t------------------------------------------------------------------------------------------------------------------*/\n\n\n\t// Converts an array of event objects into an array of event segment objects.\n\t// A custom `rangeToSegsFunc` may be given for arbitrarily slicing up events.\n\t// Doesn't guarantee an order for the resulting array.\n\teventsToSegs: function(events, rangeToSegsFunc) {\n\t\tvar eventRanges = this.eventsToRanges(events);\n\t\tvar segs = [];\n\t\tvar i;\n\n\t\tfor (i = 0; i < eventRanges.length; i++) {\n\t\t\tsegs.push.apply(\n\t\t\t\tsegs,\n\t\t\t\tthis.eventRangeToSegs(eventRanges[i], rangeToSegsFunc)\n\t\t\t);\n\t\t}\n\n\t\treturn segs;\n\t},\n\n\n\t// Converts an array of events into an array of \"range\" objects.\n\t// A \"range\" object is a plain object with start/end properties denoting the time it covers. Also an event property.\n\t// For \"normal\" events, this will be identical to the event's start/end, but for \"inverse-background\" events,\n\t// will create an array of ranges that span the time *not* covered by the given event.\n\t// Doesn't guarantee an order for the resulting array.\n\teventsToRanges: function(events) {\n\t\tvar _this = this;\n\t\tvar eventsById = groupEventsById(events);\n\t\tvar ranges = [];\n\n\t\t// group by ID so that related inverse-background events can be rendered together\n\t\t$.each(eventsById, function(id, eventGroup) {\n\t\t\tif (eventGroup.length) {\n\t\t\t\tranges.push.apply(\n\t\t\t\t\tranges,\n\t\t\t\t\tisInverseBgEvent(eventGroup[0]) ?\n\t\t\t\t\t\t_this.eventsToInverseRanges(eventGroup) :\n\t\t\t\t\t\t_this.eventsToNormalRanges(eventGroup)\n\t\t\t\t);\n\t\t\t}\n\t\t});\n\n\t\treturn ranges;\n\t},\n\n\n\t// Converts an array of \"normal\" events (not inverted rendering) into a parallel array of ranges\n\teventsToNormalRanges: function(events) {\n\t\tvar calendar = this.view.calendar;\n\t\tvar ranges = [];\n\t\tvar i, event;\n\t\tvar eventStart, eventEnd;\n\n\t\tfor (i = 0; i < events.length; i++) {\n\t\t\tevent = events[i];\n\n\t\t\t// make copies and normalize by stripping timezone\n\t\t\teventStart = event.start.clone().stripZone();\n\t\t\teventEnd = calendar.getEventEnd(event).stripZone();\n\n\t\t\tranges.push({\n\t\t\t\tevent: event,\n\t\t\t\tstart: eventStart,\n\t\t\t\tend: eventEnd,\n\t\t\t\teventStartMS: +eventStart,\n\t\t\t\teventDurationMS: eventEnd - eventStart\n\t\t\t});\n\t\t}\n\n\t\treturn ranges;\n\t},\n\n\n\t// Converts an array of events, with inverse-background rendering, into an array of range objects.\n\t// The range objects will cover all the time NOT covered by the events.\n\teventsToInverseRanges: function(events) {\n\t\tvar view = this.view;\n\t\tvar viewStart = view.start.clone().stripZone(); // normalize timezone\n\t\tvar viewEnd = view.end.clone().stripZone(); // normalize timezone\n\t\tvar normalRanges = this.eventsToNormalRanges(events); // will give us normalized dates we can use w/o copies\n\t\tvar inverseRanges = [];\n\t\tvar event0 = events[0]; // assign this to each range's `.event`\n\t\tvar start = viewStart; // the end of the previous range. the start of the new range\n\t\tvar i, normalRange;\n\n\t\t// ranges need to be in order. required for our date-walking algorithm\n\t\tnormalRanges.sort(compareNormalRanges);\n\n\t\tfor (i = 0; i < normalRanges.length; i++) {\n\t\t\tnormalRange = normalRanges[i];\n\n\t\t\t// add the span of time before the event (if there is any)\n\t\t\tif (normalRange.start > start) { // compare millisecond time (skip any ambig logic)\n\t\t\t\tinverseRanges.push({\n\t\t\t\t\tevent: event0,\n\t\t\t\t\tstart: start,\n\t\t\t\t\tend: normalRange.start\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tstart = normalRange.end;\n\t\t}\n\n\t\t// add the span of time after the last event (if there is any)\n\t\tif (start < viewEnd) { // compare millisecond time (skip any ambig logic)\n\t\t\tinverseRanges.push({\n\t\t\t\tevent: event0,\n\t\t\t\tstart: start,\n\t\t\t\tend: viewEnd\n\t\t\t});\n\t\t}\n\n\t\treturn inverseRanges;\n\t},\n\n\n\t// Slices the given event range into one or more segment objects.\n\t// A `rangeToSegsFunc` custom slicing function can be given.\n\teventRangeToSegs: function(eventRange, rangeToSegsFunc) {\n\t\tvar segs;\n\t\tvar i, seg;\n\n\t\teventRange = this.view.calendar.ensureVisibleEventRange(eventRange);\n\n\t\tif (rangeToSegsFunc) {\n\t\t\tsegs = rangeToSegsFunc(eventRange);\n\t\t}\n\t\telse {\n\t\t\tsegs = this.rangeToSegs(eventRange); // defined by the subclass\n\t\t}\n\n\t\tfor (i = 0; i < segs.length; i++) {\n\t\t\tseg = segs[i];\n\t\t\tseg.event = eventRange.event;\n\t\t\tseg.eventStartMS = eventRange.eventStartMS;\n\t\t\tseg.eventDurationMS = eventRange.eventDurationMS;\n\t\t}\n\n\t\treturn segs;\n\t},\n\n\n\tsortSegs: function(segs) {\n\t\tsegs.sort(proxy(this, 'compareSegs'));\n\t},\n\n\n\t// A cmp function for determining which segments should take visual priority\n\t// DOES NOT WORK ON INVERTED BACKGROUND EVENTS because they have no eventStartMS/eventDurationMS\n\tcompareSegs: function(seg1, seg2) {\n\t\treturn seg1.eventStartMS - seg2.eventStartMS || // earlier events go first\n\t\t\tseg2.eventDurationMS - seg1.eventDurationMS || // tie? longer events go first\n\t\t\tseg2.event.allDay - seg1.event.allDay || // tie? put all-day events first (booleans cast to 0/1)\n\t\t\tcompareByFieldSpecs(seg1.event, seg2.event, this.view.eventOrderSpecs);\n\t}\n\n});\n\n\n/* Utilities\n----------------------------------------------------------------------------------------------------------------------*/\n\n\nfunction isBgEvent(event) { // returns true if background OR inverse-background\n\tvar rendering = getEventRendering(event);\n\treturn rendering === 'background' || rendering === 'inverse-background';\n}\n\n\nfunction isInverseBgEvent(event) {\n\treturn getEventRendering(event) === 'inverse-background';\n}\n\n\nfunction getEventRendering(event) {\n\treturn firstDefined((event.source || {}).rendering, event.rendering);\n}\n\n\nfunction groupEventsById(events) {\n\tvar eventsById = {};\n\tvar i, event;\n\n\tfor (i = 0; i < events.length; i++) {\n\t\tevent = events[i];\n\t\t(eventsById[event._id] || (eventsById[event._id] = [])).push(event);\n\t}\n\n\treturn eventsById;\n}\n\n\n// A cmp function for determining which non-inverted \"ranges\" (see above) happen earlier\nfunction compareNormalRanges(range1, range2) {\n\treturn range1.eventStartMS - range2.eventStartMS; // earlier ranges go first\n}\n\n\n/* External-Dragging-Element Data\n----------------------------------------------------------------------------------------------------------------------*/\n\n// Require all HTML5 data-* attributes used by FullCalendar to have this prefix.\n// A value of '' will query attributes like data-event. A value of 'fc' will query attributes like data-fc-event.\nfc.dataAttrPrefix = '';\n\n// Given a jQuery element that might represent a dragged FullCalendar event, returns an intermediate data structure\n// to be used for Event Object creation.\n// A defined `.eventProps`, even when empty, indicates that an event should be created.\nfunction getDraggedElMeta(el) {\n\tvar prefix = fc.dataAttrPrefix;\n\tvar eventProps; // properties for creating the event, not related to date/time\n\tvar startTime; // a Duration\n\tvar duration;\n\tvar stick;\n\n\tif (prefix) { prefix += '-'; }\n\teventProps = el.data(prefix + 'event') || null;\n\n\tif (eventProps) {\n\t\tif (typeof eventProps === 'object') {\n\t\t\teventProps = $.extend({}, eventProps); // make a copy\n\t\t}\n\t\telse { // something like 1 or true. still signal event creation\n\t\t\teventProps = {};\n\t\t}\n\n\t\t// pluck special-cased date/time properties\n\t\tstartTime = eventProps.start;\n\t\tif (startTime == null) { startTime = eventProps.time; } // accept 'time' as well\n\t\tduration = eventProps.duration;\n\t\tstick = eventProps.stick;\n\t\tdelete eventProps.start;\n\t\tdelete eventProps.time;\n\t\tdelete eventProps.duration;\n\t\tdelete eventProps.stick;\n\t}\n\n\t// fallback to standalone attribute values for each of the date/time properties\n\tif (startTime == null) { startTime = el.data(prefix + 'start'); }\n\tif (startTime == null) { startTime = el.data(prefix + 'time'); } // accept 'time' as well\n\tif (duration == null) { duration = el.data(prefix + 'duration'); }\n\tif (stick == null) { stick = el.data(prefix + 'stick'); }\n\n\t// massage into correct data types\n\tstartTime = startTime != null ? moment.duration(startTime) : null;\n\tduration = duration != null ? moment.duration(duration) : null;\n\tstick = Boolean(stick);\n\n\treturn { eventProps: eventProps, startTime: startTime, duration: duration, stick: stick };\n}\n\n\n;;\n\n/* A component that renders a grid of whole-days that runs horizontally. There can be multiple rows, one per week.\n----------------------------------------------------------------------------------------------------------------------*/\n\nvar DayGrid = Grid.extend({\n\n\tnumbersVisible: false, // should render a row for day/week numbers? set by outside view. TODO: make internal\n\tbottomCoordPadding: 0, // hack for extending the hit area for the last row of the coordinate grid\n\tbreakOnWeeks: null, // should create a new row for each week? set by outside view\n\n\tcellDates: null, // flat chronological array of each cell's dates\n\tdayToCellOffsets: null, // maps days offsets from grid's start date, to cell offsets\n\n\trowEls: null, // set of fake row elements\n\tdayEls: null, // set of whole-day elements comprising the row's background\n\thelperEls: null, // set of cell skeleton elements for rendering the mock event \"helper\"\n\n\n\tconstructor: function() {\n\t\tGrid.apply(this, arguments);\n\n\t\tthis.cellDuration = moment.duration(1, 'day'); // for Grid system\n\t},\n\n\n\t// Renders the rows and columns into the component's `this.el`, which should already be assigned.\n\t// isRigid determins whether the individual rows should ignore the contents and be a constant height.\n\t// Relies on the view's colCnt and rowCnt. In the future, this component should probably be self-sufficient.\n\trenderDates: function(isRigid) {\n\t\tvar view = this.view;\n\t\tvar rowCnt = this.rowCnt;\n\t\tvar colCnt = this.colCnt;\n\t\tvar cellCnt = rowCnt * colCnt;\n\t\tvar html = '';\n\t\tvar row;\n\t\tvar i, cell;\n\n\t\tfor (row = 0; row < rowCnt; row++) {\n\t\t\thtml += this.dayRowHtml(row, isRigid);\n\t\t}\n\t\tthis.el.html(html);\n\n\t\tthis.rowEls = this.el.find('.fc-row');\n\t\tthis.dayEls = this.el.find('.fc-day');\n\n\t\t// trigger dayRender with each cell's element\n\t\tfor (i = 0; i < cellCnt; i++) {\n\t\t\tcell = this.getCell(i);\n\t\t\tview.trigger('dayRender', null, cell.start, this.dayEls.eq(i));\n\t\t}\n\t},\n\n\n\tunrenderDates: function() {\n\t\tthis.removeSegPopover();\n\t},\n\n\n\trenderBusinessHours: function() {\n\t\tvar events = this.view.calendar.getBusinessHoursEvents(true); // wholeDay=true\n\t\tvar segs = this.eventsToSegs(events);\n\n\t\tthis.renderFill('businessHours', segs, 'bgevent');\n\t},\n\n\n\t// Generates the HTML for a single row. `row` is the row number.\n\tdayRowHtml: function(row, isRigid) {\n\t\tvar view = this.view;\n\t\tvar classes = [ 'fc-row', 'fc-week', view.widgetContentClass ];\n\n\t\tif (isRigid) {\n\t\t\tclasses.push('fc-rigid');\n\t\t}\n\n\t\treturn '' +\n\t\t\t'' +\n\t\t\t\t' ' +\n\t\t\t\t\t' ' +\n\t\t\t\t\t\tthis.rowHtml('day', row) + // leverages RowRenderer. calls dayCellHtml()\n\t\t\t\t\t' ' +\n\t\t\t\t' ' +\n\t\t\t\t' ' +\n\t\t\t\t\t' ' +\n\t\t\t\t\t\t(this.numbersVisible ?\n\t\t\t\t\t\t\t'' +\n\t\t\t\t\t\t\t\tthis.rowHtml('number', row) + // leverages RowRenderer. View will define render method\n\t\t\t\t\t\t\t'' :\n\t\t\t\t\t\t\t''\n\t\t\t\t\t\t\t) +\n\t\t\t\t\t' ' +\n\t\t\t\t' ' +\n\t\t\t' ';\n\t},\n\n\n\t// Renders the HTML for a whole-day cell. Will eventually end up in the day-row's background.\n\t// We go through a 'day' row type instead of just doing a 'bg' row type so that the View can do custom rendering\n\t// specifically for whole-day rows, whereas a 'bg' might also be used for other purposes (TimeGrid bg for example).\n\tdayCellHtml: function(cell) {\n\t\treturn this.bgCellHtml(cell);\n\t},\n\n\n\t/* Options\n\t------------------------------------------------------------------------------------------------------------------*/\n\n\n\t// Computes a default column header formatting string if `colFormat` is not explicitly defined\n\tcomputeColHeadFormat: function() {\n\t\tif (this.rowCnt > 1) { // more than one week row. day numbers will be in each cell\n\t\t\treturn 'ddd'; // \"Sat\"\n\t\t}\n\t\telse if (this.colCnt > 1) { // multiple days, so full single date string WON'T be in title text\n\t\t\treturn this.view.opt('dayOfMonthFormat'); // \"Sat 12/10\"\n\t\t}\n\t\telse { // single day, so full single date string will probably be in title text\n\t\t\treturn 'dddd'; // \"Saturday\"\n\t\t}\n\t},\n\n\n\t// Computes a default event time formatting string if `timeFormat` is not explicitly defined\n\tcomputeEventTimeFormat: function() {\n\t\treturn this.view.opt('extraSmallTimeFormat'); // like \"6p\" or \"6:30p\"\n\t},\n\n\n\t// Computes a default `displayEventEnd` value if one is not expliclty defined\n\tcomputeDisplayEventEnd: function() {\n\t\treturn this.colCnt == 1; // we'll likely have space if there's only one day\n\t},\n\n\n\t/* Cell System\n\t------------------------------------------------------------------------------------------------------------------*/\n\n\n\trangeUpdated: function() {\n\t\tvar cellDates;\n\t\tvar firstDay;\n\t\tvar rowCnt;\n\t\tvar colCnt;\n\n\t\tthis.updateCellDates(); // populates cellDates and dayToCellOffsets\n\t\tcellDates = this.cellDates;\n\n\t\tif (this.breakOnWeeks) {\n\t\t\t// count columns until the day-of-week repeats\n\t\t\tfirstDay = cellDates[0].day();\n\t\t\tfor (colCnt = 1; colCnt < cellDates.length; colCnt++) {\n\t\t\t\tif (cellDates[colCnt].day() == firstDay) {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\trowCnt = Math.ceil(cellDates.length / colCnt);\n\t\t}\n\t\telse {\n\t\t\trowCnt = 1;\n\t\t\tcolCnt = cellDates.length;\n\t\t}\n\n\t\tthis.rowCnt = rowCnt;\n\t\tthis.colCnt = colCnt;\n\t},\n\n\n\t// Populates cellDates and dayToCellOffsets\n\tupdateCellDates: function() {\n\t\tvar view = this.view;\n\t\tvar date = this.start.clone();\n\t\tvar dates = [];\n\t\tvar offset = -1;\n\t\tvar offsets = [];\n\n\t\twhile (date.isBefore(this.end)) { // loop each day from start to end\n\t\t\tif (view.isHiddenDay(date)) {\n\t\t\t\toffsets.push(offset + 0.5); // mark that it's between offsets\n\t\t\t}\n\t\t\telse {\n\t\t\t\toffset++;\n\t\t\t\toffsets.push(offset);\n\t\t\t\tdates.push(date.clone());\n\t\t\t}\n\t\t\tdate.add(1, 'days');\n\t\t}\n\n\t\tthis.cellDates = dates;\n\t\tthis.dayToCellOffsets = offsets;\n\t},\n\n\n\t// Given a cell object, generates its start date. Returns a reference-free copy.\n\tcomputeCellDate: function(cell) {\n\t\tvar colCnt = this.colCnt;\n\t\tvar index = cell.row * colCnt + (this.isRTL ? colCnt - cell.col - 1 : cell.col);\n\n\t\treturn this.cellDates[index].clone();\n\t},\n\n\n\t// Retrieves the element representing the given row\n\tgetRowEl: function(row) {\n\t\treturn this.rowEls.eq(row);\n\t},\n\n\n\t// Retrieves the element representing the given column\n\tgetColEl: function(col) {\n\t\treturn this.dayEls.eq(col);\n\t},\n\n\n\t// Gets the whole-day element associated with the cell\n\tgetCellDayEl: function(cell) {\n\t\treturn this.dayEls.eq(cell.row * this.colCnt + cell.col);\n\t},\n\n\n\t// Overrides Grid's method for when row coordinates are computed\n\tcomputeRowCoords: function() {\n\t\tvar rowCoords = Grid.prototype.computeRowCoords.call(this); // call the super-method\n\n\t\t// hack for extending last row (used by AgendaView)\n\t\trowCoords[rowCoords.length - 1].bottom += this.bottomCoordPadding;\n\n\t\treturn rowCoords;\n\t},\n\n\n\t/* Dates\n\t------------------------------------------------------------------------------------------------------------------*/\n\n\n\t// Slices up a date range by row into an array of segments\n\trangeToSegs: function(range) {\n\t\tvar isRTL = this.isRTL;\n\t\tvar rowCnt = this.rowCnt;\n\t\tvar colCnt = this.colCnt;\n\t\tvar segs = [];\n\t\tvar first, last; // inclusive cell-offset range for given range\n\t\tvar row;\n\t\tvar rowFirst, rowLast; // inclusive cell-offset range for current row\n\t\tvar isStart, isEnd;\n\t\tvar segFirst, segLast; // inclusive cell-offset range for segment\n\t\tvar seg;\n\n\t\trange = this.view.computeDayRange(range); // make whole-day range, considering nextDayThreshold\n\t\tfirst = this.dateToCellOffset(range.start);\n\t\tlast = this.dateToCellOffset(range.end.subtract(1, 'days')); // offset of inclusive end date\n\n\t\tfor (row = 0; row < rowCnt; row++) {\n\t\t\trowFirst = row * colCnt;\n\t\t\trowLast = rowFirst + colCnt - 1;\n\n\t\t\t// intersect segment's offset range with the row's\n\t\t\tsegFirst = Math.max(rowFirst, first);\n\t\t\tsegLast = Math.min(rowLast, last);\n\n\t\t\t// deal with in-between indices\n\t\t\tsegFirst = Math.ceil(segFirst); // in-between starts round to next cell\n\t\t\tsegLast = Math.floor(segLast); // in-between ends round to prev cell\n\n\t\t\tif (segFirst <= segLast) { // was there any intersection with the current row?\n\n\t\t\t\t// must be matching integers to be the segment's start/end\n\t\t\t\tisStart = segFirst === first;\n\t\t\t\tisEnd = segLast === last;\n\n\t\t\t\t// translate offsets to be relative to start-of-row\n\t\t\t\tsegFirst -= rowFirst;\n\t\t\t\tsegLast -= rowFirst;\n\n\t\t\t\tseg = { row: row, isStart: isStart, isEnd: isEnd };\n\t\t\t\tif (isRTL) {\n\t\t\t\t\tseg.leftCol = colCnt - segLast - 1;\n\t\t\t\t\tseg.rightCol = colCnt - segFirst - 1;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tseg.leftCol = segFirst;\n\t\t\t\t\tseg.rightCol = segLast;\n\t\t\t\t}\n\t\t\t\tsegs.push(seg);\n\t\t\t}\n\t\t}\n\n\t\treturn segs;\n\t},\n\n\n\t// Given a date, returns its chronolocial cell-offset from the first cell of the grid.\n\t// If the date lies between cells (because of hiddenDays), returns a floating-point value between offsets.\n\t// If before the first offset, returns a negative number.\n\t// If after the last offset, returns an offset past the last cell offset.\n\t// Only works for *start* dates of cells. Will not work for exclusive end dates for cells.\n\tdateToCellOffset: function(date) {\n\t\tvar offsets = this.dayToCellOffsets;\n\t\tvar day = date.diff(this.start, 'days');\n\n\t\tif (day < 0) {\n\t\t\treturn offsets[0] - 1;\n\t\t}\n\t\telse if (day >= offsets.length) {\n\t\t\treturn offsets[offsets.length - 1] + 1;\n\t\t}\n\t\telse {\n\t\t\treturn offsets[day];\n\t\t}\n\t},\n\n\n\t/* Event Drag Visualization\n\t------------------------------------------------------------------------------------------------------------------*/\n\t// TODO: move to DayGrid.event, similar to what we did with Grid's drag methods\n\n\n\t// Renders a visual indication of an event or external element being dragged.\n\t// The dropLocation's end can be null. seg can be null. See Grid::renderDrag for more info.\n\trenderDrag: function(dropLocation, seg) {\n\n\t\t// always render a highlight underneath\n\t\tthis.renderHighlight(this.eventRangeToSegs(dropLocation));\n\n\t\t// if a segment from the same calendar but another component is being dragged, render a helper event\n\t\tif (seg && !seg.el.closest(this.el).length) {\n\n\t\t\tthis.renderRangeHelper(dropLocation, seg);\n\t\t\tthis.applyDragOpacity(this.helperEls);\n\n\t\t\treturn true; // a helper has been rendered\n\t\t}\n\t},\n\n\n\t// Unrenders any visual indication of a hovering event\n\tunrenderDrag: function() {\n\t\tthis.unrenderHighlight();\n\t\tthis.unrenderHelper();\n\t},\n\n\n\t/* Event Resize Visualization\n\t------------------------------------------------------------------------------------------------------------------*/\n\n\n\t// Renders a visual indication of an event being resized\n\trenderEventResize: function(range, seg) {\n\t\tthis.renderHighlight(this.eventRangeToSegs(range));\n\t\tthis.renderRangeHelper(range, seg);\n\t},\n\n\n\t// Unrenders a visual indication of an event being resized\n\tunrenderEventResize: function() {\n\t\tthis.unrenderHighlight();\n\t\tthis.unrenderHelper();\n\t},\n\n\n\t/* Event Helper\n\t------------------------------------------------------------------------------------------------------------------*/\n\n\n\t// Renders a mock \"helper\" event. `sourceSeg` is the associated internal segment object. It can be null.\n\trenderHelper: function(event, sourceSeg) {\n\t\tvar helperNodes = [];\n\t\tvar segs = this.eventsToSegs([ event ]);\n\t\tvar rowStructs;\n\n\t\tsegs = this.renderFgSegEls(segs); // assigns each seg's el and returns a subset of segs that were rendered\n\t\trowStructs = this.renderSegRows(segs);\n\n\t\t// inject each new event skeleton into each associated row\n\t\tthis.rowEls.each(function(row, rowNode) {\n\t\t\tvar rowEl = $(rowNode); // the .fc-row\n\t\t\tvar skeletonEl = $(''); // will be absolutely positioned\n\t\t\tvar skeletonTop;\n\n\t\t\t// If there is an original segment, match the top position. Otherwise, put it at the row's top level\n\t\t\tif (sourceSeg && sourceSeg.row === row) {\n\t\t\t\tskeletonTop = sourceSeg.el.position().top;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tskeletonTop = rowEl.find('.fc-content-skeleton tbody').position().top;\n\t\t\t}\n\n\t\t\tskeletonEl.css('top', skeletonTop)\n\t\t\t\t.find('table')\n\t\t\t\t\t.append(rowStructs[row].tbodyEl);\n\n\t\t\trowEl.append(skeletonEl);\n\t\t\thelperNodes.push(skeletonEl[0]);\n\t\t});\n\n\t\tthis.helperEls = $(helperNodes); // array -> jQuery set\n\t},\n\n\n\t// Unrenders any visual indication of a mock helper event\n\tunrenderHelper: function() {\n\t\tif (this.helperEls) {\n\t\t\tthis.helperEls.remove();\n\t\t\tthis.helperEls = null;\n\t\t}\n\t},\n\n\n\t/* Fill System (highlight, background events, business hours)\n\t------------------------------------------------------------------------------------------------------------------*/\n\n\n\tfillSegTag: 'td', // override the default tag name\n\n\n\t// Renders a set of rectangles over the given segments of days.\n\t// Only returns segments that successfully rendered.\n\trenderFill: function(type, segs, className) {\n\t\tvar nodes = [];\n\t\tvar i, seg;\n\t\tvar skeletonEl;\n\n\t\tsegs = this.renderFillSegEls(type, segs); // assignes `.el` to each seg. returns successfully rendered segs\n\n\t\tfor (i = 0; i < segs.length; i++) {\n\t\t\tseg = segs[i];\n\t\t\tskeletonEl = this.renderFillRow(type, seg, className);\n\t\t\tthis.rowEls.eq(seg.row).append(skeletonEl);\n\t\t\tnodes.push(skeletonEl[0]);\n\t\t}\n\n\t\tthis.elsByFill[type] = $(nodes);\n\n\t\treturn segs;\n\t},\n\n\n\t// Generates the HTML needed for one row of a fill. Requires the seg's el to be rendered.\n\trenderFillRow: function(type, seg, className) {\n\t\tvar colCnt = this.colCnt;\n\t\tvar startCol = seg.leftCol;\n\t\tvar endCol = seg.rightCol + 1;\n\t\tvar skeletonEl;\n\t\tvar trEl;\n\n\t\tclassName = className || type.toLowerCase();\n\n\t\tskeletonEl = $(\n\t\t\t'' +\n\t\t\t\t' ' +\n\t\t\t' '\n\t\t);\n\t\ttrEl = skeletonEl.find('tr');\n\n\t\tif (startCol > 0) {\n\t\t\ttrEl.append(' | ');\n\t\t}\n\n\t\ttrEl.append(\n\t\t\tseg.el.attr('colspan', endCol - startCol)\n\t\t);\n\n\t\tif (endCol < colCnt) {\n\t\t\ttrEl.append(' | ');\n\t\t}\n\n\t\tthis.bookendCells(trEl, type);\n\n\t\treturn skeletonEl;\n\t}\n\n});\n\n;;\n\n/* Event-rendering methods for the DayGrid class\n----------------------------------------------------------------------------------------------------------------------*/\n\nDayGrid.mixin({\n\n\trowStructs: null, // an array of objects, each holding information about a row's foreground event-rendering\n\n\n\t// Unrenders all events currently rendered on the grid\n\tunrenderEvents: function() {\n\t\tthis.removeSegPopover(); // removes the \"more..\" events popover\n\t\tGrid.prototype.unrenderEvents.apply(this, arguments); // calls the super-method\n\t},\n\n\n\t// Retrieves all rendered segment objects currently rendered on the grid\n\tgetEventSegs: function() {\n\t\treturn Grid.prototype.getEventSegs.call(this) // get the segments from the super-method\n\t\t\t.concat(this.popoverSegs || []); // append the segments from the \"more...\" popover\n\t},\n\n\n\t// Renders the given background event segments onto the grid\n\trenderBgSegs: function(segs) {\n\n\t\t// don't render timed background events\n\t\tvar allDaySegs = $.grep(segs, function(seg) {\n\t\t\treturn seg.event.allDay;\n\t\t});\n\n\t\treturn Grid.prototype.renderBgSegs.call(this, allDaySegs); // call the super-method\n\t},\n\n\n\t// Renders the given foreground event segments onto the grid\n\trenderFgSegs: function(segs) {\n\t\tvar rowStructs;\n\n\t\t// render an `.el` on each seg\n\t\t// returns a subset of the segs. segs that were actually rendered\n\t\tsegs = this.renderFgSegEls(segs);\n\n\t\trowStructs = this.rowStructs = this.renderSegRows(segs);\n\n\t\t// append to each row's content skeleton\n\t\tthis.rowEls.each(function(i, rowNode) {\n\t\t\t$(rowNode).find('.fc-content-skeleton > table').append(\n\t\t\t\trowStructs[i].tbodyEl\n\t\t\t);\n\t\t});\n\n\t\treturn segs; // return only the segs that were actually rendered\n\t},\n\n\n\t// Unrenders all currently rendered foreground event segments\n\tunrenderFgSegs: function() {\n\t\tvar rowStructs = this.rowStructs || [];\n\t\tvar rowStruct;\n\n\t\twhile ((rowStruct = rowStructs.pop())) {\n\t\t\trowStruct.tbodyEl.remove();\n\t\t}\n\n\t\tthis.rowStructs = null;\n\t},\n\n\n\t// Uses the given events array to generate elements that should be appended to each row's content skeleton.\n\t// Returns an array of rowStruct objects (see the bottom of `renderSegRow`).\n\t// PRECONDITION: each segment shoud already have a rendered and assigned `.el`\n\trenderSegRows: function(segs) {\n\t\tvar rowStructs = [];\n\t\tvar segRows;\n\t\tvar row;\n\n\t\tsegRows = this.groupSegRows(segs); // group into nested arrays\n\n\t\t// iterate each row of segment groupings\n\t\tfor (row = 0; row < segRows.length; row++) {\n\t\t\trowStructs.push(\n\t\t\t\tthis.renderSegRow(row, segRows[row])\n\t\t\t);\n\t\t}\n\n\t\treturn rowStructs;\n\t},\n\n\n\t// Builds the HTML to be used for the default element for an individual segment\n\tfgSegHtml: function(seg, disableResizing) {\n\t\tvar view = this.view;\n\t\tvar event = seg.event;\n\t\tvar isDraggable = view.isEventDraggable(event);\n\t\tvar isResizableFromStart = !disableResizing && event.allDay &&\n\t\t\tseg.isStart && view.isEventResizableFromStart(event);\n\t\tvar isResizableFromEnd = !disableResizing && event.allDay &&\n\t\t\tseg.isEnd && view.isEventResizableFromEnd(event);\n\t\tvar classes = this.getSegClasses(seg, isDraggable, isResizableFromStart || isResizableFromEnd);\n\t\tvar skinCss = cssToStr(this.getEventSkinCss(event));\n\t\tvar timeHtml = '';\n\t\tvar timeText;\n\t\tvar titleHtml;\n\n\t\tclasses.unshift('fc-day-grid-event', 'fc-h-event');\n\n\t\t// Only display a timed events time if it is the starting segment\n\t\tif (seg.isStart) {\n\t\t\ttimeText = this.getEventTimeText(event);\n\t\t\tif (timeText) {\n\t\t\t\ttimeHtml = '' + htmlEscape(timeText) + '';\n\t\t\t}\n\t\t}\n\n\t\ttitleHtml =\n\t\t\t'' +\n\t\t\t\t(htmlEscape(event.title || '') || ' ') + // we always want one line of height\n\t\t\t'';\n\t\t\n\t\treturn '' +\n\t\t\t\t'' +\n\t\t\t\t\t(this.isRTL ?\n\t\t\t\t\t\ttitleHtml + ' ' + timeHtml : // put a natural space in between\n\t\t\t\t\t\ttimeHtml + ' ' + titleHtml //\n\t\t\t\t\t\t) +\n\t\t\t\t' ' +\n\t\t\t\t(isResizableFromStart ?\n\t\t\t\t\t'' :\n\t\t\t\t\t''\n\t\t\t\t\t) +\n\t\t\t\t(isResizableFromEnd ?\n\t\t\t\t\t'' :\n\t\t\t\t\t''\n\t\t\t\t\t) +\n\t\t\t'';\n\t},\n\n\n\t// Given a row # and an array of segments all in the same row, render a element, a skeleton that contains\n\t// the segments. Returns object with a bunch of internal data about how the render was calculated.\n\t// NOTE: modifies rowSegs\n\trenderSegRow: function(row, rowSegs) {\n\t\tvar colCnt = this.colCnt;\n\t\tvar segLevels = this.buildSegLevels(rowSegs); // group into sub-arrays of levels\n\t\tvar levelCnt = Math.max(1, segLevels.length); // ensure at least one level\n\t\tvar tbody = $('');\n\t\tvar segMatrix = []; // lookup for which segments are rendered into which level+col cells\n\t\tvar cellMatrix = []; // lookup for all elements of the level+col matrix\n\t\tvar loneCellMatrix = []; // lookup for | elements that only take up a single column\n\t\tvar i, levelSegs;\n\t\tvar col;\n\t\tvar tr;\n\t\tvar j, seg;\n\t\tvar td;\n\n\t\t// populates empty cells from the current column (`col`) to `endCol`\n\t\tfunction emptyCellsUntil(endCol) {\n\t\t\twhile (col < endCol) {\n\t\t\t\t// try to grab a cell from the level above and extend its rowspan. otherwise, create a fresh cell\n\t\t\t\ttd = (loneCellMatrix[i - 1] || [])[col];\n\t\t\t\tif (td) {\n\t\t\t\t\ttd.attr(\n\t\t\t\t\t\t'rowspan',\n\t\t\t\t\t\tparseInt(td.attr('rowspan') || 1, 10) + 1\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\ttd = $(' | | ');\n\t\t\t\t\ttr.append(td);\n\t\t\t\t}\n\t\t\t\tcellMatrix[i][col] = td;\n\t\t\t\tloneCellMatrix[i][col] = td;\n\t\t\t\tcol++;\n\t\t\t}\n\t\t}\n\n\t\tfor (i = 0; i < levelCnt; i++) { // iterate through all levels\n\t\t\tlevelSegs = segLevels[i];\n\t\t\tcol = 0;\n\t\t\ttr = $(' |
');\n\n\t\t\tsegMatrix.push([]);\n\t\t\tcellMatrix.push([]);\n\t\t\tloneCellMatrix.push([]);\n\n\t\t\t// levelCnt might be 1 even though there are no actual levels. protect against this.\n\t\t\t// this single empty row is useful for styling.\n\t\t\tif (levelSegs) {\n\t\t\t\tfor (j = 0; j < levelSegs.length; j++) { // iterate through segments in level\n\t\t\t\t\tseg = levelSegs[j];\n\n\t\t\t\t\temptyCellsUntil(seg.leftCol);\n\n\t\t\t\t\t// create a container that occupies or more columns. append the event element.\n\t\t\t\t\ttd = $(' ').append(seg.el);\n\t\t\t\t\tif (seg.leftCol != seg.rightCol) {\n\t\t\t\t\t\ttd.attr('colspan', seg.rightCol - seg.leftCol + 1);\n\t\t\t\t\t}\n\t\t\t\t\telse { // a single-column segment\n\t\t\t\t\t\tloneCellMatrix[i][col] = td;\n\t\t\t\t\t}\n\n\t\t\t\t\twhile (col <= seg.rightCol) {\n\t\t\t\t\t\tcellMatrix[i][col] = td;\n\t\t\t\t\t\tsegMatrix[i][col] = seg;\n\t\t\t\t\t\tcol++;\n\t\t\t\t\t}\n\n\t\t\t\t\ttr.append(td);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\temptyCellsUntil(colCnt); // finish off the row\n\t\t\tthis.bookendCells(tr, 'eventSkeleton');\n\t\t\ttbody.append(tr);\n\t\t}\n\n\t\treturn { // a \"rowStruct\"\n\t\t\trow: row, // the row number\n\t\t\ttbodyEl: tbody,\n\t\t\tcellMatrix: cellMatrix,\n\t\t\tsegMatrix: segMatrix,\n\t\t\tsegLevels: segLevels,\n\t\t\tsegs: rowSegs\n\t\t};\n\t},\n\n\n\t// Stacks a flat array of segments, which are all assumed to be in the same row, into subarrays of vertical levels.\n\t// NOTE: modifies segs\n\tbuildSegLevels: function(segs) {\n\t\tvar levels = [];\n\t\tvar i, seg;\n\t\tvar j;\n\n\t\t// Give preference to elements with certain criteria, so they have\n\t\t// a chance to be closer to the top.\n\t\tthis.sortSegs(segs);\n\t\t\n\t\tfor (i = 0; i < segs.length; i++) {\n\t\t\tseg = segs[i];\n\n\t\t\t// loop through levels, starting with the topmost, until the segment doesn't collide with other segments\n\t\t\tfor (j = 0; j < levels.length; j++) {\n\t\t\t\tif (!isDaySegCollision(seg, levels[j])) {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\t// `j` now holds the desired subrow index\n\t\t\tseg.level = j;\n\n\t\t\t// create new level array if needed and append segment\n\t\t\t(levels[j] || (levels[j] = [])).push(seg);\n\t\t}\n\n\t\t// order segments left-to-right. very important if calendar is RTL\n\t\tfor (j = 0; j < levels.length; j++) {\n\t\t\tlevels[j].sort(compareDaySegCols);\n\t\t}\n\n\t\treturn levels;\n\t},\n\n\n\t// Given a flat array of segments, return an array of sub-arrays, grouped by each segment's row\n\tgroupSegRows: function(segs) {\n\t\tvar segRows = [];\n\t\tvar i;\n\n\t\tfor (i = 0; i < this.rowCnt; i++) {\n\t\t\tsegRows.push([]);\n\t\t}\n\n\t\tfor (i = 0; i < segs.length; i++) {\n\t\t\tsegRows[segs[i].row].push(segs[i]);\n\t\t}\n\n\t\treturn segRows;\n\t}\n\n});\n\n\n// Computes whether two segments' columns collide. They are assumed to be in the same row.\nfunction isDaySegCollision(seg, otherSegs) {\n\tvar i, otherSeg;\n\n\tfor (i = 0; i < otherSegs.length; i++) {\n\t\totherSeg = otherSegs[i];\n\n\t\tif (\n\t\t\totherSeg.leftCol <= seg.rightCol &&\n\t\t\totherSeg.rightCol >= seg.leftCol\n\t\t) {\n\t\t\treturn true;\n\t\t}\n\t}\n\n\treturn false;\n}\n\n\n// A cmp function for determining the leftmost event\nfunction compareDaySegCols(a, b) {\n\treturn a.leftCol - b.leftCol;\n}\n\n;;\n\n/* Methods relate to limiting the number events for a given day on a DayGrid\n----------------------------------------------------------------------------------------------------------------------*/\n// NOTE: all the segs being passed around in here are foreground segs\n\nDayGrid.mixin({\n\n\tsegPopover: null, // the Popover that holds events that can't fit in a cell. null when not visible\n\tpopoverSegs: null, // an array of segment objects that the segPopover holds. null when not visible\n\n\n\tremoveSegPopover: function() {\n\t\tif (this.segPopover) {\n\t\t\tthis.segPopover.hide(); // in handler, will call segPopover's removeElement\n\t\t}\n\t},\n\n\n\t// Limits the number of \"levels\" (vertically stacking layers of events) for each row of the grid.\n\t// `levelLimit` can be false (don't limit), a number, or true (should be computed).\n\tlimitRows: function(levelLimit) {\n\t\tvar rowStructs = this.rowStructs || [];\n\t\tvar row; // row #\n\t\tvar rowLevelLimit;\n\n\t\tfor (row = 0; row < rowStructs.length; row++) {\n\t\t\tthis.unlimitRow(row);\n\n\t\t\tif (!levelLimit) {\n\t\t\t\trowLevelLimit = false;\n\t\t\t}\n\t\t\telse if (typeof levelLimit === 'number') {\n\t\t\t\trowLevelLimit = levelLimit;\n\t\t\t}\n\t\t\telse {\n\t\t\t\trowLevelLimit = this.computeRowLevelLimit(row);\n\t\t\t}\n\n\t\t\tif (rowLevelLimit !== false) {\n\t\t\t\tthis.limitRow(row, rowLevelLimit);\n\t\t\t}\n\t\t}\n\t},\n\n\n\t// Computes the number of levels a row will accomodate without going outside its bounds.\n\t// Assumes the row is \"rigid\" (maintains a constant height regardless of what is inside).\n\t// `row` is the row number.\n\tcomputeRowLevelLimit: function(row) {\n\t\tvar rowEl = this.rowEls.eq(row); // the containing \"fake\" row div\n\t\tvar rowHeight = rowEl.height(); // TODO: cache somehow?\n\t\tvar trEls = this.rowStructs[row].tbodyEl.children();\n\t\tvar i, trEl;\n\t\tvar trHeight;\n\n\t\tfunction iterInnerHeights(i, childNode) {\n\t\t\ttrHeight = Math.max(trHeight, $(childNode).outerHeight());\n\t\t}\n\n\t\t// Reveal one level | at a time and stop when we find one out of bounds\n\t\tfor (i = 0; i < trEls.length; i++) {\n\t\t\ttrEl = trEls.eq(i).removeClass('fc-limited'); // reset to original state (reveal)\n\n\t\t\t// with rowspans>1 and IE8, trEl.outerHeight() would return the height of the largest cell,\n\t\t\t// so instead, find the tallest inner content element.\n\t\t\ttrHeight = 0;\n\t\t\ttrEl.find('> td > :first-child').each(iterInnerHeights);\n\n\t\t\tif (trEl.position().top + trHeight > rowHeight) {\n\t\t\t\treturn i;\n\t\t\t}\n\t\t}\n\n\t\treturn false; // should not limit at all\n\t},\n\n\n\t// Limits the given grid row to the maximum number of levels and injects \"more\" links if necessary.\n\t// `row` is the row number.\n\t// `levelLimit` is a number for the maximum (inclusive) number of levels allowed.\n\tlimitRow: function(row, levelLimit) {\n\t\tvar _this = this;\n\t\tvar rowStruct = this.rowStructs[row];\n\t\tvar moreNodes = []; // array of \"more\" links and DOM nodes\n\t\tvar col = 0; // col #, left-to-right (not chronologically)\n\t\tvar cell;\n\t\tvar levelSegs; // array of segment objects in the last allowable level, ordered left-to-right\n\t\tvar cellMatrix; // a matrix (by level, then column) of all | jQuery elements in the row\n\t\tvar limitedNodes; // array of temporarily hidden level | and segment DOM nodes\n\t\tvar i, seg;\n\t\tvar segsBelow; // array of segment objects below `seg` in the current `col`\n\t\tvar totalSegsBelow; // total number of segments below `seg` in any of the columns `seg` occupies\n\t\tvar colSegsBelow; // array of segment arrays, below seg, one for each column (offset from segs's first column)\n\t\tvar td, rowspan;\n\t\tvar segMoreNodes; // array of \"more\" | cells that will stand-in for the current seg's cell\n\t\tvar j;\n\t\tvar moreTd, moreWrap, moreLink;\n\n\t\t// Iterates through empty level cells and places \"more\" links inside if need be\n\t\tfunction emptyCellsUntil(endCol) { // goes from current `col` to `endCol`\n\t\t\twhile (col < endCol) {\n\t\t\t\tcell = _this.getCell(row, col);\n\t\t\t\tsegsBelow = _this.getCellSegs(cell, levelLimit);\n\t\t\t\tif (segsBelow.length) {\n\t\t\t\t\ttd = cellMatrix[levelLimit - 1][col];\n\t\t\t\t\tmoreLink = _this.renderMoreLink(cell, segsBelow);\n\t\t\t\t\tmoreWrap = $('').append(moreLink);\n\t\t\t\t\ttd.append(moreWrap);\n\t\t\t\t\tmoreNodes.push(moreWrap[0]);\n\t\t\t\t}\n\t\t\t\tcol++;\n\t\t\t}\n\t\t}\n\n\t\tif (levelLimit && levelLimit < rowStruct.segLevels.length) { // is it actually over the limit?\n\t\t\tlevelSegs = rowStruct.segLevels[levelLimit - 1];\n\t\t\tcellMatrix = rowStruct.cellMatrix;\n\n\t\t\tlimitedNodes = rowStruct.tbodyEl.children().slice(levelLimit) // get level | elements past the limit\n\t\t\t\t.addClass('fc-limited').get(); // hide elements and get a simple DOM-nodes array\n\n\t\t\t// iterate though segments in the last allowable level\n\t\t\tfor (i = 0; i < levelSegs.length; i++) {\n\t\t\t\tseg = levelSegs[i];\n\t\t\t\temptyCellsUntil(seg.leftCol); // process empty cells before the segment\n\n\t\t\t\t// determine *all* segments below `seg` that occupy the same columns\n\t\t\t\tcolSegsBelow = [];\n\t\t\t\ttotalSegsBelow = 0;\n\t\t\t\twhile (col <= seg.rightCol) {\n\t\t\t\t\tcell = this.getCell(row, col);\n\t\t\t\t\tsegsBelow = this.getCellSegs(cell, levelLimit);\n\t\t\t\t\tcolSegsBelow.push(segsBelow);\n\t\t\t\t\ttotalSegsBelow += segsBelow.length;\n\t\t\t\t\tcol++;\n\t\t\t\t}\n\n\t\t\t\tif (totalSegsBelow) { // do we need to replace this segment with one or many \"more\" links?\n\t\t\t\t\ttd = cellMatrix[levelLimit - 1][seg.leftCol]; // the segment's parent cell\n\t\t\t\t\trowspan = td.attr('rowspan') || 1;\n\t\t\t\t\tsegMoreNodes = [];\n\n\t\t\t\t\t// make a replacement for each column the segment occupies. will be one for each colspan\n\t\t\t\t\tfor (j = 0; j < colSegsBelow.length; j++) {\n\t\t\t\t\t\tmoreTd = $(' | ').attr('rowspan', rowspan);\n\t\t\t\t\t\tsegsBelow = colSegsBelow[j];\n\t\t\t\t\t\tcell = this.getCell(row, seg.leftCol + j);\n\t\t\t\t\t\tmoreLink = this.renderMoreLink(cell, [ seg ].concat(segsBelow)); // count seg as hidden too\n\t\t\t\t\t\tmoreWrap = $('').append(moreLink);\n\t\t\t\t\t\tmoreTd.append(moreWrap);\n\t\t\t\t\t\tsegMoreNodes.push(moreTd[0]);\n\t\t\t\t\t\tmoreNodes.push(moreTd[0]);\n\t\t\t\t\t}\n\n\t\t\t\t\ttd.addClass('fc-limited').after($(segMoreNodes)); // hide original | and inject replacements\n\t\t\t\t\tlimitedNodes.push(td[0]);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\temptyCellsUntil(this.colCnt); // finish off the level\n\t\t\trowStruct.moreEls = $(moreNodes); // for easy undoing later\n\t\t\trowStruct.limitedEls = $(limitedNodes); // for easy undoing later\n\t\t}\n\t},\n\n\n\t// Reveals all levels and removes all \"more\"-related elements for a grid's row.\n\t// `row` is a row number.\n\tunlimitRow: function(row) {\n\t\tvar rowStruct = this.rowStructs[row];\n\n\t\tif (rowStruct.moreEls) {\n\t\t\trowStruct.moreEls.remove();\n\t\t\trowStruct.moreEls = null;\n\t\t}\n\n\t\tif (rowStruct.limitedEls) {\n\t\t\trowStruct.limitedEls.removeClass('fc-limited');\n\t\t\trowStruct.limitedEls = null;\n\t\t}\n\t},\n\n\n\t// Renders an element that represents hidden event element for a cell.\n\t// Responsible for attaching click handler as well.\n\trenderMoreLink: function(cell, hiddenSegs) {\n\t\tvar _this = this;\n\t\tvar view = this.view;\n\n\t\treturn $('')\n\t\t\t.text(\n\t\t\t\tthis.getMoreLinkText(hiddenSegs.length)\n\t\t\t)\n\t\t\t.on('click', function(ev) {\n\t\t\t\tvar clickOption = view.opt('eventLimitClick');\n\t\t\t\tvar date = cell.start;\n\t\t\t\tvar moreEl = $(this);\n\t\t\t\tvar dayEl = _this.getCellDayEl(cell);\n\t\t\t\tvar allSegs = _this.getCellSegs(cell);\n\n\t\t\t\t// rescope the segments to be within the cell's date\n\t\t\t\tvar reslicedAllSegs = _this.resliceDaySegs(allSegs, date);\n\t\t\t\tvar reslicedHiddenSegs = _this.resliceDaySegs(hiddenSegs, date);\n\n\t\t\t\tif (typeof clickOption === 'function') {\n\t\t\t\t\t// the returned value can be an atomic option\n\t\t\t\t\tclickOption = view.trigger('eventLimitClick', null, {\n\t\t\t\t\t\tdate: date,\n\t\t\t\t\t\tdayEl: dayEl,\n\t\t\t\t\t\tmoreEl: moreEl,\n\t\t\t\t\t\tsegs: reslicedAllSegs,\n\t\t\t\t\t\thiddenSegs: reslicedHiddenSegs\n\t\t\t\t\t}, ev);\n\t\t\t\t}\n\n\t\t\t\tif (clickOption === 'popover') {\n\t\t\t\t\t_this.showSegPopover(cell, moreEl, reslicedAllSegs);\n\t\t\t\t}\n\t\t\t\telse if (typeof clickOption === 'string') { // a view name\n\t\t\t\t\tview.calendar.zoomTo(date, clickOption);\n\t\t\t\t}\n\t\t\t});\n\t},\n\n\n\t// Reveals the popover that displays all events within a cell\n\tshowSegPopover: function(cell, moreLink, segs) {\n\t\tvar _this = this;\n\t\tvar view = this.view;\n\t\tvar moreWrap = moreLink.parent(); // the wrapper around the \n\t\tvar topEl; // the element we want to match the top coordinate of\n\t\tvar options;\n\n\t\tif (this.rowCnt == 1) {\n\t\t\ttopEl = view.el; // will cause the popover to cover any sort of header\n\t\t}\n\t\telse {\n\t\t\ttopEl = this.rowEls.eq(cell.row); // will align with top of row\n\t\t}\n\n\t\toptions = {\n\t\t\tclassName: 'fc-more-popover',\n\t\t\tcontent: this.renderSegPopoverContent(cell, segs),\n\t\t\tparentEl: this.el,\n\t\t\ttop: topEl.offset().top,\n\t\t\tautoHide: true, // when the user clicks elsewhere, hide the popover\n\t\t\tviewportConstrain: view.opt('popoverViewportConstrain'),\n\t\t\thide: function() {\n\t\t\t\t// kill everything when the popover is hidden\n\t\t\t\t_this.segPopover.removeElement();\n\t\t\t\t_this.segPopover = null;\n\t\t\t\t_this.popoverSegs = null;\n\t\t\t}\n\t\t};\n\n\t\t// Determine horizontal coordinate.\n\t\t// We use the moreWrap instead of the to avoid border confusion.\n\t\tif (this.isRTL) {\n\t\t\toptions.right = moreWrap.offset().left + moreWrap.outerWidth() + 1; // +1 to be over cell border\n\t\t}\n\t\telse {\n\t\t\toptions.left = moreWrap.offset().left - 1; // -1 to be over cell border\n\t\t}\n\n\t\tthis.segPopover = new Popover(options);\n\t\tthis.segPopover.show();\n\t},\n\n\n\t// Builds the inner DOM contents of the segment popover\n\trenderSegPopoverContent: function(cell, segs) {\n\t\tvar view = this.view;\n\t\tvar isTheme = view.opt('theme');\n\t\tvar title = cell.start.format(view.opt('dayPopoverFormat'));\n\t\tvar content = $(\n\t\t\t' | | |