From bc966de4b3913bb6aae2d6d29a7415cf091c8378 Mon Sep 17 00:00:00 2001 From: Sean Kenny Date: Mon, 15 Sep 2014 18:36:20 +0100 Subject: [PATCH] @dist@ --- dist/cdnjs/2.1.1/fullcalendar.css | 1866 +-- dist/cdnjs/2.1.1/fullcalendar.js | 17393 +++++++++++----------- dist/cdnjs/2.1.1/fullcalendar.min.css | 8 +- dist/cdnjs/2.1.1/fullcalendar.min.js | 6 +- dist/cdnjs/2.1.1/fullcalendar.print.css | 402 +- dist/cdnjs/2.1.1/gcal.js | 200 +- dist/cdnjs/2.1.1/lang/is.js | 2 +- dist/fullcalendar-2.1.1.zip | Bin 336134 -> 339936 bytes dist/fullcalendar.css | 1866 +-- dist/fullcalendar.js | 17393 +++++++++++----------- dist/fullcalendar.min.css | 8 +- dist/fullcalendar.min.js | 6 +- dist/fullcalendar.print.css | 402 +- dist/gcal.js | 200 +- dist/lang/is.js | 2 +- 15 files changed, 20474 insertions(+), 19280 deletions(-) diff --git a/dist/cdnjs/2.1.1/fullcalendar.css b/dist/cdnjs/2.1.1/fullcalendar.css index ab8b6e6..a5b9d33 100644 --- a/dist/cdnjs/2.1.1/fullcalendar.css +++ b/dist/cdnjs/2.1.1/fullcalendar.css @@ -1,933 +1,933 @@ -/*! - * FullCalendar v2.1.1 Stylesheet - * Docs & License: http://arshaw.com/fullcalendar/ - * (c) 2013 Adam Shaw - */ - - -.fc { - direction: ltr; - text-align: left; -} - -.fc-rtl { - text-align: right; -} - -body .fc { /* extra precedence to overcome jqui */ - font-size: 1em; -} - - -/* Colors ---------------------------------------------------------------------------------------------------*/ - -.fc-unthemed th, -.fc-unthemed td, -.fc-unthemed hr, -.fc-unthemed thead, -.fc-unthemed tbody, -.fc-unthemed .fc-row, -.fc-unthemed .fc-popover { - border-color: #ddd; -} - -.fc-unthemed .fc-popover { - background-color: #fff; -} - -.fc-unthemed hr, -.fc-unthemed .fc-popover .fc-header { - background: #eee; -} - -.fc-unthemed .fc-popover .fc-header .fc-close { - color: #666; -} - -.fc-unthemed .fc-today { - background: #fcf8e3; -} - -.fc-highlight { /* when user is selecting cells */ - background: #bce8f1; - opacity: .3; - filter: alpha(opacity=30); /* for IE */ -} - - -/* Icons (inline elements with styled text that mock arrow icons) ---------------------------------------------------------------------------------------------------*/ - -.fc-icon { - display: inline-block; - font-size: 2em; - line-height: .5em; - height: .5em; /* will make the total height 1em */ - font-family: "Courier New", Courier, monospace; -} - -.fc-icon-left-single-arrow:after { - content: "\02039"; - font-weight: bold; -} - -.fc-icon-right-single-arrow:after { - content: "\0203A"; - font-weight: bold; -} - -.fc-icon-left-double-arrow:after { - content: "\000AB"; -} - -.fc-icon-right-double-arrow:after { - content: "\000BB"; -} - -.fc-icon-x:after { - content: "\000D7"; -} - - -/* Buttons (styled ' - ) - .click(function() { - // don't process clicks for disabled buttons - if (!button.hasClass(tm + '-state-disabled')) { - - buttonClick(); - - // after the click action, if the button becomes the "active" tab, or disabled, - // it should never have a hover class, so remove it now. - if ( - button.hasClass(tm + '-state-active') || - button.hasClass(tm + '-state-disabled') - ) { - button.removeClass(tm + '-state-hover'); - } - } - }) - .mousedown(function() { - // the *down* effect (mouse pressed in). - // only on buttons that are not the "active" tab, or disabled - button - .not('.' + tm + '-state-active') - .not('.' + tm + '-state-disabled') - .addClass(tm + '-state-down'); - }) - .mouseup(function() { - // undo the *down* effect - button.removeClass(tm + '-state-down'); - }) - .hover( - function() { - // the *hover* effect. - // only on buttons that are not the "active" tab, or disabled - button - .not('.' + tm + '-state-active') - .not('.' + tm + '-state-disabled') - .addClass(tm + '-state-hover'); - }, - function() { - // undo the *hover* effect - button - .removeClass(tm + '-state-hover') - .removeClass(tm + '-state-down'); // if mouseleave happens before mouseup - } - ); - - groupChildren = groupChildren.add(button); - } - } - }); - - if (isOnlyButtons) { - groupChildren - .first().addClass(tm + '-corner-left').end() - .last().addClass(tm + '-corner-right').end(); - } - - if (groupChildren.length > 1) { - groupEl = $('
'); - if (isOnlyButtons) { - groupEl.addClass('fc-button-group'); - } - groupEl.append(groupChildren); - sectionEl.append(groupEl); - } - else { - sectionEl.append(groupChildren); // 1 or 0 children - } - }); - } - - return sectionEl; - } - - - function updateTitle(text) { - el.find('h2').text(text); - } - - - function activateButton(buttonName) { - el.find('.fc-' + buttonName + '-button') - .addClass(tm + '-state-active'); - } - - - function deactivateButton(buttonName) { - el.find('.fc-' + buttonName + '-button') - .removeClass(tm + '-state-active'); - } - - - function disableButton(buttonName) { - el.find('.fc-' + buttonName + '-button') - .attr('disabled', 'disabled') - .addClass(tm + '-state-disabled'); - } - - - function enableButton(buttonName) { - el.find('.fc-' + buttonName + '-button') - .removeAttr('disabled') - .removeClass(tm + '-state-disabled'); - } - - - function getViewsWithButtons() { - return viewsWithButtons; - } - -} - -;; - -fc.sourceNormalizers = []; -fc.sourceFetchers = []; - -var ajaxDefaults = { - dataType: 'json', - cache: false -}; - -var eventGUID = 1; - - -function EventManager(options) { // assumed to be a calendar - var t = this; - - - // exports - t.isFetchNeeded = isFetchNeeded; - t.fetchEvents = fetchEvents; - t.addEventSource = addEventSource; - t.removeEventSource = removeEventSource; - t.updateEvent = updateEvent; - t.renderEvent = renderEvent; - t.removeEvents = removeEvents; - t.clientEvents = clientEvents; - t.mutateEvent = mutateEvent; - - - // imports - var trigger = t.trigger; - var getView = t.getView; - var reportEvents = t.reportEvents; - var getEventEnd = t.getEventEnd; - - - // locals - var stickySource = { events: [] }; - var sources = [ stickySource ]; - var rangeStart, rangeEnd; - var currentFetchID = 0; - var pendingSourceCnt = 0; - var loadingLevel = 0; - var cache = []; - - - $.each( - (options.events ? [ options.events ] : []).concat(options.eventSources || []), - function(i, sourceInput) { - var source = buildEventSource(sourceInput); - if (source) { - sources.push(source); - } - } - ); - - - - /* Fetching - -----------------------------------------------------------------------------*/ - - - function isFetchNeeded(start, end) { - return !rangeStart || // nothing has been fetched yet? - // or, a part of the new range is outside of the old range? (after normalizing) - start.clone().stripZone() < rangeStart.clone().stripZone() || - end.clone().stripZone() > rangeEnd.clone().stripZone(); - } - - - function fetchEvents(start, end) { - rangeStart = start; - rangeEnd = end; - cache = []; - var fetchID = ++currentFetchID; - var len = sources.length; - pendingSourceCnt = len; - for (var i=0; i cells, find the cell with the largest natural width and set the widths of all the -// cells to be that width. -// PREREQUISITE: if you want a cell to take up width, it needs to have a single inner element w/ display:inline -function matchCellWidths(els) { - var maxInnerWidth = 0; - - els.find('> *').each(function(i, innerEl) { - var innerWidth = $(innerEl).outerWidth(); - if (innerWidth > maxInnerWidth) { - maxInnerWidth = innerWidth; - } - }); - - maxInnerWidth++; // sometimes not accurate of width the text needs to stay on one line. insurance - - els.width(maxInnerWidth); - - return maxInnerWidth; -} - - -// Turns a container element into a scroller if its contents is taller than the allotted height. -// Returns true if the element is now a scroller, false otherwise. -// NOTE: this method is best because it takes weird zooming dimensions into account -function setPotentialScroller(containerEl, height) { - containerEl.height(height).addClass('fc-scroller'); - - // are scrollbars needed? - if (containerEl[0].scrollHeight - 1 > containerEl[0].clientHeight) { // !!! -1 because IE is often off-by-one :( - return true; - } - - unsetScroller(containerEl); // undo - return false; -} - - -// Takes an element that might have been a scroller, and turns it back into a normal element. -function unsetScroller(containerEl) { - containerEl.height('').removeClass('fc-scroller'); -} - - -/* General DOM Utilities -----------------------------------------------------------------------------------------------------------------------*/ - - -// borrowed from https://github.com/jquery/jquery-ui/blob/1.11.0/ui/core.js#L51 -function getScrollParent(el) { - var position = el.css('position'), - scrollParent = el.parents().filter(function() { - var parent = $(this); - return (/(auto|scroll)/).test( - parent.css('overflow') + parent.css('overflow-y') + parent.css('overflow-x') - ); - }).eq(0); - - return position === 'fixed' || !scrollParent.length ? $(el[0].ownerDocument || document) : scrollParent; -} - - -// Given a container element, return an object with the pixel values of the left/right scrollbars. -// Left scrollbars might occur on RTL browsers (IE maybe?) but I have not tested. -// PREREQUISITE: container element must have a single child with display:block -function getScrollbarWidths(container) { - var containerLeft = container.offset().left; - var containerRight = containerLeft + container.width(); - var inner = container.children(); - var innerLeft = inner.offset().left; - var innerRight = innerLeft + inner.outerWidth(); - - return { - left: innerLeft - containerLeft, - right: containerRight - innerRight - }; -} - - -// Returns a boolean whether this was a left mouse click and no ctrl key (which means right click on Mac) -function isPrimaryMouseButton(ev) { - return ev.which == 1 && !ev.ctrlKey; -} - - -/* FullCalendar-specific Misc Utilities -----------------------------------------------------------------------------------------------------------------------*/ - - -// Creates a basic segment with the intersection of the two ranges. Returns undefined if no intersection. -// Expects all dates to be normalized to the same timezone beforehand. -function intersectionToSeg(subjectStart, subjectEnd, intervalStart, intervalEnd) { - var segStart, segEnd; - var isStart, isEnd; - - if (subjectEnd > intervalStart && subjectStart < intervalEnd) { // in bounds at all? - - if (subjectStart >= intervalStart) { - segStart = subjectStart.clone(); - isStart = true; - } - else { - segStart = intervalStart.clone(); - isStart = false; - } - - if (subjectEnd <= intervalEnd) { - segEnd = subjectEnd.clone(); - isEnd = true; - } - else { - segEnd = intervalEnd.clone(); - isEnd = false; - } - - return { - start: segStart, - end: segEnd, - isStart: isStart, - isEnd: isEnd - }; - } -} - - -function smartProperty(obj, name) { // get a camel-cased/namespaced property of an object - obj = obj || {}; - if (obj[name] !== undefined) { - return obj[name]; - } - var parts = name.split(/(?=[A-Z])/), - i = parts.length - 1, res; - for (; i>=0; i--) { - res = obj[parts[i].toLowerCase()]; - if (res !== undefined) { - return res; - } - } - return obj['default']; -} - - -/* Date Utilities -----------------------------------------------------------------------------------------------------------------------*/ - -var dayIDs = [ 'sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat' ]; - - -// Diffs the two moments into a Duration where full-days are recorded first, then the remaining time. -// Moments will have their timezones normalized. -function dayishDiff(a, b) { - return moment.duration({ - days: a.clone().stripTime().diff(b.clone().stripTime(), 'days'), - ms: a.time() - b.time() - }); -} - - -function isNativeDate(input) { - return Object.prototype.toString.call(input) === '[object Date]' || input instanceof Date; -} - - -function dateCompare(a, b) { // works with Moments and native Dates - return a - b; -} - - -/* General Utilities -----------------------------------------------------------------------------------------------------------------------*/ - -fc.applyAll = applyAll; // export - - -// Create an object that has the given prototype. Just like Object.create -function createObject(proto) { - var f = function() {}; - f.prototype = proto; - return new f(); -} - - -// Copies specifically-owned (non-protoype) properties of `b` onto `a`. -// FYI, $.extend would copy *all* properties of `b` onto `a`. -function extend(a, b) { - for (var i in b) { - if (b.hasOwnProperty(i)) { - a[i] = b[i]; - } - } -} - - -function applyAll(functions, thisObj, args) { - if ($.isFunction(functions)) { - functions = [ functions ]; - } - if (functions) { - var i; - var ret; - for (i=0; i/g, '>') - .replace(/'/g, ''') - .replace(/"/g, '"') - .replace(/\n/g, '
'); -} - - -function stripHtmlEntities(text) { - return text.replace(/&.*?;/g, ''); -} - - -function capitaliseFirstLetter(str) { - return str.charAt(0).toUpperCase() + str.slice(1); -} - - -// Returns a function, that, as long as it continues to be invoked, will not -// be triggered. The function will be called after it stops being called for -// N milliseconds. -// https://github.com/jashkenas/underscore/blob/1.6.0/underscore.js#L714 -function debounce(func, wait) { - var timeoutId; - var args; - var context; - var timestamp; // of most recent call - var later = function() { - var last = +new Date() - timestamp; - if (last < wait && last > 0) { - timeoutId = setTimeout(later, wait - last); - } - else { - timeoutId = null; - func.apply(context, args); - if (!timeoutId) { - context = args = null; - } - } - }; - - return function() { - context = this; - args = arguments; - timestamp = +new Date(); - if (!timeoutId) { - timeoutId = setTimeout(later, wait); - } - }; -} - -;; - -var ambigDateOfMonthRegex = /^\s*\d{4}-\d\d$/; -var ambigTimeOrZoneRegex = - /^\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+)?)?)?)?)?$/; - - -// Creating -// ------------------------------------------------------------------------------------------------- - -// Creates a new moment, similar to the vanilla moment(...) constructor, but with -// extra features (ambiguous time, enhanced formatting). When gived an existing moment, -// it will function as a clone (and retain the zone of the moment). Anything else will -// result in a moment in the local zone. -fc.moment = function() { - return makeMoment(arguments); -}; - -// Sames as fc.moment, but forces the resulting moment to be in the UTC timezone. -fc.moment.utc = function() { - var mom = makeMoment(arguments, true); - - // Force it into UTC because makeMoment doesn't guarantee it. - if (mom.hasTime()) { // don't give ambiguously-timed moments a UTC zone - mom.utc(); - } - - return mom; -}; - -// Same as fc.moment, but when given an ISO8601 string, the timezone offset is preserved. -// ISO8601 strings with no timezone offset will become ambiguously zoned. -fc.moment.parseZone = function() { - return makeMoment(arguments, true, true); -}; - -// Builds an FCMoment from args. When given an existing moment, it clones. When given a native -// Date, or called with no arguments (the current time), the resulting moment will be local. -// Anything else needs to be "parsed" (a string or an array), and will be affected by: -// parseAsUTC - if there is no zone information, should we parse the input in UTC? -// parseZone - if there is zone information, should we force the zone of the moment? -function makeMoment(args, parseAsUTC, parseZone) { - var input = args[0]; - var isSingleString = args.length == 1 && typeof input === 'string'; - var isAmbigTime; - var isAmbigZone; - var ambigMatch; - var output; // an object with fields for the new FCMoment object - - if (moment.isMoment(input)) { - output = moment.apply(null, args); // clone it - - // the ambig properties have not been preserved in the clone, so reassign them - if (input._ambigTime) { - output._ambigTime = true; - } - if (input._ambigZone) { - output._ambigZone = true; - } - } - else if (isNativeDate(input) || input === undefined) { - output = moment.apply(null, args); // will be local - } - else { // "parsing" is required - isAmbigTime = false; - isAmbigZone = false; - - if (isSingleString) { - if (ambigDateOfMonthRegex.test(input)) { - // accept strings like '2014-05', but convert to the first of the month - input += '-01'; - args = [ input ]; // for when we pass it on to moment's constructor - isAmbigTime = true; - isAmbigZone = true; - } - else if ((ambigMatch = ambigTimeOrZoneRegex.exec(input))) { - isAmbigTime = !ambigMatch[5]; // no time part? - isAmbigZone = true; - } - } - else if ($.isArray(input)) { - // arrays have no timezone information, so assume ambiguous zone - isAmbigZone = true; - } - // otherwise, probably a string with a format - - if (parseAsUTC) { - output = moment.utc.apply(moment, args); - } - else { - output = moment.apply(null, args); - } - - if (isAmbigTime) { - output._ambigTime = true; - output._ambigZone = true; // ambiguous time always means ambiguous zone - } - else if (parseZone) { // let's record the inputted zone somehow - if (isAmbigZone) { - output._ambigZone = true; - } - else if (isSingleString) { - output.zone(input); // if not a valid zone, will assign UTC - } - } - } - - return new FCMoment(output); -} - -// Our subclass of Moment. -// Accepts an object with the internal Moment properties that should be copied over to -// `this` object (most likely another Moment object). The values in this data must not -// be referenced by anything else (two moments sharing a Date object for example). -function FCMoment(internalData) { - extend(this, internalData); -} - -// Chain the prototype to Moment's -FCMoment.prototype = createObject(moment.fn); - -// We need this because Moment's implementation won't create an FCMoment, -// nor will it copy over the ambig flags. -FCMoment.prototype.clone = function() { - return makeMoment([ this ]); -}; - - -// Time-of-day -// ------------------------------------------------------------------------------------------------- - -// GETTER -// Returns a Duration with the hours/minutes/seconds/ms values of the moment. -// If the moment has an ambiguous time, a duration of 00:00 will be returned. -// -// SETTER -// You can supply a Duration, a Moment, or a Duration-like argument. -// When setting the time, and the moment has an ambiguous time, it then becomes unambiguous. -FCMoment.prototype.time = function(time) { - if (time == null) { // getter - return moment.duration({ - hours: this.hours(), - minutes: this.minutes(), - seconds: this.seconds(), - milliseconds: this.milliseconds() - }); - } - else { // setter - - delete this._ambigTime; // mark that the moment now has a time - - if (!moment.isDuration(time) && !moment.isMoment(time)) { - time = moment.duration(time); - } - - // The day value should cause overflow (so 24 hours becomes 00:00:00 of next day). - // Only for Duration times, not Moment times. - var dayHours = 0; - if (moment.isDuration(time)) { - dayHours = Math.floor(time.asDays()) * 24; - } - - // We need to set the individual fields. - // Can't use startOf('day') then add duration. In case of DST at start of day. - return this.hours(dayHours + time.hours()) - .minutes(time.minutes()) - .seconds(time.seconds()) - .milliseconds(time.milliseconds()); - } -}; - -// Converts the moment to UTC, stripping out its time-of-day and timezone offset, -// but preserving its YMD. A moment with a stripped time will display no time -// nor timezone offset when .format() is called. -FCMoment.prototype.stripTime = function() { - var a = this.toArray(); // year,month,date,hours,minutes,seconds as an array - - // set the internal UTC flag - moment.fn.utc.call(this); // call the original method, because we don't want to affect _ambigZone - - this.year(a[0]) // TODO: find a way to do this in one shot - .month(a[1]) - .date(a[2]) - .hours(0) - .minutes(0) - .seconds(0) - .milliseconds(0); - - // Mark the time as ambiguous. This needs to happen after the .utc() call, which calls .zone(), which - // clears all ambig flags. Same concept with the .year/month/date calls in the case of moment-timezone. - this._ambigTime = true; - this._ambigZone = true; // if ambiguous time, also ambiguous timezone offset - - return this; // for chaining -}; - -// Returns if the moment has a non-ambiguous time (boolean) -FCMoment.prototype.hasTime = function() { - return !this._ambigTime; -}; - - -// Timezone -// ------------------------------------------------------------------------------------------------- - -// Converts the moment to UTC, stripping out its timezone offset, but preserving its -// YMD and time-of-day. A moment with a stripped timezone offset will display no -// timezone offset when .format() is called. -FCMoment.prototype.stripZone = function() { - var a = this.toArray(); // year,month,date,hours,minutes,seconds as an array - var wasAmbigTime = this._ambigTime; - - moment.fn.utc.call(this); // set the internal UTC flag - - this.year(a[0]) // TODO: find a way to do this in one shot - .month(a[1]) - .date(a[2]) - .hours(a[3]) - .minutes(a[4]) - .seconds(a[5]) - .milliseconds(a[6]); - - if (wasAmbigTime) { - // the above call to .utc()/.zone() unfortunately clears the ambig flags, so reassign - this._ambigTime = true; - } - - // Mark the zone as ambiguous. This needs to happen after the .utc() call, which calls .zone(), which - // clears all ambig flags. Same concept with the .year/month/date calls in the case of moment-timezone. - this._ambigZone = true; - - return this; // for chaining -}; - -// Returns of the moment has a non-ambiguous timezone offset (boolean) -FCMoment.prototype.hasZone = function() { - return !this._ambigZone; -}; - -// this method implicitly marks a zone -FCMoment.prototype.zone = function(tzo) { - - if (tzo != null) { - // FYI, the delete statements need to be before the .zone() call or else chaos ensues - // for reasons I don't understand. - delete this._ambigTime; - delete this._ambigZone; - } - - return moment.fn.zone.apply(this, arguments); -}; - -// this method implicitly marks a zone -FCMoment.prototype.local = function() { - var a = this.toArray(); // year,month,date,hours,minutes,seconds as an array - var wasAmbigZone = this._ambigZone; - - // will happen anyway via .local()/.zone(), but don't want to rely on internal implementation - delete this._ambigTime; - delete this._ambigZone; - - moment.fn.local.apply(this, arguments); - - if (wasAmbigZone) { - // If the moment was ambiguously zoned, the date fields were stored as UTC. - // We want to preserve these, but in local time. - this.year(a[0]) // TODO: find a way to do this in one shot - .month(a[1]) - .date(a[2]) - .hours(a[3]) - .minutes(a[4]) - .seconds(a[5]) - .milliseconds(a[6]); - } - - return this; // for chaining -}; - -// this method implicitly marks a zone -FCMoment.prototype.utc = function() { - - // will happen anyway via .local()/.zone(), but don't want to rely on internal implementation - delete this._ambigTime; - delete this._ambigZone; - - return moment.fn.utc.apply(this, arguments); -}; - - -// Formatting -// ------------------------------------------------------------------------------------------------- - -FCMoment.prototype.format = function() { - if (arguments[0]) { - return formatDate(this, arguments[0]); // our extended formatting - } - if (this._ambigTime) { - return momentFormat(this, 'YYYY-MM-DD'); - } - if (this._ambigZone) { - return momentFormat(this, 'YYYY-MM-DD[T]HH:mm:ss'); - } - return momentFormat(this); // default moment original formatting -}; - -FCMoment.prototype.toISOString = function() { - if (this._ambigTime) { - return momentFormat(this, 'YYYY-MM-DD'); - } - if (this._ambigZone) { - return momentFormat(this, 'YYYY-MM-DD[T]HH:mm:ss'); - } - return moment.fn.toISOString.apply(this, arguments); -}; - - -// Querying -// ------------------------------------------------------------------------------------------------- - -// Is the moment within the specified range? `end` is exclusive. -FCMoment.prototype.isWithin = function(start, end) { - var a = commonlyAmbiguate([ this, start, end ]); - return a[0] >= a[1] && a[0] < a[2]; -}; - -// When isSame is called with units, timezone ambiguity is normalized before the comparison happens. -// If no units are specified, the two moments must be identically the same, with matching ambig flags. -FCMoment.prototype.isSame = function(input, units) { - var a; - - if (units) { - a = commonlyAmbiguate([ this, input ], true); // normalize timezones but don't erase times - return moment.fn.isSame.call(a[0], a[1], units); - } - else { - input = fc.moment.parseZone(input); // normalize input - return moment.fn.isSame.call(this, input) && - Boolean(this._ambigTime) === Boolean(input._ambigTime) && - Boolean(this._ambigZone) === Boolean(input._ambigZone); - } -}; - -// Make these query methods work with ambiguous moments -$.each([ - 'isBefore', - 'isAfter' -], function(i, methodName) { - FCMoment.prototype[methodName] = function(input, units) { - var a = commonlyAmbiguate([ this, input ]); - return moment.fn[methodName].call(a[0], a[1], units); - }; -}); - - -// Misc Internals -// ------------------------------------------------------------------------------------------------- - -// given an array of moment-like inputs, return a parallel array w/ moments similarly ambiguated. -// for example, of one moment has ambig time, but not others, all moments will have their time stripped. -// set `preserveTime` to `true` to keep times, but only normalize zone ambiguity. -function commonlyAmbiguate(inputs, preserveTime) { - var outputs = []; - var anyAmbigTime = false; - var anyAmbigZone = false; - var i; - - for (i=0; i "MMMM D YYYY" - formatStr = localeData.longDateFormat(formatStr) || formatStr; - // BTW, this is not important for `formatDate` because it is impossible to put custom tokens - // or non-zero areas in Moment's localized format strings. - - separator = separator || ' - '; - - return formatRangeWithChunks( - date1, - date2, - getFormatStringChunks(formatStr), - separator, - isRTL - ); -} -fc.formatRange = formatRange; // expose - - -function formatRangeWithChunks(date1, date2, chunks, separator, isRTL) { - var chunkStr; // the rendering of the chunk - var leftI; - var leftStr = ''; - var rightI; - var rightStr = ''; - var middleI; - var middleStr1 = ''; - var middleStr2 = ''; - var middleStr = ''; - - // Start at the leftmost side of the formatting string and continue until you hit a token - // that is not the same between dates. - for (leftI=0; leftIleftI; rightI--) { - chunkStr = formatSimilarChunk(date1, date2, chunks[rightI]); - if (chunkStr === false) { - break; - } - rightStr = chunkStr + rightStr; - } - - // The area in the middle is different for both of the dates. - // Collect them distinctly so we can jam them together later. - for (middleI=leftI; middleI<=rightI; middleI++) { - middleStr1 += formatDateWithChunk(date1, chunks[middleI]); - middleStr2 += formatDateWithChunk(date2, chunks[middleI]); - } - - if (middleStr1 || middleStr2) { - if (isRTL) { - middleStr = middleStr2 + separator + middleStr1; - } - else { - middleStr = middleStr1 + separator + middleStr2; - } - } - - return leftStr + middleStr + rightStr; -} - - -var similarUnitMap = { - Y: 'year', - M: 'month', - D: 'day', // day of month - d: 'day', // day of week - // prevents a separator between anything time-related... - A: 'second', // AM/PM - a: 'second', // am/pm - T: 'second', // A/P - t: 'second', // a/p - H: 'second', // hour (24) - h: 'second', // hour (12) - m: 'second', // minute - s: 'second' // second -}; -// TODO: week maybe? - - -// Given a formatting chunk, and given that both dates are similar in the regard the -// formatting chunk is concerned, format date1 against `chunk`. Otherwise, return `false`. -function formatSimilarChunk(date1, date2, chunk) { - var token; - var unit; - - if (typeof chunk === 'string') { // a literal string - return chunk; - } - else if ((token = chunk.token)) { - unit = similarUnitMap[token.charAt(0)]; - // are the dates the same for this unit of measurement? - if (unit && date1.isSame(date2, unit)) { - return momentFormat(date1, token); // would be the same if we used `date2` - // BTW, don't support custom tokens - } - } - - return false; // the chunk is NOT the same for the two dates - // BTW, don't support splitting on non-zero areas -} - - -// Chunking Utils -// ------------------------------------------------------------------------------------------------- - - -var formatStringChunkCache = {}; - - -function getFormatStringChunks(formatStr) { - if (formatStr in formatStringChunkCache) { - return formatStringChunkCache[formatStr]; - } - return (formatStringChunkCache[formatStr] = chunkFormatString(formatStr)); -} - - -// Break the formatting string into an array of chunks -function chunkFormatString(formatStr) { - var chunks = []; - var chunker = /\[([^\]]*)\]|\(([^\)]*)\)|(LT|(\w)\4*o?)|([^\w\[\(]+)/g; // TODO: more descrimination - var match; - - while ((match = chunker.exec(formatStr))) { - if (match[1]) { // a literal string inside [ ... ] - chunks.push(match[1]); - } - else if (match[2]) { // non-zero formatting inside ( ... ) - chunks.push({ maybe: chunkFormatString(match[2]) }); - } - else if (match[3]) { // a formatting token - chunks.push({ token: match[3] }); - } - else if (match[5]) { // an unenclosed literal string - chunks.push(match[5]); - } - } - - return chunks; -} - -;; - -/* A rectangular panel that is absolutely positioned over other content ------------------------------------------------------------------------------------------------------------------------- -Options: - - className (string) - - content (HTML string or jQuery element set) - - parentEl - - top - - left - - right (the x coord of where the right edge should be. not a "CSS" right) - - autoHide (boolean) - - show (callback) - - hide (callback) -*/ - -function Popover(options) { - this.options = options || {}; -} - - -Popover.prototype = { - - isHidden: true, - options: null, - el: null, // the container element for the popover. generated by this object - documentMousedownProxy: null, // document mousedown handler bound to `this` - margin: 10, // the space required between the popover and the edges of the scroll container - - - // Shows the popover on the specified position. Renders it if not already - show: function() { - if (this.isHidden) { - if (!this.el) { - this.render(); - } - this.el.show(); - this.position(); - this.isHidden = false; - this.trigger('show'); - } - }, - - - // Hides the popover, through CSS, but does not remove it from the DOM - hide: function() { - if (!this.isHidden) { - this.el.hide(); - this.isHidden = true; - this.trigger('hide'); - } - }, - - - // Creates `this.el` and renders content inside of it - render: function() { - var _this = this; - var options = this.options; - - this.el = $('
') - .addClass(options.className || '') - .css({ - // position initially to the top left to avoid creating scrollbars - top: 0, - left: 0 - }) - .append(options.content) - .appendTo(options.parentEl); - - // when a click happens on anything inside with a 'fc-close' className, hide the popover - this.el.on('click', '.fc-close', function() { - _this.hide(); - }); - - if (options.autoHide) { - $(document).on('mousedown', this.documentMousedownProxy = $.proxy(this, 'documentMousedown')); - } - }, - - - // Triggered when the user clicks *anywhere* in the document, for the autoHide feature - documentMousedown: function(ev) { - // only hide the popover if the click happened outside the popover - if (this.el && !$(ev.target).closest(this.el).length) { - this.hide(); - } - }, - - - // Hides and unregisters any handlers - destroy: function() { - this.hide(); - - if (this.el) { - this.el.remove(); - this.el = null; - } - - $(document).off('mousedown', this.documentMousedownProxy); - }, - - - // Positions the popover optimally, using the top/left/right options - position: function() { - var options = this.options; - var origin = this.el.offsetParent().offset(); - var width = this.el.outerWidth(); - var height = this.el.outerHeight(); - var windowEl = $(window); - var viewportEl = getScrollParent(this.el); - var viewportTop; - var viewportLeft; - var viewportOffset; - var top; // the "position" (not "offset") values for the popover - var left; // - - // compute top and left - top = options.top || 0; - if (options.left !== undefined) { - left = options.left; - } - else if (options.right !== undefined) { - left = options.right - width; // derive the left value from the right value - } - else { - left = 0; - } - - if (viewportEl.is(window) || viewportEl.is(document)) { // normalize getScrollParent's result - viewportEl = windowEl; - viewportTop = 0; // the window is always at the top left - viewportLeft = 0; // (and .offset() won't work if called here) - } - else { - viewportOffset = viewportEl.offset(); - viewportTop = viewportOffset.top; - viewportLeft = viewportOffset.left; - } - - // if the window is scrolled, it causes the visible area to be further down - viewportTop += windowEl.scrollTop(); - viewportLeft += windowEl.scrollLeft(); - - // constrain to the view port. if constrained by two edges, give precedence to top/left - if (options.viewportConstrain !== false) { - top = Math.min(top, viewportTop + viewportEl.outerHeight() - height - this.margin); - top = Math.max(top, viewportTop + this.margin); - left = Math.min(left, viewportLeft + viewportEl.outerWidth() - width - this.margin); - left = Math.max(left, viewportLeft + this.margin); - } - - this.el.css({ - top: top - origin.top, - left: left - origin.left - }); - }, - - - // Triggers a callback. Calls a function in the option hash of the same name. - // Arguments beyond the first `name` are forwarded on. - // TODO: better code reuse for this. Repeat code - trigger: function(name) { - if (this.options[name]) { - this.options[name].apply(this, Array.prototype.slice.call(arguments, 1)); - } - } - -}; - -;; - -/* A "coordinate map" converts pixel coordinates into an associated cell, which has an associated date ------------------------------------------------------------------------------------------------------------------------- -Common interface: - - CoordMap.prototype = { - build: function() {}, - getCell: function(x, y) {} - }; - -*/ - -/* Coordinate map for a grid component -----------------------------------------------------------------------------------------------------------------------*/ - -function GridCoordMap(grid) { - this.grid = grid; -} - - -GridCoordMap.prototype = { - - grid: null, // reference to the Grid - rows: null, // the top-to-bottom y coordinates. including the bottom of the last item - cols: null, // the left-to-right x coordinates. including the right of the last item - - containerEl: null, // container element that all coordinates are constrained to. optionally assigned - minX: null, - maxX: null, // exclusive - minY: null, - maxY: null, // exclusive - - - // Queries the grid for the coordinates of all the cells - build: function() { - this.grid.buildCoords( - this.rows = [], - this.cols = [] - ); - this.computeBounds(); - }, - - - // Given a coordinate of the document, gets the associated cell. If no cell is underneath, returns null - getCell: function(x, y) { - var cell = null; - var rows = this.rows; - var cols = this.cols; - var r = -1; - var c = -1; - var i; - - if (this.inBounds(x, y)) { - - for (i = 0; i < rows.length; i++) { - if (y >= rows[i][0] && y < rows[i][1]) { - r = i; - break; - } - } - - for (i = 0; i < cols.length; i++) { - if (x >= cols[i][0] && x < cols[i][1]) { - c = i; - break; - } - } - - if (r >= 0 && c >= 0) { - cell = { row: r, col: c }; - cell.grid = this.grid; - cell.date = this.grid.getCellDate(cell); - } - } - - return cell; - }, - - - // If there is a containerEl, compute the bounds into min/max values - computeBounds: function() { - var containerOffset; - - if (this.containerEl) { - containerOffset = this.containerEl.offset(); - this.minX = containerOffset.left; - this.maxX = containerOffset.left + this.containerEl.outerWidth(); - this.minY = containerOffset.top; - this.maxY = containerOffset.top + this.containerEl.outerHeight(); - } - }, - - - // Determines if the given coordinates are in bounds. If no `containerEl`, always true - inBounds: function(x, y) { - if (this.containerEl) { - return x >= this.minX && x < this.maxX && y >= this.minY && y < this.maxY; - } - return true; - } - -}; - - -/* Coordinate map that is a combination of multiple other coordinate maps -----------------------------------------------------------------------------------------------------------------------*/ - -function ComboCoordMap(coordMaps) { - this.coordMaps = coordMaps; -} - - -ComboCoordMap.prototype = { - - coordMaps: null, // an array of CoordMaps - - - // Builds all coordMaps - build: function() { - var coordMaps = this.coordMaps; - var i; - - for (i = 0; i < coordMaps.length; i++) { - coordMaps[i].build(); - } - }, - - - // Queries all coordMaps for the cell underneath the given coordinates, returning the first result - getCell: function(x, y) { - var coordMaps = this.coordMaps; - var cell = null; - var i; - - for (i = 0; i < coordMaps.length && !cell; i++) { - cell = coordMaps[i].getCell(x, y); - } - - return cell; - } - -}; -;; - -/* Tracks mouse movements over a CoordMap and raises events about which cell the mouse is over. -----------------------------------------------------------------------------------------------------------------------*/ -// TODO: implement scrolling - -function DragListener(coordMap, options) { - this.coordMap = coordMap; - this.options = options || {}; -} - - -DragListener.prototype = { - - coordMap: null, - options: null, - - isListening: false, - isDragging: false, - - // the cell/date the mouse was over when listening started - origCell: null, - origDate: null, - - // the cell/date the mouse is over - cell: null, - date: null, - - // coordinates of the initial mousedown - mouseX0: null, - mouseY0: null, - - // handler attached to the document, bound to the DragListener's `this` - mousemoveProxy: null, - mouseupProxy: null, - - scrollEl: null, - scrollBounds: null, // { top, bottom, left, right } - scrollTopVel: null, // pixels per second - scrollLeftVel: null, // pixels per second - scrollIntervalId: null, // ID of setTimeout for scrolling animation loop - scrollHandlerProxy: null, // this-scoped function for handling when scrollEl is scrolled - - scrollSensitivity: 30, // pixels from edge for scrolling to start - scrollSpeed: 200, // pixels per second, at maximum speed - scrollIntervalMs: 50, // millisecond wait between scroll increment - - - // Call this when the user does a mousedown. Will probably lead to startListening - mousedown: function(ev) { - if (isPrimaryMouseButton(ev)) { - - ev.preventDefault(); // prevents native selection in most browsers - - this.startListening(ev); - - // start the drag immediately if there is no minimum distance for a drag start - if (!this.options.distance) { - this.startDrag(ev); - } - } - }, - - - // Call this to start tracking mouse movements - startListening: function(ev) { - var scrollParent; - var cell; - - if (!this.isListening) { - - // grab scroll container and attach handler - if (ev && this.options.scroll) { - scrollParent = getScrollParent($(ev.target)); - if (!scrollParent.is(window) && !scrollParent.is(document)) { - this.scrollEl = scrollParent; - - // scope to `this`, and use `debounce` to make sure rapid calls don't happen - this.scrollHandlerProxy = debounce($.proxy(this, 'scrollHandler'), 100); - this.scrollEl.on('scroll', this.scrollHandlerProxy); - } - } - - this.computeCoords(); // relies on `scrollEl` - - // get info on the initial cell, date, and coordinates - if (ev) { - cell = this.getCell(ev); - this.origCell = cell; - this.origDate = cell ? cell.date : null; - - this.mouseX0 = ev.pageX; - this.mouseY0 = ev.pageY; - } - - $(document) - .on('mousemove', this.mousemoveProxy = $.proxy(this, 'mousemove')) - .on('mouseup', this.mouseupProxy = $.proxy(this, 'mouseup')) - .on('selectstart', this.preventDefault); // prevents native selection in IE<=8 - - this.isListening = true; - this.trigger('listenStart', ev); - } - }, - - - // Recomputes the drag-critical positions of elements - computeCoords: function() { - this.coordMap.build(); - this.computeScrollBounds(); - }, - - - // Called when the user moves the mouse - mousemove: function(ev) { - var minDistance; - var distanceSq; // current distance from mouseX0/mouseY0, squared - - if (!this.isDragging) { // if not already dragging... - // then start the drag if the minimum distance criteria is met - minDistance = this.options.distance || 1; - distanceSq = Math.pow(ev.pageX - this.mouseX0, 2) + Math.pow(ev.pageY - this.mouseY0, 2); - if (distanceSq >= minDistance * minDistance) { // use pythagorean theorem - this.startDrag(ev); - } - } - - if (this.isDragging) { - this.drag(ev); // report a drag, even if this mousemove initiated the drag - } - }, - - - // Call this to initiate a legitimate drag. - // This function is called internally from this class, but can also be called explicitly from outside - startDrag: function(ev) { - var cell; - - if (!this.isListening) { // startDrag must have manually initiated - this.startListening(); - } - - if (!this.isDragging) { - this.isDragging = true; - this.trigger('dragStart', ev); - - // report the initial cell the mouse is over - cell = this.getCell(ev); - if (cell) { - this.cellOver(cell, true); - } - } - }, - - - // Called while the mouse is being moved and when we know a legitimate drag is taking place - drag: function(ev) { - var cell; - - if (this.isDragging) { - cell = this.getCell(ev); - - if (!isCellsEqual(cell, this.cell)) { // a different cell than before? - if (this.cell) { - this.cellOut(); - } - if (cell) { - this.cellOver(cell); - } - } - - this.dragScroll(ev); // will possibly cause scrolling - } - }, - - - // Called when a the mouse has just moved over a new cell - cellOver: function(cell) { - this.cell = cell; - this.date = cell.date; - this.trigger('cellOver', cell, cell.date); - }, - - - // Called when the mouse has just moved out of a cell - cellOut: function() { - if (this.cell) { - this.trigger('cellOut', this.cell); - this.cell = null; - this.date = null; - } - }, - - - // Called when the user does a mouseup - mouseup: function(ev) { - this.stopDrag(ev); - this.stopListening(ev); - }, - - - // Called when the drag is over. Will not cause listening to stop however. - // A concluding 'cellOut' event will NOT be triggered. - stopDrag: function(ev) { - if (this.isDragging) { - this.stopScrolling(); - this.trigger('dragStop', ev); - this.isDragging = false; - } - }, - - - // Call this to stop listening to the user's mouse events - stopListening: function(ev) { - if (this.isListening) { - - // remove the scroll handler if there is a scrollEl - if (this.scrollEl) { - this.scrollEl.off('scroll', this.scrollHandlerProxy); - this.scrollHandlerProxy = null; - } - - $(document) - .off('mousemove', this.mousemoveProxy) - .off('mouseup', this.mouseupProxy) - .off('selectstart', this.preventDefault); - - this.mousemoveProxy = null; - this.mouseupProxy = null; - - this.isListening = false; - this.trigger('listenStop', ev); - - this.origCell = this.cell = null; - this.origDate = this.date = null; - } - }, - - - // Gets the cell underneath the coordinates for the given mouse event - getCell: function(ev) { - return this.coordMap.getCell(ev.pageX, ev.pageY); - }, - - - // Triggers a callback. Calls a function in the option hash of the same name. - // Arguments beyond the first `name` are forwarded on. - trigger: function(name) { - if (this.options[name]) { - this.options[name].apply(this, Array.prototype.slice.call(arguments, 1)); - } - }, - - - // Stops a given mouse event from doing it's native browser action. In our case, text selection. - preventDefault: function(ev) { - ev.preventDefault(); - }, - - - /* Scrolling - ------------------------------------------------------------------------------------------------------------------*/ - - - // Computes and stores the bounding rectangle of scrollEl - computeScrollBounds: function() { - var el = this.scrollEl; - var offset; - - if (el) { - offset = el.offset(); - this.scrollBounds = { - top: offset.top, - left: offset.left, - bottom: offset.top + el.outerHeight(), - right: offset.left + el.outerWidth() - }; - } - }, - - - // Called when the dragging is in progress and scrolling should be updated - dragScroll: function(ev) { - var sensitivity = this.scrollSensitivity; - var bounds = this.scrollBounds; - var topCloseness, bottomCloseness; - var leftCloseness, rightCloseness; - var topVel = 0; - var leftVel = 0; - - if (bounds) { // only scroll if scrollEl exists - - // compute closeness to edges. valid range is from 0.0 - 1.0 - topCloseness = (sensitivity - (ev.pageY - bounds.top)) / sensitivity; - bottomCloseness = (sensitivity - (bounds.bottom - ev.pageY)) / sensitivity; - leftCloseness = (sensitivity - (ev.pageX - bounds.left)) / sensitivity; - rightCloseness = (sensitivity - (bounds.right - ev.pageX)) / sensitivity; - - // translate vertical closeness into velocity. - // mouse must be completely in bounds for velocity to happen. - if (topCloseness >= 0 && topCloseness <= 1) { - topVel = topCloseness * this.scrollSpeed * -1; // negative. for scrolling up - } - else if (bottomCloseness >= 0 && bottomCloseness <= 1) { - topVel = bottomCloseness * this.scrollSpeed; - } - - // translate horizontal closeness into velocity - if (leftCloseness >= 0 && leftCloseness <= 1) { - leftVel = leftCloseness * this.scrollSpeed * -1; // negative. for scrolling left - } - else if (rightCloseness >= 0 && rightCloseness <= 1) { - leftVel = rightCloseness * this.scrollSpeed; - } - } - - this.setScrollVel(topVel, leftVel); - }, - - - // Sets the speed-of-scrolling for the scrollEl - setScrollVel: function(topVel, leftVel) { - - this.scrollTopVel = topVel; - this.scrollLeftVel = leftVel; - - this.constrainScrollVel(); // massages into realistic values - - // if there is non-zero velocity, and an animation loop hasn't already started, then START - if ((this.scrollTopVel || this.scrollLeftVel) && !this.scrollIntervalId) { - this.scrollIntervalId = setInterval( - $.proxy(this, 'scrollIntervalFunc'), // scope to `this` - this.scrollIntervalMs - ); - } - }, - - - // Forces scrollTopVel and scrollLeftVel to be zero if scrolling has already gone all the way - constrainScrollVel: function() { - var el = this.scrollEl; - - if (this.scrollTopVel < 0) { // scrolling up? - if (el.scrollTop() <= 0) { // already scrolled all the way up? - this.scrollTopVel = 0; - } - } - else if (this.scrollTopVel > 0) { // scrolling down? - if (el.scrollTop() + el[0].clientHeight >= el[0].scrollHeight) { // already scrolled all the way down? - this.scrollTopVel = 0; - } - } - - if (this.scrollLeftVel < 0) { // scrolling left? - if (el.scrollLeft() <= 0) { // already scrolled all the left? - this.scrollLeftVel = 0; - } - } - else if (this.scrollLeftVel > 0) { // scrolling right? - if (el.scrollLeft() + el[0].clientWidth >= el[0].scrollWidth) { // already scrolled all the way right? - this.scrollLeftVel = 0; - } - } - }, - - - // This function gets called during every iteration of the scrolling animation loop - scrollIntervalFunc: function() { - var el = this.scrollEl; - var frac = this.scrollIntervalMs / 1000; // considering animation frequency, what the vel should be mult'd by - - // change the value of scrollEl's scroll - if (this.scrollTopVel) { - el.scrollTop(el.scrollTop() + this.scrollTopVel * frac); - } - if (this.scrollLeftVel) { - el.scrollLeft(el.scrollLeft() + this.scrollLeftVel * frac); - } - - this.constrainScrollVel(); // since the scroll values changed, recompute the velocities - - // if scrolled all the way, which causes the vels to be zero, stop the animation loop - if (!this.scrollTopVel && !this.scrollLeftVel) { - this.stopScrolling(); - } - }, - - - // Kills any existing scrolling animation loop - stopScrolling: function() { - if (this.scrollIntervalId) { - clearInterval(this.scrollIntervalId); - this.scrollIntervalId = null; - - // when all done with scrolling, recompute positions since they probably changed - this.computeCoords(); - } - }, - - - // Get called when the scrollEl is scrolled (NOTE: this is delayed via debounce) - scrollHandler: function() { - // recompute all coordinates, but *only* if this is *not* part of our scrolling animation - if (!this.scrollIntervalId) { - this.computeCoords(); - } - } - -}; - - -// Returns `true` if the cells are identically equal. `false` otherwise. -// They must have the same row, col, and be from the same grid. -// Two null values will be considered equal, as two "out of the grid" states are the same. -function isCellsEqual(cell1, cell2) { - - if (!cell1 && !cell2) { - return true; - } - - if (cell1 && cell2) { - return cell1.grid === cell2.grid && - cell1.row === cell2.row && - cell1.col === cell2.col; - } - - return false; -} - -;; - -/* Creates a clone of an element and lets it track the mouse as it moves -----------------------------------------------------------------------------------------------------------------------*/ - -function MouseFollower(sourceEl, options) { - this.options = options = options || {}; - this.sourceEl = sourceEl; - this.parentEl = options.parentEl ? $(options.parentEl) : sourceEl.parent(); // default to sourceEl's parent -} - - -MouseFollower.prototype = { - - options: null, - - sourceEl: null, // the element that will be cloned and made to look like it is dragging - el: null, // the clone of `sourceEl` that will track the mouse - parentEl: null, // the element that `el` (the clone) will be attached to - - // the initial position of el, relative to the offset parent. made to match the initial offset of sourceEl - top0: null, - left0: null, - - // the initial position of the mouse - mouseY0: null, - mouseX0: null, - - // the number of pixels the mouse has moved from its initial position - topDelta: null, - leftDelta: null, - - mousemoveProxy: null, // document mousemove handler, bound to the MouseFollower's `this` - - isFollowing: false, - isHidden: false, - isAnimating: false, // doing the revert animation? - - - // Causes the element to start following the mouse - start: function(ev) { - if (!this.isFollowing) { - this.isFollowing = true; - - this.mouseY0 = ev.pageY; - this.mouseX0 = ev.pageX; - this.topDelta = 0; - this.leftDelta = 0; - - if (!this.isHidden) { - this.updatePosition(); - } - - $(document).on('mousemove', this.mousemoveProxy = $.proxy(this, 'mousemove')); - } - }, - - - // Causes the element to stop following the mouse. If shouldRevert is true, will animate back to original position. - // `callback` gets invoked when the animation is complete. If no animation, it is invoked immediately. - stop: function(shouldRevert, callback) { - var _this = this; - var revertDuration = this.options.revertDuration; - - function complete() { - this.isAnimating = false; - _this.destroyEl(); - - this.top0 = this.left0 = null; // reset state for future updatePosition calls - - if (callback) { - callback(); - } - } - - if (this.isFollowing && !this.isAnimating) { // disallow more than one stop animation at a time - this.isFollowing = false; - - $(document).off('mousemove', this.mousemoveProxy); - - if (shouldRevert && revertDuration && !this.isHidden) { // do a revert animation? - this.isAnimating = true; - this.el.animate({ - top: this.top0, - left: this.left0 - }, { - duration: revertDuration, - complete: complete - }); - } - else { - complete(); - } - } - }, - - - // Gets the tracking element. Create it if necessary - getEl: function() { - var el = this.el; - - if (!el) { - this.sourceEl.width(); // hack to force IE8 to compute correct bounding box - el = this.el = this.sourceEl.clone() - .css({ - position: 'absolute', - visibility: '', // in case original element was hidden (commonly through hideEvents()) - display: this.isHidden ? 'none' : '', // for when initially hidden - margin: 0, - right: 'auto', // erase and set width instead - bottom: 'auto', // erase and set height instead - width: this.sourceEl.width(), // explicit height in case there was a 'right' value - height: this.sourceEl.height(), // explicit width in case there was a 'bottom' value - opacity: this.options.opacity || '', - zIndex: this.options.zIndex - }) - .appendTo(this.parentEl); - } - - return el; - }, - - - // Removes the tracking element if it has already been created - destroyEl: function() { - if (this.el) { - this.el.remove(); - this.el = null; - } - }, - - - // Update the CSS position of the tracking element - updatePosition: function() { - var sourceOffset; - var origin; - - this.getEl(); // ensure this.el - - // make sure origin info was computed - if (this.top0 === null) { - this.sourceEl.width(); // hack to force IE8 to compute correct bounding box - sourceOffset = this.sourceEl.offset(); - origin = this.el.offsetParent().offset(); - this.top0 = sourceOffset.top - origin.top; - this.left0 = sourceOffset.left - origin.left; - } - - this.el.css({ - top: this.top0 + this.topDelta, - left: this.left0 + this.leftDelta - }); - }, - - - // Gets called when the user moves the mouse - mousemove: function(ev) { - this.topDelta = ev.pageY - this.mouseY0; - this.leftDelta = ev.pageX - this.mouseX0; - - if (!this.isHidden) { - this.updatePosition(); - } - }, - - - // Temporarily makes the tracking element invisible. Can be called before following starts - hide: function() { - if (!this.isHidden) { - this.isHidden = true; - if (this.el) { - this.el.hide(); - } - } - }, - - - // Show the tracking element after it has been temporarily hidden - show: function() { - if (this.isHidden) { - this.isHidden = false; - this.updatePosition(); - this.getEl().show(); - } - } - -}; - -;; - -/* A utility class for rendering rows. -----------------------------------------------------------------------------------------------------------------------*/ -// It leverages methods of the subclass and the View to determine custom rendering behavior for each row "type" -// (such as highlight rows, day rows, helper rows, etc). - -function RowRenderer(view) { - this.view = view; -} - - -RowRenderer.prototype = { - - view: null, // a View object - cellHtml: '', // plain default HTML used for a cell when no other is available - - - // Renders the HTML for a row, leveraging custom cell-HTML-renderers based on the `rowType`. - // Also applies the "intro" and "outro" cells, which are specified by the subclass and views. - // `row` is an optional row number. - rowHtml: function(rowType, row) { - var view = this.view; - var renderCell = this.getHtmlRenderer('cell', rowType); - var cellHtml = ''; - var col; - var date; - - row = row || 0; - - for (col = 0; col < view.colCnt; col++) { - date = view.cellToDate(row, col); - cellHtml += renderCell(row, col, date); - } - - cellHtml = this.bookendCells(cellHtml, rowType, row); // apply intro and outro - - return '' + cellHtml + ''; - }, - - - // Applies the "intro" and "outro" HTML to the given cells. - // Intro means the leftmost cell when the calendar is LTR and the rightmost cell when RTL. Vice-versa for outro. - // `cells` can be an HTML string of 's or a jQuery element - // `row` is an optional row number. - bookendCells: function(cells, rowType, row) { - var view = this.view; - var intro = this.getHtmlRenderer('intro', rowType)(row || 0); - var outro = this.getHtmlRenderer('outro', rowType)(row || 0); - var isRTL = view.opt('isRTL'); - var prependHtml = isRTL ? outro : intro; - var appendHtml = isRTL ? intro : outro; - - if (typeof cells === 'string') { - return prependHtml + cells + appendHtml; - } - else { // a jQuery element - return cells.prepend(prependHtml).append(appendHtml); - } - }, - - - // Returns an HTML-rendering function given a specific `rendererName` (like cell, intro, or outro) and a specific - // `rowType` (like day, eventSkeleton, helperSkeleton), which is optional. - // If a renderer for the specific rowType doesn't exist, it will fall back to a generic renderer. - // We will query the View object first for any custom rendering functions, then the methods of the subclass. - getHtmlRenderer: function(rendererName, rowType) { - var view = this.view; - var generalName; // like "cellHtml" - var specificName; // like "dayCellHtml". based on rowType - var provider; // either the View or the RowRenderer subclass, whichever provided the method - var renderer; - - generalName = rendererName + 'Html'; - if (rowType) { - specificName = rowType + capitaliseFirstLetter(rendererName) + 'Html'; - } - - if (specificName && (renderer = view[specificName])) { - provider = view; - } - else if (specificName && (renderer = this[specificName])) { - provider = this; - } - else if ((renderer = view[generalName])) { - provider = view; - } - else if ((renderer = this[generalName])) { - provider = this; - } - - if (typeof renderer === 'function') { - return function(row) { - return renderer.apply(provider, arguments) || ''; // use correct `this` and always return a string - }; - } - - // the rendered can be a plain string as well. if not specified, always an empty string. - return function() { - return renderer || ''; - }; - } - -}; - -;; - -/* An abstract class comprised of a "grid" of cells that each represent a specific datetime -----------------------------------------------------------------------------------------------------------------------*/ - -function Grid(view) { - RowRenderer.call(this, view); // call the super-constructor - this.coordMap = new GridCoordMap(this); -} - - -Grid.prototype = createObject(RowRenderer.prototype); // declare the super-class -$.extend(Grid.prototype, { - - el: null, // the containing element - coordMap: null, // a GridCoordMap that converts pixel values to datetimes - cellDuration: null, // a cell's duration. subclasses must assign this ASAP - - - // Renders the grid into the `el` element. - // Subclasses should override and call this super-method when done. - render: function() { - this.bindHandlers(); - }, - - - // Called when the grid's resources need to be cleaned up - destroy: function() { - // subclasses can implement - }, - - - /* Coordinates & Cells - ------------------------------------------------------------------------------------------------------------------*/ - - - // Populates the given empty arrays with the y and x coordinates of the cells - buildCoords: function(rows, cols) { - // subclasses must implement - }, - - - // Given a cell object, returns the date for that cell - getCellDate: function(cell) { - // subclasses must implement - }, - - - // Given a cell object, returns the element that represents the cell's whole-day - getCellDayEl: function(cell) { - // subclasses must implement - }, - - - // Converts a range with an inclusive `start` and an exclusive `end` into an array of segment objects - rangeToSegs: function(start, end) { - // subclasses must implement - }, - - - /* Handlers - ------------------------------------------------------------------------------------------------------------------*/ - - - // Attach handlers to `this.el`, using bubbling to listen to all ancestors. - // We don't need to undo any of this in a "destroy" method, because the view will simply remove `this.el` from the - // DOM and jQuery will be smart enough to garbage collect the handlers. - bindHandlers: function() { - var _this = this; - - this.el.on('mousedown', function(ev) { - if ( - !$(ev.target).is('.fc-event-container *, .fc-more') && // not an an event element, or "more.." link - !$(ev.target).closest('.fc-popover').length // not on a popover (like the "more.." events one) - ) { - _this.dayMousedown(ev); - } - }); - - this.bindSegHandlers(); // attach event-element-related handlers. in Grid.events.js - }, - - - // Process a mousedown on an element that represents a day. For day clicking and selecting. - dayMousedown: function(ev) { - var _this = this; - var view = this.view; - var isSelectable = view.opt('selectable'); - var dates = null; // the inclusive dates of the selection. will be null if no selection - var start; // the inclusive start of the selection - var end; // the *exclusive* end of the selection - var dayEl; - - // this listener tracks a mousedown on a day element, and a subsequent drag. - // if the drag ends on the same day, it is a 'dayClick'. - // if 'selectable' is enabled, this listener also detects selections. - var dragListener = new DragListener(this.coordMap, { - //distance: 5, // needs more work if we want dayClick to fire correctly - scroll: view.opt('dragScroll'), - dragStart: function() { - view.unselect(); // since we could be rendering a new selection, we want to clear any old one - }, - cellOver: function(cell, date) { - if (dragListener.origDate) { // click needs to have started on a cell - - dayEl = _this.getCellDayEl(cell); - - dates = [ date, dragListener.origDate ].sort(dateCompare); - start = dates[0]; - end = dates[1].clone().add(_this.cellDuration); - - if (isSelectable) { - _this.renderSelection(start, end); - } - } - }, - cellOut: function(cell, date) { - dates = null; - _this.destroySelection(); - }, - listenStop: function(ev) { - if (dates) { // started and ended on a cell? - if (dates[0].isSame(dates[1])) { - view.trigger('dayClick', dayEl[0], start, ev); - } - if (isSelectable) { - // the selection will already have been rendered. just report it - view.reportSelection(start, end, ev); - } - } - } - }); - - dragListener.mousedown(ev); // start listening, which will eventually initiate a dragStart - }, - - - /* Event Dragging - ------------------------------------------------------------------------------------------------------------------*/ - - - // Renders a visual indication of a event being dragged over the given date(s). - // `end` can be null, as well as `seg`. See View's documentation on renderDrag for more info. - // A returned value of `true` signals that a mock "helper" event has been rendered. - renderDrag: function(start, end, seg) { - // subclasses must implement - }, - - - // Unrenders a visual indication of an event being dragged - destroyDrag: function() { - // subclasses must implement - }, - - - /* Event Resizing - ------------------------------------------------------------------------------------------------------------------*/ - - - // Renders a visual indication of an event being resized. - // `start` and `end` are the updated dates of the event. `seg` is the original segment object involved in the drag. - renderResize: function(start, end, seg) { - // subclasses must implement - }, - - - // Unrenders a visual indication of an event being resized. - destroyResize: function() { - // subclasses must implement - }, - - - /* Event Helper - ------------------------------------------------------------------------------------------------------------------*/ - - - // Renders a mock event over the given date(s). - // `end` can be null, in which case the mock event that is rendered will have a null end time. - // `sourceSeg` is the internal segment object involved in the drag. If null, something external is dragging. - renderRangeHelper: function(start, end, sourceSeg) { - var view = this.view; - var fakeEvent; - - // compute the end time if forced to do so (this is what EventManager does) - if (!end && view.opt('forceEventDuration')) { - end = view.calendar.getDefaultEventEnd(!start.hasTime(), start); - } - - fakeEvent = sourceSeg ? createObject(sourceSeg.event) : {}; // mask the original event object if possible - fakeEvent.start = start; - fakeEvent.end = end; - fakeEvent.allDay = !(start.hasTime() || (end && end.hasTime())); // freshly compute allDay - - // this extra className will be useful for differentiating real events from mock events in CSS - fakeEvent.className = (fakeEvent.className || []).concat('fc-helper'); - - // if something external is being dragged in, don't render a resizer - if (!sourceSeg) { - fakeEvent.editable = false; - } - - this.renderHelper(fakeEvent, sourceSeg); // do the actual rendering - }, - - - // Renders a mock event - renderHelper: function(event, sourceSeg) { - // subclasses must implement - }, - - - // Unrenders a mock event - destroyHelper: function() { - // subclasses must implement - }, - - - /* Selection - ------------------------------------------------------------------------------------------------------------------*/ - - - // Renders a visual indication of a selection. Will highlight by default but can be overridden by subclasses. - renderSelection: function(start, end) { - this.renderHighlight(start, end); - }, - - - // Unrenders any visual indications of a selection. Will unrender a highlight by default. - destroySelection: function() { - this.destroyHighlight(); - }, - - - /* Highlight - ------------------------------------------------------------------------------------------------------------------*/ - - - // Puts visual emphasis on a certain date range - renderHighlight: function(start, end) { - // subclasses should implement - }, - - - // Removes visual emphasis on a date range - destroyHighlight: function() { - // subclasses should implement - }, - - - - /* Generic rendering utilities for subclasses - ------------------------------------------------------------------------------------------------------------------*/ - - - // Renders a day-of-week header row - headHtml: function() { - return '' + - '
' + - '' + - '' + - this.rowHtml('head') + // leverages RowRenderer - '' + - '
' + - '
'; - }, - - - // Used by the `headHtml` method, via RowRenderer, for rendering the HTML of a day-of-week header cell - headCellHtml: function(row, col, date) { - var view = this.view; - var calendar = view.calendar; - var colFormat = view.opt('columnFormat'); - - return '' + - '' + - htmlEscape(calendar.formatDate(date, colFormat)) + - ''; - }, - - - // Renders the HTML for a single-day background cell - bgCellHtml: function(row, col, date) { - var view = this.view; - var classes = this.getDayClasses(date); - - classes.unshift('fc-day', view.widgetContentClass); - - return ''; - }, - - - // Computes HTML classNames for a single-day cell - getDayClasses: function(date) { - var view = this.view; - var today = view.calendar.getNow().stripTime(); - var classes = [ 'fc-' + dayIDs[date.day()] ]; - - if ( - view.name === 'month' && - date.month() != view.intervalStart.month() - ) { - classes.push('fc-other-month'); - } - - if (date.isSame(today, 'day')) { - classes.push( - 'fc-today', - view.highlightStateClass - ); - } - else if (date < today) { - classes.push('fc-past'); - } - else { - classes.push('fc-future'); - } - - return classes; - } - -}); - -;; - -/* Event-rendering and event-interaction methods for the abstract Grid class -----------------------------------------------------------------------------------------------------------------------*/ - -$.extend(Grid.prototype, { - - mousedOverSeg: null, // the segment object the user's mouse is over. null if over nothing - isDraggingSeg: false, // is a segment being dragged? boolean - isResizingSeg: false, // is a segment being resized? boolean - - - // Renders the given events onto the grid - renderEvents: function(events) { - // subclasses must implement - }, - - - // Retrieves all rendered segment objects in this grid - getSegs: function() { - // subclasses must implement - }, - - - // Unrenders all events. Subclasses should implement, calling this super-method first. - destroyEvents: function() { - this.triggerSegMouseout(); // trigger an eventMouseout if user's mouse is over an event - }, - - - // Renders a `el` property for each seg, and only returns segments that successfully rendered - renderSegs: function(segs, disableResizing) { - var view = this.view; - var html = ''; - var renderedSegs = []; - var i; - - // build a large concatenation of event segment HTML - for (i = 0; i < segs.length; i++) { - html += this.renderSegHtml(segs[i], disableResizing); - } - - // Grab individual elements from the combined HTML string. Use each as the default rendering. - // Then, compute the 'el' for each segment. An el might be null if the eventRender callback returned false. - $(html).each(function(i, node) { - var seg = segs[i]; - var el = view.resolveEventEl(seg.event, $(node)); - if (el) { - el.data('fc-seg', seg); // used by handlers - seg.el = el; - renderedSegs.push(seg); - } - }); - - return renderedSegs; - }, - - - // Generates the HTML for the default rendering of a segment - renderSegHtml: function(seg, disableResizing) { - // subclasses must implement - }, - - - // Converts an array of event objects into an array of segment objects - eventsToSegs: function(events, intervalStart, intervalEnd) { - var _this = this; - - return $.map(events, function(event) { - return _this.eventToSegs(event, intervalStart, intervalEnd); // $.map flattens all returned arrays together - }); - }, - - - // Slices a single event into an array of event segments. - // When `intervalStart` and `intervalEnd` are specified, intersect the events with that interval. - // Otherwise, let the subclass decide how it wants to slice the segments over the grid. - eventToSegs: function(event, intervalStart, intervalEnd) { - var eventStart = event.start.clone().stripZone(); // normalize - var eventEnd = this.view.calendar.getEventEnd(event).stripZone(); // compute (if necessary) and normalize - var segs; - var i, seg; - - if (intervalStart && intervalEnd) { - seg = intersectionToSeg(eventStart, eventEnd, intervalStart, intervalEnd); - segs = seg ? [ seg ] : []; - } - else { - segs = this.rangeToSegs(eventStart, eventEnd); // defined by the subclass - } - - // assign extra event-related properties to the segment objects - for (i = 0; i < segs.length; i++) { - seg = segs[i]; - seg.event = event; - seg.eventStartMS = +eventStart; - seg.eventDurationMS = eventEnd - eventStart; - } - - return segs; - }, - - - /* Handlers - ------------------------------------------------------------------------------------------------------------------*/ - - - // Attaches event-element-related handlers to the container element and leverage bubbling - bindSegHandlers: function() { - var _this = this; - var view = this.view; - - $.each( - { - mouseenter: function(seg, ev) { - _this.triggerSegMouseover(seg, ev); - }, - mouseleave: function(seg, ev) { - _this.triggerSegMouseout(seg, ev); - }, - click: function(seg, ev) { - return view.trigger('eventClick', this, seg.event, ev); // can return `false` to cancel - }, - mousedown: function(seg, ev) { - if ($(ev.target).is('.fc-resizer') && view.isEventResizable(seg.event)) { - _this.segResizeMousedown(seg, ev); - } - else if (view.isEventDraggable(seg.event)) { - _this.segDragMousedown(seg, ev); - } - } - }, - function(name, func) { - // attach the handler to the container element and only listen for real event elements via bubbling - _this.el.on(name, '.fc-event-container > *', function(ev) { - var seg = $(this).data('fc-seg'); // grab segment data. put there by View::renderEvents - - // only call the handlers if there is not a drag/resize in progress - if (seg && !_this.isDraggingSeg && !_this.isResizingSeg) { - return func.call(this, seg, ev); // `this` will be the event element - } - }); - } - ); - }, - - - // Updates internal state and triggers handlers for when an event element is moused over - triggerSegMouseover: function(seg, ev) { - if (!this.mousedOverSeg) { - this.mousedOverSeg = seg; - this.view.trigger('eventMouseover', seg.el[0], seg.event, ev); - } - }, - - - // Updates internal state and triggers handlers for when an event element is moused out. - // Can be given no arguments, in which case it will mouseout the segment that was previously moused over. - triggerSegMouseout: function(seg, ev) { - ev = ev || {}; // if given no args, make a mock mouse event - - if (this.mousedOverSeg) { - seg = seg || this.mousedOverSeg; // if given no args, use the currently moused-over segment - this.mousedOverSeg = null; - this.view.trigger('eventMouseout', seg.el[0], seg.event, ev); - } - }, - - - /* Dragging - ------------------------------------------------------------------------------------------------------------------*/ - - - // Called when the user does a mousedown on an event, which might lead to dragging. - // Generic enough to work with any type of Grid. - segDragMousedown: function(seg, ev) { - var _this = this; - var view = this.view; - var el = seg.el; - var event = seg.event; - var newStart, newEnd; - - // A clone of the original element that will move with the mouse - var mouseFollower = new MouseFollower(seg.el, { - parentEl: view.el, - opacity: view.opt('dragOpacity'), - revertDuration: view.opt('dragRevertDuration'), - zIndex: 2 // one above the .fc-view - }); - - // Tracks mouse movement over the *view's* coordinate map. Allows dragging and dropping between subcomponents - // of the view. - var dragListener = new DragListener(view.coordMap, { - distance: 5, - scroll: view.opt('dragScroll'), - listenStart: function(ev) { - mouseFollower.hide(); // don't show until we know this is a real drag - mouseFollower.start(ev); - }, - dragStart: function(ev) { - _this.triggerSegMouseout(seg, ev); // ensure a mouseout on the manipulated event has been reported - _this.isDraggingSeg = true; - view.hideEvent(event); // hide all event segments. our mouseFollower will take over - view.trigger('eventDragStart', el[0], event, ev, {}); // last argument is jqui dummy - }, - cellOver: function(cell, date) { - var origDate = seg.cellDate || dragListener.origDate; - var res = _this.computeDraggedEventDates(seg, origDate, date); - newStart = res.start; - newEnd = res.end; - - if (view.renderDrag(newStart, newEnd, seg)) { // have the view render a visual indication - mouseFollower.hide(); // if the view is already using a mock event "helper", hide our own - } - else { - mouseFollower.show(); - } - }, - cellOut: function() { // called before mouse moves to a different cell OR moved out of all cells - newStart = null; - view.destroyDrag(); // unrender whatever was done in view.renderDrag - mouseFollower.show(); // show in case we are moving out of all cells - }, - dragStop: function(ev) { - var hasChanged = newStart && !newStart.isSame(event.start); - - // do revert animation if hasn't changed. calls a callback when finished (whether animation or not) - mouseFollower.stop(!hasChanged, function() { - _this.isDraggingSeg = false; - view.destroyDrag(); - view.showEvent(event); - view.trigger('eventDragStop', el[0], event, ev, {}); // last argument is jqui dummy - - if (hasChanged) { - view.eventDrop(el[0], event, newStart, ev); // will rerender all events... - } - }); - }, - listenStop: function() { - mouseFollower.stop(); // put in listenStop in case there was a mousedown but the drag never started - } - }); - - dragListener.mousedown(ev); // start listening, which will eventually lead to a dragStart - }, - - - // Given a segment, the dates where a drag began and ended, calculates the Event Object's new start and end dates - computeDraggedEventDates: function(seg, dragStartDate, dropDate) { - var view = this.view; - var event = seg.event; - var start = event.start; - var end = view.calendar.getEventEnd(event); - var delta; - var newStart; - var newEnd; - - if (dropDate.hasTime() === dragStartDate.hasTime()) { - delta = dayishDiff(dropDate, dragStartDate); - newStart = start.clone().add(delta); - if (event.end === null) { // do we need to compute an end? - newEnd = null; - } - else { - newEnd = end.clone().add(delta); - } - } - else { - // if switching from day <-> timed, start should be reset to the dropped date, and the end cleared - newStart = dropDate; - newEnd = null; // end should be cleared - } - - return { start: newStart, end: newEnd }; - }, - - - /* Resizing - ------------------------------------------------------------------------------------------------------------------*/ - - - // Called when the user does a mousedown on an event's resizer, which might lead to resizing. - // Generic enough to work with any type of Grid. - segResizeMousedown: function(seg, ev) { - var _this = this; - var view = this.view; - var el = seg.el; - var event = seg.event; - var start = event.start; - var end = view.calendar.getEventEnd(event); - var newEnd = null; - var dragListener; - - function destroy() { // resets the rendering - _this.destroyResize(); - view.showEvent(event); - } - - // Tracks mouse movement over the *grid's* coordinate map - dragListener = new DragListener(this.coordMap, { - distance: 5, - scroll: view.opt('dragScroll'), - dragStart: function(ev) { - _this.triggerSegMouseout(seg, ev); // ensure a mouseout on the manipulated event has been reported - _this.isResizingSeg = true; - view.trigger('eventResizeStart', el[0], event, ev, {}); // last argument is jqui dummy - }, - cellOver: function(cell, date) { - // compute the new end. don't allow it to go before the event's start - if (date.isBefore(start)) { // allows comparing ambig to non-ambig - date = start; - } - newEnd = date.clone().add(_this.cellDuration); // make it an exclusive end - - if (newEnd.isSame(end)) { - newEnd = null; - destroy(); - } - else { - _this.renderResize(start, newEnd, seg); - view.hideEvent(event); - } - }, - cellOut: function() { // called before mouse moves to a different cell OR moved out of all cells - newEnd = null; - destroy(); - }, - dragStop: function(ev) { - _this.isResizingSeg = false; - destroy(); - view.trigger('eventResizeStop', el[0], event, ev, {}); // last argument is jqui dummy - - if (newEnd) { - view.eventResize(el[0], event, newEnd, ev); // will rerender all events... - } - } - }); - - dragListener.mousedown(ev); // start listening, which will eventually lead to a dragStart - }, - - - /* Rendering Utils - ------------------------------------------------------------------------------------------------------------------*/ - - - // Generic utility for generating the HTML classNames for an event segment's element - getSegClasses: function(seg, isDraggable, isResizable) { - var event = seg.event; - var classes = [ - 'fc-event', - seg.isStart ? 'fc-start' : 'fc-not-start', - seg.isEnd ? 'fc-end' : 'fc-not-end' - ].concat( - event.className, - event.source ? event.source.className : [] - ); - - if (isDraggable) { - classes.push('fc-draggable'); - } - if (isResizable) { - classes.push('fc-resizable'); - } - - return classes; - }, - - - // Utility for generating a CSS string with all the event skin-related properties - getEventSkinCss: function(event) { - var view = this.view; - var source = event.source || {}; - var eventColor = event.color; - var sourceColor = source.color; - var optionColor = view.opt('eventColor'); - var backgroundColor = - event.backgroundColor || - eventColor || - source.backgroundColor || - sourceColor || - view.opt('eventBackgroundColor') || - optionColor; - var borderColor = - event.borderColor || - eventColor || - source.borderColor || - sourceColor || - view.opt('eventBorderColor') || - optionColor; - var textColor = - event.textColor || - source.textColor || - view.opt('eventTextColor'); - var statements = []; - if (backgroundColor) { - statements.push('background-color:' + backgroundColor); - } - if (borderColor) { - statements.push('border-color:' + borderColor); - } - if (textColor) { - statements.push('color:' + textColor); - } - return statements.join(';'); - } - -}); - - -/* Event Segment Utilities -----------------------------------------------------------------------------------------------------------------------*/ - - -// A cmp function for determining which segments should take visual priority -function compareSegs(seg1, seg2) { - return seg1.eventStartMS - seg2.eventStartMS || // earlier events go first - seg2.eventDurationMS - seg1.eventDurationMS || // tie? longer events go first - seg2.event.allDay - seg1.event.allDay || // tie? put all-day events first (booleans cast to 0/1) - (seg1.event.title || '').localeCompare(seg2.event.title); // tie? alphabetically by title -} - - -;; - -/* A component that renders a grid of whole-days that runs horizontally. There can be multiple rows, one per week. -----------------------------------------------------------------------------------------------------------------------*/ - -function DayGrid(view) { - Grid.call(this, view); // call the super-constructor -} - - -DayGrid.prototype = createObject(Grid.prototype); // declare the super-class -$.extend(DayGrid.prototype, { - - numbersVisible: false, // should render a row for day/week numbers? manually set by the view - cellDuration: moment.duration({ days: 1 }), // required for Grid.event.js. Each cell is always a single day - bottomCoordPadding: 0, // hack for extending the hit area for the last row of the coordinate grid - - rowEls: null, // set of fake row elements - dayEls: null, // set of whole-day elements comprising the row's background - helperEls: null, // set of cell skeleton elements for rendering the mock event "helper" - highlightEls: null, // set of cell skeleton elements for rendering the highlight - - - // Renders the rows and columns into the component's `this.el`, which should already be assigned. - // isRigid determins whether the individual rows should ignore the contents and be a constant height. - // Relies on the view's colCnt and rowCnt. In the future, this component should probably be self-sufficient. - render: function(isRigid) { - var view = this.view; - var html = ''; - var row; - - for (row = 0; row < view.rowCnt; row++) { - html += this.dayRowHtml(row, isRigid); - } - this.el.html(html); - - this.rowEls = this.el.find('.fc-row'); - this.dayEls = this.el.find('.fc-day'); - - // run all the day cells through the dayRender callback - this.dayEls.each(function(i, node) { - var date = view.cellToDate(Math.floor(i / view.colCnt), i % view.colCnt); - view.trigger('dayRender', null, date, $(node)); - }); - - Grid.prototype.render.call(this); // call the super-method - }, - - - destroy: function() { - this.destroySegPopover(); - }, - - - // Generates the HTML for a single row. `row` is the row number. - dayRowHtml: function(row, isRigid) { - var view = this.view; - var classes = [ 'fc-row', 'fc-week', view.widgetContentClass ]; - - if (isRigid) { - classes.push('fc-rigid'); - } - - return '' + - '
' + - '
' + - '' + - this.rowHtml('day', row) + // leverages RowRenderer. calls dayCellHtml() - '
' + - '
' + - '
' + - '' + - (this.numbersVisible ? - '' + - this.rowHtml('number', row) + // leverages RowRenderer. View will define render method - '' : - '' - ) + - '
' + - '
' + - '
'; - }, - - - // Renders the HTML for a whole-day cell. Will eventually end up in the day-row's background. - // We go through a 'day' row type instead of just doing a 'bg' row type so that the View can do custom rendering - // specifically for whole-day rows, whereas a 'bg' might also be used for other purposes (TimeGrid bg for example). - dayCellHtml: function(row, col, date) { - return this.bgCellHtml(row, col, date); - }, - - - /* Coordinates & Cells - ------------------------------------------------------------------------------------------------------------------*/ - - - // Populates the empty `rows` and `cols` arrays with coordinates of the cells. For CoordGrid. - buildCoords: function(rows, cols) { - var colCnt = this.view.colCnt; - var e, n, p; - - this.dayEls.slice(0, colCnt).each(function(i, _e) { // iterate the first row of day elements - e = $(_e); - n = e.offset().left; - if (i) { - p[1] = n; - } - p = [ n ]; - cols[i] = p; - }); - p[1] = n + e.outerWidth(); - - this.rowEls.each(function(i, _e) { - e = $(_e); - n = e.offset().top; - if (i) { - p[1] = n; - } - p = [ n ]; - rows[i] = p; - }); - p[1] = n + e.outerHeight() + this.bottomCoordPadding; // hack to extend hit area of last row - }, - - - // Converts a cell to a date - getCellDate: function(cell) { - return this.view.cellToDate(cell); // leverages the View's cell system - }, - - - // Gets the whole-day element associated with the cell - getCellDayEl: function(cell) { - return this.dayEls.eq(cell.row * this.view.colCnt + cell.col); - }, - - - // Converts a range with an inclusive `start` and an exclusive `end` into an array of segment objects - rangeToSegs: function(start, end) { - return this.view.rangeToSegments(start, end); // leverages the View's cell system - }, - - - /* Event Drag Visualization - ------------------------------------------------------------------------------------------------------------------*/ - - - // Renders a visual indication of an event hovering over the given date(s). - // `end` can be null, as well as `seg`. See View's documentation on renderDrag for more info. - // A returned value of `true` signals that a mock "helper" event has been rendered. - renderDrag: function(start, end, seg) { - var opacity; - - // always render a highlight underneath - this.renderHighlight( - start, - end || this.view.calendar.getDefaultEventEnd(true, start) - ); - - // if a segment from the same calendar but another component is being dragged, render a helper event - if (seg && !seg.el.closest(this.el).length) { - - this.renderRangeHelper(start, end, seg); - - opacity = this.view.opt('dragOpacity'); - if (opacity !== undefined) { - this.helperEls.css('opacity', opacity); - } - - return true; // a helper has been rendered - } - }, - - - // Unrenders any visual indication of a hovering event - destroyDrag: function() { - this.destroyHighlight(); - this.destroyHelper(); - }, - - - /* Event Resize Visualization - ------------------------------------------------------------------------------------------------------------------*/ - - - // Renders a visual indication of an event being resized - renderResize: function(start, end, seg) { - this.renderHighlight(start, end); - this.renderRangeHelper(start, end, seg); - }, - - - // Unrenders a visual indication of an event being resized - destroyResize: function() { - this.destroyHighlight(); - this.destroyHelper(); - }, - - - /* Event Helper - ------------------------------------------------------------------------------------------------------------------*/ - - - // Renders a mock "helper" event. `sourceSeg` is the associated internal segment object. It can be null. - renderHelper: function(event, sourceSeg) { - var helperNodes = []; - var rowStructs = this.renderEventRows([ event ]); - - // inject each new event skeleton into each associated row - this.rowEls.each(function(row, rowNode) { - var rowEl = $(rowNode); // the .fc-row - var skeletonEl = $('
'); // will be absolutely positioned - var skeletonTop; - - // If there is an original segment, match the top position. Otherwise, put it at the row's top level - if (sourceSeg && sourceSeg.row === row) { - skeletonTop = sourceSeg.el.position().top; - } - else { - skeletonTop = rowEl.find('.fc-content-skeleton tbody').position().top; - } - - skeletonEl.css('top', skeletonTop) - .find('table') - .append(rowStructs[row].tbodyEl); - - rowEl.append(skeletonEl); - helperNodes.push(skeletonEl[0]); - }); - - this.helperEls = $(helperNodes); // array -> jQuery set - }, - - - // Unrenders any visual indication of a mock helper event - destroyHelper: function() { - if (this.helperEls) { - this.helperEls.remove(); - this.helperEls = null; - } - }, - - - /* Highlighting - ------------------------------------------------------------------------------------------------------------------*/ - - - // Renders an emphasis on the given date range. `start` is an inclusive, `end` is exclusive. - renderHighlight: function(start, end) { - var segs = this.rangeToSegs(start, end); - var highlightNodes = []; - var i, seg; - var el; - - // build an event skeleton for each row that needs it - for (i = 0; i < segs.length; i++) { - seg = segs[i]; - el = $( - this.highlightSkeletonHtml(seg.leftCol, seg.rightCol + 1) // make end exclusive - ); - el.appendTo(this.rowEls[seg.row]); - highlightNodes.push(el[0]); - } - - this.highlightEls = $(highlightNodes); // array -> jQuery set - }, - - - // Unrenders any visual emphasis on a date range - destroyHighlight: function() { - if (this.highlightEls) { - this.highlightEls.remove(); - this.highlightEls = null; - } - }, - - - // Generates the HTML used to build a single-row "highlight skeleton", a table that frames highlight cells - highlightSkeletonHtml: function(startCol, endCol) { - var colCnt = this.view.colCnt; - var cellHtml = ''; - - if (startCol > 0) { - cellHtml += ' elements that should be appended to each row's content skeleton. - // Returns an array of rowStruct objects (see the bottom of `renderEventRow`). - renderEventRows: function(events) { - var segs = this.eventsToSegs(events); - var rowStructs = []; - var segRows; - var row; - - segs = this.renderSegs(segs); // returns a new array with only visible segments - segRows = this.groupSegRows(segs); // group into nested arrays - - // iterate each row of segment groupings - for (row = 0; row < segRows.length; row++) { - rowStructs.push( - this.renderEventRow(row, segRows[row]) - ); - } - - return rowStructs; - }, - - - // Builds the HTML to be used for the default element for an individual segment - renderSegHtml: function(seg, disableResizing) { - var view = this.view; - var isRTL = view.opt('isRTL'); - var event = seg.event; - var isDraggable = view.isEventDraggable(event); - var isResizable = !disableResizing && event.allDay && seg.isEnd && view.isEventResizable(event); - var classes = this.getSegClasses(seg, isDraggable, isResizable); - var skinCss = this.getEventSkinCss(event); - var timeHtml = ''; - var titleHtml; - - classes.unshift('fc-day-grid-event'); - - // Only display a timed events time if it is the starting segment - if (!event.allDay && seg.isStart) { - timeHtml = '' + htmlEscape(view.getEventTimeText(event)) + ''; - } - - titleHtml = - '' + - (htmlEscape(event.title || '') || ' ') + // we always want one line of height - ''; - - return '' + - '
' + - (isRTL ? - titleHtml + ' ' + timeHtml : // put a natural space in between - timeHtml + ' ' + titleHtml // - ) + - '
' + - (isResizable ? - '
' : - '' - ) + - ''; - }, - - - // Given a row # and an array of segments all in the same row, render a
element, a skeleton that contains - // the segments. Returns object with a bunch of internal data about how the render was calculated. - renderEventRow: function(row, rowSegs) { - var view = this.view; - var colCnt = view.colCnt; - var segLevels = this.buildSegLevels(rowSegs); // group into sub-arrays of levels - var levelCnt = Math.max(1, segLevels.length); // ensure at least one level - var tbody = $(''); - var segMatrix = []; // lookup for which segments are rendered into which level+col cells - var cellMatrix = []; // lookup for all '); - - segMatrix.push([]); - cellMatrix.push([]); - loneCellMatrix.push([]); - - // levelCnt might be 1 even though there are no actual levels. protect against this. - // this single empty row is useful for styling. - if (levelSegs) { - for (j = 0; j < levelSegs.length; j++) { // iterate through segments in level - seg = levelSegs[j]; - - emptyCellsUntil(seg.leftCol); - - // create a container that occupies or more columns. append the event element. - td = $(' at a time and stop when we find one out of bounds - for (i = 0; i < trEls.length; i++) { - trEl = trEls.eq(i).removeClass('fc-limited'); // get and reveal - if (trEl.position().top + trEl.outerHeight() > rowHeight) { - return i; - } - } - - return false; // should not limit at all - }, - - - // Limits the given grid row to the maximum number of levels and injects "more" links if necessary. - // `row` is the row number. - // `levelLimit` is a number for the maximum (inclusive) number of levels allowed. - limitRow: function(row, levelLimit) { - var _this = this; - var view = this.view; - var rowStruct = this.rowStructs[row]; - var moreNodes = []; // array of "more" links and and segment elements past the limit - .addClass('fc-limited').get(); // hide elements and get a simple DOM-nodes array - - // iterate though segments in the last allowable level - for (i = 0; i < levelSegs.length; i++) { - seg = levelSegs[i]; - emptyCellsUntil(seg.leftCol); // process empty cells before the segment - - // determine *all* segments below `seg` that occupy the same columns - colSegsBelow = []; - totalSegsBelow = 0; - while (col <= seg.rightCol) { - cell = { row: row, col: col }; - segsBelow = this.getCellSegs(cell, levelLimit); - colSegsBelow.push(segsBelow); - totalSegsBelow += segsBelow.length; - col++; - } - - if (totalSegsBelow) { // do we need to replace this segment with one or many "more" links? - td = cellMatrix[levelLimit - 1][seg.leftCol]; // the segment's parent cell - rowspan = td.attr('rowspan') || 1; - segMoreNodes = []; - - // make a replacement '; - - html += - '' + - (!isRTL ? axisHtml : '') + - '"; - - slotTime.add(this.slotDuration); - } - - return html; - }, - - - // Parses various options into properties of this object - processOptions: function() { - var view = this.view; - var slotDuration = view.opt('slotDuration'); - var snapDuration = view.opt('snapDuration'); - - slotDuration = moment.duration(slotDuration); - snapDuration = snapDuration ? moment.duration(snapDuration) : slotDuration; - - this.slotDuration = slotDuration; - this.snapDuration = snapDuration; - this.cellDuration = snapDuration; // important to assign this for Grid.events.js - - this.minTime = moment.duration(view.opt('minTime')); - this.maxTime = moment.duration(view.opt('maxTime')); - }, - - - // Slices up a date range into a segment for each column - rangeToSegs: function(rangeStart, rangeEnd) { - var view = this.view; - var segs = []; - var seg; - var col; - var cellDate; - var colStart, colEnd; - - // normalize - rangeStart = rangeStart.clone().stripZone(); - rangeEnd = rangeEnd.clone().stripZone(); - - for (col = 0; col < view.colCnt; col++) { - cellDate = view.cellToDate(0, col); // use the View's cell system for this - colStart = cellDate.clone().time(this.minTime); - colEnd = cellDate.clone().time(this.maxTime); - seg = intersectionToSeg(rangeStart, rangeEnd, colStart, colEnd); - if (seg) { - seg.col = col; - segs.push(seg); - } - } - - return segs; - }, - - - /* Coordinates - ------------------------------------------------------------------------------------------------------------------*/ - - - // Called when there is a window resize/zoom and we need to recalculate coordinates for the grid - resize: function() { - this.computeSlatTops(); - this.updateSegVerticals(); - }, - - - // Populates the given empty `rows` and `cols` arrays with offset positions of the "snap" cells. - // "Snap" cells are different the slots because they might have finer granularity. - buildCoords: function(rows, cols) { - var colCnt = this.view.colCnt; - var originTop = this.el.offset().top; - var snapTime = moment.duration(+this.minTime); - var p = null; - var e, n; - - this.dayEls.slice(0, colCnt).each(function(i, _e) { - e = $(_e); - n = e.offset().left; - if (p) { - p[1] = n; - } - p = [ n ]; - cols[i] = p; - }); - p[1] = n + e.outerWidth(); - - p = null; - while (snapTime < this.maxTime) { - n = originTop + this.computeTimeTop(snapTime); - if (p) { - p[1] = n; - } - p = [ n ]; - rows.push(p); - snapTime.add(this.snapDuration); - } - p[1] = originTop + this.computeTimeTop(snapTime); // the position of the exclusive end - }, - - - // Gets the datetime for the given slot cell - getCellDate: function(cell) { - var view = this.view; - var calendar = view.calendar; - - return calendar.rezoneDate( // since we are adding a time, it needs to be in the calendar's timezone - view.cellToDate(0, cell.col) // View's coord system only accounts for start-of-day for column - .time(this.minTime + this.snapDuration * cell.row) - ); - }, - - - // Gets the element that represents the whole-day the cell resides on - getCellDayEl: function(cell) { - return this.dayEls.eq(cell.col); - }, - - - // Computes the top coordinate, relative to the bounds of the grid, of the given date. - // A `startOfDayDate` must be given for avoiding ambiguity over how to treat midnight. - computeDateTop: function(date, startOfDayDate) { - return this.computeTimeTop( - moment.duration( - date.clone().stripZone() - startOfDayDate.clone().stripTime() - ) - ); - }, - - - // Computes the top coordinate, relative to the bounds of the grid, of the given time (a Duration). - computeTimeTop: function(time) { - var slatCoverage = (time - this.minTime) / this.slotDuration; // floating-point value of # of slots covered - var slatIndex; - var slatRemainder; - var slatTop; - var slatBottom; - - // constrain. because minTime/maxTime might be customized - slatCoverage = Math.max(0, slatCoverage); - slatCoverage = Math.min(this.slatEls.length, slatCoverage); - - slatIndex = Math.floor(slatCoverage); // an integer index of the furthest whole slot - slatRemainder = slatCoverage - slatIndex; - slatTop = this.slatTops[slatIndex]; // the top position of the furthest whole slot - - if (slatRemainder) { // time spans part-way into the slot - slatBottom = this.slatTops[slatIndex + 1]; - return slatTop + (slatBottom - slatTop) * slatRemainder; // part-way between slots - } - else { - return slatTop; - } - }, - - - // Queries each `slatEl` for its position relative to the grid's container and stores it in `slatTops`. - // Includes the the bottom of the last slat as the last item in the array. - computeSlatTops: function() { - var tops = []; - var top; - - this.slatEls.each(function(i, node) { - top = $(node).position().top; - tops.push(top); - }); - - tops.push(top + this.slatEls.last().outerHeight()); // bottom of the last slat - - this.slatTops = tops; - }, - - - /* Event Drag Visualization - ------------------------------------------------------------------------------------------------------------------*/ - - - // Renders a visual indication of an event being dragged over the specified date(s). - // `end` and `seg` can be null. See View's documentation on renderDrag for more info. - renderDrag: function(start, end, seg) { - var opacity; - - if (seg) { // if there is event information for this drag, render a helper event - this.renderRangeHelper(start, end, seg); - - opacity = this.view.opt('dragOpacity'); - if (opacity !== undefined) { - this.helperEl.css('opacity', opacity); - } - - return true; // signal that a helper has been rendered - } - else { - // otherwise, just render a highlight - this.renderHighlight( - start, - end || this.view.calendar.getDefaultEventEnd(false, start) - ); - } - }, - - - // Unrenders any visual indication of an event being dragged - destroyDrag: function() { - this.destroyHelper(); - this.destroyHighlight(); - }, - - - /* Event Resize Visualization - ------------------------------------------------------------------------------------------------------------------*/ - - - // Renders a visual indication of an event being resized - renderResize: function(start, end, seg) { - this.renderRangeHelper(start, end, seg); - }, - - - // Unrenders any visual indication of an event being resized - destroyResize: function() { - this.destroyHelper(); - }, - - - /* Event Helper - ------------------------------------------------------------------------------------------------------------------*/ - - - // Renders a mock "helper" event. `sourceSeg` is the original segment object and might be null (an external drag) - renderHelper: function(event, sourceSeg) { - var res = this.renderEventTable([ event ]); - var tableEl = res.tableEl; - var segs = res.segs; - var i, seg; - var sourceEl; - - // Try to make the segment that is in the same row as sourceSeg look the same - for (i = 0; i < segs.length; i++) { - seg = segs[i]; - if (sourceSeg && sourceSeg.col === seg.col) { - sourceEl = sourceSeg.el; - seg.el.css({ - left: sourceEl.css('left'), - right: sourceEl.css('right'), - 'margin-left': sourceEl.css('margin-left'), - 'margin-right': sourceEl.css('margin-right') - }); - } - } - - this.helperEl = $('
') - .append(tableEl) - .appendTo(this.el); - }, - - - // Unrenders any mock helper event - destroyHelper: function() { - if (this.helperEl) { - this.helperEl.remove(); - this.helperEl = null; - } - }, - - - /* Selection - ------------------------------------------------------------------------------------------------------------------*/ - - - // Renders a visual indication of a selection. Overrides the default, which was to simply render a highlight. - renderSelection: function(start, end) { - if (this.view.opt('selectHelper')) { // this setting signals that a mock helper event should be rendered - this.renderRangeHelper(start, end); - } - else { - this.renderHighlight(start, end); - } - }, - - - // Unrenders any visual indication of a selection - destroySelection: function() { - this.destroyHelper(); - this.destroyHighlight(); - }, - - - /* Highlight - ------------------------------------------------------------------------------------------------------------------*/ - - - // Renders an emphasis on the given date range. `start` is inclusive. `end` is exclusive. - renderHighlight: function(start, end) { - this.highlightEl = $( - this.highlightSkeletonHtml(start, end) - ).appendTo(this.el); - }, - - - // Unrenders the emphasis on a date range - destroyHighlight: function() { - if (this.highlightEl) { - this.highlightEl.remove(); - this.highlightEl = null; - } - }, - - - // Generates HTML for a table element with containers in each column, responsible for absolutely positioning the - // highlight elements to cover the highlighted slots. - highlightSkeletonHtml: function(start, end) { - var view = this.view; - var segs = this.rangeToSegs(start, end); - var cellHtml = ''; - var col = 0; - var i, seg; - var dayDate; - var top, bottom; - - for (i = 0; i < segs.length; i++) { // loop through the segments. one per column - seg = segs[i]; - - // need empty cells beforehand? - if (col < seg.col) { - cellHtml += '
'; - - col++; - } - - // need empty cells after the last segment? - if (col < view.colCnt) { - cellHtml += ''; - } - }, - - - // Generates the HTML that will go before content-skeleton cells that display the day/week numbers. - // Queried by the DayGrid subcomponent. Ordering depends on isRTL. - numberIntroHtml: function(row) { - if (this.weekNumbersVisible) { - return '' + - ''; - } - }, - - - // Generates the HTML that goes before the day bg cells for each day-row. - // Queried by the DayGrid subcomponent. Ordering depends on isRTL. - dayIntroHtml: function() { - if (this.weekNumbersVisible) { - return ''; - } - }, - - - // Generates the HTML that goes before every other type of row generated by DayGrid. Ordering depends on isRTL. - // Affects helper-skeleton and highlight-skeleton rows. - introHtml: function() { - if (this.weekNumbersVisible) { - return ''; - } - }, - - - // Generates the HTML for the '; - }, - - - // Generates an HTML attribute string for setting the width of the week number column, if it is known - weekNumberStyleAttr: function() { - if (this.weekNumberWidth !== null) { - return 'style="width:' + this.weekNumberWidth + 'px"'; - } - return ''; - }, - - - // Determines whether each row should have a constant height - hasRigidRows: function() { - var eventLimit = this.opt('eventLimit'); - return eventLimit && typeof eventLimit !== 'number'; - }, - - - /* Dimensions - ------------------------------------------------------------------------------------------------------------------*/ - - - // Refreshes the horizontal dimensions of the view - updateWidth: function() { - if (this.weekNumbersVisible) { - // Make sure all week number cells running down the side have the same width. - // Record the width for cells created later. - this.weekNumberWidth = matchCellWidths( - this.el.find('.fc-week-number') - ); - } - }, - - - // Adjusts the vertical dimensions of the view to the specified values - setHeight: function(totalHeight, isAuto) { - var eventLimit = this.opt('eventLimit'); - var scrollerHeight; - - // reset all heights to be natural - unsetScroller(this.scrollerEl); - uncompensateScroll(this.headRowEl); - - this.dayGrid.destroySegPopover(); // kill the "more" popover if displayed - - // is the event limit a constant level number? - if (eventLimit && typeof eventLimit === 'number') { - this.dayGrid.limitRows(eventLimit); // limit the levels first so the height can redistribute after - } - - scrollerHeight = this.computeScrollerHeight(totalHeight); - this.setGridHeight(scrollerHeight, isAuto); - - // is the event limit dynamically calculated? - if (eventLimit && typeof eventLimit !== 'number') { - this.dayGrid.limitRows(eventLimit); // limit the levels after the grid's row heights have been set - } - - if (!isAuto && setPotentialScroller(this.scrollerEl, scrollerHeight)) { // using scrollbars? - - compensateScroll(this.headRowEl, getScrollbarWidths(this.scrollerEl)); - - // doing the scrollbar compensation might have created text overflow which created more height. redo - scrollerHeight = this.computeScrollerHeight(totalHeight); - this.scrollerEl.height(scrollerHeight); - - this.restoreScroll(); - } - }, - - - // Sets the height of just the DayGrid component in this view - setGridHeight: function(height, isAuto) { - if (isAuto) { - undistributeHeight(this.dayGrid.rowEls); // let the rows be their natural height with no expanding - } - else { - distributeHeight(this.dayGrid.rowEls, height, true); // true = compensate for height-hogging rows - } - }, - - - /* Events - ------------------------------------------------------------------------------------------------------------------*/ - - - // Renders the given events onto the view and populates the segments array - renderEvents: function(events) { - this.dayGrid.renderEvents(events); - - this.updateHeight(); // must compensate for events that overflow the row - - View.prototype.renderEvents.call(this, events); // call the super-method - }, - - - // Retrieves all segment objects that are rendered in the view - getSegs: function() { - return this.dayGrid.getSegs(); - }, - - - // Unrenders all event elements and clears internal segment data - destroyEvents: function() { - View.prototype.destroyEvents.call(this); // do this before dayGrid's segs have been cleared - - this.recordScroll(); // removing events will reduce height and mess with the scroll, so record beforehand - this.dayGrid.destroyEvents(); - - // we DON'T need to call updateHeight() because: - // A) a renderEvents() call always happens after this, which will eventually call updateHeight() - // B) in IE8, this causes a flash whenever events are rerendered - }, - - - /* Event Dragging - ------------------------------------------------------------------------------------------------------------------*/ - - - // Renders a visual indication of an event being dragged over the view. - // A returned value of `true` signals that a mock "helper" event has been rendered. - renderDrag: function(start, end, seg) { - return this.dayGrid.renderDrag(start, end, seg); - }, - - - // Unrenders the visual indication of an event being dragged over the view - destroyDrag: function() { - this.dayGrid.destroyDrag(); - }, - - - /* Selection - ------------------------------------------------------------------------------------------------------------------*/ - - - // Renders a visual indication of a selection - renderSelection: function(start, end) { - this.dayGrid.renderSelection(start, end); - }, - - - // Unrenders a visual indications of a selection - destroySelection: function() { - this.dayGrid.destroySelection(); - } - -}); - -;; - -/* A month view with day cells running in rows (one-per-week) and columns -----------------------------------------------------------------------------------------------------------------------*/ - -setDefaults({ - fixedWeekCount: true -}); - -fcViews.month = MonthView; // register the view - -function MonthView(calendar) { - BasicView.call(this, calendar); // call the super-constructor -} - - -MonthView.prototype = createObject(BasicView.prototype); // define the super-class -$.extend(MonthView.prototype, { - - name: 'month', - - - incrementDate: function(date, delta) { - return date.clone().stripTime().add(delta, 'months').startOf('month'); - }, - - - render: function(date) { - var rowCnt; - - this.intervalStart = date.clone().stripTime().startOf('month'); - this.intervalEnd = this.intervalStart.clone().add(1, 'months'); - - this.start = this.intervalStart.clone(); - this.start = this.skipHiddenDays(this.start); // move past the first week if no visible days - this.start.startOf('week'); - this.start = this.skipHiddenDays(this.start); // move past the first invisible days of the week - - this.end = this.intervalEnd.clone(); - this.end = this.skipHiddenDays(this.end, -1, true); // move in from the last week if no visible days - this.end.add((7 - this.end.weekday()) % 7, 'days'); // move to end of week if not already - this.end = this.skipHiddenDays(this.end, -1, true); // move in from the last invisible days of the week - - rowCnt = Math.ceil( // need to ceil in case there are hidden days - this.end.diff(this.start, 'weeks', true) // returnfloat=true - ); - if (this.isFixedWeeks()) { - this.end.add(6 - rowCnt, 'weeks'); - rowCnt = 6; - } - - this.title = this.calendar.formatDate(this.intervalStart, this.opt('titleFormat')); - - BasicView.prototype.render.call(this, rowCnt, this.getCellsPerWeek(), true); // call the super-method - }, - - - // Overrides the default BasicView behavior to have special multi-week auto-height logic - setGridHeight: function(height, isAuto) { - - isAuto = isAuto || this.opt('weekMode') === 'variable'; // LEGACY: weekMode is deprecated - - // if auto, make the height of each row the height that it would be if there were 6 weeks - if (isAuto) { - height *= this.rowCnt / 6; - } - - distributeHeight(this.dayGrid.rowEls, height, !isAuto); // if auto, don't compensate for height-hogging rows - }, - - - isFixedWeeks: function() { - var weekMode = this.opt('weekMode'); // LEGACY: weekMode is deprecated - if (weekMode) { - return weekMode === 'fixed'; // if any other type of weekMode, assume NOT fixed - } - - return this.opt('fixedWeekCount'); - } - -}); - -;; - -/* A week view with simple day cells running horizontally -----------------------------------------------------------------------------------------------------------------------*/ -// TODO: a WeekView mixin for calculating dates and titles - -fcViews.basicWeek = BasicWeekView; // register this view - -function BasicWeekView(calendar) { - BasicView.call(this, calendar); // call the super-constructor -} - - -BasicWeekView.prototype = createObject(BasicView.prototype); // define the super-class -$.extend(BasicWeekView.prototype, { - - name: 'basicWeek', - - - incrementDate: function(date, delta) { - return date.clone().stripTime().add(delta, 'weeks').startOf('week'); - }, - - - render: function(date) { - - this.intervalStart = date.clone().stripTime().startOf('week'); - this.intervalEnd = this.intervalStart.clone().add(1, 'weeks'); - - this.start = this.skipHiddenDays(this.intervalStart); - this.end = this.skipHiddenDays(this.intervalEnd, -1, true); - - this.title = this.calendar.formatRange( - this.start, - this.end.clone().subtract(1), // make inclusive by subtracting 1 ms - this.opt('titleFormat'), - ' \u2014 ' // emphasized dash - ); - - BasicView.prototype.render.call(this, 1, this.getCellsPerWeek(), false); // call the super-method - } - -}); -;; - -/* A view with a single simple day cell -----------------------------------------------------------------------------------------------------------------------*/ - -fcViews.basicDay = BasicDayView; // register this view - -function BasicDayView(calendar) { - BasicView.call(this, calendar); // call the super-constructor -} - - -BasicDayView.prototype = createObject(BasicView.prototype); // define the super-class -$.extend(BasicDayView.prototype, { - - name: 'basicDay', - - - incrementDate: function(date, delta) { - var out = date.clone().stripTime().add(delta, 'days'); - out = this.skipHiddenDays(out, delta < 0 ? -1 : 1); - return out; - }, - - - render: function(date) { - - this.start = this.intervalStart = date.clone().stripTime(); - this.end = this.intervalEnd = this.start.clone().add(1, 'days'); - - this.title = this.calendar.formatDate(this.start, this.opt('titleFormat')); - - BasicView.prototype.render.call(this, 1, 1, false); // call the super-method - } - -}); -;; - -/* An abstract class for all agenda-related views. Displays one more columns with time slots running vertically. -----------------------------------------------------------------------------------------------------------------------*/ -// Is a manager for the TimeGrid subcomponent and possibly the DayGrid subcomponent (if allDaySlot is on). -// Responsible for managing width/height. - -setDefaults({ - allDaySlot: true, - allDayText: 'all-day', - - scrollTime: '06:00:00', - - slotDuration: '00:30:00', - - axisFormat: generateAgendaAxisFormat, - timeFormat: { - agenda: generateAgendaTimeFormat - }, - - minTime: '00:00:00', - maxTime: '24:00:00', - slotEventOverlap: true -}); - -var AGENDA_ALL_DAY_EVENT_LIMIT = 5; - - -function generateAgendaAxisFormat(options, langData) { - return langData.longDateFormat('LT') - .replace(':mm', '(:mm)') - .replace(/(\Wmm)$/, '($1)') // like above, but for foreign langs - .replace(/\s*a$/i, 'a'); // convert AM/PM/am/pm to lowercase. remove any spaces beforehand -} - - -function generateAgendaTimeFormat(options, langData) { - return langData.longDateFormat('LT') - .replace(/\s*a$/i, ''); // remove trailing AM/PM -} - - -function AgendaView(calendar) { - View.call(this, calendar); // call the super-constructor - - this.timeGrid = new TimeGrid(this); - - if (this.opt('allDaySlot')) { // should we display the "all-day" area? - this.dayGrid = new DayGrid(this); // the all-day subcomponent of this view - - // the coordinate grid will be a combination of both subcomponents' grids - this.coordMap = new ComboCoordMap([ - this.dayGrid.coordMap, - this.timeGrid.coordMap - ]); - } - else { - this.coordMap = this.timeGrid.coordMap; - } -} - - -AgendaView.prototype = createObject(View.prototype); // define the super-class -$.extend(AgendaView.prototype, { - - timeGrid: null, // the main time-grid subcomponent of this view - dayGrid: null, // the "all-day" subcomponent. if all-day is turned off, this will be null - - axisWidth: null, // the width of the time axis running down the side - - noScrollRowEls: null, // set of fake row elements that must compensate when scrollerEl has scrollbars - - // when the time-grid isn't tall enough to occupy the given height, we render an
underneath - bottomRuleEl: null, - bottomRuleHeight: null, - - - /* Rendering - ------------------------------------------------------------------------------------------------------------------*/ - - - // Renders the view into `this.el`, which has already been assigned. - // `colCnt` has been calculated by a subclass and passed here. - render: function(colCnt) { - - // needed for cell-to-date and date-to-cell calculations in View - this.rowCnt = 1; - this.colCnt = colCnt; - - this.el.addClass('fc-agenda-view').html(this.renderHtml()); - - // the element that wraps the time-grid that will probably scroll - this.scrollerEl = this.el.find('.fc-time-grid-container'); - this.timeGrid.coordMap.containerEl = this.scrollerEl; // don't accept clicks/etc outside of this - - this.timeGrid.el = this.el.find('.fc-time-grid'); - this.timeGrid.render(); - - // the
that sometimes displays under the time-grid - this.bottomRuleEl = $('
') - .appendTo(this.timeGrid.el); // inject it into the time-grid - - if (this.dayGrid) { - this.dayGrid.el = this.el.find('.fc-day-grid'); - this.dayGrid.render(); - - // have the day-grid extend it's coordinate area over the
dividing the two grids - this.dayGrid.bottomCoordPadding = this.dayGrid.el.next('hr').outerHeight(); - } - - this.noScrollRowEls = this.el.find('.fc-row:not(.fc-scroller *)'); // fake rows not within the scroller - - View.prototype.render.call(this); // call the super-method - - this.resetScroll(); // do this after sizes have been set - }, - - - // Make subcomponents ready for cleanup - destroy: function() { - this.timeGrid.destroy(); - if (this.dayGrid) { - this.dayGrid.destroy(); - } - View.prototype.destroy.call(this); // call the super-method - }, - - - // Builds the HTML skeleton for the view. - // The day-grid and time-grid components will render inside containers defined by this HTML. - renderHtml: function() { - return '' + - '
'; - } - if (endCol > startCol) { - cellHtml += ''; - } - if (colCnt > endCol) { - cellHtml += ''; - } - - cellHtml = this.bookendCells(cellHtml, 'highlight'); - - return '' + - '
' + - '' + - '' + - cellHtml + - '' + - '
' + - '
'; - } - -}); - -;; - -/* Event-rendering methods for the DayGrid class -----------------------------------------------------------------------------------------------------------------------*/ - -$.extend(DayGrid.prototype, { - - segs: null, - rowStructs: null, // an array of objects, each holding information about a row's event-rendering - - - // Render the given events onto the Grid and return the rendered segments - renderEvents: function(events) { - var rowStructs = this.rowStructs = this.renderEventRows(events); - var segs = []; - - // append to each row's content skeleton - this.rowEls.each(function(i, rowNode) { - $(rowNode).find('.fc-content-skeleton > table').append( - rowStructs[i].tbodyEl - ); - segs.push.apply(segs, rowStructs[i].segs); - }); - - this.segs = segs; - }, - - - // Retrieves all segment objects that have been rendered - getSegs: function() { - return (this.segs || []).concat( - this.popoverSegs || [] // segs rendered in the "more" events popover - ); - }, - - - // Removes all rendered event elements - destroyEvents: function() { - var rowStructs; - var rowStruct; - - Grid.prototype.destroyEvents.call(this); // call the super-method - - rowStructs = this.rowStructs || []; - while ((rowStruct = rowStructs.pop())) { - rowStruct.tbodyEl.remove(); - } - - this.segs = null; - this.destroySegPopover(); // removes the "more.." events popover - }, - - - // Uses the given events array to generate
elements of the level+col matrix - var loneCellMatrix = []; // lookup for elements that only take up a single column - var i, levelSegs; - var col; - var tr; - var j, seg; - var td; - - // populates empty cells from the current column (`col`) to `endCol` - function emptyCellsUntil(endCol) { - while (col < endCol) { - // try to grab a cell from the level above and extend its rowspan. otherwise, create a fresh cell - td = (loneCellMatrix[i - 1] || [])[col]; - if (td) { - td.attr( - 'rowspan', - parseInt(td.attr('rowspan') || 1, 10) + 1 - ); - } - else { - td = $(''); - tr.append(td); - } - cellMatrix[i][col] = td; - loneCellMatrix[i][col] = td; - col++; - } - } - - for (i = 0; i < levelCnt; i++) { // iterate through all levels - levelSegs = segLevels[i]; - col = 0; - tr = $('
').append(seg.el); - if (seg.leftCol != seg.rightCol) { - td.attr('colspan', seg.rightCol - seg.leftCol + 1); - } - else { // a single-column segment - loneCellMatrix[i][col] = td; - } - - while (col <= seg.rightCol) { - cellMatrix[i][col] = td; - segMatrix[i][col] = seg; - col++; - } - - tr.append(td); - } - } - - emptyCellsUntil(colCnt); // finish off the row - this.bookendCells(tr, 'eventSkeleton'); - tbody.append(tr); - } - - return { // a "rowStruct" - row: row, // the row number - tbodyEl: tbody, - cellMatrix: cellMatrix, - segMatrix: segMatrix, - segLevels: segLevels, - segs: rowSegs - }; - }, - - - // Stacks a flat array of segments, which are all assumed to be in the same row, into subarrays of vertical levels. - buildSegLevels: function(segs) { - var levels = []; - var i, seg; - var j; - - // Give preference to elements with certain criteria, so they have - // a chance to be closer to the top. - segs.sort(compareSegs); - - for (i = 0; i < segs.length; i++) { - seg = segs[i]; - - // loop through levels, starting with the topmost, until the segment doesn't collide with other segments - for (j = 0; j < levels.length; j++) { - if (!isDaySegCollision(seg, levels[j])) { - break; - } - } - // `j` now holds the desired subrow index - seg.level = j; - - // create new level array if needed and append segment - (levels[j] || (levels[j] = [])).push(seg); - } - - // order segments left-to-right. very important if calendar is RTL - for (j = 0; j < levels.length; j++) { - levels[j].sort(compareDaySegCols); - } - - return levels; - }, - - - // Given a flat array of segments, return an array of sub-arrays, grouped by each segment's row - groupSegRows: function(segs) { - var view = this.view; - var segRows = []; - var i; - - for (i = 0; i < view.rowCnt; i++) { - segRows.push([]); - } - - for (i = 0; i < segs.length; i++) { - segRows[segs[i].row].push(segs[i]); - } - - return segRows; - } - -}); - - -// Computes whether two segments' columns collide. They are assumed to be in the same row. -function isDaySegCollision(seg, otherSegs) { - var i, otherSeg; - - for (i = 0; i < otherSegs.length; i++) { - otherSeg = otherSegs[i]; - - if ( - otherSeg.leftCol <= seg.rightCol && - otherSeg.rightCol >= seg.leftCol - ) { - return true; - } - } - - return false; -} - - -// A cmp function for determining the leftmost event -function compareDaySegCols(a, b) { - return a.leftCol - b.leftCol; -} - -;; - -/* Methods relate to limiting the number events for a given day on a DayGrid -----------------------------------------------------------------------------------------------------------------------*/ - -$.extend(DayGrid.prototype, { - - - segPopover: null, // the Popover that holds events that can't fit in a cell. null when not visible - popoverSegs: null, // an array of segment objects that the segPopover holds. null when not visible - - - destroySegPopover: function() { - if (this.segPopover) { - this.segPopover.hide(); // will trigger destruction of `segPopover` and `popoverSegs` - } - }, - - - // Limits the number of "levels" (vertically stacking layers of events) for each row of the grid. - // `levelLimit` can be false (don't limit), a number, or true (should be computed). - limitRows: function(levelLimit) { - var rowStructs = this.rowStructs || []; - var row; // row # - var rowLevelLimit; - - for (row = 0; row < rowStructs.length; row++) { - this.unlimitRow(row); - - if (!levelLimit) { - rowLevelLimit = false; - } - else if (typeof levelLimit === 'number') { - rowLevelLimit = levelLimit; - } - else { - rowLevelLimit = this.computeRowLevelLimit(row); - } - - if (rowLevelLimit !== false) { - this.limitRow(row, rowLevelLimit); - } - } - }, - - - // Computes the number of levels a row will accomodate without going outside its bounds. - // Assumes the row is "rigid" (maintains a constant height regardless of what is inside). - // `row` is the row number. - computeRowLevelLimit: function(row) { - var rowEl = this.rowEls.eq(row); // the containing "fake" row div - var rowHeight = rowEl.height(); // TODO: cache somehow? - var trEls = this.rowStructs[row].tbodyEl.children(); - var i, trEl; - - // Reveal one level
DOM nodes - var col = 0; // col # - var cell; - var levelSegs; // array of segment objects in the last allowable level, ordered left-to-right - var cellMatrix; // a matrix (by level, then column) of all jQuery elements in the row - var limitedNodes; // array of temporarily hidden level
DOM nodes - var i, seg; - var segsBelow; // array of segment objects below `seg` in the current `col` - var totalSegsBelow; // total number of segments below `seg` in any of the columns `seg` occupies - var colSegsBelow; // array of segment arrays, below seg, one for each column (offset from segs's first column) - var td, rowspan; - var segMoreNodes; // array of "more" cells that will stand-in for the current seg's cell - var j; - var moreTd, moreWrap, moreLink; - - // Iterates through empty level cells and places "more" links inside if need be - function emptyCellsUntil(endCol) { // goes from current `col` to `endCol` - while (col < endCol) { - cell = { row: row, col: col }; - segsBelow = _this.getCellSegs(cell, levelLimit); - if (segsBelow.length) { - td = cellMatrix[levelLimit - 1][col]; - moreLink = _this.renderMoreLink(cell, segsBelow); - moreWrap = $('
').append(moreLink); - td.append(moreWrap); - moreNodes.push(moreWrap[0]); - } - col++; - } - } - - if (levelLimit && levelLimit < rowStruct.segLevels.length) { // is it actually over the limit? - levelSegs = rowStruct.segLevels[levelLimit - 1]; - cellMatrix = rowStruct.cellMatrix; - - limitedNodes = rowStruct.tbodyEl.children().slice(levelLimit) // get level
for each column the segment occupies. will be one for each colspan - for (j = 0; j < colSegsBelow.length; j++) { - moreTd = $('').attr('rowspan', rowspan); - segsBelow = colSegsBelow[j]; - cell = { row: row, col: seg.leftCol + j }; - moreLink = this.renderMoreLink(cell, [ seg ].concat(segsBelow)); // count seg as hidden too - moreWrap = $('
').append(moreLink); - moreTd.append(moreWrap); - segMoreNodes.push(moreTd[0]); - moreNodes.push(moreTd[0]); - } - - td.addClass('fc-limited').after($(segMoreNodes)); // hide original
and inject replacements - limitedNodes.push(td[0]); - } - } - - emptyCellsUntil(view.colCnt); // finish off the level - rowStruct.moreEls = $(moreNodes); // for easy undoing later - rowStruct.limitedEls = $(limitedNodes); // for easy undoing later - } - }, - - - // Reveals all levels and removes all "more"-related elements for a grid's row. - // `row` is a row number. - unlimitRow: function(row) { - var rowStruct = this.rowStructs[row]; - - if (rowStruct.moreEls) { - rowStruct.moreEls.remove(); - rowStruct.moreEls = null; - } - - if (rowStruct.limitedEls) { - rowStruct.limitedEls.removeClass('fc-limited'); - rowStruct.limitedEls = null; - } - }, - - - // Renders an element that represents hidden event element for a cell. - // Responsible for attaching click handler as well. - renderMoreLink: function(cell, hiddenSegs) { - var _this = this; - var view = this.view; - - return $('') - .text( - this.getMoreLinkText(hiddenSegs.length) - ) - .on('click', function(ev) { - var clickOption = view.opt('eventLimitClick'); - var date = view.cellToDate(cell); - var moreEl = $(this); - var dayEl = _this.getCellDayEl(cell); - var allSegs = _this.getCellSegs(cell); - - // rescope the segments to be within the cell's date - var reslicedAllSegs = _this.resliceDaySegs(allSegs, date); - var reslicedHiddenSegs = _this.resliceDaySegs(hiddenSegs, date); - - if (typeof clickOption === 'function') { - // the returned value can be an atomic option - clickOption = view.trigger('eventLimitClick', null, { - date: date, - dayEl: dayEl, - moreEl: moreEl, - segs: reslicedAllSegs, - hiddenSegs: reslicedHiddenSegs - }, ev); - } - - if (clickOption === 'popover') { - _this.showSegPopover(date, cell, moreEl, reslicedAllSegs); - } - else if (typeof clickOption === 'string') { // a view name - view.calendar.zoomTo(date, clickOption); - } - }); - }, - - - // Reveals the popover that displays all events within a cell - showSegPopover: function(date, cell, moreLink, segs) { - var _this = this; - var view = this.view; - var moreWrap = moreLink.parent(); // the
wrapper around the - var topEl; // the element we want to match the top coordinate of - var options; - - if (view.rowCnt == 1) { - topEl = this.view.el; // will cause the popover to cover any sort of header - } - else { - topEl = this.rowEls.eq(cell.row); // will align with top of row - } - - options = { - className: 'fc-more-popover', - content: this.renderSegPopoverContent(date, segs), - parentEl: this.el, - top: topEl.offset().top, - autoHide: true, // when the user clicks elsewhere, hide the popover - viewportConstrain: view.opt('popoverViewportConstrain'), - hide: function() { - // destroy everything when the popover is hidden - _this.segPopover.destroy(); - _this.segPopover = null; - _this.popoverSegs = null; - } - }; - - // Determine horizontal coordinate. - // We use the moreWrap instead of the
to avoid border confusion. - if (view.opt('isRTL')) { - options.right = moreWrap.offset().left + moreWrap.outerWidth() + 1; // +1 to be over cell border - } - else { - options.left = moreWrap.offset().left - 1; // -1 to be over cell border - } - - this.segPopover = new Popover(options); - this.segPopover.show(); - }, - - - // Builds the inner DOM contents of the segment popover - renderSegPopoverContent: function(date, segs) { - var view = this.view; - var isTheme = view.opt('theme'); - var title = date.format(view.opt('dayPopoverFormat')); - var content = $( - '
' + - '' + - '' + - htmlEscape(title) + - '' + - '
' + - '
' + - '
' + - '
' + - '
' - ); - var segContainer = content.find('.fc-event-container'); - var i; - - // render each seg's `el` and only return the visible segs - segs = this.renderSegs(segs, true); // disableResizing=true - this.popoverSegs = segs; - - for (i = 0; i < segs.length; i++) { - - // because segments in the popover are not part of a grid coordinate system, provide a hint to any - // grids that want to do drag-n-drop about which cell it came from - segs[i].cellDate = date; - - segContainer.append(segs[i].el); - } - - return content; - }, - - - // Given the events within an array of segment objects, reslice them to be in a single day - resliceDaySegs: function(segs, dayDate) { - var events = $.map(segs, function(seg) { - return seg.event; - }); - var dayStart = dayDate.clone().stripTime(); - var dayEnd = dayStart.clone().add(1, 'days'); - - return this.eventsToSegs(events, dayStart, dayEnd); - }, - - - // Generates the text that should be inside a "more" link, given the number of events it represents - getMoreLinkText: function(num) { - var view = this.view; - var opt = view.opt('eventLimitText'); - - if (typeof opt === 'function') { - return opt(num); - } - else { - return '+' + num + ' ' + opt; - } - }, - - - // Returns segments within a given cell. - // If `startLevel` is specified, returns only events including and below that level. Otherwise returns all segs. - getCellSegs: function(cell, startLevel) { - var segMatrix = this.rowStructs[cell.row].segMatrix; - var level = startLevel || 0; - var segs = []; - var seg; - - while (level < segMatrix.length) { - seg = segMatrix[level][cell.col]; - if (seg) { - segs.push(seg); - } - level++; - } - - return segs; - } - -}); - -;; - -/* A component that renders one or more columns of vertical time slots -----------------------------------------------------------------------------------------------------------------------*/ - -function TimeGrid(view) { - Grid.call(this, view); // call the super-constructor -} - - -TimeGrid.prototype = createObject(Grid.prototype); // define the super-class -$.extend(TimeGrid.prototype, { - - slotDuration: null, // duration of a "slot", a distinct time segment on given day, visualized by lines - snapDuration: null, // granularity of time for dragging and selecting - - minTime: null, // Duration object that denotes the first visible time of any given day - maxTime: null, // Duration object that denotes the exclusive visible end time of any given day - - dayEls: null, // cells elements in the day-row background - slatEls: null, // elements running horizontally across all columns - - slatTops: null, // an array of top positions, relative to the container. last item holds bottom of last slot - - highlightEl: null, // cell skeleton element for rendering the highlight - helperEl: null, // cell skeleton element for rendering the mock event "helper" - - - // Renders the time grid into `this.el`, which should already be assigned. - // Relies on the view's colCnt. In the future, this component should probably be self-sufficient. - render: function() { - this.processOptions(); - - this.el.html(this.renderHtml()); - - this.dayEls = this.el.find('.fc-day'); - this.slatEls = this.el.find('.fc-slats tr'); - - this.computeSlatTops(); - - Grid.prototype.render.call(this); // call the super-method - }, - - - // Renders the basic HTML skeleton for the grid - renderHtml: function() { - return '' + - '
' + - '' + - this.rowHtml('slotBg') + // leverages RowRenderer, which will call slotBgCellHtml - '
' + - '
' + - '
' + - '' + - this.slatRowHtml() + - '
' + - '
'; - }, - - - // Renders the HTML for a vertical background cell behind the slots. - // This method is distinct from 'bg' because we wanted a new `rowType` so the View could customize the rendering. - slotBgCellHtml: function(row, col, date) { - return this.bgCellHtml(row, col, date); - }, - - - // Generates the HTML for the horizontal "slats" that run width-wise. Has a time axis on a side. Depends on RTL. - slatRowHtml: function() { - var view = this.view; - var calendar = view.calendar; - var isRTL = view.opt('isRTL'); - var html = ''; - var slotNormal = this.slotDuration.asMinutes() % 15 === 0; - var slotTime = moment.duration(+this.minTime); // wish there was .clone() for durations - var slotDate; // will be on the view's first day, but we only care about its time - var minutes; - var axisHtml; - - // Calculate the time for each slot - while (slotTime < this.maxTime) { - slotDate = view.start.clone().time(slotTime); // will be in UTC but that's good. to avoid DST issues - minutes = slotDate.minutes(); - - axisHtml = - '
' + - ((!slotNormal || !minutes) ? // if irregular slot duration, or on the hour, then display the time - '' + // for matchCellWidths - htmlEscape(calendar.formatDate(slotDate, view.opt('axisFormat'))) + - '' : - '' - ) + - '
' + - (isRTL ? axisHtml : '') + - "
'; - col = seg.col; - } - - // compute vertical position - dayDate = view.cellToDate(0, col); - top = this.computeDateTop(seg.start, dayDate); - bottom = this.computeDateTop(seg.end, dayDate); // the y position of the bottom edge - - // generate the cell HTML. bottom becomes negative because it needs to be a CSS value relative to the - // bottom edge of the zero-height container. - cellHtml += - '' + - '
' + - '
' + - '
' + - '
'; - } - - cellHtml = this.bookendCells(cellHtml, 'highlight'); - - return '' + - '
' + - '' + - '' + - cellHtml + - '' + - '
' + - '
'; - } - -}); - -;; - -/* Event-rendering methods for the TimeGrid class -----------------------------------------------------------------------------------------------------------------------*/ - -$.extend(TimeGrid.prototype, { - - segs: null, // segment objects rendered in the component. null of events haven't been rendered yet - eventSkeletonEl: null, // has cells with event-containers, which contain absolutely positioned event elements - - - // Renders the events onto the grid and returns an array of segments that have been rendered - renderEvents: function(events) { - var res = this.renderEventTable(events); - - this.eventSkeletonEl = $('
').append(res.tableEl); - this.el.append(this.eventSkeletonEl); - - this.segs = res.segs; - }, - - - // Retrieves rendered segment objects - getSegs: function() { - return this.segs || []; - }, - - - // Removes all event segment elements from the view - destroyEvents: function() { - Grid.prototype.destroyEvents.call(this); // call the super-method - - if (this.eventSkeletonEl) { - this.eventSkeletonEl.remove(); - this.eventSkeletonEl = null; - } - - this.segs = null; - }, - - - // Renders and returns the portion of the event-skeleton. - // Returns an object with properties 'tbodyEl' and 'segs'. - renderEventTable: function(events) { - var tableEl = $('
'); - var trEl = tableEl.find('tr'); - var segs = this.eventsToSegs(events); - var segCols; - var i, seg; - var col, colSegs; - var containerEl; - - segs = this.renderSegs(segs); // returns only the visible segs - segCols = this.groupSegCols(segs); // group into sub-arrays, and assigns 'col' to each seg - - this.computeSegVerticals(segs); // compute and assign top/bottom - - for (col = 0; col < segCols.length; col++) { // iterate each column grouping - colSegs = segCols[col]; - placeSlotSegs(colSegs); // compute horizontal coordinates, z-index's, and reorder the array - - containerEl = $('
'); - - // assign positioning CSS and insert into container - for (i = 0; i < colSegs.length; i++) { - seg = colSegs[i]; - seg.el.css(this.generateSegPositionCss(seg)); - - // if the height is short, add a className for alternate styling - if (seg.bottom - seg.top < 30) { - seg.el.addClass('fc-short'); - } - - containerEl.append(seg.el); - } - - trEl.append($('
').append(containerEl)); - } - - this.bookendCells(trEl, 'eventSkeleton'); - - return { - tableEl: tableEl, - segs: segs - }; - }, - - - // Refreshes the CSS top/bottom coordinates for each segment element. Probably after a window resize/zoom. - updateSegVerticals: function() { - var segs = this.segs; - var i; - - if (segs) { - this.computeSegVerticals(segs); - - for (i = 0; i < segs.length; i++) { - segs[i].el.css( - this.generateSegVerticalCss(segs[i]) - ); - } - } - }, - - - // For each segment in an array, computes and assigns its top and bottom properties - computeSegVerticals: function(segs) { - var i, seg; - - for (i = 0; i < segs.length; i++) { - seg = segs[i]; - seg.top = this.computeDateTop(seg.start, seg.start); - seg.bottom = this.computeDateTop(seg.end, seg.start); - } - }, - - - // Renders the HTML for a single event segment's default rendering - renderSegHtml: function(seg, disableResizing) { - var view = this.view; - var event = seg.event; - var isDraggable = view.isEventDraggable(event); - var isResizable = !disableResizing && seg.isEnd && view.isEventResizable(event); - var classes = this.getSegClasses(seg, isDraggable, isResizable); - var skinCss = this.getEventSkinCss(event); - var timeText; - var fullTimeText; // more verbose time text. for the print stylesheet - var startTimeText; // just the start time text - - classes.unshift('fc-time-grid-event'); - - if (view.isMultiDayEvent(event)) { // if the event appears to span more than one day... - // Don't display time text on segments that run entirely through a day. - // That would appear as midnight-midnight and would look dumb. - // Otherwise, display the time text for the *segment's* times (like 6pm-midnight or midnight-10am) - if (seg.isStart || seg.isEnd) { - timeText = view.getEventTimeText(seg.start, seg.end); - fullTimeText = view.getEventTimeText(seg.start, seg.end, 'LT'); - startTimeText = view.getEventTimeText(seg.start, null); - } - } else { - // Display the normal time text for the *event's* times - timeText = view.getEventTimeText(event); - fullTimeText = view.getEventTimeText(event, 'LT'); - startTimeText = view.getEventTimeText(event.start, null); - } - - return '' + - '
' + - (timeText ? - '
' + - '' + htmlEscape(timeText) + '' + - '
' : - '' - ) + - (event.title ? - '
' + - htmlEscape(event.title) + - '
' : - '' - ) + - '
' + - '
' + - (isResizable ? - '
' : - '' - ) + - ''; - }, - - - // Generates an object with CSS properties/values that should be applied to an event segment element. - // Contains important positioning-related properties that should be applied to any event element, customized or not. - generateSegPositionCss: function(seg) { - var view = this.view; - var isRTL = view.opt('isRTL'); - var shouldOverlap = view.opt('slotEventOverlap'); - var backwardCoord = seg.backwardCoord; // the left side if LTR. the right side if RTL. floating-point - var forwardCoord = seg.forwardCoord; // the right side if LTR. the left side if RTL. floating-point - var props = this.generateSegVerticalCss(seg); // get top/bottom first - var left; // amount of space from left edge, a fraction of the total width - var right; // amount of space from right edge, a fraction of the total width - - if (shouldOverlap) { - // double the width, but don't go beyond the maximum forward coordinate (1.0) - forwardCoord = Math.min(1, backwardCoord + (forwardCoord - backwardCoord) * 2); - } - - if (isRTL) { - left = 1 - forwardCoord; - right = backwardCoord; - } - else { - left = backwardCoord; - right = 1 - forwardCoord; - } - - props.zIndex = seg.level + 1; // convert from 0-base to 1-based - props.left = left * 100 + '%'; - props.right = right * 100 + '%'; - - if (shouldOverlap && seg.forwardPressure) { - // add padding to the edge so that forward stacked events don't cover the resizer's icon - props[isRTL ? 'marginLeft' : 'marginRight'] = 10 * 2; // 10 is a guesstimate of the icon's width - } - - return props; - }, - - - // Generates an object with CSS properties for the top/bottom coordinates of a segment element - generateSegVerticalCss: function(seg) { - return { - top: seg.top, - bottom: -seg.bottom // flipped because needs to be space beyond bottom edge of event container - }; - }, - - - // Given a flat array of segments, return an array of sub-arrays, grouped by each segment's col - groupSegCols: function(segs) { - var view = this.view; - var segCols = []; - var i; - - for (i = 0; i < view.colCnt; i++) { - segCols.push([]); - } - - for (i = 0; i < segs.length; i++) { - segCols[segs[i].col].push(segs[i]); - } - - return segCols; - } - -}); - - -// Given an array of segments that are all in the same column, sets the backwardCoord and forwardCoord on each. -// Also reorders the given array by date! -function placeSlotSegs(segs) { - var levels; - var level0; - var i; - - segs.sort(compareSegs); // order by date - levels = buildSlotSegLevels(segs); - computeForwardSlotSegs(levels); - - if ((level0 = levels[0])) { - - for (i = 0; i < level0.length; i++) { - computeSlotSegPressures(level0[i]); - } - - for (i = 0; i < level0.length; i++) { - computeSlotSegCoords(level0[i], 0, 0); - } - } -} - - -// Builds an array of segments "levels". The first level will be the leftmost tier of segments if the calendar is -// left-to-right, or the rightmost if the calendar is right-to-left. Assumes the segments are already ordered by date. -function buildSlotSegLevels(segs) { - var levels = []; - var i, seg; - var j; - - for (i=0; i seg2.top && seg1.top < seg2.bottom; -} - - -// A cmp function for determining which forward segment to rely on more when computing coordinates. -function compareForwardSlotSegs(seg1, seg2) { - // put higher-pressure first - return seg2.forwardPressure - seg1.forwardPressure || - // put segments that are closer to initial edge first (and favor ones with no coords yet) - (seg1.backwardCoord || 0) - (seg2.backwardCoord || 0) || - // do normal sorting... - compareSegs(seg1, seg2); -} - -;; - -/* An abstract class from which other views inherit from -----------------------------------------------------------------------------------------------------------------------*/ -// Newer methods should be written as prototype methods, not in the monster `View` function at the bottom. - -View.prototype = { - - calendar: null, // owner Calendar object - coordMap: null, // a CoordMap object for converting pixel regions to dates - el: null, // the view's containing element. set by Calendar - - // important Moments - start: null, // the date of the very first cell - end: null, // the date after the very last cell - intervalStart: null, // the start of the interval of time the view represents (1st of month for month view) - intervalEnd: null, // the exclusive end of the interval of time the view represents - - // used for cell-to-date and date-to-cell calculations - rowCnt: null, // # of weeks - colCnt: null, // # of days displayed in a week - - isSelected: false, // boolean whether cells are user-selected or not - - // subclasses can optionally use a scroll container - scrollerEl: null, // the element that will most likely scroll when content is too tall - scrollTop: null, // cached vertical scroll value - - // classNames styled by jqui themes - widgetHeaderClass: null, - widgetContentClass: null, - highlightStateClass: null, - - // document handlers, bound to `this` object - documentMousedownProxy: null, - documentDragStartProxy: null, - - - // Serves as a "constructor" to suppliment the monster `View` constructor below - init: function() { - var tm = this.opt('theme') ? 'ui' : 'fc'; - - this.widgetHeaderClass = tm + '-widget-header'; - this.widgetContentClass = tm + '-widget-content'; - this.highlightStateClass = tm + '-state-highlight'; - - // save references to `this`-bound handlers - this.documentMousedownProxy = $.proxy(this, 'documentMousedown'); - this.documentDragStartProxy = $.proxy(this, 'documentDragStart'); - }, - - - // Renders the view inside an already-defined `this.el`. - // Subclasses should override this and then call the super method afterwards. - render: function() { - this.updateSize(); - this.trigger('viewRender', this, this, this.el); - - // attach handlers to document. do it here to allow for destroy/rerender - $(document) - .on('mousedown', this.documentMousedownProxy) - .on('dragstart', this.documentDragStartProxy); // jqui drag - }, - - - // Clears all view rendering, event elements, and unregisters handlers - destroy: function() { - this.unselect(); - this.trigger('viewDestroy', this, this, this.el); - this.destroyEvents(); - this.el.empty(); // removes inner contents but leaves the element intact - - $(document) - .off('mousedown', this.documentMousedownProxy) - .off('dragstart', this.documentDragStartProxy); - }, - - - // Used to determine what happens when the users clicks next/prev. Given -1 for prev, 1 for next. - // Should apply the delta to `date` (a Moment) and return it. - incrementDate: function(date, delta) { - // subclasses should implement - }, - - - /* Dimensions - ------------------------------------------------------------------------------------------------------------------*/ - - - // Refreshes anything dependant upon sizing of the container element of the grid - updateSize: function(isResize) { - if (isResize) { - this.recordScroll(); - } - this.updateHeight(); - this.updateWidth(); - }, - - - // Refreshes the horizontal dimensions of the calendar - updateWidth: function() { - // subclasses should implement - }, - - - // Refreshes the vertical dimensions of the calendar - updateHeight: function() { - var calendar = this.calendar; // we poll the calendar for height information - - this.setHeight( - calendar.getSuggestedViewHeight(), - calendar.isHeightAuto() - ); - }, - - - // Updates the vertical dimensions of the calendar to the specified height. - // if `isAuto` is set to true, height becomes merely a suggestion and the view should use its "natural" height. - setHeight: function(height, isAuto) { - // subclasses should implement - }, - - - // Given the total height of the view, return the number of pixels that should be used for the scroller. - // Utility for subclasses. - computeScrollerHeight: function(totalHeight) { - var both = this.el.add(this.scrollerEl); - var otherHeight; // cumulative height of everything that is not the scrollerEl in the view (header+borders) - - // fuckin IE8/9/10/11 sometimes returns 0 for dimensions. this weird hack was the only thing that worked - both.css({ - position: 'relative', // cause a reflow, which will force fresh dimension recalculation - left: -1 // ensure reflow in case the el was already relative. negative is less likely to cause new scroll - }); - otherHeight = this.el.outerHeight() - this.scrollerEl.height(); // grab the dimensions - both.css({ position: '', left: '' }); // undo hack - - return totalHeight - otherHeight; - }, - - - // Called for remembering the current scroll value of the scroller. - // Should be called before there is a destructive operation (like removing DOM elements) that might inadvertently - // change the scroll of the container. - recordScroll: function() { - if (this.scrollerEl) { - this.scrollTop = this.scrollerEl.scrollTop(); - } - }, - - - // Set the scroll value of the scroller to the previously recorded value. - // Should be called after we know the view's dimensions have been restored following some type of destructive - // operation (like temporarily removing DOM elements). - restoreScroll: function() { - if (this.scrollTop !== null) { - this.scrollerEl.scrollTop(this.scrollTop); - } - }, - - - /* Events - ------------------------------------------------------------------------------------------------------------------*/ - - - // Renders the events onto the view. - // Should be overriden by subclasses. Subclasses should call the super-method afterwards. - renderEvents: function(events) { - this.segEach(function(seg) { - this.trigger('eventAfterRender', seg.event, seg.event, seg.el); - }); - this.trigger('eventAfterAllRender'); - }, - - - // Removes event elements from the view. - // Should be overridden by subclasses. Should call this super-method FIRST, then subclass DOM destruction. - destroyEvents: function() { - this.segEach(function(seg) { - this.trigger('eventDestroy', seg.event, seg.event, seg.el); - }); - }, - - - // Given an event and the default element used for rendering, returns the element that should actually be used. - // Basically runs events and elements through the eventRender hook. - resolveEventEl: function(event, el) { - var custom = this.trigger('eventRender', event, event, el); - - if (custom === false) { // means don't render at all - el = null; - } - else if (custom && custom !== true) { - el = $(custom); - } - - return el; - }, - - - // Hides all rendered event segments linked to the given event - showEvent: function(event) { - this.segEach(function(seg) { - seg.el.css('visibility', ''); - }, event); - }, - - - // Shows all rendered event segments linked to the given event - hideEvent: function(event) { - this.segEach(function(seg) { - seg.el.css('visibility', 'hidden'); - }, event); - }, - - - // Iterates through event segments. Goes through all by default. - // If the optional `event` argument is specified, only iterates through segments linked to that event. - // The `this` value of the callback function will be the view. - segEach: function(func, event) { - var segs = this.getSegs(); - var i; - - for (i = 0; i < segs.length; i++) { - if (!event || segs[i].event._id === event._id) { - func.call(this, segs[i]); - } - } - }, - - - // Retrieves all the rendered segment objects for the view - getSegs: function() { - // subclasses must implement - }, - - - /* Event Drag Visualization - ------------------------------------------------------------------------------------------------------------------*/ - - - // Renders a visual indication of an event hovering over the specified date. - // `end` is a Moment and might be null. - // `seg` might be null. if specified, it is the segment object of the event being dragged. - // otherwise, an external event from outside the calendar is being dragged. - renderDrag: function(start, end, seg) { - // subclasses should implement - }, - - - // Unrenders a visual indication of event hovering - destroyDrag: function() { - // subclasses should implement - }, - - - // Handler for accepting externally dragged events being dropped in the view. - // Gets called when jqui's 'dragstart' is fired. - documentDragStart: function(ev, ui) { - var _this = this; - var dropDate = null; - var dragListener; - - if (this.opt('droppable')) { // only listen if this setting is on - - // listener that tracks mouse movement over date-associated pixel regions - dragListener = new DragListener(this.coordMap, { - cellOver: function(cell, date) { - dropDate = date; - _this.renderDrag(date); - }, - cellOut: function() { - dropDate = null; - _this.destroyDrag(); - } - }); - - // gets called, only once, when jqui drag is finished - $(document).one('dragstop', function(ev, ui) { - _this.destroyDrag(); - if (dropDate) { - _this.trigger('drop', ev.target, dropDate, ev, ui); - } - }); - - dragListener.startDrag(ev); // start listening immediately - } - }, - - - /* Selection - ------------------------------------------------------------------------------------------------------------------*/ - - - // Selects a date range on the view. `start` and `end` are both Moments. - // `ev` is the native mouse event that begin the interaction. - select: function(start, end, ev) { - this.unselect(ev); - this.renderSelection(start, end); - this.reportSelection(start, end, ev); - }, - - - // Renders a visual indication of the selection - renderSelection: function(start, end) { - // subclasses should implement - }, - - - // Called when a new selection is made. Updates internal state and triggers handlers. - reportSelection: function(start, end, ev) { - this.isSelected = true; - this.trigger('select', null, start, end, ev); - }, - - - // Undoes a selection. updates in the internal state and triggers handlers. - // `ev` is the native mouse event that began the interaction. - unselect: function(ev) { - if (this.isSelected) { - this.isSelected = false; - this.destroySelection(); - this.trigger('unselect', null, ev); - } - }, - - - // Unrenders a visual indication of selection - destroySelection: function() { - // subclasses should implement - }, - - - // Handler for unselecting when the user clicks something and the 'unselectAuto' setting is on - documentMousedown: function(ev) { - var ignore; - - // is there a selection, and has the user made a proper left click? - if (this.isSelected && this.opt('unselectAuto') && isPrimaryMouseButton(ev)) { - - // only unselect if the clicked element is not identical to or inside of an 'unselectCancel' element - ignore = this.opt('unselectCancel'); - if (!ignore || !$(ev.target).closest(ignore).length) { - this.unselect(ev); - } - } - } - -}; - - -// We are mixing JavaScript OOP design patterns here by putting methods and member variables in the closed scope of the -// constructor. Going forward, methods should be part of the prototype. -function View(calendar) { - var t = this; - - // exports - t.calendar = calendar; - t.opt = opt; - t.trigger = trigger; - t.isEventDraggable = isEventDraggable; - t.isEventResizable = isEventResizable; - t.eventDrop = eventDrop; - t.eventResize = eventResize; - - // imports - var reportEventChange = calendar.reportEventChange; - - // locals - var options = calendar.options; - var nextDayThreshold = moment.duration(options.nextDayThreshold); - - - t.init(); // the "constructor" that concerns the prototype methods - - - function opt(name) { - var v = options[name]; - if ($.isPlainObject(v) && !isForcedAtomicOption(name)) { - return smartProperty(v, t.name); - } - return v; - } - - - function trigger(name, thisObj) { - return calendar.trigger.apply( - calendar, - [name, thisObj || t].concat(Array.prototype.slice.call(arguments, 2), [t]) - ); - } - - - - /* Event Editable Boolean Calculations - ------------------------------------------------------------------------------*/ - - - function isEventDraggable(event) { - var source = event.source || {}; - - return firstDefined( - event.startEditable, - source.startEditable, - opt('eventStartEditable'), - event.editable, - source.editable, - opt('editable') - ); - } - - - function isEventResizable(event) { - var source = event.source || {}; - - return firstDefined( - event.durationEditable, - source.durationEditable, - opt('eventDurationEditable'), - event.editable, - source.editable, - opt('editable') - ); - } - - - - /* Event Elements - ------------------------------------------------------------------------------*/ - - - // Compute the text that should be displayed on an event's element. - // Based off the settings of the view. Possible signatures: - // .getEventTimeText(event, formatStr) - // .getEventTimeText(startMoment, endMoment, formatStr) - // .getEventTimeText(startMoment, null, formatStr) - // `timeFormat` is used but the `formatStr` argument can be used to override. - t.getEventTimeText = function(event, formatStr) { - var start; - var end; - - if (typeof event === 'object' && typeof formatStr === 'object') { - // first two arguments are actually moments (or null). shift arguments. - start = event; - end = formatStr; - formatStr = arguments[2]; - } - else { - // otherwise, an event object was the first argument - start = event.start; - end = event.end; - } - - formatStr = formatStr || opt('timeFormat'); - - if (end && opt('displayEventEnd')) { - return calendar.formatRange(start, end, formatStr); - } - else { - return calendar.formatDate(start, formatStr); - } - }; - - - - /* Event Modification Reporting - ---------------------------------------------------------------------------------*/ - - - function eventDrop(el, event, newStart, ev) { - var mutateResult = calendar.mutateEvent(event, newStart, null); - - trigger( - 'eventDrop', - el, - event, - mutateResult.dateDelta, - function() { - mutateResult.undo(); - reportEventChange(); - }, - ev, - {} // jqui dummy - ); - - reportEventChange(); - } - - - function eventResize(el, event, newEnd, ev) { - var mutateResult = calendar.mutateEvent(event, null, newEnd); - - trigger( - 'eventResize', - el, - event, - mutateResult.durationDelta, - function() { - mutateResult.undo(); - reportEventChange(); - }, - ev, - {} // jqui dummy - ); - - reportEventChange(); - } - - - // ==================================================================================================== - // Utilities for day "cells" - // ==================================================================================================== - // The "basic" views are completely made up of day cells. - // The "agenda" views have day cells at the top "all day" slot. - // This was the obvious common place to put these utilities, but they should be abstracted out into - // a more meaningful class (like DayEventRenderer). - // ==================================================================================================== - - - // For determining how a given "cell" translates into a "date": - // - // 1. Convert the "cell" (row and column) into a "cell offset" (the # of the cell, cronologically from the first). - // Keep in mind that column indices are inverted with isRTL. This is taken into account. - // - // 2. Convert the "cell offset" to a "day offset" (the # of days since the first visible day in the view). - // - // 3. Convert the "day offset" into a "date" (a Moment). - // - // The reverse transformation happens when transforming a date into a cell. - - - // exports - t.isHiddenDay = isHiddenDay; - t.skipHiddenDays = skipHiddenDays; - t.getCellsPerWeek = getCellsPerWeek; - t.dateToCell = dateToCell; - t.dateToDayOffset = dateToDayOffset; - t.dayOffsetToCellOffset = dayOffsetToCellOffset; - t.cellOffsetToCell = cellOffsetToCell; - t.cellToDate = cellToDate; - t.cellToCellOffset = cellToCellOffset; - t.cellOffsetToDayOffset = cellOffsetToDayOffset; - t.dayOffsetToDate = dayOffsetToDate; - t.rangeToSegments = rangeToSegments; - t.isMultiDayEvent = isMultiDayEvent; - - - // internals - var hiddenDays = opt('hiddenDays') || []; // array of day-of-week indices that are hidden - var isHiddenDayHash = []; // is the day-of-week hidden? (hash with day-of-week-index -> bool) - var cellsPerWeek; - var dayToCellMap = []; // hash from dayIndex -> cellIndex, for one week - var cellToDayMap = []; // hash from cellIndex -> dayIndex, for one week - var isRTL = opt('isRTL'); - - - // initialize important internal variables - (function() { - - if (opt('weekends') === false) { - hiddenDays.push(0, 6); // 0=sunday, 6=saturday - } - - // Loop through a hypothetical week and determine which - // days-of-week are hidden. Record in both hashes (one is the reverse of the other). - for (var dayIndex=0, cellIndex=0; dayIndex<7; dayIndex++) { - dayToCellMap[dayIndex] = cellIndex; - isHiddenDayHash[dayIndex] = $.inArray(dayIndex, hiddenDays) != -1; - if (!isHiddenDayHash[dayIndex]) { - cellToDayMap[cellIndex] = dayIndex; - cellIndex++; - } - } - - cellsPerWeek = cellIndex; - if (!cellsPerWeek) { - throw 'invalid hiddenDays'; // all days were hidden? bad. - } - - })(); - - - // Is the current day hidden? - // `day` is a day-of-week index (0-6), or a Moment - function isHiddenDay(day) { - if (moment.isMoment(day)) { - day = day.day(); - } - return isHiddenDayHash[day]; - } - - - function getCellsPerWeek() { - return cellsPerWeek; - } - - - // Incrementing the current day until it is no longer a hidden day, returning a copy. - // If the initial value of `date` is not a hidden day, don't do anything. - // Pass `isExclusive` as `true` if you are dealing with an end date. - // `inc` defaults to `1` (increment one day forward each time) - function skipHiddenDays(date, inc, isExclusive) { - var out = date.clone(); - inc = inc || 1; - while ( - isHiddenDayHash[(out.day() + (isExclusive ? inc : 0) + 7) % 7] - ) { - out.add(inc, 'days'); - } - return out; - } - - - // - // TRANSFORMATIONS: cell -> cell offset -> day offset -> date - // - - // cell -> date (combines all transformations) - // Possible arguments: - // - row, col - // - { row:#, col: # } - function cellToDate() { - var cellOffset = cellToCellOffset.apply(null, arguments); - var dayOffset = cellOffsetToDayOffset(cellOffset); - var date = dayOffsetToDate(dayOffset); - return date; - } - - // cell -> cell offset - // Possible arguments: - // - row, col - // - { row:#, col:# } - function cellToCellOffset(row, col) { - var colCnt = t.colCnt; - - // rtl variables. wish we could pre-populate these. but where? - var dis = isRTL ? -1 : 1; - var dit = isRTL ? colCnt - 1 : 0; - - if (typeof row == 'object') { - col = row.col; - row = row.row; - } - var cellOffset = row * colCnt + (col * dis + dit); // column, adjusted for RTL (dis & dit) - - return cellOffset; - } - - // cell offset -> day offset - function cellOffsetToDayOffset(cellOffset) { - var day0 = t.start.day(); // first date's day of week - cellOffset += dayToCellMap[day0]; // normlize cellOffset to beginning-of-week - return Math.floor(cellOffset / cellsPerWeek) * 7 + // # of days from full weeks - cellToDayMap[ // # of days from partial last week - (cellOffset % cellsPerWeek + cellsPerWeek) % cellsPerWeek // crazy math to handle negative cellOffsets - ] - - day0; // adjustment for beginning-of-week normalization - } - - // day offset -> date - function dayOffsetToDate(dayOffset) { - return t.start.clone().add(dayOffset, 'days'); - } - - - // - // TRANSFORMATIONS: date -> day offset -> cell offset -> cell - // - - // date -> cell (combines all transformations) - function dateToCell(date) { - var dayOffset = dateToDayOffset(date); - var cellOffset = dayOffsetToCellOffset(dayOffset); - var cell = cellOffsetToCell(cellOffset); - return cell; - } - - // date -> day offset - function dateToDayOffset(date) { - return date.clone().stripTime().diff(t.start, 'days'); - } - - // day offset -> cell offset - function dayOffsetToCellOffset(dayOffset) { - var day0 = t.start.day(); // first date's day of week - dayOffset += day0; // normalize dayOffset to beginning-of-week - return Math.floor(dayOffset / 7) * cellsPerWeek + // # of cells from full weeks - dayToCellMap[ // # of cells from partial last week - (dayOffset % 7 + 7) % 7 // crazy math to handle negative dayOffsets - ] - - dayToCellMap[day0]; // adjustment for beginning-of-week normalization - } - - // cell offset -> cell (object with row & col keys) - function cellOffsetToCell(cellOffset) { - var colCnt = t.colCnt; - - // rtl variables. wish we could pre-populate these. but where? - var dis = isRTL ? -1 : 1; - var dit = isRTL ? colCnt - 1 : 0; - - var row = Math.floor(cellOffset / colCnt); - var col = ((cellOffset % colCnt + colCnt) % colCnt) * dis + dit; // column, adjusted for RTL (dis & dit) - return { - row: row, - col: col - }; - } - - - // - // Converts a date range into an array of segment objects. - // "Segments" are horizontal stretches of time, sliced up by row. - // A segment object has the following properties: - // - row - // - cols - // - isStart - // - isEnd - // - function rangeToSegments(start, end) { - - var rowCnt = t.rowCnt; - var colCnt = t.colCnt; - var segments = []; // array of segments to return - - // day offset for given date range - var dayRange = computeDayRange(start, end); // convert to a whole-day range - var rangeDayOffsetStart = dateToDayOffset(dayRange.start); - var rangeDayOffsetEnd = dateToDayOffset(dayRange.end); // an exclusive value - - // first and last cell offset for the given date range - // "last" implies inclusivity - var rangeCellOffsetFirst = dayOffsetToCellOffset(rangeDayOffsetStart); - var rangeCellOffsetLast = dayOffsetToCellOffset(rangeDayOffsetEnd) - 1; - - // loop through all the rows in the view - for (var row=0; row
' + - '' + // needed for matchCellWidths - htmlEscape(this.opt('weekNumberTitle')) + - '' + - '' + - '' + // needed for matchCellWidths - this.calendar.calculateWeekNumber(this.cellToDate(row, 0)) + - '' + - 's of the "number" row in the DayGrid's content skeleton. - // The number row will only exist if either day numbers or week numbers are turned on. - numberCellHtml: function(row, col, date) { - var classes; - - if (!this.dayNumbersVisible) { // if there are week numbers but not day numbers - return ''; // will create an empty space above events :( - } - - classes = this.dayGrid.getDayClasses(date); - classes.unshift('fc-day-number'); - - return '' + - '' + - date.date() + - '
' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '
' + - this.timeGrid.headHtml() + // render the day-of-week headers - '
' + - (this.dayGrid ? - '
' + - '
' : - '' - ) + - '
' + - '
' + - '
' + - '
'; - }, - - - // Generates the HTML that will go before the day-of week header cells. - // Queried by the TimeGrid subcomponent when generating rows. Ordering depends on isRTL. - headIntroHtml: function() { - var date; - var weekNumber; - var weekTitle; - var weekText; - - if (this.opt('weekNumbers')) { - date = this.cellToDate(0, 0); - weekNumber = this.calendar.calculateWeekNumber(date); - weekTitle = this.opt('weekNumberTitle'); - - if (this.opt('isRTL')) { - weekText = weekNumber + weekTitle; - } - else { - weekText = weekTitle + weekNumber; - } - - return '' + - '' + - '' + // needed for matchCellWidths - htmlEscape(weekText) + - '' + - ''; - } - else { - return ''; - } - }, - - - // Generates the HTML that goes before the all-day cells. - // Queried by the DayGrid subcomponent when generating rows. Ordering depends on isRTL. - dayIntroHtml: function() { - return '' + - '' + - '' + // needed for matchCellWidths - (this.opt('allDayHtml') || htmlEscape(this.opt('allDayText'))) + - '' + - ''; - }, - - - // Generates the HTML that goes before the bg of the TimeGrid slot area. Long vertical column. - slotBgIntroHtml: function() { - return ''; - }, - - - // Generates the HTML that goes before all other types of cells. - // Affects content-skeleton, helper-skeleton, highlight-skeleton for both the time-grid and day-grid. - // Queried by the TimeGrid and DayGrid subcomponents when generating rows. Ordering depends on isRTL. - introHtml: function() { - return ''; - }, - - - // Generates an HTML attribute string for setting the width of the axis, if it is known - axisStyleAttr: function() { - if (this.axisWidth !== null) { - return 'style="width:' + this.axisWidth + 'px"'; - } - return ''; - }, - - - /* Dimensions - ------------------------------------------------------------------------------------------------------------------*/ - - updateSize: function(isResize) { - if (isResize) { - this.timeGrid.resize(); - } - View.prototype.updateSize.call(this, isResize); - }, - - - // Refreshes the horizontal dimensions of the view - updateWidth: function() { - // make all axis cells line up, and record the width so newly created axis cells will have it - this.axisWidth = matchCellWidths(this.el.find('.fc-axis')); - }, - - - // Adjusts the vertical dimensions of the view to the specified values - setHeight: function(totalHeight, isAuto) { - var eventLimit; - var scrollerHeight; - - if (this.bottomRuleHeight === null) { - // calculate the height of the rule the very first time - this.bottomRuleHeight = this.bottomRuleEl.outerHeight(); - } - this.bottomRuleEl.hide(); // .show() will be called later if this
is necessary - - // reset all dimensions back to the original state - this.scrollerEl.css('overflow', ''); - unsetScroller(this.scrollerEl); - uncompensateScroll(this.noScrollRowEls); - - // limit number of events in the all-day area - if (this.dayGrid) { - this.dayGrid.destroySegPopover(); // kill the "more" popover if displayed - - eventLimit = this.opt('eventLimit'); - if (eventLimit && typeof eventLimit !== 'number') { - eventLimit = AGENDA_ALL_DAY_EVENT_LIMIT; // make sure "auto" goes to a real number - } - if (eventLimit) { - this.dayGrid.limitRows(eventLimit); - } - } - - if (!isAuto) { // should we force dimensions of the scroll container, or let the contents be natural height? - - scrollerHeight = this.computeScrollerHeight(totalHeight); - if (setPotentialScroller(this.scrollerEl, scrollerHeight)) { // using scrollbars? - - // make the all-day and header rows lines up - compensateScroll(this.noScrollRowEls, getScrollbarWidths(this.scrollerEl)); - - // the scrollbar compensation might have changed text flow, which might affect height, so recalculate - // and reapply the desired height to the scroller. - scrollerHeight = this.computeScrollerHeight(totalHeight); - this.scrollerEl.height(scrollerHeight); - - this.restoreScroll(); - } - else { // no scrollbars - // still, force a height and display the bottom rule (marks the end of day) - this.scrollerEl.height(scrollerHeight).css('overflow', 'hidden'); // in case
goes outside - this.bottomRuleEl.show(); - } - } - }, - - - // Sets the scroll value of the scroller to the intial pre-configured state prior to allowing the user to change it. - resetScroll: function() { - var _this = this; - var scrollTime = moment.duration(this.opt('scrollTime')); - var top = this.timeGrid.computeTimeTop(scrollTime); - - // zoom can give weird floating-point values. rather scroll a little bit further - top = Math.ceil(top); - - if (top) { - top++; // to overcome top border that slots beyond the first have. looks better - } - - function scroll() { - _this.scrollerEl.scrollTop(top); - } - - scroll(); - setTimeout(scroll, 0); // overrides any previous scroll state made by the browser - }, - - - /* Events - ------------------------------------------------------------------------------------------------------------------*/ - - - // Renders events onto the view and populates the View's segment array - renderEvents: function(events) { - var dayEvents = []; - var timedEvents = []; - var daySegs = []; - var timedSegs; - var i; - - // separate the events into all-day and timed - for (i = 0; i < events.length; i++) { - if (events[i].allDay) { - dayEvents.push(events[i]); - } - else { - timedEvents.push(events[i]); - } - } - - // render the events in the subcomponents - timedSegs = this.timeGrid.renderEvents(timedEvents); - if (this.dayGrid) { - daySegs = this.dayGrid.renderEvents(dayEvents); - } - - // the all-day area is flexible and might have a lot of events, so shift the height - this.updateHeight(); - - View.prototype.renderEvents.call(this, events); // call the super-method - }, - - - // Retrieves all segment objects that are rendered in the view - getSegs: function() { - return this.timeGrid.getSegs().concat( - this.dayGrid ? this.dayGrid.getSegs() : [] - ); - }, - - - // Unrenders all event elements and clears internal segment data - destroyEvents: function() { - View.prototype.destroyEvents.call(this); // do this before the grids' segs have been cleared - - // if destroyEvents is being called as part of an event rerender, renderEvents will be called shortly - // after, so remember what the scroll value was so we can restore it. - this.recordScroll(); - - // destroy the events in the subcomponents - this.timeGrid.destroyEvents(); - if (this.dayGrid) { - this.dayGrid.destroyEvents(); - } - - // we DON'T need to call updateHeight() because: - // A) a renderEvents() call always happens after this, which will eventually call updateHeight() - // B) in IE8, this causes a flash whenever events are rerendered - }, - - - /* Event Dragging - ------------------------------------------------------------------------------------------------------------------*/ - - - // Renders a visual indication of an event being dragged over the view. - // A returned value of `true` signals that a mock "helper" event has been rendered. - renderDrag: function(start, end, seg) { - if (start.hasTime()) { - return this.timeGrid.renderDrag(start, end, seg); - } - else if (this.dayGrid) { - return this.dayGrid.renderDrag(start, end, seg); - } - }, - - - // Unrenders a visual indications of an event being dragged over the view - destroyDrag: function() { - this.timeGrid.destroyDrag(); - if (this.dayGrid) { - this.dayGrid.destroyDrag(); - } - }, - - - /* Selection - ------------------------------------------------------------------------------------------------------------------*/ - - - // Renders a visual indication of a selection - renderSelection: function(start, end) { - if (start.hasTime() || end.hasTime()) { - this.timeGrid.renderSelection(start, end); - } - else if (this.dayGrid) { - this.dayGrid.renderSelection(start, end); - } - }, - - - // Unrenders a visual indications of a selection - destroySelection: function() { - this.timeGrid.destroySelection(); - if (this.dayGrid) { - this.dayGrid.destroySelection(); - } - } - -}); - -;; - -/* A week view with an all-day cell area at the top, and a time grid below -----------------------------------------------------------------------------------------------------------------------*/ -// TODO: a WeekView mixin for calculating dates and titles - -fcViews.agendaWeek = AgendaWeekView; // register the view - -function AgendaWeekView(calendar) { - AgendaView.call(this, calendar); // call the super-constructor -} - - -AgendaWeekView.prototype = createObject(AgendaView.prototype); // define the super-class -$.extend(AgendaWeekView.prototype, { - - name: 'agendaWeek', - - - incrementDate: function(date, delta) { - return date.clone().stripTime().add(delta, 'weeks').startOf('week'); - }, - - - render: function(date) { - - this.intervalStart = date.clone().stripTime().startOf('week'); - this.intervalEnd = this.intervalStart.clone().add(1, 'weeks'); - - this.start = this.skipHiddenDays(this.intervalStart); - this.end = this.skipHiddenDays(this.intervalEnd, -1, true); - - this.title = this.calendar.formatRange( - this.start, - this.end.clone().subtract(1), // make inclusive by subtracting 1 ms - this.opt('titleFormat'), - ' \u2014 ' // emphasized dash - ); - - AgendaView.prototype.render.call(this, this.getCellsPerWeek()); // call the super-method - } - -}); - -;; - -/* A day view with an all-day cell area at the top, and a time grid below -----------------------------------------------------------------------------------------------------------------------*/ - -fcViews.agendaDay = AgendaDayView; // register the view - -function AgendaDayView(calendar) { - AgendaView.call(this, calendar); // call the super-constructor -} - - -AgendaDayView.prototype = createObject(AgendaView.prototype); // define the super-class -$.extend(AgendaDayView.prototype, { - - name: 'agendaDay', - - - incrementDate: function(date, delta) { - var out = date.clone().stripTime().add(delta, 'days'); - out = this.skipHiddenDays(out, delta < 0 ? -1 : 1); - return out; - }, - - - render: function(date) { - - this.start = this.intervalStart = date.clone().stripTime(); - this.end = this.intervalEnd = this.start.clone().add(1, 'days'); - - this.title = this.calendar.formatDate(this.start, this.opt('titleFormat')); - - AgendaView.prototype.render.call(this, 1); // call the super-method - } - -}); - -;; - +/*! + * FullCalendar v2.1.1 + * Docs & License: http://arshaw.com/fullcalendar/ + * (c) 2013 Adam Shaw + */ + +(function(factory) { + if (typeof define === 'function' && define.amd) { + define([ 'jquery', 'moment' ], factory); + } + else { + factory(jQuery, moment); + } +})(function($, moment) { + +;; + +var defaults = { + + lang: 'en', + + defaultTimedEventDuration: '02:00:00', + defaultAllDayEventDuration: { days: 1 }, + forceEventDuration: false, + nextDayThreshold: '09:00:00', // 9am + + // display + defaultView: 'month', + aspectRatio: 1.35, + header: { + left: 'title', + center: '', + right: 'today prev,next' + }, + weekends: true, + weekNumbers: false, + + weekNumberTitle: 'W', + weekNumberCalculation: 'local', + + //editable: false, + + // event ajax + lazyFetching: true, + startParam: 'start', + endParam: 'end', + timezoneParam: 'timezone', + + timezone: false, + + //allDayDefault: undefined, + + // time formats + titleFormat: { + month: 'MMMM YYYY', // like "September 1986". each language will override this + week: 'll', // like "Sep 4 1986" + day: 'LL' // like "September 4 1986" + }, + columnFormat: { + month: 'ddd', // like "Sat" + week: generateWeekColumnFormat, + day: 'dddd' // like "Saturday" + }, + timeFormat: { // for event elements + 'default': generateShortTimeFormat + }, + + displayEventEnd: { + month: false, + basicWeek: false, + 'default': true + }, + + // locale + isRTL: false, + defaultButtonText: { + prev: "prev", + next: "next", + prevYear: "prev year", + nextYear: "next year", + today: 'today', + month: 'month', + week: 'week', + day: 'day' + }, + + buttonIcons: { + prev: 'left-single-arrow', + next: 'right-single-arrow', + prevYear: 'left-double-arrow', + nextYear: 'right-double-arrow' + }, + + // jquery-ui theming + theme: false, + themeButtonIcons: { + prev: 'circle-triangle-w', + next: 'circle-triangle-e', + prevYear: 'seek-prev', + nextYear: 'seek-next' + }, + + dragOpacity: .75, + dragRevertDuration: 500, + dragScroll: true, + + //selectable: false, + unselectAuto: true, + + dropAccept: '*', + + eventLimit: false, + eventLimitText: 'more', + eventLimitClick: 'popover', + dayPopoverFormat: 'LL', + + handleWindowResize: true, + windowResizeDelay: 200 // milliseconds before a rerender happens + +}; + + +function generateShortTimeFormat(options, langData) { + return langData.longDateFormat('LT') + .replace(':mm', '(:mm)') + .replace(/(\Wmm)$/, '($1)') // like above, but for foreign langs + .replace(/\s*a$/i, 't'); // convert to AM/PM/am/pm to lowercase one-letter. remove any spaces beforehand +} + + +function generateWeekColumnFormat(options, langData) { + var format = langData.longDateFormat('L'); // for the format like "MM/DD/YYYY" + format = format.replace(/^Y+[^\w\s]*|[^\w\s]*Y+$/g, ''); // strip the year off the edge, as well as other misc non-whitespace chars + if (options.isRTL) { + format += ' ddd'; // for RTL, add day-of-week to end + } + else { + format = 'ddd ' + format; // for LTR, add day-of-week to beginning + } + return format; +} + + +var langOptionHash = { + en: { + columnFormat: { + week: 'ddd M/D' // override for english. different from the generated default, which is MM/DD + }, + dayPopoverFormat: 'dddd, MMMM D' + } +}; + + +// right-to-left defaults +var rtlDefaults = { + header: { + left: 'next,prev today', + center: '', + right: 'title' + }, + buttonIcons: { + prev: 'right-single-arrow', + next: 'left-single-arrow', + prevYear: 'right-double-arrow', + nextYear: 'left-double-arrow' + }, + themeButtonIcons: { + prev: 'circle-triangle-e', + next: 'circle-triangle-w', + nextYear: 'seek-prev', + prevYear: 'seek-next' + } +}; + +;; + +var fc = $.fullCalendar = { version: "2.1.1" }; +var fcViews = fc.views = {}; + + +$.fn.fullCalendar = function(options) { + var args = Array.prototype.slice.call(arguments, 1); // for a possible method call + var res = this; // what this function will return (this jQuery object by default) + + this.each(function(i, _element) { // loop each DOM element involved + var element = $(_element); + var calendar = element.data('fullCalendar'); // get the existing calendar object (if any) + var singleRes; // the returned value of this single method call + + // a method call + if (typeof options === 'string') { + if (calendar && $.isFunction(calendar[options])) { + singleRes = calendar[options].apply(calendar, args); + if (!i) { + res = singleRes; // record the first method call result + } + if (options === 'destroy') { // for the destroy method, must remove Calendar object data + element.removeData('fullCalendar'); + } + } + } + // a new calendar initialization + else if (!calendar) { // don't initialize twice + calendar = new Calendar(element, options); + element.data('fullCalendar', calendar); + calendar.render(); + } + }); + + return res; +}; + + +// function for adding/overriding defaults +function setDefaults(d) { + mergeOptions(defaults, d); +} + + +// Recursively combines option hash-objects. +// Better than `$.extend(true, ...)` because arrays are not traversed/copied. +// +// called like: +// mergeOptions(target, obj1, obj2, ...) +// +function mergeOptions(target) { + + function mergeIntoTarget(name, value) { + if ($.isPlainObject(value) && $.isPlainObject(target[name]) && !isForcedAtomicOption(name)) { + // merge into a new object to avoid destruction + target[name] = mergeOptions({}, target[name], value); // combine. `value` object takes precedence + } + else if (value !== undefined) { // only use values that are set and not undefined + target[name] = value; + } + } + + for (var i=1; i").prependTo(element); + + header = new Header(t, options); + headerElement = header.render(); + if (headerElement) { + element.prepend(headerElement); + } + + changeView(options.defaultView); + + if (options.handleWindowResize) { + windowResizeProxy = debounce(windowResize, options.windowResizeDelay); // prevents rapid calls + $(window).resize(windowResizeProxy); + } + } + + + function destroy() { + + if (currentView) { + currentView.destroy(); + } + + header.destroy(); + content.remove(); + element.removeClass('fc fc-ltr fc-rtl fc-unthemed ui-widget'); + + $(window).unbind('resize', windowResizeProxy); + } + + + function elementVisible() { + return element.is(':visible'); + } + + + + // View Rendering + // ----------------------------------------------------------------------------------- + + + function changeView(viewName) { + renderView(0, viewName); + } + + + // Renders a view because of a date change, view-type change, or for the first time + function renderView(delta, viewName) { + ignoreWindowResize++; + + // if viewName is changing, destroy the old view + if (currentView && viewName && currentView.name !== viewName) { + header.deactivateButton(currentView.name); + freezeContentHeight(); // prevent a scroll jump when view element is removed + if (currentView.start) { // rendered before? + currentView.destroy(); + } + currentView.el.remove(); + currentView = null; + } + + // if viewName changed, or the view was never created, create a fresh view + if (!currentView && viewName) { + currentView = new fcViews[viewName](t); + currentView.el = $("
").appendTo(content); + header.activateButton(viewName); + } + + if (currentView) { + + // let the view determine what the delta means + if (delta) { + date = currentView.incrementDate(date, delta); + } + + // render or rerender the view + if ( + !currentView.start || // never rendered before + delta || // explicit date window change + !date.isWithin(currentView.intervalStart, currentView.intervalEnd) // implicit date window change + ) { + if (elementVisible()) { + + freezeContentHeight(); + if (currentView.start) { // rendered before? + currentView.destroy(); + } + currentView.render(date); + unfreezeContentHeight(); + + // need to do this after View::render, so dates are calculated + updateTitle(); + updateTodayButton(); + + getAndRenderEvents(); + } + } + } + + unfreezeContentHeight(); // undo any lone freezeContentHeight calls + ignoreWindowResize--; + } + + + + // Resizing + // ----------------------------------------------------------------------------------- + + + t.getSuggestedViewHeight = function() { + if (suggestedViewHeight === undefined) { + calcSize(); + } + return suggestedViewHeight; + }; + + + t.isHeightAuto = function() { + return options.contentHeight === 'auto' || options.height === 'auto'; + }; + + + function updateSize(shouldRecalc) { + if (elementVisible()) { + + if (shouldRecalc) { + _calcSize(); + } + + ignoreWindowResize++; + currentView.updateSize(true); // isResize=true. will poll getSuggestedViewHeight() and isHeightAuto() + ignoreWindowResize--; + + return true; // signal success + } + } + + + function calcSize() { + if (elementVisible()) { + _calcSize(); + } + } + + + function _calcSize() { // assumes elementVisible + if (typeof options.contentHeight === 'number') { // exists and not 'auto' + suggestedViewHeight = options.contentHeight; + } + else if (typeof options.height === 'number') { // exists and not 'auto' + suggestedViewHeight = options.height - (headerElement ? headerElement.outerHeight(true) : 0); + } + else { + suggestedViewHeight = Math.round(content.width() / Math.max(options.aspectRatio, .5)); + } + } + + + function windowResize(ev) { + if ( + !ignoreWindowResize && + ev.target === window && // so we don't process jqui "resize" events that have bubbled up + currentView.start // view has already been rendered + ) { + if (updateSize(true)) { + currentView.trigger('windowResize', _element); + } + } + } + + + + /* Event Fetching/Rendering + -----------------------------------------------------------------------------*/ + // TODO: going forward, most of this stuff should be directly handled by the view + + + function refetchEvents() { // can be called as an API method + destroyEvents(); // so that events are cleared before user starts waiting for AJAX + fetchAndRenderEvents(); + } + + + function renderEvents() { // destroys old events if previously rendered + if (elementVisible()) { + freezeContentHeight(); + currentView.destroyEvents(); // no performance cost if never rendered + currentView.renderEvents(events); + unfreezeContentHeight(); + } + } + + + function destroyEvents() { + freezeContentHeight(); + currentView.destroyEvents(); + unfreezeContentHeight(); + } + + + function getAndRenderEvents() { + if (!options.lazyFetching || isFetchNeeded(currentView.start, currentView.end)) { + fetchAndRenderEvents(); + } + else { + renderEvents(); + } + } + + + function fetchAndRenderEvents() { + fetchEvents(currentView.start, currentView.end); + // ... will call reportEvents + // ... which will call renderEvents + } + + + // called when event data arrives + function reportEvents(_events) { + events = _events; + renderEvents(); + } + + + // called when a single event's data has been changed + function reportEventChange() { + renderEvents(); + } + + + + /* Header Updating + -----------------------------------------------------------------------------*/ + + + function updateTitle() { + header.updateTitle(currentView.title); + } + + + function updateTodayButton() { + var now = t.getNow(); + if (now.isWithin(currentView.intervalStart, currentView.intervalEnd)) { + header.disableButton('today'); + } + else { + header.enableButton('today'); + } + } + + + + /* Selection + -----------------------------------------------------------------------------*/ + + + function select(start, end) { + + start = t.moment(start); + if (end) { + end = t.moment(end); + } + else if (start.hasTime()) { + end = start.clone().add(t.defaultTimedEventDuration); + } + else { + end = start.clone().add(t.defaultAllDayEventDuration); + } + + currentView.select(start, end); + } + + + function unselect() { // safe to be called before renderView + if (currentView) { + currentView.unselect(); + } + } + + + + /* Date + -----------------------------------------------------------------------------*/ + + + function prev() { + renderView(-1); + } + + + function next() { + renderView(1); + } + + + function prevYear() { + date.add(-1, 'years'); + renderView(); + } + + + function nextYear() { + date.add(1, 'years'); + renderView(); + } + + + function today() { + date = t.getNow(); + renderView(); + } + + + function gotoDate(dateInput) { + date = t.moment(dateInput); + renderView(); + } + + + function incrementDate(delta) { + date.add(moment.duration(delta)); + renderView(); + } + + + // Forces navigation to a view for the given date. + // `viewName` can be a specific view name or a generic one like "week" or "day". + function zoomTo(newDate, viewName) { + var viewStr; + var match; + + if (!viewName || fcViews[viewName] === undefined) { // a general view name, or "auto" + viewName = viewName || 'day'; + viewStr = header.getViewsWithButtons().join(' '); // space-separated string of all the views in the header + + // try to match a general view name, like "week", against a specific one, like "agendaWeek" + match = viewStr.match(new RegExp('\\w+' + capitaliseFirstLetter(viewName))); + + // fall back to the day view being used in the header + if (!match) { + match = viewStr.match(/\w+Day/); + } + + viewName = match ? match[0] : 'agendaDay'; // fall back to agendaDay + } + + date = newDate; + changeView(viewName); + } + + + function getDate() { + return date.clone(); + } + + + + /* Height "Freezing" + -----------------------------------------------------------------------------*/ + + + function freezeContentHeight() { + content.css({ + width: '100%', + height: content.height(), + overflow: 'hidden' + }); + } + + + function unfreezeContentHeight() { + content.css({ + width: '', + height: '', + overflow: '' + }); + } + + + + /* Misc + -----------------------------------------------------------------------------*/ + + + function getCalendar() { + return t; + } + + + function getView() { + return currentView; + } + + + function option(name, value) { + if (value === undefined) { + return options[name]; + } + if (name == 'height' || name == 'contentHeight' || name == 'aspectRatio') { + options[name] = value; + updateSize(true); // true = allow recalculation of height + } + } + + + function trigger(name, thisObj) { + if (options[name]) { + return options[name].apply( + thisObj || _element, + Array.prototype.slice.call(arguments, 2) + ); + } + } + +} + +;; + +/* Top toolbar area with buttons and title +----------------------------------------------------------------------------------------------------------------------*/ +// TODO: rename all header-related things to "toolbar" + +function Header(calendar, options) { + var t = this; + + // exports + t.render = render; + t.destroy = destroy; + t.updateTitle = updateTitle; + t.activateButton = activateButton; + t.deactivateButton = deactivateButton; + t.disableButton = disableButton; + t.enableButton = enableButton; + t.getViewsWithButtons = getViewsWithButtons; + + // locals + var el = $(); + var viewsWithButtons = []; + var tm; + + + function render() { + var sections = options.header; + + tm = options.theme ? 'ui' : 'fc'; + + if (sections) { + el = $("
") + .append(renderSection('left')) + .append(renderSection('right')) + .append(renderSection('center')) + .append('
'); + + return el; + } + } + + + function destroy() { + el.remove(); + } + + + function renderSection(position) { + var sectionEl = $('
'); + var buttonStr = options.header[position]; + + if (buttonStr) { + $.each(buttonStr.split(' '), function(i) { + var groupChildren = $(); + var isOnlyButtons = true; + var groupEl; + + $.each(this.split(','), function(j, buttonName) { + var buttonClick; + var themeIcon; + var normalIcon; + var defaultText; + var customText; + var innerHtml; + var classes; + var button; + + if (buttonName == 'title') { + groupChildren = groupChildren.add($('

 

')); // we always want it to take up height + isOnlyButtons = false; + } + else { + if (calendar[buttonName]) { // a calendar method + buttonClick = function() { + calendar[buttonName](); + }; + } + else if (fcViews[buttonName]) { // a view name + buttonClick = function() { + calendar.changeView(buttonName); + }; + viewsWithButtons.push(buttonName); + } + if (buttonClick) { + + // smartProperty allows different text per view button (ex: "Agenda Week" vs "Basic Week") + themeIcon = smartProperty(options.themeButtonIcons, buttonName); + normalIcon = smartProperty(options.buttonIcons, buttonName); + defaultText = smartProperty(options.defaultButtonText, buttonName); + customText = smartProperty(options.buttonText, buttonName); + + if (customText) { + innerHtml = htmlEscape(customText); + } + else if (themeIcon && options.theme) { + innerHtml = ""; + } + else if (normalIcon && !options.theme) { + innerHtml = ""; + } + else { + innerHtml = htmlEscape(defaultText || buttonName); + } + + classes = [ + 'fc-' + buttonName + '-button', + tm + '-button', + tm + '-state-default' + ]; + + button = $( // type="button" so that it doesn't submit a form + '' + ) + .click(function() { + // don't process clicks for disabled buttons + if (!button.hasClass(tm + '-state-disabled')) { + + buttonClick(); + + // after the click action, if the button becomes the "active" tab, or disabled, + // it should never have a hover class, so remove it now. + if ( + button.hasClass(tm + '-state-active') || + button.hasClass(tm + '-state-disabled') + ) { + button.removeClass(tm + '-state-hover'); + } + } + }) + .mousedown(function() { + // the *down* effect (mouse pressed in). + // only on buttons that are not the "active" tab, or disabled + button + .not('.' + tm + '-state-active') + .not('.' + tm + '-state-disabled') + .addClass(tm + '-state-down'); + }) + .mouseup(function() { + // undo the *down* effect + button.removeClass(tm + '-state-down'); + }) + .hover( + function() { + // the *hover* effect. + // only on buttons that are not the "active" tab, or disabled + button + .not('.' + tm + '-state-active') + .not('.' + tm + '-state-disabled') + .addClass(tm + '-state-hover'); + }, + function() { + // undo the *hover* effect + button + .removeClass(tm + '-state-hover') + .removeClass(tm + '-state-down'); // if mouseleave happens before mouseup + } + ); + + groupChildren = groupChildren.add(button); + } + } + }); + + if (isOnlyButtons) { + groupChildren + .first().addClass(tm + '-corner-left').end() + .last().addClass(tm + '-corner-right').end(); + } + + if (groupChildren.length > 1) { + groupEl = $('
'); + if (isOnlyButtons) { + groupEl.addClass('fc-button-group'); + } + groupEl.append(groupChildren); + sectionEl.append(groupEl); + } + else { + sectionEl.append(groupChildren); // 1 or 0 children + } + }); + } + + return sectionEl; + } + + + function updateTitle(text) { + el.find('h2').text(text); + } + + + function activateButton(buttonName) { + el.find('.fc-' + buttonName + '-button') + .addClass(tm + '-state-active'); + } + + + function deactivateButton(buttonName) { + el.find('.fc-' + buttonName + '-button') + .removeClass(tm + '-state-active'); + } + + + function disableButton(buttonName) { + el.find('.fc-' + buttonName + '-button') + .attr('disabled', 'disabled') + .addClass(tm + '-state-disabled'); + } + + + function enableButton(buttonName) { + el.find('.fc-' + buttonName + '-button') + .removeAttr('disabled') + .removeClass(tm + '-state-disabled'); + } + + + function getViewsWithButtons() { + return viewsWithButtons; + } + +} + +;; + +fc.sourceNormalizers = []; +fc.sourceFetchers = []; + +var ajaxDefaults = { + dataType: 'json', + cache: false +}; + +var eventGUID = 1; + + +function EventManager(options) { // assumed to be a calendar + var t = this; + + + // exports + t.isFetchNeeded = isFetchNeeded; + t.fetchEvents = fetchEvents; + t.addEventSource = addEventSource; + t.removeEventSource = removeEventSource; + t.updateEvent = updateEvent; + t.renderEvent = renderEvent; + t.removeEvents = removeEvents; + t.clientEvents = clientEvents; + t.mutateEvent = mutateEvent; + + + // imports + var trigger = t.trigger; + var getView = t.getView; + var reportEvents = t.reportEvents; + var getEventEnd = t.getEventEnd; + + + // locals + var stickySource = { events: [] }; + var sources = [ stickySource ]; + var rangeStart, rangeEnd; + var currentFetchID = 0; + var pendingSourceCnt = 0; + var loadingLevel = 0; + var cache = []; + + + $.each( + (options.events ? [ options.events ] : []).concat(options.eventSources || []), + function(i, sourceInput) { + var source = buildEventSource(sourceInput); + if (source) { + sources.push(source); + } + } + ); + + + + /* Fetching + -----------------------------------------------------------------------------*/ + + + function isFetchNeeded(start, end) { + return !rangeStart || // nothing has been fetched yet? + // or, a part of the new range is outside of the old range? (after normalizing) + start.clone().stripZone() < rangeStart.clone().stripZone() || + end.clone().stripZone() > rangeEnd.clone().stripZone(); + } + + + function fetchEvents(start, end) { + rangeStart = start; + rangeEnd = end; + cache = []; + var fetchID = ++currentFetchID; + var len = sources.length; + pendingSourceCnt = len; + for (var i=0; i cells, find the cell with the largest natural width and set the widths of all the +// cells to be that width. +// PREREQUISITE: if you want a cell to take up width, it needs to have a single inner element w/ display:inline +function matchCellWidths(els) { + var maxInnerWidth = 0; + + els.find('> *').each(function(i, innerEl) { + var innerWidth = $(innerEl).outerWidth(); + if (innerWidth > maxInnerWidth) { + maxInnerWidth = innerWidth; + } + }); + + maxInnerWidth++; // sometimes not accurate of width the text needs to stay on one line. insurance + + els.width(maxInnerWidth); + + return maxInnerWidth; +} + + +// Turns a container element into a scroller if its contents is taller than the allotted height. +// Returns true if the element is now a scroller, false otherwise. +// NOTE: this method is best because it takes weird zooming dimensions into account +function setPotentialScroller(containerEl, height) { + containerEl.height(height).addClass('fc-scroller'); + + // are scrollbars needed? + if (containerEl[0].scrollHeight - 1 > containerEl[0].clientHeight) { // !!! -1 because IE is often off-by-one :( + return true; + } + + unsetScroller(containerEl); // undo + return false; +} + + +// Takes an element that might have been a scroller, and turns it back into a normal element. +function unsetScroller(containerEl) { + containerEl.height('').removeClass('fc-scroller'); +} + + +/* General DOM Utilities +----------------------------------------------------------------------------------------------------------------------*/ + + +// borrowed from https://github.com/jquery/jquery-ui/blob/1.11.0/ui/core.js#L51 +function getScrollParent(el) { + var position = el.css('position'), + scrollParent = el.parents().filter(function() { + var parent = $(this); + return (/(auto|scroll)/).test( + parent.css('overflow') + parent.css('overflow-y') + parent.css('overflow-x') + ); + }).eq(0); + + return position === 'fixed' || !scrollParent.length ? $(el[0].ownerDocument || document) : scrollParent; +} + + +// Given a container element, return an object with the pixel values of the left/right scrollbars. +// Left scrollbars might occur on RTL browsers (IE maybe?) but I have not tested. +// PREREQUISITE: container element must have a single child with display:block +function getScrollbarWidths(container) { + var containerLeft = container.offset().left; + var containerRight = containerLeft + container.width(); + var inner = container.children(); + var innerLeft = inner.offset().left; + var innerRight = innerLeft + inner.outerWidth(); + + return { + left: innerLeft - containerLeft, + right: containerRight - innerRight + }; +} + + +// Returns a boolean whether this was a left mouse click and no ctrl key (which means right click on Mac) +function isPrimaryMouseButton(ev) { + return ev.which == 1 && !ev.ctrlKey; +} + + +/* FullCalendar-specific Misc Utilities +----------------------------------------------------------------------------------------------------------------------*/ + + +// Creates a basic segment with the intersection of the two ranges. Returns undefined if no intersection. +// Expects all dates to be normalized to the same timezone beforehand. +function intersectionToSeg(subjectStart, subjectEnd, intervalStart, intervalEnd) { + var segStart, segEnd; + var isStart, isEnd; + + if (subjectEnd > intervalStart && subjectStart < intervalEnd) { // in bounds at all? + + if (subjectStart >= intervalStart) { + segStart = subjectStart.clone(); + isStart = true; + } + else { + segStart = intervalStart.clone(); + isStart = false; + } + + if (subjectEnd <= intervalEnd) { + segEnd = subjectEnd.clone(); + isEnd = true; + } + else { + segEnd = intervalEnd.clone(); + isEnd = false; + } + + return { + start: segStart, + end: segEnd, + isStart: isStart, + isEnd: isEnd + }; + } +} + + +function smartProperty(obj, name) { // get a camel-cased/namespaced property of an object + obj = obj || {}; + if (obj[name] !== undefined) { + return obj[name]; + } + var parts = name.split(/(?=[A-Z])/), + i = parts.length - 1, res; + for (; i>=0; i--) { + res = obj[parts[i].toLowerCase()]; + if (res !== undefined) { + return res; + } + } + return obj['default']; +} + + +/* Date Utilities +----------------------------------------------------------------------------------------------------------------------*/ + +var dayIDs = [ 'sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat' ]; + + +// Diffs the two moments into a Duration where full-days are recorded first, then the remaining time. +// Moments will have their timezones normalized. +function dayishDiff(a, b) { + return moment.duration({ + days: a.clone().stripTime().diff(b.clone().stripTime(), 'days'), + ms: a.time() - b.time() + }); +} + + +function isNativeDate(input) { + return Object.prototype.toString.call(input) === '[object Date]' || input instanceof Date; +} + + +function dateCompare(a, b) { // works with Moments and native Dates + return a - b; +} + + +/* General Utilities +----------------------------------------------------------------------------------------------------------------------*/ + +fc.applyAll = applyAll; // export + + +// Create an object that has the given prototype. Just like Object.create +function createObject(proto) { + var f = function() {}; + f.prototype = proto; + return new f(); +} + + +// Copies specifically-owned (non-protoype) properties of `b` onto `a`. +// FYI, $.extend would copy *all* properties of `b` onto `a`. +function extend(a, b) { + for (var i in b) { + if (b.hasOwnProperty(i)) { + a[i] = b[i]; + } + } +} + + +function applyAll(functions, thisObj, args) { + if ($.isFunction(functions)) { + functions = [ functions ]; + } + if (functions) { + var i; + var ret; + for (i=0; i/g, '>') + .replace(/'/g, ''') + .replace(/"/g, '"') + .replace(/\n/g, '
'); +} + + +function stripHtmlEntities(text) { + return text.replace(/&.*?;/g, ''); +} + + +function capitaliseFirstLetter(str) { + return str.charAt(0).toUpperCase() + str.slice(1); +} + + +// Returns a function, that, as long as it continues to be invoked, will not +// be triggered. The function will be called after it stops being called for +// N milliseconds. +// https://github.com/jashkenas/underscore/blob/1.6.0/underscore.js#L714 +function debounce(func, wait) { + var timeoutId; + var args; + var context; + var timestamp; // of most recent call + var later = function() { + var last = +new Date() - timestamp; + if (last < wait && last > 0) { + timeoutId = setTimeout(later, wait - last); + } + else { + timeoutId = null; + func.apply(context, args); + if (!timeoutId) { + context = args = null; + } + } + }; + + return function() { + context = this; + args = arguments; + timestamp = +new Date(); + if (!timeoutId) { + timeoutId = setTimeout(later, wait); + } + }; +} + +;; + +var ambigDateOfMonthRegex = /^\s*\d{4}-\d\d$/; +var ambigTimeOrZoneRegex = + /^\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+)?)?)?)?)?$/; + + +// Creating +// ------------------------------------------------------------------------------------------------- + +// Creates a new moment, similar to the vanilla moment(...) constructor, but with +// extra features (ambiguous time, enhanced formatting). When gived an existing moment, +// it will function as a clone (and retain the zone of the moment). Anything else will +// result in a moment in the local zone. +fc.moment = function() { + return makeMoment(arguments); +}; + +// Sames as fc.moment, but forces the resulting moment to be in the UTC timezone. +fc.moment.utc = function() { + var mom = makeMoment(arguments, true); + + // Force it into UTC because makeMoment doesn't guarantee it. + if (mom.hasTime()) { // don't give ambiguously-timed moments a UTC zone + mom.utc(); + } + + return mom; +}; + +// Same as fc.moment, but when given an ISO8601 string, the timezone offset is preserved. +// ISO8601 strings with no timezone offset will become ambiguously zoned. +fc.moment.parseZone = function() { + return makeMoment(arguments, true, true); +}; + +// Builds an FCMoment from args. When given an existing moment, it clones. When given a native +// Date, or called with no arguments (the current time), the resulting moment will be local. +// Anything else needs to be "parsed" (a string or an array), and will be affected by: +// parseAsUTC - if there is no zone information, should we parse the input in UTC? +// parseZone - if there is zone information, should we force the zone of the moment? +function makeMoment(args, parseAsUTC, parseZone) { + var input = args[0]; + var isSingleString = args.length == 1 && typeof input === 'string'; + var isAmbigTime; + var isAmbigZone; + var ambigMatch; + var output; // an object with fields for the new FCMoment object + + if (moment.isMoment(input)) { + output = moment.apply(null, args); // clone it + + // the ambig properties have not been preserved in the clone, so reassign them + if (input._ambigTime) { + output._ambigTime = true; + } + if (input._ambigZone) { + output._ambigZone = true; + } + } + else if (isNativeDate(input) || input === undefined) { + output = moment.apply(null, args); // will be local + } + else { // "parsing" is required + isAmbigTime = false; + isAmbigZone = false; + + if (isSingleString) { + if (ambigDateOfMonthRegex.test(input)) { + // accept strings like '2014-05', but convert to the first of the month + input += '-01'; + args = [ input ]; // for when we pass it on to moment's constructor + isAmbigTime = true; + isAmbigZone = true; + } + else if ((ambigMatch = ambigTimeOrZoneRegex.exec(input))) { + isAmbigTime = !ambigMatch[5]; // no time part? + isAmbigZone = true; + } + } + else if ($.isArray(input)) { + // arrays have no timezone information, so assume ambiguous zone + isAmbigZone = true; + } + // otherwise, probably a string with a format + + if (parseAsUTC) { + output = moment.utc.apply(moment, args); + } + else { + output = moment.apply(null, args); + } + + if (isAmbigTime) { + output._ambigTime = true; + output._ambigZone = true; // ambiguous time always means ambiguous zone + } + else if (parseZone) { // let's record the inputted zone somehow + if (isAmbigZone) { + output._ambigZone = true; + } + else if (isSingleString) { + output.zone(input); // if not a valid zone, will assign UTC + } + } + } + + return new FCMoment(output); +} + +// Our subclass of Moment. +// Accepts an object with the internal Moment properties that should be copied over to +// `this` object (most likely another Moment object). The values in this data must not +// be referenced by anything else (two moments sharing a Date object for example). +function FCMoment(internalData) { + extend(this, internalData); +} + +// Chain the prototype to Moment's +FCMoment.prototype = createObject(moment.fn); + +// We need this because Moment's implementation won't create an FCMoment, +// nor will it copy over the ambig flags. +FCMoment.prototype.clone = function() { + return makeMoment([ this ]); +}; + + +// Time-of-day +// ------------------------------------------------------------------------------------------------- + +// GETTER +// Returns a Duration with the hours/minutes/seconds/ms values of the moment. +// If the moment has an ambiguous time, a duration of 00:00 will be returned. +// +// SETTER +// You can supply a Duration, a Moment, or a Duration-like argument. +// When setting the time, and the moment has an ambiguous time, it then becomes unambiguous. +FCMoment.prototype.time = function(time) { + if (time == null) { // getter + return moment.duration({ + hours: this.hours(), + minutes: this.minutes(), + seconds: this.seconds(), + milliseconds: this.milliseconds() + }); + } + else { // setter + + delete this._ambigTime; // mark that the moment now has a time + + if (!moment.isDuration(time) && !moment.isMoment(time)) { + time = moment.duration(time); + } + + // The day value should cause overflow (so 24 hours becomes 00:00:00 of next day). + // Only for Duration times, not Moment times. + var dayHours = 0; + if (moment.isDuration(time)) { + dayHours = Math.floor(time.asDays()) * 24; + } + + // We need to set the individual fields. + // Can't use startOf('day') then add duration. In case of DST at start of day. + return this.hours(dayHours + time.hours()) + .minutes(time.minutes()) + .seconds(time.seconds()) + .milliseconds(time.milliseconds()); + } +}; + +// Converts the moment to UTC, stripping out its time-of-day and timezone offset, +// but preserving its YMD. A moment with a stripped time will display no time +// nor timezone offset when .format() is called. +FCMoment.prototype.stripTime = function() { + var a = this.toArray(); // year,month,date,hours,minutes,seconds as an array + + // set the internal UTC flag + moment.fn.utc.call(this); // call the original method, because we don't want to affect _ambigZone + + this.year(a[0]) // TODO: find a way to do this in one shot + .month(a[1]) + .date(a[2]) + .hours(0) + .minutes(0) + .seconds(0) + .milliseconds(0); + + // Mark the time as ambiguous. This needs to happen after the .utc() call, which calls .zone(), which + // clears all ambig flags. Same concept with the .year/month/date calls in the case of moment-timezone. + this._ambigTime = true; + this._ambigZone = true; // if ambiguous time, also ambiguous timezone offset + + return this; // for chaining +}; + +// Returns if the moment has a non-ambiguous time (boolean) +FCMoment.prototype.hasTime = function() { + return !this._ambigTime; +}; + + +// Timezone +// ------------------------------------------------------------------------------------------------- + +// Converts the moment to UTC, stripping out its timezone offset, but preserving its +// YMD and time-of-day. A moment with a stripped timezone offset will display no +// timezone offset when .format() is called. +FCMoment.prototype.stripZone = function() { + var a = this.toArray(); // year,month,date,hours,minutes,seconds as an array + var wasAmbigTime = this._ambigTime; + + moment.fn.utc.call(this); // set the internal UTC flag + + this.year(a[0]) // TODO: find a way to do this in one shot + .month(a[1]) + .date(a[2]) + .hours(a[3]) + .minutes(a[4]) + .seconds(a[5]) + .milliseconds(a[6]); + + if (wasAmbigTime) { + // the above call to .utc()/.zone() unfortunately clears the ambig flags, so reassign + this._ambigTime = true; + } + + // Mark the zone as ambiguous. This needs to happen after the .utc() call, which calls .zone(), which + // clears all ambig flags. Same concept with the .year/month/date calls in the case of moment-timezone. + this._ambigZone = true; + + return this; // for chaining +}; + +// Returns of the moment has a non-ambiguous timezone offset (boolean) +FCMoment.prototype.hasZone = function() { + return !this._ambigZone; +}; + +// this method implicitly marks a zone +FCMoment.prototype.zone = function(tzo) { + + if (tzo != null) { + // FYI, the delete statements need to be before the .zone() call or else chaos ensues + // for reasons I don't understand. + delete this._ambigTime; + delete this._ambigZone; + } + + return moment.fn.zone.apply(this, arguments); +}; + +// this method implicitly marks a zone +FCMoment.prototype.local = function() { + var a = this.toArray(); // year,month,date,hours,minutes,seconds as an array + var wasAmbigZone = this._ambigZone; + + // will happen anyway via .local()/.zone(), but don't want to rely on internal implementation + delete this._ambigTime; + delete this._ambigZone; + + moment.fn.local.apply(this, arguments); + + if (wasAmbigZone) { + // If the moment was ambiguously zoned, the date fields were stored as UTC. + // We want to preserve these, but in local time. + this.year(a[0]) // TODO: find a way to do this in one shot + .month(a[1]) + .date(a[2]) + .hours(a[3]) + .minutes(a[4]) + .seconds(a[5]) + .milliseconds(a[6]); + } + + return this; // for chaining +}; + +// this method implicitly marks a zone +FCMoment.prototype.utc = function() { + + // will happen anyway via .local()/.zone(), but don't want to rely on internal implementation + delete this._ambigTime; + delete this._ambigZone; + + return moment.fn.utc.apply(this, arguments); +}; + + +// Formatting +// ------------------------------------------------------------------------------------------------- + +FCMoment.prototype.format = function() { + if (arguments[0]) { + return formatDate(this, arguments[0]); // our extended formatting + } + if (this._ambigTime) { + return momentFormat(this, 'YYYY-MM-DD'); + } + if (this._ambigZone) { + return momentFormat(this, 'YYYY-MM-DD[T]HH:mm:ss'); + } + return momentFormat(this); // default moment original formatting +}; + +FCMoment.prototype.toISOString = function() { + if (this._ambigTime) { + return momentFormat(this, 'YYYY-MM-DD'); + } + if (this._ambigZone) { + return momentFormat(this, 'YYYY-MM-DD[T]HH:mm:ss'); + } + return moment.fn.toISOString.apply(this, arguments); +}; + + +// Querying +// ------------------------------------------------------------------------------------------------- + +// Is the moment within the specified range? `end` is exclusive. +FCMoment.prototype.isWithin = function(start, end) { + var a = commonlyAmbiguate([ this, start, end ]); + return a[0] >= a[1] && a[0] < a[2]; +}; + +// When isSame is called with units, timezone ambiguity is normalized before the comparison happens. +// If no units are specified, the two moments must be identically the same, with matching ambig flags. +FCMoment.prototype.isSame = function(input, units) { + var a; + + if (units) { + a = commonlyAmbiguate([ this, input ], true); // normalize timezones but don't erase times + return moment.fn.isSame.call(a[0], a[1], units); + } + else { + input = fc.moment.parseZone(input); // normalize input + return moment.fn.isSame.call(this, input) && + Boolean(this._ambigTime) === Boolean(input._ambigTime) && + Boolean(this._ambigZone) === Boolean(input._ambigZone); + } +}; + +// Make these query methods work with ambiguous moments +$.each([ + 'isBefore', + 'isAfter' +], function(i, methodName) { + FCMoment.prototype[methodName] = function(input, units) { + var a = commonlyAmbiguate([ this, input ]); + return moment.fn[methodName].call(a[0], a[1], units); + }; +}); + + +// Misc Internals +// ------------------------------------------------------------------------------------------------- + +// given an array of moment-like inputs, return a parallel array w/ moments similarly ambiguated. +// for example, of one moment has ambig time, but not others, all moments will have their time stripped. +// set `preserveTime` to `true` to keep times, but only normalize zone ambiguity. +function commonlyAmbiguate(inputs, preserveTime) { + var outputs = []; + var anyAmbigTime = false; + var anyAmbigZone = false; + var i; + + for (i=0; i "MMMM D YYYY" + formatStr = localeData.longDateFormat(formatStr) || formatStr; + // BTW, this is not important for `formatDate` because it is impossible to put custom tokens + // or non-zero areas in Moment's localized format strings. + + separator = separator || ' - '; + + return formatRangeWithChunks( + date1, + date2, + getFormatStringChunks(formatStr), + separator, + isRTL + ); +} +fc.formatRange = formatRange; // expose + + +function formatRangeWithChunks(date1, date2, chunks, separator, isRTL) { + var chunkStr; // the rendering of the chunk + var leftI; + var leftStr = ''; + var rightI; + var rightStr = ''; + var middleI; + var middleStr1 = ''; + var middleStr2 = ''; + var middleStr = ''; + + // Start at the leftmost side of the formatting string and continue until you hit a token + // that is not the same between dates. + for (leftI=0; leftIleftI; rightI--) { + chunkStr = formatSimilarChunk(date1, date2, chunks[rightI]); + if (chunkStr === false) { + break; + } + rightStr = chunkStr + rightStr; + } + + // The area in the middle is different for both of the dates. + // Collect them distinctly so we can jam them together later. + for (middleI=leftI; middleI<=rightI; middleI++) { + middleStr1 += formatDateWithChunk(date1, chunks[middleI]); + middleStr2 += formatDateWithChunk(date2, chunks[middleI]); + } + + if (middleStr1 || middleStr2) { + if (isRTL) { + middleStr = middleStr2 + separator + middleStr1; + } + else { + middleStr = middleStr1 + separator + middleStr2; + } + } + + return leftStr + middleStr + rightStr; +} + + +var similarUnitMap = { + Y: 'year', + M: 'month', + D: 'day', // day of month + d: 'day', // day of week + // prevents a separator between anything time-related... + A: 'second', // AM/PM + a: 'second', // am/pm + T: 'second', // A/P + t: 'second', // a/p + H: 'second', // hour (24) + h: 'second', // hour (12) + m: 'second', // minute + s: 'second' // second +}; +// TODO: week maybe? + + +// Given a formatting chunk, and given that both dates are similar in the regard the +// formatting chunk is concerned, format date1 against `chunk`. Otherwise, return `false`. +function formatSimilarChunk(date1, date2, chunk) { + var token; + var unit; + + if (typeof chunk === 'string') { // a literal string + return chunk; + } + else if ((token = chunk.token)) { + unit = similarUnitMap[token.charAt(0)]; + // are the dates the same for this unit of measurement? + if (unit && date1.isSame(date2, unit)) { + return momentFormat(date1, token); // would be the same if we used `date2` + // BTW, don't support custom tokens + } + } + + return false; // the chunk is NOT the same for the two dates + // BTW, don't support splitting on non-zero areas +} + + +// Chunking Utils +// ------------------------------------------------------------------------------------------------- + + +var formatStringChunkCache = {}; + + +function getFormatStringChunks(formatStr) { + if (formatStr in formatStringChunkCache) { + return formatStringChunkCache[formatStr]; + } + return (formatStringChunkCache[formatStr] = chunkFormatString(formatStr)); +} + + +// Break the formatting string into an array of chunks +function chunkFormatString(formatStr) { + var chunks = []; + var chunker = /\[([^\]]*)\]|\(([^\)]*)\)|(LT|(\w)\4*o?)|([^\w\[\(]+)/g; // TODO: more descrimination + var match; + + while ((match = chunker.exec(formatStr))) { + if (match[1]) { // a literal string inside [ ... ] + chunks.push(match[1]); + } + else if (match[2]) { // non-zero formatting inside ( ... ) + chunks.push({ maybe: chunkFormatString(match[2]) }); + } + else if (match[3]) { // a formatting token + chunks.push({ token: match[3] }); + } + else if (match[5]) { // an unenclosed literal string + chunks.push(match[5]); + } + } + + return chunks; +} + +;; + +/* A rectangular panel that is absolutely positioned over other content +------------------------------------------------------------------------------------------------------------------------ +Options: + - className (string) + - content (HTML string or jQuery element set) + - parentEl + - top + - left + - right (the x coord of where the right edge should be. not a "CSS" right) + - autoHide (boolean) + - show (callback) + - hide (callback) +*/ + +function Popover(options) { + this.options = options || {}; +} + + +Popover.prototype = { + + isHidden: true, + options: null, + el: null, // the container element for the popover. generated by this object + documentMousedownProxy: null, // document mousedown handler bound to `this` + margin: 10, // the space required between the popover and the edges of the scroll container + + + // Shows the popover on the specified position. Renders it if not already + show: function() { + if (this.isHidden) { + if (!this.el) { + this.render(); + } + this.el.show(); + this.position(); + this.isHidden = false; + this.trigger('show'); + } + }, + + + // Hides the popover, through CSS, but does not remove it from the DOM + hide: function() { + if (!this.isHidden) { + this.el.hide(); + this.isHidden = true; + this.trigger('hide'); + } + }, + + + // Creates `this.el` and renders content inside of it + render: function() { + var _this = this; + var options = this.options; + + this.el = $('
') + .addClass(options.className || '') + .css({ + // position initially to the top left to avoid creating scrollbars + top: 0, + left: 0 + }) + .append(options.content) + .appendTo(options.parentEl); + + // when a click happens on anything inside with a 'fc-close' className, hide the popover + this.el.on('click', '.fc-close', function() { + _this.hide(); + }); + + if (options.autoHide) { + $(document).on('mousedown', this.documentMousedownProxy = $.proxy(this, 'documentMousedown')); + } + }, + + + // Triggered when the user clicks *anywhere* in the document, for the autoHide feature + documentMousedown: function(ev) { + // only hide the popover if the click happened outside the popover + if (this.el && !$(ev.target).closest(this.el).length) { + this.hide(); + } + }, + + + // Hides and unregisters any handlers + destroy: function() { + this.hide(); + + if (this.el) { + this.el.remove(); + this.el = null; + } + + $(document).off('mousedown', this.documentMousedownProxy); + }, + + + // Positions the popover optimally, using the top/left/right options + position: function() { + var options = this.options; + var origin = this.el.offsetParent().offset(); + var width = this.el.outerWidth(); + var height = this.el.outerHeight(); + var windowEl = $(window); + var viewportEl = getScrollParent(this.el); + var viewportTop; + var viewportLeft; + var viewportOffset; + var top; // the "position" (not "offset") values for the popover + var left; // + + // compute top and left + top = options.top || 0; + if (options.left !== undefined) { + left = options.left; + } + else if (options.right !== undefined) { + left = options.right - width; // derive the left value from the right value + } + else { + left = 0; + } + + if (viewportEl.is(window) || viewportEl.is(document)) { // normalize getScrollParent's result + viewportEl = windowEl; + viewportTop = 0; // the window is always at the top left + viewportLeft = 0; // (and .offset() won't work if called here) + } + else { + viewportOffset = viewportEl.offset(); + viewportTop = viewportOffset.top; + viewportLeft = viewportOffset.left; + } + + // if the window is scrolled, it causes the visible area to be further down + viewportTop += windowEl.scrollTop(); + viewportLeft += windowEl.scrollLeft(); + + // constrain to the view port. if constrained by two edges, give precedence to top/left + if (options.viewportConstrain !== false) { + top = Math.min(top, viewportTop + viewportEl.outerHeight() - height - this.margin); + top = Math.max(top, viewportTop + this.margin); + left = Math.min(left, viewportLeft + viewportEl.outerWidth() - width - this.margin); + left = Math.max(left, viewportLeft + this.margin); + } + + this.el.css({ + top: top - origin.top, + left: left - origin.left + }); + }, + + + // Triggers a callback. Calls a function in the option hash of the same name. + // Arguments beyond the first `name` are forwarded on. + // TODO: better code reuse for this. Repeat code + trigger: function(name) { + if (this.options[name]) { + this.options[name].apply(this, Array.prototype.slice.call(arguments, 1)); + } + } + +}; + +;; + +/* A "coordinate map" converts pixel coordinates into an associated cell, which has an associated date +------------------------------------------------------------------------------------------------------------------------ +Common interface: + + CoordMap.prototype = { + build: function() {}, + getCell: function(x, y) {} + }; + +*/ + +/* Coordinate map for a grid component +----------------------------------------------------------------------------------------------------------------------*/ + +function GridCoordMap(grid) { + this.grid = grid; +} + + +GridCoordMap.prototype = { + + grid: null, // reference to the Grid + rows: null, // the top-to-bottom y coordinates. including the bottom of the last item + cols: null, // the left-to-right x coordinates. including the right of the last item + + containerEl: null, // container element that all coordinates are constrained to. optionally assigned + minX: null, + maxX: null, // exclusive + minY: null, + maxY: null, // exclusive + + + // Queries the grid for the coordinates of all the cells + build: function() { + this.grid.buildCoords( + this.rows = [], + this.cols = [] + ); + this.computeBounds(); + }, + + + // Given a coordinate of the document, gets the associated cell. If no cell is underneath, returns null + getCell: function(x, y) { + var cell = null; + var rows = this.rows; + var cols = this.cols; + var r = -1; + var c = -1; + var i; + + if (this.inBounds(x, y)) { + + for (i = 0; i < rows.length; i++) { + if (y >= rows[i][0] && y < rows[i][1]) { + r = i; + break; + } + } + + for (i = 0; i < cols.length; i++) { + if (x >= cols[i][0] && x < cols[i][1]) { + c = i; + break; + } + } + + if (r >= 0 && c >= 0) { + cell = { row: r, col: c }; + cell.grid = this.grid; + cell.date = this.grid.getCellDate(cell); + } + } + + return cell; + }, + + + // If there is a containerEl, compute the bounds into min/max values + computeBounds: function() { + var containerOffset; + + if (this.containerEl) { + containerOffset = this.containerEl.offset(); + this.minX = containerOffset.left; + this.maxX = containerOffset.left + this.containerEl.outerWidth(); + this.minY = containerOffset.top; + this.maxY = containerOffset.top + this.containerEl.outerHeight(); + } + }, + + + // Determines if the given coordinates are in bounds. If no `containerEl`, always true + inBounds: function(x, y) { + if (this.containerEl) { + return x >= this.minX && x < this.maxX && y >= this.minY && y < this.maxY; + } + return true; + } + +}; + + +/* Coordinate map that is a combination of multiple other coordinate maps +----------------------------------------------------------------------------------------------------------------------*/ + +function ComboCoordMap(coordMaps) { + this.coordMaps = coordMaps; +} + + +ComboCoordMap.prototype = { + + coordMaps: null, // an array of CoordMaps + + + // Builds all coordMaps + build: function() { + var coordMaps = this.coordMaps; + var i; + + for (i = 0; i < coordMaps.length; i++) { + coordMaps[i].build(); + } + }, + + + // Queries all coordMaps for the cell underneath the given coordinates, returning the first result + getCell: function(x, y) { + var coordMaps = this.coordMaps; + var cell = null; + var i; + + for (i = 0; i < coordMaps.length && !cell; i++) { + cell = coordMaps[i].getCell(x, y); + } + + return cell; + } + +}; +;; + +/* Tracks mouse movements over a CoordMap and raises events about which cell the mouse is over. +----------------------------------------------------------------------------------------------------------------------*/ +// TODO: implement scrolling + +function DragListener(coordMap, options) { + this.coordMap = coordMap; + this.options = options || {}; +} + + +DragListener.prototype = { + + coordMap: null, + options: null, + + isListening: false, + isDragging: false, + + // the cell/date the mouse was over when listening started + origCell: null, + origDate: null, + + // the cell/date the mouse is over + cell: null, + date: null, + + // coordinates of the initial mousedown + mouseX0: null, + mouseY0: null, + + // handler attached to the document, bound to the DragListener's `this` + mousemoveProxy: null, + mouseupProxy: null, + + scrollEl: null, + scrollBounds: null, // { top, bottom, left, right } + scrollTopVel: null, // pixels per second + scrollLeftVel: null, // pixels per second + scrollIntervalId: null, // ID of setTimeout for scrolling animation loop + scrollHandlerProxy: null, // this-scoped function for handling when scrollEl is scrolled + + scrollSensitivity: 30, // pixels from edge for scrolling to start + scrollSpeed: 200, // pixels per second, at maximum speed + scrollIntervalMs: 50, // millisecond wait between scroll increment + + + // Call this when the user does a mousedown. Will probably lead to startListening + mousedown: function(ev) { + if (isPrimaryMouseButton(ev)) { + + ev.preventDefault(); // prevents native selection in most browsers + + this.startListening(ev); + + // start the drag immediately if there is no minimum distance for a drag start + if (!this.options.distance) { + this.startDrag(ev); + } + } + }, + + + // Call this to start tracking mouse movements + startListening: function(ev) { + var scrollParent; + var cell; + + if (!this.isListening) { + + // grab scroll container and attach handler + if (ev && this.options.scroll) { + scrollParent = getScrollParent($(ev.target)); + if (!scrollParent.is(window) && !scrollParent.is(document)) { + this.scrollEl = scrollParent; + + // scope to `this`, and use `debounce` to make sure rapid calls don't happen + this.scrollHandlerProxy = debounce($.proxy(this, 'scrollHandler'), 100); + this.scrollEl.on('scroll', this.scrollHandlerProxy); + } + } + + this.computeCoords(); // relies on `scrollEl` + + // get info on the initial cell, date, and coordinates + if (ev) { + cell = this.getCell(ev); + this.origCell = cell; + this.origDate = cell ? cell.date : null; + + this.mouseX0 = ev.pageX; + this.mouseY0 = ev.pageY; + } + + $(document) + .on('mousemove', this.mousemoveProxy = $.proxy(this, 'mousemove')) + .on('mouseup', this.mouseupProxy = $.proxy(this, 'mouseup')) + .on('selectstart', this.preventDefault); // prevents native selection in IE<=8 + + this.isListening = true; + this.trigger('listenStart', ev); + } + }, + + + // Recomputes the drag-critical positions of elements + computeCoords: function() { + this.coordMap.build(); + this.computeScrollBounds(); + }, + + + // Called when the user moves the mouse + mousemove: function(ev) { + var minDistance; + var distanceSq; // current distance from mouseX0/mouseY0, squared + + if (!this.isDragging) { // if not already dragging... + // then start the drag if the minimum distance criteria is met + minDistance = this.options.distance || 1; + distanceSq = Math.pow(ev.pageX - this.mouseX0, 2) + Math.pow(ev.pageY - this.mouseY0, 2); + if (distanceSq >= minDistance * minDistance) { // use pythagorean theorem + this.startDrag(ev); + } + } + + if (this.isDragging) { + this.drag(ev); // report a drag, even if this mousemove initiated the drag + } + }, + + + // Call this to initiate a legitimate drag. + // This function is called internally from this class, but can also be called explicitly from outside + startDrag: function(ev) { + var cell; + + if (!this.isListening) { // startDrag must have manually initiated + this.startListening(); + } + + if (!this.isDragging) { + this.isDragging = true; + this.trigger('dragStart', ev); + + // report the initial cell the mouse is over + cell = this.getCell(ev); + if (cell) { + this.cellOver(cell, true); + } + } + }, + + + // Called while the mouse is being moved and when we know a legitimate drag is taking place + drag: function(ev) { + var cell; + + if (this.isDragging) { + cell = this.getCell(ev); + + if (!isCellsEqual(cell, this.cell)) { // a different cell than before? + if (this.cell) { + this.cellOut(); + } + if (cell) { + this.cellOver(cell); + } + } + + this.dragScroll(ev); // will possibly cause scrolling + } + }, + + + // Called when a the mouse has just moved over a new cell + cellOver: function(cell) { + this.cell = cell; + this.date = cell.date; + this.trigger('cellOver', cell, cell.date); + }, + + + // Called when the mouse has just moved out of a cell + cellOut: function() { + if (this.cell) { + this.trigger('cellOut', this.cell); + this.cell = null; + this.date = null; + } + }, + + + // Called when the user does a mouseup + mouseup: function(ev) { + this.stopDrag(ev); + this.stopListening(ev); + }, + + + // Called when the drag is over. Will not cause listening to stop however. + // A concluding 'cellOut' event will NOT be triggered. + stopDrag: function(ev) { + if (this.isDragging) { + this.stopScrolling(); + this.trigger('dragStop', ev); + this.isDragging = false; + } + }, + + + // Call this to stop listening to the user's mouse events + stopListening: function(ev) { + if (this.isListening) { + + // remove the scroll handler if there is a scrollEl + if (this.scrollEl) { + this.scrollEl.off('scroll', this.scrollHandlerProxy); + this.scrollHandlerProxy = null; + } + + $(document) + .off('mousemove', this.mousemoveProxy) + .off('mouseup', this.mouseupProxy) + .off('selectstart', this.preventDefault); + + this.mousemoveProxy = null; + this.mouseupProxy = null; + + this.isListening = false; + this.trigger('listenStop', ev); + + this.origCell = this.cell = null; + this.origDate = this.date = null; + } + }, + + + // Gets the cell underneath the coordinates for the given mouse event + getCell: function(ev) { + return this.coordMap.getCell(ev.pageX, ev.pageY); + }, + + + // Triggers a callback. Calls a function in the option hash of the same name. + // Arguments beyond the first `name` are forwarded on. + trigger: function(name) { + if (this.options[name]) { + this.options[name].apply(this, Array.prototype.slice.call(arguments, 1)); + } + }, + + + // Stops a given mouse event from doing it's native browser action. In our case, text selection. + preventDefault: function(ev) { + ev.preventDefault(); + }, + + + /* Scrolling + ------------------------------------------------------------------------------------------------------------------*/ + + + // Computes and stores the bounding rectangle of scrollEl + computeScrollBounds: function() { + var el = this.scrollEl; + var offset; + + if (el) { + offset = el.offset(); + this.scrollBounds = { + top: offset.top, + left: offset.left, + bottom: offset.top + el.outerHeight(), + right: offset.left + el.outerWidth() + }; + } + }, + + + // Called when the dragging is in progress and scrolling should be updated + dragScroll: function(ev) { + var sensitivity = this.scrollSensitivity; + var bounds = this.scrollBounds; + var topCloseness, bottomCloseness; + var leftCloseness, rightCloseness; + var topVel = 0; + var leftVel = 0; + + if (bounds) { // only scroll if scrollEl exists + + // compute closeness to edges. valid range is from 0.0 - 1.0 + topCloseness = (sensitivity - (ev.pageY - bounds.top)) / sensitivity; + bottomCloseness = (sensitivity - (bounds.bottom - ev.pageY)) / sensitivity; + leftCloseness = (sensitivity - (ev.pageX - bounds.left)) / sensitivity; + rightCloseness = (sensitivity - (bounds.right - ev.pageX)) / sensitivity; + + // translate vertical closeness into velocity. + // mouse must be completely in bounds for velocity to happen. + if (topCloseness >= 0 && topCloseness <= 1) { + topVel = topCloseness * this.scrollSpeed * -1; // negative. for scrolling up + } + else if (bottomCloseness >= 0 && bottomCloseness <= 1) { + topVel = bottomCloseness * this.scrollSpeed; + } + + // translate horizontal closeness into velocity + if (leftCloseness >= 0 && leftCloseness <= 1) { + leftVel = leftCloseness * this.scrollSpeed * -1; // negative. for scrolling left + } + else if (rightCloseness >= 0 && rightCloseness <= 1) { + leftVel = rightCloseness * this.scrollSpeed; + } + } + + this.setScrollVel(topVel, leftVel); + }, + + + // Sets the speed-of-scrolling for the scrollEl + setScrollVel: function(topVel, leftVel) { + + this.scrollTopVel = topVel; + this.scrollLeftVel = leftVel; + + this.constrainScrollVel(); // massages into realistic values + + // if there is non-zero velocity, and an animation loop hasn't already started, then START + if ((this.scrollTopVel || this.scrollLeftVel) && !this.scrollIntervalId) { + this.scrollIntervalId = setInterval( + $.proxy(this, 'scrollIntervalFunc'), // scope to `this` + this.scrollIntervalMs + ); + } + }, + + + // Forces scrollTopVel and scrollLeftVel to be zero if scrolling has already gone all the way + constrainScrollVel: function() { + var el = this.scrollEl; + + if (this.scrollTopVel < 0) { // scrolling up? + if (el.scrollTop() <= 0) { // already scrolled all the way up? + this.scrollTopVel = 0; + } + } + else if (this.scrollTopVel > 0) { // scrolling down? + if (el.scrollTop() + el[0].clientHeight >= el[0].scrollHeight) { // already scrolled all the way down? + this.scrollTopVel = 0; + } + } + + if (this.scrollLeftVel < 0) { // scrolling left? + if (el.scrollLeft() <= 0) { // already scrolled all the left? + this.scrollLeftVel = 0; + } + } + else if (this.scrollLeftVel > 0) { // scrolling right? + if (el.scrollLeft() + el[0].clientWidth >= el[0].scrollWidth) { // already scrolled all the way right? + this.scrollLeftVel = 0; + } + } + }, + + + // This function gets called during every iteration of the scrolling animation loop + scrollIntervalFunc: function() { + var el = this.scrollEl; + var frac = this.scrollIntervalMs / 1000; // considering animation frequency, what the vel should be mult'd by + + // change the value of scrollEl's scroll + if (this.scrollTopVel) { + el.scrollTop(el.scrollTop() + this.scrollTopVel * frac); + } + if (this.scrollLeftVel) { + el.scrollLeft(el.scrollLeft() + this.scrollLeftVel * frac); + } + + this.constrainScrollVel(); // since the scroll values changed, recompute the velocities + + // if scrolled all the way, which causes the vels to be zero, stop the animation loop + if (!this.scrollTopVel && !this.scrollLeftVel) { + this.stopScrolling(); + } + }, + + + // Kills any existing scrolling animation loop + stopScrolling: function() { + if (this.scrollIntervalId) { + clearInterval(this.scrollIntervalId); + this.scrollIntervalId = null; + + // when all done with scrolling, recompute positions since they probably changed + this.computeCoords(); + } + }, + + + // Get called when the scrollEl is scrolled (NOTE: this is delayed via debounce) + scrollHandler: function() { + // recompute all coordinates, but *only* if this is *not* part of our scrolling animation + if (!this.scrollIntervalId) { + this.computeCoords(); + } + } + +}; + + +// Returns `true` if the cells are identically equal. `false` otherwise. +// They must have the same row, col, and be from the same grid. +// Two null values will be considered equal, as two "out of the grid" states are the same. +function isCellsEqual(cell1, cell2) { + + if (!cell1 && !cell2) { + return true; + } + + if (cell1 && cell2) { + return cell1.grid === cell2.grid && + cell1.row === cell2.row && + cell1.col === cell2.col; + } + + return false; +} + +;; + +/* Creates a clone of an element and lets it track the mouse as it moves +----------------------------------------------------------------------------------------------------------------------*/ + +function MouseFollower(sourceEl, options) { + this.options = options = options || {}; + this.sourceEl = sourceEl; + this.parentEl = options.parentEl ? $(options.parentEl) : sourceEl.parent(); // default to sourceEl's parent +} + + +MouseFollower.prototype = { + + options: null, + + sourceEl: null, // the element that will be cloned and made to look like it is dragging + el: null, // the clone of `sourceEl` that will track the mouse + parentEl: null, // the element that `el` (the clone) will be attached to + + // the initial position of el, relative to the offset parent. made to match the initial offset of sourceEl + top0: null, + left0: null, + + // the initial position of the mouse + mouseY0: null, + mouseX0: null, + + // the number of pixels the mouse has moved from its initial position + topDelta: null, + leftDelta: null, + + mousemoveProxy: null, // document mousemove handler, bound to the MouseFollower's `this` + + isFollowing: false, + isHidden: false, + isAnimating: false, // doing the revert animation? + + + // Causes the element to start following the mouse + start: function(ev) { + if (!this.isFollowing) { + this.isFollowing = true; + + this.mouseY0 = ev.pageY; + this.mouseX0 = ev.pageX; + this.topDelta = 0; + this.leftDelta = 0; + + if (!this.isHidden) { + this.updatePosition(); + } + + $(document).on('mousemove', this.mousemoveProxy = $.proxy(this, 'mousemove')); + } + }, + + + // Causes the element to stop following the mouse. If shouldRevert is true, will animate back to original position. + // `callback` gets invoked when the animation is complete. If no animation, it is invoked immediately. + stop: function(shouldRevert, callback) { + var _this = this; + var revertDuration = this.options.revertDuration; + + function complete() { + this.isAnimating = false; + _this.destroyEl(); + + this.top0 = this.left0 = null; // reset state for future updatePosition calls + + if (callback) { + callback(); + } + } + + if (this.isFollowing && !this.isAnimating) { // disallow more than one stop animation at a time + this.isFollowing = false; + + $(document).off('mousemove', this.mousemoveProxy); + + if (shouldRevert && revertDuration && !this.isHidden) { // do a revert animation? + this.isAnimating = true; + this.el.animate({ + top: this.top0, + left: this.left0 + }, { + duration: revertDuration, + complete: complete + }); + } + else { + complete(); + } + } + }, + + + // Gets the tracking element. Create it if necessary + getEl: function() { + var el = this.el; + + if (!el) { + this.sourceEl.width(); // hack to force IE8 to compute correct bounding box + el = this.el = this.sourceEl.clone() + .css({ + position: 'absolute', + visibility: '', // in case original element was hidden (commonly through hideEvents()) + display: this.isHidden ? 'none' : '', // for when initially hidden + margin: 0, + right: 'auto', // erase and set width instead + bottom: 'auto', // erase and set height instead + width: this.sourceEl.width(), // explicit height in case there was a 'right' value + height: this.sourceEl.height(), // explicit width in case there was a 'bottom' value + opacity: this.options.opacity || '', + zIndex: this.options.zIndex + }) + .appendTo(this.parentEl); + } + + return el; + }, + + + // Removes the tracking element if it has already been created + destroyEl: function() { + if (this.el) { + this.el.remove(); + this.el = null; + } + }, + + + // Update the CSS position of the tracking element + updatePosition: function() { + var sourceOffset; + var origin; + + this.getEl(); // ensure this.el + + // make sure origin info was computed + if (this.top0 === null) { + this.sourceEl.width(); // hack to force IE8 to compute correct bounding box + sourceOffset = this.sourceEl.offset(); + origin = this.el.offsetParent().offset(); + this.top0 = sourceOffset.top - origin.top; + this.left0 = sourceOffset.left - origin.left; + } + + this.el.css({ + top: this.top0 + this.topDelta, + left: this.left0 + this.leftDelta + }); + }, + + + // Gets called when the user moves the mouse + mousemove: function(ev) { + this.topDelta = ev.pageY - this.mouseY0; + this.leftDelta = ev.pageX - this.mouseX0; + + if (!this.isHidden) { + this.updatePosition(); + } + }, + + + // Temporarily makes the tracking element invisible. Can be called before following starts + hide: function() { + if (!this.isHidden) { + this.isHidden = true; + if (this.el) { + this.el.hide(); + } + } + }, + + + // Show the tracking element after it has been temporarily hidden + show: function() { + if (this.isHidden) { + this.isHidden = false; + this.updatePosition(); + this.getEl().show(); + } + } + +}; + +;; + +/* A utility class for rendering rows. +----------------------------------------------------------------------------------------------------------------------*/ +// It leverages methods of the subclass and the View to determine custom rendering behavior for each row "type" +// (such as highlight rows, day rows, helper rows, etc). + +function RowRenderer(view) { + this.view = view; +} + + +RowRenderer.prototype = { + + view: null, // a View object + cellHtml: '', // plain default HTML used for a cell when no other is available + + + // Renders the HTML for a row, leveraging custom cell-HTML-renderers based on the `rowType`. + // Also applies the "intro" and "outro" cells, which are specified by the subclass and views. + // `row` is an optional row number. + rowHtml: function(rowType, row) { + var view = this.view; + var renderCell = this.getHtmlRenderer('cell', rowType); + var cellHtml = ''; + var col; + var date; + + row = row || 0; + + for (col = 0; col < view.colCnt; col++) { + date = view.cellToDate(row, col); + cellHtml += renderCell(row, col, date); + } + + cellHtml = this.bookendCells(cellHtml, rowType, row); // apply intro and outro + + return '' + cellHtml + ''; + }, + + + // Applies the "intro" and "outro" HTML to the given cells. + // Intro means the leftmost cell when the calendar is LTR and the rightmost cell when RTL. Vice-versa for outro. + // `cells` can be an HTML string of 's or a jQuery element + // `row` is an optional row number. + bookendCells: function(cells, rowType, row) { + var view = this.view; + var intro = this.getHtmlRenderer('intro', rowType)(row || 0); + var outro = this.getHtmlRenderer('outro', rowType)(row || 0); + var isRTL = view.opt('isRTL'); + var prependHtml = isRTL ? outro : intro; + var appendHtml = isRTL ? intro : outro; + + if (typeof cells === 'string') { + return prependHtml + cells + appendHtml; + } + else { // a jQuery element + return cells.prepend(prependHtml).append(appendHtml); + } + }, + + + // Returns an HTML-rendering function given a specific `rendererName` (like cell, intro, or outro) and a specific + // `rowType` (like day, eventSkeleton, helperSkeleton), which is optional. + // If a renderer for the specific rowType doesn't exist, it will fall back to a generic renderer. + // We will query the View object first for any custom rendering functions, then the methods of the subclass. + getHtmlRenderer: function(rendererName, rowType) { + var view = this.view; + var generalName; // like "cellHtml" + var specificName; // like "dayCellHtml". based on rowType + var provider; // either the View or the RowRenderer subclass, whichever provided the method + var renderer; + + generalName = rendererName + 'Html'; + if (rowType) { + specificName = rowType + capitaliseFirstLetter(rendererName) + 'Html'; + } + + if (specificName && (renderer = view[specificName])) { + provider = view; + } + else if (specificName && (renderer = this[specificName])) { + provider = this; + } + else if ((renderer = view[generalName])) { + provider = view; + } + else if ((renderer = this[generalName])) { + provider = this; + } + + if (typeof renderer === 'function') { + return function(row) { + return renderer.apply(provider, arguments) || ''; // use correct `this` and always return a string + }; + } + + // the rendered can be a plain string as well. if not specified, always an empty string. + return function() { + return renderer || ''; + }; + } + +}; + +;; + +/* An abstract class comprised of a "grid" of cells that each represent a specific datetime +----------------------------------------------------------------------------------------------------------------------*/ + +function Grid(view) { + RowRenderer.call(this, view); // call the super-constructor + this.coordMap = new GridCoordMap(this); +} + + +Grid.prototype = createObject(RowRenderer.prototype); // declare the super-class +$.extend(Grid.prototype, { + + el: null, // the containing element + coordMap: null, // a GridCoordMap that converts pixel values to datetimes + cellDuration: null, // a cell's duration. subclasses must assign this ASAP + + + // Renders the grid into the `el` element. + // Subclasses should override and call this super-method when done. + render: function() { + this.bindHandlers(); + }, + + + // Called when the grid's resources need to be cleaned up + destroy: function() { + // subclasses can implement + }, + + + /* Coordinates & Cells + ------------------------------------------------------------------------------------------------------------------*/ + + + // Populates the given empty arrays with the y and x coordinates of the cells + buildCoords: function(rows, cols) { + // subclasses must implement + }, + + + // Given a cell object, returns the date for that cell + getCellDate: function(cell) { + // subclasses must implement + }, + + + // Given a cell object, returns the element that represents the cell's whole-day + getCellDayEl: function(cell) { + // subclasses must implement + }, + + + // Converts a range with an inclusive `start` and an exclusive `end` into an array of segment objects + rangeToSegs: function(start, end) { + // subclasses must implement + }, + + + /* Handlers + ------------------------------------------------------------------------------------------------------------------*/ + + + // Attach handlers to `this.el`, using bubbling to listen to all ancestors. + // We don't need to undo any of this in a "destroy" method, because the view will simply remove `this.el` from the + // DOM and jQuery will be smart enough to garbage collect the handlers. + bindHandlers: function() { + var _this = this; + + this.el.on('mousedown', function(ev) { + if ( + !$(ev.target).is('.fc-event-container *, .fc-more') && // not an an event element, or "more.." link + !$(ev.target).closest('.fc-popover').length // not on a popover (like the "more.." events one) + ) { + _this.dayMousedown(ev); + } + }); + + this.bindSegHandlers(); // attach event-element-related handlers. in Grid.events.js + }, + + + // Process a mousedown on an element that represents a day. For day clicking and selecting. + dayMousedown: function(ev) { + var _this = this; + var view = this.view; + var isSelectable = view.opt('selectable'); + var dates = null; // the inclusive dates of the selection. will be null if no selection + var start; // the inclusive start of the selection + var end; // the *exclusive* end of the selection + var dayEl; + + // this listener tracks a mousedown on a day element, and a subsequent drag. + // if the drag ends on the same day, it is a 'dayClick'. + // if 'selectable' is enabled, this listener also detects selections. + var dragListener = new DragListener(this.coordMap, { + //distance: 5, // needs more work if we want dayClick to fire correctly + scroll: view.opt('dragScroll'), + dragStart: function() { + view.unselect(); // since we could be rendering a new selection, we want to clear any old one + }, + cellOver: function(cell, date) { + if (dragListener.origDate) { // click needs to have started on a cell + + dayEl = _this.getCellDayEl(cell); + + dates = [ date, dragListener.origDate ].sort(dateCompare); + start = dates[0]; + end = dates[1].clone().add(_this.cellDuration); + + if (isSelectable) { + _this.renderSelection(start, end); + } + } + }, + cellOut: function(cell, date) { + dates = null; + _this.destroySelection(); + }, + listenStop: function(ev) { + if (dates) { // started and ended on a cell? + if (dates[0].isSame(dates[1])) { + view.trigger('dayClick', dayEl[0], start, ev); + } + if (isSelectable) { + // the selection will already have been rendered. just report it + view.reportSelection(start, end, ev); + } + } + } + }); + + dragListener.mousedown(ev); // start listening, which will eventually initiate a dragStart + }, + + + /* Event Dragging + ------------------------------------------------------------------------------------------------------------------*/ + + + // Renders a visual indication of a event being dragged over the given date(s). + // `end` can be null, as well as `seg`. See View's documentation on renderDrag for more info. + // A returned value of `true` signals that a mock "helper" event has been rendered. + renderDrag: function(start, end, seg) { + // subclasses must implement + }, + + + // Unrenders a visual indication of an event being dragged + destroyDrag: function() { + // subclasses must implement + }, + + + /* Event Resizing + ------------------------------------------------------------------------------------------------------------------*/ + + + // Renders a visual indication of an event being resized. + // `start` and `end` are the updated dates of the event. `seg` is the original segment object involved in the drag. + renderResize: function(start, end, seg) { + // subclasses must implement + }, + + + // Unrenders a visual indication of an event being resized. + destroyResize: function() { + // subclasses must implement + }, + + + /* Event Helper + ------------------------------------------------------------------------------------------------------------------*/ + + + // Renders a mock event over the given date(s). + // `end` can be null, in which case the mock event that is rendered will have a null end time. + // `sourceSeg` is the internal segment object involved in the drag. If null, something external is dragging. + renderRangeHelper: function(start, end, sourceSeg) { + var view = this.view; + var fakeEvent; + + // compute the end time if forced to do so (this is what EventManager does) + if (!end && view.opt('forceEventDuration')) { + end = view.calendar.getDefaultEventEnd(!start.hasTime(), start); + } + + fakeEvent = sourceSeg ? createObject(sourceSeg.event) : {}; // mask the original event object if possible + fakeEvent.start = start; + fakeEvent.end = end; + fakeEvent.allDay = !(start.hasTime() || (end && end.hasTime())); // freshly compute allDay + + // this extra className will be useful for differentiating real events from mock events in CSS + fakeEvent.className = (fakeEvent.className || []).concat('fc-helper'); + + // if something external is being dragged in, don't render a resizer + if (!sourceSeg) { + fakeEvent.editable = false; + } + + this.renderHelper(fakeEvent, sourceSeg); // do the actual rendering + }, + + + // Renders a mock event + renderHelper: function(event, sourceSeg) { + // subclasses must implement + }, + + + // Unrenders a mock event + destroyHelper: function() { + // subclasses must implement + }, + + + /* Selection + ------------------------------------------------------------------------------------------------------------------*/ + + + // Renders a visual indication of a selection. Will highlight by default but can be overridden by subclasses. + renderSelection: function(start, end) { + this.renderHighlight(start, end); + }, + + + // Unrenders any visual indications of a selection. Will unrender a highlight by default. + destroySelection: function() { + this.destroyHighlight(); + }, + + + /* Highlight + ------------------------------------------------------------------------------------------------------------------*/ + + + // Puts visual emphasis on a certain date range + renderHighlight: function(start, end) { + // subclasses should implement + }, + + + // Removes visual emphasis on a date range + destroyHighlight: function() { + // subclasses should implement + }, + + + + /* Generic rendering utilities for subclasses + ------------------------------------------------------------------------------------------------------------------*/ + + + // Renders a day-of-week header row + headHtml: function() { + return '' + + '
' + + '' + + '' + + this.rowHtml('head') + // leverages RowRenderer + '' + + '
' + + '
'; + }, + + + // Used by the `headHtml` method, via RowRenderer, for rendering the HTML of a day-of-week header cell + headCellHtml: function(row, col, date) { + var view = this.view; + var calendar = view.calendar; + var colFormat = view.opt('columnFormat'); + + return '' + + '' + + htmlEscape(calendar.formatDate(date, colFormat)) + + ''; + }, + + + // Renders the HTML for a single-day background cell + bgCellHtml: function(row, col, date) { + var view = this.view; + var classes = this.getDayClasses(date); + + classes.unshift('fc-day', view.widgetContentClass); + + return ''; + }, + + + // Computes HTML classNames for a single-day cell + getDayClasses: function(date) { + var view = this.view; + var today = view.calendar.getNow().stripTime(); + var classes = [ 'fc-' + dayIDs[date.day()] ]; + + if ( + view.name === 'month' && + date.month() != view.intervalStart.month() + ) { + classes.push('fc-other-month'); + } + + if (date.isSame(today, 'day')) { + classes.push( + 'fc-today', + view.highlightStateClass + ); + } + else if (date < today) { + classes.push('fc-past'); + } + else { + classes.push('fc-future'); + } + + return classes; + } + +}); + +;; + +/* Event-rendering and event-interaction methods for the abstract Grid class +----------------------------------------------------------------------------------------------------------------------*/ + +$.extend(Grid.prototype, { + + mousedOverSeg: null, // the segment object the user's mouse is over. null if over nothing + isDraggingSeg: false, // is a segment being dragged? boolean + isResizingSeg: false, // is a segment being resized? boolean + + + // Renders the given events onto the grid + renderEvents: function(events) { + // subclasses must implement + }, + + + // Retrieves all rendered segment objects in this grid + getSegs: function() { + // subclasses must implement + }, + + + // Unrenders all events. Subclasses should implement, calling this super-method first. + destroyEvents: function() { + this.triggerSegMouseout(); // trigger an eventMouseout if user's mouse is over an event + }, + + + // Renders a `el` property for each seg, and only returns segments that successfully rendered + renderSegs: function(segs, disableResizing) { + var view = this.view; + var html = ''; + var renderedSegs = []; + var i; + + // build a large concatenation of event segment HTML + for (i = 0; i < segs.length; i++) { + html += this.renderSegHtml(segs[i], disableResizing); + } + + // Grab individual elements from the combined HTML string. Use each as the default rendering. + // Then, compute the 'el' for each segment. An el might be null if the eventRender callback returned false. + $(html).each(function(i, node) { + var seg = segs[i]; + var el = view.resolveEventEl(seg.event, $(node)); + if (el) { + el.data('fc-seg', seg); // used by handlers + seg.el = el; + renderedSegs.push(seg); + } + }); + + return renderedSegs; + }, + + + // Generates the HTML for the default rendering of a segment + renderSegHtml: function(seg, disableResizing) { + // subclasses must implement + }, + + + // Converts an array of event objects into an array of segment objects + eventsToSegs: function(events, intervalStart, intervalEnd) { + var _this = this; + + return $.map(events, function(event) { + return _this.eventToSegs(event, intervalStart, intervalEnd); // $.map flattens all returned arrays together + }); + }, + + + // Slices a single event into an array of event segments. + // When `intervalStart` and `intervalEnd` are specified, intersect the events with that interval. + // Otherwise, let the subclass decide how it wants to slice the segments over the grid. + eventToSegs: function(event, intervalStart, intervalEnd) { + var eventStart = event.start.clone().stripZone(); // normalize + var eventEnd = this.view.calendar.getEventEnd(event).stripZone(); // compute (if necessary) and normalize + var segs; + var i, seg; + + if (intervalStart && intervalEnd) { + seg = intersectionToSeg(eventStart, eventEnd, intervalStart, intervalEnd); + segs = seg ? [ seg ] : []; + } + else { + segs = this.rangeToSegs(eventStart, eventEnd); // defined by the subclass + } + + // assign extra event-related properties to the segment objects + for (i = 0; i < segs.length; i++) { + seg = segs[i]; + seg.event = event; + seg.eventStartMS = +eventStart; + seg.eventDurationMS = eventEnd - eventStart; + } + + return segs; + }, + + + /* Handlers + ------------------------------------------------------------------------------------------------------------------*/ + + + // Attaches event-element-related handlers to the container element and leverage bubbling + bindSegHandlers: function() { + var _this = this; + var view = this.view; + + $.each( + { + mouseenter: function(seg, ev) { + _this.triggerSegMouseover(seg, ev); + }, + mouseleave: function(seg, ev) { + _this.triggerSegMouseout(seg, ev); + }, + click: function(seg, ev) { + return view.trigger('eventClick', this, seg.event, ev); // can return `false` to cancel + }, + mousedown: function(seg, ev) { + if ($(ev.target).is('.fc-resizer') && view.isEventResizable(seg.event)) { + _this.segResizeMousedown(seg, ev); + } + else if (view.isEventDraggable(seg.event)) { + _this.segDragMousedown(seg, ev); + } + } + }, + function(name, func) { + // attach the handler to the container element and only listen for real event elements via bubbling + _this.el.on(name, '.fc-event-container > *', function(ev) { + var seg = $(this).data('fc-seg'); // grab segment data. put there by View::renderEvents + + // only call the handlers if there is not a drag/resize in progress + if (seg && !_this.isDraggingSeg && !_this.isResizingSeg) { + return func.call(this, seg, ev); // `this` will be the event element + } + }); + } + ); + }, + + + // Updates internal state and triggers handlers for when an event element is moused over + triggerSegMouseover: function(seg, ev) { + if (!this.mousedOverSeg) { + this.mousedOverSeg = seg; + this.view.trigger('eventMouseover', seg.el[0], seg.event, ev); + } + }, + + + // Updates internal state and triggers handlers for when an event element is moused out. + // Can be given no arguments, in which case it will mouseout the segment that was previously moused over. + triggerSegMouseout: function(seg, ev) { + ev = ev || {}; // if given no args, make a mock mouse event + + if (this.mousedOverSeg) { + seg = seg || this.mousedOverSeg; // if given no args, use the currently moused-over segment + this.mousedOverSeg = null; + this.view.trigger('eventMouseout', seg.el[0], seg.event, ev); + } + }, + + + /* Dragging + ------------------------------------------------------------------------------------------------------------------*/ + + + // Called when the user does a mousedown on an event, which might lead to dragging. + // Generic enough to work with any type of Grid. + segDragMousedown: function(seg, ev) { + var _this = this; + var view = this.view; + var el = seg.el; + var event = seg.event; + var newStart, newEnd; + + // A clone of the original element that will move with the mouse + var mouseFollower = new MouseFollower(seg.el, { + parentEl: view.el, + opacity: view.opt('dragOpacity'), + revertDuration: view.opt('dragRevertDuration'), + zIndex: 2 // one above the .fc-view + }); + + // Tracks mouse movement over the *view's* coordinate map. Allows dragging and dropping between subcomponents + // of the view. + var dragListener = new DragListener(view.coordMap, { + distance: 5, + scroll: view.opt('dragScroll'), + listenStart: function(ev) { + mouseFollower.hide(); // don't show until we know this is a real drag + mouseFollower.start(ev); + }, + dragStart: function(ev) { + _this.triggerSegMouseout(seg, ev); // ensure a mouseout on the manipulated event has been reported + _this.isDraggingSeg = true; + view.hideEvent(event); // hide all event segments. our mouseFollower will take over + view.trigger('eventDragStart', el[0], event, ev, {}); // last argument is jqui dummy + }, + cellOver: function(cell, date) { + var origDate = seg.cellDate || dragListener.origDate; + var res = _this.computeDraggedEventDates(seg, origDate, date); + newStart = res.start; + newEnd = res.end; + + if (view.renderDrag(newStart, newEnd, seg)) { // have the view render a visual indication + mouseFollower.hide(); // if the view is already using a mock event "helper", hide our own + } + else { + mouseFollower.show(); + } + }, + cellOut: function() { // called before mouse moves to a different cell OR moved out of all cells + newStart = null; + view.destroyDrag(); // unrender whatever was done in view.renderDrag + mouseFollower.show(); // show in case we are moving out of all cells + }, + dragStop: function(ev) { + var hasChanged = newStart && !newStart.isSame(event.start); + + // do revert animation if hasn't changed. calls a callback when finished (whether animation or not) + mouseFollower.stop(!hasChanged, function() { + _this.isDraggingSeg = false; + view.destroyDrag(); + view.showEvent(event); + view.trigger('eventDragStop', el[0], event, ev, {}); // last argument is jqui dummy + + if (hasChanged) { + view.eventDrop(el[0], event, newStart, ev); // will rerender all events... + } + }); + }, + listenStop: function() { + mouseFollower.stop(); // put in listenStop in case there was a mousedown but the drag never started + } + }); + + dragListener.mousedown(ev); // start listening, which will eventually lead to a dragStart + }, + + + // Given a segment, the dates where a drag began and ended, calculates the Event Object's new start and end dates + computeDraggedEventDates: function(seg, dragStartDate, dropDate) { + var view = this.view; + var event = seg.event; + var start = event.start; + var end = view.calendar.getEventEnd(event); + var delta; + var newStart; + var newEnd; + + if (dropDate.hasTime() === dragStartDate.hasTime()) { + delta = dayishDiff(dropDate, dragStartDate); + newStart = start.clone().add(delta); + if (event.end === null) { // do we need to compute an end? + newEnd = null; + } + else { + newEnd = end.clone().add(delta); + } + } + else { + // if switching from day <-> timed, start should be reset to the dropped date, and the end cleared + newStart = dropDate; + newEnd = null; // end should be cleared + } + + return { start: newStart, end: newEnd }; + }, + + + /* Resizing + ------------------------------------------------------------------------------------------------------------------*/ + + + // Called when the user does a mousedown on an event's resizer, which might lead to resizing. + // Generic enough to work with any type of Grid. + segResizeMousedown: function(seg, ev) { + var _this = this; + var view = this.view; + var el = seg.el; + var event = seg.event; + var start = event.start; + var end = view.calendar.getEventEnd(event); + var newEnd = null; + var dragListener; + + function destroy() { // resets the rendering + _this.destroyResize(); + view.showEvent(event); + } + + // Tracks mouse movement over the *grid's* coordinate map + dragListener = new DragListener(this.coordMap, { + distance: 5, + scroll: view.opt('dragScroll'), + dragStart: function(ev) { + _this.triggerSegMouseout(seg, ev); // ensure a mouseout on the manipulated event has been reported + _this.isResizingSeg = true; + view.trigger('eventResizeStart', el[0], event, ev, {}); // last argument is jqui dummy + }, + cellOver: function(cell, date) { + // compute the new end. don't allow it to go before the event's start + if (date.isBefore(start)) { // allows comparing ambig to non-ambig + date = start; + } + newEnd = date.clone().add(_this.cellDuration); // make it an exclusive end + + if (newEnd.isSame(end)) { + newEnd = null; + destroy(); + } + else { + _this.renderResize(start, newEnd, seg); + view.hideEvent(event); + } + }, + cellOut: function() { // called before mouse moves to a different cell OR moved out of all cells + newEnd = null; + destroy(); + }, + dragStop: function(ev) { + _this.isResizingSeg = false; + destroy(); + view.trigger('eventResizeStop', el[0], event, ev, {}); // last argument is jqui dummy + + if (newEnd) { + view.eventResize(el[0], event, newEnd, ev); // will rerender all events... + } + } + }); + + dragListener.mousedown(ev); // start listening, which will eventually lead to a dragStart + }, + + + /* Rendering Utils + ------------------------------------------------------------------------------------------------------------------*/ + + + // Generic utility for generating the HTML classNames for an event segment's element + getSegClasses: function(seg, isDraggable, isResizable) { + var event = seg.event; + var classes = [ + 'fc-event', + seg.isStart ? 'fc-start' : 'fc-not-start', + seg.isEnd ? 'fc-end' : 'fc-not-end' + ].concat( + event.className, + event.source ? event.source.className : [] + ); + + if (isDraggable) { + classes.push('fc-draggable'); + } + if (isResizable) { + classes.push('fc-resizable'); + } + + return classes; + }, + + + // Utility for generating a CSS string with all the event skin-related properties + getEventSkinCss: function(event) { + var view = this.view; + var source = event.source || {}; + var eventColor = event.color; + var sourceColor = source.color; + var optionColor = view.opt('eventColor'); + var backgroundColor = + event.backgroundColor || + eventColor || + source.backgroundColor || + sourceColor || + view.opt('eventBackgroundColor') || + optionColor; + var borderColor = + event.borderColor || + eventColor || + source.borderColor || + sourceColor || + view.opt('eventBorderColor') || + optionColor; + var textColor = + event.textColor || + source.textColor || + view.opt('eventTextColor'); + var statements = []; + if (backgroundColor) { + statements.push('background-color:' + backgroundColor); + } + if (borderColor) { + statements.push('border-color:' + borderColor); + } + if (textColor) { + statements.push('color:' + textColor); + } + return statements.join(';'); + } + +}); + + +/* Event Segment Utilities +----------------------------------------------------------------------------------------------------------------------*/ + + +// A cmp function for determining which segments should take visual priority +function compareSegs(seg1, seg2) { + return seg1.eventStartMS - seg2.eventStartMS || // earlier events go first + seg2.eventDurationMS - seg1.eventDurationMS || // tie? longer events go first + seg2.event.allDay - seg1.event.allDay || // tie? put all-day events first (booleans cast to 0/1) + (seg1.event.title || '').localeCompare(seg2.event.title); // tie? alphabetically by title +} + + +;; + +/* A component that renders a grid of whole-days that runs horizontally. There can be multiple rows, one per week. +----------------------------------------------------------------------------------------------------------------------*/ + +function DayGrid(view) { + Grid.call(this, view); // call the super-constructor +} + + +DayGrid.prototype = createObject(Grid.prototype); // declare the super-class +$.extend(DayGrid.prototype, { + + numbersVisible: false, // should render a row for day/week numbers? manually set by the view + cellDuration: moment.duration({ days: 1 }), // required for Grid.event.js. Each cell is always a single day + bottomCoordPadding: 0, // hack for extending the hit area for the last row of the coordinate grid + + rowEls: null, // set of fake row elements + dayEls: null, // set of whole-day elements comprising the row's background + helperEls: null, // set of cell skeleton elements for rendering the mock event "helper" + highlightEls: null, // set of cell skeleton elements for rendering the highlight + + + // Renders the rows and columns into the component's `this.el`, which should already be assigned. + // isRigid determins whether the individual rows should ignore the contents and be a constant height. + // Relies on the view's colCnt and rowCnt. In the future, this component should probably be self-sufficient. + render: function(isRigid) { + var view = this.view; + var html = ''; + var row; + + for (row = 0; row < view.rowCnt; row++) { + html += this.dayRowHtml(row, isRigid); + } + this.el.html(html); + + this.rowEls = this.el.find('.fc-row'); + this.dayEls = this.el.find('.fc-day'); + + // run all the day cells through the dayRender callback + this.dayEls.each(function(i, node) { + var date = view.cellToDate(Math.floor(i / view.colCnt), i % view.colCnt); + view.trigger('dayRender', null, date, $(node)); + }); + + Grid.prototype.render.call(this); // call the super-method + }, + + + destroy: function() { + this.destroySegPopover(); + }, + + + // Generates the HTML for a single row. `row` is the row number. + dayRowHtml: function(row, isRigid) { + var view = this.view; + var classes = [ 'fc-row', 'fc-week', view.widgetContentClass ]; + + if (isRigid) { + classes.push('fc-rigid'); + } + + return '' + + '
' + + '
' + + '' + + this.rowHtml('day', row) + // leverages RowRenderer. calls dayCellHtml() + '
' + + '
' + + '
' + + '' + + (this.numbersVisible ? + '' + + this.rowHtml('number', row) + // leverages RowRenderer. View will define render method + '' : + '' + ) + + '
' + + '
' + + '
'; + }, + + + // Renders the HTML for a whole-day cell. Will eventually end up in the day-row's background. + // We go through a 'day' row type instead of just doing a 'bg' row type so that the View can do custom rendering + // specifically for whole-day rows, whereas a 'bg' might also be used for other purposes (TimeGrid bg for example). + dayCellHtml: function(row, col, date) { + return this.bgCellHtml(row, col, date); + }, + + + /* Coordinates & Cells + ------------------------------------------------------------------------------------------------------------------*/ + + + // Populates the empty `rows` and `cols` arrays with coordinates of the cells. For CoordGrid. + buildCoords: function(rows, cols) { + var colCnt = this.view.colCnt; + var e, n, p; + + this.dayEls.slice(0, colCnt).each(function(i, _e) { // iterate the first row of day elements + e = $(_e); + n = e.offset().left; + if (i) { + p[1] = n; + } + p = [ n ]; + cols[i] = p; + }); + p[1] = n + e.outerWidth(); + + this.rowEls.each(function(i, _e) { + e = $(_e); + n = e.offset().top; + if (i) { + p[1] = n; + } + p = [ n ]; + rows[i] = p; + }); + p[1] = n + e.outerHeight() + this.bottomCoordPadding; // hack to extend hit area of last row + }, + + + // Converts a cell to a date + getCellDate: function(cell) { + return this.view.cellToDate(cell); // leverages the View's cell system + }, + + + // Gets the whole-day element associated with the cell + getCellDayEl: function(cell) { + return this.dayEls.eq(cell.row * this.view.colCnt + cell.col); + }, + + + // Converts a range with an inclusive `start` and an exclusive `end` into an array of segment objects + rangeToSegs: function(start, end) { + return this.view.rangeToSegments(start, end); // leverages the View's cell system + }, + + + /* Event Drag Visualization + ------------------------------------------------------------------------------------------------------------------*/ + + + // Renders a visual indication of an event hovering over the given date(s). + // `end` can be null, as well as `seg`. See View's documentation on renderDrag for more info. + // A returned value of `true` signals that a mock "helper" event has been rendered. + renderDrag: function(start, end, seg) { + var opacity; + + // always render a highlight underneath + this.renderHighlight( + start, + end || this.view.calendar.getDefaultEventEnd(true, start) + ); + + // if a segment from the same calendar but another component is being dragged, render a helper event + if (seg && !seg.el.closest(this.el).length) { + + this.renderRangeHelper(start, end, seg); + + opacity = this.view.opt('dragOpacity'); + if (opacity !== undefined) { + this.helperEls.css('opacity', opacity); + } + + return true; // a helper has been rendered + } + }, + + + // Unrenders any visual indication of a hovering event + destroyDrag: function() { + this.destroyHighlight(); + this.destroyHelper(); + }, + + + /* Event Resize Visualization + ------------------------------------------------------------------------------------------------------------------*/ + + + // Renders a visual indication of an event being resized + renderResize: function(start, end, seg) { + this.renderHighlight(start, end); + this.renderRangeHelper(start, end, seg); + }, + + + // Unrenders a visual indication of an event being resized + destroyResize: function() { + this.destroyHighlight(); + this.destroyHelper(); + }, + + + /* Event Helper + ------------------------------------------------------------------------------------------------------------------*/ + + + // Renders a mock "helper" event. `sourceSeg` is the associated internal segment object. It can be null. + renderHelper: function(event, sourceSeg) { + var helperNodes = []; + var rowStructs = this.renderEventRows([ event ]); + + // inject each new event skeleton into each associated row + this.rowEls.each(function(row, rowNode) { + var rowEl = $(rowNode); // the .fc-row + var skeletonEl = $('
'); // will be absolutely positioned + var skeletonTop; + + // If there is an original segment, match the top position. Otherwise, put it at the row's top level + if (sourceSeg && sourceSeg.row === row) { + skeletonTop = sourceSeg.el.position().top; + } + else { + skeletonTop = rowEl.find('.fc-content-skeleton tbody').position().top; + } + + skeletonEl.css('top', skeletonTop) + .find('table') + .append(rowStructs[row].tbodyEl); + + rowEl.append(skeletonEl); + helperNodes.push(skeletonEl[0]); + }); + + this.helperEls = $(helperNodes); // array -> jQuery set + }, + + + // Unrenders any visual indication of a mock helper event + destroyHelper: function() { + if (this.helperEls) { + this.helperEls.remove(); + this.helperEls = null; + } + }, + + + /* Highlighting + ------------------------------------------------------------------------------------------------------------------*/ + + + // Renders an emphasis on the given date range. `start` is an inclusive, `end` is exclusive. + renderHighlight: function(start, end) { + var segs = this.rangeToSegs(start, end); + var highlightNodes = []; + var i, seg; + var el; + + // build an event skeleton for each row that needs it + for (i = 0; i < segs.length; i++) { + seg = segs[i]; + el = $( + this.highlightSkeletonHtml(seg.leftCol, seg.rightCol + 1) // make end exclusive + ); + el.appendTo(this.rowEls[seg.row]); + highlightNodes.push(el[0]); + } + + this.highlightEls = $(highlightNodes); // array -> jQuery set + }, + + + // Unrenders any visual emphasis on a date range + destroyHighlight: function() { + if (this.highlightEls) { + this.highlightEls.remove(); + this.highlightEls = null; + } + }, + + + // Generates the HTML used to build a single-row "highlight skeleton", a table that frames highlight cells + highlightSkeletonHtml: function(startCol, endCol) { + var colCnt = this.view.colCnt; + var cellHtml = ''; + + if (startCol > 0) { + cellHtml += ' elements that should be appended to each row's content skeleton. + // Returns an array of rowStruct objects (see the bottom of `renderEventRow`). + renderEventRows: function(events) { + var segs = this.eventsToSegs(events); + var rowStructs = []; + var segRows; + var row; + + segs = this.renderSegs(segs); // returns a new array with only visible segments + segRows = this.groupSegRows(segs); // group into nested arrays + + // iterate each row of segment groupings + for (row = 0; row < segRows.length; row++) { + rowStructs.push( + this.renderEventRow(row, segRows[row]) + ); + } + + return rowStructs; + }, + + + // Builds the HTML to be used for the default element for an individual segment + renderSegHtml: function(seg, disableResizing) { + var view = this.view; + var isRTL = view.opt('isRTL'); + var event = seg.event; + var isDraggable = view.isEventDraggable(event); + var isResizable = !disableResizing && event.allDay && seg.isEnd && view.isEventResizable(event); + var classes = this.getSegClasses(seg, isDraggable, isResizable); + var skinCss = this.getEventSkinCss(event); + var timeHtml = ''; + var titleHtml; + + classes.unshift('fc-day-grid-event'); + + // Only display a timed events time if it is the starting segment + if (!event.allDay && seg.isStart) { + timeHtml = '' + htmlEscape(view.getEventTimeText(event)) + ''; + } + + titleHtml = + '' + + (htmlEscape(event.title || '') || ' ') + // we always want one line of height + ''; + + return '' + + '
' + + (isRTL ? + titleHtml + ' ' + timeHtml : // put a natural space in between + timeHtml + ' ' + titleHtml // + ) + + '
' + + (isResizable ? + '
' : + '' + ) + + ''; + }, + + + // Given a row # and an array of segments all in the same row, render a
element, a skeleton that contains + // the segments. Returns object with a bunch of internal data about how the render was calculated. + renderEventRow: function(row, rowSegs) { + var view = this.view; + var colCnt = view.colCnt; + var segLevels = this.buildSegLevels(rowSegs); // group into sub-arrays of levels + var levelCnt = Math.max(1, segLevels.length); // ensure at least one level + var tbody = $(''); + var segMatrix = []; // lookup for which segments are rendered into which level+col cells + var cellMatrix = []; // lookup for all '); + + segMatrix.push([]); + cellMatrix.push([]); + loneCellMatrix.push([]); + + // levelCnt might be 1 even though there are no actual levels. protect against this. + // this single empty row is useful for styling. + if (levelSegs) { + for (j = 0; j < levelSegs.length; j++) { // iterate through segments in level + seg = levelSegs[j]; + + emptyCellsUntil(seg.leftCol); + + // create a container that occupies or more columns. append the event element. + td = $(' at a time and stop when we find one out of bounds + for (i = 0; i < trEls.length; i++) { + trEl = trEls.eq(i).removeClass('fc-limited'); // get and reveal + if (trEl.position().top + trEl.outerHeight() > rowHeight) { + return i; + } + } + + return false; // should not limit at all + }, + + + // Limits the given grid row to the maximum number of levels and injects "more" links if necessary. + // `row` is the row number. + // `levelLimit` is a number for the maximum (inclusive) number of levels allowed. + limitRow: function(row, levelLimit) { + var _this = this; + var view = this.view; + var rowStruct = this.rowStructs[row]; + var moreNodes = []; // array of "more" links and and segment elements past the limit + .addClass('fc-limited').get(); // hide elements and get a simple DOM-nodes array + + // iterate though segments in the last allowable level + for (i = 0; i < levelSegs.length; i++) { + seg = levelSegs[i]; + emptyCellsUntil(seg.leftCol); // process empty cells before the segment + + // determine *all* segments below `seg` that occupy the same columns + colSegsBelow = []; + totalSegsBelow = 0; + while (col <= seg.rightCol) { + cell = { row: row, col: col }; + segsBelow = this.getCellSegs(cell, levelLimit); + colSegsBelow.push(segsBelow); + totalSegsBelow += segsBelow.length; + col++; + } + + if (totalSegsBelow) { // do we need to replace this segment with one or many "more" links? + td = cellMatrix[levelLimit - 1][seg.leftCol]; // the segment's parent cell + rowspan = td.attr('rowspan') || 1; + segMoreNodes = []; + + // make a replacement '; + + html += + '' + + (!isRTL ? axisHtml : '') + + '"; + + slotTime.add(this.slotDuration); + } + + return html; + }, + + + // Parses various options into properties of this object + processOptions: function() { + var view = this.view; + var slotDuration = view.opt('slotDuration'); + var snapDuration = view.opt('snapDuration'); + + slotDuration = moment.duration(slotDuration); + snapDuration = snapDuration ? moment.duration(snapDuration) : slotDuration; + + this.slotDuration = slotDuration; + this.snapDuration = snapDuration; + this.cellDuration = snapDuration; // important to assign this for Grid.events.js + + this.minTime = moment.duration(view.opt('minTime')); + this.maxTime = moment.duration(view.opt('maxTime')); + }, + + + // Slices up a date range into a segment for each column + rangeToSegs: function(rangeStart, rangeEnd) { + var view = this.view; + var segs = []; + var seg; + var col; + var cellDate; + var colStart, colEnd; + + // normalize + rangeStart = rangeStart.clone().stripZone(); + rangeEnd = rangeEnd.clone().stripZone(); + + for (col = 0; col < view.colCnt; col++) { + cellDate = view.cellToDate(0, col); // use the View's cell system for this + colStart = cellDate.clone().time(this.minTime); + colEnd = cellDate.clone().time(this.maxTime); + seg = intersectionToSeg(rangeStart, rangeEnd, colStart, colEnd); + if (seg) { + seg.col = col; + segs.push(seg); + } + } + + return segs; + }, + + + /* Coordinates + ------------------------------------------------------------------------------------------------------------------*/ + + + // Called when there is a window resize/zoom and we need to recalculate coordinates for the grid + resize: function() { + this.computeSlatTops(); + this.updateSegVerticals(); + }, + + + // Populates the given empty `rows` and `cols` arrays with offset positions of the "snap" cells. + // "Snap" cells are different the slots because they might have finer granularity. + buildCoords: function(rows, cols) { + var colCnt = this.view.colCnt; + var originTop = this.el.offset().top; + var snapTime = moment.duration(+this.minTime); + var p = null; + var e, n; + + this.dayEls.slice(0, colCnt).each(function(i, _e) { + e = $(_e); + n = e.offset().left; + if (p) { + p[1] = n; + } + p = [ n ]; + cols[i] = p; + }); + p[1] = n + e.outerWidth(); + + p = null; + while (snapTime < this.maxTime) { + n = originTop + this.computeTimeTop(snapTime); + if (p) { + p[1] = n; + } + p = [ n ]; + rows.push(p); + snapTime.add(this.snapDuration); + } + p[1] = originTop + this.computeTimeTop(snapTime); // the position of the exclusive end + }, + + + // Gets the datetime for the given slot cell + getCellDate: function(cell) { + var view = this.view; + var calendar = view.calendar; + + return calendar.rezoneDate( // since we are adding a time, it needs to be in the calendar's timezone + view.cellToDate(0, cell.col) // View's coord system only accounts for start-of-day for column + .time(this.minTime + this.snapDuration * cell.row) + ); + }, + + + // Gets the element that represents the whole-day the cell resides on + getCellDayEl: function(cell) { + return this.dayEls.eq(cell.col); + }, + + + // Computes the top coordinate, relative to the bounds of the grid, of the given date. + // A `startOfDayDate` must be given for avoiding ambiguity over how to treat midnight. + computeDateTop: function(date, startOfDayDate) { + return this.computeTimeTop( + moment.duration( + date.clone().stripZone() - startOfDayDate.clone().stripTime() + ) + ); + }, + + + // Computes the top coordinate, relative to the bounds of the grid, of the given time (a Duration). + computeTimeTop: function(time) { + var slatCoverage = (time - this.minTime) / this.slotDuration; // floating-point value of # of slots covered + var slatIndex; + var slatRemainder; + var slatTop; + var slatBottom; + + // constrain. because minTime/maxTime might be customized + slatCoverage = Math.max(0, slatCoverage); + slatCoverage = Math.min(this.slatEls.length, slatCoverage); + + slatIndex = Math.floor(slatCoverage); // an integer index of the furthest whole slot + slatRemainder = slatCoverage - slatIndex; + slatTop = this.slatTops[slatIndex]; // the top position of the furthest whole slot + + if (slatRemainder) { // time spans part-way into the slot + slatBottom = this.slatTops[slatIndex + 1]; + return slatTop + (slatBottom - slatTop) * slatRemainder; // part-way between slots + } + else { + return slatTop; + } + }, + + + // Queries each `slatEl` for its position relative to the grid's container and stores it in `slatTops`. + // Includes the the bottom of the last slat as the last item in the array. + computeSlatTops: function() { + var tops = []; + var top; + + this.slatEls.each(function(i, node) { + top = $(node).position().top; + tops.push(top); + }); + + tops.push(top + this.slatEls.last().outerHeight()); // bottom of the last slat + + this.slatTops = tops; + }, + + + /* Event Drag Visualization + ------------------------------------------------------------------------------------------------------------------*/ + + + // Renders a visual indication of an event being dragged over the specified date(s). + // `end` and `seg` can be null. See View's documentation on renderDrag for more info. + renderDrag: function(start, end, seg) { + var opacity; + + if (seg) { // if there is event information for this drag, render a helper event + this.renderRangeHelper(start, end, seg); + + opacity = this.view.opt('dragOpacity'); + if (opacity !== undefined) { + this.helperEl.css('opacity', opacity); + } + + return true; // signal that a helper has been rendered + } + else { + // otherwise, just render a highlight + this.renderHighlight( + start, + end || this.view.calendar.getDefaultEventEnd(false, start) + ); + } + }, + + + // Unrenders any visual indication of an event being dragged + destroyDrag: function() { + this.destroyHelper(); + this.destroyHighlight(); + }, + + + /* Event Resize Visualization + ------------------------------------------------------------------------------------------------------------------*/ + + + // Renders a visual indication of an event being resized + renderResize: function(start, end, seg) { + this.renderRangeHelper(start, end, seg); + }, + + + // Unrenders any visual indication of an event being resized + destroyResize: function() { + this.destroyHelper(); + }, + + + /* Event Helper + ------------------------------------------------------------------------------------------------------------------*/ + + + // Renders a mock "helper" event. `sourceSeg` is the original segment object and might be null (an external drag) + renderHelper: function(event, sourceSeg) { + var res = this.renderEventTable([ event ]); + var tableEl = res.tableEl; + var segs = res.segs; + var i, seg; + var sourceEl; + + // Try to make the segment that is in the same row as sourceSeg look the same + for (i = 0; i < segs.length; i++) { + seg = segs[i]; + if (sourceSeg && sourceSeg.col === seg.col) { + sourceEl = sourceSeg.el; + seg.el.css({ + left: sourceEl.css('left'), + right: sourceEl.css('right'), + 'margin-left': sourceEl.css('margin-left'), + 'margin-right': sourceEl.css('margin-right') + }); + } + } + + this.helperEl = $('
') + .append(tableEl) + .appendTo(this.el); + }, + + + // Unrenders any mock helper event + destroyHelper: function() { + if (this.helperEl) { + this.helperEl.remove(); + this.helperEl = null; + } + }, + + + /* Selection + ------------------------------------------------------------------------------------------------------------------*/ + + + // Renders a visual indication of a selection. Overrides the default, which was to simply render a highlight. + renderSelection: function(start, end) { + if (this.view.opt('selectHelper')) { // this setting signals that a mock helper event should be rendered + this.renderRangeHelper(start, end); + } + else { + this.renderHighlight(start, end); + } + }, + + + // Unrenders any visual indication of a selection + destroySelection: function() { + this.destroyHelper(); + this.destroyHighlight(); + }, + + + /* Highlight + ------------------------------------------------------------------------------------------------------------------*/ + + + // Renders an emphasis on the given date range. `start` is inclusive. `end` is exclusive. + renderHighlight: function(start, end) { + this.highlightEl = $( + this.highlightSkeletonHtml(start, end) + ).appendTo(this.el); + }, + + + // Unrenders the emphasis on a date range + destroyHighlight: function() { + if (this.highlightEl) { + this.highlightEl.remove(); + this.highlightEl = null; + } + }, + + + // Generates HTML for a table element with containers in each column, responsible for absolutely positioning the + // highlight elements to cover the highlighted slots. + highlightSkeletonHtml: function(start, end) { + var view = this.view; + var segs = this.rangeToSegs(start, end); + var cellHtml = ''; + var col = 0; + var i, seg; + var dayDate; + var top, bottom; + + for (i = 0; i < segs.length; i++) { // loop through the segments. one per column + seg = segs[i]; + + // need empty cells beforehand? + if (col < seg.col) { + cellHtml += '
'; + + col++; + } + + // need empty cells after the last segment? + if (col < view.colCnt) { + cellHtml += ''; + } + }, + + + // Generates the HTML that will go before content-skeleton cells that display the day/week numbers. + // Queried by the DayGrid subcomponent. Ordering depends on isRTL. + numberIntroHtml: function(row) { + if (this.weekNumbersVisible) { + return '' + + ''; + } + }, + + + // Generates the HTML that goes before the day bg cells for each day-row. + // Queried by the DayGrid subcomponent. Ordering depends on isRTL. + dayIntroHtml: function() { + if (this.weekNumbersVisible) { + return ''; + } + }, + + + // Generates the HTML that goes before every other type of row generated by DayGrid. Ordering depends on isRTL. + // Affects helper-skeleton and highlight-skeleton rows. + introHtml: function() { + if (this.weekNumbersVisible) { + return ''; + } + }, + + + // Generates the HTML for the '; + }, + + + // Generates an HTML attribute string for setting the width of the week number column, if it is known + weekNumberStyleAttr: function() { + if (this.weekNumberWidth !== null) { + return 'style="width:' + this.weekNumberWidth + 'px"'; + } + return ''; + }, + + + // Determines whether each row should have a constant height + hasRigidRows: function() { + var eventLimit = this.opt('eventLimit'); + return eventLimit && typeof eventLimit !== 'number'; + }, + + + /* Dimensions + ------------------------------------------------------------------------------------------------------------------*/ + + + // Refreshes the horizontal dimensions of the view + updateWidth: function() { + if (this.weekNumbersVisible) { + // Make sure all week number cells running down the side have the same width. + // Record the width for cells created later. + this.weekNumberWidth = matchCellWidths( + this.el.find('.fc-week-number') + ); + } + }, + + + // Adjusts the vertical dimensions of the view to the specified values + setHeight: function(totalHeight, isAuto) { + var eventLimit = this.opt('eventLimit'); + var scrollerHeight; + + // reset all heights to be natural + unsetScroller(this.scrollerEl); + uncompensateScroll(this.headRowEl); + + this.dayGrid.destroySegPopover(); // kill the "more" popover if displayed + + // is the event limit a constant level number? + if (eventLimit && typeof eventLimit === 'number') { + this.dayGrid.limitRows(eventLimit); // limit the levels first so the height can redistribute after + } + + scrollerHeight = this.computeScrollerHeight(totalHeight); + this.setGridHeight(scrollerHeight, isAuto); + + // is the event limit dynamically calculated? + if (eventLimit && typeof eventLimit !== 'number') { + this.dayGrid.limitRows(eventLimit); // limit the levels after the grid's row heights have been set + } + + if (!isAuto && setPotentialScroller(this.scrollerEl, scrollerHeight)) { // using scrollbars? + + compensateScroll(this.headRowEl, getScrollbarWidths(this.scrollerEl)); + + // doing the scrollbar compensation might have created text overflow which created more height. redo + scrollerHeight = this.computeScrollerHeight(totalHeight); + this.scrollerEl.height(scrollerHeight); + + this.restoreScroll(); + } + }, + + + // Sets the height of just the DayGrid component in this view + setGridHeight: function(height, isAuto) { + if (isAuto) { + undistributeHeight(this.dayGrid.rowEls); // let the rows be their natural height with no expanding + } + else { + distributeHeight(this.dayGrid.rowEls, height, true); // true = compensate for height-hogging rows + } + }, + + + /* Events + ------------------------------------------------------------------------------------------------------------------*/ + + + // Renders the given events onto the view and populates the segments array + renderEvents: function(events) { + this.dayGrid.renderEvents(events); + + this.updateHeight(); // must compensate for events that overflow the row + + View.prototype.renderEvents.call(this, events); // call the super-method + }, + + + // Retrieves all segment objects that are rendered in the view + getSegs: function() { + return this.dayGrid.getSegs(); + }, + + + // Unrenders all event elements and clears internal segment data + destroyEvents: function() { + View.prototype.destroyEvents.call(this); // do this before dayGrid's segs have been cleared + + this.recordScroll(); // removing events will reduce height and mess with the scroll, so record beforehand + this.dayGrid.destroyEvents(); + + // we DON'T need to call updateHeight() because: + // A) a renderEvents() call always happens after this, which will eventually call updateHeight() + // B) in IE8, this causes a flash whenever events are rerendered + }, + + + /* Event Dragging + ------------------------------------------------------------------------------------------------------------------*/ + + + // Renders a visual indication of an event being dragged over the view. + // A returned value of `true` signals that a mock "helper" event has been rendered. + renderDrag: function(start, end, seg) { + return this.dayGrid.renderDrag(start, end, seg); + }, + + + // Unrenders the visual indication of an event being dragged over the view + destroyDrag: function() { + this.dayGrid.destroyDrag(); + }, + + + /* Selection + ------------------------------------------------------------------------------------------------------------------*/ + + + // Renders a visual indication of a selection + renderSelection: function(start, end) { + this.dayGrid.renderSelection(start, end); + }, + + + // Unrenders a visual indications of a selection + destroySelection: function() { + this.dayGrid.destroySelection(); + } + +}); + +;; + +/* A month view with day cells running in rows (one-per-week) and columns +----------------------------------------------------------------------------------------------------------------------*/ + +setDefaults({ + fixedWeekCount: true +}); + +fcViews.month = MonthView; // register the view + +function MonthView(calendar) { + BasicView.call(this, calendar); // call the super-constructor +} + + +MonthView.prototype = createObject(BasicView.prototype); // define the super-class +$.extend(MonthView.prototype, { + + name: 'month', + + + incrementDate: function(date, delta) { + return date.clone().stripTime().add(delta, 'months').startOf('month'); + }, + + + render: function(date) { + var rowCnt; + + this.intervalStart = date.clone().stripTime().startOf('month'); + this.intervalEnd = this.intervalStart.clone().add(1, 'months'); + + this.start = this.intervalStart.clone(); + this.start = this.skipHiddenDays(this.start); // move past the first week if no visible days + this.start.startOf('week'); + this.start = this.skipHiddenDays(this.start); // move past the first invisible days of the week + + this.end = this.intervalEnd.clone(); + this.end = this.skipHiddenDays(this.end, -1, true); // move in from the last week if no visible days + this.end.add((7 - this.end.weekday()) % 7, 'days'); // move to end of week if not already + this.end = this.skipHiddenDays(this.end, -1, true); // move in from the last invisible days of the week + + rowCnt = Math.ceil( // need to ceil in case there are hidden days + this.end.diff(this.start, 'weeks', true) // returnfloat=true + ); + if (this.isFixedWeeks()) { + this.end.add(6 - rowCnt, 'weeks'); + rowCnt = 6; + } + + this.title = this.calendar.formatDate(this.intervalStart, this.opt('titleFormat')); + + BasicView.prototype.render.call(this, rowCnt, this.getCellsPerWeek(), true); // call the super-method + }, + + + // Overrides the default BasicView behavior to have special multi-week auto-height logic + setGridHeight: function(height, isAuto) { + + isAuto = isAuto || this.opt('weekMode') === 'variable'; // LEGACY: weekMode is deprecated + + // if auto, make the height of each row the height that it would be if there were 6 weeks + if (isAuto) { + height *= this.rowCnt / 6; + } + + distributeHeight(this.dayGrid.rowEls, height, !isAuto); // if auto, don't compensate for height-hogging rows + }, + + + isFixedWeeks: function() { + var weekMode = this.opt('weekMode'); // LEGACY: weekMode is deprecated + if (weekMode) { + return weekMode === 'fixed'; // if any other type of weekMode, assume NOT fixed + } + + return this.opt('fixedWeekCount'); + } + +}); + +;; + +/* A week view with simple day cells running horizontally +----------------------------------------------------------------------------------------------------------------------*/ +// TODO: a WeekView mixin for calculating dates and titles + +fcViews.basicWeek = BasicWeekView; // register this view + +function BasicWeekView(calendar) { + BasicView.call(this, calendar); // call the super-constructor +} + + +BasicWeekView.prototype = createObject(BasicView.prototype); // define the super-class +$.extend(BasicWeekView.prototype, { + + name: 'basicWeek', + + + incrementDate: function(date, delta) { + return date.clone().stripTime().add(delta, 'weeks').startOf('week'); + }, + + + render: function(date) { + + this.intervalStart = date.clone().stripTime().startOf('week'); + this.intervalEnd = this.intervalStart.clone().add(1, 'weeks'); + + this.start = this.skipHiddenDays(this.intervalStart); + this.end = this.skipHiddenDays(this.intervalEnd, -1, true); + + this.title = this.calendar.formatRange( + this.start, + this.end.clone().subtract(1), // make inclusive by subtracting 1 ms + this.opt('titleFormat'), + ' \u2014 ' // emphasized dash + ); + + BasicView.prototype.render.call(this, 1, this.getCellsPerWeek(), false); // call the super-method + } + +}); +;; + +/* A view with a single simple day cell +----------------------------------------------------------------------------------------------------------------------*/ + +fcViews.basicDay = BasicDayView; // register this view + +function BasicDayView(calendar) { + BasicView.call(this, calendar); // call the super-constructor +} + + +BasicDayView.prototype = createObject(BasicView.prototype); // define the super-class +$.extend(BasicDayView.prototype, { + + name: 'basicDay', + + + incrementDate: function(date, delta) { + var out = date.clone().stripTime().add(delta, 'days'); + out = this.skipHiddenDays(out, delta < 0 ? -1 : 1); + return out; + }, + + + render: function(date) { + + this.start = this.intervalStart = date.clone().stripTime(); + this.end = this.intervalEnd = this.start.clone().add(1, 'days'); + + this.title = this.calendar.formatDate(this.start, this.opt('titleFormat')); + + BasicView.prototype.render.call(this, 1, 1, false); // call the super-method + } + +}); +;; + +/* An abstract class for all agenda-related views. Displays one more columns with time slots running vertically. +----------------------------------------------------------------------------------------------------------------------*/ +// Is a manager for the TimeGrid subcomponent and possibly the DayGrid subcomponent (if allDaySlot is on). +// Responsible for managing width/height. + +setDefaults({ + allDaySlot: true, + allDayText: 'all-day', + + scrollTime: '06:00:00', + + slotDuration: '00:30:00', + + axisFormat: generateAgendaAxisFormat, + timeFormat: { + agenda: generateAgendaTimeFormat + }, + + minTime: '00:00:00', + maxTime: '24:00:00', + slotEventOverlap: true +}); + +var AGENDA_ALL_DAY_EVENT_LIMIT = 5; + + +function generateAgendaAxisFormat(options, langData) { + return langData.longDateFormat('LT') + .replace(':mm', '(:mm)') + .replace(/(\Wmm)$/, '($1)') // like above, but for foreign langs + .replace(/\s*a$/i, 'a'); // convert AM/PM/am/pm to lowercase. remove any spaces beforehand +} + + +function generateAgendaTimeFormat(options, langData) { + return langData.longDateFormat('LT') + .replace(/\s*a$/i, ''); // remove trailing AM/PM +} + + +function AgendaView(calendar) { + View.call(this, calendar); // call the super-constructor + + this.timeGrid = new TimeGrid(this); + + if (this.opt('allDaySlot')) { // should we display the "all-day" area? + this.dayGrid = new DayGrid(this); // the all-day subcomponent of this view + + // the coordinate grid will be a combination of both subcomponents' grids + this.coordMap = new ComboCoordMap([ + this.dayGrid.coordMap, + this.timeGrid.coordMap + ]); + } + else { + this.coordMap = this.timeGrid.coordMap; + } +} + + +AgendaView.prototype = createObject(View.prototype); // define the super-class +$.extend(AgendaView.prototype, { + + timeGrid: null, // the main time-grid subcomponent of this view + dayGrid: null, // the "all-day" subcomponent. if all-day is turned off, this will be null + + axisWidth: null, // the width of the time axis running down the side + + noScrollRowEls: null, // set of fake row elements that must compensate when scrollerEl has scrollbars + + // when the time-grid isn't tall enough to occupy the given height, we render an
underneath + bottomRuleEl: null, + bottomRuleHeight: null, + + + /* Rendering + ------------------------------------------------------------------------------------------------------------------*/ + + + // Renders the view into `this.el`, which has already been assigned. + // `colCnt` has been calculated by a subclass and passed here. + render: function(colCnt) { + + // needed for cell-to-date and date-to-cell calculations in View + this.rowCnt = 1; + this.colCnt = colCnt; + + this.el.addClass('fc-agenda-view').html(this.renderHtml()); + + // the element that wraps the time-grid that will probably scroll + this.scrollerEl = this.el.find('.fc-time-grid-container'); + this.timeGrid.coordMap.containerEl = this.scrollerEl; // don't accept clicks/etc outside of this + + this.timeGrid.el = this.el.find('.fc-time-grid'); + this.timeGrid.render(); + + // the
that sometimes displays under the time-grid + this.bottomRuleEl = $('
') + .appendTo(this.timeGrid.el); // inject it into the time-grid + + if (this.dayGrid) { + this.dayGrid.el = this.el.find('.fc-day-grid'); + this.dayGrid.render(); + + // have the day-grid extend it's coordinate area over the
dividing the two grids + this.dayGrid.bottomCoordPadding = this.dayGrid.el.next('hr').outerHeight(); + } + + this.noScrollRowEls = this.el.find('.fc-row:not(.fc-scroller *)'); // fake rows not within the scroller + + View.prototype.render.call(this); // call the super-method + + this.resetScroll(); // do this after sizes have been set + }, + + + // Make subcomponents ready for cleanup + destroy: function() { + this.timeGrid.destroy(); + if (this.dayGrid) { + this.dayGrid.destroy(); + } + View.prototype.destroy.call(this); // call the super-method + }, + + + // Builds the HTML skeleton for the view. + // The day-grid and time-grid components will render inside containers defined by this HTML. + renderHtml: function() { + return '' + + '
'; + } + if (endCol > startCol) { + cellHtml += ''; + } + if (colCnt > endCol) { + cellHtml += ''; + } + + cellHtml = this.bookendCells(cellHtml, 'highlight'); + + return '' + + '
' + + '' + + '' + + cellHtml + + '' + + '
' + + '
'; + } + +}); + +;; + +/* Event-rendering methods for the DayGrid class +----------------------------------------------------------------------------------------------------------------------*/ + +$.extend(DayGrid.prototype, { + + segs: null, + rowStructs: null, // an array of objects, each holding information about a row's event-rendering + + + // Render the given events onto the Grid and return the rendered segments + renderEvents: function(events) { + var rowStructs = this.rowStructs = this.renderEventRows(events); + var segs = []; + + // append to each row's content skeleton + this.rowEls.each(function(i, rowNode) { + $(rowNode).find('.fc-content-skeleton > table').append( + rowStructs[i].tbodyEl + ); + segs.push.apply(segs, rowStructs[i].segs); + }); + + this.segs = segs; + }, + + + // Retrieves all segment objects that have been rendered + getSegs: function() { + return (this.segs || []).concat( + this.popoverSegs || [] // segs rendered in the "more" events popover + ); + }, + + + // Removes all rendered event elements + destroyEvents: function() { + var rowStructs; + var rowStruct; + + Grid.prototype.destroyEvents.call(this); // call the super-method + + rowStructs = this.rowStructs || []; + while ((rowStruct = rowStructs.pop())) { + rowStruct.tbodyEl.remove(); + } + + this.segs = null; + this.destroySegPopover(); // removes the "more.." events popover + }, + + + // Uses the given events array to generate
elements of the level+col matrix + var loneCellMatrix = []; // lookup for elements that only take up a single column + var i, levelSegs; + var col; + var tr; + var j, seg; + var td; + + // populates empty cells from the current column (`col`) to `endCol` + function emptyCellsUntil(endCol) { + while (col < endCol) { + // try to grab a cell from the level above and extend its rowspan. otherwise, create a fresh cell + td = (loneCellMatrix[i - 1] || [])[col]; + if (td) { + td.attr( + 'rowspan', + parseInt(td.attr('rowspan') || 1, 10) + 1 + ); + } + else { + td = $(''); + tr.append(td); + } + cellMatrix[i][col] = td; + loneCellMatrix[i][col] = td; + col++; + } + } + + for (i = 0; i < levelCnt; i++) { // iterate through all levels + levelSegs = segLevels[i]; + col = 0; + tr = $('
').append(seg.el); + if (seg.leftCol != seg.rightCol) { + td.attr('colspan', seg.rightCol - seg.leftCol + 1); + } + else { // a single-column segment + loneCellMatrix[i][col] = td; + } + + while (col <= seg.rightCol) { + cellMatrix[i][col] = td; + segMatrix[i][col] = seg; + col++; + } + + tr.append(td); + } + } + + emptyCellsUntil(colCnt); // finish off the row + this.bookendCells(tr, 'eventSkeleton'); + tbody.append(tr); + } + + return { // a "rowStruct" + row: row, // the row number + tbodyEl: tbody, + cellMatrix: cellMatrix, + segMatrix: segMatrix, + segLevels: segLevels, + segs: rowSegs + }; + }, + + + // Stacks a flat array of segments, which are all assumed to be in the same row, into subarrays of vertical levels. + buildSegLevels: function(segs) { + var levels = []; + var i, seg; + var j; + + // Give preference to elements with certain criteria, so they have + // a chance to be closer to the top. + segs.sort(compareSegs); + + for (i = 0; i < segs.length; i++) { + seg = segs[i]; + + // loop through levels, starting with the topmost, until the segment doesn't collide with other segments + for (j = 0; j < levels.length; j++) { + if (!isDaySegCollision(seg, levels[j])) { + break; + } + } + // `j` now holds the desired subrow index + seg.level = j; + + // create new level array if needed and append segment + (levels[j] || (levels[j] = [])).push(seg); + } + + // order segments left-to-right. very important if calendar is RTL + for (j = 0; j < levels.length; j++) { + levels[j].sort(compareDaySegCols); + } + + return levels; + }, + + + // Given a flat array of segments, return an array of sub-arrays, grouped by each segment's row + groupSegRows: function(segs) { + var view = this.view; + var segRows = []; + var i; + + for (i = 0; i < view.rowCnt; i++) { + segRows.push([]); + } + + for (i = 0; i < segs.length; i++) { + segRows[segs[i].row].push(segs[i]); + } + + return segRows; + } + +}); + + +// Computes whether two segments' columns collide. They are assumed to be in the same row. +function isDaySegCollision(seg, otherSegs) { + var i, otherSeg; + + for (i = 0; i < otherSegs.length; i++) { + otherSeg = otherSegs[i]; + + if ( + otherSeg.leftCol <= seg.rightCol && + otherSeg.rightCol >= seg.leftCol + ) { + return true; + } + } + + return false; +} + + +// A cmp function for determining the leftmost event +function compareDaySegCols(a, b) { + return a.leftCol - b.leftCol; +} + +;; + +/* Methods relate to limiting the number events for a given day on a DayGrid +----------------------------------------------------------------------------------------------------------------------*/ + +$.extend(DayGrid.prototype, { + + + segPopover: null, // the Popover that holds events that can't fit in a cell. null when not visible + popoverSegs: null, // an array of segment objects that the segPopover holds. null when not visible + + + destroySegPopover: function() { + if (this.segPopover) { + this.segPopover.hide(); // will trigger destruction of `segPopover` and `popoverSegs` + } + }, + + + // Limits the number of "levels" (vertically stacking layers of events) for each row of the grid. + // `levelLimit` can be false (don't limit), a number, or true (should be computed). + limitRows: function(levelLimit) { + var rowStructs = this.rowStructs || []; + var row; // row # + var rowLevelLimit; + + for (row = 0; row < rowStructs.length; row++) { + this.unlimitRow(row); + + if (!levelLimit) { + rowLevelLimit = false; + } + else if (typeof levelLimit === 'number') { + rowLevelLimit = levelLimit; + } + else { + rowLevelLimit = this.computeRowLevelLimit(row); + } + + if (rowLevelLimit !== false) { + this.limitRow(row, rowLevelLimit); + } + } + }, + + + // Computes the number of levels a row will accomodate without going outside its bounds. + // Assumes the row is "rigid" (maintains a constant height regardless of what is inside). + // `row` is the row number. + computeRowLevelLimit: function(row) { + var rowEl = this.rowEls.eq(row); // the containing "fake" row div + var rowHeight = rowEl.height(); // TODO: cache somehow? + var trEls = this.rowStructs[row].tbodyEl.children(); + var i, trEl; + + // Reveal one level
DOM nodes + var col = 0; // col # + var cell; + var levelSegs; // array of segment objects in the last allowable level, ordered left-to-right + var cellMatrix; // a matrix (by level, then column) of all jQuery elements in the row + var limitedNodes; // array of temporarily hidden level
DOM nodes + var i, seg; + var segsBelow; // array of segment objects below `seg` in the current `col` + var totalSegsBelow; // total number of segments below `seg` in any of the columns `seg` occupies + var colSegsBelow; // array of segment arrays, below seg, one for each column (offset from segs's first column) + var td, rowspan; + var segMoreNodes; // array of "more" cells that will stand-in for the current seg's cell + var j; + var moreTd, moreWrap, moreLink; + + // Iterates through empty level cells and places "more" links inside if need be + function emptyCellsUntil(endCol) { // goes from current `col` to `endCol` + while (col < endCol) { + cell = { row: row, col: col }; + segsBelow = _this.getCellSegs(cell, levelLimit); + if (segsBelow.length) { + td = cellMatrix[levelLimit - 1][col]; + moreLink = _this.renderMoreLink(cell, segsBelow); + moreWrap = $('
').append(moreLink); + td.append(moreWrap); + moreNodes.push(moreWrap[0]); + } + col++; + } + } + + if (levelLimit && levelLimit < rowStruct.segLevels.length) { // is it actually over the limit? + levelSegs = rowStruct.segLevels[levelLimit - 1]; + cellMatrix = rowStruct.cellMatrix; + + limitedNodes = rowStruct.tbodyEl.children().slice(levelLimit) // get level
for each column the segment occupies. will be one for each colspan + for (j = 0; j < colSegsBelow.length; j++) { + moreTd = $('').attr('rowspan', rowspan); + segsBelow = colSegsBelow[j]; + cell = { row: row, col: seg.leftCol + j }; + moreLink = this.renderMoreLink(cell, [ seg ].concat(segsBelow)); // count seg as hidden too + moreWrap = $('
').append(moreLink); + moreTd.append(moreWrap); + segMoreNodes.push(moreTd[0]); + moreNodes.push(moreTd[0]); + } + + td.addClass('fc-limited').after($(segMoreNodes)); // hide original
and inject replacements + limitedNodes.push(td[0]); + } + } + + emptyCellsUntil(view.colCnt); // finish off the level + rowStruct.moreEls = $(moreNodes); // for easy undoing later + rowStruct.limitedEls = $(limitedNodes); // for easy undoing later + } + }, + + + // Reveals all levels and removes all "more"-related elements for a grid's row. + // `row` is a row number. + unlimitRow: function(row) { + var rowStruct = this.rowStructs[row]; + + if (rowStruct.moreEls) { + rowStruct.moreEls.remove(); + rowStruct.moreEls = null; + } + + if (rowStruct.limitedEls) { + rowStruct.limitedEls.removeClass('fc-limited'); + rowStruct.limitedEls = null; + } + }, + + + // Renders an element that represents hidden event element for a cell. + // Responsible for attaching click handler as well. + renderMoreLink: function(cell, hiddenSegs) { + var _this = this; + var view = this.view; + + return $('') + .text( + this.getMoreLinkText(hiddenSegs.length) + ) + .on('click', function(ev) { + var clickOption = view.opt('eventLimitClick'); + var date = view.cellToDate(cell); + var moreEl = $(this); + var dayEl = _this.getCellDayEl(cell); + var allSegs = _this.getCellSegs(cell); + + // rescope the segments to be within the cell's date + var reslicedAllSegs = _this.resliceDaySegs(allSegs, date); + var reslicedHiddenSegs = _this.resliceDaySegs(hiddenSegs, date); + + if (typeof clickOption === 'function') { + // the returned value can be an atomic option + clickOption = view.trigger('eventLimitClick', null, { + date: date, + dayEl: dayEl, + moreEl: moreEl, + segs: reslicedAllSegs, + hiddenSegs: reslicedHiddenSegs + }, ev); + } + + if (clickOption === 'popover') { + _this.showSegPopover(date, cell, moreEl, reslicedAllSegs); + } + else if (typeof clickOption === 'string') { // a view name + view.calendar.zoomTo(date, clickOption); + } + }); + }, + + + // Reveals the popover that displays all events within a cell + showSegPopover: function(date, cell, moreLink, segs) { + var _this = this; + var view = this.view; + var moreWrap = moreLink.parent(); // the
wrapper around the + var topEl; // the element we want to match the top coordinate of + var options; + + if (view.rowCnt == 1) { + topEl = this.view.el; // will cause the popover to cover any sort of header + } + else { + topEl = this.rowEls.eq(cell.row); // will align with top of row + } + + options = { + className: 'fc-more-popover', + content: this.renderSegPopoverContent(date, segs), + parentEl: this.el, + top: topEl.offset().top, + autoHide: true, // when the user clicks elsewhere, hide the popover + viewportConstrain: view.opt('popoverViewportConstrain'), + hide: function() { + // destroy everything when the popover is hidden + _this.segPopover.destroy(); + _this.segPopover = null; + _this.popoverSegs = null; + } + }; + + // Determine horizontal coordinate. + // We use the moreWrap instead of the
to avoid border confusion. + if (view.opt('isRTL')) { + options.right = moreWrap.offset().left + moreWrap.outerWidth() + 1; // +1 to be over cell border + } + else { + options.left = moreWrap.offset().left - 1; // -1 to be over cell border + } + + this.segPopover = new Popover(options); + this.segPopover.show(); + }, + + + // Builds the inner DOM contents of the segment popover + renderSegPopoverContent: function(date, segs) { + var view = this.view; + var isTheme = view.opt('theme'); + var title = date.format(view.opt('dayPopoverFormat')); + var content = $( + '
' + + '' + + '' + + htmlEscape(title) + + '' + + '
' + + '
' + + '
' + + '
' + + '
' + ); + var segContainer = content.find('.fc-event-container'); + var i; + + // render each seg's `el` and only return the visible segs + segs = this.renderSegs(segs, true); // disableResizing=true + this.popoverSegs = segs; + + for (i = 0; i < segs.length; i++) { + + // because segments in the popover are not part of a grid coordinate system, provide a hint to any + // grids that want to do drag-n-drop about which cell it came from + segs[i].cellDate = date; + + segContainer.append(segs[i].el); + } + + return content; + }, + + + // Given the events within an array of segment objects, reslice them to be in a single day + resliceDaySegs: function(segs, dayDate) { + var events = $.map(segs, function(seg) { + return seg.event; + }); + var dayStart = dayDate.clone().stripTime(); + var dayEnd = dayStart.clone().add(1, 'days'); + + return this.eventsToSegs(events, dayStart, dayEnd); + }, + + + // Generates the text that should be inside a "more" link, given the number of events it represents + getMoreLinkText: function(num) { + var view = this.view; + var opt = view.opt('eventLimitText'); + + if (typeof opt === 'function') { + return opt(num); + } + else { + return '+' + num + ' ' + opt; + } + }, + + + // Returns segments within a given cell. + // If `startLevel` is specified, returns only events including and below that level. Otherwise returns all segs. + getCellSegs: function(cell, startLevel) { + var segMatrix = this.rowStructs[cell.row].segMatrix; + var level = startLevel || 0; + var segs = []; + var seg; + + while (level < segMatrix.length) { + seg = segMatrix[level][cell.col]; + if (seg) { + segs.push(seg); + } + level++; + } + + return segs; + } + +}); + +;; + +/* A component that renders one or more columns of vertical time slots +----------------------------------------------------------------------------------------------------------------------*/ + +function TimeGrid(view) { + Grid.call(this, view); // call the super-constructor +} + + +TimeGrid.prototype = createObject(Grid.prototype); // define the super-class +$.extend(TimeGrid.prototype, { + + slotDuration: null, // duration of a "slot", a distinct time segment on given day, visualized by lines + snapDuration: null, // granularity of time for dragging and selecting + + minTime: null, // Duration object that denotes the first visible time of any given day + maxTime: null, // Duration object that denotes the exclusive visible end time of any given day + + dayEls: null, // cells elements in the day-row background + slatEls: null, // elements running horizontally across all columns + + slatTops: null, // an array of top positions, relative to the container. last item holds bottom of last slot + + highlightEl: null, // cell skeleton element for rendering the highlight + helperEl: null, // cell skeleton element for rendering the mock event "helper" + + + // Renders the time grid into `this.el`, which should already be assigned. + // Relies on the view's colCnt. In the future, this component should probably be self-sufficient. + render: function() { + this.processOptions(); + + this.el.html(this.renderHtml()); + + this.dayEls = this.el.find('.fc-day'); + this.slatEls = this.el.find('.fc-slats tr'); + + this.computeSlatTops(); + + Grid.prototype.render.call(this); // call the super-method + }, + + + // Renders the basic HTML skeleton for the grid + renderHtml: function() { + return '' + + '
' + + '' + + this.rowHtml('slotBg') + // leverages RowRenderer, which will call slotBgCellHtml + '
' + + '
' + + '
' + + '' + + this.slatRowHtml() + + '
' + + '
'; + }, + + + // Renders the HTML for a vertical background cell behind the slots. + // This method is distinct from 'bg' because we wanted a new `rowType` so the View could customize the rendering. + slotBgCellHtml: function(row, col, date) { + return this.bgCellHtml(row, col, date); + }, + + + // Generates the HTML for the horizontal "slats" that run width-wise. Has a time axis on a side. Depends on RTL. + slatRowHtml: function() { + var view = this.view; + var calendar = view.calendar; + var isRTL = view.opt('isRTL'); + var html = ''; + var slotNormal = this.slotDuration.asMinutes() % 15 === 0; + var slotTime = moment.duration(+this.minTime); // wish there was .clone() for durations + var slotDate; // will be on the view's first day, but we only care about its time + var minutes; + var axisHtml; + + // Calculate the time for each slot + while (slotTime < this.maxTime) { + slotDate = view.start.clone().time(slotTime); // will be in UTC but that's good. to avoid DST issues + minutes = slotDate.minutes(); + + axisHtml = + '
' + + ((!slotNormal || !minutes) ? // if irregular slot duration, or on the hour, then display the time + '' + // for matchCellWidths + htmlEscape(calendar.formatDate(slotDate, view.opt('axisFormat'))) + + '' : + '' + ) + + '
' + + (isRTL ? axisHtml : '') + + "
'; + col = seg.col; + } + + // compute vertical position + dayDate = view.cellToDate(0, col); + top = this.computeDateTop(seg.start, dayDate); + bottom = this.computeDateTop(seg.end, dayDate); // the y position of the bottom edge + + // generate the cell HTML. bottom becomes negative because it needs to be a CSS value relative to the + // bottom edge of the zero-height container. + cellHtml += + '' + + '
' + + '
' + + '
' + + '
'; + } + + cellHtml = this.bookendCells(cellHtml, 'highlight'); + + return '' + + '
' + + '' + + '' + + cellHtml + + '' + + '
' + + '
'; + } + +}); + +;; + +/* Event-rendering methods for the TimeGrid class +----------------------------------------------------------------------------------------------------------------------*/ + +$.extend(TimeGrid.prototype, { + + segs: null, // segment objects rendered in the component. null of events haven't been rendered yet + eventSkeletonEl: null, // has cells with event-containers, which contain absolutely positioned event elements + + + // Renders the events onto the grid and returns an array of segments that have been rendered + renderEvents: function(events) { + var res = this.renderEventTable(events); + + this.eventSkeletonEl = $('
').append(res.tableEl); + this.el.append(this.eventSkeletonEl); + + this.segs = res.segs; + }, + + + // Retrieves rendered segment objects + getSegs: function() { + return this.segs || []; + }, + + + // Removes all event segment elements from the view + destroyEvents: function() { + Grid.prototype.destroyEvents.call(this); // call the super-method + + if (this.eventSkeletonEl) { + this.eventSkeletonEl.remove(); + this.eventSkeletonEl = null; + } + + this.segs = null; + }, + + + // Renders and returns the portion of the event-skeleton. + // Returns an object with properties 'tbodyEl' and 'segs'. + renderEventTable: function(events) { + var tableEl = $('
'); + var trEl = tableEl.find('tr'); + var segs = this.eventsToSegs(events); + var segCols; + var i, seg; + var col, colSegs; + var containerEl; + + segs = this.renderSegs(segs); // returns only the visible segs + segCols = this.groupSegCols(segs); // group into sub-arrays, and assigns 'col' to each seg + + this.computeSegVerticals(segs); // compute and assign top/bottom + + for (col = 0; col < segCols.length; col++) { // iterate each column grouping + colSegs = segCols[col]; + placeSlotSegs(colSegs); // compute horizontal coordinates, z-index's, and reorder the array + + containerEl = $('
'); + + // assign positioning CSS and insert into container + for (i = 0; i < colSegs.length; i++) { + seg = colSegs[i]; + seg.el.css(this.generateSegPositionCss(seg)); + + // if the height is short, add a className for alternate styling + if (seg.bottom - seg.top < 30) { + seg.el.addClass('fc-short'); + } + + containerEl.append(seg.el); + } + + trEl.append($('
').append(containerEl)); + } + + this.bookendCells(trEl, 'eventSkeleton'); + + return { + tableEl: tableEl, + segs: segs + }; + }, + + + // Refreshes the CSS top/bottom coordinates for each segment element. Probably after a window resize/zoom. + updateSegVerticals: function() { + var segs = this.segs; + var i; + + if (segs) { + this.computeSegVerticals(segs); + + for (i = 0; i < segs.length; i++) { + segs[i].el.css( + this.generateSegVerticalCss(segs[i]) + ); + } + } + }, + + + // For each segment in an array, computes and assigns its top and bottom properties + computeSegVerticals: function(segs) { + var i, seg; + + for (i = 0; i < segs.length; i++) { + seg = segs[i]; + seg.top = this.computeDateTop(seg.start, seg.start); + seg.bottom = this.computeDateTop(seg.end, seg.start); + } + }, + + + // Renders the HTML for a single event segment's default rendering + renderSegHtml: function(seg, disableResizing) { + var view = this.view; + var event = seg.event; + var isDraggable = view.isEventDraggable(event); + var isResizable = !disableResizing && seg.isEnd && view.isEventResizable(event); + var classes = this.getSegClasses(seg, isDraggable, isResizable); + var skinCss = this.getEventSkinCss(event); + var timeText; + var fullTimeText; // more verbose time text. for the print stylesheet + var startTimeText; // just the start time text + + classes.unshift('fc-time-grid-event'); + + if (view.isMultiDayEvent(event)) { // if the event appears to span more than one day... + // Don't display time text on segments that run entirely through a day. + // That would appear as midnight-midnight and would look dumb. + // Otherwise, display the time text for the *segment's* times (like 6pm-midnight or midnight-10am) + if (seg.isStart || seg.isEnd) { + timeText = view.getEventTimeText(seg.start, seg.end); + fullTimeText = view.getEventTimeText(seg.start, seg.end, 'LT'); + startTimeText = view.getEventTimeText(seg.start, null); + } + } else { + // Display the normal time text for the *event's* times + timeText = view.getEventTimeText(event); + fullTimeText = view.getEventTimeText(event, 'LT'); + startTimeText = view.getEventTimeText(event.start, null); + } + + return '' + + '
' + + (timeText ? + '
' + + '' + htmlEscape(timeText) + '' + + '
' : + '' + ) + + (event.title ? + '
' + + htmlEscape(event.title) + + '
' : + '' + ) + + '
' + + '
' + + (isResizable ? + '
' : + '' + ) + + ''; + }, + + + // Generates an object with CSS properties/values that should be applied to an event segment element. + // Contains important positioning-related properties that should be applied to any event element, customized or not. + generateSegPositionCss: function(seg) { + var view = this.view; + var isRTL = view.opt('isRTL'); + var shouldOverlap = view.opt('slotEventOverlap'); + var backwardCoord = seg.backwardCoord; // the left side if LTR. the right side if RTL. floating-point + var forwardCoord = seg.forwardCoord; // the right side if LTR. the left side if RTL. floating-point + var props = this.generateSegVerticalCss(seg); // get top/bottom first + var left; // amount of space from left edge, a fraction of the total width + var right; // amount of space from right edge, a fraction of the total width + + if (shouldOverlap) { + // double the width, but don't go beyond the maximum forward coordinate (1.0) + forwardCoord = Math.min(1, backwardCoord + (forwardCoord - backwardCoord) * 2); + } + + if (isRTL) { + left = 1 - forwardCoord; + right = backwardCoord; + } + else { + left = backwardCoord; + right = 1 - forwardCoord; + } + + props.zIndex = seg.level + 1; // convert from 0-base to 1-based + props.left = left * 100 + '%'; + props.right = right * 100 + '%'; + + if (shouldOverlap && seg.forwardPressure) { + // add padding to the edge so that forward stacked events don't cover the resizer's icon + props[isRTL ? 'marginLeft' : 'marginRight'] = 10 * 2; // 10 is a guesstimate of the icon's width + } + + return props; + }, + + + // Generates an object with CSS properties for the top/bottom coordinates of a segment element + generateSegVerticalCss: function(seg) { + return { + top: seg.top, + bottom: -seg.bottom // flipped because needs to be space beyond bottom edge of event container + }; + }, + + + // Given a flat array of segments, return an array of sub-arrays, grouped by each segment's col + groupSegCols: function(segs) { + var view = this.view; + var segCols = []; + var i; + + for (i = 0; i < view.colCnt; i++) { + segCols.push([]); + } + + for (i = 0; i < segs.length; i++) { + segCols[segs[i].col].push(segs[i]); + } + + return segCols; + } + +}); + + +// Given an array of segments that are all in the same column, sets the backwardCoord and forwardCoord on each. +// Also reorders the given array by date! +function placeSlotSegs(segs) { + var levels; + var level0; + var i; + + segs.sort(compareSegs); // order by date + levels = buildSlotSegLevels(segs); + computeForwardSlotSegs(levels); + + if ((level0 = levels[0])) { + + for (i = 0; i < level0.length; i++) { + computeSlotSegPressures(level0[i]); + } + + for (i = 0; i < level0.length; i++) { + computeSlotSegCoords(level0[i], 0, 0); + } + } +} + + +// Builds an array of segments "levels". The first level will be the leftmost tier of segments if the calendar is +// left-to-right, or the rightmost if the calendar is right-to-left. Assumes the segments are already ordered by date. +function buildSlotSegLevels(segs) { + var levels = []; + var i, seg; + var j; + + for (i=0; i seg2.top && seg1.top < seg2.bottom; +} + + +// A cmp function for determining which forward segment to rely on more when computing coordinates. +function compareForwardSlotSegs(seg1, seg2) { + // put higher-pressure first + return seg2.forwardPressure - seg1.forwardPressure || + // put segments that are closer to initial edge first (and favor ones with no coords yet) + (seg1.backwardCoord || 0) - (seg2.backwardCoord || 0) || + // do normal sorting... + compareSegs(seg1, seg2); +} + +;; + +/* An abstract class from which other views inherit from +----------------------------------------------------------------------------------------------------------------------*/ +// Newer methods should be written as prototype methods, not in the monster `View` function at the bottom. + +View.prototype = { + + calendar: null, // owner Calendar object + coordMap: null, // a CoordMap object for converting pixel regions to dates + el: null, // the view's containing element. set by Calendar + + // important Moments + start: null, // the date of the very first cell + end: null, // the date after the very last cell + intervalStart: null, // the start of the interval of time the view represents (1st of month for month view) + intervalEnd: null, // the exclusive end of the interval of time the view represents + + // used for cell-to-date and date-to-cell calculations + rowCnt: null, // # of weeks + colCnt: null, // # of days displayed in a week + + isSelected: false, // boolean whether cells are user-selected or not + + // subclasses can optionally use a scroll container + scrollerEl: null, // the element that will most likely scroll when content is too tall + scrollTop: null, // cached vertical scroll value + + // classNames styled by jqui themes + widgetHeaderClass: null, + widgetContentClass: null, + highlightStateClass: null, + + // document handlers, bound to `this` object + documentMousedownProxy: null, + documentDragStartProxy: null, + + + // Serves as a "constructor" to suppliment the monster `View` constructor below + init: function() { + var tm = this.opt('theme') ? 'ui' : 'fc'; + + this.widgetHeaderClass = tm + '-widget-header'; + this.widgetContentClass = tm + '-widget-content'; + this.highlightStateClass = tm + '-state-highlight'; + + // save references to `this`-bound handlers + this.documentMousedownProxy = $.proxy(this, 'documentMousedown'); + this.documentDragStartProxy = $.proxy(this, 'documentDragStart'); + }, + + + // Renders the view inside an already-defined `this.el`. + // Subclasses should override this and then call the super method afterwards. + render: function() { + this.updateSize(); + this.trigger('viewRender', this, this, this.el); + + // attach handlers to document. do it here to allow for destroy/rerender + $(document) + .on('mousedown', this.documentMousedownProxy) + .on('dragstart', this.documentDragStartProxy); // jqui drag + }, + + + // Clears all view rendering, event elements, and unregisters handlers + destroy: function() { + this.unselect(); + this.trigger('viewDestroy', this, this, this.el); + this.destroyEvents(); + this.el.empty(); // removes inner contents but leaves the element intact + + $(document) + .off('mousedown', this.documentMousedownProxy) + .off('dragstart', this.documentDragStartProxy); + }, + + + // Used to determine what happens when the users clicks next/prev. Given -1 for prev, 1 for next. + // Should apply the delta to `date` (a Moment) and return it. + incrementDate: function(date, delta) { + // subclasses should implement + }, + + + /* Dimensions + ------------------------------------------------------------------------------------------------------------------*/ + + + // Refreshes anything dependant upon sizing of the container element of the grid + updateSize: function(isResize) { + if (isResize) { + this.recordScroll(); + } + this.updateHeight(); + this.updateWidth(); + }, + + + // Refreshes the horizontal dimensions of the calendar + updateWidth: function() { + // subclasses should implement + }, + + + // Refreshes the vertical dimensions of the calendar + updateHeight: function() { + var calendar = this.calendar; // we poll the calendar for height information + + this.setHeight( + calendar.getSuggestedViewHeight(), + calendar.isHeightAuto() + ); + }, + + + // Updates the vertical dimensions of the calendar to the specified height. + // if `isAuto` is set to true, height becomes merely a suggestion and the view should use its "natural" height. + setHeight: function(height, isAuto) { + // subclasses should implement + }, + + + // Given the total height of the view, return the number of pixels that should be used for the scroller. + // Utility for subclasses. + computeScrollerHeight: function(totalHeight) { + var both = this.el.add(this.scrollerEl); + var otherHeight; // cumulative height of everything that is not the scrollerEl in the view (header+borders) + + // fuckin IE8/9/10/11 sometimes returns 0 for dimensions. this weird hack was the only thing that worked + both.css({ + position: 'relative', // cause a reflow, which will force fresh dimension recalculation + left: -1 // ensure reflow in case the el was already relative. negative is less likely to cause new scroll + }); + otherHeight = this.el.outerHeight() - this.scrollerEl.height(); // grab the dimensions + both.css({ position: '', left: '' }); // undo hack + + return totalHeight - otherHeight; + }, + + + // Called for remembering the current scroll value of the scroller. + // Should be called before there is a destructive operation (like removing DOM elements) that might inadvertently + // change the scroll of the container. + recordScroll: function() { + if (this.scrollerEl) { + this.scrollTop = this.scrollerEl.scrollTop(); + } + }, + + + // Set the scroll value of the scroller to the previously recorded value. + // Should be called after we know the view's dimensions have been restored following some type of destructive + // operation (like temporarily removing DOM elements). + restoreScroll: function() { + if (this.scrollTop !== null) { + this.scrollerEl.scrollTop(this.scrollTop); + } + }, + + + /* Events + ------------------------------------------------------------------------------------------------------------------*/ + + + // Renders the events onto the view. + // Should be overriden by subclasses. Subclasses should call the super-method afterwards. + renderEvents: function(events) { + this.segEach(function(seg) { + this.trigger('eventAfterRender', seg.event, seg.event, seg.el); + }); + this.trigger('eventAfterAllRender'); + }, + + + // Removes event elements from the view. + // Should be overridden by subclasses. Should call this super-method FIRST, then subclass DOM destruction. + destroyEvents: function() { + this.segEach(function(seg) { + this.trigger('eventDestroy', seg.event, seg.event, seg.el); + }); + }, + + + // Given an event and the default element used for rendering, returns the element that should actually be used. + // Basically runs events and elements through the eventRender hook. + resolveEventEl: function(event, el) { + var custom = this.trigger('eventRender', event, event, el); + + if (custom === false) { // means don't render at all + el = null; + } + else if (custom && custom !== true) { + el = $(custom); + } + + return el; + }, + + + // Hides all rendered event segments linked to the given event + showEvent: function(event) { + this.segEach(function(seg) { + seg.el.css('visibility', ''); + }, event); + }, + + + // Shows all rendered event segments linked to the given event + hideEvent: function(event) { + this.segEach(function(seg) { + seg.el.css('visibility', 'hidden'); + }, event); + }, + + + // Iterates through event segments. Goes through all by default. + // If the optional `event` argument is specified, only iterates through segments linked to that event. + // The `this` value of the callback function will be the view. + segEach: function(func, event) { + var segs = this.getSegs(); + var i; + + for (i = 0; i < segs.length; i++) { + if (!event || segs[i].event._id === event._id) { + func.call(this, segs[i]); + } + } + }, + + + // Retrieves all the rendered segment objects for the view + getSegs: function() { + // subclasses must implement + }, + + + /* Event Drag Visualization + ------------------------------------------------------------------------------------------------------------------*/ + + + // Renders a visual indication of an event hovering over the specified date. + // `end` is a Moment and might be null. + // `seg` might be null. if specified, it is the segment object of the event being dragged. + // otherwise, an external event from outside the calendar is being dragged. + renderDrag: function(start, end, seg) { + // subclasses should implement + }, + + + // Unrenders a visual indication of event hovering + destroyDrag: function() { + // subclasses should implement + }, + + + // Handler for accepting externally dragged events being dropped in the view. + // Gets called when jqui's 'dragstart' is fired. + documentDragStart: function(ev, ui) { + var _this = this; + var dropDate = null; + var dragListener; + + if (this.opt('droppable')) { // only listen if this setting is on + + // listener that tracks mouse movement over date-associated pixel regions + dragListener = new DragListener(this.coordMap, { + cellOver: function(cell, date) { + dropDate = date; + _this.renderDrag(date); + }, + cellOut: function() { + dropDate = null; + _this.destroyDrag(); + } + }); + + // gets called, only once, when jqui drag is finished + $(document).one('dragstop', function(ev, ui) { + _this.destroyDrag(); + if (dropDate) { + _this.trigger('drop', ev.target, dropDate, ev, ui); + } + }); + + dragListener.startDrag(ev); // start listening immediately + } + }, + + + /* Selection + ------------------------------------------------------------------------------------------------------------------*/ + + + // Selects a date range on the view. `start` and `end` are both Moments. + // `ev` is the native mouse event that begin the interaction. + select: function(start, end, ev) { + this.unselect(ev); + this.renderSelection(start, end); + this.reportSelection(start, end, ev); + }, + + + // Renders a visual indication of the selection + renderSelection: function(start, end) { + // subclasses should implement + }, + + + // Called when a new selection is made. Updates internal state and triggers handlers. + reportSelection: function(start, end, ev) { + this.isSelected = true; + this.trigger('select', null, start, end, ev); + }, + + + // Undoes a selection. updates in the internal state and triggers handlers. + // `ev` is the native mouse event that began the interaction. + unselect: function(ev) { + if (this.isSelected) { + this.isSelected = false; + this.destroySelection(); + this.trigger('unselect', null, ev); + } + }, + + + // Unrenders a visual indication of selection + destroySelection: function() { + // subclasses should implement + }, + + + // Handler for unselecting when the user clicks something and the 'unselectAuto' setting is on + documentMousedown: function(ev) { + var ignore; + + // is there a selection, and has the user made a proper left click? + if (this.isSelected && this.opt('unselectAuto') && isPrimaryMouseButton(ev)) { + + // only unselect if the clicked element is not identical to or inside of an 'unselectCancel' element + ignore = this.opt('unselectCancel'); + if (!ignore || !$(ev.target).closest(ignore).length) { + this.unselect(ev); + } + } + } + +}; + + +// We are mixing JavaScript OOP design patterns here by putting methods and member variables in the closed scope of the +// constructor. Going forward, methods should be part of the prototype. +function View(calendar) { + var t = this; + + // exports + t.calendar = calendar; + t.opt = opt; + t.trigger = trigger; + t.isEventDraggable = isEventDraggable; + t.isEventResizable = isEventResizable; + t.eventDrop = eventDrop; + t.eventResize = eventResize; + + // imports + var reportEventChange = calendar.reportEventChange; + + // locals + var options = calendar.options; + var nextDayThreshold = moment.duration(options.nextDayThreshold); + + + t.init(); // the "constructor" that concerns the prototype methods + + + function opt(name) { + var v = options[name]; + if ($.isPlainObject(v) && !isForcedAtomicOption(name)) { + return smartProperty(v, t.name); + } + return v; + } + + + function trigger(name, thisObj) { + return calendar.trigger.apply( + calendar, + [name, thisObj || t].concat(Array.prototype.slice.call(arguments, 2), [t]) + ); + } + + + + /* Event Editable Boolean Calculations + ------------------------------------------------------------------------------*/ + + + function isEventDraggable(event) { + var source = event.source || {}; + + return firstDefined( + event.startEditable, + source.startEditable, + opt('eventStartEditable'), + event.editable, + source.editable, + opt('editable') + ); + } + + + function isEventResizable(event) { + var source = event.source || {}; + + return firstDefined( + event.durationEditable, + source.durationEditable, + opt('eventDurationEditable'), + event.editable, + source.editable, + opt('editable') + ); + } + + + + /* Event Elements + ------------------------------------------------------------------------------*/ + + + // Compute the text that should be displayed on an event's element. + // Based off the settings of the view. Possible signatures: + // .getEventTimeText(event, formatStr) + // .getEventTimeText(startMoment, endMoment, formatStr) + // .getEventTimeText(startMoment, null, formatStr) + // `timeFormat` is used but the `formatStr` argument can be used to override. + t.getEventTimeText = function(event, formatStr) { + var start; + var end; + + if (typeof event === 'object' && typeof formatStr === 'object') { + // first two arguments are actually moments (or null). shift arguments. + start = event; + end = formatStr; + formatStr = arguments[2]; + } + else { + // otherwise, an event object was the first argument + start = event.start; + end = event.end; + } + + formatStr = formatStr || opt('timeFormat'); + + if (end && opt('displayEventEnd')) { + return calendar.formatRange(start, end, formatStr); + } + else { + return calendar.formatDate(start, formatStr); + } + }; + + + + /* Event Modification Reporting + ---------------------------------------------------------------------------------*/ + + + function eventDrop(el, event, newStart, ev) { + var mutateResult = calendar.mutateEvent(event, newStart, null); + + trigger( + 'eventDrop', + el, + event, + mutateResult.dateDelta, + function() { + mutateResult.undo(); + reportEventChange(); + }, + ev, + {} // jqui dummy + ); + + reportEventChange(); + } + + + function eventResize(el, event, newEnd, ev) { + var mutateResult = calendar.mutateEvent(event, null, newEnd); + + trigger( + 'eventResize', + el, + event, + mutateResult.durationDelta, + function() { + mutateResult.undo(); + reportEventChange(); + }, + ev, + {} // jqui dummy + ); + + reportEventChange(); + } + + + // ==================================================================================================== + // Utilities for day "cells" + // ==================================================================================================== + // The "basic" views are completely made up of day cells. + // The "agenda" views have day cells at the top "all day" slot. + // This was the obvious common place to put these utilities, but they should be abstracted out into + // a more meaningful class (like DayEventRenderer). + // ==================================================================================================== + + + // For determining how a given "cell" translates into a "date": + // + // 1. Convert the "cell" (row and column) into a "cell offset" (the # of the cell, cronologically from the first). + // Keep in mind that column indices are inverted with isRTL. This is taken into account. + // + // 2. Convert the "cell offset" to a "day offset" (the # of days since the first visible day in the view). + // + // 3. Convert the "day offset" into a "date" (a Moment). + // + // The reverse transformation happens when transforming a date into a cell. + + + // exports + t.isHiddenDay = isHiddenDay; + t.skipHiddenDays = skipHiddenDays; + t.getCellsPerWeek = getCellsPerWeek; + t.dateToCell = dateToCell; + t.dateToDayOffset = dateToDayOffset; + t.dayOffsetToCellOffset = dayOffsetToCellOffset; + t.cellOffsetToCell = cellOffsetToCell; + t.cellToDate = cellToDate; + t.cellToCellOffset = cellToCellOffset; + t.cellOffsetToDayOffset = cellOffsetToDayOffset; + t.dayOffsetToDate = dayOffsetToDate; + t.rangeToSegments = rangeToSegments; + t.isMultiDayEvent = isMultiDayEvent; + + + // internals + var hiddenDays = opt('hiddenDays') || []; // array of day-of-week indices that are hidden + var isHiddenDayHash = []; // is the day-of-week hidden? (hash with day-of-week-index -> bool) + var cellsPerWeek; + var dayToCellMap = []; // hash from dayIndex -> cellIndex, for one week + var cellToDayMap = []; // hash from cellIndex -> dayIndex, for one week + var isRTL = opt('isRTL'); + + + // initialize important internal variables + (function() { + + if (opt('weekends') === false) { + hiddenDays.push(0, 6); // 0=sunday, 6=saturday + } + + // Loop through a hypothetical week and determine which + // days-of-week are hidden. Record in both hashes (one is the reverse of the other). + for (var dayIndex=0, cellIndex=0; dayIndex<7; dayIndex++) { + dayToCellMap[dayIndex] = cellIndex; + isHiddenDayHash[dayIndex] = $.inArray(dayIndex, hiddenDays) != -1; + if (!isHiddenDayHash[dayIndex]) { + cellToDayMap[cellIndex] = dayIndex; + cellIndex++; + } + } + + cellsPerWeek = cellIndex; + if (!cellsPerWeek) { + throw 'invalid hiddenDays'; // all days were hidden? bad. + } + + })(); + + + // Is the current day hidden? + // `day` is a day-of-week index (0-6), or a Moment + function isHiddenDay(day) { + if (moment.isMoment(day)) { + day = day.day(); + } + return isHiddenDayHash[day]; + } + + + function getCellsPerWeek() { + return cellsPerWeek; + } + + + // Incrementing the current day until it is no longer a hidden day, returning a copy. + // If the initial value of `date` is not a hidden day, don't do anything. + // Pass `isExclusive` as `true` if you are dealing with an end date. + // `inc` defaults to `1` (increment one day forward each time) + function skipHiddenDays(date, inc, isExclusive) { + var out = date.clone(); + inc = inc || 1; + while ( + isHiddenDayHash[(out.day() + (isExclusive ? inc : 0) + 7) % 7] + ) { + out.add(inc, 'days'); + } + return out; + } + + + // + // TRANSFORMATIONS: cell -> cell offset -> day offset -> date + // + + // cell -> date (combines all transformations) + // Possible arguments: + // - row, col + // - { row:#, col: # } + function cellToDate() { + var cellOffset = cellToCellOffset.apply(null, arguments); + var dayOffset = cellOffsetToDayOffset(cellOffset); + var date = dayOffsetToDate(dayOffset); + return date; + } + + // cell -> cell offset + // Possible arguments: + // - row, col + // - { row:#, col:# } + function cellToCellOffset(row, col) { + var colCnt = t.colCnt; + + // rtl variables. wish we could pre-populate these. but where? + var dis = isRTL ? -1 : 1; + var dit = isRTL ? colCnt - 1 : 0; + + if (typeof row == 'object') { + col = row.col; + row = row.row; + } + var cellOffset = row * colCnt + (col * dis + dit); // column, adjusted for RTL (dis & dit) + + return cellOffset; + } + + // cell offset -> day offset + function cellOffsetToDayOffset(cellOffset) { + var day0 = t.start.day(); // first date's day of week + cellOffset += dayToCellMap[day0]; // normlize cellOffset to beginning-of-week + return Math.floor(cellOffset / cellsPerWeek) * 7 + // # of days from full weeks + cellToDayMap[ // # of days from partial last week + (cellOffset % cellsPerWeek + cellsPerWeek) % cellsPerWeek // crazy math to handle negative cellOffsets + ] - + day0; // adjustment for beginning-of-week normalization + } + + // day offset -> date + function dayOffsetToDate(dayOffset) { + return t.start.clone().add(dayOffset, 'days'); + } + + + // + // TRANSFORMATIONS: date -> day offset -> cell offset -> cell + // + + // date -> cell (combines all transformations) + function dateToCell(date) { + var dayOffset = dateToDayOffset(date); + var cellOffset = dayOffsetToCellOffset(dayOffset); + var cell = cellOffsetToCell(cellOffset); + return cell; + } + + // date -> day offset + function dateToDayOffset(date) { + return date.clone().stripTime().diff(t.start, 'days'); + } + + // day offset -> cell offset + function dayOffsetToCellOffset(dayOffset) { + var day0 = t.start.day(); // first date's day of week + dayOffset += day0; // normalize dayOffset to beginning-of-week + return Math.floor(dayOffset / 7) * cellsPerWeek + // # of cells from full weeks + dayToCellMap[ // # of cells from partial last week + (dayOffset % 7 + 7) % 7 // crazy math to handle negative dayOffsets + ] - + dayToCellMap[day0]; // adjustment for beginning-of-week normalization + } + + // cell offset -> cell (object with row & col keys) + function cellOffsetToCell(cellOffset) { + var colCnt = t.colCnt; + + // rtl variables. wish we could pre-populate these. but where? + var dis = isRTL ? -1 : 1; + var dit = isRTL ? colCnt - 1 : 0; + + var row = Math.floor(cellOffset / colCnt); + var col = ((cellOffset % colCnt + colCnt) % colCnt) * dis + dit; // column, adjusted for RTL (dis & dit) + return { + row: row, + col: col + }; + } + + + // + // Converts a date range into an array of segment objects. + // "Segments" are horizontal stretches of time, sliced up by row. + // A segment object has the following properties: + // - row + // - cols + // - isStart + // - isEnd + // + function rangeToSegments(start, end) { + + var rowCnt = t.rowCnt; + var colCnt = t.colCnt; + var segments = []; // array of segments to return + + // day offset for given date range + var dayRange = computeDayRange(start, end); // convert to a whole-day range + var rangeDayOffsetStart = dateToDayOffset(dayRange.start); + var rangeDayOffsetEnd = dateToDayOffset(dayRange.end); // an exclusive value + + // first and last cell offset for the given date range + // "last" implies inclusivity + var rangeCellOffsetFirst = dayOffsetToCellOffset(rangeDayOffsetStart); + var rangeCellOffsetLast = dayOffsetToCellOffset(rangeDayOffsetEnd) - 1; + + // loop through all the rows in the view + for (var row=0; row
' + + '' + // needed for matchCellWidths + htmlEscape(this.opt('weekNumberTitle')) + + '' + + '' + + '' + // needed for matchCellWidths + this.calendar.calculateWeekNumber(this.cellToDate(row, 0)) + + '' + + 's of the "number" row in the DayGrid's content skeleton. + // The number row will only exist if either day numbers or week numbers are turned on. + numberCellHtml: function(row, col, date) { + var classes; + + if (!this.dayNumbersVisible) { // if there are week numbers but not day numbers + return ''; // will create an empty space above events :( + } + + classes = this.dayGrid.getDayClasses(date); + classes.unshift('fc-day-number'); + + return '' + + '' + + date.date() + + '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '
' + + this.timeGrid.headHtml() + // render the day-of-week headers + '
' + + (this.dayGrid ? + '
' + + '
' : + '' + ) + + '
' + + '
' + + '
' + + '
'; + }, + + + // Generates the HTML that will go before the day-of week header cells. + // Queried by the TimeGrid subcomponent when generating rows. Ordering depends on isRTL. + headIntroHtml: function() { + var date; + var weekNumber; + var weekTitle; + var weekText; + + if (this.opt('weekNumbers')) { + date = this.cellToDate(0, 0); + weekNumber = this.calendar.calculateWeekNumber(date); + weekTitle = this.opt('weekNumberTitle'); + + if (this.opt('isRTL')) { + weekText = weekNumber + weekTitle; + } + else { + weekText = weekTitle + weekNumber; + } + + return '' + + '' + + '' + // needed for matchCellWidths + htmlEscape(weekText) + + '' + + ''; + } + else { + return ''; + } + }, + + + // Generates the HTML that goes before the all-day cells. + // Queried by the DayGrid subcomponent when generating rows. Ordering depends on isRTL. + dayIntroHtml: function() { + return '' + + '' + + '' + // needed for matchCellWidths + (this.opt('allDayHtml') || htmlEscape(this.opt('allDayText'))) + + '' + + ''; + }, + + + // Generates the HTML that goes before the bg of the TimeGrid slot area. Long vertical column. + slotBgIntroHtml: function() { + return ''; + }, + + + // Generates the HTML that goes before all other types of cells. + // Affects content-skeleton, helper-skeleton, highlight-skeleton for both the time-grid and day-grid. + // Queried by the TimeGrid and DayGrid subcomponents when generating rows. Ordering depends on isRTL. + introHtml: function() { + return ''; + }, + + + // Generates an HTML attribute string for setting the width of the axis, if it is known + axisStyleAttr: function() { + if (this.axisWidth !== null) { + return 'style="width:' + this.axisWidth + 'px"'; + } + return ''; + }, + + + /* Dimensions + ------------------------------------------------------------------------------------------------------------------*/ + + updateSize: function(isResize) { + if (isResize) { + this.timeGrid.resize(); + } + View.prototype.updateSize.call(this, isResize); + }, + + + // Refreshes the horizontal dimensions of the view + updateWidth: function() { + // make all axis cells line up, and record the width so newly created axis cells will have it + this.axisWidth = matchCellWidths(this.el.find('.fc-axis')); + }, + + + // Adjusts the vertical dimensions of the view to the specified values + setHeight: function(totalHeight, isAuto) { + var eventLimit; + var scrollerHeight; + + if (this.bottomRuleHeight === null) { + // calculate the height of the rule the very first time + this.bottomRuleHeight = this.bottomRuleEl.outerHeight(); + } + this.bottomRuleEl.hide(); // .show() will be called later if this
is necessary + + // reset all dimensions back to the original state + this.scrollerEl.css('overflow', ''); + unsetScroller(this.scrollerEl); + uncompensateScroll(this.noScrollRowEls); + + // limit number of events in the all-day area + if (this.dayGrid) { + this.dayGrid.destroySegPopover(); // kill the "more" popover if displayed + + eventLimit = this.opt('eventLimit'); + if (eventLimit && typeof eventLimit !== 'number') { + eventLimit = AGENDA_ALL_DAY_EVENT_LIMIT; // make sure "auto" goes to a real number + } + if (eventLimit) { + this.dayGrid.limitRows(eventLimit); + } + } + + if (!isAuto) { // should we force dimensions of the scroll container, or let the contents be natural height? + + scrollerHeight = this.computeScrollerHeight(totalHeight); + if (setPotentialScroller(this.scrollerEl, scrollerHeight)) { // using scrollbars? + + // make the all-day and header rows lines up + compensateScroll(this.noScrollRowEls, getScrollbarWidths(this.scrollerEl)); + + // the scrollbar compensation might have changed text flow, which might affect height, so recalculate + // and reapply the desired height to the scroller. + scrollerHeight = this.computeScrollerHeight(totalHeight); + this.scrollerEl.height(scrollerHeight); + + this.restoreScroll(); + } + else { // no scrollbars + // still, force a height and display the bottom rule (marks the end of day) + this.scrollerEl.height(scrollerHeight).css('overflow', 'hidden'); // in case
goes outside + this.bottomRuleEl.show(); + } + } + }, + + + // Sets the scroll value of the scroller to the intial pre-configured state prior to allowing the user to change it. + resetScroll: function() { + var _this = this; + var scrollTime = moment.duration(this.opt('scrollTime')); + var top = this.timeGrid.computeTimeTop(scrollTime); + + // zoom can give weird floating-point values. rather scroll a little bit further + top = Math.ceil(top); + + if (top) { + top++; // to overcome top border that slots beyond the first have. looks better + } + + function scroll() { + _this.scrollerEl.scrollTop(top); + } + + scroll(); + setTimeout(scroll, 0); // overrides any previous scroll state made by the browser + }, + + + /* Events + ------------------------------------------------------------------------------------------------------------------*/ + + + // Renders events onto the view and populates the View's segment array + renderEvents: function(events) { + var dayEvents = []; + var timedEvents = []; + var daySegs = []; + var timedSegs; + var i; + + // separate the events into all-day and timed + for (i = 0; i < events.length; i++) { + if (events[i].allDay) { + dayEvents.push(events[i]); + } + else { + timedEvents.push(events[i]); + } + } + + // render the events in the subcomponents + timedSegs = this.timeGrid.renderEvents(timedEvents); + if (this.dayGrid) { + daySegs = this.dayGrid.renderEvents(dayEvents); + } + + // the all-day area is flexible and might have a lot of events, so shift the height + this.updateHeight(); + + // /View.prototype.renderEvents.call(this, events); // call the super-method + }, + + + // Retrieves all segment objects that are rendered in the view + getSegs: function() { + return this.timeGrid.getSegs().concat( + this.dayGrid ? this.dayGrid.getSegs() : [] + ); + }, + + + // Unrenders all event elements and clears internal segment data + destroyEvents: function() { + View.prototype.destroyEvents.call(this); // do this before the grids' segs have been cleared + + // if destroyEvents is being called as part of an event rerender, renderEvents will be called shortly + // after, so remember what the scroll value was so we can restore it. + this.recordScroll(); + + // destroy the events in the subcomponents + this.timeGrid.destroyEvents(); + if (this.dayGrid) { + this.dayGrid.destroyEvents(); + } + + // we DON'T need to call updateHeight() because: + // A) a renderEvents() call always happens after this, which will eventually call updateHeight() + // B) in IE8, this causes a flash whenever events are rerendered + }, + + + /* Event Dragging + ------------------------------------------------------------------------------------------------------------------*/ + + + // Renders a visual indication of an event being dragged over the view. + // A returned value of `true` signals that a mock "helper" event has been rendered. + renderDrag: function(start, end, seg) { + if (start.hasTime()) { + return this.timeGrid.renderDrag(start, end, seg); + } + else if (this.dayGrid) { + return this.dayGrid.renderDrag(start, end, seg); + } + }, + + + // Unrenders a visual indications of an event being dragged over the view + destroyDrag: function() { + this.timeGrid.destroyDrag(); + if (this.dayGrid) { + this.dayGrid.destroyDrag(); + } + }, + + + /* Selection + ------------------------------------------------------------------------------------------------------------------*/ + + + // Renders a visual indication of a selection + renderSelection: function(start, end) { + if (start.hasTime() || end.hasTime()) { + this.timeGrid.renderSelection(start, end); + } + else if (this.dayGrid) { + this.dayGrid.renderSelection(start, end); + } + }, + + + // Unrenders a visual indications of a selection + destroySelection: function() { + this.timeGrid.destroySelection(); + if (this.dayGrid) { + this.dayGrid.destroySelection(); + } + } + +}); + +;; + +/* A week view with an all-day cell area at the top, and a time grid below +----------------------------------------------------------------------------------------------------------------------*/ +// TODO: a WeekView mixin for calculating dates and titles + +fcViews.agendaWeek = AgendaWeekView; // register the view + +function AgendaWeekView(calendar) { + AgendaView.call(this, calendar); // call the super-constructor +} + + +AgendaWeekView.prototype = createObject(AgendaView.prototype); // define the super-class +$.extend(AgendaWeekView.prototype, { + + name: 'agendaWeek', + + + incrementDate: function(date, delta) { + return date.clone().stripTime().add(delta, 'weeks').startOf('week'); + }, + + + render: function(date) { + + this.intervalStart = date.clone().stripTime().startOf('week'); + this.intervalEnd = this.intervalStart.clone().add(1, 'weeks'); + + this.start = this.skipHiddenDays(this.intervalStart); + this.end = this.skipHiddenDays(this.intervalEnd, -1, true); + + this.title = this.calendar.formatRange( + this.start, + this.end.clone().subtract(1), // make inclusive by subtracting 1 ms + this.opt('titleFormat'), + ' \u2014 ' // emphasized dash + ); + + AgendaView.prototype.render.call(this, this.getCellsPerWeek()); // call the super-method + } + +}); + +;; + +/* A day view with an all-day cell area at the top, and a time grid below +----------------------------------------------------------------------------------------------------------------------*/ + +fcViews.agendaDay = AgendaDayView; // register the view + +function AgendaDayView(calendar) { + AgendaView.call(this, calendar); // call the super-constructor +} + + +AgendaDayView.prototype = createObject(AgendaView.prototype); // define the super-class +$.extend(AgendaDayView.prototype, { + + name: 'agendaDay', + + + incrementDate: function(date, delta) { + var out = date.clone().stripTime().add(delta, 'days'); + out = this.skipHiddenDays(out, delta < 0 ? -1 : 1); + return out; + }, + + + render: function(date) { + + this.start = this.intervalStart = date.clone().stripTime(); + this.end = this.intervalEnd = this.start.clone().add(1, 'days'); + + this.title = this.calendar.formatDate(this.start, this.opt('titleFormat')); + + AgendaView.prototype.render.call(this, 1); // call the super-method + } + +}); + +;; + +/* A component that renders a grid of whole-days that runs horizontally. There can be multiple rows, one per week. +----------------------------------------------------------------------------------------------------------------------*/ + +function ResourceDayGrid(view) { + DayGrid.call(this, view); // call the super-constructor +} + + +ResourceDayGrid.prototype = createObject(DayGrid.prototype); // declare the super-class +$.extend(ResourceDayGrid.prototype, { + rangeToSegs: function(rangeStart, rangeEnd, resourceIds) { + var col, segments = []; + + var currentDate = this.view.calendar.getDate(); + if((rangeStart <= currentDate) && (currentDate < rangeEnd)) { + var view = this.view; + var resources = view.calendar.fetchResources(); + + for (col = 0; col < view.colCnt; col++) { + var resource = resources[col]; + if (resourceIds.indexOf(resource.id) > -1){ + segments.push({ + row: 0, + leftCol: col, + rightCol: col, + isStart: true, + isEnd: true + }); + } + } + } + + return segments; + } +}); +;; + +/* Event-rendering methods for the DayGrid class +----------------------------------------------------------------------------------------------------------------------*/ + +$.extend(ResourceDayGrid.prototype, { + eventToSegs: function(event, intervalStart, intervalEnd) { + var eventStart = event.start.clone().stripZone(); // normalize + var eventEnd = this.view.calendar.getEventEnd(event).stripZone(); // compute (if necessary) and normalize + var segs; + var i, seg; + + if (intervalStart && intervalEnd) { + seg = intersectionToSeg(eventStart, eventEnd, intervalStart, intervalEnd); + segs = seg ? [ seg ] : []; + } + else { + segs = this.rangeToSegs(eventStart, eventEnd, event.resources); // defined by the subclass + } + + // assign extra event-related properties to the segment objects + for (i = 0; i < segs.length; i++) { + seg = segs[i]; + seg.event = event; + seg.eventStartMS = +eventStart; + seg.eventDurationMS = eventEnd - eventStart; + } + + return segs; + } +}); +;; + +/* A component that renders one or more columns of vertical time slots +----------------------------------------------------------------------------------------------------------------------*/ + +function ResourceTimeGrid(view) { + TimeGrid.call(this, view); // call the super-constructor +} + + +ResourceTimeGrid.prototype = createObject(TimeGrid.prototype); // define the super-class +$.extend(ResourceTimeGrid.prototype, { +// Slices up a date range into a segment for each column +// Each column represents a resource. An event can be assigned to multiple resources +// so we need to build segs accordingly + rangeToSegs: function(rangeStart, rangeEnd, resourceIds) { + var view = this.view; + var segs = []; + var seg; + var col; + var cellDate; + var colStart, colEnd; + + var resources = view.calendar.fetchResources(); + + // normalize + rangeStart = rangeStart.clone().stripZone(); + rangeEnd = rangeEnd.clone().stripZone(); + + for (col = 0; col < view.colCnt; col++) { + var resource = resources[col]; + if (resourceIds.indexOf(resource.id) > -1){ + cellDate = view.cellToDate(0, col); // use the View's cell system for this + colStart = cellDate.clone().time(this.minTime); + colEnd = cellDate.clone().time(this.maxTime); + seg = intersectionToSeg(rangeStart, rangeEnd, colStart, colEnd); + if (seg) { + seg.col = col; + segs.push(seg); + } + } + } + + return segs; + } + +}); + +;; + +/* Event-rendering methods for the TimeGrid class +----------------------------------------------------------------------------------------------------------------------*/ + +$.extend(ResourceTimeGrid.prototype, { +// Slices a single event into an array of event segments. + // When `intervalStart` and `intervalEnd` are specified, intersect the events with that interval. + // Otherwise, let the subclass decide how it wants to slice the segments over the grid. + eventToSegs: function(event, intervalStart, intervalEnd) { + var eventStart = event.start.clone().stripZone(); // normalize + var eventEnd = this.view.calendar.getEventEnd(event).stripZone(); // compute (if necessary) and normalize + var segs; + var i, seg; + + if (intervalStart && intervalEnd) { + seg = intersectionToSeg(eventStart, eventEnd, intervalStart, intervalEnd); + segs = seg ? [ seg ] : []; + } + else { + segs = this.rangeToSegs(eventStart, eventEnd, event.resources); // defined by the subclass + } + + // assign extra event-related properties to the segment objects + for (i = 0; i < segs.length; i++) { + seg = segs[i]; + seg.event = event; + seg.eventStartMS = +eventStart; + seg.eventDurationMS = eventEnd - eventStart; + } + + return segs; + } +}); + +;; + +/* An abstract class for all agenda-related views. Displays one more columns with time slots running vertically. +----------------------------------------------------------------------------------------------------------------------*/ +// Is a manager for the TimeGrid subcomponent and possibly the DayGrid subcomponent (if allDaySlot is on). +// Responsible for managing width/height. + +setDefaults({ + allDaySlot: true, + allDayText: 'all-day', + + scrollTime: '06:00:00', + + slotDuration: '00:30:00', + + axisFormat: generateAgendaAxisFormat, + timeFormat: { + agenda: generateAgendaTimeFormat + }, + + minTime: '00:00:00', + maxTime: '24:00:00', + slotEventOverlap: true +}); + +function ResourceView(calendar) { + View.call(this, calendar); // call the super-constructor + + // overrides - the view.js should expose these on the prototype then we wouldn't have to do this. + this.cellToDate = ResourceView.prototype.cellToDate; + + this.timeGrid = new ResourceTimeGrid(this); + + if (this.opt('allDaySlot')) { // should we display the "all-day" area? + this.dayGrid = new ResourceDayGrid(this); // the all-day subcomponent of this view + + // the coordinate grid will be a combination of both subcomponents' grids + this.coordMap = new ComboCoordMap([ + this.dayGrid.coordMap, + this.timeGrid.coordMap + ]); + } + else { + this.coordMap = this.timeGrid.coordMap; + } +} + + +ResourceView.prototype = createObject(AgendaView.prototype); // define the super-class +$.extend(ResourceView.prototype, { + + cellToDate: function() { + return this.start.clone(); + }, + + // fix the classes, etc. + headCellHtml: function(row, col, date) { + var view = this; + var calendar = view.calendar; + var resource = calendar.fetchResources()[col]; + return '' + + '' + + htmlEscape(resource.name) + + ''; + } + +}); + +;; + +/* A day view with an all-day cell area at the top, and a time grid below by resource +----------------------------------------------------------------------------------------------------------------------*/ + +fcViews.resourceDay = ResourceDayView; + +function ResourceDayView(calendar) { // TODO: make a ResourceView mixin + ResourceView.call(this, calendar); // call the super-constructor +} + +ResourceDayView.prototype = createObject(ResourceView.prototype); // define the super-class +$.extend(ResourceDayView.prototype, { + + name: 'resourceDay', + + + incrementDate: function(date, delta) { + var out = date.clone().stripTime().add(delta, 'days'); + out = this.skipHiddenDays(out, delta < 0 ? -1 : 1); + return out; + }, + + + render: function(date) { + this.start = this.intervalStart = date.clone().stripTime(); + this.end = this.intervalEnd = this.start.clone().add(1, 'days'); + + this.title = this.calendar.formatDate(this.start, this.opt('titleFormat')); + + ResourceView.prototype.render.call(this, this.calendar.fetchResources().length); // call the super-method + }, + + // Computes HTML classNames for a single-day cell + getDayClasses: function(date) { + var view = this.view; + var today = view.calendar.getNow().stripTime(); + var classes = [ 'fc-' + dayIDs[date.day()] ]; + + if ( + view.name === 'month' && + date.month() != view.intervalStart.month() + ) { + classes.push('fc-other-month'); + } + + if (date.isSame(today, 'day')) { + classes.push( + 'fc-todaysss', + view.highlightStateClass + ); + } + else if (date < today) { + classes.push('fc-past'); + } + else { + classes.push('fc-future'); + } + + return classes; + } + +}); +;; + }); \ No newline at end of file diff --git a/dist/cdnjs/2.1.1/fullcalendar.min.css b/dist/cdnjs/2.1.1/fullcalendar.min.css index 2858443..31aaeb4 100644 --- a/dist/cdnjs/2.1.1/fullcalendar.min.css +++ b/dist/cdnjs/2.1.1/fullcalendar.min.css @@ -1,5 +1,5 @@ -/*! - * FullCalendar v2.1.1 Stylesheet - * Docs & License: http://arshaw.com/fullcalendar/ - * (c) 2013 Adam Shaw +/*! + * FullCalendar v2.1.1 Stylesheet + * Docs & License: http://arshaw.com/fullcalendar/ + * (c) 2013 Adam Shaw */.fc{direction:ltr;text-align:left}.fc-rtl{text-align:right}body .fc{font-size:1em}.fc-unthemed .fc-popover,.fc-unthemed .fc-row,.fc-unthemed hr,.fc-unthemed tbody,.fc-unthemed td,.fc-unthemed th,.fc-unthemed thead{border-color:#ddd}.fc-unthemed .fc-popover{background-color:#fff}.fc-unthemed .fc-popover .fc-header,.fc-unthemed hr{background:#eee}.fc-unthemed .fc-popover .fc-header .fc-close{color:#666}.fc-unthemed .fc-today{background:#fcf8e3}.fc-highlight{background:#bce8f1;opacity:.3;filter:alpha(opacity=30)}.fc-icon{display:inline-block;font-size:2em;line-height:.5em;height:.5em;font-family:"Courier New",Courier,monospace}.fc-icon-left-single-arrow:after{content:"\02039";font-weight:700}.fc-icon-right-single-arrow:after{content:"\0203A";font-weight:700}.fc-icon-left-double-arrow:after{content:"\000AB"}.fc-icon-right-double-arrow:after{content:"\000BB"}.fc-icon-x:after{content:"\000D7"}.fc button{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;margin:0;height:2.1em;padding:0 .6em;font-size:1em;white-space:nowrap;cursor:pointer}.fc button::-moz-focus-inner{margin:0;padding:0}.fc-state-default{border:1px solid}.fc-state-default.fc-corner-left{border-top-left-radius:4px;border-bottom-left-radius:4px}.fc-state-default.fc-corner-right{border-top-right-radius:4px;border-bottom-right-radius:4px}.fc button .fc-icon{position:relative;top:.05em;margin:0 .1em}.fc-state-default{background-color:#f5f5f5;background-image:-moz-linear-gradient(top,#fff,#e6e6e6);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fff),to(#e6e6e6));background-image:-webkit-linear-gradient(top,#fff,#e6e6e6);background-image:-o-linear-gradient(top,#fff,#e6e6e6);background-image:linear-gradient(to bottom,#fff,#e6e6e6);background-repeat:repeat-x;border-color:#e6e6e6 #e6e6e6 #bfbfbf;border-color:rgba(0,0,0,.1) rgba(0,0,0,.1) rgba(0,0,0,.25);color:#333;text-shadow:0 1px 1px rgba(255,255,255,.75);box-shadow:inset 0 1px 0 rgba(255,255,255,.2),0 1px 2px rgba(0,0,0,.05)}.fc-state-active,.fc-state-disabled,.fc-state-down,.fc-state-hover{color:#333;background-color:#e6e6e6}.fc-state-hover{color:#333;text-decoration:none;background-position:0 -15px;-webkit-transition:background-position .1s linear;-moz-transition:background-position .1s linear;-o-transition:background-position .1s linear;transition:background-position .1s linear}.fc-state-active,.fc-state-down{background-color:#ccc;background-image:none;box-shadow:inset 0 2px 4px rgba(0,0,0,.15),0 1px 2px rgba(0,0,0,.05)}.fc-state-disabled{cursor:default;background-image:none;opacity:.65;filter:alpha(opacity=65);box-shadow:none}.fc-button-group{display:inline-block}.fc .fc-button-group>*{float:left;margin:0 0 0 -1px}.fc .fc-button-group>:first-child{margin-left:0}.fc-popover{position:absolute;box-shadow:0 2px 6px rgba(0,0,0,.15)}.fc-popover .fc-header{padding:2px 4px}.fc-popover .fc-header .fc-title{margin:0 2px}.fc-popover .fc-header .fc-close{cursor:pointer}.fc-ltr .fc-popover .fc-header .fc-title,.fc-rtl .fc-popover .fc-header .fc-close{float:left}.fc-ltr .fc-popover .fc-header .fc-close,.fc-rtl .fc-popover .fc-header .fc-title{float:right}.fc-unthemed .fc-popover{border-width:1px;border-style:solid}.fc-unthemed .fc-popover .fc-header .fc-close{font-size:25px;margin-top:4px}.fc-popover>.ui-widget-header+.ui-widget-content{border-top:0}.fc hr{height:0;margin:0;padding:0 0 2px;border-style:solid;border-width:1px 0}.fc-clear{clear:both}.fc-bg,.fc-helper-skeleton,.fc-highlight-skeleton{position:absolute;top:0;left:0;right:0}.fc-bg{bottom:0}.fc-bg table{height:100%}.fc table{width:100%;table-layout:fixed;border-collapse:collapse;border-spacing:0;font-size:1em}.fc th{text-align:center}.fc td,.fc th{border-style:solid;border-width:1px;padding:0;vertical-align:top}.fc td.fc-today{border-style:double}.fc .fc-row{border-style:solid;border-width:0}.fc-row table{border-left:0 hidden transparent;border-right:0 hidden transparent;border-bottom:0 hidden transparent}.fc-row:first-child table{border-top:0 hidden transparent}.fc-row{position:relative}.fc-row .fc-bg{z-index:1}.fc-row .fc-highlight-skeleton{z-index:2;bottom:0}.fc-row .fc-highlight-skeleton table{height:100%}.fc-row .fc-highlight-skeleton td{border-color:transparent}.fc-row .fc-content-skeleton{position:relative;z-index:3;padding-bottom:2px}.fc-row .fc-helper-skeleton{z-index:4}.fc-row .fc-content-skeleton td,.fc-row .fc-helper-skeleton td{background:0 0;border-color:transparent;border-bottom:0}.fc-row .fc-content-skeleton tbody td,.fc-row .fc-helper-skeleton tbody td{border-top:0}.fc-scroller{overflow-y:scroll;overflow-x:hidden}.fc-scroller>*{position:relative;width:100%;overflow:hidden}.fc-event{position:relative;display:block;font-size:.85em;line-height:1.3;border-radius:3px;border:1px solid #3a87ad;background-color:#3a87ad;font-weight:400}.fc-event,.fc-event:hover,.ui-widget .fc-event{color:#fff;text-decoration:none}.fc-event.fc-draggable,.fc-event[href]{cursor:pointer}.fc-day-grid-event{margin:1px 2px 0;padding:0 1px}.fc-ltr .fc-day-grid-event.fc-not-start,.fc-rtl .fc-day-grid-event.fc-not-end{margin-left:0;border-left-width:0;padding-left:1px;border-top-left-radius:0;border-bottom-left-radius:0}.fc-ltr .fc-day-grid-event.fc-not-end,.fc-rtl .fc-day-grid-event.fc-not-start{margin-right:0;border-right-width:0;padding-right:1px;border-top-right-radius:0;border-bottom-right-radius:0}.fc-day-grid-event>.fc-content{white-space:nowrap;overflow:hidden}.fc-day-grid-event .fc-time{font-weight:700}.fc-day-grid-event .fc-resizer{position:absolute;top:0;bottom:0;width:7px}.fc-ltr .fc-day-grid-event .fc-resizer{right:-3px;cursor:e-resize}.fc-rtl .fc-day-grid-event .fc-resizer{left:-3px;cursor:w-resize}a.fc-more{margin:1px 3px;font-size:.85em;cursor:pointer;text-decoration:none}a.fc-more:hover{text-decoration:underline}.fc-limited{display:none}.fc-day-grid .fc-row{z-index:1}.fc-more-popover{z-index:2;width:220px}.fc-more-popover .fc-event-container{padding:10px}.fc-toolbar{text-align:center;margin-bottom:1em}.fc-toolbar .fc-left{float:left}.fc-toolbar .fc-right{float:right}.fc-toolbar .fc-center{display:inline-block}.fc .fc-toolbar>*>*{float:left;margin-left:.75em}.fc .fc-toolbar>*>:first-child{margin-left:0}.fc-toolbar h2{margin:0}.fc-toolbar button{position:relative}.fc-toolbar .fc-state-hover,.fc-toolbar .ui-state-hover{z-index:2}.fc-toolbar .fc-state-down{z-index:3}.fc-toolbar .fc-state-active,.fc-toolbar .ui-state-active{z-index:4}.fc-toolbar button:focus{z-index:5}.fc-view-container *,.fc-view-container :after,.fc-view-container :before{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}.fc-view,.fc-view>table{position:relative;z-index:1}.fc-basicDay-view .fc-content-skeleton,.fc-basicWeek-view .fc-content-skeleton{padding-top:1px;padding-bottom:1em}.fc-basic-view tbody .fc-row{min-height:4em}.fc-row.fc-rigid{overflow:hidden}.fc-row.fc-rigid .fc-content-skeleton{position:absolute;top:0;left:0;right:0}.fc-basic-view .fc-day-number,.fc-basic-view .fc-week-number{padding:0 2px}.fc-basic-view td.fc-day-number,.fc-basic-view td.fc-week-number span{padding-top:2px;padding-bottom:2px}.fc-basic-view .fc-week-number{text-align:center}.fc-basic-view .fc-week-number span{display:inline-block;min-width:1.25em}.fc-ltr .fc-basic-view .fc-day-number{text-align:right}.fc-rtl .fc-basic-view .fc-day-number{text-align:left}.fc-day-number.fc-other-month{opacity:.3;filter:alpha(opacity=30)}.fc-agenda-view .fc-day-grid{position:relative;z-index:2}.fc-agenda-view .fc-day-grid .fc-row{min-height:3em}.fc-agenda-view .fc-day-grid .fc-row .fc-content-skeleton{padding-top:1px;padding-bottom:1em}.fc .fc-axis{vertical-align:middle;padding:0 4px;white-space:nowrap}.fc-ltr .fc-axis{text-align:right}.fc-rtl .fc-axis{text-align:left}.ui-widget td.fc-axis{font-weight:400}.fc-time-grid,.fc-time-grid-container{position:relative;z-index:1}.fc-time-grid{min-height:100%}.fc-time-grid table{border:0 hidden transparent}.fc-time-grid>.fc-bg{z-index:1}.fc-time-grid .fc-slats,.fc-time-grid>hr{position:relative;z-index:2}.fc-time-grid .fc-highlight-skeleton{z-index:3}.fc-time-grid .fc-content-skeleton{position:absolute;z-index:4;top:0;left:0;right:0}.fc-time-grid>.fc-helper-skeleton{z-index:5}.fc-slats td{height:1.5em;border-bottom:0}.fc-slats .fc-minor td{border-top-style:dotted}.fc-slats .ui-widget-content{background:0 0}.fc-time-grid .fc-highlight-container{position:relative}.fc-time-grid .fc-highlight{position:absolute;left:0;right:0}.fc-time-grid .fc-event-container{position:relative}.fc-ltr .fc-time-grid .fc-event-container{margin:0 2.5% 0 2px}.fc-rtl .fc-time-grid .fc-event-container{margin:0 2px 0 2.5%}.fc-time-grid .fc-event{position:absolute;z-index:1}.fc-time-grid-event.fc-not-start{border-top-width:0;padding-top:1px;border-top-left-radius:0;border-top-right-radius:0}.fc-time-grid-event.fc-not-end{border-bottom-width:0;padding-bottom:1px;border-bottom-left-radius:0;border-bottom-right-radius:0}.fc-time-grid-event{overflow:hidden}.fc-time-grid-event>.fc-content{position:relative;z-index:2}.fc-time-grid-event .fc-time,.fc-time-grid-event .fc-title{padding:0 1px}.fc-time-grid-event .fc-time{font-size:.85em;white-space:nowrap}.fc-time-grid-event .fc-bg{z-index:1;background:#fff;opacity:.25;filter:alpha(opacity=25)}.fc-time-grid-event.fc-short .fc-content{white-space:nowrap}.fc-time-grid-event.fc-short .fc-time,.fc-time-grid-event.fc-short .fc-title{display:inline-block;vertical-align:top}.fc-time-grid-event.fc-short .fc-time span{display:none}.fc-time-grid-event.fc-short .fc-time:before{content:attr(data-start)}.fc-time-grid-event.fc-short .fc-time:after{content:"\000A0-\000A0"}.fc-time-grid-event.fc-short .fc-title{font-size:.85em;padding:0}.fc-time-grid-event .fc-resizer{position:absolute;z-index:3;left:0;right:0;bottom:0;height:8px;overflow:hidden;line-height:8px;font-size:11px;font-family:monospace;text-align:center;cursor:s-resize}.fc-time-grid-event .fc-resizer:after{content:"="} \ No newline at end of file diff --git a/dist/cdnjs/2.1.1/fullcalendar.min.js b/dist/cdnjs/2.1.1/fullcalendar.min.js index aa83c57..93bbc04 100644 --- a/dist/cdnjs/2.1.1/fullcalendar.min.js +++ b/dist/cdnjs/2.1.1/fullcalendar.min.js @@ -3,6 +3,6 @@ * Docs & License: http://arshaw.com/fullcalendar/ * (c) 2013 Adam Shaw */ -(function(t){"function"==typeof define&&define.amd?define(["jquery","moment"],t):t(jQuery,moment)})(function(t,e){function i(t,e){return e.longDateFormat("LT").replace(":mm","(:mm)").replace(/(\Wmm)$/,"($1)").replace(/\s*a$/i,"t")}function n(t,e){var i=e.longDateFormat("L");return i=i.replace(/^Y+[^\w\s]*|[^\w\s]*Y+$/g,""),t.isRTL?i+=" ddd":i="ddd "+i,i}function r(t){o(De,t)}function o(e){function i(i,n){t.isPlainObject(n)&&t.isPlainObject(e[i])&&!s(i)?e[i]=o({},e[i],n):void 0!==n&&(e[i]=n)}for(var n=1;arguments.length>n;n++)t.each(arguments[n],i);return e}function s(t){return/(Time|Duration)$/.test(t)}function l(i,n){function r(t){var i=e.localeData||e.langData;return i.call(e,t)||i.call(e,"en")}function s(t){ie?h()&&(p(),f(t)):l()}function l(){ne=K.theme?"ui":"fc",i.addClass("fc"),K.isRTL?i.addClass("fc-rtl"):i.addClass("fc-ltr"),K.theme?i.addClass("ui-widget"):i.addClass("fc-unthemed"),ie=t("
").prependTo(i),te=new a(q,K),ee=te.render(),ee&&i.prepend(ee),u(K.defaultView),K.handleWindowResize&&(se=L(v,K.windowResizeDelay),t(window).resize(se))}function d(){re&&re.destroy(),te.destroy(),ie.remove(),i.removeClass("fc fc-ltr fc-rtl fc-unthemed ui-widget"),t(window).unbind("resize",se)}function h(){return i.is(":visible")}function u(t){f(0,t)}function f(e,i){he++,re&&i&&re.name!==i&&(te.deactivateButton(re.name),I(),re.start&&re.destroy(),re.el.remove(),re=null),!re&&i&&(re=new xe[i](q),re.el=t("
").appendTo(ie),te.activateButton(i)),re&&(e&&(le=re.incrementDate(le,e)),re.start&&!e&&le.isWithin(re.intervalStart,re.intervalEnd)||h()&&(I(),re.start&&re.destroy(),re.render(le),Z(),C(),x(),b())),Z(),he--}function g(t){return h()?(t&&m(),he++,re.updateSize(!0),he--,!0):void 0}function p(){h()&&m()}function m(){oe="number"==typeof K.contentHeight?K.contentHeight:"number"==typeof K.height?K.height-(ee?ee.outerHeight(!0):0):Math.round(ie.width()/Math.max(K.aspectRatio,.5))}function v(t){!he&&t.target===window&&re.start&&g(!0)&&re.trigger("windowResize",de)}function y(){E(),S()}function w(){h()&&(I(),re.destroyEvents(),re.renderEvents(ue),Z())}function E(){I(),re.destroyEvents(),Z()}function b(){!K.lazyFetching||ae(re.start,re.end)?S():w()}function S(){ce(re.start,re.end)}function D(t){ue=t,w()}function T(){w()}function C(){te.updateTitle(re.title)}function x(){var t=q.getNow();t.isWithin(re.intervalStart,re.intervalEnd)?te.disableButton("today"):te.enableButton("today")}function k(t,e){t=q.moment(t),e=e?q.moment(e):t.hasTime()?t.clone().add(q.defaultTimedEventDuration):t.clone().add(q.defaultAllDayEventDuration),re.select(t,e)}function M(){re&&re.unselect()}function R(){f(-1)}function P(){f(1)}function G(){le.add(-1,"years"),f()}function N(){le.add(1,"years"),f()}function Y(){le=q.getNow(),f()}function A(t){le=q.moment(t),f()}function _(t){le.add(e.duration(t)),f()}function O(t,e){var i,n;e&&void 0!==xe[e]||(e=e||"day",i=te.getViewsWithButtons().join(" "),n=i.match(RegExp("\\w+"+z(e))),n||(n=i.match(/\w+Day/)),e=n?n[0]:"agendaDay"),le=t,u(e)}function F(){return le.clone()}function I(){ie.css({width:"100%",height:ie.height(),overflow:"hidden"})}function Z(){ie.css({width:"",height:"",overflow:""})}function B(){return q}function j(){return re}function X(t,e){return void 0===e?K[t]:(("height"==t||"contentHeight"==t||"aspectRatio"==t)&&(K[t]=e,g(!0)),void 0)}function $(t,e){return K[t]?K[t].apply(e||de,Array.prototype.slice.call(arguments,2)):void 0}var q=this;n=n||{};var U,K=o({},De,n);U=K.lang in Te?Te[K.lang]:Te[De.lang],U&&(K=o({},De,U,n)),K.isRTL&&(K=o({},De,Ce,U||{},n)),q.options=K,q.render=s,q.destroy=d,q.refetchEvents=y,q.reportEvents=D,q.reportEventChange=T,q.rerenderEvents=w,q.changeView=u,q.select=k,q.unselect=M,q.prev=R,q.next=P,q.prevYear=G,q.nextYear=N,q.today=Y,q.gotoDate=A,q.incrementDate=_,q.zoomTo=O,q.getDate=F,q.getCalendar=B,q.getView=j,q.option=X,q.trigger=$;var Q=H(r(K.lang));if(K.monthNames&&(Q._months=K.monthNames),K.monthNamesShort&&(Q._monthsShort=K.monthNamesShort),K.dayNames&&(Q._weekdays=K.dayNames),K.dayNamesShort&&(Q._weekdaysShort=K.dayNamesShort),null!=K.firstDay){var J=H(Q._week);J.dow=K.firstDay,Q._week=J}q.defaultAllDayEventDuration=e.duration(K.defaultAllDayEventDuration),q.defaultTimedEventDuration=e.duration(K.defaultTimedEventDuration),q.moment=function(){var t;return"local"===K.timezone?(t=He.moment.apply(null,arguments),t.hasTime()&&t.local()):t="UTC"===K.timezone?He.moment.utc.apply(null,arguments):He.moment.parseZone.apply(null,arguments),"_locale"in t?t._locale=Q:t._lang=Q,t},q.getIsAmbigTimezone=function(){return"local"!==K.timezone&&"UTC"!==K.timezone},q.rezoneDate=function(t){return q.moment(t.toArray())},q.getNow=function(){var t=K.now;return"function"==typeof t&&(t=t()),q.moment(t)},q.calculateWeekNumber=function(t){var e=K.weekNumberCalculation;return"function"==typeof e?e(t):"local"===e?t.week():"ISO"===e.toUpperCase()?t.isoWeek():void 0},q.getEventEnd=function(t){return t.end?t.end.clone():q.getDefaultEventEnd(t.allDay,t.start)},q.getDefaultEventEnd=function(t,e){var i=e.clone();return t?i.stripTime().add(q.defaultAllDayEventDuration):i.add(q.defaultTimedEventDuration),q.getIsAmbigTimezone()&&i.stripZone(),i},q.formatRange=function(t,e,i){return"function"==typeof i&&(i=i.call(q,K,Q)),W(t,e,i,null,K.isRTL)},q.formatDate=function(t,e){return"function"==typeof e&&(e=e.call(q,K,Q)),V(t,e)},c.call(q,K);var te,ee,ie,ne,re,oe,se,le,ae=q.isFetchNeeded,ce=q.fetchEvents,de=i[0],he=0,ue=[];le=null!=K.defaultDate?q.moment(K.defaultDate):q.getNow(),q.getSuggestedViewHeight=function(){return void 0===oe&&p(),oe},q.isHeightAuto=function(){return"auto"===K.contentHeight||"auto"===K.height}}function a(e,i){function n(){var e=i.header;return f=i.theme?"ui":"fc",e?g=t("
").append(o("left")).append(o("right")).append(o("center")).append('
'):void 0}function r(){g.remove()}function o(n){var r=t('
'),o=i.header[n];return o&&t.each(o.split(" "),function(){var n,o=t(),s=!0;t.each(this.split(","),function(n,r){var l,a,c,d,h,u,g,m;"title"==r?(o=o.add(t("

 

")),s=!1):(e[r]?l=function(){e[r]()}:xe[r]&&(l=function(){e.changeView(r)},p.push(r)),l&&(a=S(i.themeButtonIcons,r),c=S(i.buttonIcons,r),d=S(i.defaultButtonText,r),h=S(i.buttonText,r),u=h?R(h):a&&i.theme?"":c&&!i.theme?"":R(d||r),g=["fc-"+r+"-button",f+"-button",f+"-state-default"],m=t('").click(function(){m.hasClass(f+"-state-disabled")||(l(),(m.hasClass(f+"-state-active")||m.hasClass(f+"-state-disabled"))&&m.removeClass(f+"-state-hover"))}).mousedown(function(){m.not("."+f+"-state-active").not("."+f+"-state-disabled").addClass(f+"-state-down")}).mouseup(function(){m.removeClass(f+"-state-down")}).hover(function(){m.not("."+f+"-state-active").not("."+f+"-state-disabled").addClass(f+"-state-hover")},function(){m.removeClass(f+"-state-hover").removeClass(f+"-state-down")}),o=o.add(m)))}),s&&o.first().addClass(f+"-corner-left").end().last().addClass(f+"-corner-right").end(),o.length>1?(n=t("
"),s&&n.addClass("fc-button-group"),n.append(o),r.append(n)):r.append(o)}),r}function s(t){g.find("h2").text(t)}function l(t){g.find(".fc-"+t+"-button").addClass(f+"-state-active")}function a(t){g.find(".fc-"+t+"-button").removeClass(f+"-state-active")}function c(t){g.find(".fc-"+t+"-button").attr("disabled","disabled").addClass(f+"-state-disabled")}function d(t){g.find(".fc-"+t+"-button").removeAttr("disabled").removeClass(f+"-state-disabled")}function h(){return p}var u=this;u.render=n,u.destroy=r,u.updateTitle=s,u.activateButton=l,u.deactivateButton=a,u.disableButton=c,u.enableButton=d,u.getViewsWithButtons=h;var f,g=t(),p=[]}function c(e){function i(t,e){return!T||t.clone().stripZone()C.clone().stripZone()}function n(t,e){T=t,C=e,A=[];var i=++G,n=L.length;N=n;for(var o=0;n>o;o++)r(L[o],i)}function r(e,i){o(e,function(n){var r,o,s=t.isArray(e.events);if(i==G){if(n)for(r=0;n.length>r;r++)o=n[r],s||(o=w(o,e)),o&&A.push(o);N--,N||R(A)}})}function o(i,n){var r,s,l=He.sourceFetchers;for(r=0;l.length>r;r++){if(s=l[r].call(S,i,T.clone(),C.clone(),e.timezone,n),s===!0)return;if("object"==typeof s)return o(s,n),void 0}var a=i.events;if(a)t.isFunction(a)?(v(),a.call(S,T.clone(),C.clone(),e.timezone,function(t){n(t),y()})):t.isArray(a)?n(a):n();else{var c=i.url;if(c){var d,h=i.success,u=i.error,f=i.complete;d=t.isFunction(i.data)?i.data():i.data;var g=t.extend({},d||{}),p=M(i.startParam,e.startParam),m=M(i.endParam,e.endParam),w=M(i.timezoneParam,e.timezoneParam);p&&(g[p]=T.format()),m&&(g[m]=C.format()),e.timezone&&"local"!=e.timezone&&(g[w]=e.timezone),v(),t.ajax(t.extend({},ke,i,{data:g,success:function(e){e=e||[];var i=k(h,this,arguments);t.isArray(i)&&(e=i),n(e)},error:function(){k(u,this,arguments),n()},complete:function(){k(f,this,arguments),y()}}))}else n()}}function s(t){var e=l(t);e&&(L.push(e),N++,r(e,G))}function l(e){var i,n,r=He.sourceNormalizers;if(t.isFunction(e)||t.isArray(e)?i={events:e}:"string"==typeof e?i={url:e}:"object"==typeof e&&(i=t.extend({},e)),i){for(i.className?"string"==typeof i.className&&(i.className=i.className.split(/\s+/)):i.className=[],t.isArray(i.events)&&(i.origArray=i.events,i.events=t.map(i.events,function(t){return w(t,i)})),n=0;r.length>n;n++)r[n].call(S,i);return i}}function a(e){L=t.grep(L,function(t){return!c(t,e)}),A=t.grep(A,function(t){return!c(t.source,e)}),R(A)}function c(t,e){return t&&e&&h(t)==h(e)}function h(t){return("object"==typeof t?t.origArray||t.url||t.events:null)||t}function u(t){t.start=S.moment(t.start),t.end&&(t.end=S.moment(t.end)),E(t),f(t),R(A)}function f(t){var e,i,n,r;for(e=0;A.length>e;e++)if(i=A[e],i._id==t._id&&i!==t)for(n=0;V.length>n;n++)r=V[n],void 0!==t[r]&&(i[r]=t[r])}function g(t,e){var i=w(t);i&&(i.source||(e&&(z.events.push(i),i.source=z),A.push(i)),R(A))}function p(e){var i,n;for(null==e?e=function(){return!0}:t.isFunction(e)||(i=e+"",e=function(t){return t._id==i}),A=t.grep(A,e,!0),n=0;L.length>n;n++)t.isArray(L[n].events)&&(L[n].events=t.grep(L[n].events,e,!0));R(A)}function m(e){return t.isFunction(e)?t.grep(A,e):null!=e?(e+="",t.grep(A,function(t){return t._id==e})):A}function v(){Y++||H("loading",null,!0,x())}function y(){--Y||H("loading",null,!1,x())}function w(i,n){var r,o,s,l,a={};return e.eventDataTransform&&(i=e.eventDataTransform(i)),n&&n.eventDataTransform&&(i=n.eventDataTransform(i)),r=S.moment(i.start||i.date),r.isValid()&&(o=null,!i.end||(o=S.moment(i.end),o.isValid()))?(s=i.allDay,void 0===s&&(l=M(n?n.allDayDefault:void 0,e.allDayDefault),s=void 0!==l?l:!(r.hasTime()||o&&o.hasTime())),s?(r.hasTime()&&r.stripTime(),o&&o.hasTime()&&o.stripTime()):(r.hasTime()||(r=S.rezoneDate(r)),o&&!o.hasTime()&&(o=S.rezoneDate(o))),t.extend(a,i),n&&(a.source=n),a._id=i._id||(void 0===i.id?"_fc"+Me++:i.id+""),a.className=i.className?"string"==typeof i.className?i.className.split(/\s+/):i.className:[],a.resources?"string"==typeof a.resources&&(a.resources=a.resources.split(/\s+/)):a.resources=[],a.allDay=s,a.start=r,a.end=o,e.forceEventDuration&&!a.end&&(a.end=P(a)),d(a),a):void 0}function E(t,e,i){var n,r,o,s,l=t._allDay,a=t._start,c=t._end,d=!1;return e||i||(e=t.start,i=t.end),n=t.allDay!=l?t.allDay:!(e||i).hasTime(),n&&(e&&(e=e.clone().stripTime()),i&&(i=i.clone().stripTime())),e&&(r=n?D(e,a.clone().stripTime()):D(e,a)),n!=l?d=!0:i&&(o=D(i||S.getDefaultEventEnd(n,e||a),e||a).subtract(D(c||S.getDefaultEventEnd(l,a),a))),s=b(m(t._id),d,n,r,o),{dateDelta:r,durationDelta:o,undo:s}}function b(i,n,r,o,s){var l=S.getIsAmbigTimezone(),a=[];return t.each(i,function(t,i){var c=i._allDay,h=i._start,u=i._end,f=null!=r?r:c,g=h.clone(),p=!n&&u?u.clone():null;f?(g.stripTime(),p&&p.stripTime()):(g.hasTime()||(g=S.rezoneDate(g)),p&&!p.hasTime()&&(p=S.rezoneDate(p))),p||!e.forceEventDuration&&!+s||(p=S.getDefaultEventEnd(f,g)),g.add(o),p&&p.add(o).add(s),l&&(+o||+s)&&(g.stripZone(),p&&p.stripZone()),i.allDay=f,i.start=g,i.end=p,d(i),a.push(function(){i.allDay=c,i.start=h,i.end=u,d(i)})}),function(){for(var t=0;a.length>t;t++)a[t]()}}var S=this;S.isFetchNeeded=i,S.fetchEvents=n,S.addEventSource=s,S.removeEventSource=a,S.updateEvent=u,S.renderEvent=g,S.removeEvents=p,S.clientEvents=m,S.mutateEvent=E;var T,C,H=S.trigger,x=S.getView,R=S.reportEvents,P=S.getEventEnd,z={events:[]},L=[z],G=0,N=0,Y=0,A=[];t.each((e.events?[e.events]:[]).concat(e.eventSources||[]),function(t,e){var i=l(e);i&&L.push(i)});var V=["title","url","allDay","className","editable","color","backgroundColor","borderColor","textColor"]}function d(t){t._allDay=t.allDay,t._start=t.start.clone(),t._end=t.end?t.end.clone():null}function h(t,e){e.left&&t.css({"border-left-width":1,"margin-left":e.left-1}),e.right&&t.css({"border-right-width":1,"margin-right":e.right-1})}function u(t){t.css({"margin-left":"","margin-right":"","border-left-width":"","border-right-width":""})}function f(e,i,n){var r=Math.floor(i/e.length),o=Math.floor(i-r*(e.length-1)),s=[],l=[],a=[],c=0;g(e),e.each(function(i,n){var d=i===e.length-1?o:r,h=t(n).outerHeight(!0);d>h?(s.push(n),l.push(h),a.push(t(n).height())):c+=h}),n&&(i-=c,r=Math.floor(i/s.length),o=Math.floor(i-r*(s.length-1))),t(s).each(function(e,i){var n=e===s.length-1?o:r,c=l[e],d=a[e],h=n-(c-d);n>c&&t(i).height(h)})}function g(t){t.height("")}function p(e){var i=0;return e.find("> *").each(function(e,n){var r=t(n).outerWidth();r>i&&(i=r)}),i++,e.width(i),i}function m(t,e){return t.height(e).addClass("fc-scroller"),t[0].scrollHeight-1>t[0].clientHeight?!0:(v(t),!1)}function v(t){t.height("").removeClass("fc-scroller")}function y(e){var i=e.css("position"),n=e.parents().filter(function(){var e=t(this);return/(auto|scroll)/.test(e.css("overflow")+e.css("overflow-y")+e.css("overflow-x"))}).eq(0);return"fixed"!==i&&n.length?n:t(e[0].ownerDocument||document)}function w(t){var e=t.offset().left,i=e+t.width(),n=t.children(),r=n.offset().left,o=r+n.outerWidth();return{left:r-e,right:i-o}}function E(t){return 1==t.which&&!t.ctrlKey}function b(t,e,i,n){var r,o,s,l;return e>i&&n>t?(t>=i?(r=t.clone(),s=!0):(r=i.clone(),s=!1),n>=e?(o=e.clone(),l=!0):(o=n.clone(),l=!1),{start:r,end:o,isStart:s,isEnd:l}):void 0}function S(t,e){if(t=t||{},void 0!==t[e])return t[e];for(var i,n=e.split(/(?=[A-Z])/),r=n.length-1;r>=0;r--)if(i=t[n[r].toLowerCase()],void 0!==i)return i;return t["default"]}function D(t,i){return e.duration({days:t.clone().stripTime().diff(i.clone().stripTime(),"days"),ms:t.time()-i.time()})}function T(t){return"[object Date]"===Object.prototype.toString.call(t)||t instanceof Date}function C(t,e){return t-e}function H(t){var e=function(){};return e.prototype=t,new e}function x(t,e){for(var i in e)e.hasOwnProperty(i)&&(t[i]=e[i])}function k(e,i,n){if(t.isFunction(e)&&(e=[e]),e){var r,o;for(r=0;e.length>r;r++)o=e[r].apply(i,n)||o;return o}}function M(){for(var t=0;arguments.length>t;t++)if(void 0!==arguments[t])return arguments[t]}function R(t){return(t+"").replace(/&/g,"&").replace(//g,">").replace(/'/g,"'").replace(/"/g,""").replace(/\n/g,"
")}function P(t){return t.replace(/&.*?;/g,"")}function z(t){return t.charAt(0).toUpperCase()+t.slice(1)}function L(t,e){var i,n,r,o,s=function(){var l=+new Date-o;e>l&&l>0?i=setTimeout(s,e-l):(i=null,t.apply(r,n),i||(r=n=null))};return function(){r=this,n=arguments,o=+new Date,i||(i=setTimeout(s,e))}}function G(i,n,r){var o,s,l,a,c=i[0],d=1==i.length&&"string"==typeof c;return e.isMoment(c)?(a=e.apply(null,i),c._ambigTime&&(a._ambigTime=!0),c._ambigZone&&(a._ambigZone=!0)):T(c)||void 0===c?a=e.apply(null,i):(o=!1,s=!1,d?Pe.test(c)?(c+="-01",i=[c],o=!0,s=!0):(l=ze.exec(c))&&(o=!l[5],s=!0):t.isArray(c)&&(s=!0),a=n?e.utc.apply(e,i):e.apply(null,i),o?(a._ambigTime=!0,a._ambigZone=!0):r&&(s?a._ambigZone=!0:d&&a.zone(c))),new N(a)}function N(t){x(this,t)}function Y(t,e){var i,n=[],r=!1,o=!1;for(i=0;t.length>i;i++)n.push(He.moment.parseZone(t[i])),r=r||n[i]._ambigTime,o=o||n[i]._ambigZone;for(i=0;n.length>i;i++)r&&!e?n[i].stripTime():o&&n[i].stripZone();return n}function A(t,i){return e.fn.format.call(t,i)}function V(t,e){return _(t,Z(e))}function _(t,e){var i,n="";for(i=0;e.length>i;i++)n+=O(t,e[i]);return n}function O(t,e){var i,n;return"string"==typeof e?e:(i=e.token)?Le[i]?Le[i](t):A(t,i):e.maybe&&(n=_(t,e.maybe),n.match(/[1-9]/))?n:""}function W(t,e,i,n,r){var o;return t=He.moment.parseZone(t),e=He.moment.parseZone(e),o=(t.localeData||t.lang).call(t),i=o.longDateFormat(i)||i,n=n||" - ",F(t,e,Z(i),n,r)}function F(t,e,i,n,r){var o,s,l,a,c="",d="",h="",u="",f="";for(s=0;i.length>s&&(o=I(t,e,i[s]),o!==!1);s++)c+=o;for(l=i.length-1;l>s&&(o=I(t,e,i[l]),o!==!1);l--)d=o+d;for(a=s;l>=a;a++)h+=O(t,i[a]),u+=O(e,i[a]);return(h||u)&&(f=r?u+n+h:h+n+u),c+f+d}function I(t,e,i){var n,r;return"string"==typeof i?i:(n=i.token)&&(r=Ge[n.charAt(0)],r&&t.isSame(e,r))?A(t,n):!1}function Z(t){return t in Ne?Ne[t]:Ne[t]=B(t)}function B(t){for(var e,i=[],n=/\[([^\]]*)\]|\(([^\)]*)\)|(LT|(\w)\4*o?)|([^\w\[\(]+)/g;e=n.exec(t);)e[1]?i.push(e[1]):e[2]?i.push({maybe:B(e[2])}):e[3]?i.push({token:e[3]}):e[5]&&i.push(e[5]);return i}function j(t){this.options=t||{}}function X(t){this.grid=t}function $(t){this.coordMaps=t}function q(t,e){this.coordMap=t,this.options=e||{}}function U(t,e){return t||e?t&&e?t.grid===e.grid&&t.row===e.row&&t.col===e.col:!1:!0}function K(e,i){this.options=i=i||{},this.sourceEl=e,this.parentEl=i.parentEl?t(i.parentEl):e.parent()}function Q(t){this.view=t}function J(t){Q.call(this,t),this.coordMap=new X(this)}function te(t,e){return t.eventStartMS-e.eventStartMS||e.eventDurationMS-t.eventDurationMS||e.event.allDay-t.event.allDay||(t.event.title||"").localeCompare(e.event.title)}function ee(t){J.call(this,t)}function ie(t,e){var i,n;for(i=0;e.length>i;i++)if(n=e[i],n.leftCol<=t.rightCol&&n.rightCol>=t.leftCol)return!0;return!1}function ne(t,e){return t.leftCol-e.leftCol}function re(t){J.call(this,t)}function oe(t){var e,i,n;if(t.sort(te),e=se(t),le(e),i=e[0]){for(n=0;i.length>n;n++)ae(i[n]);for(n=0;i.length>n;n++)ce(i[n],0,0)}}function se(t){var e,i,n,r=[];for(e=0;t.length>e;e++){for(i=t[e],n=0;r.length>n&&de(i,r[n]).length;n++);i.level=n,(r[n]||(r[n]=[])).push(i)}return r}function le(t){var e,i,n,r,o;for(e=0;t.length>e;e++)for(i=t[e],n=0;i.length>n;n++)for(r=i[n],r.forwardSegs=[],o=e+1;t.length>o;o++)de(r,t[o],r.forwardSegs)}function ae(t){var e,i,n=t.forwardSegs,r=0;if(void 0===t.forwardPressure){for(e=0;n.length>e;e++)i=n[e],ae(i),r=Math.max(r,1+i.forwardPressure);t.forwardPressure=r}}function ce(t,e,i){var n,r=t.forwardSegs;if(void 0===t.forwardCoord)for(r.length?(r.sort(ue),ce(r[0],e+1,i),t.forwardCoord=r[0].backwardCoord):t.forwardCoord=1,t.backwardCoord=t.forwardCoord-(t.forwardCoord-i)/(e+1),n=0;r.length>n;n++)ce(r[n],0,t.forwardCoord)}function de(t,e,i){i=i||[];for(var n=0;e.length>n;n++)he(t,e[n])&&i.push(e[n]);return i}function he(t,e){return t.bottom>e.top&&t.topd;d++){var h=d*n,u=h+n-1,f=Math.max(a,h),g=Math.min(c,u);if(g>=f){var m=E(f),v=E(g),b=[m.col,v.col].sort(),S=p(f)==s,T=p(g)+1==l;r.push({row:d,leftCol:b[0],rightCol:b[1],isStart:S,isEnd:T})}}return r}function D(t,e){var i,n,r=t.clone().stripTime();return e&&(i=e.clone().stripTime(),n=+e.time(),n&&n>=k&&i.add(1,"days")),(!e||r>=i)&&(i=r.clone().add(1,"days")),{start:r,end:i}}function T(t){var e=D(t.start,t.end);return e.end.diff(e.start,"days")>1}var C=this;C.calendar=i,C.opt=n,C.trigger=r,C.isEventDraggable=o,C.isEventResizable=l,C.eventDrop=a,C.eventResize=c;var H=i.reportEventChange,x=i.options,k=e.duration(x.nextDayThreshold);C.init(),C.getEventTimeText=function(t,e){var r,o;return"object"==typeof t&&"object"==typeof e?(r=t,o=e,e=arguments[2]):(r=t.start,o=t.end),e=e||n("timeFormat"),o&&n("displayEventEnd")?i.formatRange(r,o,e):i.formatDate(r,e)},C.isHiddenDay=d,C.skipHiddenDays=u,C.getCellsPerWeek=h,C.dateToCell=v,C.dateToDayOffset=y,C.dayOffsetToCellOffset=w,C.cellOffsetToCell=E,C.cellToDate=f,C.cellToCellOffset=g,C.cellOffsetToDayOffset=p,C.dayOffsetToDate=m,C.rangeToSegments=b,C.isMultiDayEvent=T;var R,P=n("hiddenDays")||[],z=[],L=[],G=[],N=n("isRTL");(function(){n("weekends")===!1&&P.push(0,6);for(var e=0,i=0;7>e;e++)L[e]=i,z[e]=-1!=t.inArray(e,P),z[e]||(G[i]=e,i++);if(R=i,!R)throw"invalid hiddenDays"})()}function ge(t){fe.call(this,t),this.dayGrid=new ee(this),this.coordMap=this.dayGrid.coordMap}function pe(t){ge.call(this,t)}function me(t){ge.call(this,t)}function ve(t){ge.call(this,t)}function ye(t,e){return e.longDateFormat("LT").replace(":mm","(:mm)").replace(/(\Wmm)$/,"($1)").replace(/\s*a$/i,"a")}function we(t,e){return e.longDateFormat("LT").replace(/\s*a$/i,"")}function Ee(t){fe.call(this,t),this.timeGrid=new re(this),this.opt("allDaySlot")?(this.dayGrid=new ee(this),this.coordMap=new $([this.dayGrid.coordMap,this.timeGrid.coordMap])):this.coordMap=this.timeGrid.coordMap}function be(t){Ee.call(this,t)}function Se(t){Ee.call(this,t)}var De={lang:"en",defaultTimedEventDuration:"02:00:00",defaultAllDayEventDuration:{days:1},forceEventDuration:!1,nextDayThreshold:"09:00:00",defaultView:"month",aspectRatio:1.35,header:{left:"title",center:"",right:"today prev,next"},weekends:!0,weekNumbers:!1,weekNumberTitle:"W",weekNumberCalculation:"local",lazyFetching:!0,startParam:"start",endParam:"end",timezoneParam:"timezone",timezone:!1,titleFormat:{month:"MMMM YYYY",week:"ll",day:"LL"},columnFormat:{month:"ddd",week:n,day:"dddd"},timeFormat:{"default":i},displayEventEnd:{month:!1,basicWeek:!1,"default":!0},isRTL:!1,defaultButtonText:{prev:"prev",next:"next",prevYear:"prev year",nextYear:"next year",today:"today",month:"month",week:"week",day:"day"},buttonIcons:{prev:"left-single-arrow",next:"right-single-arrow",prevYear:"left-double-arrow",nextYear:"right-double-arrow"},theme:!1,themeButtonIcons:{prev:"circle-triangle-w",next:"circle-triangle-e",prevYear:"seek-prev",nextYear:"seek-next"},dragOpacity:.75,dragRevertDuration:500,dragScroll:!0,unselectAuto:!0,dropAccept:"*",eventLimit:!1,eventLimitText:"more",eventLimitClick:"popover",dayPopoverFormat:"LL",handleWindowResize:!0,windowResizeDelay:200},Te={en:{columnFormat:{week:"ddd M/D"},dayPopoverFormat:"dddd, MMMM D"}},Ce={header:{left:"next,prev today",center:"",right:"title"},buttonIcons:{prev:"right-single-arrow",next:"left-single-arrow",prevYear:"right-double-arrow",nextYear:"left-double-arrow"},themeButtonIcons:{prev:"circle-triangle-e",next:"circle-triangle-w",nextYear:"seek-prev",prevYear:"seek-next"}},He=t.fullCalendar={version:"2.1.1"},xe=He.views={};t.fn.fullCalendar=function(e){var i=Array.prototype.slice.call(arguments,1),n=this;return this.each(function(r,o){var s,a=t(o),c=a.data("fullCalendar");"string"==typeof e?c&&t.isFunction(c[e])&&(s=c[e].apply(c,i),r||(n=s),"destroy"===e&&a.removeData("fullCalendar")):c||(c=new l(a,e),a.data("fullCalendar",c),c.render())}),n},He.langs=Te,He.datepickerLang=function(e,i,n){var r=Te[e];r||(r=Te[e]={}),o(r,{isRTL:n.isRTL,weekNumberTitle:n.weekHeader,titleFormat:{month:n.showMonthAfterYear?"YYYY["+n.yearSuffix+"] MMMM":"MMMM YYYY["+n.yearSuffix+"]"},defaultButtonText:{prev:P(n.prevText),next:P(n.nextText),today:P(n.currentText)}}),t.datepicker&&(t.datepicker.regional[i]=t.datepicker.regional[e]=n,t.datepicker.regional.en=t.datepicker.regional[""],t.datepicker.setDefaults(n))},He.lang=function(t,e){var i;e&&(i=Te[t],i||(i=Te[t]={}),o(i,e||{})),De.lang=t},He.sourceNormalizers=[],He.sourceFetchers=[];var ke={dataType:"json",cache:!1},Me=1,Re=["sun","mon","tue","wed","thu","fri","sat"];He.applyAll=k;var Pe=/^\s*\d{4}-\d\d$/,ze=/^\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+)?)?)?)?)?$/;He.moment=function(){return G(arguments)},He.moment.utc=function(){var t=G(arguments,!0);return t.hasTime()&&t.utc(),t},He.moment.parseZone=function(){return G(arguments,!0,!0)},N.prototype=H(e.fn),N.prototype.clone=function(){return G([this])},N.prototype.time=function(t){if(null==t)return e.duration({hours:this.hours(),minutes:this.minutes(),seconds:this.seconds(),milliseconds:this.milliseconds()});delete this._ambigTime,e.isDuration(t)||e.isMoment(t)||(t=e.duration(t));var i=0;return e.isDuration(t)&&(i=24*Math.floor(t.asDays())),this.hours(i+t.hours()).minutes(t.minutes()).seconds(t.seconds()).milliseconds(t.milliseconds())},N.prototype.stripTime=function(){var t=this.toArray();return e.fn.utc.call(this),this.year(t[0]).month(t[1]).date(t[2]).hours(0).minutes(0).seconds(0).milliseconds(0),this._ambigTime=!0,this._ambigZone=!0,this},N.prototype.hasTime=function(){return!this._ambigTime},N.prototype.stripZone=function(){var t=this.toArray(),i=this._ambigTime;return e.fn.utc.call(this),this.year(t[0]).month(t[1]).date(t[2]).hours(t[3]).minutes(t[4]).seconds(t[5]).milliseconds(t[6]),i&&(this._ambigTime=!0),this._ambigZone=!0,this},N.prototype.hasZone=function(){return!this._ambigZone},N.prototype.zone=function(t){return null!=t&&(delete this._ambigTime,delete this._ambigZone),e.fn.zone.apply(this,arguments)},N.prototype.local=function(){var t=this.toArray(),i=this._ambigZone;return delete this._ambigTime,delete this._ambigZone,e.fn.local.apply(this,arguments),i&&this.year(t[0]).month(t[1]).date(t[2]).hours(t[3]).minutes(t[4]).seconds(t[5]).milliseconds(t[6]),this},N.prototype.utc=function(){return delete this._ambigTime,delete this._ambigZone,e.fn.utc.apply(this,arguments)},N.prototype.format=function(){return arguments[0]?V(this,arguments[0]):this._ambigTime?A(this,"YYYY-MM-DD"):this._ambigZone?A(this,"YYYY-MM-DD[T]HH:mm:ss"):A(this)},N.prototype.toISOString=function(){return this._ambigTime?A(this,"YYYY-MM-DD"):this._ambigZone?A(this,"YYYY-MM-DD[T]HH:mm:ss"):e.fn.toISOString.apply(this,arguments)},N.prototype.isWithin=function(t,e){var i=Y([this,t,e]);return i[0]>=i[1]&&i[0]').addClass(i.className||"").css({top:0,left:0}).append(i.content).appendTo(i.parentEl),this.el.on("click",".fc-close",function(){e.hide()}),i.autoHide&&t(document).on("mousedown",this.documentMousedownProxy=t.proxy(this,"documentMousedown"))},documentMousedown:function(e){this.el&&!t(e.target).closest(this.el).length&&this.hide()},destroy:function(){this.hide(),this.el&&(this.el.remove(),this.el=null),t(document).off("mousedown",this.documentMousedownProxy)},position:function(){var e,i,n,r,o,s=this.options,l=this.el.offsetParent().offset(),a=this.el.outerWidth(),c=this.el.outerHeight(),d=t(window),h=y(this.el);r=s.top||0,o=void 0!==s.left?s.left:void 0!==s.right?s.right-a:0,h.is(window)||h.is(document)?(h=d,e=0,i=0):(n=h.offset(),e=n.top,i=n.left),e+=d.scrollTop(),i+=d.scrollLeft(),s.viewportConstrain!==!1&&(r=Math.min(r,e+h.outerHeight()-c-this.margin),r=Math.max(r,e+this.margin),o=Math.min(o,i+h.outerWidth()-a-this.margin),o=Math.max(o,i+this.margin)),this.el.css({top:r-l.top,left:o-l.left})},trigger:function(t){this.options[t]&&this.options[t].apply(this,Array.prototype.slice.call(arguments,1))}},X.prototype={grid:null,rows:null,cols:null,containerEl:null,minX:null,maxX:null,minY:null,maxY:null,build:function(){this.grid.buildCoords(this.rows=[],this.cols=[]),this.computeBounds()},getCell:function(t,e){var i,n=null,r=this.rows,o=this.cols,s=-1,l=-1;if(this.inBounds(t,e)){for(i=0;r.length>i;i++)if(e>=r[i][0]&&r[i][1]>e){s=i;break}for(i=0;o.length>i;i++)if(t>=o[i][0]&&o[i][1]>t){l=i;break}s>=0&&l>=0&&(n={row:s,col:l},n.grid=this.grid,n.date=this.grid.getCellDate(n))}return n},computeBounds:function(){var t;this.containerEl&&(t=this.containerEl.offset(),this.minX=t.left,this.maxX=t.left+this.containerEl.outerWidth(),this.minY=t.top,this.maxY=t.top+this.containerEl.outerHeight())},inBounds:function(t,e){return this.containerEl?t>=this.minX&&this.maxX>t&&e>=this.minY&&this.maxY>e:!0}},$.prototype={coordMaps:null,build:function(){var t,e=this.coordMaps;for(t=0;e.length>t;t++)e[t].build()},getCell:function(t,e){var i,n=this.coordMaps,r=null;for(i=0;n.length>i&&!r;i++)r=n[i].getCell(t,e);return r}},q.prototype={coordMap:null,options:null,isListening:!1,isDragging:!1,origCell:null,origDate:null,cell:null,date:null,mouseX0:null,mouseY0:null,mousemoveProxy:null,mouseupProxy:null,scrollEl:null,scrollBounds:null,scrollTopVel:null,scrollLeftVel:null,scrollIntervalId:null,scrollHandlerProxy:null,scrollSensitivity:30,scrollSpeed:200,scrollIntervalMs:50,mousedown:function(t){E(t)&&(t.preventDefault(),this.startListening(t),this.options.distance||this.startDrag(t))},startListening:function(e){var i,n;this.isListening||(e&&this.options.scroll&&(i=y(t(e.target)),i.is(window)||i.is(document)||(this.scrollEl=i,this.scrollHandlerProxy=L(t.proxy(this,"scrollHandler"),100),this.scrollEl.on("scroll",this.scrollHandlerProxy))),this.computeCoords(),e&&(n=this.getCell(e),this.origCell=n,this.origDate=n?n.date:null,this.mouseX0=e.pageX,this.mouseY0=e.pageY),t(document).on("mousemove",this.mousemoveProxy=t.proxy(this,"mousemove")).on("mouseup",this.mouseupProxy=t.proxy(this,"mouseup")).on("selectstart",this.preventDefault),this.isListening=!0,this.trigger("listenStart",e))},computeCoords:function(){this.coordMap.build(),this.computeScrollBounds()},mousemove:function(t){var e,i;this.isDragging||(e=this.options.distance||1,i=Math.pow(t.pageX-this.mouseX0,2)+Math.pow(t.pageY-this.mouseY0,2),i>=e*e&&this.startDrag(t)),this.isDragging&&this.drag(t) -},startDrag:function(t){var e;this.isListening||this.startListening(),this.isDragging||(this.isDragging=!0,this.trigger("dragStart",t),e=this.getCell(t),e&&this.cellOver(e,!0))},drag:function(t){var e;this.isDragging&&(e=this.getCell(t),U(e,this.cell)||(this.cell&&this.cellOut(),e&&this.cellOver(e)),this.dragScroll(t))},cellOver:function(t){this.cell=t,this.date=t.date,this.trigger("cellOver",t,t.date)},cellOut:function(){this.cell&&(this.trigger("cellOut",this.cell),this.cell=null,this.date=null)},mouseup:function(t){this.stopDrag(t),this.stopListening(t)},stopDrag:function(t){this.isDragging&&(this.stopScrolling(),this.trigger("dragStop",t),this.isDragging=!1)},stopListening:function(e){this.isListening&&(this.scrollEl&&(this.scrollEl.off("scroll",this.scrollHandlerProxy),this.scrollHandlerProxy=null),t(document).off("mousemove",this.mousemoveProxy).off("mouseup",this.mouseupProxy).off("selectstart",this.preventDefault),this.mousemoveProxy=null,this.mouseupProxy=null,this.isListening=!1,this.trigger("listenStop",e),this.origCell=this.cell=null,this.origDate=this.date=null)},getCell:function(t){return this.coordMap.getCell(t.pageX,t.pageY)},trigger:function(t){this.options[t]&&this.options[t].apply(this,Array.prototype.slice.call(arguments,1))},preventDefault:function(t){t.preventDefault()},computeScrollBounds:function(){var t,e=this.scrollEl;e&&(t=e.offset(),this.scrollBounds={top:t.top,left:t.left,bottom:t.top+e.outerHeight(),right:t.left+e.outerWidth()})},dragScroll:function(t){var e,i,n,r,o=this.scrollSensitivity,s=this.scrollBounds,l=0,a=0;s&&(e=(o-(t.pageY-s.top))/o,i=(o-(s.bottom-t.pageY))/o,n=(o-(t.pageX-s.left))/o,r=(o-(s.right-t.pageX))/o,e>=0&&1>=e?l=-1*e*this.scrollSpeed:i>=0&&1>=i&&(l=i*this.scrollSpeed),n>=0&&1>=n?a=-1*n*this.scrollSpeed:r>=0&&1>=r&&(a=r*this.scrollSpeed)),this.setScrollVel(l,a)},setScrollVel:function(e,i){this.scrollTopVel=e,this.scrollLeftVel=i,this.constrainScrollVel(),!this.scrollTopVel&&!this.scrollLeftVel||this.scrollIntervalId||(this.scrollIntervalId=setInterval(t.proxy(this,"scrollIntervalFunc"),this.scrollIntervalMs))},constrainScrollVel:function(){var t=this.scrollEl;0>this.scrollTopVel?0>=t.scrollTop()&&(this.scrollTopVel=0):this.scrollTopVel>0&&t.scrollTop()+t[0].clientHeight>=t[0].scrollHeight&&(this.scrollTopVel=0),0>this.scrollLeftVel?0>=t.scrollLeft()&&(this.scrollLeftVel=0):this.scrollLeftVel>0&&t.scrollLeft()+t[0].clientWidth>=t[0].scrollWidth&&(this.scrollLeftVel=0)},scrollIntervalFunc:function(){var t=this.scrollEl,e=this.scrollIntervalMs/1e3;this.scrollTopVel&&t.scrollTop(t.scrollTop()+this.scrollTopVel*e),this.scrollLeftVel&&t.scrollLeft(t.scrollLeft()+this.scrollLeftVel*e),this.constrainScrollVel(),this.scrollTopVel||this.scrollLeftVel||this.stopScrolling()},stopScrolling:function(){this.scrollIntervalId&&(clearInterval(this.scrollIntervalId),this.scrollIntervalId=null,this.computeCoords())},scrollHandler:function(){this.scrollIntervalId||this.computeCoords()}},K.prototype={options:null,sourceEl:null,el:null,parentEl:null,top0:null,left0:null,mouseY0:null,mouseX0:null,topDelta:null,leftDelta:null,mousemoveProxy:null,isFollowing:!1,isHidden:!1,isAnimating:!1,start:function(e){this.isFollowing||(this.isFollowing=!0,this.mouseY0=e.pageY,this.mouseX0=e.pageX,this.topDelta=0,this.leftDelta=0,this.isHidden||this.updatePosition(),t(document).on("mousemove",this.mousemoveProxy=t.proxy(this,"mousemove")))},stop:function(e,i){function n(){this.isAnimating=!1,r.destroyEl(),this.top0=this.left0=null,i&&i()}var r=this,o=this.options.revertDuration;this.isFollowing&&!this.isAnimating&&(this.isFollowing=!1,t(document).off("mousemove",this.mousemoveProxy),e&&o&&!this.isHidden?(this.isAnimating=!0,this.el.animate({top:this.top0,left:this.left0},{duration:o,complete:n})):n())},getEl:function(){var t=this.el;return t||(this.sourceEl.width(),t=this.el=this.sourceEl.clone().css({position:"absolute",visibility:"",display:this.isHidden?"none":"",margin:0,right:"auto",bottom:"auto",width:this.sourceEl.width(),height:this.sourceEl.height(),opacity:this.options.opacity||"",zIndex:this.options.zIndex}).appendTo(this.parentEl)),t},destroyEl:function(){this.el&&(this.el.remove(),this.el=null)},updatePosition:function(){var t,e;this.getEl(),null===this.top0&&(this.sourceEl.width(),t=this.sourceEl.offset(),e=this.el.offsetParent().offset(),this.top0=t.top-e.top,this.left0=t.left-e.left),this.el.css({top:this.top0+this.topDelta,left:this.left0+this.leftDelta})},mousemove:function(t){this.topDelta=t.pageY-this.mouseY0,this.leftDelta=t.pageX-this.mouseX0,this.isHidden||this.updatePosition()},hide:function(){this.isHidden||(this.isHidden=!0,this.el&&this.el.hide())},show:function(){this.isHidden&&(this.isHidden=!1,this.updatePosition(),this.getEl().show())}},Q.prototype={view:null,cellHtml:"",rowHtml:function(t,e){var i,n,r=this.view,o=this.getHtmlRenderer("cell",t),s="";for(e=e||0,i=0;r.colCnt>i;i++)n=r.cellToDate(e,i),s+=o(e,i,n);return s=this.bookendCells(s,t,e),""+s+""},bookendCells:function(t,e,i){var n=this.view,r=this.getHtmlRenderer("intro",e)(i||0),o=this.getHtmlRenderer("outro",e)(i||0),s=n.opt("isRTL"),l=s?o:r,a=s?r:o;return"string"==typeof t?l+t+a:t.prepend(l).append(a)},getHtmlRenderer:function(t,e){var i,n,r,o,s=this.view;return i=t+"Html",e&&(n=e+z(t)+"Html"),n&&(o=s[n])?r=s:n&&(o=this[n])?r=this:(o=s[i])?r=s:(o=this[i])&&(r=this),"function"==typeof o?function(){return o.apply(r,arguments)||""}:function(){return o||""}}},J.prototype=H(Q.prototype),t.extend(J.prototype,{el:null,coordMap:null,cellDuration:null,render:function(){this.bindHandlers()},destroy:function(){},buildCoords:function(){},getCellDate:function(){},getCellDayEl:function(){},rangeToSegs:function(){},bindHandlers:function(){var e=this;this.el.on("mousedown",function(i){t(i.target).is(".fc-event-container *, .fc-more")||t(i.target).closest(".fc-popover").length||e.dayMousedown(i)}),this.bindSegHandlers()},dayMousedown:function(t){var e,i,n,r=this,o=this.view,s=o.opt("selectable"),l=null,a=new q(this.coordMap,{scroll:o.opt("dragScroll"),dragStart:function(){o.unselect()},cellOver:function(t,o){a.origDate&&(n=r.getCellDayEl(t),l=[o,a.origDate].sort(C),e=l[0],i=l[1].clone().add(r.cellDuration),s&&r.renderSelection(e,i))},cellOut:function(){l=null,r.destroySelection()},listenStop:function(t){l&&(l[0].isSame(l[1])&&o.trigger("dayClick",n[0],e,t),s&&o.reportSelection(e,i,t))}});a.mousedown(t)},renderDrag:function(){},destroyDrag:function(){},renderResize:function(){},destroyResize:function(){},renderRangeHelper:function(t,e,i){var n,r=this.view;!e&&r.opt("forceEventDuration")&&(e=r.calendar.getDefaultEventEnd(!t.hasTime(),t)),n=i?H(i.event):{},n.start=t,n.end=e,n.allDay=!(t.hasTime()||e&&e.hasTime()),n.className=(n.className||[]).concat("fc-helper"),i||(n.editable=!1),this.renderHelper(n,i)},renderHelper:function(){},destroyHelper:function(){},renderSelection:function(t,e){this.renderHighlight(t,e)},destroySelection:function(){this.destroyHighlight()},renderHighlight:function(){},destroyHighlight:function(){},headHtml:function(){return'
'+""+""+this.rowHtml("head")+""+"
"+"
"},headCellHtml:function(t,e,i){var n=this.view,r=n.calendar,o=n.opt("columnFormat");return''+R(r.formatDate(i,o))+""},bgCellHtml:function(t,e,i){var n=this.view,r=this.getDayClasses(i);return r.unshift("fc-day",n.widgetContentClass),''},getDayClasses:function(t){var e=this.view,i=e.calendar.getNow().stripTime(),n=["fc-"+Re[t.day()]];return"month"===e.name&&t.month()!=e.intervalStart.month()&&n.push("fc-other-month"),t.isSame(i,"day")?n.push("fc-today",e.highlightStateClass):i>t?n.push("fc-past"):n.push("fc-future"),n}}),t.extend(J.prototype,{mousedOverSeg:null,isDraggingSeg:!1,isResizingSeg:!1,renderEvents:function(){},getSegs:function(){},destroyEvents:function(){this.triggerSegMouseout()},renderSegs:function(e,i){var n,r=this.view,o="",s=[];for(n=0;e.length>n;n++)o+=this.renderSegHtml(e[n],i);return t(o).each(function(i,n){var o=e[i],l=r.resolveEventEl(o.event,t(n));l&&(l.data("fc-seg",o),o.el=l,s.push(o))}),s},renderSegHtml:function(){},eventsToSegs:function(e,i,n){var r=this;return t.map(e,function(t){return r.eventToSegs(t,i,n)})},eventToSegs:function(t,e,i){var n,r,o,s=t.start.clone().stripZone(),l=this.view.calendar.getEventEnd(t).stripZone();for(e&&i?(o=b(s,l,e,i),n=o?[o]:[]):n=this.rangeToSegs(s,l),r=0;n.length>r;r++)o=n[r],o.event=t,o.eventStartMS=+s,o.eventDurationMS=l-s;return n},bindSegHandlers:function(){var e=this,i=this.view;t.each({mouseenter:function(t,i){e.triggerSegMouseover(t,i)},mouseleave:function(t,i){e.triggerSegMouseout(t,i)},click:function(t,e){return i.trigger("eventClick",this,t.event,e)},mousedown:function(n,r){t(r.target).is(".fc-resizer")&&i.isEventResizable(n.event)?e.segResizeMousedown(n,r):i.isEventDraggable(n.event)&&e.segDragMousedown(n,r)}},function(i,n){e.el.on(i,".fc-event-container > *",function(i){var r=t(this).data("fc-seg");return!r||e.isDraggingSeg||e.isResizingSeg?void 0:n.call(this,r,i)})})},triggerSegMouseover:function(t,e){this.mousedOverSeg||(this.mousedOverSeg=t,this.view.trigger("eventMouseover",t.el[0],t.event,e))},triggerSegMouseout:function(t,e){e=e||{},this.mousedOverSeg&&(t=t||this.mousedOverSeg,this.mousedOverSeg=null,this.view.trigger("eventMouseout",t.el[0],t.event,e))},segDragMousedown:function(t,e){var i,n,r=this,o=this.view,s=t.el,l=t.event,a=new K(t.el,{parentEl:o.el,opacity:o.opt("dragOpacity"),revertDuration:o.opt("dragRevertDuration"),zIndex:2}),c=new q(o.coordMap,{distance:5,scroll:o.opt("dragScroll"),listenStart:function(t){a.hide(),a.start(t)},dragStart:function(e){r.triggerSegMouseout(t,e),r.isDraggingSeg=!0,o.hideEvent(l),o.trigger("eventDragStart",s[0],l,e,{})},cellOver:function(e,s){var l=t.cellDate||c.origDate,d=r.computeDraggedEventDates(t,l,s);i=d.start,n=d.end,o.renderDrag(i,n,t)?a.hide():a.show()},cellOut:function(){i=null,o.destroyDrag(),a.show()},dragStop:function(t){var e=i&&!i.isSame(l.start);a.stop(!e,function(){r.isDraggingSeg=!1,o.destroyDrag(),o.showEvent(l),o.trigger("eventDragStop",s[0],l,t,{}),e&&o.eventDrop(s[0],l,i,t)})},listenStop:function(){a.stop()}});c.mousedown(e)},computeDraggedEventDates:function(t,e,i){var n,r,o,s=this.view,l=t.event,a=l.start,c=s.calendar.getEventEnd(l);return i.hasTime()===e.hasTime()?(n=D(i,e),r=a.clone().add(n),o=null===l.end?null:c.clone().add(n)):(r=i,o=null),{start:r,end:o}},segResizeMousedown:function(t,e){function i(){r.destroyResize(),o.showEvent(l)}var n,r=this,o=this.view,s=t.el,l=t.event,a=l.start,c=o.calendar.getEventEnd(l),d=null;n=new q(this.coordMap,{distance:5,scroll:o.opt("dragScroll"),dragStart:function(e){r.triggerSegMouseout(t,e),r.isResizingSeg=!0,o.trigger("eventResizeStart",s[0],l,e,{})},cellOver:function(e,n){n.isBefore(a)&&(n=a),d=n.clone().add(r.cellDuration),d.isSame(c)?(d=null,i()):(r.renderResize(a,d,t),o.hideEvent(l))},cellOut:function(){d=null,i()},dragStop:function(t){r.isResizingSeg=!1,i(),o.trigger("eventResizeStop",s[0],l,t,{}),d&&o.eventResize(s[0],l,d,t)}}),n.mousedown(e)},getSegClasses:function(t,e,i){var n=t.event,r=["fc-event",t.isStart?"fc-start":"fc-not-start",t.isEnd?"fc-end":"fc-not-end"].concat(n.className,n.source?n.source.className:[]);return e&&r.push("fc-draggable"),i&&r.push("fc-resizable"),r},getEventSkinCss:function(t){var e=this.view,i=t.source||{},n=t.color,r=i.color,o=e.opt("eventColor"),s=t.backgroundColor||n||i.backgroundColor||r||e.opt("eventBackgroundColor")||o,l=t.borderColor||n||i.borderColor||r||e.opt("eventBorderColor")||o,a=t.textColor||i.textColor||e.opt("eventTextColor"),c=[];return s&&c.push("background-color:"+s),l&&c.push("border-color:"+l),a&&c.push("color:"+a),c.join(";")}}),ee.prototype=H(J.prototype),t.extend(ee.prototype,{numbersVisible:!1,cellDuration:e.duration({days:1}),bottomCoordPadding:0,rowEls:null,dayEls:null,helperEls:null,highlightEls:null,render:function(e){var i,n=this.view,r="";for(i=0;n.rowCnt>i;i++)r+=this.dayRowHtml(i,e);this.el.html(r),this.rowEls=this.el.find(".fc-row"),this.dayEls=this.el.find(".fc-day"),this.dayEls.each(function(e,i){var r=n.cellToDate(Math.floor(e/n.colCnt),e%n.colCnt);n.trigger("dayRender",null,r,t(i))}),J.prototype.render.call(this)},destroy:function(){this.destroySegPopover()},dayRowHtml:function(t,e){var i=this.view,n=["fc-row","fc-week",i.widgetContentClass];return e&&n.push("fc-rigid"),'
'+'
'+""+this.rowHtml("day",t)+"
"+"
"+'
'+""+(this.numbersVisible?""+this.rowHtml("number",t)+"":"")+"
"+"
"+"
"},dayCellHtml:function(t,e,i){return this.bgCellHtml(t,e,i)},buildCoords:function(e,i){var n,r,o,s=this.view.colCnt;this.dayEls.slice(0,s).each(function(e,s){n=t(s),r=n.offset().left,e&&(o[1]=r),o=[r],i[e]=o}),o[1]=r+n.outerWidth(),this.rowEls.each(function(i,s){n=t(s),r=n.offset().top,i&&(o[1]=r),o=[r],e[i]=o}),o[1]=r+n.outerHeight()+this.bottomCoordPadding},getCellDate:function(t){return this.view.cellToDate(t)},getCellDayEl:function(t){return this.dayEls.eq(t.row*this.view.colCnt+t.col)},rangeToSegs:function(t,e){return this.view.rangeToSegments(t,e)},renderDrag:function(t,e,i){var n;return this.renderHighlight(t,e||this.view.calendar.getDefaultEventEnd(!0,t)),i&&!i.el.closest(this.el).length?(this.renderRangeHelper(t,e,i),n=this.view.opt("dragOpacity"),void 0!==n&&this.helperEls.css("opacity",n),!0):void 0},destroyDrag:function(){this.destroyHighlight(),this.destroyHelper()},renderResize:function(t,e,i){this.renderHighlight(t,e),this.renderRangeHelper(t,e,i)},destroyResize:function(){this.destroyHighlight(),this.destroyHelper()},renderHelper:function(e,i){var n=[],r=this.renderEventRows([e]);this.rowEls.each(function(e,o){var s,l=t(o),a=t('
');s=i&&i.row===e?i.el.position().top:l.find(".fc-content-skeleton tbody").position().top,a.css("top",s).find("table").append(r[e].tbodyEl),l.append(a),n.push(a[0])}),this.helperEls=t(n)},destroyHelper:function(){this.helperEls&&(this.helperEls.remove(),this.helperEls=null)},renderHighlight:function(e,i){var n,r,o,s=this.rangeToSegs(e,i),l=[];for(n=0;s.length>n;n++)r=s[n],o=t(this.highlightSkeletonHtml(r.leftCol,r.rightCol+1)),o.appendTo(this.rowEls[r.row]),l.push(o[0]);this.highlightEls=t(l)},destroyHighlight:function(){this.highlightEls&&(this.highlightEls.remove(),this.highlightEls=null)},highlightSkeletonHtml:function(t,e){var i=this.view.colCnt,n="";return t>0&&(n+='"),m=[],v=[],y=[];for(r=0;g>r;r++){if(o=f[r],s=0,l=t(""),m.push([]),v.push([]),y.push([]),o)for(a=0;o.length>a;a++){for(c=o[a],n(c.leftCol),d=t('",l+=""+(s?"":n)+'",c.add(this.slotDuration);return l},processOptions:function(){var t=this.view,i=t.opt("slotDuration"),n=t.opt("snapDuration");i=e.duration(i),n=n?e.duration(n):i,this.slotDuration=i,this.snapDuration=n,this.cellDuration=n,this.minTime=e.duration(t.opt("minTime")),this.maxTime=e.duration(t.opt("maxTime"))},rangeToSegs:function(t,e){var i,n,r,o,s,l=this.view,a=[];for(t=t.clone().stripZone(),e=e.clone().stripZone(),n=0;l.colCnt>n;n++)r=l.cellToDate(0,n),o=r.clone().time(this.minTime),s=r.clone().time(this.maxTime),i=b(t,e,o,s),i&&(i.col=n,a.push(i));return a},resize:function(){this.computeSlatTops(),this.updateSegVerticals()},buildCoords:function(i,n){var r,o,s=this.view.colCnt,l=this.el.offset().top,a=e.duration(+this.minTime),c=null;for(this.dayEls.slice(0,s).each(function(e,i){r=t(i),o=r.offset().left,c&&(c[1]=o),c=[o],n[e]=c}),c[1]=o+r.outerWidth(),c=null;this.maxTime>a;)o=l+this.computeTimeTop(a),c&&(c[1]=o),c=[o],i.push(c),a.add(this.snapDuration);c[1]=l+this.computeTimeTop(a)},getCellDate:function(t){var e=this.view,i=e.calendar;return i.rezoneDate(e.cellToDate(0,t.col).time(this.minTime+this.snapDuration*t.row))},getCellDayEl:function(t){return this.dayEls.eq(t.col)},computeDateTop:function(t,i){return this.computeTimeTop(e.duration(t.clone().stripZone()-i.clone().stripTime()))},computeTimeTop:function(t){var e,i,n,r,o=(t-this.minTime)/this.slotDuration;return o=Math.max(0,o),o=Math.min(this.slatEls.length,o),e=Math.floor(o),i=o-e,n=this.slatTops[e],i?(r=this.slatTops[e+1],n+(r-n)*i):n},computeSlatTops:function(){var e,i=[];this.slatEls.each(function(n,r){e=t(r).position().top,i.push(e)}),i.push(e+this.slatEls.last().outerHeight()),this.slatTops=i},renderDrag:function(t,e,i){var n;return i?(this.renderRangeHelper(t,e,i),n=this.view.opt("dragOpacity"),void 0!==n&&this.helperEl.css("opacity",n),!0):(this.renderHighlight(t,e||this.view.calendar.getDefaultEventEnd(!1,t)),void 0)},destroyDrag:function(){this.destroyHelper(),this.destroyHighlight()},renderResize:function(t,e,i){this.renderRangeHelper(t,e,i)},destroyResize:function(){this.destroyHelper()},renderHelper:function(e,i){var n,r,o,s=this.renderEventTable([e]),l=s.tableEl,a=s.segs;for(n=0;a.length>n;n++)r=a[n],i&&i.col===r.col&&(o=i.el,r.el.css({left:o.css("left"),right:o.css("right"),"margin-left":o.css("margin-left"),"margin-right":o.css("margin-right")}));this.helperEl=t('
').append(l).appendTo(this.el)},destroyHelper:function(){this.helperEl&&(this.helperEl.remove(),this.helperEl=null)},renderSelection:function(t,e){this.view.opt("selectHelper")?this.renderRangeHelper(t,e):this.renderHighlight(t,e)},destroySelection:function(){this.destroyHelper(),this.destroyHighlight()},renderHighlight:function(e,i){this.highlightEl=t(this.highlightSkeletonHtml(e,i)).appendTo(this.el)},destroyHighlight:function(){this.highlightEl&&(this.highlightEl.remove(),this.highlightEl=null)},highlightSkeletonHtml:function(t,e){var i,n,r,o,s,l=this.view,a=this.rangeToSegs(t,e),c="",d=0;for(i=0;a.length>i;i++)n=a[i],n.col>d&&(c+='
",d++;return l.colCnt>d&&(c+='":void 0},numberIntroHtml:function(t){return this.weekNumbersVisible?'":void 0},dayIntroHtml:function(){return this.weekNumbersVisible?'":void 0},introHtml:function(){return this.weekNumbersVisible?'":void 0},numberCellHtml:function(t,e,i){var n;return this.dayNumbersVisible?(n=this.dayGrid.getDayClasses(i),n.unshift("fc-day-number"),'"):""):'"},dayIntroHtml:function(){return'"},slotBgIntroHtml:function(){return'"},introHtml:function(){return'"},axisStyleAttr:function(){return null!==this.axisWidth?'style="width:'+this.axisWidth+'px"':""},updateSize:function(t){t&&this.timeGrid.resize(),fe.prototype.updateSize.call(this,t)},updateWidth:function(){this.axisWidth=p(this.el.find(".fc-axis"))},setHeight:function(t,e){var i,n;null===this.bottomRuleHeight&&(this.bottomRuleHeight=this.bottomRuleEl.outerHeight()),this.bottomRuleEl.hide(),this.scrollerEl.css("overflow",""),v(this.scrollerEl),u(this.noScrollRowEls),this.dayGrid&&(this.dayGrid.destroySegPopover(),i=this.opt("eventLimit"),i&&"number"!=typeof i&&(i=Ye),i&&this.dayGrid.limitRows(i)),e||(n=this.computeScrollerHeight(t),m(this.scrollerEl,n)?(h(this.noScrollRowEls,w(this.scrollerEl)),n=this.computeScrollerHeight(t),this.scrollerEl.height(n),this.restoreScroll()):(this.scrollerEl.height(n).css("overflow","hidden"),this.bottomRuleEl.show()))},resetScroll:function(){function t(){i.scrollerEl.scrollTop(r)}var i=this,n=e.duration(this.opt("scrollTime")),r=this.timeGrid.computeTimeTop(n);r=Math.ceil(r),r&&r++,t(),setTimeout(t,0)},renderEvents:function(t){var e,i,n=[],r=[],o=[];for(i=0;t.length>i;i++)t[i].allDay?n.push(t[i]):r.push(t[i]);e=this.timeGrid.renderEvents(r),this.dayGrid&&(o=this.dayGrid.renderEvents(n)),this.updateHeight(),fe.prototype.renderEvents.call(this,t)},getSegs:function(){return this.timeGrid.getSegs().concat(this.dayGrid?this.dayGrid.getSegs():[])},destroyEvents:function(){fe.prototype.destroyEvents.call(this),this.recordScroll(),this.timeGrid.destroyEvents(),this.dayGrid&&this.dayGrid.destroyEvents()},renderDrag:function(t,e,i){return t.hasTime()?this.timeGrid.renderDrag(t,e,i):this.dayGrid?this.dayGrid.renderDrag(t,e,i):void 0},destroyDrag:function(){this.timeGrid.destroyDrag(),this.dayGrid&&this.dayGrid.destroyDrag()},renderSelection:function(t,e){t.hasTime()||e.hasTime()?this.timeGrid.renderSelection(t,e):this.dayGrid&&this.dayGrid.renderSelection(t,e)},destroySelection:function(){this.timeGrid.destroySelection(),this.dayGrid&&this.dayGrid.destroySelection()}}),xe.agendaWeek=be,be.prototype=H(Ee.prototype),t.extend(be.prototype,{name:"agendaWeek",incrementDate:function(t,e){return t.clone().stripTime().add(e,"weeks").startOf("week")},render:function(t){this.intervalStart=t.clone().stripTime().startOf("week"),this.intervalEnd=this.intervalStart.clone().add(1,"weeks"),this.start=this.skipHiddenDays(this.intervalStart),this.end=this.skipHiddenDays(this.intervalEnd,-1,!0),this.title=this.calendar.formatRange(this.start,this.end.clone().subtract(1),this.opt("titleFormat")," — "),Ee.prototype.render.call(this,this.getCellsPerWeek())}}),xe.agendaDay=Se,Se.prototype=H(Ee.prototype),t.extend(Se.prototype,{name:"agendaDay",incrementDate:function(t,e){var i=t.clone().stripTime().add(e,"days");return i=this.skipHiddenDays(i,0>e?-1:1)},render:function(t){this.start=this.intervalStart=t.clone().stripTime(),this.end=this.intervalEnd=this.start.clone().add(1,"days"),this.title=this.calendar.formatDate(this.start,this.opt("titleFormat")),Ee.prototype.render.call(this,1)}})}); \ No newline at end of file +(function(t){"function"==typeof define&&define.amd?define(["jquery","moment"],t):t(jQuery,moment)})(function(t,e){function i(t,e){return e.longDateFormat("LT").replace(":mm","(:mm)").replace(/(\Wmm)$/,"($1)").replace(/\s*a$/i,"t")}function n(t,e){var i=e.longDateFormat("L");return i=i.replace(/^Y+[^\w\s]*|[^\w\s]*Y+$/g,""),t.isRTL?i+=" ddd":i="ddd "+i,i}function r(t){o(ke,t)}function o(e){function i(i,n){t.isPlainObject(n)&&t.isPlainObject(e[i])&&!s(i)?e[i]=o({},e[i],n):void 0!==n&&(e[i]=n)}for(var n=1;arguments.length>n;n++)t.each(arguments[n],i);return e}function s(t){return/(Time|Duration)$/.test(t)}function l(i,n){function r(t){var i=e.localeData||e.langData;return i.call(e,t)||i.call(e,"en")}function s(t){ne?u()&&(m(),p(t)):l()}function l(){re=Q.theme?"ui":"fc",i.addClass("fc"),Q.isRTL?i.addClass("fc-rtl"):i.addClass("fc-ltr"),Q.theme?i.addClass("ui-widget"):i.addClass("fc-unthemed"),ne=t("
").prependTo(i),ee=new a(U,Q),ie=ee.render(),ie&&i.prepend(ie),f(Q.defaultView),Q.handleWindowResize&&(le=L(y,Q.windowResizeDelay),t(window).resize(le))}function d(){oe&&oe.destroy(),ee.destroy(),ne.remove(),i.removeClass("fc fc-ltr fc-rtl fc-unthemed ui-widget"),t(window).unbind("resize",le)}function u(){return i.is(":visible")}function f(t){p(0,t)}function p(e,i){ue++,oe&&i&&oe.name!==i&&(ee.deactivateButton(oe.name),I(),oe.start&&oe.destroy(),oe.el.remove(),oe=null),!oe&&i&&(oe=new ze[i](U),oe.el=t("
").appendTo(ne),ee.activateButton(i)),oe&&(e&&(ae=oe.incrementDate(ae,e)),oe.start&&!e&&ae.isWithin(oe.intervalStart,oe.intervalEnd)||u()&&(I(),oe.start&&oe.destroy(),oe.render(ae),B(),H(),k(),S())),B(),ue--}function g(t){return u()?(t&&v(),ue++,oe.updateSize(!0),ue--,!0):void 0}function m(){u()&&v()}function v(){se="number"==typeof Q.contentHeight?Q.contentHeight:"number"==typeof Q.height?Q.height-(ie?ie.outerHeight(!0):0):Math.round(ne.width()/Math.max(Q.aspectRatio,.5))}function y(t){!ue&&t.target===window&&oe.start&&g(!0)&&oe.trigger("windowResize",he)}function w(){D(),b()}function E(){u()&&(I(),oe.destroyEvents(),oe.renderEvents(fe),B())}function D(){I(),oe.destroyEvents(),B()}function S(){!Q.lazyFetching||ce(oe.start,oe.end)?b():E()}function b(){de(oe.start,oe.end)}function T(t){fe=t,E()}function C(){E()}function H(){ee.updateTitle(oe.title)}function k(){var t=U.getNow();t.isWithin(oe.intervalStart,oe.intervalEnd)?ee.disableButton("today"):ee.enableButton("today")}function M(t,e){t=U.moment(t),e=e?U.moment(e):t.hasTime()?t.clone().add(U.defaultTimedEventDuration):t.clone().add(U.defaultAllDayEventDuration),oe.select(t,e)}function R(){oe&&oe.unselect()}function P(){p(-1)}function z(){p(1)}function N(){ae.add(-1,"years"),p()}function _(){ae.add(1,"years"),p()}function A(){ae=U.getNow(),p()}function Y(t){ae=U.moment(t),p()}function O(t){ae.add(e.duration(t)),p()}function F(t,e){var i,n;e&&void 0!==ze[e]||(e=e||"day",i=ee.getViewsWithButtons().join(" "),n=i.match(RegExp("\\w+"+G(e))),n||(n=i.match(/\w+Day/)),e=n?n[0]:"agendaDay"),ae=t,f(e)}function Z(){return ae.clone()}function I(){ne.css({width:"100%",height:ne.height(),overflow:"hidden"})}function B(){ne.css({width:"",height:"",overflow:""})}function j(){return U}function X(){return oe}function $(t,e){return void 0===e?Q[t]:(("height"==t||"contentHeight"==t||"aspectRatio"==t)&&(Q[t]=e,g(!0)),void 0)}function q(t,e){return Q[t]?Q[t].apply(e||he,Array.prototype.slice.call(arguments,2)):void 0}var U=this;n=n||{};var K,Q=o({},ke,n);K=Q.lang in Me?Me[Q.lang]:Me[ke.lang],K&&(Q=o({},ke,K,n)),Q.isRTL&&(Q=o({},ke,Re,K||{},n)),U.options=Q,U.render=s,U.destroy=d,U.refetchEvents=w,U.reportEvents=T,U.reportEventChange=C,U.rerenderEvents=E,U.changeView=f,U.select=M,U.unselect=R,U.prev=P,U.next=z,U.prevYear=N,U.nextYear=_,U.today=A,U.gotoDate=Y,U.incrementDate=O,U.zoomTo=F,U.getDate=Z,U.getCalendar=j,U.getView=X,U.option=$,U.trigger=q;var J=x(r(Q.lang));if(Q.monthNames&&(J._months=Q.monthNames),Q.monthNamesShort&&(J._monthsShort=Q.monthNamesShort),Q.dayNames&&(J._weekdays=Q.dayNames),Q.dayNamesShort&&(J._weekdaysShort=Q.dayNamesShort),null!=Q.firstDay){var te=x(J._week);te.dow=Q.firstDay,J._week=te}U.defaultAllDayEventDuration=e.duration(Q.defaultAllDayEventDuration),U.defaultTimedEventDuration=e.duration(Q.defaultTimedEventDuration),U.moment=function(){var t;return"local"===Q.timezone?(t=Pe.moment.apply(null,arguments),t.hasTime()&&t.local()):t="UTC"===Q.timezone?Pe.moment.utc.apply(null,arguments):Pe.moment.parseZone.apply(null,arguments),"_locale"in t?t._locale=J:t._lang=J,t},U.getIsAmbigTimezone=function(){return"local"!==Q.timezone&&"UTC"!==Q.timezone},U.rezoneDate=function(t){return U.moment(t.toArray())},U.getNow=function(){var t=Q.now;return"function"==typeof t&&(t=t()),U.moment(t)},U.calculateWeekNumber=function(t){var e=Q.weekNumberCalculation;return"function"==typeof e?e(t):"local"===e?t.week():"ISO"===e.toUpperCase()?t.isoWeek():void 0},U.getEventEnd=function(t){return t.end?t.end.clone():U.getDefaultEventEnd(t.allDay,t.start)},U.getDefaultEventEnd=function(t,e){var i=e.clone();return t?i.stripTime().add(U.defaultAllDayEventDuration):i.add(U.defaultTimedEventDuration),U.getIsAmbigTimezone()&&i.stripZone(),i},U.formatRange=function(t,e,i){return"function"==typeof i&&(i=i.call(U,Q,J)),W(t,e,i,null,Q.isRTL)},U.formatDate=function(t,e){return"function"==typeof e&&(e=e.call(U,Q,J)),V(t,e)},c.call(U,Q),h.call(U,Q);var ee,ie,ne,re,oe,se,le,ae,ce=U.isFetchNeeded,de=U.fetchEvents,he=i[0],ue=0,fe=[];ae=null!=Q.defaultDate?U.moment(Q.defaultDate):U.getNow(),U.getSuggestedViewHeight=function(){return void 0===se&&m(),se},U.isHeightAuto=function(){return"auto"===Q.contentHeight||"auto"===Q.height}}function a(e,i){function n(){var e=i.header;return f=i.theme?"ui":"fc",e?p=t("
").append(o("left")).append(o("right")).append(o("center")).append('
'):void 0}function r(){p.remove()}function o(n){var r=t('
'),o=i.header[n];return o&&t.each(o.split(" "),function(){var n,o=t(),s=!0;t.each(this.split(","),function(n,r){var l,a,c,d,h,u,p,m;"title"==r?(o=o.add(t("

 

")),s=!1):(e[r]?l=function(){e[r]()}:ze[r]&&(l=function(){e.changeView(r)},g.push(r)),l&&(a=b(i.themeButtonIcons,r),c=b(i.buttonIcons,r),d=b(i.defaultButtonText,r),h=b(i.buttonText,r),u=h?P(h):a&&i.theme?"":c&&!i.theme?"":P(d||r),p=["fc-"+r+"-button",f+"-button",f+"-state-default"],m=t('").click(function(){m.hasClass(f+"-state-disabled")||(l(),(m.hasClass(f+"-state-active")||m.hasClass(f+"-state-disabled"))&&m.removeClass(f+"-state-hover"))}).mousedown(function(){m.not("."+f+"-state-active").not("."+f+"-state-disabled").addClass(f+"-state-down")}).mouseup(function(){m.removeClass(f+"-state-down")}).hover(function(){m.not("."+f+"-state-active").not("."+f+"-state-disabled").addClass(f+"-state-hover")},function(){m.removeClass(f+"-state-hover").removeClass(f+"-state-down")}),o=o.add(m)))}),s&&o.first().addClass(f+"-corner-left").end().last().addClass(f+"-corner-right").end(),o.length>1?(n=t("
"),s&&n.addClass("fc-button-group"),n.append(o),r.append(n)):r.append(o)}),r}function s(t){p.find("h2").text(t)}function l(t){p.find(".fc-"+t+"-button").addClass(f+"-state-active")}function a(t){p.find(".fc-"+t+"-button").removeClass(f+"-state-active")}function c(t){p.find(".fc-"+t+"-button").attr("disabled","disabled").addClass(f+"-state-disabled")}function d(t){p.find(".fc-"+t+"-button").removeAttr("disabled").removeClass(f+"-state-disabled")}function h(){return g}var u=this;u.render=n,u.destroy=r,u.updateTitle=s,u.activateButton=l,u.deactivateButton=a,u.disableButton=c,u.enableButton=d,u.getViewsWithButtons=h;var f,p=t(),g=[]}function c(e){function i(t,e){return!b||t.clone().stripZone()C.clone().stripZone()}function n(t,e){b=t,C=e,A=[];var i=++L,n=G.length;N=n;for(var o=0;n>o;o++)r(G[o],i)}function r(e,i){o(e,function(n){var r,o,s=t.isArray(e.events);if(i==L){if(n)for(r=0;n.length>r;r++)o=n[r],s||(o=w(o,e)),o&&A.push(o);N--,N||k(A)}})}function o(i,n){var r,s,l=Pe.sourceFetchers;for(r=0;l.length>r;r++){if(s=l[r].call(S,i,b.clone(),C.clone(),e.timezone,n),s===!0)return;if("object"==typeof s)return o(s,n),void 0}var a=i.events;if(a)t.isFunction(a)?(v(),a.call(S,b.clone(),C.clone(),e.timezone,function(t){n(t),y()})):t.isArray(a)?n(a):n();else{var c=i.url;if(c){var d,h=i.success,u=i.error,f=i.complete;d=t.isFunction(i.data)?i.data():i.data;var p=t.extend({},d||{}),g=R(i.startParam,e.startParam),m=R(i.endParam,e.endParam),w=R(i.timezoneParam,e.timezoneParam);g&&(p[g]=b.format()),m&&(p[m]=C.format()),e.timezone&&"local"!=e.timezone&&(p[w]=e.timezone),v(),t.ajax(t.extend({},Ge,i,{data:p,success:function(e){e=e||[];var i=M(h,this,arguments);t.isArray(i)&&(e=i),n(e)},error:function(){M(u,this,arguments),n()},complete:function(){M(f,this,arguments),y()}}))}else n()}}function s(t){var e=l(t);e&&(G.push(e),N++,r(e,L))}function l(e){var i,n,r=Pe.sourceNormalizers;if(t.isFunction(e)||t.isArray(e)?i={events:e}:"string"==typeof e?i={url:e}:"object"==typeof e&&(i=t.extend({},e)),i){for(i.className?"string"==typeof i.className&&(i.className=i.className.split(/\s+/)):i.className=[],t.isArray(i.events)&&(i.origArray=i.events,i.events=t.map(i.events,function(t){return w(t,i)})),n=0;r.length>n;n++)r[n].call(S,i);return i}}function a(e){G=t.grep(G,function(t){return!c(t,e)}),A=t.grep(A,function(t){return!c(t.source,e)}),k(A)}function c(t,e){return t&&e&&h(t)==h(e)}function h(t){return("object"==typeof t?t.origArray||t.url||t.events:null)||t}function u(t){t.start=S.moment(t.start),t.end&&(t.end=S.moment(t.end)),E(t),f(t),k(A)}function f(t){var e,i,n,r;for(e=0;A.length>e;e++)if(i=A[e],i._id==t._id&&i!==t)for(n=0;Y.length>n;n++)r=Y[n],void 0!==t[r]&&(i[r]=t[r])}function p(t,e){var i=w(t);i&&(i.source||(e&&(z.events.push(i),i.source=z),A.push(i)),k(A))}function g(e){var i,n;for(null==e?e=function(){return!0}:t.isFunction(e)||(i=e+"",e=function(t){return t._id==i}),A=t.grep(A,e,!0),n=0;G.length>n;n++)t.isArray(G[n].events)&&(G[n].events=t.grep(G[n].events,e,!0));k(A)}function m(e){return t.isFunction(e)?t.grep(A,e):null!=e?(e+="",t.grep(A,function(t){return t._id==e})):A}function v(){_++||H("loading",null,!0,x())}function y(){--_||H("loading",null,!1,x())}function w(i,n){var r,o,s,l,a={};return e.eventDataTransform&&(i=e.eventDataTransform(i)),n&&n.eventDataTransform&&(i=n.eventDataTransform(i)),r=S.moment(i.start||i.date),r.isValid()&&(o=null,!i.end||(o=S.moment(i.end),o.isValid()))?(s=i.allDay,void 0===s&&(l=R(n?n.allDayDefault:void 0,e.allDayDefault),s=void 0!==l?l:!(r.hasTime()||o&&o.hasTime())),s?(r.hasTime()&&r.stripTime(),o&&o.hasTime()&&o.stripTime()):(r.hasTime()||(r=S.rezoneDate(r)),o&&!o.hasTime()&&(o=S.rezoneDate(o))),t.extend(a,i),n&&(a.source=n),a._id=i._id||(void 0===i.id?"_fc"+Le++:i.id+""),a.className=i.className?"string"==typeof i.className?i.className.split(/\s+/):i.className:[],a.resources?"string"==typeof a.resources&&(a.resources=a.resources.split(/\s+/)):a.resources=[],a.allDay=s,a.start=r,a.end=o,e.forceEventDuration&&!a.end&&(a.end=P(a)),d(a),a):void 0}function E(t,e,i){var n,r,o,s,l=t._allDay,a=t._start,c=t._end,d=!1;return e||i||(e=t.start,i=t.end),n=t.allDay!=l?t.allDay:!(e||i).hasTime(),n&&(e&&(e=e.clone().stripTime()),i&&(i=i.clone().stripTime())),e&&(r=n?T(e,a.clone().stripTime()):T(e,a)),n!=l?d=!0:i&&(o=T(i||S.getDefaultEventEnd(n,e||a),e||a).subtract(T(c||S.getDefaultEventEnd(l,a),a))),s=D(m(t._id),d,n,r,o),{dateDelta:r,durationDelta:o,undo:s}}function D(i,n,r,o,s){var l=S.getIsAmbigTimezone(),a=[];return t.each(i,function(t,i){var c=i._allDay,h=i._start,u=i._end,f=null!=r?r:c,p=h.clone(),g=!n&&u?u.clone():null;f?(p.stripTime(),g&&g.stripTime()):(p.hasTime()||(p=S.rezoneDate(p)),g&&!g.hasTime()&&(g=S.rezoneDate(g))),g||!e.forceEventDuration&&!+s||(g=S.getDefaultEventEnd(f,p)),p.add(o),g&&g.add(o).add(s),l&&(+o||+s)&&(p.stripZone(),g&&g.stripZone()),i.allDay=f,i.start=p,i.end=g,d(i),a.push(function(){i.allDay=c,i.start=h,i.end=u,d(i)})}),function(){for(var t=0;a.length>t;t++)a[t]()}}var S=this;S.isFetchNeeded=i,S.fetchEvents=n,S.addEventSource=s,S.removeEventSource=a,S.updateEvent=u,S.renderEvent=p,S.removeEvents=g,S.clientEvents=m,S.mutateEvent=E;var b,C,H=S.trigger,x=S.getView,k=S.reportEvents,P=S.getEventEnd,z={events:[]},G=[z],L=0,N=0,_=0,A=[];t.each((e.events?[e.events]:[]).concat(e.eventSources||[]),function(t,e){var i=l(e);i&&G.push(i)});var Y=["title","url","allDay","className","editable","color","backgroundColor","borderColor","textColor"]}function d(t){t._allDay=t.allDay,t._start=t.start.clone(),t._end=t.end?t.end.clone():null}function h(e){function i(e){h=[];var i;if(t.isFunction(e))i={resources:e},h.push(i),c=void 0;else if("string"==typeof e)i={url:e},h.push(i),c=void 0;else if("object"==typeof e&&null!=e){for(var n=0;e.length>n;n++){var r=e[n];o(r),i={resources:r},h.push(i)}c=void 0}}function n(i,n){var o;if(i=i!==void 0?i:!0,!i||void 0===o){o=[];for(var s=h.length,l=0;s>l;l++){var a=r(h[l],n);o=o.concat(a)}}return t.isFunction(e.resourceFilter)?t.grep(o,e.resourceFilter):o}function r(i,n){var r=i.resources;if(r){if(t.isFunction(r))return r()}else{var o=i.url;if(o){var s={};if("object"==typeof n){var l=e.startParam,a=e.endParam;l&&(s[l]=Math.round(+n.intervalStart/1e3)),a&&(s[a]=Math.round(+n.intervalEnd/1e3))}t.ajax(t.extend({},Ge,i,{data:s,dataType:"json",cache:!1,success:function(t){t=t||[],r=t},error:function(){},async:!1}))}}return r}function o(t){t.className?"string"==typeof t.className&&(t.className=t.className.split(/\s+/)):t.className=[];for(var e=Pe.sourceNormalizers,i=0;e.length>i;i++)e[i](t)}function s(t,e,i,n){var r,o,s,c,d=t._allDay,h=t._start,u=t._end,f=!1;return i||n||(i=t.start,n=t.end),r=t.allDay!=d?t.allDay:!(i||n).hasTime(),r&&(i&&(i=i.clone().stripTime()),n&&(n=n.clone().stripTime())),i&&(o=r?T(i,h.clone().stripTime()):T(i,h)),r!=d?f=!0:n&&(s=T(n||a.getDefaultEventEnd(r,i||h),i||h).subtract(T(u||a.getDefaultEventEnd(d,h),h))),c=l(a.clientEvents(t._id),f,r,o,s,e),{dateDelta:o,durationDelta:s,undo:c}}function l(i,n,r,o,s,l){var c=a.getIsAmbigTimezone(),h=[];return t.each(i,function(t,i){var u=i.resources,f=i._allDay,p=i._start,g=i._end,m=null!=r?r:f,v=p.clone(),y=!n&&g?g.clone():null;m?(v.stripTime(),y&&y.stripTime()):(v.hasTime()||(v=a.rezoneDate(v)),y&&!y.hasTime()&&(y=a.rezoneDate(y))),y||!e.forceEventDuration&&!+s||(y=a.getDefaultEventEnd(m,v)),v.add(o),y&&y.add(o).add(s),c&&(+o||+s)&&(v.stripZone(),y&&y.stripZone()),i.allDay=m,i.start=v,i.end=y,i.resources=l,d(i),h.push(function(){i.allDay=f,i.start=p,i.end=g,i.resources=u,d(i)})}),function(){for(var t=0;h.length>t;t++)h[t]()}}var a=this;a.fetchResources=n,a.setResources=i,a.mutateResourceEvent=s;var c,h=[];i(e.resources)}function u(t,e){e.left&&t.css({"border-left-width":1,"margin-left":e.left-1}),e.right&&t.css({"border-right-width":1,"margin-right":e.right-1})}function f(t){t.css({"margin-left":"","margin-right":"","border-left-width":"","border-right-width":""})}function p(e,i,n){var r=Math.floor(i/e.length),o=Math.floor(i-r*(e.length-1)),s=[],l=[],a=[],c=0;g(e),e.each(function(i,n){var d=i===e.length-1?o:r,h=t(n).outerHeight(!0);d>h?(s.push(n),l.push(h),a.push(t(n).height())):c+=h}),n&&(i-=c,r=Math.floor(i/s.length),o=Math.floor(i-r*(s.length-1))),t(s).each(function(e,i){var n=e===s.length-1?o:r,c=l[e],d=a[e],h=n-(c-d);n>c&&t(i).height(h)})}function g(t){t.height("")}function m(e){var i=0;return e.find("> *").each(function(e,n){var r=t(n).outerWidth();r>i&&(i=r)}),i++,e.width(i),i}function v(t,e){return t.height(e).addClass("fc-scroller"),t[0].scrollHeight-1>t[0].clientHeight?!0:(y(t),!1)}function y(t){t.height("").removeClass("fc-scroller")}function w(e){var i=e.css("position"),n=e.parents().filter(function(){var e=t(this);return/(auto|scroll)/.test(e.css("overflow")+e.css("overflow-y")+e.css("overflow-x"))}).eq(0);return"fixed"!==i&&n.length?n:t(e[0].ownerDocument||document)}function E(t){var e=t.offset().left,i=e+t.width(),n=t.children(),r=n.offset().left,o=r+n.outerWidth();return{left:r-e,right:i-o}}function D(t){return 1==t.which&&!t.ctrlKey}function S(t,e,i,n){var r,o,s,l;return e>i&&n>t?(t>=i?(r=t.clone(),s=!0):(r=i.clone(),s=!1),n>=e?(o=e.clone(),l=!0):(o=n.clone(),l=!1),{start:r,end:o,isStart:s,isEnd:l}):void 0}function b(t,e){if(t=t||{},void 0!==t[e])return t[e];for(var i,n=e.split(/(?=[A-Z])/),r=n.length-1;r>=0;r--)if(i=t[n[r].toLowerCase()],void 0!==i)return i;return t["default"]}function T(t,i){return e.duration({days:t.clone().stripTime().diff(i.clone().stripTime(),"days"),ms:t.time()-i.time()})}function C(t){return"[object Date]"===Object.prototype.toString.call(t)||t instanceof Date}function H(t,e){return t-e}function x(t){var e=function(){};return e.prototype=t,new e}function k(t,e){for(var i in e)e.hasOwnProperty(i)&&(t[i]=e[i])}function M(e,i,n){if(t.isFunction(e)&&(e=[e]),e){var r,o;for(r=0;e.length>r;r++)o=e[r].apply(i,n)||o;return o}}function R(){for(var t=0;arguments.length>t;t++)if(void 0!==arguments[t])return arguments[t]}function P(t){return(t+"").replace(/&/g,"&").replace(//g,">").replace(/'/g,"'").replace(/"/g,""").replace(/\n/g,"
")}function z(t){return t.replace(/&.*?;/g,"")}function G(t){return t.charAt(0).toUpperCase()+t.slice(1)}function L(t,e){var i,n,r,o,s=function(){var l=+new Date-o;e>l&&l>0?i=setTimeout(s,e-l):(i=null,t.apply(r,n),i||(r=n=null))};return function(){r=this,n=arguments,o=+new Date,i||(i=setTimeout(s,e))}}function N(i,n,r){var o,s,l,a,c=i[0],d=1==i.length&&"string"==typeof c;return e.isMoment(c)?(a=e.apply(null,i),c._ambigTime&&(a._ambigTime=!0),c._ambigZone&&(a._ambigZone=!0)):C(c)||void 0===c?a=e.apply(null,i):(o=!1,s=!1,d?_e.test(c)?(c+="-01",i=[c],o=!0,s=!0):(l=Ae.exec(c))&&(o=!l[5],s=!0):t.isArray(c)&&(s=!0),a=n?e.utc.apply(e,i):e.apply(null,i),o?(a._ambigTime=!0,a._ambigZone=!0):r&&(s?a._ambigZone=!0:d&&a.zone(c))),new _(a)}function _(t){k(this,t)}function A(t,e){var i,n=[],r=!1,o=!1;for(i=0;t.length>i;i++)n.push(Pe.moment.parseZone(t[i])),r=r||n[i]._ambigTime,o=o||n[i]._ambigZone;for(i=0;n.length>i;i++)r&&!e?n[i].stripTime():o&&n[i].stripZone();return n}function Y(t,i){return e.fn.format.call(t,i)}function V(t,e){return O(t,B(e))}function O(t,e){var i,n="";for(i=0;e.length>i;i++)n+=F(t,e[i]);return n}function F(t,e){var i,n;return"string"==typeof e?e:(i=e.token)?Ye[i]?Ye[i](t):Y(t,i):e.maybe&&(n=O(t,e.maybe),n.match(/[1-9]/))?n:""}function W(t,e,i,n,r){var o;return t=Pe.moment.parseZone(t),e=Pe.moment.parseZone(e),o=(t.localeData||t.lang).call(t),i=o.longDateFormat(i)||i,n=n||" - ",Z(t,e,B(i),n,r)}function Z(t,e,i,n,r){var o,s,l,a,c="",d="",h="",u="",f="";for(s=0;i.length>s&&(o=I(t,e,i[s]),o!==!1);s++)c+=o;for(l=i.length-1;l>s&&(o=I(t,e,i[l]),o!==!1);l--)d=o+d;for(a=s;l>=a;a++)h+=F(t,i[a]),u+=F(e,i[a]);return(h||u)&&(f=r?u+n+h:h+n+u),c+f+d}function I(t,e,i){var n,r;return"string"==typeof i?i:(n=i.token)&&(r=Ve[n.charAt(0)],r&&t.isSame(e,r))?Y(t,n):!1}function B(t){return t in Oe?Oe[t]:Oe[t]=j(t)}function j(t){for(var e,i=[],n=/\[([^\]]*)\]|\(([^\)]*)\)|(LT|(\w)\4*o?)|([^\w\[\(]+)/g;e=n.exec(t);)e[1]?i.push(e[1]):e[2]?i.push({maybe:j(e[2])}):e[3]?i.push({token:e[3]}):e[5]&&i.push(e[5]);return i}function X(t){this.options=t||{}}function $(t){this.grid=t}function q(t){this.coordMaps=t}function U(t,e){this.coordMap=t,this.options=e||{}}function K(t,e){return t||e?t&&e?t.grid===e.grid&&t.row===e.row&&t.col===e.col:!1:!0}function Q(e,i){this.options=i=i||{},this.sourceEl=e,this.parentEl=i.parentEl?t(i.parentEl):e.parent()}function J(t){this.view=t}function te(t){J.call(this,t),this.coordMap=new $(this)}function ee(t,e){return t.eventStartMS-e.eventStartMS||e.eventDurationMS-t.eventDurationMS||e.event.allDay-t.event.allDay||(t.event.title||"").localeCompare(e.event.title)}function ie(t){te.call(this,t)}function ne(t,e){var i,n;for(i=0;e.length>i;i++)if(n=e[i],n.leftCol<=t.rightCol&&n.rightCol>=t.leftCol)return!0;return!1}function re(t,e){return t.leftCol-e.leftCol}function oe(t){te.call(this,t)}function se(t){var e,i,n;if(t.sort(ee),e=le(t),ae(e),i=e[0]){for(n=0;i.length>n;n++)ce(i[n]);for(n=0;i.length>n;n++)de(i[n],0,0)}}function le(t){var e,i,n,r=[];for(e=0;t.length>e;e++){for(i=t[e],n=0;r.length>n&&he(i,r[n]).length;n++);i.level=n,(r[n]||(r[n]=[])).push(i)}return r}function ae(t){var e,i,n,r,o;for(e=0;t.length>e;e++)for(i=t[e],n=0;i.length>n;n++)for(r=i[n],r.forwardSegs=[],o=e+1;t.length>o;o++)he(r,t[o],r.forwardSegs)}function ce(t){var e,i,n=t.forwardSegs,r=0;if(void 0===t.forwardPressure){for(e=0;n.length>e;e++)i=n[e],ce(i),r=Math.max(r,1+i.forwardPressure);t.forwardPressure=r}}function de(t,e,i){var n,r=t.forwardSegs;if(void 0===t.forwardCoord)for(r.length?(r.sort(fe),de(r[0],e+1,i),t.forwardCoord=r[0].backwardCoord):t.forwardCoord=1,t.backwardCoord=t.forwardCoord-(t.forwardCoord-i)/(e+1),n=0;r.length>n;n++)de(r[n],0,t.forwardCoord)}function he(t,e,i){i=i||[];for(var n=0;e.length>n;n++)ue(t,e[n])&&i.push(e[n]);return i}function ue(t,e){return t.bottom>e.top&&t.topd;d++){var h=d*n,u=h+n-1,f=Math.max(a,h),p=Math.min(c,u);if(p>=f){var m=E(f),v=E(p),D=[m.col,v.col].sort(),b=g(f)==s,T=g(p)+1==l;r.push({row:d,leftCol:D[0],rightCol:D[1],isStart:b,isEnd:T})}}return r}function S(t,e){var i,n,r=t.clone().stripTime();return e&&(i=e.clone().stripTime(),n=+e.time(),n&&n>=k&&i.add(1,"days")),(!e||r>=i)&&(i=r.clone().add(1,"days")),{start:r,end:i}}function T(t){var e=S(t.start,t.end);return e.end.diff(e.start,"days")>1}var C=this;C.calendar=i,C.opt=n,C.trigger=r,C.isEventDraggable=o,C.isEventResizable=l,C.eventDrop=a,C.eventResize=c;var H=i.reportEventChange,x=i.options,k=e.duration(x.nextDayThreshold);C.init(),C.getEventTimeText=function(t,e){var r,o;return"object"==typeof t&&"object"==typeof e?(r=t,o=e,e=arguments[2]):(r=t.start,o=t.end),e=e||n("timeFormat"),o&&n("displayEventEnd")?i.formatRange(r,o,e):i.formatDate(r,e)},C.isHiddenDay=d,C.skipHiddenDays=u,C.getCellsPerWeek=h,C.dateToCell=v,C.dateToDayOffset=y,C.dayOffsetToCellOffset=w,C.cellOffsetToCell=E,C.cellToDate=f,C.cellToCellOffset=p,C.cellOffsetToDayOffset=g,C.dayOffsetToDate=m,C.rangeToSegments=D,C.isMultiDayEvent=T;var M,P=n("hiddenDays")||[],z=[],G=[],L=[],N=n("isRTL");(function(){n("weekends")===!1&&P.push(0,6);for(var e=0,i=0;7>e;e++)G[e]=i,z[e]=-1!=t.inArray(e,P),z[e]||(L[i]=e,i++);if(M=i,!M)throw"invalid hiddenDays"})()}function ge(t){pe.call(this,t),this.dayGrid=new ie(this),this.coordMap=this.dayGrid.coordMap}function me(t){ge.call(this,t)}function ve(t){ge.call(this,t)}function ye(t){ge.call(this,t)}function we(t,e){return e.longDateFormat("LT").replace(":mm","(:mm)").replace(/(\Wmm)$/,"($1)").replace(/\s*a$/i,"a")}function Ee(t,e){return e.longDateFormat("LT").replace(/\s*a$/i,"")}function De(t){pe.call(this,t),this.timeGrid=new oe(this),this.opt("allDaySlot")?(this.dayGrid=new ie(this),this.coordMap=new q([this.dayGrid.coordMap,this.timeGrid.coordMap])):this.coordMap=this.timeGrid.coordMap}function Se(t){De.call(this,t)}function be(t){De.call(this,t)}function Te(t){ie.call(this,t)}function Ce(t){oe.call(this,t)}function He(t){pe.call(this,t),this.cellToDate=He.prototype.cellToDate,this.timeGrid=new Ce(this),this.opt("allDaySlot")?(this.dayGrid=new Te(this),this.coordMap=new q([this.dayGrid.coordMap,this.timeGrid.coordMap])):this.coordMap=this.timeGrid.coordMap}function xe(t){He.call(this,t)}var ke={lang:"en",defaultTimedEventDuration:"02:00:00",defaultAllDayEventDuration:{days:1},forceEventDuration:!1,nextDayThreshold:"09:00:00",defaultView:"month",aspectRatio:1.35,header:{left:"title",center:"",right:"today prev,next"},weekends:!0,weekNumbers:!1,weekNumberTitle:"W",weekNumberCalculation:"local",lazyFetching:!0,startParam:"start",endParam:"end",timezoneParam:"timezone",timezone:!1,titleFormat:{month:"MMMM YYYY",week:"ll",day:"LL"},columnFormat:{month:"ddd",week:n,day:"dddd"},timeFormat:{"default":i},displayEventEnd:{month:!1,basicWeek:!1,"default":!0},isRTL:!1,defaultButtonText:{prev:"prev",next:"next",prevYear:"prev year",nextYear:"next year",today:"today",month:"month",week:"week",day:"day"},buttonIcons:{prev:"left-single-arrow",next:"right-single-arrow",prevYear:"left-double-arrow",nextYear:"right-double-arrow"},theme:!1,themeButtonIcons:{prev:"circle-triangle-w",next:"circle-triangle-e",prevYear:"seek-prev",nextYear:"seek-next"},dragOpacity:.75,dragRevertDuration:500,dragScroll:!0,unselectAuto:!0,dropAccept:"*",eventLimit:!1,eventLimitText:"more",eventLimitClick:"popover",dayPopoverFormat:"LL",handleWindowResize:!0,windowResizeDelay:200},Me={en:{columnFormat:{week:"ddd M/D"},dayPopoverFormat:"dddd, MMMM D"}},Re={header:{left:"next,prev today",center:"",right:"title"},buttonIcons:{prev:"right-single-arrow",next:"left-single-arrow",prevYear:"right-double-arrow",nextYear:"left-double-arrow"},themeButtonIcons:{prev:"circle-triangle-e",next:"circle-triangle-w",nextYear:"seek-prev",prevYear:"seek-next"}},Pe=t.fullCalendar={version:"2.1.1"},ze=Pe.views={};t.fn.fullCalendar=function(e){var i=Array.prototype.slice.call(arguments,1),n=this;return this.each(function(r,o){var s,a=t(o),c=a.data("fullCalendar");"string"==typeof e?c&&t.isFunction(c[e])&&(s=c[e].apply(c,i),r||(n=s),"destroy"===e&&a.removeData("fullCalendar")):c||(c=new l(a,e),a.data("fullCalendar",c),c.render())}),n},Pe.langs=Me,Pe.datepickerLang=function(e,i,n){var r=Me[e];r||(r=Me[e]={}),o(r,{isRTL:n.isRTL,weekNumberTitle:n.weekHeader,titleFormat:{month:n.showMonthAfterYear?"YYYY["+n.yearSuffix+"] MMMM":"MMMM YYYY["+n.yearSuffix+"]"},defaultButtonText:{prev:z(n.prevText),next:z(n.nextText),today:z(n.currentText)}}),t.datepicker&&(t.datepicker.regional[i]=t.datepicker.regional[e]=n,t.datepicker.regional.en=t.datepicker.regional[""],t.datepicker.setDefaults(n))},Pe.lang=function(t,e){var i;e&&(i=Me[t],i||(i=Me[t]={}),o(i,e||{})),ke.lang=t},Pe.sourceNormalizers=[],Pe.sourceFetchers=[];var Ge={dataType:"json",cache:!1},Le=1,Ne=["sun","mon","tue","wed","thu","fri","sat"];Pe.applyAll=M;var _e=/^\s*\d{4}-\d\d$/,Ae=/^\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+)?)?)?)?)?$/;Pe.moment=function(){return N(arguments)},Pe.moment.utc=function(){var t=N(arguments,!0);return t.hasTime()&&t.utc(),t},Pe.moment.parseZone=function(){return N(arguments,!0,!0)},_.prototype=x(e.fn),_.prototype.clone=function(){return N([this])},_.prototype.time=function(t){if(null==t)return e.duration({hours:this.hours(),minutes:this.minutes(),seconds:this.seconds(),milliseconds:this.milliseconds()});delete this._ambigTime,e.isDuration(t)||e.isMoment(t)||(t=e.duration(t));var i=0;return e.isDuration(t)&&(i=24*Math.floor(t.asDays())),this.hours(i+t.hours()).minutes(t.minutes()).seconds(t.seconds()).milliseconds(t.milliseconds())},_.prototype.stripTime=function(){var t=this.toArray();return e.fn.utc.call(this),this.year(t[0]).month(t[1]).date(t[2]).hours(0).minutes(0).seconds(0).milliseconds(0),this._ambigTime=!0,this._ambigZone=!0,this},_.prototype.hasTime=function(){return!this._ambigTime},_.prototype.stripZone=function(){var t=this.toArray(),i=this._ambigTime;return e.fn.utc.call(this),this.year(t[0]).month(t[1]).date(t[2]).hours(t[3]).minutes(t[4]).seconds(t[5]).milliseconds(t[6]),i&&(this._ambigTime=!0),this._ambigZone=!0,this},_.prototype.hasZone=function(){return!this._ambigZone},_.prototype.zone=function(t){return null!=t&&(delete this._ambigTime,delete this._ambigZone),e.fn.zone.apply(this,arguments)},_.prototype.local=function(){var t=this.toArray(),i=this._ambigZone;return delete this._ambigTime,delete this._ambigZone,e.fn.local.apply(this,arguments),i&&this.year(t[0]).month(t[1]).date(t[2]).hours(t[3]).minutes(t[4]).seconds(t[5]).milliseconds(t[6]),this},_.prototype.utc=function(){return delete this._ambigTime,delete this._ambigZone,e.fn.utc.apply(this,arguments)},_.prototype.format=function(){return arguments[0]?V(this,arguments[0]):this._ambigTime?Y(this,"YYYY-MM-DD"):this._ambigZone?Y(this,"YYYY-MM-DD[T]HH:mm:ss"):Y(this)},_.prototype.toISOString=function(){return this._ambigTime?Y(this,"YYYY-MM-DD"):this._ambigZone?Y(this,"YYYY-MM-DD[T]HH:mm:ss"):e.fn.toISOString.apply(this,arguments)},_.prototype.isWithin=function(t,e){var i=A([this,t,e]);return i[0]>=i[1]&&i[0]').addClass(i.className||"").css({top:0,left:0}).append(i.content).appendTo(i.parentEl),this.el.on("click",".fc-close",function(){e.hide()}),i.autoHide&&t(document).on("mousedown",this.documentMousedownProxy=t.proxy(this,"documentMousedown"))},documentMousedown:function(e){this.el&&!t(e.target).closest(this.el).length&&this.hide()},destroy:function(){this.hide(),this.el&&(this.el.remove(),this.el=null),t(document).off("mousedown",this.documentMousedownProxy)},position:function(){var e,i,n,r,o,s=this.options,l=this.el.offsetParent().offset(),a=this.el.outerWidth(),c=this.el.outerHeight(),d=t(window),h=w(this.el);r=s.top||0,o=void 0!==s.left?s.left:void 0!==s.right?s.right-a:0,h.is(window)||h.is(document)?(h=d,e=0,i=0):(n=h.offset(),e=n.top,i=n.left),e+=d.scrollTop(),i+=d.scrollLeft(),s.viewportConstrain!==!1&&(r=Math.min(r,e+h.outerHeight()-c-this.margin),r=Math.max(r,e+this.margin),o=Math.min(o,i+h.outerWidth()-a-this.margin),o=Math.max(o,i+this.margin)),this.el.css({top:r-l.top,left:o-l.left}) +},trigger:function(t){this.options[t]&&this.options[t].apply(this,Array.prototype.slice.call(arguments,1))}},$.prototype={grid:null,rows:null,cols:null,containerEl:null,minX:null,maxX:null,minY:null,maxY:null,build:function(){this.grid.buildCoords(this.rows=[],this.cols=[]),this.computeBounds()},getCell:function(t,e){var i,n=null,r=this.rows,o=this.cols,s=-1,l=-1;if(this.inBounds(t,e)){for(i=0;r.length>i;i++)if(e>=r[i][0]&&r[i][1]>e){s=i;break}for(i=0;o.length>i;i++)if(t>=o[i][0]&&o[i][1]>t){l=i;break}s>=0&&l>=0&&(n={row:s,col:l},n.grid=this.grid,n.date=this.grid.getCellDate(n))}return n},computeBounds:function(){var t;this.containerEl&&(t=this.containerEl.offset(),this.minX=t.left,this.maxX=t.left+this.containerEl.outerWidth(),this.minY=t.top,this.maxY=t.top+this.containerEl.outerHeight())},inBounds:function(t,e){return this.containerEl?t>=this.minX&&this.maxX>t&&e>=this.minY&&this.maxY>e:!0}},q.prototype={coordMaps:null,build:function(){var t,e=this.coordMaps;for(t=0;e.length>t;t++)e[t].build()},getCell:function(t,e){var i,n=this.coordMaps,r=null;for(i=0;n.length>i&&!r;i++)r=n[i].getCell(t,e);return r}},U.prototype={coordMap:null,options:null,isListening:!1,isDragging:!1,origCell:null,origDate:null,cell:null,date:null,mouseX0:null,mouseY0:null,mousemoveProxy:null,mouseupProxy:null,scrollEl:null,scrollBounds:null,scrollTopVel:null,scrollLeftVel:null,scrollIntervalId:null,scrollHandlerProxy:null,scrollSensitivity:30,scrollSpeed:200,scrollIntervalMs:50,mousedown:function(t){D(t)&&(t.preventDefault(),this.startListening(t),this.options.distance||this.startDrag(t))},startListening:function(e){var i,n;this.isListening||(e&&this.options.scroll&&(i=w(t(e.target)),i.is(window)||i.is(document)||(this.scrollEl=i,this.scrollHandlerProxy=L(t.proxy(this,"scrollHandler"),100),this.scrollEl.on("scroll",this.scrollHandlerProxy))),this.computeCoords(),e&&(n=this.getCell(e),this.origCell=n,this.origDate=n?n.date:null,this.mouseX0=e.pageX,this.mouseY0=e.pageY),t(document).on("mousemove",this.mousemoveProxy=t.proxy(this,"mousemove")).on("mouseup",this.mouseupProxy=t.proxy(this,"mouseup")).on("selectstart",this.preventDefault),this.isListening=!0,this.trigger("listenStart",e))},computeCoords:function(){this.coordMap.build(),this.computeScrollBounds()},mousemove:function(t){var e,i;this.isDragging||(e=this.options.distance||1,i=Math.pow(t.pageX-this.mouseX0,2)+Math.pow(t.pageY-this.mouseY0,2),i>=e*e&&this.startDrag(t)),this.isDragging&&this.drag(t)},startDrag:function(t){var e;this.isListening||this.startListening(),this.isDragging||(this.isDragging=!0,this.trigger("dragStart",t),e=this.getCell(t),e&&this.cellOver(e,!0))},drag:function(t){var e;this.isDragging&&(e=this.getCell(t),K(e,this.cell)||(this.cell&&this.cellOut(),e&&this.cellOver(e)),this.dragScroll(t))},cellOver:function(t){this.cell=t,this.date=t.date,this.trigger("cellOver",t,t.date)},cellOut:function(){this.cell&&(this.trigger("cellOut",this.cell),this.cell=null,this.date=null)},mouseup:function(t){this.stopDrag(t),this.stopListening(t)},stopDrag:function(t){this.isDragging&&(this.stopScrolling(),this.trigger("dragStop",t),this.isDragging=!1)},stopListening:function(e){this.isListening&&(this.scrollEl&&(this.scrollEl.off("scroll",this.scrollHandlerProxy),this.scrollHandlerProxy=null),t(document).off("mousemove",this.mousemoveProxy).off("mouseup",this.mouseupProxy).off("selectstart",this.preventDefault),this.mousemoveProxy=null,this.mouseupProxy=null,this.isListening=!1,this.trigger("listenStop",e),this.origCell=this.cell=null,this.origDate=this.date=null)},getCell:function(t){return this.coordMap.getCell(t.pageX,t.pageY)},trigger:function(t){this.options[t]&&this.options[t].apply(this,Array.prototype.slice.call(arguments,1))},preventDefault:function(t){t.preventDefault()},computeScrollBounds:function(){var t,e=this.scrollEl;e&&(t=e.offset(),this.scrollBounds={top:t.top,left:t.left,bottom:t.top+e.outerHeight(),right:t.left+e.outerWidth()})},dragScroll:function(t){var e,i,n,r,o=this.scrollSensitivity,s=this.scrollBounds,l=0,a=0;s&&(e=(o-(t.pageY-s.top))/o,i=(o-(s.bottom-t.pageY))/o,n=(o-(t.pageX-s.left))/o,r=(o-(s.right-t.pageX))/o,e>=0&&1>=e?l=-1*e*this.scrollSpeed:i>=0&&1>=i&&(l=i*this.scrollSpeed),n>=0&&1>=n?a=-1*n*this.scrollSpeed:r>=0&&1>=r&&(a=r*this.scrollSpeed)),this.setScrollVel(l,a)},setScrollVel:function(e,i){this.scrollTopVel=e,this.scrollLeftVel=i,this.constrainScrollVel(),!this.scrollTopVel&&!this.scrollLeftVel||this.scrollIntervalId||(this.scrollIntervalId=setInterval(t.proxy(this,"scrollIntervalFunc"),this.scrollIntervalMs))},constrainScrollVel:function(){var t=this.scrollEl;0>this.scrollTopVel?0>=t.scrollTop()&&(this.scrollTopVel=0):this.scrollTopVel>0&&t.scrollTop()+t[0].clientHeight>=t[0].scrollHeight&&(this.scrollTopVel=0),0>this.scrollLeftVel?0>=t.scrollLeft()&&(this.scrollLeftVel=0):this.scrollLeftVel>0&&t.scrollLeft()+t[0].clientWidth>=t[0].scrollWidth&&(this.scrollLeftVel=0)},scrollIntervalFunc:function(){var t=this.scrollEl,e=this.scrollIntervalMs/1e3;this.scrollTopVel&&t.scrollTop(t.scrollTop()+this.scrollTopVel*e),this.scrollLeftVel&&t.scrollLeft(t.scrollLeft()+this.scrollLeftVel*e),this.constrainScrollVel(),this.scrollTopVel||this.scrollLeftVel||this.stopScrolling()},stopScrolling:function(){this.scrollIntervalId&&(clearInterval(this.scrollIntervalId),this.scrollIntervalId=null,this.computeCoords())},scrollHandler:function(){this.scrollIntervalId||this.computeCoords()}},Q.prototype={options:null,sourceEl:null,el:null,parentEl:null,top0:null,left0:null,mouseY0:null,mouseX0:null,topDelta:null,leftDelta:null,mousemoveProxy:null,isFollowing:!1,isHidden:!1,isAnimating:!1,start:function(e){this.isFollowing||(this.isFollowing=!0,this.mouseY0=e.pageY,this.mouseX0=e.pageX,this.topDelta=0,this.leftDelta=0,this.isHidden||this.updatePosition(),t(document).on("mousemove",this.mousemoveProxy=t.proxy(this,"mousemove")))},stop:function(e,i){function n(){this.isAnimating=!1,r.destroyEl(),this.top0=this.left0=null,i&&i()}var r=this,o=this.options.revertDuration;this.isFollowing&&!this.isAnimating&&(this.isFollowing=!1,t(document).off("mousemove",this.mousemoveProxy),e&&o&&!this.isHidden?(this.isAnimating=!0,this.el.animate({top:this.top0,left:this.left0},{duration:o,complete:n})):n())},getEl:function(){var t=this.el;return t||(this.sourceEl.width(),t=this.el=this.sourceEl.clone().css({position:"absolute",visibility:"",display:this.isHidden?"none":"",margin:0,right:"auto",bottom:"auto",width:this.sourceEl.width(),height:this.sourceEl.height(),opacity:this.options.opacity||"",zIndex:this.options.zIndex}).appendTo(this.parentEl)),t},destroyEl:function(){this.el&&(this.el.remove(),this.el=null)},updatePosition:function(){var t,e;this.getEl(),null===this.top0&&(this.sourceEl.width(),t=this.sourceEl.offset(),e=this.el.offsetParent().offset(),this.top0=t.top-e.top,this.left0=t.left-e.left),this.el.css({top:this.top0+this.topDelta,left:this.left0+this.leftDelta})},mousemove:function(t){this.topDelta=t.pageY-this.mouseY0,this.leftDelta=t.pageX-this.mouseX0,this.isHidden||this.updatePosition()},hide:function(){this.isHidden||(this.isHidden=!0,this.el&&this.el.hide())},show:function(){this.isHidden&&(this.isHidden=!1,this.updatePosition(),this.getEl().show())}},J.prototype={view:null,cellHtml:"
"+s+""},bookendCells:function(t,e,i){var n=this.view,r=this.getHtmlRenderer("intro",e)(i||0),o=this.getHtmlRenderer("outro",e)(i||0),s=n.opt("isRTL"),l=s?o:r,a=s?r:o;return"string"==typeof t?l+t+a:t.prepend(l).append(a)},getHtmlRenderer:function(t,e){var i,n,r,o,s=this.view;return i=t+"Html",e&&(n=e+G(t)+"Html"),n&&(o=s[n])?r=s:n&&(o=this[n])?r=this:(o=s[i])?r=s:(o=this[i])&&(r=this),"function"==typeof o?function(){return o.apply(r,arguments)||""}:function(){return o||""}}},te.prototype=x(J.prototype),t.extend(te.prototype,{el:null,coordMap:null,cellDuration:null,render:function(){this.bindHandlers()},destroy:function(){},buildCoords:function(){},getCellDate:function(){},getCellDayEl:function(){},rangeToSegs:function(){},bindHandlers:function(){var e=this;this.el.on("mousedown",function(i){t(i.target).is(".fc-event-container *, .fc-more")||t(i.target).closest(".fc-popover").length||e.dayMousedown(i)}),this.bindSegHandlers()},dayMousedown:function(t){var e,i,n,r=this,o=this.view,s=o.opt("selectable"),l=null,a=new U(this.coordMap,{scroll:o.opt("dragScroll"),dragStart:function(){o.unselect()},cellOver:function(t,o){a.origDate&&(n=r.getCellDayEl(t),l=[o,a.origDate].sort(H),e=l[0],i=l[1].clone().add(r.cellDuration),s&&r.renderSelection(e,i))},cellOut:function(){l=null,r.destroySelection()},listenStop:function(t){l&&(l[0].isSame(l[1])&&o.trigger("dayClick",n[0],e,t),s&&o.reportSelection(e,i,t))}});a.mousedown(t)},renderDrag:function(){},destroyDrag:function(){},renderResize:function(){},destroyResize:function(){},renderRangeHelper:function(t,e,i){var n,r=this.view;!e&&r.opt("forceEventDuration")&&(e=r.calendar.getDefaultEventEnd(!t.hasTime(),t)),n=i?x(i.event):{},n.start=t,n.end=e,n.allDay=!(t.hasTime()||e&&e.hasTime()),n.className=(n.className||[]).concat("fc-helper"),i||(n.editable=!1),this.renderHelper(n,i)},renderHelper:function(){},destroyHelper:function(){},renderSelection:function(t,e){this.renderHighlight(t,e)},destroySelection:function(){this.destroyHighlight()},renderHighlight:function(){},destroyHighlight:function(){},headHtml:function(){return'
'+"
'),e>t&&(n+=''),i>e&&(n+=''),n=this.bookendCells(n,"highlight"),'
'+n+""+"
"+"
"}}),t.extend(ee.prototype,{segs:null,rowStructs:null,renderEvents:function(e){var i=this.rowStructs=this.renderEventRows(e),n=[];this.rowEls.each(function(e,r){t(r).find(".fc-content-skeleton > table").append(i[e].tbodyEl),n.push.apply(n,i[e].segs)}),this.segs=n},getSegs:function(){return(this.segs||[]).concat(this.popoverSegs||[])},destroyEvents:function(){var t,e;for(J.prototype.destroyEvents.call(this),t=this.rowStructs||[];e=t.pop();)e.tbodyEl.remove();this.segs=null,this.destroySegPopover()},renderEventRows:function(t){var e,i,n=this.eventsToSegs(t),r=[];for(n=this.renderSegs(n),e=this.groupSegRows(n),i=0;e.length>i;i++)r.push(this.renderEventRow(i,e[i]));return r},renderSegHtml:function(t,e){var i,n=this.view,r=n.opt("isRTL"),o=t.event,s=n.isEventDraggable(o),l=!e&&o.allDay&&t.isEnd&&n.isEventResizable(o),a=this.getSegClasses(t,s,l),c=this.getEventSkinCss(o),d="";return a.unshift("fc-day-grid-event"),!o.allDay&&t.isStart&&(d=''+R(n.getEventTimeText(o))+""),i=''+(R(o.title||"")||" ")+"",'"+'
'+(r?i+" "+d:d+" "+i)+"
"+(l?'
':"")+""},renderEventRow:function(e,i){function n(e){for(;e>s;)d=(y[r-1]||[])[s],d?d.attr("rowspan",parseInt(d.attr("rowspan")||1,10)+1):(d=t("
"),l.append(d)),v[r][s]=d,y[r][s]=d,s++}var r,o,s,l,a,c,d,h=this.view,u=h.colCnt,f=this.buildSegLevels(i),g=Math.max(1,f.length),p=t("
').append(c.el),c.leftCol!=c.rightCol?d.attr("colspan",c.rightCol-c.leftCol+1):y[r][s]=d;c.rightCol>=s;)v[r][s]=d,m[r][s]=c,s++;l.append(d)}n(u),this.bookendCells(l,"eventSkeleton"),p.append(l)}return{row:e,tbodyEl:p,cellMatrix:v,segMatrix:m,segLevels:f,segs:i}},buildSegLevels:function(t){var e,i,n,r=[];for(t.sort(te),e=0;t.length>e;e++){for(i=t[e],n=0;r.length>n&&ie(i,r[n]);n++);i.level=n,(r[n]||(r[n]=[])).push(i)}for(n=0;r.length>n;n++)r[n].sort(ne);return r},groupSegRows:function(t){var e,i=this.view,n=[];for(e=0;i.rowCnt>e;e++)n.push([]);for(e=0;t.length>e;e++)n[t[e].row].push(t[e]);return n}}),t.extend(ee.prototype,{segPopover:null,popoverSegs:null,destroySegPopover:function(){this.segPopover&&this.segPopover.hide()},limitRows:function(t){var e,i,n=this.rowStructs||[];for(e=0;n.length>e;e++)this.unlimitRow(e),i=t?"number"==typeof t?t:this.computeRowLevelLimit(e):!1,i!==!1&&this.limitRow(e,i)},computeRowLevelLimit:function(t){var e,i,n=this.rowEls.eq(t),r=n.height(),o=this.rowStructs[t].tbodyEl.children();for(e=0;o.length>e;e++)if(i=o.eq(e).removeClass("fc-limited"),i.position().top+i.outerHeight()>r)return e;return!1},limitRow:function(e,i){function n(n){for(;n>T;)r={row:e,col:T},d=E.getCellSegs(r,i),d.length&&(f=s[i-1][T],w=E.renderMoreLink(r,d),y=t("
").append(w),f.append(y),D.push(y[0])),T++}var r,o,s,l,a,c,d,h,u,f,g,p,m,v,y,w,E=this,b=this.view,S=this.rowStructs[e],D=[],T=0;if(i&&S.segLevels.length>i){for(o=S.segLevels[i-1],s=S.cellMatrix,l=S.tbodyEl.children().slice(i).addClass("fc-limited").get(),a=0;o.length>a;a++){for(c=o[a],n(c.leftCol),u=[],h=0;c.rightCol>=T;)r={row:e,col:T},d=this.getCellSegs(r,i),u.push(d),h+=d.length,T++;if(h){for(f=s[i-1][c.leftCol],g=f.attr("rowspan")||1,p=[],m=0;u.length>m;m++)v=t('
').attr("rowspan",g),d=u[m],r={row:e,col:c.leftCol+m},w=this.renderMoreLink(r,[c].concat(d)),y=t("
").append(w),v.append(y),p.push(v[0]),D.push(v[0]);f.addClass("fc-limited").after(t(p)),l.push(f[0])}}n(b.colCnt),S.moreEls=t(D),S.limitedEls=t(l)}},unlimitRow:function(t){var e=this.rowStructs[t];e.moreEls&&(e.moreEls.remove(),e.moreEls=null),e.limitedEls&&(e.limitedEls.removeClass("fc-limited"),e.limitedEls=null)},renderMoreLink:function(e,i){var n=this,r=this.view;return t('').text(this.getMoreLinkText(i.length)).on("click",function(o){var s=r.opt("eventLimitClick"),l=r.cellToDate(e),a=t(this),c=n.getCellDayEl(e),d=n.getCellSegs(e),h=n.resliceDaySegs(d,l),u=n.resliceDaySegs(i,l);"function"==typeof s&&(s=r.trigger("eventLimitClick",null,{date:l,dayEl:c,moreEl:a,segs:h,hiddenSegs:u},o)),"popover"===s?n.showSegPopover(l,e,a,h):"string"==typeof s&&r.calendar.zoomTo(l,s)})},showSegPopover:function(t,e,i,n){var r,o,s=this,l=this.view,a=i.parent();r=1==l.rowCnt?this.view.el:this.rowEls.eq(e.row),o={className:"fc-more-popover",content:this.renderSegPopoverContent(t,n),parentEl:this.el,top:r.offset().top,autoHide:!0,viewportConstrain:l.opt("popoverViewportConstrain"),hide:function(){s.segPopover.destroy(),s.segPopover=null,s.popoverSegs=null}},l.opt("isRTL")?o.right=a.offset().left+a.outerWidth()+1:o.left=a.offset().left-1,this.segPopover=new j(o),this.segPopover.show()},renderSegPopoverContent:function(e,i){var n,r=this.view,o=r.opt("theme"),s=e.format(r.opt("dayPopoverFormat")),l=t('
'+''+''+R(s)+""+'
'+"
"+'
'+'
'+"
"),a=l.find(".fc-event-container");for(i=this.renderSegs(i,!0),this.popoverSegs=i,n=0;i.length>n;n++)i[n].cellDate=e,a.append(i[n].el);return l},resliceDaySegs:function(e,i){var n=t.map(e,function(t){return t.event}),r=i.clone().stripTime(),o=r.clone().add(1,"days");return this.eventsToSegs(n,r,o)},getMoreLinkText:function(t){var e=this.view,i=e.opt("eventLimitText");return"function"==typeof i?i(t):"+"+t+" "+i},getCellSegs:function(t,e){for(var i,n=this.rowStructs[t.row].segMatrix,r=e||0,o=[];n.length>r;)i=n[r][t.col],i&&o.push(i),r++;return o}}),re.prototype=H(J.prototype),t.extend(re.prototype,{slotDuration:null,snapDuration:null,minTime:null,maxTime:null,dayEls:null,slatEls:null,slatTops:null,highlightEl:null,helperEl:null,render:function(){this.processOptions(),this.el.html(this.renderHtml()),this.dayEls=this.el.find(".fc-day"),this.slatEls=this.el.find(".fc-slats tr"),this.computeSlatTops(),J.prototype.render.call(this)},renderHtml:function(){return'
'+this.rowHtml("slotBg")+"
"+"
"+'
'+""+this.slatRowHtml()+"
"+"
"},slotBgCellHtml:function(t,e,i){return this.bgCellHtml(t,e,i)},slatRowHtml:function(){for(var t,i,n,r=this.view,o=r.calendar,s=r.opt("isRTL"),l="",a=0===this.slotDuration.asMinutes()%15,c=e.duration(+this.minTime);this.maxTime>c;)t=r.start.clone().time(c),i=t.minutes(),n='
"+(a&&i?"":""+R(o.formatDate(t,r.opt("axisFormat")))+"")+"
'+(s?n:"")+"
',d=n.col),r=l.cellToDate(0,d),o=this.computeDateTop(n.start,r),s=this.computeDateTop(n.end,r),c+='
'+"
"+"
'),c=this.bookendCells(c,"highlight"),'
'+c+""+"
"+"
"}}),t.extend(re.prototype,{segs:null,eventSkeletonEl:null,renderEvents:function(e){var i=this.renderEventTable(e);this.eventSkeletonEl=t('
').append(i.tableEl),this.el.append(this.eventSkeletonEl),this.segs=i.segs},getSegs:function(){return this.segs||[]},destroyEvents:function(){J.prototype.destroyEvents.call(this),this.eventSkeletonEl&&(this.eventSkeletonEl.remove(),this.eventSkeletonEl=null),this.segs=null},renderEventTable:function(e){var i,n,r,o,s,l,a=t("
"),c=a.find("tr"),d=this.eventsToSegs(e);for(d=this.renderSegs(d),i=this.groupSegCols(d),this.computeSegVerticals(d),o=0;i.length>o;o++){for(s=i[o],oe(s),l=t('
'),n=0;s.length>n;n++)r=s[n],r.el.css(this.generateSegPositionCss(r)),30>r.bottom-r.top&&r.el.addClass("fc-short"),l.append(r.el);c.append(t("
").append(l))}return this.bookendCells(c,"eventSkeleton"),{tableEl:a,segs:d}},updateSegVerticals:function(){var t,e=this.segs;if(e)for(this.computeSegVerticals(e),t=0;e.length>t;t++)e[t].el.css(this.generateSegVerticalCss(e[t]))},computeSegVerticals:function(t){var e,i;for(e=0;t.length>e;e++)i=t[e],i.top=this.computeDateTop(i.start,i.start),i.bottom=this.computeDateTop(i.end,i.start)},renderSegHtml:function(t,e){var i,n,r,o=this.view,s=t.event,l=o.isEventDraggable(s),a=!e&&t.isEnd&&o.isEventResizable(s),c=this.getSegClasses(t,l,a),d=this.getEventSkinCss(s);return c.unshift("fc-time-grid-event"),o.isMultiDayEvent(s)?(t.isStart||t.isEnd)&&(i=o.getEventTimeText(t.start,t.end),n=o.getEventTimeText(t.start,t.end,"LT"),r=o.getEventTimeText(t.start,null)):(i=o.getEventTimeText(s),n=o.getEventTimeText(s,"LT"),r=o.getEventTimeText(s.start,null)),'"+'
'+(i?'
"+""+R(i)+""+"
":"")+(s.title?'
'+R(s.title)+"
":"")+"
"+'
'+(a?'
':"")+""},generateSegPositionCss:function(t){var e,i,n=this.view,r=n.opt("isRTL"),o=n.opt("slotEventOverlap"),s=t.backwardCoord,l=t.forwardCoord,a=this.generateSegVerticalCss(t);return o&&(l=Math.min(1,s+2*(l-s))),r?(e=1-l,i=s):(e=s,i=1-l),a.zIndex=t.level+1,a.left=100*e+"%",a.right=100*i+"%",o&&t.forwardPressure&&(a[r?"marginLeft":"marginRight"]=20),a},generateSegVerticalCss:function(t){return{top:t.top,bottom:-t.bottom}},groupSegCols:function(t){var e,i=this.view,n=[];for(e=0;i.colCnt>e;e++)n.push([]);for(e=0;t.length>e;e++)n[t[e].col].push(t[e]);return n}}),fe.prototype={calendar:null,coordMap:null,el:null,start:null,end:null,intervalStart:null,intervalEnd:null,rowCnt:null,colCnt:null,isSelected:!1,scrollerEl:null,scrollTop:null,widgetHeaderClass:null,widgetContentClass:null,highlightStateClass:null,documentMousedownProxy:null,documentDragStartProxy:null,init:function(){var e=this.opt("theme")?"ui":"fc";this.widgetHeaderClass=e+"-widget-header",this.widgetContentClass=e+"-widget-content",this.highlightStateClass=e+"-state-highlight",this.documentMousedownProxy=t.proxy(this,"documentMousedown"),this.documentDragStartProxy=t.proxy(this,"documentDragStart")},render:function(){this.updateSize(),this.trigger("viewRender",this,this,this.el),t(document).on("mousedown",this.documentMousedownProxy).on("dragstart",this.documentDragStartProxy)},destroy:function(){this.unselect(),this.trigger("viewDestroy",this,this,this.el),this.destroyEvents(),this.el.empty(),t(document).off("mousedown",this.documentMousedownProxy).off("dragstart",this.documentDragStartProxy)},incrementDate:function(){},updateSize:function(t){t&&this.recordScroll(),this.updateHeight(),this.updateWidth()},updateWidth:function(){},updateHeight:function(){var t=this.calendar;this.setHeight(t.getSuggestedViewHeight(),t.isHeightAuto())},setHeight:function(){},computeScrollerHeight:function(t){var e,i=this.el.add(this.scrollerEl);return i.css({position:"relative",left:-1}),e=this.el.outerHeight()-this.scrollerEl.height(),i.css({position:"",left:""}),t-e},recordScroll:function(){this.scrollerEl&&(this.scrollTop=this.scrollerEl.scrollTop())},restoreScroll:function(){null!==this.scrollTop&&this.scrollerEl.scrollTop(this.scrollTop)},renderEvents:function(){this.segEach(function(t){this.trigger("eventAfterRender",t.event,t.event,t.el)}),this.trigger("eventAfterAllRender")},destroyEvents:function(){this.segEach(function(t){this.trigger("eventDestroy",t.event,t.event,t.el)})},resolveEventEl:function(e,i){var n=this.trigger("eventRender",e,e,i);return n===!1?i=null:n&&n!==!0&&(i=t(n)),i},showEvent:function(t){this.segEach(function(t){t.el.css("visibility","")},t)},hideEvent:function(t){this.segEach(function(t){t.el.css("visibility","hidden")},t)},segEach:function(t,e){var i,n=this.getSegs();for(i=0;n.length>i;i++)e&&n[i].event._id!==e._id||t.call(this,n[i])},getSegs:function(){},renderDrag:function(){},destroyDrag:function(){},documentDragStart:function(e){var i,n=this,r=null;this.opt("droppable")&&(i=new q(this.coordMap,{cellOver:function(t,e){r=e,n.renderDrag(e)},cellOut:function(){r=null,n.destroyDrag()}}),t(document).one("dragstop",function(t,e){n.destroyDrag(),r&&n.trigger("drop",t.target,r,t,e)}),i.startDrag(e))},select:function(t,e,i){this.unselect(i),this.renderSelection(t,e),this.reportSelection(t,e,i)},renderSelection:function(){},reportSelection:function(t,e,i){this.isSelected=!0,this.trigger("select",null,t,e,i)},unselect:function(t){this.isSelected&&(this.isSelected=!1,this.destroySelection(),this.trigger("unselect",null,t))},destroySelection:function(){},documentMousedown:function(e){var i;this.isSelected&&this.opt("unselectAuto")&&E(e)&&(i=this.opt("unselectCancel"),i&&t(e.target).closest(i).length||this.unselect(e))}},ge.prototype=H(fe.prototype),t.extend(ge.prototype,{dayGrid:null,dayNumbersVisible:!1,weekNumbersVisible:!1,weekNumberWidth:null,headRowEl:null,render:function(t,e,i){this.rowCnt=t,this.colCnt=e,this.dayNumbersVisible=i,this.weekNumbersVisible=this.opt("weekNumbers"),this.dayGrid.numbersVisible=this.dayNumbersVisible||this.weekNumbersVisible,this.el.addClass("fc-basic-view").html(this.renderHtml()),this.headRowEl=this.el.find("thead .fc-row"),this.scrollerEl=this.el.find(".fc-day-grid-container"),this.dayGrid.coordMap.containerEl=this.scrollerEl,this.dayGrid.el=this.el.find(".fc-day-grid"),this.dayGrid.render(this.hasRigidRows()),fe.prototype.render.call(this)},destroy:function(){this.dayGrid.destroy(),fe.prototype.destroy.call(this)},renderHtml:function(){return'"+""+""+""+""+'"+""+""+"
'+this.dayGrid.headHtml()+"
'+'
'+'
'+"
"+"
" -},headIntroHtml:function(){return this.weekNumbersVisible?'
"+""+R(this.opt("weekNumberTitle"))+""+""+""+this.calendar.calculateWeekNumber(this.cellToDate(t,0))+""+"'+i.date()+""},weekNumberStyleAttr:function(){return null!==this.weekNumberWidth?'style="width:'+this.weekNumberWidth+'px"':""},hasRigidRows:function(){var t=this.opt("eventLimit");return t&&"number"!=typeof t},updateWidth:function(){this.weekNumbersVisible&&(this.weekNumberWidth=p(this.el.find(".fc-week-number")))},setHeight:function(t,e){var i,n=this.opt("eventLimit");v(this.scrollerEl),u(this.headRowEl),this.dayGrid.destroySegPopover(),n&&"number"==typeof n&&this.dayGrid.limitRows(n),i=this.computeScrollerHeight(t),this.setGridHeight(i,e),n&&"number"!=typeof n&&this.dayGrid.limitRows(n),!e&&m(this.scrollerEl,i)&&(h(this.headRowEl,w(this.scrollerEl)),i=this.computeScrollerHeight(t),this.scrollerEl.height(i),this.restoreScroll())},setGridHeight:function(t,e){e?g(this.dayGrid.rowEls):f(this.dayGrid.rowEls,t,!0)},renderEvents:function(t){this.dayGrid.renderEvents(t),this.updateHeight(),fe.prototype.renderEvents.call(this,t)},getSegs:function(){return this.dayGrid.getSegs()},destroyEvents:function(){fe.prototype.destroyEvents.call(this),this.recordScroll(),this.dayGrid.destroyEvents()},renderDrag:function(t,e,i){return this.dayGrid.renderDrag(t,e,i)},destroyDrag:function(){this.dayGrid.destroyDrag()},renderSelection:function(t,e){this.dayGrid.renderSelection(t,e)},destroySelection:function(){this.dayGrid.destroySelection()}}),r({fixedWeekCount:!0}),xe.month=pe,pe.prototype=H(ge.prototype),t.extend(pe.prototype,{name:"month",incrementDate:function(t,e){return t.clone().stripTime().add(e,"months").startOf("month")},render:function(t){var e;this.intervalStart=t.clone().stripTime().startOf("month"),this.intervalEnd=this.intervalStart.clone().add(1,"months"),this.start=this.intervalStart.clone(),this.start=this.skipHiddenDays(this.start),this.start.startOf("week"),this.start=this.skipHiddenDays(this.start),this.end=this.intervalEnd.clone(),this.end=this.skipHiddenDays(this.end,-1,!0),this.end.add((7-this.end.weekday())%7,"days"),this.end=this.skipHiddenDays(this.end,-1,!0),e=Math.ceil(this.end.diff(this.start,"weeks",!0)),this.isFixedWeeks()&&(this.end.add(6-e,"weeks"),e=6),this.title=this.calendar.formatDate(this.intervalStart,this.opt("titleFormat")),ge.prototype.render.call(this,e,this.getCellsPerWeek(),!0)},setGridHeight:function(t,e){e=e||"variable"===this.opt("weekMode"),e&&(t*=this.rowCnt/6),f(this.dayGrid.rowEls,t,!e)},isFixedWeeks:function(){var t=this.opt("weekMode");return t?"fixed"===t:this.opt("fixedWeekCount")}}),xe.basicWeek=me,me.prototype=H(ge.prototype),t.extend(me.prototype,{name:"basicWeek",incrementDate:function(t,e){return t.clone().stripTime().add(e,"weeks").startOf("week")},render:function(t){this.intervalStart=t.clone().stripTime().startOf("week"),this.intervalEnd=this.intervalStart.clone().add(1,"weeks"),this.start=this.skipHiddenDays(this.intervalStart),this.end=this.skipHiddenDays(this.intervalEnd,-1,!0),this.title=this.calendar.formatRange(this.start,this.end.clone().subtract(1),this.opt("titleFormat")," — "),ge.prototype.render.call(this,1,this.getCellsPerWeek(),!1)}}),xe.basicDay=ve,ve.prototype=H(ge.prototype),t.extend(ve.prototype,{name:"basicDay",incrementDate:function(t,e){var i=t.clone().stripTime().add(e,"days");return i=this.skipHiddenDays(i,0>e?-1:1)},render:function(t){this.start=this.intervalStart=t.clone().stripTime(),this.end=this.intervalEnd=this.start.clone().add(1,"days"),this.title=this.calendar.formatDate(this.start,this.opt("titleFormat")),ge.prototype.render.call(this,1,1,!1)}}),r({allDaySlot:!0,allDayText:"all-day",scrollTime:"06:00:00",slotDuration:"00:30:00",axisFormat:ye,timeFormat:{agenda:we},minTime:"00:00:00",maxTime:"24:00:00",slotEventOverlap:!0});var Ye=5;Ee.prototype=H(fe.prototype),t.extend(Ee.prototype,{timeGrid:null,dayGrid:null,axisWidth:null,noScrollRowEls:null,bottomRuleEl:null,bottomRuleHeight:null,render:function(e){this.rowCnt=1,this.colCnt=e,this.el.addClass("fc-agenda-view").html(this.renderHtml()),this.scrollerEl=this.el.find(".fc-time-grid-container"),this.timeGrid.coordMap.containerEl=this.scrollerEl,this.timeGrid.el=this.el.find(".fc-time-grid"),this.timeGrid.render(),this.bottomRuleEl=t('
').appendTo(this.timeGrid.el),this.dayGrid&&(this.dayGrid.el=this.el.find(".fc-day-grid"),this.dayGrid.render(),this.dayGrid.bottomCoordPadding=this.dayGrid.el.next("hr").outerHeight()),this.noScrollRowEls=this.el.find(".fc-row:not(.fc-scroller *)"),fe.prototype.render.call(this),this.resetScroll()},destroy:function(){this.timeGrid.destroy(),this.dayGrid&&this.dayGrid.destroy(),fe.prototype.destroy.call(this)},renderHtml:function(){return'"+""+""+""+""+'"+""+""+"
'+this.timeGrid.headHtml()+"
'+(this.dayGrid?'

':"")+'
'+'
'+"
"+"
"},headIntroHtml:function(){var t,e,i,n;return this.opt("weekNumbers")?(t=this.cellToDate(0,0),e=this.calendar.calculateWeekNumber(t),i=this.opt("weekNumberTitle"),n=this.opt("isRTL")?e+i:i+e,'
"+""+R(n)+""+""+""+(this.opt("allDayHtml")||R(this.opt("allDayText")))+""+"",rowHtml:function(t,e){var i,n,r=this.view,o=this.getHtmlRenderer("cell",t),s="";for(e=e||0,i=0;r.colCnt>i;i++)n=r.cellToDate(e,i),s+=o(e,i,n);return s=this.bookendCells(s,t,e),"
"+""+this.rowHtml("head")+""+"
"+"
"},headCellHtml:function(t,e,i){var n=this.view,r=n.calendar,o=n.opt("columnFormat");return''+P(r.formatDate(i,o))+""},bgCellHtml:function(t,e,i){var n=this.view,r=this.getDayClasses(i);return r.unshift("fc-day",n.widgetContentClass),''},getDayClasses:function(t){var e=this.view,i=e.calendar.getNow().stripTime(),n=["fc-"+Ne[t.day()]];return"month"===e.name&&t.month()!=e.intervalStart.month()&&n.push("fc-other-month"),t.isSame(i,"day")?n.push("fc-today",e.highlightStateClass):i>t?n.push("fc-past"):n.push("fc-future"),n}}),t.extend(te.prototype,{mousedOverSeg:null,isDraggingSeg:!1,isResizingSeg:!1,renderEvents:function(){},getSegs:function(){},destroyEvents:function(){this.triggerSegMouseout()},renderSegs:function(e,i){var n,r=this.view,o="",s=[];for(n=0;e.length>n;n++)o+=this.renderSegHtml(e[n],i);return t(o).each(function(i,n){var o=e[i],l=r.resolveEventEl(o.event,t(n));l&&(l.data("fc-seg",o),o.el=l,s.push(o))}),s},renderSegHtml:function(){},eventsToSegs:function(e,i,n){var r=this;return t.map(e,function(t){return r.eventToSegs(t,i,n)})},eventToSegs:function(t,e,i){var n,r,o,s=t.start.clone().stripZone(),l=this.view.calendar.getEventEnd(t).stripZone();for(e&&i?(o=S(s,l,e,i),n=o?[o]:[]):n=this.rangeToSegs(s,l),r=0;n.length>r;r++)o=n[r],o.event=t,o.eventStartMS=+s,o.eventDurationMS=l-s;return n},bindSegHandlers:function(){var e=this,i=this.view;t.each({mouseenter:function(t,i){e.triggerSegMouseover(t,i)},mouseleave:function(t,i){e.triggerSegMouseout(t,i)},click:function(t,e){return i.trigger("eventClick",this,t.event,e)},mousedown:function(n,r){t(r.target).is(".fc-resizer")&&i.isEventResizable(n.event)?e.segResizeMousedown(n,r):i.isEventDraggable(n.event)&&e.segDragMousedown(n,r)}},function(i,n){e.el.on(i,".fc-event-container > *",function(i){var r=t(this).data("fc-seg");return!r||e.isDraggingSeg||e.isResizingSeg?void 0:n.call(this,r,i)})})},triggerSegMouseover:function(t,e){this.mousedOverSeg||(this.mousedOverSeg=t,this.view.trigger("eventMouseover",t.el[0],t.event,e))},triggerSegMouseout:function(t,e){e=e||{},this.mousedOverSeg&&(t=t||this.mousedOverSeg,this.mousedOverSeg=null,this.view.trigger("eventMouseout",t.el[0],t.event,e))},segDragMousedown:function(t,e){var i,n,r=this,o=this.view,s=t.el,l=t.event,a=new Q(t.el,{parentEl:o.el,opacity:o.opt("dragOpacity"),revertDuration:o.opt("dragRevertDuration"),zIndex:2}),c=new U(o.coordMap,{distance:5,scroll:o.opt("dragScroll"),listenStart:function(t){a.hide(),a.start(t)},dragStart:function(e){r.triggerSegMouseout(t,e),r.isDraggingSeg=!0,o.hideEvent(l),o.trigger("eventDragStart",s[0],l,e,{})},cellOver:function(e,s){var l=t.cellDate||c.origDate,d=r.computeDraggedEventDates(t,l,s);i=d.start,n=d.end,o.renderDrag(i,n,t)?a.hide():a.show()},cellOut:function(){i=null,o.destroyDrag(),a.show()},dragStop:function(t){var e=i&&!i.isSame(l.start);a.stop(!e,function(){r.isDraggingSeg=!1,o.destroyDrag(),o.showEvent(l),o.trigger("eventDragStop",s[0],l,t,{}),e&&o.eventDrop(s[0],l,i,t)})},listenStop:function(){a.stop()}});c.mousedown(e)},computeDraggedEventDates:function(t,e,i){var n,r,o,s=this.view,l=t.event,a=l.start,c=s.calendar.getEventEnd(l);return i.hasTime()===e.hasTime()?(n=T(i,e),r=a.clone().add(n),o=null===l.end?null:c.clone().add(n)):(r=i,o=null),{start:r,end:o}},segResizeMousedown:function(t,e){function i(){r.destroyResize(),o.showEvent(l)}var n,r=this,o=this.view,s=t.el,l=t.event,a=l.start,c=o.calendar.getEventEnd(l),d=null;n=new U(this.coordMap,{distance:5,scroll:o.opt("dragScroll"),dragStart:function(e){r.triggerSegMouseout(t,e),r.isResizingSeg=!0,o.trigger("eventResizeStart",s[0],l,e,{})},cellOver:function(e,n){n.isBefore(a)&&(n=a),d=n.clone().add(r.cellDuration),d.isSame(c)?(d=null,i()):(r.renderResize(a,d,t),o.hideEvent(l))},cellOut:function(){d=null,i()},dragStop:function(t){r.isResizingSeg=!1,i(),o.trigger("eventResizeStop",s[0],l,t,{}),d&&o.eventResize(s[0],l,d,t)}}),n.mousedown(e)},getSegClasses:function(t,e,i){var n=t.event,r=["fc-event",t.isStart?"fc-start":"fc-not-start",t.isEnd?"fc-end":"fc-not-end"].concat(n.className,n.source?n.source.className:[]);return e&&r.push("fc-draggable"),i&&r.push("fc-resizable"),r},getEventSkinCss:function(t){var e=this.view,i=t.source||{},n=t.color,r=i.color,o=e.opt("eventColor"),s=t.backgroundColor||n||i.backgroundColor||r||e.opt("eventBackgroundColor")||o,l=t.borderColor||n||i.borderColor||r||e.opt("eventBorderColor")||o,a=t.textColor||i.textColor||e.opt("eventTextColor"),c=[];return s&&c.push("background-color:"+s),l&&c.push("border-color:"+l),a&&c.push("color:"+a),c.join(";")}}),ie.prototype=x(te.prototype),t.extend(ie.prototype,{numbersVisible:!1,cellDuration:e.duration({days:1}),bottomCoordPadding:0,rowEls:null,dayEls:null,helperEls:null,highlightEls:null,render:function(e){var i,n=this.view,r="";for(i=0;n.rowCnt>i;i++)r+=this.dayRowHtml(i,e);this.el.html(r),this.rowEls=this.el.find(".fc-row"),this.dayEls=this.el.find(".fc-day"),this.dayEls.each(function(e,i){var r=n.cellToDate(Math.floor(e/n.colCnt),e%n.colCnt);n.trigger("dayRender",null,r,t(i))}),te.prototype.render.call(this)},destroy:function(){this.destroySegPopover()},dayRowHtml:function(t,e){var i=this.view,n=["fc-row","fc-week",i.widgetContentClass];return e&&n.push("fc-rigid"),'
'+'
'+""+this.rowHtml("day",t)+"
"+"
"+'
'+""+(this.numbersVisible?""+this.rowHtml("number",t)+"":"")+"
"+"
"+"
"},dayCellHtml:function(t,e,i){return this.bgCellHtml(t,e,i)},buildCoords:function(e,i){var n,r,o,s=this.view.colCnt;this.dayEls.slice(0,s).each(function(e,s){n=t(s),r=n.offset().left,e&&(o[1]=r),o=[r],i[e]=o}),o[1]=r+n.outerWidth(),this.rowEls.each(function(i,s){n=t(s),r=n.offset().top,i&&(o[1]=r),o=[r],e[i]=o}),o[1]=r+n.outerHeight()+this.bottomCoordPadding},getCellDate:function(t){return this.view.cellToDate(t)},getCellDayEl:function(t){return this.dayEls.eq(t.row*this.view.colCnt+t.col)},rangeToSegs:function(t,e){return this.view.rangeToSegments(t,e)},renderDrag:function(t,e,i){var n;return this.renderHighlight(t,e||this.view.calendar.getDefaultEventEnd(!0,t)),i&&!i.el.closest(this.el).length?(this.renderRangeHelper(t,e,i),n=this.view.opt("dragOpacity"),void 0!==n&&this.helperEls.css("opacity",n),!0):void 0},destroyDrag:function(){this.destroyHighlight(),this.destroyHelper()},renderResize:function(t,e,i){this.renderHighlight(t,e),this.renderRangeHelper(t,e,i)},destroyResize:function(){this.destroyHighlight(),this.destroyHelper()},renderHelper:function(e,i){var n=[],r=this.renderEventRows([e]);this.rowEls.each(function(e,o){var s,l=t(o),a=t('
');s=i&&i.row===e?i.el.position().top:l.find(".fc-content-skeleton tbody").position().top,a.css("top",s).find("table").append(r[e].tbodyEl),l.append(a),n.push(a[0])}),this.helperEls=t(n)},destroyHelper:function(){this.helperEls&&(this.helperEls.remove(),this.helperEls=null)},renderHighlight:function(e,i){var n,r,o,s=this.rangeToSegs(e,i),l=[];for(n=0;s.length>n;n++)r=s[n],o=t(this.highlightSkeletonHtml(r.leftCol,r.rightCol+1)),o.appendTo(this.rowEls[r.row]),l.push(o[0]);this.highlightEls=t(l)},destroyHighlight:function(){this.highlightEls&&(this.highlightEls.remove(),this.highlightEls=null)},highlightSkeletonHtml:function(t,e){var i=this.view.colCnt,n="";return t>0&&(n+='"),m=[],v=[],y=[];for(r=0;p>r;r++){if(o=f[r],s=0,l=t(""),m.push([]),v.push([]),y.push([]),o)for(a=0;o.length>a;a++){for(c=o[a],n(c.leftCol),d=t('",l+=""+(s?"":n)+'",c.add(this.slotDuration);return l},processOptions:function(){var t=this.view,i=t.opt("slotDuration"),n=t.opt("snapDuration");i=e.duration(i),n=n?e.duration(n):i,this.slotDuration=i,this.snapDuration=n,this.cellDuration=n,this.minTime=e.duration(t.opt("minTime")),this.maxTime=e.duration(t.opt("maxTime"))},rangeToSegs:function(t,e){var i,n,r,o,s,l=this.view,a=[];for(t=t.clone().stripZone(),e=e.clone().stripZone(),n=0;l.colCnt>n;n++)r=l.cellToDate(0,n),o=r.clone().time(this.minTime),s=r.clone().time(this.maxTime),i=S(t,e,o,s),i&&(i.col=n,a.push(i));return a},resize:function(){this.computeSlatTops(),this.updateSegVerticals()},buildCoords:function(i,n){var r,o,s=this.view.colCnt,l=this.el.offset().top,a=e.duration(+this.minTime),c=null;for(this.dayEls.slice(0,s).each(function(e,i){r=t(i),o=r.offset().left,c&&(c[1]=o),c=[o],n[e]=c}),c[1]=o+r.outerWidth(),c=null;this.maxTime>a;)o=l+this.computeTimeTop(a),c&&(c[1]=o),c=[o],i.push(c),a.add(this.snapDuration);c[1]=l+this.computeTimeTop(a)},getCellDate:function(t){var e=this.view,i=e.calendar;return i.rezoneDate(e.cellToDate(0,t.col).time(this.minTime+this.snapDuration*t.row))},getCellDayEl:function(t){return this.dayEls.eq(t.col)},computeDateTop:function(t,i){return this.computeTimeTop(e.duration(t.clone().stripZone()-i.clone().stripTime()))},computeTimeTop:function(t){var e,i,n,r,o=(t-this.minTime)/this.slotDuration;return o=Math.max(0,o),o=Math.min(this.slatEls.length,o),e=Math.floor(o),i=o-e,n=this.slatTops[e],i?(r=this.slatTops[e+1],n+(r-n)*i):n},computeSlatTops:function(){var e,i=[];this.slatEls.each(function(n,r){e=t(r).position().top,i.push(e)}),i.push(e+this.slatEls.last().outerHeight()),this.slatTops=i},renderDrag:function(t,e,i){var n;return i?(this.renderRangeHelper(t,e,i),n=this.view.opt("dragOpacity"),void 0!==n&&this.helperEl.css("opacity",n),!0):(this.renderHighlight(t,e||this.view.calendar.getDefaultEventEnd(!1,t)),void 0)},destroyDrag:function(){this.destroyHelper(),this.destroyHighlight()},renderResize:function(t,e,i){this.renderRangeHelper(t,e,i)},destroyResize:function(){this.destroyHelper()},renderHelper:function(e,i){var n,r,o,s=this.renderEventTable([e]),l=s.tableEl,a=s.segs;for(n=0;a.length>n;n++)r=a[n],i&&i.col===r.col&&(o=i.el,r.el.css({left:o.css("left"),right:o.css("right"),"margin-left":o.css("margin-left"),"margin-right":o.css("margin-right")}));this.helperEl=t('
').append(l).appendTo(this.el)},destroyHelper:function(){this.helperEl&&(this.helperEl.remove(),this.helperEl=null)},renderSelection:function(t,e){this.view.opt("selectHelper")?this.renderRangeHelper(t,e):this.renderHighlight(t,e)},destroySelection:function(){this.destroyHelper(),this.destroyHighlight()},renderHighlight:function(e,i){this.highlightEl=t(this.highlightSkeletonHtml(e,i)).appendTo(this.el)},destroyHighlight:function(){this.highlightEl&&(this.highlightEl.remove(),this.highlightEl=null)},highlightSkeletonHtml:function(t,e){var i,n,r,o,s,l=this.view,a=this.rangeToSegs(t,e),c="",d=0;for(i=0;a.length>i;i++)n=a[i],n.col>d&&(c+='
",d++;return l.colCnt>d&&(c+='":void 0},numberIntroHtml:function(t){return this.weekNumbersVisible?'":void 0},dayIntroHtml:function(){return this.weekNumbersVisible?'":void 0},introHtml:function(){return this.weekNumbersVisible?'":void 0},numberCellHtml:function(t,e,i){var n;return this.dayNumbersVisible?(n=this.dayGrid.getDayClasses(i),n.unshift("fc-day-number"),'"):""):'"},dayIntroHtml:function(){return'"},slotBgIntroHtml:function(){return'"},introHtml:function(){return'"},axisStyleAttr:function(){return null!==this.axisWidth?'style="width:'+this.axisWidth+'px"':""},updateSize:function(t){t&&this.timeGrid.resize(),pe.prototype.updateSize.call(this,t)},updateWidth:function(){this.axisWidth=m(this.el.find(".fc-axis"))},setHeight:function(t,e){var i,n;null===this.bottomRuleHeight&&(this.bottomRuleHeight=this.bottomRuleEl.outerHeight()),this.bottomRuleEl.hide(),this.scrollerEl.css("overflow",""),y(this.scrollerEl),f(this.noScrollRowEls),this.dayGrid&&(this.dayGrid.destroySegPopover(),i=this.opt("eventLimit"),i&&"number"!=typeof i&&(i=Fe),i&&this.dayGrid.limitRows(i)),e||(n=this.computeScrollerHeight(t),v(this.scrollerEl,n)?(u(this.noScrollRowEls,E(this.scrollerEl)),n=this.computeScrollerHeight(t),this.scrollerEl.height(n),this.restoreScroll()):(this.scrollerEl.height(n).css("overflow","hidden"),this.bottomRuleEl.show()))},resetScroll:function(){function t(){i.scrollerEl.scrollTop(r)}var i=this,n=e.duration(this.opt("scrollTime")),r=this.timeGrid.computeTimeTop(n);r=Math.ceil(r),r&&r++,t(),setTimeout(t,0)},renderEvents:function(t){var e,i,n=[],r=[],o=[];for(i=0;t.length>i;i++)t[i].allDay?n.push(t[i]):r.push(t[i]);e=this.timeGrid.renderEvents(r),this.dayGrid&&(o=this.dayGrid.renderEvents(n)),this.updateHeight()},getSegs:function(){return this.timeGrid.getSegs().concat(this.dayGrid?this.dayGrid.getSegs():[])},destroyEvents:function(){pe.prototype.destroyEvents.call(this),this.recordScroll(),this.timeGrid.destroyEvents(),this.dayGrid&&this.dayGrid.destroyEvents()},renderDrag:function(t,e,i){return t.hasTime()?this.timeGrid.renderDrag(t,e,i):this.dayGrid?this.dayGrid.renderDrag(t,e,i):void 0},destroyDrag:function(){this.timeGrid.destroyDrag(),this.dayGrid&&this.dayGrid.destroyDrag()},renderSelection:function(t,e){t.hasTime()||e.hasTime()?this.timeGrid.renderSelection(t,e):this.dayGrid&&this.dayGrid.renderSelection(t,e)},destroySelection:function(){this.timeGrid.destroySelection(),this.dayGrid&&this.dayGrid.destroySelection()}}),ze.agendaWeek=Se,Se.prototype=x(De.prototype),t.extend(Se.prototype,{name:"agendaWeek",incrementDate:function(t,e){return t.clone().stripTime().add(e,"weeks").startOf("week")},render:function(t){this.intervalStart=t.clone().stripTime().startOf("week"),this.intervalEnd=this.intervalStart.clone().add(1,"weeks"),this.start=this.skipHiddenDays(this.intervalStart),this.end=this.skipHiddenDays(this.intervalEnd,-1,!0),this.title=this.calendar.formatRange(this.start,this.end.clone().subtract(1),this.opt("titleFormat")," — "),De.prototype.render.call(this,this.getCellsPerWeek())}}),ze.agendaDay=be,be.prototype=x(De.prototype),t.extend(be.prototype,{name:"agendaDay",incrementDate:function(t,e){var i=t.clone().stripTime().add(e,"days");return i=this.skipHiddenDays(i,0>e?-1:1)},render:function(t){this.start=this.intervalStart=t.clone().stripTime(),this.end=this.intervalEnd=this.start.clone().add(1,"days"),this.title=this.calendar.formatDate(this.start,this.opt("titleFormat")),De.prototype.render.call(this,1)}}),Te.prototype=x(ie.prototype),t.extend(Te.prototype,{rangeToSegs:function(t,e,i){var n,r=[],o=this.view.calendar.getDate();if(o>=t&&e>o){var s=this.view,l=s.calendar.fetchResources();for(n=0;s.colCnt>n;n++){var a=l[n];i.indexOf(a.id)>-1&&r.push({row:0,leftCol:n,rightCol:n,isStart:!0,isEnd:!0})}}return r}}),t.extend(Te.prototype,{eventToSegs:function(t,e,i){var n,r,o,s=t.start.clone().stripZone(),l=this.view.calendar.getEventEnd(t).stripZone();for(e&&i?(o=S(s,l,e,i),n=o?[o]:[]):n=this.rangeToSegs(s,l,t.resources),r=0;n.length>r;r++)o=n[r],o.event=t,o.eventStartMS=+s,o.eventDurationMS=l-s;return n}}),Ce.prototype=x(oe.prototype),t.extend(Ce.prototype,{rangeToSegs:function(t,e,i){var n,r,o,s,l,a=this.view,c=[],d=a.calendar.fetchResources();for(t=t.clone().stripZone(),e=e.clone().stripZone(),r=0;a.colCnt>r;r++){var h=d[r];i.indexOf(h.id)>-1&&(o=a.cellToDate(0,r),s=o.clone().time(this.minTime),l=o.clone().time(this.maxTime),n=S(t,e,s,l),n&&(n.col=r,c.push(n)))}return c}}),t.extend(Ce.prototype,{eventToSegs:function(t,e,i){var n,r,o,s=t.start.clone().stripZone(),l=this.view.calendar.getEventEnd(t).stripZone();for(e&&i?(o=S(s,l,e,i),n=o?[o]:[]):n=this.rangeToSegs(s,l,t.resources),r=0;n.length>r;r++)o=n[r],o.event=t,o.eventStartMS=+s,o.eventDurationMS=l-s;return n}}),r({allDaySlot:!0,allDayText:"all-day",scrollTime:"06:00:00",slotDuration:"00:30:00",axisFormat:we,timeFormat:{agenda:Ee},minTime:"00:00:00",maxTime:"24:00:00",slotEventOverlap:!0}),He.prototype=x(De.prototype),t.extend(He.prototype,{cellToDate:function(){return this.start.clone()},headCellHtml:function(t,e){var i=this,n=i.calendar,r=n.fetchResources()[e];return'"}}),ze.resourceDay=xe,xe.prototype=x(He.prototype),t.extend(xe.prototype,{name:"resourceDay",incrementDate:function(t,e){var i=t.clone().stripTime().add(e,"days");return i=this.skipHiddenDays(i,0>e?-1:1)},render:function(t){this.start=this.intervalStart=t.clone().stripTime(),this.end=this.intervalEnd=this.start.clone().add(1,"days"),this.title=this.calendar.formatDate(this.start,this.opt("titleFormat")),He.prototype.render.call(this,this.calendar.fetchResources().length)},getDayClasses:function(t){var e=this.view,i=e.calendar.getNow().stripTime(),n=["fc-"+Ne[t.day()]];return"month"===e.name&&t.month()!=e.intervalStart.month()&&n.push("fc-other-month"),t.isSame(i,"day")?n.push("fc-todaysss",e.highlightStateClass):i>t?n.push("fc-past"):n.push("fc-future"),n}})}); \ No newline at end of file diff --git a/dist/cdnjs/2.1.1/fullcalendar.print.css b/dist/cdnjs/2.1.1/fullcalendar.print.css index dc073b7..c276fe3 100644 --- a/dist/cdnjs/2.1.1/fullcalendar.print.css +++ b/dist/cdnjs/2.1.1/fullcalendar.print.css @@ -1,201 +1,201 @@ -/*! - * FullCalendar v2.1.1 Print Stylesheet - * Docs & License: http://arshaw.com/fullcalendar/ - * (c) 2013 Adam Shaw - */ - -/* - * Include this stylesheet on your page to get a more printer-friendly calendar. - * When including this stylesheet, use the media='print' attribute of the tag. - * Make sure to include this stylesheet IN ADDITION to the regular fullcalendar.css. - */ - -.fc { - max-width: 100% !important; -} - - -/* Global Event Restyling ---------------------------------------------------------------------------------------------------*/ - -.fc-event { - background: #fff !important; - color: #000 !important; - page-break-inside: avoid; -} - -.fc-event .fc-resizer { - display: none; -} - - -/* Table & Day-Row Restyling ---------------------------------------------------------------------------------------------------*/ - -th, -td, -hr, -thead, -tbody, -.fc-row { - border-color: #ccc !important; - background: #fff !important; -} - -/* kill the overlaid, absolutely-positioned common components */ -.fc-bg, -.fc-highlight-skeleton, -.fc-helper-skeleton { - display: none; -} - -/* don't force a min-height on rows (for DayGrid) */ -.fc tbody .fc-row { - height: auto !important; /* undo height that JS set in distributeHeight */ - min-height: 0 !important; /* undo the min-height from each view's specific stylesheet */ -} - -.fc tbody .fc-row .fc-content-skeleton { - position: static; /* undo .fc-rigid */ - padding-bottom: 0 !important; /* use a more border-friendly method for this... */ -} - -.fc tbody .fc-row .fc-content-skeleton tbody tr:last-child td { /* only works in newer browsers */ - padding-bottom: 1em; /* ...gives space within the skeleton. also ensures min height in a way */ -} - -.fc tbody .fc-row .fc-content-skeleton table { - /* provides a min-height for the row, but only effective for IE, which exaggerates this value, - making it look more like 3em. for other browers, it will already be this tall */ - height: 1em; -} - - -/* Undo month-view event limiting. Display all events and hide the "more" links ---------------------------------------------------------------------------------------------------*/ - -.fc-more-cell, -.fc-more { - display: none !important; -} - -.fc tr.fc-limited { - display: table-row !important; -} - -.fc td.fc-limited { - display: table-cell !important; -} - -.fc-popover { - display: none; /* never display the "more.." popover in print mode */ -} - - -/* TimeGrid Restyling ---------------------------------------------------------------------------------------------------*/ - -/* undo the min-height 100% trick used to fill the container's height */ -.fc-time-grid { - min-height: 0 !important; -} - -/* don't display the side axis at all ("all-day" and time cells) */ -.fc-agenda-view .fc-axis { - display: none; -} - -/* don't display the horizontal lines */ -.fc-slats, -.fc-time-grid hr { /* this hr is used when height is underused and needs to be filled */ - display: none !important; /* important overrides inline declaration */ -} - -/* let the container that holds the events be naturally positioned and create real height */ -.fc-time-grid .fc-content-skeleton { - position: static; -} - -/* in case there are no events, we still want some height */ -.fc-time-grid .fc-content-skeleton table { - height: 4em; -} - -/* kill the horizontal spacing made by the event container. event margins will be done below */ -.fc-time-grid .fc-event-container { - margin: 0 !important; -} - - -/* TimeGrid *Event* Restyling ---------------------------------------------------------------------------------------------------*/ - -/* naturally position events, vertically stacking them */ -.fc-time-grid .fc-event { - position: static !important; - margin: 3px 2px !important; -} - -/* for events that continue to a future day, give the bottom border back */ -.fc-time-grid .fc-event.fc-not-end { - border-bottom-width: 1px !important; -} - -/* indicate the event continues via "..." text */ -.fc-time-grid .fc-event.fc-not-end:after { - content: "..."; -} - -/* for events that are continuations from previous days, give the top border back */ -.fc-time-grid .fc-event.fc-not-start { - border-top-width: 1px !important; -} - -/* indicate the event is a continuation via "..." text */ -.fc-time-grid .fc-event.fc-not-start:before { - content: "..."; -} - -/* time */ - -/* undo a previous declaration and let the time text span to a second line */ -.fc-time-grid .fc-event .fc-time { - white-space: normal !important; -} - -/* hide the the time that is normally displayed... */ -.fc-time-grid .fc-event .fc-time span { - display: none; -} - -/* ...replace it with a more verbose version (includes AM/PM) stored in an html attribute */ -.fc-time-grid .fc-event .fc-time:after { - content: attr(data-full); -} - - -/* Vertical Scroller & Containers ---------------------------------------------------------------------------------------------------*/ - -/* kill the scrollbars and allow natural height */ -.fc-scroller, -.fc-day-grid-container, /* these divs might be assigned height, which we need to cleared */ -.fc-time-grid-container { /* */ - overflow: visible !important; - height: auto !important; -} - -/* kill the horizontal border/padding used to compensate for scrollbars */ -.fc-row { - border: 0 !important; - margin: 0 !important; -} - - -/* Button Controls ---------------------------------------------------------------------------------------------------*/ - -.fc-button-group, -.fc button { - display: none; /* don't display any button-related controls */ -} +/*! + * FullCalendar v2.1.1 Print Stylesheet + * Docs & License: http://arshaw.com/fullcalendar/ + * (c) 2013 Adam Shaw + */ + +/* + * Include this stylesheet on your page to get a more printer-friendly calendar. + * When including this stylesheet, use the media='print' attribute of the tag. + * Make sure to include this stylesheet IN ADDITION to the regular fullcalendar.css. + */ + +.fc { + max-width: 100% !important; +} + + +/* Global Event Restyling +--------------------------------------------------------------------------------------------------*/ + +.fc-event { + background: #fff !important; + color: #000 !important; + page-break-inside: avoid; +} + +.fc-event .fc-resizer { + display: none; +} + + +/* Table & Day-Row Restyling +--------------------------------------------------------------------------------------------------*/ + +th, +td, +hr, +thead, +tbody, +.fc-row { + border-color: #ccc !important; + background: #fff !important; +} + +/* kill the overlaid, absolutely-positioned common components */ +.fc-bg, +.fc-highlight-skeleton, +.fc-helper-skeleton { + display: none; +} + +/* don't force a min-height on rows (for DayGrid) */ +.fc tbody .fc-row { + height: auto !important; /* undo height that JS set in distributeHeight */ + min-height: 0 !important; /* undo the min-height from each view's specific stylesheet */ +} + +.fc tbody .fc-row .fc-content-skeleton { + position: static; /* undo .fc-rigid */ + padding-bottom: 0 !important; /* use a more border-friendly method for this... */ +} + +.fc tbody .fc-row .fc-content-skeleton tbody tr:last-child td { /* only works in newer browsers */ + padding-bottom: 1em; /* ...gives space within the skeleton. also ensures min height in a way */ +} + +.fc tbody .fc-row .fc-content-skeleton table { + /* provides a min-height for the row, but only effective for IE, which exaggerates this value, + making it look more like 3em. for other browers, it will already be this tall */ + height: 1em; +} + + +/* Undo month-view event limiting. Display all events and hide the "more" links +--------------------------------------------------------------------------------------------------*/ + +.fc-more-cell, +.fc-more { + display: none !important; +} + +.fc tr.fc-limited { + display: table-row !important; +} + +.fc td.fc-limited { + display: table-cell !important; +} + +.fc-popover { + display: none; /* never display the "more.." popover in print mode */ +} + + +/* TimeGrid Restyling +--------------------------------------------------------------------------------------------------*/ + +/* undo the min-height 100% trick used to fill the container's height */ +.fc-time-grid { + min-height: 0 !important; +} + +/* don't display the side axis at all ("all-day" and time cells) */ +.fc-agenda-view .fc-axis { + display: none; +} + +/* don't display the horizontal lines */ +.fc-slats, +.fc-time-grid hr { /* this hr is used when height is underused and needs to be filled */ + display: none !important; /* important overrides inline declaration */ +} + +/* let the container that holds the events be naturally positioned and create real height */ +.fc-time-grid .fc-content-skeleton { + position: static; +} + +/* in case there are no events, we still want some height */ +.fc-time-grid .fc-content-skeleton table { + height: 4em; +} + +/* kill the horizontal spacing made by the event container. event margins will be done below */ +.fc-time-grid .fc-event-container { + margin: 0 !important; +} + + +/* TimeGrid *Event* Restyling +--------------------------------------------------------------------------------------------------*/ + +/* naturally position events, vertically stacking them */ +.fc-time-grid .fc-event { + position: static !important; + margin: 3px 2px !important; +} + +/* for events that continue to a future day, give the bottom border back */ +.fc-time-grid .fc-event.fc-not-end { + border-bottom-width: 1px !important; +} + +/* indicate the event continues via "..." text */ +.fc-time-grid .fc-event.fc-not-end:after { + content: "..."; +} + +/* for events that are continuations from previous days, give the top border back */ +.fc-time-grid .fc-event.fc-not-start { + border-top-width: 1px !important; +} + +/* indicate the event is a continuation via "..." text */ +.fc-time-grid .fc-event.fc-not-start:before { + content: "..."; +} + +/* time */ + +/* undo a previous declaration and let the time text span to a second line */ +.fc-time-grid .fc-event .fc-time { + white-space: normal !important; +} + +/* hide the the time that is normally displayed... */ +.fc-time-grid .fc-event .fc-time span { + display: none; +} + +/* ...replace it with a more verbose version (includes AM/PM) stored in an html attribute */ +.fc-time-grid .fc-event .fc-time:after { + content: attr(data-full); +} + + +/* Vertical Scroller & Containers +--------------------------------------------------------------------------------------------------*/ + +/* kill the scrollbars and allow natural height */ +.fc-scroller, +.fc-day-grid-container, /* these divs might be assigned height, which we need to cleared */ +.fc-time-grid-container { /* */ + overflow: visible !important; + height: auto !important; +} + +/* kill the horizontal border/padding used to compensate for scrollbars */ +.fc-row { + border: 0 !important; + margin: 0 !important; +} + + +/* Button Controls +--------------------------------------------------------------------------------------------------*/ + +.fc-button-group, +.fc button { + display: none; /* don't display any button-related controls */ +} diff --git a/dist/cdnjs/2.1.1/gcal.js b/dist/cdnjs/2.1.1/gcal.js index 754fb72..c7f3b3b 100644 --- a/dist/cdnjs/2.1.1/gcal.js +++ b/dist/cdnjs/2.1.1/gcal.js @@ -1,100 +1,100 @@ -/*! - * FullCalendar v2.1.1 Google Calendar Plugin - * Docs & License: http://arshaw.com/fullcalendar/ - * (c) 2013 Adam Shaw - */ - -(function(factory) { - if (typeof define === 'function' && define.amd) { - define([ 'jquery' ], factory); - } - else { - factory(jQuery); - } -})(function($) { - - -var fc = $.fullCalendar; -var applyAll = fc.applyAll; - - -fc.sourceNormalizers.push(function(sourceOptions) { - if (sourceOptions.dataType == 'gcal' || - sourceOptions.dataType === undefined && - (sourceOptions.url || '').match(/^(http|https):\/\/www.google.com\/calendar\/feeds\//)) { - sourceOptions.dataType = 'gcal'; - if (sourceOptions.editable === undefined) { - sourceOptions.editable = false; - } - } -}); - - -fc.sourceFetchers.push(function(sourceOptions, start, end, timezone) { - if (sourceOptions.dataType == 'gcal') { - return transformOptions(sourceOptions, start, end, timezone); - } -}); - - -function transformOptions(sourceOptions, start, end, timezone) { - - var success = sourceOptions.success; - var data = $.extend({}, sourceOptions.data || {}, { - singleevents: true, - 'max-results': 9999 - }); - - return $.extend({}, sourceOptions, { - url: sourceOptions.url.replace(/\/basic$/, '/full') + '?alt=json-in-script&callback=?', - dataType: 'jsonp', - data: data, - timezoneParam: 'ctz', - startParam: 'start-min', - endParam: 'start-max', - success: function(data) { - var events = []; - if (data.feed.entry) { - $.each(data.feed.entry, function(i, entry) { - - var url; - $.each(entry.link, function(i, link) { - if (link.type == 'text/html') { - url = link.href; - if (timezone && timezone != 'local') { - url += (url.indexOf('?') == -1 ? '?' : '&') + 'ctz=' + encodeURIComponent(timezone); - } - } - }); - - events.push({ - id: entry.gCal$uid.value, - title: entry.title.$t, - start: entry.gd$when[0].startTime, - end: entry.gd$when[0].endTime, - url: url, - location: entry.gd$where[0].valueString, - description: entry.content.$t - }); - - }); - } - var args = [events].concat(Array.prototype.slice.call(arguments, 1)); - var res = applyAll(success, this, args); - if ($.isArray(res)) { - return res; - } - return events; - } - }); - -} - - -// legacy -fc.gcalFeed = function(url, sourceOptions) { - return $.extend({}, sourceOptions, { url: url, dataType: 'gcal' }); -}; - - -}); +/*! + * FullCalendar v2.1.1 Google Calendar Plugin + * Docs & License: http://arshaw.com/fullcalendar/ + * (c) 2013 Adam Shaw + */ + +(function(factory) { + if (typeof define === 'function' && define.amd) { + define([ 'jquery' ], factory); + } + else { + factory(jQuery); + } +})(function($) { + + +var fc = $.fullCalendar; +var applyAll = fc.applyAll; + + +fc.sourceNormalizers.push(function(sourceOptions) { + if (sourceOptions.dataType == 'gcal' || + sourceOptions.dataType === undefined && + (sourceOptions.url || '').match(/^(http|https):\/\/www.google.com\/calendar\/feeds\//)) { + sourceOptions.dataType = 'gcal'; + if (sourceOptions.editable === undefined) { + sourceOptions.editable = false; + } + } +}); + + +fc.sourceFetchers.push(function(sourceOptions, start, end, timezone) { + if (sourceOptions.dataType == 'gcal') { + return transformOptions(sourceOptions, start, end, timezone); + } +}); + + +function transformOptions(sourceOptions, start, end, timezone) { + + var success = sourceOptions.success; + var data = $.extend({}, sourceOptions.data || {}, { + singleevents: true, + 'max-results': 9999 + }); + + return $.extend({}, sourceOptions, { + url: sourceOptions.url.replace(/\/basic$/, '/full') + '?alt=json-in-script&callback=?', + dataType: 'jsonp', + data: data, + timezoneParam: 'ctz', + startParam: 'start-min', + endParam: 'start-max', + success: function(data) { + var events = []; + if (data.feed.entry) { + $.each(data.feed.entry, function(i, entry) { + + var url; + $.each(entry.link, function(i, link) { + if (link.type == 'text/html') { + url = link.href; + if (timezone && timezone != 'local') { + url += (url.indexOf('?') == -1 ? '?' : '&') + 'ctz=' + encodeURIComponent(timezone); + } + } + }); + + events.push({ + id: entry.gCal$uid.value, + title: entry.title.$t, + start: entry.gd$when[0].startTime, + end: entry.gd$when[0].endTime, + url: url, + location: entry.gd$where[0].valueString, + description: entry.content.$t + }); + + }); + } + var args = [events].concat(Array.prototype.slice.call(arguments, 1)); + var res = applyAll(success, this, args); + if ($.isArray(res)) { + return res; + } + return events; + } + }); + +} + + +// legacy +fc.gcalFeed = function(url, sourceOptions) { + return $.extend({}, sourceOptions, { url: url, dataType: 'gcal' }); +}; + + +}); diff --git a/dist/cdnjs/2.1.1/lang/is.js b/dist/cdnjs/2.1.1/lang/is.js index 7bbdebf..4601695 100644 --- a/dist/cdnjs/2.1.1/lang/is.js +++ b/dist/cdnjs/2.1.1/lang/is.js @@ -1 +1 @@ -(function(e){"function"==typeof define&&define.amd?define(["jquery","moment"],e):e(jQuery,moment)})(function(e,t){function n(e){return 11===e%100?!0:1===e%10?!1:!0}function i(e,t,i,r){var a=e+" ";switch(i){case"s":return t||r?"nokkrar sekúndur":"nokkrum sekúndum";case"m":return t?"mínúta":"mínútu";case"mm":return n(e)?a+(t||r?"mínútur":"mínútum"):t?a+"mínúta":a+"mínútu";case"hh":return n(e)?a+(t||r?"klukkustundir":"klukkustundum"):a+"klukkustund";case"d":return t?"dagur":r?"dag":"degi";case"dd":return n(e)?t?a+"dagar":a+(r?"daga":"dögum"):t?a+"dagur":a+(r?"dag":"degi");case"M":return t?"mánuður":r?"mánuð":"mánuði";case"MM":return n(e)?t?a+"mánuðir":a+(r?"mánuði":"mánuðum"):t?a+"mánuður":a+(r?"mánuð":"mánuði");case"y":return t||r?"ár":"ári";case"yy":return n(e)?a+(t||r?"ár":"árum"):a+(t||r?"ár":"ári")}}(t.defineLocale||t.lang).call(t,"is",{months:"janúar_febrúar_mars_apríl_maí_júní_júlí_ágúst_september_október_nóvember_desember".split("_"),monthsShort:"jan_feb_mar_apr_maí_jún_júl_ágú_sep_okt_nóv_des".split("_"),weekdays:"sunnudagur_mánudagur_þriðjudagur_miðvikudagur_fimmtudagur_föstudagur_laugardagur".split("_"),weekdaysShort:"sun_mán_þri_mið_fim_fös_lau".split("_"),weekdaysMin:"Su_Má_Þr_Mi_Fi_Fö_La".split("_"),longDateFormat:{LT:"H:mm",L:"DD/MM/YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY [kl.] LT",LLLL:"dddd, D. MMMM YYYY [kl.] LT"},calendar:{sameDay:"[í dag kl.] LT",nextDay:"[á morgun kl.] LT",nextWeek:"dddd [kl.] LT",lastDay:"[í gær kl.] LT",lastWeek:"[síðasta] dddd [kl.] LT",sameElse:"L"},relativeTime:{future:"eftir %s",past:"fyrir %s síðan",s:i,m:i,mm:i,h:"klukkustund",hh:i,d:i,dd:i,M:i,MM:i,y:i,yy:i},ordinal:"%d.",week:{dow:1,doy:4}}),e.fullCalendar.datepickerLang("is","is",{closeText:"Loka",prevText:"< Fyrri",nextText:"Næsti >",currentText:"Í dag",monthNames:["Janúar","Febrúar","Mars","Apríl","Maí","Júní","Júlí","Ágúst","September","Október","Nóvember","Desember"],monthNamesShort:["Jan","Feb","Mar","Apr","Maí","Jún","Júl","Ágú","Sep","Okt","Nóv","Des"],dayNames:["Sunnudagur","Mánudagur","Þriðjudagur","Miðvikudagur","Fimmtudagur","Föstudagur","Laugardagur"],dayNamesShort:["Sun","Mán","Þri","Mið","Fim","Fös","Lau"],dayNamesMin:["Su","Má","Þr","Mi","Fi","Fö","La"],weekHeader:"Vika",dateFormat:"dd.mm.yy",firstDay:0,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),e.fullCalendar.lang("is",{defaultButtonText:{month:"Mánuður",week:"Vika",day:"Dagur",list:"Dagskrá"},allDayHtml:"Allan
daginn",eventLimitText:"meira"})}); \ No newline at end of file +(function(e){"function"==typeof define&&define.amd?define(["jquery","moment"],e):e(jQuery,moment)})(function(e,t){function n(e){return 11===e%100?!0:1===e%10?!1:!0}function r(e,t,r,i){var a=e+" ";switch(r){case"s":return t||i?"nokkrar sekúndur":"nokkrum sekúndum";case"m":return t?"mínúta":"mínútu";case"mm":return n(e)?a+(t||i?"mínútur":"mínútum"):t?a+"mínúta":a+"mínútu";case"hh":return n(e)?a+(t||i?"klukkustundir":"klukkustundum"):a+"klukkustund";case"d":return t?"dagur":i?"dag":"degi";case"dd":return n(e)?t?a+"dagar":a+(i?"daga":"dögum"):t?a+"dagur":a+(i?"dag":"degi");case"M":return t?"mánuður":i?"mánuð":"mánuði";case"MM":return n(e)?t?a+"mánuðir":a+(i?"mánuði":"mánuðum"):t?a+"mánuður":a+(i?"mánuð":"mánuði");case"y":return t||i?"ár":"ári";case"yy":return n(e)?a+(t||i?"ár":"árum"):a+(t||i?"ár":"ári")}}(t.defineLocale||t.lang).call(t,"is",{months:"janúar_febrúar_mars_apríl_maí_júní_júlí_ágúst_september_október_nóvember_desember".split("_"),monthsShort:"jan_feb_mar_apr_maí_jún_júl_ágú_sep_okt_nóv_des".split("_"),weekdays:"sunnudagur_mánudagur_þriðjudagur_miðvikudagur_fimmtudagur_föstudagur_laugardagur".split("_"),weekdaysShort:"sun_mán_þri_mið_fim_fös_lau".split("_"),weekdaysMin:"Su_Má_Þr_Mi_Fi_Fö_La".split("_"),longDateFormat:{LT:"H:mm",L:"DD/MM/YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY [kl.] LT",LLLL:"dddd, D. MMMM YYYY [kl.] LT"},calendar:{sameDay:"[í dag kl.] LT",nextDay:"[á morgun kl.] LT",nextWeek:"dddd [kl.] LT",lastDay:"[í gær kl.] LT",lastWeek:"[síðasta] dddd [kl.] LT",sameElse:"L"},relativeTime:{future:"eftir %s",past:"fyrir %s síðan",s:r,m:r,mm:r,h:"klukkustund",hh:r,d:r,dd:r,M:r,MM:r,y:r,yy:r},ordinal:"%d.",week:{dow:1,doy:4}}),e.fullCalendar.datepickerLang("is","is",{closeText:"Loka",prevText:"< Fyrri",nextText:"Næsti >",currentText:"Í dag",monthNames:["Janúar","Febrúar","Mars","Apríl","Maí","Júní","Júlí","Ágúst","September","Október","Nóvember","Desember"],monthNamesShort:["Jan","Feb","Mar","Apr","Maí","Jún","Júl","Ágú","Sep","Okt","Nóv","Des"],dayNames:["Sunnudagur","Mánudagur","Þriðjudagur","Miðvikudagur","Fimmtudagur","Föstudagur","Laugardagur"],dayNamesShort:["Sun","Mán","Þri","Mið","Fim","Fös","Lau"],dayNamesMin:["Su","Má","Þr","Mi","Fi","Fö","La"],weekHeader:"Vika",dateFormat:"dd.mm.yy",firstDay:0,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),e.fullCalendar.lang("is",{defaultButtonText:{month:"Mánuður",week:"Vika",day:"Dagur",list:"Dagskrá"},allDayHtml:"Allan
daginn",eventLimitText:"meira"})}); \ No newline at end of file diff --git a/dist/fullcalendar-2.1.1.zip b/dist/fullcalendar-2.1.1.zip index a98d1779c4dd46e57bd1ae0fb5cff3f228ca6a64..6c17f1cdf6c38e11d6f9948fcb6c44c78840c5fe 100644 GIT binary patch delta 155417 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%0Dp1oCfe_ROvY91)bC>e00UBi5Llh5t2cHfP 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 D1Us&Z diff --git a/dist/fullcalendar.css b/dist/fullcalendar.css index ab8b6e6..a5b9d33 100644 --- a/dist/fullcalendar.css +++ b/dist/fullcalendar.css @@ -1,933 +1,933 @@ -/*! - * FullCalendar v2.1.1 Stylesheet - * Docs & License: http://arshaw.com/fullcalendar/ - * (c) 2013 Adam Shaw - */ - - -.fc { - direction: ltr; - text-align: left; -} - -.fc-rtl { - text-align: right; -} - -body .fc { /* extra precedence to overcome jqui */ - font-size: 1em; -} - - -/* Colors ---------------------------------------------------------------------------------------------------*/ - -.fc-unthemed th, -.fc-unthemed td, -.fc-unthemed hr, -.fc-unthemed thead, -.fc-unthemed tbody, -.fc-unthemed .fc-row, -.fc-unthemed .fc-popover { - border-color: #ddd; -} - -.fc-unthemed .fc-popover { - background-color: #fff; -} - -.fc-unthemed hr, -.fc-unthemed .fc-popover .fc-header { - background: #eee; -} - -.fc-unthemed .fc-popover .fc-header .fc-close { - color: #666; -} - -.fc-unthemed .fc-today { - background: #fcf8e3; -} - -.fc-highlight { /* when user is selecting cells */ - background: #bce8f1; - opacity: .3; - filter: alpha(opacity=30); /* for IE */ -} - - -/* Icons (inline elements with styled text that mock arrow icons) ---------------------------------------------------------------------------------------------------*/ - -.fc-icon { - display: inline-block; - font-size: 2em; - line-height: .5em; - height: .5em; /* will make the total height 1em */ - font-family: "Courier New", Courier, monospace; -} - -.fc-icon-left-single-arrow:after { - content: "\02039"; - font-weight: bold; -} - -.fc-icon-right-single-arrow:after { - content: "\0203A"; - font-weight: bold; -} - -.fc-icon-left-double-arrow:after { - content: "\000AB"; -} - -.fc-icon-right-double-arrow:after { - content: "\000BB"; -} - -.fc-icon-x:after { - content: "\000D7"; -} - - -/* Buttons (styled

rows. -----------------------------------------------------------------------------------------------------------------------*/ -// It leverages methods of the subclass and the View to determine custom rendering behavior for each row "type" -// (such as highlight rows, day rows, helper rows, etc). - -function RowRenderer(view) { - this.view = view; -} - - -RowRenderer.prototype = { - - view: null, // a View object - cellHtml: '' + cellHtml + ''; - }, - - - // Applies the "intro" and "outro" HTML to the given cells. - // Intro means the leftmost cell when the calendar is LTR and the rightmost cell when RTL. Vice-versa for outro. - // `cells` can be an HTML string of element - // `row` is an optional row number. - bookendCells: function(cells, rowType, row) { - var view = this.view; - var intro = this.getHtmlRenderer('intro', rowType)(row || 0); - var outro = this.getHtmlRenderer('outro', rowType)(row || 0); - var isRTL = view.opt('isRTL'); - var prependHtml = isRTL ? outro : intro; - var appendHtml = isRTL ? intro : outro; - - if (typeof cells === 'string') { - return prependHtml + cells + appendHtml; - } - else { // a jQuery element - return cells.prepend(prependHtml).append(appendHtml); - } - }, - - - // Returns an HTML-rendering function given a specific `rendererName` (like cell, intro, or outro) and a specific - // `rowType` (like day, eventSkeleton, helperSkeleton), which is optional. - // If a renderer for the specific rowType doesn't exist, it will fall back to a generic renderer. - // We will query the View object first for any custom rendering functions, then the methods of the subclass. - getHtmlRenderer: function(rendererName, rowType) { - var view = this.view; - var generalName; // like "cellHtml" - var specificName; // like "dayCellHtml". based on rowType - var provider; // either the View or the RowRenderer subclass, whichever provided the method - var renderer; - - generalName = rendererName + 'Html'; - if (rowType) { - specificName = rowType + capitaliseFirstLetter(rendererName) + 'Html'; - } - - if (specificName && (renderer = view[specificName])) { - provider = view; - } - else if (specificName && (renderer = this[specificName])) { - provider = this; - } - else if ((renderer = view[generalName])) { - provider = view; - } - else if ((renderer = this[generalName])) { - provider = this; - } - - if (typeof renderer === 'function') { - return function(row) { - return renderer.apply(provider, arguments) || ''; // use correct `this` and always return a string - }; - } - - // the rendered can be a plain string as well. if not specified, always an empty string. - return function() { - return renderer || ''; - }; - } - -}; - -;; - -/* An abstract class comprised of a "grid" of cells that each represent a specific datetime -----------------------------------------------------------------------------------------------------------------------*/ - -function Grid(view) { - RowRenderer.call(this, view); // call the super-constructor - this.coordMap = new GridCoordMap(this); -} - - -Grid.prototype = createObject(RowRenderer.prototype); // declare the super-class -$.extend(Grid.prototype, { - - el: null, // the containing element - coordMap: null, // a GridCoordMap that converts pixel values to datetimes - cellDuration: null, // a cell's duration. subclasses must assign this ASAP - - - // Renders the grid into the `el` element. - // Subclasses should override and call this super-method when done. - render: function() { - this.bindHandlers(); - }, - - - // Called when the grid's resources need to be cleaned up - destroy: function() { - // subclasses can implement - }, - - - /* Coordinates & Cells - ------------------------------------------------------------------------------------------------------------------*/ - - - // Populates the given empty arrays with the y and x coordinates of the cells - buildCoords: function(rows, cols) { - // subclasses must implement - }, - - - // Given a cell object, returns the date for that cell - getCellDate: function(cell) { - // subclasses must implement - }, - - - // Given a cell object, returns the element that represents the cell's whole-day - getCellDayEl: function(cell) { - // subclasses must implement - }, - - - // Converts a range with an inclusive `start` and an exclusive `end` into an array of segment objects - rangeToSegs: function(start, end) { - // subclasses must implement - }, - - - /* Handlers - ------------------------------------------------------------------------------------------------------------------*/ - - - // Attach handlers to `this.el`, using bubbling to listen to all ancestors. - // We don't need to undo any of this in a "destroy" method, because the view will simply remove `this.el` from the - // DOM and jQuery will be smart enough to garbage collect the handlers. - bindHandlers: function() { - var _this = this; - - this.el.on('mousedown', function(ev) { - if ( - !$(ev.target).is('.fc-event-container *, .fc-more') && // not an an event element, or "more.." link - !$(ev.target).closest('.fc-popover').length // not on a popover (like the "more.." events one) - ) { - _this.dayMousedown(ev); - } - }); - - this.bindSegHandlers(); // attach event-element-related handlers. in Grid.events.js - }, - - - // Process a mousedown on an element that represents a day. For day clicking and selecting. - dayMousedown: function(ev) { - var _this = this; - var view = this.view; - var isSelectable = view.opt('selectable'); - var dates = null; // the inclusive dates of the selection. will be null if no selection - var start; // the inclusive start of the selection - var end; // the *exclusive* end of the selection - var dayEl; - - // this listener tracks a mousedown on a day element, and a subsequent drag. - // if the drag ends on the same day, it is a 'dayClick'. - // if 'selectable' is enabled, this listener also detects selections. - var dragListener = new DragListener(this.coordMap, { - //distance: 5, // needs more work if we want dayClick to fire correctly - scroll: view.opt('dragScroll'), - dragStart: function() { - view.unselect(); // since we could be rendering a new selection, we want to clear any old one - }, - cellOver: function(cell, date) { - if (dragListener.origDate) { // click needs to have started on a cell - - dayEl = _this.getCellDayEl(cell); - - dates = [ date, dragListener.origDate ].sort(dateCompare); - start = dates[0]; - end = dates[1].clone().add(_this.cellDuration); - - if (isSelectable) { - _this.renderSelection(start, end); - } - } - }, - cellOut: function(cell, date) { - dates = null; - _this.destroySelection(); - }, - listenStop: function(ev) { - if (dates) { // started and ended on a cell? - if (dates[0].isSame(dates[1])) { - view.trigger('dayClick', dayEl[0], start, ev); - } - if (isSelectable) { - // the selection will already have been rendered. just report it - view.reportSelection(start, end, ev); - } - } - } - }); - - dragListener.mousedown(ev); // start listening, which will eventually initiate a dragStart - }, - - - /* Event Dragging - ------------------------------------------------------------------------------------------------------------------*/ - - - // Renders a visual indication of a event being dragged over the given date(s). - // `end` can be null, as well as `seg`. See View's documentation on renderDrag for more info. - // A returned value of `true` signals that a mock "helper" event has been rendered. - renderDrag: function(start, end, seg) { - // subclasses must implement - }, - - - // Unrenders a visual indication of an event being dragged - destroyDrag: function() { - // subclasses must implement - }, - - - /* Event Resizing - ------------------------------------------------------------------------------------------------------------------*/ - - - // Renders a visual indication of an event being resized. - // `start` and `end` are the updated dates of the event. `seg` is the original segment object involved in the drag. - renderResize: function(start, end, seg) { - // subclasses must implement - }, - - - // Unrenders a visual indication of an event being resized. - destroyResize: function() { - // subclasses must implement - }, - - - /* Event Helper - ------------------------------------------------------------------------------------------------------------------*/ - - - // Renders a mock event over the given date(s). - // `end` can be null, in which case the mock event that is rendered will have a null end time. - // `sourceSeg` is the internal segment object involved in the drag. If null, something external is dragging. - renderRangeHelper: function(start, end, sourceSeg) { - var view = this.view; - var fakeEvent; - - // compute the end time if forced to do so (this is what EventManager does) - if (!end && view.opt('forceEventDuration')) { - end = view.calendar.getDefaultEventEnd(!start.hasTime(), start); - } - - fakeEvent = sourceSeg ? createObject(sourceSeg.event) : {}; // mask the original event object if possible - fakeEvent.start = start; - fakeEvent.end = end; - fakeEvent.allDay = !(start.hasTime() || (end && end.hasTime())); // freshly compute allDay - - // this extra className will be useful for differentiating real events from mock events in CSS - fakeEvent.className = (fakeEvent.className || []).concat('fc-helper'); - - // if something external is being dragged in, don't render a resizer - if (!sourceSeg) { - fakeEvent.editable = false; - } - - this.renderHelper(fakeEvent, sourceSeg); // do the actual rendering - }, - - - // Renders a mock event - renderHelper: function(event, sourceSeg) { - // subclasses must implement - }, - - - // Unrenders a mock event - destroyHelper: function() { - // subclasses must implement - }, - - - /* Selection - ------------------------------------------------------------------------------------------------------------------*/ - - - // Renders a visual indication of a selection. Will highlight by default but can be overridden by subclasses. - renderSelection: function(start, end) { - this.renderHighlight(start, end); - }, - - - // Unrenders any visual indications of a selection. Will unrender a highlight by default. - destroySelection: function() { - this.destroyHighlight(); - }, - - - /* Highlight - ------------------------------------------------------------------------------------------------------------------*/ - - - // Puts visual emphasis on a certain date range - renderHighlight: function(start, end) { - // subclasses should implement - }, - - - // Removes visual emphasis on a date range - destroyHighlight: function() { - // subclasses should implement - }, - - - - /* Generic rendering utilities for subclasses - ------------------------------------------------------------------------------------------------------------------*/ - - - // Renders a day-of-week header row - headHtml: function() { - return '' + - '
' + - '
'),e>t&&(n+=''),i>e&&(n+=''),n=this.bookendCells(n,"highlight"),'
'+n+""+"
"+"
"}}),t.extend(ie.prototype,{segs:null,rowStructs:null,renderEvents:function(e){var i=this.rowStructs=this.renderEventRows(e),n=[];this.rowEls.each(function(e,r){t(r).find(".fc-content-skeleton > table").append(i[e].tbodyEl),n.push.apply(n,i[e].segs)}),this.segs=n},getSegs:function(){return(this.segs||[]).concat(this.popoverSegs||[])},destroyEvents:function(){var t,e;for(te.prototype.destroyEvents.call(this),t=this.rowStructs||[];e=t.pop();)e.tbodyEl.remove();this.segs=null,this.destroySegPopover()},renderEventRows:function(t){var e,i,n=this.eventsToSegs(t),r=[];for(n=this.renderSegs(n),e=this.groupSegRows(n),i=0;e.length>i;i++)r.push(this.renderEventRow(i,e[i]));return r},renderSegHtml:function(t,e){var i,n=this.view,r=n.opt("isRTL"),o=t.event,s=n.isEventDraggable(o),l=!e&&o.allDay&&t.isEnd&&n.isEventResizable(o),a=this.getSegClasses(t,s,l),c=this.getEventSkinCss(o),d="";return a.unshift("fc-day-grid-event"),!o.allDay&&t.isStart&&(d=''+P(n.getEventTimeText(o))+""),i=''+(P(o.title||"")||" ")+"",'"+'
'+(r?i+" "+d:d+" "+i)+"
"+(l?'
':"")+""},renderEventRow:function(e,i){function n(e){for(;e>s;)d=(y[r-1]||[])[s],d?d.attr("rowspan",parseInt(d.attr("rowspan")||1,10)+1):(d=t("
"),l.append(d)),v[r][s]=d,y[r][s]=d,s++}var r,o,s,l,a,c,d,h=this.view,u=h.colCnt,f=this.buildSegLevels(i),p=Math.max(1,f.length),g=t("
').append(c.el),c.leftCol!=c.rightCol?d.attr("colspan",c.rightCol-c.leftCol+1):y[r][s]=d;c.rightCol>=s;)v[r][s]=d,m[r][s]=c,s++;l.append(d)}n(u),this.bookendCells(l,"eventSkeleton"),g.append(l)}return{row:e,tbodyEl:g,cellMatrix:v,segMatrix:m,segLevels:f,segs:i}},buildSegLevels:function(t){var e,i,n,r=[];for(t.sort(ee),e=0;t.length>e;e++){for(i=t[e],n=0;r.length>n&&ne(i,r[n]);n++);i.level=n,(r[n]||(r[n]=[])).push(i)}for(n=0;r.length>n;n++)r[n].sort(re);return r},groupSegRows:function(t){var e,i=this.view,n=[];for(e=0;i.rowCnt>e;e++)n.push([]);for(e=0;t.length>e;e++)n[t[e].row].push(t[e]);return n}}),t.extend(ie.prototype,{segPopover:null,popoverSegs:null,destroySegPopover:function(){this.segPopover&&this.segPopover.hide()},limitRows:function(t){var e,i,n=this.rowStructs||[];for(e=0;n.length>e;e++)this.unlimitRow(e),i=t?"number"==typeof t?t:this.computeRowLevelLimit(e):!1,i!==!1&&this.limitRow(e,i)},computeRowLevelLimit:function(t){var e,i,n=this.rowEls.eq(t),r=n.height(),o=this.rowStructs[t].tbodyEl.children();for(e=0;o.length>e;e++)if(i=o.eq(e).removeClass("fc-limited"),i.position().top+i.outerHeight()>r)return e;return!1},limitRow:function(e,i){function n(n){for(;n>T;)r={row:e,col:T},d=E.getCellSegs(r,i),d.length&&(f=s[i-1][T],w=E.renderMoreLink(r,d),y=t("
").append(w),f.append(y),b.push(y[0])),T++}var r,o,s,l,a,c,d,h,u,f,p,g,m,v,y,w,E=this,D=this.view,S=this.rowStructs[e],b=[],T=0;if(i&&S.segLevels.length>i){for(o=S.segLevels[i-1],s=S.cellMatrix,l=S.tbodyEl.children().slice(i).addClass("fc-limited").get(),a=0;o.length>a;a++){for(c=o[a],n(c.leftCol),u=[],h=0;c.rightCol>=T;)r={row:e,col:T},d=this.getCellSegs(r,i),u.push(d),h+=d.length,T++;if(h){for(f=s[i-1][c.leftCol],p=f.attr("rowspan")||1,g=[],m=0;u.length>m;m++)v=t('
').attr("rowspan",p),d=u[m],r={row:e,col:c.leftCol+m},w=this.renderMoreLink(r,[c].concat(d)),y=t("
").append(w),v.append(y),g.push(v[0]),b.push(v[0]);f.addClass("fc-limited").after(t(g)),l.push(f[0])}}n(D.colCnt),S.moreEls=t(b),S.limitedEls=t(l)}},unlimitRow:function(t){var e=this.rowStructs[t];e.moreEls&&(e.moreEls.remove(),e.moreEls=null),e.limitedEls&&(e.limitedEls.removeClass("fc-limited"),e.limitedEls=null)},renderMoreLink:function(e,i){var n=this,r=this.view;return t('').text(this.getMoreLinkText(i.length)).on("click",function(o){var s=r.opt("eventLimitClick"),l=r.cellToDate(e),a=t(this),c=n.getCellDayEl(e),d=n.getCellSegs(e),h=n.resliceDaySegs(d,l),u=n.resliceDaySegs(i,l);"function"==typeof s&&(s=r.trigger("eventLimitClick",null,{date:l,dayEl:c,moreEl:a,segs:h,hiddenSegs:u},o)),"popover"===s?n.showSegPopover(l,e,a,h):"string"==typeof s&&r.calendar.zoomTo(l,s)})},showSegPopover:function(t,e,i,n){var r,o,s=this,l=this.view,a=i.parent();r=1==l.rowCnt?this.view.el:this.rowEls.eq(e.row),o={className:"fc-more-popover",content:this.renderSegPopoverContent(t,n),parentEl:this.el,top:r.offset().top,autoHide:!0,viewportConstrain:l.opt("popoverViewportConstrain"),hide:function(){s.segPopover.destroy(),s.segPopover=null,s.popoverSegs=null}},l.opt("isRTL")?o.right=a.offset().left+a.outerWidth()+1:o.left=a.offset().left-1,this.segPopover=new X(o),this.segPopover.show()},renderSegPopoverContent:function(e,i){var n,r=this.view,o=r.opt("theme"),s=e.format(r.opt("dayPopoverFormat")),l=t('
'+''+''+P(s)+""+'
'+"
"+'
'+'
'+"
"),a=l.find(".fc-event-container");for(i=this.renderSegs(i,!0),this.popoverSegs=i,n=0;i.length>n;n++)i[n].cellDate=e,a.append(i[n].el);return l},resliceDaySegs:function(e,i){var n=t.map(e,function(t){return t.event}),r=i.clone().stripTime(),o=r.clone().add(1,"days");return this.eventsToSegs(n,r,o)},getMoreLinkText:function(t){var e=this.view,i=e.opt("eventLimitText");return"function"==typeof i?i(t):"+"+t+" "+i},getCellSegs:function(t,e){for(var i,n=this.rowStructs[t.row].segMatrix,r=e||0,o=[];n.length>r;)i=n[r][t.col],i&&o.push(i),r++;return o}}),oe.prototype=x(te.prototype),t.extend(oe.prototype,{slotDuration:null,snapDuration:null,minTime:null,maxTime:null,dayEls:null,slatEls:null,slatTops:null,highlightEl:null,helperEl:null,render:function(){this.processOptions(),this.el.html(this.renderHtml()),this.dayEls=this.el.find(".fc-day"),this.slatEls=this.el.find(".fc-slats tr"),this.computeSlatTops(),te.prototype.render.call(this)},renderHtml:function(){return'
'+this.rowHtml("slotBg")+"
"+"
"+'
'+""+this.slatRowHtml()+"
"+"
"},slotBgCellHtml:function(t,e,i){return this.bgCellHtml(t,e,i)},slatRowHtml:function(){for(var t,i,n,r=this.view,o=r.calendar,s=r.opt("isRTL"),l="",a=0===this.slotDuration.asMinutes()%15,c=e.duration(+this.minTime);this.maxTime>c;)t=r.start.clone().time(c),i=t.minutes(),n='
"+(a&&i?"":""+P(o.formatDate(t,r.opt("axisFormat")))+"")+"
'+(s?n:"")+"
',d=n.col),r=l.cellToDate(0,d),o=this.computeDateTop(n.start,r),s=this.computeDateTop(n.end,r),c+='
'+"
"+"
'),c=this.bookendCells(c,"highlight"),'
'+c+""+"
"+"
"}}),t.extend(oe.prototype,{segs:null,eventSkeletonEl:null,renderEvents:function(e){var i=this.renderEventTable(e);this.eventSkeletonEl=t('
').append(i.tableEl),this.el.append(this.eventSkeletonEl),this.segs=i.segs},getSegs:function(){return this.segs||[]},destroyEvents:function(){te.prototype.destroyEvents.call(this),this.eventSkeletonEl&&(this.eventSkeletonEl.remove(),this.eventSkeletonEl=null),this.segs=null},renderEventTable:function(e){var i,n,r,o,s,l,a=t("
"),c=a.find("tr"),d=this.eventsToSegs(e);for(d=this.renderSegs(d),i=this.groupSegCols(d),this.computeSegVerticals(d),o=0;i.length>o;o++){for(s=i[o],se(s),l=t('
'),n=0;s.length>n;n++)r=s[n],r.el.css(this.generateSegPositionCss(r)),30>r.bottom-r.top&&r.el.addClass("fc-short"),l.append(r.el);c.append(t("
").append(l))}return this.bookendCells(c,"eventSkeleton"),{tableEl:a,segs:d}},updateSegVerticals:function(){var t,e=this.segs;if(e)for(this.computeSegVerticals(e),t=0;e.length>t;t++)e[t].el.css(this.generateSegVerticalCss(e[t]))},computeSegVerticals:function(t){var e,i;for(e=0;t.length>e;e++)i=t[e],i.top=this.computeDateTop(i.start,i.start),i.bottom=this.computeDateTop(i.end,i.start)},renderSegHtml:function(t,e){var i,n,r,o=this.view,s=t.event,l=o.isEventDraggable(s),a=!e&&t.isEnd&&o.isEventResizable(s),c=this.getSegClasses(t,l,a),d=this.getEventSkinCss(s);return c.unshift("fc-time-grid-event"),o.isMultiDayEvent(s)?(t.isStart||t.isEnd)&&(i=o.getEventTimeText(t.start,t.end),n=o.getEventTimeText(t.start,t.end,"LT"),r=o.getEventTimeText(t.start,null)):(i=o.getEventTimeText(s),n=o.getEventTimeText(s,"LT"),r=o.getEventTimeText(s.start,null)),'"+'
'+(i?'
"+""+P(i)+""+"
":"")+(s.title?'
'+P(s.title)+"
":"")+"
"+'
'+(a?'
':"")+""},generateSegPositionCss:function(t){var e,i,n=this.view,r=n.opt("isRTL"),o=n.opt("slotEventOverlap"),s=t.backwardCoord,l=t.forwardCoord,a=this.generateSegVerticalCss(t);return o&&(l=Math.min(1,s+2*(l-s))),r?(e=1-l,i=s):(e=s,i=1-l),a.zIndex=t.level+1,a.left=100*e+"%",a.right=100*i+"%",o&&t.forwardPressure&&(a[r?"marginLeft":"marginRight"]=20),a},generateSegVerticalCss:function(t){return{top:t.top,bottom:-t.bottom}},groupSegCols:function(t){var e,i=this.view,n=[];for(e=0;i.colCnt>e;e++)n.push([]);for(e=0;t.length>e;e++)n[t[e].col].push(t[e]);return n}}),pe.prototype={calendar:null,coordMap:null,el:null,start:null,end:null,intervalStart:null,intervalEnd:null,rowCnt:null,colCnt:null,isSelected:!1,scrollerEl:null,scrollTop:null,widgetHeaderClass:null,widgetContentClass:null,highlightStateClass:null,documentMousedownProxy:null,documentDragStartProxy:null,init:function(){var e=this.opt("theme")?"ui":"fc";this.widgetHeaderClass=e+"-widget-header",this.widgetContentClass=e+"-widget-content",this.highlightStateClass=e+"-state-highlight",this.documentMousedownProxy=t.proxy(this,"documentMousedown"),this.documentDragStartProxy=t.proxy(this,"documentDragStart")},render:function(){this.updateSize(),this.trigger("viewRender",this,this,this.el),t(document).on("mousedown",this.documentMousedownProxy).on("dragstart",this.documentDragStartProxy)},destroy:function(){this.unselect(),this.trigger("viewDestroy",this,this,this.el),this.destroyEvents(),this.el.empty(),t(document).off("mousedown",this.documentMousedownProxy).off("dragstart",this.documentDragStartProxy)},incrementDate:function(){},updateSize:function(t){t&&this.recordScroll(),this.updateHeight(),this.updateWidth()},updateWidth:function(){},updateHeight:function(){var t=this.calendar;this.setHeight(t.getSuggestedViewHeight(),t.isHeightAuto())},setHeight:function(){},computeScrollerHeight:function(t){var e,i=this.el.add(this.scrollerEl);return i.css({position:"relative",left:-1}),e=this.el.outerHeight()-this.scrollerEl.height(),i.css({position:"",left:""}),t-e},recordScroll:function(){this.scrollerEl&&(this.scrollTop=this.scrollerEl.scrollTop()) +},restoreScroll:function(){null!==this.scrollTop&&this.scrollerEl.scrollTop(this.scrollTop)},renderEvents:function(){this.segEach(function(t){this.trigger("eventAfterRender",t.event,t.event,t.el)}),this.trigger("eventAfterAllRender")},destroyEvents:function(){this.segEach(function(t){this.trigger("eventDestroy",t.event,t.event,t.el)})},resolveEventEl:function(e,i){var n=this.trigger("eventRender",e,e,i);return n===!1?i=null:n&&n!==!0&&(i=t(n)),i},showEvent:function(t){this.segEach(function(t){t.el.css("visibility","")},t)},hideEvent:function(t){this.segEach(function(t){t.el.css("visibility","hidden")},t)},segEach:function(t,e){var i,n=this.getSegs();for(i=0;n.length>i;i++)e&&n[i].event._id!==e._id||t.call(this,n[i])},getSegs:function(){},renderDrag:function(){},destroyDrag:function(){},documentDragStart:function(e){var i,n=this,r=null;this.opt("droppable")&&(i=new U(this.coordMap,{cellOver:function(t,e){r=e,n.renderDrag(e)},cellOut:function(){r=null,n.destroyDrag()}}),t(document).one("dragstop",function(t,e){n.destroyDrag(),r&&n.trigger("drop",t.target,r,t,e)}),i.startDrag(e))},select:function(t,e,i){this.unselect(i),this.renderSelection(t,e),this.reportSelection(t,e,i)},renderSelection:function(){},reportSelection:function(t,e,i){this.isSelected=!0,this.trigger("select",null,t,e,i)},unselect:function(t){this.isSelected&&(this.isSelected=!1,this.destroySelection(),this.trigger("unselect",null,t))},destroySelection:function(){},documentMousedown:function(e){var i;this.isSelected&&this.opt("unselectAuto")&&D(e)&&(i=this.opt("unselectCancel"),i&&t(e.target).closest(i).length||this.unselect(e))}},ge.prototype=x(pe.prototype),t.extend(ge.prototype,{dayGrid:null,dayNumbersVisible:!1,weekNumbersVisible:!1,weekNumberWidth:null,headRowEl:null,render:function(t,e,i){this.rowCnt=t,this.colCnt=e,this.dayNumbersVisible=i,this.weekNumbersVisible=this.opt("weekNumbers"),this.dayGrid.numbersVisible=this.dayNumbersVisible||this.weekNumbersVisible,this.el.addClass("fc-basic-view").html(this.renderHtml()),this.headRowEl=this.el.find("thead .fc-row"),this.scrollerEl=this.el.find(".fc-day-grid-container"),this.dayGrid.coordMap.containerEl=this.scrollerEl,this.dayGrid.el=this.el.find(".fc-day-grid"),this.dayGrid.render(this.hasRigidRows()),pe.prototype.render.call(this)},destroy:function(){this.dayGrid.destroy(),pe.prototype.destroy.call(this)},renderHtml:function(){return'"+""+""+""+""+'"+""+""+"
'+this.dayGrid.headHtml()+"
'+'
'+'
'+"
"+"
"},headIntroHtml:function(){return this.weekNumbersVisible?'
"+""+P(this.opt("weekNumberTitle"))+""+""+""+this.calendar.calculateWeekNumber(this.cellToDate(t,0))+""+"'+i.date()+""},weekNumberStyleAttr:function(){return null!==this.weekNumberWidth?'style="width:'+this.weekNumberWidth+'px"':""},hasRigidRows:function(){var t=this.opt("eventLimit");return t&&"number"!=typeof t},updateWidth:function(){this.weekNumbersVisible&&(this.weekNumberWidth=m(this.el.find(".fc-week-number")))},setHeight:function(t,e){var i,n=this.opt("eventLimit");y(this.scrollerEl),f(this.headRowEl),this.dayGrid.destroySegPopover(),n&&"number"==typeof n&&this.dayGrid.limitRows(n),i=this.computeScrollerHeight(t),this.setGridHeight(i,e),n&&"number"!=typeof n&&this.dayGrid.limitRows(n),!e&&v(this.scrollerEl,i)&&(u(this.headRowEl,E(this.scrollerEl)),i=this.computeScrollerHeight(t),this.scrollerEl.height(i),this.restoreScroll())},setGridHeight:function(t,e){e?g(this.dayGrid.rowEls):p(this.dayGrid.rowEls,t,!0)},renderEvents:function(t){this.dayGrid.renderEvents(t),this.updateHeight(),pe.prototype.renderEvents.call(this,t)},getSegs:function(){return this.dayGrid.getSegs()},destroyEvents:function(){pe.prototype.destroyEvents.call(this),this.recordScroll(),this.dayGrid.destroyEvents()},renderDrag:function(t,e,i){return this.dayGrid.renderDrag(t,e,i)},destroyDrag:function(){this.dayGrid.destroyDrag()},renderSelection:function(t,e){this.dayGrid.renderSelection(t,e)},destroySelection:function(){this.dayGrid.destroySelection()}}),r({fixedWeekCount:!0}),ze.month=me,me.prototype=x(ge.prototype),t.extend(me.prototype,{name:"month",incrementDate:function(t,e){return t.clone().stripTime().add(e,"months").startOf("month")},render:function(t){var e;this.intervalStart=t.clone().stripTime().startOf("month"),this.intervalEnd=this.intervalStart.clone().add(1,"months"),this.start=this.intervalStart.clone(),this.start=this.skipHiddenDays(this.start),this.start.startOf("week"),this.start=this.skipHiddenDays(this.start),this.end=this.intervalEnd.clone(),this.end=this.skipHiddenDays(this.end,-1,!0),this.end.add((7-this.end.weekday())%7,"days"),this.end=this.skipHiddenDays(this.end,-1,!0),e=Math.ceil(this.end.diff(this.start,"weeks",!0)),this.isFixedWeeks()&&(this.end.add(6-e,"weeks"),e=6),this.title=this.calendar.formatDate(this.intervalStart,this.opt("titleFormat")),ge.prototype.render.call(this,e,this.getCellsPerWeek(),!0)},setGridHeight:function(t,e){e=e||"variable"===this.opt("weekMode"),e&&(t*=this.rowCnt/6),p(this.dayGrid.rowEls,t,!e)},isFixedWeeks:function(){var t=this.opt("weekMode");return t?"fixed"===t:this.opt("fixedWeekCount")}}),ze.basicWeek=ve,ve.prototype=x(ge.prototype),t.extend(ve.prototype,{name:"basicWeek",incrementDate:function(t,e){return t.clone().stripTime().add(e,"weeks").startOf("week")},render:function(t){this.intervalStart=t.clone().stripTime().startOf("week"),this.intervalEnd=this.intervalStart.clone().add(1,"weeks"),this.start=this.skipHiddenDays(this.intervalStart),this.end=this.skipHiddenDays(this.intervalEnd,-1,!0),this.title=this.calendar.formatRange(this.start,this.end.clone().subtract(1),this.opt("titleFormat")," — "),ge.prototype.render.call(this,1,this.getCellsPerWeek(),!1)}}),ze.basicDay=ye,ye.prototype=x(ge.prototype),t.extend(ye.prototype,{name:"basicDay",incrementDate:function(t,e){var i=t.clone().stripTime().add(e,"days");return i=this.skipHiddenDays(i,0>e?-1:1)},render:function(t){this.start=this.intervalStart=t.clone().stripTime(),this.end=this.intervalEnd=this.start.clone().add(1,"days"),this.title=this.calendar.formatDate(this.start,this.opt("titleFormat")),ge.prototype.render.call(this,1,1,!1)}}),r({allDaySlot:!0,allDayText:"all-day",scrollTime:"06:00:00",slotDuration:"00:30:00",axisFormat:we,timeFormat:{agenda:Ee},minTime:"00:00:00",maxTime:"24:00:00",slotEventOverlap:!0});var Fe=5;De.prototype=x(pe.prototype),t.extend(De.prototype,{timeGrid:null,dayGrid:null,axisWidth:null,noScrollRowEls:null,bottomRuleEl:null,bottomRuleHeight:null,render:function(e){this.rowCnt=1,this.colCnt=e,this.el.addClass("fc-agenda-view").html(this.renderHtml()),this.scrollerEl=this.el.find(".fc-time-grid-container"),this.timeGrid.coordMap.containerEl=this.scrollerEl,this.timeGrid.el=this.el.find(".fc-time-grid"),this.timeGrid.render(),this.bottomRuleEl=t('
').appendTo(this.timeGrid.el),this.dayGrid&&(this.dayGrid.el=this.el.find(".fc-day-grid"),this.dayGrid.render(),this.dayGrid.bottomCoordPadding=this.dayGrid.el.next("hr").outerHeight()),this.noScrollRowEls=this.el.find(".fc-row:not(.fc-scroller *)"),pe.prototype.render.call(this),this.resetScroll()},destroy:function(){this.timeGrid.destroy(),this.dayGrid&&this.dayGrid.destroy(),pe.prototype.destroy.call(this)},renderHtml:function(){return'"+""+""+""+""+'"+""+""+"
'+this.timeGrid.headHtml()+"
'+(this.dayGrid?'

':"")+'
'+'
'+"
"+"
"},headIntroHtml:function(){var t,e,i,n;return this.opt("weekNumbers")?(t=this.cellToDate(0,0),e=this.calendar.calculateWeekNumber(t),i=this.opt("weekNumberTitle"),n=this.opt("isRTL")?e+i:i+e,'
"+""+P(n)+""+""+""+(this.opt("allDayHtml")||P(this.opt("allDayText")))+""+"'+P(r.name)+"s are globally styled */ - border-color: transparent; - - /* don't put a border between events and/or the day number */ - border-bottom: 0; -} - -.fc-row .fc-content-skeleton tbody td, /* cells with events inside (so NOT the day number cell) */ -.fc-row .fc-helper-skeleton tbody td { - /* don't put a border between event cells */ - border-top: 0; -} - - -/* Scrolling Container ---------------------------------------------------------------------------------------------------*/ - -.fc-scroller { /* this class goes on elements for guaranteed vertical scrollbars */ - overflow-y: scroll; - overflow-x: hidden; -} - -.fc-scroller > * { /* we expect an immediate inner element */ - position: relative; /* re-scope all positions */ - width: 100%; /* hack to force re-sizing this inner element when scrollbars appear/disappear */ - overflow: hidden; /* don't let negative margins or absolute positioning create further scroll */ -} - - -/* Global Event Styles ---------------------------------------------------------------------------------------------------*/ - -.fc-event { - position: relative; /* for resize handle and other inner positioning */ - display: block; /* make the tag block */ - font-size: .85em; - line-height: 1.3; - border-radius: 3px; - border: 1px solid #3a87ad; /* default BORDER color */ - background-color: #3a87ad; /* default BACKGROUND color */ - font-weight: normal; /* undo jqui's ui-widget-header bold */ -} - -/* overpower some of bootstrap's and jqui's styles on tags */ -.fc-event, -.fc-event:hover, -.ui-widget .fc-event { - color: #fff; /* default TEXT color */ - text-decoration: none; /* if has an href */ -} - -.fc-event[href], -.fc-event.fc-draggable { - cursor: pointer; /* give events with links and draggable events a hand mouse pointer */ -} - - -/* DayGrid events ----------------------------------------------------------------------------------------------------- -We use the full "fc-day-grid-event" class instead of using descendants because the event won't -be a descendant of the grid when it is being dragged. -*/ - -.fc-day-grid-event { - margin: 1px 2px 0; /* spacing between events and edges */ - padding: 0 1px; -} - -/* events that are continuing to/from another week. kill rounded corners and butt up against edge */ - -.fc-ltr .fc-day-grid-event.fc-not-start, -.fc-rtl .fc-day-grid-event.fc-not-end { - margin-left: 0; - border-left-width: 0; - padding-left: 1px; /* replace the border with padding */ - border-top-left-radius: 0; - border-bottom-left-radius: 0; -} - -.fc-ltr .fc-day-grid-event.fc-not-end, -.fc-rtl .fc-day-grid-event.fc-not-start { - margin-right: 0; - border-right-width: 0; - padding-right: 1px; /* replace the border with padding */ - border-top-right-radius: 0; - border-bottom-right-radius: 0; -} - -.fc-day-grid-event > .fc-content { /* force events to be one-line tall */ - white-space: nowrap; - overflow: hidden; -} - -.fc-day-grid-event .fc-time { - font-weight: bold; -} - -/* resize handle (outside of fc-content, so can go outside of bounds) */ - -.fc-day-grid-event .fc-resizer { - position: absolute; - top: 0; - bottom: 0; - width: 7px; -} - -.fc-ltr .fc-day-grid-event .fc-resizer { - right: -3px; - cursor: e-resize; -} - -.fc-rtl .fc-day-grid-event .fc-resizer { - left: -3px; - cursor: w-resize; -} - - -/* Event Limiting ---------------------------------------------------------------------------------------------------*/ - -/* "more" link that represents hidden events */ - -a.fc-more { - margin: 1px 3px; - font-size: .85em; - cursor: pointer; - text-decoration: none; -} - -a.fc-more:hover { - text-decoration: underline; -} - -.fc-limited { /* rows and cells that are hidden because of a "more" link */ - display: none; -} - -/* popover that appears when "more" link is clicked */ - -.fc-day-grid .fc-row { - z-index: 1; /* make the "more" popover one higher than this */ -} - -.fc-more-popover { - z-index: 2; - width: 220px; -} - -.fc-more-popover .fc-event-container { - padding: 10px; -} - -/* Toolbar ---------------------------------------------------------------------------------------------------*/ - -.fc-toolbar { - text-align: center; - margin-bottom: 1em; -} - -.fc-toolbar .fc-left { - float: left; -} - -.fc-toolbar .fc-right { - float: right; -} - -.fc-toolbar .fc-center { - display: inline-block; -} - -/* the things within each left/right/center section */ -.fc .fc-toolbar > * > * { /* extra precedence to override button border margins */ - float: left; - margin-left: .75em; -} - -/* the first thing within each left/center/right section */ -.fc .fc-toolbar > * > :first-child { /* extra precedence to override button border margins */ - margin-left: 0; -} - -/* title text */ - -.fc-toolbar h2 { - margin: 0; -} - -/* button layering (for border precedence) */ - -.fc-toolbar button { - position: relative; -} - -.fc-toolbar .fc-state-hover, -.fc-toolbar .ui-state-hover { - z-index: 2; -} - -.fc-toolbar .fc-state-down { - z-index: 3; -} - -.fc-toolbar .fc-state-active, -.fc-toolbar .ui-state-active { - z-index: 4; -} - -.fc-toolbar button:focus { - z-index: 5; -} - - -/* View Structure ---------------------------------------------------------------------------------------------------*/ - -/* undo twitter bootstrap's box-sizing rules. normalizes positioning techniques */ -/* don't do this for the toolbar because we'll want bootstrap to style those buttons as some pt */ -.fc-view-container *, -.fc-view-container *:before, -.fc-view-container *:after { - -webkit-box-sizing: content-box; - -moz-box-sizing: content-box; - box-sizing: content-box; -} - -.fc-view, /* scope positioning and z-index's for everything within the view */ -.fc-view > table { /* so dragged elements can be above the view's main element */ - position: relative; - z-index: 1; -} - -/* BasicView ---------------------------------------------------------------------------------------------------*/ - -/* day row structure */ - -.fc-basicWeek-view .fc-content-skeleton, -.fc-basicDay-view .fc-content-skeleton { - /* we are sure there are no day numbers in these views, so... */ - padding-top: 1px; /* add a pixel to make sure there are 2px padding above events */ - padding-bottom: 1em; /* ensure a space at bottom of cell for user selecting/clicking */ -} - -.fc-basic-view tbody .fc-row { - min-height: 4em; /* ensure that all rows are at least this tall */ -} - -/* a "rigid" row will take up a constant amount of height because content-skeleton is absolute */ - -.fc-row.fc-rigid { - overflow: hidden; -} - -.fc-row.fc-rigid .fc-content-skeleton { - position: absolute; - top: 0; - left: 0; - right: 0; -} - -/* week and day number styling */ - -.fc-basic-view .fc-week-number, -.fc-basic-view .fc-day-number { - padding: 0 2px; -} - -.fc-basic-view td.fc-week-number span, -.fc-basic-view td.fc-day-number { - padding-top: 2px; - padding-bottom: 2px; -} - -.fc-basic-view .fc-week-number { - text-align: center; -} - -.fc-basic-view .fc-week-number span { - /* work around the way we do column resizing and ensure a minimum width */ - display: inline-block; - min-width: 1.25em; -} - -.fc-ltr .fc-basic-view .fc-day-number { - text-align: right; -} - -.fc-rtl .fc-basic-view .fc-day-number { - text-align: left; -} - -.fc-day-number.fc-other-month { - opacity: 0.3; - filter: alpha(opacity=30); /* for IE */ - /* opacity with small font can sometimes look too faded - might want to set the 'color' property instead - making day-numbers bold also fixes the problem */ -} - -/* AgendaView all-day area ---------------------------------------------------------------------------------------------------*/ - -.fc-agenda-view .fc-day-grid { - position: relative; - z-index: 2; /* so the "more.." popover will be over the time grid */ -} - -.fc-agenda-view .fc-day-grid .fc-row { - min-height: 3em; /* all-day section will never get shorter than this */ -} - -.fc-agenda-view .fc-day-grid .fc-row .fc-content-skeleton { - padding-top: 1px; /* add a pixel to make sure there are 2px padding above events */ - padding-bottom: 1em; /* give space underneath events for clicking/selecting days */ -} - - -/* TimeGrid axis running down the side (for both the all-day area and the slot area) ---------------------------------------------------------------------------------------------------*/ - -.fc .fc-axis { /* .fc to overcome default cell styles */ - vertical-align: middle; - padding: 0 4px; - white-space: nowrap; -} - -.fc-ltr .fc-axis { - text-align: right; -} - -.fc-rtl .fc-axis { - text-align: left; -} - -.ui-widget td.fc-axis { - font-weight: normal; /* overcome jqui theme making it bold */ -} - - -/* TimeGrid Structure ---------------------------------------------------------------------------------------------------*/ - -.fc-time-grid-container, /* so scroll container's z-index is below all-day */ -.fc-time-grid { /* so slats/bg/content/etc positions get scoped within here */ - position: relative; - z-index: 1; -} - -.fc-time-grid { - min-height: 100%; /* so if height setting is 'auto', .fc-bg stretches to fill height */ -} - -.fc-time-grid table { /* don't put outer borders on slats/bg/content/etc */ - border: 0 hidden transparent; -} - -.fc-time-grid > .fc-bg { - z-index: 1; -} - -.fc-time-grid .fc-slats, -.fc-time-grid > hr { /* the
AgendaView injects when grid is shorter than scroller */ - position: relative; - z-index: 2; -} - -.fc-time-grid .fc-highlight-skeleton { - z-index: 3; -} - -.fc-time-grid .fc-content-skeleton { - position: absolute; - z-index: 4; - top: 0; - left: 0; - right: 0; -} - -.fc-time-grid > .fc-helper-skeleton { - z-index: 5; -} - - -/* TimeGrid Slats (lines that run horizontally) ---------------------------------------------------------------------------------------------------*/ - -.fc-slats td { - height: 1.5em; - border-bottom: 0; /* each cell is responsible for its top border */ -} - -.fc-slats .fc-minor td { - border-top-style: dotted; -} - -.fc-slats .ui-widget-content { /* for jqui theme */ - background: none; /* see through to fc-bg */ -} - - -/* TimeGrid Highlighting Slots ---------------------------------------------------------------------------------------------------*/ - -.fc-time-grid .fc-highlight-container { /* a div within a cell within the fc-highlight-skeleton */ - position: relative; /* scopes the left/right of the fc-highlight to be in the column */ -} - -.fc-time-grid .fc-highlight { - position: absolute; - left: 0; - right: 0; - /* top and bottom will be in by JS */ -} - - -/* TimeGrid Event Containment ---------------------------------------------------------------------------------------------------*/ - -.fc-time-grid .fc-event-container { /* a div within a cell within the fc-content-skeleton */ - position: relative; -} - -.fc-ltr .fc-time-grid .fc-event-container { /* space on the sides of events for LTR (default) */ - margin: 0 2.5% 0 2px; -} - -.fc-rtl .fc-time-grid .fc-event-container { /* space on the sides of events for RTL */ - margin: 0 2px 0 2.5%; -} - -.fc-time-grid .fc-event { - position: absolute; - z-index: 1; /* scope inner z-index's */ -} - - -/* TimeGrid Event Styling ----------------------------------------------------------------------------------------------------- -We use the full "fc-time-grid-event" class instead of using descendants because the event won't -be a descendant of the grid when it is being dragged. -*/ - -.fc-time-grid-event.fc-not-start { /* events that are continuing from another day */ - /* replace space made by the top border with padding */ - border-top-width: 0; - padding-top: 1px; - - /* remove top rounded corners */ - border-top-left-radius: 0; - border-top-right-radius: 0; -} - -.fc-time-grid-event.fc-not-end { - /* replace space made by the top border with padding */ - border-bottom-width: 0; - padding-bottom: 1px; - - /* remove bottom rounded corners */ - border-bottom-left-radius: 0; - border-bottom-right-radius: 0; -} - -.fc-time-grid-event { - overflow: hidden; /* don't let the bg flow over rounded corners */ -} - -.fc-time-grid-event > .fc-content { /* contains the time and title, but no bg and resizer */ - position: relative; - z-index: 2; /* above the bg */ -} - -.fc-time-grid-event .fc-time, -.fc-time-grid-event .fc-title { - padding: 0 1px; -} - -.fc-time-grid-event .fc-time { - font-size: .85em; - white-space: nowrap; -} - -.fc-time-grid-event .fc-bg { - z-index: 1; - background: #fff; - opacity: .25; - filter: alpha(opacity=25); /* for IE */ -} - -/* short mode, where time and title are on the same line */ - -.fc-time-grid-event.fc-short .fc-content { - /* don't wrap to second line (now that contents will be inline) */ - white-space: nowrap; -} - -.fc-time-grid-event.fc-short .fc-time, -.fc-time-grid-event.fc-short .fc-title { - /* put the time and title on the same line */ - display: inline-block; - vertical-align: top; -} - -.fc-time-grid-event.fc-short .fc-time span { - display: none; /* don't display the full time text... */ -} - -.fc-time-grid-event.fc-short .fc-time:before { - content: attr(data-start); /* ...instead, display only the start time */ -} - -.fc-time-grid-event.fc-short .fc-time:after { - content: "\000A0-\000A0"; /* seperate with a dash, wrapped in nbsp's */ -} - -.fc-time-grid-event.fc-short .fc-title { - font-size: .85em; /* make the title text the same size as the time */ - padding: 0; /* undo padding from above */ -} - -/* resizer */ - -.fc-time-grid-event .fc-resizer { - position: absolute; - z-index: 3; /* above content */ - left: 0; - right: 0; - bottom: 0; - height: 8px; - overflow: hidden; - line-height: 8px; - font-size: 11px; - font-family: monospace; - text-align: center; - cursor: s-resize; -} - -.fc-time-grid-event .fc-resizer:after { - content: "="; -} +/*! + * FullCalendar v2.1.1 Stylesheet + * Docs & License: http://arshaw.com/fullcalendar/ + * (c) 2013 Adam Shaw + */ + + +.fc { + direction: ltr; + text-align: left; +} + +.fc-rtl { + text-align: right; +} + +body .fc { /* extra precedence to overcome jqui */ + font-size: 1em; +} + + +/* Colors +--------------------------------------------------------------------------------------------------*/ + +.fc-unthemed th, +.fc-unthemed td, +.fc-unthemed hr, +.fc-unthemed thead, +.fc-unthemed tbody, +.fc-unthemed .fc-row, +.fc-unthemed .fc-popover { + border-color: #ddd; +} + +.fc-unthemed .fc-popover { + background-color: #fff; +} + +.fc-unthemed hr, +.fc-unthemed .fc-popover .fc-header { + background: #eee; +} + +.fc-unthemed .fc-popover .fc-header .fc-close { + color: #666; +} + +.fc-unthemed .fc-today { + background: #fcf8e3; +} + +.fc-highlight { /* when user is selecting cells */ + background: #bce8f1; + opacity: .3; + filter: alpha(opacity=30); /* for IE */ +} + + +/* Icons (inline elements with styled text that mock arrow icons) +--------------------------------------------------------------------------------------------------*/ + +.fc-icon { + display: inline-block; + font-size: 2em; + line-height: .5em; + height: .5em; /* will make the total height 1em */ + font-family: "Courier New", Courier, monospace; +} + +.fc-icon-left-single-arrow:after { + content: "\02039"; + font-weight: bold; +} + +.fc-icon-right-single-arrow:after { + content: "\0203A"; + font-weight: bold; +} + +.fc-icon-left-double-arrow:after { + content: "\000AB"; +} + +.fc-icon-right-double-arrow:after { + content: "\000BB"; +} + +.fc-icon-x:after { + content: "\000D7"; +} + + +/* Buttons (styled
s are globally styled */ + border-color: transparent; + + /* don't put a border between events and/or the day number */ + border-bottom: 0; +} + +.fc-row .fc-content-skeleton tbody td, /* cells with events inside (so NOT the day number cell) */ +.fc-row .fc-helper-skeleton tbody td { + /* don't put a border between event cells */ + border-top: 0; +} + + +/* Scrolling Container +--------------------------------------------------------------------------------------------------*/ + +.fc-scroller { /* this class goes on elements for guaranteed vertical scrollbars */ + overflow-y: scroll; + overflow-x: hidden; +} + +.fc-scroller > * { /* we expect an immediate inner element */ + position: relative; /* re-scope all positions */ + width: 100%; /* hack to force re-sizing this inner element when scrollbars appear/disappear */ + overflow: hidden; /* don't let negative margins or absolute positioning create further scroll */ +} + + +/* Global Event Styles +--------------------------------------------------------------------------------------------------*/ + +.fc-event { + position: relative; /* for resize handle and other inner positioning */ + display: block; /* make the tag block */ + font-size: .85em; + line-height: 1.3; + border-radius: 3px; + border: 1px solid #3a87ad; /* default BORDER color */ + background-color: #3a87ad; /* default BACKGROUND color */ + font-weight: normal; /* undo jqui's ui-widget-header bold */ +} + +/* overpower some of bootstrap's and jqui's styles on tags */ +.fc-event, +.fc-event:hover, +.ui-widget .fc-event { + color: #fff; /* default TEXT color */ + text-decoration: none; /* if has an href */ +} + +.fc-event[href], +.fc-event.fc-draggable { + cursor: pointer; /* give events with links and draggable events a hand mouse pointer */ +} + + +/* DayGrid events +---------------------------------------------------------------------------------------------------- +We use the full "fc-day-grid-event" class instead of using descendants because the event won't +be a descendant of the grid when it is being dragged. +*/ + +.fc-day-grid-event { + margin: 1px 2px 0; /* spacing between events and edges */ + padding: 0 1px; +} + +/* events that are continuing to/from another week. kill rounded corners and butt up against edge */ + +.fc-ltr .fc-day-grid-event.fc-not-start, +.fc-rtl .fc-day-grid-event.fc-not-end { + margin-left: 0; + border-left-width: 0; + padding-left: 1px; /* replace the border with padding */ + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} + +.fc-ltr .fc-day-grid-event.fc-not-end, +.fc-rtl .fc-day-grid-event.fc-not-start { + margin-right: 0; + border-right-width: 0; + padding-right: 1px; /* replace the border with padding */ + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} + +.fc-day-grid-event > .fc-content { /* force events to be one-line tall */ + white-space: nowrap; + overflow: hidden; +} + +.fc-day-grid-event .fc-time { + font-weight: bold; +} + +/* resize handle (outside of fc-content, so can go outside of bounds) */ + +.fc-day-grid-event .fc-resizer { + position: absolute; + top: 0; + bottom: 0; + width: 7px; +} + +.fc-ltr .fc-day-grid-event .fc-resizer { + right: -3px; + cursor: e-resize; +} + +.fc-rtl .fc-day-grid-event .fc-resizer { + left: -3px; + cursor: w-resize; +} + + +/* Event Limiting +--------------------------------------------------------------------------------------------------*/ + +/* "more" link that represents hidden events */ + +a.fc-more { + margin: 1px 3px; + font-size: .85em; + cursor: pointer; + text-decoration: none; +} + +a.fc-more:hover { + text-decoration: underline; +} + +.fc-limited { /* rows and cells that are hidden because of a "more" link */ + display: none; +} + +/* popover that appears when "more" link is clicked */ + +.fc-day-grid .fc-row { + z-index: 1; /* make the "more" popover one higher than this */ +} + +.fc-more-popover { + z-index: 2; + width: 220px; +} + +.fc-more-popover .fc-event-container { + padding: 10px; +} + +/* Toolbar +--------------------------------------------------------------------------------------------------*/ + +.fc-toolbar { + text-align: center; + margin-bottom: 1em; +} + +.fc-toolbar .fc-left { + float: left; +} + +.fc-toolbar .fc-right { + float: right; +} + +.fc-toolbar .fc-center { + display: inline-block; +} + +/* the things within each left/right/center section */ +.fc .fc-toolbar > * > * { /* extra precedence to override button border margins */ + float: left; + margin-left: .75em; +} + +/* the first thing within each left/center/right section */ +.fc .fc-toolbar > * > :first-child { /* extra precedence to override button border margins */ + margin-left: 0; +} + +/* title text */ + +.fc-toolbar h2 { + margin: 0; +} + +/* button layering (for border precedence) */ + +.fc-toolbar button { + position: relative; +} + +.fc-toolbar .fc-state-hover, +.fc-toolbar .ui-state-hover { + z-index: 2; +} + +.fc-toolbar .fc-state-down { + z-index: 3; +} + +.fc-toolbar .fc-state-active, +.fc-toolbar .ui-state-active { + z-index: 4; +} + +.fc-toolbar button:focus { + z-index: 5; +} + + +/* View Structure +--------------------------------------------------------------------------------------------------*/ + +/* undo twitter bootstrap's box-sizing rules. normalizes positioning techniques */ +/* don't do this for the toolbar because we'll want bootstrap to style those buttons as some pt */ +.fc-view-container *, +.fc-view-container *:before, +.fc-view-container *:after { + -webkit-box-sizing: content-box; + -moz-box-sizing: content-box; + box-sizing: content-box; +} + +.fc-view, /* scope positioning and z-index's for everything within the view */ +.fc-view > table { /* so dragged elements can be above the view's main element */ + position: relative; + z-index: 1; +} + +/* BasicView +--------------------------------------------------------------------------------------------------*/ + +/* day row structure */ + +.fc-basicWeek-view .fc-content-skeleton, +.fc-basicDay-view .fc-content-skeleton { + /* we are sure there are no day numbers in these views, so... */ + padding-top: 1px; /* add a pixel to make sure there are 2px padding above events */ + padding-bottom: 1em; /* ensure a space at bottom of cell for user selecting/clicking */ +} + +.fc-basic-view tbody .fc-row { + min-height: 4em; /* ensure that all rows are at least this tall */ +} + +/* a "rigid" row will take up a constant amount of height because content-skeleton is absolute */ + +.fc-row.fc-rigid { + overflow: hidden; +} + +.fc-row.fc-rigid .fc-content-skeleton { + position: absolute; + top: 0; + left: 0; + right: 0; +} + +/* week and day number styling */ + +.fc-basic-view .fc-week-number, +.fc-basic-view .fc-day-number { + padding: 0 2px; +} + +.fc-basic-view td.fc-week-number span, +.fc-basic-view td.fc-day-number { + padding-top: 2px; + padding-bottom: 2px; +} + +.fc-basic-view .fc-week-number { + text-align: center; +} + +.fc-basic-view .fc-week-number span { + /* work around the way we do column resizing and ensure a minimum width */ + display: inline-block; + min-width: 1.25em; +} + +.fc-ltr .fc-basic-view .fc-day-number { + text-align: right; +} + +.fc-rtl .fc-basic-view .fc-day-number { + text-align: left; +} + +.fc-day-number.fc-other-month { + opacity: 0.3; + filter: alpha(opacity=30); /* for IE */ + /* opacity with small font can sometimes look too faded + might want to set the 'color' property instead + making day-numbers bold also fixes the problem */ +} + +/* AgendaView all-day area +--------------------------------------------------------------------------------------------------*/ + +.fc-agenda-view .fc-day-grid { + position: relative; + z-index: 2; /* so the "more.." popover will be over the time grid */ +} + +.fc-agenda-view .fc-day-grid .fc-row { + min-height: 3em; /* all-day section will never get shorter than this */ +} + +.fc-agenda-view .fc-day-grid .fc-row .fc-content-skeleton { + padding-top: 1px; /* add a pixel to make sure there are 2px padding above events */ + padding-bottom: 1em; /* give space underneath events for clicking/selecting days */ +} + + +/* TimeGrid axis running down the side (for both the all-day area and the slot area) +--------------------------------------------------------------------------------------------------*/ + +.fc .fc-axis { /* .fc to overcome default cell styles */ + vertical-align: middle; + padding: 0 4px; + white-space: nowrap; +} + +.fc-ltr .fc-axis { + text-align: right; +} + +.fc-rtl .fc-axis { + text-align: left; +} + +.ui-widget td.fc-axis { + font-weight: normal; /* overcome jqui theme making it bold */ +} + + +/* TimeGrid Structure +--------------------------------------------------------------------------------------------------*/ + +.fc-time-grid-container, /* so scroll container's z-index is below all-day */ +.fc-time-grid { /* so slats/bg/content/etc positions get scoped within here */ + position: relative; + z-index: 1; +} + +.fc-time-grid { + min-height: 100%; /* so if height setting is 'auto', .fc-bg stretches to fill height */ +} + +.fc-time-grid table { /* don't put outer borders on slats/bg/content/etc */ + border: 0 hidden transparent; +} + +.fc-time-grid > .fc-bg { + z-index: 1; +} + +.fc-time-grid .fc-slats, +.fc-time-grid > hr { /* the
AgendaView injects when grid is shorter than scroller */ + position: relative; + z-index: 2; +} + +.fc-time-grid .fc-highlight-skeleton { + z-index: 3; +} + +.fc-time-grid .fc-content-skeleton { + position: absolute; + z-index: 4; + top: 0; + left: 0; + right: 0; +} + +.fc-time-grid > .fc-helper-skeleton { + z-index: 5; +} + + +/* TimeGrid Slats (lines that run horizontally) +--------------------------------------------------------------------------------------------------*/ + +.fc-slats td { + height: 1.5em; + border-bottom: 0; /* each cell is responsible for its top border */ +} + +.fc-slats .fc-minor td { + border-top-style: dotted; +} + +.fc-slats .ui-widget-content { /* for jqui theme */ + background: none; /* see through to fc-bg */ +} + + +/* TimeGrid Highlighting Slots +--------------------------------------------------------------------------------------------------*/ + +.fc-time-grid .fc-highlight-container { /* a div within a cell within the fc-highlight-skeleton */ + position: relative; /* scopes the left/right of the fc-highlight to be in the column */ +} + +.fc-time-grid .fc-highlight { + position: absolute; + left: 0; + right: 0; + /* top and bottom will be in by JS */ +} + + +/* TimeGrid Event Containment +--------------------------------------------------------------------------------------------------*/ + +.fc-time-grid .fc-event-container { /* a div within a cell within the fc-content-skeleton */ + position: relative; +} + +.fc-ltr .fc-time-grid .fc-event-container { /* space on the sides of events for LTR (default) */ + margin: 0 2.5% 0 2px; +} + +.fc-rtl .fc-time-grid .fc-event-container { /* space on the sides of events for RTL */ + margin: 0 2px 0 2.5%; +} + +.fc-time-grid .fc-event { + position: absolute; + z-index: 1; /* scope inner z-index's */ +} + + +/* TimeGrid Event Styling +---------------------------------------------------------------------------------------------------- +We use the full "fc-time-grid-event" class instead of using descendants because the event won't +be a descendant of the grid when it is being dragged. +*/ + +.fc-time-grid-event.fc-not-start { /* events that are continuing from another day */ + /* replace space made by the top border with padding */ + border-top-width: 0; + padding-top: 1px; + + /* remove top rounded corners */ + border-top-left-radius: 0; + border-top-right-radius: 0; +} + +.fc-time-grid-event.fc-not-end { + /* replace space made by the top border with padding */ + border-bottom-width: 0; + padding-bottom: 1px; + + /* remove bottom rounded corners */ + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; +} + +.fc-time-grid-event { + overflow: hidden; /* don't let the bg flow over rounded corners */ +} + +.fc-time-grid-event > .fc-content { /* contains the time and title, but no bg and resizer */ + position: relative; + z-index: 2; /* above the bg */ +} + +.fc-time-grid-event .fc-time, +.fc-time-grid-event .fc-title { + padding: 0 1px; +} + +.fc-time-grid-event .fc-time { + font-size: .85em; + white-space: nowrap; +} + +.fc-time-grid-event .fc-bg { + z-index: 1; + background: #fff; + opacity: .25; + filter: alpha(opacity=25); /* for IE */ +} + +/* short mode, where time and title are on the same line */ + +.fc-time-grid-event.fc-short .fc-content { + /* don't wrap to second line (now that contents will be inline) */ + white-space: nowrap; +} + +.fc-time-grid-event.fc-short .fc-time, +.fc-time-grid-event.fc-short .fc-title { + /* put the time and title on the same line */ + display: inline-block; + vertical-align: top; +} + +.fc-time-grid-event.fc-short .fc-time span { + display: none; /* don't display the full time text... */ +} + +.fc-time-grid-event.fc-short .fc-time:before { + content: attr(data-start); /* ...instead, display only the start time */ +} + +.fc-time-grid-event.fc-short .fc-time:after { + content: "\000A0-\000A0"; /* seperate with a dash, wrapped in nbsp's */ +} + +.fc-time-grid-event.fc-short .fc-title { + font-size: .85em; /* make the title text the same size as the time */ + padding: 0; /* undo padding from above */ +} + +/* resizer */ + +.fc-time-grid-event .fc-resizer { + position: absolute; + z-index: 3; /* above content */ + left: 0; + right: 0; + bottom: 0; + height: 8px; + overflow: hidden; + line-height: 8px; + font-size: 11px; + font-family: monospace; + text-align: center; + cursor: s-resize; +} + +.fc-time-grid-event .fc-resizer:after { + content: "="; +} diff --git a/dist/fullcalendar.js b/dist/fullcalendar.js index 6984157..9cadab6 100644 --- a/dist/fullcalendar.js +++ b/dist/fullcalendar.js @@ -1,8399 +1,8996 @@ -/*! - * FullCalendar v2.1.1 - * Docs & License: http://arshaw.com/fullcalendar/ - * (c) 2013 Adam Shaw - */ - -(function(factory) { - if (typeof define === 'function' && define.amd) { - define([ 'jquery', 'moment' ], factory); - } - else { - factory(jQuery, moment); - } -})(function($, moment) { - -;; - -var defaults = { - - lang: 'en', - - defaultTimedEventDuration: '02:00:00', - defaultAllDayEventDuration: { days: 1 }, - forceEventDuration: false, - nextDayThreshold: '09:00:00', // 9am - - // display - defaultView: 'month', - aspectRatio: 1.35, - header: { - left: 'title', - center: '', - right: 'today prev,next' - }, - weekends: true, - weekNumbers: false, - - weekNumberTitle: 'W', - weekNumberCalculation: 'local', - - //editable: false, - - // event ajax - lazyFetching: true, - startParam: 'start', - endParam: 'end', - timezoneParam: 'timezone', - - timezone: false, - - //allDayDefault: undefined, - - // time formats - titleFormat: { - month: 'MMMM YYYY', // like "September 1986". each language will override this - week: 'll', // like "Sep 4 1986" - day: 'LL' // like "September 4 1986" - }, - columnFormat: { - month: 'ddd', // like "Sat" - week: generateWeekColumnFormat, - day: 'dddd' // like "Saturday" - }, - timeFormat: { // for event elements - 'default': generateShortTimeFormat - }, - - displayEventEnd: { - month: false, - basicWeek: false, - 'default': true - }, - - // locale - isRTL: false, - defaultButtonText: { - prev: "prev", - next: "next", - prevYear: "prev year", - nextYear: "next year", - today: 'today', - month: 'month', - week: 'week', - day: 'day' - }, - - buttonIcons: { - prev: 'left-single-arrow', - next: 'right-single-arrow', - prevYear: 'left-double-arrow', - nextYear: 'right-double-arrow' - }, - - // jquery-ui theming - theme: false, - themeButtonIcons: { - prev: 'circle-triangle-w', - next: 'circle-triangle-e', - prevYear: 'seek-prev', - nextYear: 'seek-next' - }, - - dragOpacity: .75, - dragRevertDuration: 500, - dragScroll: true, - - //selectable: false, - unselectAuto: true, - - dropAccept: '*', - - eventLimit: false, - eventLimitText: 'more', - eventLimitClick: 'popover', - dayPopoverFormat: 'LL', - - handleWindowResize: true, - windowResizeDelay: 200 // milliseconds before a rerender happens - -}; - - -function generateShortTimeFormat(options, langData) { - return langData.longDateFormat('LT') - .replace(':mm', '(:mm)') - .replace(/(\Wmm)$/, '($1)') // like above, but for foreign langs - .replace(/\s*a$/i, 't'); // convert to AM/PM/am/pm to lowercase one-letter. remove any spaces beforehand -} - - -function generateWeekColumnFormat(options, langData) { - var format = langData.longDateFormat('L'); // for the format like "MM/DD/YYYY" - format = format.replace(/^Y+[^\w\s]*|[^\w\s]*Y+$/g, ''); // strip the year off the edge, as well as other misc non-whitespace chars - if (options.isRTL) { - format += ' ddd'; // for RTL, add day-of-week to end - } - else { - format = 'ddd ' + format; // for LTR, add day-of-week to beginning - } - return format; -} - - -var langOptionHash = { - en: { - columnFormat: { - week: 'ddd M/D' // override for english. different from the generated default, which is MM/DD - }, - dayPopoverFormat: 'dddd, MMMM D' - } -}; - - -// right-to-left defaults -var rtlDefaults = { - header: { - left: 'next,prev today', - center: '', - right: 'title' - }, - buttonIcons: { - prev: 'right-single-arrow', - next: 'left-single-arrow', - prevYear: 'right-double-arrow', - nextYear: 'left-double-arrow' - }, - themeButtonIcons: { - prev: 'circle-triangle-e', - next: 'circle-triangle-w', - nextYear: 'seek-prev', - prevYear: 'seek-next' - } -}; - -;; - -var fc = $.fullCalendar = { version: "2.1.1" }; -var fcViews = fc.views = {}; - - -$.fn.fullCalendar = function(options) { - var args = Array.prototype.slice.call(arguments, 1); // for a possible method call - var res = this; // what this function will return (this jQuery object by default) - - this.each(function(i, _element) { // loop each DOM element involved - var element = $(_element); - var calendar = element.data('fullCalendar'); // get the existing calendar object (if any) - var singleRes; // the returned value of this single method call - - // a method call - if (typeof options === 'string') { - if (calendar && $.isFunction(calendar[options])) { - singleRes = calendar[options].apply(calendar, args); - if (!i) { - res = singleRes; // record the first method call result - } - if (options === 'destroy') { // for the destroy method, must remove Calendar object data - element.removeData('fullCalendar'); - } - } - } - // a new calendar initialization - else if (!calendar) { // don't initialize twice - calendar = new Calendar(element, options); - element.data('fullCalendar', calendar); - calendar.render(); - } - }); - - return res; -}; - - -// function for adding/overriding defaults -function setDefaults(d) { - mergeOptions(defaults, d); -} - - -// Recursively combines option hash-objects. -// Better than `$.extend(true, ...)` because arrays are not traversed/copied. -// -// called like: -// mergeOptions(target, obj1, obj2, ...) -// -function mergeOptions(target) { - - function mergeIntoTarget(name, value) { - if ($.isPlainObject(value) && $.isPlainObject(target[name]) && !isForcedAtomicOption(name)) { - // merge into a new object to avoid destruction - target[name] = mergeOptions({}, target[name], value); // combine. `value` object takes precedence - } - else if (value !== undefined) { // only use values that are set and not undefined - target[name] = value; - } - } - - for (var i=1; i").prependTo(element); - - header = new Header(t, options); - headerElement = header.render(); - if (headerElement) { - element.prepend(headerElement); - } - - changeView(options.defaultView); - - if (options.handleWindowResize) { - windowResizeProxy = debounce(windowResize, options.windowResizeDelay); // prevents rapid calls - $(window).resize(windowResizeProxy); - } - } - - - function destroy() { - - if (currentView) { - currentView.destroy(); - } - - header.destroy(); - content.remove(); - element.removeClass('fc fc-ltr fc-rtl fc-unthemed ui-widget'); - - $(window).unbind('resize', windowResizeProxy); - } - - - function elementVisible() { - return element.is(':visible'); - } - - - - // View Rendering - // ----------------------------------------------------------------------------------- - - - function changeView(viewName) { - renderView(0, viewName); - } - - - // Renders a view because of a date change, view-type change, or for the first time - function renderView(delta, viewName) { - ignoreWindowResize++; - - // if viewName is changing, destroy the old view - if (currentView && viewName && currentView.name !== viewName) { - header.deactivateButton(currentView.name); - freezeContentHeight(); // prevent a scroll jump when view element is removed - if (currentView.start) { // rendered before? - currentView.destroy(); - } - currentView.el.remove(); - currentView = null; - } - - // if viewName changed, or the view was never created, create a fresh view - if (!currentView && viewName) { - currentView = new fcViews[viewName](t); - currentView.el = $("
").appendTo(content); - header.activateButton(viewName); - } - - if (currentView) { - - // let the view determine what the delta means - if (delta) { - date = currentView.incrementDate(date, delta); - } - - // render or rerender the view - if ( - !currentView.start || // never rendered before - delta || // explicit date window change - !date.isWithin(currentView.intervalStart, currentView.intervalEnd) // implicit date window change - ) { - if (elementVisible()) { - - freezeContentHeight(); - if (currentView.start) { // rendered before? - currentView.destroy(); - } - currentView.render(date); - unfreezeContentHeight(); - - // need to do this after View::render, so dates are calculated - updateTitle(); - updateTodayButton(); - - getAndRenderEvents(); - } - } - } - - unfreezeContentHeight(); // undo any lone freezeContentHeight calls - ignoreWindowResize--; - } - - - - // Resizing - // ----------------------------------------------------------------------------------- - - - t.getSuggestedViewHeight = function() { - if (suggestedViewHeight === undefined) { - calcSize(); - } - return suggestedViewHeight; - }; - - - t.isHeightAuto = function() { - return options.contentHeight === 'auto' || options.height === 'auto'; - }; - - - function updateSize(shouldRecalc) { - if (elementVisible()) { - - if (shouldRecalc) { - _calcSize(); - } - - ignoreWindowResize++; - currentView.updateSize(true); // isResize=true. will poll getSuggestedViewHeight() and isHeightAuto() - ignoreWindowResize--; - - return true; // signal success - } - } - - - function calcSize() { - if (elementVisible()) { - _calcSize(); - } - } - - - function _calcSize() { // assumes elementVisible - if (typeof options.contentHeight === 'number') { // exists and not 'auto' - suggestedViewHeight = options.contentHeight; - } - else if (typeof options.height === 'number') { // exists and not 'auto' - suggestedViewHeight = options.height - (headerElement ? headerElement.outerHeight(true) : 0); - } - else { - suggestedViewHeight = Math.round(content.width() / Math.max(options.aspectRatio, .5)); - } - } - - - function windowResize(ev) { - if ( - !ignoreWindowResize && - ev.target === window && // so we don't process jqui "resize" events that have bubbled up - currentView.start // view has already been rendered - ) { - if (updateSize(true)) { - currentView.trigger('windowResize', _element); - } - } - } - - - - /* Event Fetching/Rendering - -----------------------------------------------------------------------------*/ - // TODO: going forward, most of this stuff should be directly handled by the view - - - function refetchEvents() { // can be called as an API method - destroyEvents(); // so that events are cleared before user starts waiting for AJAX - fetchAndRenderEvents(); - } - - - function renderEvents() { // destroys old events if previously rendered - if (elementVisible()) { - freezeContentHeight(); - currentView.destroyEvents(); // no performance cost if never rendered - currentView.renderEvents(events); - unfreezeContentHeight(); - } - } - - - function destroyEvents() { - freezeContentHeight(); - currentView.destroyEvents(); - unfreezeContentHeight(); - } - - - function getAndRenderEvents() { - if (!options.lazyFetching || isFetchNeeded(currentView.start, currentView.end)) { - fetchAndRenderEvents(); - } - else { - renderEvents(); - } - } - - - function fetchAndRenderEvents() { - fetchEvents(currentView.start, currentView.end); - // ... will call reportEvents - // ... which will call renderEvents - } - - - // called when event data arrives - function reportEvents(_events) { - events = _events; - renderEvents(); - } - - - // called when a single event's data has been changed - function reportEventChange() { - renderEvents(); - } - - - - /* Header Updating - -----------------------------------------------------------------------------*/ - - - function updateTitle() { - header.updateTitle(currentView.title); - } - - - function updateTodayButton() { - var now = t.getNow(); - if (now.isWithin(currentView.intervalStart, currentView.intervalEnd)) { - header.disableButton('today'); - } - else { - header.enableButton('today'); - } - } - - - - /* Selection - -----------------------------------------------------------------------------*/ - - - function select(start, end) { - - start = t.moment(start); - if (end) { - end = t.moment(end); - } - else if (start.hasTime()) { - end = start.clone().add(t.defaultTimedEventDuration); - } - else { - end = start.clone().add(t.defaultAllDayEventDuration); - } - - currentView.select(start, end); - } - - - function unselect() { // safe to be called before renderView - if (currentView) { - currentView.unselect(); - } - } - - - - /* Date - -----------------------------------------------------------------------------*/ - - - function prev() { - renderView(-1); - } - - - function next() { - renderView(1); - } - - - function prevYear() { - date.add(-1, 'years'); - renderView(); - } - - - function nextYear() { - date.add(1, 'years'); - renderView(); - } - - - function today() { - date = t.getNow(); - renderView(); - } - - - function gotoDate(dateInput) { - date = t.moment(dateInput); - renderView(); - } - - - function incrementDate(delta) { - date.add(moment.duration(delta)); - renderView(); - } - - - // Forces navigation to a view for the given date. - // `viewName` can be a specific view name or a generic one like "week" or "day". - function zoomTo(newDate, viewName) { - var viewStr; - var match; - - if (!viewName || fcViews[viewName] === undefined) { // a general view name, or "auto" - viewName = viewName || 'day'; - viewStr = header.getViewsWithButtons().join(' '); // space-separated string of all the views in the header - - // try to match a general view name, like "week", against a specific one, like "agendaWeek" - match = viewStr.match(new RegExp('\\w+' + capitaliseFirstLetter(viewName))); - - // fall back to the day view being used in the header - if (!match) { - match = viewStr.match(/\w+Day/); - } - - viewName = match ? match[0] : 'agendaDay'; // fall back to agendaDay - } - - date = newDate; - changeView(viewName); - } - - - function getDate() { - return date.clone(); - } - - - - /* Height "Freezing" - -----------------------------------------------------------------------------*/ - - - function freezeContentHeight() { - content.css({ - width: '100%', - height: content.height(), - overflow: 'hidden' - }); - } - - - function unfreezeContentHeight() { - content.css({ - width: '', - height: '', - overflow: '' - }); - } - - - - /* Misc - -----------------------------------------------------------------------------*/ - - - function getCalendar() { - return t; - } - - - function getView() { - return currentView; - } - - - function option(name, value) { - if (value === undefined) { - return options[name]; - } - if (name == 'height' || name == 'contentHeight' || name == 'aspectRatio') { - options[name] = value; - updateSize(true); // true = allow recalculation of height - } - } - - - function trigger(name, thisObj) { - if (options[name]) { - return options[name].apply( - thisObj || _element, - Array.prototype.slice.call(arguments, 2) - ); - } - } - -} - -;; - -/* Top toolbar area with buttons and title -----------------------------------------------------------------------------------------------------------------------*/ -// TODO: rename all header-related things to "toolbar" - -function Header(calendar, options) { - var t = this; - - // exports - t.render = render; - t.destroy = destroy; - t.updateTitle = updateTitle; - t.activateButton = activateButton; - t.deactivateButton = deactivateButton; - t.disableButton = disableButton; - t.enableButton = enableButton; - t.getViewsWithButtons = getViewsWithButtons; - - // locals - var el = $(); - var viewsWithButtons = []; - var tm; - - - function render() { - var sections = options.header; - - tm = options.theme ? 'ui' : 'fc'; - - if (sections) { - el = $("
") - .append(renderSection('left')) - .append(renderSection('right')) - .append(renderSection('center')) - .append('
'); - - return el; - } - } - - - function destroy() { - el.remove(); - } - - - function renderSection(position) { - var sectionEl = $('
'); - var buttonStr = options.header[position]; - - if (buttonStr) { - $.each(buttonStr.split(' '), function(i) { - var groupChildren = $(); - var isOnlyButtons = true; - var groupEl; - - $.each(this.split(','), function(j, buttonName) { - var buttonClick; - var themeIcon; - var normalIcon; - var defaultText; - var customText; - var innerHtml; - var classes; - var button; - - if (buttonName == 'title') { - groupChildren = groupChildren.add($('

 

')); // we always want it to take up height - isOnlyButtons = false; - } - else { - if (calendar[buttonName]) { // a calendar method - buttonClick = function() { - calendar[buttonName](); - }; - } - else if (fcViews[buttonName]) { // a view name - buttonClick = function() { - calendar.changeView(buttonName); - }; - viewsWithButtons.push(buttonName); - } - if (buttonClick) { - - // smartProperty allows different text per view button (ex: "Agenda Week" vs "Basic Week") - themeIcon = smartProperty(options.themeButtonIcons, buttonName); - normalIcon = smartProperty(options.buttonIcons, buttonName); - defaultText = smartProperty(options.defaultButtonText, buttonName); - customText = smartProperty(options.buttonText, buttonName); - - if (customText) { - innerHtml = htmlEscape(customText); - } - else if (themeIcon && options.theme) { - innerHtml = ""; - } - else if (normalIcon && !options.theme) { - innerHtml = ""; - } - else { - innerHtml = htmlEscape(defaultText || buttonName); - } - - classes = [ - 'fc-' + buttonName + '-button', - tm + '-button', - tm + '-state-default' - ]; - - button = $( // type="button" so that it doesn't submit a form - '' - ) - .click(function() { - // don't process clicks for disabled buttons - if (!button.hasClass(tm + '-state-disabled')) { - - buttonClick(); - - // after the click action, if the button becomes the "active" tab, or disabled, - // it should never have a hover class, so remove it now. - if ( - button.hasClass(tm + '-state-active') || - button.hasClass(tm + '-state-disabled') - ) { - button.removeClass(tm + '-state-hover'); - } - } - }) - .mousedown(function() { - // the *down* effect (mouse pressed in). - // only on buttons that are not the "active" tab, or disabled - button - .not('.' + tm + '-state-active') - .not('.' + tm + '-state-disabled') - .addClass(tm + '-state-down'); - }) - .mouseup(function() { - // undo the *down* effect - button.removeClass(tm + '-state-down'); - }) - .hover( - function() { - // the *hover* effect. - // only on buttons that are not the "active" tab, or disabled - button - .not('.' + tm + '-state-active') - .not('.' + tm + '-state-disabled') - .addClass(tm + '-state-hover'); - }, - function() { - // undo the *hover* effect - button - .removeClass(tm + '-state-hover') - .removeClass(tm + '-state-down'); // if mouseleave happens before mouseup - } - ); - - groupChildren = groupChildren.add(button); - } - } - }); - - if (isOnlyButtons) { - groupChildren - .first().addClass(tm + '-corner-left').end() - .last().addClass(tm + '-corner-right').end(); - } - - if (groupChildren.length > 1) { - groupEl = $('
'); - if (isOnlyButtons) { - groupEl.addClass('fc-button-group'); - } - groupEl.append(groupChildren); - sectionEl.append(groupEl); - } - else { - sectionEl.append(groupChildren); // 1 or 0 children - } - }); - } - - return sectionEl; - } - - - function updateTitle(text) { - el.find('h2').text(text); - } - - - function activateButton(buttonName) { - el.find('.fc-' + buttonName + '-button') - .addClass(tm + '-state-active'); - } - - - function deactivateButton(buttonName) { - el.find('.fc-' + buttonName + '-button') - .removeClass(tm + '-state-active'); - } - - - function disableButton(buttonName) { - el.find('.fc-' + buttonName + '-button') - .attr('disabled', 'disabled') - .addClass(tm + '-state-disabled'); - } - - - function enableButton(buttonName) { - el.find('.fc-' + buttonName + '-button') - .removeAttr('disabled') - .removeClass(tm + '-state-disabled'); - } - - - function getViewsWithButtons() { - return viewsWithButtons; - } - -} - -;; - -fc.sourceNormalizers = []; -fc.sourceFetchers = []; - -var ajaxDefaults = { - dataType: 'json', - cache: false -}; - -var eventGUID = 1; - - -function EventManager(options) { // assumed to be a calendar - var t = this; - - - // exports - t.isFetchNeeded = isFetchNeeded; - t.fetchEvents = fetchEvents; - t.addEventSource = addEventSource; - t.removeEventSource = removeEventSource; - t.updateEvent = updateEvent; - t.renderEvent = renderEvent; - t.removeEvents = removeEvents; - t.clientEvents = clientEvents; - t.mutateEvent = mutateEvent; - - - // imports - var trigger = t.trigger; - var getView = t.getView; - var reportEvents = t.reportEvents; - var getEventEnd = t.getEventEnd; - - - // locals - var stickySource = { events: [] }; - var sources = [ stickySource ]; - var rangeStart, rangeEnd; - var currentFetchID = 0; - var pendingSourceCnt = 0; - var loadingLevel = 0; - var cache = []; - - - $.each( - (options.events ? [ options.events ] : []).concat(options.eventSources || []), - function(i, sourceInput) { - var source = buildEventSource(sourceInput); - if (source) { - sources.push(source); - } - } - ); - - - - /* Fetching - -----------------------------------------------------------------------------*/ - - - function isFetchNeeded(start, end) { - return !rangeStart || // nothing has been fetched yet? - // or, a part of the new range is outside of the old range? (after normalizing) - start.clone().stripZone() < rangeStart.clone().stripZone() || - end.clone().stripZone() > rangeEnd.clone().stripZone(); - } - - - function fetchEvents(start, end) { - rangeStart = start; - rangeEnd = end; - cache = []; - var fetchID = ++currentFetchID; - var len = sources.length; - pendingSourceCnt = len; - for (var i=0; i cells, find the cell with the largest natural width and set the widths of all the -// cells to be that width. -// PREREQUISITE: if you want a cell to take up width, it needs to have a single inner element w/ display:inline -function matchCellWidths(els) { - var maxInnerWidth = 0; - - els.find('> *').each(function(i, innerEl) { - var innerWidth = $(innerEl).outerWidth(); - if (innerWidth > maxInnerWidth) { - maxInnerWidth = innerWidth; - } - }); - - maxInnerWidth++; // sometimes not accurate of width the text needs to stay on one line. insurance - - els.width(maxInnerWidth); - - return maxInnerWidth; -} - - -// Turns a container element into a scroller if its contents is taller than the allotted height. -// Returns true if the element is now a scroller, false otherwise. -// NOTE: this method is best because it takes weird zooming dimensions into account -function setPotentialScroller(containerEl, height) { - containerEl.height(height).addClass('fc-scroller'); - - // are scrollbars needed? - if (containerEl[0].scrollHeight - 1 > containerEl[0].clientHeight) { // !!! -1 because IE is often off-by-one :( - return true; - } - - unsetScroller(containerEl); // undo - return false; -} - - -// Takes an element that might have been a scroller, and turns it back into a normal element. -function unsetScroller(containerEl) { - containerEl.height('').removeClass('fc-scroller'); -} - - -/* General DOM Utilities -----------------------------------------------------------------------------------------------------------------------*/ - - -// borrowed from https://github.com/jquery/jquery-ui/blob/1.11.0/ui/core.js#L51 -function getScrollParent(el) { - var position = el.css('position'), - scrollParent = el.parents().filter(function() { - var parent = $(this); - return (/(auto|scroll)/).test( - parent.css('overflow') + parent.css('overflow-y') + parent.css('overflow-x') - ); - }).eq(0); - - return position === 'fixed' || !scrollParent.length ? $(el[0].ownerDocument || document) : scrollParent; -} - - -// Given a container element, return an object with the pixel values of the left/right scrollbars. -// Left scrollbars might occur on RTL browsers (IE maybe?) but I have not tested. -// PREREQUISITE: container element must have a single child with display:block -function getScrollbarWidths(container) { - var containerLeft = container.offset().left; - var containerRight = containerLeft + container.width(); - var inner = container.children(); - var innerLeft = inner.offset().left; - var innerRight = innerLeft + inner.outerWidth(); - - return { - left: innerLeft - containerLeft, - right: containerRight - innerRight - }; -} - - -// Returns a boolean whether this was a left mouse click and no ctrl key (which means right click on Mac) -function isPrimaryMouseButton(ev) { - return ev.which == 1 && !ev.ctrlKey; -} - - -/* FullCalendar-specific Misc Utilities -----------------------------------------------------------------------------------------------------------------------*/ - - -// Creates a basic segment with the intersection of the two ranges. Returns undefined if no intersection. -// Expects all dates to be normalized to the same timezone beforehand. -function intersectionToSeg(subjectStart, subjectEnd, intervalStart, intervalEnd) { - var segStart, segEnd; - var isStart, isEnd; - - if (subjectEnd > intervalStart && subjectStart < intervalEnd) { // in bounds at all? - - if (subjectStart >= intervalStart) { - segStart = subjectStart.clone(); - isStart = true; - } - else { - segStart = intervalStart.clone(); - isStart = false; - } - - if (subjectEnd <= intervalEnd) { - segEnd = subjectEnd.clone(); - isEnd = true; - } - else { - segEnd = intervalEnd.clone(); - isEnd = false; - } - - return { - start: segStart, - end: segEnd, - isStart: isStart, - isEnd: isEnd - }; - } -} - - -function smartProperty(obj, name) { // get a camel-cased/namespaced property of an object - obj = obj || {}; - if (obj[name] !== undefined) { - return obj[name]; - } - var parts = name.split(/(?=[A-Z])/), - i = parts.length - 1, res; - for (; i>=0; i--) { - res = obj[parts[i].toLowerCase()]; - if (res !== undefined) { - return res; - } - } - return obj['default']; -} - - -/* Date Utilities -----------------------------------------------------------------------------------------------------------------------*/ - -var dayIDs = [ 'sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat' ]; - - -// Diffs the two moments into a Duration where full-days are recorded first, then the remaining time. -// Moments will have their timezones normalized. -function dayishDiff(a, b) { - return moment.duration({ - days: a.clone().stripTime().diff(b.clone().stripTime(), 'days'), - ms: a.time() - b.time() - }); -} - - -function isNativeDate(input) { - return Object.prototype.toString.call(input) === '[object Date]' || input instanceof Date; -} - - -function dateCompare(a, b) { // works with Moments and native Dates - return a - b; -} - - -/* General Utilities -----------------------------------------------------------------------------------------------------------------------*/ - -fc.applyAll = applyAll; // export - - -// Create an object that has the given prototype. Just like Object.create -function createObject(proto) { - var f = function() {}; - f.prototype = proto; - return new f(); -} - - -// Copies specifically-owned (non-protoype) properties of `b` onto `a`. -// FYI, $.extend would copy *all* properties of `b` onto `a`. -function extend(a, b) { - for (var i in b) { - if (b.hasOwnProperty(i)) { - a[i] = b[i]; - } - } -} - - -function applyAll(functions, thisObj, args) { - if ($.isFunction(functions)) { - functions = [ functions ]; - } - if (functions) { - var i; - var ret; - for (i=0; i/g, '>') - .replace(/'/g, ''') - .replace(/"/g, '"') - .replace(/\n/g, '
'); -} - - -function stripHtmlEntities(text) { - return text.replace(/&.*?;/g, ''); -} - - -function capitaliseFirstLetter(str) { - return str.charAt(0).toUpperCase() + str.slice(1); -} - - -// Returns a function, that, as long as it continues to be invoked, will not -// be triggered. The function will be called after it stops being called for -// N milliseconds. -// https://github.com/jashkenas/underscore/blob/1.6.0/underscore.js#L714 -function debounce(func, wait) { - var timeoutId; - var args; - var context; - var timestamp; // of most recent call - var later = function() { - var last = +new Date() - timestamp; - if (last < wait && last > 0) { - timeoutId = setTimeout(later, wait - last); - } - else { - timeoutId = null; - func.apply(context, args); - if (!timeoutId) { - context = args = null; - } - } - }; - - return function() { - context = this; - args = arguments; - timestamp = +new Date(); - if (!timeoutId) { - timeoutId = setTimeout(later, wait); - } - }; -} - -;; - -var ambigDateOfMonthRegex = /^\s*\d{4}-\d\d$/; -var ambigTimeOrZoneRegex = - /^\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+)?)?)?)?)?$/; - - -// Creating -// ------------------------------------------------------------------------------------------------- - -// Creates a new moment, similar to the vanilla moment(...) constructor, but with -// extra features (ambiguous time, enhanced formatting). When gived an existing moment, -// it will function as a clone (and retain the zone of the moment). Anything else will -// result in a moment in the local zone. -fc.moment = function() { - return makeMoment(arguments); -}; - -// Sames as fc.moment, but forces the resulting moment to be in the UTC timezone. -fc.moment.utc = function() { - var mom = makeMoment(arguments, true); - - // Force it into UTC because makeMoment doesn't guarantee it. - if (mom.hasTime()) { // don't give ambiguously-timed moments a UTC zone - mom.utc(); - } - - return mom; -}; - -// Same as fc.moment, but when given an ISO8601 string, the timezone offset is preserved. -// ISO8601 strings with no timezone offset will become ambiguously zoned. -fc.moment.parseZone = function() { - return makeMoment(arguments, true, true); -}; - -// Builds an FCMoment from args. When given an existing moment, it clones. When given a native -// Date, or called with no arguments (the current time), the resulting moment will be local. -// Anything else needs to be "parsed" (a string or an array), and will be affected by: -// parseAsUTC - if there is no zone information, should we parse the input in UTC? -// parseZone - if there is zone information, should we force the zone of the moment? -function makeMoment(args, parseAsUTC, parseZone) { - var input = args[0]; - var isSingleString = args.length == 1 && typeof input === 'string'; - var isAmbigTime; - var isAmbigZone; - var ambigMatch; - var output; // an object with fields for the new FCMoment object - - if (moment.isMoment(input)) { - output = moment.apply(null, args); // clone it - - // the ambig properties have not been preserved in the clone, so reassign them - if (input._ambigTime) { - output._ambigTime = true; - } - if (input._ambigZone) { - output._ambigZone = true; - } - } - else if (isNativeDate(input) || input === undefined) { - output = moment.apply(null, args); // will be local - } - else { // "parsing" is required - isAmbigTime = false; - isAmbigZone = false; - - if (isSingleString) { - if (ambigDateOfMonthRegex.test(input)) { - // accept strings like '2014-05', but convert to the first of the month - input += '-01'; - args = [ input ]; // for when we pass it on to moment's constructor - isAmbigTime = true; - isAmbigZone = true; - } - else if ((ambigMatch = ambigTimeOrZoneRegex.exec(input))) { - isAmbigTime = !ambigMatch[5]; // no time part? - isAmbigZone = true; - } - } - else if ($.isArray(input)) { - // arrays have no timezone information, so assume ambiguous zone - isAmbigZone = true; - } - // otherwise, probably a string with a format - - if (parseAsUTC) { - output = moment.utc.apply(moment, args); - } - else { - output = moment.apply(null, args); - } - - if (isAmbigTime) { - output._ambigTime = true; - output._ambigZone = true; // ambiguous time always means ambiguous zone - } - else if (parseZone) { // let's record the inputted zone somehow - if (isAmbigZone) { - output._ambigZone = true; - } - else if (isSingleString) { - output.zone(input); // if not a valid zone, will assign UTC - } - } - } - - return new FCMoment(output); -} - -// Our subclass of Moment. -// Accepts an object with the internal Moment properties that should be copied over to -// `this` object (most likely another Moment object). The values in this data must not -// be referenced by anything else (two moments sharing a Date object for example). -function FCMoment(internalData) { - extend(this, internalData); -} - -// Chain the prototype to Moment's -FCMoment.prototype = createObject(moment.fn); - -// We need this because Moment's implementation won't create an FCMoment, -// nor will it copy over the ambig flags. -FCMoment.prototype.clone = function() { - return makeMoment([ this ]); -}; - - -// Time-of-day -// ------------------------------------------------------------------------------------------------- - -// GETTER -// Returns a Duration with the hours/minutes/seconds/ms values of the moment. -// If the moment has an ambiguous time, a duration of 00:00 will be returned. -// -// SETTER -// You can supply a Duration, a Moment, or a Duration-like argument. -// When setting the time, and the moment has an ambiguous time, it then becomes unambiguous. -FCMoment.prototype.time = function(time) { - if (time == null) { // getter - return moment.duration({ - hours: this.hours(), - minutes: this.minutes(), - seconds: this.seconds(), - milliseconds: this.milliseconds() - }); - } - else { // setter - - delete this._ambigTime; // mark that the moment now has a time - - if (!moment.isDuration(time) && !moment.isMoment(time)) { - time = moment.duration(time); - } - - // The day value should cause overflow (so 24 hours becomes 00:00:00 of next day). - // Only for Duration times, not Moment times. - var dayHours = 0; - if (moment.isDuration(time)) { - dayHours = Math.floor(time.asDays()) * 24; - } - - // We need to set the individual fields. - // Can't use startOf('day') then add duration. In case of DST at start of day. - return this.hours(dayHours + time.hours()) - .minutes(time.minutes()) - .seconds(time.seconds()) - .milliseconds(time.milliseconds()); - } -}; - -// Converts the moment to UTC, stripping out its time-of-day and timezone offset, -// but preserving its YMD. A moment with a stripped time will display no time -// nor timezone offset when .format() is called. -FCMoment.prototype.stripTime = function() { - var a = this.toArray(); // year,month,date,hours,minutes,seconds as an array - - // set the internal UTC flag - moment.fn.utc.call(this); // call the original method, because we don't want to affect _ambigZone - - this.year(a[0]) // TODO: find a way to do this in one shot - .month(a[1]) - .date(a[2]) - .hours(0) - .minutes(0) - .seconds(0) - .milliseconds(0); - - // Mark the time as ambiguous. This needs to happen after the .utc() call, which calls .zone(), which - // clears all ambig flags. Same concept with the .year/month/date calls in the case of moment-timezone. - this._ambigTime = true; - this._ambigZone = true; // if ambiguous time, also ambiguous timezone offset - - return this; // for chaining -}; - -// Returns if the moment has a non-ambiguous time (boolean) -FCMoment.prototype.hasTime = function() { - return !this._ambigTime; -}; - - -// Timezone -// ------------------------------------------------------------------------------------------------- - -// Converts the moment to UTC, stripping out its timezone offset, but preserving its -// YMD and time-of-day. A moment with a stripped timezone offset will display no -// timezone offset when .format() is called. -FCMoment.prototype.stripZone = function() { - var a = this.toArray(); // year,month,date,hours,minutes,seconds as an array - var wasAmbigTime = this._ambigTime; - - moment.fn.utc.call(this); // set the internal UTC flag - - this.year(a[0]) // TODO: find a way to do this in one shot - .month(a[1]) - .date(a[2]) - .hours(a[3]) - .minutes(a[4]) - .seconds(a[5]) - .milliseconds(a[6]); - - if (wasAmbigTime) { - // the above call to .utc()/.zone() unfortunately clears the ambig flags, so reassign - this._ambigTime = true; - } - - // Mark the zone as ambiguous. This needs to happen after the .utc() call, which calls .zone(), which - // clears all ambig flags. Same concept with the .year/month/date calls in the case of moment-timezone. - this._ambigZone = true; - - return this; // for chaining -}; - -// Returns of the moment has a non-ambiguous timezone offset (boolean) -FCMoment.prototype.hasZone = function() { - return !this._ambigZone; -}; - -// this method implicitly marks a zone -FCMoment.prototype.zone = function(tzo) { - - if (tzo != null) { - // FYI, the delete statements need to be before the .zone() call or else chaos ensues - // for reasons I don't understand. - delete this._ambigTime; - delete this._ambigZone; - } - - return moment.fn.zone.apply(this, arguments); -}; - -// this method implicitly marks a zone -FCMoment.prototype.local = function() { - var a = this.toArray(); // year,month,date,hours,minutes,seconds as an array - var wasAmbigZone = this._ambigZone; - - // will happen anyway via .local()/.zone(), but don't want to rely on internal implementation - delete this._ambigTime; - delete this._ambigZone; - - moment.fn.local.apply(this, arguments); - - if (wasAmbigZone) { - // If the moment was ambiguously zoned, the date fields were stored as UTC. - // We want to preserve these, but in local time. - this.year(a[0]) // TODO: find a way to do this in one shot - .month(a[1]) - .date(a[2]) - .hours(a[3]) - .minutes(a[4]) - .seconds(a[5]) - .milliseconds(a[6]); - } - - return this; // for chaining -}; - -// this method implicitly marks a zone -FCMoment.prototype.utc = function() { - - // will happen anyway via .local()/.zone(), but don't want to rely on internal implementation - delete this._ambigTime; - delete this._ambigZone; - - return moment.fn.utc.apply(this, arguments); -}; - - -// Formatting -// ------------------------------------------------------------------------------------------------- - -FCMoment.prototype.format = function() { - if (arguments[0]) { - return formatDate(this, arguments[0]); // our extended formatting - } - if (this._ambigTime) { - return momentFormat(this, 'YYYY-MM-DD'); - } - if (this._ambigZone) { - return momentFormat(this, 'YYYY-MM-DD[T]HH:mm:ss'); - } - return momentFormat(this); // default moment original formatting -}; - -FCMoment.prototype.toISOString = function() { - if (this._ambigTime) { - return momentFormat(this, 'YYYY-MM-DD'); - } - if (this._ambigZone) { - return momentFormat(this, 'YYYY-MM-DD[T]HH:mm:ss'); - } - return moment.fn.toISOString.apply(this, arguments); -}; - - -// Querying -// ------------------------------------------------------------------------------------------------- - -// Is the moment within the specified range? `end` is exclusive. -FCMoment.prototype.isWithin = function(start, end) { - var a = commonlyAmbiguate([ this, start, end ]); - return a[0] >= a[1] && a[0] < a[2]; -}; - -// When isSame is called with units, timezone ambiguity is normalized before the comparison happens. -// If no units are specified, the two moments must be identically the same, with matching ambig flags. -FCMoment.prototype.isSame = function(input, units) { - var a; - - if (units) { - a = commonlyAmbiguate([ this, input ], true); // normalize timezones but don't erase times - return moment.fn.isSame.call(a[0], a[1], units); - } - else { - input = fc.moment.parseZone(input); // normalize input - return moment.fn.isSame.call(this, input) && - Boolean(this._ambigTime) === Boolean(input._ambigTime) && - Boolean(this._ambigZone) === Boolean(input._ambigZone); - } -}; - -// Make these query methods work with ambiguous moments -$.each([ - 'isBefore', - 'isAfter' -], function(i, methodName) { - FCMoment.prototype[methodName] = function(input, units) { - var a = commonlyAmbiguate([ this, input ]); - return moment.fn[methodName].call(a[0], a[1], units); - }; -}); - - -// Misc Internals -// ------------------------------------------------------------------------------------------------- - -// given an array of moment-like inputs, return a parallel array w/ moments similarly ambiguated. -// for example, of one moment has ambig time, but not others, all moments will have their time stripped. -// set `preserveTime` to `true` to keep times, but only normalize zone ambiguity. -function commonlyAmbiguate(inputs, preserveTime) { - var outputs = []; - var anyAmbigTime = false; - var anyAmbigZone = false; - var i; - - for (i=0; i "MMMM D YYYY" - formatStr = localeData.longDateFormat(formatStr) || formatStr; - // BTW, this is not important for `formatDate` because it is impossible to put custom tokens - // or non-zero areas in Moment's localized format strings. - - separator = separator || ' - '; - - return formatRangeWithChunks( - date1, - date2, - getFormatStringChunks(formatStr), - separator, - isRTL - ); -} -fc.formatRange = formatRange; // expose - - -function formatRangeWithChunks(date1, date2, chunks, separator, isRTL) { - var chunkStr; // the rendering of the chunk - var leftI; - var leftStr = ''; - var rightI; - var rightStr = ''; - var middleI; - var middleStr1 = ''; - var middleStr2 = ''; - var middleStr = ''; - - // Start at the leftmost side of the formatting string and continue until you hit a token - // that is not the same between dates. - for (leftI=0; leftIleftI; rightI--) { - chunkStr = formatSimilarChunk(date1, date2, chunks[rightI]); - if (chunkStr === false) { - break; - } - rightStr = chunkStr + rightStr; - } - - // The area in the middle is different for both of the dates. - // Collect them distinctly so we can jam them together later. - for (middleI=leftI; middleI<=rightI; middleI++) { - middleStr1 += formatDateWithChunk(date1, chunks[middleI]); - middleStr2 += formatDateWithChunk(date2, chunks[middleI]); - } - - if (middleStr1 || middleStr2) { - if (isRTL) { - middleStr = middleStr2 + separator + middleStr1; - } - else { - middleStr = middleStr1 + separator + middleStr2; - } - } - - return leftStr + middleStr + rightStr; -} - - -var similarUnitMap = { - Y: 'year', - M: 'month', - D: 'day', // day of month - d: 'day', // day of week - // prevents a separator between anything time-related... - A: 'second', // AM/PM - a: 'second', // am/pm - T: 'second', // A/P - t: 'second', // a/p - H: 'second', // hour (24) - h: 'second', // hour (12) - m: 'second', // minute - s: 'second' // second -}; -// TODO: week maybe? - - -// Given a formatting chunk, and given that both dates are similar in the regard the -// formatting chunk is concerned, format date1 against `chunk`. Otherwise, return `false`. -function formatSimilarChunk(date1, date2, chunk) { - var token; - var unit; - - if (typeof chunk === 'string') { // a literal string - return chunk; - } - else if ((token = chunk.token)) { - unit = similarUnitMap[token.charAt(0)]; - // are the dates the same for this unit of measurement? - if (unit && date1.isSame(date2, unit)) { - return momentFormat(date1, token); // would be the same if we used `date2` - // BTW, don't support custom tokens - } - } - - return false; // the chunk is NOT the same for the two dates - // BTW, don't support splitting on non-zero areas -} - - -// Chunking Utils -// ------------------------------------------------------------------------------------------------- - - -var formatStringChunkCache = {}; - - -function getFormatStringChunks(formatStr) { - if (formatStr in formatStringChunkCache) { - return formatStringChunkCache[formatStr]; - } - return (formatStringChunkCache[formatStr] = chunkFormatString(formatStr)); -} - - -// Break the formatting string into an array of chunks -function chunkFormatString(formatStr) { - var chunks = []; - var chunker = /\[([^\]]*)\]|\(([^\)]*)\)|(LT|(\w)\4*o?)|([^\w\[\(]+)/g; // TODO: more descrimination - var match; - - while ((match = chunker.exec(formatStr))) { - if (match[1]) { // a literal string inside [ ... ] - chunks.push(match[1]); - } - else if (match[2]) { // non-zero formatting inside ( ... ) - chunks.push({ maybe: chunkFormatString(match[2]) }); - } - else if (match[3]) { // a formatting token - chunks.push({ token: match[3] }); - } - else if (match[5]) { // an unenclosed literal string - chunks.push(match[5]); - } - } - - return chunks; -} - -;; - -/* A rectangular panel that is absolutely positioned over other content ------------------------------------------------------------------------------------------------------------------------- -Options: - - className (string) - - content (HTML string or jQuery element set) - - parentEl - - top - - left - - right (the x coord of where the right edge should be. not a "CSS" right) - - autoHide (boolean) - - show (callback) - - hide (callback) -*/ - -function Popover(options) { - this.options = options || {}; -} - - -Popover.prototype = { - - isHidden: true, - options: null, - el: null, // the container element for the popover. generated by this object - documentMousedownProxy: null, // document mousedown handler bound to `this` - margin: 10, // the space required between the popover and the edges of the scroll container - - - // Shows the popover on the specified position. Renders it if not already - show: function() { - if (this.isHidden) { - if (!this.el) { - this.render(); - } - this.el.show(); - this.position(); - this.isHidden = false; - this.trigger('show'); - } - }, - - - // Hides the popover, through CSS, but does not remove it from the DOM - hide: function() { - if (!this.isHidden) { - this.el.hide(); - this.isHidden = true; - this.trigger('hide'); - } - }, - - - // Creates `this.el` and renders content inside of it - render: function() { - var _this = this; - var options = this.options; - - this.el = $('
') - .addClass(options.className || '') - .css({ - // position initially to the top left to avoid creating scrollbars - top: 0, - left: 0 - }) - .append(options.content) - .appendTo(options.parentEl); - - // when a click happens on anything inside with a 'fc-close' className, hide the popover - this.el.on('click', '.fc-close', function() { - _this.hide(); - }); - - if (options.autoHide) { - $(document).on('mousedown', this.documentMousedownProxy = $.proxy(this, 'documentMousedown')); - } - }, - - - // Triggered when the user clicks *anywhere* in the document, for the autoHide feature - documentMousedown: function(ev) { - // only hide the popover if the click happened outside the popover - if (this.el && !$(ev.target).closest(this.el).length) { - this.hide(); - } - }, - - - // Hides and unregisters any handlers - destroy: function() { - this.hide(); - - if (this.el) { - this.el.remove(); - this.el = null; - } - - $(document).off('mousedown', this.documentMousedownProxy); - }, - - - // Positions the popover optimally, using the top/left/right options - position: function() { - var options = this.options; - var origin = this.el.offsetParent().offset(); - var width = this.el.outerWidth(); - var height = this.el.outerHeight(); - var windowEl = $(window); - var viewportEl = getScrollParent(this.el); - var viewportTop; - var viewportLeft; - var viewportOffset; - var top; // the "position" (not "offset") values for the popover - var left; // - - // compute top and left - top = options.top || 0; - if (options.left !== undefined) { - left = options.left; - } - else if (options.right !== undefined) { - left = options.right - width; // derive the left value from the right value - } - else { - left = 0; - } - - if (viewportEl.is(window) || viewportEl.is(document)) { // normalize getScrollParent's result - viewportEl = windowEl; - viewportTop = 0; // the window is always at the top left - viewportLeft = 0; // (and .offset() won't work if called here) - } - else { - viewportOffset = viewportEl.offset(); - viewportTop = viewportOffset.top; - viewportLeft = viewportOffset.left; - } - - // if the window is scrolled, it causes the visible area to be further down - viewportTop += windowEl.scrollTop(); - viewportLeft += windowEl.scrollLeft(); - - // constrain to the view port. if constrained by two edges, give precedence to top/left - if (options.viewportConstrain !== false) { - top = Math.min(top, viewportTop + viewportEl.outerHeight() - height - this.margin); - top = Math.max(top, viewportTop + this.margin); - left = Math.min(left, viewportLeft + viewportEl.outerWidth() - width - this.margin); - left = Math.max(left, viewportLeft + this.margin); - } - - this.el.css({ - top: top - origin.top, - left: left - origin.left - }); - }, - - - // Triggers a callback. Calls a function in the option hash of the same name. - // Arguments beyond the first `name` are forwarded on. - // TODO: better code reuse for this. Repeat code - trigger: function(name) { - if (this.options[name]) { - this.options[name].apply(this, Array.prototype.slice.call(arguments, 1)); - } - } - -}; - -;; - -/* A "coordinate map" converts pixel coordinates into an associated cell, which has an associated date ------------------------------------------------------------------------------------------------------------------------- -Common interface: - - CoordMap.prototype = { - build: function() {}, - getCell: function(x, y) {} - }; - -*/ - -/* Coordinate map for a grid component -----------------------------------------------------------------------------------------------------------------------*/ - -function GridCoordMap(grid) { - this.grid = grid; -} - - -GridCoordMap.prototype = { - - grid: null, // reference to the Grid - rows: null, // the top-to-bottom y coordinates. including the bottom of the last item - cols: null, // the left-to-right x coordinates. including the right of the last item - - containerEl: null, // container element that all coordinates are constrained to. optionally assigned - minX: null, - maxX: null, // exclusive - minY: null, - maxY: null, // exclusive - - - // Queries the grid for the coordinates of all the cells - build: function() { - this.grid.buildCoords( - this.rows = [], - this.cols = [] - ); - this.computeBounds(); - }, - - - // Given a coordinate of the document, gets the associated cell. If no cell is underneath, returns null - getCell: function(x, y) { - var cell = null; - var rows = this.rows; - var cols = this.cols; - var r = -1; - var c = -1; - var i; - - if (this.inBounds(x, y)) { - - for (i = 0; i < rows.length; i++) { - if (y >= rows[i][0] && y < rows[i][1]) { - r = i; - break; - } - } - - for (i = 0; i < cols.length; i++) { - if (x >= cols[i][0] && x < cols[i][1]) { - c = i; - break; - } - } - - if (r >= 0 && c >= 0) { - cell = { row: r, col: c }; - cell.grid = this.grid; - cell.date = this.grid.getCellDate(cell); - } - } - - return cell; - }, - - - // If there is a containerEl, compute the bounds into min/max values - computeBounds: function() { - var containerOffset; - - if (this.containerEl) { - containerOffset = this.containerEl.offset(); - this.minX = containerOffset.left; - this.maxX = containerOffset.left + this.containerEl.outerWidth(); - this.minY = containerOffset.top; - this.maxY = containerOffset.top + this.containerEl.outerHeight(); - } - }, - - - // Determines if the given coordinates are in bounds. If no `containerEl`, always true - inBounds: function(x, y) { - if (this.containerEl) { - return x >= this.minX && x < this.maxX && y >= this.minY && y < this.maxY; - } - return true; - } - -}; - - -/* Coordinate map that is a combination of multiple other coordinate maps -----------------------------------------------------------------------------------------------------------------------*/ - -function ComboCoordMap(coordMaps) { - this.coordMaps = coordMaps; -} - - -ComboCoordMap.prototype = { - - coordMaps: null, // an array of CoordMaps - - - // Builds all coordMaps - build: function() { - var coordMaps = this.coordMaps; - var i; - - for (i = 0; i < coordMaps.length; i++) { - coordMaps[i].build(); - } - }, - - - // Queries all coordMaps for the cell underneath the given coordinates, returning the first result - getCell: function(x, y) { - var coordMaps = this.coordMaps; - var cell = null; - var i; - - for (i = 0; i < coordMaps.length && !cell; i++) { - cell = coordMaps[i].getCell(x, y); - } - - return cell; - } - -}; -;; - -/* Tracks mouse movements over a CoordMap and raises events about which cell the mouse is over. -----------------------------------------------------------------------------------------------------------------------*/ -// TODO: implement scrolling - -function DragListener(coordMap, options) { - this.coordMap = coordMap; - this.options = options || {}; -} - - -DragListener.prototype = { - - coordMap: null, - options: null, - - isListening: false, - isDragging: false, - - // the cell/date the mouse was over when listening started - origCell: null, - origDate: null, - - // the cell/date the mouse is over - cell: null, - date: null, - - // coordinates of the initial mousedown - mouseX0: null, - mouseY0: null, - - // handler attached to the document, bound to the DragListener's `this` - mousemoveProxy: null, - mouseupProxy: null, - - scrollEl: null, - scrollBounds: null, // { top, bottom, left, right } - scrollTopVel: null, // pixels per second - scrollLeftVel: null, // pixels per second - scrollIntervalId: null, // ID of setTimeout for scrolling animation loop - scrollHandlerProxy: null, // this-scoped function for handling when scrollEl is scrolled - - scrollSensitivity: 30, // pixels from edge for scrolling to start - scrollSpeed: 200, // pixels per second, at maximum speed - scrollIntervalMs: 50, // millisecond wait between scroll increment - - - // Call this when the user does a mousedown. Will probably lead to startListening - mousedown: function(ev) { - if (isPrimaryMouseButton(ev)) { - - ev.preventDefault(); // prevents native selection in most browsers - - this.startListening(ev); - - // start the drag immediately if there is no minimum distance for a drag start - if (!this.options.distance) { - this.startDrag(ev); - } - } - }, - - - // Call this to start tracking mouse movements - startListening: function(ev) { - var scrollParent; - var cell; - - if (!this.isListening) { - - // grab scroll container and attach handler - if (ev && this.options.scroll) { - scrollParent = getScrollParent($(ev.target)); - if (!scrollParent.is(window) && !scrollParent.is(document)) { - this.scrollEl = scrollParent; - - // scope to `this`, and use `debounce` to make sure rapid calls don't happen - this.scrollHandlerProxy = debounce($.proxy(this, 'scrollHandler'), 100); - this.scrollEl.on('scroll', this.scrollHandlerProxy); - } - } - - this.computeCoords(); // relies on `scrollEl` - - // get info on the initial cell, date, and coordinates - if (ev) { - cell = this.getCell(ev); - this.origCell = cell; - this.origDate = cell ? cell.date : null; - - this.mouseX0 = ev.pageX; - this.mouseY0 = ev.pageY; - } - - $(document) - .on('mousemove', this.mousemoveProxy = $.proxy(this, 'mousemove')) - .on('mouseup', this.mouseupProxy = $.proxy(this, 'mouseup')) - .on('selectstart', this.preventDefault); // prevents native selection in IE<=8 - - this.isListening = true; - this.trigger('listenStart', ev); - } - }, - - - // Recomputes the drag-critical positions of elements - computeCoords: function() { - this.coordMap.build(); - this.computeScrollBounds(); - }, - - - // Called when the user moves the mouse - mousemove: function(ev) { - var minDistance; - var distanceSq; // current distance from mouseX0/mouseY0, squared - - if (!this.isDragging) { // if not already dragging... - // then start the drag if the minimum distance criteria is met - minDistance = this.options.distance || 1; - distanceSq = Math.pow(ev.pageX - this.mouseX0, 2) + Math.pow(ev.pageY - this.mouseY0, 2); - if (distanceSq >= minDistance * minDistance) { // use pythagorean theorem - this.startDrag(ev); - } - } - - if (this.isDragging) { - this.drag(ev); // report a drag, even if this mousemove initiated the drag - } - }, - - - // Call this to initiate a legitimate drag. - // This function is called internally from this class, but can also be called explicitly from outside - startDrag: function(ev) { - var cell; - - if (!this.isListening) { // startDrag must have manually initiated - this.startListening(); - } - - if (!this.isDragging) { - this.isDragging = true; - this.trigger('dragStart', ev); - - // report the initial cell the mouse is over - cell = this.getCell(ev); - if (cell) { - this.cellOver(cell, true); - } - } - }, - - - // Called while the mouse is being moved and when we know a legitimate drag is taking place - drag: function(ev) { - var cell; - - if (this.isDragging) { - cell = this.getCell(ev); - - if (!isCellsEqual(cell, this.cell)) { // a different cell than before? - if (this.cell) { - this.cellOut(); - } - if (cell) { - this.cellOver(cell); - } - } - - this.dragScroll(ev); // will possibly cause scrolling - } - }, - - - // Called when a the mouse has just moved over a new cell - cellOver: function(cell) { - this.cell = cell; - this.date = cell.date; - this.trigger('cellOver', cell, cell.date); - }, - - - // Called when the mouse has just moved out of a cell - cellOut: function() { - if (this.cell) { - this.trigger('cellOut', this.cell); - this.cell = null; - this.date = null; - } - }, - - - // Called when the user does a mouseup - mouseup: function(ev) { - this.stopDrag(ev); - this.stopListening(ev); - }, - - - // Called when the drag is over. Will not cause listening to stop however. - // A concluding 'cellOut' event will NOT be triggered. - stopDrag: function(ev) { - if (this.isDragging) { - this.stopScrolling(); - this.trigger('dragStop', ev); - this.isDragging = false; - } - }, - - - // Call this to stop listening to the user's mouse events - stopListening: function(ev) { - if (this.isListening) { - - // remove the scroll handler if there is a scrollEl - if (this.scrollEl) { - this.scrollEl.off('scroll', this.scrollHandlerProxy); - this.scrollHandlerProxy = null; - } - - $(document) - .off('mousemove', this.mousemoveProxy) - .off('mouseup', this.mouseupProxy) - .off('selectstart', this.preventDefault); - - this.mousemoveProxy = null; - this.mouseupProxy = null; - - this.isListening = false; - this.trigger('listenStop', ev); - - this.origCell = this.cell = null; - this.origDate = this.date = null; - } - }, - - - // Gets the cell underneath the coordinates for the given mouse event - getCell: function(ev) { - return this.coordMap.getCell(ev.pageX, ev.pageY); - }, - - - // Triggers a callback. Calls a function in the option hash of the same name. - // Arguments beyond the first `name` are forwarded on. - trigger: function(name) { - if (this.options[name]) { - this.options[name].apply(this, Array.prototype.slice.call(arguments, 1)); - } - }, - - - // Stops a given mouse event from doing it's native browser action. In our case, text selection. - preventDefault: function(ev) { - ev.preventDefault(); - }, - - - /* Scrolling - ------------------------------------------------------------------------------------------------------------------*/ - - - // Computes and stores the bounding rectangle of scrollEl - computeScrollBounds: function() { - var el = this.scrollEl; - var offset; - - if (el) { - offset = el.offset(); - this.scrollBounds = { - top: offset.top, - left: offset.left, - bottom: offset.top + el.outerHeight(), - right: offset.left + el.outerWidth() - }; - } - }, - - - // Called when the dragging is in progress and scrolling should be updated - dragScroll: function(ev) { - var sensitivity = this.scrollSensitivity; - var bounds = this.scrollBounds; - var topCloseness, bottomCloseness; - var leftCloseness, rightCloseness; - var topVel = 0; - var leftVel = 0; - - if (bounds) { // only scroll if scrollEl exists - - // compute closeness to edges. valid range is from 0.0 - 1.0 - topCloseness = (sensitivity - (ev.pageY - bounds.top)) / sensitivity; - bottomCloseness = (sensitivity - (bounds.bottom - ev.pageY)) / sensitivity; - leftCloseness = (sensitivity - (ev.pageX - bounds.left)) / sensitivity; - rightCloseness = (sensitivity - (bounds.right - ev.pageX)) / sensitivity; - - // translate vertical closeness into velocity. - // mouse must be completely in bounds for velocity to happen. - if (topCloseness >= 0 && topCloseness <= 1) { - topVel = topCloseness * this.scrollSpeed * -1; // negative. for scrolling up - } - else if (bottomCloseness >= 0 && bottomCloseness <= 1) { - topVel = bottomCloseness * this.scrollSpeed; - } - - // translate horizontal closeness into velocity - if (leftCloseness >= 0 && leftCloseness <= 1) { - leftVel = leftCloseness * this.scrollSpeed * -1; // negative. for scrolling left - } - else if (rightCloseness >= 0 && rightCloseness <= 1) { - leftVel = rightCloseness * this.scrollSpeed; - } - } - - this.setScrollVel(topVel, leftVel); - }, - - - // Sets the speed-of-scrolling for the scrollEl - setScrollVel: function(topVel, leftVel) { - - this.scrollTopVel = topVel; - this.scrollLeftVel = leftVel; - - this.constrainScrollVel(); // massages into realistic values - - // if there is non-zero velocity, and an animation loop hasn't already started, then START - if ((this.scrollTopVel || this.scrollLeftVel) && !this.scrollIntervalId) { - this.scrollIntervalId = setInterval( - $.proxy(this, 'scrollIntervalFunc'), // scope to `this` - this.scrollIntervalMs - ); - } - }, - - - // Forces scrollTopVel and scrollLeftVel to be zero if scrolling has already gone all the way - constrainScrollVel: function() { - var el = this.scrollEl; - - if (this.scrollTopVel < 0) { // scrolling up? - if (el.scrollTop() <= 0) { // already scrolled all the way up? - this.scrollTopVel = 0; - } - } - else if (this.scrollTopVel > 0) { // scrolling down? - if (el.scrollTop() + el[0].clientHeight >= el[0].scrollHeight) { // already scrolled all the way down? - this.scrollTopVel = 0; - } - } - - if (this.scrollLeftVel < 0) { // scrolling left? - if (el.scrollLeft() <= 0) { // already scrolled all the left? - this.scrollLeftVel = 0; - } - } - else if (this.scrollLeftVel > 0) { // scrolling right? - if (el.scrollLeft() + el[0].clientWidth >= el[0].scrollWidth) { // already scrolled all the way right? - this.scrollLeftVel = 0; - } - } - }, - - - // This function gets called during every iteration of the scrolling animation loop - scrollIntervalFunc: function() { - var el = this.scrollEl; - var frac = this.scrollIntervalMs / 1000; // considering animation frequency, what the vel should be mult'd by - - // change the value of scrollEl's scroll - if (this.scrollTopVel) { - el.scrollTop(el.scrollTop() + this.scrollTopVel * frac); - } - if (this.scrollLeftVel) { - el.scrollLeft(el.scrollLeft() + this.scrollLeftVel * frac); - } - - this.constrainScrollVel(); // since the scroll values changed, recompute the velocities - - // if scrolled all the way, which causes the vels to be zero, stop the animation loop - if (!this.scrollTopVel && !this.scrollLeftVel) { - this.stopScrolling(); - } - }, - - - // Kills any existing scrolling animation loop - stopScrolling: function() { - if (this.scrollIntervalId) { - clearInterval(this.scrollIntervalId); - this.scrollIntervalId = null; - - // when all done with scrolling, recompute positions since they probably changed - this.computeCoords(); - } - }, - - - // Get called when the scrollEl is scrolled (NOTE: this is delayed via debounce) - scrollHandler: function() { - // recompute all coordinates, but *only* if this is *not* part of our scrolling animation - if (!this.scrollIntervalId) { - this.computeCoords(); - } - } - -}; - - -// Returns `true` if the cells are identically equal. `false` otherwise. -// They must have the same row, col, and be from the same grid. -// Two null values will be considered equal, as two "out of the grid" states are the same. -function isCellsEqual(cell1, cell2) { - - if (!cell1 && !cell2) { - return true; - } - - if (cell1 && cell2) { - return cell1.grid === cell2.grid && - cell1.row === cell2.row && - cell1.col === cell2.col; - } - - return false; -} - -;; - -/* Creates a clone of an element and lets it track the mouse as it moves -----------------------------------------------------------------------------------------------------------------------*/ - -function MouseFollower(sourceEl, options) { - this.options = options = options || {}; - this.sourceEl = sourceEl; - this.parentEl = options.parentEl ? $(options.parentEl) : sourceEl.parent(); // default to sourceEl's parent -} - - -MouseFollower.prototype = { - - options: null, - - sourceEl: null, // the element that will be cloned and made to look like it is dragging - el: null, // the clone of `sourceEl` that will track the mouse - parentEl: null, // the element that `el` (the clone) will be attached to - - // the initial position of el, relative to the offset parent. made to match the initial offset of sourceEl - top0: null, - left0: null, - - // the initial position of the mouse - mouseY0: null, - mouseX0: null, - - // the number of pixels the mouse has moved from its initial position - topDelta: null, - leftDelta: null, - - mousemoveProxy: null, // document mousemove handler, bound to the MouseFollower's `this` - - isFollowing: false, - isHidden: false, - isAnimating: false, // doing the revert animation? - - - // Causes the element to start following the mouse - start: function(ev) { - if (!this.isFollowing) { - this.isFollowing = true; - - this.mouseY0 = ev.pageY; - this.mouseX0 = ev.pageX; - this.topDelta = 0; - this.leftDelta = 0; - - if (!this.isHidden) { - this.updatePosition(); - } - - $(document).on('mousemove', this.mousemoveProxy = $.proxy(this, 'mousemove')); - } - }, - - - // Causes the element to stop following the mouse. If shouldRevert is true, will animate back to original position. - // `callback` gets invoked when the animation is complete. If no animation, it is invoked immediately. - stop: function(shouldRevert, callback) { - var _this = this; - var revertDuration = this.options.revertDuration; - - function complete() { - this.isAnimating = false; - _this.destroyEl(); - - this.top0 = this.left0 = null; // reset state for future updatePosition calls - - if (callback) { - callback(); - } - } - - if (this.isFollowing && !this.isAnimating) { // disallow more than one stop animation at a time - this.isFollowing = false; - - $(document).off('mousemove', this.mousemoveProxy); - - if (shouldRevert && revertDuration && !this.isHidden) { // do a revert animation? - this.isAnimating = true; - this.el.animate({ - top: this.top0, - left: this.left0 - }, { - duration: revertDuration, - complete: complete - }); - } - else { - complete(); - } - } - }, - - - // Gets the tracking element. Create it if necessary - getEl: function() { - var el = this.el; - - if (!el) { - this.sourceEl.width(); // hack to force IE8 to compute correct bounding box - el = this.el = this.sourceEl.clone() - .css({ - position: 'absolute', - visibility: '', // in case original element was hidden (commonly through hideEvents()) - display: this.isHidden ? 'none' : '', // for when initially hidden - margin: 0, - right: 'auto', // erase and set width instead - bottom: 'auto', // erase and set height instead - width: this.sourceEl.width(), // explicit height in case there was a 'right' value - height: this.sourceEl.height(), // explicit width in case there was a 'bottom' value - opacity: this.options.opacity || '', - zIndex: this.options.zIndex - }) - .appendTo(this.parentEl); - } - - return el; - }, - - - // Removes the tracking element if it has already been created - destroyEl: function() { - if (this.el) { - this.el.remove(); - this.el = null; - } - }, - - - // Update the CSS position of the tracking element - updatePosition: function() { - var sourceOffset; - var origin; - - this.getEl(); // ensure this.el - - // make sure origin info was computed - if (this.top0 === null) { - this.sourceEl.width(); // hack to force IE8 to compute correct bounding box - sourceOffset = this.sourceEl.offset(); - origin = this.el.offsetParent().offset(); - this.top0 = sourceOffset.top - origin.top; - this.left0 = sourceOffset.left - origin.left; - } - - this.el.css({ - top: this.top0 + this.topDelta, - left: this.left0 + this.leftDelta - }); - }, - - - // Gets called when the user moves the mouse - mousemove: function(ev) { - this.topDelta = ev.pageY - this.mouseY0; - this.leftDelta = ev.pageX - this.mouseX0; - - if (!this.isHidden) { - this.updatePosition(); - } - }, - - - // Temporarily makes the tracking element invisible. Can be called before following starts - hide: function() { - if (!this.isHidden) { - this.isHidden = true; - if (this.el) { - this.el.hide(); - } - } - }, - - - // Show the tracking element after it has been temporarily hidden - show: function() { - if (this.isHidden) { - this.isHidden = false; - this.updatePosition(); - this.getEl().show(); - } - } - -}; - -;; - -/* A utility class for rendering
', // plain default HTML used for a cell when no other is available - - - // Renders the HTML for a row, leveraging custom cell-HTML-renderers based on the `rowType`. - // Also applies the "intro" and "outro" cells, which are specified by the subclass and views. - // `row` is an optional row number. - rowHtml: function(rowType, row) { - var view = this.view; - var renderCell = this.getHtmlRenderer('cell', rowType); - var cellHtml = ''; - var col; - var date; - - row = row || 0; - - for (col = 0; col < view.colCnt; col++) { - date = view.cellToDate(row, col); - cellHtml += renderCell(row, col, date); - } - - cellHtml = this.bookendCells(cellHtml, rowType, row); // apply intro and outro - - return '
's or a jQuery
' + - '' + - this.rowHtml('head') + // leverages RowRenderer - '' + - '
' + - '
'; - }, - - - // Used by the `headHtml` method, via RowRenderer, for rendering the HTML of a day-of-week header cell - headCellHtml: function(row, col, date) { - var view = this.view; - var calendar = view.calendar; - var colFormat = view.opt('columnFormat'); - - return '' + - '' + - htmlEscape(calendar.formatDate(date, colFormat)) + - ''; - }, - - - // Renders the HTML for a single-day background cell - bgCellHtml: function(row, col, date) { - var view = this.view; - var classes = this.getDayClasses(date); - - classes.unshift('fc-day', view.widgetContentClass); - - return ''; - }, - - - // Computes HTML classNames for a single-day cell - getDayClasses: function(date) { - var view = this.view; - var today = view.calendar.getNow().stripTime(); - var classes = [ 'fc-' + dayIDs[date.day()] ]; - - if ( - view.name === 'month' && - date.month() != view.intervalStart.month() - ) { - classes.push('fc-other-month'); - } - - if (date.isSame(today, 'day')) { - classes.push( - 'fc-today', - view.highlightStateClass - ); - } - else if (date < today) { - classes.push('fc-past'); - } - else { - classes.push('fc-future'); - } - - return classes; - } - -}); - -;; - -/* Event-rendering and event-interaction methods for the abstract Grid class -----------------------------------------------------------------------------------------------------------------------*/ - -$.extend(Grid.prototype, { - - mousedOverSeg: null, // the segment object the user's mouse is over. null if over nothing - isDraggingSeg: false, // is a segment being dragged? boolean - isResizingSeg: false, // is a segment being resized? boolean - - - // Renders the given events onto the grid - renderEvents: function(events) { - // subclasses must implement - }, - - - // Retrieves all rendered segment objects in this grid - getSegs: function() { - // subclasses must implement - }, - - - // Unrenders all events. Subclasses should implement, calling this super-method first. - destroyEvents: function() { - this.triggerSegMouseout(); // trigger an eventMouseout if user's mouse is over an event - }, - - - // Renders a `el` property for each seg, and only returns segments that successfully rendered - renderSegs: function(segs, disableResizing) { - var view = this.view; - var html = ''; - var renderedSegs = []; - var i; - - // build a large concatenation of event segment HTML - for (i = 0; i < segs.length; i++) { - html += this.renderSegHtml(segs[i], disableResizing); - } - - // Grab individual elements from the combined HTML string. Use each as the default rendering. - // Then, compute the 'el' for each segment. An el might be null if the eventRender callback returned false. - $(html).each(function(i, node) { - var seg = segs[i]; - var el = view.resolveEventEl(seg.event, $(node)); - if (el) { - el.data('fc-seg', seg); // used by handlers - seg.el = el; - renderedSegs.push(seg); - } - }); - - return renderedSegs; - }, - - - // Generates the HTML for the default rendering of a segment - renderSegHtml: function(seg, disableResizing) { - // subclasses must implement - }, - - - // Converts an array of event objects into an array of segment objects - eventsToSegs: function(events, intervalStart, intervalEnd) { - var _this = this; - - return $.map(events, function(event) { - return _this.eventToSegs(event, intervalStart, intervalEnd); // $.map flattens all returned arrays together - }); - }, - - - // Slices a single event into an array of event segments. - // When `intervalStart` and `intervalEnd` are specified, intersect the events with that interval. - // Otherwise, let the subclass decide how it wants to slice the segments over the grid. - eventToSegs: function(event, intervalStart, intervalEnd) { - var eventStart = event.start.clone().stripZone(); // normalize - var eventEnd = this.view.calendar.getEventEnd(event).stripZone(); // compute (if necessary) and normalize - var segs; - var i, seg; - - if (intervalStart && intervalEnd) { - seg = intersectionToSeg(eventStart, eventEnd, intervalStart, intervalEnd); - segs = seg ? [ seg ] : []; - } - else { - segs = this.rangeToSegs(eventStart, eventEnd); // defined by the subclass - } - - // assign extra event-related properties to the segment objects - for (i = 0; i < segs.length; i++) { - seg = segs[i]; - seg.event = event; - seg.eventStartMS = +eventStart; - seg.eventDurationMS = eventEnd - eventStart; - } - - return segs; - }, - - - /* Handlers - ------------------------------------------------------------------------------------------------------------------*/ - - - // Attaches event-element-related handlers to the container element and leverage bubbling - bindSegHandlers: function() { - var _this = this; - var view = this.view; - - $.each( - { - mouseenter: function(seg, ev) { - _this.triggerSegMouseover(seg, ev); - }, - mouseleave: function(seg, ev) { - _this.triggerSegMouseout(seg, ev); - }, - click: function(seg, ev) { - return view.trigger('eventClick', this, seg.event, ev); // can return `false` to cancel - }, - mousedown: function(seg, ev) { - if ($(ev.target).is('.fc-resizer') && view.isEventResizable(seg.event)) { - _this.segResizeMousedown(seg, ev); - } - else if (view.isEventDraggable(seg.event)) { - _this.segDragMousedown(seg, ev); - } - } - }, - function(name, func) { - // attach the handler to the container element and only listen for real event elements via bubbling - _this.el.on(name, '.fc-event-container > *', function(ev) { - var seg = $(this).data('fc-seg'); // grab segment data. put there by View::renderEvents - - // only call the handlers if there is not a drag/resize in progress - if (seg && !_this.isDraggingSeg && !_this.isResizingSeg) { - return func.call(this, seg, ev); // `this` will be the event element - } - }); - } - ); - }, - - - // Updates internal state and triggers handlers for when an event element is moused over - triggerSegMouseover: function(seg, ev) { - if (!this.mousedOverSeg) { - this.mousedOverSeg = seg; - this.view.trigger('eventMouseover', seg.el[0], seg.event, ev); - } - }, - - - // Updates internal state and triggers handlers for when an event element is moused out. - // Can be given no arguments, in which case it will mouseout the segment that was previously moused over. - triggerSegMouseout: function(seg, ev) { - ev = ev || {}; // if given no args, make a mock mouse event - - if (this.mousedOverSeg) { - seg = seg || this.mousedOverSeg; // if given no args, use the currently moused-over segment - this.mousedOverSeg = null; - this.view.trigger('eventMouseout', seg.el[0], seg.event, ev); - } - }, - - - /* Dragging - ------------------------------------------------------------------------------------------------------------------*/ - - - // Called when the user does a mousedown on an event, which might lead to dragging. - // Generic enough to work with any type of Grid. - segDragMousedown: function(seg, ev) { - var _this = this; - var view = this.view; - var el = seg.el; - var event = seg.event; - var newStart, newEnd; - - // A clone of the original element that will move with the mouse - var mouseFollower = new MouseFollower(seg.el, { - parentEl: view.el, - opacity: view.opt('dragOpacity'), - revertDuration: view.opt('dragRevertDuration'), - zIndex: 2 // one above the .fc-view - }); - - // Tracks mouse movement over the *view's* coordinate map. Allows dragging and dropping between subcomponents - // of the view. - var dragListener = new DragListener(view.coordMap, { - distance: 5, - scroll: view.opt('dragScroll'), - listenStart: function(ev) { - mouseFollower.hide(); // don't show until we know this is a real drag - mouseFollower.start(ev); - }, - dragStart: function(ev) { - _this.triggerSegMouseout(seg, ev); // ensure a mouseout on the manipulated event has been reported - _this.isDraggingSeg = true; - view.hideEvent(event); // hide all event segments. our mouseFollower will take over - view.trigger('eventDragStart', el[0], event, ev, {}); // last argument is jqui dummy - }, - cellOver: function(cell, date) { - var origDate = seg.cellDate || dragListener.origDate; - var res = _this.computeDraggedEventDates(seg, origDate, date); - newStart = res.start; - newEnd = res.end; - - if (view.renderDrag(newStart, newEnd, seg)) { // have the view render a visual indication - mouseFollower.hide(); // if the view is already using a mock event "helper", hide our own - } - else { - mouseFollower.show(); - } - }, - cellOut: function() { // called before mouse moves to a different cell OR moved out of all cells - newStart = null; - view.destroyDrag(); // unrender whatever was done in view.renderDrag - mouseFollower.show(); // show in case we are moving out of all cells - }, - dragStop: function(ev) { - var hasChanged = newStart && !newStart.isSame(event.start); - - // do revert animation if hasn't changed. calls a callback when finished (whether animation or not) - mouseFollower.stop(!hasChanged, function() { - _this.isDraggingSeg = false; - view.destroyDrag(); - view.showEvent(event); - view.trigger('eventDragStop', el[0], event, ev, {}); // last argument is jqui dummy - - if (hasChanged) { - view.eventDrop(el[0], event, newStart, ev); // will rerender all events... - } - }); - }, - listenStop: function() { - mouseFollower.stop(); // put in listenStop in case there was a mousedown but the drag never started - } - }); - - dragListener.mousedown(ev); // start listening, which will eventually lead to a dragStart - }, - - - // Given a segment, the dates where a drag began and ended, calculates the Event Object's new start and end dates - computeDraggedEventDates: function(seg, dragStartDate, dropDate) { - var view = this.view; - var event = seg.event; - var start = event.start; - var end = view.calendar.getEventEnd(event); - var delta; - var newStart; - var newEnd; - - if (dropDate.hasTime() === dragStartDate.hasTime()) { - delta = dayishDiff(dropDate, dragStartDate); - newStart = start.clone().add(delta); - if (event.end === null) { // do we need to compute an end? - newEnd = null; - } - else { - newEnd = end.clone().add(delta); - } - } - else { - // if switching from day <-> timed, start should be reset to the dropped date, and the end cleared - newStart = dropDate; - newEnd = null; // end should be cleared - } - - return { start: newStart, end: newEnd }; - }, - - - /* Resizing - ------------------------------------------------------------------------------------------------------------------*/ - - - // Called when the user does a mousedown on an event's resizer, which might lead to resizing. - // Generic enough to work with any type of Grid. - segResizeMousedown: function(seg, ev) { - var _this = this; - var view = this.view; - var el = seg.el; - var event = seg.event; - var start = event.start; - var end = view.calendar.getEventEnd(event); - var newEnd = null; - var dragListener; - - function destroy() { // resets the rendering - _this.destroyResize(); - view.showEvent(event); - } - - // Tracks mouse movement over the *grid's* coordinate map - dragListener = new DragListener(this.coordMap, { - distance: 5, - scroll: view.opt('dragScroll'), - dragStart: function(ev) { - _this.triggerSegMouseout(seg, ev); // ensure a mouseout on the manipulated event has been reported - _this.isResizingSeg = true; - view.trigger('eventResizeStart', el[0], event, ev, {}); // last argument is jqui dummy - }, - cellOver: function(cell, date) { - // compute the new end. don't allow it to go before the event's start - if (date.isBefore(start)) { // allows comparing ambig to non-ambig - date = start; - } - newEnd = date.clone().add(_this.cellDuration); // make it an exclusive end - - if (newEnd.isSame(end)) { - newEnd = null; - destroy(); - } - else { - _this.renderResize(start, newEnd, seg); - view.hideEvent(event); - } - }, - cellOut: function() { // called before mouse moves to a different cell OR moved out of all cells - newEnd = null; - destroy(); - }, - dragStop: function(ev) { - _this.isResizingSeg = false; - destroy(); - view.trigger('eventResizeStop', el[0], event, ev, {}); // last argument is jqui dummy - - if (newEnd) { - view.eventResize(el[0], event, newEnd, ev); // will rerender all events... - } - } - }); - - dragListener.mousedown(ev); // start listening, which will eventually lead to a dragStart - }, - - - /* Rendering Utils - ------------------------------------------------------------------------------------------------------------------*/ - - - // Generic utility for generating the HTML classNames for an event segment's element - getSegClasses: function(seg, isDraggable, isResizable) { - var event = seg.event; - var classes = [ - 'fc-event', - seg.isStart ? 'fc-start' : 'fc-not-start', - seg.isEnd ? 'fc-end' : 'fc-not-end' - ].concat( - event.className, - event.source ? event.source.className : [] - ); - - if (isDraggable) { - classes.push('fc-draggable'); - } - if (isResizable) { - classes.push('fc-resizable'); - } - - return classes; - }, - - - // Utility for generating a CSS string with all the event skin-related properties - getEventSkinCss: function(event) { - var view = this.view; - var source = event.source || {}; - var eventColor = event.color; - var sourceColor = source.color; - var optionColor = view.opt('eventColor'); - var backgroundColor = - event.backgroundColor || - eventColor || - source.backgroundColor || - sourceColor || - view.opt('eventBackgroundColor') || - optionColor; - var borderColor = - event.borderColor || - eventColor || - source.borderColor || - sourceColor || - view.opt('eventBorderColor') || - optionColor; - var textColor = - event.textColor || - source.textColor || - view.opt('eventTextColor'); - var statements = []; - if (backgroundColor) { - statements.push('background-color:' + backgroundColor); - } - if (borderColor) { - statements.push('border-color:' + borderColor); - } - if (textColor) { - statements.push('color:' + textColor); - } - return statements.join(';'); - } - -}); - - -/* Event Segment Utilities -----------------------------------------------------------------------------------------------------------------------*/ - - -// A cmp function for determining which segments should take visual priority -function compareSegs(seg1, seg2) { - return seg1.eventStartMS - seg2.eventStartMS || // earlier events go first - seg2.eventDurationMS - seg1.eventDurationMS || // tie? longer events go first - seg2.event.allDay - seg1.event.allDay || // tie? put all-day events first (booleans cast to 0/1) - (seg1.event.title || '').localeCompare(seg2.event.title); // tie? alphabetically by title -} - - -;; - -/* A component that renders a grid of whole-days that runs horizontally. There can be multiple rows, one per week. -----------------------------------------------------------------------------------------------------------------------*/ - -function DayGrid(view) { - Grid.call(this, view); // call the super-constructor -} - - -DayGrid.prototype = createObject(Grid.prototype); // declare the super-class -$.extend(DayGrid.prototype, { - - numbersVisible: false, // should render a row for day/week numbers? manually set by the view - cellDuration: moment.duration({ days: 1 }), // required for Grid.event.js. Each cell is always a single day - bottomCoordPadding: 0, // hack for extending the hit area for the last row of the coordinate grid - - rowEls: null, // set of fake row elements - dayEls: null, // set of whole-day elements comprising the row's background - helperEls: null, // set of cell skeleton elements for rendering the mock event "helper" - highlightEls: null, // set of cell skeleton elements for rendering the highlight - - - // Renders the rows and columns into the component's `this.el`, which should already be assigned. - // isRigid determins whether the individual rows should ignore the contents and be a constant height. - // Relies on the view's colCnt and rowCnt. In the future, this component should probably be self-sufficient. - render: function(isRigid) { - var view = this.view; - var html = ''; - var row; - - for (row = 0; row < view.rowCnt; row++) { - html += this.dayRowHtml(row, isRigid); - } - this.el.html(html); - - this.rowEls = this.el.find('.fc-row'); - this.dayEls = this.el.find('.fc-day'); - - // run all the day cells through the dayRender callback - this.dayEls.each(function(i, node) { - var date = view.cellToDate(Math.floor(i / view.colCnt), i % view.colCnt); - view.trigger('dayRender', null, date, $(node)); - }); - - Grid.prototype.render.call(this); // call the super-method - }, - - - destroy: function() { - this.destroySegPopover(); - }, - - - // Generates the HTML for a single row. `row` is the row number. - dayRowHtml: function(row, isRigid) { - var view = this.view; - var classes = [ 'fc-row', 'fc-week', view.widgetContentClass ]; - - if (isRigid) { - classes.push('fc-rigid'); - } - - return '' + - '
' + - '
' + - '' + - this.rowHtml('day', row) + // leverages RowRenderer. calls dayCellHtml() - '
' + - '
' + - '
' + - '' + - (this.numbersVisible ? - '' + - this.rowHtml('number', row) + // leverages RowRenderer. View will define render method - '' : - '' - ) + - '
' + - '
' + - '
'; - }, - - - // Renders the HTML for a whole-day cell. Will eventually end up in the day-row's background. - // We go through a 'day' row type instead of just doing a 'bg' row type so that the View can do custom rendering - // specifically for whole-day rows, whereas a 'bg' might also be used for other purposes (TimeGrid bg for example). - dayCellHtml: function(row, col, date) { - return this.bgCellHtml(row, col, date); - }, - - - /* Coordinates & Cells - ------------------------------------------------------------------------------------------------------------------*/ - - - // Populates the empty `rows` and `cols` arrays with coordinates of the cells. For CoordGrid. - buildCoords: function(rows, cols) { - var colCnt = this.view.colCnt; - var e, n, p; - - this.dayEls.slice(0, colCnt).each(function(i, _e) { // iterate the first row of day elements - e = $(_e); - n = e.offset().left; - if (i) { - p[1] = n; - } - p = [ n ]; - cols[i] = p; - }); - p[1] = n + e.outerWidth(); - - this.rowEls.each(function(i, _e) { - e = $(_e); - n = e.offset().top; - if (i) { - p[1] = n; - } - p = [ n ]; - rows[i] = p; - }); - p[1] = n + e.outerHeight() + this.bottomCoordPadding; // hack to extend hit area of last row - }, - - - // Converts a cell to a date - getCellDate: function(cell) { - return this.view.cellToDate(cell); // leverages the View's cell system - }, - - - // Gets the whole-day element associated with the cell - getCellDayEl: function(cell) { - return this.dayEls.eq(cell.row * this.view.colCnt + cell.col); - }, - - - // Converts a range with an inclusive `start` and an exclusive `end` into an array of segment objects - rangeToSegs: function(start, end) { - return this.view.rangeToSegments(start, end); // leverages the View's cell system - }, - - - /* Event Drag Visualization - ------------------------------------------------------------------------------------------------------------------*/ - - - // Renders a visual indication of an event hovering over the given date(s). - // `end` can be null, as well as `seg`. See View's documentation on renderDrag for more info. - // A returned value of `true` signals that a mock "helper" event has been rendered. - renderDrag: function(start, end, seg) { - var opacity; - - // always render a highlight underneath - this.renderHighlight( - start, - end || this.view.calendar.getDefaultEventEnd(true, start) - ); - - // if a segment from the same calendar but another component is being dragged, render a helper event - if (seg && !seg.el.closest(this.el).length) { - - this.renderRangeHelper(start, end, seg); - - opacity = this.view.opt('dragOpacity'); - if (opacity !== undefined) { - this.helperEls.css('opacity', opacity); - } - - return true; // a helper has been rendered - } - }, - - - // Unrenders any visual indication of a hovering event - destroyDrag: function() { - this.destroyHighlight(); - this.destroyHelper(); - }, - - - /* Event Resize Visualization - ------------------------------------------------------------------------------------------------------------------*/ - - - // Renders a visual indication of an event being resized - renderResize: function(start, end, seg) { - this.renderHighlight(start, end); - this.renderRangeHelper(start, end, seg); - }, - - - // Unrenders a visual indication of an event being resized - destroyResize: function() { - this.destroyHighlight(); - this.destroyHelper(); - }, - - - /* Event Helper - ------------------------------------------------------------------------------------------------------------------*/ - - - // Renders a mock "helper" event. `sourceSeg` is the associated internal segment object. It can be null. - renderHelper: function(event, sourceSeg) { - var helperNodes = []; - var rowStructs = this.renderEventRows([ event ]); - - // inject each new event skeleton into each associated row - this.rowEls.each(function(row, rowNode) { - var rowEl = $(rowNode); // the .fc-row - var skeletonEl = $('
'); // will be absolutely positioned - var skeletonTop; - - // If there is an original segment, match the top position. Otherwise, put it at the row's top level - if (sourceSeg && sourceSeg.row === row) { - skeletonTop = sourceSeg.el.position().top; - } - else { - skeletonTop = rowEl.find('.fc-content-skeleton tbody').position().top; - } - - skeletonEl.css('top', skeletonTop) - .find('table') - .append(rowStructs[row].tbodyEl); - - rowEl.append(skeletonEl); - helperNodes.push(skeletonEl[0]); - }); - - this.helperEls = $(helperNodes); // array -> jQuery set - }, - - - // Unrenders any visual indication of a mock helper event - destroyHelper: function() { - if (this.helperEls) { - this.helperEls.remove(); - this.helperEls = null; - } - }, - - - /* Highlighting - ------------------------------------------------------------------------------------------------------------------*/ - - - // Renders an emphasis on the given date range. `start` is an inclusive, `end` is exclusive. - renderHighlight: function(start, end) { - var segs = this.rangeToSegs(start, end); - var highlightNodes = []; - var i, seg; - var el; - - // build an event skeleton for each row that needs it - for (i = 0; i < segs.length; i++) { - seg = segs[i]; - el = $( - this.highlightSkeletonHtml(seg.leftCol, seg.rightCol + 1) // make end exclusive - ); - el.appendTo(this.rowEls[seg.row]); - highlightNodes.push(el[0]); - } - - this.highlightEls = $(highlightNodes); // array -> jQuery set - }, - - - // Unrenders any visual emphasis on a date range - destroyHighlight: function() { - if (this.highlightEls) { - this.highlightEls.remove(); - this.highlightEls = null; - } - }, - - - // Generates the HTML used to build a single-row "highlight skeleton", a table that frames highlight cells - highlightSkeletonHtml: function(startCol, endCol) { - var colCnt = this.view.colCnt; - var cellHtml = ''; - - if (startCol > 0) { - cellHtml += ' elements that should be appended to each row's content skeleton. - // Returns an array of rowStruct objects (see the bottom of `renderEventRow`). - renderEventRows: function(events) { - var segs = this.eventsToSegs(events); - var rowStructs = []; - var segRows; - var row; - - segs = this.renderSegs(segs); // returns a new array with only visible segments - segRows = this.groupSegRows(segs); // group into nested arrays - - // iterate each row of segment groupings - for (row = 0; row < segRows.length; row++) { - rowStructs.push( - this.renderEventRow(row, segRows[row]) - ); - } - - return rowStructs; - }, - - - // Builds the HTML to be used for the default element for an individual segment - renderSegHtml: function(seg, disableResizing) { - var view = this.view; - var isRTL = view.opt('isRTL'); - var event = seg.event; - var isDraggable = view.isEventDraggable(event); - var isResizable = !disableResizing && event.allDay && seg.isEnd && view.isEventResizable(event); - var classes = this.getSegClasses(seg, isDraggable, isResizable); - var skinCss = this.getEventSkinCss(event); - var timeHtml = ''; - var titleHtml; - - classes.unshift('fc-day-grid-event'); - - // Only display a timed events time if it is the starting segment - if (!event.allDay && seg.isStart) { - timeHtml = '' + htmlEscape(view.getEventTimeText(event)) + ''; - } - - titleHtml = - '' + - (htmlEscape(event.title || '') || ' ') + // we always want one line of height - ''; - - return '' + - '
' + - (isRTL ? - titleHtml + ' ' + timeHtml : // put a natural space in between - timeHtml + ' ' + titleHtml // - ) + - '
' + - (isResizable ? - '
' : - '' - ) + - ''; - }, - - - // Given a row # and an array of segments all in the same row, render a
element, a skeleton that contains - // the segments. Returns object with a bunch of internal data about how the render was calculated. - renderEventRow: function(row, rowSegs) { - var view = this.view; - var colCnt = view.colCnt; - var segLevels = this.buildSegLevels(rowSegs); // group into sub-arrays of levels - var levelCnt = Math.max(1, segLevels.length); // ensure at least one level - var tbody = $(''); - var segMatrix = []; // lookup for which segments are rendered into which level+col cells - var cellMatrix = []; // lookup for all '); - - segMatrix.push([]); - cellMatrix.push([]); - loneCellMatrix.push([]); - - // levelCnt might be 1 even though there are no actual levels. protect against this. - // this single empty row is useful for styling. - if (levelSegs) { - for (j = 0; j < levelSegs.length; j++) { // iterate through segments in level - seg = levelSegs[j]; - - emptyCellsUntil(seg.leftCol); - - // create a container that occupies or more columns. append the event element. - td = $(' at a time and stop when we find one out of bounds - for (i = 0; i < trEls.length; i++) { - trEl = trEls.eq(i).removeClass('fc-limited'); // get and reveal - if (trEl.position().top + trEl.outerHeight() > rowHeight) { - return i; - } - } - - return false; // should not limit at all - }, - - - // Limits the given grid row to the maximum number of levels and injects "more" links if necessary. - // `row` is the row number. - // `levelLimit` is a number for the maximum (inclusive) number of levels allowed. - limitRow: function(row, levelLimit) { - var _this = this; - var view = this.view; - var rowStruct = this.rowStructs[row]; - var moreNodes = []; // array of "more" links and and segment elements past the limit - .addClass('fc-limited').get(); // hide elements and get a simple DOM-nodes array - - // iterate though segments in the last allowable level - for (i = 0; i < levelSegs.length; i++) { - seg = levelSegs[i]; - emptyCellsUntil(seg.leftCol); // process empty cells before the segment - - // determine *all* segments below `seg` that occupy the same columns - colSegsBelow = []; - totalSegsBelow = 0; - while (col <= seg.rightCol) { - cell = { row: row, col: col }; - segsBelow = this.getCellSegs(cell, levelLimit); - colSegsBelow.push(segsBelow); - totalSegsBelow += segsBelow.length; - col++; - } - - if (totalSegsBelow) { // do we need to replace this segment with one or many "more" links? - td = cellMatrix[levelLimit - 1][seg.leftCol]; // the segment's parent cell - rowspan = td.attr('rowspan') || 1; - segMoreNodes = []; - - // make a replacement '; - - html += - '' + - (!isRTL ? axisHtml : '') + - '"; - - slotTime.add(this.slotDuration); - } - - return html; - }, - - - // Parses various options into properties of this object - processOptions: function() { - var view = this.view; - var slotDuration = view.opt('slotDuration'); - var snapDuration = view.opt('snapDuration'); - - slotDuration = moment.duration(slotDuration); - snapDuration = snapDuration ? moment.duration(snapDuration) : slotDuration; - - this.slotDuration = slotDuration; - this.snapDuration = snapDuration; - this.cellDuration = snapDuration; // important to assign this for Grid.events.js - - this.minTime = moment.duration(view.opt('minTime')); - this.maxTime = moment.duration(view.opt('maxTime')); - }, - - - // Slices up a date range into a segment for each column - rangeToSegs: function(rangeStart, rangeEnd) { - var view = this.view; - var segs = []; - var seg; - var col; - var cellDate; - var colStart, colEnd; - - // normalize - rangeStart = rangeStart.clone().stripZone(); - rangeEnd = rangeEnd.clone().stripZone(); - - for (col = 0; col < view.colCnt; col++) { - cellDate = view.cellToDate(0, col); // use the View's cell system for this - colStart = cellDate.clone().time(this.minTime); - colEnd = cellDate.clone().time(this.maxTime); - seg = intersectionToSeg(rangeStart, rangeEnd, colStart, colEnd); - if (seg) { - seg.col = col; - segs.push(seg); - } - } - - return segs; - }, - - - /* Coordinates - ------------------------------------------------------------------------------------------------------------------*/ - - - // Called when there is a window resize/zoom and we need to recalculate coordinates for the grid - resize: function() { - this.computeSlatTops(); - this.updateSegVerticals(); - }, - - - // Populates the given empty `rows` and `cols` arrays with offset positions of the "snap" cells. - // "Snap" cells are different the slots because they might have finer granularity. - buildCoords: function(rows, cols) { - var colCnt = this.view.colCnt; - var originTop = this.el.offset().top; - var snapTime = moment.duration(+this.minTime); - var p = null; - var e, n; - - this.dayEls.slice(0, colCnt).each(function(i, _e) { - e = $(_e); - n = e.offset().left; - if (p) { - p[1] = n; - } - p = [ n ]; - cols[i] = p; - }); - p[1] = n + e.outerWidth(); - - p = null; - while (snapTime < this.maxTime) { - n = originTop + this.computeTimeTop(snapTime); - if (p) { - p[1] = n; - } - p = [ n ]; - rows.push(p); - snapTime.add(this.snapDuration); - } - p[1] = originTop + this.computeTimeTop(snapTime); // the position of the exclusive end - }, - - - // Gets the datetime for the given slot cell - getCellDate: function(cell) { - var view = this.view; - var calendar = view.calendar; - - return calendar.rezoneDate( // since we are adding a time, it needs to be in the calendar's timezone - view.cellToDate(0, cell.col) // View's coord system only accounts for start-of-day for column - .time(this.minTime + this.snapDuration * cell.row) - ); - }, - - - // Gets the element that represents the whole-day the cell resides on - getCellDayEl: function(cell) { - return this.dayEls.eq(cell.col); - }, - - - // Computes the top coordinate, relative to the bounds of the grid, of the given date. - // A `startOfDayDate` must be given for avoiding ambiguity over how to treat midnight. - computeDateTop: function(date, startOfDayDate) { - return this.computeTimeTop( - moment.duration( - date.clone().stripZone() - startOfDayDate.clone().stripTime() - ) - ); - }, - - - // Computes the top coordinate, relative to the bounds of the grid, of the given time (a Duration). - computeTimeTop: function(time) { - var slatCoverage = (time - this.minTime) / this.slotDuration; // floating-point value of # of slots covered - var slatIndex; - var slatRemainder; - var slatTop; - var slatBottom; - - // constrain. because minTime/maxTime might be customized - slatCoverage = Math.max(0, slatCoverage); - slatCoverage = Math.min(this.slatEls.length, slatCoverage); - - slatIndex = Math.floor(slatCoverage); // an integer index of the furthest whole slot - slatRemainder = slatCoverage - slatIndex; - slatTop = this.slatTops[slatIndex]; // the top position of the furthest whole slot - - if (slatRemainder) { // time spans part-way into the slot - slatBottom = this.slatTops[slatIndex + 1]; - return slatTop + (slatBottom - slatTop) * slatRemainder; // part-way between slots - } - else { - return slatTop; - } - }, - - - // Queries each `slatEl` for its position relative to the grid's container and stores it in `slatTops`. - // Includes the the bottom of the last slat as the last item in the array. - computeSlatTops: function() { - var tops = []; - var top; - - this.slatEls.each(function(i, node) { - top = $(node).position().top; - tops.push(top); - }); - - tops.push(top + this.slatEls.last().outerHeight()); // bottom of the last slat - - this.slatTops = tops; - }, - - - /* Event Drag Visualization - ------------------------------------------------------------------------------------------------------------------*/ - - - // Renders a visual indication of an event being dragged over the specified date(s). - // `end` and `seg` can be null. See View's documentation on renderDrag for more info. - renderDrag: function(start, end, seg) { - var opacity; - - if (seg) { // if there is event information for this drag, render a helper event - this.renderRangeHelper(start, end, seg); - - opacity = this.view.opt('dragOpacity'); - if (opacity !== undefined) { - this.helperEl.css('opacity', opacity); - } - - return true; // signal that a helper has been rendered - } - else { - // otherwise, just render a highlight - this.renderHighlight( - start, - end || this.view.calendar.getDefaultEventEnd(false, start) - ); - } - }, - - - // Unrenders any visual indication of an event being dragged - destroyDrag: function() { - this.destroyHelper(); - this.destroyHighlight(); - }, - - - /* Event Resize Visualization - ------------------------------------------------------------------------------------------------------------------*/ - - - // Renders a visual indication of an event being resized - renderResize: function(start, end, seg) { - this.renderRangeHelper(start, end, seg); - }, - - - // Unrenders any visual indication of an event being resized - destroyResize: function() { - this.destroyHelper(); - }, - - - /* Event Helper - ------------------------------------------------------------------------------------------------------------------*/ - - - // Renders a mock "helper" event. `sourceSeg` is the original segment object and might be null (an external drag) - renderHelper: function(event, sourceSeg) { - var res = this.renderEventTable([ event ]); - var tableEl = res.tableEl; - var segs = res.segs; - var i, seg; - var sourceEl; - - // Try to make the segment that is in the same row as sourceSeg look the same - for (i = 0; i < segs.length; i++) { - seg = segs[i]; - if (sourceSeg && sourceSeg.col === seg.col) { - sourceEl = sourceSeg.el; - seg.el.css({ - left: sourceEl.css('left'), - right: sourceEl.css('right'), - 'margin-left': sourceEl.css('margin-left'), - 'margin-right': sourceEl.css('margin-right') - }); - } - } - - this.helperEl = $('
') - .append(tableEl) - .appendTo(this.el); - }, - - - // Unrenders any mock helper event - destroyHelper: function() { - if (this.helperEl) { - this.helperEl.remove(); - this.helperEl = null; - } - }, - - - /* Selection - ------------------------------------------------------------------------------------------------------------------*/ - - - // Renders a visual indication of a selection. Overrides the default, which was to simply render a highlight. - renderSelection: function(start, end) { - if (this.view.opt('selectHelper')) { // this setting signals that a mock helper event should be rendered - this.renderRangeHelper(start, end); - } - else { - this.renderHighlight(start, end); - } - }, - - - // Unrenders any visual indication of a selection - destroySelection: function() { - this.destroyHelper(); - this.destroyHighlight(); - }, - - - /* Highlight - ------------------------------------------------------------------------------------------------------------------*/ - - - // Renders an emphasis on the given date range. `start` is inclusive. `end` is exclusive. - renderHighlight: function(start, end) { - this.highlightEl = $( - this.highlightSkeletonHtml(start, end) - ).appendTo(this.el); - }, - - - // Unrenders the emphasis on a date range - destroyHighlight: function() { - if (this.highlightEl) { - this.highlightEl.remove(); - this.highlightEl = null; - } - }, - - - // Generates HTML for a table element with containers in each column, responsible for absolutely positioning the - // highlight elements to cover the highlighted slots. - highlightSkeletonHtml: function(start, end) { - var view = this.view; - var segs = this.rangeToSegs(start, end); - var cellHtml = ''; - var col = 0; - var i, seg; - var dayDate; - var top, bottom; - - for (i = 0; i < segs.length; i++) { // loop through the segments. one per column - seg = segs[i]; - - // need empty cells beforehand? - if (col < seg.col) { - cellHtml += '
'; - - col++; - } - - // need empty cells after the last segment? - if (col < view.colCnt) { - cellHtml += ''; - } - }, - - - // Generates the HTML that will go before content-skeleton cells that display the day/week numbers. - // Queried by the DayGrid subcomponent. Ordering depends on isRTL. - numberIntroHtml: function(row) { - if (this.weekNumbersVisible) { - return '' + - ''; - } - }, - - - // Generates the HTML that goes before the day bg cells for each day-row. - // Queried by the DayGrid subcomponent. Ordering depends on isRTL. - dayIntroHtml: function() { - if (this.weekNumbersVisible) { - return ''; - } - }, - - - // Generates the HTML that goes before every other type of row generated by DayGrid. Ordering depends on isRTL. - // Affects helper-skeleton and highlight-skeleton rows. - introHtml: function() { - if (this.weekNumbersVisible) { - return ''; - } - }, - - - // Generates the HTML for the '; - }, - - - // Generates an HTML attribute string for setting the width of the week number column, if it is known - weekNumberStyleAttr: function() { - if (this.weekNumberWidth !== null) { - return 'style="width:' + this.weekNumberWidth + 'px"'; - } - return ''; - }, - - - // Determines whether each row should have a constant height - hasRigidRows: function() { - var eventLimit = this.opt('eventLimit'); - return eventLimit && typeof eventLimit !== 'number'; - }, - - - /* Dimensions - ------------------------------------------------------------------------------------------------------------------*/ - - - // Refreshes the horizontal dimensions of the view - updateWidth: function() { - if (this.weekNumbersVisible) { - // Make sure all week number cells running down the side have the same width. - // Record the width for cells created later. - this.weekNumberWidth = matchCellWidths( - this.el.find('.fc-week-number') - ); - } - }, - - - // Adjusts the vertical dimensions of the view to the specified values - setHeight: function(totalHeight, isAuto) { - var eventLimit = this.opt('eventLimit'); - var scrollerHeight; - - // reset all heights to be natural - unsetScroller(this.scrollerEl); - uncompensateScroll(this.headRowEl); - - this.dayGrid.destroySegPopover(); // kill the "more" popover if displayed - - // is the event limit a constant level number? - if (eventLimit && typeof eventLimit === 'number') { - this.dayGrid.limitRows(eventLimit); // limit the levels first so the height can redistribute after - } - - scrollerHeight = this.computeScrollerHeight(totalHeight); - this.setGridHeight(scrollerHeight, isAuto); - - // is the event limit dynamically calculated? - if (eventLimit && typeof eventLimit !== 'number') { - this.dayGrid.limitRows(eventLimit); // limit the levels after the grid's row heights have been set - } - - if (!isAuto && setPotentialScroller(this.scrollerEl, scrollerHeight)) { // using scrollbars? - - compensateScroll(this.headRowEl, getScrollbarWidths(this.scrollerEl)); - - // doing the scrollbar compensation might have created text overflow which created more height. redo - scrollerHeight = this.computeScrollerHeight(totalHeight); - this.scrollerEl.height(scrollerHeight); - - this.restoreScroll(); - } - }, - - - // Sets the height of just the DayGrid component in this view - setGridHeight: function(height, isAuto) { - if (isAuto) { - undistributeHeight(this.dayGrid.rowEls); // let the rows be their natural height with no expanding - } - else { - distributeHeight(this.dayGrid.rowEls, height, true); // true = compensate for height-hogging rows - } - }, - - - /* Events - ------------------------------------------------------------------------------------------------------------------*/ - - - // Renders the given events onto the view and populates the segments array - renderEvents: function(events) { - this.dayGrid.renderEvents(events); - - this.updateHeight(); // must compensate for events that overflow the row - - View.prototype.renderEvents.call(this, events); // call the super-method - }, - - - // Retrieves all segment objects that are rendered in the view - getSegs: function() { - return this.dayGrid.getSegs(); - }, - - - // Unrenders all event elements and clears internal segment data - destroyEvents: function() { - View.prototype.destroyEvents.call(this); // do this before dayGrid's segs have been cleared - - this.recordScroll(); // removing events will reduce height and mess with the scroll, so record beforehand - this.dayGrid.destroyEvents(); - - // we DON'T need to call updateHeight() because: - // A) a renderEvents() call always happens after this, which will eventually call updateHeight() - // B) in IE8, this causes a flash whenever events are rerendered - }, - - - /* Event Dragging - ------------------------------------------------------------------------------------------------------------------*/ - - - // Renders a visual indication of an event being dragged over the view. - // A returned value of `true` signals that a mock "helper" event has been rendered. - renderDrag: function(start, end, seg) { - return this.dayGrid.renderDrag(start, end, seg); - }, - - - // Unrenders the visual indication of an event being dragged over the view - destroyDrag: function() { - this.dayGrid.destroyDrag(); - }, - - - /* Selection - ------------------------------------------------------------------------------------------------------------------*/ - - - // Renders a visual indication of a selection - renderSelection: function(start, end) { - this.dayGrid.renderSelection(start, end); - }, - - - // Unrenders a visual indications of a selection - destroySelection: function() { - this.dayGrid.destroySelection(); - } - -}); - -;; - -/* A month view with day cells running in rows (one-per-week) and columns -----------------------------------------------------------------------------------------------------------------------*/ - -setDefaults({ - fixedWeekCount: true -}); - -fcViews.month = MonthView; // register the view - -function MonthView(calendar) { - BasicView.call(this, calendar); // call the super-constructor -} - - -MonthView.prototype = createObject(BasicView.prototype); // define the super-class -$.extend(MonthView.prototype, { - - name: 'month', - - - incrementDate: function(date, delta) { - return date.clone().stripTime().add(delta, 'months').startOf('month'); - }, - - - render: function(date) { - var rowCnt; - - this.intervalStart = date.clone().stripTime().startOf('month'); - this.intervalEnd = this.intervalStart.clone().add(1, 'months'); - - this.start = this.intervalStart.clone(); - this.start = this.skipHiddenDays(this.start); // move past the first week if no visible days - this.start.startOf('week'); - this.start = this.skipHiddenDays(this.start); // move past the first invisible days of the week - - this.end = this.intervalEnd.clone(); - this.end = this.skipHiddenDays(this.end, -1, true); // move in from the last week if no visible days - this.end.add((7 - this.end.weekday()) % 7, 'days'); // move to end of week if not already - this.end = this.skipHiddenDays(this.end, -1, true); // move in from the last invisible days of the week - - rowCnt = Math.ceil( // need to ceil in case there are hidden days - this.end.diff(this.start, 'weeks', true) // returnfloat=true - ); - if (this.isFixedWeeks()) { - this.end.add(6 - rowCnt, 'weeks'); - rowCnt = 6; - } - - this.title = this.calendar.formatDate(this.intervalStart, this.opt('titleFormat')); - - BasicView.prototype.render.call(this, rowCnt, this.getCellsPerWeek(), true); // call the super-method - }, - - - // Overrides the default BasicView behavior to have special multi-week auto-height logic - setGridHeight: function(height, isAuto) { - - isAuto = isAuto || this.opt('weekMode') === 'variable'; // LEGACY: weekMode is deprecated - - // if auto, make the height of each row the height that it would be if there were 6 weeks - if (isAuto) { - height *= this.rowCnt / 6; - } - - distributeHeight(this.dayGrid.rowEls, height, !isAuto); // if auto, don't compensate for height-hogging rows - }, - - - isFixedWeeks: function() { - var weekMode = this.opt('weekMode'); // LEGACY: weekMode is deprecated - if (weekMode) { - return weekMode === 'fixed'; // if any other type of weekMode, assume NOT fixed - } - - return this.opt('fixedWeekCount'); - } - -}); - -;; - -/* A week view with simple day cells running horizontally -----------------------------------------------------------------------------------------------------------------------*/ -// TODO: a WeekView mixin for calculating dates and titles - -fcViews.basicWeek = BasicWeekView; // register this view - -function BasicWeekView(calendar) { - BasicView.call(this, calendar); // call the super-constructor -} - - -BasicWeekView.prototype = createObject(BasicView.prototype); // define the super-class -$.extend(BasicWeekView.prototype, { - - name: 'basicWeek', - - - incrementDate: function(date, delta) { - return date.clone().stripTime().add(delta, 'weeks').startOf('week'); - }, - - - render: function(date) { - - this.intervalStart = date.clone().stripTime().startOf('week'); - this.intervalEnd = this.intervalStart.clone().add(1, 'weeks'); - - this.start = this.skipHiddenDays(this.intervalStart); - this.end = this.skipHiddenDays(this.intervalEnd, -1, true); - - this.title = this.calendar.formatRange( - this.start, - this.end.clone().subtract(1), // make inclusive by subtracting 1 ms - this.opt('titleFormat'), - ' \u2014 ' // emphasized dash - ); - - BasicView.prototype.render.call(this, 1, this.getCellsPerWeek(), false); // call the super-method - } - -}); -;; - -/* A view with a single simple day cell -----------------------------------------------------------------------------------------------------------------------*/ - -fcViews.basicDay = BasicDayView; // register this view - -function BasicDayView(calendar) { - BasicView.call(this, calendar); // call the super-constructor -} - - -BasicDayView.prototype = createObject(BasicView.prototype); // define the super-class -$.extend(BasicDayView.prototype, { - - name: 'basicDay', - - - incrementDate: function(date, delta) { - var out = date.clone().stripTime().add(delta, 'days'); - out = this.skipHiddenDays(out, delta < 0 ? -1 : 1); - return out; - }, - - - render: function(date) { - - this.start = this.intervalStart = date.clone().stripTime(); - this.end = this.intervalEnd = this.start.clone().add(1, 'days'); - - this.title = this.calendar.formatDate(this.start, this.opt('titleFormat')); - - BasicView.prototype.render.call(this, 1, 1, false); // call the super-method - } - -}); -;; - -/* An abstract class for all agenda-related views. Displays one more columns with time slots running vertically. -----------------------------------------------------------------------------------------------------------------------*/ -// Is a manager for the TimeGrid subcomponent and possibly the DayGrid subcomponent (if allDaySlot is on). -// Responsible for managing width/height. - -setDefaults({ - allDaySlot: true, - allDayText: 'all-day', - - scrollTime: '06:00:00', - - slotDuration: '00:30:00', - - axisFormat: generateAgendaAxisFormat, - timeFormat: { - agenda: generateAgendaTimeFormat - }, - - minTime: '00:00:00', - maxTime: '24:00:00', - slotEventOverlap: true -}); - -var AGENDA_ALL_DAY_EVENT_LIMIT = 5; - - -function generateAgendaAxisFormat(options, langData) { - return langData.longDateFormat('LT') - .replace(':mm', '(:mm)') - .replace(/(\Wmm)$/, '($1)') // like above, but for foreign langs - .replace(/\s*a$/i, 'a'); // convert AM/PM/am/pm to lowercase. remove any spaces beforehand -} - - -function generateAgendaTimeFormat(options, langData) { - return langData.longDateFormat('LT') - .replace(/\s*a$/i, ''); // remove trailing AM/PM -} - - -function AgendaView(calendar) { - View.call(this, calendar); // call the super-constructor - - this.timeGrid = new TimeGrid(this); - - if (this.opt('allDaySlot')) { // should we display the "all-day" area? - this.dayGrid = new DayGrid(this); // the all-day subcomponent of this view - - // the coordinate grid will be a combination of both subcomponents' grids - this.coordMap = new ComboCoordMap([ - this.dayGrid.coordMap, - this.timeGrid.coordMap - ]); - } - else { - this.coordMap = this.timeGrid.coordMap; - } -} - - -AgendaView.prototype = createObject(View.prototype); // define the super-class -$.extend(AgendaView.prototype, { - - timeGrid: null, // the main time-grid subcomponent of this view - dayGrid: null, // the "all-day" subcomponent. if all-day is turned off, this will be null - - axisWidth: null, // the width of the time axis running down the side - - noScrollRowEls: null, // set of fake row elements that must compensate when scrollerEl has scrollbars - - // when the time-grid isn't tall enough to occupy the given height, we render an
underneath - bottomRuleEl: null, - bottomRuleHeight: null, - - - /* Rendering - ------------------------------------------------------------------------------------------------------------------*/ - - - // Renders the view into `this.el`, which has already been assigned. - // `colCnt` has been calculated by a subclass and passed here. - render: function(colCnt) { - - // needed for cell-to-date and date-to-cell calculations in View - this.rowCnt = 1; - this.colCnt = colCnt; - - this.el.addClass('fc-agenda-view').html(this.renderHtml()); - - // the element that wraps the time-grid that will probably scroll - this.scrollerEl = this.el.find('.fc-time-grid-container'); - this.timeGrid.coordMap.containerEl = this.scrollerEl; // don't accept clicks/etc outside of this - - this.timeGrid.el = this.el.find('.fc-time-grid'); - this.timeGrid.render(); - - // the
that sometimes displays under the time-grid - this.bottomRuleEl = $('
') - .appendTo(this.timeGrid.el); // inject it into the time-grid - - if (this.dayGrid) { - this.dayGrid.el = this.el.find('.fc-day-grid'); - this.dayGrid.render(); - - // have the day-grid extend it's coordinate area over the
dividing the two grids - this.dayGrid.bottomCoordPadding = this.dayGrid.el.next('hr').outerHeight(); - } - - this.noScrollRowEls = this.el.find('.fc-row:not(.fc-scroller *)'); // fake rows not within the scroller - - View.prototype.render.call(this); // call the super-method - - this.resetScroll(); // do this after sizes have been set - }, - - - // Make subcomponents ready for cleanup - destroy: function() { - this.timeGrid.destroy(); - if (this.dayGrid) { - this.dayGrid.destroy(); - } - View.prototype.destroy.call(this); // call the super-method - }, - - - // Builds the HTML skeleton for the view. - // The day-grid and time-grid components will render inside containers defined by this HTML. - renderHtml: function() { - return '' + - '
'; - } - if (endCol > startCol) { - cellHtml += ''; - } - if (colCnt > endCol) { - cellHtml += ''; - } - - cellHtml = this.bookendCells(cellHtml, 'highlight'); - - return '' + - '
' + - '' + - '' + - cellHtml + - '' + - '
' + - '
'; - } - -}); - -;; - -/* Event-rendering methods for the DayGrid class -----------------------------------------------------------------------------------------------------------------------*/ - -$.extend(DayGrid.prototype, { - - segs: null, - rowStructs: null, // an array of objects, each holding information about a row's event-rendering - - - // Render the given events onto the Grid and return the rendered segments - renderEvents: function(events) { - var rowStructs = this.rowStructs = this.renderEventRows(events); - var segs = []; - - // append to each row's content skeleton - this.rowEls.each(function(i, rowNode) { - $(rowNode).find('.fc-content-skeleton > table').append( - rowStructs[i].tbodyEl - ); - segs.push.apply(segs, rowStructs[i].segs); - }); - - this.segs = segs; - }, - - - // Retrieves all segment objects that have been rendered - getSegs: function() { - return (this.segs || []).concat( - this.popoverSegs || [] // segs rendered in the "more" events popover - ); - }, - - - // Removes all rendered event elements - destroyEvents: function() { - var rowStructs; - var rowStruct; - - Grid.prototype.destroyEvents.call(this); // call the super-method - - rowStructs = this.rowStructs || []; - while ((rowStruct = rowStructs.pop())) { - rowStruct.tbodyEl.remove(); - } - - this.segs = null; - this.destroySegPopover(); // removes the "more.." events popover - }, - - - // Uses the given events array to generate
elements of the level+col matrix - var loneCellMatrix = []; // lookup for elements that only take up a single column - var i, levelSegs; - var col; - var tr; - var j, seg; - var td; - - // populates empty cells from the current column (`col`) to `endCol` - function emptyCellsUntil(endCol) { - while (col < endCol) { - // try to grab a cell from the level above and extend its rowspan. otherwise, create a fresh cell - td = (loneCellMatrix[i - 1] || [])[col]; - if (td) { - td.attr( - 'rowspan', - parseInt(td.attr('rowspan') || 1, 10) + 1 - ); - } - else { - td = $(''); - tr.append(td); - } - cellMatrix[i][col] = td; - loneCellMatrix[i][col] = td; - col++; - } - } - - for (i = 0; i < levelCnt; i++) { // iterate through all levels - levelSegs = segLevels[i]; - col = 0; - tr = $('
').append(seg.el); - if (seg.leftCol != seg.rightCol) { - td.attr('colspan', seg.rightCol - seg.leftCol + 1); - } - else { // a single-column segment - loneCellMatrix[i][col] = td; - } - - while (col <= seg.rightCol) { - cellMatrix[i][col] = td; - segMatrix[i][col] = seg; - col++; - } - - tr.append(td); - } - } - - emptyCellsUntil(colCnt); // finish off the row - this.bookendCells(tr, 'eventSkeleton'); - tbody.append(tr); - } - - return { // a "rowStruct" - row: row, // the row number - tbodyEl: tbody, - cellMatrix: cellMatrix, - segMatrix: segMatrix, - segLevels: segLevels, - segs: rowSegs - }; - }, - - - // Stacks a flat array of segments, which are all assumed to be in the same row, into subarrays of vertical levels. - buildSegLevels: function(segs) { - var levels = []; - var i, seg; - var j; - - // Give preference to elements with certain criteria, so they have - // a chance to be closer to the top. - segs.sort(compareSegs); - - for (i = 0; i < segs.length; i++) { - seg = segs[i]; - - // loop through levels, starting with the topmost, until the segment doesn't collide with other segments - for (j = 0; j < levels.length; j++) { - if (!isDaySegCollision(seg, levels[j])) { - break; - } - } - // `j` now holds the desired subrow index - seg.level = j; - - // create new level array if needed and append segment - (levels[j] || (levels[j] = [])).push(seg); - } - - // order segments left-to-right. very important if calendar is RTL - for (j = 0; j < levels.length; j++) { - levels[j].sort(compareDaySegCols); - } - - return levels; - }, - - - // Given a flat array of segments, return an array of sub-arrays, grouped by each segment's row - groupSegRows: function(segs) { - var view = this.view; - var segRows = []; - var i; - - for (i = 0; i < view.rowCnt; i++) { - segRows.push([]); - } - - for (i = 0; i < segs.length; i++) { - segRows[segs[i].row].push(segs[i]); - } - - return segRows; - } - -}); - - -// Computes whether two segments' columns collide. They are assumed to be in the same row. -function isDaySegCollision(seg, otherSegs) { - var i, otherSeg; - - for (i = 0; i < otherSegs.length; i++) { - otherSeg = otherSegs[i]; - - if ( - otherSeg.leftCol <= seg.rightCol && - otherSeg.rightCol >= seg.leftCol - ) { - return true; - } - } - - return false; -} - - -// A cmp function for determining the leftmost event -function compareDaySegCols(a, b) { - return a.leftCol - b.leftCol; -} - -;; - -/* Methods relate to limiting the number events for a given day on a DayGrid -----------------------------------------------------------------------------------------------------------------------*/ - -$.extend(DayGrid.prototype, { - - - segPopover: null, // the Popover that holds events that can't fit in a cell. null when not visible - popoverSegs: null, // an array of segment objects that the segPopover holds. null when not visible - - - destroySegPopover: function() { - if (this.segPopover) { - this.segPopover.hide(); // will trigger destruction of `segPopover` and `popoverSegs` - } - }, - - - // Limits the number of "levels" (vertically stacking layers of events) for each row of the grid. - // `levelLimit` can be false (don't limit), a number, or true (should be computed). - limitRows: function(levelLimit) { - var rowStructs = this.rowStructs || []; - var row; // row # - var rowLevelLimit; - - for (row = 0; row < rowStructs.length; row++) { - this.unlimitRow(row); - - if (!levelLimit) { - rowLevelLimit = false; - } - else if (typeof levelLimit === 'number') { - rowLevelLimit = levelLimit; - } - else { - rowLevelLimit = this.computeRowLevelLimit(row); - } - - if (rowLevelLimit !== false) { - this.limitRow(row, rowLevelLimit); - } - } - }, - - - // Computes the number of levels a row will accomodate without going outside its bounds. - // Assumes the row is "rigid" (maintains a constant height regardless of what is inside). - // `row` is the row number. - computeRowLevelLimit: function(row) { - var rowEl = this.rowEls.eq(row); // the containing "fake" row div - var rowHeight = rowEl.height(); // TODO: cache somehow? - var trEls = this.rowStructs[row].tbodyEl.children(); - var i, trEl; - - // Reveal one level
DOM nodes - var col = 0; // col # - var cell; - var levelSegs; // array of segment objects in the last allowable level, ordered left-to-right - var cellMatrix; // a matrix (by level, then column) of all jQuery elements in the row - var limitedNodes; // array of temporarily hidden level
DOM nodes - var i, seg; - var segsBelow; // array of segment objects below `seg` in the current `col` - var totalSegsBelow; // total number of segments below `seg` in any of the columns `seg` occupies - var colSegsBelow; // array of segment arrays, below seg, one for each column (offset from segs's first column) - var td, rowspan; - var segMoreNodes; // array of "more" cells that will stand-in for the current seg's cell - var j; - var moreTd, moreWrap, moreLink; - - // Iterates through empty level cells and places "more" links inside if need be - function emptyCellsUntil(endCol) { // goes from current `col` to `endCol` - while (col < endCol) { - cell = { row: row, col: col }; - segsBelow = _this.getCellSegs(cell, levelLimit); - if (segsBelow.length) { - td = cellMatrix[levelLimit - 1][col]; - moreLink = _this.renderMoreLink(cell, segsBelow); - moreWrap = $('
').append(moreLink); - td.append(moreWrap); - moreNodes.push(moreWrap[0]); - } - col++; - } - } - - if (levelLimit && levelLimit < rowStruct.segLevels.length) { // is it actually over the limit? - levelSegs = rowStruct.segLevels[levelLimit - 1]; - cellMatrix = rowStruct.cellMatrix; - - limitedNodes = rowStruct.tbodyEl.children().slice(levelLimit) // get level
for each column the segment occupies. will be one for each colspan - for (j = 0; j < colSegsBelow.length; j++) { - moreTd = $('').attr('rowspan', rowspan); - segsBelow = colSegsBelow[j]; - cell = { row: row, col: seg.leftCol + j }; - moreLink = this.renderMoreLink(cell, [ seg ].concat(segsBelow)); // count seg as hidden too - moreWrap = $('
').append(moreLink); - moreTd.append(moreWrap); - segMoreNodes.push(moreTd[0]); - moreNodes.push(moreTd[0]); - } - - td.addClass('fc-limited').after($(segMoreNodes)); // hide original
and inject replacements - limitedNodes.push(td[0]); - } - } - - emptyCellsUntil(view.colCnt); // finish off the level - rowStruct.moreEls = $(moreNodes); // for easy undoing later - rowStruct.limitedEls = $(limitedNodes); // for easy undoing later - } - }, - - - // Reveals all levels and removes all "more"-related elements for a grid's row. - // `row` is a row number. - unlimitRow: function(row) { - var rowStruct = this.rowStructs[row]; - - if (rowStruct.moreEls) { - rowStruct.moreEls.remove(); - rowStruct.moreEls = null; - } - - if (rowStruct.limitedEls) { - rowStruct.limitedEls.removeClass('fc-limited'); - rowStruct.limitedEls = null; - } - }, - - - // Renders an element that represents hidden event element for a cell. - // Responsible for attaching click handler as well. - renderMoreLink: function(cell, hiddenSegs) { - var _this = this; - var view = this.view; - - return $('') - .text( - this.getMoreLinkText(hiddenSegs.length) - ) - .on('click', function(ev) { - var clickOption = view.opt('eventLimitClick'); - var date = view.cellToDate(cell); - var moreEl = $(this); - var dayEl = _this.getCellDayEl(cell); - var allSegs = _this.getCellSegs(cell); - - // rescope the segments to be within the cell's date - var reslicedAllSegs = _this.resliceDaySegs(allSegs, date); - var reslicedHiddenSegs = _this.resliceDaySegs(hiddenSegs, date); - - if (typeof clickOption === 'function') { - // the returned value can be an atomic option - clickOption = view.trigger('eventLimitClick', null, { - date: date, - dayEl: dayEl, - moreEl: moreEl, - segs: reslicedAllSegs, - hiddenSegs: reslicedHiddenSegs - }, ev); - } - - if (clickOption === 'popover') { - _this.showSegPopover(date, cell, moreEl, reslicedAllSegs); - } - else if (typeof clickOption === 'string') { // a view name - view.calendar.zoomTo(date, clickOption); - } - }); - }, - - - // Reveals the popover that displays all events within a cell - showSegPopover: function(date, cell, moreLink, segs) { - var _this = this; - var view = this.view; - var moreWrap = moreLink.parent(); // the
wrapper around the - var topEl; // the element we want to match the top coordinate of - var options; - - if (view.rowCnt == 1) { - topEl = this.view.el; // will cause the popover to cover any sort of header - } - else { - topEl = this.rowEls.eq(cell.row); // will align with top of row - } - - options = { - className: 'fc-more-popover', - content: this.renderSegPopoverContent(date, segs), - parentEl: this.el, - top: topEl.offset().top, - autoHide: true, // when the user clicks elsewhere, hide the popover - viewportConstrain: view.opt('popoverViewportConstrain'), - hide: function() { - // destroy everything when the popover is hidden - _this.segPopover.destroy(); - _this.segPopover = null; - _this.popoverSegs = null; - } - }; - - // Determine horizontal coordinate. - // We use the moreWrap instead of the
to avoid border confusion. - if (view.opt('isRTL')) { - options.right = moreWrap.offset().left + moreWrap.outerWidth() + 1; // +1 to be over cell border - } - else { - options.left = moreWrap.offset().left - 1; // -1 to be over cell border - } - - this.segPopover = new Popover(options); - this.segPopover.show(); - }, - - - // Builds the inner DOM contents of the segment popover - renderSegPopoverContent: function(date, segs) { - var view = this.view; - var isTheme = view.opt('theme'); - var title = date.format(view.opt('dayPopoverFormat')); - var content = $( - '
' + - '' + - '' + - htmlEscape(title) + - '' + - '
' + - '
' + - '
' + - '
' + - '
' - ); - var segContainer = content.find('.fc-event-container'); - var i; - - // render each seg's `el` and only return the visible segs - segs = this.renderSegs(segs, true); // disableResizing=true - this.popoverSegs = segs; - - for (i = 0; i < segs.length; i++) { - - // because segments in the popover are not part of a grid coordinate system, provide a hint to any - // grids that want to do drag-n-drop about which cell it came from - segs[i].cellDate = date; - - segContainer.append(segs[i].el); - } - - return content; - }, - - - // Given the events within an array of segment objects, reslice them to be in a single day - resliceDaySegs: function(segs, dayDate) { - var events = $.map(segs, function(seg) { - return seg.event; - }); - var dayStart = dayDate.clone().stripTime(); - var dayEnd = dayStart.clone().add(1, 'days'); - - return this.eventsToSegs(events, dayStart, dayEnd); - }, - - - // Generates the text that should be inside a "more" link, given the number of events it represents - getMoreLinkText: function(num) { - var view = this.view; - var opt = view.opt('eventLimitText'); - - if (typeof opt === 'function') { - return opt(num); - } - else { - return '+' + num + ' ' + opt; - } - }, - - - // Returns segments within a given cell. - // If `startLevel` is specified, returns only events including and below that level. Otherwise returns all segs. - getCellSegs: function(cell, startLevel) { - var segMatrix = this.rowStructs[cell.row].segMatrix; - var level = startLevel || 0; - var segs = []; - var seg; - - while (level < segMatrix.length) { - seg = segMatrix[level][cell.col]; - if (seg) { - segs.push(seg); - } - level++; - } - - return segs; - } - -}); - -;; - -/* A component that renders one or more columns of vertical time slots -----------------------------------------------------------------------------------------------------------------------*/ - -function TimeGrid(view) { - Grid.call(this, view); // call the super-constructor -} - - -TimeGrid.prototype = createObject(Grid.prototype); // define the super-class -$.extend(TimeGrid.prototype, { - - slotDuration: null, // duration of a "slot", a distinct time segment on given day, visualized by lines - snapDuration: null, // granularity of time for dragging and selecting - - minTime: null, // Duration object that denotes the first visible time of any given day - maxTime: null, // Duration object that denotes the exclusive visible end time of any given day - - dayEls: null, // cells elements in the day-row background - slatEls: null, // elements running horizontally across all columns - - slatTops: null, // an array of top positions, relative to the container. last item holds bottom of last slot - - highlightEl: null, // cell skeleton element for rendering the highlight - helperEl: null, // cell skeleton element for rendering the mock event "helper" - - - // Renders the time grid into `this.el`, which should already be assigned. - // Relies on the view's colCnt. In the future, this component should probably be self-sufficient. - render: function() { - this.processOptions(); - - this.el.html(this.renderHtml()); - - this.dayEls = this.el.find('.fc-day'); - this.slatEls = this.el.find('.fc-slats tr'); - - this.computeSlatTops(); - - Grid.prototype.render.call(this); // call the super-method - }, - - - // Renders the basic HTML skeleton for the grid - renderHtml: function() { - return '' + - '
' + - '' + - this.rowHtml('slotBg') + // leverages RowRenderer, which will call slotBgCellHtml - '
' + - '
' + - '
' + - '' + - this.slatRowHtml() + - '
' + - '
'; - }, - - - // Renders the HTML for a vertical background cell behind the slots. - // This method is distinct from 'bg' because we wanted a new `rowType` so the View could customize the rendering. - slotBgCellHtml: function(row, col, date) { - return this.bgCellHtml(row, col, date); - }, - - - // Generates the HTML for the horizontal "slats" that run width-wise. Has a time axis on a side. Depends on RTL. - slatRowHtml: function() { - var view = this.view; - var calendar = view.calendar; - var isRTL = view.opt('isRTL'); - var html = ''; - var slotNormal = this.slotDuration.asMinutes() % 15 === 0; - var slotTime = moment.duration(+this.minTime); // wish there was .clone() for durations - var slotDate; // will be on the view's first day, but we only care about its time - var minutes; - var axisHtml; - - // Calculate the time for each slot - while (slotTime < this.maxTime) { - slotDate = view.start.clone().time(slotTime); // will be in UTC but that's good. to avoid DST issues - minutes = slotDate.minutes(); - - axisHtml = - '
' + - ((!slotNormal || !minutes) ? // if irregular slot duration, or on the hour, then display the time - '' + // for matchCellWidths - htmlEscape(calendar.formatDate(slotDate, view.opt('axisFormat'))) + - '' : - '' - ) + - '
' + - (isRTL ? axisHtml : '') + - "
'; - col = seg.col; - } - - // compute vertical position - dayDate = view.cellToDate(0, col); - top = this.computeDateTop(seg.start, dayDate); - bottom = this.computeDateTop(seg.end, dayDate); // the y position of the bottom edge - - // generate the cell HTML. bottom becomes negative because it needs to be a CSS value relative to the - // bottom edge of the zero-height container. - cellHtml += - '' + - '
' + - '
' + - '
' + - '
'; - } - - cellHtml = this.bookendCells(cellHtml, 'highlight'); - - return '' + - '
' + - '' + - '' + - cellHtml + - '' + - '
' + - '
'; - } - -}); - -;; - -/* Event-rendering methods for the TimeGrid class -----------------------------------------------------------------------------------------------------------------------*/ - -$.extend(TimeGrid.prototype, { - - segs: null, // segment objects rendered in the component. null of events haven't been rendered yet - eventSkeletonEl: null, // has cells with event-containers, which contain absolutely positioned event elements - - - // Renders the events onto the grid and returns an array of segments that have been rendered - renderEvents: function(events) { - var res = this.renderEventTable(events); - - this.eventSkeletonEl = $('
').append(res.tableEl); - this.el.append(this.eventSkeletonEl); - - this.segs = res.segs; - }, - - - // Retrieves rendered segment objects - getSegs: function() { - return this.segs || []; - }, - - - // Removes all event segment elements from the view - destroyEvents: function() { - Grid.prototype.destroyEvents.call(this); // call the super-method - - if (this.eventSkeletonEl) { - this.eventSkeletonEl.remove(); - this.eventSkeletonEl = null; - } - - this.segs = null; - }, - - - // Renders and returns the portion of the event-skeleton. - // Returns an object with properties 'tbodyEl' and 'segs'. - renderEventTable: function(events) { - var tableEl = $('
'); - var trEl = tableEl.find('tr'); - var segs = this.eventsToSegs(events); - var segCols; - var i, seg; - var col, colSegs; - var containerEl; - - segs = this.renderSegs(segs); // returns only the visible segs - segCols = this.groupSegCols(segs); // group into sub-arrays, and assigns 'col' to each seg - - this.computeSegVerticals(segs); // compute and assign top/bottom - - for (col = 0; col < segCols.length; col++) { // iterate each column grouping - colSegs = segCols[col]; - placeSlotSegs(colSegs); // compute horizontal coordinates, z-index's, and reorder the array - - containerEl = $('
'); - - // assign positioning CSS and insert into container - for (i = 0; i < colSegs.length; i++) { - seg = colSegs[i]; - seg.el.css(this.generateSegPositionCss(seg)); - - // if the height is short, add a className for alternate styling - if (seg.bottom - seg.top < 30) { - seg.el.addClass('fc-short'); - } - - containerEl.append(seg.el); - } - - trEl.append($('
').append(containerEl)); - } - - this.bookendCells(trEl, 'eventSkeleton'); - - return { - tableEl: tableEl, - segs: segs - }; - }, - - - // Refreshes the CSS top/bottom coordinates for each segment element. Probably after a window resize/zoom. - updateSegVerticals: function() { - var segs = this.segs; - var i; - - if (segs) { - this.computeSegVerticals(segs); - - for (i = 0; i < segs.length; i++) { - segs[i].el.css( - this.generateSegVerticalCss(segs[i]) - ); - } - } - }, - - - // For each segment in an array, computes and assigns its top and bottom properties - computeSegVerticals: function(segs) { - var i, seg; - - for (i = 0; i < segs.length; i++) { - seg = segs[i]; - seg.top = this.computeDateTop(seg.start, seg.start); - seg.bottom = this.computeDateTop(seg.end, seg.start); - } - }, - - - // Renders the HTML for a single event segment's default rendering - renderSegHtml: function(seg, disableResizing) { - var view = this.view; - var event = seg.event; - var isDraggable = view.isEventDraggable(event); - var isResizable = !disableResizing && seg.isEnd && view.isEventResizable(event); - var classes = this.getSegClasses(seg, isDraggable, isResizable); - var skinCss = this.getEventSkinCss(event); - var timeText; - var fullTimeText; // more verbose time text. for the print stylesheet - var startTimeText; // just the start time text - - classes.unshift('fc-time-grid-event'); - - if (view.isMultiDayEvent(event)) { // if the event appears to span more than one day... - // Don't display time text on segments that run entirely through a day. - // That would appear as midnight-midnight and would look dumb. - // Otherwise, display the time text for the *segment's* times (like 6pm-midnight or midnight-10am) - if (seg.isStart || seg.isEnd) { - timeText = view.getEventTimeText(seg.start, seg.end); - fullTimeText = view.getEventTimeText(seg.start, seg.end, 'LT'); - startTimeText = view.getEventTimeText(seg.start, null); - } - } else { - // Display the normal time text for the *event's* times - timeText = view.getEventTimeText(event); - fullTimeText = view.getEventTimeText(event, 'LT'); - startTimeText = view.getEventTimeText(event.start, null); - } - - return '' + - '
' + - (timeText ? - '
' + - '' + htmlEscape(timeText) + '' + - '
' : - '' - ) + - (event.title ? - '
' + - htmlEscape(event.title) + - '
' : - '' - ) + - '
' + - '
' + - (isResizable ? - '
' : - '' - ) + - ''; - }, - - - // Generates an object with CSS properties/values that should be applied to an event segment element. - // Contains important positioning-related properties that should be applied to any event element, customized or not. - generateSegPositionCss: function(seg) { - var view = this.view; - var isRTL = view.opt('isRTL'); - var shouldOverlap = view.opt('slotEventOverlap'); - var backwardCoord = seg.backwardCoord; // the left side if LTR. the right side if RTL. floating-point - var forwardCoord = seg.forwardCoord; // the right side if LTR. the left side if RTL. floating-point - var props = this.generateSegVerticalCss(seg); // get top/bottom first - var left; // amount of space from left edge, a fraction of the total width - var right; // amount of space from right edge, a fraction of the total width - - if (shouldOverlap) { - // double the width, but don't go beyond the maximum forward coordinate (1.0) - forwardCoord = Math.min(1, backwardCoord + (forwardCoord - backwardCoord) * 2); - } - - if (isRTL) { - left = 1 - forwardCoord; - right = backwardCoord; - } - else { - left = backwardCoord; - right = 1 - forwardCoord; - } - - props.zIndex = seg.level + 1; // convert from 0-base to 1-based - props.left = left * 100 + '%'; - props.right = right * 100 + '%'; - - if (shouldOverlap && seg.forwardPressure) { - // add padding to the edge so that forward stacked events don't cover the resizer's icon - props[isRTL ? 'marginLeft' : 'marginRight'] = 10 * 2; // 10 is a guesstimate of the icon's width - } - - return props; - }, - - - // Generates an object with CSS properties for the top/bottom coordinates of a segment element - generateSegVerticalCss: function(seg) { - return { - top: seg.top, - bottom: -seg.bottom // flipped because needs to be space beyond bottom edge of event container - }; - }, - - - // Given a flat array of segments, return an array of sub-arrays, grouped by each segment's col - groupSegCols: function(segs) { - var view = this.view; - var segCols = []; - var i; - - for (i = 0; i < view.colCnt; i++) { - segCols.push([]); - } - - for (i = 0; i < segs.length; i++) { - segCols[segs[i].col].push(segs[i]); - } - - return segCols; - } - -}); - - -// Given an array of segments that are all in the same column, sets the backwardCoord and forwardCoord on each. -// Also reorders the given array by date! -function placeSlotSegs(segs) { - var levels; - var level0; - var i; - - segs.sort(compareSegs); // order by date - levels = buildSlotSegLevels(segs); - computeForwardSlotSegs(levels); - - if ((level0 = levels[0])) { - - for (i = 0; i < level0.length; i++) { - computeSlotSegPressures(level0[i]); - } - - for (i = 0; i < level0.length; i++) { - computeSlotSegCoords(level0[i], 0, 0); - } - } -} - - -// Builds an array of segments "levels". The first level will be the leftmost tier of segments if the calendar is -// left-to-right, or the rightmost if the calendar is right-to-left. Assumes the segments are already ordered by date. -function buildSlotSegLevels(segs) { - var levels = []; - var i, seg; - var j; - - for (i=0; i seg2.top && seg1.top < seg2.bottom; -} - - -// A cmp function for determining which forward segment to rely on more when computing coordinates. -function compareForwardSlotSegs(seg1, seg2) { - // put higher-pressure first - return seg2.forwardPressure - seg1.forwardPressure || - // put segments that are closer to initial edge first (and favor ones with no coords yet) - (seg1.backwardCoord || 0) - (seg2.backwardCoord || 0) || - // do normal sorting... - compareSegs(seg1, seg2); -} - -;; - -/* An abstract class from which other views inherit from -----------------------------------------------------------------------------------------------------------------------*/ -// Newer methods should be written as prototype methods, not in the monster `View` function at the bottom. - -View.prototype = { - - calendar: null, // owner Calendar object - coordMap: null, // a CoordMap object for converting pixel regions to dates - el: null, // the view's containing element. set by Calendar - - // important Moments - start: null, // the date of the very first cell - end: null, // the date after the very last cell - intervalStart: null, // the start of the interval of time the view represents (1st of month for month view) - intervalEnd: null, // the exclusive end of the interval of time the view represents - - // used for cell-to-date and date-to-cell calculations - rowCnt: null, // # of weeks - colCnt: null, // # of days displayed in a week - - isSelected: false, // boolean whether cells are user-selected or not - - // subclasses can optionally use a scroll container - scrollerEl: null, // the element that will most likely scroll when content is too tall - scrollTop: null, // cached vertical scroll value - - // classNames styled by jqui themes - widgetHeaderClass: null, - widgetContentClass: null, - highlightStateClass: null, - - // document handlers, bound to `this` object - documentMousedownProxy: null, - documentDragStartProxy: null, - - - // Serves as a "constructor" to suppliment the monster `View` constructor below - init: function() { - var tm = this.opt('theme') ? 'ui' : 'fc'; - - this.widgetHeaderClass = tm + '-widget-header'; - this.widgetContentClass = tm + '-widget-content'; - this.highlightStateClass = tm + '-state-highlight'; - - // save references to `this`-bound handlers - this.documentMousedownProxy = $.proxy(this, 'documentMousedown'); - this.documentDragStartProxy = $.proxy(this, 'documentDragStart'); - }, - - - // Renders the view inside an already-defined `this.el`. - // Subclasses should override this and then call the super method afterwards. - render: function() { - this.updateSize(); - this.trigger('viewRender', this, this, this.el); - - // attach handlers to document. do it here to allow for destroy/rerender - $(document) - .on('mousedown', this.documentMousedownProxy) - .on('dragstart', this.documentDragStartProxy); // jqui drag - }, - - - // Clears all view rendering, event elements, and unregisters handlers - destroy: function() { - this.unselect(); - this.trigger('viewDestroy', this, this, this.el); - this.destroyEvents(); - this.el.empty(); // removes inner contents but leaves the element intact - - $(document) - .off('mousedown', this.documentMousedownProxy) - .off('dragstart', this.documentDragStartProxy); - }, - - - // Used to determine what happens when the users clicks next/prev. Given -1 for prev, 1 for next. - // Should apply the delta to `date` (a Moment) and return it. - incrementDate: function(date, delta) { - // subclasses should implement - }, - - - /* Dimensions - ------------------------------------------------------------------------------------------------------------------*/ - - - // Refreshes anything dependant upon sizing of the container element of the grid - updateSize: function(isResize) { - if (isResize) { - this.recordScroll(); - } - this.updateHeight(); - this.updateWidth(); - }, - - - // Refreshes the horizontal dimensions of the calendar - updateWidth: function() { - // subclasses should implement - }, - - - // Refreshes the vertical dimensions of the calendar - updateHeight: function() { - var calendar = this.calendar; // we poll the calendar for height information - - this.setHeight( - calendar.getSuggestedViewHeight(), - calendar.isHeightAuto() - ); - }, - - - // Updates the vertical dimensions of the calendar to the specified height. - // if `isAuto` is set to true, height becomes merely a suggestion and the view should use its "natural" height. - setHeight: function(height, isAuto) { - // subclasses should implement - }, - - - // Given the total height of the view, return the number of pixels that should be used for the scroller. - // Utility for subclasses. - computeScrollerHeight: function(totalHeight) { - var both = this.el.add(this.scrollerEl); - var otherHeight; // cumulative height of everything that is not the scrollerEl in the view (header+borders) - - // fuckin IE8/9/10/11 sometimes returns 0 for dimensions. this weird hack was the only thing that worked - both.css({ - position: 'relative', // cause a reflow, which will force fresh dimension recalculation - left: -1 // ensure reflow in case the el was already relative. negative is less likely to cause new scroll - }); - otherHeight = this.el.outerHeight() - this.scrollerEl.height(); // grab the dimensions - both.css({ position: '', left: '' }); // undo hack - - return totalHeight - otherHeight; - }, - - - // Called for remembering the current scroll value of the scroller. - // Should be called before there is a destructive operation (like removing DOM elements) that might inadvertently - // change the scroll of the container. - recordScroll: function() { - if (this.scrollerEl) { - this.scrollTop = this.scrollerEl.scrollTop(); - } - }, - - - // Set the scroll value of the scroller to the previously recorded value. - // Should be called after we know the view's dimensions have been restored following some type of destructive - // operation (like temporarily removing DOM elements). - restoreScroll: function() { - if (this.scrollTop !== null) { - this.scrollerEl.scrollTop(this.scrollTop); - } - }, - - - /* Events - ------------------------------------------------------------------------------------------------------------------*/ - - - // Renders the events onto the view. - // Should be overriden by subclasses. Subclasses should call the super-method afterwards. - renderEvents: function(events) { - this.segEach(function(seg) { - this.trigger('eventAfterRender', seg.event, seg.event, seg.el); - }); - this.trigger('eventAfterAllRender'); - }, - - - // Removes event elements from the view. - // Should be overridden by subclasses. Should call this super-method FIRST, then subclass DOM destruction. - destroyEvents: function() { - this.segEach(function(seg) { - this.trigger('eventDestroy', seg.event, seg.event, seg.el); - }); - }, - - - // Given an event and the default element used for rendering, returns the element that should actually be used. - // Basically runs events and elements through the eventRender hook. - resolveEventEl: function(event, el) { - var custom = this.trigger('eventRender', event, event, el); - - if (custom === false) { // means don't render at all - el = null; - } - else if (custom && custom !== true) { - el = $(custom); - } - - return el; - }, - - - // Hides all rendered event segments linked to the given event - showEvent: function(event) { - this.segEach(function(seg) { - seg.el.css('visibility', ''); - }, event); - }, - - - // Shows all rendered event segments linked to the given event - hideEvent: function(event) { - this.segEach(function(seg) { - seg.el.css('visibility', 'hidden'); - }, event); - }, - - - // Iterates through event segments. Goes through all by default. - // If the optional `event` argument is specified, only iterates through segments linked to that event. - // The `this` value of the callback function will be the view. - segEach: function(func, event) { - var segs = this.getSegs(); - var i; - - for (i = 0; i < segs.length; i++) { - if (!event || segs[i].event._id === event._id) { - func.call(this, segs[i]); - } - } - }, - - - // Retrieves all the rendered segment objects for the view - getSegs: function() { - // subclasses must implement - }, - - - /* Event Drag Visualization - ------------------------------------------------------------------------------------------------------------------*/ - - - // Renders a visual indication of an event hovering over the specified date. - // `end` is a Moment and might be null. - // `seg` might be null. if specified, it is the segment object of the event being dragged. - // otherwise, an external event from outside the calendar is being dragged. - renderDrag: function(start, end, seg) { - // subclasses should implement - }, - - - // Unrenders a visual indication of event hovering - destroyDrag: function() { - // subclasses should implement - }, - - - // Handler for accepting externally dragged events being dropped in the view. - // Gets called when jqui's 'dragstart' is fired. - documentDragStart: function(ev, ui) { - var _this = this; - var dropDate = null; - var dragListener; - - if (this.opt('droppable')) { // only listen if this setting is on - - // listener that tracks mouse movement over date-associated pixel regions - dragListener = new DragListener(this.coordMap, { - cellOver: function(cell, date) { - dropDate = date; - _this.renderDrag(date); - }, - cellOut: function() { - dropDate = null; - _this.destroyDrag(); - } - }); - - // gets called, only once, when jqui drag is finished - $(document).one('dragstop', function(ev, ui) { - _this.destroyDrag(); - if (dropDate) { - _this.trigger('drop', ev.target, dropDate, ev, ui); - } - }); - - dragListener.startDrag(ev); // start listening immediately - } - }, - - - /* Selection - ------------------------------------------------------------------------------------------------------------------*/ - - - // Selects a date range on the view. `start` and `end` are both Moments. - // `ev` is the native mouse event that begin the interaction. - select: function(start, end, ev) { - this.unselect(ev); - this.renderSelection(start, end); - this.reportSelection(start, end, ev); - }, - - - // Renders a visual indication of the selection - renderSelection: function(start, end) { - // subclasses should implement - }, - - - // Called when a new selection is made. Updates internal state and triggers handlers. - reportSelection: function(start, end, ev) { - this.isSelected = true; - this.trigger('select', null, start, end, ev); - }, - - - // Undoes a selection. updates in the internal state and triggers handlers. - // `ev` is the native mouse event that began the interaction. - unselect: function(ev) { - if (this.isSelected) { - this.isSelected = false; - this.destroySelection(); - this.trigger('unselect', null, ev); - } - }, - - - // Unrenders a visual indication of selection - destroySelection: function() { - // subclasses should implement - }, - - - // Handler for unselecting when the user clicks something and the 'unselectAuto' setting is on - documentMousedown: function(ev) { - var ignore; - - // is there a selection, and has the user made a proper left click? - if (this.isSelected && this.opt('unselectAuto') && isPrimaryMouseButton(ev)) { - - // only unselect if the clicked element is not identical to or inside of an 'unselectCancel' element - ignore = this.opt('unselectCancel'); - if (!ignore || !$(ev.target).closest(ignore).length) { - this.unselect(ev); - } - } - } - -}; - - -// We are mixing JavaScript OOP design patterns here by putting methods and member variables in the closed scope of the -// constructor. Going forward, methods should be part of the prototype. -function View(calendar) { - var t = this; - - // exports - t.calendar = calendar; - t.opt = opt; - t.trigger = trigger; - t.isEventDraggable = isEventDraggable; - t.isEventResizable = isEventResizable; - t.eventDrop = eventDrop; - t.eventResize = eventResize; - - // imports - var reportEventChange = calendar.reportEventChange; - - // locals - var options = calendar.options; - var nextDayThreshold = moment.duration(options.nextDayThreshold); - - - t.init(); // the "constructor" that concerns the prototype methods - - - function opt(name) { - var v = options[name]; - if ($.isPlainObject(v) && !isForcedAtomicOption(name)) { - return smartProperty(v, t.name); - } - return v; - } - - - function trigger(name, thisObj) { - return calendar.trigger.apply( - calendar, - [name, thisObj || t].concat(Array.prototype.slice.call(arguments, 2), [t]) - ); - } - - - - /* Event Editable Boolean Calculations - ------------------------------------------------------------------------------*/ - - - function isEventDraggable(event) { - var source = event.source || {}; - - return firstDefined( - event.startEditable, - source.startEditable, - opt('eventStartEditable'), - event.editable, - source.editable, - opt('editable') - ); - } - - - function isEventResizable(event) { - var source = event.source || {}; - - return firstDefined( - event.durationEditable, - source.durationEditable, - opt('eventDurationEditable'), - event.editable, - source.editable, - opt('editable') - ); - } - - - - /* Event Elements - ------------------------------------------------------------------------------*/ - - - // Compute the text that should be displayed on an event's element. - // Based off the settings of the view. Possible signatures: - // .getEventTimeText(event, formatStr) - // .getEventTimeText(startMoment, endMoment, formatStr) - // .getEventTimeText(startMoment, null, formatStr) - // `timeFormat` is used but the `formatStr` argument can be used to override. - t.getEventTimeText = function(event, formatStr) { - var start; - var end; - - if (typeof event === 'object' && typeof formatStr === 'object') { - // first two arguments are actually moments (or null). shift arguments. - start = event; - end = formatStr; - formatStr = arguments[2]; - } - else { - // otherwise, an event object was the first argument - start = event.start; - end = event.end; - } - - formatStr = formatStr || opt('timeFormat'); - - if (end && opt('displayEventEnd')) { - return calendar.formatRange(start, end, formatStr); - } - else { - return calendar.formatDate(start, formatStr); - } - }; - - - - /* Event Modification Reporting - ---------------------------------------------------------------------------------*/ - - - function eventDrop(el, event, newStart, ev) { - var mutateResult = calendar.mutateEvent(event, newStart, null); - - trigger( - 'eventDrop', - el, - event, - mutateResult.dateDelta, - function() { - mutateResult.undo(); - reportEventChange(); - }, - ev, - {} // jqui dummy - ); - - reportEventChange(); - } - - - function eventResize(el, event, newEnd, ev) { - var mutateResult = calendar.mutateEvent(event, null, newEnd); - - trigger( - 'eventResize', - el, - event, - mutateResult.durationDelta, - function() { - mutateResult.undo(); - reportEventChange(); - }, - ev, - {} // jqui dummy - ); - - reportEventChange(); - } - - - // ==================================================================================================== - // Utilities for day "cells" - // ==================================================================================================== - // The "basic" views are completely made up of day cells. - // The "agenda" views have day cells at the top "all day" slot. - // This was the obvious common place to put these utilities, but they should be abstracted out into - // a more meaningful class (like DayEventRenderer). - // ==================================================================================================== - - - // For determining how a given "cell" translates into a "date": - // - // 1. Convert the "cell" (row and column) into a "cell offset" (the # of the cell, cronologically from the first). - // Keep in mind that column indices are inverted with isRTL. This is taken into account. - // - // 2. Convert the "cell offset" to a "day offset" (the # of days since the first visible day in the view). - // - // 3. Convert the "day offset" into a "date" (a Moment). - // - // The reverse transformation happens when transforming a date into a cell. - - - // exports - t.isHiddenDay = isHiddenDay; - t.skipHiddenDays = skipHiddenDays; - t.getCellsPerWeek = getCellsPerWeek; - t.dateToCell = dateToCell; - t.dateToDayOffset = dateToDayOffset; - t.dayOffsetToCellOffset = dayOffsetToCellOffset; - t.cellOffsetToCell = cellOffsetToCell; - t.cellToDate = cellToDate; - t.cellToCellOffset = cellToCellOffset; - t.cellOffsetToDayOffset = cellOffsetToDayOffset; - t.dayOffsetToDate = dayOffsetToDate; - t.rangeToSegments = rangeToSegments; - t.isMultiDayEvent = isMultiDayEvent; - - - // internals - var hiddenDays = opt('hiddenDays') || []; // array of day-of-week indices that are hidden - var isHiddenDayHash = []; // is the day-of-week hidden? (hash with day-of-week-index -> bool) - var cellsPerWeek; - var dayToCellMap = []; // hash from dayIndex -> cellIndex, for one week - var cellToDayMap = []; // hash from cellIndex -> dayIndex, for one week - var isRTL = opt('isRTL'); - - - // initialize important internal variables - (function() { - - if (opt('weekends') === false) { - hiddenDays.push(0, 6); // 0=sunday, 6=saturday - } - - // Loop through a hypothetical week and determine which - // days-of-week are hidden. Record in both hashes (one is the reverse of the other). - for (var dayIndex=0, cellIndex=0; dayIndex<7; dayIndex++) { - dayToCellMap[dayIndex] = cellIndex; - isHiddenDayHash[dayIndex] = $.inArray(dayIndex, hiddenDays) != -1; - if (!isHiddenDayHash[dayIndex]) { - cellToDayMap[cellIndex] = dayIndex; - cellIndex++; - } - } - - cellsPerWeek = cellIndex; - if (!cellsPerWeek) { - throw 'invalid hiddenDays'; // all days were hidden? bad. - } - - })(); - - - // Is the current day hidden? - // `day` is a day-of-week index (0-6), or a Moment - function isHiddenDay(day) { - if (moment.isMoment(day)) { - day = day.day(); - } - return isHiddenDayHash[day]; - } - - - function getCellsPerWeek() { - return cellsPerWeek; - } - - - // Incrementing the current day until it is no longer a hidden day, returning a copy. - // If the initial value of `date` is not a hidden day, don't do anything. - // Pass `isExclusive` as `true` if you are dealing with an end date. - // `inc` defaults to `1` (increment one day forward each time) - function skipHiddenDays(date, inc, isExclusive) { - var out = date.clone(); - inc = inc || 1; - while ( - isHiddenDayHash[(out.day() + (isExclusive ? inc : 0) + 7) % 7] - ) { - out.add(inc, 'days'); - } - return out; - } - - - // - // TRANSFORMATIONS: cell -> cell offset -> day offset -> date - // - - // cell -> date (combines all transformations) - // Possible arguments: - // - row, col - // - { row:#, col: # } - function cellToDate() { - var cellOffset = cellToCellOffset.apply(null, arguments); - var dayOffset = cellOffsetToDayOffset(cellOffset); - var date = dayOffsetToDate(dayOffset); - return date; - } - - // cell -> cell offset - // Possible arguments: - // - row, col - // - { row:#, col:# } - function cellToCellOffset(row, col) { - var colCnt = t.colCnt; - - // rtl variables. wish we could pre-populate these. but where? - var dis = isRTL ? -1 : 1; - var dit = isRTL ? colCnt - 1 : 0; - - if (typeof row == 'object') { - col = row.col; - row = row.row; - } - var cellOffset = row * colCnt + (col * dis + dit); // column, adjusted for RTL (dis & dit) - - return cellOffset; - } - - // cell offset -> day offset - function cellOffsetToDayOffset(cellOffset) { - var day0 = t.start.day(); // first date's day of week - cellOffset += dayToCellMap[day0]; // normlize cellOffset to beginning-of-week - return Math.floor(cellOffset / cellsPerWeek) * 7 + // # of days from full weeks - cellToDayMap[ // # of days from partial last week - (cellOffset % cellsPerWeek + cellsPerWeek) % cellsPerWeek // crazy math to handle negative cellOffsets - ] - - day0; // adjustment for beginning-of-week normalization - } - - // day offset -> date - function dayOffsetToDate(dayOffset) { - return t.start.clone().add(dayOffset, 'days'); - } - - - // - // TRANSFORMATIONS: date -> day offset -> cell offset -> cell - // - - // date -> cell (combines all transformations) - function dateToCell(date) { - var dayOffset = dateToDayOffset(date); - var cellOffset = dayOffsetToCellOffset(dayOffset); - var cell = cellOffsetToCell(cellOffset); - return cell; - } - - // date -> day offset - function dateToDayOffset(date) { - return date.clone().stripTime().diff(t.start, 'days'); - } - - // day offset -> cell offset - function dayOffsetToCellOffset(dayOffset) { - var day0 = t.start.day(); // first date's day of week - dayOffset += day0; // normalize dayOffset to beginning-of-week - return Math.floor(dayOffset / 7) * cellsPerWeek + // # of cells from full weeks - dayToCellMap[ // # of cells from partial last week - (dayOffset % 7 + 7) % 7 // crazy math to handle negative dayOffsets - ] - - dayToCellMap[day0]; // adjustment for beginning-of-week normalization - } - - // cell offset -> cell (object with row & col keys) - function cellOffsetToCell(cellOffset) { - var colCnt = t.colCnt; - - // rtl variables. wish we could pre-populate these. but where? - var dis = isRTL ? -1 : 1; - var dit = isRTL ? colCnt - 1 : 0; - - var row = Math.floor(cellOffset / colCnt); - var col = ((cellOffset % colCnt + colCnt) % colCnt) * dis + dit; // column, adjusted for RTL (dis & dit) - return { - row: row, - col: col - }; - } - - - // - // Converts a date range into an array of segment objects. - // "Segments" are horizontal stretches of time, sliced up by row. - // A segment object has the following properties: - // - row - // - cols - // - isStart - // - isEnd - // - function rangeToSegments(start, end) { - - var rowCnt = t.rowCnt; - var colCnt = t.colCnt; - var segments = []; // array of segments to return - - // day offset for given date range - var dayRange = computeDayRange(start, end); // convert to a whole-day range - var rangeDayOffsetStart = dateToDayOffset(dayRange.start); - var rangeDayOffsetEnd = dateToDayOffset(dayRange.end); // an exclusive value - - // first and last cell offset for the given date range - // "last" implies inclusivity - var rangeCellOffsetFirst = dayOffsetToCellOffset(rangeDayOffsetStart); - var rangeCellOffsetLast = dayOffsetToCellOffset(rangeDayOffsetEnd) - 1; - - // loop through all the rows in the view - for (var row=0; row
' + - '' + // needed for matchCellWidths - htmlEscape(this.opt('weekNumberTitle')) + - '' + - '' + - '' + // needed for matchCellWidths - this.calendar.calculateWeekNumber(this.cellToDate(row, 0)) + - '' + - 's of the "number" row in the DayGrid's content skeleton. - // The number row will only exist if either day numbers or week numbers are turned on. - numberCellHtml: function(row, col, date) { - var classes; - - if (!this.dayNumbersVisible) { // if there are week numbers but not day numbers - return ''; // will create an empty space above events :( - } - - classes = this.dayGrid.getDayClasses(date); - classes.unshift('fc-day-number'); - - return '' + - '' + - date.date() + - '
' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '
' + - this.timeGrid.headHtml() + // render the day-of-week headers - '
' + - (this.dayGrid ? - '
' + - '
' : - '' - ) + - '
' + - '
' + - '
' + - '
'; - }, - - - // Generates the HTML that will go before the day-of week header cells. - // Queried by the TimeGrid subcomponent when generating rows. Ordering depends on isRTL. - headIntroHtml: function() { - var date; - var weekNumber; - var weekTitle; - var weekText; - - if (this.opt('weekNumbers')) { - date = this.cellToDate(0, 0); - weekNumber = this.calendar.calculateWeekNumber(date); - weekTitle = this.opt('weekNumberTitle'); - - if (this.opt('isRTL')) { - weekText = weekNumber + weekTitle; - } - else { - weekText = weekTitle + weekNumber; - } - - return '' + - '' + - '' + // needed for matchCellWidths - htmlEscape(weekText) + - '' + - ''; - } - else { - return ''; - } - }, - - - // Generates the HTML that goes before the all-day cells. - // Queried by the DayGrid subcomponent when generating rows. Ordering depends on isRTL. - dayIntroHtml: function() { - return '' + - '' + - '' + // needed for matchCellWidths - (this.opt('allDayHtml') || htmlEscape(this.opt('allDayText'))) + - '' + - ''; - }, - - - // Generates the HTML that goes before the bg of the TimeGrid slot area. Long vertical column. - slotBgIntroHtml: function() { - return ''; - }, - - - // Generates the HTML that goes before all other types of cells. - // Affects content-skeleton, helper-skeleton, highlight-skeleton for both the time-grid and day-grid. - // Queried by the TimeGrid and DayGrid subcomponents when generating rows. Ordering depends on isRTL. - introHtml: function() { - return ''; - }, - - - // Generates an HTML attribute string for setting the width of the axis, if it is known - axisStyleAttr: function() { - if (this.axisWidth !== null) { - return 'style="width:' + this.axisWidth + 'px"'; - } - return ''; - }, - - - /* Dimensions - ------------------------------------------------------------------------------------------------------------------*/ - - updateSize: function(isResize) { - if (isResize) { - this.timeGrid.resize(); - } - View.prototype.updateSize.call(this, isResize); - }, - - - // Refreshes the horizontal dimensions of the view - updateWidth: function() { - // make all axis cells line up, and record the width so newly created axis cells will have it - this.axisWidth = matchCellWidths(this.el.find('.fc-axis')); - }, - - - // Adjusts the vertical dimensions of the view to the specified values - setHeight: function(totalHeight, isAuto) { - var eventLimit; - var scrollerHeight; - - if (this.bottomRuleHeight === null) { - // calculate the height of the rule the very first time - this.bottomRuleHeight = this.bottomRuleEl.outerHeight(); - } - this.bottomRuleEl.hide(); // .show() will be called later if this
is necessary - - // reset all dimensions back to the original state - this.scrollerEl.css('overflow', ''); - unsetScroller(this.scrollerEl); - uncompensateScroll(this.noScrollRowEls); - - // limit number of events in the all-day area - if (this.dayGrid) { - this.dayGrid.destroySegPopover(); // kill the "more" popover if displayed - - eventLimit = this.opt('eventLimit'); - if (eventLimit && typeof eventLimit !== 'number') { - eventLimit = AGENDA_ALL_DAY_EVENT_LIMIT; // make sure "auto" goes to a real number - } - if (eventLimit) { - this.dayGrid.limitRows(eventLimit); - } - } - - if (!isAuto) { // should we force dimensions of the scroll container, or let the contents be natural height? - - scrollerHeight = this.computeScrollerHeight(totalHeight); - if (setPotentialScroller(this.scrollerEl, scrollerHeight)) { // using scrollbars? - - // make the all-day and header rows lines up - compensateScroll(this.noScrollRowEls, getScrollbarWidths(this.scrollerEl)); - - // the scrollbar compensation might have changed text flow, which might affect height, so recalculate - // and reapply the desired height to the scroller. - scrollerHeight = this.computeScrollerHeight(totalHeight); - this.scrollerEl.height(scrollerHeight); - - this.restoreScroll(); - } - else { // no scrollbars - // still, force a height and display the bottom rule (marks the end of day) - this.scrollerEl.height(scrollerHeight).css('overflow', 'hidden'); // in case
goes outside - this.bottomRuleEl.show(); - } - } - }, - - - // Sets the scroll value of the scroller to the intial pre-configured state prior to allowing the user to change it. - resetScroll: function() { - var _this = this; - var scrollTime = moment.duration(this.opt('scrollTime')); - var top = this.timeGrid.computeTimeTop(scrollTime); - - // zoom can give weird floating-point values. rather scroll a little bit further - top = Math.ceil(top); - - if (top) { - top++; // to overcome top border that slots beyond the first have. looks better - } - - function scroll() { - _this.scrollerEl.scrollTop(top); - } - - scroll(); - setTimeout(scroll, 0); // overrides any previous scroll state made by the browser - }, - - - /* Events - ------------------------------------------------------------------------------------------------------------------*/ - - - // Renders events onto the view and populates the View's segment array - renderEvents: function(events) { - var dayEvents = []; - var timedEvents = []; - var daySegs = []; - var timedSegs; - var i; - - // separate the events into all-day and timed - for (i = 0; i < events.length; i++) { - if (events[i].allDay) { - dayEvents.push(events[i]); - } - else { - timedEvents.push(events[i]); - } - } - - // render the events in the subcomponents - timedSegs = this.timeGrid.renderEvents(timedEvents); - if (this.dayGrid) { - daySegs = this.dayGrid.renderEvents(dayEvents); - } - - // the all-day area is flexible and might have a lot of events, so shift the height - this.updateHeight(); - - View.prototype.renderEvents.call(this, events); // call the super-method - }, - - - // Retrieves all segment objects that are rendered in the view - getSegs: function() { - return this.timeGrid.getSegs().concat( - this.dayGrid ? this.dayGrid.getSegs() : [] - ); - }, - - - // Unrenders all event elements and clears internal segment data - destroyEvents: function() { - View.prototype.destroyEvents.call(this); // do this before the grids' segs have been cleared - - // if destroyEvents is being called as part of an event rerender, renderEvents will be called shortly - // after, so remember what the scroll value was so we can restore it. - this.recordScroll(); - - // destroy the events in the subcomponents - this.timeGrid.destroyEvents(); - if (this.dayGrid) { - this.dayGrid.destroyEvents(); - } - - // we DON'T need to call updateHeight() because: - // A) a renderEvents() call always happens after this, which will eventually call updateHeight() - // B) in IE8, this causes a flash whenever events are rerendered - }, - - - /* Event Dragging - ------------------------------------------------------------------------------------------------------------------*/ - - - // Renders a visual indication of an event being dragged over the view. - // A returned value of `true` signals that a mock "helper" event has been rendered. - renderDrag: function(start, end, seg) { - if (start.hasTime()) { - return this.timeGrid.renderDrag(start, end, seg); - } - else if (this.dayGrid) { - return this.dayGrid.renderDrag(start, end, seg); - } - }, - - - // Unrenders a visual indications of an event being dragged over the view - destroyDrag: function() { - this.timeGrid.destroyDrag(); - if (this.dayGrid) { - this.dayGrid.destroyDrag(); - } - }, - - - /* Selection - ------------------------------------------------------------------------------------------------------------------*/ - - - // Renders a visual indication of a selection - renderSelection: function(start, end) { - if (start.hasTime() || end.hasTime()) { - this.timeGrid.renderSelection(start, end); - } - else if (this.dayGrid) { - this.dayGrid.renderSelection(start, end); - } - }, - - - // Unrenders a visual indications of a selection - destroySelection: function() { - this.timeGrid.destroySelection(); - if (this.dayGrid) { - this.dayGrid.destroySelection(); - } - } - -}); - -;; - -/* A week view with an all-day cell area at the top, and a time grid below -----------------------------------------------------------------------------------------------------------------------*/ -// TODO: a WeekView mixin for calculating dates and titles - -fcViews.agendaWeek = AgendaWeekView; // register the view - -function AgendaWeekView(calendar) { - AgendaView.call(this, calendar); // call the super-constructor -} - - -AgendaWeekView.prototype = createObject(AgendaView.prototype); // define the super-class -$.extend(AgendaWeekView.prototype, { - - name: 'agendaWeek', - - - incrementDate: function(date, delta) { - return date.clone().stripTime().add(delta, 'weeks').startOf('week'); - }, - - - render: function(date) { - - this.intervalStart = date.clone().stripTime().startOf('week'); - this.intervalEnd = this.intervalStart.clone().add(1, 'weeks'); - - this.start = this.skipHiddenDays(this.intervalStart); - this.end = this.skipHiddenDays(this.intervalEnd, -1, true); - - this.title = this.calendar.formatRange( - this.start, - this.end.clone().subtract(1), // make inclusive by subtracting 1 ms - this.opt('titleFormat'), - ' \u2014 ' // emphasized dash - ); - - AgendaView.prototype.render.call(this, this.getCellsPerWeek()); // call the super-method - } - -}); - -;; - -/* A day view with an all-day cell area at the top, and a time grid below -----------------------------------------------------------------------------------------------------------------------*/ - -fcViews.agendaDay = AgendaDayView; // register the view - -function AgendaDayView(calendar) { - AgendaView.call(this, calendar); // call the super-constructor -} - - -AgendaDayView.prototype = createObject(AgendaView.prototype); // define the super-class -$.extend(AgendaDayView.prototype, { - - name: 'agendaDay', - - - incrementDate: function(date, delta) { - var out = date.clone().stripTime().add(delta, 'days'); - out = this.skipHiddenDays(out, delta < 0 ? -1 : 1); - return out; - }, - - - render: function(date) { - - this.start = this.intervalStart = date.clone().stripTime(); - this.end = this.intervalEnd = this.start.clone().add(1, 'days'); - - this.title = this.calendar.formatDate(this.start, this.opt('titleFormat')); - - AgendaView.prototype.render.call(this, 1); // call the super-method - } - -}); - -;; - +/*! + * FullCalendar v2.1.1 + * Docs & License: http://arshaw.com/fullcalendar/ + * (c) 2013 Adam Shaw + */ + +(function(factory) { + if (typeof define === 'function' && define.amd) { + define([ 'jquery', 'moment' ], factory); + } + else { + factory(jQuery, moment); + } +})(function($, moment) { + +;; + +var defaults = { + + lang: 'en', + + defaultTimedEventDuration: '02:00:00', + defaultAllDayEventDuration: { days: 1 }, + forceEventDuration: false, + nextDayThreshold: '09:00:00', // 9am + + // display + defaultView: 'month', + aspectRatio: 1.35, + header: { + left: 'title', + center: '', + right: 'today prev,next' + }, + weekends: true, + weekNumbers: false, + + weekNumberTitle: 'W', + weekNumberCalculation: 'local', + + //editable: false, + + // event ajax + lazyFetching: true, + startParam: 'start', + endParam: 'end', + timezoneParam: 'timezone', + + timezone: false, + + //allDayDefault: undefined, + + // time formats + titleFormat: { + month: 'MMMM YYYY', // like "September 1986". each language will override this + week: 'll', // like "Sep 4 1986" + day: 'LL' // like "September 4 1986" + }, + columnFormat: { + month: 'ddd', // like "Sat" + week: generateWeekColumnFormat, + day: 'dddd' // like "Saturday" + }, + timeFormat: { // for event elements + 'default': generateShortTimeFormat + }, + + displayEventEnd: { + month: false, + basicWeek: false, + 'default': true + }, + + // locale + isRTL: false, + defaultButtonText: { + prev: "prev", + next: "next", + prevYear: "prev year", + nextYear: "next year", + today: 'today', + month: 'month', + week: 'week', + day: 'day' + }, + + buttonIcons: { + prev: 'left-single-arrow', + next: 'right-single-arrow', + prevYear: 'left-double-arrow', + nextYear: 'right-double-arrow' + }, + + // jquery-ui theming + theme: false, + themeButtonIcons: { + prev: 'circle-triangle-w', + next: 'circle-triangle-e', + prevYear: 'seek-prev', + nextYear: 'seek-next' + }, + + dragOpacity: .75, + dragRevertDuration: 500, + dragScroll: true, + + //selectable: false, + unselectAuto: true, + + dropAccept: '*', + + eventLimit: false, + eventLimitText: 'more', + eventLimitClick: 'popover', + dayPopoverFormat: 'LL', + + handleWindowResize: true, + windowResizeDelay: 200 // milliseconds before a rerender happens + +}; + + +function generateShortTimeFormat(options, langData) { + return langData.longDateFormat('LT') + .replace(':mm', '(:mm)') + .replace(/(\Wmm)$/, '($1)') // like above, but for foreign langs + .replace(/\s*a$/i, 't'); // convert to AM/PM/am/pm to lowercase one-letter. remove any spaces beforehand +} + + +function generateWeekColumnFormat(options, langData) { + var format = langData.longDateFormat('L'); // for the format like "MM/DD/YYYY" + format = format.replace(/^Y+[^\w\s]*|[^\w\s]*Y+$/g, ''); // strip the year off the edge, as well as other misc non-whitespace chars + if (options.isRTL) { + format += ' ddd'; // for RTL, add day-of-week to end + } + else { + format = 'ddd ' + format; // for LTR, add day-of-week to beginning + } + return format; +} + + +var langOptionHash = { + en: { + columnFormat: { + week: 'ddd M/D' // override for english. different from the generated default, which is MM/DD + }, + dayPopoverFormat: 'dddd, MMMM D' + } +}; + + +// right-to-left defaults +var rtlDefaults = { + header: { + left: 'next,prev today', + center: '', + right: 'title' + }, + buttonIcons: { + prev: 'right-single-arrow', + next: 'left-single-arrow', + prevYear: 'right-double-arrow', + nextYear: 'left-double-arrow' + }, + themeButtonIcons: { + prev: 'circle-triangle-e', + next: 'circle-triangle-w', + nextYear: 'seek-prev', + prevYear: 'seek-next' + } +}; + +;; + +var fc = $.fullCalendar = { version: "2.1.1" }; +var fcViews = fc.views = {}; + + +$.fn.fullCalendar = function(options) { + var args = Array.prototype.slice.call(arguments, 1); // for a possible method call + var res = this; // what this function will return (this jQuery object by default) + + this.each(function(i, _element) { // loop each DOM element involved + var element = $(_element); + var calendar = element.data('fullCalendar'); // get the existing calendar object (if any) + var singleRes; // the returned value of this single method call + + // a method call + if (typeof options === 'string') { + if (calendar && $.isFunction(calendar[options])) { + singleRes = calendar[options].apply(calendar, args); + if (!i) { + res = singleRes; // record the first method call result + } + if (options === 'destroy') { // for the destroy method, must remove Calendar object data + element.removeData('fullCalendar'); + } + } + } + // a new calendar initialization + else if (!calendar) { // don't initialize twice + calendar = new Calendar(element, options); + element.data('fullCalendar', calendar); + calendar.render(); + } + }); + + return res; +}; + + +// function for adding/overriding defaults +function setDefaults(d) { + mergeOptions(defaults, d); +} + + +// Recursively combines option hash-objects. +// Better than `$.extend(true, ...)` because arrays are not traversed/copied. +// +// called like: +// mergeOptions(target, obj1, obj2, ...) +// +function mergeOptions(target) { + + function mergeIntoTarget(name, value) { + if ($.isPlainObject(value) && $.isPlainObject(target[name]) && !isForcedAtomicOption(name)) { + // merge into a new object to avoid destruction + target[name] = mergeOptions({}, target[name], value); // combine. `value` object takes precedence + } + else if (value !== undefined) { // only use values that are set and not undefined + target[name] = value; + } + } + + for (var i=1; i").prependTo(element); + + header = new Header(t, options); + headerElement = header.render(); + if (headerElement) { + element.prepend(headerElement); + } + + changeView(options.defaultView); + + if (options.handleWindowResize) { + windowResizeProxy = debounce(windowResize, options.windowResizeDelay); // prevents rapid calls + $(window).resize(windowResizeProxy); + } + } + + + function destroy() { + + if (currentView) { + currentView.destroy(); + } + + header.destroy(); + content.remove(); + element.removeClass('fc fc-ltr fc-rtl fc-unthemed ui-widget'); + + $(window).unbind('resize', windowResizeProxy); + } + + + function elementVisible() { + return element.is(':visible'); + } + + + + // View Rendering + // ----------------------------------------------------------------------------------- + + + function changeView(viewName) { + renderView(0, viewName); + } + + + // Renders a view because of a date change, view-type change, or for the first time + function renderView(delta, viewName) { + ignoreWindowResize++; + + // if viewName is changing, destroy the old view + if (currentView && viewName && currentView.name !== viewName) { + header.deactivateButton(currentView.name); + freezeContentHeight(); // prevent a scroll jump when view element is removed + if (currentView.start) { // rendered before? + currentView.destroy(); + } + currentView.el.remove(); + currentView = null; + } + + // if viewName changed, or the view was never created, create a fresh view + if (!currentView && viewName) { + currentView = new fcViews[viewName](t); + currentView.el = $("
").appendTo(content); + header.activateButton(viewName); + } + + if (currentView) { + + // let the view determine what the delta means + if (delta) { + date = currentView.incrementDate(date, delta); + } + + // render or rerender the view + if ( + !currentView.start || // never rendered before + delta || // explicit date window change + !date.isWithin(currentView.intervalStart, currentView.intervalEnd) // implicit date window change + ) { + if (elementVisible()) { + + freezeContentHeight(); + if (currentView.start) { // rendered before? + currentView.destroy(); + } + currentView.render(date); + unfreezeContentHeight(); + + // need to do this after View::render, so dates are calculated + updateTitle(); + updateTodayButton(); + + getAndRenderEvents(); + } + } + } + + unfreezeContentHeight(); // undo any lone freezeContentHeight calls + ignoreWindowResize--; + } + + + + // Resizing + // ----------------------------------------------------------------------------------- + + + t.getSuggestedViewHeight = function() { + if (suggestedViewHeight === undefined) { + calcSize(); + } + return suggestedViewHeight; + }; + + + t.isHeightAuto = function() { + return options.contentHeight === 'auto' || options.height === 'auto'; + }; + + + function updateSize(shouldRecalc) { + if (elementVisible()) { + + if (shouldRecalc) { + _calcSize(); + } + + ignoreWindowResize++; + currentView.updateSize(true); // isResize=true. will poll getSuggestedViewHeight() and isHeightAuto() + ignoreWindowResize--; + + return true; // signal success + } + } + + + function calcSize() { + if (elementVisible()) { + _calcSize(); + } + } + + + function _calcSize() { // assumes elementVisible + if (typeof options.contentHeight === 'number') { // exists and not 'auto' + suggestedViewHeight = options.contentHeight; + } + else if (typeof options.height === 'number') { // exists and not 'auto' + suggestedViewHeight = options.height - (headerElement ? headerElement.outerHeight(true) : 0); + } + else { + suggestedViewHeight = Math.round(content.width() / Math.max(options.aspectRatio, .5)); + } + } + + + function windowResize(ev) { + if ( + !ignoreWindowResize && + ev.target === window && // so we don't process jqui "resize" events that have bubbled up + currentView.start // view has already been rendered + ) { + if (updateSize(true)) { + currentView.trigger('windowResize', _element); + } + } + } + + + + /* Event Fetching/Rendering + -----------------------------------------------------------------------------*/ + // TODO: going forward, most of this stuff should be directly handled by the view + + + function refetchEvents() { // can be called as an API method + destroyEvents(); // so that events are cleared before user starts waiting for AJAX + fetchAndRenderEvents(); + } + + + function renderEvents() { // destroys old events if previously rendered + if (elementVisible()) { + freezeContentHeight(); + currentView.destroyEvents(); // no performance cost if never rendered + currentView.renderEvents(events); + unfreezeContentHeight(); + } + } + + + function destroyEvents() { + freezeContentHeight(); + currentView.destroyEvents(); + unfreezeContentHeight(); + } + + + function getAndRenderEvents() { + if (!options.lazyFetching || isFetchNeeded(currentView.start, currentView.end)) { + fetchAndRenderEvents(); + } + else { + renderEvents(); + } + } + + + function fetchAndRenderEvents() { + fetchEvents(currentView.start, currentView.end); + // ... will call reportEvents + // ... which will call renderEvents + } + + + // called when event data arrives + function reportEvents(_events) { + events = _events; + renderEvents(); + } + + + // called when a single event's data has been changed + function reportEventChange() { + renderEvents(); + } + + + + /* Header Updating + -----------------------------------------------------------------------------*/ + + + function updateTitle() { + header.updateTitle(currentView.title); + } + + + function updateTodayButton() { + var now = t.getNow(); + if (now.isWithin(currentView.intervalStart, currentView.intervalEnd)) { + header.disableButton('today'); + } + else { + header.enableButton('today'); + } + } + + + + /* Selection + -----------------------------------------------------------------------------*/ + + + function select(start, end) { + + start = t.moment(start); + if (end) { + end = t.moment(end); + } + else if (start.hasTime()) { + end = start.clone().add(t.defaultTimedEventDuration); + } + else { + end = start.clone().add(t.defaultAllDayEventDuration); + } + + currentView.select(start, end); + } + + + function unselect() { // safe to be called before renderView + if (currentView) { + currentView.unselect(); + } + } + + + + /* Date + -----------------------------------------------------------------------------*/ + + + function prev() { + renderView(-1); + } + + + function next() { + renderView(1); + } + + + function prevYear() { + date.add(-1, 'years'); + renderView(); + } + + + function nextYear() { + date.add(1, 'years'); + renderView(); + } + + + function today() { + date = t.getNow(); + renderView(); + } + + + function gotoDate(dateInput) { + date = t.moment(dateInput); + renderView(); + } + + + function incrementDate(delta) { + date.add(moment.duration(delta)); + renderView(); + } + + + // Forces navigation to a view for the given date. + // `viewName` can be a specific view name or a generic one like "week" or "day". + function zoomTo(newDate, viewName) { + var viewStr; + var match; + + if (!viewName || fcViews[viewName] === undefined) { // a general view name, or "auto" + viewName = viewName || 'day'; + viewStr = header.getViewsWithButtons().join(' '); // space-separated string of all the views in the header + + // try to match a general view name, like "week", against a specific one, like "agendaWeek" + match = viewStr.match(new RegExp('\\w+' + capitaliseFirstLetter(viewName))); + + // fall back to the day view being used in the header + if (!match) { + match = viewStr.match(/\w+Day/); + } + + viewName = match ? match[0] : 'agendaDay'; // fall back to agendaDay + } + + date = newDate; + changeView(viewName); + } + + + function getDate() { + return date.clone(); + } + + + + /* Height "Freezing" + -----------------------------------------------------------------------------*/ + + + function freezeContentHeight() { + content.css({ + width: '100%', + height: content.height(), + overflow: 'hidden' + }); + } + + + function unfreezeContentHeight() { + content.css({ + width: '', + height: '', + overflow: '' + }); + } + + + + /* Misc + -----------------------------------------------------------------------------*/ + + + function getCalendar() { + return t; + } + + + function getView() { + return currentView; + } + + + function option(name, value) { + if (value === undefined) { + return options[name]; + } + if (name == 'height' || name == 'contentHeight' || name == 'aspectRatio') { + options[name] = value; + updateSize(true); // true = allow recalculation of height + } + } + + + function trigger(name, thisObj) { + if (options[name]) { + return options[name].apply( + thisObj || _element, + Array.prototype.slice.call(arguments, 2) + ); + } + } + +} + +;; + +/* Top toolbar area with buttons and title +----------------------------------------------------------------------------------------------------------------------*/ +// TODO: rename all header-related things to "toolbar" + +function Header(calendar, options) { + var t = this; + + // exports + t.render = render; + t.destroy = destroy; + t.updateTitle = updateTitle; + t.activateButton = activateButton; + t.deactivateButton = deactivateButton; + t.disableButton = disableButton; + t.enableButton = enableButton; + t.getViewsWithButtons = getViewsWithButtons; + + // locals + var el = $(); + var viewsWithButtons = []; + var tm; + + + function render() { + var sections = options.header; + + tm = options.theme ? 'ui' : 'fc'; + + if (sections) { + el = $("
") + .append(renderSection('left')) + .append(renderSection('right')) + .append(renderSection('center')) + .append('
'); + + return el; + } + } + + + function destroy() { + el.remove(); + } + + + function renderSection(position) { + var sectionEl = $('
'); + var buttonStr = options.header[position]; + + if (buttonStr) { + $.each(buttonStr.split(' '), function(i) { + var groupChildren = $(); + var isOnlyButtons = true; + var groupEl; + + $.each(this.split(','), function(j, buttonName) { + var buttonClick; + var themeIcon; + var normalIcon; + var defaultText; + var customText; + var innerHtml; + var classes; + var button; + + if (buttonName == 'title') { + groupChildren = groupChildren.add($('

 

')); // we always want it to take up height + isOnlyButtons = false; + } + else { + if (calendar[buttonName]) { // a calendar method + buttonClick = function() { + calendar[buttonName](); + }; + } + else if (fcViews[buttonName]) { // a view name + buttonClick = function() { + calendar.changeView(buttonName); + }; + viewsWithButtons.push(buttonName); + } + if (buttonClick) { + + // smartProperty allows different text per view button (ex: "Agenda Week" vs "Basic Week") + themeIcon = smartProperty(options.themeButtonIcons, buttonName); + normalIcon = smartProperty(options.buttonIcons, buttonName); + defaultText = smartProperty(options.defaultButtonText, buttonName); + customText = smartProperty(options.buttonText, buttonName); + + if (customText) { + innerHtml = htmlEscape(customText); + } + else if (themeIcon && options.theme) { + innerHtml = ""; + } + else if (normalIcon && !options.theme) { + innerHtml = ""; + } + else { + innerHtml = htmlEscape(defaultText || buttonName); + } + + classes = [ + 'fc-' + buttonName + '-button', + tm + '-button', + tm + '-state-default' + ]; + + button = $( // type="button" so that it doesn't submit a form + '' + ) + .click(function() { + // don't process clicks for disabled buttons + if (!button.hasClass(tm + '-state-disabled')) { + + buttonClick(); + + // after the click action, if the button becomes the "active" tab, or disabled, + // it should never have a hover class, so remove it now. + if ( + button.hasClass(tm + '-state-active') || + button.hasClass(tm + '-state-disabled') + ) { + button.removeClass(tm + '-state-hover'); + } + } + }) + .mousedown(function() { + // the *down* effect (mouse pressed in). + // only on buttons that are not the "active" tab, or disabled + button + .not('.' + tm + '-state-active') + .not('.' + tm + '-state-disabled') + .addClass(tm + '-state-down'); + }) + .mouseup(function() { + // undo the *down* effect + button.removeClass(tm + '-state-down'); + }) + .hover( + function() { + // the *hover* effect. + // only on buttons that are not the "active" tab, or disabled + button + .not('.' + tm + '-state-active') + .not('.' + tm + '-state-disabled') + .addClass(tm + '-state-hover'); + }, + function() { + // undo the *hover* effect + button + .removeClass(tm + '-state-hover') + .removeClass(tm + '-state-down'); // if mouseleave happens before mouseup + } + ); + + groupChildren = groupChildren.add(button); + } + } + }); + + if (isOnlyButtons) { + groupChildren + .first().addClass(tm + '-corner-left').end() + .last().addClass(tm + '-corner-right').end(); + } + + if (groupChildren.length > 1) { + groupEl = $('
'); + if (isOnlyButtons) { + groupEl.addClass('fc-button-group'); + } + groupEl.append(groupChildren); + sectionEl.append(groupEl); + } + else { + sectionEl.append(groupChildren); // 1 or 0 children + } + }); + } + + return sectionEl; + } + + + function updateTitle(text) { + el.find('h2').text(text); + } + + + function activateButton(buttonName) { + el.find('.fc-' + buttonName + '-button') + .addClass(tm + '-state-active'); + } + + + function deactivateButton(buttonName) { + el.find('.fc-' + buttonName + '-button') + .removeClass(tm + '-state-active'); + } + + + function disableButton(buttonName) { + el.find('.fc-' + buttonName + '-button') + .attr('disabled', 'disabled') + .addClass(tm + '-state-disabled'); + } + + + function enableButton(buttonName) { + el.find('.fc-' + buttonName + '-button') + .removeAttr('disabled') + .removeClass(tm + '-state-disabled'); + } + + + function getViewsWithButtons() { + return viewsWithButtons; + } + +} + +;; + +fc.sourceNormalizers = []; +fc.sourceFetchers = []; + +var ajaxDefaults = { + dataType: 'json', + cache: false +}; + +var eventGUID = 1; + + +function EventManager(options) { // assumed to be a calendar + var t = this; + + + // exports + t.isFetchNeeded = isFetchNeeded; + t.fetchEvents = fetchEvents; + t.addEventSource = addEventSource; + t.removeEventSource = removeEventSource; + t.updateEvent = updateEvent; + t.renderEvent = renderEvent; + t.removeEvents = removeEvents; + t.clientEvents = clientEvents; + t.mutateEvent = mutateEvent; + + + // imports + var trigger = t.trigger; + var getView = t.getView; + var reportEvents = t.reportEvents; + var getEventEnd = t.getEventEnd; + + + // locals + var stickySource = { events: [] }; + var sources = [ stickySource ]; + var rangeStart, rangeEnd; + var currentFetchID = 0; + var pendingSourceCnt = 0; + var loadingLevel = 0; + var cache = []; + + + $.each( + (options.events ? [ options.events ] : []).concat(options.eventSources || []), + function(i, sourceInput) { + var source = buildEventSource(sourceInput); + if (source) { + sources.push(source); + } + } + ); + + + + /* Fetching + -----------------------------------------------------------------------------*/ + + + function isFetchNeeded(start, end) { + return !rangeStart || // nothing has been fetched yet? + // or, a part of the new range is outside of the old range? (after normalizing) + start.clone().stripZone() < rangeStart.clone().stripZone() || + end.clone().stripZone() > rangeEnd.clone().stripZone(); + } + + + function fetchEvents(start, end) { + rangeStart = start; + rangeEnd = end; + cache = []; + var fetchID = ++currentFetchID; + var len = sources.length; + pendingSourceCnt = len; + for (var i=0; i cells, find the cell with the largest natural width and set the widths of all the +// cells to be that width. +// PREREQUISITE: if you want a cell to take up width, it needs to have a single inner element w/ display:inline +function matchCellWidths(els) { + var maxInnerWidth = 0; + + els.find('> *').each(function(i, innerEl) { + var innerWidth = $(innerEl).outerWidth(); + if (innerWidth > maxInnerWidth) { + maxInnerWidth = innerWidth; + } + }); + + maxInnerWidth++; // sometimes not accurate of width the text needs to stay on one line. insurance + + els.width(maxInnerWidth); + + return maxInnerWidth; +} + + +// Turns a container element into a scroller if its contents is taller than the allotted height. +// Returns true if the element is now a scroller, false otherwise. +// NOTE: this method is best because it takes weird zooming dimensions into account +function setPotentialScroller(containerEl, height) { + containerEl.height(height).addClass('fc-scroller'); + + // are scrollbars needed? + if (containerEl[0].scrollHeight - 1 > containerEl[0].clientHeight) { // !!! -1 because IE is often off-by-one :( + return true; + } + + unsetScroller(containerEl); // undo + return false; +} + + +// Takes an element that might have been a scroller, and turns it back into a normal element. +function unsetScroller(containerEl) { + containerEl.height('').removeClass('fc-scroller'); +} + + +/* General DOM Utilities +----------------------------------------------------------------------------------------------------------------------*/ + + +// borrowed from https://github.com/jquery/jquery-ui/blob/1.11.0/ui/core.js#L51 +function getScrollParent(el) { + var position = el.css('position'), + scrollParent = el.parents().filter(function() { + var parent = $(this); + return (/(auto|scroll)/).test( + parent.css('overflow') + parent.css('overflow-y') + parent.css('overflow-x') + ); + }).eq(0); + + return position === 'fixed' || !scrollParent.length ? $(el[0].ownerDocument || document) : scrollParent; +} + + +// Given a container element, return an object with the pixel values of the left/right scrollbars. +// Left scrollbars might occur on RTL browsers (IE maybe?) but I have not tested. +// PREREQUISITE: container element must have a single child with display:block +function getScrollbarWidths(container) { + var containerLeft = container.offset().left; + var containerRight = containerLeft + container.width(); + var inner = container.children(); + var innerLeft = inner.offset().left; + var innerRight = innerLeft + inner.outerWidth(); + + return { + left: innerLeft - containerLeft, + right: containerRight - innerRight + }; +} + + +// Returns a boolean whether this was a left mouse click and no ctrl key (which means right click on Mac) +function isPrimaryMouseButton(ev) { + return ev.which == 1 && !ev.ctrlKey; +} + + +/* FullCalendar-specific Misc Utilities +----------------------------------------------------------------------------------------------------------------------*/ + + +// Creates a basic segment with the intersection of the two ranges. Returns undefined if no intersection. +// Expects all dates to be normalized to the same timezone beforehand. +function intersectionToSeg(subjectStart, subjectEnd, intervalStart, intervalEnd) { + var segStart, segEnd; + var isStart, isEnd; + + if (subjectEnd > intervalStart && subjectStart < intervalEnd) { // in bounds at all? + + if (subjectStart >= intervalStart) { + segStart = subjectStart.clone(); + isStart = true; + } + else { + segStart = intervalStart.clone(); + isStart = false; + } + + if (subjectEnd <= intervalEnd) { + segEnd = subjectEnd.clone(); + isEnd = true; + } + else { + segEnd = intervalEnd.clone(); + isEnd = false; + } + + return { + start: segStart, + end: segEnd, + isStart: isStart, + isEnd: isEnd + }; + } +} + + +function smartProperty(obj, name) { // get a camel-cased/namespaced property of an object + obj = obj || {}; + if (obj[name] !== undefined) { + return obj[name]; + } + var parts = name.split(/(?=[A-Z])/), + i = parts.length - 1, res; + for (; i>=0; i--) { + res = obj[parts[i].toLowerCase()]; + if (res !== undefined) { + return res; + } + } + return obj['default']; +} + + +/* Date Utilities +----------------------------------------------------------------------------------------------------------------------*/ + +var dayIDs = [ 'sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat' ]; + + +// Diffs the two moments into a Duration where full-days are recorded first, then the remaining time. +// Moments will have their timezones normalized. +function dayishDiff(a, b) { + return moment.duration({ + days: a.clone().stripTime().diff(b.clone().stripTime(), 'days'), + ms: a.time() - b.time() + }); +} + + +function isNativeDate(input) { + return Object.prototype.toString.call(input) === '[object Date]' || input instanceof Date; +} + + +function dateCompare(a, b) { // works with Moments and native Dates + return a - b; +} + + +/* General Utilities +----------------------------------------------------------------------------------------------------------------------*/ + +fc.applyAll = applyAll; // export + + +// Create an object that has the given prototype. Just like Object.create +function createObject(proto) { + var f = function() {}; + f.prototype = proto; + return new f(); +} + + +// Copies specifically-owned (non-protoype) properties of `b` onto `a`. +// FYI, $.extend would copy *all* properties of `b` onto `a`. +function extend(a, b) { + for (var i in b) { + if (b.hasOwnProperty(i)) { + a[i] = b[i]; + } + } +} + + +function applyAll(functions, thisObj, args) { + if ($.isFunction(functions)) { + functions = [ functions ]; + } + if (functions) { + var i; + var ret; + for (i=0; i/g, '>') + .replace(/'/g, ''') + .replace(/"/g, '"') + .replace(/\n/g, '
'); +} + + +function stripHtmlEntities(text) { + return text.replace(/&.*?;/g, ''); +} + + +function capitaliseFirstLetter(str) { + return str.charAt(0).toUpperCase() + str.slice(1); +} + + +// Returns a function, that, as long as it continues to be invoked, will not +// be triggered. The function will be called after it stops being called for +// N milliseconds. +// https://github.com/jashkenas/underscore/blob/1.6.0/underscore.js#L714 +function debounce(func, wait) { + var timeoutId; + var args; + var context; + var timestamp; // of most recent call + var later = function() { + var last = +new Date() - timestamp; + if (last < wait && last > 0) { + timeoutId = setTimeout(later, wait - last); + } + else { + timeoutId = null; + func.apply(context, args); + if (!timeoutId) { + context = args = null; + } + } + }; + + return function() { + context = this; + args = arguments; + timestamp = +new Date(); + if (!timeoutId) { + timeoutId = setTimeout(later, wait); + } + }; +} + +;; + +var ambigDateOfMonthRegex = /^\s*\d{4}-\d\d$/; +var ambigTimeOrZoneRegex = + /^\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+)?)?)?)?)?$/; + + +// Creating +// ------------------------------------------------------------------------------------------------- + +// Creates a new moment, similar to the vanilla moment(...) constructor, but with +// extra features (ambiguous time, enhanced formatting). When gived an existing moment, +// it will function as a clone (and retain the zone of the moment). Anything else will +// result in a moment in the local zone. +fc.moment = function() { + return makeMoment(arguments); +}; + +// Sames as fc.moment, but forces the resulting moment to be in the UTC timezone. +fc.moment.utc = function() { + var mom = makeMoment(arguments, true); + + // Force it into UTC because makeMoment doesn't guarantee it. + if (mom.hasTime()) { // don't give ambiguously-timed moments a UTC zone + mom.utc(); + } + + return mom; +}; + +// Same as fc.moment, but when given an ISO8601 string, the timezone offset is preserved. +// ISO8601 strings with no timezone offset will become ambiguously zoned. +fc.moment.parseZone = function() { + return makeMoment(arguments, true, true); +}; + +// Builds an FCMoment from args. When given an existing moment, it clones. When given a native +// Date, or called with no arguments (the current time), the resulting moment will be local. +// Anything else needs to be "parsed" (a string or an array), and will be affected by: +// parseAsUTC - if there is no zone information, should we parse the input in UTC? +// parseZone - if there is zone information, should we force the zone of the moment? +function makeMoment(args, parseAsUTC, parseZone) { + var input = args[0]; + var isSingleString = args.length == 1 && typeof input === 'string'; + var isAmbigTime; + var isAmbigZone; + var ambigMatch; + var output; // an object with fields for the new FCMoment object + + if (moment.isMoment(input)) { + output = moment.apply(null, args); // clone it + + // the ambig properties have not been preserved in the clone, so reassign them + if (input._ambigTime) { + output._ambigTime = true; + } + if (input._ambigZone) { + output._ambigZone = true; + } + } + else if (isNativeDate(input) || input === undefined) { + output = moment.apply(null, args); // will be local + } + else { // "parsing" is required + isAmbigTime = false; + isAmbigZone = false; + + if (isSingleString) { + if (ambigDateOfMonthRegex.test(input)) { + // accept strings like '2014-05', but convert to the first of the month + input += '-01'; + args = [ input ]; // for when we pass it on to moment's constructor + isAmbigTime = true; + isAmbigZone = true; + } + else if ((ambigMatch = ambigTimeOrZoneRegex.exec(input))) { + isAmbigTime = !ambigMatch[5]; // no time part? + isAmbigZone = true; + } + } + else if ($.isArray(input)) { + // arrays have no timezone information, so assume ambiguous zone + isAmbigZone = true; + } + // otherwise, probably a string with a format + + if (parseAsUTC) { + output = moment.utc.apply(moment, args); + } + else { + output = moment.apply(null, args); + } + + if (isAmbigTime) { + output._ambigTime = true; + output._ambigZone = true; // ambiguous time always means ambiguous zone + } + else if (parseZone) { // let's record the inputted zone somehow + if (isAmbigZone) { + output._ambigZone = true; + } + else if (isSingleString) { + output.zone(input); // if not a valid zone, will assign UTC + } + } + } + + return new FCMoment(output); +} + +// Our subclass of Moment. +// Accepts an object with the internal Moment properties that should be copied over to +// `this` object (most likely another Moment object). The values in this data must not +// be referenced by anything else (two moments sharing a Date object for example). +function FCMoment(internalData) { + extend(this, internalData); +} + +// Chain the prototype to Moment's +FCMoment.prototype = createObject(moment.fn); + +// We need this because Moment's implementation won't create an FCMoment, +// nor will it copy over the ambig flags. +FCMoment.prototype.clone = function() { + return makeMoment([ this ]); +}; + + +// Time-of-day +// ------------------------------------------------------------------------------------------------- + +// GETTER +// Returns a Duration with the hours/minutes/seconds/ms values of the moment. +// If the moment has an ambiguous time, a duration of 00:00 will be returned. +// +// SETTER +// You can supply a Duration, a Moment, or a Duration-like argument. +// When setting the time, and the moment has an ambiguous time, it then becomes unambiguous. +FCMoment.prototype.time = function(time) { + if (time == null) { // getter + return moment.duration({ + hours: this.hours(), + minutes: this.minutes(), + seconds: this.seconds(), + milliseconds: this.milliseconds() + }); + } + else { // setter + + delete this._ambigTime; // mark that the moment now has a time + + if (!moment.isDuration(time) && !moment.isMoment(time)) { + time = moment.duration(time); + } + + // The day value should cause overflow (so 24 hours becomes 00:00:00 of next day). + // Only for Duration times, not Moment times. + var dayHours = 0; + if (moment.isDuration(time)) { + dayHours = Math.floor(time.asDays()) * 24; + } + + // We need to set the individual fields. + // Can't use startOf('day') then add duration. In case of DST at start of day. + return this.hours(dayHours + time.hours()) + .minutes(time.minutes()) + .seconds(time.seconds()) + .milliseconds(time.milliseconds()); + } +}; + +// Converts the moment to UTC, stripping out its time-of-day and timezone offset, +// but preserving its YMD. A moment with a stripped time will display no time +// nor timezone offset when .format() is called. +FCMoment.prototype.stripTime = function() { + var a = this.toArray(); // year,month,date,hours,minutes,seconds as an array + + // set the internal UTC flag + moment.fn.utc.call(this); // call the original method, because we don't want to affect _ambigZone + + this.year(a[0]) // TODO: find a way to do this in one shot + .month(a[1]) + .date(a[2]) + .hours(0) + .minutes(0) + .seconds(0) + .milliseconds(0); + + // Mark the time as ambiguous. This needs to happen after the .utc() call, which calls .zone(), which + // clears all ambig flags. Same concept with the .year/month/date calls in the case of moment-timezone. + this._ambigTime = true; + this._ambigZone = true; // if ambiguous time, also ambiguous timezone offset + + return this; // for chaining +}; + +// Returns if the moment has a non-ambiguous time (boolean) +FCMoment.prototype.hasTime = function() { + return !this._ambigTime; +}; + + +// Timezone +// ------------------------------------------------------------------------------------------------- + +// Converts the moment to UTC, stripping out its timezone offset, but preserving its +// YMD and time-of-day. A moment with a stripped timezone offset will display no +// timezone offset when .format() is called. +FCMoment.prototype.stripZone = function() { + var a = this.toArray(); // year,month,date,hours,minutes,seconds as an array + var wasAmbigTime = this._ambigTime; + + moment.fn.utc.call(this); // set the internal UTC flag + + this.year(a[0]) // TODO: find a way to do this in one shot + .month(a[1]) + .date(a[2]) + .hours(a[3]) + .minutes(a[4]) + .seconds(a[5]) + .milliseconds(a[6]); + + if (wasAmbigTime) { + // the above call to .utc()/.zone() unfortunately clears the ambig flags, so reassign + this._ambigTime = true; + } + + // Mark the zone as ambiguous. This needs to happen after the .utc() call, which calls .zone(), which + // clears all ambig flags. Same concept with the .year/month/date calls in the case of moment-timezone. + this._ambigZone = true; + + return this; // for chaining +}; + +// Returns of the moment has a non-ambiguous timezone offset (boolean) +FCMoment.prototype.hasZone = function() { + return !this._ambigZone; +}; + +// this method implicitly marks a zone +FCMoment.prototype.zone = function(tzo) { + + if (tzo != null) { + // FYI, the delete statements need to be before the .zone() call or else chaos ensues + // for reasons I don't understand. + delete this._ambigTime; + delete this._ambigZone; + } + + return moment.fn.zone.apply(this, arguments); +}; + +// this method implicitly marks a zone +FCMoment.prototype.local = function() { + var a = this.toArray(); // year,month,date,hours,minutes,seconds as an array + var wasAmbigZone = this._ambigZone; + + // will happen anyway via .local()/.zone(), but don't want to rely on internal implementation + delete this._ambigTime; + delete this._ambigZone; + + moment.fn.local.apply(this, arguments); + + if (wasAmbigZone) { + // If the moment was ambiguously zoned, the date fields were stored as UTC. + // We want to preserve these, but in local time. + this.year(a[0]) // TODO: find a way to do this in one shot + .month(a[1]) + .date(a[2]) + .hours(a[3]) + .minutes(a[4]) + .seconds(a[5]) + .milliseconds(a[6]); + } + + return this; // for chaining +}; + +// this method implicitly marks a zone +FCMoment.prototype.utc = function() { + + // will happen anyway via .local()/.zone(), but don't want to rely on internal implementation + delete this._ambigTime; + delete this._ambigZone; + + return moment.fn.utc.apply(this, arguments); +}; + + +// Formatting +// ------------------------------------------------------------------------------------------------- + +FCMoment.prototype.format = function() { + if (arguments[0]) { + return formatDate(this, arguments[0]); // our extended formatting + } + if (this._ambigTime) { + return momentFormat(this, 'YYYY-MM-DD'); + } + if (this._ambigZone) { + return momentFormat(this, 'YYYY-MM-DD[T]HH:mm:ss'); + } + return momentFormat(this); // default moment original formatting +}; + +FCMoment.prototype.toISOString = function() { + if (this._ambigTime) { + return momentFormat(this, 'YYYY-MM-DD'); + } + if (this._ambigZone) { + return momentFormat(this, 'YYYY-MM-DD[T]HH:mm:ss'); + } + return moment.fn.toISOString.apply(this, arguments); +}; + + +// Querying +// ------------------------------------------------------------------------------------------------- + +// Is the moment within the specified range? `end` is exclusive. +FCMoment.prototype.isWithin = function(start, end) { + var a = commonlyAmbiguate([ this, start, end ]); + return a[0] >= a[1] && a[0] < a[2]; +}; + +// When isSame is called with units, timezone ambiguity is normalized before the comparison happens. +// If no units are specified, the two moments must be identically the same, with matching ambig flags. +FCMoment.prototype.isSame = function(input, units) { + var a; + + if (units) { + a = commonlyAmbiguate([ this, input ], true); // normalize timezones but don't erase times + return moment.fn.isSame.call(a[0], a[1], units); + } + else { + input = fc.moment.parseZone(input); // normalize input + return moment.fn.isSame.call(this, input) && + Boolean(this._ambigTime) === Boolean(input._ambigTime) && + Boolean(this._ambigZone) === Boolean(input._ambigZone); + } +}; + +// Make these query methods work with ambiguous moments +$.each([ + 'isBefore', + 'isAfter' +], function(i, methodName) { + FCMoment.prototype[methodName] = function(input, units) { + var a = commonlyAmbiguate([ this, input ]); + return moment.fn[methodName].call(a[0], a[1], units); + }; +}); + + +// Misc Internals +// ------------------------------------------------------------------------------------------------- + +// given an array of moment-like inputs, return a parallel array w/ moments similarly ambiguated. +// for example, of one moment has ambig time, but not others, all moments will have their time stripped. +// set `preserveTime` to `true` to keep times, but only normalize zone ambiguity. +function commonlyAmbiguate(inputs, preserveTime) { + var outputs = []; + var anyAmbigTime = false; + var anyAmbigZone = false; + var i; + + for (i=0; i "MMMM D YYYY" + formatStr = localeData.longDateFormat(formatStr) || formatStr; + // BTW, this is not important for `formatDate` because it is impossible to put custom tokens + // or non-zero areas in Moment's localized format strings. + + separator = separator || ' - '; + + return formatRangeWithChunks( + date1, + date2, + getFormatStringChunks(formatStr), + separator, + isRTL + ); +} +fc.formatRange = formatRange; // expose + + +function formatRangeWithChunks(date1, date2, chunks, separator, isRTL) { + var chunkStr; // the rendering of the chunk + var leftI; + var leftStr = ''; + var rightI; + var rightStr = ''; + var middleI; + var middleStr1 = ''; + var middleStr2 = ''; + var middleStr = ''; + + // Start at the leftmost side of the formatting string and continue until you hit a token + // that is not the same between dates. + for (leftI=0; leftIleftI; rightI--) { + chunkStr = formatSimilarChunk(date1, date2, chunks[rightI]); + if (chunkStr === false) { + break; + } + rightStr = chunkStr + rightStr; + } + + // The area in the middle is different for both of the dates. + // Collect them distinctly so we can jam them together later. + for (middleI=leftI; middleI<=rightI; middleI++) { + middleStr1 += formatDateWithChunk(date1, chunks[middleI]); + middleStr2 += formatDateWithChunk(date2, chunks[middleI]); + } + + if (middleStr1 || middleStr2) { + if (isRTL) { + middleStr = middleStr2 + separator + middleStr1; + } + else { + middleStr = middleStr1 + separator + middleStr2; + } + } + + return leftStr + middleStr + rightStr; +} + + +var similarUnitMap = { + Y: 'year', + M: 'month', + D: 'day', // day of month + d: 'day', // day of week + // prevents a separator between anything time-related... + A: 'second', // AM/PM + a: 'second', // am/pm + T: 'second', // A/P + t: 'second', // a/p + H: 'second', // hour (24) + h: 'second', // hour (12) + m: 'second', // minute + s: 'second' // second +}; +// TODO: week maybe? + + +// Given a formatting chunk, and given that both dates are similar in the regard the +// formatting chunk is concerned, format date1 against `chunk`. Otherwise, return `false`. +function formatSimilarChunk(date1, date2, chunk) { + var token; + var unit; + + if (typeof chunk === 'string') { // a literal string + return chunk; + } + else if ((token = chunk.token)) { + unit = similarUnitMap[token.charAt(0)]; + // are the dates the same for this unit of measurement? + if (unit && date1.isSame(date2, unit)) { + return momentFormat(date1, token); // would be the same if we used `date2` + // BTW, don't support custom tokens + } + } + + return false; // the chunk is NOT the same for the two dates + // BTW, don't support splitting on non-zero areas +} + + +// Chunking Utils +// ------------------------------------------------------------------------------------------------- + + +var formatStringChunkCache = {}; + + +function getFormatStringChunks(formatStr) { + if (formatStr in formatStringChunkCache) { + return formatStringChunkCache[formatStr]; + } + return (formatStringChunkCache[formatStr] = chunkFormatString(formatStr)); +} + + +// Break the formatting string into an array of chunks +function chunkFormatString(formatStr) { + var chunks = []; + var chunker = /\[([^\]]*)\]|\(([^\)]*)\)|(LT|(\w)\4*o?)|([^\w\[\(]+)/g; // TODO: more descrimination + var match; + + while ((match = chunker.exec(formatStr))) { + if (match[1]) { // a literal string inside [ ... ] + chunks.push(match[1]); + } + else if (match[2]) { // non-zero formatting inside ( ... ) + chunks.push({ maybe: chunkFormatString(match[2]) }); + } + else if (match[3]) { // a formatting token + chunks.push({ token: match[3] }); + } + else if (match[5]) { // an unenclosed literal string + chunks.push(match[5]); + } + } + + return chunks; +} + +;; + +/* A rectangular panel that is absolutely positioned over other content +------------------------------------------------------------------------------------------------------------------------ +Options: + - className (string) + - content (HTML string or jQuery element set) + - parentEl + - top + - left + - right (the x coord of where the right edge should be. not a "CSS" right) + - autoHide (boolean) + - show (callback) + - hide (callback) +*/ + +function Popover(options) { + this.options = options || {}; +} + + +Popover.prototype = { + + isHidden: true, + options: null, + el: null, // the container element for the popover. generated by this object + documentMousedownProxy: null, // document mousedown handler bound to `this` + margin: 10, // the space required between the popover and the edges of the scroll container + + + // Shows the popover on the specified position. Renders it if not already + show: function() { + if (this.isHidden) { + if (!this.el) { + this.render(); + } + this.el.show(); + this.position(); + this.isHidden = false; + this.trigger('show'); + } + }, + + + // Hides the popover, through CSS, but does not remove it from the DOM + hide: function() { + if (!this.isHidden) { + this.el.hide(); + this.isHidden = true; + this.trigger('hide'); + } + }, + + + // Creates `this.el` and renders content inside of it + render: function() { + var _this = this; + var options = this.options; + + this.el = $('
') + .addClass(options.className || '') + .css({ + // position initially to the top left to avoid creating scrollbars + top: 0, + left: 0 + }) + .append(options.content) + .appendTo(options.parentEl); + + // when a click happens on anything inside with a 'fc-close' className, hide the popover + this.el.on('click', '.fc-close', function() { + _this.hide(); + }); + + if (options.autoHide) { + $(document).on('mousedown', this.documentMousedownProxy = $.proxy(this, 'documentMousedown')); + } + }, + + + // Triggered when the user clicks *anywhere* in the document, for the autoHide feature + documentMousedown: function(ev) { + // only hide the popover if the click happened outside the popover + if (this.el && !$(ev.target).closest(this.el).length) { + this.hide(); + } + }, + + + // Hides and unregisters any handlers + destroy: function() { + this.hide(); + + if (this.el) { + this.el.remove(); + this.el = null; + } + + $(document).off('mousedown', this.documentMousedownProxy); + }, + + + // Positions the popover optimally, using the top/left/right options + position: function() { + var options = this.options; + var origin = this.el.offsetParent().offset(); + var width = this.el.outerWidth(); + var height = this.el.outerHeight(); + var windowEl = $(window); + var viewportEl = getScrollParent(this.el); + var viewportTop; + var viewportLeft; + var viewportOffset; + var top; // the "position" (not "offset") values for the popover + var left; // + + // compute top and left + top = options.top || 0; + if (options.left !== undefined) { + left = options.left; + } + else if (options.right !== undefined) { + left = options.right - width; // derive the left value from the right value + } + else { + left = 0; + } + + if (viewportEl.is(window) || viewportEl.is(document)) { // normalize getScrollParent's result + viewportEl = windowEl; + viewportTop = 0; // the window is always at the top left + viewportLeft = 0; // (and .offset() won't work if called here) + } + else { + viewportOffset = viewportEl.offset(); + viewportTop = viewportOffset.top; + viewportLeft = viewportOffset.left; + } + + // if the window is scrolled, it causes the visible area to be further down + viewportTop += windowEl.scrollTop(); + viewportLeft += windowEl.scrollLeft(); + + // constrain to the view port. if constrained by two edges, give precedence to top/left + if (options.viewportConstrain !== false) { + top = Math.min(top, viewportTop + viewportEl.outerHeight() - height - this.margin); + top = Math.max(top, viewportTop + this.margin); + left = Math.min(left, viewportLeft + viewportEl.outerWidth() - width - this.margin); + left = Math.max(left, viewportLeft + this.margin); + } + + this.el.css({ + top: top - origin.top, + left: left - origin.left + }); + }, + + + // Triggers a callback. Calls a function in the option hash of the same name. + // Arguments beyond the first `name` are forwarded on. + // TODO: better code reuse for this. Repeat code + trigger: function(name) { + if (this.options[name]) { + this.options[name].apply(this, Array.prototype.slice.call(arguments, 1)); + } + } + +}; + +;; + +/* A "coordinate map" converts pixel coordinates into an associated cell, which has an associated date +------------------------------------------------------------------------------------------------------------------------ +Common interface: + + CoordMap.prototype = { + build: function() {}, + getCell: function(x, y) {} + }; + +*/ + +/* Coordinate map for a grid component +----------------------------------------------------------------------------------------------------------------------*/ + +function GridCoordMap(grid) { + this.grid = grid; +} + + +GridCoordMap.prototype = { + + grid: null, // reference to the Grid + rows: null, // the top-to-bottom y coordinates. including the bottom of the last item + cols: null, // the left-to-right x coordinates. including the right of the last item + + containerEl: null, // container element that all coordinates are constrained to. optionally assigned + minX: null, + maxX: null, // exclusive + minY: null, + maxY: null, // exclusive + + + // Queries the grid for the coordinates of all the cells + build: function() { + this.grid.buildCoords( + this.rows = [], + this.cols = [] + ); + this.computeBounds(); + }, + + + // Given a coordinate of the document, gets the associated cell. If no cell is underneath, returns null + getCell: function(x, y) { + var cell = null; + var rows = this.rows; + var cols = this.cols; + var r = -1; + var c = -1; + var i; + + if (this.inBounds(x, y)) { + + for (i = 0; i < rows.length; i++) { + if (y >= rows[i][0] && y < rows[i][1]) { + r = i; + break; + } + } + + for (i = 0; i < cols.length; i++) { + if (x >= cols[i][0] && x < cols[i][1]) { + c = i; + break; + } + } + + if (r >= 0 && c >= 0) { + cell = { row: r, col: c }; + cell.grid = this.grid; + cell.date = this.grid.getCellDate(cell); + } + } + + return cell; + }, + + + // If there is a containerEl, compute the bounds into min/max values + computeBounds: function() { + var containerOffset; + + if (this.containerEl) { + containerOffset = this.containerEl.offset(); + this.minX = containerOffset.left; + this.maxX = containerOffset.left + this.containerEl.outerWidth(); + this.minY = containerOffset.top; + this.maxY = containerOffset.top + this.containerEl.outerHeight(); + } + }, + + + // Determines if the given coordinates are in bounds. If no `containerEl`, always true + inBounds: function(x, y) { + if (this.containerEl) { + return x >= this.minX && x < this.maxX && y >= this.minY && y < this.maxY; + } + return true; + } + +}; + + +/* Coordinate map that is a combination of multiple other coordinate maps +----------------------------------------------------------------------------------------------------------------------*/ + +function ComboCoordMap(coordMaps) { + this.coordMaps = coordMaps; +} + + +ComboCoordMap.prototype = { + + coordMaps: null, // an array of CoordMaps + + + // Builds all coordMaps + build: function() { + var coordMaps = this.coordMaps; + var i; + + for (i = 0; i < coordMaps.length; i++) { + coordMaps[i].build(); + } + }, + + + // Queries all coordMaps for the cell underneath the given coordinates, returning the first result + getCell: function(x, y) { + var coordMaps = this.coordMaps; + var cell = null; + var i; + + for (i = 0; i < coordMaps.length && !cell; i++) { + cell = coordMaps[i].getCell(x, y); + } + + return cell; + } + +}; +;; + +/* Tracks mouse movements over a CoordMap and raises events about which cell the mouse is over. +----------------------------------------------------------------------------------------------------------------------*/ +// TODO: implement scrolling + +function DragListener(coordMap, options) { + this.coordMap = coordMap; + this.options = options || {}; +} + + +DragListener.prototype = { + + coordMap: null, + options: null, + + isListening: false, + isDragging: false, + + // the cell/date the mouse was over when listening started + origCell: null, + origDate: null, + + // the cell/date the mouse is over + cell: null, + date: null, + + // coordinates of the initial mousedown + mouseX0: null, + mouseY0: null, + + // handler attached to the document, bound to the DragListener's `this` + mousemoveProxy: null, + mouseupProxy: null, + + scrollEl: null, + scrollBounds: null, // { top, bottom, left, right } + scrollTopVel: null, // pixels per second + scrollLeftVel: null, // pixels per second + scrollIntervalId: null, // ID of setTimeout for scrolling animation loop + scrollHandlerProxy: null, // this-scoped function for handling when scrollEl is scrolled + + scrollSensitivity: 30, // pixels from edge for scrolling to start + scrollSpeed: 200, // pixels per second, at maximum speed + scrollIntervalMs: 50, // millisecond wait between scroll increment + + + // Call this when the user does a mousedown. Will probably lead to startListening + mousedown: function(ev) { + if (isPrimaryMouseButton(ev)) { + + ev.preventDefault(); // prevents native selection in most browsers + + this.startListening(ev); + + // start the drag immediately if there is no minimum distance for a drag start + if (!this.options.distance) { + this.startDrag(ev); + } + } + }, + + + // Call this to start tracking mouse movements + startListening: function(ev) { + var scrollParent; + var cell; + + if (!this.isListening) { + + // grab scroll container and attach handler + if (ev && this.options.scroll) { + scrollParent = getScrollParent($(ev.target)); + if (!scrollParent.is(window) && !scrollParent.is(document)) { + this.scrollEl = scrollParent; + + // scope to `this`, and use `debounce` to make sure rapid calls don't happen + this.scrollHandlerProxy = debounce($.proxy(this, 'scrollHandler'), 100); + this.scrollEl.on('scroll', this.scrollHandlerProxy); + } + } + + this.computeCoords(); // relies on `scrollEl` + + // get info on the initial cell, date, and coordinates + if (ev) { + cell = this.getCell(ev); + this.origCell = cell; + this.origDate = cell ? cell.date : null; + + this.mouseX0 = ev.pageX; + this.mouseY0 = ev.pageY; + } + + $(document) + .on('mousemove', this.mousemoveProxy = $.proxy(this, 'mousemove')) + .on('mouseup', this.mouseupProxy = $.proxy(this, 'mouseup')) + .on('selectstart', this.preventDefault); // prevents native selection in IE<=8 + + this.isListening = true; + this.trigger('listenStart', ev); + } + }, + + + // Recomputes the drag-critical positions of elements + computeCoords: function() { + this.coordMap.build(); + this.computeScrollBounds(); + }, + + + // Called when the user moves the mouse + mousemove: function(ev) { + var minDistance; + var distanceSq; // current distance from mouseX0/mouseY0, squared + + if (!this.isDragging) { // if not already dragging... + // then start the drag if the minimum distance criteria is met + minDistance = this.options.distance || 1; + distanceSq = Math.pow(ev.pageX - this.mouseX0, 2) + Math.pow(ev.pageY - this.mouseY0, 2); + if (distanceSq >= minDistance * minDistance) { // use pythagorean theorem + this.startDrag(ev); + } + } + + if (this.isDragging) { + this.drag(ev); // report a drag, even if this mousemove initiated the drag + } + }, + + + // Call this to initiate a legitimate drag. + // This function is called internally from this class, but can also be called explicitly from outside + startDrag: function(ev) { + var cell; + + if (!this.isListening) { // startDrag must have manually initiated + this.startListening(); + } + + if (!this.isDragging) { + this.isDragging = true; + this.trigger('dragStart', ev); + + // report the initial cell the mouse is over + cell = this.getCell(ev); + if (cell) { + this.cellOver(cell, true); + } + } + }, + + + // Called while the mouse is being moved and when we know a legitimate drag is taking place + drag: function(ev) { + var cell; + + if (this.isDragging) { + cell = this.getCell(ev); + + if (!isCellsEqual(cell, this.cell)) { // a different cell than before? + if (this.cell) { + this.cellOut(); + } + if (cell) { + this.cellOver(cell); + } + } + + this.dragScroll(ev); // will possibly cause scrolling + } + }, + + + // Called when a the mouse has just moved over a new cell + cellOver: function(cell) { + this.cell = cell; + this.date = cell.date; + this.trigger('cellOver', cell, cell.date); + }, + + + // Called when the mouse has just moved out of a cell + cellOut: function() { + if (this.cell) { + this.trigger('cellOut', this.cell); + this.cell = null; + this.date = null; + } + }, + + + // Called when the user does a mouseup + mouseup: function(ev) { + this.stopDrag(ev); + this.stopListening(ev); + }, + + + // Called when the drag is over. Will not cause listening to stop however. + // A concluding 'cellOut' event will NOT be triggered. + stopDrag: function(ev) { + if (this.isDragging) { + this.stopScrolling(); + this.trigger('dragStop', ev); + this.isDragging = false; + } + }, + + + // Call this to stop listening to the user's mouse events + stopListening: function(ev) { + if (this.isListening) { + + // remove the scroll handler if there is a scrollEl + if (this.scrollEl) { + this.scrollEl.off('scroll', this.scrollHandlerProxy); + this.scrollHandlerProxy = null; + } + + $(document) + .off('mousemove', this.mousemoveProxy) + .off('mouseup', this.mouseupProxy) + .off('selectstart', this.preventDefault); + + this.mousemoveProxy = null; + this.mouseupProxy = null; + + this.isListening = false; + this.trigger('listenStop', ev); + + this.origCell = this.cell = null; + this.origDate = this.date = null; + } + }, + + + // Gets the cell underneath the coordinates for the given mouse event + getCell: function(ev) { + return this.coordMap.getCell(ev.pageX, ev.pageY); + }, + + + // Triggers a callback. Calls a function in the option hash of the same name. + // Arguments beyond the first `name` are forwarded on. + trigger: function(name) { + if (this.options[name]) { + this.options[name].apply(this, Array.prototype.slice.call(arguments, 1)); + } + }, + + + // Stops a given mouse event from doing it's native browser action. In our case, text selection. + preventDefault: function(ev) { + ev.preventDefault(); + }, + + + /* Scrolling + ------------------------------------------------------------------------------------------------------------------*/ + + + // Computes and stores the bounding rectangle of scrollEl + computeScrollBounds: function() { + var el = this.scrollEl; + var offset; + + if (el) { + offset = el.offset(); + this.scrollBounds = { + top: offset.top, + left: offset.left, + bottom: offset.top + el.outerHeight(), + right: offset.left + el.outerWidth() + }; + } + }, + + + // Called when the dragging is in progress and scrolling should be updated + dragScroll: function(ev) { + var sensitivity = this.scrollSensitivity; + var bounds = this.scrollBounds; + var topCloseness, bottomCloseness; + var leftCloseness, rightCloseness; + var topVel = 0; + var leftVel = 0; + + if (bounds) { // only scroll if scrollEl exists + + // compute closeness to edges. valid range is from 0.0 - 1.0 + topCloseness = (sensitivity - (ev.pageY - bounds.top)) / sensitivity; + bottomCloseness = (sensitivity - (bounds.bottom - ev.pageY)) / sensitivity; + leftCloseness = (sensitivity - (ev.pageX - bounds.left)) / sensitivity; + rightCloseness = (sensitivity - (bounds.right - ev.pageX)) / sensitivity; + + // translate vertical closeness into velocity. + // mouse must be completely in bounds for velocity to happen. + if (topCloseness >= 0 && topCloseness <= 1) { + topVel = topCloseness * this.scrollSpeed * -1; // negative. for scrolling up + } + else if (bottomCloseness >= 0 && bottomCloseness <= 1) { + topVel = bottomCloseness * this.scrollSpeed; + } + + // translate horizontal closeness into velocity + if (leftCloseness >= 0 && leftCloseness <= 1) { + leftVel = leftCloseness * this.scrollSpeed * -1; // negative. for scrolling left + } + else if (rightCloseness >= 0 && rightCloseness <= 1) { + leftVel = rightCloseness * this.scrollSpeed; + } + } + + this.setScrollVel(topVel, leftVel); + }, + + + // Sets the speed-of-scrolling for the scrollEl + setScrollVel: function(topVel, leftVel) { + + this.scrollTopVel = topVel; + this.scrollLeftVel = leftVel; + + this.constrainScrollVel(); // massages into realistic values + + // if there is non-zero velocity, and an animation loop hasn't already started, then START + if ((this.scrollTopVel || this.scrollLeftVel) && !this.scrollIntervalId) { + this.scrollIntervalId = setInterval( + $.proxy(this, 'scrollIntervalFunc'), // scope to `this` + this.scrollIntervalMs + ); + } + }, + + + // Forces scrollTopVel and scrollLeftVel to be zero if scrolling has already gone all the way + constrainScrollVel: function() { + var el = this.scrollEl; + + if (this.scrollTopVel < 0) { // scrolling up? + if (el.scrollTop() <= 0) { // already scrolled all the way up? + this.scrollTopVel = 0; + } + } + else if (this.scrollTopVel > 0) { // scrolling down? + if (el.scrollTop() + el[0].clientHeight >= el[0].scrollHeight) { // already scrolled all the way down? + this.scrollTopVel = 0; + } + } + + if (this.scrollLeftVel < 0) { // scrolling left? + if (el.scrollLeft() <= 0) { // already scrolled all the left? + this.scrollLeftVel = 0; + } + } + else if (this.scrollLeftVel > 0) { // scrolling right? + if (el.scrollLeft() + el[0].clientWidth >= el[0].scrollWidth) { // already scrolled all the way right? + this.scrollLeftVel = 0; + } + } + }, + + + // This function gets called during every iteration of the scrolling animation loop + scrollIntervalFunc: function() { + var el = this.scrollEl; + var frac = this.scrollIntervalMs / 1000; // considering animation frequency, what the vel should be mult'd by + + // change the value of scrollEl's scroll + if (this.scrollTopVel) { + el.scrollTop(el.scrollTop() + this.scrollTopVel * frac); + } + if (this.scrollLeftVel) { + el.scrollLeft(el.scrollLeft() + this.scrollLeftVel * frac); + } + + this.constrainScrollVel(); // since the scroll values changed, recompute the velocities + + // if scrolled all the way, which causes the vels to be zero, stop the animation loop + if (!this.scrollTopVel && !this.scrollLeftVel) { + this.stopScrolling(); + } + }, + + + // Kills any existing scrolling animation loop + stopScrolling: function() { + if (this.scrollIntervalId) { + clearInterval(this.scrollIntervalId); + this.scrollIntervalId = null; + + // when all done with scrolling, recompute positions since they probably changed + this.computeCoords(); + } + }, + + + // Get called when the scrollEl is scrolled (NOTE: this is delayed via debounce) + scrollHandler: function() { + // recompute all coordinates, but *only* if this is *not* part of our scrolling animation + if (!this.scrollIntervalId) { + this.computeCoords(); + } + } + +}; + + +// Returns `true` if the cells are identically equal. `false` otherwise. +// They must have the same row, col, and be from the same grid. +// Two null values will be considered equal, as two "out of the grid" states are the same. +function isCellsEqual(cell1, cell2) { + + if (!cell1 && !cell2) { + return true; + } + + if (cell1 && cell2) { + return cell1.grid === cell2.grid && + cell1.row === cell2.row && + cell1.col === cell2.col; + } + + return false; +} + +;; + +/* Creates a clone of an element and lets it track the mouse as it moves +----------------------------------------------------------------------------------------------------------------------*/ + +function MouseFollower(sourceEl, options) { + this.options = options = options || {}; + this.sourceEl = sourceEl; + this.parentEl = options.parentEl ? $(options.parentEl) : sourceEl.parent(); // default to sourceEl's parent +} + + +MouseFollower.prototype = { + + options: null, + + sourceEl: null, // the element that will be cloned and made to look like it is dragging + el: null, // the clone of `sourceEl` that will track the mouse + parentEl: null, // the element that `el` (the clone) will be attached to + + // the initial position of el, relative to the offset parent. made to match the initial offset of sourceEl + top0: null, + left0: null, + + // the initial position of the mouse + mouseY0: null, + mouseX0: null, + + // the number of pixels the mouse has moved from its initial position + topDelta: null, + leftDelta: null, + + mousemoveProxy: null, // document mousemove handler, bound to the MouseFollower's `this` + + isFollowing: false, + isHidden: false, + isAnimating: false, // doing the revert animation? + + + // Causes the element to start following the mouse + start: function(ev) { + if (!this.isFollowing) { + this.isFollowing = true; + + this.mouseY0 = ev.pageY; + this.mouseX0 = ev.pageX; + this.topDelta = 0; + this.leftDelta = 0; + + if (!this.isHidden) { + this.updatePosition(); + } + + $(document).on('mousemove', this.mousemoveProxy = $.proxy(this, 'mousemove')); + } + }, + + + // Causes the element to stop following the mouse. If shouldRevert is true, will animate back to original position. + // `callback` gets invoked when the animation is complete. If no animation, it is invoked immediately. + stop: function(shouldRevert, callback) { + var _this = this; + var revertDuration = this.options.revertDuration; + + function complete() { + this.isAnimating = false; + _this.destroyEl(); + + this.top0 = this.left0 = null; // reset state for future updatePosition calls + + if (callback) { + callback(); + } + } + + if (this.isFollowing && !this.isAnimating) { // disallow more than one stop animation at a time + this.isFollowing = false; + + $(document).off('mousemove', this.mousemoveProxy); + + if (shouldRevert && revertDuration && !this.isHidden) { // do a revert animation? + this.isAnimating = true; + this.el.animate({ + top: this.top0, + left: this.left0 + }, { + duration: revertDuration, + complete: complete + }); + } + else { + complete(); + } + } + }, + + + // Gets the tracking element. Create it if necessary + getEl: function() { + var el = this.el; + + if (!el) { + this.sourceEl.width(); // hack to force IE8 to compute correct bounding box + el = this.el = this.sourceEl.clone() + .css({ + position: 'absolute', + visibility: '', // in case original element was hidden (commonly through hideEvents()) + display: this.isHidden ? 'none' : '', // for when initially hidden + margin: 0, + right: 'auto', // erase and set width instead + bottom: 'auto', // erase and set height instead + width: this.sourceEl.width(), // explicit height in case there was a 'right' value + height: this.sourceEl.height(), // explicit width in case there was a 'bottom' value + opacity: this.options.opacity || '', + zIndex: this.options.zIndex + }) + .appendTo(this.parentEl); + } + + return el; + }, + + + // Removes the tracking element if it has already been created + destroyEl: function() { + if (this.el) { + this.el.remove(); + this.el = null; + } + }, + + + // Update the CSS position of the tracking element + updatePosition: function() { + var sourceOffset; + var origin; + + this.getEl(); // ensure this.el + + // make sure origin info was computed + if (this.top0 === null) { + this.sourceEl.width(); // hack to force IE8 to compute correct bounding box + sourceOffset = this.sourceEl.offset(); + origin = this.el.offsetParent().offset(); + this.top0 = sourceOffset.top - origin.top; + this.left0 = sourceOffset.left - origin.left; + } + + this.el.css({ + top: this.top0 + this.topDelta, + left: this.left0 + this.leftDelta + }); + }, + + + // Gets called when the user moves the mouse + mousemove: function(ev) { + this.topDelta = ev.pageY - this.mouseY0; + this.leftDelta = ev.pageX - this.mouseX0; + + if (!this.isHidden) { + this.updatePosition(); + } + }, + + + // Temporarily makes the tracking element invisible. Can be called before following starts + hide: function() { + if (!this.isHidden) { + this.isHidden = true; + if (this.el) { + this.el.hide(); + } + } + }, + + + // Show the tracking element after it has been temporarily hidden + show: function() { + if (this.isHidden) { + this.isHidden = false; + this.updatePosition(); + this.getEl().show(); + } + } + +}; + +;; + +/* A utility class for rendering rows. +----------------------------------------------------------------------------------------------------------------------*/ +// It leverages methods of the subclass and the View to determine custom rendering behavior for each row "type" +// (such as highlight rows, day rows, helper rows, etc). + +function RowRenderer(view) { + this.view = view; +} + + +RowRenderer.prototype = { + + view: null, // a View object + cellHtml: '', // plain default HTML used for a cell when no other is available + + + // Renders the HTML for a row, leveraging custom cell-HTML-renderers based on the `rowType`. + // Also applies the "intro" and "outro" cells, which are specified by the subclass and views. + // `row` is an optional row number. + rowHtml: function(rowType, row) { + var view = this.view; + var renderCell = this.getHtmlRenderer('cell', rowType); + var cellHtml = ''; + var col; + var date; + + row = row || 0; + + for (col = 0; col < view.colCnt; col++) { + date = view.cellToDate(row, col); + cellHtml += renderCell(row, col, date); + } + + cellHtml = this.bookendCells(cellHtml, rowType, row); // apply intro and outro + + return '' + cellHtml + ''; + }, + + + // Applies the "intro" and "outro" HTML to the given cells. + // Intro means the leftmost cell when the calendar is LTR and the rightmost cell when RTL. Vice-versa for outro. + // `cells` can be an HTML string of 's or a jQuery element + // `row` is an optional row number. + bookendCells: function(cells, rowType, row) { + var view = this.view; + var intro = this.getHtmlRenderer('intro', rowType)(row || 0); + var outro = this.getHtmlRenderer('outro', rowType)(row || 0); + var isRTL = view.opt('isRTL'); + var prependHtml = isRTL ? outro : intro; + var appendHtml = isRTL ? intro : outro; + + if (typeof cells === 'string') { + return prependHtml + cells + appendHtml; + } + else { // a jQuery element + return cells.prepend(prependHtml).append(appendHtml); + } + }, + + + // Returns an HTML-rendering function given a specific `rendererName` (like cell, intro, or outro) and a specific + // `rowType` (like day, eventSkeleton, helperSkeleton), which is optional. + // If a renderer for the specific rowType doesn't exist, it will fall back to a generic renderer. + // We will query the View object first for any custom rendering functions, then the methods of the subclass. + getHtmlRenderer: function(rendererName, rowType) { + var view = this.view; + var generalName; // like "cellHtml" + var specificName; // like "dayCellHtml". based on rowType + var provider; // either the View or the RowRenderer subclass, whichever provided the method + var renderer; + + generalName = rendererName + 'Html'; + if (rowType) { + specificName = rowType + capitaliseFirstLetter(rendererName) + 'Html'; + } + + if (specificName && (renderer = view[specificName])) { + provider = view; + } + else if (specificName && (renderer = this[specificName])) { + provider = this; + } + else if ((renderer = view[generalName])) { + provider = view; + } + else if ((renderer = this[generalName])) { + provider = this; + } + + if (typeof renderer === 'function') { + return function(row) { + return renderer.apply(provider, arguments) || ''; // use correct `this` and always return a string + }; + } + + // the rendered can be a plain string as well. if not specified, always an empty string. + return function() { + return renderer || ''; + }; + } + +}; + +;; + +/* An abstract class comprised of a "grid" of cells that each represent a specific datetime +----------------------------------------------------------------------------------------------------------------------*/ + +function Grid(view) { + RowRenderer.call(this, view); // call the super-constructor + this.coordMap = new GridCoordMap(this); +} + + +Grid.prototype = createObject(RowRenderer.prototype); // declare the super-class +$.extend(Grid.prototype, { + + el: null, // the containing element + coordMap: null, // a GridCoordMap that converts pixel values to datetimes + cellDuration: null, // a cell's duration. subclasses must assign this ASAP + + + // Renders the grid into the `el` element. + // Subclasses should override and call this super-method when done. + render: function() { + this.bindHandlers(); + }, + + + // Called when the grid's resources need to be cleaned up + destroy: function() { + // subclasses can implement + }, + + + /* Coordinates & Cells + ------------------------------------------------------------------------------------------------------------------*/ + + + // Populates the given empty arrays with the y and x coordinates of the cells + buildCoords: function(rows, cols) { + // subclasses must implement + }, + + + // Given a cell object, returns the date for that cell + getCellDate: function(cell) { + // subclasses must implement + }, + + + // Given a cell object, returns the element that represents the cell's whole-day + getCellDayEl: function(cell) { + // subclasses must implement + }, + + + // Converts a range with an inclusive `start` and an exclusive `end` into an array of segment objects + rangeToSegs: function(start, end) { + // subclasses must implement + }, + + + /* Handlers + ------------------------------------------------------------------------------------------------------------------*/ + + + // Attach handlers to `this.el`, using bubbling to listen to all ancestors. + // We don't need to undo any of this in a "destroy" method, because the view will simply remove `this.el` from the + // DOM and jQuery will be smart enough to garbage collect the handlers. + bindHandlers: function() { + var _this = this; + + this.el.on('mousedown', function(ev) { + if ( + !$(ev.target).is('.fc-event-container *, .fc-more') && // not an an event element, or "more.." link + !$(ev.target).closest('.fc-popover').length // not on a popover (like the "more.." events one) + ) { + _this.dayMousedown(ev); + } + }); + + this.bindSegHandlers(); // attach event-element-related handlers. in Grid.events.js + }, + + + // Process a mousedown on an element that represents a day. For day clicking and selecting. + dayMousedown: function(ev) { + var _this = this; + var view = this.view; + var isSelectable = view.opt('selectable'); + var dates = null; // the inclusive dates of the selection. will be null if no selection + var start; // the inclusive start of the selection + var end; // the *exclusive* end of the selection + var dayEl; + + // this listener tracks a mousedown on a day element, and a subsequent drag. + // if the drag ends on the same day, it is a 'dayClick'. + // if 'selectable' is enabled, this listener also detects selections. + var dragListener = new DragListener(this.coordMap, { + //distance: 5, // needs more work if we want dayClick to fire correctly + scroll: view.opt('dragScroll'), + dragStart: function() { + view.unselect(); // since we could be rendering a new selection, we want to clear any old one + }, + cellOver: function(cell, date) { + if (dragListener.origDate) { // click needs to have started on a cell + + dayEl = _this.getCellDayEl(cell); + + dates = [ date, dragListener.origDate ].sort(dateCompare); + start = dates[0]; + end = dates[1].clone().add(_this.cellDuration); + + if (isSelectable) { + _this.renderSelection(start, end); + } + } + }, + cellOut: function(cell, date) { + dates = null; + _this.destroySelection(); + }, + listenStop: function(ev) { + if (dates) { // started and ended on a cell? + if (dates[0].isSame(dates[1])) { + view.trigger('dayClick', dayEl[0], start, ev); + } + if (isSelectable) { + // the selection will already have been rendered. just report it + view.reportSelection(start, end, ev); + } + } + } + }); + + dragListener.mousedown(ev); // start listening, which will eventually initiate a dragStart + }, + + + /* Event Dragging + ------------------------------------------------------------------------------------------------------------------*/ + + + // Renders a visual indication of a event being dragged over the given date(s). + // `end` can be null, as well as `seg`. See View's documentation on renderDrag for more info. + // A returned value of `true` signals that a mock "helper" event has been rendered. + renderDrag: function(start, end, seg) { + // subclasses must implement + }, + + + // Unrenders a visual indication of an event being dragged + destroyDrag: function() { + // subclasses must implement + }, + + + /* Event Resizing + ------------------------------------------------------------------------------------------------------------------*/ + + + // Renders a visual indication of an event being resized. + // `start` and `end` are the updated dates of the event. `seg` is the original segment object involved in the drag. + renderResize: function(start, end, seg) { + // subclasses must implement + }, + + + // Unrenders a visual indication of an event being resized. + destroyResize: function() { + // subclasses must implement + }, + + + /* Event Helper + ------------------------------------------------------------------------------------------------------------------*/ + + + // Renders a mock event over the given date(s). + // `end` can be null, in which case the mock event that is rendered will have a null end time. + // `sourceSeg` is the internal segment object involved in the drag. If null, something external is dragging. + renderRangeHelper: function(start, end, sourceSeg) { + var view = this.view; + var fakeEvent; + + // compute the end time if forced to do so (this is what EventManager does) + if (!end && view.opt('forceEventDuration')) { + end = view.calendar.getDefaultEventEnd(!start.hasTime(), start); + } + + fakeEvent = sourceSeg ? createObject(sourceSeg.event) : {}; // mask the original event object if possible + fakeEvent.start = start; + fakeEvent.end = end; + fakeEvent.allDay = !(start.hasTime() || (end && end.hasTime())); // freshly compute allDay + + // this extra className will be useful for differentiating real events from mock events in CSS + fakeEvent.className = (fakeEvent.className || []).concat('fc-helper'); + + // if something external is being dragged in, don't render a resizer + if (!sourceSeg) { + fakeEvent.editable = false; + } + + this.renderHelper(fakeEvent, sourceSeg); // do the actual rendering + }, + + + // Renders a mock event + renderHelper: function(event, sourceSeg) { + // subclasses must implement + }, + + + // Unrenders a mock event + destroyHelper: function() { + // subclasses must implement + }, + + + /* Selection + ------------------------------------------------------------------------------------------------------------------*/ + + + // Renders a visual indication of a selection. Will highlight by default but can be overridden by subclasses. + renderSelection: function(start, end) { + this.renderHighlight(start, end); + }, + + + // Unrenders any visual indications of a selection. Will unrender a highlight by default. + destroySelection: function() { + this.destroyHighlight(); + }, + + + /* Highlight + ------------------------------------------------------------------------------------------------------------------*/ + + + // Puts visual emphasis on a certain date range + renderHighlight: function(start, end) { + // subclasses should implement + }, + + + // Removes visual emphasis on a date range + destroyHighlight: function() { + // subclasses should implement + }, + + + + /* Generic rendering utilities for subclasses + ------------------------------------------------------------------------------------------------------------------*/ + + + // Renders a day-of-week header row + headHtml: function() { + return '' + + '
' + + '' + + '' + + this.rowHtml('head') + // leverages RowRenderer + '' + + '
' + + '
'; + }, + + + // Used by the `headHtml` method, via RowRenderer, for rendering the HTML of a day-of-week header cell + headCellHtml: function(row, col, date) { + var view = this.view; + var calendar = view.calendar; + var colFormat = view.opt('columnFormat'); + + return '' + + '' + + htmlEscape(calendar.formatDate(date, colFormat)) + + ''; + }, + + + // Renders the HTML for a single-day background cell + bgCellHtml: function(row, col, date) { + var view = this.view; + var classes = this.getDayClasses(date); + + classes.unshift('fc-day', view.widgetContentClass); + + return ''; + }, + + + // Computes HTML classNames for a single-day cell + getDayClasses: function(date) { + var view = this.view; + var today = view.calendar.getNow().stripTime(); + var classes = [ 'fc-' + dayIDs[date.day()] ]; + + if ( + view.name === 'month' && + date.month() != view.intervalStart.month() + ) { + classes.push('fc-other-month'); + } + + if (date.isSame(today, 'day')) { + classes.push( + 'fc-today', + view.highlightStateClass + ); + } + else if (date < today) { + classes.push('fc-past'); + } + else { + classes.push('fc-future'); + } + + return classes; + } + +}); + +;; + +/* Event-rendering and event-interaction methods for the abstract Grid class +----------------------------------------------------------------------------------------------------------------------*/ + +$.extend(Grid.prototype, { + + mousedOverSeg: null, // the segment object the user's mouse is over. null if over nothing + isDraggingSeg: false, // is a segment being dragged? boolean + isResizingSeg: false, // is a segment being resized? boolean + + + // Renders the given events onto the grid + renderEvents: function(events) { + // subclasses must implement + }, + + + // Retrieves all rendered segment objects in this grid + getSegs: function() { + // subclasses must implement + }, + + + // Unrenders all events. Subclasses should implement, calling this super-method first. + destroyEvents: function() { + this.triggerSegMouseout(); // trigger an eventMouseout if user's mouse is over an event + }, + + + // Renders a `el` property for each seg, and only returns segments that successfully rendered + renderSegs: function(segs, disableResizing) { + var view = this.view; + var html = ''; + var renderedSegs = []; + var i; + + // build a large concatenation of event segment HTML + for (i = 0; i < segs.length; i++) { + html += this.renderSegHtml(segs[i], disableResizing); + } + + // Grab individual elements from the combined HTML string. Use each as the default rendering. + // Then, compute the 'el' for each segment. An el might be null if the eventRender callback returned false. + $(html).each(function(i, node) { + var seg = segs[i]; + var el = view.resolveEventEl(seg.event, $(node)); + if (el) { + el.data('fc-seg', seg); // used by handlers + seg.el = el; + renderedSegs.push(seg); + } + }); + + return renderedSegs; + }, + + + // Generates the HTML for the default rendering of a segment + renderSegHtml: function(seg, disableResizing) { + // subclasses must implement + }, + + + // Converts an array of event objects into an array of segment objects + eventsToSegs: function(events, intervalStart, intervalEnd) { + var _this = this; + + return $.map(events, function(event) { + return _this.eventToSegs(event, intervalStart, intervalEnd); // $.map flattens all returned arrays together + }); + }, + + + // Slices a single event into an array of event segments. + // When `intervalStart` and `intervalEnd` are specified, intersect the events with that interval. + // Otherwise, let the subclass decide how it wants to slice the segments over the grid. + eventToSegs: function(event, intervalStart, intervalEnd) { + var eventStart = event.start.clone().stripZone(); // normalize + var eventEnd = this.view.calendar.getEventEnd(event).stripZone(); // compute (if necessary) and normalize + var segs; + var i, seg; + + if (intervalStart && intervalEnd) { + seg = intersectionToSeg(eventStart, eventEnd, intervalStart, intervalEnd); + segs = seg ? [ seg ] : []; + } + else { + segs = this.rangeToSegs(eventStart, eventEnd); // defined by the subclass + } + + // assign extra event-related properties to the segment objects + for (i = 0; i < segs.length; i++) { + seg = segs[i]; + seg.event = event; + seg.eventStartMS = +eventStart; + seg.eventDurationMS = eventEnd - eventStart; + } + + return segs; + }, + + + /* Handlers + ------------------------------------------------------------------------------------------------------------------*/ + + + // Attaches event-element-related handlers to the container element and leverage bubbling + bindSegHandlers: function() { + var _this = this; + var view = this.view; + + $.each( + { + mouseenter: function(seg, ev) { + _this.triggerSegMouseover(seg, ev); + }, + mouseleave: function(seg, ev) { + _this.triggerSegMouseout(seg, ev); + }, + click: function(seg, ev) { + return view.trigger('eventClick', this, seg.event, ev); // can return `false` to cancel + }, + mousedown: function(seg, ev) { + if ($(ev.target).is('.fc-resizer') && view.isEventResizable(seg.event)) { + _this.segResizeMousedown(seg, ev); + } + else if (view.isEventDraggable(seg.event)) { + _this.segDragMousedown(seg, ev); + } + } + }, + function(name, func) { + // attach the handler to the container element and only listen for real event elements via bubbling + _this.el.on(name, '.fc-event-container > *', function(ev) { + var seg = $(this).data('fc-seg'); // grab segment data. put there by View::renderEvents + + // only call the handlers if there is not a drag/resize in progress + if (seg && !_this.isDraggingSeg && !_this.isResizingSeg) { + return func.call(this, seg, ev); // `this` will be the event element + } + }); + } + ); + }, + + + // Updates internal state and triggers handlers for when an event element is moused over + triggerSegMouseover: function(seg, ev) { + if (!this.mousedOverSeg) { + this.mousedOverSeg = seg; + this.view.trigger('eventMouseover', seg.el[0], seg.event, ev); + } + }, + + + // Updates internal state and triggers handlers for when an event element is moused out. + // Can be given no arguments, in which case it will mouseout the segment that was previously moused over. + triggerSegMouseout: function(seg, ev) { + ev = ev || {}; // if given no args, make a mock mouse event + + if (this.mousedOverSeg) { + seg = seg || this.mousedOverSeg; // if given no args, use the currently moused-over segment + this.mousedOverSeg = null; + this.view.trigger('eventMouseout', seg.el[0], seg.event, ev); + } + }, + + + /* Dragging + ------------------------------------------------------------------------------------------------------------------*/ + + + // Called when the user does a mousedown on an event, which might lead to dragging. + // Generic enough to work with any type of Grid. + segDragMousedown: function(seg, ev) { + var _this = this; + var view = this.view; + var el = seg.el; + var event = seg.event; + var newStart, newEnd; + + // A clone of the original element that will move with the mouse + var mouseFollower = new MouseFollower(seg.el, { + parentEl: view.el, + opacity: view.opt('dragOpacity'), + revertDuration: view.opt('dragRevertDuration'), + zIndex: 2 // one above the .fc-view + }); + + // Tracks mouse movement over the *view's* coordinate map. Allows dragging and dropping between subcomponents + // of the view. + var dragListener = new DragListener(view.coordMap, { + distance: 5, + scroll: view.opt('dragScroll'), + listenStart: function(ev) { + mouseFollower.hide(); // don't show until we know this is a real drag + mouseFollower.start(ev); + }, + dragStart: function(ev) { + _this.triggerSegMouseout(seg, ev); // ensure a mouseout on the manipulated event has been reported + _this.isDraggingSeg = true; + view.hideEvent(event); // hide all event segments. our mouseFollower will take over + view.trigger('eventDragStart', el[0], event, ev, {}); // last argument is jqui dummy + }, + cellOver: function(cell, date) { + var origDate = seg.cellDate || dragListener.origDate; + var res = _this.computeDraggedEventDates(seg, origDate, date); + newStart = res.start; + newEnd = res.end; + + if (view.renderDrag(newStart, newEnd, seg)) { // have the view render a visual indication + mouseFollower.hide(); // if the view is already using a mock event "helper", hide our own + } + else { + mouseFollower.show(); + } + }, + cellOut: function() { // called before mouse moves to a different cell OR moved out of all cells + newStart = null; + view.destroyDrag(); // unrender whatever was done in view.renderDrag + mouseFollower.show(); // show in case we are moving out of all cells + }, + dragStop: function(ev) { + var hasChanged = newStart && !newStart.isSame(event.start); + + // do revert animation if hasn't changed. calls a callback when finished (whether animation or not) + mouseFollower.stop(!hasChanged, function() { + _this.isDraggingSeg = false; + view.destroyDrag(); + view.showEvent(event); + view.trigger('eventDragStop', el[0], event, ev, {}); // last argument is jqui dummy + + if (hasChanged) { + view.eventDrop(el[0], event, newStart, ev); // will rerender all events... + } + }); + }, + listenStop: function() { + mouseFollower.stop(); // put in listenStop in case there was a mousedown but the drag never started + } + }); + + dragListener.mousedown(ev); // start listening, which will eventually lead to a dragStart + }, + + + // Given a segment, the dates where a drag began and ended, calculates the Event Object's new start and end dates + computeDraggedEventDates: function(seg, dragStartDate, dropDate) { + var view = this.view; + var event = seg.event; + var start = event.start; + var end = view.calendar.getEventEnd(event); + var delta; + var newStart; + var newEnd; + + if (dropDate.hasTime() === dragStartDate.hasTime()) { + delta = dayishDiff(dropDate, dragStartDate); + newStart = start.clone().add(delta); + if (event.end === null) { // do we need to compute an end? + newEnd = null; + } + else { + newEnd = end.clone().add(delta); + } + } + else { + // if switching from day <-> timed, start should be reset to the dropped date, and the end cleared + newStart = dropDate; + newEnd = null; // end should be cleared + } + + return { start: newStart, end: newEnd }; + }, + + + /* Resizing + ------------------------------------------------------------------------------------------------------------------*/ + + + // Called when the user does a mousedown on an event's resizer, which might lead to resizing. + // Generic enough to work with any type of Grid. + segResizeMousedown: function(seg, ev) { + var _this = this; + var view = this.view; + var el = seg.el; + var event = seg.event; + var start = event.start; + var end = view.calendar.getEventEnd(event); + var newEnd = null; + var dragListener; + + function destroy() { // resets the rendering + _this.destroyResize(); + view.showEvent(event); + } + + // Tracks mouse movement over the *grid's* coordinate map + dragListener = new DragListener(this.coordMap, { + distance: 5, + scroll: view.opt('dragScroll'), + dragStart: function(ev) { + _this.triggerSegMouseout(seg, ev); // ensure a mouseout on the manipulated event has been reported + _this.isResizingSeg = true; + view.trigger('eventResizeStart', el[0], event, ev, {}); // last argument is jqui dummy + }, + cellOver: function(cell, date) { + // compute the new end. don't allow it to go before the event's start + if (date.isBefore(start)) { // allows comparing ambig to non-ambig + date = start; + } + newEnd = date.clone().add(_this.cellDuration); // make it an exclusive end + + if (newEnd.isSame(end)) { + newEnd = null; + destroy(); + } + else { + _this.renderResize(start, newEnd, seg); + view.hideEvent(event); + } + }, + cellOut: function() { // called before mouse moves to a different cell OR moved out of all cells + newEnd = null; + destroy(); + }, + dragStop: function(ev) { + _this.isResizingSeg = false; + destroy(); + view.trigger('eventResizeStop', el[0], event, ev, {}); // last argument is jqui dummy + + if (newEnd) { + view.eventResize(el[0], event, newEnd, ev); // will rerender all events... + } + } + }); + + dragListener.mousedown(ev); // start listening, which will eventually lead to a dragStart + }, + + + /* Rendering Utils + ------------------------------------------------------------------------------------------------------------------*/ + + + // Generic utility for generating the HTML classNames for an event segment's element + getSegClasses: function(seg, isDraggable, isResizable) { + var event = seg.event; + var classes = [ + 'fc-event', + seg.isStart ? 'fc-start' : 'fc-not-start', + seg.isEnd ? 'fc-end' : 'fc-not-end' + ].concat( + event.className, + event.source ? event.source.className : [] + ); + + if (isDraggable) { + classes.push('fc-draggable'); + } + if (isResizable) { + classes.push('fc-resizable'); + } + + return classes; + }, + + + // Utility for generating a CSS string with all the event skin-related properties + getEventSkinCss: function(event) { + var view = this.view; + var source = event.source || {}; + var eventColor = event.color; + var sourceColor = source.color; + var optionColor = view.opt('eventColor'); + var backgroundColor = + event.backgroundColor || + eventColor || + source.backgroundColor || + sourceColor || + view.opt('eventBackgroundColor') || + optionColor; + var borderColor = + event.borderColor || + eventColor || + source.borderColor || + sourceColor || + view.opt('eventBorderColor') || + optionColor; + var textColor = + event.textColor || + source.textColor || + view.opt('eventTextColor'); + var statements = []; + if (backgroundColor) { + statements.push('background-color:' + backgroundColor); + } + if (borderColor) { + statements.push('border-color:' + borderColor); + } + if (textColor) { + statements.push('color:' + textColor); + } + return statements.join(';'); + } + +}); + + +/* Event Segment Utilities +----------------------------------------------------------------------------------------------------------------------*/ + + +// A cmp function for determining which segments should take visual priority +function compareSegs(seg1, seg2) { + return seg1.eventStartMS - seg2.eventStartMS || // earlier events go first + seg2.eventDurationMS - seg1.eventDurationMS || // tie? longer events go first + seg2.event.allDay - seg1.event.allDay || // tie? put all-day events first (booleans cast to 0/1) + (seg1.event.title || '').localeCompare(seg2.event.title); // tie? alphabetically by title +} + + +;; + +/* A component that renders a grid of whole-days that runs horizontally. There can be multiple rows, one per week. +----------------------------------------------------------------------------------------------------------------------*/ + +function DayGrid(view) { + Grid.call(this, view); // call the super-constructor +} + + +DayGrid.prototype = createObject(Grid.prototype); // declare the super-class +$.extend(DayGrid.prototype, { + + numbersVisible: false, // should render a row for day/week numbers? manually set by the view + cellDuration: moment.duration({ days: 1 }), // required for Grid.event.js. Each cell is always a single day + bottomCoordPadding: 0, // hack for extending the hit area for the last row of the coordinate grid + + rowEls: null, // set of fake row elements + dayEls: null, // set of whole-day elements comprising the row's background + helperEls: null, // set of cell skeleton elements for rendering the mock event "helper" + highlightEls: null, // set of cell skeleton elements for rendering the highlight + + + // Renders the rows and columns into the component's `this.el`, which should already be assigned. + // isRigid determins whether the individual rows should ignore the contents and be a constant height. + // Relies on the view's colCnt and rowCnt. In the future, this component should probably be self-sufficient. + render: function(isRigid) { + var view = this.view; + var html = ''; + var row; + + for (row = 0; row < view.rowCnt; row++) { + html += this.dayRowHtml(row, isRigid); + } + this.el.html(html); + + this.rowEls = this.el.find('.fc-row'); + this.dayEls = this.el.find('.fc-day'); + + // run all the day cells through the dayRender callback + this.dayEls.each(function(i, node) { + var date = view.cellToDate(Math.floor(i / view.colCnt), i % view.colCnt); + view.trigger('dayRender', null, date, $(node)); + }); + + Grid.prototype.render.call(this); // call the super-method + }, + + + destroy: function() { + this.destroySegPopover(); + }, + + + // Generates the HTML for a single row. `row` is the row number. + dayRowHtml: function(row, isRigid) { + var view = this.view; + var classes = [ 'fc-row', 'fc-week', view.widgetContentClass ]; + + if (isRigid) { + classes.push('fc-rigid'); + } + + return '' + + '
' + + '
' + + '' + + this.rowHtml('day', row) + // leverages RowRenderer. calls dayCellHtml() + '
' + + '
' + + '
' + + '' + + (this.numbersVisible ? + '' + + this.rowHtml('number', row) + // leverages RowRenderer. View will define render method + '' : + '' + ) + + '
' + + '
' + + '
'; + }, + + + // Renders the HTML for a whole-day cell. Will eventually end up in the day-row's background. + // We go through a 'day' row type instead of just doing a 'bg' row type so that the View can do custom rendering + // specifically for whole-day rows, whereas a 'bg' might also be used for other purposes (TimeGrid bg for example). + dayCellHtml: function(row, col, date) { + return this.bgCellHtml(row, col, date); + }, + + + /* Coordinates & Cells + ------------------------------------------------------------------------------------------------------------------*/ + + + // Populates the empty `rows` and `cols` arrays with coordinates of the cells. For CoordGrid. + buildCoords: function(rows, cols) { + var colCnt = this.view.colCnt; + var e, n, p; + + this.dayEls.slice(0, colCnt).each(function(i, _e) { // iterate the first row of day elements + e = $(_e); + n = e.offset().left; + if (i) { + p[1] = n; + } + p = [ n ]; + cols[i] = p; + }); + p[1] = n + e.outerWidth(); + + this.rowEls.each(function(i, _e) { + e = $(_e); + n = e.offset().top; + if (i) { + p[1] = n; + } + p = [ n ]; + rows[i] = p; + }); + p[1] = n + e.outerHeight() + this.bottomCoordPadding; // hack to extend hit area of last row + }, + + + // Converts a cell to a date + getCellDate: function(cell) { + return this.view.cellToDate(cell); // leverages the View's cell system + }, + + + // Gets the whole-day element associated with the cell + getCellDayEl: function(cell) { + return this.dayEls.eq(cell.row * this.view.colCnt + cell.col); + }, + + + // Converts a range with an inclusive `start` and an exclusive `end` into an array of segment objects + rangeToSegs: function(start, end) { + return this.view.rangeToSegments(start, end); // leverages the View's cell system + }, + + + /* Event Drag Visualization + ------------------------------------------------------------------------------------------------------------------*/ + + + // Renders a visual indication of an event hovering over the given date(s). + // `end` can be null, as well as `seg`. See View's documentation on renderDrag for more info. + // A returned value of `true` signals that a mock "helper" event has been rendered. + renderDrag: function(start, end, seg) { + var opacity; + + // always render a highlight underneath + this.renderHighlight( + start, + end || this.view.calendar.getDefaultEventEnd(true, start) + ); + + // if a segment from the same calendar but another component is being dragged, render a helper event + if (seg && !seg.el.closest(this.el).length) { + + this.renderRangeHelper(start, end, seg); + + opacity = this.view.opt('dragOpacity'); + if (opacity !== undefined) { + this.helperEls.css('opacity', opacity); + } + + return true; // a helper has been rendered + } + }, + + + // Unrenders any visual indication of a hovering event + destroyDrag: function() { + this.destroyHighlight(); + this.destroyHelper(); + }, + + + /* Event Resize Visualization + ------------------------------------------------------------------------------------------------------------------*/ + + + // Renders a visual indication of an event being resized + renderResize: function(start, end, seg) { + this.renderHighlight(start, end); + this.renderRangeHelper(start, end, seg); + }, + + + // Unrenders a visual indication of an event being resized + destroyResize: function() { + this.destroyHighlight(); + this.destroyHelper(); + }, + + + /* Event Helper + ------------------------------------------------------------------------------------------------------------------*/ + + + // Renders a mock "helper" event. `sourceSeg` is the associated internal segment object. It can be null. + renderHelper: function(event, sourceSeg) { + var helperNodes = []; + var rowStructs = this.renderEventRows([ event ]); + + // inject each new event skeleton into each associated row + this.rowEls.each(function(row, rowNode) { + var rowEl = $(rowNode); // the .fc-row + var skeletonEl = $('
'); // will be absolutely positioned + var skeletonTop; + + // If there is an original segment, match the top position. Otherwise, put it at the row's top level + if (sourceSeg && sourceSeg.row === row) { + skeletonTop = sourceSeg.el.position().top; + } + else { + skeletonTop = rowEl.find('.fc-content-skeleton tbody').position().top; + } + + skeletonEl.css('top', skeletonTop) + .find('table') + .append(rowStructs[row].tbodyEl); + + rowEl.append(skeletonEl); + helperNodes.push(skeletonEl[0]); + }); + + this.helperEls = $(helperNodes); // array -> jQuery set + }, + + + // Unrenders any visual indication of a mock helper event + destroyHelper: function() { + if (this.helperEls) { + this.helperEls.remove(); + this.helperEls = null; + } + }, + + + /* Highlighting + ------------------------------------------------------------------------------------------------------------------*/ + + + // Renders an emphasis on the given date range. `start` is an inclusive, `end` is exclusive. + renderHighlight: function(start, end) { + var segs = this.rangeToSegs(start, end); + var highlightNodes = []; + var i, seg; + var el; + + // build an event skeleton for each row that needs it + for (i = 0; i < segs.length; i++) { + seg = segs[i]; + el = $( + this.highlightSkeletonHtml(seg.leftCol, seg.rightCol + 1) // make end exclusive + ); + el.appendTo(this.rowEls[seg.row]); + highlightNodes.push(el[0]); + } + + this.highlightEls = $(highlightNodes); // array -> jQuery set + }, + + + // Unrenders any visual emphasis on a date range + destroyHighlight: function() { + if (this.highlightEls) { + this.highlightEls.remove(); + this.highlightEls = null; + } + }, + + + // Generates the HTML used to build a single-row "highlight skeleton", a table that frames highlight cells + highlightSkeletonHtml: function(startCol, endCol) { + var colCnt = this.view.colCnt; + var cellHtml = ''; + + if (startCol > 0) { + cellHtml += ' elements that should be appended to each row's content skeleton. + // Returns an array of rowStruct objects (see the bottom of `renderEventRow`). + renderEventRows: function(events) { + var segs = this.eventsToSegs(events); + var rowStructs = []; + var segRows; + var row; + + segs = this.renderSegs(segs); // returns a new array with only visible segments + segRows = this.groupSegRows(segs); // group into nested arrays + + // iterate each row of segment groupings + for (row = 0; row < segRows.length; row++) { + rowStructs.push( + this.renderEventRow(row, segRows[row]) + ); + } + + return rowStructs; + }, + + + // Builds the HTML to be used for the default element for an individual segment + renderSegHtml: function(seg, disableResizing) { + var view = this.view; + var isRTL = view.opt('isRTL'); + var event = seg.event; + var isDraggable = view.isEventDraggable(event); + var isResizable = !disableResizing && event.allDay && seg.isEnd && view.isEventResizable(event); + var classes = this.getSegClasses(seg, isDraggable, isResizable); + var skinCss = this.getEventSkinCss(event); + var timeHtml = ''; + var titleHtml; + + classes.unshift('fc-day-grid-event'); + + // Only display a timed events time if it is the starting segment + if (!event.allDay && seg.isStart) { + timeHtml = '' + htmlEscape(view.getEventTimeText(event)) + ''; + } + + titleHtml = + '' + + (htmlEscape(event.title || '') || ' ') + // we always want one line of height + ''; + + return '' + + '
' + + (isRTL ? + titleHtml + ' ' + timeHtml : // put a natural space in between + timeHtml + ' ' + titleHtml // + ) + + '
' + + (isResizable ? + '
' : + '' + ) + + ''; + }, + + + // Given a row # and an array of segments all in the same row, render a
element, a skeleton that contains + // the segments. Returns object with a bunch of internal data about how the render was calculated. + renderEventRow: function(row, rowSegs) { + var view = this.view; + var colCnt = view.colCnt; + var segLevels = this.buildSegLevels(rowSegs); // group into sub-arrays of levels + var levelCnt = Math.max(1, segLevels.length); // ensure at least one level + var tbody = $(''); + var segMatrix = []; // lookup for which segments are rendered into which level+col cells + var cellMatrix = []; // lookup for all '); + + segMatrix.push([]); + cellMatrix.push([]); + loneCellMatrix.push([]); + + // levelCnt might be 1 even though there are no actual levels. protect against this. + // this single empty row is useful for styling. + if (levelSegs) { + for (j = 0; j < levelSegs.length; j++) { // iterate through segments in level + seg = levelSegs[j]; + + emptyCellsUntil(seg.leftCol); + + // create a container that occupies or more columns. append the event element. + td = $(' at a time and stop when we find one out of bounds + for (i = 0; i < trEls.length; i++) { + trEl = trEls.eq(i).removeClass('fc-limited'); // get and reveal + if (trEl.position().top + trEl.outerHeight() > rowHeight) { + return i; + } + } + + return false; // should not limit at all + }, + + + // Limits the given grid row to the maximum number of levels and injects "more" links if necessary. + // `row` is the row number. + // `levelLimit` is a number for the maximum (inclusive) number of levels allowed. + limitRow: function(row, levelLimit) { + var _this = this; + var view = this.view; + var rowStruct = this.rowStructs[row]; + var moreNodes = []; // array of "more" links and and segment elements past the limit + .addClass('fc-limited').get(); // hide elements and get a simple DOM-nodes array + + // iterate though segments in the last allowable level + for (i = 0; i < levelSegs.length; i++) { + seg = levelSegs[i]; + emptyCellsUntil(seg.leftCol); // process empty cells before the segment + + // determine *all* segments below `seg` that occupy the same columns + colSegsBelow = []; + totalSegsBelow = 0; + while (col <= seg.rightCol) { + cell = { row: row, col: col }; + segsBelow = this.getCellSegs(cell, levelLimit); + colSegsBelow.push(segsBelow); + totalSegsBelow += segsBelow.length; + col++; + } + + if (totalSegsBelow) { // do we need to replace this segment with one or many "more" links? + td = cellMatrix[levelLimit - 1][seg.leftCol]; // the segment's parent cell + rowspan = td.attr('rowspan') || 1; + segMoreNodes = []; + + // make a replacement '; + + html += + '' + + (!isRTL ? axisHtml : '') + + '"; + + slotTime.add(this.slotDuration); + } + + return html; + }, + + + // Parses various options into properties of this object + processOptions: function() { + var view = this.view; + var slotDuration = view.opt('slotDuration'); + var snapDuration = view.opt('snapDuration'); + + slotDuration = moment.duration(slotDuration); + snapDuration = snapDuration ? moment.duration(snapDuration) : slotDuration; + + this.slotDuration = slotDuration; + this.snapDuration = snapDuration; + this.cellDuration = snapDuration; // important to assign this for Grid.events.js + + this.minTime = moment.duration(view.opt('minTime')); + this.maxTime = moment.duration(view.opt('maxTime')); + }, + + + // Slices up a date range into a segment for each column + rangeToSegs: function(rangeStart, rangeEnd) { + var view = this.view; + var segs = []; + var seg; + var col; + var cellDate; + var colStart, colEnd; + + // normalize + rangeStart = rangeStart.clone().stripZone(); + rangeEnd = rangeEnd.clone().stripZone(); + + for (col = 0; col < view.colCnt; col++) { + cellDate = view.cellToDate(0, col); // use the View's cell system for this + colStart = cellDate.clone().time(this.minTime); + colEnd = cellDate.clone().time(this.maxTime); + seg = intersectionToSeg(rangeStart, rangeEnd, colStart, colEnd); + if (seg) { + seg.col = col; + segs.push(seg); + } + } + + return segs; + }, + + + /* Coordinates + ------------------------------------------------------------------------------------------------------------------*/ + + + // Called when there is a window resize/zoom and we need to recalculate coordinates for the grid + resize: function() { + this.computeSlatTops(); + this.updateSegVerticals(); + }, + + + // Populates the given empty `rows` and `cols` arrays with offset positions of the "snap" cells. + // "Snap" cells are different the slots because they might have finer granularity. + buildCoords: function(rows, cols) { + var colCnt = this.view.colCnt; + var originTop = this.el.offset().top; + var snapTime = moment.duration(+this.minTime); + var p = null; + var e, n; + + this.dayEls.slice(0, colCnt).each(function(i, _e) { + e = $(_e); + n = e.offset().left; + if (p) { + p[1] = n; + } + p = [ n ]; + cols[i] = p; + }); + p[1] = n + e.outerWidth(); + + p = null; + while (snapTime < this.maxTime) { + n = originTop + this.computeTimeTop(snapTime); + if (p) { + p[1] = n; + } + p = [ n ]; + rows.push(p); + snapTime.add(this.snapDuration); + } + p[1] = originTop + this.computeTimeTop(snapTime); // the position of the exclusive end + }, + + + // Gets the datetime for the given slot cell + getCellDate: function(cell) { + var view = this.view; + var calendar = view.calendar; + + return calendar.rezoneDate( // since we are adding a time, it needs to be in the calendar's timezone + view.cellToDate(0, cell.col) // View's coord system only accounts for start-of-day for column + .time(this.minTime + this.snapDuration * cell.row) + ); + }, + + + // Gets the element that represents the whole-day the cell resides on + getCellDayEl: function(cell) { + return this.dayEls.eq(cell.col); + }, + + + // Computes the top coordinate, relative to the bounds of the grid, of the given date. + // A `startOfDayDate` must be given for avoiding ambiguity over how to treat midnight. + computeDateTop: function(date, startOfDayDate) { + return this.computeTimeTop( + moment.duration( + date.clone().stripZone() - startOfDayDate.clone().stripTime() + ) + ); + }, + + + // Computes the top coordinate, relative to the bounds of the grid, of the given time (a Duration). + computeTimeTop: function(time) { + var slatCoverage = (time - this.minTime) / this.slotDuration; // floating-point value of # of slots covered + var slatIndex; + var slatRemainder; + var slatTop; + var slatBottom; + + // constrain. because minTime/maxTime might be customized + slatCoverage = Math.max(0, slatCoverage); + slatCoverage = Math.min(this.slatEls.length, slatCoverage); + + slatIndex = Math.floor(slatCoverage); // an integer index of the furthest whole slot + slatRemainder = slatCoverage - slatIndex; + slatTop = this.slatTops[slatIndex]; // the top position of the furthest whole slot + + if (slatRemainder) { // time spans part-way into the slot + slatBottom = this.slatTops[slatIndex + 1]; + return slatTop + (slatBottom - slatTop) * slatRemainder; // part-way between slots + } + else { + return slatTop; + } + }, + + + // Queries each `slatEl` for its position relative to the grid's container and stores it in `slatTops`. + // Includes the the bottom of the last slat as the last item in the array. + computeSlatTops: function() { + var tops = []; + var top; + + this.slatEls.each(function(i, node) { + top = $(node).position().top; + tops.push(top); + }); + + tops.push(top + this.slatEls.last().outerHeight()); // bottom of the last slat + + this.slatTops = tops; + }, + + + /* Event Drag Visualization + ------------------------------------------------------------------------------------------------------------------*/ + + + // Renders a visual indication of an event being dragged over the specified date(s). + // `end` and `seg` can be null. See View's documentation on renderDrag for more info. + renderDrag: function(start, end, seg) { + var opacity; + + if (seg) { // if there is event information for this drag, render a helper event + this.renderRangeHelper(start, end, seg); + + opacity = this.view.opt('dragOpacity'); + if (opacity !== undefined) { + this.helperEl.css('opacity', opacity); + } + + return true; // signal that a helper has been rendered + } + else { + // otherwise, just render a highlight + this.renderHighlight( + start, + end || this.view.calendar.getDefaultEventEnd(false, start) + ); + } + }, + + + // Unrenders any visual indication of an event being dragged + destroyDrag: function() { + this.destroyHelper(); + this.destroyHighlight(); + }, + + + /* Event Resize Visualization + ------------------------------------------------------------------------------------------------------------------*/ + + + // Renders a visual indication of an event being resized + renderResize: function(start, end, seg) { + this.renderRangeHelper(start, end, seg); + }, + + + // Unrenders any visual indication of an event being resized + destroyResize: function() { + this.destroyHelper(); + }, + + + /* Event Helper + ------------------------------------------------------------------------------------------------------------------*/ + + + // Renders a mock "helper" event. `sourceSeg` is the original segment object and might be null (an external drag) + renderHelper: function(event, sourceSeg) { + var res = this.renderEventTable([ event ]); + var tableEl = res.tableEl; + var segs = res.segs; + var i, seg; + var sourceEl; + + // Try to make the segment that is in the same row as sourceSeg look the same + for (i = 0; i < segs.length; i++) { + seg = segs[i]; + if (sourceSeg && sourceSeg.col === seg.col) { + sourceEl = sourceSeg.el; + seg.el.css({ + left: sourceEl.css('left'), + right: sourceEl.css('right'), + 'margin-left': sourceEl.css('margin-left'), + 'margin-right': sourceEl.css('margin-right') + }); + } + } + + this.helperEl = $('
') + .append(tableEl) + .appendTo(this.el); + }, + + + // Unrenders any mock helper event + destroyHelper: function() { + if (this.helperEl) { + this.helperEl.remove(); + this.helperEl = null; + } + }, + + + /* Selection + ------------------------------------------------------------------------------------------------------------------*/ + + + // Renders a visual indication of a selection. Overrides the default, which was to simply render a highlight. + renderSelection: function(start, end) { + if (this.view.opt('selectHelper')) { // this setting signals that a mock helper event should be rendered + this.renderRangeHelper(start, end); + } + else { + this.renderHighlight(start, end); + } + }, + + + // Unrenders any visual indication of a selection + destroySelection: function() { + this.destroyHelper(); + this.destroyHighlight(); + }, + + + /* Highlight + ------------------------------------------------------------------------------------------------------------------*/ + + + // Renders an emphasis on the given date range. `start` is inclusive. `end` is exclusive. + renderHighlight: function(start, end) { + this.highlightEl = $( + this.highlightSkeletonHtml(start, end) + ).appendTo(this.el); + }, + + + // Unrenders the emphasis on a date range + destroyHighlight: function() { + if (this.highlightEl) { + this.highlightEl.remove(); + this.highlightEl = null; + } + }, + + + // Generates HTML for a table element with containers in each column, responsible for absolutely positioning the + // highlight elements to cover the highlighted slots. + highlightSkeletonHtml: function(start, end) { + var view = this.view; + var segs = this.rangeToSegs(start, end); + var cellHtml = ''; + var col = 0; + var i, seg; + var dayDate; + var top, bottom; + + for (i = 0; i < segs.length; i++) { // loop through the segments. one per column + seg = segs[i]; + + // need empty cells beforehand? + if (col < seg.col) { + cellHtml += '
'; + + col++; + } + + // need empty cells after the last segment? + if (col < view.colCnt) { + cellHtml += ''; + } + }, + + + // Generates the HTML that will go before content-skeleton cells that display the day/week numbers. + // Queried by the DayGrid subcomponent. Ordering depends on isRTL. + numberIntroHtml: function(row) { + if (this.weekNumbersVisible) { + return '' + + ''; + } + }, + + + // Generates the HTML that goes before the day bg cells for each day-row. + // Queried by the DayGrid subcomponent. Ordering depends on isRTL. + dayIntroHtml: function() { + if (this.weekNumbersVisible) { + return ''; + } + }, + + + // Generates the HTML that goes before every other type of row generated by DayGrid. Ordering depends on isRTL. + // Affects helper-skeleton and highlight-skeleton rows. + introHtml: function() { + if (this.weekNumbersVisible) { + return ''; + } + }, + + + // Generates the HTML for the '; + }, + + + // Generates an HTML attribute string for setting the width of the week number column, if it is known + weekNumberStyleAttr: function() { + if (this.weekNumberWidth !== null) { + return 'style="width:' + this.weekNumberWidth + 'px"'; + } + return ''; + }, + + + // Determines whether each row should have a constant height + hasRigidRows: function() { + var eventLimit = this.opt('eventLimit'); + return eventLimit && typeof eventLimit !== 'number'; + }, + + + /* Dimensions + ------------------------------------------------------------------------------------------------------------------*/ + + + // Refreshes the horizontal dimensions of the view + updateWidth: function() { + if (this.weekNumbersVisible) { + // Make sure all week number cells running down the side have the same width. + // Record the width for cells created later. + this.weekNumberWidth = matchCellWidths( + this.el.find('.fc-week-number') + ); + } + }, + + + // Adjusts the vertical dimensions of the view to the specified values + setHeight: function(totalHeight, isAuto) { + var eventLimit = this.opt('eventLimit'); + var scrollerHeight; + + // reset all heights to be natural + unsetScroller(this.scrollerEl); + uncompensateScroll(this.headRowEl); + + this.dayGrid.destroySegPopover(); // kill the "more" popover if displayed + + // is the event limit a constant level number? + if (eventLimit && typeof eventLimit === 'number') { + this.dayGrid.limitRows(eventLimit); // limit the levels first so the height can redistribute after + } + + scrollerHeight = this.computeScrollerHeight(totalHeight); + this.setGridHeight(scrollerHeight, isAuto); + + // is the event limit dynamically calculated? + if (eventLimit && typeof eventLimit !== 'number') { + this.dayGrid.limitRows(eventLimit); // limit the levels after the grid's row heights have been set + } + + if (!isAuto && setPotentialScroller(this.scrollerEl, scrollerHeight)) { // using scrollbars? + + compensateScroll(this.headRowEl, getScrollbarWidths(this.scrollerEl)); + + // doing the scrollbar compensation might have created text overflow which created more height. redo + scrollerHeight = this.computeScrollerHeight(totalHeight); + this.scrollerEl.height(scrollerHeight); + + this.restoreScroll(); + } + }, + + + // Sets the height of just the DayGrid component in this view + setGridHeight: function(height, isAuto) { + if (isAuto) { + undistributeHeight(this.dayGrid.rowEls); // let the rows be their natural height with no expanding + } + else { + distributeHeight(this.dayGrid.rowEls, height, true); // true = compensate for height-hogging rows + } + }, + + + /* Events + ------------------------------------------------------------------------------------------------------------------*/ + + + // Renders the given events onto the view and populates the segments array + renderEvents: function(events) { + this.dayGrid.renderEvents(events); + + this.updateHeight(); // must compensate for events that overflow the row + + View.prototype.renderEvents.call(this, events); // call the super-method + }, + + + // Retrieves all segment objects that are rendered in the view + getSegs: function() { + return this.dayGrid.getSegs(); + }, + + + // Unrenders all event elements and clears internal segment data + destroyEvents: function() { + View.prototype.destroyEvents.call(this); // do this before dayGrid's segs have been cleared + + this.recordScroll(); // removing events will reduce height and mess with the scroll, so record beforehand + this.dayGrid.destroyEvents(); + + // we DON'T need to call updateHeight() because: + // A) a renderEvents() call always happens after this, which will eventually call updateHeight() + // B) in IE8, this causes a flash whenever events are rerendered + }, + + + /* Event Dragging + ------------------------------------------------------------------------------------------------------------------*/ + + + // Renders a visual indication of an event being dragged over the view. + // A returned value of `true` signals that a mock "helper" event has been rendered. + renderDrag: function(start, end, seg) { + return this.dayGrid.renderDrag(start, end, seg); + }, + + + // Unrenders the visual indication of an event being dragged over the view + destroyDrag: function() { + this.dayGrid.destroyDrag(); + }, + + + /* Selection + ------------------------------------------------------------------------------------------------------------------*/ + + + // Renders a visual indication of a selection + renderSelection: function(start, end) { + this.dayGrid.renderSelection(start, end); + }, + + + // Unrenders a visual indications of a selection + destroySelection: function() { + this.dayGrid.destroySelection(); + } + +}); + +;; + +/* A month view with day cells running in rows (one-per-week) and columns +----------------------------------------------------------------------------------------------------------------------*/ + +setDefaults({ + fixedWeekCount: true +}); + +fcViews.month = MonthView; // register the view + +function MonthView(calendar) { + BasicView.call(this, calendar); // call the super-constructor +} + + +MonthView.prototype = createObject(BasicView.prototype); // define the super-class +$.extend(MonthView.prototype, { + + name: 'month', + + + incrementDate: function(date, delta) { + return date.clone().stripTime().add(delta, 'months').startOf('month'); + }, + + + render: function(date) { + var rowCnt; + + this.intervalStart = date.clone().stripTime().startOf('month'); + this.intervalEnd = this.intervalStart.clone().add(1, 'months'); + + this.start = this.intervalStart.clone(); + this.start = this.skipHiddenDays(this.start); // move past the first week if no visible days + this.start.startOf('week'); + this.start = this.skipHiddenDays(this.start); // move past the first invisible days of the week + + this.end = this.intervalEnd.clone(); + this.end = this.skipHiddenDays(this.end, -1, true); // move in from the last week if no visible days + this.end.add((7 - this.end.weekday()) % 7, 'days'); // move to end of week if not already + this.end = this.skipHiddenDays(this.end, -1, true); // move in from the last invisible days of the week + + rowCnt = Math.ceil( // need to ceil in case there are hidden days + this.end.diff(this.start, 'weeks', true) // returnfloat=true + ); + if (this.isFixedWeeks()) { + this.end.add(6 - rowCnt, 'weeks'); + rowCnt = 6; + } + + this.title = this.calendar.formatDate(this.intervalStart, this.opt('titleFormat')); + + BasicView.prototype.render.call(this, rowCnt, this.getCellsPerWeek(), true); // call the super-method + }, + + + // Overrides the default BasicView behavior to have special multi-week auto-height logic + setGridHeight: function(height, isAuto) { + + isAuto = isAuto || this.opt('weekMode') === 'variable'; // LEGACY: weekMode is deprecated + + // if auto, make the height of each row the height that it would be if there were 6 weeks + if (isAuto) { + height *= this.rowCnt / 6; + } + + distributeHeight(this.dayGrid.rowEls, height, !isAuto); // if auto, don't compensate for height-hogging rows + }, + + + isFixedWeeks: function() { + var weekMode = this.opt('weekMode'); // LEGACY: weekMode is deprecated + if (weekMode) { + return weekMode === 'fixed'; // if any other type of weekMode, assume NOT fixed + } + + return this.opt('fixedWeekCount'); + } + +}); + +;; + +/* A week view with simple day cells running horizontally +----------------------------------------------------------------------------------------------------------------------*/ +// TODO: a WeekView mixin for calculating dates and titles + +fcViews.basicWeek = BasicWeekView; // register this view + +function BasicWeekView(calendar) { + BasicView.call(this, calendar); // call the super-constructor +} + + +BasicWeekView.prototype = createObject(BasicView.prototype); // define the super-class +$.extend(BasicWeekView.prototype, { + + name: 'basicWeek', + + + incrementDate: function(date, delta) { + return date.clone().stripTime().add(delta, 'weeks').startOf('week'); + }, + + + render: function(date) { + + this.intervalStart = date.clone().stripTime().startOf('week'); + this.intervalEnd = this.intervalStart.clone().add(1, 'weeks'); + + this.start = this.skipHiddenDays(this.intervalStart); + this.end = this.skipHiddenDays(this.intervalEnd, -1, true); + + this.title = this.calendar.formatRange( + this.start, + this.end.clone().subtract(1), // make inclusive by subtracting 1 ms + this.opt('titleFormat'), + ' \u2014 ' // emphasized dash + ); + + BasicView.prototype.render.call(this, 1, this.getCellsPerWeek(), false); // call the super-method + } + +}); +;; + +/* A view with a single simple day cell +----------------------------------------------------------------------------------------------------------------------*/ + +fcViews.basicDay = BasicDayView; // register this view + +function BasicDayView(calendar) { + BasicView.call(this, calendar); // call the super-constructor +} + + +BasicDayView.prototype = createObject(BasicView.prototype); // define the super-class +$.extend(BasicDayView.prototype, { + + name: 'basicDay', + + + incrementDate: function(date, delta) { + var out = date.clone().stripTime().add(delta, 'days'); + out = this.skipHiddenDays(out, delta < 0 ? -1 : 1); + return out; + }, + + + render: function(date) { + + this.start = this.intervalStart = date.clone().stripTime(); + this.end = this.intervalEnd = this.start.clone().add(1, 'days'); + + this.title = this.calendar.formatDate(this.start, this.opt('titleFormat')); + + BasicView.prototype.render.call(this, 1, 1, false); // call the super-method + } + +}); +;; + +/* An abstract class for all agenda-related views. Displays one more columns with time slots running vertically. +----------------------------------------------------------------------------------------------------------------------*/ +// Is a manager for the TimeGrid subcomponent and possibly the DayGrid subcomponent (if allDaySlot is on). +// Responsible for managing width/height. + +setDefaults({ + allDaySlot: true, + allDayText: 'all-day', + + scrollTime: '06:00:00', + + slotDuration: '00:30:00', + + axisFormat: generateAgendaAxisFormat, + timeFormat: { + agenda: generateAgendaTimeFormat + }, + + minTime: '00:00:00', + maxTime: '24:00:00', + slotEventOverlap: true +}); + +var AGENDA_ALL_DAY_EVENT_LIMIT = 5; + + +function generateAgendaAxisFormat(options, langData) { + return langData.longDateFormat('LT') + .replace(':mm', '(:mm)') + .replace(/(\Wmm)$/, '($1)') // like above, but for foreign langs + .replace(/\s*a$/i, 'a'); // convert AM/PM/am/pm to lowercase. remove any spaces beforehand +} + + +function generateAgendaTimeFormat(options, langData) { + return langData.longDateFormat('LT') + .replace(/\s*a$/i, ''); // remove trailing AM/PM +} + + +function AgendaView(calendar) { + View.call(this, calendar); // call the super-constructor + + this.timeGrid = new TimeGrid(this); + + if (this.opt('allDaySlot')) { // should we display the "all-day" area? + this.dayGrid = new DayGrid(this); // the all-day subcomponent of this view + + // the coordinate grid will be a combination of both subcomponents' grids + this.coordMap = new ComboCoordMap([ + this.dayGrid.coordMap, + this.timeGrid.coordMap + ]); + } + else { + this.coordMap = this.timeGrid.coordMap; + } +} + + +AgendaView.prototype = createObject(View.prototype); // define the super-class +$.extend(AgendaView.prototype, { + + timeGrid: null, // the main time-grid subcomponent of this view + dayGrid: null, // the "all-day" subcomponent. if all-day is turned off, this will be null + + axisWidth: null, // the width of the time axis running down the side + + noScrollRowEls: null, // set of fake row elements that must compensate when scrollerEl has scrollbars + + // when the time-grid isn't tall enough to occupy the given height, we render an
underneath + bottomRuleEl: null, + bottomRuleHeight: null, + + + /* Rendering + ------------------------------------------------------------------------------------------------------------------*/ + + + // Renders the view into `this.el`, which has already been assigned. + // `colCnt` has been calculated by a subclass and passed here. + render: function(colCnt) { + + // needed for cell-to-date and date-to-cell calculations in View + this.rowCnt = 1; + this.colCnt = colCnt; + + this.el.addClass('fc-agenda-view').html(this.renderHtml()); + + // the element that wraps the time-grid that will probably scroll + this.scrollerEl = this.el.find('.fc-time-grid-container'); + this.timeGrid.coordMap.containerEl = this.scrollerEl; // don't accept clicks/etc outside of this + + this.timeGrid.el = this.el.find('.fc-time-grid'); + this.timeGrid.render(); + + // the
that sometimes displays under the time-grid + this.bottomRuleEl = $('
') + .appendTo(this.timeGrid.el); // inject it into the time-grid + + if (this.dayGrid) { + this.dayGrid.el = this.el.find('.fc-day-grid'); + this.dayGrid.render(); + + // have the day-grid extend it's coordinate area over the
dividing the two grids + this.dayGrid.bottomCoordPadding = this.dayGrid.el.next('hr').outerHeight(); + } + + this.noScrollRowEls = this.el.find('.fc-row:not(.fc-scroller *)'); // fake rows not within the scroller + + View.prototype.render.call(this); // call the super-method + + this.resetScroll(); // do this after sizes have been set + }, + + + // Make subcomponents ready for cleanup + destroy: function() { + this.timeGrid.destroy(); + if (this.dayGrid) { + this.dayGrid.destroy(); + } + View.prototype.destroy.call(this); // call the super-method + }, + + + // Builds the HTML skeleton for the view. + // The day-grid and time-grid components will render inside containers defined by this HTML. + renderHtml: function() { + return '' + + '
'; + } + if (endCol > startCol) { + cellHtml += ''; + } + if (colCnt > endCol) { + cellHtml += ''; + } + + cellHtml = this.bookendCells(cellHtml, 'highlight'); + + return '' + + '
' + + '' + + '' + + cellHtml + + '' + + '
' + + '
'; + } + +}); + +;; + +/* Event-rendering methods for the DayGrid class +----------------------------------------------------------------------------------------------------------------------*/ + +$.extend(DayGrid.prototype, { + + segs: null, + rowStructs: null, // an array of objects, each holding information about a row's event-rendering + + + // Render the given events onto the Grid and return the rendered segments + renderEvents: function(events) { + var rowStructs = this.rowStructs = this.renderEventRows(events); + var segs = []; + + // append to each row's content skeleton + this.rowEls.each(function(i, rowNode) { + $(rowNode).find('.fc-content-skeleton > table').append( + rowStructs[i].tbodyEl + ); + segs.push.apply(segs, rowStructs[i].segs); + }); + + this.segs = segs; + }, + + + // Retrieves all segment objects that have been rendered + getSegs: function() { + return (this.segs || []).concat( + this.popoverSegs || [] // segs rendered in the "more" events popover + ); + }, + + + // Removes all rendered event elements + destroyEvents: function() { + var rowStructs; + var rowStruct; + + Grid.prototype.destroyEvents.call(this); // call the super-method + + rowStructs = this.rowStructs || []; + while ((rowStruct = rowStructs.pop())) { + rowStruct.tbodyEl.remove(); + } + + this.segs = null; + this.destroySegPopover(); // removes the "more.." events popover + }, + + + // Uses the given events array to generate
elements of the level+col matrix + var loneCellMatrix = []; // lookup for elements that only take up a single column + var i, levelSegs; + var col; + var tr; + var j, seg; + var td; + + // populates empty cells from the current column (`col`) to `endCol` + function emptyCellsUntil(endCol) { + while (col < endCol) { + // try to grab a cell from the level above and extend its rowspan. otherwise, create a fresh cell + td = (loneCellMatrix[i - 1] || [])[col]; + if (td) { + td.attr( + 'rowspan', + parseInt(td.attr('rowspan') || 1, 10) + 1 + ); + } + else { + td = $(''); + tr.append(td); + } + cellMatrix[i][col] = td; + loneCellMatrix[i][col] = td; + col++; + } + } + + for (i = 0; i < levelCnt; i++) { // iterate through all levels + levelSegs = segLevels[i]; + col = 0; + tr = $('
').append(seg.el); + if (seg.leftCol != seg.rightCol) { + td.attr('colspan', seg.rightCol - seg.leftCol + 1); + } + else { // a single-column segment + loneCellMatrix[i][col] = td; + } + + while (col <= seg.rightCol) { + cellMatrix[i][col] = td; + segMatrix[i][col] = seg; + col++; + } + + tr.append(td); + } + } + + emptyCellsUntil(colCnt); // finish off the row + this.bookendCells(tr, 'eventSkeleton'); + tbody.append(tr); + } + + return { // a "rowStruct" + row: row, // the row number + tbodyEl: tbody, + cellMatrix: cellMatrix, + segMatrix: segMatrix, + segLevels: segLevels, + segs: rowSegs + }; + }, + + + // Stacks a flat array of segments, which are all assumed to be in the same row, into subarrays of vertical levels. + buildSegLevels: function(segs) { + var levels = []; + var i, seg; + var j; + + // Give preference to elements with certain criteria, so they have + // a chance to be closer to the top. + segs.sort(compareSegs); + + for (i = 0; i < segs.length; i++) { + seg = segs[i]; + + // loop through levels, starting with the topmost, until the segment doesn't collide with other segments + for (j = 0; j < levels.length; j++) { + if (!isDaySegCollision(seg, levels[j])) { + break; + } + } + // `j` now holds the desired subrow index + seg.level = j; + + // create new level array if needed and append segment + (levels[j] || (levels[j] = [])).push(seg); + } + + // order segments left-to-right. very important if calendar is RTL + for (j = 0; j < levels.length; j++) { + levels[j].sort(compareDaySegCols); + } + + return levels; + }, + + + // Given a flat array of segments, return an array of sub-arrays, grouped by each segment's row + groupSegRows: function(segs) { + var view = this.view; + var segRows = []; + var i; + + for (i = 0; i < view.rowCnt; i++) { + segRows.push([]); + } + + for (i = 0; i < segs.length; i++) { + segRows[segs[i].row].push(segs[i]); + } + + return segRows; + } + +}); + + +// Computes whether two segments' columns collide. They are assumed to be in the same row. +function isDaySegCollision(seg, otherSegs) { + var i, otherSeg; + + for (i = 0; i < otherSegs.length; i++) { + otherSeg = otherSegs[i]; + + if ( + otherSeg.leftCol <= seg.rightCol && + otherSeg.rightCol >= seg.leftCol + ) { + return true; + } + } + + return false; +} + + +// A cmp function for determining the leftmost event +function compareDaySegCols(a, b) { + return a.leftCol - b.leftCol; +} + +;; + +/* Methods relate to limiting the number events for a given day on a DayGrid +----------------------------------------------------------------------------------------------------------------------*/ + +$.extend(DayGrid.prototype, { + + + segPopover: null, // the Popover that holds events that can't fit in a cell. null when not visible + popoverSegs: null, // an array of segment objects that the segPopover holds. null when not visible + + + destroySegPopover: function() { + if (this.segPopover) { + this.segPopover.hide(); // will trigger destruction of `segPopover` and `popoverSegs` + } + }, + + + // Limits the number of "levels" (vertically stacking layers of events) for each row of the grid. + // `levelLimit` can be false (don't limit), a number, or true (should be computed). + limitRows: function(levelLimit) { + var rowStructs = this.rowStructs || []; + var row; // row # + var rowLevelLimit; + + for (row = 0; row < rowStructs.length; row++) { + this.unlimitRow(row); + + if (!levelLimit) { + rowLevelLimit = false; + } + else if (typeof levelLimit === 'number') { + rowLevelLimit = levelLimit; + } + else { + rowLevelLimit = this.computeRowLevelLimit(row); + } + + if (rowLevelLimit !== false) { + this.limitRow(row, rowLevelLimit); + } + } + }, + + + // Computes the number of levels a row will accomodate without going outside its bounds. + // Assumes the row is "rigid" (maintains a constant height regardless of what is inside). + // `row` is the row number. + computeRowLevelLimit: function(row) { + var rowEl = this.rowEls.eq(row); // the containing "fake" row div + var rowHeight = rowEl.height(); // TODO: cache somehow? + var trEls = this.rowStructs[row].tbodyEl.children(); + var i, trEl; + + // Reveal one level
DOM nodes + var col = 0; // col # + var cell; + var levelSegs; // array of segment objects in the last allowable level, ordered left-to-right + var cellMatrix; // a matrix (by level, then column) of all jQuery elements in the row + var limitedNodes; // array of temporarily hidden level
DOM nodes + var i, seg; + var segsBelow; // array of segment objects below `seg` in the current `col` + var totalSegsBelow; // total number of segments below `seg` in any of the columns `seg` occupies + var colSegsBelow; // array of segment arrays, below seg, one for each column (offset from segs's first column) + var td, rowspan; + var segMoreNodes; // array of "more" cells that will stand-in for the current seg's cell + var j; + var moreTd, moreWrap, moreLink; + + // Iterates through empty level cells and places "more" links inside if need be + function emptyCellsUntil(endCol) { // goes from current `col` to `endCol` + while (col < endCol) { + cell = { row: row, col: col }; + segsBelow = _this.getCellSegs(cell, levelLimit); + if (segsBelow.length) { + td = cellMatrix[levelLimit - 1][col]; + moreLink = _this.renderMoreLink(cell, segsBelow); + moreWrap = $('
').append(moreLink); + td.append(moreWrap); + moreNodes.push(moreWrap[0]); + } + col++; + } + } + + if (levelLimit && levelLimit < rowStruct.segLevels.length) { // is it actually over the limit? + levelSegs = rowStruct.segLevels[levelLimit - 1]; + cellMatrix = rowStruct.cellMatrix; + + limitedNodes = rowStruct.tbodyEl.children().slice(levelLimit) // get level
for each column the segment occupies. will be one for each colspan + for (j = 0; j < colSegsBelow.length; j++) { + moreTd = $('').attr('rowspan', rowspan); + segsBelow = colSegsBelow[j]; + cell = { row: row, col: seg.leftCol + j }; + moreLink = this.renderMoreLink(cell, [ seg ].concat(segsBelow)); // count seg as hidden too + moreWrap = $('
').append(moreLink); + moreTd.append(moreWrap); + segMoreNodes.push(moreTd[0]); + moreNodes.push(moreTd[0]); + } + + td.addClass('fc-limited').after($(segMoreNodes)); // hide original
and inject replacements + limitedNodes.push(td[0]); + } + } + + emptyCellsUntil(view.colCnt); // finish off the level + rowStruct.moreEls = $(moreNodes); // for easy undoing later + rowStruct.limitedEls = $(limitedNodes); // for easy undoing later + } + }, + + + // Reveals all levels and removes all "more"-related elements for a grid's row. + // `row` is a row number. + unlimitRow: function(row) { + var rowStruct = this.rowStructs[row]; + + if (rowStruct.moreEls) { + rowStruct.moreEls.remove(); + rowStruct.moreEls = null; + } + + if (rowStruct.limitedEls) { + rowStruct.limitedEls.removeClass('fc-limited'); + rowStruct.limitedEls = null; + } + }, + + + // Renders an element that represents hidden event element for a cell. + // Responsible for attaching click handler as well. + renderMoreLink: function(cell, hiddenSegs) { + var _this = this; + var view = this.view; + + return $('') + .text( + this.getMoreLinkText(hiddenSegs.length) + ) + .on('click', function(ev) { + var clickOption = view.opt('eventLimitClick'); + var date = view.cellToDate(cell); + var moreEl = $(this); + var dayEl = _this.getCellDayEl(cell); + var allSegs = _this.getCellSegs(cell); + + // rescope the segments to be within the cell's date + var reslicedAllSegs = _this.resliceDaySegs(allSegs, date); + var reslicedHiddenSegs = _this.resliceDaySegs(hiddenSegs, date); + + if (typeof clickOption === 'function') { + // the returned value can be an atomic option + clickOption = view.trigger('eventLimitClick', null, { + date: date, + dayEl: dayEl, + moreEl: moreEl, + segs: reslicedAllSegs, + hiddenSegs: reslicedHiddenSegs + }, ev); + } + + if (clickOption === 'popover') { + _this.showSegPopover(date, cell, moreEl, reslicedAllSegs); + } + else if (typeof clickOption === 'string') { // a view name + view.calendar.zoomTo(date, clickOption); + } + }); + }, + + + // Reveals the popover that displays all events within a cell + showSegPopover: function(date, cell, moreLink, segs) { + var _this = this; + var view = this.view; + var moreWrap = moreLink.parent(); // the
wrapper around the + var topEl; // the element we want to match the top coordinate of + var options; + + if (view.rowCnt == 1) { + topEl = this.view.el; // will cause the popover to cover any sort of header + } + else { + topEl = this.rowEls.eq(cell.row); // will align with top of row + } + + options = { + className: 'fc-more-popover', + content: this.renderSegPopoverContent(date, segs), + parentEl: this.el, + top: topEl.offset().top, + autoHide: true, // when the user clicks elsewhere, hide the popover + viewportConstrain: view.opt('popoverViewportConstrain'), + hide: function() { + // destroy everything when the popover is hidden + _this.segPopover.destroy(); + _this.segPopover = null; + _this.popoverSegs = null; + } + }; + + // Determine horizontal coordinate. + // We use the moreWrap instead of the
to avoid border confusion. + if (view.opt('isRTL')) { + options.right = moreWrap.offset().left + moreWrap.outerWidth() + 1; // +1 to be over cell border + } + else { + options.left = moreWrap.offset().left - 1; // -1 to be over cell border + } + + this.segPopover = new Popover(options); + this.segPopover.show(); + }, + + + // Builds the inner DOM contents of the segment popover + renderSegPopoverContent: function(date, segs) { + var view = this.view; + var isTheme = view.opt('theme'); + var title = date.format(view.opt('dayPopoverFormat')); + var content = $( + '
' + + '' + + '' + + htmlEscape(title) + + '' + + '
' + + '
' + + '
' + + '
' + + '
' + ); + var segContainer = content.find('.fc-event-container'); + var i; + + // render each seg's `el` and only return the visible segs + segs = this.renderSegs(segs, true); // disableResizing=true + this.popoverSegs = segs; + + for (i = 0; i < segs.length; i++) { + + // because segments in the popover are not part of a grid coordinate system, provide a hint to any + // grids that want to do drag-n-drop about which cell it came from + segs[i].cellDate = date; + + segContainer.append(segs[i].el); + } + + return content; + }, + + + // Given the events within an array of segment objects, reslice them to be in a single day + resliceDaySegs: function(segs, dayDate) { + var events = $.map(segs, function(seg) { + return seg.event; + }); + var dayStart = dayDate.clone().stripTime(); + var dayEnd = dayStart.clone().add(1, 'days'); + + return this.eventsToSegs(events, dayStart, dayEnd); + }, + + + // Generates the text that should be inside a "more" link, given the number of events it represents + getMoreLinkText: function(num) { + var view = this.view; + var opt = view.opt('eventLimitText'); + + if (typeof opt === 'function') { + return opt(num); + } + else { + return '+' + num + ' ' + opt; + } + }, + + + // Returns segments within a given cell. + // If `startLevel` is specified, returns only events including and below that level. Otherwise returns all segs. + getCellSegs: function(cell, startLevel) { + var segMatrix = this.rowStructs[cell.row].segMatrix; + var level = startLevel || 0; + var segs = []; + var seg; + + while (level < segMatrix.length) { + seg = segMatrix[level][cell.col]; + if (seg) { + segs.push(seg); + } + level++; + } + + return segs; + } + +}); + +;; + +/* A component that renders one or more columns of vertical time slots +----------------------------------------------------------------------------------------------------------------------*/ + +function TimeGrid(view) { + Grid.call(this, view); // call the super-constructor +} + + +TimeGrid.prototype = createObject(Grid.prototype); // define the super-class +$.extend(TimeGrid.prototype, { + + slotDuration: null, // duration of a "slot", a distinct time segment on given day, visualized by lines + snapDuration: null, // granularity of time for dragging and selecting + + minTime: null, // Duration object that denotes the first visible time of any given day + maxTime: null, // Duration object that denotes the exclusive visible end time of any given day + + dayEls: null, // cells elements in the day-row background + slatEls: null, // elements running horizontally across all columns + + slatTops: null, // an array of top positions, relative to the container. last item holds bottom of last slot + + highlightEl: null, // cell skeleton element for rendering the highlight + helperEl: null, // cell skeleton element for rendering the mock event "helper" + + + // Renders the time grid into `this.el`, which should already be assigned. + // Relies on the view's colCnt. In the future, this component should probably be self-sufficient. + render: function() { + this.processOptions(); + + this.el.html(this.renderHtml()); + + this.dayEls = this.el.find('.fc-day'); + this.slatEls = this.el.find('.fc-slats tr'); + + this.computeSlatTops(); + + Grid.prototype.render.call(this); // call the super-method + }, + + + // Renders the basic HTML skeleton for the grid + renderHtml: function() { + return '' + + '
' + + '' + + this.rowHtml('slotBg') + // leverages RowRenderer, which will call slotBgCellHtml + '
' + + '
' + + '
' + + '' + + this.slatRowHtml() + + '
' + + '
'; + }, + + + // Renders the HTML for a vertical background cell behind the slots. + // This method is distinct from 'bg' because we wanted a new `rowType` so the View could customize the rendering. + slotBgCellHtml: function(row, col, date) { + return this.bgCellHtml(row, col, date); + }, + + + // Generates the HTML for the horizontal "slats" that run width-wise. Has a time axis on a side. Depends on RTL. + slatRowHtml: function() { + var view = this.view; + var calendar = view.calendar; + var isRTL = view.opt('isRTL'); + var html = ''; + var slotNormal = this.slotDuration.asMinutes() % 15 === 0; + var slotTime = moment.duration(+this.minTime); // wish there was .clone() for durations + var slotDate; // will be on the view's first day, but we only care about its time + var minutes; + var axisHtml; + + // Calculate the time for each slot + while (slotTime < this.maxTime) { + slotDate = view.start.clone().time(slotTime); // will be in UTC but that's good. to avoid DST issues + minutes = slotDate.minutes(); + + axisHtml = + '
' + + ((!slotNormal || !minutes) ? // if irregular slot duration, or on the hour, then display the time + '' + // for matchCellWidths + htmlEscape(calendar.formatDate(slotDate, view.opt('axisFormat'))) + + '' : + '' + ) + + '
' + + (isRTL ? axisHtml : '') + + "
'; + col = seg.col; + } + + // compute vertical position + dayDate = view.cellToDate(0, col); + top = this.computeDateTop(seg.start, dayDate); + bottom = this.computeDateTop(seg.end, dayDate); // the y position of the bottom edge + + // generate the cell HTML. bottom becomes negative because it needs to be a CSS value relative to the + // bottom edge of the zero-height container. + cellHtml += + '' + + '
' + + '
' + + '
' + + '
'; + } + + cellHtml = this.bookendCells(cellHtml, 'highlight'); + + return '' + + '
' + + '' + + '' + + cellHtml + + '' + + '
' + + '
'; + } + +}); + +;; + +/* Event-rendering methods for the TimeGrid class +----------------------------------------------------------------------------------------------------------------------*/ + +$.extend(TimeGrid.prototype, { + + segs: null, // segment objects rendered in the component. null of events haven't been rendered yet + eventSkeletonEl: null, // has cells with event-containers, which contain absolutely positioned event elements + + + // Renders the events onto the grid and returns an array of segments that have been rendered + renderEvents: function(events) { + var res = this.renderEventTable(events); + + this.eventSkeletonEl = $('
').append(res.tableEl); + this.el.append(this.eventSkeletonEl); + + this.segs = res.segs; + }, + + + // Retrieves rendered segment objects + getSegs: function() { + return this.segs || []; + }, + + + // Removes all event segment elements from the view + destroyEvents: function() { + Grid.prototype.destroyEvents.call(this); // call the super-method + + if (this.eventSkeletonEl) { + this.eventSkeletonEl.remove(); + this.eventSkeletonEl = null; + } + + this.segs = null; + }, + + + // Renders and returns the portion of the event-skeleton. + // Returns an object with properties 'tbodyEl' and 'segs'. + renderEventTable: function(events) { + var tableEl = $('
'); + var trEl = tableEl.find('tr'); + var segs = this.eventsToSegs(events); + var segCols; + var i, seg; + var col, colSegs; + var containerEl; + + segs = this.renderSegs(segs); // returns only the visible segs + segCols = this.groupSegCols(segs); // group into sub-arrays, and assigns 'col' to each seg + + this.computeSegVerticals(segs); // compute and assign top/bottom + + for (col = 0; col < segCols.length; col++) { // iterate each column grouping + colSegs = segCols[col]; + placeSlotSegs(colSegs); // compute horizontal coordinates, z-index's, and reorder the array + + containerEl = $('
'); + + // assign positioning CSS and insert into container + for (i = 0; i < colSegs.length; i++) { + seg = colSegs[i]; + seg.el.css(this.generateSegPositionCss(seg)); + + // if the height is short, add a className for alternate styling + if (seg.bottom - seg.top < 30) { + seg.el.addClass('fc-short'); + } + + containerEl.append(seg.el); + } + + trEl.append($('
').append(containerEl)); + } + + this.bookendCells(trEl, 'eventSkeleton'); + + return { + tableEl: tableEl, + segs: segs + }; + }, + + + // Refreshes the CSS top/bottom coordinates for each segment element. Probably after a window resize/zoom. + updateSegVerticals: function() { + var segs = this.segs; + var i; + + if (segs) { + this.computeSegVerticals(segs); + + for (i = 0; i < segs.length; i++) { + segs[i].el.css( + this.generateSegVerticalCss(segs[i]) + ); + } + } + }, + + + // For each segment in an array, computes and assigns its top and bottom properties + computeSegVerticals: function(segs) { + var i, seg; + + for (i = 0; i < segs.length; i++) { + seg = segs[i]; + seg.top = this.computeDateTop(seg.start, seg.start); + seg.bottom = this.computeDateTop(seg.end, seg.start); + } + }, + + + // Renders the HTML for a single event segment's default rendering + renderSegHtml: function(seg, disableResizing) { + var view = this.view; + var event = seg.event; + var isDraggable = view.isEventDraggable(event); + var isResizable = !disableResizing && seg.isEnd && view.isEventResizable(event); + var classes = this.getSegClasses(seg, isDraggable, isResizable); + var skinCss = this.getEventSkinCss(event); + var timeText; + var fullTimeText; // more verbose time text. for the print stylesheet + var startTimeText; // just the start time text + + classes.unshift('fc-time-grid-event'); + + if (view.isMultiDayEvent(event)) { // if the event appears to span more than one day... + // Don't display time text on segments that run entirely through a day. + // That would appear as midnight-midnight and would look dumb. + // Otherwise, display the time text for the *segment's* times (like 6pm-midnight or midnight-10am) + if (seg.isStart || seg.isEnd) { + timeText = view.getEventTimeText(seg.start, seg.end); + fullTimeText = view.getEventTimeText(seg.start, seg.end, 'LT'); + startTimeText = view.getEventTimeText(seg.start, null); + } + } else { + // Display the normal time text for the *event's* times + timeText = view.getEventTimeText(event); + fullTimeText = view.getEventTimeText(event, 'LT'); + startTimeText = view.getEventTimeText(event.start, null); + } + + return '' + + '
' + + (timeText ? + '
' + + '' + htmlEscape(timeText) + '' + + '
' : + '' + ) + + (event.title ? + '
' + + htmlEscape(event.title) + + '
' : + '' + ) + + '
' + + '
' + + (isResizable ? + '
' : + '' + ) + + ''; + }, + + + // Generates an object with CSS properties/values that should be applied to an event segment element. + // Contains important positioning-related properties that should be applied to any event element, customized or not. + generateSegPositionCss: function(seg) { + var view = this.view; + var isRTL = view.opt('isRTL'); + var shouldOverlap = view.opt('slotEventOverlap'); + var backwardCoord = seg.backwardCoord; // the left side if LTR. the right side if RTL. floating-point + var forwardCoord = seg.forwardCoord; // the right side if LTR. the left side if RTL. floating-point + var props = this.generateSegVerticalCss(seg); // get top/bottom first + var left; // amount of space from left edge, a fraction of the total width + var right; // amount of space from right edge, a fraction of the total width + + if (shouldOverlap) { + // double the width, but don't go beyond the maximum forward coordinate (1.0) + forwardCoord = Math.min(1, backwardCoord + (forwardCoord - backwardCoord) * 2); + } + + if (isRTL) { + left = 1 - forwardCoord; + right = backwardCoord; + } + else { + left = backwardCoord; + right = 1 - forwardCoord; + } + + props.zIndex = seg.level + 1; // convert from 0-base to 1-based + props.left = left * 100 + '%'; + props.right = right * 100 + '%'; + + if (shouldOverlap && seg.forwardPressure) { + // add padding to the edge so that forward stacked events don't cover the resizer's icon + props[isRTL ? 'marginLeft' : 'marginRight'] = 10 * 2; // 10 is a guesstimate of the icon's width + } + + return props; + }, + + + // Generates an object with CSS properties for the top/bottom coordinates of a segment element + generateSegVerticalCss: function(seg) { + return { + top: seg.top, + bottom: -seg.bottom // flipped because needs to be space beyond bottom edge of event container + }; + }, + + + // Given a flat array of segments, return an array of sub-arrays, grouped by each segment's col + groupSegCols: function(segs) { + var view = this.view; + var segCols = []; + var i; + + for (i = 0; i < view.colCnt; i++) { + segCols.push([]); + } + + for (i = 0; i < segs.length; i++) { + segCols[segs[i].col].push(segs[i]); + } + + return segCols; + } + +}); + + +// Given an array of segments that are all in the same column, sets the backwardCoord and forwardCoord on each. +// Also reorders the given array by date! +function placeSlotSegs(segs) { + var levels; + var level0; + var i; + + segs.sort(compareSegs); // order by date + levels = buildSlotSegLevels(segs); + computeForwardSlotSegs(levels); + + if ((level0 = levels[0])) { + + for (i = 0; i < level0.length; i++) { + computeSlotSegPressures(level0[i]); + } + + for (i = 0; i < level0.length; i++) { + computeSlotSegCoords(level0[i], 0, 0); + } + } +} + + +// Builds an array of segments "levels". The first level will be the leftmost tier of segments if the calendar is +// left-to-right, or the rightmost if the calendar is right-to-left. Assumes the segments are already ordered by date. +function buildSlotSegLevels(segs) { + var levels = []; + var i, seg; + var j; + + for (i=0; i seg2.top && seg1.top < seg2.bottom; +} + + +// A cmp function for determining which forward segment to rely on more when computing coordinates. +function compareForwardSlotSegs(seg1, seg2) { + // put higher-pressure first + return seg2.forwardPressure - seg1.forwardPressure || + // put segments that are closer to initial edge first (and favor ones with no coords yet) + (seg1.backwardCoord || 0) - (seg2.backwardCoord || 0) || + // do normal sorting... + compareSegs(seg1, seg2); +} + +;; + +/* An abstract class from which other views inherit from +----------------------------------------------------------------------------------------------------------------------*/ +// Newer methods should be written as prototype methods, not in the monster `View` function at the bottom. + +View.prototype = { + + calendar: null, // owner Calendar object + coordMap: null, // a CoordMap object for converting pixel regions to dates + el: null, // the view's containing element. set by Calendar + + // important Moments + start: null, // the date of the very first cell + end: null, // the date after the very last cell + intervalStart: null, // the start of the interval of time the view represents (1st of month for month view) + intervalEnd: null, // the exclusive end of the interval of time the view represents + + // used for cell-to-date and date-to-cell calculations + rowCnt: null, // # of weeks + colCnt: null, // # of days displayed in a week + + isSelected: false, // boolean whether cells are user-selected or not + + // subclasses can optionally use a scroll container + scrollerEl: null, // the element that will most likely scroll when content is too tall + scrollTop: null, // cached vertical scroll value + + // classNames styled by jqui themes + widgetHeaderClass: null, + widgetContentClass: null, + highlightStateClass: null, + + // document handlers, bound to `this` object + documentMousedownProxy: null, + documentDragStartProxy: null, + + + // Serves as a "constructor" to suppliment the monster `View` constructor below + init: function() { + var tm = this.opt('theme') ? 'ui' : 'fc'; + + this.widgetHeaderClass = tm + '-widget-header'; + this.widgetContentClass = tm + '-widget-content'; + this.highlightStateClass = tm + '-state-highlight'; + + // save references to `this`-bound handlers + this.documentMousedownProxy = $.proxy(this, 'documentMousedown'); + this.documentDragStartProxy = $.proxy(this, 'documentDragStart'); + }, + + + // Renders the view inside an already-defined `this.el`. + // Subclasses should override this and then call the super method afterwards. + render: function() { + this.updateSize(); + this.trigger('viewRender', this, this, this.el); + + // attach handlers to document. do it here to allow for destroy/rerender + $(document) + .on('mousedown', this.documentMousedownProxy) + .on('dragstart', this.documentDragStartProxy); // jqui drag + }, + + + // Clears all view rendering, event elements, and unregisters handlers + destroy: function() { + this.unselect(); + this.trigger('viewDestroy', this, this, this.el); + this.destroyEvents(); + this.el.empty(); // removes inner contents but leaves the element intact + + $(document) + .off('mousedown', this.documentMousedownProxy) + .off('dragstart', this.documentDragStartProxy); + }, + + + // Used to determine what happens when the users clicks next/prev. Given -1 for prev, 1 for next. + // Should apply the delta to `date` (a Moment) and return it. + incrementDate: function(date, delta) { + // subclasses should implement + }, + + + /* Dimensions + ------------------------------------------------------------------------------------------------------------------*/ + + + // Refreshes anything dependant upon sizing of the container element of the grid + updateSize: function(isResize) { + if (isResize) { + this.recordScroll(); + } + this.updateHeight(); + this.updateWidth(); + }, + + + // Refreshes the horizontal dimensions of the calendar + updateWidth: function() { + // subclasses should implement + }, + + + // Refreshes the vertical dimensions of the calendar + updateHeight: function() { + var calendar = this.calendar; // we poll the calendar for height information + + this.setHeight( + calendar.getSuggestedViewHeight(), + calendar.isHeightAuto() + ); + }, + + + // Updates the vertical dimensions of the calendar to the specified height. + // if `isAuto` is set to true, height becomes merely a suggestion and the view should use its "natural" height. + setHeight: function(height, isAuto) { + // subclasses should implement + }, + + + // Given the total height of the view, return the number of pixels that should be used for the scroller. + // Utility for subclasses. + computeScrollerHeight: function(totalHeight) { + var both = this.el.add(this.scrollerEl); + var otherHeight; // cumulative height of everything that is not the scrollerEl in the view (header+borders) + + // fuckin IE8/9/10/11 sometimes returns 0 for dimensions. this weird hack was the only thing that worked + both.css({ + position: 'relative', // cause a reflow, which will force fresh dimension recalculation + left: -1 // ensure reflow in case the el was already relative. negative is less likely to cause new scroll + }); + otherHeight = this.el.outerHeight() - this.scrollerEl.height(); // grab the dimensions + both.css({ position: '', left: '' }); // undo hack + + return totalHeight - otherHeight; + }, + + + // Called for remembering the current scroll value of the scroller. + // Should be called before there is a destructive operation (like removing DOM elements) that might inadvertently + // change the scroll of the container. + recordScroll: function() { + if (this.scrollerEl) { + this.scrollTop = this.scrollerEl.scrollTop(); + } + }, + + + // Set the scroll value of the scroller to the previously recorded value. + // Should be called after we know the view's dimensions have been restored following some type of destructive + // operation (like temporarily removing DOM elements). + restoreScroll: function() { + if (this.scrollTop !== null) { + this.scrollerEl.scrollTop(this.scrollTop); + } + }, + + + /* Events + ------------------------------------------------------------------------------------------------------------------*/ + + + // Renders the events onto the view. + // Should be overriden by subclasses. Subclasses should call the super-method afterwards. + renderEvents: function(events) { + this.segEach(function(seg) { + this.trigger('eventAfterRender', seg.event, seg.event, seg.el); + }); + this.trigger('eventAfterAllRender'); + }, + + + // Removes event elements from the view. + // Should be overridden by subclasses. Should call this super-method FIRST, then subclass DOM destruction. + destroyEvents: function() { + this.segEach(function(seg) { + this.trigger('eventDestroy', seg.event, seg.event, seg.el); + }); + }, + + + // Given an event and the default element used for rendering, returns the element that should actually be used. + // Basically runs events and elements through the eventRender hook. + resolveEventEl: function(event, el) { + var custom = this.trigger('eventRender', event, event, el); + + if (custom === false) { // means don't render at all + el = null; + } + else if (custom && custom !== true) { + el = $(custom); + } + + return el; + }, + + + // Hides all rendered event segments linked to the given event + showEvent: function(event) { + this.segEach(function(seg) { + seg.el.css('visibility', ''); + }, event); + }, + + + // Shows all rendered event segments linked to the given event + hideEvent: function(event) { + this.segEach(function(seg) { + seg.el.css('visibility', 'hidden'); + }, event); + }, + + + // Iterates through event segments. Goes through all by default. + // If the optional `event` argument is specified, only iterates through segments linked to that event. + // The `this` value of the callback function will be the view. + segEach: function(func, event) { + var segs = this.getSegs(); + var i; + + for (i = 0; i < segs.length; i++) { + if (!event || segs[i].event._id === event._id) { + func.call(this, segs[i]); + } + } + }, + + + // Retrieves all the rendered segment objects for the view + getSegs: function() { + // subclasses must implement + }, + + + /* Event Drag Visualization + ------------------------------------------------------------------------------------------------------------------*/ + + + // Renders a visual indication of an event hovering over the specified date. + // `end` is a Moment and might be null. + // `seg` might be null. if specified, it is the segment object of the event being dragged. + // otherwise, an external event from outside the calendar is being dragged. + renderDrag: function(start, end, seg) { + // subclasses should implement + }, + + + // Unrenders a visual indication of event hovering + destroyDrag: function() { + // subclasses should implement + }, + + + // Handler for accepting externally dragged events being dropped in the view. + // Gets called when jqui's 'dragstart' is fired. + documentDragStart: function(ev, ui) { + var _this = this; + var dropDate = null; + var dragListener; + + if (this.opt('droppable')) { // only listen if this setting is on + + // listener that tracks mouse movement over date-associated pixel regions + dragListener = new DragListener(this.coordMap, { + cellOver: function(cell, date) { + dropDate = date; + _this.renderDrag(date); + }, + cellOut: function() { + dropDate = null; + _this.destroyDrag(); + } + }); + + // gets called, only once, when jqui drag is finished + $(document).one('dragstop', function(ev, ui) { + _this.destroyDrag(); + if (dropDate) { + _this.trigger('drop', ev.target, dropDate, ev, ui); + } + }); + + dragListener.startDrag(ev); // start listening immediately + } + }, + + + /* Selection + ------------------------------------------------------------------------------------------------------------------*/ + + + // Selects a date range on the view. `start` and `end` are both Moments. + // `ev` is the native mouse event that begin the interaction. + select: function(start, end, ev) { + this.unselect(ev); + this.renderSelection(start, end); + this.reportSelection(start, end, ev); + }, + + + // Renders a visual indication of the selection + renderSelection: function(start, end) { + // subclasses should implement + }, + + + // Called when a new selection is made. Updates internal state and triggers handlers. + reportSelection: function(start, end, ev) { + this.isSelected = true; + this.trigger('select', null, start, end, ev); + }, + + + // Undoes a selection. updates in the internal state and triggers handlers. + // `ev` is the native mouse event that began the interaction. + unselect: function(ev) { + if (this.isSelected) { + this.isSelected = false; + this.destroySelection(); + this.trigger('unselect', null, ev); + } + }, + + + // Unrenders a visual indication of selection + destroySelection: function() { + // subclasses should implement + }, + + + // Handler for unselecting when the user clicks something and the 'unselectAuto' setting is on + documentMousedown: function(ev) { + var ignore; + + // is there a selection, and has the user made a proper left click? + if (this.isSelected && this.opt('unselectAuto') && isPrimaryMouseButton(ev)) { + + // only unselect if the clicked element is not identical to or inside of an 'unselectCancel' element + ignore = this.opt('unselectCancel'); + if (!ignore || !$(ev.target).closest(ignore).length) { + this.unselect(ev); + } + } + } + +}; + + +// We are mixing JavaScript OOP design patterns here by putting methods and member variables in the closed scope of the +// constructor. Going forward, methods should be part of the prototype. +function View(calendar) { + var t = this; + + // exports + t.calendar = calendar; + t.opt = opt; + t.trigger = trigger; + t.isEventDraggable = isEventDraggable; + t.isEventResizable = isEventResizable; + t.eventDrop = eventDrop; + t.eventResize = eventResize; + + // imports + var reportEventChange = calendar.reportEventChange; + + // locals + var options = calendar.options; + var nextDayThreshold = moment.duration(options.nextDayThreshold); + + + t.init(); // the "constructor" that concerns the prototype methods + + + function opt(name) { + var v = options[name]; + if ($.isPlainObject(v) && !isForcedAtomicOption(name)) { + return smartProperty(v, t.name); + } + return v; + } + + + function trigger(name, thisObj) { + return calendar.trigger.apply( + calendar, + [name, thisObj || t].concat(Array.prototype.slice.call(arguments, 2), [t]) + ); + } + + + + /* Event Editable Boolean Calculations + ------------------------------------------------------------------------------*/ + + + function isEventDraggable(event) { + var source = event.source || {}; + + return firstDefined( + event.startEditable, + source.startEditable, + opt('eventStartEditable'), + event.editable, + source.editable, + opt('editable') + ); + } + + + function isEventResizable(event) { + var source = event.source || {}; + + return firstDefined( + event.durationEditable, + source.durationEditable, + opt('eventDurationEditable'), + event.editable, + source.editable, + opt('editable') + ); + } + + + + /* Event Elements + ------------------------------------------------------------------------------*/ + + + // Compute the text that should be displayed on an event's element. + // Based off the settings of the view. Possible signatures: + // .getEventTimeText(event, formatStr) + // .getEventTimeText(startMoment, endMoment, formatStr) + // .getEventTimeText(startMoment, null, formatStr) + // `timeFormat` is used but the `formatStr` argument can be used to override. + t.getEventTimeText = function(event, formatStr) { + var start; + var end; + + if (typeof event === 'object' && typeof formatStr === 'object') { + // first two arguments are actually moments (or null). shift arguments. + start = event; + end = formatStr; + formatStr = arguments[2]; + } + else { + // otherwise, an event object was the first argument + start = event.start; + end = event.end; + } + + formatStr = formatStr || opt('timeFormat'); + + if (end && opt('displayEventEnd')) { + return calendar.formatRange(start, end, formatStr); + } + else { + return calendar.formatDate(start, formatStr); + } + }; + + + + /* Event Modification Reporting + ---------------------------------------------------------------------------------*/ + + + function eventDrop(el, event, newStart, ev) { + var mutateResult = calendar.mutateEvent(event, newStart, null); + + trigger( + 'eventDrop', + el, + event, + mutateResult.dateDelta, + function() { + mutateResult.undo(); + reportEventChange(); + }, + ev, + {} // jqui dummy + ); + + reportEventChange(); + } + + + function eventResize(el, event, newEnd, ev) { + var mutateResult = calendar.mutateEvent(event, null, newEnd); + + trigger( + 'eventResize', + el, + event, + mutateResult.durationDelta, + function() { + mutateResult.undo(); + reportEventChange(); + }, + ev, + {} // jqui dummy + ); + + reportEventChange(); + } + + + // ==================================================================================================== + // Utilities for day "cells" + // ==================================================================================================== + // The "basic" views are completely made up of day cells. + // The "agenda" views have day cells at the top "all day" slot. + // This was the obvious common place to put these utilities, but they should be abstracted out into + // a more meaningful class (like DayEventRenderer). + // ==================================================================================================== + + + // For determining how a given "cell" translates into a "date": + // + // 1. Convert the "cell" (row and column) into a "cell offset" (the # of the cell, cronologically from the first). + // Keep in mind that column indices are inverted with isRTL. This is taken into account. + // + // 2. Convert the "cell offset" to a "day offset" (the # of days since the first visible day in the view). + // + // 3. Convert the "day offset" into a "date" (a Moment). + // + // The reverse transformation happens when transforming a date into a cell. + + + // exports + t.isHiddenDay = isHiddenDay; + t.skipHiddenDays = skipHiddenDays; + t.getCellsPerWeek = getCellsPerWeek; + t.dateToCell = dateToCell; + t.dateToDayOffset = dateToDayOffset; + t.dayOffsetToCellOffset = dayOffsetToCellOffset; + t.cellOffsetToCell = cellOffsetToCell; + t.cellToDate = cellToDate; + t.cellToCellOffset = cellToCellOffset; + t.cellOffsetToDayOffset = cellOffsetToDayOffset; + t.dayOffsetToDate = dayOffsetToDate; + t.rangeToSegments = rangeToSegments; + t.isMultiDayEvent = isMultiDayEvent; + + + // internals + var hiddenDays = opt('hiddenDays') || []; // array of day-of-week indices that are hidden + var isHiddenDayHash = []; // is the day-of-week hidden? (hash with day-of-week-index -> bool) + var cellsPerWeek; + var dayToCellMap = []; // hash from dayIndex -> cellIndex, for one week + var cellToDayMap = []; // hash from cellIndex -> dayIndex, for one week + var isRTL = opt('isRTL'); + + + // initialize important internal variables + (function() { + + if (opt('weekends') === false) { + hiddenDays.push(0, 6); // 0=sunday, 6=saturday + } + + // Loop through a hypothetical week and determine which + // days-of-week are hidden. Record in both hashes (one is the reverse of the other). + for (var dayIndex=0, cellIndex=0; dayIndex<7; dayIndex++) { + dayToCellMap[dayIndex] = cellIndex; + isHiddenDayHash[dayIndex] = $.inArray(dayIndex, hiddenDays) != -1; + if (!isHiddenDayHash[dayIndex]) { + cellToDayMap[cellIndex] = dayIndex; + cellIndex++; + } + } + + cellsPerWeek = cellIndex; + if (!cellsPerWeek) { + throw 'invalid hiddenDays'; // all days were hidden? bad. + } + + })(); + + + // Is the current day hidden? + // `day` is a day-of-week index (0-6), or a Moment + function isHiddenDay(day) { + if (moment.isMoment(day)) { + day = day.day(); + } + return isHiddenDayHash[day]; + } + + + function getCellsPerWeek() { + return cellsPerWeek; + } + + + // Incrementing the current day until it is no longer a hidden day, returning a copy. + // If the initial value of `date` is not a hidden day, don't do anything. + // Pass `isExclusive` as `true` if you are dealing with an end date. + // `inc` defaults to `1` (increment one day forward each time) + function skipHiddenDays(date, inc, isExclusive) { + var out = date.clone(); + inc = inc || 1; + while ( + isHiddenDayHash[(out.day() + (isExclusive ? inc : 0) + 7) % 7] + ) { + out.add(inc, 'days'); + } + return out; + } + + + // + // TRANSFORMATIONS: cell -> cell offset -> day offset -> date + // + + // cell -> date (combines all transformations) + // Possible arguments: + // - row, col + // - { row:#, col: # } + function cellToDate() { + var cellOffset = cellToCellOffset.apply(null, arguments); + var dayOffset = cellOffsetToDayOffset(cellOffset); + var date = dayOffsetToDate(dayOffset); + return date; + } + + // cell -> cell offset + // Possible arguments: + // - row, col + // - { row:#, col:# } + function cellToCellOffset(row, col) { + var colCnt = t.colCnt; + + // rtl variables. wish we could pre-populate these. but where? + var dis = isRTL ? -1 : 1; + var dit = isRTL ? colCnt - 1 : 0; + + if (typeof row == 'object') { + col = row.col; + row = row.row; + } + var cellOffset = row * colCnt + (col * dis + dit); // column, adjusted for RTL (dis & dit) + + return cellOffset; + } + + // cell offset -> day offset + function cellOffsetToDayOffset(cellOffset) { + var day0 = t.start.day(); // first date's day of week + cellOffset += dayToCellMap[day0]; // normlize cellOffset to beginning-of-week + return Math.floor(cellOffset / cellsPerWeek) * 7 + // # of days from full weeks + cellToDayMap[ // # of days from partial last week + (cellOffset % cellsPerWeek + cellsPerWeek) % cellsPerWeek // crazy math to handle negative cellOffsets + ] - + day0; // adjustment for beginning-of-week normalization + } + + // day offset -> date + function dayOffsetToDate(dayOffset) { + return t.start.clone().add(dayOffset, 'days'); + } + + + // + // TRANSFORMATIONS: date -> day offset -> cell offset -> cell + // + + // date -> cell (combines all transformations) + function dateToCell(date) { + var dayOffset = dateToDayOffset(date); + var cellOffset = dayOffsetToCellOffset(dayOffset); + var cell = cellOffsetToCell(cellOffset); + return cell; + } + + // date -> day offset + function dateToDayOffset(date) { + return date.clone().stripTime().diff(t.start, 'days'); + } + + // day offset -> cell offset + function dayOffsetToCellOffset(dayOffset) { + var day0 = t.start.day(); // first date's day of week + dayOffset += day0; // normalize dayOffset to beginning-of-week + return Math.floor(dayOffset / 7) * cellsPerWeek + // # of cells from full weeks + dayToCellMap[ // # of cells from partial last week + (dayOffset % 7 + 7) % 7 // crazy math to handle negative dayOffsets + ] - + dayToCellMap[day0]; // adjustment for beginning-of-week normalization + } + + // cell offset -> cell (object with row & col keys) + function cellOffsetToCell(cellOffset) { + var colCnt = t.colCnt; + + // rtl variables. wish we could pre-populate these. but where? + var dis = isRTL ? -1 : 1; + var dit = isRTL ? colCnt - 1 : 0; + + var row = Math.floor(cellOffset / colCnt); + var col = ((cellOffset % colCnt + colCnt) % colCnt) * dis + dit; // column, adjusted for RTL (dis & dit) + return { + row: row, + col: col + }; + } + + + // + // Converts a date range into an array of segment objects. + // "Segments" are horizontal stretches of time, sliced up by row. + // A segment object has the following properties: + // - row + // - cols + // - isStart + // - isEnd + // + function rangeToSegments(start, end) { + + var rowCnt = t.rowCnt; + var colCnt = t.colCnt; + var segments = []; // array of segments to return + + // day offset for given date range + var dayRange = computeDayRange(start, end); // convert to a whole-day range + var rangeDayOffsetStart = dateToDayOffset(dayRange.start); + var rangeDayOffsetEnd = dateToDayOffset(dayRange.end); // an exclusive value + + // first and last cell offset for the given date range + // "last" implies inclusivity + var rangeCellOffsetFirst = dayOffsetToCellOffset(rangeDayOffsetStart); + var rangeCellOffsetLast = dayOffsetToCellOffset(rangeDayOffsetEnd) - 1; + + // loop through all the rows in the view + for (var row=0; row
' + + '' + // needed for matchCellWidths + htmlEscape(this.opt('weekNumberTitle')) + + '' + + '' + + '' + // needed for matchCellWidths + this.calendar.calculateWeekNumber(this.cellToDate(row, 0)) + + '' + + 's of the "number" row in the DayGrid's content skeleton. + // The number row will only exist if either day numbers or week numbers are turned on. + numberCellHtml: function(row, col, date) { + var classes; + + if (!this.dayNumbersVisible) { // if there are week numbers but not day numbers + return ''; // will create an empty space above events :( + } + + classes = this.dayGrid.getDayClasses(date); + classes.unshift('fc-day-number'); + + return '' + + '' + + date.date() + + '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '
' + + this.timeGrid.headHtml() + // render the day-of-week headers + '
' + + (this.dayGrid ? + '
' + + '
' : + '' + ) + + '
' + + '
' + + '
' + + '
'; + }, + + + // Generates the HTML that will go before the day-of week header cells. + // Queried by the TimeGrid subcomponent when generating rows. Ordering depends on isRTL. + headIntroHtml: function() { + var date; + var weekNumber; + var weekTitle; + var weekText; + + if (this.opt('weekNumbers')) { + date = this.cellToDate(0, 0); + weekNumber = this.calendar.calculateWeekNumber(date); + weekTitle = this.opt('weekNumberTitle'); + + if (this.opt('isRTL')) { + weekText = weekNumber + weekTitle; + } + else { + weekText = weekTitle + weekNumber; + } + + return '' + + '' + + '' + // needed for matchCellWidths + htmlEscape(weekText) + + '' + + ''; + } + else { + return ''; + } + }, + + + // Generates the HTML that goes before the all-day cells. + // Queried by the DayGrid subcomponent when generating rows. Ordering depends on isRTL. + dayIntroHtml: function() { + return '' + + '' + + '' + // needed for matchCellWidths + (this.opt('allDayHtml') || htmlEscape(this.opt('allDayText'))) + + '' + + ''; + }, + + + // Generates the HTML that goes before the bg of the TimeGrid slot area. Long vertical column. + slotBgIntroHtml: function() { + return ''; + }, + + + // Generates the HTML that goes before all other types of cells. + // Affects content-skeleton, helper-skeleton, highlight-skeleton for both the time-grid and day-grid. + // Queried by the TimeGrid and DayGrid subcomponents when generating rows. Ordering depends on isRTL. + introHtml: function() { + return ''; + }, + + + // Generates an HTML attribute string for setting the width of the axis, if it is known + axisStyleAttr: function() { + if (this.axisWidth !== null) { + return 'style="width:' + this.axisWidth + 'px"'; + } + return ''; + }, + + + /* Dimensions + ------------------------------------------------------------------------------------------------------------------*/ + + updateSize: function(isResize) { + if (isResize) { + this.timeGrid.resize(); + } + View.prototype.updateSize.call(this, isResize); + }, + + + // Refreshes the horizontal dimensions of the view + updateWidth: function() { + // make all axis cells line up, and record the width so newly created axis cells will have it + this.axisWidth = matchCellWidths(this.el.find('.fc-axis')); + }, + + + // Adjusts the vertical dimensions of the view to the specified values + setHeight: function(totalHeight, isAuto) { + var eventLimit; + var scrollerHeight; + + if (this.bottomRuleHeight === null) { + // calculate the height of the rule the very first time + this.bottomRuleHeight = this.bottomRuleEl.outerHeight(); + } + this.bottomRuleEl.hide(); // .show() will be called later if this
is necessary + + // reset all dimensions back to the original state + this.scrollerEl.css('overflow', ''); + unsetScroller(this.scrollerEl); + uncompensateScroll(this.noScrollRowEls); + + // limit number of events in the all-day area + if (this.dayGrid) { + this.dayGrid.destroySegPopover(); // kill the "more" popover if displayed + + eventLimit = this.opt('eventLimit'); + if (eventLimit && typeof eventLimit !== 'number') { + eventLimit = AGENDA_ALL_DAY_EVENT_LIMIT; // make sure "auto" goes to a real number + } + if (eventLimit) { + this.dayGrid.limitRows(eventLimit); + } + } + + if (!isAuto) { // should we force dimensions of the scroll container, or let the contents be natural height? + + scrollerHeight = this.computeScrollerHeight(totalHeight); + if (setPotentialScroller(this.scrollerEl, scrollerHeight)) { // using scrollbars? + + // make the all-day and header rows lines up + compensateScroll(this.noScrollRowEls, getScrollbarWidths(this.scrollerEl)); + + // the scrollbar compensation might have changed text flow, which might affect height, so recalculate + // and reapply the desired height to the scroller. + scrollerHeight = this.computeScrollerHeight(totalHeight); + this.scrollerEl.height(scrollerHeight); + + this.restoreScroll(); + } + else { // no scrollbars + // still, force a height and display the bottom rule (marks the end of day) + this.scrollerEl.height(scrollerHeight).css('overflow', 'hidden'); // in case
goes outside + this.bottomRuleEl.show(); + } + } + }, + + + // Sets the scroll value of the scroller to the intial pre-configured state prior to allowing the user to change it. + resetScroll: function() { + var _this = this; + var scrollTime = moment.duration(this.opt('scrollTime')); + var top = this.timeGrid.computeTimeTop(scrollTime); + + // zoom can give weird floating-point values. rather scroll a little bit further + top = Math.ceil(top); + + if (top) { + top++; // to overcome top border that slots beyond the first have. looks better + } + + function scroll() { + _this.scrollerEl.scrollTop(top); + } + + scroll(); + setTimeout(scroll, 0); // overrides any previous scroll state made by the browser + }, + + + /* Events + ------------------------------------------------------------------------------------------------------------------*/ + + + // Renders events onto the view and populates the View's segment array + renderEvents: function(events) { + var dayEvents = []; + var timedEvents = []; + var daySegs = []; + var timedSegs; + var i; + + // separate the events into all-day and timed + for (i = 0; i < events.length; i++) { + if (events[i].allDay) { + dayEvents.push(events[i]); + } + else { + timedEvents.push(events[i]); + } + } + + // render the events in the subcomponents + timedSegs = this.timeGrid.renderEvents(timedEvents); + if (this.dayGrid) { + daySegs = this.dayGrid.renderEvents(dayEvents); + } + + // the all-day area is flexible and might have a lot of events, so shift the height + this.updateHeight(); + + // /View.prototype.renderEvents.call(this, events); // call the super-method + }, + + + // Retrieves all segment objects that are rendered in the view + getSegs: function() { + return this.timeGrid.getSegs().concat( + this.dayGrid ? this.dayGrid.getSegs() : [] + ); + }, + + + // Unrenders all event elements and clears internal segment data + destroyEvents: function() { + View.prototype.destroyEvents.call(this); // do this before the grids' segs have been cleared + + // if destroyEvents is being called as part of an event rerender, renderEvents will be called shortly + // after, so remember what the scroll value was so we can restore it. + this.recordScroll(); + + // destroy the events in the subcomponents + this.timeGrid.destroyEvents(); + if (this.dayGrid) { + this.dayGrid.destroyEvents(); + } + + // we DON'T need to call updateHeight() because: + // A) a renderEvents() call always happens after this, which will eventually call updateHeight() + // B) in IE8, this causes a flash whenever events are rerendered + }, + + + /* Event Dragging + ------------------------------------------------------------------------------------------------------------------*/ + + + // Renders a visual indication of an event being dragged over the view. + // A returned value of `true` signals that a mock "helper" event has been rendered. + renderDrag: function(start, end, seg) { + if (start.hasTime()) { + return this.timeGrid.renderDrag(start, end, seg); + } + else if (this.dayGrid) { + return this.dayGrid.renderDrag(start, end, seg); + } + }, + + + // Unrenders a visual indications of an event being dragged over the view + destroyDrag: function() { + this.timeGrid.destroyDrag(); + if (this.dayGrid) { + this.dayGrid.destroyDrag(); + } + }, + + + /* Selection + ------------------------------------------------------------------------------------------------------------------*/ + + + // Renders a visual indication of a selection + renderSelection: function(start, end) { + if (start.hasTime() || end.hasTime()) { + this.timeGrid.renderSelection(start, end); + } + else if (this.dayGrid) { + this.dayGrid.renderSelection(start, end); + } + }, + + + // Unrenders a visual indications of a selection + destroySelection: function() { + this.timeGrid.destroySelection(); + if (this.dayGrid) { + this.dayGrid.destroySelection(); + } + } + +}); + +;; + +/* A week view with an all-day cell area at the top, and a time grid below +----------------------------------------------------------------------------------------------------------------------*/ +// TODO: a WeekView mixin for calculating dates and titles + +fcViews.agendaWeek = AgendaWeekView; // register the view + +function AgendaWeekView(calendar) { + AgendaView.call(this, calendar); // call the super-constructor +} + + +AgendaWeekView.prototype = createObject(AgendaView.prototype); // define the super-class +$.extend(AgendaWeekView.prototype, { + + name: 'agendaWeek', + + + incrementDate: function(date, delta) { + return date.clone().stripTime().add(delta, 'weeks').startOf('week'); + }, + + + render: function(date) { + + this.intervalStart = date.clone().stripTime().startOf('week'); + this.intervalEnd = this.intervalStart.clone().add(1, 'weeks'); + + this.start = this.skipHiddenDays(this.intervalStart); + this.end = this.skipHiddenDays(this.intervalEnd, -1, true); + + this.title = this.calendar.formatRange( + this.start, + this.end.clone().subtract(1), // make inclusive by subtracting 1 ms + this.opt('titleFormat'), + ' \u2014 ' // emphasized dash + ); + + AgendaView.prototype.render.call(this, this.getCellsPerWeek()); // call the super-method + } + +}); + +;; + +/* A day view with an all-day cell area at the top, and a time grid below +----------------------------------------------------------------------------------------------------------------------*/ + +fcViews.agendaDay = AgendaDayView; // register the view + +function AgendaDayView(calendar) { + AgendaView.call(this, calendar); // call the super-constructor +} + + +AgendaDayView.prototype = createObject(AgendaView.prototype); // define the super-class +$.extend(AgendaDayView.prototype, { + + name: 'agendaDay', + + + incrementDate: function(date, delta) { + var out = date.clone().stripTime().add(delta, 'days'); + out = this.skipHiddenDays(out, delta < 0 ? -1 : 1); + return out; + }, + + + render: function(date) { + + this.start = this.intervalStart = date.clone().stripTime(); + this.end = this.intervalEnd = this.start.clone().add(1, 'days'); + + this.title = this.calendar.formatDate(this.start, this.opt('titleFormat')); + + AgendaView.prototype.render.call(this, 1); // call the super-method + } + +}); + +;; + +/* A component that renders a grid of whole-days that runs horizontally. There can be multiple rows, one per week. +----------------------------------------------------------------------------------------------------------------------*/ + +function ResourceDayGrid(view) { + DayGrid.call(this, view); // call the super-constructor +} + + +ResourceDayGrid.prototype = createObject(DayGrid.prototype); // declare the super-class +$.extend(ResourceDayGrid.prototype, { + rangeToSegs: function(rangeStart, rangeEnd, resourceIds) { + var col, segments = []; + + var currentDate = this.view.calendar.getDate(); + if((rangeStart <= currentDate) && (currentDate < rangeEnd)) { + var view = this.view; + var resources = view.calendar.fetchResources(); + + for (col = 0; col < view.colCnt; col++) { + var resource = resources[col]; + if (resourceIds.indexOf(resource.id) > -1){ + segments.push({ + row: 0, + leftCol: col, + rightCol: col, + isStart: true, + isEnd: true + }); + } + } + } + + return segments; + } +}); +;; + +/* Event-rendering methods for the DayGrid class +----------------------------------------------------------------------------------------------------------------------*/ + +$.extend(ResourceDayGrid.prototype, { + eventToSegs: function(event, intervalStart, intervalEnd) { + var eventStart = event.start.clone().stripZone(); // normalize + var eventEnd = this.view.calendar.getEventEnd(event).stripZone(); // compute (if necessary) and normalize + var segs; + var i, seg; + + if (intervalStart && intervalEnd) { + seg = intersectionToSeg(eventStart, eventEnd, intervalStart, intervalEnd); + segs = seg ? [ seg ] : []; + } + else { + segs = this.rangeToSegs(eventStart, eventEnd, event.resources); // defined by the subclass + } + + // assign extra event-related properties to the segment objects + for (i = 0; i < segs.length; i++) { + seg = segs[i]; + seg.event = event; + seg.eventStartMS = +eventStart; + seg.eventDurationMS = eventEnd - eventStart; + } + + return segs; + } +}); +;; + +/* A component that renders one or more columns of vertical time slots +----------------------------------------------------------------------------------------------------------------------*/ + +function ResourceTimeGrid(view) { + TimeGrid.call(this, view); // call the super-constructor +} + + +ResourceTimeGrid.prototype = createObject(TimeGrid.prototype); // define the super-class +$.extend(ResourceTimeGrid.prototype, { +// Slices up a date range into a segment for each column +// Each column represents a resource. An event can be assigned to multiple resources +// so we need to build segs accordingly + rangeToSegs: function(rangeStart, rangeEnd, resourceIds) { + var view = this.view; + var segs = []; + var seg; + var col; + var cellDate; + var colStart, colEnd; + + var resources = view.calendar.fetchResources(); + + // normalize + rangeStart = rangeStart.clone().stripZone(); + rangeEnd = rangeEnd.clone().stripZone(); + + for (col = 0; col < view.colCnt; col++) { + var resource = resources[col]; + if (resourceIds.indexOf(resource.id) > -1){ + cellDate = view.cellToDate(0, col); // use the View's cell system for this + colStart = cellDate.clone().time(this.minTime); + colEnd = cellDate.clone().time(this.maxTime); + seg = intersectionToSeg(rangeStart, rangeEnd, colStart, colEnd); + if (seg) { + seg.col = col; + segs.push(seg); + } + } + } + + return segs; + } + +}); + +;; + +/* Event-rendering methods for the TimeGrid class +----------------------------------------------------------------------------------------------------------------------*/ + +$.extend(ResourceTimeGrid.prototype, { +// Slices a single event into an array of event segments. + // When `intervalStart` and `intervalEnd` are specified, intersect the events with that interval. + // Otherwise, let the subclass decide how it wants to slice the segments over the grid. + eventToSegs: function(event, intervalStart, intervalEnd) { + var eventStart = event.start.clone().stripZone(); // normalize + var eventEnd = this.view.calendar.getEventEnd(event).stripZone(); // compute (if necessary) and normalize + var segs; + var i, seg; + + if (intervalStart && intervalEnd) { + seg = intersectionToSeg(eventStart, eventEnd, intervalStart, intervalEnd); + segs = seg ? [ seg ] : []; + } + else { + segs = this.rangeToSegs(eventStart, eventEnd, event.resources); // defined by the subclass + } + + // assign extra event-related properties to the segment objects + for (i = 0; i < segs.length; i++) { + seg = segs[i]; + seg.event = event; + seg.eventStartMS = +eventStart; + seg.eventDurationMS = eventEnd - eventStart; + } + + return segs; + } +}); + +;; + +/* An abstract class for all agenda-related views. Displays one more columns with time slots running vertically. +----------------------------------------------------------------------------------------------------------------------*/ +// Is a manager for the TimeGrid subcomponent and possibly the DayGrid subcomponent (if allDaySlot is on). +// Responsible for managing width/height. + +setDefaults({ + allDaySlot: true, + allDayText: 'all-day', + + scrollTime: '06:00:00', + + slotDuration: '00:30:00', + + axisFormat: generateAgendaAxisFormat, + timeFormat: { + agenda: generateAgendaTimeFormat + }, + + minTime: '00:00:00', + maxTime: '24:00:00', + slotEventOverlap: true +}); + +function ResourceView(calendar) { + View.call(this, calendar); // call the super-constructor + + // overrides - the view.js should expose these on the prototype then we wouldn't have to do this. + this.cellToDate = ResourceView.prototype.cellToDate; + + this.timeGrid = new ResourceTimeGrid(this); + + if (this.opt('allDaySlot')) { // should we display the "all-day" area? + this.dayGrid = new ResourceDayGrid(this); // the all-day subcomponent of this view + + // the coordinate grid will be a combination of both subcomponents' grids + this.coordMap = new ComboCoordMap([ + this.dayGrid.coordMap, + this.timeGrid.coordMap + ]); + } + else { + this.coordMap = this.timeGrid.coordMap; + } +} + + +ResourceView.prototype = createObject(AgendaView.prototype); // define the super-class +$.extend(ResourceView.prototype, { + + cellToDate: function() { + return this.start.clone(); + }, + + // fix the classes, etc. + headCellHtml: function(row, col, date) { + var view = this; + var calendar = view.calendar; + var resource = calendar.fetchResources()[col]; + return '' + + '' + + htmlEscape(resource.name) + + ''; + } + +}); + +;; + +/* A day view with an all-day cell area at the top, and a time grid below by resource +----------------------------------------------------------------------------------------------------------------------*/ + +fcViews.resourceDay = ResourceDayView; + +function ResourceDayView(calendar) { // TODO: make a ResourceView mixin + ResourceView.call(this, calendar); // call the super-constructor +} + +ResourceDayView.prototype = createObject(ResourceView.prototype); // define the super-class +$.extend(ResourceDayView.prototype, { + + name: 'resourceDay', + + + incrementDate: function(date, delta) { + var out = date.clone().stripTime().add(delta, 'days'); + out = this.skipHiddenDays(out, delta < 0 ? -1 : 1); + return out; + }, + + + render: function(date) { + this.start = this.intervalStart = date.clone().stripTime(); + this.end = this.intervalEnd = this.start.clone().add(1, 'days'); + + this.title = this.calendar.formatDate(this.start, this.opt('titleFormat')); + + ResourceView.prototype.render.call(this, this.calendar.fetchResources().length); // call the super-method + }, + + // Computes HTML classNames for a single-day cell + getDayClasses: function(date) { + var view = this.view; + var today = view.calendar.getNow().stripTime(); + var classes = [ 'fc-' + dayIDs[date.day()] ]; + + if ( + view.name === 'month' && + date.month() != view.intervalStart.month() + ) { + classes.push('fc-other-month'); + } + + if (date.isSame(today, 'day')) { + classes.push( + 'fc-todaysss', + view.highlightStateClass + ); + } + else if (date < today) { + classes.push('fc-past'); + } + else { + classes.push('fc-future'); + } + + return classes; + } + +}); +;; + }); \ No newline at end of file diff --git a/dist/fullcalendar.min.css b/dist/fullcalendar.min.css index 2858443..31aaeb4 100644 --- a/dist/fullcalendar.min.css +++ b/dist/fullcalendar.min.css @@ -1,5 +1,5 @@ -/*! - * FullCalendar v2.1.1 Stylesheet - * Docs & License: http://arshaw.com/fullcalendar/ - * (c) 2013 Adam Shaw +/*! + * FullCalendar v2.1.1 Stylesheet + * Docs & License: http://arshaw.com/fullcalendar/ + * (c) 2013 Adam Shaw */.fc{direction:ltr;text-align:left}.fc-rtl{text-align:right}body .fc{font-size:1em}.fc-unthemed .fc-popover,.fc-unthemed .fc-row,.fc-unthemed hr,.fc-unthemed tbody,.fc-unthemed td,.fc-unthemed th,.fc-unthemed thead{border-color:#ddd}.fc-unthemed .fc-popover{background-color:#fff}.fc-unthemed .fc-popover .fc-header,.fc-unthemed hr{background:#eee}.fc-unthemed .fc-popover .fc-header .fc-close{color:#666}.fc-unthemed .fc-today{background:#fcf8e3}.fc-highlight{background:#bce8f1;opacity:.3;filter:alpha(opacity=30)}.fc-icon{display:inline-block;font-size:2em;line-height:.5em;height:.5em;font-family:"Courier New",Courier,monospace}.fc-icon-left-single-arrow:after{content:"\02039";font-weight:700}.fc-icon-right-single-arrow:after{content:"\0203A";font-weight:700}.fc-icon-left-double-arrow:after{content:"\000AB"}.fc-icon-right-double-arrow:after{content:"\000BB"}.fc-icon-x:after{content:"\000D7"}.fc button{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;margin:0;height:2.1em;padding:0 .6em;font-size:1em;white-space:nowrap;cursor:pointer}.fc button::-moz-focus-inner{margin:0;padding:0}.fc-state-default{border:1px solid}.fc-state-default.fc-corner-left{border-top-left-radius:4px;border-bottom-left-radius:4px}.fc-state-default.fc-corner-right{border-top-right-radius:4px;border-bottom-right-radius:4px}.fc button .fc-icon{position:relative;top:.05em;margin:0 .1em}.fc-state-default{background-color:#f5f5f5;background-image:-moz-linear-gradient(top,#fff,#e6e6e6);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fff),to(#e6e6e6));background-image:-webkit-linear-gradient(top,#fff,#e6e6e6);background-image:-o-linear-gradient(top,#fff,#e6e6e6);background-image:linear-gradient(to bottom,#fff,#e6e6e6);background-repeat:repeat-x;border-color:#e6e6e6 #e6e6e6 #bfbfbf;border-color:rgba(0,0,0,.1) rgba(0,0,0,.1) rgba(0,0,0,.25);color:#333;text-shadow:0 1px 1px rgba(255,255,255,.75);box-shadow:inset 0 1px 0 rgba(255,255,255,.2),0 1px 2px rgba(0,0,0,.05)}.fc-state-active,.fc-state-disabled,.fc-state-down,.fc-state-hover{color:#333;background-color:#e6e6e6}.fc-state-hover{color:#333;text-decoration:none;background-position:0 -15px;-webkit-transition:background-position .1s linear;-moz-transition:background-position .1s linear;-o-transition:background-position .1s linear;transition:background-position .1s linear}.fc-state-active,.fc-state-down{background-color:#ccc;background-image:none;box-shadow:inset 0 2px 4px rgba(0,0,0,.15),0 1px 2px rgba(0,0,0,.05)}.fc-state-disabled{cursor:default;background-image:none;opacity:.65;filter:alpha(opacity=65);box-shadow:none}.fc-button-group{display:inline-block}.fc .fc-button-group>*{float:left;margin:0 0 0 -1px}.fc .fc-button-group>:first-child{margin-left:0}.fc-popover{position:absolute;box-shadow:0 2px 6px rgba(0,0,0,.15)}.fc-popover .fc-header{padding:2px 4px}.fc-popover .fc-header .fc-title{margin:0 2px}.fc-popover .fc-header .fc-close{cursor:pointer}.fc-ltr .fc-popover .fc-header .fc-title,.fc-rtl .fc-popover .fc-header .fc-close{float:left}.fc-ltr .fc-popover .fc-header .fc-close,.fc-rtl .fc-popover .fc-header .fc-title{float:right}.fc-unthemed .fc-popover{border-width:1px;border-style:solid}.fc-unthemed .fc-popover .fc-header .fc-close{font-size:25px;margin-top:4px}.fc-popover>.ui-widget-header+.ui-widget-content{border-top:0}.fc hr{height:0;margin:0;padding:0 0 2px;border-style:solid;border-width:1px 0}.fc-clear{clear:both}.fc-bg,.fc-helper-skeleton,.fc-highlight-skeleton{position:absolute;top:0;left:0;right:0}.fc-bg{bottom:0}.fc-bg table{height:100%}.fc table{width:100%;table-layout:fixed;border-collapse:collapse;border-spacing:0;font-size:1em}.fc th{text-align:center}.fc td,.fc th{border-style:solid;border-width:1px;padding:0;vertical-align:top}.fc td.fc-today{border-style:double}.fc .fc-row{border-style:solid;border-width:0}.fc-row table{border-left:0 hidden transparent;border-right:0 hidden transparent;border-bottom:0 hidden transparent}.fc-row:first-child table{border-top:0 hidden transparent}.fc-row{position:relative}.fc-row .fc-bg{z-index:1}.fc-row .fc-highlight-skeleton{z-index:2;bottom:0}.fc-row .fc-highlight-skeleton table{height:100%}.fc-row .fc-highlight-skeleton td{border-color:transparent}.fc-row .fc-content-skeleton{position:relative;z-index:3;padding-bottom:2px}.fc-row .fc-helper-skeleton{z-index:4}.fc-row .fc-content-skeleton td,.fc-row .fc-helper-skeleton td{background:0 0;border-color:transparent;border-bottom:0}.fc-row .fc-content-skeleton tbody td,.fc-row .fc-helper-skeleton tbody td{border-top:0}.fc-scroller{overflow-y:scroll;overflow-x:hidden}.fc-scroller>*{position:relative;width:100%;overflow:hidden}.fc-event{position:relative;display:block;font-size:.85em;line-height:1.3;border-radius:3px;border:1px solid #3a87ad;background-color:#3a87ad;font-weight:400}.fc-event,.fc-event:hover,.ui-widget .fc-event{color:#fff;text-decoration:none}.fc-event.fc-draggable,.fc-event[href]{cursor:pointer}.fc-day-grid-event{margin:1px 2px 0;padding:0 1px}.fc-ltr .fc-day-grid-event.fc-not-start,.fc-rtl .fc-day-grid-event.fc-not-end{margin-left:0;border-left-width:0;padding-left:1px;border-top-left-radius:0;border-bottom-left-radius:0}.fc-ltr .fc-day-grid-event.fc-not-end,.fc-rtl .fc-day-grid-event.fc-not-start{margin-right:0;border-right-width:0;padding-right:1px;border-top-right-radius:0;border-bottom-right-radius:0}.fc-day-grid-event>.fc-content{white-space:nowrap;overflow:hidden}.fc-day-grid-event .fc-time{font-weight:700}.fc-day-grid-event .fc-resizer{position:absolute;top:0;bottom:0;width:7px}.fc-ltr .fc-day-grid-event .fc-resizer{right:-3px;cursor:e-resize}.fc-rtl .fc-day-grid-event .fc-resizer{left:-3px;cursor:w-resize}a.fc-more{margin:1px 3px;font-size:.85em;cursor:pointer;text-decoration:none}a.fc-more:hover{text-decoration:underline}.fc-limited{display:none}.fc-day-grid .fc-row{z-index:1}.fc-more-popover{z-index:2;width:220px}.fc-more-popover .fc-event-container{padding:10px}.fc-toolbar{text-align:center;margin-bottom:1em}.fc-toolbar .fc-left{float:left}.fc-toolbar .fc-right{float:right}.fc-toolbar .fc-center{display:inline-block}.fc .fc-toolbar>*>*{float:left;margin-left:.75em}.fc .fc-toolbar>*>:first-child{margin-left:0}.fc-toolbar h2{margin:0}.fc-toolbar button{position:relative}.fc-toolbar .fc-state-hover,.fc-toolbar .ui-state-hover{z-index:2}.fc-toolbar .fc-state-down{z-index:3}.fc-toolbar .fc-state-active,.fc-toolbar .ui-state-active{z-index:4}.fc-toolbar button:focus{z-index:5}.fc-view-container *,.fc-view-container :after,.fc-view-container :before{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}.fc-view,.fc-view>table{position:relative;z-index:1}.fc-basicDay-view .fc-content-skeleton,.fc-basicWeek-view .fc-content-skeleton{padding-top:1px;padding-bottom:1em}.fc-basic-view tbody .fc-row{min-height:4em}.fc-row.fc-rigid{overflow:hidden}.fc-row.fc-rigid .fc-content-skeleton{position:absolute;top:0;left:0;right:0}.fc-basic-view .fc-day-number,.fc-basic-view .fc-week-number{padding:0 2px}.fc-basic-view td.fc-day-number,.fc-basic-view td.fc-week-number span{padding-top:2px;padding-bottom:2px}.fc-basic-view .fc-week-number{text-align:center}.fc-basic-view .fc-week-number span{display:inline-block;min-width:1.25em}.fc-ltr .fc-basic-view .fc-day-number{text-align:right}.fc-rtl .fc-basic-view .fc-day-number{text-align:left}.fc-day-number.fc-other-month{opacity:.3;filter:alpha(opacity=30)}.fc-agenda-view .fc-day-grid{position:relative;z-index:2}.fc-agenda-view .fc-day-grid .fc-row{min-height:3em}.fc-agenda-view .fc-day-grid .fc-row .fc-content-skeleton{padding-top:1px;padding-bottom:1em}.fc .fc-axis{vertical-align:middle;padding:0 4px;white-space:nowrap}.fc-ltr .fc-axis{text-align:right}.fc-rtl .fc-axis{text-align:left}.ui-widget td.fc-axis{font-weight:400}.fc-time-grid,.fc-time-grid-container{position:relative;z-index:1}.fc-time-grid{min-height:100%}.fc-time-grid table{border:0 hidden transparent}.fc-time-grid>.fc-bg{z-index:1}.fc-time-grid .fc-slats,.fc-time-grid>hr{position:relative;z-index:2}.fc-time-grid .fc-highlight-skeleton{z-index:3}.fc-time-grid .fc-content-skeleton{position:absolute;z-index:4;top:0;left:0;right:0}.fc-time-grid>.fc-helper-skeleton{z-index:5}.fc-slats td{height:1.5em;border-bottom:0}.fc-slats .fc-minor td{border-top-style:dotted}.fc-slats .ui-widget-content{background:0 0}.fc-time-grid .fc-highlight-container{position:relative}.fc-time-grid .fc-highlight{position:absolute;left:0;right:0}.fc-time-grid .fc-event-container{position:relative}.fc-ltr .fc-time-grid .fc-event-container{margin:0 2.5% 0 2px}.fc-rtl .fc-time-grid .fc-event-container{margin:0 2px 0 2.5%}.fc-time-grid .fc-event{position:absolute;z-index:1}.fc-time-grid-event.fc-not-start{border-top-width:0;padding-top:1px;border-top-left-radius:0;border-top-right-radius:0}.fc-time-grid-event.fc-not-end{border-bottom-width:0;padding-bottom:1px;border-bottom-left-radius:0;border-bottom-right-radius:0}.fc-time-grid-event{overflow:hidden}.fc-time-grid-event>.fc-content{position:relative;z-index:2}.fc-time-grid-event .fc-time,.fc-time-grid-event .fc-title{padding:0 1px}.fc-time-grid-event .fc-time{font-size:.85em;white-space:nowrap}.fc-time-grid-event .fc-bg{z-index:1;background:#fff;opacity:.25;filter:alpha(opacity=25)}.fc-time-grid-event.fc-short .fc-content{white-space:nowrap}.fc-time-grid-event.fc-short .fc-time,.fc-time-grid-event.fc-short .fc-title{display:inline-block;vertical-align:top}.fc-time-grid-event.fc-short .fc-time span{display:none}.fc-time-grid-event.fc-short .fc-time:before{content:attr(data-start)}.fc-time-grid-event.fc-short .fc-time:after{content:"\000A0-\000A0"}.fc-time-grid-event.fc-short .fc-title{font-size:.85em;padding:0}.fc-time-grid-event .fc-resizer{position:absolute;z-index:3;left:0;right:0;bottom:0;height:8px;overflow:hidden;line-height:8px;font-size:11px;font-family:monospace;text-align:center;cursor:s-resize}.fc-time-grid-event .fc-resizer:after{content:"="} \ No newline at end of file diff --git a/dist/fullcalendar.min.js b/dist/fullcalendar.min.js index aa83c57..93bbc04 100644 --- a/dist/fullcalendar.min.js +++ b/dist/fullcalendar.min.js @@ -3,6 +3,6 @@ * Docs & License: http://arshaw.com/fullcalendar/ * (c) 2013 Adam Shaw */ -(function(t){"function"==typeof define&&define.amd?define(["jquery","moment"],t):t(jQuery,moment)})(function(t,e){function i(t,e){return e.longDateFormat("LT").replace(":mm","(:mm)").replace(/(\Wmm)$/,"($1)").replace(/\s*a$/i,"t")}function n(t,e){var i=e.longDateFormat("L");return i=i.replace(/^Y+[^\w\s]*|[^\w\s]*Y+$/g,""),t.isRTL?i+=" ddd":i="ddd "+i,i}function r(t){o(De,t)}function o(e){function i(i,n){t.isPlainObject(n)&&t.isPlainObject(e[i])&&!s(i)?e[i]=o({},e[i],n):void 0!==n&&(e[i]=n)}for(var n=1;arguments.length>n;n++)t.each(arguments[n],i);return e}function s(t){return/(Time|Duration)$/.test(t)}function l(i,n){function r(t){var i=e.localeData||e.langData;return i.call(e,t)||i.call(e,"en")}function s(t){ie?h()&&(p(),f(t)):l()}function l(){ne=K.theme?"ui":"fc",i.addClass("fc"),K.isRTL?i.addClass("fc-rtl"):i.addClass("fc-ltr"),K.theme?i.addClass("ui-widget"):i.addClass("fc-unthemed"),ie=t("
").prependTo(i),te=new a(q,K),ee=te.render(),ee&&i.prepend(ee),u(K.defaultView),K.handleWindowResize&&(se=L(v,K.windowResizeDelay),t(window).resize(se))}function d(){re&&re.destroy(),te.destroy(),ie.remove(),i.removeClass("fc fc-ltr fc-rtl fc-unthemed ui-widget"),t(window).unbind("resize",se)}function h(){return i.is(":visible")}function u(t){f(0,t)}function f(e,i){he++,re&&i&&re.name!==i&&(te.deactivateButton(re.name),I(),re.start&&re.destroy(),re.el.remove(),re=null),!re&&i&&(re=new xe[i](q),re.el=t("
").appendTo(ie),te.activateButton(i)),re&&(e&&(le=re.incrementDate(le,e)),re.start&&!e&&le.isWithin(re.intervalStart,re.intervalEnd)||h()&&(I(),re.start&&re.destroy(),re.render(le),Z(),C(),x(),b())),Z(),he--}function g(t){return h()?(t&&m(),he++,re.updateSize(!0),he--,!0):void 0}function p(){h()&&m()}function m(){oe="number"==typeof K.contentHeight?K.contentHeight:"number"==typeof K.height?K.height-(ee?ee.outerHeight(!0):0):Math.round(ie.width()/Math.max(K.aspectRatio,.5))}function v(t){!he&&t.target===window&&re.start&&g(!0)&&re.trigger("windowResize",de)}function y(){E(),S()}function w(){h()&&(I(),re.destroyEvents(),re.renderEvents(ue),Z())}function E(){I(),re.destroyEvents(),Z()}function b(){!K.lazyFetching||ae(re.start,re.end)?S():w()}function S(){ce(re.start,re.end)}function D(t){ue=t,w()}function T(){w()}function C(){te.updateTitle(re.title)}function x(){var t=q.getNow();t.isWithin(re.intervalStart,re.intervalEnd)?te.disableButton("today"):te.enableButton("today")}function k(t,e){t=q.moment(t),e=e?q.moment(e):t.hasTime()?t.clone().add(q.defaultTimedEventDuration):t.clone().add(q.defaultAllDayEventDuration),re.select(t,e)}function M(){re&&re.unselect()}function R(){f(-1)}function P(){f(1)}function G(){le.add(-1,"years"),f()}function N(){le.add(1,"years"),f()}function Y(){le=q.getNow(),f()}function A(t){le=q.moment(t),f()}function _(t){le.add(e.duration(t)),f()}function O(t,e){var i,n;e&&void 0!==xe[e]||(e=e||"day",i=te.getViewsWithButtons().join(" "),n=i.match(RegExp("\\w+"+z(e))),n||(n=i.match(/\w+Day/)),e=n?n[0]:"agendaDay"),le=t,u(e)}function F(){return le.clone()}function I(){ie.css({width:"100%",height:ie.height(),overflow:"hidden"})}function Z(){ie.css({width:"",height:"",overflow:""})}function B(){return q}function j(){return re}function X(t,e){return void 0===e?K[t]:(("height"==t||"contentHeight"==t||"aspectRatio"==t)&&(K[t]=e,g(!0)),void 0)}function $(t,e){return K[t]?K[t].apply(e||de,Array.prototype.slice.call(arguments,2)):void 0}var q=this;n=n||{};var U,K=o({},De,n);U=K.lang in Te?Te[K.lang]:Te[De.lang],U&&(K=o({},De,U,n)),K.isRTL&&(K=o({},De,Ce,U||{},n)),q.options=K,q.render=s,q.destroy=d,q.refetchEvents=y,q.reportEvents=D,q.reportEventChange=T,q.rerenderEvents=w,q.changeView=u,q.select=k,q.unselect=M,q.prev=R,q.next=P,q.prevYear=G,q.nextYear=N,q.today=Y,q.gotoDate=A,q.incrementDate=_,q.zoomTo=O,q.getDate=F,q.getCalendar=B,q.getView=j,q.option=X,q.trigger=$;var Q=H(r(K.lang));if(K.monthNames&&(Q._months=K.monthNames),K.monthNamesShort&&(Q._monthsShort=K.monthNamesShort),K.dayNames&&(Q._weekdays=K.dayNames),K.dayNamesShort&&(Q._weekdaysShort=K.dayNamesShort),null!=K.firstDay){var J=H(Q._week);J.dow=K.firstDay,Q._week=J}q.defaultAllDayEventDuration=e.duration(K.defaultAllDayEventDuration),q.defaultTimedEventDuration=e.duration(K.defaultTimedEventDuration),q.moment=function(){var t;return"local"===K.timezone?(t=He.moment.apply(null,arguments),t.hasTime()&&t.local()):t="UTC"===K.timezone?He.moment.utc.apply(null,arguments):He.moment.parseZone.apply(null,arguments),"_locale"in t?t._locale=Q:t._lang=Q,t},q.getIsAmbigTimezone=function(){return"local"!==K.timezone&&"UTC"!==K.timezone},q.rezoneDate=function(t){return q.moment(t.toArray())},q.getNow=function(){var t=K.now;return"function"==typeof t&&(t=t()),q.moment(t)},q.calculateWeekNumber=function(t){var e=K.weekNumberCalculation;return"function"==typeof e?e(t):"local"===e?t.week():"ISO"===e.toUpperCase()?t.isoWeek():void 0},q.getEventEnd=function(t){return t.end?t.end.clone():q.getDefaultEventEnd(t.allDay,t.start)},q.getDefaultEventEnd=function(t,e){var i=e.clone();return t?i.stripTime().add(q.defaultAllDayEventDuration):i.add(q.defaultTimedEventDuration),q.getIsAmbigTimezone()&&i.stripZone(),i},q.formatRange=function(t,e,i){return"function"==typeof i&&(i=i.call(q,K,Q)),W(t,e,i,null,K.isRTL)},q.formatDate=function(t,e){return"function"==typeof e&&(e=e.call(q,K,Q)),V(t,e)},c.call(q,K);var te,ee,ie,ne,re,oe,se,le,ae=q.isFetchNeeded,ce=q.fetchEvents,de=i[0],he=0,ue=[];le=null!=K.defaultDate?q.moment(K.defaultDate):q.getNow(),q.getSuggestedViewHeight=function(){return void 0===oe&&p(),oe},q.isHeightAuto=function(){return"auto"===K.contentHeight||"auto"===K.height}}function a(e,i){function n(){var e=i.header;return f=i.theme?"ui":"fc",e?g=t("
").append(o("left")).append(o("right")).append(o("center")).append('
'):void 0}function r(){g.remove()}function o(n){var r=t('
'),o=i.header[n];return o&&t.each(o.split(" "),function(){var n,o=t(),s=!0;t.each(this.split(","),function(n,r){var l,a,c,d,h,u,g,m;"title"==r?(o=o.add(t("

 

")),s=!1):(e[r]?l=function(){e[r]()}:xe[r]&&(l=function(){e.changeView(r)},p.push(r)),l&&(a=S(i.themeButtonIcons,r),c=S(i.buttonIcons,r),d=S(i.defaultButtonText,r),h=S(i.buttonText,r),u=h?R(h):a&&i.theme?"":c&&!i.theme?"":R(d||r),g=["fc-"+r+"-button",f+"-button",f+"-state-default"],m=t('").click(function(){m.hasClass(f+"-state-disabled")||(l(),(m.hasClass(f+"-state-active")||m.hasClass(f+"-state-disabled"))&&m.removeClass(f+"-state-hover"))}).mousedown(function(){m.not("."+f+"-state-active").not("."+f+"-state-disabled").addClass(f+"-state-down")}).mouseup(function(){m.removeClass(f+"-state-down")}).hover(function(){m.not("."+f+"-state-active").not("."+f+"-state-disabled").addClass(f+"-state-hover")},function(){m.removeClass(f+"-state-hover").removeClass(f+"-state-down")}),o=o.add(m)))}),s&&o.first().addClass(f+"-corner-left").end().last().addClass(f+"-corner-right").end(),o.length>1?(n=t("
"),s&&n.addClass("fc-button-group"),n.append(o),r.append(n)):r.append(o)}),r}function s(t){g.find("h2").text(t)}function l(t){g.find(".fc-"+t+"-button").addClass(f+"-state-active")}function a(t){g.find(".fc-"+t+"-button").removeClass(f+"-state-active")}function c(t){g.find(".fc-"+t+"-button").attr("disabled","disabled").addClass(f+"-state-disabled")}function d(t){g.find(".fc-"+t+"-button").removeAttr("disabled").removeClass(f+"-state-disabled")}function h(){return p}var u=this;u.render=n,u.destroy=r,u.updateTitle=s,u.activateButton=l,u.deactivateButton=a,u.disableButton=c,u.enableButton=d,u.getViewsWithButtons=h;var f,g=t(),p=[]}function c(e){function i(t,e){return!T||t.clone().stripZone()C.clone().stripZone()}function n(t,e){T=t,C=e,A=[];var i=++G,n=L.length;N=n;for(var o=0;n>o;o++)r(L[o],i)}function r(e,i){o(e,function(n){var r,o,s=t.isArray(e.events);if(i==G){if(n)for(r=0;n.length>r;r++)o=n[r],s||(o=w(o,e)),o&&A.push(o);N--,N||R(A)}})}function o(i,n){var r,s,l=He.sourceFetchers;for(r=0;l.length>r;r++){if(s=l[r].call(S,i,T.clone(),C.clone(),e.timezone,n),s===!0)return;if("object"==typeof s)return o(s,n),void 0}var a=i.events;if(a)t.isFunction(a)?(v(),a.call(S,T.clone(),C.clone(),e.timezone,function(t){n(t),y()})):t.isArray(a)?n(a):n();else{var c=i.url;if(c){var d,h=i.success,u=i.error,f=i.complete;d=t.isFunction(i.data)?i.data():i.data;var g=t.extend({},d||{}),p=M(i.startParam,e.startParam),m=M(i.endParam,e.endParam),w=M(i.timezoneParam,e.timezoneParam);p&&(g[p]=T.format()),m&&(g[m]=C.format()),e.timezone&&"local"!=e.timezone&&(g[w]=e.timezone),v(),t.ajax(t.extend({},ke,i,{data:g,success:function(e){e=e||[];var i=k(h,this,arguments);t.isArray(i)&&(e=i),n(e)},error:function(){k(u,this,arguments),n()},complete:function(){k(f,this,arguments),y()}}))}else n()}}function s(t){var e=l(t);e&&(L.push(e),N++,r(e,G))}function l(e){var i,n,r=He.sourceNormalizers;if(t.isFunction(e)||t.isArray(e)?i={events:e}:"string"==typeof e?i={url:e}:"object"==typeof e&&(i=t.extend({},e)),i){for(i.className?"string"==typeof i.className&&(i.className=i.className.split(/\s+/)):i.className=[],t.isArray(i.events)&&(i.origArray=i.events,i.events=t.map(i.events,function(t){return w(t,i)})),n=0;r.length>n;n++)r[n].call(S,i);return i}}function a(e){L=t.grep(L,function(t){return!c(t,e)}),A=t.grep(A,function(t){return!c(t.source,e)}),R(A)}function c(t,e){return t&&e&&h(t)==h(e)}function h(t){return("object"==typeof t?t.origArray||t.url||t.events:null)||t}function u(t){t.start=S.moment(t.start),t.end&&(t.end=S.moment(t.end)),E(t),f(t),R(A)}function f(t){var e,i,n,r;for(e=0;A.length>e;e++)if(i=A[e],i._id==t._id&&i!==t)for(n=0;V.length>n;n++)r=V[n],void 0!==t[r]&&(i[r]=t[r])}function g(t,e){var i=w(t);i&&(i.source||(e&&(z.events.push(i),i.source=z),A.push(i)),R(A))}function p(e){var i,n;for(null==e?e=function(){return!0}:t.isFunction(e)||(i=e+"",e=function(t){return t._id==i}),A=t.grep(A,e,!0),n=0;L.length>n;n++)t.isArray(L[n].events)&&(L[n].events=t.grep(L[n].events,e,!0));R(A)}function m(e){return t.isFunction(e)?t.grep(A,e):null!=e?(e+="",t.grep(A,function(t){return t._id==e})):A}function v(){Y++||H("loading",null,!0,x())}function y(){--Y||H("loading",null,!1,x())}function w(i,n){var r,o,s,l,a={};return e.eventDataTransform&&(i=e.eventDataTransform(i)),n&&n.eventDataTransform&&(i=n.eventDataTransform(i)),r=S.moment(i.start||i.date),r.isValid()&&(o=null,!i.end||(o=S.moment(i.end),o.isValid()))?(s=i.allDay,void 0===s&&(l=M(n?n.allDayDefault:void 0,e.allDayDefault),s=void 0!==l?l:!(r.hasTime()||o&&o.hasTime())),s?(r.hasTime()&&r.stripTime(),o&&o.hasTime()&&o.stripTime()):(r.hasTime()||(r=S.rezoneDate(r)),o&&!o.hasTime()&&(o=S.rezoneDate(o))),t.extend(a,i),n&&(a.source=n),a._id=i._id||(void 0===i.id?"_fc"+Me++:i.id+""),a.className=i.className?"string"==typeof i.className?i.className.split(/\s+/):i.className:[],a.resources?"string"==typeof a.resources&&(a.resources=a.resources.split(/\s+/)):a.resources=[],a.allDay=s,a.start=r,a.end=o,e.forceEventDuration&&!a.end&&(a.end=P(a)),d(a),a):void 0}function E(t,e,i){var n,r,o,s,l=t._allDay,a=t._start,c=t._end,d=!1;return e||i||(e=t.start,i=t.end),n=t.allDay!=l?t.allDay:!(e||i).hasTime(),n&&(e&&(e=e.clone().stripTime()),i&&(i=i.clone().stripTime())),e&&(r=n?D(e,a.clone().stripTime()):D(e,a)),n!=l?d=!0:i&&(o=D(i||S.getDefaultEventEnd(n,e||a),e||a).subtract(D(c||S.getDefaultEventEnd(l,a),a))),s=b(m(t._id),d,n,r,o),{dateDelta:r,durationDelta:o,undo:s}}function b(i,n,r,o,s){var l=S.getIsAmbigTimezone(),a=[];return t.each(i,function(t,i){var c=i._allDay,h=i._start,u=i._end,f=null!=r?r:c,g=h.clone(),p=!n&&u?u.clone():null;f?(g.stripTime(),p&&p.stripTime()):(g.hasTime()||(g=S.rezoneDate(g)),p&&!p.hasTime()&&(p=S.rezoneDate(p))),p||!e.forceEventDuration&&!+s||(p=S.getDefaultEventEnd(f,g)),g.add(o),p&&p.add(o).add(s),l&&(+o||+s)&&(g.stripZone(),p&&p.stripZone()),i.allDay=f,i.start=g,i.end=p,d(i),a.push(function(){i.allDay=c,i.start=h,i.end=u,d(i)})}),function(){for(var t=0;a.length>t;t++)a[t]()}}var S=this;S.isFetchNeeded=i,S.fetchEvents=n,S.addEventSource=s,S.removeEventSource=a,S.updateEvent=u,S.renderEvent=g,S.removeEvents=p,S.clientEvents=m,S.mutateEvent=E;var T,C,H=S.trigger,x=S.getView,R=S.reportEvents,P=S.getEventEnd,z={events:[]},L=[z],G=0,N=0,Y=0,A=[];t.each((e.events?[e.events]:[]).concat(e.eventSources||[]),function(t,e){var i=l(e);i&&L.push(i)});var V=["title","url","allDay","className","editable","color","backgroundColor","borderColor","textColor"]}function d(t){t._allDay=t.allDay,t._start=t.start.clone(),t._end=t.end?t.end.clone():null}function h(t,e){e.left&&t.css({"border-left-width":1,"margin-left":e.left-1}),e.right&&t.css({"border-right-width":1,"margin-right":e.right-1})}function u(t){t.css({"margin-left":"","margin-right":"","border-left-width":"","border-right-width":""})}function f(e,i,n){var r=Math.floor(i/e.length),o=Math.floor(i-r*(e.length-1)),s=[],l=[],a=[],c=0;g(e),e.each(function(i,n){var d=i===e.length-1?o:r,h=t(n).outerHeight(!0);d>h?(s.push(n),l.push(h),a.push(t(n).height())):c+=h}),n&&(i-=c,r=Math.floor(i/s.length),o=Math.floor(i-r*(s.length-1))),t(s).each(function(e,i){var n=e===s.length-1?o:r,c=l[e],d=a[e],h=n-(c-d);n>c&&t(i).height(h)})}function g(t){t.height("")}function p(e){var i=0;return e.find("> *").each(function(e,n){var r=t(n).outerWidth();r>i&&(i=r)}),i++,e.width(i),i}function m(t,e){return t.height(e).addClass("fc-scroller"),t[0].scrollHeight-1>t[0].clientHeight?!0:(v(t),!1)}function v(t){t.height("").removeClass("fc-scroller")}function y(e){var i=e.css("position"),n=e.parents().filter(function(){var e=t(this);return/(auto|scroll)/.test(e.css("overflow")+e.css("overflow-y")+e.css("overflow-x"))}).eq(0);return"fixed"!==i&&n.length?n:t(e[0].ownerDocument||document)}function w(t){var e=t.offset().left,i=e+t.width(),n=t.children(),r=n.offset().left,o=r+n.outerWidth();return{left:r-e,right:i-o}}function E(t){return 1==t.which&&!t.ctrlKey}function b(t,e,i,n){var r,o,s,l;return e>i&&n>t?(t>=i?(r=t.clone(),s=!0):(r=i.clone(),s=!1),n>=e?(o=e.clone(),l=!0):(o=n.clone(),l=!1),{start:r,end:o,isStart:s,isEnd:l}):void 0}function S(t,e){if(t=t||{},void 0!==t[e])return t[e];for(var i,n=e.split(/(?=[A-Z])/),r=n.length-1;r>=0;r--)if(i=t[n[r].toLowerCase()],void 0!==i)return i;return t["default"]}function D(t,i){return e.duration({days:t.clone().stripTime().diff(i.clone().stripTime(),"days"),ms:t.time()-i.time()})}function T(t){return"[object Date]"===Object.prototype.toString.call(t)||t instanceof Date}function C(t,e){return t-e}function H(t){var e=function(){};return e.prototype=t,new e}function x(t,e){for(var i in e)e.hasOwnProperty(i)&&(t[i]=e[i])}function k(e,i,n){if(t.isFunction(e)&&(e=[e]),e){var r,o;for(r=0;e.length>r;r++)o=e[r].apply(i,n)||o;return o}}function M(){for(var t=0;arguments.length>t;t++)if(void 0!==arguments[t])return arguments[t]}function R(t){return(t+"").replace(/&/g,"&").replace(//g,">").replace(/'/g,"'").replace(/"/g,""").replace(/\n/g,"
")}function P(t){return t.replace(/&.*?;/g,"")}function z(t){return t.charAt(0).toUpperCase()+t.slice(1)}function L(t,e){var i,n,r,o,s=function(){var l=+new Date-o;e>l&&l>0?i=setTimeout(s,e-l):(i=null,t.apply(r,n),i||(r=n=null))};return function(){r=this,n=arguments,o=+new Date,i||(i=setTimeout(s,e))}}function G(i,n,r){var o,s,l,a,c=i[0],d=1==i.length&&"string"==typeof c;return e.isMoment(c)?(a=e.apply(null,i),c._ambigTime&&(a._ambigTime=!0),c._ambigZone&&(a._ambigZone=!0)):T(c)||void 0===c?a=e.apply(null,i):(o=!1,s=!1,d?Pe.test(c)?(c+="-01",i=[c],o=!0,s=!0):(l=ze.exec(c))&&(o=!l[5],s=!0):t.isArray(c)&&(s=!0),a=n?e.utc.apply(e,i):e.apply(null,i),o?(a._ambigTime=!0,a._ambigZone=!0):r&&(s?a._ambigZone=!0:d&&a.zone(c))),new N(a)}function N(t){x(this,t)}function Y(t,e){var i,n=[],r=!1,o=!1;for(i=0;t.length>i;i++)n.push(He.moment.parseZone(t[i])),r=r||n[i]._ambigTime,o=o||n[i]._ambigZone;for(i=0;n.length>i;i++)r&&!e?n[i].stripTime():o&&n[i].stripZone();return n}function A(t,i){return e.fn.format.call(t,i)}function V(t,e){return _(t,Z(e))}function _(t,e){var i,n="";for(i=0;e.length>i;i++)n+=O(t,e[i]);return n}function O(t,e){var i,n;return"string"==typeof e?e:(i=e.token)?Le[i]?Le[i](t):A(t,i):e.maybe&&(n=_(t,e.maybe),n.match(/[1-9]/))?n:""}function W(t,e,i,n,r){var o;return t=He.moment.parseZone(t),e=He.moment.parseZone(e),o=(t.localeData||t.lang).call(t),i=o.longDateFormat(i)||i,n=n||" - ",F(t,e,Z(i),n,r)}function F(t,e,i,n,r){var o,s,l,a,c="",d="",h="",u="",f="";for(s=0;i.length>s&&(o=I(t,e,i[s]),o!==!1);s++)c+=o;for(l=i.length-1;l>s&&(o=I(t,e,i[l]),o!==!1);l--)d=o+d;for(a=s;l>=a;a++)h+=O(t,i[a]),u+=O(e,i[a]);return(h||u)&&(f=r?u+n+h:h+n+u),c+f+d}function I(t,e,i){var n,r;return"string"==typeof i?i:(n=i.token)&&(r=Ge[n.charAt(0)],r&&t.isSame(e,r))?A(t,n):!1}function Z(t){return t in Ne?Ne[t]:Ne[t]=B(t)}function B(t){for(var e,i=[],n=/\[([^\]]*)\]|\(([^\)]*)\)|(LT|(\w)\4*o?)|([^\w\[\(]+)/g;e=n.exec(t);)e[1]?i.push(e[1]):e[2]?i.push({maybe:B(e[2])}):e[3]?i.push({token:e[3]}):e[5]&&i.push(e[5]);return i}function j(t){this.options=t||{}}function X(t){this.grid=t}function $(t){this.coordMaps=t}function q(t,e){this.coordMap=t,this.options=e||{}}function U(t,e){return t||e?t&&e?t.grid===e.grid&&t.row===e.row&&t.col===e.col:!1:!0}function K(e,i){this.options=i=i||{},this.sourceEl=e,this.parentEl=i.parentEl?t(i.parentEl):e.parent()}function Q(t){this.view=t}function J(t){Q.call(this,t),this.coordMap=new X(this)}function te(t,e){return t.eventStartMS-e.eventStartMS||e.eventDurationMS-t.eventDurationMS||e.event.allDay-t.event.allDay||(t.event.title||"").localeCompare(e.event.title)}function ee(t){J.call(this,t)}function ie(t,e){var i,n;for(i=0;e.length>i;i++)if(n=e[i],n.leftCol<=t.rightCol&&n.rightCol>=t.leftCol)return!0;return!1}function ne(t,e){return t.leftCol-e.leftCol}function re(t){J.call(this,t)}function oe(t){var e,i,n;if(t.sort(te),e=se(t),le(e),i=e[0]){for(n=0;i.length>n;n++)ae(i[n]);for(n=0;i.length>n;n++)ce(i[n],0,0)}}function se(t){var e,i,n,r=[];for(e=0;t.length>e;e++){for(i=t[e],n=0;r.length>n&&de(i,r[n]).length;n++);i.level=n,(r[n]||(r[n]=[])).push(i)}return r}function le(t){var e,i,n,r,o;for(e=0;t.length>e;e++)for(i=t[e],n=0;i.length>n;n++)for(r=i[n],r.forwardSegs=[],o=e+1;t.length>o;o++)de(r,t[o],r.forwardSegs)}function ae(t){var e,i,n=t.forwardSegs,r=0;if(void 0===t.forwardPressure){for(e=0;n.length>e;e++)i=n[e],ae(i),r=Math.max(r,1+i.forwardPressure);t.forwardPressure=r}}function ce(t,e,i){var n,r=t.forwardSegs;if(void 0===t.forwardCoord)for(r.length?(r.sort(ue),ce(r[0],e+1,i),t.forwardCoord=r[0].backwardCoord):t.forwardCoord=1,t.backwardCoord=t.forwardCoord-(t.forwardCoord-i)/(e+1),n=0;r.length>n;n++)ce(r[n],0,t.forwardCoord)}function de(t,e,i){i=i||[];for(var n=0;e.length>n;n++)he(t,e[n])&&i.push(e[n]);return i}function he(t,e){return t.bottom>e.top&&t.topd;d++){var h=d*n,u=h+n-1,f=Math.max(a,h),g=Math.min(c,u);if(g>=f){var m=E(f),v=E(g),b=[m.col,v.col].sort(),S=p(f)==s,T=p(g)+1==l;r.push({row:d,leftCol:b[0],rightCol:b[1],isStart:S,isEnd:T})}}return r}function D(t,e){var i,n,r=t.clone().stripTime();return e&&(i=e.clone().stripTime(),n=+e.time(),n&&n>=k&&i.add(1,"days")),(!e||r>=i)&&(i=r.clone().add(1,"days")),{start:r,end:i}}function T(t){var e=D(t.start,t.end);return e.end.diff(e.start,"days")>1}var C=this;C.calendar=i,C.opt=n,C.trigger=r,C.isEventDraggable=o,C.isEventResizable=l,C.eventDrop=a,C.eventResize=c;var H=i.reportEventChange,x=i.options,k=e.duration(x.nextDayThreshold);C.init(),C.getEventTimeText=function(t,e){var r,o;return"object"==typeof t&&"object"==typeof e?(r=t,o=e,e=arguments[2]):(r=t.start,o=t.end),e=e||n("timeFormat"),o&&n("displayEventEnd")?i.formatRange(r,o,e):i.formatDate(r,e)},C.isHiddenDay=d,C.skipHiddenDays=u,C.getCellsPerWeek=h,C.dateToCell=v,C.dateToDayOffset=y,C.dayOffsetToCellOffset=w,C.cellOffsetToCell=E,C.cellToDate=f,C.cellToCellOffset=g,C.cellOffsetToDayOffset=p,C.dayOffsetToDate=m,C.rangeToSegments=b,C.isMultiDayEvent=T;var R,P=n("hiddenDays")||[],z=[],L=[],G=[],N=n("isRTL");(function(){n("weekends")===!1&&P.push(0,6);for(var e=0,i=0;7>e;e++)L[e]=i,z[e]=-1!=t.inArray(e,P),z[e]||(G[i]=e,i++);if(R=i,!R)throw"invalid hiddenDays"})()}function ge(t){fe.call(this,t),this.dayGrid=new ee(this),this.coordMap=this.dayGrid.coordMap}function pe(t){ge.call(this,t)}function me(t){ge.call(this,t)}function ve(t){ge.call(this,t)}function ye(t,e){return e.longDateFormat("LT").replace(":mm","(:mm)").replace(/(\Wmm)$/,"($1)").replace(/\s*a$/i,"a")}function we(t,e){return e.longDateFormat("LT").replace(/\s*a$/i,"")}function Ee(t){fe.call(this,t),this.timeGrid=new re(this),this.opt("allDaySlot")?(this.dayGrid=new ee(this),this.coordMap=new $([this.dayGrid.coordMap,this.timeGrid.coordMap])):this.coordMap=this.timeGrid.coordMap}function be(t){Ee.call(this,t)}function Se(t){Ee.call(this,t)}var De={lang:"en",defaultTimedEventDuration:"02:00:00",defaultAllDayEventDuration:{days:1},forceEventDuration:!1,nextDayThreshold:"09:00:00",defaultView:"month",aspectRatio:1.35,header:{left:"title",center:"",right:"today prev,next"},weekends:!0,weekNumbers:!1,weekNumberTitle:"W",weekNumberCalculation:"local",lazyFetching:!0,startParam:"start",endParam:"end",timezoneParam:"timezone",timezone:!1,titleFormat:{month:"MMMM YYYY",week:"ll",day:"LL"},columnFormat:{month:"ddd",week:n,day:"dddd"},timeFormat:{"default":i},displayEventEnd:{month:!1,basicWeek:!1,"default":!0},isRTL:!1,defaultButtonText:{prev:"prev",next:"next",prevYear:"prev year",nextYear:"next year",today:"today",month:"month",week:"week",day:"day"},buttonIcons:{prev:"left-single-arrow",next:"right-single-arrow",prevYear:"left-double-arrow",nextYear:"right-double-arrow"},theme:!1,themeButtonIcons:{prev:"circle-triangle-w",next:"circle-triangle-e",prevYear:"seek-prev",nextYear:"seek-next"},dragOpacity:.75,dragRevertDuration:500,dragScroll:!0,unselectAuto:!0,dropAccept:"*",eventLimit:!1,eventLimitText:"more",eventLimitClick:"popover",dayPopoverFormat:"LL",handleWindowResize:!0,windowResizeDelay:200},Te={en:{columnFormat:{week:"ddd M/D"},dayPopoverFormat:"dddd, MMMM D"}},Ce={header:{left:"next,prev today",center:"",right:"title"},buttonIcons:{prev:"right-single-arrow",next:"left-single-arrow",prevYear:"right-double-arrow",nextYear:"left-double-arrow"},themeButtonIcons:{prev:"circle-triangle-e",next:"circle-triangle-w",nextYear:"seek-prev",prevYear:"seek-next"}},He=t.fullCalendar={version:"2.1.1"},xe=He.views={};t.fn.fullCalendar=function(e){var i=Array.prototype.slice.call(arguments,1),n=this;return this.each(function(r,o){var s,a=t(o),c=a.data("fullCalendar");"string"==typeof e?c&&t.isFunction(c[e])&&(s=c[e].apply(c,i),r||(n=s),"destroy"===e&&a.removeData("fullCalendar")):c||(c=new l(a,e),a.data("fullCalendar",c),c.render())}),n},He.langs=Te,He.datepickerLang=function(e,i,n){var r=Te[e];r||(r=Te[e]={}),o(r,{isRTL:n.isRTL,weekNumberTitle:n.weekHeader,titleFormat:{month:n.showMonthAfterYear?"YYYY["+n.yearSuffix+"] MMMM":"MMMM YYYY["+n.yearSuffix+"]"},defaultButtonText:{prev:P(n.prevText),next:P(n.nextText),today:P(n.currentText)}}),t.datepicker&&(t.datepicker.regional[i]=t.datepicker.regional[e]=n,t.datepicker.regional.en=t.datepicker.regional[""],t.datepicker.setDefaults(n))},He.lang=function(t,e){var i;e&&(i=Te[t],i||(i=Te[t]={}),o(i,e||{})),De.lang=t},He.sourceNormalizers=[],He.sourceFetchers=[];var ke={dataType:"json",cache:!1},Me=1,Re=["sun","mon","tue","wed","thu","fri","sat"];He.applyAll=k;var Pe=/^\s*\d{4}-\d\d$/,ze=/^\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+)?)?)?)?)?$/;He.moment=function(){return G(arguments)},He.moment.utc=function(){var t=G(arguments,!0);return t.hasTime()&&t.utc(),t},He.moment.parseZone=function(){return G(arguments,!0,!0)},N.prototype=H(e.fn),N.prototype.clone=function(){return G([this])},N.prototype.time=function(t){if(null==t)return e.duration({hours:this.hours(),minutes:this.minutes(),seconds:this.seconds(),milliseconds:this.milliseconds()});delete this._ambigTime,e.isDuration(t)||e.isMoment(t)||(t=e.duration(t));var i=0;return e.isDuration(t)&&(i=24*Math.floor(t.asDays())),this.hours(i+t.hours()).minutes(t.minutes()).seconds(t.seconds()).milliseconds(t.milliseconds())},N.prototype.stripTime=function(){var t=this.toArray();return e.fn.utc.call(this),this.year(t[0]).month(t[1]).date(t[2]).hours(0).minutes(0).seconds(0).milliseconds(0),this._ambigTime=!0,this._ambigZone=!0,this},N.prototype.hasTime=function(){return!this._ambigTime},N.prototype.stripZone=function(){var t=this.toArray(),i=this._ambigTime;return e.fn.utc.call(this),this.year(t[0]).month(t[1]).date(t[2]).hours(t[3]).minutes(t[4]).seconds(t[5]).milliseconds(t[6]),i&&(this._ambigTime=!0),this._ambigZone=!0,this},N.prototype.hasZone=function(){return!this._ambigZone},N.prototype.zone=function(t){return null!=t&&(delete this._ambigTime,delete this._ambigZone),e.fn.zone.apply(this,arguments)},N.prototype.local=function(){var t=this.toArray(),i=this._ambigZone;return delete this._ambigTime,delete this._ambigZone,e.fn.local.apply(this,arguments),i&&this.year(t[0]).month(t[1]).date(t[2]).hours(t[3]).minutes(t[4]).seconds(t[5]).milliseconds(t[6]),this},N.prototype.utc=function(){return delete this._ambigTime,delete this._ambigZone,e.fn.utc.apply(this,arguments)},N.prototype.format=function(){return arguments[0]?V(this,arguments[0]):this._ambigTime?A(this,"YYYY-MM-DD"):this._ambigZone?A(this,"YYYY-MM-DD[T]HH:mm:ss"):A(this)},N.prototype.toISOString=function(){return this._ambigTime?A(this,"YYYY-MM-DD"):this._ambigZone?A(this,"YYYY-MM-DD[T]HH:mm:ss"):e.fn.toISOString.apply(this,arguments)},N.prototype.isWithin=function(t,e){var i=Y([this,t,e]);return i[0]>=i[1]&&i[0]').addClass(i.className||"").css({top:0,left:0}).append(i.content).appendTo(i.parentEl),this.el.on("click",".fc-close",function(){e.hide()}),i.autoHide&&t(document).on("mousedown",this.documentMousedownProxy=t.proxy(this,"documentMousedown"))},documentMousedown:function(e){this.el&&!t(e.target).closest(this.el).length&&this.hide()},destroy:function(){this.hide(),this.el&&(this.el.remove(),this.el=null),t(document).off("mousedown",this.documentMousedownProxy)},position:function(){var e,i,n,r,o,s=this.options,l=this.el.offsetParent().offset(),a=this.el.outerWidth(),c=this.el.outerHeight(),d=t(window),h=y(this.el);r=s.top||0,o=void 0!==s.left?s.left:void 0!==s.right?s.right-a:0,h.is(window)||h.is(document)?(h=d,e=0,i=0):(n=h.offset(),e=n.top,i=n.left),e+=d.scrollTop(),i+=d.scrollLeft(),s.viewportConstrain!==!1&&(r=Math.min(r,e+h.outerHeight()-c-this.margin),r=Math.max(r,e+this.margin),o=Math.min(o,i+h.outerWidth()-a-this.margin),o=Math.max(o,i+this.margin)),this.el.css({top:r-l.top,left:o-l.left})},trigger:function(t){this.options[t]&&this.options[t].apply(this,Array.prototype.slice.call(arguments,1))}},X.prototype={grid:null,rows:null,cols:null,containerEl:null,minX:null,maxX:null,minY:null,maxY:null,build:function(){this.grid.buildCoords(this.rows=[],this.cols=[]),this.computeBounds()},getCell:function(t,e){var i,n=null,r=this.rows,o=this.cols,s=-1,l=-1;if(this.inBounds(t,e)){for(i=0;r.length>i;i++)if(e>=r[i][0]&&r[i][1]>e){s=i;break}for(i=0;o.length>i;i++)if(t>=o[i][0]&&o[i][1]>t){l=i;break}s>=0&&l>=0&&(n={row:s,col:l},n.grid=this.grid,n.date=this.grid.getCellDate(n))}return n},computeBounds:function(){var t;this.containerEl&&(t=this.containerEl.offset(),this.minX=t.left,this.maxX=t.left+this.containerEl.outerWidth(),this.minY=t.top,this.maxY=t.top+this.containerEl.outerHeight())},inBounds:function(t,e){return this.containerEl?t>=this.minX&&this.maxX>t&&e>=this.minY&&this.maxY>e:!0}},$.prototype={coordMaps:null,build:function(){var t,e=this.coordMaps;for(t=0;e.length>t;t++)e[t].build()},getCell:function(t,e){var i,n=this.coordMaps,r=null;for(i=0;n.length>i&&!r;i++)r=n[i].getCell(t,e);return r}},q.prototype={coordMap:null,options:null,isListening:!1,isDragging:!1,origCell:null,origDate:null,cell:null,date:null,mouseX0:null,mouseY0:null,mousemoveProxy:null,mouseupProxy:null,scrollEl:null,scrollBounds:null,scrollTopVel:null,scrollLeftVel:null,scrollIntervalId:null,scrollHandlerProxy:null,scrollSensitivity:30,scrollSpeed:200,scrollIntervalMs:50,mousedown:function(t){E(t)&&(t.preventDefault(),this.startListening(t),this.options.distance||this.startDrag(t))},startListening:function(e){var i,n;this.isListening||(e&&this.options.scroll&&(i=y(t(e.target)),i.is(window)||i.is(document)||(this.scrollEl=i,this.scrollHandlerProxy=L(t.proxy(this,"scrollHandler"),100),this.scrollEl.on("scroll",this.scrollHandlerProxy))),this.computeCoords(),e&&(n=this.getCell(e),this.origCell=n,this.origDate=n?n.date:null,this.mouseX0=e.pageX,this.mouseY0=e.pageY),t(document).on("mousemove",this.mousemoveProxy=t.proxy(this,"mousemove")).on("mouseup",this.mouseupProxy=t.proxy(this,"mouseup")).on("selectstart",this.preventDefault),this.isListening=!0,this.trigger("listenStart",e))},computeCoords:function(){this.coordMap.build(),this.computeScrollBounds()},mousemove:function(t){var e,i;this.isDragging||(e=this.options.distance||1,i=Math.pow(t.pageX-this.mouseX0,2)+Math.pow(t.pageY-this.mouseY0,2),i>=e*e&&this.startDrag(t)),this.isDragging&&this.drag(t) -},startDrag:function(t){var e;this.isListening||this.startListening(),this.isDragging||(this.isDragging=!0,this.trigger("dragStart",t),e=this.getCell(t),e&&this.cellOver(e,!0))},drag:function(t){var e;this.isDragging&&(e=this.getCell(t),U(e,this.cell)||(this.cell&&this.cellOut(),e&&this.cellOver(e)),this.dragScroll(t))},cellOver:function(t){this.cell=t,this.date=t.date,this.trigger("cellOver",t,t.date)},cellOut:function(){this.cell&&(this.trigger("cellOut",this.cell),this.cell=null,this.date=null)},mouseup:function(t){this.stopDrag(t),this.stopListening(t)},stopDrag:function(t){this.isDragging&&(this.stopScrolling(),this.trigger("dragStop",t),this.isDragging=!1)},stopListening:function(e){this.isListening&&(this.scrollEl&&(this.scrollEl.off("scroll",this.scrollHandlerProxy),this.scrollHandlerProxy=null),t(document).off("mousemove",this.mousemoveProxy).off("mouseup",this.mouseupProxy).off("selectstart",this.preventDefault),this.mousemoveProxy=null,this.mouseupProxy=null,this.isListening=!1,this.trigger("listenStop",e),this.origCell=this.cell=null,this.origDate=this.date=null)},getCell:function(t){return this.coordMap.getCell(t.pageX,t.pageY)},trigger:function(t){this.options[t]&&this.options[t].apply(this,Array.prototype.slice.call(arguments,1))},preventDefault:function(t){t.preventDefault()},computeScrollBounds:function(){var t,e=this.scrollEl;e&&(t=e.offset(),this.scrollBounds={top:t.top,left:t.left,bottom:t.top+e.outerHeight(),right:t.left+e.outerWidth()})},dragScroll:function(t){var e,i,n,r,o=this.scrollSensitivity,s=this.scrollBounds,l=0,a=0;s&&(e=(o-(t.pageY-s.top))/o,i=(o-(s.bottom-t.pageY))/o,n=(o-(t.pageX-s.left))/o,r=(o-(s.right-t.pageX))/o,e>=0&&1>=e?l=-1*e*this.scrollSpeed:i>=0&&1>=i&&(l=i*this.scrollSpeed),n>=0&&1>=n?a=-1*n*this.scrollSpeed:r>=0&&1>=r&&(a=r*this.scrollSpeed)),this.setScrollVel(l,a)},setScrollVel:function(e,i){this.scrollTopVel=e,this.scrollLeftVel=i,this.constrainScrollVel(),!this.scrollTopVel&&!this.scrollLeftVel||this.scrollIntervalId||(this.scrollIntervalId=setInterval(t.proxy(this,"scrollIntervalFunc"),this.scrollIntervalMs))},constrainScrollVel:function(){var t=this.scrollEl;0>this.scrollTopVel?0>=t.scrollTop()&&(this.scrollTopVel=0):this.scrollTopVel>0&&t.scrollTop()+t[0].clientHeight>=t[0].scrollHeight&&(this.scrollTopVel=0),0>this.scrollLeftVel?0>=t.scrollLeft()&&(this.scrollLeftVel=0):this.scrollLeftVel>0&&t.scrollLeft()+t[0].clientWidth>=t[0].scrollWidth&&(this.scrollLeftVel=0)},scrollIntervalFunc:function(){var t=this.scrollEl,e=this.scrollIntervalMs/1e3;this.scrollTopVel&&t.scrollTop(t.scrollTop()+this.scrollTopVel*e),this.scrollLeftVel&&t.scrollLeft(t.scrollLeft()+this.scrollLeftVel*e),this.constrainScrollVel(),this.scrollTopVel||this.scrollLeftVel||this.stopScrolling()},stopScrolling:function(){this.scrollIntervalId&&(clearInterval(this.scrollIntervalId),this.scrollIntervalId=null,this.computeCoords())},scrollHandler:function(){this.scrollIntervalId||this.computeCoords()}},K.prototype={options:null,sourceEl:null,el:null,parentEl:null,top0:null,left0:null,mouseY0:null,mouseX0:null,topDelta:null,leftDelta:null,mousemoveProxy:null,isFollowing:!1,isHidden:!1,isAnimating:!1,start:function(e){this.isFollowing||(this.isFollowing=!0,this.mouseY0=e.pageY,this.mouseX0=e.pageX,this.topDelta=0,this.leftDelta=0,this.isHidden||this.updatePosition(),t(document).on("mousemove",this.mousemoveProxy=t.proxy(this,"mousemove")))},stop:function(e,i){function n(){this.isAnimating=!1,r.destroyEl(),this.top0=this.left0=null,i&&i()}var r=this,o=this.options.revertDuration;this.isFollowing&&!this.isAnimating&&(this.isFollowing=!1,t(document).off("mousemove",this.mousemoveProxy),e&&o&&!this.isHidden?(this.isAnimating=!0,this.el.animate({top:this.top0,left:this.left0},{duration:o,complete:n})):n())},getEl:function(){var t=this.el;return t||(this.sourceEl.width(),t=this.el=this.sourceEl.clone().css({position:"absolute",visibility:"",display:this.isHidden?"none":"",margin:0,right:"auto",bottom:"auto",width:this.sourceEl.width(),height:this.sourceEl.height(),opacity:this.options.opacity||"",zIndex:this.options.zIndex}).appendTo(this.parentEl)),t},destroyEl:function(){this.el&&(this.el.remove(),this.el=null)},updatePosition:function(){var t,e;this.getEl(),null===this.top0&&(this.sourceEl.width(),t=this.sourceEl.offset(),e=this.el.offsetParent().offset(),this.top0=t.top-e.top,this.left0=t.left-e.left),this.el.css({top:this.top0+this.topDelta,left:this.left0+this.leftDelta})},mousemove:function(t){this.topDelta=t.pageY-this.mouseY0,this.leftDelta=t.pageX-this.mouseX0,this.isHidden||this.updatePosition()},hide:function(){this.isHidden||(this.isHidden=!0,this.el&&this.el.hide())},show:function(){this.isHidden&&(this.isHidden=!1,this.updatePosition(),this.getEl().show())}},Q.prototype={view:null,cellHtml:"",rowHtml:function(t,e){var i,n,r=this.view,o=this.getHtmlRenderer("cell",t),s="";for(e=e||0,i=0;r.colCnt>i;i++)n=r.cellToDate(e,i),s+=o(e,i,n);return s=this.bookendCells(s,t,e),""+s+""},bookendCells:function(t,e,i){var n=this.view,r=this.getHtmlRenderer("intro",e)(i||0),o=this.getHtmlRenderer("outro",e)(i||0),s=n.opt("isRTL"),l=s?o:r,a=s?r:o;return"string"==typeof t?l+t+a:t.prepend(l).append(a)},getHtmlRenderer:function(t,e){var i,n,r,o,s=this.view;return i=t+"Html",e&&(n=e+z(t)+"Html"),n&&(o=s[n])?r=s:n&&(o=this[n])?r=this:(o=s[i])?r=s:(o=this[i])&&(r=this),"function"==typeof o?function(){return o.apply(r,arguments)||""}:function(){return o||""}}},J.prototype=H(Q.prototype),t.extend(J.prototype,{el:null,coordMap:null,cellDuration:null,render:function(){this.bindHandlers()},destroy:function(){},buildCoords:function(){},getCellDate:function(){},getCellDayEl:function(){},rangeToSegs:function(){},bindHandlers:function(){var e=this;this.el.on("mousedown",function(i){t(i.target).is(".fc-event-container *, .fc-more")||t(i.target).closest(".fc-popover").length||e.dayMousedown(i)}),this.bindSegHandlers()},dayMousedown:function(t){var e,i,n,r=this,o=this.view,s=o.opt("selectable"),l=null,a=new q(this.coordMap,{scroll:o.opt("dragScroll"),dragStart:function(){o.unselect()},cellOver:function(t,o){a.origDate&&(n=r.getCellDayEl(t),l=[o,a.origDate].sort(C),e=l[0],i=l[1].clone().add(r.cellDuration),s&&r.renderSelection(e,i))},cellOut:function(){l=null,r.destroySelection()},listenStop:function(t){l&&(l[0].isSame(l[1])&&o.trigger("dayClick",n[0],e,t),s&&o.reportSelection(e,i,t))}});a.mousedown(t)},renderDrag:function(){},destroyDrag:function(){},renderResize:function(){},destroyResize:function(){},renderRangeHelper:function(t,e,i){var n,r=this.view;!e&&r.opt("forceEventDuration")&&(e=r.calendar.getDefaultEventEnd(!t.hasTime(),t)),n=i?H(i.event):{},n.start=t,n.end=e,n.allDay=!(t.hasTime()||e&&e.hasTime()),n.className=(n.className||[]).concat("fc-helper"),i||(n.editable=!1),this.renderHelper(n,i)},renderHelper:function(){},destroyHelper:function(){},renderSelection:function(t,e){this.renderHighlight(t,e)},destroySelection:function(){this.destroyHighlight()},renderHighlight:function(){},destroyHighlight:function(){},headHtml:function(){return'
'+""+""+this.rowHtml("head")+""+"
"+"
"},headCellHtml:function(t,e,i){var n=this.view,r=n.calendar,o=n.opt("columnFormat");return''+R(r.formatDate(i,o))+""},bgCellHtml:function(t,e,i){var n=this.view,r=this.getDayClasses(i);return r.unshift("fc-day",n.widgetContentClass),''},getDayClasses:function(t){var e=this.view,i=e.calendar.getNow().stripTime(),n=["fc-"+Re[t.day()]];return"month"===e.name&&t.month()!=e.intervalStart.month()&&n.push("fc-other-month"),t.isSame(i,"day")?n.push("fc-today",e.highlightStateClass):i>t?n.push("fc-past"):n.push("fc-future"),n}}),t.extend(J.prototype,{mousedOverSeg:null,isDraggingSeg:!1,isResizingSeg:!1,renderEvents:function(){},getSegs:function(){},destroyEvents:function(){this.triggerSegMouseout()},renderSegs:function(e,i){var n,r=this.view,o="",s=[];for(n=0;e.length>n;n++)o+=this.renderSegHtml(e[n],i);return t(o).each(function(i,n){var o=e[i],l=r.resolveEventEl(o.event,t(n));l&&(l.data("fc-seg",o),o.el=l,s.push(o))}),s},renderSegHtml:function(){},eventsToSegs:function(e,i,n){var r=this;return t.map(e,function(t){return r.eventToSegs(t,i,n)})},eventToSegs:function(t,e,i){var n,r,o,s=t.start.clone().stripZone(),l=this.view.calendar.getEventEnd(t).stripZone();for(e&&i?(o=b(s,l,e,i),n=o?[o]:[]):n=this.rangeToSegs(s,l),r=0;n.length>r;r++)o=n[r],o.event=t,o.eventStartMS=+s,o.eventDurationMS=l-s;return n},bindSegHandlers:function(){var e=this,i=this.view;t.each({mouseenter:function(t,i){e.triggerSegMouseover(t,i)},mouseleave:function(t,i){e.triggerSegMouseout(t,i)},click:function(t,e){return i.trigger("eventClick",this,t.event,e)},mousedown:function(n,r){t(r.target).is(".fc-resizer")&&i.isEventResizable(n.event)?e.segResizeMousedown(n,r):i.isEventDraggable(n.event)&&e.segDragMousedown(n,r)}},function(i,n){e.el.on(i,".fc-event-container > *",function(i){var r=t(this).data("fc-seg");return!r||e.isDraggingSeg||e.isResizingSeg?void 0:n.call(this,r,i)})})},triggerSegMouseover:function(t,e){this.mousedOverSeg||(this.mousedOverSeg=t,this.view.trigger("eventMouseover",t.el[0],t.event,e))},triggerSegMouseout:function(t,e){e=e||{},this.mousedOverSeg&&(t=t||this.mousedOverSeg,this.mousedOverSeg=null,this.view.trigger("eventMouseout",t.el[0],t.event,e))},segDragMousedown:function(t,e){var i,n,r=this,o=this.view,s=t.el,l=t.event,a=new K(t.el,{parentEl:o.el,opacity:o.opt("dragOpacity"),revertDuration:o.opt("dragRevertDuration"),zIndex:2}),c=new q(o.coordMap,{distance:5,scroll:o.opt("dragScroll"),listenStart:function(t){a.hide(),a.start(t)},dragStart:function(e){r.triggerSegMouseout(t,e),r.isDraggingSeg=!0,o.hideEvent(l),o.trigger("eventDragStart",s[0],l,e,{})},cellOver:function(e,s){var l=t.cellDate||c.origDate,d=r.computeDraggedEventDates(t,l,s);i=d.start,n=d.end,o.renderDrag(i,n,t)?a.hide():a.show()},cellOut:function(){i=null,o.destroyDrag(),a.show()},dragStop:function(t){var e=i&&!i.isSame(l.start);a.stop(!e,function(){r.isDraggingSeg=!1,o.destroyDrag(),o.showEvent(l),o.trigger("eventDragStop",s[0],l,t,{}),e&&o.eventDrop(s[0],l,i,t)})},listenStop:function(){a.stop()}});c.mousedown(e)},computeDraggedEventDates:function(t,e,i){var n,r,o,s=this.view,l=t.event,a=l.start,c=s.calendar.getEventEnd(l);return i.hasTime()===e.hasTime()?(n=D(i,e),r=a.clone().add(n),o=null===l.end?null:c.clone().add(n)):(r=i,o=null),{start:r,end:o}},segResizeMousedown:function(t,e){function i(){r.destroyResize(),o.showEvent(l)}var n,r=this,o=this.view,s=t.el,l=t.event,a=l.start,c=o.calendar.getEventEnd(l),d=null;n=new q(this.coordMap,{distance:5,scroll:o.opt("dragScroll"),dragStart:function(e){r.triggerSegMouseout(t,e),r.isResizingSeg=!0,o.trigger("eventResizeStart",s[0],l,e,{})},cellOver:function(e,n){n.isBefore(a)&&(n=a),d=n.clone().add(r.cellDuration),d.isSame(c)?(d=null,i()):(r.renderResize(a,d,t),o.hideEvent(l))},cellOut:function(){d=null,i()},dragStop:function(t){r.isResizingSeg=!1,i(),o.trigger("eventResizeStop",s[0],l,t,{}),d&&o.eventResize(s[0],l,d,t)}}),n.mousedown(e)},getSegClasses:function(t,e,i){var n=t.event,r=["fc-event",t.isStart?"fc-start":"fc-not-start",t.isEnd?"fc-end":"fc-not-end"].concat(n.className,n.source?n.source.className:[]);return e&&r.push("fc-draggable"),i&&r.push("fc-resizable"),r},getEventSkinCss:function(t){var e=this.view,i=t.source||{},n=t.color,r=i.color,o=e.opt("eventColor"),s=t.backgroundColor||n||i.backgroundColor||r||e.opt("eventBackgroundColor")||o,l=t.borderColor||n||i.borderColor||r||e.opt("eventBorderColor")||o,a=t.textColor||i.textColor||e.opt("eventTextColor"),c=[];return s&&c.push("background-color:"+s),l&&c.push("border-color:"+l),a&&c.push("color:"+a),c.join(";")}}),ee.prototype=H(J.prototype),t.extend(ee.prototype,{numbersVisible:!1,cellDuration:e.duration({days:1}),bottomCoordPadding:0,rowEls:null,dayEls:null,helperEls:null,highlightEls:null,render:function(e){var i,n=this.view,r="";for(i=0;n.rowCnt>i;i++)r+=this.dayRowHtml(i,e);this.el.html(r),this.rowEls=this.el.find(".fc-row"),this.dayEls=this.el.find(".fc-day"),this.dayEls.each(function(e,i){var r=n.cellToDate(Math.floor(e/n.colCnt),e%n.colCnt);n.trigger("dayRender",null,r,t(i))}),J.prototype.render.call(this)},destroy:function(){this.destroySegPopover()},dayRowHtml:function(t,e){var i=this.view,n=["fc-row","fc-week",i.widgetContentClass];return e&&n.push("fc-rigid"),'
'+'
'+""+this.rowHtml("day",t)+"
"+"
"+'
'+""+(this.numbersVisible?""+this.rowHtml("number",t)+"":"")+"
"+"
"+"
"},dayCellHtml:function(t,e,i){return this.bgCellHtml(t,e,i)},buildCoords:function(e,i){var n,r,o,s=this.view.colCnt;this.dayEls.slice(0,s).each(function(e,s){n=t(s),r=n.offset().left,e&&(o[1]=r),o=[r],i[e]=o}),o[1]=r+n.outerWidth(),this.rowEls.each(function(i,s){n=t(s),r=n.offset().top,i&&(o[1]=r),o=[r],e[i]=o}),o[1]=r+n.outerHeight()+this.bottomCoordPadding},getCellDate:function(t){return this.view.cellToDate(t)},getCellDayEl:function(t){return this.dayEls.eq(t.row*this.view.colCnt+t.col)},rangeToSegs:function(t,e){return this.view.rangeToSegments(t,e)},renderDrag:function(t,e,i){var n;return this.renderHighlight(t,e||this.view.calendar.getDefaultEventEnd(!0,t)),i&&!i.el.closest(this.el).length?(this.renderRangeHelper(t,e,i),n=this.view.opt("dragOpacity"),void 0!==n&&this.helperEls.css("opacity",n),!0):void 0},destroyDrag:function(){this.destroyHighlight(),this.destroyHelper()},renderResize:function(t,e,i){this.renderHighlight(t,e),this.renderRangeHelper(t,e,i)},destroyResize:function(){this.destroyHighlight(),this.destroyHelper()},renderHelper:function(e,i){var n=[],r=this.renderEventRows([e]);this.rowEls.each(function(e,o){var s,l=t(o),a=t('
');s=i&&i.row===e?i.el.position().top:l.find(".fc-content-skeleton tbody").position().top,a.css("top",s).find("table").append(r[e].tbodyEl),l.append(a),n.push(a[0])}),this.helperEls=t(n)},destroyHelper:function(){this.helperEls&&(this.helperEls.remove(),this.helperEls=null)},renderHighlight:function(e,i){var n,r,o,s=this.rangeToSegs(e,i),l=[];for(n=0;s.length>n;n++)r=s[n],o=t(this.highlightSkeletonHtml(r.leftCol,r.rightCol+1)),o.appendTo(this.rowEls[r.row]),l.push(o[0]);this.highlightEls=t(l)},destroyHighlight:function(){this.highlightEls&&(this.highlightEls.remove(),this.highlightEls=null)},highlightSkeletonHtml:function(t,e){var i=this.view.colCnt,n="";return t>0&&(n+='"),m=[],v=[],y=[];for(r=0;g>r;r++){if(o=f[r],s=0,l=t(""),m.push([]),v.push([]),y.push([]),o)for(a=0;o.length>a;a++){for(c=o[a],n(c.leftCol),d=t('",l+=""+(s?"":n)+'",c.add(this.slotDuration);return l},processOptions:function(){var t=this.view,i=t.opt("slotDuration"),n=t.opt("snapDuration");i=e.duration(i),n=n?e.duration(n):i,this.slotDuration=i,this.snapDuration=n,this.cellDuration=n,this.minTime=e.duration(t.opt("minTime")),this.maxTime=e.duration(t.opt("maxTime"))},rangeToSegs:function(t,e){var i,n,r,o,s,l=this.view,a=[];for(t=t.clone().stripZone(),e=e.clone().stripZone(),n=0;l.colCnt>n;n++)r=l.cellToDate(0,n),o=r.clone().time(this.minTime),s=r.clone().time(this.maxTime),i=b(t,e,o,s),i&&(i.col=n,a.push(i));return a},resize:function(){this.computeSlatTops(),this.updateSegVerticals()},buildCoords:function(i,n){var r,o,s=this.view.colCnt,l=this.el.offset().top,a=e.duration(+this.minTime),c=null;for(this.dayEls.slice(0,s).each(function(e,i){r=t(i),o=r.offset().left,c&&(c[1]=o),c=[o],n[e]=c}),c[1]=o+r.outerWidth(),c=null;this.maxTime>a;)o=l+this.computeTimeTop(a),c&&(c[1]=o),c=[o],i.push(c),a.add(this.snapDuration);c[1]=l+this.computeTimeTop(a)},getCellDate:function(t){var e=this.view,i=e.calendar;return i.rezoneDate(e.cellToDate(0,t.col).time(this.minTime+this.snapDuration*t.row))},getCellDayEl:function(t){return this.dayEls.eq(t.col)},computeDateTop:function(t,i){return this.computeTimeTop(e.duration(t.clone().stripZone()-i.clone().stripTime()))},computeTimeTop:function(t){var e,i,n,r,o=(t-this.minTime)/this.slotDuration;return o=Math.max(0,o),o=Math.min(this.slatEls.length,o),e=Math.floor(o),i=o-e,n=this.slatTops[e],i?(r=this.slatTops[e+1],n+(r-n)*i):n},computeSlatTops:function(){var e,i=[];this.slatEls.each(function(n,r){e=t(r).position().top,i.push(e)}),i.push(e+this.slatEls.last().outerHeight()),this.slatTops=i},renderDrag:function(t,e,i){var n;return i?(this.renderRangeHelper(t,e,i),n=this.view.opt("dragOpacity"),void 0!==n&&this.helperEl.css("opacity",n),!0):(this.renderHighlight(t,e||this.view.calendar.getDefaultEventEnd(!1,t)),void 0)},destroyDrag:function(){this.destroyHelper(),this.destroyHighlight()},renderResize:function(t,e,i){this.renderRangeHelper(t,e,i)},destroyResize:function(){this.destroyHelper()},renderHelper:function(e,i){var n,r,o,s=this.renderEventTable([e]),l=s.tableEl,a=s.segs;for(n=0;a.length>n;n++)r=a[n],i&&i.col===r.col&&(o=i.el,r.el.css({left:o.css("left"),right:o.css("right"),"margin-left":o.css("margin-left"),"margin-right":o.css("margin-right")}));this.helperEl=t('
').append(l).appendTo(this.el)},destroyHelper:function(){this.helperEl&&(this.helperEl.remove(),this.helperEl=null)},renderSelection:function(t,e){this.view.opt("selectHelper")?this.renderRangeHelper(t,e):this.renderHighlight(t,e)},destroySelection:function(){this.destroyHelper(),this.destroyHighlight()},renderHighlight:function(e,i){this.highlightEl=t(this.highlightSkeletonHtml(e,i)).appendTo(this.el)},destroyHighlight:function(){this.highlightEl&&(this.highlightEl.remove(),this.highlightEl=null)},highlightSkeletonHtml:function(t,e){var i,n,r,o,s,l=this.view,a=this.rangeToSegs(t,e),c="",d=0;for(i=0;a.length>i;i++)n=a[i],n.col>d&&(c+='
",d++;return l.colCnt>d&&(c+='":void 0},numberIntroHtml:function(t){return this.weekNumbersVisible?'":void 0},dayIntroHtml:function(){return this.weekNumbersVisible?'":void 0},introHtml:function(){return this.weekNumbersVisible?'":void 0},numberCellHtml:function(t,e,i){var n;return this.dayNumbersVisible?(n=this.dayGrid.getDayClasses(i),n.unshift("fc-day-number"),'"):""):'"},dayIntroHtml:function(){return'"},slotBgIntroHtml:function(){return'"},introHtml:function(){return'"},axisStyleAttr:function(){return null!==this.axisWidth?'style="width:'+this.axisWidth+'px"':""},updateSize:function(t){t&&this.timeGrid.resize(),fe.prototype.updateSize.call(this,t)},updateWidth:function(){this.axisWidth=p(this.el.find(".fc-axis"))},setHeight:function(t,e){var i,n;null===this.bottomRuleHeight&&(this.bottomRuleHeight=this.bottomRuleEl.outerHeight()),this.bottomRuleEl.hide(),this.scrollerEl.css("overflow",""),v(this.scrollerEl),u(this.noScrollRowEls),this.dayGrid&&(this.dayGrid.destroySegPopover(),i=this.opt("eventLimit"),i&&"number"!=typeof i&&(i=Ye),i&&this.dayGrid.limitRows(i)),e||(n=this.computeScrollerHeight(t),m(this.scrollerEl,n)?(h(this.noScrollRowEls,w(this.scrollerEl)),n=this.computeScrollerHeight(t),this.scrollerEl.height(n),this.restoreScroll()):(this.scrollerEl.height(n).css("overflow","hidden"),this.bottomRuleEl.show()))},resetScroll:function(){function t(){i.scrollerEl.scrollTop(r)}var i=this,n=e.duration(this.opt("scrollTime")),r=this.timeGrid.computeTimeTop(n);r=Math.ceil(r),r&&r++,t(),setTimeout(t,0)},renderEvents:function(t){var e,i,n=[],r=[],o=[];for(i=0;t.length>i;i++)t[i].allDay?n.push(t[i]):r.push(t[i]);e=this.timeGrid.renderEvents(r),this.dayGrid&&(o=this.dayGrid.renderEvents(n)),this.updateHeight(),fe.prototype.renderEvents.call(this,t)},getSegs:function(){return this.timeGrid.getSegs().concat(this.dayGrid?this.dayGrid.getSegs():[])},destroyEvents:function(){fe.prototype.destroyEvents.call(this),this.recordScroll(),this.timeGrid.destroyEvents(),this.dayGrid&&this.dayGrid.destroyEvents()},renderDrag:function(t,e,i){return t.hasTime()?this.timeGrid.renderDrag(t,e,i):this.dayGrid?this.dayGrid.renderDrag(t,e,i):void 0},destroyDrag:function(){this.timeGrid.destroyDrag(),this.dayGrid&&this.dayGrid.destroyDrag()},renderSelection:function(t,e){t.hasTime()||e.hasTime()?this.timeGrid.renderSelection(t,e):this.dayGrid&&this.dayGrid.renderSelection(t,e)},destroySelection:function(){this.timeGrid.destroySelection(),this.dayGrid&&this.dayGrid.destroySelection()}}),xe.agendaWeek=be,be.prototype=H(Ee.prototype),t.extend(be.prototype,{name:"agendaWeek",incrementDate:function(t,e){return t.clone().stripTime().add(e,"weeks").startOf("week")},render:function(t){this.intervalStart=t.clone().stripTime().startOf("week"),this.intervalEnd=this.intervalStart.clone().add(1,"weeks"),this.start=this.skipHiddenDays(this.intervalStart),this.end=this.skipHiddenDays(this.intervalEnd,-1,!0),this.title=this.calendar.formatRange(this.start,this.end.clone().subtract(1),this.opt("titleFormat")," — "),Ee.prototype.render.call(this,this.getCellsPerWeek())}}),xe.agendaDay=Se,Se.prototype=H(Ee.prototype),t.extend(Se.prototype,{name:"agendaDay",incrementDate:function(t,e){var i=t.clone().stripTime().add(e,"days");return i=this.skipHiddenDays(i,0>e?-1:1)},render:function(t){this.start=this.intervalStart=t.clone().stripTime(),this.end=this.intervalEnd=this.start.clone().add(1,"days"),this.title=this.calendar.formatDate(this.start,this.opt("titleFormat")),Ee.prototype.render.call(this,1)}})}); \ No newline at end of file +(function(t){"function"==typeof define&&define.amd?define(["jquery","moment"],t):t(jQuery,moment)})(function(t,e){function i(t,e){return e.longDateFormat("LT").replace(":mm","(:mm)").replace(/(\Wmm)$/,"($1)").replace(/\s*a$/i,"t")}function n(t,e){var i=e.longDateFormat("L");return i=i.replace(/^Y+[^\w\s]*|[^\w\s]*Y+$/g,""),t.isRTL?i+=" ddd":i="ddd "+i,i}function r(t){o(ke,t)}function o(e){function i(i,n){t.isPlainObject(n)&&t.isPlainObject(e[i])&&!s(i)?e[i]=o({},e[i],n):void 0!==n&&(e[i]=n)}for(var n=1;arguments.length>n;n++)t.each(arguments[n],i);return e}function s(t){return/(Time|Duration)$/.test(t)}function l(i,n){function r(t){var i=e.localeData||e.langData;return i.call(e,t)||i.call(e,"en")}function s(t){ne?u()&&(m(),p(t)):l()}function l(){re=Q.theme?"ui":"fc",i.addClass("fc"),Q.isRTL?i.addClass("fc-rtl"):i.addClass("fc-ltr"),Q.theme?i.addClass("ui-widget"):i.addClass("fc-unthemed"),ne=t("
").prependTo(i),ee=new a(U,Q),ie=ee.render(),ie&&i.prepend(ie),f(Q.defaultView),Q.handleWindowResize&&(le=L(y,Q.windowResizeDelay),t(window).resize(le))}function d(){oe&&oe.destroy(),ee.destroy(),ne.remove(),i.removeClass("fc fc-ltr fc-rtl fc-unthemed ui-widget"),t(window).unbind("resize",le)}function u(){return i.is(":visible")}function f(t){p(0,t)}function p(e,i){ue++,oe&&i&&oe.name!==i&&(ee.deactivateButton(oe.name),I(),oe.start&&oe.destroy(),oe.el.remove(),oe=null),!oe&&i&&(oe=new ze[i](U),oe.el=t("
").appendTo(ne),ee.activateButton(i)),oe&&(e&&(ae=oe.incrementDate(ae,e)),oe.start&&!e&&ae.isWithin(oe.intervalStart,oe.intervalEnd)||u()&&(I(),oe.start&&oe.destroy(),oe.render(ae),B(),H(),k(),S())),B(),ue--}function g(t){return u()?(t&&v(),ue++,oe.updateSize(!0),ue--,!0):void 0}function m(){u()&&v()}function v(){se="number"==typeof Q.contentHeight?Q.contentHeight:"number"==typeof Q.height?Q.height-(ie?ie.outerHeight(!0):0):Math.round(ne.width()/Math.max(Q.aspectRatio,.5))}function y(t){!ue&&t.target===window&&oe.start&&g(!0)&&oe.trigger("windowResize",he)}function w(){D(),b()}function E(){u()&&(I(),oe.destroyEvents(),oe.renderEvents(fe),B())}function D(){I(),oe.destroyEvents(),B()}function S(){!Q.lazyFetching||ce(oe.start,oe.end)?b():E()}function b(){de(oe.start,oe.end)}function T(t){fe=t,E()}function C(){E()}function H(){ee.updateTitle(oe.title)}function k(){var t=U.getNow();t.isWithin(oe.intervalStart,oe.intervalEnd)?ee.disableButton("today"):ee.enableButton("today")}function M(t,e){t=U.moment(t),e=e?U.moment(e):t.hasTime()?t.clone().add(U.defaultTimedEventDuration):t.clone().add(U.defaultAllDayEventDuration),oe.select(t,e)}function R(){oe&&oe.unselect()}function P(){p(-1)}function z(){p(1)}function N(){ae.add(-1,"years"),p()}function _(){ae.add(1,"years"),p()}function A(){ae=U.getNow(),p()}function Y(t){ae=U.moment(t),p()}function O(t){ae.add(e.duration(t)),p()}function F(t,e){var i,n;e&&void 0!==ze[e]||(e=e||"day",i=ee.getViewsWithButtons().join(" "),n=i.match(RegExp("\\w+"+G(e))),n||(n=i.match(/\w+Day/)),e=n?n[0]:"agendaDay"),ae=t,f(e)}function Z(){return ae.clone()}function I(){ne.css({width:"100%",height:ne.height(),overflow:"hidden"})}function B(){ne.css({width:"",height:"",overflow:""})}function j(){return U}function X(){return oe}function $(t,e){return void 0===e?Q[t]:(("height"==t||"contentHeight"==t||"aspectRatio"==t)&&(Q[t]=e,g(!0)),void 0)}function q(t,e){return Q[t]?Q[t].apply(e||he,Array.prototype.slice.call(arguments,2)):void 0}var U=this;n=n||{};var K,Q=o({},ke,n);K=Q.lang in Me?Me[Q.lang]:Me[ke.lang],K&&(Q=o({},ke,K,n)),Q.isRTL&&(Q=o({},ke,Re,K||{},n)),U.options=Q,U.render=s,U.destroy=d,U.refetchEvents=w,U.reportEvents=T,U.reportEventChange=C,U.rerenderEvents=E,U.changeView=f,U.select=M,U.unselect=R,U.prev=P,U.next=z,U.prevYear=N,U.nextYear=_,U.today=A,U.gotoDate=Y,U.incrementDate=O,U.zoomTo=F,U.getDate=Z,U.getCalendar=j,U.getView=X,U.option=$,U.trigger=q;var J=x(r(Q.lang));if(Q.monthNames&&(J._months=Q.monthNames),Q.monthNamesShort&&(J._monthsShort=Q.monthNamesShort),Q.dayNames&&(J._weekdays=Q.dayNames),Q.dayNamesShort&&(J._weekdaysShort=Q.dayNamesShort),null!=Q.firstDay){var te=x(J._week);te.dow=Q.firstDay,J._week=te}U.defaultAllDayEventDuration=e.duration(Q.defaultAllDayEventDuration),U.defaultTimedEventDuration=e.duration(Q.defaultTimedEventDuration),U.moment=function(){var t;return"local"===Q.timezone?(t=Pe.moment.apply(null,arguments),t.hasTime()&&t.local()):t="UTC"===Q.timezone?Pe.moment.utc.apply(null,arguments):Pe.moment.parseZone.apply(null,arguments),"_locale"in t?t._locale=J:t._lang=J,t},U.getIsAmbigTimezone=function(){return"local"!==Q.timezone&&"UTC"!==Q.timezone},U.rezoneDate=function(t){return U.moment(t.toArray())},U.getNow=function(){var t=Q.now;return"function"==typeof t&&(t=t()),U.moment(t)},U.calculateWeekNumber=function(t){var e=Q.weekNumberCalculation;return"function"==typeof e?e(t):"local"===e?t.week():"ISO"===e.toUpperCase()?t.isoWeek():void 0},U.getEventEnd=function(t){return t.end?t.end.clone():U.getDefaultEventEnd(t.allDay,t.start)},U.getDefaultEventEnd=function(t,e){var i=e.clone();return t?i.stripTime().add(U.defaultAllDayEventDuration):i.add(U.defaultTimedEventDuration),U.getIsAmbigTimezone()&&i.stripZone(),i},U.formatRange=function(t,e,i){return"function"==typeof i&&(i=i.call(U,Q,J)),W(t,e,i,null,Q.isRTL)},U.formatDate=function(t,e){return"function"==typeof e&&(e=e.call(U,Q,J)),V(t,e)},c.call(U,Q),h.call(U,Q);var ee,ie,ne,re,oe,se,le,ae,ce=U.isFetchNeeded,de=U.fetchEvents,he=i[0],ue=0,fe=[];ae=null!=Q.defaultDate?U.moment(Q.defaultDate):U.getNow(),U.getSuggestedViewHeight=function(){return void 0===se&&m(),se},U.isHeightAuto=function(){return"auto"===Q.contentHeight||"auto"===Q.height}}function a(e,i){function n(){var e=i.header;return f=i.theme?"ui":"fc",e?p=t("
").append(o("left")).append(o("right")).append(o("center")).append('
'):void 0}function r(){p.remove()}function o(n){var r=t('
'),o=i.header[n];return o&&t.each(o.split(" "),function(){var n,o=t(),s=!0;t.each(this.split(","),function(n,r){var l,a,c,d,h,u,p,m;"title"==r?(o=o.add(t("

 

")),s=!1):(e[r]?l=function(){e[r]()}:ze[r]&&(l=function(){e.changeView(r)},g.push(r)),l&&(a=b(i.themeButtonIcons,r),c=b(i.buttonIcons,r),d=b(i.defaultButtonText,r),h=b(i.buttonText,r),u=h?P(h):a&&i.theme?"":c&&!i.theme?"":P(d||r),p=["fc-"+r+"-button",f+"-button",f+"-state-default"],m=t('").click(function(){m.hasClass(f+"-state-disabled")||(l(),(m.hasClass(f+"-state-active")||m.hasClass(f+"-state-disabled"))&&m.removeClass(f+"-state-hover"))}).mousedown(function(){m.not("."+f+"-state-active").not("."+f+"-state-disabled").addClass(f+"-state-down")}).mouseup(function(){m.removeClass(f+"-state-down")}).hover(function(){m.not("."+f+"-state-active").not("."+f+"-state-disabled").addClass(f+"-state-hover")},function(){m.removeClass(f+"-state-hover").removeClass(f+"-state-down")}),o=o.add(m)))}),s&&o.first().addClass(f+"-corner-left").end().last().addClass(f+"-corner-right").end(),o.length>1?(n=t("
"),s&&n.addClass("fc-button-group"),n.append(o),r.append(n)):r.append(o)}),r}function s(t){p.find("h2").text(t)}function l(t){p.find(".fc-"+t+"-button").addClass(f+"-state-active")}function a(t){p.find(".fc-"+t+"-button").removeClass(f+"-state-active")}function c(t){p.find(".fc-"+t+"-button").attr("disabled","disabled").addClass(f+"-state-disabled")}function d(t){p.find(".fc-"+t+"-button").removeAttr("disabled").removeClass(f+"-state-disabled")}function h(){return g}var u=this;u.render=n,u.destroy=r,u.updateTitle=s,u.activateButton=l,u.deactivateButton=a,u.disableButton=c,u.enableButton=d,u.getViewsWithButtons=h;var f,p=t(),g=[]}function c(e){function i(t,e){return!b||t.clone().stripZone()C.clone().stripZone()}function n(t,e){b=t,C=e,A=[];var i=++L,n=G.length;N=n;for(var o=0;n>o;o++)r(G[o],i)}function r(e,i){o(e,function(n){var r,o,s=t.isArray(e.events);if(i==L){if(n)for(r=0;n.length>r;r++)o=n[r],s||(o=w(o,e)),o&&A.push(o);N--,N||k(A)}})}function o(i,n){var r,s,l=Pe.sourceFetchers;for(r=0;l.length>r;r++){if(s=l[r].call(S,i,b.clone(),C.clone(),e.timezone,n),s===!0)return;if("object"==typeof s)return o(s,n),void 0}var a=i.events;if(a)t.isFunction(a)?(v(),a.call(S,b.clone(),C.clone(),e.timezone,function(t){n(t),y()})):t.isArray(a)?n(a):n();else{var c=i.url;if(c){var d,h=i.success,u=i.error,f=i.complete;d=t.isFunction(i.data)?i.data():i.data;var p=t.extend({},d||{}),g=R(i.startParam,e.startParam),m=R(i.endParam,e.endParam),w=R(i.timezoneParam,e.timezoneParam);g&&(p[g]=b.format()),m&&(p[m]=C.format()),e.timezone&&"local"!=e.timezone&&(p[w]=e.timezone),v(),t.ajax(t.extend({},Ge,i,{data:p,success:function(e){e=e||[];var i=M(h,this,arguments);t.isArray(i)&&(e=i),n(e)},error:function(){M(u,this,arguments),n()},complete:function(){M(f,this,arguments),y()}}))}else n()}}function s(t){var e=l(t);e&&(G.push(e),N++,r(e,L))}function l(e){var i,n,r=Pe.sourceNormalizers;if(t.isFunction(e)||t.isArray(e)?i={events:e}:"string"==typeof e?i={url:e}:"object"==typeof e&&(i=t.extend({},e)),i){for(i.className?"string"==typeof i.className&&(i.className=i.className.split(/\s+/)):i.className=[],t.isArray(i.events)&&(i.origArray=i.events,i.events=t.map(i.events,function(t){return w(t,i)})),n=0;r.length>n;n++)r[n].call(S,i);return i}}function a(e){G=t.grep(G,function(t){return!c(t,e)}),A=t.grep(A,function(t){return!c(t.source,e)}),k(A)}function c(t,e){return t&&e&&h(t)==h(e)}function h(t){return("object"==typeof t?t.origArray||t.url||t.events:null)||t}function u(t){t.start=S.moment(t.start),t.end&&(t.end=S.moment(t.end)),E(t),f(t),k(A)}function f(t){var e,i,n,r;for(e=0;A.length>e;e++)if(i=A[e],i._id==t._id&&i!==t)for(n=0;Y.length>n;n++)r=Y[n],void 0!==t[r]&&(i[r]=t[r])}function p(t,e){var i=w(t);i&&(i.source||(e&&(z.events.push(i),i.source=z),A.push(i)),k(A))}function g(e){var i,n;for(null==e?e=function(){return!0}:t.isFunction(e)||(i=e+"",e=function(t){return t._id==i}),A=t.grep(A,e,!0),n=0;G.length>n;n++)t.isArray(G[n].events)&&(G[n].events=t.grep(G[n].events,e,!0));k(A)}function m(e){return t.isFunction(e)?t.grep(A,e):null!=e?(e+="",t.grep(A,function(t){return t._id==e})):A}function v(){_++||H("loading",null,!0,x())}function y(){--_||H("loading",null,!1,x())}function w(i,n){var r,o,s,l,a={};return e.eventDataTransform&&(i=e.eventDataTransform(i)),n&&n.eventDataTransform&&(i=n.eventDataTransform(i)),r=S.moment(i.start||i.date),r.isValid()&&(o=null,!i.end||(o=S.moment(i.end),o.isValid()))?(s=i.allDay,void 0===s&&(l=R(n?n.allDayDefault:void 0,e.allDayDefault),s=void 0!==l?l:!(r.hasTime()||o&&o.hasTime())),s?(r.hasTime()&&r.stripTime(),o&&o.hasTime()&&o.stripTime()):(r.hasTime()||(r=S.rezoneDate(r)),o&&!o.hasTime()&&(o=S.rezoneDate(o))),t.extend(a,i),n&&(a.source=n),a._id=i._id||(void 0===i.id?"_fc"+Le++:i.id+""),a.className=i.className?"string"==typeof i.className?i.className.split(/\s+/):i.className:[],a.resources?"string"==typeof a.resources&&(a.resources=a.resources.split(/\s+/)):a.resources=[],a.allDay=s,a.start=r,a.end=o,e.forceEventDuration&&!a.end&&(a.end=P(a)),d(a),a):void 0}function E(t,e,i){var n,r,o,s,l=t._allDay,a=t._start,c=t._end,d=!1;return e||i||(e=t.start,i=t.end),n=t.allDay!=l?t.allDay:!(e||i).hasTime(),n&&(e&&(e=e.clone().stripTime()),i&&(i=i.clone().stripTime())),e&&(r=n?T(e,a.clone().stripTime()):T(e,a)),n!=l?d=!0:i&&(o=T(i||S.getDefaultEventEnd(n,e||a),e||a).subtract(T(c||S.getDefaultEventEnd(l,a),a))),s=D(m(t._id),d,n,r,o),{dateDelta:r,durationDelta:o,undo:s}}function D(i,n,r,o,s){var l=S.getIsAmbigTimezone(),a=[];return t.each(i,function(t,i){var c=i._allDay,h=i._start,u=i._end,f=null!=r?r:c,p=h.clone(),g=!n&&u?u.clone():null;f?(p.stripTime(),g&&g.stripTime()):(p.hasTime()||(p=S.rezoneDate(p)),g&&!g.hasTime()&&(g=S.rezoneDate(g))),g||!e.forceEventDuration&&!+s||(g=S.getDefaultEventEnd(f,p)),p.add(o),g&&g.add(o).add(s),l&&(+o||+s)&&(p.stripZone(),g&&g.stripZone()),i.allDay=f,i.start=p,i.end=g,d(i),a.push(function(){i.allDay=c,i.start=h,i.end=u,d(i)})}),function(){for(var t=0;a.length>t;t++)a[t]()}}var S=this;S.isFetchNeeded=i,S.fetchEvents=n,S.addEventSource=s,S.removeEventSource=a,S.updateEvent=u,S.renderEvent=p,S.removeEvents=g,S.clientEvents=m,S.mutateEvent=E;var b,C,H=S.trigger,x=S.getView,k=S.reportEvents,P=S.getEventEnd,z={events:[]},G=[z],L=0,N=0,_=0,A=[];t.each((e.events?[e.events]:[]).concat(e.eventSources||[]),function(t,e){var i=l(e);i&&G.push(i)});var Y=["title","url","allDay","className","editable","color","backgroundColor","borderColor","textColor"]}function d(t){t._allDay=t.allDay,t._start=t.start.clone(),t._end=t.end?t.end.clone():null}function h(e){function i(e){h=[];var i;if(t.isFunction(e))i={resources:e},h.push(i),c=void 0;else if("string"==typeof e)i={url:e},h.push(i),c=void 0;else if("object"==typeof e&&null!=e){for(var n=0;e.length>n;n++){var r=e[n];o(r),i={resources:r},h.push(i)}c=void 0}}function n(i,n){var o;if(i=i!==void 0?i:!0,!i||void 0===o){o=[];for(var s=h.length,l=0;s>l;l++){var a=r(h[l],n);o=o.concat(a)}}return t.isFunction(e.resourceFilter)?t.grep(o,e.resourceFilter):o}function r(i,n){var r=i.resources;if(r){if(t.isFunction(r))return r()}else{var o=i.url;if(o){var s={};if("object"==typeof n){var l=e.startParam,a=e.endParam;l&&(s[l]=Math.round(+n.intervalStart/1e3)),a&&(s[a]=Math.round(+n.intervalEnd/1e3))}t.ajax(t.extend({},Ge,i,{data:s,dataType:"json",cache:!1,success:function(t){t=t||[],r=t},error:function(){},async:!1}))}}return r}function o(t){t.className?"string"==typeof t.className&&(t.className=t.className.split(/\s+/)):t.className=[];for(var e=Pe.sourceNormalizers,i=0;e.length>i;i++)e[i](t)}function s(t,e,i,n){var r,o,s,c,d=t._allDay,h=t._start,u=t._end,f=!1;return i||n||(i=t.start,n=t.end),r=t.allDay!=d?t.allDay:!(i||n).hasTime(),r&&(i&&(i=i.clone().stripTime()),n&&(n=n.clone().stripTime())),i&&(o=r?T(i,h.clone().stripTime()):T(i,h)),r!=d?f=!0:n&&(s=T(n||a.getDefaultEventEnd(r,i||h),i||h).subtract(T(u||a.getDefaultEventEnd(d,h),h))),c=l(a.clientEvents(t._id),f,r,o,s,e),{dateDelta:o,durationDelta:s,undo:c}}function l(i,n,r,o,s,l){var c=a.getIsAmbigTimezone(),h=[];return t.each(i,function(t,i){var u=i.resources,f=i._allDay,p=i._start,g=i._end,m=null!=r?r:f,v=p.clone(),y=!n&&g?g.clone():null;m?(v.stripTime(),y&&y.stripTime()):(v.hasTime()||(v=a.rezoneDate(v)),y&&!y.hasTime()&&(y=a.rezoneDate(y))),y||!e.forceEventDuration&&!+s||(y=a.getDefaultEventEnd(m,v)),v.add(o),y&&y.add(o).add(s),c&&(+o||+s)&&(v.stripZone(),y&&y.stripZone()),i.allDay=m,i.start=v,i.end=y,i.resources=l,d(i),h.push(function(){i.allDay=f,i.start=p,i.end=g,i.resources=u,d(i)})}),function(){for(var t=0;h.length>t;t++)h[t]()}}var a=this;a.fetchResources=n,a.setResources=i,a.mutateResourceEvent=s;var c,h=[];i(e.resources)}function u(t,e){e.left&&t.css({"border-left-width":1,"margin-left":e.left-1}),e.right&&t.css({"border-right-width":1,"margin-right":e.right-1})}function f(t){t.css({"margin-left":"","margin-right":"","border-left-width":"","border-right-width":""})}function p(e,i,n){var r=Math.floor(i/e.length),o=Math.floor(i-r*(e.length-1)),s=[],l=[],a=[],c=0;g(e),e.each(function(i,n){var d=i===e.length-1?o:r,h=t(n).outerHeight(!0);d>h?(s.push(n),l.push(h),a.push(t(n).height())):c+=h}),n&&(i-=c,r=Math.floor(i/s.length),o=Math.floor(i-r*(s.length-1))),t(s).each(function(e,i){var n=e===s.length-1?o:r,c=l[e],d=a[e],h=n-(c-d);n>c&&t(i).height(h)})}function g(t){t.height("")}function m(e){var i=0;return e.find("> *").each(function(e,n){var r=t(n).outerWidth();r>i&&(i=r)}),i++,e.width(i),i}function v(t,e){return t.height(e).addClass("fc-scroller"),t[0].scrollHeight-1>t[0].clientHeight?!0:(y(t),!1)}function y(t){t.height("").removeClass("fc-scroller")}function w(e){var i=e.css("position"),n=e.parents().filter(function(){var e=t(this);return/(auto|scroll)/.test(e.css("overflow")+e.css("overflow-y")+e.css("overflow-x"))}).eq(0);return"fixed"!==i&&n.length?n:t(e[0].ownerDocument||document)}function E(t){var e=t.offset().left,i=e+t.width(),n=t.children(),r=n.offset().left,o=r+n.outerWidth();return{left:r-e,right:i-o}}function D(t){return 1==t.which&&!t.ctrlKey}function S(t,e,i,n){var r,o,s,l;return e>i&&n>t?(t>=i?(r=t.clone(),s=!0):(r=i.clone(),s=!1),n>=e?(o=e.clone(),l=!0):(o=n.clone(),l=!1),{start:r,end:o,isStart:s,isEnd:l}):void 0}function b(t,e){if(t=t||{},void 0!==t[e])return t[e];for(var i,n=e.split(/(?=[A-Z])/),r=n.length-1;r>=0;r--)if(i=t[n[r].toLowerCase()],void 0!==i)return i;return t["default"]}function T(t,i){return e.duration({days:t.clone().stripTime().diff(i.clone().stripTime(),"days"),ms:t.time()-i.time()})}function C(t){return"[object Date]"===Object.prototype.toString.call(t)||t instanceof Date}function H(t,e){return t-e}function x(t){var e=function(){};return e.prototype=t,new e}function k(t,e){for(var i in e)e.hasOwnProperty(i)&&(t[i]=e[i])}function M(e,i,n){if(t.isFunction(e)&&(e=[e]),e){var r,o;for(r=0;e.length>r;r++)o=e[r].apply(i,n)||o;return o}}function R(){for(var t=0;arguments.length>t;t++)if(void 0!==arguments[t])return arguments[t]}function P(t){return(t+"").replace(/&/g,"&").replace(//g,">").replace(/'/g,"'").replace(/"/g,""").replace(/\n/g,"
")}function z(t){return t.replace(/&.*?;/g,"")}function G(t){return t.charAt(0).toUpperCase()+t.slice(1)}function L(t,e){var i,n,r,o,s=function(){var l=+new Date-o;e>l&&l>0?i=setTimeout(s,e-l):(i=null,t.apply(r,n),i||(r=n=null))};return function(){r=this,n=arguments,o=+new Date,i||(i=setTimeout(s,e))}}function N(i,n,r){var o,s,l,a,c=i[0],d=1==i.length&&"string"==typeof c;return e.isMoment(c)?(a=e.apply(null,i),c._ambigTime&&(a._ambigTime=!0),c._ambigZone&&(a._ambigZone=!0)):C(c)||void 0===c?a=e.apply(null,i):(o=!1,s=!1,d?_e.test(c)?(c+="-01",i=[c],o=!0,s=!0):(l=Ae.exec(c))&&(o=!l[5],s=!0):t.isArray(c)&&(s=!0),a=n?e.utc.apply(e,i):e.apply(null,i),o?(a._ambigTime=!0,a._ambigZone=!0):r&&(s?a._ambigZone=!0:d&&a.zone(c))),new _(a)}function _(t){k(this,t)}function A(t,e){var i,n=[],r=!1,o=!1;for(i=0;t.length>i;i++)n.push(Pe.moment.parseZone(t[i])),r=r||n[i]._ambigTime,o=o||n[i]._ambigZone;for(i=0;n.length>i;i++)r&&!e?n[i].stripTime():o&&n[i].stripZone();return n}function Y(t,i){return e.fn.format.call(t,i)}function V(t,e){return O(t,B(e))}function O(t,e){var i,n="";for(i=0;e.length>i;i++)n+=F(t,e[i]);return n}function F(t,e){var i,n;return"string"==typeof e?e:(i=e.token)?Ye[i]?Ye[i](t):Y(t,i):e.maybe&&(n=O(t,e.maybe),n.match(/[1-9]/))?n:""}function W(t,e,i,n,r){var o;return t=Pe.moment.parseZone(t),e=Pe.moment.parseZone(e),o=(t.localeData||t.lang).call(t),i=o.longDateFormat(i)||i,n=n||" - ",Z(t,e,B(i),n,r)}function Z(t,e,i,n,r){var o,s,l,a,c="",d="",h="",u="",f="";for(s=0;i.length>s&&(o=I(t,e,i[s]),o!==!1);s++)c+=o;for(l=i.length-1;l>s&&(o=I(t,e,i[l]),o!==!1);l--)d=o+d;for(a=s;l>=a;a++)h+=F(t,i[a]),u+=F(e,i[a]);return(h||u)&&(f=r?u+n+h:h+n+u),c+f+d}function I(t,e,i){var n,r;return"string"==typeof i?i:(n=i.token)&&(r=Ve[n.charAt(0)],r&&t.isSame(e,r))?Y(t,n):!1}function B(t){return t in Oe?Oe[t]:Oe[t]=j(t)}function j(t){for(var e,i=[],n=/\[([^\]]*)\]|\(([^\)]*)\)|(LT|(\w)\4*o?)|([^\w\[\(]+)/g;e=n.exec(t);)e[1]?i.push(e[1]):e[2]?i.push({maybe:j(e[2])}):e[3]?i.push({token:e[3]}):e[5]&&i.push(e[5]);return i}function X(t){this.options=t||{}}function $(t){this.grid=t}function q(t){this.coordMaps=t}function U(t,e){this.coordMap=t,this.options=e||{}}function K(t,e){return t||e?t&&e?t.grid===e.grid&&t.row===e.row&&t.col===e.col:!1:!0}function Q(e,i){this.options=i=i||{},this.sourceEl=e,this.parentEl=i.parentEl?t(i.parentEl):e.parent()}function J(t){this.view=t}function te(t){J.call(this,t),this.coordMap=new $(this)}function ee(t,e){return t.eventStartMS-e.eventStartMS||e.eventDurationMS-t.eventDurationMS||e.event.allDay-t.event.allDay||(t.event.title||"").localeCompare(e.event.title)}function ie(t){te.call(this,t)}function ne(t,e){var i,n;for(i=0;e.length>i;i++)if(n=e[i],n.leftCol<=t.rightCol&&n.rightCol>=t.leftCol)return!0;return!1}function re(t,e){return t.leftCol-e.leftCol}function oe(t){te.call(this,t)}function se(t){var e,i,n;if(t.sort(ee),e=le(t),ae(e),i=e[0]){for(n=0;i.length>n;n++)ce(i[n]);for(n=0;i.length>n;n++)de(i[n],0,0)}}function le(t){var e,i,n,r=[];for(e=0;t.length>e;e++){for(i=t[e],n=0;r.length>n&&he(i,r[n]).length;n++);i.level=n,(r[n]||(r[n]=[])).push(i)}return r}function ae(t){var e,i,n,r,o;for(e=0;t.length>e;e++)for(i=t[e],n=0;i.length>n;n++)for(r=i[n],r.forwardSegs=[],o=e+1;t.length>o;o++)he(r,t[o],r.forwardSegs)}function ce(t){var e,i,n=t.forwardSegs,r=0;if(void 0===t.forwardPressure){for(e=0;n.length>e;e++)i=n[e],ce(i),r=Math.max(r,1+i.forwardPressure);t.forwardPressure=r}}function de(t,e,i){var n,r=t.forwardSegs;if(void 0===t.forwardCoord)for(r.length?(r.sort(fe),de(r[0],e+1,i),t.forwardCoord=r[0].backwardCoord):t.forwardCoord=1,t.backwardCoord=t.forwardCoord-(t.forwardCoord-i)/(e+1),n=0;r.length>n;n++)de(r[n],0,t.forwardCoord)}function he(t,e,i){i=i||[];for(var n=0;e.length>n;n++)ue(t,e[n])&&i.push(e[n]);return i}function ue(t,e){return t.bottom>e.top&&t.topd;d++){var h=d*n,u=h+n-1,f=Math.max(a,h),p=Math.min(c,u);if(p>=f){var m=E(f),v=E(p),D=[m.col,v.col].sort(),b=g(f)==s,T=g(p)+1==l;r.push({row:d,leftCol:D[0],rightCol:D[1],isStart:b,isEnd:T})}}return r}function S(t,e){var i,n,r=t.clone().stripTime();return e&&(i=e.clone().stripTime(),n=+e.time(),n&&n>=k&&i.add(1,"days")),(!e||r>=i)&&(i=r.clone().add(1,"days")),{start:r,end:i}}function T(t){var e=S(t.start,t.end);return e.end.diff(e.start,"days")>1}var C=this;C.calendar=i,C.opt=n,C.trigger=r,C.isEventDraggable=o,C.isEventResizable=l,C.eventDrop=a,C.eventResize=c;var H=i.reportEventChange,x=i.options,k=e.duration(x.nextDayThreshold);C.init(),C.getEventTimeText=function(t,e){var r,o;return"object"==typeof t&&"object"==typeof e?(r=t,o=e,e=arguments[2]):(r=t.start,o=t.end),e=e||n("timeFormat"),o&&n("displayEventEnd")?i.formatRange(r,o,e):i.formatDate(r,e)},C.isHiddenDay=d,C.skipHiddenDays=u,C.getCellsPerWeek=h,C.dateToCell=v,C.dateToDayOffset=y,C.dayOffsetToCellOffset=w,C.cellOffsetToCell=E,C.cellToDate=f,C.cellToCellOffset=p,C.cellOffsetToDayOffset=g,C.dayOffsetToDate=m,C.rangeToSegments=D,C.isMultiDayEvent=T;var M,P=n("hiddenDays")||[],z=[],G=[],L=[],N=n("isRTL");(function(){n("weekends")===!1&&P.push(0,6);for(var e=0,i=0;7>e;e++)G[e]=i,z[e]=-1!=t.inArray(e,P),z[e]||(L[i]=e,i++);if(M=i,!M)throw"invalid hiddenDays"})()}function ge(t){pe.call(this,t),this.dayGrid=new ie(this),this.coordMap=this.dayGrid.coordMap}function me(t){ge.call(this,t)}function ve(t){ge.call(this,t)}function ye(t){ge.call(this,t)}function we(t,e){return e.longDateFormat("LT").replace(":mm","(:mm)").replace(/(\Wmm)$/,"($1)").replace(/\s*a$/i,"a")}function Ee(t,e){return e.longDateFormat("LT").replace(/\s*a$/i,"")}function De(t){pe.call(this,t),this.timeGrid=new oe(this),this.opt("allDaySlot")?(this.dayGrid=new ie(this),this.coordMap=new q([this.dayGrid.coordMap,this.timeGrid.coordMap])):this.coordMap=this.timeGrid.coordMap}function Se(t){De.call(this,t)}function be(t){De.call(this,t)}function Te(t){ie.call(this,t)}function Ce(t){oe.call(this,t)}function He(t){pe.call(this,t),this.cellToDate=He.prototype.cellToDate,this.timeGrid=new Ce(this),this.opt("allDaySlot")?(this.dayGrid=new Te(this),this.coordMap=new q([this.dayGrid.coordMap,this.timeGrid.coordMap])):this.coordMap=this.timeGrid.coordMap}function xe(t){He.call(this,t)}var ke={lang:"en",defaultTimedEventDuration:"02:00:00",defaultAllDayEventDuration:{days:1},forceEventDuration:!1,nextDayThreshold:"09:00:00",defaultView:"month",aspectRatio:1.35,header:{left:"title",center:"",right:"today prev,next"},weekends:!0,weekNumbers:!1,weekNumberTitle:"W",weekNumberCalculation:"local",lazyFetching:!0,startParam:"start",endParam:"end",timezoneParam:"timezone",timezone:!1,titleFormat:{month:"MMMM YYYY",week:"ll",day:"LL"},columnFormat:{month:"ddd",week:n,day:"dddd"},timeFormat:{"default":i},displayEventEnd:{month:!1,basicWeek:!1,"default":!0},isRTL:!1,defaultButtonText:{prev:"prev",next:"next",prevYear:"prev year",nextYear:"next year",today:"today",month:"month",week:"week",day:"day"},buttonIcons:{prev:"left-single-arrow",next:"right-single-arrow",prevYear:"left-double-arrow",nextYear:"right-double-arrow"},theme:!1,themeButtonIcons:{prev:"circle-triangle-w",next:"circle-triangle-e",prevYear:"seek-prev",nextYear:"seek-next"},dragOpacity:.75,dragRevertDuration:500,dragScroll:!0,unselectAuto:!0,dropAccept:"*",eventLimit:!1,eventLimitText:"more",eventLimitClick:"popover",dayPopoverFormat:"LL",handleWindowResize:!0,windowResizeDelay:200},Me={en:{columnFormat:{week:"ddd M/D"},dayPopoverFormat:"dddd, MMMM D"}},Re={header:{left:"next,prev today",center:"",right:"title"},buttonIcons:{prev:"right-single-arrow",next:"left-single-arrow",prevYear:"right-double-arrow",nextYear:"left-double-arrow"},themeButtonIcons:{prev:"circle-triangle-e",next:"circle-triangle-w",nextYear:"seek-prev",prevYear:"seek-next"}},Pe=t.fullCalendar={version:"2.1.1"},ze=Pe.views={};t.fn.fullCalendar=function(e){var i=Array.prototype.slice.call(arguments,1),n=this;return this.each(function(r,o){var s,a=t(o),c=a.data("fullCalendar");"string"==typeof e?c&&t.isFunction(c[e])&&(s=c[e].apply(c,i),r||(n=s),"destroy"===e&&a.removeData("fullCalendar")):c||(c=new l(a,e),a.data("fullCalendar",c),c.render())}),n},Pe.langs=Me,Pe.datepickerLang=function(e,i,n){var r=Me[e];r||(r=Me[e]={}),o(r,{isRTL:n.isRTL,weekNumberTitle:n.weekHeader,titleFormat:{month:n.showMonthAfterYear?"YYYY["+n.yearSuffix+"] MMMM":"MMMM YYYY["+n.yearSuffix+"]"},defaultButtonText:{prev:z(n.prevText),next:z(n.nextText),today:z(n.currentText)}}),t.datepicker&&(t.datepicker.regional[i]=t.datepicker.regional[e]=n,t.datepicker.regional.en=t.datepicker.regional[""],t.datepicker.setDefaults(n))},Pe.lang=function(t,e){var i;e&&(i=Me[t],i||(i=Me[t]={}),o(i,e||{})),ke.lang=t},Pe.sourceNormalizers=[],Pe.sourceFetchers=[];var Ge={dataType:"json",cache:!1},Le=1,Ne=["sun","mon","tue","wed","thu","fri","sat"];Pe.applyAll=M;var _e=/^\s*\d{4}-\d\d$/,Ae=/^\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+)?)?)?)?)?$/;Pe.moment=function(){return N(arguments)},Pe.moment.utc=function(){var t=N(arguments,!0);return t.hasTime()&&t.utc(),t},Pe.moment.parseZone=function(){return N(arguments,!0,!0)},_.prototype=x(e.fn),_.prototype.clone=function(){return N([this])},_.prototype.time=function(t){if(null==t)return e.duration({hours:this.hours(),minutes:this.minutes(),seconds:this.seconds(),milliseconds:this.milliseconds()});delete this._ambigTime,e.isDuration(t)||e.isMoment(t)||(t=e.duration(t));var i=0;return e.isDuration(t)&&(i=24*Math.floor(t.asDays())),this.hours(i+t.hours()).minutes(t.minutes()).seconds(t.seconds()).milliseconds(t.milliseconds())},_.prototype.stripTime=function(){var t=this.toArray();return e.fn.utc.call(this),this.year(t[0]).month(t[1]).date(t[2]).hours(0).minutes(0).seconds(0).milliseconds(0),this._ambigTime=!0,this._ambigZone=!0,this},_.prototype.hasTime=function(){return!this._ambigTime},_.prototype.stripZone=function(){var t=this.toArray(),i=this._ambigTime;return e.fn.utc.call(this),this.year(t[0]).month(t[1]).date(t[2]).hours(t[3]).minutes(t[4]).seconds(t[5]).milliseconds(t[6]),i&&(this._ambigTime=!0),this._ambigZone=!0,this},_.prototype.hasZone=function(){return!this._ambigZone},_.prototype.zone=function(t){return null!=t&&(delete this._ambigTime,delete this._ambigZone),e.fn.zone.apply(this,arguments)},_.prototype.local=function(){var t=this.toArray(),i=this._ambigZone;return delete this._ambigTime,delete this._ambigZone,e.fn.local.apply(this,arguments),i&&this.year(t[0]).month(t[1]).date(t[2]).hours(t[3]).minutes(t[4]).seconds(t[5]).milliseconds(t[6]),this},_.prototype.utc=function(){return delete this._ambigTime,delete this._ambigZone,e.fn.utc.apply(this,arguments)},_.prototype.format=function(){return arguments[0]?V(this,arguments[0]):this._ambigTime?Y(this,"YYYY-MM-DD"):this._ambigZone?Y(this,"YYYY-MM-DD[T]HH:mm:ss"):Y(this)},_.prototype.toISOString=function(){return this._ambigTime?Y(this,"YYYY-MM-DD"):this._ambigZone?Y(this,"YYYY-MM-DD[T]HH:mm:ss"):e.fn.toISOString.apply(this,arguments)},_.prototype.isWithin=function(t,e){var i=A([this,t,e]);return i[0]>=i[1]&&i[0]').addClass(i.className||"").css({top:0,left:0}).append(i.content).appendTo(i.parentEl),this.el.on("click",".fc-close",function(){e.hide()}),i.autoHide&&t(document).on("mousedown",this.documentMousedownProxy=t.proxy(this,"documentMousedown"))},documentMousedown:function(e){this.el&&!t(e.target).closest(this.el).length&&this.hide()},destroy:function(){this.hide(),this.el&&(this.el.remove(),this.el=null),t(document).off("mousedown",this.documentMousedownProxy)},position:function(){var e,i,n,r,o,s=this.options,l=this.el.offsetParent().offset(),a=this.el.outerWidth(),c=this.el.outerHeight(),d=t(window),h=w(this.el);r=s.top||0,o=void 0!==s.left?s.left:void 0!==s.right?s.right-a:0,h.is(window)||h.is(document)?(h=d,e=0,i=0):(n=h.offset(),e=n.top,i=n.left),e+=d.scrollTop(),i+=d.scrollLeft(),s.viewportConstrain!==!1&&(r=Math.min(r,e+h.outerHeight()-c-this.margin),r=Math.max(r,e+this.margin),o=Math.min(o,i+h.outerWidth()-a-this.margin),o=Math.max(o,i+this.margin)),this.el.css({top:r-l.top,left:o-l.left}) +},trigger:function(t){this.options[t]&&this.options[t].apply(this,Array.prototype.slice.call(arguments,1))}},$.prototype={grid:null,rows:null,cols:null,containerEl:null,minX:null,maxX:null,minY:null,maxY:null,build:function(){this.grid.buildCoords(this.rows=[],this.cols=[]),this.computeBounds()},getCell:function(t,e){var i,n=null,r=this.rows,o=this.cols,s=-1,l=-1;if(this.inBounds(t,e)){for(i=0;r.length>i;i++)if(e>=r[i][0]&&r[i][1]>e){s=i;break}for(i=0;o.length>i;i++)if(t>=o[i][0]&&o[i][1]>t){l=i;break}s>=0&&l>=0&&(n={row:s,col:l},n.grid=this.grid,n.date=this.grid.getCellDate(n))}return n},computeBounds:function(){var t;this.containerEl&&(t=this.containerEl.offset(),this.minX=t.left,this.maxX=t.left+this.containerEl.outerWidth(),this.minY=t.top,this.maxY=t.top+this.containerEl.outerHeight())},inBounds:function(t,e){return this.containerEl?t>=this.minX&&this.maxX>t&&e>=this.minY&&this.maxY>e:!0}},q.prototype={coordMaps:null,build:function(){var t,e=this.coordMaps;for(t=0;e.length>t;t++)e[t].build()},getCell:function(t,e){var i,n=this.coordMaps,r=null;for(i=0;n.length>i&&!r;i++)r=n[i].getCell(t,e);return r}},U.prototype={coordMap:null,options:null,isListening:!1,isDragging:!1,origCell:null,origDate:null,cell:null,date:null,mouseX0:null,mouseY0:null,mousemoveProxy:null,mouseupProxy:null,scrollEl:null,scrollBounds:null,scrollTopVel:null,scrollLeftVel:null,scrollIntervalId:null,scrollHandlerProxy:null,scrollSensitivity:30,scrollSpeed:200,scrollIntervalMs:50,mousedown:function(t){D(t)&&(t.preventDefault(),this.startListening(t),this.options.distance||this.startDrag(t))},startListening:function(e){var i,n;this.isListening||(e&&this.options.scroll&&(i=w(t(e.target)),i.is(window)||i.is(document)||(this.scrollEl=i,this.scrollHandlerProxy=L(t.proxy(this,"scrollHandler"),100),this.scrollEl.on("scroll",this.scrollHandlerProxy))),this.computeCoords(),e&&(n=this.getCell(e),this.origCell=n,this.origDate=n?n.date:null,this.mouseX0=e.pageX,this.mouseY0=e.pageY),t(document).on("mousemove",this.mousemoveProxy=t.proxy(this,"mousemove")).on("mouseup",this.mouseupProxy=t.proxy(this,"mouseup")).on("selectstart",this.preventDefault),this.isListening=!0,this.trigger("listenStart",e))},computeCoords:function(){this.coordMap.build(),this.computeScrollBounds()},mousemove:function(t){var e,i;this.isDragging||(e=this.options.distance||1,i=Math.pow(t.pageX-this.mouseX0,2)+Math.pow(t.pageY-this.mouseY0,2),i>=e*e&&this.startDrag(t)),this.isDragging&&this.drag(t)},startDrag:function(t){var e;this.isListening||this.startListening(),this.isDragging||(this.isDragging=!0,this.trigger("dragStart",t),e=this.getCell(t),e&&this.cellOver(e,!0))},drag:function(t){var e;this.isDragging&&(e=this.getCell(t),K(e,this.cell)||(this.cell&&this.cellOut(),e&&this.cellOver(e)),this.dragScroll(t))},cellOver:function(t){this.cell=t,this.date=t.date,this.trigger("cellOver",t,t.date)},cellOut:function(){this.cell&&(this.trigger("cellOut",this.cell),this.cell=null,this.date=null)},mouseup:function(t){this.stopDrag(t),this.stopListening(t)},stopDrag:function(t){this.isDragging&&(this.stopScrolling(),this.trigger("dragStop",t),this.isDragging=!1)},stopListening:function(e){this.isListening&&(this.scrollEl&&(this.scrollEl.off("scroll",this.scrollHandlerProxy),this.scrollHandlerProxy=null),t(document).off("mousemove",this.mousemoveProxy).off("mouseup",this.mouseupProxy).off("selectstart",this.preventDefault),this.mousemoveProxy=null,this.mouseupProxy=null,this.isListening=!1,this.trigger("listenStop",e),this.origCell=this.cell=null,this.origDate=this.date=null)},getCell:function(t){return this.coordMap.getCell(t.pageX,t.pageY)},trigger:function(t){this.options[t]&&this.options[t].apply(this,Array.prototype.slice.call(arguments,1))},preventDefault:function(t){t.preventDefault()},computeScrollBounds:function(){var t,e=this.scrollEl;e&&(t=e.offset(),this.scrollBounds={top:t.top,left:t.left,bottom:t.top+e.outerHeight(),right:t.left+e.outerWidth()})},dragScroll:function(t){var e,i,n,r,o=this.scrollSensitivity,s=this.scrollBounds,l=0,a=0;s&&(e=(o-(t.pageY-s.top))/o,i=(o-(s.bottom-t.pageY))/o,n=(o-(t.pageX-s.left))/o,r=(o-(s.right-t.pageX))/o,e>=0&&1>=e?l=-1*e*this.scrollSpeed:i>=0&&1>=i&&(l=i*this.scrollSpeed),n>=0&&1>=n?a=-1*n*this.scrollSpeed:r>=0&&1>=r&&(a=r*this.scrollSpeed)),this.setScrollVel(l,a)},setScrollVel:function(e,i){this.scrollTopVel=e,this.scrollLeftVel=i,this.constrainScrollVel(),!this.scrollTopVel&&!this.scrollLeftVel||this.scrollIntervalId||(this.scrollIntervalId=setInterval(t.proxy(this,"scrollIntervalFunc"),this.scrollIntervalMs))},constrainScrollVel:function(){var t=this.scrollEl;0>this.scrollTopVel?0>=t.scrollTop()&&(this.scrollTopVel=0):this.scrollTopVel>0&&t.scrollTop()+t[0].clientHeight>=t[0].scrollHeight&&(this.scrollTopVel=0),0>this.scrollLeftVel?0>=t.scrollLeft()&&(this.scrollLeftVel=0):this.scrollLeftVel>0&&t.scrollLeft()+t[0].clientWidth>=t[0].scrollWidth&&(this.scrollLeftVel=0)},scrollIntervalFunc:function(){var t=this.scrollEl,e=this.scrollIntervalMs/1e3;this.scrollTopVel&&t.scrollTop(t.scrollTop()+this.scrollTopVel*e),this.scrollLeftVel&&t.scrollLeft(t.scrollLeft()+this.scrollLeftVel*e),this.constrainScrollVel(),this.scrollTopVel||this.scrollLeftVel||this.stopScrolling()},stopScrolling:function(){this.scrollIntervalId&&(clearInterval(this.scrollIntervalId),this.scrollIntervalId=null,this.computeCoords())},scrollHandler:function(){this.scrollIntervalId||this.computeCoords()}},Q.prototype={options:null,sourceEl:null,el:null,parentEl:null,top0:null,left0:null,mouseY0:null,mouseX0:null,topDelta:null,leftDelta:null,mousemoveProxy:null,isFollowing:!1,isHidden:!1,isAnimating:!1,start:function(e){this.isFollowing||(this.isFollowing=!0,this.mouseY0=e.pageY,this.mouseX0=e.pageX,this.topDelta=0,this.leftDelta=0,this.isHidden||this.updatePosition(),t(document).on("mousemove",this.mousemoveProxy=t.proxy(this,"mousemove")))},stop:function(e,i){function n(){this.isAnimating=!1,r.destroyEl(),this.top0=this.left0=null,i&&i()}var r=this,o=this.options.revertDuration;this.isFollowing&&!this.isAnimating&&(this.isFollowing=!1,t(document).off("mousemove",this.mousemoveProxy),e&&o&&!this.isHidden?(this.isAnimating=!0,this.el.animate({top:this.top0,left:this.left0},{duration:o,complete:n})):n())},getEl:function(){var t=this.el;return t||(this.sourceEl.width(),t=this.el=this.sourceEl.clone().css({position:"absolute",visibility:"",display:this.isHidden?"none":"",margin:0,right:"auto",bottom:"auto",width:this.sourceEl.width(),height:this.sourceEl.height(),opacity:this.options.opacity||"",zIndex:this.options.zIndex}).appendTo(this.parentEl)),t},destroyEl:function(){this.el&&(this.el.remove(),this.el=null)},updatePosition:function(){var t,e;this.getEl(),null===this.top0&&(this.sourceEl.width(),t=this.sourceEl.offset(),e=this.el.offsetParent().offset(),this.top0=t.top-e.top,this.left0=t.left-e.left),this.el.css({top:this.top0+this.topDelta,left:this.left0+this.leftDelta})},mousemove:function(t){this.topDelta=t.pageY-this.mouseY0,this.leftDelta=t.pageX-this.mouseX0,this.isHidden||this.updatePosition()},hide:function(){this.isHidden||(this.isHidden=!0,this.el&&this.el.hide())},show:function(){this.isHidden&&(this.isHidden=!1,this.updatePosition(),this.getEl().show())}},J.prototype={view:null,cellHtml:"
"+s+""},bookendCells:function(t,e,i){var n=this.view,r=this.getHtmlRenderer("intro",e)(i||0),o=this.getHtmlRenderer("outro",e)(i||0),s=n.opt("isRTL"),l=s?o:r,a=s?r:o;return"string"==typeof t?l+t+a:t.prepend(l).append(a)},getHtmlRenderer:function(t,e){var i,n,r,o,s=this.view;return i=t+"Html",e&&(n=e+G(t)+"Html"),n&&(o=s[n])?r=s:n&&(o=this[n])?r=this:(o=s[i])?r=s:(o=this[i])&&(r=this),"function"==typeof o?function(){return o.apply(r,arguments)||""}:function(){return o||""}}},te.prototype=x(J.prototype),t.extend(te.prototype,{el:null,coordMap:null,cellDuration:null,render:function(){this.bindHandlers()},destroy:function(){},buildCoords:function(){},getCellDate:function(){},getCellDayEl:function(){},rangeToSegs:function(){},bindHandlers:function(){var e=this;this.el.on("mousedown",function(i){t(i.target).is(".fc-event-container *, .fc-more")||t(i.target).closest(".fc-popover").length||e.dayMousedown(i)}),this.bindSegHandlers()},dayMousedown:function(t){var e,i,n,r=this,o=this.view,s=o.opt("selectable"),l=null,a=new U(this.coordMap,{scroll:o.opt("dragScroll"),dragStart:function(){o.unselect()},cellOver:function(t,o){a.origDate&&(n=r.getCellDayEl(t),l=[o,a.origDate].sort(H),e=l[0],i=l[1].clone().add(r.cellDuration),s&&r.renderSelection(e,i))},cellOut:function(){l=null,r.destroySelection()},listenStop:function(t){l&&(l[0].isSame(l[1])&&o.trigger("dayClick",n[0],e,t),s&&o.reportSelection(e,i,t))}});a.mousedown(t)},renderDrag:function(){},destroyDrag:function(){},renderResize:function(){},destroyResize:function(){},renderRangeHelper:function(t,e,i){var n,r=this.view;!e&&r.opt("forceEventDuration")&&(e=r.calendar.getDefaultEventEnd(!t.hasTime(),t)),n=i?x(i.event):{},n.start=t,n.end=e,n.allDay=!(t.hasTime()||e&&e.hasTime()),n.className=(n.className||[]).concat("fc-helper"),i||(n.editable=!1),this.renderHelper(n,i)},renderHelper:function(){},destroyHelper:function(){},renderSelection:function(t,e){this.renderHighlight(t,e)},destroySelection:function(){this.destroyHighlight()},renderHighlight:function(){},destroyHighlight:function(){},headHtml:function(){return'
'+"
'),e>t&&(n+=''),i>e&&(n+=''),n=this.bookendCells(n,"highlight"),'
'+n+""+"
"+"
"}}),t.extend(ee.prototype,{segs:null,rowStructs:null,renderEvents:function(e){var i=this.rowStructs=this.renderEventRows(e),n=[];this.rowEls.each(function(e,r){t(r).find(".fc-content-skeleton > table").append(i[e].tbodyEl),n.push.apply(n,i[e].segs)}),this.segs=n},getSegs:function(){return(this.segs||[]).concat(this.popoverSegs||[])},destroyEvents:function(){var t,e;for(J.prototype.destroyEvents.call(this),t=this.rowStructs||[];e=t.pop();)e.tbodyEl.remove();this.segs=null,this.destroySegPopover()},renderEventRows:function(t){var e,i,n=this.eventsToSegs(t),r=[];for(n=this.renderSegs(n),e=this.groupSegRows(n),i=0;e.length>i;i++)r.push(this.renderEventRow(i,e[i]));return r},renderSegHtml:function(t,e){var i,n=this.view,r=n.opt("isRTL"),o=t.event,s=n.isEventDraggable(o),l=!e&&o.allDay&&t.isEnd&&n.isEventResizable(o),a=this.getSegClasses(t,s,l),c=this.getEventSkinCss(o),d="";return a.unshift("fc-day-grid-event"),!o.allDay&&t.isStart&&(d=''+R(n.getEventTimeText(o))+""),i=''+(R(o.title||"")||" ")+"",'"+'
'+(r?i+" "+d:d+" "+i)+"
"+(l?'
':"")+""},renderEventRow:function(e,i){function n(e){for(;e>s;)d=(y[r-1]||[])[s],d?d.attr("rowspan",parseInt(d.attr("rowspan")||1,10)+1):(d=t("
"),l.append(d)),v[r][s]=d,y[r][s]=d,s++}var r,o,s,l,a,c,d,h=this.view,u=h.colCnt,f=this.buildSegLevels(i),g=Math.max(1,f.length),p=t("
').append(c.el),c.leftCol!=c.rightCol?d.attr("colspan",c.rightCol-c.leftCol+1):y[r][s]=d;c.rightCol>=s;)v[r][s]=d,m[r][s]=c,s++;l.append(d)}n(u),this.bookendCells(l,"eventSkeleton"),p.append(l)}return{row:e,tbodyEl:p,cellMatrix:v,segMatrix:m,segLevels:f,segs:i}},buildSegLevels:function(t){var e,i,n,r=[];for(t.sort(te),e=0;t.length>e;e++){for(i=t[e],n=0;r.length>n&&ie(i,r[n]);n++);i.level=n,(r[n]||(r[n]=[])).push(i)}for(n=0;r.length>n;n++)r[n].sort(ne);return r},groupSegRows:function(t){var e,i=this.view,n=[];for(e=0;i.rowCnt>e;e++)n.push([]);for(e=0;t.length>e;e++)n[t[e].row].push(t[e]);return n}}),t.extend(ee.prototype,{segPopover:null,popoverSegs:null,destroySegPopover:function(){this.segPopover&&this.segPopover.hide()},limitRows:function(t){var e,i,n=this.rowStructs||[];for(e=0;n.length>e;e++)this.unlimitRow(e),i=t?"number"==typeof t?t:this.computeRowLevelLimit(e):!1,i!==!1&&this.limitRow(e,i)},computeRowLevelLimit:function(t){var e,i,n=this.rowEls.eq(t),r=n.height(),o=this.rowStructs[t].tbodyEl.children();for(e=0;o.length>e;e++)if(i=o.eq(e).removeClass("fc-limited"),i.position().top+i.outerHeight()>r)return e;return!1},limitRow:function(e,i){function n(n){for(;n>T;)r={row:e,col:T},d=E.getCellSegs(r,i),d.length&&(f=s[i-1][T],w=E.renderMoreLink(r,d),y=t("
").append(w),f.append(y),D.push(y[0])),T++}var r,o,s,l,a,c,d,h,u,f,g,p,m,v,y,w,E=this,b=this.view,S=this.rowStructs[e],D=[],T=0;if(i&&S.segLevels.length>i){for(o=S.segLevels[i-1],s=S.cellMatrix,l=S.tbodyEl.children().slice(i).addClass("fc-limited").get(),a=0;o.length>a;a++){for(c=o[a],n(c.leftCol),u=[],h=0;c.rightCol>=T;)r={row:e,col:T},d=this.getCellSegs(r,i),u.push(d),h+=d.length,T++;if(h){for(f=s[i-1][c.leftCol],g=f.attr("rowspan")||1,p=[],m=0;u.length>m;m++)v=t('
').attr("rowspan",g),d=u[m],r={row:e,col:c.leftCol+m},w=this.renderMoreLink(r,[c].concat(d)),y=t("
").append(w),v.append(y),p.push(v[0]),D.push(v[0]);f.addClass("fc-limited").after(t(p)),l.push(f[0])}}n(b.colCnt),S.moreEls=t(D),S.limitedEls=t(l)}},unlimitRow:function(t){var e=this.rowStructs[t];e.moreEls&&(e.moreEls.remove(),e.moreEls=null),e.limitedEls&&(e.limitedEls.removeClass("fc-limited"),e.limitedEls=null)},renderMoreLink:function(e,i){var n=this,r=this.view;return t('').text(this.getMoreLinkText(i.length)).on("click",function(o){var s=r.opt("eventLimitClick"),l=r.cellToDate(e),a=t(this),c=n.getCellDayEl(e),d=n.getCellSegs(e),h=n.resliceDaySegs(d,l),u=n.resliceDaySegs(i,l);"function"==typeof s&&(s=r.trigger("eventLimitClick",null,{date:l,dayEl:c,moreEl:a,segs:h,hiddenSegs:u},o)),"popover"===s?n.showSegPopover(l,e,a,h):"string"==typeof s&&r.calendar.zoomTo(l,s)})},showSegPopover:function(t,e,i,n){var r,o,s=this,l=this.view,a=i.parent();r=1==l.rowCnt?this.view.el:this.rowEls.eq(e.row),o={className:"fc-more-popover",content:this.renderSegPopoverContent(t,n),parentEl:this.el,top:r.offset().top,autoHide:!0,viewportConstrain:l.opt("popoverViewportConstrain"),hide:function(){s.segPopover.destroy(),s.segPopover=null,s.popoverSegs=null}},l.opt("isRTL")?o.right=a.offset().left+a.outerWidth()+1:o.left=a.offset().left-1,this.segPopover=new j(o),this.segPopover.show()},renderSegPopoverContent:function(e,i){var n,r=this.view,o=r.opt("theme"),s=e.format(r.opt("dayPopoverFormat")),l=t('
'+''+''+R(s)+""+'
'+"
"+'
'+'
'+"
"),a=l.find(".fc-event-container");for(i=this.renderSegs(i,!0),this.popoverSegs=i,n=0;i.length>n;n++)i[n].cellDate=e,a.append(i[n].el);return l},resliceDaySegs:function(e,i){var n=t.map(e,function(t){return t.event}),r=i.clone().stripTime(),o=r.clone().add(1,"days");return this.eventsToSegs(n,r,o)},getMoreLinkText:function(t){var e=this.view,i=e.opt("eventLimitText");return"function"==typeof i?i(t):"+"+t+" "+i},getCellSegs:function(t,e){for(var i,n=this.rowStructs[t.row].segMatrix,r=e||0,o=[];n.length>r;)i=n[r][t.col],i&&o.push(i),r++;return o}}),re.prototype=H(J.prototype),t.extend(re.prototype,{slotDuration:null,snapDuration:null,minTime:null,maxTime:null,dayEls:null,slatEls:null,slatTops:null,highlightEl:null,helperEl:null,render:function(){this.processOptions(),this.el.html(this.renderHtml()),this.dayEls=this.el.find(".fc-day"),this.slatEls=this.el.find(".fc-slats tr"),this.computeSlatTops(),J.prototype.render.call(this)},renderHtml:function(){return'
'+this.rowHtml("slotBg")+"
"+"
"+'
'+""+this.slatRowHtml()+"
"+"
"},slotBgCellHtml:function(t,e,i){return this.bgCellHtml(t,e,i)},slatRowHtml:function(){for(var t,i,n,r=this.view,o=r.calendar,s=r.opt("isRTL"),l="",a=0===this.slotDuration.asMinutes()%15,c=e.duration(+this.minTime);this.maxTime>c;)t=r.start.clone().time(c),i=t.minutes(),n='
"+(a&&i?"":""+R(o.formatDate(t,r.opt("axisFormat")))+"")+"
'+(s?n:"")+"
',d=n.col),r=l.cellToDate(0,d),o=this.computeDateTop(n.start,r),s=this.computeDateTop(n.end,r),c+='
'+"
"+"
'),c=this.bookendCells(c,"highlight"),'
'+c+""+"
"+"
"}}),t.extend(re.prototype,{segs:null,eventSkeletonEl:null,renderEvents:function(e){var i=this.renderEventTable(e);this.eventSkeletonEl=t('
').append(i.tableEl),this.el.append(this.eventSkeletonEl),this.segs=i.segs},getSegs:function(){return this.segs||[]},destroyEvents:function(){J.prototype.destroyEvents.call(this),this.eventSkeletonEl&&(this.eventSkeletonEl.remove(),this.eventSkeletonEl=null),this.segs=null},renderEventTable:function(e){var i,n,r,o,s,l,a=t("
"),c=a.find("tr"),d=this.eventsToSegs(e);for(d=this.renderSegs(d),i=this.groupSegCols(d),this.computeSegVerticals(d),o=0;i.length>o;o++){for(s=i[o],oe(s),l=t('
'),n=0;s.length>n;n++)r=s[n],r.el.css(this.generateSegPositionCss(r)),30>r.bottom-r.top&&r.el.addClass("fc-short"),l.append(r.el);c.append(t("
").append(l))}return this.bookendCells(c,"eventSkeleton"),{tableEl:a,segs:d}},updateSegVerticals:function(){var t,e=this.segs;if(e)for(this.computeSegVerticals(e),t=0;e.length>t;t++)e[t].el.css(this.generateSegVerticalCss(e[t]))},computeSegVerticals:function(t){var e,i;for(e=0;t.length>e;e++)i=t[e],i.top=this.computeDateTop(i.start,i.start),i.bottom=this.computeDateTop(i.end,i.start)},renderSegHtml:function(t,e){var i,n,r,o=this.view,s=t.event,l=o.isEventDraggable(s),a=!e&&t.isEnd&&o.isEventResizable(s),c=this.getSegClasses(t,l,a),d=this.getEventSkinCss(s);return c.unshift("fc-time-grid-event"),o.isMultiDayEvent(s)?(t.isStart||t.isEnd)&&(i=o.getEventTimeText(t.start,t.end),n=o.getEventTimeText(t.start,t.end,"LT"),r=o.getEventTimeText(t.start,null)):(i=o.getEventTimeText(s),n=o.getEventTimeText(s,"LT"),r=o.getEventTimeText(s.start,null)),'"+'
'+(i?'
"+""+R(i)+""+"
":"")+(s.title?'
'+R(s.title)+"
":"")+"
"+'
'+(a?'
':"")+""},generateSegPositionCss:function(t){var e,i,n=this.view,r=n.opt("isRTL"),o=n.opt("slotEventOverlap"),s=t.backwardCoord,l=t.forwardCoord,a=this.generateSegVerticalCss(t);return o&&(l=Math.min(1,s+2*(l-s))),r?(e=1-l,i=s):(e=s,i=1-l),a.zIndex=t.level+1,a.left=100*e+"%",a.right=100*i+"%",o&&t.forwardPressure&&(a[r?"marginLeft":"marginRight"]=20),a},generateSegVerticalCss:function(t){return{top:t.top,bottom:-t.bottom}},groupSegCols:function(t){var e,i=this.view,n=[];for(e=0;i.colCnt>e;e++)n.push([]);for(e=0;t.length>e;e++)n[t[e].col].push(t[e]);return n}}),fe.prototype={calendar:null,coordMap:null,el:null,start:null,end:null,intervalStart:null,intervalEnd:null,rowCnt:null,colCnt:null,isSelected:!1,scrollerEl:null,scrollTop:null,widgetHeaderClass:null,widgetContentClass:null,highlightStateClass:null,documentMousedownProxy:null,documentDragStartProxy:null,init:function(){var e=this.opt("theme")?"ui":"fc";this.widgetHeaderClass=e+"-widget-header",this.widgetContentClass=e+"-widget-content",this.highlightStateClass=e+"-state-highlight",this.documentMousedownProxy=t.proxy(this,"documentMousedown"),this.documentDragStartProxy=t.proxy(this,"documentDragStart")},render:function(){this.updateSize(),this.trigger("viewRender",this,this,this.el),t(document).on("mousedown",this.documentMousedownProxy).on("dragstart",this.documentDragStartProxy)},destroy:function(){this.unselect(),this.trigger("viewDestroy",this,this,this.el),this.destroyEvents(),this.el.empty(),t(document).off("mousedown",this.documentMousedownProxy).off("dragstart",this.documentDragStartProxy)},incrementDate:function(){},updateSize:function(t){t&&this.recordScroll(),this.updateHeight(),this.updateWidth()},updateWidth:function(){},updateHeight:function(){var t=this.calendar;this.setHeight(t.getSuggestedViewHeight(),t.isHeightAuto())},setHeight:function(){},computeScrollerHeight:function(t){var e,i=this.el.add(this.scrollerEl);return i.css({position:"relative",left:-1}),e=this.el.outerHeight()-this.scrollerEl.height(),i.css({position:"",left:""}),t-e},recordScroll:function(){this.scrollerEl&&(this.scrollTop=this.scrollerEl.scrollTop())},restoreScroll:function(){null!==this.scrollTop&&this.scrollerEl.scrollTop(this.scrollTop)},renderEvents:function(){this.segEach(function(t){this.trigger("eventAfterRender",t.event,t.event,t.el)}),this.trigger("eventAfterAllRender")},destroyEvents:function(){this.segEach(function(t){this.trigger("eventDestroy",t.event,t.event,t.el)})},resolveEventEl:function(e,i){var n=this.trigger("eventRender",e,e,i);return n===!1?i=null:n&&n!==!0&&(i=t(n)),i},showEvent:function(t){this.segEach(function(t){t.el.css("visibility","")},t)},hideEvent:function(t){this.segEach(function(t){t.el.css("visibility","hidden")},t)},segEach:function(t,e){var i,n=this.getSegs();for(i=0;n.length>i;i++)e&&n[i].event._id!==e._id||t.call(this,n[i])},getSegs:function(){},renderDrag:function(){},destroyDrag:function(){},documentDragStart:function(e){var i,n=this,r=null;this.opt("droppable")&&(i=new q(this.coordMap,{cellOver:function(t,e){r=e,n.renderDrag(e)},cellOut:function(){r=null,n.destroyDrag()}}),t(document).one("dragstop",function(t,e){n.destroyDrag(),r&&n.trigger("drop",t.target,r,t,e)}),i.startDrag(e))},select:function(t,e,i){this.unselect(i),this.renderSelection(t,e),this.reportSelection(t,e,i)},renderSelection:function(){},reportSelection:function(t,e,i){this.isSelected=!0,this.trigger("select",null,t,e,i)},unselect:function(t){this.isSelected&&(this.isSelected=!1,this.destroySelection(),this.trigger("unselect",null,t))},destroySelection:function(){},documentMousedown:function(e){var i;this.isSelected&&this.opt("unselectAuto")&&E(e)&&(i=this.opt("unselectCancel"),i&&t(e.target).closest(i).length||this.unselect(e))}},ge.prototype=H(fe.prototype),t.extend(ge.prototype,{dayGrid:null,dayNumbersVisible:!1,weekNumbersVisible:!1,weekNumberWidth:null,headRowEl:null,render:function(t,e,i){this.rowCnt=t,this.colCnt=e,this.dayNumbersVisible=i,this.weekNumbersVisible=this.opt("weekNumbers"),this.dayGrid.numbersVisible=this.dayNumbersVisible||this.weekNumbersVisible,this.el.addClass("fc-basic-view").html(this.renderHtml()),this.headRowEl=this.el.find("thead .fc-row"),this.scrollerEl=this.el.find(".fc-day-grid-container"),this.dayGrid.coordMap.containerEl=this.scrollerEl,this.dayGrid.el=this.el.find(".fc-day-grid"),this.dayGrid.render(this.hasRigidRows()),fe.prototype.render.call(this)},destroy:function(){this.dayGrid.destroy(),fe.prototype.destroy.call(this)},renderHtml:function(){return'"+""+""+""+""+'"+""+""+"
'+this.dayGrid.headHtml()+"
'+'
'+'
'+"
"+"
" -},headIntroHtml:function(){return this.weekNumbersVisible?'
"+""+R(this.opt("weekNumberTitle"))+""+""+""+this.calendar.calculateWeekNumber(this.cellToDate(t,0))+""+"'+i.date()+""},weekNumberStyleAttr:function(){return null!==this.weekNumberWidth?'style="width:'+this.weekNumberWidth+'px"':""},hasRigidRows:function(){var t=this.opt("eventLimit");return t&&"number"!=typeof t},updateWidth:function(){this.weekNumbersVisible&&(this.weekNumberWidth=p(this.el.find(".fc-week-number")))},setHeight:function(t,e){var i,n=this.opt("eventLimit");v(this.scrollerEl),u(this.headRowEl),this.dayGrid.destroySegPopover(),n&&"number"==typeof n&&this.dayGrid.limitRows(n),i=this.computeScrollerHeight(t),this.setGridHeight(i,e),n&&"number"!=typeof n&&this.dayGrid.limitRows(n),!e&&m(this.scrollerEl,i)&&(h(this.headRowEl,w(this.scrollerEl)),i=this.computeScrollerHeight(t),this.scrollerEl.height(i),this.restoreScroll())},setGridHeight:function(t,e){e?g(this.dayGrid.rowEls):f(this.dayGrid.rowEls,t,!0)},renderEvents:function(t){this.dayGrid.renderEvents(t),this.updateHeight(),fe.prototype.renderEvents.call(this,t)},getSegs:function(){return this.dayGrid.getSegs()},destroyEvents:function(){fe.prototype.destroyEvents.call(this),this.recordScroll(),this.dayGrid.destroyEvents()},renderDrag:function(t,e,i){return this.dayGrid.renderDrag(t,e,i)},destroyDrag:function(){this.dayGrid.destroyDrag()},renderSelection:function(t,e){this.dayGrid.renderSelection(t,e)},destroySelection:function(){this.dayGrid.destroySelection()}}),r({fixedWeekCount:!0}),xe.month=pe,pe.prototype=H(ge.prototype),t.extend(pe.prototype,{name:"month",incrementDate:function(t,e){return t.clone().stripTime().add(e,"months").startOf("month")},render:function(t){var e;this.intervalStart=t.clone().stripTime().startOf("month"),this.intervalEnd=this.intervalStart.clone().add(1,"months"),this.start=this.intervalStart.clone(),this.start=this.skipHiddenDays(this.start),this.start.startOf("week"),this.start=this.skipHiddenDays(this.start),this.end=this.intervalEnd.clone(),this.end=this.skipHiddenDays(this.end,-1,!0),this.end.add((7-this.end.weekday())%7,"days"),this.end=this.skipHiddenDays(this.end,-1,!0),e=Math.ceil(this.end.diff(this.start,"weeks",!0)),this.isFixedWeeks()&&(this.end.add(6-e,"weeks"),e=6),this.title=this.calendar.formatDate(this.intervalStart,this.opt("titleFormat")),ge.prototype.render.call(this,e,this.getCellsPerWeek(),!0)},setGridHeight:function(t,e){e=e||"variable"===this.opt("weekMode"),e&&(t*=this.rowCnt/6),f(this.dayGrid.rowEls,t,!e)},isFixedWeeks:function(){var t=this.opt("weekMode");return t?"fixed"===t:this.opt("fixedWeekCount")}}),xe.basicWeek=me,me.prototype=H(ge.prototype),t.extend(me.prototype,{name:"basicWeek",incrementDate:function(t,e){return t.clone().stripTime().add(e,"weeks").startOf("week")},render:function(t){this.intervalStart=t.clone().stripTime().startOf("week"),this.intervalEnd=this.intervalStart.clone().add(1,"weeks"),this.start=this.skipHiddenDays(this.intervalStart),this.end=this.skipHiddenDays(this.intervalEnd,-1,!0),this.title=this.calendar.formatRange(this.start,this.end.clone().subtract(1),this.opt("titleFormat")," — "),ge.prototype.render.call(this,1,this.getCellsPerWeek(),!1)}}),xe.basicDay=ve,ve.prototype=H(ge.prototype),t.extend(ve.prototype,{name:"basicDay",incrementDate:function(t,e){var i=t.clone().stripTime().add(e,"days");return i=this.skipHiddenDays(i,0>e?-1:1)},render:function(t){this.start=this.intervalStart=t.clone().stripTime(),this.end=this.intervalEnd=this.start.clone().add(1,"days"),this.title=this.calendar.formatDate(this.start,this.opt("titleFormat")),ge.prototype.render.call(this,1,1,!1)}}),r({allDaySlot:!0,allDayText:"all-day",scrollTime:"06:00:00",slotDuration:"00:30:00",axisFormat:ye,timeFormat:{agenda:we},minTime:"00:00:00",maxTime:"24:00:00",slotEventOverlap:!0});var Ye=5;Ee.prototype=H(fe.prototype),t.extend(Ee.prototype,{timeGrid:null,dayGrid:null,axisWidth:null,noScrollRowEls:null,bottomRuleEl:null,bottomRuleHeight:null,render:function(e){this.rowCnt=1,this.colCnt=e,this.el.addClass("fc-agenda-view").html(this.renderHtml()),this.scrollerEl=this.el.find(".fc-time-grid-container"),this.timeGrid.coordMap.containerEl=this.scrollerEl,this.timeGrid.el=this.el.find(".fc-time-grid"),this.timeGrid.render(),this.bottomRuleEl=t('
').appendTo(this.timeGrid.el),this.dayGrid&&(this.dayGrid.el=this.el.find(".fc-day-grid"),this.dayGrid.render(),this.dayGrid.bottomCoordPadding=this.dayGrid.el.next("hr").outerHeight()),this.noScrollRowEls=this.el.find(".fc-row:not(.fc-scroller *)"),fe.prototype.render.call(this),this.resetScroll()},destroy:function(){this.timeGrid.destroy(),this.dayGrid&&this.dayGrid.destroy(),fe.prototype.destroy.call(this)},renderHtml:function(){return'"+""+""+""+""+'"+""+""+"
'+this.timeGrid.headHtml()+"
'+(this.dayGrid?'

':"")+'
'+'
'+"
"+"
"},headIntroHtml:function(){var t,e,i,n;return this.opt("weekNumbers")?(t=this.cellToDate(0,0),e=this.calendar.calculateWeekNumber(t),i=this.opt("weekNumberTitle"),n=this.opt("isRTL")?e+i:i+e,'
"+""+R(n)+""+""+""+(this.opt("allDayHtml")||R(this.opt("allDayText")))+""+"",rowHtml:function(t,e){var i,n,r=this.view,o=this.getHtmlRenderer("cell",t),s="";for(e=e||0,i=0;r.colCnt>i;i++)n=r.cellToDate(e,i),s+=o(e,i,n);return s=this.bookendCells(s,t,e),"
"+""+this.rowHtml("head")+""+"
"+"
"},headCellHtml:function(t,e,i){var n=this.view,r=n.calendar,o=n.opt("columnFormat");return''+P(r.formatDate(i,o))+""},bgCellHtml:function(t,e,i){var n=this.view,r=this.getDayClasses(i);return r.unshift("fc-day",n.widgetContentClass),''},getDayClasses:function(t){var e=this.view,i=e.calendar.getNow().stripTime(),n=["fc-"+Ne[t.day()]];return"month"===e.name&&t.month()!=e.intervalStart.month()&&n.push("fc-other-month"),t.isSame(i,"day")?n.push("fc-today",e.highlightStateClass):i>t?n.push("fc-past"):n.push("fc-future"),n}}),t.extend(te.prototype,{mousedOverSeg:null,isDraggingSeg:!1,isResizingSeg:!1,renderEvents:function(){},getSegs:function(){},destroyEvents:function(){this.triggerSegMouseout()},renderSegs:function(e,i){var n,r=this.view,o="",s=[];for(n=0;e.length>n;n++)o+=this.renderSegHtml(e[n],i);return t(o).each(function(i,n){var o=e[i],l=r.resolveEventEl(o.event,t(n));l&&(l.data("fc-seg",o),o.el=l,s.push(o))}),s},renderSegHtml:function(){},eventsToSegs:function(e,i,n){var r=this;return t.map(e,function(t){return r.eventToSegs(t,i,n)})},eventToSegs:function(t,e,i){var n,r,o,s=t.start.clone().stripZone(),l=this.view.calendar.getEventEnd(t).stripZone();for(e&&i?(o=S(s,l,e,i),n=o?[o]:[]):n=this.rangeToSegs(s,l),r=0;n.length>r;r++)o=n[r],o.event=t,o.eventStartMS=+s,o.eventDurationMS=l-s;return n},bindSegHandlers:function(){var e=this,i=this.view;t.each({mouseenter:function(t,i){e.triggerSegMouseover(t,i)},mouseleave:function(t,i){e.triggerSegMouseout(t,i)},click:function(t,e){return i.trigger("eventClick",this,t.event,e)},mousedown:function(n,r){t(r.target).is(".fc-resizer")&&i.isEventResizable(n.event)?e.segResizeMousedown(n,r):i.isEventDraggable(n.event)&&e.segDragMousedown(n,r)}},function(i,n){e.el.on(i,".fc-event-container > *",function(i){var r=t(this).data("fc-seg");return!r||e.isDraggingSeg||e.isResizingSeg?void 0:n.call(this,r,i)})})},triggerSegMouseover:function(t,e){this.mousedOverSeg||(this.mousedOverSeg=t,this.view.trigger("eventMouseover",t.el[0],t.event,e))},triggerSegMouseout:function(t,e){e=e||{},this.mousedOverSeg&&(t=t||this.mousedOverSeg,this.mousedOverSeg=null,this.view.trigger("eventMouseout",t.el[0],t.event,e))},segDragMousedown:function(t,e){var i,n,r=this,o=this.view,s=t.el,l=t.event,a=new Q(t.el,{parentEl:o.el,opacity:o.opt("dragOpacity"),revertDuration:o.opt("dragRevertDuration"),zIndex:2}),c=new U(o.coordMap,{distance:5,scroll:o.opt("dragScroll"),listenStart:function(t){a.hide(),a.start(t)},dragStart:function(e){r.triggerSegMouseout(t,e),r.isDraggingSeg=!0,o.hideEvent(l),o.trigger("eventDragStart",s[0],l,e,{})},cellOver:function(e,s){var l=t.cellDate||c.origDate,d=r.computeDraggedEventDates(t,l,s);i=d.start,n=d.end,o.renderDrag(i,n,t)?a.hide():a.show()},cellOut:function(){i=null,o.destroyDrag(),a.show()},dragStop:function(t){var e=i&&!i.isSame(l.start);a.stop(!e,function(){r.isDraggingSeg=!1,o.destroyDrag(),o.showEvent(l),o.trigger("eventDragStop",s[0],l,t,{}),e&&o.eventDrop(s[0],l,i,t)})},listenStop:function(){a.stop()}});c.mousedown(e)},computeDraggedEventDates:function(t,e,i){var n,r,o,s=this.view,l=t.event,a=l.start,c=s.calendar.getEventEnd(l);return i.hasTime()===e.hasTime()?(n=T(i,e),r=a.clone().add(n),o=null===l.end?null:c.clone().add(n)):(r=i,o=null),{start:r,end:o}},segResizeMousedown:function(t,e){function i(){r.destroyResize(),o.showEvent(l)}var n,r=this,o=this.view,s=t.el,l=t.event,a=l.start,c=o.calendar.getEventEnd(l),d=null;n=new U(this.coordMap,{distance:5,scroll:o.opt("dragScroll"),dragStart:function(e){r.triggerSegMouseout(t,e),r.isResizingSeg=!0,o.trigger("eventResizeStart",s[0],l,e,{})},cellOver:function(e,n){n.isBefore(a)&&(n=a),d=n.clone().add(r.cellDuration),d.isSame(c)?(d=null,i()):(r.renderResize(a,d,t),o.hideEvent(l))},cellOut:function(){d=null,i()},dragStop:function(t){r.isResizingSeg=!1,i(),o.trigger("eventResizeStop",s[0],l,t,{}),d&&o.eventResize(s[0],l,d,t)}}),n.mousedown(e)},getSegClasses:function(t,e,i){var n=t.event,r=["fc-event",t.isStart?"fc-start":"fc-not-start",t.isEnd?"fc-end":"fc-not-end"].concat(n.className,n.source?n.source.className:[]);return e&&r.push("fc-draggable"),i&&r.push("fc-resizable"),r},getEventSkinCss:function(t){var e=this.view,i=t.source||{},n=t.color,r=i.color,o=e.opt("eventColor"),s=t.backgroundColor||n||i.backgroundColor||r||e.opt("eventBackgroundColor")||o,l=t.borderColor||n||i.borderColor||r||e.opt("eventBorderColor")||o,a=t.textColor||i.textColor||e.opt("eventTextColor"),c=[];return s&&c.push("background-color:"+s),l&&c.push("border-color:"+l),a&&c.push("color:"+a),c.join(";")}}),ie.prototype=x(te.prototype),t.extend(ie.prototype,{numbersVisible:!1,cellDuration:e.duration({days:1}),bottomCoordPadding:0,rowEls:null,dayEls:null,helperEls:null,highlightEls:null,render:function(e){var i,n=this.view,r="";for(i=0;n.rowCnt>i;i++)r+=this.dayRowHtml(i,e);this.el.html(r),this.rowEls=this.el.find(".fc-row"),this.dayEls=this.el.find(".fc-day"),this.dayEls.each(function(e,i){var r=n.cellToDate(Math.floor(e/n.colCnt),e%n.colCnt);n.trigger("dayRender",null,r,t(i))}),te.prototype.render.call(this)},destroy:function(){this.destroySegPopover()},dayRowHtml:function(t,e){var i=this.view,n=["fc-row","fc-week",i.widgetContentClass];return e&&n.push("fc-rigid"),'
'+'
'+""+this.rowHtml("day",t)+"
"+"
"+'
'+""+(this.numbersVisible?""+this.rowHtml("number",t)+"":"")+"
"+"
"+"
"},dayCellHtml:function(t,e,i){return this.bgCellHtml(t,e,i)},buildCoords:function(e,i){var n,r,o,s=this.view.colCnt;this.dayEls.slice(0,s).each(function(e,s){n=t(s),r=n.offset().left,e&&(o[1]=r),o=[r],i[e]=o}),o[1]=r+n.outerWidth(),this.rowEls.each(function(i,s){n=t(s),r=n.offset().top,i&&(o[1]=r),o=[r],e[i]=o}),o[1]=r+n.outerHeight()+this.bottomCoordPadding},getCellDate:function(t){return this.view.cellToDate(t)},getCellDayEl:function(t){return this.dayEls.eq(t.row*this.view.colCnt+t.col)},rangeToSegs:function(t,e){return this.view.rangeToSegments(t,e)},renderDrag:function(t,e,i){var n;return this.renderHighlight(t,e||this.view.calendar.getDefaultEventEnd(!0,t)),i&&!i.el.closest(this.el).length?(this.renderRangeHelper(t,e,i),n=this.view.opt("dragOpacity"),void 0!==n&&this.helperEls.css("opacity",n),!0):void 0},destroyDrag:function(){this.destroyHighlight(),this.destroyHelper()},renderResize:function(t,e,i){this.renderHighlight(t,e),this.renderRangeHelper(t,e,i)},destroyResize:function(){this.destroyHighlight(),this.destroyHelper()},renderHelper:function(e,i){var n=[],r=this.renderEventRows([e]);this.rowEls.each(function(e,o){var s,l=t(o),a=t('
');s=i&&i.row===e?i.el.position().top:l.find(".fc-content-skeleton tbody").position().top,a.css("top",s).find("table").append(r[e].tbodyEl),l.append(a),n.push(a[0])}),this.helperEls=t(n)},destroyHelper:function(){this.helperEls&&(this.helperEls.remove(),this.helperEls=null)},renderHighlight:function(e,i){var n,r,o,s=this.rangeToSegs(e,i),l=[];for(n=0;s.length>n;n++)r=s[n],o=t(this.highlightSkeletonHtml(r.leftCol,r.rightCol+1)),o.appendTo(this.rowEls[r.row]),l.push(o[0]);this.highlightEls=t(l)},destroyHighlight:function(){this.highlightEls&&(this.highlightEls.remove(),this.highlightEls=null)},highlightSkeletonHtml:function(t,e){var i=this.view.colCnt,n="";return t>0&&(n+='"),m=[],v=[],y=[];for(r=0;p>r;r++){if(o=f[r],s=0,l=t(""),m.push([]),v.push([]),y.push([]),o)for(a=0;o.length>a;a++){for(c=o[a],n(c.leftCol),d=t('",l+=""+(s?"":n)+'",c.add(this.slotDuration);return l},processOptions:function(){var t=this.view,i=t.opt("slotDuration"),n=t.opt("snapDuration");i=e.duration(i),n=n?e.duration(n):i,this.slotDuration=i,this.snapDuration=n,this.cellDuration=n,this.minTime=e.duration(t.opt("minTime")),this.maxTime=e.duration(t.opt("maxTime"))},rangeToSegs:function(t,e){var i,n,r,o,s,l=this.view,a=[];for(t=t.clone().stripZone(),e=e.clone().stripZone(),n=0;l.colCnt>n;n++)r=l.cellToDate(0,n),o=r.clone().time(this.minTime),s=r.clone().time(this.maxTime),i=S(t,e,o,s),i&&(i.col=n,a.push(i));return a},resize:function(){this.computeSlatTops(),this.updateSegVerticals()},buildCoords:function(i,n){var r,o,s=this.view.colCnt,l=this.el.offset().top,a=e.duration(+this.minTime),c=null;for(this.dayEls.slice(0,s).each(function(e,i){r=t(i),o=r.offset().left,c&&(c[1]=o),c=[o],n[e]=c}),c[1]=o+r.outerWidth(),c=null;this.maxTime>a;)o=l+this.computeTimeTop(a),c&&(c[1]=o),c=[o],i.push(c),a.add(this.snapDuration);c[1]=l+this.computeTimeTop(a)},getCellDate:function(t){var e=this.view,i=e.calendar;return i.rezoneDate(e.cellToDate(0,t.col).time(this.minTime+this.snapDuration*t.row))},getCellDayEl:function(t){return this.dayEls.eq(t.col)},computeDateTop:function(t,i){return this.computeTimeTop(e.duration(t.clone().stripZone()-i.clone().stripTime()))},computeTimeTop:function(t){var e,i,n,r,o=(t-this.minTime)/this.slotDuration;return o=Math.max(0,o),o=Math.min(this.slatEls.length,o),e=Math.floor(o),i=o-e,n=this.slatTops[e],i?(r=this.slatTops[e+1],n+(r-n)*i):n},computeSlatTops:function(){var e,i=[];this.slatEls.each(function(n,r){e=t(r).position().top,i.push(e)}),i.push(e+this.slatEls.last().outerHeight()),this.slatTops=i},renderDrag:function(t,e,i){var n;return i?(this.renderRangeHelper(t,e,i),n=this.view.opt("dragOpacity"),void 0!==n&&this.helperEl.css("opacity",n),!0):(this.renderHighlight(t,e||this.view.calendar.getDefaultEventEnd(!1,t)),void 0)},destroyDrag:function(){this.destroyHelper(),this.destroyHighlight()},renderResize:function(t,e,i){this.renderRangeHelper(t,e,i)},destroyResize:function(){this.destroyHelper()},renderHelper:function(e,i){var n,r,o,s=this.renderEventTable([e]),l=s.tableEl,a=s.segs;for(n=0;a.length>n;n++)r=a[n],i&&i.col===r.col&&(o=i.el,r.el.css({left:o.css("left"),right:o.css("right"),"margin-left":o.css("margin-left"),"margin-right":o.css("margin-right")}));this.helperEl=t('
').append(l).appendTo(this.el)},destroyHelper:function(){this.helperEl&&(this.helperEl.remove(),this.helperEl=null)},renderSelection:function(t,e){this.view.opt("selectHelper")?this.renderRangeHelper(t,e):this.renderHighlight(t,e)},destroySelection:function(){this.destroyHelper(),this.destroyHighlight()},renderHighlight:function(e,i){this.highlightEl=t(this.highlightSkeletonHtml(e,i)).appendTo(this.el)},destroyHighlight:function(){this.highlightEl&&(this.highlightEl.remove(),this.highlightEl=null)},highlightSkeletonHtml:function(t,e){var i,n,r,o,s,l=this.view,a=this.rangeToSegs(t,e),c="",d=0;for(i=0;a.length>i;i++)n=a[i],n.col>d&&(c+='
",d++;return l.colCnt>d&&(c+='":void 0},numberIntroHtml:function(t){return this.weekNumbersVisible?'":void 0},dayIntroHtml:function(){return this.weekNumbersVisible?'":void 0},introHtml:function(){return this.weekNumbersVisible?'":void 0},numberCellHtml:function(t,e,i){var n;return this.dayNumbersVisible?(n=this.dayGrid.getDayClasses(i),n.unshift("fc-day-number"),'"):""):'"},dayIntroHtml:function(){return'"},slotBgIntroHtml:function(){return'"},introHtml:function(){return'"},axisStyleAttr:function(){return null!==this.axisWidth?'style="width:'+this.axisWidth+'px"':""},updateSize:function(t){t&&this.timeGrid.resize(),pe.prototype.updateSize.call(this,t)},updateWidth:function(){this.axisWidth=m(this.el.find(".fc-axis"))},setHeight:function(t,e){var i,n;null===this.bottomRuleHeight&&(this.bottomRuleHeight=this.bottomRuleEl.outerHeight()),this.bottomRuleEl.hide(),this.scrollerEl.css("overflow",""),y(this.scrollerEl),f(this.noScrollRowEls),this.dayGrid&&(this.dayGrid.destroySegPopover(),i=this.opt("eventLimit"),i&&"number"!=typeof i&&(i=Fe),i&&this.dayGrid.limitRows(i)),e||(n=this.computeScrollerHeight(t),v(this.scrollerEl,n)?(u(this.noScrollRowEls,E(this.scrollerEl)),n=this.computeScrollerHeight(t),this.scrollerEl.height(n),this.restoreScroll()):(this.scrollerEl.height(n).css("overflow","hidden"),this.bottomRuleEl.show()))},resetScroll:function(){function t(){i.scrollerEl.scrollTop(r)}var i=this,n=e.duration(this.opt("scrollTime")),r=this.timeGrid.computeTimeTop(n);r=Math.ceil(r),r&&r++,t(),setTimeout(t,0)},renderEvents:function(t){var e,i,n=[],r=[],o=[];for(i=0;t.length>i;i++)t[i].allDay?n.push(t[i]):r.push(t[i]);e=this.timeGrid.renderEvents(r),this.dayGrid&&(o=this.dayGrid.renderEvents(n)),this.updateHeight()},getSegs:function(){return this.timeGrid.getSegs().concat(this.dayGrid?this.dayGrid.getSegs():[])},destroyEvents:function(){pe.prototype.destroyEvents.call(this),this.recordScroll(),this.timeGrid.destroyEvents(),this.dayGrid&&this.dayGrid.destroyEvents()},renderDrag:function(t,e,i){return t.hasTime()?this.timeGrid.renderDrag(t,e,i):this.dayGrid?this.dayGrid.renderDrag(t,e,i):void 0},destroyDrag:function(){this.timeGrid.destroyDrag(),this.dayGrid&&this.dayGrid.destroyDrag()},renderSelection:function(t,e){t.hasTime()||e.hasTime()?this.timeGrid.renderSelection(t,e):this.dayGrid&&this.dayGrid.renderSelection(t,e)},destroySelection:function(){this.timeGrid.destroySelection(),this.dayGrid&&this.dayGrid.destroySelection()}}),ze.agendaWeek=Se,Se.prototype=x(De.prototype),t.extend(Se.prototype,{name:"agendaWeek",incrementDate:function(t,e){return t.clone().stripTime().add(e,"weeks").startOf("week")},render:function(t){this.intervalStart=t.clone().stripTime().startOf("week"),this.intervalEnd=this.intervalStart.clone().add(1,"weeks"),this.start=this.skipHiddenDays(this.intervalStart),this.end=this.skipHiddenDays(this.intervalEnd,-1,!0),this.title=this.calendar.formatRange(this.start,this.end.clone().subtract(1),this.opt("titleFormat")," — "),De.prototype.render.call(this,this.getCellsPerWeek())}}),ze.agendaDay=be,be.prototype=x(De.prototype),t.extend(be.prototype,{name:"agendaDay",incrementDate:function(t,e){var i=t.clone().stripTime().add(e,"days");return i=this.skipHiddenDays(i,0>e?-1:1)},render:function(t){this.start=this.intervalStart=t.clone().stripTime(),this.end=this.intervalEnd=this.start.clone().add(1,"days"),this.title=this.calendar.formatDate(this.start,this.opt("titleFormat")),De.prototype.render.call(this,1)}}),Te.prototype=x(ie.prototype),t.extend(Te.prototype,{rangeToSegs:function(t,e,i){var n,r=[],o=this.view.calendar.getDate();if(o>=t&&e>o){var s=this.view,l=s.calendar.fetchResources();for(n=0;s.colCnt>n;n++){var a=l[n];i.indexOf(a.id)>-1&&r.push({row:0,leftCol:n,rightCol:n,isStart:!0,isEnd:!0})}}return r}}),t.extend(Te.prototype,{eventToSegs:function(t,e,i){var n,r,o,s=t.start.clone().stripZone(),l=this.view.calendar.getEventEnd(t).stripZone();for(e&&i?(o=S(s,l,e,i),n=o?[o]:[]):n=this.rangeToSegs(s,l,t.resources),r=0;n.length>r;r++)o=n[r],o.event=t,o.eventStartMS=+s,o.eventDurationMS=l-s;return n}}),Ce.prototype=x(oe.prototype),t.extend(Ce.prototype,{rangeToSegs:function(t,e,i){var n,r,o,s,l,a=this.view,c=[],d=a.calendar.fetchResources();for(t=t.clone().stripZone(),e=e.clone().stripZone(),r=0;a.colCnt>r;r++){var h=d[r];i.indexOf(h.id)>-1&&(o=a.cellToDate(0,r),s=o.clone().time(this.minTime),l=o.clone().time(this.maxTime),n=S(t,e,s,l),n&&(n.col=r,c.push(n)))}return c}}),t.extend(Ce.prototype,{eventToSegs:function(t,e,i){var n,r,o,s=t.start.clone().stripZone(),l=this.view.calendar.getEventEnd(t).stripZone();for(e&&i?(o=S(s,l,e,i),n=o?[o]:[]):n=this.rangeToSegs(s,l,t.resources),r=0;n.length>r;r++)o=n[r],o.event=t,o.eventStartMS=+s,o.eventDurationMS=l-s;return n}}),r({allDaySlot:!0,allDayText:"all-day",scrollTime:"06:00:00",slotDuration:"00:30:00",axisFormat:we,timeFormat:{agenda:Ee},minTime:"00:00:00",maxTime:"24:00:00",slotEventOverlap:!0}),He.prototype=x(De.prototype),t.extend(He.prototype,{cellToDate:function(){return this.start.clone()},headCellHtml:function(t,e){var i=this,n=i.calendar,r=n.fetchResources()[e];return'"}}),ze.resourceDay=xe,xe.prototype=x(He.prototype),t.extend(xe.prototype,{name:"resourceDay",incrementDate:function(t,e){var i=t.clone().stripTime().add(e,"days");return i=this.skipHiddenDays(i,0>e?-1:1)},render:function(t){this.start=this.intervalStart=t.clone().stripTime(),this.end=this.intervalEnd=this.start.clone().add(1,"days"),this.title=this.calendar.formatDate(this.start,this.opt("titleFormat")),He.prototype.render.call(this,this.calendar.fetchResources().length)},getDayClasses:function(t){var e=this.view,i=e.calendar.getNow().stripTime(),n=["fc-"+Ne[t.day()]];return"month"===e.name&&t.month()!=e.intervalStart.month()&&n.push("fc-other-month"),t.isSame(i,"day")?n.push("fc-todaysss",e.highlightStateClass):i>t?n.push("fc-past"):n.push("fc-future"),n}})}); \ No newline at end of file diff --git a/dist/fullcalendar.print.css b/dist/fullcalendar.print.css index dc073b7..c276fe3 100644 --- a/dist/fullcalendar.print.css +++ b/dist/fullcalendar.print.css @@ -1,201 +1,201 @@ -/*! - * FullCalendar v2.1.1 Print Stylesheet - * Docs & License: http://arshaw.com/fullcalendar/ - * (c) 2013 Adam Shaw - */ - -/* - * Include this stylesheet on your page to get a more printer-friendly calendar. - * When including this stylesheet, use the media='print' attribute of the tag. - * Make sure to include this stylesheet IN ADDITION to the regular fullcalendar.css. - */ - -.fc { - max-width: 100% !important; -} - - -/* Global Event Restyling ---------------------------------------------------------------------------------------------------*/ - -.fc-event { - background: #fff !important; - color: #000 !important; - page-break-inside: avoid; -} - -.fc-event .fc-resizer { - display: none; -} - - -/* Table & Day-Row Restyling ---------------------------------------------------------------------------------------------------*/ - -th, -td, -hr, -thead, -tbody, -.fc-row { - border-color: #ccc !important; - background: #fff !important; -} - -/* kill the overlaid, absolutely-positioned common components */ -.fc-bg, -.fc-highlight-skeleton, -.fc-helper-skeleton { - display: none; -} - -/* don't force a min-height on rows (for DayGrid) */ -.fc tbody .fc-row { - height: auto !important; /* undo height that JS set in distributeHeight */ - min-height: 0 !important; /* undo the min-height from each view's specific stylesheet */ -} - -.fc tbody .fc-row .fc-content-skeleton { - position: static; /* undo .fc-rigid */ - padding-bottom: 0 !important; /* use a more border-friendly method for this... */ -} - -.fc tbody .fc-row .fc-content-skeleton tbody tr:last-child td { /* only works in newer browsers */ - padding-bottom: 1em; /* ...gives space within the skeleton. also ensures min height in a way */ -} - -.fc tbody .fc-row .fc-content-skeleton table { - /* provides a min-height for the row, but only effective for IE, which exaggerates this value, - making it look more like 3em. for other browers, it will already be this tall */ - height: 1em; -} - - -/* Undo month-view event limiting. Display all events and hide the "more" links ---------------------------------------------------------------------------------------------------*/ - -.fc-more-cell, -.fc-more { - display: none !important; -} - -.fc tr.fc-limited { - display: table-row !important; -} - -.fc td.fc-limited { - display: table-cell !important; -} - -.fc-popover { - display: none; /* never display the "more.." popover in print mode */ -} - - -/* TimeGrid Restyling ---------------------------------------------------------------------------------------------------*/ - -/* undo the min-height 100% trick used to fill the container's height */ -.fc-time-grid { - min-height: 0 !important; -} - -/* don't display the side axis at all ("all-day" and time cells) */ -.fc-agenda-view .fc-axis { - display: none; -} - -/* don't display the horizontal lines */ -.fc-slats, -.fc-time-grid hr { /* this hr is used when height is underused and needs to be filled */ - display: none !important; /* important overrides inline declaration */ -} - -/* let the container that holds the events be naturally positioned and create real height */ -.fc-time-grid .fc-content-skeleton { - position: static; -} - -/* in case there are no events, we still want some height */ -.fc-time-grid .fc-content-skeleton table { - height: 4em; -} - -/* kill the horizontal spacing made by the event container. event margins will be done below */ -.fc-time-grid .fc-event-container { - margin: 0 !important; -} - - -/* TimeGrid *Event* Restyling ---------------------------------------------------------------------------------------------------*/ - -/* naturally position events, vertically stacking them */ -.fc-time-grid .fc-event { - position: static !important; - margin: 3px 2px !important; -} - -/* for events that continue to a future day, give the bottom border back */ -.fc-time-grid .fc-event.fc-not-end { - border-bottom-width: 1px !important; -} - -/* indicate the event continues via "..." text */ -.fc-time-grid .fc-event.fc-not-end:after { - content: "..."; -} - -/* for events that are continuations from previous days, give the top border back */ -.fc-time-grid .fc-event.fc-not-start { - border-top-width: 1px !important; -} - -/* indicate the event is a continuation via "..." text */ -.fc-time-grid .fc-event.fc-not-start:before { - content: "..."; -} - -/* time */ - -/* undo a previous declaration and let the time text span to a second line */ -.fc-time-grid .fc-event .fc-time { - white-space: normal !important; -} - -/* hide the the time that is normally displayed... */ -.fc-time-grid .fc-event .fc-time span { - display: none; -} - -/* ...replace it with a more verbose version (includes AM/PM) stored in an html attribute */ -.fc-time-grid .fc-event .fc-time:after { - content: attr(data-full); -} - - -/* Vertical Scroller & Containers ---------------------------------------------------------------------------------------------------*/ - -/* kill the scrollbars and allow natural height */ -.fc-scroller, -.fc-day-grid-container, /* these divs might be assigned height, which we need to cleared */ -.fc-time-grid-container { /* */ - overflow: visible !important; - height: auto !important; -} - -/* kill the horizontal border/padding used to compensate for scrollbars */ -.fc-row { - border: 0 !important; - margin: 0 !important; -} - - -/* Button Controls ---------------------------------------------------------------------------------------------------*/ - -.fc-button-group, -.fc button { - display: none; /* don't display any button-related controls */ -} +/*! + * FullCalendar v2.1.1 Print Stylesheet + * Docs & License: http://arshaw.com/fullcalendar/ + * (c) 2013 Adam Shaw + */ + +/* + * Include this stylesheet on your page to get a more printer-friendly calendar. + * When including this stylesheet, use the media='print' attribute of the tag. + * Make sure to include this stylesheet IN ADDITION to the regular fullcalendar.css. + */ + +.fc { + max-width: 100% !important; +} + + +/* Global Event Restyling +--------------------------------------------------------------------------------------------------*/ + +.fc-event { + background: #fff !important; + color: #000 !important; + page-break-inside: avoid; +} + +.fc-event .fc-resizer { + display: none; +} + + +/* Table & Day-Row Restyling +--------------------------------------------------------------------------------------------------*/ + +th, +td, +hr, +thead, +tbody, +.fc-row { + border-color: #ccc !important; + background: #fff !important; +} + +/* kill the overlaid, absolutely-positioned common components */ +.fc-bg, +.fc-highlight-skeleton, +.fc-helper-skeleton { + display: none; +} + +/* don't force a min-height on rows (for DayGrid) */ +.fc tbody .fc-row { + height: auto !important; /* undo height that JS set in distributeHeight */ + min-height: 0 !important; /* undo the min-height from each view's specific stylesheet */ +} + +.fc tbody .fc-row .fc-content-skeleton { + position: static; /* undo .fc-rigid */ + padding-bottom: 0 !important; /* use a more border-friendly method for this... */ +} + +.fc tbody .fc-row .fc-content-skeleton tbody tr:last-child td { /* only works in newer browsers */ + padding-bottom: 1em; /* ...gives space within the skeleton. also ensures min height in a way */ +} + +.fc tbody .fc-row .fc-content-skeleton table { + /* provides a min-height for the row, but only effective for IE, which exaggerates this value, + making it look more like 3em. for other browers, it will already be this tall */ + height: 1em; +} + + +/* Undo month-view event limiting. Display all events and hide the "more" links +--------------------------------------------------------------------------------------------------*/ + +.fc-more-cell, +.fc-more { + display: none !important; +} + +.fc tr.fc-limited { + display: table-row !important; +} + +.fc td.fc-limited { + display: table-cell !important; +} + +.fc-popover { + display: none; /* never display the "more.." popover in print mode */ +} + + +/* TimeGrid Restyling +--------------------------------------------------------------------------------------------------*/ + +/* undo the min-height 100% trick used to fill the container's height */ +.fc-time-grid { + min-height: 0 !important; +} + +/* don't display the side axis at all ("all-day" and time cells) */ +.fc-agenda-view .fc-axis { + display: none; +} + +/* don't display the horizontal lines */ +.fc-slats, +.fc-time-grid hr { /* this hr is used when height is underused and needs to be filled */ + display: none !important; /* important overrides inline declaration */ +} + +/* let the container that holds the events be naturally positioned and create real height */ +.fc-time-grid .fc-content-skeleton { + position: static; +} + +/* in case there are no events, we still want some height */ +.fc-time-grid .fc-content-skeleton table { + height: 4em; +} + +/* kill the horizontal spacing made by the event container. event margins will be done below */ +.fc-time-grid .fc-event-container { + margin: 0 !important; +} + + +/* TimeGrid *Event* Restyling +--------------------------------------------------------------------------------------------------*/ + +/* naturally position events, vertically stacking them */ +.fc-time-grid .fc-event { + position: static !important; + margin: 3px 2px !important; +} + +/* for events that continue to a future day, give the bottom border back */ +.fc-time-grid .fc-event.fc-not-end { + border-bottom-width: 1px !important; +} + +/* indicate the event continues via "..." text */ +.fc-time-grid .fc-event.fc-not-end:after { + content: "..."; +} + +/* for events that are continuations from previous days, give the top border back */ +.fc-time-grid .fc-event.fc-not-start { + border-top-width: 1px !important; +} + +/* indicate the event is a continuation via "..." text */ +.fc-time-grid .fc-event.fc-not-start:before { + content: "..."; +} + +/* time */ + +/* undo a previous declaration and let the time text span to a second line */ +.fc-time-grid .fc-event .fc-time { + white-space: normal !important; +} + +/* hide the the time that is normally displayed... */ +.fc-time-grid .fc-event .fc-time span { + display: none; +} + +/* ...replace it with a more verbose version (includes AM/PM) stored in an html attribute */ +.fc-time-grid .fc-event .fc-time:after { + content: attr(data-full); +} + + +/* Vertical Scroller & Containers +--------------------------------------------------------------------------------------------------*/ + +/* kill the scrollbars and allow natural height */ +.fc-scroller, +.fc-day-grid-container, /* these divs might be assigned height, which we need to cleared */ +.fc-time-grid-container { /* */ + overflow: visible !important; + height: auto !important; +} + +/* kill the horizontal border/padding used to compensate for scrollbars */ +.fc-row { + border: 0 !important; + margin: 0 !important; +} + + +/* Button Controls +--------------------------------------------------------------------------------------------------*/ + +.fc-button-group, +.fc button { + display: none; /* don't display any button-related controls */ +} diff --git a/dist/gcal.js b/dist/gcal.js index 754fb72..c7f3b3b 100644 --- a/dist/gcal.js +++ b/dist/gcal.js @@ -1,100 +1,100 @@ -/*! - * FullCalendar v2.1.1 Google Calendar Plugin - * Docs & License: http://arshaw.com/fullcalendar/ - * (c) 2013 Adam Shaw - */ - -(function(factory) { - if (typeof define === 'function' && define.amd) { - define([ 'jquery' ], factory); - } - else { - factory(jQuery); - } -})(function($) { - - -var fc = $.fullCalendar; -var applyAll = fc.applyAll; - - -fc.sourceNormalizers.push(function(sourceOptions) { - if (sourceOptions.dataType == 'gcal' || - sourceOptions.dataType === undefined && - (sourceOptions.url || '').match(/^(http|https):\/\/www.google.com\/calendar\/feeds\//)) { - sourceOptions.dataType = 'gcal'; - if (sourceOptions.editable === undefined) { - sourceOptions.editable = false; - } - } -}); - - -fc.sourceFetchers.push(function(sourceOptions, start, end, timezone) { - if (sourceOptions.dataType == 'gcal') { - return transformOptions(sourceOptions, start, end, timezone); - } -}); - - -function transformOptions(sourceOptions, start, end, timezone) { - - var success = sourceOptions.success; - var data = $.extend({}, sourceOptions.data || {}, { - singleevents: true, - 'max-results': 9999 - }); - - return $.extend({}, sourceOptions, { - url: sourceOptions.url.replace(/\/basic$/, '/full') + '?alt=json-in-script&callback=?', - dataType: 'jsonp', - data: data, - timezoneParam: 'ctz', - startParam: 'start-min', - endParam: 'start-max', - success: function(data) { - var events = []; - if (data.feed.entry) { - $.each(data.feed.entry, function(i, entry) { - - var url; - $.each(entry.link, function(i, link) { - if (link.type == 'text/html') { - url = link.href; - if (timezone && timezone != 'local') { - url += (url.indexOf('?') == -1 ? '?' : '&') + 'ctz=' + encodeURIComponent(timezone); - } - } - }); - - events.push({ - id: entry.gCal$uid.value, - title: entry.title.$t, - start: entry.gd$when[0].startTime, - end: entry.gd$when[0].endTime, - url: url, - location: entry.gd$where[0].valueString, - description: entry.content.$t - }); - - }); - } - var args = [events].concat(Array.prototype.slice.call(arguments, 1)); - var res = applyAll(success, this, args); - if ($.isArray(res)) { - return res; - } - return events; - } - }); - -} - - -// legacy -fc.gcalFeed = function(url, sourceOptions) { - return $.extend({}, sourceOptions, { url: url, dataType: 'gcal' }); -}; - - -}); +/*! + * FullCalendar v2.1.1 Google Calendar Plugin + * Docs & License: http://arshaw.com/fullcalendar/ + * (c) 2013 Adam Shaw + */ + +(function(factory) { + if (typeof define === 'function' && define.amd) { + define([ 'jquery' ], factory); + } + else { + factory(jQuery); + } +})(function($) { + + +var fc = $.fullCalendar; +var applyAll = fc.applyAll; + + +fc.sourceNormalizers.push(function(sourceOptions) { + if (sourceOptions.dataType == 'gcal' || + sourceOptions.dataType === undefined && + (sourceOptions.url || '').match(/^(http|https):\/\/www.google.com\/calendar\/feeds\//)) { + sourceOptions.dataType = 'gcal'; + if (sourceOptions.editable === undefined) { + sourceOptions.editable = false; + } + } +}); + + +fc.sourceFetchers.push(function(sourceOptions, start, end, timezone) { + if (sourceOptions.dataType == 'gcal') { + return transformOptions(sourceOptions, start, end, timezone); + } +}); + + +function transformOptions(sourceOptions, start, end, timezone) { + + var success = sourceOptions.success; + var data = $.extend({}, sourceOptions.data || {}, { + singleevents: true, + 'max-results': 9999 + }); + + return $.extend({}, sourceOptions, { + url: sourceOptions.url.replace(/\/basic$/, '/full') + '?alt=json-in-script&callback=?', + dataType: 'jsonp', + data: data, + timezoneParam: 'ctz', + startParam: 'start-min', + endParam: 'start-max', + success: function(data) { + var events = []; + if (data.feed.entry) { + $.each(data.feed.entry, function(i, entry) { + + var url; + $.each(entry.link, function(i, link) { + if (link.type == 'text/html') { + url = link.href; + if (timezone && timezone != 'local') { + url += (url.indexOf('?') == -1 ? '?' : '&') + 'ctz=' + encodeURIComponent(timezone); + } + } + }); + + events.push({ + id: entry.gCal$uid.value, + title: entry.title.$t, + start: entry.gd$when[0].startTime, + end: entry.gd$when[0].endTime, + url: url, + location: entry.gd$where[0].valueString, + description: entry.content.$t + }); + + }); + } + var args = [events].concat(Array.prototype.slice.call(arguments, 1)); + var res = applyAll(success, this, args); + if ($.isArray(res)) { + return res; + } + return events; + } + }); + +} + + +// legacy +fc.gcalFeed = function(url, sourceOptions) { + return $.extend({}, sourceOptions, { url: url, dataType: 'gcal' }); +}; + + +}); diff --git a/dist/lang/is.js b/dist/lang/is.js index 7bbdebf..4601695 100644 --- a/dist/lang/is.js +++ b/dist/lang/is.js @@ -1 +1 @@ -(function(e){"function"==typeof define&&define.amd?define(["jquery","moment"],e):e(jQuery,moment)})(function(e,t){function n(e){return 11===e%100?!0:1===e%10?!1:!0}function i(e,t,i,r){var a=e+" ";switch(i){case"s":return t||r?"nokkrar sekúndur":"nokkrum sekúndum";case"m":return t?"mínúta":"mínútu";case"mm":return n(e)?a+(t||r?"mínútur":"mínútum"):t?a+"mínúta":a+"mínútu";case"hh":return n(e)?a+(t||r?"klukkustundir":"klukkustundum"):a+"klukkustund";case"d":return t?"dagur":r?"dag":"degi";case"dd":return n(e)?t?a+"dagar":a+(r?"daga":"dögum"):t?a+"dagur":a+(r?"dag":"degi");case"M":return t?"mánuður":r?"mánuð":"mánuði";case"MM":return n(e)?t?a+"mánuðir":a+(r?"mánuði":"mánuðum"):t?a+"mánuður":a+(r?"mánuð":"mánuði");case"y":return t||r?"ár":"ári";case"yy":return n(e)?a+(t||r?"ár":"árum"):a+(t||r?"ár":"ári")}}(t.defineLocale||t.lang).call(t,"is",{months:"janúar_febrúar_mars_apríl_maí_júní_júlí_ágúst_september_október_nóvember_desember".split("_"),monthsShort:"jan_feb_mar_apr_maí_jún_júl_ágú_sep_okt_nóv_des".split("_"),weekdays:"sunnudagur_mánudagur_þriðjudagur_miðvikudagur_fimmtudagur_föstudagur_laugardagur".split("_"),weekdaysShort:"sun_mán_þri_mið_fim_fös_lau".split("_"),weekdaysMin:"Su_Má_Þr_Mi_Fi_Fö_La".split("_"),longDateFormat:{LT:"H:mm",L:"DD/MM/YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY [kl.] LT",LLLL:"dddd, D. MMMM YYYY [kl.] LT"},calendar:{sameDay:"[í dag kl.] LT",nextDay:"[á morgun kl.] LT",nextWeek:"dddd [kl.] LT",lastDay:"[í gær kl.] LT",lastWeek:"[síðasta] dddd [kl.] LT",sameElse:"L"},relativeTime:{future:"eftir %s",past:"fyrir %s síðan",s:i,m:i,mm:i,h:"klukkustund",hh:i,d:i,dd:i,M:i,MM:i,y:i,yy:i},ordinal:"%d.",week:{dow:1,doy:4}}),e.fullCalendar.datepickerLang("is","is",{closeText:"Loka",prevText:"< Fyrri",nextText:"Næsti >",currentText:"Í dag",monthNames:["Janúar","Febrúar","Mars","Apríl","Maí","Júní","Júlí","Ágúst","September","Október","Nóvember","Desember"],monthNamesShort:["Jan","Feb","Mar","Apr","Maí","Jún","Júl","Ágú","Sep","Okt","Nóv","Des"],dayNames:["Sunnudagur","Mánudagur","Þriðjudagur","Miðvikudagur","Fimmtudagur","Föstudagur","Laugardagur"],dayNamesShort:["Sun","Mán","Þri","Mið","Fim","Fös","Lau"],dayNamesMin:["Su","Má","Þr","Mi","Fi","Fö","La"],weekHeader:"Vika",dateFormat:"dd.mm.yy",firstDay:0,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),e.fullCalendar.lang("is",{defaultButtonText:{month:"Mánuður",week:"Vika",day:"Dagur",list:"Dagskrá"},allDayHtml:"Allan
daginn",eventLimitText:"meira"})}); \ No newline at end of file +(function(e){"function"==typeof define&&define.amd?define(["jquery","moment"],e):e(jQuery,moment)})(function(e,t){function n(e){return 11===e%100?!0:1===e%10?!1:!0}function r(e,t,r,i){var a=e+" ";switch(r){case"s":return t||i?"nokkrar sekúndur":"nokkrum sekúndum";case"m":return t?"mínúta":"mínútu";case"mm":return n(e)?a+(t||i?"mínútur":"mínútum"):t?a+"mínúta":a+"mínútu";case"hh":return n(e)?a+(t||i?"klukkustundir":"klukkustundum"):a+"klukkustund";case"d":return t?"dagur":i?"dag":"degi";case"dd":return n(e)?t?a+"dagar":a+(i?"daga":"dögum"):t?a+"dagur":a+(i?"dag":"degi");case"M":return t?"mánuður":i?"mánuð":"mánuði";case"MM":return n(e)?t?a+"mánuðir":a+(i?"mánuði":"mánuðum"):t?a+"mánuður":a+(i?"mánuð":"mánuði");case"y":return t||i?"ár":"ári";case"yy":return n(e)?a+(t||i?"ár":"árum"):a+(t||i?"ár":"ári")}}(t.defineLocale||t.lang).call(t,"is",{months:"janúar_febrúar_mars_apríl_maí_júní_júlí_ágúst_september_október_nóvember_desember".split("_"),monthsShort:"jan_feb_mar_apr_maí_jún_júl_ágú_sep_okt_nóv_des".split("_"),weekdays:"sunnudagur_mánudagur_þriðjudagur_miðvikudagur_fimmtudagur_föstudagur_laugardagur".split("_"),weekdaysShort:"sun_mán_þri_mið_fim_fös_lau".split("_"),weekdaysMin:"Su_Má_Þr_Mi_Fi_Fö_La".split("_"),longDateFormat:{LT:"H:mm",L:"DD/MM/YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY [kl.] LT",LLLL:"dddd, D. MMMM YYYY [kl.] LT"},calendar:{sameDay:"[í dag kl.] LT",nextDay:"[á morgun kl.] LT",nextWeek:"dddd [kl.] LT",lastDay:"[í gær kl.] LT",lastWeek:"[síðasta] dddd [kl.] LT",sameElse:"L"},relativeTime:{future:"eftir %s",past:"fyrir %s síðan",s:r,m:r,mm:r,h:"klukkustund",hh:r,d:r,dd:r,M:r,MM:r,y:r,yy:r},ordinal:"%d.",week:{dow:1,doy:4}}),e.fullCalendar.datepickerLang("is","is",{closeText:"Loka",prevText:"< Fyrri",nextText:"Næsti >",currentText:"Í dag",monthNames:["Janúar","Febrúar","Mars","Apríl","Maí","Júní","Júlí","Ágúst","September","Október","Nóvember","Desember"],monthNamesShort:["Jan","Feb","Mar","Apr","Maí","Jún","Júl","Ágú","Sep","Okt","Nóv","Des"],dayNames:["Sunnudagur","Mánudagur","Þriðjudagur","Miðvikudagur","Fimmtudagur","Föstudagur","Laugardagur"],dayNamesShort:["Sun","Mán","Þri","Mið","Fim","Fös","Lau"],dayNamesMin:["Su","Má","Þr","Mi","Fi","Fö","La"],weekHeader:"Vika",dateFormat:"dd.mm.yy",firstDay:0,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),e.fullCalendar.lang("is",{defaultButtonText:{month:"Mánuður",week:"Vika",day:"Dagur",list:"Dagskrá"},allDayHtml:"Allan
daginn",eventLimitText:"meira"})}); \ No newline at end of file
'),e>t&&(n+=''),i>e&&(n+=''),n=this.bookendCells(n,"highlight"),'
'+n+""+"
"+"
"}}),t.extend(ie.prototype,{segs:null,rowStructs:null,renderEvents:function(e){var i=this.rowStructs=this.renderEventRows(e),n=[];this.rowEls.each(function(e,r){t(r).find(".fc-content-skeleton > table").append(i[e].tbodyEl),n.push.apply(n,i[e].segs)}),this.segs=n},getSegs:function(){return(this.segs||[]).concat(this.popoverSegs||[])},destroyEvents:function(){var t,e;for(te.prototype.destroyEvents.call(this),t=this.rowStructs||[];e=t.pop();)e.tbodyEl.remove();this.segs=null,this.destroySegPopover()},renderEventRows:function(t){var e,i,n=this.eventsToSegs(t),r=[];for(n=this.renderSegs(n),e=this.groupSegRows(n),i=0;e.length>i;i++)r.push(this.renderEventRow(i,e[i]));return r},renderSegHtml:function(t,e){var i,n=this.view,r=n.opt("isRTL"),o=t.event,s=n.isEventDraggable(o),l=!e&&o.allDay&&t.isEnd&&n.isEventResizable(o),a=this.getSegClasses(t,s,l),c=this.getEventSkinCss(o),d="";return a.unshift("fc-day-grid-event"),!o.allDay&&t.isStart&&(d=''+P(n.getEventTimeText(o))+""),i=''+(P(o.title||"")||" ")+"",'"+'
'+(r?i+" "+d:d+" "+i)+"
"+(l?'
':"")+""},renderEventRow:function(e,i){function n(e){for(;e>s;)d=(y[r-1]||[])[s],d?d.attr("rowspan",parseInt(d.attr("rowspan")||1,10)+1):(d=t("
"),l.append(d)),v[r][s]=d,y[r][s]=d,s++}var r,o,s,l,a,c,d,h=this.view,u=h.colCnt,f=this.buildSegLevels(i),p=Math.max(1,f.length),g=t("
').append(c.el),c.leftCol!=c.rightCol?d.attr("colspan",c.rightCol-c.leftCol+1):y[r][s]=d;c.rightCol>=s;)v[r][s]=d,m[r][s]=c,s++;l.append(d)}n(u),this.bookendCells(l,"eventSkeleton"),g.append(l)}return{row:e,tbodyEl:g,cellMatrix:v,segMatrix:m,segLevels:f,segs:i}},buildSegLevels:function(t){var e,i,n,r=[];for(t.sort(ee),e=0;t.length>e;e++){for(i=t[e],n=0;r.length>n&&ne(i,r[n]);n++);i.level=n,(r[n]||(r[n]=[])).push(i)}for(n=0;r.length>n;n++)r[n].sort(re);return r},groupSegRows:function(t){var e,i=this.view,n=[];for(e=0;i.rowCnt>e;e++)n.push([]);for(e=0;t.length>e;e++)n[t[e].row].push(t[e]);return n}}),t.extend(ie.prototype,{segPopover:null,popoverSegs:null,destroySegPopover:function(){this.segPopover&&this.segPopover.hide()},limitRows:function(t){var e,i,n=this.rowStructs||[];for(e=0;n.length>e;e++)this.unlimitRow(e),i=t?"number"==typeof t?t:this.computeRowLevelLimit(e):!1,i!==!1&&this.limitRow(e,i)},computeRowLevelLimit:function(t){var e,i,n=this.rowEls.eq(t),r=n.height(),o=this.rowStructs[t].tbodyEl.children();for(e=0;o.length>e;e++)if(i=o.eq(e).removeClass("fc-limited"),i.position().top+i.outerHeight()>r)return e;return!1},limitRow:function(e,i){function n(n){for(;n>T;)r={row:e,col:T},d=E.getCellSegs(r,i),d.length&&(f=s[i-1][T],w=E.renderMoreLink(r,d),y=t("
").append(w),f.append(y),b.push(y[0])),T++}var r,o,s,l,a,c,d,h,u,f,p,g,m,v,y,w,E=this,D=this.view,S=this.rowStructs[e],b=[],T=0;if(i&&S.segLevels.length>i){for(o=S.segLevels[i-1],s=S.cellMatrix,l=S.tbodyEl.children().slice(i).addClass("fc-limited").get(),a=0;o.length>a;a++){for(c=o[a],n(c.leftCol),u=[],h=0;c.rightCol>=T;)r={row:e,col:T},d=this.getCellSegs(r,i),u.push(d),h+=d.length,T++;if(h){for(f=s[i-1][c.leftCol],p=f.attr("rowspan")||1,g=[],m=0;u.length>m;m++)v=t('
').attr("rowspan",p),d=u[m],r={row:e,col:c.leftCol+m},w=this.renderMoreLink(r,[c].concat(d)),y=t("
").append(w),v.append(y),g.push(v[0]),b.push(v[0]);f.addClass("fc-limited").after(t(g)),l.push(f[0])}}n(D.colCnt),S.moreEls=t(b),S.limitedEls=t(l)}},unlimitRow:function(t){var e=this.rowStructs[t];e.moreEls&&(e.moreEls.remove(),e.moreEls=null),e.limitedEls&&(e.limitedEls.removeClass("fc-limited"),e.limitedEls=null)},renderMoreLink:function(e,i){var n=this,r=this.view;return t('').text(this.getMoreLinkText(i.length)).on("click",function(o){var s=r.opt("eventLimitClick"),l=r.cellToDate(e),a=t(this),c=n.getCellDayEl(e),d=n.getCellSegs(e),h=n.resliceDaySegs(d,l),u=n.resliceDaySegs(i,l);"function"==typeof s&&(s=r.trigger("eventLimitClick",null,{date:l,dayEl:c,moreEl:a,segs:h,hiddenSegs:u},o)),"popover"===s?n.showSegPopover(l,e,a,h):"string"==typeof s&&r.calendar.zoomTo(l,s)})},showSegPopover:function(t,e,i,n){var r,o,s=this,l=this.view,a=i.parent();r=1==l.rowCnt?this.view.el:this.rowEls.eq(e.row),o={className:"fc-more-popover",content:this.renderSegPopoverContent(t,n),parentEl:this.el,top:r.offset().top,autoHide:!0,viewportConstrain:l.opt("popoverViewportConstrain"),hide:function(){s.segPopover.destroy(),s.segPopover=null,s.popoverSegs=null}},l.opt("isRTL")?o.right=a.offset().left+a.outerWidth()+1:o.left=a.offset().left-1,this.segPopover=new X(o),this.segPopover.show()},renderSegPopoverContent:function(e,i){var n,r=this.view,o=r.opt("theme"),s=e.format(r.opt("dayPopoverFormat")),l=t('
'+''+''+P(s)+""+'
'+"
"+'
'+'
'+"
"),a=l.find(".fc-event-container");for(i=this.renderSegs(i,!0),this.popoverSegs=i,n=0;i.length>n;n++)i[n].cellDate=e,a.append(i[n].el);return l},resliceDaySegs:function(e,i){var n=t.map(e,function(t){return t.event}),r=i.clone().stripTime(),o=r.clone().add(1,"days");return this.eventsToSegs(n,r,o)},getMoreLinkText:function(t){var e=this.view,i=e.opt("eventLimitText");return"function"==typeof i?i(t):"+"+t+" "+i},getCellSegs:function(t,e){for(var i,n=this.rowStructs[t.row].segMatrix,r=e||0,o=[];n.length>r;)i=n[r][t.col],i&&o.push(i),r++;return o}}),oe.prototype=x(te.prototype),t.extend(oe.prototype,{slotDuration:null,snapDuration:null,minTime:null,maxTime:null,dayEls:null,slatEls:null,slatTops:null,highlightEl:null,helperEl:null,render:function(){this.processOptions(),this.el.html(this.renderHtml()),this.dayEls=this.el.find(".fc-day"),this.slatEls=this.el.find(".fc-slats tr"),this.computeSlatTops(),te.prototype.render.call(this)},renderHtml:function(){return'
'+this.rowHtml("slotBg")+"
"+"
"+'
'+""+this.slatRowHtml()+"
"+"
"},slotBgCellHtml:function(t,e,i){return this.bgCellHtml(t,e,i)},slatRowHtml:function(){for(var t,i,n,r=this.view,o=r.calendar,s=r.opt("isRTL"),l="",a=0===this.slotDuration.asMinutes()%15,c=e.duration(+this.minTime);this.maxTime>c;)t=r.start.clone().time(c),i=t.minutes(),n='
"+(a&&i?"":""+P(o.formatDate(t,r.opt("axisFormat")))+"")+"
'+(s?n:"")+"
',d=n.col),r=l.cellToDate(0,d),o=this.computeDateTop(n.start,r),s=this.computeDateTop(n.end,r),c+='
'+"
"+"
'),c=this.bookendCells(c,"highlight"),'
'+c+""+"
"+"
"}}),t.extend(oe.prototype,{segs:null,eventSkeletonEl:null,renderEvents:function(e){var i=this.renderEventTable(e);this.eventSkeletonEl=t('
').append(i.tableEl),this.el.append(this.eventSkeletonEl),this.segs=i.segs},getSegs:function(){return this.segs||[]},destroyEvents:function(){te.prototype.destroyEvents.call(this),this.eventSkeletonEl&&(this.eventSkeletonEl.remove(),this.eventSkeletonEl=null),this.segs=null},renderEventTable:function(e){var i,n,r,o,s,l,a=t("
"),c=a.find("tr"),d=this.eventsToSegs(e);for(d=this.renderSegs(d),i=this.groupSegCols(d),this.computeSegVerticals(d),o=0;i.length>o;o++){for(s=i[o],se(s),l=t('
'),n=0;s.length>n;n++)r=s[n],r.el.css(this.generateSegPositionCss(r)),30>r.bottom-r.top&&r.el.addClass("fc-short"),l.append(r.el);c.append(t("
").append(l))}return this.bookendCells(c,"eventSkeleton"),{tableEl:a,segs:d}},updateSegVerticals:function(){var t,e=this.segs;if(e)for(this.computeSegVerticals(e),t=0;e.length>t;t++)e[t].el.css(this.generateSegVerticalCss(e[t]))},computeSegVerticals:function(t){var e,i;for(e=0;t.length>e;e++)i=t[e],i.top=this.computeDateTop(i.start,i.start),i.bottom=this.computeDateTop(i.end,i.start)},renderSegHtml:function(t,e){var i,n,r,o=this.view,s=t.event,l=o.isEventDraggable(s),a=!e&&t.isEnd&&o.isEventResizable(s),c=this.getSegClasses(t,l,a),d=this.getEventSkinCss(s);return c.unshift("fc-time-grid-event"),o.isMultiDayEvent(s)?(t.isStart||t.isEnd)&&(i=o.getEventTimeText(t.start,t.end),n=o.getEventTimeText(t.start,t.end,"LT"),r=o.getEventTimeText(t.start,null)):(i=o.getEventTimeText(s),n=o.getEventTimeText(s,"LT"),r=o.getEventTimeText(s.start,null)),'"+'
'+(i?'
"+""+P(i)+""+"
":"")+(s.title?'
'+P(s.title)+"
":"")+"
"+'
'+(a?'
':"")+""},generateSegPositionCss:function(t){var e,i,n=this.view,r=n.opt("isRTL"),o=n.opt("slotEventOverlap"),s=t.backwardCoord,l=t.forwardCoord,a=this.generateSegVerticalCss(t);return o&&(l=Math.min(1,s+2*(l-s))),r?(e=1-l,i=s):(e=s,i=1-l),a.zIndex=t.level+1,a.left=100*e+"%",a.right=100*i+"%",o&&t.forwardPressure&&(a[r?"marginLeft":"marginRight"]=20),a},generateSegVerticalCss:function(t){return{top:t.top,bottom:-t.bottom}},groupSegCols:function(t){var e,i=this.view,n=[];for(e=0;i.colCnt>e;e++)n.push([]);for(e=0;t.length>e;e++)n[t[e].col].push(t[e]);return n}}),pe.prototype={calendar:null,coordMap:null,el:null,start:null,end:null,intervalStart:null,intervalEnd:null,rowCnt:null,colCnt:null,isSelected:!1,scrollerEl:null,scrollTop:null,widgetHeaderClass:null,widgetContentClass:null,highlightStateClass:null,documentMousedownProxy:null,documentDragStartProxy:null,init:function(){var e=this.opt("theme")?"ui":"fc";this.widgetHeaderClass=e+"-widget-header",this.widgetContentClass=e+"-widget-content",this.highlightStateClass=e+"-state-highlight",this.documentMousedownProxy=t.proxy(this,"documentMousedown"),this.documentDragStartProxy=t.proxy(this,"documentDragStart")},render:function(){this.updateSize(),this.trigger("viewRender",this,this,this.el),t(document).on("mousedown",this.documentMousedownProxy).on("dragstart",this.documentDragStartProxy)},destroy:function(){this.unselect(),this.trigger("viewDestroy",this,this,this.el),this.destroyEvents(),this.el.empty(),t(document).off("mousedown",this.documentMousedownProxy).off("dragstart",this.documentDragStartProxy)},incrementDate:function(){},updateSize:function(t){t&&this.recordScroll(),this.updateHeight(),this.updateWidth()},updateWidth:function(){},updateHeight:function(){var t=this.calendar;this.setHeight(t.getSuggestedViewHeight(),t.isHeightAuto())},setHeight:function(){},computeScrollerHeight:function(t){var e,i=this.el.add(this.scrollerEl);return i.css({position:"relative",left:-1}),e=this.el.outerHeight()-this.scrollerEl.height(),i.css({position:"",left:""}),t-e},recordScroll:function(){this.scrollerEl&&(this.scrollTop=this.scrollerEl.scrollTop()) +},restoreScroll:function(){null!==this.scrollTop&&this.scrollerEl.scrollTop(this.scrollTop)},renderEvents:function(){this.segEach(function(t){this.trigger("eventAfterRender",t.event,t.event,t.el)}),this.trigger("eventAfterAllRender")},destroyEvents:function(){this.segEach(function(t){this.trigger("eventDestroy",t.event,t.event,t.el)})},resolveEventEl:function(e,i){var n=this.trigger("eventRender",e,e,i);return n===!1?i=null:n&&n!==!0&&(i=t(n)),i},showEvent:function(t){this.segEach(function(t){t.el.css("visibility","")},t)},hideEvent:function(t){this.segEach(function(t){t.el.css("visibility","hidden")},t)},segEach:function(t,e){var i,n=this.getSegs();for(i=0;n.length>i;i++)e&&n[i].event._id!==e._id||t.call(this,n[i])},getSegs:function(){},renderDrag:function(){},destroyDrag:function(){},documentDragStart:function(e){var i,n=this,r=null;this.opt("droppable")&&(i=new U(this.coordMap,{cellOver:function(t,e){r=e,n.renderDrag(e)},cellOut:function(){r=null,n.destroyDrag()}}),t(document).one("dragstop",function(t,e){n.destroyDrag(),r&&n.trigger("drop",t.target,r,t,e)}),i.startDrag(e))},select:function(t,e,i){this.unselect(i),this.renderSelection(t,e),this.reportSelection(t,e,i)},renderSelection:function(){},reportSelection:function(t,e,i){this.isSelected=!0,this.trigger("select",null,t,e,i)},unselect:function(t){this.isSelected&&(this.isSelected=!1,this.destroySelection(),this.trigger("unselect",null,t))},destroySelection:function(){},documentMousedown:function(e){var i;this.isSelected&&this.opt("unselectAuto")&&D(e)&&(i=this.opt("unselectCancel"),i&&t(e.target).closest(i).length||this.unselect(e))}},ge.prototype=x(pe.prototype),t.extend(ge.prototype,{dayGrid:null,dayNumbersVisible:!1,weekNumbersVisible:!1,weekNumberWidth:null,headRowEl:null,render:function(t,e,i){this.rowCnt=t,this.colCnt=e,this.dayNumbersVisible=i,this.weekNumbersVisible=this.opt("weekNumbers"),this.dayGrid.numbersVisible=this.dayNumbersVisible||this.weekNumbersVisible,this.el.addClass("fc-basic-view").html(this.renderHtml()),this.headRowEl=this.el.find("thead .fc-row"),this.scrollerEl=this.el.find(".fc-day-grid-container"),this.dayGrid.coordMap.containerEl=this.scrollerEl,this.dayGrid.el=this.el.find(".fc-day-grid"),this.dayGrid.render(this.hasRigidRows()),pe.prototype.render.call(this)},destroy:function(){this.dayGrid.destroy(),pe.prototype.destroy.call(this)},renderHtml:function(){return'"+""+""+""+""+'"+""+""+"
'+this.dayGrid.headHtml()+"
'+'
'+'
'+"
"+"
"},headIntroHtml:function(){return this.weekNumbersVisible?'
"+""+P(this.opt("weekNumberTitle"))+""+""+""+this.calendar.calculateWeekNumber(this.cellToDate(t,0))+""+"'+i.date()+""},weekNumberStyleAttr:function(){return null!==this.weekNumberWidth?'style="width:'+this.weekNumberWidth+'px"':""},hasRigidRows:function(){var t=this.opt("eventLimit");return t&&"number"!=typeof t},updateWidth:function(){this.weekNumbersVisible&&(this.weekNumberWidth=m(this.el.find(".fc-week-number")))},setHeight:function(t,e){var i,n=this.opt("eventLimit");y(this.scrollerEl),f(this.headRowEl),this.dayGrid.destroySegPopover(),n&&"number"==typeof n&&this.dayGrid.limitRows(n),i=this.computeScrollerHeight(t),this.setGridHeight(i,e),n&&"number"!=typeof n&&this.dayGrid.limitRows(n),!e&&v(this.scrollerEl,i)&&(u(this.headRowEl,E(this.scrollerEl)),i=this.computeScrollerHeight(t),this.scrollerEl.height(i),this.restoreScroll())},setGridHeight:function(t,e){e?g(this.dayGrid.rowEls):p(this.dayGrid.rowEls,t,!0)},renderEvents:function(t){this.dayGrid.renderEvents(t),this.updateHeight(),pe.prototype.renderEvents.call(this,t)},getSegs:function(){return this.dayGrid.getSegs()},destroyEvents:function(){pe.prototype.destroyEvents.call(this),this.recordScroll(),this.dayGrid.destroyEvents()},renderDrag:function(t,e,i){return this.dayGrid.renderDrag(t,e,i)},destroyDrag:function(){this.dayGrid.destroyDrag()},renderSelection:function(t,e){this.dayGrid.renderSelection(t,e)},destroySelection:function(){this.dayGrid.destroySelection()}}),r({fixedWeekCount:!0}),ze.month=me,me.prototype=x(ge.prototype),t.extend(me.prototype,{name:"month",incrementDate:function(t,e){return t.clone().stripTime().add(e,"months").startOf("month")},render:function(t){var e;this.intervalStart=t.clone().stripTime().startOf("month"),this.intervalEnd=this.intervalStart.clone().add(1,"months"),this.start=this.intervalStart.clone(),this.start=this.skipHiddenDays(this.start),this.start.startOf("week"),this.start=this.skipHiddenDays(this.start),this.end=this.intervalEnd.clone(),this.end=this.skipHiddenDays(this.end,-1,!0),this.end.add((7-this.end.weekday())%7,"days"),this.end=this.skipHiddenDays(this.end,-1,!0),e=Math.ceil(this.end.diff(this.start,"weeks",!0)),this.isFixedWeeks()&&(this.end.add(6-e,"weeks"),e=6),this.title=this.calendar.formatDate(this.intervalStart,this.opt("titleFormat")),ge.prototype.render.call(this,e,this.getCellsPerWeek(),!0)},setGridHeight:function(t,e){e=e||"variable"===this.opt("weekMode"),e&&(t*=this.rowCnt/6),p(this.dayGrid.rowEls,t,!e)},isFixedWeeks:function(){var t=this.opt("weekMode");return t?"fixed"===t:this.opt("fixedWeekCount")}}),ze.basicWeek=ve,ve.prototype=x(ge.prototype),t.extend(ve.prototype,{name:"basicWeek",incrementDate:function(t,e){return t.clone().stripTime().add(e,"weeks").startOf("week")},render:function(t){this.intervalStart=t.clone().stripTime().startOf("week"),this.intervalEnd=this.intervalStart.clone().add(1,"weeks"),this.start=this.skipHiddenDays(this.intervalStart),this.end=this.skipHiddenDays(this.intervalEnd,-1,!0),this.title=this.calendar.formatRange(this.start,this.end.clone().subtract(1),this.opt("titleFormat")," — "),ge.prototype.render.call(this,1,this.getCellsPerWeek(),!1)}}),ze.basicDay=ye,ye.prototype=x(ge.prototype),t.extend(ye.prototype,{name:"basicDay",incrementDate:function(t,e){var i=t.clone().stripTime().add(e,"days");return i=this.skipHiddenDays(i,0>e?-1:1)},render:function(t){this.start=this.intervalStart=t.clone().stripTime(),this.end=this.intervalEnd=this.start.clone().add(1,"days"),this.title=this.calendar.formatDate(this.start,this.opt("titleFormat")),ge.prototype.render.call(this,1,1,!1)}}),r({allDaySlot:!0,allDayText:"all-day",scrollTime:"06:00:00",slotDuration:"00:30:00",axisFormat:we,timeFormat:{agenda:Ee},minTime:"00:00:00",maxTime:"24:00:00",slotEventOverlap:!0});var Fe=5;De.prototype=x(pe.prototype),t.extend(De.prototype,{timeGrid:null,dayGrid:null,axisWidth:null,noScrollRowEls:null,bottomRuleEl:null,bottomRuleHeight:null,render:function(e){this.rowCnt=1,this.colCnt=e,this.el.addClass("fc-agenda-view").html(this.renderHtml()),this.scrollerEl=this.el.find(".fc-time-grid-container"),this.timeGrid.coordMap.containerEl=this.scrollerEl,this.timeGrid.el=this.el.find(".fc-time-grid"),this.timeGrid.render(),this.bottomRuleEl=t('
').appendTo(this.timeGrid.el),this.dayGrid&&(this.dayGrid.el=this.el.find(".fc-day-grid"),this.dayGrid.render(),this.dayGrid.bottomCoordPadding=this.dayGrid.el.next("hr").outerHeight()),this.noScrollRowEls=this.el.find(".fc-row:not(.fc-scroller *)"),pe.prototype.render.call(this),this.resetScroll()},destroy:function(){this.timeGrid.destroy(),this.dayGrid&&this.dayGrid.destroy(),pe.prototype.destroy.call(this)},renderHtml:function(){return'"+""+""+""+""+'"+""+""+"
'+this.timeGrid.headHtml()+"
'+(this.dayGrid?'

':"")+'
'+'
'+"
"+"
"},headIntroHtml:function(){var t,e,i,n;return this.opt("weekNumbers")?(t=this.cellToDate(0,0),e=this.calendar.calculateWeekNumber(t),i=this.opt("weekNumberTitle"),n=this.opt("isRTL")?e+i:i+e,'
"+""+P(n)+""+""+""+(this.opt("allDayHtml")||P(this.opt("allDayText")))+""+"'+P(r.name)+"