From 90dbea9e2509ce022de1a3031c09073f3e5729a2 Mon Sep 17 00:00:00 2001 From: Sean Kenny Date: Mon, 15 Sep 2014 19:36:16 +0100 Subject: [PATCH] dist files --- 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 | 8 +- 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/cdnjs/package.json | 10 +- dist/fullcalendar-2.1.1.zip | Bin 336134 -> 339996 bytes dist/fullcalendar.css | 1866 +-- dist/fullcalendar.js | 17393 +++++++++++----------- dist/fullcalendar.min.css | 8 +- dist/fullcalendar.min.js | 8 +- dist/fullcalendar.print.css | 402 +- dist/gcal.js | 200 +- dist/lang/is.js | 2 +- fullcalendar.jquery.json | 10 +- license.txt | 3 +- package.json | 2 +- 19 files changed, 20488 insertions(+), 19295 deletions(-) diff --git a/dist/cdnjs/2.1.1/fullcalendar.css b/dist/cdnjs/2.1.1/fullcalendar.css index ab8b6e6..9e43656 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) 2014 Adam Shaw, Sean Kenny + */ + +(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..21182ff 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) 2014 Adam Shaw, Sean Kenny */.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..69b268f 100644 --- a/dist/cdnjs/2.1.1/fullcalendar.min.js +++ b/dist/cdnjs/2.1.1/fullcalendar.min.js @@ -1,8 +1,8 @@ /*! * FullCalendar v2.1.1 * Docs & License: http://arshaw.com/fullcalendar/ - * (c) 2013 Adam Shaw + * (c) 2014 Adam Shaw, Sean Kenny */ -(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..6078b49 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) 2014 Adam Shaw, Sean Kenny + */ + +/* + * 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..17097d1 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) 2014 Adam Shaw, Sean Kenny + */ + +(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/cdnjs/package.json b/dist/cdnjs/package.json index 7fa715f..1bff92d 100644 --- a/dist/cdnjs/package.json +++ b/dist/cdnjs/package.json @@ -21,14 +21,14 @@ "licenses": [ { "type": "MIT", - "url": "https://github.com/arshaw/fullcalendar/blob/master/license.txt" + "url": "https://github.com/seankenny/fullcalendar/blob/master/license.txt" } ], "author": { - "name": "Adam Shaw", - "email": "arshaw@arshaw.com", - "url": "http://arshaw.com/" + "name": "Adam Shaw, Sean Kenny", + "email": "srkenny@gmail.com", + "url": "http://seankenny.me" }, - "copyright": "2013 Adam Shaw", + "copyright": "2014 Adam Shaw, Sean Kenny", "filename": "fullcalendar.min.js" } \ No newline at end of file diff --git a/dist/fullcalendar-2.1.1.zip b/dist/fullcalendar-2.1.1.zip index a98d1779c4dd46e57bd1ae0fb5cff3f228ca6a64..5f6f86472939fa690a2f6112b0fd2700fd5d9003 100644 GIT binary patch delta 155484 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 delta 151519 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%0AqPvyV6( z3z5}CYJ++=8%U-Mm2ny8>j&FdX}0&ScOy3NbQ==zR05QyaTV-;h(IdMBO+*{$3h9s ztp%~EQFFnBqAE~AEeAbZJk>jGxcL-uxhVSyarzUUAC=e_bsPwlUY0F0+s8h3%kiWxQie_UKZMkEC>Bk?zfOK zZ`*nZwC_EIX22hR5Ee8bYu!D4MxNR1^ZWnf=Ks8T>w}xOZV?ZP*@8nSpL^u}e_&7y z2T{@8jQMK5;IfDpn0|boX3auwTv?}<(<(W`~ zK5k3?MN}%+o|QEA!y(plZggyup&=)$)L8c|XC=Xvx6~bfyhAZ5(VNuMXO78bs`EY1 zx}57Z3(A#L^&yrhllUz4Le5OZWxh)FPQy9ckDACV|0p)DNDX^Es=ya8xr?lq zheC{e|0%YAH18v{v0Rj%`EZFrk8yc;QidQBjcu`t>uOqB7yAH6$+nZU#?ALaYF)G! zHH;(k%@Uf1z4!1zu}!SyMy9Ckia%pVG!aQ-?=?#h&LD9T$UMxZ$U@`5nq1E@yEEG{ z+R5(Zo8Y%i=daz32eK|~ws##P?jgG+iVMm`yVc--D;h4*Og9ojg=4~OiH!(n!8hz# za5DL=J&aB~2Dbr=KnfSraxH9v*;J-y7ru8$R{3+E_nGaGh$r3N1Z<}p#MR!U$8c++ zCx|$`v{?ab53^-_c_Xl~q|8(s+}UuByh*u!C5*dW;=|5O1fMnLb3s{X0<7Szgug3& zJeC-Lqxgz8-&&I#|H-ViOG|rN%I<;8q8;yjwj>EX zN{!9KHi9ttwa(Fvv=1z^7o^ zYsD7p`$XA4=pJ=HC#9N8KCF3LRLsF7aYcG{L;l!fG>gOUlSUlw{V>0;Pi3Ml7e{1& zKVoL+FW8FoxuU&e9BPQ@S)_f!-^1eAE(hFI2ap+?qS-6)%!nC^9G8F!<{Vi)^c}to zMif=Dd}jU1$wbo1vO^q^`Ijh|gz7ps{`j42*T|ImP0k$kuls%c%(aFY9ojF-0iBK= z1u3LjOBOj$ZcfsWC=u%>^ow-+%FvvD!t$bcY1E54+}OX47f&q*BWZBSZ$Kb{yL3$9 z`me%Rbd`y}VA;RW#OWF`@TTd14mrb2G2rYN>R3+>yO-J@Wi>~{7)F61q=696W z`Yvfm*A4(7$JR54RnFbCiIEqh*koW!oJ!(bgv`L~o4Idd*M<~4p(avl(g;sxMkNTwd!IlK?L7uyl-fsATv+o6)T%+4;=>Sfhzz8;aJf+aBUG z$A6)O+5I4N;FN?ph%uM?3|!Ivb!cEqItOHYG0zw)(yp)?FGdy6v7#J*!%xvf`^#Hm zvITvxF(r)2R90f-8^-=D+vrt8s;j4gg=$eDykx#GFtf* zK!6o|f<4{782$cA-18s8CS&T6 z$fSd}*x#S;2vKZ*yD$?Q_}8!Lnv5-b=4`HEwvkxC0WniBIcT>LNZSaGpcoDtJ2Pyj zn85sp7Q2qUSZ1F%NI~|jZ9K($7@a#TCTb?=`|uEUhoGm&KsgSA4?T*ACyfFTqKco! z8HnXz&R!~GpN&-WV^t&V|B|@-$mdGHSxL^tXksc1$QmYpQ&_o}T*#&P%H56(Z=0^P zQFW;HiDB5Bl`p|?)jdc6kPWqt7|!m&V=I6Y!PJ9d; zy_8WC7P%X_rOPXA2ZP!ne|u@M%k+@3(9qt5?-Sn&Ku(NuYJPNdL6^l0{RQ%2PAM4Q7i z{vAJ)aH_8$)A4vQsdCBt>7!9YhYn=T%WT*lMMHDpd*D7@wZFSp5qwZDYA(97N-V5v?3fg(~Fv0x?G=#f6qFp62BgydtY+@bz| z>|sDaqL6mKxsw-sujdz=)22dq{T~2VKp`|&WxD6AvPKs$c3IQ~?A&Ye3d7xwI- z7apBvuW;zdjDW(N5XlA2I(fa0kb1Iz>9GZ*ID6Uf2R(sXOeR~LSs%&hy*OqfIYxfj z;Gb<(Y( zKl|n+D*lGR<@tKr<O9D_I{{o-@9|nXBM*U0S zo`zCs!nh1dR7kaaIjj(;H2h$HR?8x@$PEtH2`Y|FCZz~T_4IAmF zQGn$|haMza??syY>K9zzye4l0A<+kU%_q|K?zOl|W6i>3ylnFvH7K&rCPaSA$twbh z)2)DtB=NSKbioD5JyBpMdKQ`YF3vo(*8<6O6}2cL5&3}0|LFW27B^5I`&Dr!(KeOR)=&VV>fDU&3=mW7_ zwnX4|lRPxW==%pdwh?+ZQ{ET7`Td3#dsi=Q+985lpQ9@#5&vsM z=E334Vz=)4eq<>M(UnGLa;~esSi$iOeTj$bbJTYKLWiHfGQ+Wd?mirV{ZrW%U=8HB zf?XvdY{qkgy3WtggYh)heXV2?Z$vOQ4ZRjDfC>0TOu~QmBW}vZc~61H(X1WWIziV) ztrad+DmOCKL^iEAX)DDD=<$B~4~dImyFA_BR{+wr5GlpjSN37ai*_}gfT8*4d;(MO z@GT%}y9#inmzvUl3V6oA7lmkY(_b%C*$uphzxY4wA0tI%C|UP*r;z)G(xB|M>+$** zp9KTNNROqjP+oNL?7HJ}gAFJ=b&vc2ygD{%oRYjrNEg%eEK?5fw^U_839XMEAJ*C_ z0>YvOD&ckcQF+6~g=Ckv9nML9u#aa5%_(+h#Z)M6o?7O0VO z77vMSia;DVaHaN2HsLMCx5HSYt}}+WDMABkM`i9z{*6GvdwGLEm}2KgH*#+OZ}uRx zlAE(n;j@*IA!`C*5EzmoEW@aAU9O{pW0fkCbpp7M3nw~tJ>;rb>a9(cpGE`;z~`6i zBxKr7OX?+m1tD*=@3or}D-K%6cw6JuP&~suDf=rMu<*p#9%qNU%B!kyfdVqGZGSIcniux$nTjFDKE%2{ zT(Pb;@qz`l0hg8n#^ISGr6i&{LuRNOvxKOfV=~u&6C?h^WJI;C?nmJ7a#{KrKL&$0u5R zv}=QZPah2pn~p=W#)o35$>Z9sQw3dH-*@kjrtzVy9+SdK<-C7;b{UL#MQItBt>mR8QFlRv{YGRNC+7%tBeH!e_G#)}@Elu*o8ilt_Gl!iXvuUuO7GJ1; zHe_Zi0F9*_N3j(Ozy3CCjvst{j0n2@ng<^rD~*P~^@-(0y9mBGgacO?<@W5w>zC56 z!VzE`kPW=pj}ihG&s>_1h0)h>LzMwLHfN8VHzxXNPpx(bI=}9EWPut*{tz96DOTxB zV~3J7^zP03nANnsP??%~@zL*wzlTD9l)P;S@=Y~WN$}Q(Pzk^eWKzgGYKqnLzy?Jk z8e$S8F*O+9AdZ~OkL(7G4|D2g^>*A>QwlVAA=@`?CYn}_5ZyUk!pqJ(mYah^lh%iI zAeL4n-SanniyB;;`)alZi{0KM4sKY;S{(ckzLaXy4*2gW2kj`_M3n!yVO6Dnh|K4) ziIuUz60%o0vp|d|Jh6$yw;nYVS@>YCf3Wde%&-VG71_l{O>gNyDd6WCw6-+9L;7rm z_uE>cW?)dFp1n@3N~&&|vAbStgKS<{Qky1hZaoJmJ-dUy_NzW;$)8tds(7_fx(80w z`HLVlD&Op>B&n7kTOkyvyt3eb^thCKSCsmV15$N$oTY)VQOUAUTKVqAT}7lZc{-Is znquo$L=Y)U@BWX^C~irJ1g0t;$d}G;?ap2wo>~~&nS&@S!?Kv--9!Ok_1Vo6M5ZSd znF=P5xqFMW-IkkcLaQA?tdZL*s=3QrBEsHwdMt=Ic8EYd#ff$^O!$j`@=I_JDMPM8 zLUzKU!52K%zu{$ZD4|hU7sK8?-T8I82_&KN*?Kn;l^oQ`ZB-n4Z}z(zv%j1D?pvWy z;g~+;W&^ELWe%Wy74h348jZ$?!8>;G@uNFmZhiXj(StjWKY#e;)@>7`Vo@nOCYu@k zI#K;R6{?JGbh@J%!4;~1h5nSxUj%5Lj^#)t-)Y=tYAsV(Sz0${&;;n};a)V{AX1$t zxBr`;-A2~e@o9UdfkTw36H)y6OxY>jIAKa{!^fT_BOiUcBuTAn(0JN^&2!g~zE~o*aNl(0;XChwe z1l7x&$Ob9;AU$vb6c#s>rUiu&(?HND6LK8`KBE&}YWjjML{=^Rh`;GHb$n~)9DnwRH<%qkFU!kIM+6;TnF45eRR__^Dq4K*tF5GdqcH>MOT< zBO_?&uFnDk&2BrY)MgKgN2_}UifHO%Eg~}Pjhi-j-5sc2>ujLgo@mJ61z>&9;n0Y3 ztNG|O*MjLf;Q}Bg>zd4Xv}5|d#0E|y#OblSp8L7HbITaxg&Y>^=w@&} zAl{OWG>9e{iBnKWZcRJM%*6@E(C1BwI}m1oUc{rL8-Bc!aL~g*&l=(A+Mh)^hM^I% z7%HSK^>992Xx}Z7g<&4FNxB*w+vIi{*UQDYfM$u~k&_4i`hNb}!dAlseR~#) z4_J_YOz>AK1?ZdyQ$Lf@Saj*mx21yh?mQzu=OW<-(Yienwl%hIf=-JIaQ(*re&Y07 z?zpXahS!WPkgF}w=5^^36OwG|AgA>kdD$c`!_C8>N(B;E-i3FMGlX2@i;u69xMd?9 z73!rXA093KO{I4f>I_%l+K7m9fT|F8tl*=69ToT{rjQAy;&f-yH+(M?=^;-(!b9c9 z$dCvN>Ou53v^rrv)7e0bvvYL#iASy$+mLZ$PAsP|3Y}>`?JcD-ManK(x`{srYVV!% z8wS^HSoKYUN#f?zg4kX`jDll^z{b2eoyFJ0xWkEI;_j(d=QMldatpAGh)cPbPx>W) zB<7?~LtYQ+?C;GfL^bqILa5M7~H*AlkM0n7U@CMu-e+`(=`&OV05k+}o zsb@zLWdlM+C(uzV4B7)l)`L}m5foOy9;i!B)F#v^RMoYchXwau}#(2J~_|tf0 zhRbBxLr-5Y@&!8rM5@{=Wn9x@-bku{osm)MDr*GQS}?65gVa_?v7Y2dwm#Z?lqjjc z)6A!q>h=PF!L$^+}(4J?T=9)MIVbJ{~+WTp^qTNJ5AT_g`2hWogD{T)1FmbZIgH_>WvPUj%tr zn+Fw(^{Cp1<`$Kbp)ClxVYQ2gszBkjNHwRA;8=R9A@y+fZCs<*_C~|sYt{a+0S^1K zE7EauPsd(sg5d-*Dv3&HdlOOzO8enJ!Nuz0a1wy8%Owr^D8enLbdmq-C~Z;c$d2eb z8^K)E78OA})=+hEaHD$RI$^lFV&*?iSm;BJTIp6Mt1joju-{Tp<7FG@O!FJr61Ox)O< z(hUQqIiGY#aU_KkvJ-WG^4c}-YTP}!n^K#v_NaDBBHkk;)jPAjmjWwB^M>LFzWU87eq$cht zNU5(n?)8U3%xf8r>EDQ=myzn2kBHW4g2Wuws(zbCH9qA}>D3Rmc%qGv-$ql8sVMw2X2BnCGlvEbz)n{| z)%hdFW|i-00C^m65;VnhpW7=JT-(I;(~>*bCxMv>JsB8Px?6b8y_VeK1kr18uO)3M z^hug+!AIF27HrLbzlezbS0&#I4P0jB!ZQxYm`DS?0@Krvb$x6ez zSjO3(qBHJe&OSN7B2U?+QZ#zPb1%xil+5qA(0z;Pa zCgKb)YgHb9H54BOhbqg7>6Vy~l+z#Ud05Tjrx)2ae)dWD8w?Tu>%1E_*m&*OD9Di0 zAXhXf%YWlqgGd{QAUqXNAYOocO2zHLQ7jj;w;|uL!&ePUe!K4!)GQo#p%J1Aw-1PZ zJhJm*dj^jEk-&e~c!6!Q0+ri}yG5LWZ1FB-DXM6H{(#W8*!3+YU9XS}K8@4~=}8_# z2cCNJCogVBnMVN7B@eoS)}NO%2=%D1dQ(Bv>4LFCk&TJMWudE3OVRX9Ib8C7ctKfc zB7V8XgoR_bsbUk5bI(q48g|KpvVm z_u|l=>SkLJ2>}A2O(&ynvhH$ta+!$HO;oji&_sQ0L|<_VXVh!6W~+N0TEFgAHDVtf zzFN92V^%a|8;F>Z?o#TN{-w6sUB8O7PdEDwa)ShTVCoDLJPV^b@8tdXz_qAvp~3ho!YVqB7~}l$80SeX}$z zS}i>rdRj(0+Ix%^yp}kbK@%gAka(C#UtcZ_Cywp!mPbo@({H2vvF|5YU)z1Q;Slx> zQ<)o^FHZLNq8Lu>%xC(2qZpmPZzx)S6QRRhVwcv}K(y_h*(QAg$(HovkA^CuOJAuy zYuQjm_6>+>)N5Y_ha!#8$ZU0A&i2UwCY(W(MQNf8|4nSrk`iwU2I2CeK`-9`3yE=5dMD+y&AyCyM(o zFK6weoy89Yhn44fynNuc`h)8?yXk31ZqlxQq-DKf)uTnKy-F{9;yZzV`m^)i*f z=`hoRi1oqe(lI$ip=ZV}I3^^yY!j!2wEyQ+*MKJO%~y)EVOJyJ$ECKBCZLeVo3n>9 zVoH#UODrt0H~>kgtNs4`fXi1_W3Lo7pWkJ2Us~D}!G*3bx^3zulQ&@RYh(?8sp-|ho63jP&A(9iYJF`+wvt3cR(Y~*)itC@ zDY3d_7;YJVthJM#{7(PU8-pZ8BAwpVY!8@N@TQ#C(E`9?BjOLF4k2LGUKO4-(}jNw zJWdL*OZ|3~?x~Fq{h2Rli1G$$! zULRON8JO3WM7`$1#dMlCo|@!~My2_~>DN{$u-Kx1^oO~S74{n3^3v?pkv;f}#XgYf zm3o_r{Wz7xT;1@zD?8FXR1N#JIC_(X{7P?P6S8yxU(4#XYzsiLyZLMxkSlPctb8TS zLN8L^6~Vv)-Vu!R3~OPU(kzcDvlU1#y6G61%*7k9UuPmDgRqS%3W=Q2_+TX#Q}}Ew z)kbQ6P3U+dtAQrmD28WXd+?IPddCj0P)wAOt{n0eW(&c!j2g{f6v0%ItU1|n8Bz!B z?j|B75_LC$T?UiYwrnObL{faV2s1q4J=&f)Laf66y1b=Kt5%u96Uq&8{SDWA8m84- z-NQxMd2HC+90ly7Wwet{^hoy6GNlxIG4W1+BgNMI`B#T8RZb}Vo8QaDr!BrNx5#zw zaPnKRpOoxX_E=LBPYSsEMB-=AvF_-?9?$oZg*vT7h})fB9D|u4(mhm0Y8~mq=F!AG z4C^}Xx(X{$Oh9w@$gb)QM<)PA&jQ4FFAsn_ODX#93yHLzWOfDK*sLN`=^U?}h?nbs z9f|~Y#sj_!hG9<@-xh{(Uy}g%oHZdvmv20ZHxl%oaxD~)FE^6oEW;#2%`z@Vt z_0CSkfYYWBPE|rEWHnmjC6OzrWOZ?WudZXOCkeR3tY{61(FIyt9yiUEQ(bUNbjsE79(jY5IFtDSd zog%)S6^8BgzC^nIB9iDX>CT9B_a6lHe`aSpM?y$Fi3YLfzQAMx@0_o#P3`mve?g4s z?ay%g!FQWZgP7=GmCxveL@vl|@V)+uEfzWKpuB`I4Mr!DhsFtyc0-Hjqlo@kCaPho zqCd+Je_koJuP(YS_PHRQG@>-TFfTGLDqAsFj`0VTP|{te zkRZX?6JV2PyXfQO^KvZ({a}*w3A9b3u4fmtJJe0$<9j?rqj>ptF)CO2deu#XXWU32 z%Y+sM)J0KsBZ=;O(P8(#>qMnF=MKA&mYAWULDV5|QP46=t2`ICO z=R^qCnN_dxx9-L0&PN6(;e!X=+HHfYj_&YzmthhgLF~F+>1*H%n^#`Wa1l_@Ya;h# z(yNGY*K$f--kr7L{=a$*VH5JEg|=gD@+-G(6)a+_+IQW4S)4zE?k;bXEs|Z){$WE} z-`$*6%_MiFHP5XP&CJVNe}?F{E|x7wA(VHb)I&EcC8{BScS2@FP z|4mQ+uMXt8;-Dl!T$D0wtM@9;g$_qWROm;+& zG1>hCVGzt(6k}^UDSSVKVu}7*<|Jw-YuyKXGhsHD@r|XJlhGM7e|F8f;@|#F>wOS5 zBp&vGV#7d}jTT$cc}0t<20B+M?+XwrjcL|On655DKENn@qHr(l0;)o9!z|Wk6&g*< zOR8n_XeFLYAFfPh#ihDE!I&O{`Xmw2`EYkVM;#xKI7pL0b2M2%K*q@UW3PiNTRwqe zZz-}Bpq)7x6W+}=&Nbvf7iiY&clkOptK-0=QBE?X2gyCgeItwOr zyB0HSg5YtG;xt24*g+Blj!&Ty=*YO!y%h&Mqt(4V0msM^M}`C@Oqoc)$Jc_7Ht zW$)?a8EQI(^S`E?l?D(AE6jRLi#+Da9VZc~>p0@rCALpK)bTew2A2>gI)-l z61!4a`i1!sawEo|h5V8#I|6Rou8j5BX=G9La2pHQe@3b=E^x{|T*6|aT}v~Xs_odG zR*{P>?t^mMTp8$6erwQLUo8{fzAx_o@y^|Uy6q#NRpaOPU(Mmc9@) zk9}pR9nInQsp#y%r=`rjx0e#H(nID=JbrlZf8lME#KNT=io zgu%oF2XsYhOhNOh&bYpZiut@(UsHjznL#^=bM(P4B6W^N_$4IIGX|JQpHB-)zpw-< z92^OtdXQLKvhUgQrw&4}yPn~&v2PYQhjGR|++P-vwj{-tkkm5Ra^}gyJc~EVa3cve zf0(U5lr)n>XO*Z@pE0*y?&<`)^VsqumAD93>@dqniLJrg;z&^-Y5Lj8`Ykvr~wCKoPr3Vf9JC@M4SmV{7cfk3GKRvK_E#Nf38xM&!k-0 z{+Ak1*#=YcDZ&aLilRcc?T^k^|$yc&eo_%x{8RVaz6WL_Cc$( z7$-kjca;LY1hpH{nIaCRQs+)+Ju-Uz}{7HcE9=aHO26L4=olLo-af9HP} zJLF4eoH`Lt*-L0!#C=<4kf$eZpV&B>E4*dL@(=;(_ae|)jx*g5jQE?Twe(LY>07r@ z;4;q~HWUiSr|cHl11pe}@jkA}D4N&vc&`1~7goA1aP=I@h%dUVZS}+aG+u z|LPsHs=L+bt0H4iULK+jrU<4Squ3)$S*ud0@u$BTXTi(I&Ku zYgD_bnQ7ph*|q)cAM^2t|JXei-rSc5L`vx1_REPyRD|fxAMby8@6LC3f4=zQyL)&3 z>AU-XzW?Rp@4oo_!RL=T;s4z-M<qb@Al;gl-)?=TN4NFey(x+*Depj%+Mz zeeH|KaqS*jIMM#kxb^(@{yqkXwRQe`WAg6i`agfo(|2y_{X4gKEjGWmK-+ZCX#?a*S*FD#JgRM%*%#PIi9S#GegD-5PQUIYEdHcg`R#c_|Pk$!*>4#jc5u zPx!JP4Q|=XSg?i7l95j8&_@EcR;|-*BTY(O(&F9L;@fa2eFvF#e=_4jRo$jb{?J`K zI-M@Wv+dv|_rfF7k5w7p>P@65KON^jV~$V^An)&)SI`RRwTtrJj4>{Id6hzxQ zef6ywI{Vs%p-cx{fBm(XGT$Xi4481X0A-}OthZrR$0a${idJQ(>z(sa&)cgr(%T0N z>Gxz)_M}1pw!7l&cXvZbXxpnaa^r!>w?n)p6Le5?rGIOpGqr5&csZr~;4lKE9vv#` zsOt^Rq<*HUmNPf(#Iisb+3#nC4-qNxb_piF_3CXk&wc^{qb&0C86=;>!r z+81z9XX9&9yyXtRN3cC7+VS)4<&DesuqsGEF#1927osSYc6>~_q{-A8XW+W{fPf^<21vWmTJYJJ>1ki_B4ta^f_5xkvz z;RK6qiz^q)O;55}x3(LHUmb0~j5x0W9pie~&rbS^4R0lBuhCVQp$*S?a@u zgpNLK2G229h^R4^_0k7?3VmK%*9~C(p@hf0v%W@Gm1a7mZ>-FUCW%LvFY}mqQ+aKIpWqtv!wI-_ zJnS10f6(efVl!0^h50#4i?FTW-2{nk4>3LlF&WJZ1#t#6xk&ao=wfa?9yY*gM*S7X zLSN+6hsF4A_4TzEM>w;=mibW-z=*UcW4Qq^W&_pJ2(xiq+$QI0T?G#65X|01vh@ZO zpNK0}vJjO(P)!3p$BcT(p>?jAs$rCq2(~J|e^=XIad{DYCPP{ujRz~^+vfA2{m)Vv zp&?;LvLsk9glOYWBqUv%fF@fEqVBn{8LjThB^49_+m4N6ejNbaLKDR%Iclaljc&=o zMum^yEJCKgis}k&^ZxqFD+Ec*Q$MrT0mbB>3zku!-DL>!%mJY}!kREQ{;7mR{hE&4 ze~@b6ie1v;xnKPMQ1jt&a{aNwU}-q56e8ac*tabDAR~#KJW`-sVOQ>$NWdIDdaQVb z@e>#n5A&*!eW55kJuWq!7H8EGbhwv_3w4!bWszxr@8 z4ZkxZHtLOMoA(D6Z}&80IHRut6eQK^f5!j>aYAsg%a)ja7!!)`I(QZf9db)6qkd9K z!%Eos>G_|zev75s$og%fkv?wuvp;7s4MPFw3@4Xl;qxnJ^gv33*{Y?sxucKblDgd5 zNRxl@mNgtom$u73!=^7#f+G~v%N%@eMv0FDAgci$Z`IfArFr zaLKAFOZF{g@nfbW*k)zD6+n8AD@#BZ=s?4mDZ;!l+gu`{)6K0)OlxmYcf%&H-ZC%a zy5!`|*%vCbS006QJ!o@fI{w4+3+%$Ue8bNG{Gp;lFJ1oejM_o{YGv>kN@6%Pu}9Xb zav>=TWN7s)l%h4Nd%**28hf&9f3!yS;@I#fXgA(#zXs@PRc7^}XpBmdAlq%LWpAl+ ziu^&@jW5WxFk+lld!x?B^3HS>HC7S8H$fiz&=sm0Xe1K&rgaQP6Ic$DYCK5oY?WK8 zU69dq`&(#S9b90;b17lThXtR>qlujYjgIDne_pzz4Ml2_ zh8YwhWm@Jql4bf6_-MXMm%3I)CtwKGIwMVbP1*wCHH%fBE77O|C4> zKa37j>2fd&#e0YU$uL{Oe;Yt=qGD{Lt+&M>j~{dFaXhztcqY|P=m2lHnn@NFMud4E;~ zW@Zh9t{BGCZBn(c^$LHu9*LAS-;RV_tOa@A1y5%E?D%+njo@;pe__i9nU<-ysGTsj z0Hq&H#;m9@5G6+h{LH;0z)*8#>GF7)Lu;mu7V=Qsqju9P$Y+~r+aNdes+Ab7c@|&1 zxvYvO)HPmNfninkfbj@tyPj?RGIj73E!P!`dwphB&vq(fl8p@L88t)K5SWHp6_Caj zzSU(PhED7s3y4=he{d8K7@U(hmq{k=&@82^KqW+|v_1PqY07STOH<}>!udGWyebhC zCf3P~bEU{*jEq}0%r#r%Qo#uC2q)lYua6*P^+t^F64ERa4~8r0*I=_Q{dL__RK6it zI%XY0@%SiQuC9V6q45JmIKOO0TUk;8m%LIuT~a~i6e?Xdf66XwyUtn0rB}Xhsm?|M z4HcXR<2Zaq!*a3~q!3p)dlHn|FA078Q$Me^eM`jXh3WG8xYr^eNxPtVOGOz*YPYPo zB*ZkZg~eM-JU+o4Lxhu2nY+4!3uxiJ>pf^Sj!wA3`u_IO_rdyQbO+}lJmE*U;N+JH zY-FaSvAYsie@x?j)YBIe@LlFI!7$7jW7VPpy}+_n1t|gmQKqL@T|u_^1M;-4l+~ri zoYh_K`O%gWREAaNuxFufkYzSrY>*;9iPrm@W5#B zB-||ue<$XOK=G5_UNhJ?5`4G+0hqCkTZMK4v! zBh0C8OYHsZD#r=Pee)*y;ZI4>j76mepWY?us0A$;beh)l=K>k#U zf_58ncbhXJ0C^JzO4ApKU@x+6MTTxfe*%;1tNbM@z6d{_%OSsZZpjx80O*-&P9U%_ zDQbJ9x=0L9U4+#tUQ{s?5<4A}l%Gx%O{9sV6J?#5e%R-o0^)1Ns*dHqH-bbGLTWZyB?ssPR1hJ_ymt2a0UN(tbeczf8~<@ zt9;F6gUqhfepO`JUwio)ag9jkAc*cUoMy-aPh0dDgX0{k$V^u%WhiYEoYIW)EPm8? zTQu+@y`Di3i3>c|6?xpLLb*hhyRh@1k&tuh2XT7xK%P@Hr&0E<=@m|{A=bESr|Z1? zpP=H-3M9)5^EQ{TZWX!>HE}Xie+XtUz%qQWXQA7O*9M-Q-`yqgY*d9Paik5ftVfQQ zH*J3f#A;&=Tp<*yH>PrPL`t-itIa0MSu#o#t1#nlThum>hh3vW|Ao_*eumuch?D)A zkO$O;2zu8K37^($&o3u5{$P@f5ic#qN~*E`f0>jF3*LYGH{&tR4)gDZf4f?+f315M zq5}T2zRuN?4;RD-%$4)`j>SV0aZCV#t)R>^W5eaXc>`V9c_@%^I6&Uw|!OjoK&Yu#?Ne9^I}s~vomh5i8$ zajWaI_q?rkrT(#1`D=vt3)SejtG>&0Dfa|_Y;-C2GRNcipbqL;ws-hBIB3-=d{_>E zG?EBGbUyfA4!=v0pSP;bWgBOzR&9En=lLvG=dH-T?SEQzY;p@;6vZq!2~+}!p*&^l zVM?#1l$YQjbXl!Bc{aR0K0b~wYL$!E0DqAcpXHh$OE3mltJDF&Hj|%=}nZ{7F;3~1@H;_ zXtZo}di8sb>r z;D3I2IwtO6CdctnvnS2s9v*v|pg^V|I;gIqT-wT7=;~Q+fa!FRV6a%_L}-RFYhD1m zT=Y=D-^NQ))n4o*>m7qpwDArZhETZyLU-4Yz3#5%BmUh^+%rz(?l6wKBue1lGD&{A zD_XU`K{U5_THRPwZ{YePY^eAT+!|krwtot(J}-4E4^rrq{}(%FG0B$I6d4(#>urI5 z)}t7x-EGAUCQH}Hwx$sD$c=-U~Ytx!;MPnkzqiew-V!DX!zoDG- ze}Jp_a^Q<0Jspfy4SGF$LKx*`S>|hk1DW-M2|qMHS{Jfg=x+~~*&gaRnfh!Jw@JG* z#9fQ<_%PigJcbU69Fz21vLOAa<9}hYcR(mgj6USkhjURYh)JxueMW=?1P*a{nZA=2 zRom$}E2}y;C5AG6%58w@FwfRoQn7`)a@62?y=D`Nn8&eBNf?8|>+jCCf*DiHfYA+I z=~}wlhlJjOHVxmXs>aZM6S^@oJrxRA=*QmzWCFdKiJ5nh)16C@v#gAqDt{tj4;p)G z4^x^?Rjx~{_BOM2IL@Sf>7Tkx@^Pn4AB{|I)L3Ei;DQ=sg@s`8b^f$CRxuFXNgXqm zzj;qgb9pul;(hX@2-$* z+MDj1tR=L5{LJ?9?(u|A6MwfG3?FFh$O0w|wmT3A>!JG|X-ouGBrNa9fq80}g@+pg zi=p>dM-8#|B6BMd;ZO&)I^5;BjYdqX%U80+cxs_7U>cgr3uaR1dG_| zu-E$r3X`s2o;7nRXn^VD*v*;g$VbS1Il&+VOcn1EWP<^u(T-t|)qfn7Ri4WxqVjMM zEl6b9H3oPC!rEj41UW?R{QMTuCu6^Hv-c0%Z(5ubHQ*B8p)=3rOS%jW+|<~6ngv{e zXr33LN|4R*jcM;fd1fyRMsrV}0e2=od){1Tw^D8_w`=PrFc=ZnHu*X>!Z>sK`1mHp z$SfLWu)wib;i11vtbadnvG)YC2a`%23L4SM<6YvkdO;T0>0SPcyR3zPg*Fa*Impqc z_%-Ec8~5ZTWp5_=?FA^yV;y@L!ru1p%4dj!iEDO`FkB!kKI0ICT^yujS|Kc9FJWXb zQ`g#6iGDncuGix8CjmKWw5u@(G{TzQz(=ziAnquu(x;SK)qlIb2#}Am2&O@UnTNaf zLH|mMB1nvbycN40rqv$zf67)C|F!LwHz&uiw}A$!%3~&E20+`sC)Rg)Kp;A{Ooum- zaHC*RqD8BTux$aR;b40h_DNRFwCP~GI_lg?dG#6mDHx=kbF&YJXBTh{++4sj$!57W zY1Oh#U$LG;BY)BJ%|HXj8PvGbkn`}6SPvEWgqpVP!Ne|PDh@&nwtXPE?v>&iRKVGI!{UE3jn1^A5Os@@^%G-EnNdK-d`6N_yHJ3E%Ab$mWDy8d z)Lyl&?M0_XO8q*C<2~FSO$V4m#!<@MgBOLcos}&n4u5vGY7aZA+|3+rBC*P45uoVX zBEhu}xwYG9ceFBUU9A$MSRafqQS6da2w(vWW%g%FdqXQ#Z=uoiR^;6D*ap>@&~AMU zJRwI7dlL}}=%14f4FG)}K`<$&4S#!!utddReEKjoesTQ6B7&DvgA0CdVxyD0qJWcf zWe!dAX@6xfi_KAS2`-~A@lL{IY4~mtV8-^&dc)0NfPG8jfOtjjP&qQ*%1PY3cqygXO()VoRafpXs9A@6`EPqwnaaIy;#Z=-9w1bD>TkD0US$Bs8g z^@+!Vkxe#Xylh2RWnZC!p$y~|mXTm+A+Uc-`G0L5&V~$4YM8H02-%*^1_q&GG~5WG zV?Yab-dBz789tiTDLkgrYFFj1-SOKKA7GOE*^JBX9nMy$i(Wv?G|h3pHLrm@zLBRB zDua)$DtE5Pca)b zjeltr7b}xO+$Ljyv6s}(m3>;X73rdDXeDC1$BJCTm8#Y@$l2P5+y2H09!PXTYlS105C zhJ>=61>x~LWo)M*1e?TdUSN(vix8CG41d`c9lIoIk0~>`Z+#AZL;qaT-5oze*gZ$= zPJ@{mgddOdB)Eg8E^stEyJOOF2d{U!knty2AI&FK_LvaKc&K(Y#gs0qq8YESZ<}1t zXjAw{dhe7>?TH)+%wBPns(brT!TaAsz+ohd1gA<^ED5mZYPvKvpKf`8-6 zFNaoEn%8}(zNjC^T+`hTd0;5s$~cBe3BAs+9GRBP>cPfoXwUFoZzHF*iHsi-9rb{d z1a0KJeb>DPTrD1cg1xuR*EaiIdK(&UxC}TZgynhDIinNQy2l`Vcs@nrxheD9FBe9G z7Y-(au?saA52r31Bq?jSlaSSrEPoEIfoF~A+CCa`R|q&souMyaV6uI1R$wNB!O-c| zx|{2q20_ku$^8&tm@a}te1TvG3wpsfr74GqXG{9c4NqA!seB*=-S_jFw^B**op1kx zk=4xGAb86;#TmLzSE^PMiTMHg{kwB|3Muegh{=XzQ2D?IFC!ZEIBuvMsDE(C#$KkT zaW_JzLhJS>7h3SP<^pvgiK!old`aTp6Zqjt!aqEDhZFRm;qdJ|YDxuUD!VIiEp=~H&@&4OQxiTzI1ctA!Ilrtd&ZWSIOuD^KNWI63gX=W|Mbkv&t`eh*{uU=f1n0m zilGvJzWVV$P)i30q*%#@XA}zn09Pjf08mQ<1QY-W2nYbkUMZJ;$N@Qj>vmhmmGAq# zpMt_kEI?eONT<^o4I1Q7B-wH-IgZH0QOnvH5U@xt8w6+o)WxL6I1h22@I1-+{pYOP z0zg?w_dcI?cPwJny=KjtHE%WR{=GZ@wsmjo%d5%c;c!w<$HV#7o6mRmclVY2Xf|4G zeYW+q8P(H8-QPN2E-(9k_wNtqi}T^z-O=pg{wWnlq2hg2>5h6^pYQGeZtKB#c(HX% zIa2TcTle&8I$AcfX?NMX?!=GIV6c3DSt27T^ou6?Z)VMSYwylrF#YVau4PZ@ zbvEy6^3%co!Ek@MrYk^;H-q-!%vOJUH+3n6S}`G+?*EX&YPVVKrpm)uELaJ#u_ z;zy^RmP>2HY3if%EuEjsW_NjBU(`pPtESWMoQ^tGvpXD* zA5MmgMOWW{z3Q8|sI82h`Et_f_1mR)Cd;|i@J7qbt7hkIGd`<9!qrMwQ!75EW>XJ9 z_%FuIo2`+CFt~d<+IiE|Z+Aws=@Klc=XdWPg7=r~2Z+JRj3uv@^~vhunS0HqZDRRuUH7W1?l-&a-Qm?_`8S$>)(p>w)A6MKzL}0^Z=ctT<`*h; z7xmz2_YJvki}XkJWcZ#gyPl|xsp1sw70?}bg%F>8Hm~Vvv7FD|GZIFXKN=13V)mvc zL;FiV6V$huHK5cVz^p$JMq33Oi$PpXUo*yT#|P7?7){1E@E~C4R>N9CLf$ls=Jlj5 zk>*N&IDFdOYwgHWFs1dS@puT=BBkA;wT_Plb%k3a%*I}n9y8y=N&Dj(OgdeE6(UA(U6`Rx0Kla#4Wmrv?{ z=InfVwDR5GP~kk5^dCEHyra6_om~MjZ$$&_^S@`q<@xS>b_M#fC!i5a26o?4FNW{f zs>8)4g#WqdX|?&JlfxP;8x2(NBf&ajamztOROL1`A>molVW`GOX}rOoSGDznac5m@@`JKsS0 ze|i69y&QpRXE!&)THDIzqpqNBy(0$LXI!}^3D` z%Q!NfG|P!Ky3~)NJMh%lhvnesT|ocsj5ZE*G=Jg-ITG<`7DEV8pd6j$Y&?8_&jCSB zJ>8Izr}C4tFY4R*E2dV}gZe0a*E9oNU+6q$r!03z@KyZMDc1ct9stT6+nS{#n`#@2 zKA21%4d1uRYB3h|M2v~psXV@C04E#(S8%3QqR4(u_Gx!#zexJdlFF2?NP*&LygU0< z=Y0)Z%NcoEuKBmQ%xy(pSdn6Xk=iXh5K=$^6W{=3tBZel0UHM^7C0LcSIYgl;3%u< z0lP9WUXbzn)y++p1-iNEXpO5zhcsxT6WB1uae%$F`{NAq+Sy{NQ&>+}V3@Gy_1WWh zm)*_}KfK-UZ2tnMNcU5k%th{##RA4G=dkpHp}|hu-i*g^WFLyf zWrwdelp5)Din^^@f6Sx%IS=Z`oH{R<(7!wQ!)zS%ki+`uo0rR1{cgA8BNbI=4O#>} zq!ol#DPkT~cTiVGL3@>dH(E^VH?2NZ*m~RL@SVKxvex6edN7|4-@|{+mNSV`yNe0J znscs+v#vgeJ@wWkkJ?`fK%#bM*)f)84^f;atTWZ#C1cllthS ze(B$@`uu$4jPtANubM)t_E#ze9Kf`%C_>C>mUj|RQa}I2+ORsm(obvku#^L;1 z*uez=|I6+_>>J1y8CsHD?D!lgmSruWRl!nJ3qn#f{kE=uB1MhIjIvPCa4Z>{j>SX6 zVny%J%4suS0Fw89%>9YkF#*->9sFq*L7^y8g`B~kKBS|6Wy5)apq=O^qjVW$L%ANX z4Y}(F)W$~Ei>VDi8V6C*gm7ppEf1VXbS5*TNwzu@S#l2k!ubeCIe1csieVoM9#z__ zaMSr%73XEGz_0d~gU(-19P8@pPLHaFPMWiC15>i=RKn@d)me^_sIhi$*)Ob?%f zzi?>(LwGlT_)E3?z|2ABuNMz4UN>igjAm9sp*4R*nLPi`KC?Nr($wDE+OUn3pvk!z zsBMIF7d~`m1P^$GVZaxaUE2cC;dJ&kE<JmAS&fPThN>EhacmYQseD`4<9b|;lvJFKh*yIKW(=lj zuvZ~|ynJ;4D;BA2pmQ2|X5JHVRC3QT!~_g~hx+jqV%%a`k98=yIGGV4PI)WwKr^N) zHQ21yVl)e{@!)DXTd&m_lI%3B#UsHpyIbhgQ0wh~ zj3)36o%@G()80*EM`?C_mUJ_Djx%(xw!!8McD22`+tcm4)?PJBb4Fnrr#;h2Vd~V` z?&5ONpyd;TA3F`;no=36S1ks2_7Eklyg0#Fr7EjTtGSPd!>JloW0FRvEo{6xl}@NjV44U6nT&DUVkg3(qZ%Xw{jvXnTs#IOy#!U-In%AXhI zW7^f={OEc2yw@L!)C5%eVsSZ~MwKZl!iFBDD%4+eW5eyv-NP^LtI%PmKSEW1zPTod zVKu{FYK~eppLfSMH;n6S@KQ+B*`A|x^XYV|)0N+l?Pa|a=7mk39PKS55ueLj1-1lNq`;HJEe#&-~-t~oe5!ail|Invv6;LQ6AfolIx*Lm` zN)RRJ&CSN_pqro-i26m?5UX8(olESa#D^X{)77GeEu6N;H=TidyPfSkyx1>1z-m_R zQ&_v&nHooD8@jr>Yd1wI+AIcs5D=@nSqH(~G0^hn9i25Jc ztDk)~bC%o>@C=;MY>r)E#|fi23kqO7fRMC-7|I25=>@AOtxM9hBrm}%hiOhU~vbH*&Bt3Ur7gkG}K;kVtMi zvbto0NrCKX<7Zf)O%6PN(AK3X7p`2na77za4(7qMx=Nz`oZp4o6S4nl#d0v1SaE|D z^zDyE%LhXxmb%Qrh?G)?iPqq1lfG+kZv53LG*u_xCEQUS`)I`?TG)y2oZQ@GMWr#f z&T)Qm61iVzc|BFe#HiIQ3Lie)l$r++ti&fM&K{x%dLZ6SB1SWR*xvpM_1x32dk(%G zOb?<-bvD>Lm>$j!X4p{Y-KQ^SGMnZ$=guo(?ajm@au(H$Gg&%h7ot!Dn}Xex3C&>e z6C?{d{i4}ThA9ZJM}qf_o)Fb1{3;qj`_H1s>nQ_%%?@5#VNh?;b{-*$)YwWjTpm!)qJ7>j2v$8U!*OrMx%Pc zw$T{p^Vz%-!;2%v<)mKL2V+~@G}s1B&64*0FLI>+Fcf1y>_Qwx&QZ+4cU(hDF9*+H ztxO>NZa5!a0M`7`gT+ubRbpoR*{j}KR$POa-}>$yT*AGby}W!iI0+(~B=8GMym&Qu zSS04TzT4A9F`0+uQ zu%()LW83NG3J9HIvH*0S`qsl5_N^?G?D?;#9H+N`g)4(&58JPr=Uw-$1m+25Og0`U z*23@_hAm@f4fYJKeLM8)5B-j)+w?4Nq+0o)s%5V3G7S++M1g1`8A#=#FyO6x5Ts%} zT5Gn*R2#Xd&^=ZQa?AZ67TcI5!0wa-AvJHAuxydeSyMAO6ie40GWv&yfd62q7sE^8 zxTi;dKBn;V=r70E>Nw#rX%jsThPTxi#~02pXlqaZq!DUPeT;Js}u>tbzS4^GODvj~R>k(p2rafS``W+REUu zofRlM4jx44*9W*z^uXk1@BmX7>-mpnjJi*M5aUocF6~g$lKm}K3QIQln;egv{;Qx% zqz!-UcL6KB1Elfs0~$Qw*ffOllT7wEK7R?&@r?POGz84YWXf08b>}Od1gfp;i(YX zms_N-_>8z&B%yGl!QMf8^)58Cuz6Mp4d`Xt-~nLmyTS?$d5w2l2V4nR5mhsy#_7~L zNFQYE;{>>VvAunB^8_`{Y&aI3a!uZUojtjI6yTN@!p_c%O-1)tioPv*7xm~) z3CUu)(iWN=P5O7b^K8brxq(ZZuN-i7Al9R2IecQK*;ED7D&#RXs-7 zkP!l4<4ZyFG;pWY0$^zsn#sVO4yGX_UF*^v#?1;#I5Z++qy#V~(BR`WKI;4f-@WZ; zP#sR4X3WMjmM|VnjX>&+L2SrY+dDL6E zK^>OEu%3uyz(5XHgE_xIp#glpII~gxSchWt;{d>xZ5^uEcQCQNYRtbC+^mBu?@egC z>*GZ|q2ZE5kOLr00riIZZ5^Y=<0nm4WAukX{o>h2(&H9Ui9eFL{=Bwcc+k|bhZrqc=BiS^OaZ?>P1zRGf zPD?AnB`&E1mawFfS8Pd7gJEKRH1CfP%FiPpkv;CP1Fw#*Vv3yz2d78fvvMISgow*B z0djDb7a0^eYwvVy<*IY%G8bY^F587J1<=czn>)9l`nH5#)sYo{e`N{LEl~N)^x!Oj zjZaQRGgH!Z7p}G5p55GRFGQfjbbReeJr16$HKn%FnYY9g*j2zMOUBL6;X*IF>+WALGY@K}Mx;DUsU zE9t(y#$%IaPpXI26P71|&$RlFAv3=u0SDz!^>py^msizS z*wVh`zZd*xY8lQ7d8>D$&gAH2{Pha@+>;CEh}#4q(SZPe(vVf|lEgkrG86k41UN|) zHj%%f`gi+E)q&gQDuN>eKhiSM1DUJ2JsXun@D1s}Ke?S*Og)tMl5%WXfbY ztS`?yeH`n5E^vQnrmkY{dzGDi5Eidb8Q<2bTk3j!S3T2!=hH$SPY_Lf6s`VXHsb(7 z14`L2&?31tfKJP+>{R-)_2+yr-RX{Y#y!-Z7}=JP!ss7(&i=`3ZEoDS(U=!u7v3yqWjPCwCKlc;28Ubs zFd4Le0W5DgJJ@8Ejj;UQy{Hf7ht4rzq=amvfQb&)(C8-j$sw(+kPgoKxfNCmbQtpLIhWV$+7WJ& z;a34AiSM?qVkl>)r;A$lEYT`lXX|aeb9{}ByTnU;GG_X)K`?+T#b<;0Hfi#_Pk3Q} zSIzMHJze(aJ9u=sH|x%<;Jl>lh$yiyRmI!$W^@jp0;OEeC*RcX^WwaA<~gCFGtUWq zg@x0@CB9{cg9f>Z#sYT=9YG>TUdl|ZlJ;5BLv#yprpaspwXvdzkEu3E6umYc1abo5 z@6W1caV(yszu*_AXMgfxo!dWljF)PEQSPuPn3Hh0WG6@6yMCLpLkQFunz{4B{qE7= z<%6C7deyt{Adh5?EvJ3Ev*XI1QhQXZWuz-$R%=E0($`s0&8Lw)-g_j_X=y zYT&yBa;O6#1mcV#hpa;JU^|R|mMA%n1Sdui2UyJF5|^7y*978NjniGdiRc_JgGuUFUpb9BT@r1wA5h0V`~7ncWR>KB$eS+)`nE%B_Kc-IpDuf5;> zXRB<-l7GIMwM+jnRr(jN=XNzK5AeG@KvZUVaJ%=84pgH&7k_D$#btVb{$L5YZTlob z7R|l9Tb?&h9VNmRc3E;zAeJ&FgKZ&_P;_T@P#@yPGdbMDasb(t$_MI+8Lr-$K&u+p zUm?evm;0R6tt9zFc`XMuUR>&;7l@v$Hq${`D_E@5sWq_L8x3VxedU6%V_wkm!FHKr z-FN_jZUSqCJxWxdG=YqN;&^m@?U~(C#%gzjqf*Py7N%f?Y#g0t!FM!F=~L7<0aOqckAbPSY!_QiT~x)cuh zIRn{hHb9x?{_hYf?uYZGs}}LRW#8b<)D9ldup7m=aFM37wX@Z!z7%u;ImnM*tpK}c zP&SR!O$iVg`HnQ{p zu%8%=84haxBcTrw2=46X4*#n>$i$v}TOWN}bC~yk?cc#4$f)s|~!A!me|V*G4)iIY&S__GrXYdaQzGi>#R`ue&! zP5$+;_kPZT3DHW}ycfgw}J;{SAHd*VH^Ste$+L)nZxum^#y5|3)~tGDjTv zO}wzdxP9dD1Wzjac9E5DKxfkHW6T4v?D0c?=av6P-I==iOM=E5T^TC?@h9c{C6EI@ zcf6wn6&+K+nY^MwOj2$6zm^@vgs5l7J5gaMnd;fG{Kzajnpr4;uB|k@6orBSRY0o0 z(l?d}pYTed3L*!0iXlyKp-$Ky;wc0uqMFhR6l1L!Yw!9`tvP1UG+`!z*SqcDfSpET z0G)??e@g~N>-YsSsY${7mcSl=ACex51ZEr0bVr+VQ*$u3K^W=)z3?j!lG%M^3Ny>q z1VARP(V85x;7%N|7IZdn^#+9sH?sNS*g~1$^g*5ji4eQz0GGxa%;P-+!-jnBiYvd` ztM*Dx(|T~#gGK;vfnh}?NFEJ|GjWdwBTX2(f293V582&Bk#b;J<#58<8aHD>Rcw%N zh~+S?y2=#e%pdyTPJ@vmn1!$}3(r|54A2r-#Kqq>CJnV1(h5+Ni?%`Yo{LL)JDiV? z>oe15qHWmT&r@^{My4@G--Q!nt9*fzz_}b77*n(h_*j@Izd<@u?sr&07gzHjtqQn_ zeBDgaGS1qGByc7~)UhQu;v5(XW;||@%q`|zPcr3OK{$K^us;2Uq{^lV*6uEV8 z%dm#BiW@d6z=47d=wc60qhVCoB5vF7`pQYe{i1@f?k1|LO7I z>(9RC=9I6coMj60BP_|(T#P{5e=z+=D8X=FqSC_rbIkUhZGgS&1^3R!Gjz#MU^`Id z%JEh|69;0xdmrsm4nEw{k6k{u7)hzq5JKXg@7Sz?0Zj{ChJ`9Xk*j;(^4R?aZaR;) zxB&1xr($j^$RTy(l~Hz_958Nf_78q}*`0cu+ua5^aj&=ihu&}h@ah1Ge_RIiIx12l z4i+*oAvA1nEe9ChO;Kbzhot?{S$?Ubycx&=Ew)f>tClw(j^wk+!zt3l;M=2}eQc6* zel+~rJ6Q7_hm~atOldZ$1Qu9VH{No)?q0Lqt?x~@=c&tC28sschb+#{wt0N|adgY= z!BZVcf4Emz4VU+y_qM-!f7xCB_WAbn-fy43Ds{P-mJ)r7O-9cYfp8f?mUx{ z9YAlz>`Vubpiy}!cJ5=Urw6uV;5b=8{hAA0wmZ$H(H z|KJF*BdFvVJTAc))~$)_rUk6EZt+NW+w1Z^bCiVt+j_h1{kCSEf8u@ZYt|3z1`YK~ z-LV;6vT)e2WE6B9N$4Wxjb>#)p#8<*eOIn(u=4Pb_JPerzB6y-1~TH$q(`iov3Qol z@xd5L+wkFhFuun~L`ERWDe#Q@r2#b4FmR z!OIK7%QyNLWs^8te}XNs7f$j0T~sIhI_qtt(44?c8bfIFAH(w+r`~@pmLu}#eC^{- z8%#c2zA%%|2}Xk33^+)2;_5>2`QrcWHa17}|hQAR|UVx0jf^`cBJIM$^^qu`b}A<^YM&Jiv? zg)b-ZgVBM8`-DRXb$*P8;^9y5d1<2TU;PANr`!@I zB$`~BY+zEvf9n?PARa`9)V7=w2b=_KB8F>eSQg=b*%#bu&kA|zIb@2AfQDKcs42rb za$sx0zqG6ToYGQ_+TAzea(YxKjEx=LW#I(52)F$a9dru`XGu?#fq^t#%@AmxSfn5( z&h0R0&ri)|N?G7~WK(}wPbQ1+u-y`AVQ@~`m^Myke@Y&_NuRXwXEPELi$+DlmzN7U zZ^;=^%|AmEk3-_gOk=~P%geMXo~=~Oon4YsbY@LlkcOl28G}T!u(+Co*Q8;3AmU;} zWn0a`iJ{7Kyq$of$4nv*v!tAo@Ok1NT%F|U#6R3G>vk--D1k{!(>}?HQ^6*nCgRnd z{m(x8f6nQ~UiBX?`}z*r?B$m{67|wc37JD z-u_d)a;YZ7wJqCtY!llEbM4*)bSYsn(4mIi;Kz16nGs6?R|Rr~+Np*#Pq{{15L?0D zbYGUm(`xyO#(q;WQcd5=r$+2+tz2Hs*EXxiw=MSZ#%y8zqk3>H8$n+oVhO}yK~GNr ze?&XxUB9#Ud4F$@|D{3?)}!6^-P>TFOWteV*62d9fxT0>q(sDN^*>iymA_-3Fv$dk z>QqI%vHt$l%fD+p0}lNgYX81P2a}h7;m_elLu$j# zADe5Q&^3mPQ*;}BkKxiDa3}YYb-D-Yio<)_Tr^8fD1X_~vN7ijF{eIMxDD2Gc4=WQ zgmB;a?|^DT)M`C=vTa0y9K>MCNcv#)=X+q*2^6WG_OIK>?W?Xx;m__re`2}UI}(~# zTef2;0Q*EcZF(W78F;rmj40|!1;HsPhe*I?WZHx?YLGp-j%IlcH{gry317^0R13)T z2{^<$c>k+76p*Vx7ssJIr~-#RR8OP`P!N1*9n^|of!AQr0>8fL~Tg40lo(ev%H)fAq9E+oG^y=Um%=6$aVmMDF$Ap~?M(5toACafkSv7Gr?z zTeHd5NAj|TvHUV#G#tUUxeJJX1;q2b;H38k?Ls=ofn{|FMIH~oU8P%8Uf2Ml=6e5vD7_;&? z1m+ZF)CU@B_|0-4X&^CNJ4K!{StxUHg!!>HwP0;e?EKtx)J%5~tKL2nrG0S9rV#cV zb#(N+>}*eW-}8BVb&AjGcITCCGJ?UU(`mhkcBu0bYX`%3sLX_lA}GK?TS!Uz;Ym*X zl{UJX12a$NPC0^Ye+Ck=clil$&p_GXMEv;X4EXqItBF(xA?}ts9X-`h^h)f_czb4n zGc{6LSjD_v#utmVYV3Y{qcvNifnd$T@33Z#l-?{~uL|KvvDB(L*IXAD+$G+m^cZe} zL=%sGg3M_%oDeUm-}!NYyK^<-o`=q(57o1JuwOmLE3vb}O>k(=^H6_>-8BlHQr3agG(Wt;0Gpi#B^q53wPEeHpS!3N*I zBFz;%8{0Ao0Y8dXaWQSVG!hvyrqsk(38QOgp(eRL=jIX~yOK=oC$qU|rZ{$o#PC7# zqQ=%T4$0xeN>3(DEAOJoDafw9gE6tHm-Uu)lAZsse_%9=XsSSTlp8>DCDbQ=)Wt}N z4Q-eufjdBRr2*5eeg5w)Ju;ErAE5xyl@6Dje3A`bong8}zVi7(9O5s`#&LouZhYP} zL4Gka6)!%uv&&^m+P}OQ5w#2uDX3?j4M(JW3zKLchAc{oQyx}%P#bE?Zyalk#PAaZ zQfLSs0j4Z?%f<_a`&IIOzI^r)A|^M z4V>$%`(KrOWY**6p$Yy5{!iTi|I#9>hc*yaj)SlO>y!A^xoI=Io$jMRKQ-NM3H8ur zGVHPUkMMDAt(@qA)9s9%@(olWGOf*>Xn6-eYqD-!Rgm3qfT=7yrUI40DTqKLwWYrHDMn%v=p#xK2%W>cZs$Kw?gT zq+)I1_ZJN$wT%$&epxmdU0x#Ma|3Cbn}4N)-O<~tI!V{-0oBxOOK9s_L9y{qi_X^@ zD%zULApJ+ex7I^KYULFbxTT{`Sp~7$e_X@J!iE}_&Z=Xe(52s(sK)=Xb3ug{v|tv*2S0^FUFN7!nF`AHwS$5; zd&naF-&*iNtKbLmhQ8N4YV!Bi*H`rQqHix@bgE}bXY{D=Z%T~A-v?;wj1fS`f0%!s z0UNTD~e{4Us0X{YG6xPt&`wb|AE(I?E6ldl_Bnaj*Hl0w0v0~Y9Fe{LHG5K6hp z_749A!K1TQ$Y8jZn!$-OCO}QMGLc5mPGc)&!)e@lbk3J8`d@|DMmEN{19n6c=96e<`(+{eCi-jGK9U$c@_#z7ilh5+pahK_(pOuo9=cce?$3 zx;*kfA%qM!9N3V$Kufyo1Bc+?Ir33#ny0|@mB4h=#h(JJvRNcKU8Y1CNi&d(K0UKP z+&P&uW_vIWm#t4`ieuj7gr|&$l*O*JcQUv?B-At^TAHcdykPaBf8(g0ETbuU5?~?( zc1Ak}8K*=mZl+u_7BFL82nm4PLTJqV^~=DFI3j)S9px@ zaUk-*q7Jj|H;-Yq*Oqh#0+qEq7dL@pcz^;y^8?q1e%|`&8f;F;= ze^f9H7^)DO5e1I0_12y@8+rB{Nlh7{`q&mzA@);389+7H_yeOG@pvwfi46q@o-&CaLjMjEeTVs|APA3_5MA47rt+) z9d|D)9~Udnf5etP%y)I*N5DOH`S-AKG}uGke#u!ca@LDORE>LVkKdHrBU-M*epo|m zQE|o*0T(Xqn8&`ZIVVqRzuqhjs^kB!<8qi$Xb~SeA-6RwL`xp@h@*Grc6pK#;$H(o zm|sJb1n`gNJ&1gp9HW%C>)SR^6#r-zPa9sGm||~~f8`8qnB1homts)b{zY|WZetjvB_r(?30*xLs?30eKR*ca&tmtF3r%M08Uxo7-2;eYTKd;1a{a;Ydt8)mS|Vb-A3aTw`vTF ziY0}|6&t5*9{cdQvHctI1(~}N=T{!>_LYwI$SiwpUG4IbuY;AhW!wB;s|XHeyfhc4 z9UC*=(mw)Y*{Wl!m6WI9j0h(RT!hh4n_}Dxe`uV1$^m_4Krs*m1lB8hMpzxIEH9ug z+TKgv*896xu*4U8A@K#xW@aZd#6s&ZZmW>ce1#ECnJ~b)HX-+m&cb%sR8z6}y`I0M zX6O*=wy`Ztzrcg2OO1Y!#ObtMe;gb?gkCMcX6y=Qc_bl`p81$VonzZC`Pfb~&CPHK ze+CvfB55Qv!cixv5k>PhfFQXK?j>>{Xr-kI{Ft`i-}iW7Z`*vBbAcB{ffp)JHN;%G z7xzJHQ~4}1(F=P;9t!Nae+zp~BVC_KoJZQu<*rSMap&hg;#SHj>#L&ygtdW2L7$84=+>WB0~oA9W9lsEW4_RTB|ye`m|AxA|8Zv8|v+&Gcd10UrS>))@gKHgz9O z8#Zvm2B%qZfUPm5UgB+5XL4*y`Vf-L9qWVV!J;Wl)vT`y=&W?G~=^<4L`S z?V(njg|r+SgU&fRF#Nr`Tp#oTUPeIF8dxnQ5Fhdf0ApDR=~2R zMU}r(Mbos5d8h-ga=r{z{%$!4G6;?P3LqxA_FjFj0GuGjMx>lyISYOtWLc|OQ^R*I@+M}EDrHA?MDnKqIsYuFzwhOw&XitoVkMRN=u7Qb< zoF$^AD|~Vfy~>TeUf$b~e~K^@vd~66i4CP9+y1qJau;QDx}qV)uZ_%W%={96EL~*G z9mbqz3umEL$DBdXvSa!T*b94yMPojrqdi^DPjj@`;%kKlM3-AjJY>~cHMiGd7t%Cp zjaG=m_R$bl1{S?Fv;f$0Y)V=kTwKHU=t62aJg;1i&$eo7c$U&0e_d`LOlz&KmgZN0 z-6lkNl0=MP*yeO-QJlGfl z@9?YR2(xKPCa6+Ae^lR;+KrYzBgP_e(Jrb9ux)axMa2TZ32KQ6I9zHdTI!0Z$e{Qm zi|cI@QpP35 z4s%QVdz4v&ARDK^nPm%}CJ__j7z4yTRP=Wt5~(=T|0%&&e>lKsiaE=M8xc}3?_Apm zvGztli94Ier_3Jf-9sy(RdjC4R?*RpbFTqIBx<^1I3DUzwv<{XJDNusE`d89Cq?&> z0<~5ElVb^yWFESm*_F)3-8w(5BDlkSoT9h1xR?=!27$Wk+>pqH=Imy`w2b(kU&kAV zLFSy~cL?`m5Sv0R3UPa~V2iG3_#)h9;2?CudemJ=0cB)`C<^r*W zZS{9)tHASxueXX08&1B}VV+yW-jz&G1ZQU9#(_;Ce_}&Ol%=)$aBPD(05>8JtGU_C_@Qd=wY6ocYDZFAMCKtPgy)2{FLa1Kq*XBx zX_Kt1R${k<=+BTmY2DhG8h7nI(SGPDQx3MB@v9*UP}q;Om+W4t0ax@>+eqdr>UwS> zMq<+we?85Bkn9nV-&&D!%ouG;4MZ@Ht}L^nEV2p_^KKz^pXfCZTPgNyP?G=$H#LjY zeTw@-ZYnr3v@s&myMO?}Cqk#-U&>QZhEhL%q}X_}yqNSmUo6Kw`B`Ccv+qO!N-LTo z<_Nl~ABiPhsls#f{71XgQIn>ISi}H%c6ZpXf1Yyx+hRBb1KLy(ZBECY9=J z8(c+nB@bJ+v#jOVlQH^{%v}Wlij4Q|GvGs&?~5P;`?Z z`k`Xh>L8c9!c;NR6Oek>Got=@^D@GUFiByaOAs!hcRjS{_O!CLm*!Cyhu=O7C+b(8 zgqU$jDr)&cfRBgL%}?Y~+3;r{e{aI@IFSNjI2DX|2@TX^TaK8=I3te~WDYw5SRa8>jV@awH*u6MCZfe_WG8LPCS~ ziUoWVf(V}u?y!7w$0(dES02vlh^o~(oWhMkEpkwFKGyZo$50zy0SGq?bnL?4O^NLP zgl%Tqs@G?l+&PogOflLOMgt{Qe5!452klX+&IZEyAb-+GDD;~1ZixlTJ zVKAD5ih?m-mz4g#106UHe;1yzVrzy~Sp`+GE}OEvowVvL5C^U+bOlR35nFDK25GG~ z)C|=EX?>FF)y&AGY~+vZ{7X|n283A3|x*BMLUgoGF!w04EiTr9Wmb`I~t zTG%^2uo?VP>-;iG6PP7JcaX@V7~(-fzJ7{ES*82Dr!Lk+dE#E$f1@i=wBWDwibX^} zC}H3$$~(i{2lW&6F2mX~6d4F5*tnpD;s`K1u0^iRsr+T1kwOu&8M( z%P=Qcv8ZND1#GrX>e_)3>?R8S4#dzb7oxual`~*PlGvyMX9kjAn>uxnK2-Q4z$`x-4O$HOZ zRt*Q(XmCti>}d@rW2~s29V-iqC{zH0z<|$vfZh~TT62;> zZ9S}ZP?D!8Vfe9@SQy(Tx{tv@7!*H7Pb2x#>1l|ee`6Ep?4rC!utgK6hi+aY)lKZc zX1C3b2so=}+xlEx*0EhM$SNmC-NM@~K02buW&~W}mEv3ZFWY&~jtAR|m=c*S5+^(9 zc0QNZS2)&k4Kl|K6K7*GRwCR$A*2bc58bx;54aC>BIv1z2b%C%ei(W%ASf}oCG(~b z6znO1fAvai0L36U>x=Ci!|aA z&&Cs5cY)mQ(_|6Vc-OLtCC`%(dCw5J#+n7pw8Y~eLVZo6;`os+=6d*84_5xlg zy856Uhr#;U(wb^v7sRN?ItKpIf6pWMlJ>Hh)$dcuO$HD&ppX#^2gK5_+1!+Uz(tcy zil|Sc7mmU$YVoTipg5_V1hn_OPyds5J*=(5>-ardo^mn`7jIcJyVi28>Z`_acj*nQ zVAW?Cw5DOlhjke82sUb@Hl>Ors64oO*SVSCki~8-mfZJhYx;Nq+d^*!f5F<8#C0KT z$uPLEDK$dLM!aj3bXnSRF2)3DwirpV&(ax1%waM694k|~lL|cAV@DgcsIW z=Hl{#T6J!-HT*vQshK`pe-r{oBCCl37Bd`%U=O`0)DU`#*&JG+d#C!PS1e6^Yan@O zuOB1uP;%u>WTMRST+@20v1+-6Kd!u_jJkSeRL$Q6whm%bTxbbTEE9@Y3|`Sp&Qs7; z^tOy>iA)?CfSt}CWkm&YinAC=%nTF~&Fv_N^?72jslcF*nF^+$e<)y`r%b5GFlWXp z?7mUZd+^Sn@UrIK!F;xyEs2Lj1fIg^AQ8kfL{M^sG2ErD2zAQ+A zF2ze{JV0ZiyyNR^e?eZS*XM4dwBsh3KF%e+&!f)MhUqOMHTcuPk{W0d#5c_K;cqBI zM!U?41|S`)rlEs53f9XaK6-thjcSHYmg~3aH+QoVZPGm0+T!ZXguw$VXvp9ON=sKD zo>}yO3~ew(`Fz~9;J7{u@d>3d$+Gg2ogK>5Rz4lQK5MI3Z7vMM)G;2@K&Hxdb?RD6vSUGRcNRY> zehXp1TB=*O4YfDI(QW!%FD5H?q0`0hWb;56Tu|@=?pyV{fk4>&UME#5yt44=7E@wE zHCUB42N%%Gf6a2{Nhbk+;y^uD#cbI2}*@S0Ue+KNH*+A-(h7f#% z8K~&V+wG^5gIU40*vi&uBsXm8NIoGPH{EtWQlM?tlu&i zn~W+l7q+L&Q5@k~pGSgMu_53kf?t#LOaYoaWo^$!f4E1pw`Nq2hbO3TGn;O$5Q+$5t?=YnCcp`LIv990Wa##$0Qc ztN!4DUc3(;WJejU5v6JGRa&~O0E85 zI3PS3JzxoZPdHtMC@XX>wsDGSqO^{u;VNvy3i-qVn-pEz+bMyQRsNT<+_ut9QcYVk z?Y4|kdDTjiocA}cM6rKajl%SxD;wHihyJP-J~lEl;6f7y6}hG3aB|t%r+4J+wGUK1 zf9B=*a5mxDF9}Z$qUd4hi1CtAv$wAVLANLfYK}R{Py`_8Q>1IvwUbOUyZPzISXlY%xFg;uSHJViFd9OzH^=DqBkgT zU~gUbd6)an4sKvm<2PglAc$MokNOy3jM-p1e{{6) zouguUDuZ}G{;ryB=HQ-m95WNTuY$%&Cx~cPs6vp6+9R(}u;0;BHiJ>!^QJ70dx}fL zi?VT+X@@v0eMafJL)d_fvEbHMh<~wHe56p8cnH_hECgeqoQ~29l>t?~DS-eo8E>(a@sG%}RSkL#9hrLA zIZ~V9s%yfIf~y2Y4tz^{D5lhbevBbbB1=*3VgD})bSDYqIi{}Ir-I)}e{n#kCP}Eu zA+)2}R9$h}so#B$OHIOvTB-S;o&JNvwz%Kqo2 z?P`29-W@KNxFEAT1sGl$P@F<;CUsYH(Dm#SU4lop&`~M^7LuU+p9=Fn zQWbrLU%cb(cSa>XHaL~c#w+B;0y2@gS9LxB;8bt&&-eM~ z%w9Fsz0?$&$R8FrD31tEG<;R@oLS^Uv{A%cW|th+)aXz)!`g8fS-25gq_kIokGV4- zKs+a=^k!+PkfeO6e{$YVs-e|MfIrA34tWN>m)GVZtj!4D@$G}eL5_PLrd?h;H(J&@ zx+hh51v6?20DK%Y+>xI!7H$crN38|Y;-#s~(DBThcl|dqM}*%O`t1wbKh^IAPmTn6 zJ}r4h>Zm>yDbG|}`qT8j@jvy%a3eo0dt&(0-xk&`+VFa;e_HWz5QsMtM5%&{V`I}q z3e-2=LCxzeUy~pAEcVT)GNxy$2ywoGmWI}u(ZY$g>%$d zp@IeQgthZFPb6@>tXya!h_5*z*lT5{w zAFeFQx1v_3r;t@)dl29hPk0D9EKr(qv(>``)0PHyFg-juf9UZ72xmmUwqR-}@P~uPI;5o= zV#_Xw5(tSNhXvz&Kh@O-&Kd#_S8plnC)P89%|30WKT(_qB;On9=I|5sjnw3A58@sq z+V?zz;As6`vRSV>*?f{zSJi2CR$W#X)tl;l^|pF!fal#vTVJ!^9$f9zN+wG*a@df0;ngxR3Tvx#wh9Gjuvi5e%#@rI=h8pej{#Tca06{p1$43Vh+ znIq*&V?JlhC3WApLN@ueMY?i`0{?SGgmiKu$O;JOKHao>>0o)q`OCVrm<3}AE;XDB zhI17`c5!gQ%Xe?qPd?p}*pYrQoO!LrD&F}Ve-Ex+UP!T1;945N_62Wtg+(+GtU#uh zqh#+B6LK43y(tju(!uaXh!wHSJ`R}X=Cv9sLNZakF6koZ3{Fkc!m0T>@GrbdiV<{P zyW-%HKEq;r3Wp_^5)%>2yDw73H8fo!EX5|-Sm}3?mWV?gdz4U%>{4`THS!2PLPpA3 ze^DXP-nDtKMk*w(xa<^FaoT9{6cpc+SZq*UL-ci*eb9wba5FBVG5JkEp%95HMAeP_ zEHZ#A?r@~Z_Q+ zw(*K=4;FPTy&?Tz!woL!&K8U$S+0!be=7#+_A5QnQuimaIKeHAxST1p=?|SFI#+z< zaXr-t`d7TD4br7IwQwa_$cT+w4f#_MvAYt0I;smp1DZuQsV)!IBW**Dlz`z{`rct+BV_ z-lD5DlnQ;J z#w-~C$<&VW?{zU7NBw=g=9Io#e~w_RHuF3p^Q!q#vieF!a{uzZy#~-c#k|SO{mr6< z?Po9C0hk4j3Y?J_LwUlwm1E__H$v-~q_ryErYjfo4OcGgBFUPPzzi{5(&)~PI#k zOPk=5`|7+T05QfmtAMJ;yP+vSMX_38RVIvPhSY@!S!qsKt>8RlaIsD+i`;Av6&MPX zNGPS4q>Yx6y^%1Z<5{kYT)Y~_)6y~!Oe1M0mTsFuZo(N9RTslcRQrj17uH7CVWGM- zcbDMRWuix*aZO-r=3x~Sp4}?w>h~} z?6eF+o4KwS3V2O3|3G9c$%o{JRLhlx49Jz0(ddjJ(@EAUq@2ap65o9<8KlAEFdYU_ zYQ4StLfsv8b8Yo*e>9h`?t8g%gsXDlRvq|Zlj&$#{&;qa|HNuLHfRwRbXnmJU7F_e z(4x__m>WqKr@!?h^lcB>PX9P=f7xq;4~9Ap9h9pV(cUh~teqZ_5#i(r2SzvsIV8A) zgfJ9CaFn>oLtoFrJ6^rt?*ASy^TG+s9UlUk1|wd;)5Fn0e{V@g<~5jz%2ITO7Z~M; zM>PVf2*0MmWG1gbGky) z^)N!SyxoPDe~jv7MT9^BsVmk2gEDm}4}kX8EZ1J-E<{HOGhh7otN*FUSp) zS2Q^}*FoZN5ayyxN||ywChp-pv>4Z}BwaBuu^nl_aWAF?OewnxqX{`;SquOHi#C;X zI!H-2y?j45+yhHS9XEZi@Xo>D4YU$o+94P&hH(gQe@xfejZ2D$1&Ic764=8*@Yxen zT%MtZ?mizUczdp?k8%r6!A4ddaH&|zr&02iK0G_cOriKiud z2o_k+zj248fgD)8h>f8k;8a!x5R*KjxgZE5knqGf!}~)+3Y2LR2VGA=Og5#PjmFQ5 zkNJY3e+1IKD3H;Z-zZgx6R&towmXt97_2sKtuqV&s9*;p4r5P-4J>*1*u5`?L;G-e z(3=g2XQI<6!0ZHy!}*Cz`GzhKx~>-IqF*p+X{#3dsCQtU-_~SeSww{Ls@=jNMS2}M z4Ybtubh%M5P_)E5_w>U226^=xugk|yK37nr0 z41MCcDn|Nij7s3tRigG1rxPOEO1?uyyhD@(=XZ1-i|YafD=vrz7pa`Otyj3VyYroC zfA3zyJ<&9$<4`eAD$PmkXc)hbrR`7!-_3nZw#R(UnJJ(tj(ZH#Q8v5|qY+6Lz(I@t zv#qZ#STs##&_tf9?e7*C6w^XQXfkCRsk3G4{%H|RI+%i}`e_b%oBi?1&Ynci7E#Sl zb~8k7=b&-FJ`gO}T-p;2%y4u-vgj3&e;1C4sdE|$lea6Vc{u~PJ_?>R0CN#22*c(f z#I9Cd$x6jR_QCaGJ(d^7iFqH$QpJHt+&z6h#xBfnq@jh8W*u_Fwt&h#DZ6YgzdJ|( zOd==POt-~nsG-pMa7#OcNfT*CS7N?&`VGHf3YM5=A0&1>EZ&ZliJ4_uF5*>If69b* zhz1xO$82?Y!Qt0k3gsphCF~TsDtf3PZKuUp1#ei4C_yoOTh#cS^}?;>wn}$HXb})3 zw48Ux!b0iz0Enq@JMx5hxxU7&t&r!jq0h~D<&4+usGZu+*lj51^Nm}<7NMO~y487b zps)ZN!5$mRy+p{zoE+)BkZ)Jvf1eA-B}j$K@#J1?A%fCiBirc9kU36K95ElrPGfeB zgfICirQPFxN>g?Z(HHD$#i0eEU3U<~^M(zzGY)QirXSZ~iwL*EUt&HA5#HLzFfEv% z#i0O)X(L@LfF@VjBk%uk#pfkKr|^-uwM-{E;Vj_*?rzVvJD2YcLQqQre~&D-?_R!> z)gdX;k~lTanv=v!-v*UfAOS|uwz;*OBu4*D|HhFG?o<35S2Q>>5tvrR6k-vpqN=4J zyoD7$`kVPYiZV`|ac8gzuC%y;uAy9QUm__9np84FqluA{xl{?0*{goHcQ}tx|FN>oE_EwYm{po>v1)N=lz~NrQxU|X z_?aEpV#)*-zZ4r*_jYTcY56*!cC^@yW!b$P`i-w=Mvb z{kF%?0<*IUuGqD}f2Bv#3dyBh+B~o*_EnAHjU16y+>F2)lPCo2jYOT@YB%D;U$Og6 zg>NaXBOsQIn}flvsURI@C;?2YvYDDvN!MU)JwUwKx*seEIW#}m@y)2gWcecd5^`jM(){DqK`oTE?w@NWL%Vfg_CEIjMs8{FINs20@I!viWUWZ{@r_#6Tg--M z#;SCOYi(j~vv@*K(Ab3e0dFRfn|3zqns<-gXe?vyreJweQ5X{b`At2a40#kyT%6r4 ze>9BJ`ct5uoO-X~6X~z(uop)1`f@2@=f78$JbqOY=_vhwExIpgjOgN$z z;AcJ1YtMX3$K}Cg=yp=$4!pfjk|eePuif3Nw>!VZXb=qON@*;G&eAZy1Aq&HxY7Uc z<@_i}eNQDA@*X%bb(`sP`1EDx)!=ht7o*VJJeL+^oeUI@=|sL0c+Hl}*#+l8dCs;f zi@!%`e<>Da#Ss*?`(JPbrPeD~w5&UVo|d-3>!{Ro#xVx!32bCA(%H5NNz?GuV=FSB z=f$MQu?$8m#vWVNJVL>R%YZ!m$c1{3HCxXkpX%SJe7x{l5XLgOG^u-f5*r|{A-11q z#8(zAD3l^>*;7^RJSkkLH(wTc&9rHSy0sPafAunxad3Z-8-lqTbev&c8#b|{J3F2l zVoY*NTIxVgD`!#`4QzENyJ2=#m=#*q`Q}t8y?M%PAFMVXdpX?46>GPFa!c)0h)4@T z^OW&C$jgB#f`Uc0Rq~9|Vg8)c%|dkn6dQpLL!R)hXCT^(9D)YTsw2mQ#e(|X&hqt@!SB#qbv99w;Hxg-!~dur*03*qO=c-Q8p z!k;|3W;){DCd@}$)34}=d0Fz*VFkGvXt15vBcg41Xs$SZuM%&Y`#O2r_wJz-TeZ*J zZ0^qMmO0v2!Cn|?QcyQ5u&k&uJQKXpfAJOZ0C`Mz{5NnW4*?#^KOS5yXZ9cm?@g`u zk`>(+;X1x$o(GbdVBg~|PXr}=sPj1cz@UMIy}r)xa?EQ~+Kt{S)ydS~*+=IcIDMLG zIb^$SAd^4t*xEG3HLtW9S|ids7_(Dri{(|xgAW@mHyK6OL{oiIxj^{@mhD^ue`n3> zV-NqGTy_Xe0E2|DykSIYEV}zAuoSJeOD3XVOhrp9_k#7=9e|%UoV@ur4@JvV#y8FVWYxHXv^~u@ z!`dQTL^)%MqWjMNk;ik>Qv*|7e_qfPU)To)OsyJs;xriNz9J|WnSB3}H+J zAw_*iQSts;t+@khY%>g_XmRb%qjtIU6w#Zl2v+JkOa)LuiRn<^{YNtf61DzHn1Xr` zOELN+^%VW@diU#n}>^{03xQc?WkYgDMgDJ5Z^%?qI^V&P_yCGP-ua? z-?RuTbt_&bEaUDl3C^StSMGXYK5s6H|02NkQ| zbl6sOHCR45G!s`Y)yVz$f3mhB?B`6c?Snw_HiJezP7lkZ4d{app@m|Xanwa~VLrL3 zNNRIRdhj%?NE&iHMpLJ;{n)LbxLPUqt$D6-JUKgJqvKy5PN3F?kg-D@mcq%$teg`P z5zkhUhr=n@h5(53NAZ%mY~#cxmp4Sv;ss-8Y}#-^o39mfs}w%Ke}EWtt1QJ!Tf_HX zad$ZI=EL{jt_9&zkfo2MI^UsPXrbeAO^ecn<6DMSi5D)f#DKCcdBx{58B@E%=xVe+ z6esBh#3B<28d`_R?fP}K7+K8gtaTD#S~L?Pk$GFroS4GOE`z-uE}GGfm}xFjohzl1#35&PVWqSp7%*nncS%wiHfU>OwK&r# zRAN&KC1jHmqUDC$upz*=<)>kl9j!L+D;kWS!vz9-qp5LxaD}YEzCc6s`H(kRMu)Ji zC!bWwcS?f+y;K7#=}T7>kxuo=w)}f50d9{6OI&Zh3J$S~g$D zXuxXOij-}NE4y6izhp;hy8srwl~U(sm-G$GyTNfimfC1zYYkC8@iwsd$^-AeYuMa1 zpU*Z!&%I99_WxT72;JRMyjmt=iqR9wr_Hq77}+}#Oo z!GpWIJA~j4!3KACcMUFK2<{r(g9azK27-MY&Ux?6x!){)bk*~8RdrX-hQ+GdJ-c4Y zbMH&%-IRi5zg!~+ZziK^68`SnUMFid2Z5KlDKv(v1-^q<{0bfyGRoXcXMn&neV(T0 zuQ^{h#d(PhhbF1|Yzd|{Y#1%yW!@P~Xztzp{lvWcGH(c|j+nqN-ggv6Al}*9mzR7x z0^i@vhps)vrOPOc2vVG|dvL&3YgGd~iV`5hW$RcLYG;1!+l56!>ThulH}BkYtcrrtVK)M=`o!o&Wxo?eci9K@{laP1vbk9qRF>5q9%wluJ3I)Km9(q@j{}4 z{fsBFw5rI5?Ff(Zdv_z=aYZF~|E5J4?lIg71`CnZMu&)s8`MbApZ8TOn?jGjR?sm0 z!?oMXpN=}PD>X?oRERjiycG1o7e#CtOFA#_NEHl%W7i_h!teq;&d3jkz3>@ z>!O6jjHF9d4yvUsW4D?U^;7yc0p7_`&F1s!8{Fl_(IrkJ->d0$D%P-XLH#5<{A zTb~w?Lo&- z;O>l}5QHHZR9lSJFSpe>)?ne-C~R;{B_PoieOZ@@h^2*9T(3|8%hd_uh%B~>|IkD5Ifq7NI7;}Kn1IC{~6DaI*A?@YsP54EJZ<$ z8qfQH51&6odz@cWJzf`YC!MJWemzr)8EoFAbqfZ84l+DDd+iT?z+u@`*5)mr9z9C_ z)e0Rilk5`TD!O^fFJQ=Z+!FUo2LqJ4;6VOh*3ht z+f`c7xe5^yYMXMG}_+#>g}gd7Te+0V04_EW_YMtP$B z_|G;TcZVahYUYJpoYz=O5o#-B=-LlRoUKxhzalR`A*G;+|Nf$>B5-4WRvPV2uX4XT zN!EcBHJ2cPP2p`4zXE~JhRo*JSm)qX%Q^=O(wFG?MQtjmlsnQ;?;KO#Vw;2tD@0Jt zg#`Doe+ehAnQ_ouru)k?sz&UD6NZ{2Rv-7J`$HpKR@irB&|IhoszHgANhVFQcE3-1 zN2H1@Xq!Qd8t{EyLtK3gq^q4R@z`mod;Gx)qAR17@*swz&-z)dk+X9|o~fvUz&Q{b zq=ykpRBgz4*Np!_q2r*ej1gJUgw4vDESx$t#vmY5R;9l0`fKt~0yU{G(_RgZ#&uMt zm!|qOrn)eN0Ej)eEZ@0QT^z_jqJr(O(FS#H{2U}>F!$YaEd__E)WyUEc+D1P5 zCd-f2*~8@r+X$y%r*BPoGb~)66Oyg#D7bDb(g`h)ICKq^~WO9*@_a<#ZEb=cBVByyZyt!}p?$<0p{D%sNN{cxq(y|vf6 zRLq-qNeWyHDciZj!hCWP(VDqR3_+YlyqbAn3mYtHn9hVPa3OkmLyw(0ZOz+?Ql+`P z3cCrb!XDl^>f`C;<5l2fbIFgKn|W-?7VS^6RaEMh?{yji z-?I`^BWxHXx0f0^rH>bx^j;C?^tfSYuV;j+7E61+(m6p@9#iBcqw)#W78 zq?1K{dB+K%s^W-)8A~Ce{iCkWC(yIp$wRIn`?0J*p8#rsG(-$eCHQhr#tH;iUT|M? zfRcD0!??q}E0!`Dj{RO#T3{1u7qwkBkEb}EFZbZ{`IVNA#J&S8*QS0&BmZS(-Lt^Y zCyu_N(^N|qa)ex0{E%(;rmb5QWdZlJ#ffLfOCfw6BVpjJ#;`EYxD>L<6zw7S7-9ff z+}G7QeO=@FV~MX15khOgU>s-#F7diP_u`w%PkOm2TeOa~{7kHjS$f>kR0jA@s`$su zB_(?}mlbuHwd|ZGyHWFMngbiowyBDiaS4}Nc4(3+!ybCxE(78Rc@r*Q@4VW4O zn_}|RVEkB>C6!VlBFa|A2i28xO-SK0det$6^~#VEp9`muJRe1RubI`XIY#>p64QO& zRUNPwvdw5dqkaFj)x!HkN%`^^j1RLAj1e7(wwseun+)YM+=7 zGrm79ARto7AphrPpEyncKj@`ziR;Z{7Q`Lu0+Q|wsZ)Ym?uxAt*hN3YmEgPGy_{^L zcp5o!Lem!Z{i&CGlG&1cy<69Jh;UO`UgGt3uV4F@Gq94OE;8?#svd>4kLj^2uRUCN zT8h>K9m5Suqvz`8d55DlJEE?z9!+G93y4*-r%k+lt3@j3Jk@^hdV?aA%n5$#@QhmM>bYky@J?U_ZnlaiAq+J4JwCp6VC)8%JH>Wkm4UC1cYQ1xeiNrA(=C@GotQhaV_)fb zc}iQZjdHIt#@SCbFA3t}dAQd$e3qH0uC89!d%k(QJ}?R;psORq2lYX1fL0kge5$d? zT{09lx`l4OMCLk;iJ7UR2)0D~HZDn4F&5?7;u|ifr}3khwr9t5+0@&yG~-;!(EjJA zM8r@RM8-(xX$owqgA+IZ-RKN0f_%JkSWmP5&lO)b z-+kPyKsGRE9+Jzu%m+mCg^kp&1n&FV1fy;Au4uiLyL?Mj$u@^qE`5cq*OY1Uqfi<~ zNIeMKc2dJbd!rpBtIQkEp7HJbtQC6&qpvF3-uIu|`tHKZ&`!sK?X4(MWfv#L1?8y4 zyD6O2Wt@ZCIViqF2T%*m63hYR#IDkDLSmwR7p5Ei= zpMCn(A>PRmMoXYynmUa^OB>s$%ZY0f=qi8SCn`N8a&bTIIHXp>@liy-p zQJ*BRQ$V-h9J2S24(ZO$lrv#mU{TiO=1#S69J`lSM^UC)dkHTkwz;NQ`~D zNE4gC|3s`aZ{*ECH6{i4ZsYx9d?^rLN z=o3lPW93{ z`_;QaeN>couF=fqj$p5=SeZ>T@P>yAs>p5wp6pm|Ez$IWk^T!0HsBJyFFE27g3gH6 z$i$h%0srC_rGfwlbta&W4UguQyj4pMZ2Zgmugmx@SYSKJ$y8*`-wFC4g^gyVsFCQ?8yKk}4 zZ-RUH{ItDBM|;yFG{h8o%Nvop+<^r*ZvM`U`I5DLx7MwXE@3vxMwRbvCT-uwNYJWt zZGs$T5js&KCaTe$nnR`(zBO>RF^k8ltPj`f;@}{^xo~Z{yViIIDQ}++bG_@>_j#3; z#XjNwUByS#jk4qgiC{c_>v@|l*x#FIw)s5ZLJCJHii5NjY;-gGGSuQn`BK=@3B1&q zi6X>P=fWC{G`M^6HvdFA^L>AhnDe&r zpy9S%dOUE_R(j7z#3%{#+1;o`JGdVWu?05CVXN0Z>1+*IiqK->33fypDAOdfCo&Vw z2$Om4zZC+{$MGa^WyA+rLE~))Y;_JnYTmD$aZo=P>Q~RU1pv8gN3}uaDmf z+W4)xyG9g+>z3>CE5%pa)Na!hn5-wze%3IJOJi?7I#n`!2QEehz^Bj1(%c0Ux8ubLQttv?3%{3H zYHHX-+6%W~<#4F^7J(|}oi?zd%$k9hdvuv}I8Ke5d&HWSTRa!%XT#F+`;-v^w)gxi zH|!B($&g#rZe&QO?2_@0@H_cmsN1@bEw=R*Kltqx8`=}MPstnxOry;hN%H2=g5~+a zvM6V3Wk-8`FSjmmi*y=pZX5+EzA+l4DFT{Cvr}Tje)s)4w6kMMwfFMco@0UPu4;G< zl))}SGRrhY%=q9eFxi(L;U|*6eyI-ypMi^EAs~9N{`a~*F984pNW)f<@ZFmyZ>a}Z zN!~iI;UCBcrFO0&Vf5<7Wzv~f7)rw)?e4N|%wO8O&~8tC2AE+ZBk+?kN>|g*Js$Wc zqG~)Itw6Eu7N?R`ioqKCR7kD9gR!@0_r<3RI(6B1iNp)?d%d8OVsqFdQ3~}B>cd>) z$mYs$rtdaGMmrd-K$JV%E$ZI*3h6l$aIC)2o2<;mh^` zvcm5!llv*DYh8Pt{7gP`1`>(ZPb)OH?@@vReV~j?c9R`85lJD@8FYa^qE^?pgc@*q zHEfOm)L3T)liv7z_|b$6qu6a&3U(KpHCb@&rOd6g6OosjySpJZ?ZjcLm*mP`2D38i zH*K`oXiqqZ;h-J#F7}C|Mg(Y?{1IP3{E{ zX@}W7{8kE*e63ma$2tA=^3y%C6ssfx`N1r-?5b|pZRdzPM6W4U>g&PkThNl!x|Gy( zV`0*W4y`kSXX8uEI4GE33U!H-C?u#wBfEj>C*fSe zH|@7}sL}O>Zd`AdVs8U^(Agt#$L?hB`?i%V?kXYDnw0~wRdyzKiff~@aAZ^N!V?)6 z2~vm=xAnP8WOy24v^;{+k(_h`4q==rEOC+sv#)%NTS%c0O|Xk* zvT9-_&GlmnSJ6&mPif|-41eRHj?o{z&2}v{GdpI|*Zo#QeIIG>;kUbt>KvU$0Bz6JOk<6?(4=IO#6%^2#M=V z9kN;9IKK&leP4lupp<>|_Ii(`Gw0^mkNQL}$L#{RhI4QV`MkXclas(djGG3B;ptvm zLJutP_MyM>wxRcBbV=f`a*Cd5Mge9$1y5wBqJL^vN^sX7v*-HE=jGMi{)h-O&V-Kt zJCz%xMWZZKYeC9+0sKZZuuY(4*)Xe1`!6CeWA#{)2U z8ezA_r~d$1g}8!DL_ee$g{CW*wsu{FX~*eH>tNNbmv+5q0&F#tjFf^g@2M0jaQV3F z$YP9Qj~`@`9Nm4DX*5o7_-m-n#{Img4xnqOq*um{lBrbS9w(3m?JnL-7ZLFB3%1jt zy;Y0mdf^YHsp@pNy#odWna3P$2}Mm&ZB}F!}>+Uk1$mjlMeSSO=*8?da9$`)z>CAL5sNfc&eEzoTM0 z1yuhr7w!@e3iwZu;MQwE=O2Dc?f_b^rofO-0QSElcp1@yje)?2zk!zs(Es-K3gjXI z5C1lxj18m*gWmvCU#EdzMCiW_yaIs)z?DCOj3fn~{MT=4F(vQ|g;c7)%;&8gq6M_~ z=rz(}D2HAjOD_GRQdJEQRWRl)IAdcy!9G+#B>&DE}0g3|->LSL(9mEX1EA~QMqi^R zHUmz+&H$fV0)PJj;MfBL{wt>DQN7F^aRI{qVgAtrDDqzl*xDE9`Ui>>2xR!LbFf(i zQ0Q;;^>j_f{9oc;MaU%J$G^j{O#_DgeX#UPf$zcZa)GXpuS7|roTu7;j(sXHIbL;9yMLG26SRv7!tGtje5x--#e1V|vrb1{Srs$e7$iQj zk|d9jS4cF}TYi4%ZK139ZFWVUYc8HWCRtP6nWGQymQYo7%RPC=SMb^@77)slg1b^D99{hFN)?K>0uZzJdFLw`FWGgVDOrf z0A~lpe^1svG9Z8V`5{SNafTPMFp=JRULCDSJ;j77(Gy?8vaQE`>cNl6k*9nB`gi}5 zhq5^iDsd7jF_h*(gifJ_!|ccA91i0+L;alUL!@XL!$;Fv!xLD;Z0MDob@jC=4L0;Qt1hq5&)?vlW6=QzNg_&UDlJ~Lp z+pcupXv&-|*QW$Y4j1fIZjZ@@FE%c-Q4Q;wbGPJ%T-xff?{{5e949xr1KVMq_va5; z@Jlc6+z*cIZ>g$ujg~FD$#W+f0)(cbKDZKbMYhENi8%~+$MzsTa2Qzhn>RFn95^JT zc_~7+aQ4BN;mxQ_3splm1ofHt;YpLG;q`$=1>Zy4Bp#mT@IM-+60$-OX@=*v-`$07 zPe`6$lH^duKH)vLfu2mA9%y}^P7V^m6Nxp?oWAF~*O%Kgr{eh0{L5%io5K&&~#nAAPx;uUu8{&F{$wuf3fqnC9l$A6=gn6{Q@jov) zD~rx~5UCbpztLFXO4s5cV96KBEKK2^1donZdP3&*vIR0RlEXXsq;O3xXu>X4{|a!FCiRqp0apsr@; zP{xD%M@35VF(_g_vDO?mB7BU~YBo>8Tq;!_7SG8x0LYBXF%N*OY6thez0}_sI}?*K z6x@0@4W(X|=GB+zObihvW#tGzOVMz+5tm8?L8N6xv6t9?mw_QK{~`WXM$7S?f#CSz z=~$_n*jWpN7^9IKuIIEeUjPw)B!%xg24}~P>JJs5;}Zn-X@~ynM5?YV^vK6HgPQsX zGX%VZi|Zh$4%aKt2#x%ak7I>2(TB-3*wn*!Fw}OgRhv=$RoQi2pKI9%m;8bWl$sQ% zqHA{2auVS{i0`$Yyj0{>1^8=R8io^)n!LP`KjdZmstjz4RA?SB$nMr5iH|o^&cvQ} zV>jmw$b6=2d|%f&u$;EN6;PKJ?#@ulXckawG)OdofCb$G>d=@J%e(bv7?O=RC#)|S zmYD1ERt^Z!y=4%ow$UEF6%|iHl|>JxLQ{aP)71lfX+nVUK%F0#$t#?>#Rba62r-n{ zXBEYcCZbm+@QUE#6#i`FkNgS$=&X~~e|72+KW{IHWnY2LJ@1Voo$c^j5jj00pGtwN zW}RuK?TxcPXeIoS+)A=I`#O}==5v_!BHyw)t?i>6H)ugDZ}WJ~QQxRul$&U>7D9>= zyFhWgb}URc72~#+o~=2X%K%bMR451|Ma_}ww^cpt7&)hEUzL&`i;OyDj)@fYZ3kjl z5=*c#;GkA_H53z4PTOh93?g^?)*hyci)0Ci-$&srRd%i?@4IBxXV>Vk9>L*$hDJj2M{euYtI0LT*xi9opM7?y&^mo(EZa9sa>e7b-(6D_~$rbZGD zN~%*Glx!MRBBsdAH)H-}NJz~s>p==R0x4Tezr!fFW(&}T6Vd$kDmzy{xfUV2E` z1zBW#V2^`&Mf@U=bn*Zz5sH3eDSykP(8EXc_P4X5upjjg8Q6Ugpm>4ziwD0l*Kj4n z8#tJeO@8WhC`fLUWnrr@s%~&?A2Dnled+KQ(B;`F(IELGhR8U;j^9mad)p2Vb#J}q{aRHML#dDZQ#;g&>{^uK|nd!Tb21B|LuAL z5tJmp;o;5};m}>uuXBk;$NErLGP8aE4(m#JS?ujFYW6xtryOY}`XTZz%A;~469zM4 zkaqPs7Sjyt1X^eSdq`?YS6GdBwvGQt$Xhu}6{7T;<~u^iw9gMn?1*qQR6oB^JH#CB zUb1Ck>rc3;M2%^!*dT4^*3MjD@Vdf~c}b$4g7%hZ_!P1WzJ&Phu5IIzozd&y4vnj; zq5y?cNiF87<%!PlXr$v|)Aw1#1Qxa1K&~RBg#`?n2Em8NpD~rIsG`kX5gM_Z&fw2l zh&cE@uxbS6F1y6nZB=dlKB03z6kwiF(p_ze@>3dFor%DBUv)97M|`$dk$mBjZiQJ% zGi5}JD~v%B)C#5dWmu0aipU`Q$CK@0TuKZzuPT_rxgGhi%v{p68ul`J;yCRJ4g}5g zl%IYg|Dn{yxlpgdc6YPJ=0P}fc&L`hmj6IS;H@(Cdw z^qOh6w<*UF+!8=1Pq}ao3^&l^Gl=?5zNxao8p*AO%$xh`lxHwa+u_QCh@KY;NwK4$ zikV}9kY7`+fI_WsN~cZ#hawP{vOU~>JF@UtW7U)8nvsu}dhBf@PbO)WiCK@Z`Q~1b zWek7j#f4M)YUsBB4|`ND#s_h>J`ij>?M#rxH-&F*u>yf@!5{ZWT@fVq-9VkShiIRvY#Bq+`EgxXJusVutGDDeRXrMKLT# zbiMPl5`>eIhK!Bed6A$Z#Xw@=acj}8PH4nVd?j15(*ZKixnmMC!xYkASb7!0@be~P zYZeb(Zb`^Y(1yF0diQItdmSEFewT`-B_(otN8Hafzir>d8|f6}OP9Bnx-$>S6UQFk zq=9^=K4lsd)is3DnxV_U&rzj0k4Ep9=`UNB$E}7(;2Y@Yd zLV+h4+Nv{GA_SltrTd=9A1W}p(}0n$Ep+@s5(n~7GW&(IMUxW{j0t-p*!+}Izs^ShCZKgv*1`9MmTw`HK>FZkMH@+ZpKUF zUeJ=}#TqQqrkieI0jjIP zEAFaXNcJ+xa`r_i1mx~iwv*g#x4$CcUs6d?Ld> z_c@;N* z{e{R2Rdl_g%Oi8NJ}HEe?ZdGac!#vzXR~DMWpaVw560{6g|!kP53L;-!8WCyPTBf; zq&8_?$b)hyzK)>nzPZLliZi0<2)+qffia9S|CLauiF%BE5%1<@dGm+6787@fF^DT6 z6B&c|p|Tqdmy-QbDfBfO+p(Eyw?@8V{t!=SR*<|DlzFPA$0w!O*(FL9d2wCc$whHA zlRyMIS!J#!bE5}_Dq~x6dQ=SLJBh9Au{h!r5D9ms@0eaI+6#d zn8c8WEenBeo9+vX+qk$PaxoMykn)`=zmns`?0jMoy*;uABTtnW#YY!C(QlcYeH^B% zU7+#x{L`Mj{AdclMFH61?DU zAM>-snX?xvmRp}1>pA1NHc=W@2^A3B@E8!U4IC2P0hsztF83dI)PA}V3RX*xSE@IX z(N36c?`$bXdN)Bxu9$plW2FMJkQ4x%?jnMm;fGW$&vdZK1a;?=-tte*O*_WB+iWA8 zNauhYQT-HWu#%h+iU4){lu&QPq-%b_FCRvYLQ#L$EKN7p{drQ(Zr-Pcp?H1?hc?1t zo(=K93oqs7MZ8y%Fd(z5g*pFyDImIcrygEAEGM}P#sCdJVabtj^rS7r28Bh*FML=b zm=kRf?OT33S-_6#x_E=u3F+8cMT1o<5;O^Dby-MuG#Oz>W;UidVIAE6YuOW!s{dXx zspq=~w4qFb!2&a>RPE;~c0UH5fZWA2ool@NJ;ky}w>Qtm@GlRlyzrCTo4C{e2Zj31cze3fv7xx%z>j6NBLs=xdR zBqh&&uP#G~3zK<9??(P*AtVU>lH}G9YNlOAw^p<05UY53nfcASbukgA)fQ(%@9oDq z;$RHtgpy+;Tk=g*GS$+{)V!G+rVxLG9HYwQAog=n0lL64HiH676hSTL{6;=p$ov|` zgj3R(?kGRnZ_zqD%bVheQz;{crDK36P}sWYlYbFA$@`Thiuj*w_d5_v9#{65>#Mh# zwhz{r0x4se3~W@R3g5NtT3b>snu5YyO&-p@w4n3Xm3aI=l610BcB zZ49zz**E{V?2wD2P!vohRRB#W2l`or2VH7kc6e)QG*kE?0#p`BS^Y0m?ws}K%!3gX zn$a=(9F{i@;TQDCX99E>xDlg3=CYIml>SRM!dZrDVamR-99SGdGJL(Zl)uAWHMQG4 zD&9GIaQ4N#CD!&8U?;fFjg8s#1ci7k`PsJ|i7qD^o;nO7JD$XHJS|i^B>%9( z_Q5`gC@BU>Cs3XZb0J0roc?kc*C?8B6h-*1Dl2c+&m6!w=)IM3C3_ElbqW>W6J-wx zfijUb66V_d=DW>CMHclO^{SrOlR4IP{MC%6W2W$;)S+nn8MO$ZQ}V~@T~Gr(P6M8< zo)VsgvljG^HQ`9F_LJR>cFnyiZv0?YO6j<4Co>*l(@T$cOa6qrE**E+F-ISho3d)D zmK^=_bZM9znIsmf(?hU@-GZT)_^&nhRs3u}^vf!hiJv8sQcZ=Xb;Yn5%gHY|u45|x z_#)OZ8ei;Q9$-u}>8{x#iVH%HS${iM5sIzb288*RGb_=DW=S03c*j_cY#Y4-pTY70 zAMka8VA?BgLTLs zjX1fXE(EnTH*??pJO_tnr3bW=HOk*Hj)}PRTxS)Y7b+iQzMfEg8DQ<2 zVx(AO-QBSCc5xxZz2pOp6L-7!;K<-fHShO3ceBcLsAJRTXK_;U5ty8*;VN9KMKDR= z{Z1pK>=#2(j}=nEHDGDAQS^V`+6|E8ASR*@P5*(!h8OTa7w4Gdpnw$Ccj)b#jIGqU zAs@6nz+Afl#oWJ$g?fYkWMgK6hYGZm3mC39NA$U296hfob<+XeW3iY;L$a5x*Y}-p zq_W2$=!O~IGW&&^%P>Q@FU=%ODKyDS zdF?Vu>wN_%!#%X12??X$5{`tt5i4VK$op^iC)iaw2S? zhG;LAiTvFB2@%%D+~#U@uHQ0{&Gf0U=G6o;pM~H_7}+(+poWwbT{0zKwn|HulJEtxU+*93~Fjx zl?tSoXpNy*3Sv;I?4rdV{IpSP5-tv=hCQa)r+2cN_q32#!j4x-P{pC}_)XX<8Zoah zw;ew$ixNZM2q<;!uV(PJ@P)c5Ak_UBy`OIF3)1h7ph2rr^XwQmENVWTSu3zoOcDwJ zimNb?l#gDMfo5H+0V6M$c_fe-S*On`bQ8YmoP<$Bq*6I_LM;(QMB!-#P0+j;EqhO-KMDu?yObpuwwj>n z=C2o?MhJvUYu(~ojYIL~kb^sKIw=%%D44!GySk5YcX01nqCVX_M&@JQlcw~dusT)b zzU{2z-_Dl*BruN;XOnbt-^#y0vEB$3@C9bVf2Jd@r>^+RlwI)j!E|Qv@l+Ll2voU% zKGITm>;_1-AQ_!+6Dw0`hehz*NW^p3#XO z>2^mL(M0#hj1p0Z|*jZn_d#|^F&WC-F{?8JQO`9>{%gY;R0(!?1t3iOf z213_{b@#Cc(TRmNky+Z^c5a2Be3DJ4wDp4ZSauW~C6WYuPoa7D0%heV96!D2S-qvo zqV;J4e4^X0Mky-DQRYq1$1d+Ozq1ns)rS%7RyVTK!AGS$#HE>ANMg=ZQ0?zi;K*!f z!{1SZP6sLpE^BPs9|5H@ok*!?W7IuRe$TZkb3EZyLS)6fo?y)Qd-3h;1@ClK5tt>h z+4)B#Sqmbp&X_t6O3VRBY1OcZ_|caJGtr&}25a{Cnzm1>jpcy${D{ry=;c<#ga+0f zJPFJ-J8akuCypS>%4--Ju6Ae3goCTDw}% zi|>irt5bUr91m^`pQhrwf1Wel~venb^q;M)7niDCzs30So#^s-Lo< zo$ED$#7rw+OTUTX$-BEuC|iQsjcG-~dyjRTpUxBHjpj+d*5UHBIjbONfVZ25mY_bv zWO}?4&wyYkw1J#>kIu{1kHK2Glyzb|3;MNQjI1YRS72V0)59vpOD25a)>O)m4X=&`7otR?qZy2(R4ZRtCrJ; zT#|G{{D}S@)02~%>@)U5Nm@=DRC|Bn@rkWgZ{4xBPo6CgeaMB3N`vnEch{TqfuKap zME5FBkctALsGz01CZ)C)&P;c`2>4S{P;fNFthQeiT1Sf-E=d8z6lUt-{t0IInO(30 zjJfu#i_`a@XFUdDtkL;o}7sTl)f?neiXPORY;dEokhAi67iIV zb?gRiB#MJhS1Wo(pKl%JuJ>nz%b~)kn!io)}N%f#`&w(uorbs#U1n{2fRLn-Xf7f zH&fUb^)sSy663_*miXw*vM(4#?b*trJQRQkL_22|7+%Toy`#VtRi(%(m6(a(2YKeJ4RZ^2*H_E}TT0N_dC0N}lL2uQ9gJ$|o?b@S$0Q+TmA7 zkf9Nu-CV}qdICo`O6p7T3a{2W7x^9lc{sSYLTd}tmr%_i3WOHWtjK$3dU*L}2izr)2NC5N0M)}{@9 zX}@vw<43}2u{m>XQR+i35M2=P-1m)eoW-MqF)Hcr8mP+|L}G6PVbm68G;uH{=+CVX zn1W|;nnTJmWrWh2`V0LTpQe{KoxCDDn;Cm78ySgztdWr9!$ukk4c;{pE&}s;j^kG> z_zn+_&Q;FFJx^kPxPH(Q{@7Zy?(pbM&De;qI*ms$b%QXq|4V`5Ec1yg?v_>%iqDQj z)=?|_5pN13@Kmi4^!xdVBp&O)PMb{b8_I|wq5szgIB-i9zCLj>==^t?YQ3qy+Morw z*R8Iyaka~E#bSDFW3tW<4wEc~+@$ZPQ8Id@!Vx(9XR%j#&MmV;$ye&{*eZ4Hn1W4= zPkv66Lj+;3KF}wN>A*>&>DHF5(&&#?>+c6J51unHSNP}p<^jn`h z2uYVvxg7_^(+U5IT1L+dih%o-NU}eLn3bLWv42Q;1D4_t3Ww9R?zq}UbO9hh=5t51 zTwgTVVKR8n0iQp})X0GiKXn|5Jk1_OBt;hb28}Z)mD8Tn&*9J_X_GCzNWR4(IR~1K z78>xaBzdwm6_l|noT{>QNNA5aRV5L=PU?)y6|IB6`-b*u%$AI)5UFh>Q<)gQwaLI^93DsHrJW=;q4B$0Y97r-60pBjx?0HX z{r)h#eOS4QWk(TBoL!tTLa+<2in}oEb&}IA zD6Zk{tn9}qIri#DBMP&_H{rj#AW2(^80Bd4&|x;+m2x(y69}Or&RXSIG(VWq0r7J7 z^}@+nu4s(6loWyHBa$mao}!OblX1vSH$ztV9G5zGKB6_OE~V|rEOs8IDtRP?gwr%w z7!EY)EFk|dD;#QwE|1jXrY-8C!777oZFU!`ie7o!6+bPF%pd+WYmkVu-ipOk2cOK_ z1v~7dNpGNxYp96f3Ojv-Ybc(xwsvlbk;XiHWTd?@hq`?I{K0n1v4PL;>vV5@X_v!k zd#d&je;xPxe0Gb|Si8ylu0i{4A4_zDmU!Y&SPPWqw`61YNrhjT*R7wJGj9ED!Duve z!dGxlH2aRdjyYDoVb4x``2DX(+~^qTOV=A1?Rb0rEL+)&f#Djb4%s6;wGn z`bn}6$I*hd?Vp>#vG^_#r)oZjWU%Jhy=`A*-|e)@y3-bUIOw2j80GLGL!QRg$rAHH z%^;*Typ*bU(w>N)cvcjqjz0kS_us06E8L&-c-ctJA`fE1GW)o;^}SxY%H8e0FOms> zU^bb9-Aw}bJFaKAPOMhPtDtljat&+UkA*YcBNL8Wjq5<~PV^z(gl^tE{TMBI&*1+7 zr>ztJT6C#x<9#u7d2=C5x{L^Zb_^LyT+)4aHz^-CjF~Wx_g2KW$X_*v!&vG1*$kr4 z{4l%012JnXssr>eUOel&$M{8j##vl%LE%b^pMkdD=MHu39S6(CPvm)`-+AmDP2hI_se6JVnw^l?i7uHM7H}c(7o{u<5w~1PDpo8K`AfFwl9;y0v%4&KB33 z$feaCsp-3H&3bR7!fNF&J48=ve~_AxF~6%6&o}o7ofgg3G3QW$_uC7?mwTIgj@3l; zmw^vjPkj6D`>ArSc6jUYX$=K`v_ARDxMOZ0(^;_}Av=NMP|3X8bjOgeeo;%McT*~a zn{ln`XY%;7uv-US6>*@uvq;^;PU^M9q#y)RyrM&I*zV7BlV^}qL;8=yUJK_Z4{<|}X*mkpuB9qz_)jg&qP;2mHBtCK z6i(fsIrSj1NcbHRlK4a&`#H<;JAV9dIzQz_qCj5r&Y5&MhC$^o!OdmBZ+;&-FSh&O z7R`vDhGK#8OugA#WCRy7S>;|kV&Zh?v1yQ*M&wNzJi)9(lr@vs>N^Fx*r0eEe$#2` z=m1R|aejs!^*d_Lo2ccB**S^>6nOxi*&>(7_xOO-&Gg!^5K%r^hb|nA->@{Zh+%vZ z#rL^uTq#-*4(G)8liwzGHr#M4>X7lrmtlPXX`sENNFv~jb*aZ}U7u;S2NUP~`)YR_ zl#Q-?ZivSHEeMphSfCIn@_iH#ooU&T5_Fpf8@oxIcJ02^6$8AxSM+p{1e4*|ogeQa zceP@_KhrcYp1S77a*XfpdFE@YXl-e|M)IuQaue@;XE{uSl5zo-J24@(N5jEo1u&7s zv)B6&#oLPdAOc2WsVgXclctnCgEwfIS4ZIraVA3u$O6nkQX!z2{70bkEr#>Ug`Qqy ztPMY{S(~2wBM{rVWOiBZUh2RX%nN#`OK{{*&l0psb$i`%Y)^U@9w=c*jv5Ga zM^nEdR>ruTjnj|TsrmTV2=O8{Be7Y0#k>IhV zYV=w4bnaTxl0{-0FX6dx2zVPnVS~T3?tWyZYWtRq%*&!c9m2|Cj^eISPO0pHLT)tce z!3xkKDpqScHkO@GL@N51s9r;Oig81qQZ-vHWG;0VRc5*z3ZvUtg?>e(UpovGJm3q> zMLpMI_HDB4c`LtR#_Mwi!ph3tvzTmB+$@_RZc8U>(gA^%KTpNxCr?-aDnhzNEt80k zp5|~oz@`P2&qL!fKN*E|>rE0T#AM&( z9HVyfg!i|wcB-?ucmWC>Y2OtQ-@I{IY4T!T>X3LuruH30{>o|&`gH=&I3DeK3*e~U zuqhF`s{t+Sks8bA(`z_MLP2W?(?z^XnL@%Y>bWHMCrUgtc?-!0^}`9Z9|3N?_w5#* zg*+%FKm!XCuvqd9Zw%dte+-Pb#a!DbY#=qX6e2B_3@v5WX384jT*3WaDZUyllRSW=Y#{A+nn!8IBd8m+m%LxD~2Cp>V* z{FE{M#ZL9YfF^u3k%94I0)p+;L=`Ji>e`EmSK#%SIM_W!DP8r!K#+UmgC$d~ql*{0{(? zN{t1L`w9qSy#Og9F903G|L#g@dUt`q%R)I`7W%R|e|+2}|FV=ijs;Ep8oIJBFJ{vV z9uElt!Sn|Y0s>j~|Cn&8%-GO)uk;62K|i;bxbILQAgKRECy{%l2mT*(fwiQ&4H^O> z76Sr;;a|)HmA{yYIw}~inR#8z9rnv&p0!>9H*M5ZA)J3UtA-Hqneb<yPK&6#p`_ zr1#2J(nmo3pXI0K;k;OrFnSGW5eFKd+CzH*f#3^%fW-I6aaVB1*H5~E*5duP!1_I*c=Z}wY17vFcHvnepY8xbG z>IyD264_sY{Mo$u?>&6%_-6tU9yG}-_s^b?f1g-BYQTStBd7Y}L1Vp6__Mwn|BqN8 zAl@1LOf|nX)(tnvRsQZWeQYrp7cTbMcmHp+cP@zCT5dDk0+Vl_i5g+>R8do%e7k9et zuWI5{d4hj})B8(3*z*rNkpLR)HHvd=&qd*vvt{|R?fw^5u;F9+!U zC6a%04Zi)ur6c?&3IW0wcZQSys9y@x;ZnT`{|P1XFKlD>AMB+{9h&j~I|(Mvrc5tx z@R0x4mB!LP^N@-DaYaV-Vs2vnAN5OFJ6x&}(Lb*2|H8_*{=rI!pfO)l(*BoPZ}%Vd z)&HpxiC@%`KmS&FKv>@a-^ARjy&a z6*-Q%N*Ku=*baq-XId#*8bDhb`k%=GeJb+$Zl|ahP~!Da173Slsrn zN?_6yiq?5TK1JcE0Uc527hlr=c#Xli7GcLc+DFk5K#AA54!rIcQ8m#eS%;(bY~?BS z&@@y^n+o+Tz{RT-SROFp87;z~p_`l^dZEyQe@kCj&Qf&!-**fQE}0QL4M zlF{L=3^AG2H6;sbxSvMRKyznlAc%9H6-Zw6>X| z{U**t`r?fVe4kvVYP6Y)!rQwga%?fPYMsf@?r8~Xzmd$T$gG?A?P)v!4s{}z;+|(e0tB>N`BO z6~kE(87{W5XC#Y7BMZ%s`glGC!(o!x6@F^z$j#7v+$81Uv`G-3Ke@MQI3A%L@bo0C zy7B|%^K6`w46(}w9oy>XRW0^Q!)15`sw6+!!LO#mC(SxlI4#zPE)(;Ib{;1`^ tags, normalized to work cross-browser) ---------------------------------------------------------------------------------------------------*/ - -.fc button { - /* force height to include the border and padding */ - -moz-box-sizing: border-box; - -webkit-box-sizing: border-box; - box-sizing: border-box; - - /* dimensions */ - margin: 0; - height: 2.1em; - padding: 0 .6em; - - /* text & cursor */ - font-size: 1em; /* normalize */ - white-space: nowrap; - cursor: pointer; -} - -/* Firefox has an annoying inner border */ -.fc button::-moz-focus-inner { margin: 0; padding: 0; } - -.fc-state-default { /* non-theme */ - border: 1px solid; -} - -.fc-state-default.fc-corner-left { /* non-theme */ - border-top-left-radius: 4px; - border-bottom-left-radius: 4px; -} - -.fc-state-default.fc-corner-right { /* non-theme */ - border-top-right-radius: 4px; - border-bottom-right-radius: 4px; -} - -/* icons in buttons */ - -.fc button .fc-icon { /* non-theme */ - position: relative; - top: .05em; /* seems to be a good adjustment across browsers */ - margin: 0 .1em; -} - -/* - button states - borrowed from twitter bootstrap (http://twitter.github.com/bootstrap/) -*/ - -.fc-state-default { - background-color: #f5f5f5; - background-image: -moz-linear-gradient(top, #ffffff, #e6e6e6); - background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#e6e6e6)); - background-image: -webkit-linear-gradient(top, #ffffff, #e6e6e6); - background-image: -o-linear-gradient(top, #ffffff, #e6e6e6); - background-image: linear-gradient(to bottom, #ffffff, #e6e6e6); - background-repeat: repeat-x; - border-color: #e6e6e6 #e6e6e6 #bfbfbf; - border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); - color: #333; - text-shadow: 0 1px 1px rgba(255, 255, 255, 0.75); - box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); -} - -.fc-state-hover, -.fc-state-down, -.fc-state-active, -.fc-state-disabled { - color: #333333; - background-color: #e6e6e6; -} - -.fc-state-hover { - color: #333333; - text-decoration: none; - background-position: 0 -15px; - -webkit-transition: background-position 0.1s linear; - -moz-transition: background-position 0.1s linear; - -o-transition: background-position 0.1s linear; - transition: background-position 0.1s linear; -} - -.fc-state-down, -.fc-state-active { - background-color: #cccccc; - background-image: none; - box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05); -} - -.fc-state-disabled { - cursor: default; - background-image: none; - opacity: 0.65; - filter: alpha(opacity=65); - box-shadow: none; -} - - -/* Buttons Groups ---------------------------------------------------------------------------------------------------*/ - -.fc-button-group { - display: inline-block; -} - -/* -every button that is not first in a button group should scootch over one pixel and cover the -previous button's border... -*/ - -.fc .fc-button-group > * { /* extra precedence b/c buttons have margin set to zero */ - float: left; - margin: 0 0 0 -1px; -} - -.fc .fc-button-group > :first-child { /* same */ - margin-left: 0; -} - - -/* Popover ---------------------------------------------------------------------------------------------------*/ - -.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-rtl .fc-popover .fc-header .fc-title, -.fc-ltr .fc-popover .fc-header .fc-close { - float: right; -} - -/* unthemed */ - -.fc-unthemed .fc-popover { - border-width: 1px; - border-style: solid; -} - -.fc-unthemed .fc-popover .fc-header .fc-close { - font-size: 25px; - margin-top: 4px; -} - -/* jqui themed */ - -.fc-popover > .ui-widget-header + .ui-widget-content { - border-top: 0; /* where they meet, let the header have the border */ -} - - -/* Misc Reusable Components ---------------------------------------------------------------------------------------------------*/ - -.fc hr { - height: 0; - margin: 0; - padding: 0 0 2px; /* height is unreliable across browsers, so use padding */ - border-style: solid; - border-width: 1px 0; -} - -.fc-clear { - clear: both; -} - -.fc-bg, -.fc-highlight-skeleton, -.fc-helper-skeleton { - /* these element should always cling to top-left/right corners */ - position: absolute; - top: 0; - left: 0; - right: 0; -} - -.fc-bg { - bottom: 0; /* strech bg to bottom edge */ -} - -.fc-bg table { - height: 100%; /* strech bg to bottom edge */ -} - - -/* Tables ---------------------------------------------------------------------------------------------------*/ - -.fc table { - width: 100%; - table-layout: fixed; - border-collapse: collapse; - border-spacing: 0; - font-size: 1em; /* normalize cross-browser */ -} - -.fc th { - text-align: center; -} - -.fc th, -.fc td { - border-style: solid; - border-width: 1px; - padding: 0; - vertical-align: top; -} - -.fc td.fc-today { - border-style: double; /* overcome neighboring borders */ -} - - -/* Fake Table Rows ---------------------------------------------------------------------------------------------------*/ - -.fc .fc-row { /* extra precedence to overcome themes w/ .ui-widget-content forcing a 1px border */ - /* no visible border by default. but make available if need be (scrollbar width compensation) */ - border-style: solid; - border-width: 0; -} - -.fc-row table { - /* don't put left/right border on anything within a fake row. - the outer tbody will worry about this */ - border-left: 0 hidden transparent; - border-right: 0 hidden transparent; - - /* no bottom borders on rows */ - border-bottom: 0 hidden transparent; -} - -.fc-row:first-child table { - border-top: 0 hidden transparent; /* no top border on first row */ -} - - -/* Day Row (used within the header and the DayGrid) ---------------------------------------------------------------------------------------------------*/ - -.fc-row { - position: relative; -} - -.fc-row .fc-bg { - z-index: 1; -} - -/* highlighting cells */ - -.fc-row .fc-highlight-skeleton { - z-index: 2; - bottom: 0; /* stretch skeleton to bottom of row */ -} - -.fc-row .fc-highlight-skeleton table { - height: 100%; /* stretch skeleton to bottom of row */ -} - -.fc-row .fc-highlight-skeleton td { - border-color: transparent; -} - -/* -row content (which contains day/week numbers and events) as well as "helper" (which contains -temporary rendered events). -*/ - -.fc-row .fc-content-skeleton { - position: relative; - z-index: 3; - padding-bottom: 2px; /* matches the space above the events */ -} - -.fc-row .fc-helper-skeleton { - z-index: 4; -} - -.fc-row .fc-content-skeleton td, -.fc-row .fc-helper-skeleton td { - /* see-through to the background below */ - background: none; /* in case 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) 2014 Adam Shaw, Sean Kenny + */ + + +.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..33853a3 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) 2014 Adam Shaw, Sean Kenny + */ + +(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..21182ff 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) 2014 Adam Shaw, Sean Kenny */.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..69b268f 100644 --- a/dist/fullcalendar.min.js +++ b/dist/fullcalendar.min.js @@ -1,8 +1,8 @@ /*! * FullCalendar v2.1.1 * Docs & License: http://arshaw.com/fullcalendar/ - * (c) 2013 Adam Shaw + * (c) 2014 Adam Shaw, Sean Kenny */ -(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..6078b49 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) 2014 Adam Shaw, Sean Kenny + */ + +/* + * 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..17097d1 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) 2014 Adam Shaw, Sean Kenny + */ + +(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 diff --git a/fullcalendar.jquery.json b/fullcalendar.jquery.json index e600605..72e9af3 100644 --- a/fullcalendar.jquery.json +++ b/fullcalendar.jquery.json @@ -18,14 +18,14 @@ "licenses": [ { "type": "MIT", - "url": "https://github.com/arshaw/fullcalendar/blob/master/license.txt" + "url": "https://github.com/seankenny/fullcalendar/blob/master/license.txt" } ], "author": { - "name": "Adam Shaw", - "email": "arshaw@arshaw.com", - "url": "http://arshaw.com/" + "name": "Adam Shaw, Sean Kenny", + "email": "srkenny@gmail.com", + "url": "http://seankenny.me" }, - "copyright": "2013 Adam Shaw" + "copyright": "2014 Adam Shaw, Sean Kenny" } \ No newline at end of file diff --git a/license.txt b/license.txt index a7558e5..38ce7e7 100644 --- a/license.txt +++ b/license.txt @@ -1,5 +1,4 @@ -Copyright (c) 2013 Adam Shaw -Copyright (c) 2013 Sean Kenny +Copyright (c) 2014 Adam Shaw, Sean Kenny Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/package.json b/package.json index f941437..fed50a6 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "2.1.1", "repository": { "type": "git", - "url": "https://github.com/arshaw/fullcalendar.git" + "url": "https://github.com/seankenny/fullcalendar.git" }, "devDependencies": { "bower": "1.3.9",
'),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)+"