/*! * FullCalendar v2.7.1 * Docs & License: http://fullcalendar.io/ * (c) 2016 Adam Shaw */ !function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):"object"==typeof exports?// Node/CommonJS module.exports=a(require("jquery"),require("moment")):a(jQuery,moment)}(function(a,b){ // Merges an array of option objects into a single object function c(a){return U(a,Va)} // Given options specified for the calendar's constructor, massages any legacy options into a non-legacy form. // Converts View-Option-Hashes into the View-Specific-Options format. function d(b){var c,d={views:b.views||{}}; // iterate through all option override properties (except `views`) return a.each(b,function(b,e){"views"!=b&&( // could the value be a legacy View-Option-Hash? a.isPlainObject(e)&&!/(time|duration|interval)$/i.test(b)&&-1==a.inArray(b,Va)?(c=null,a.each(e,function(a,e){/^(month|week|day|default|basic(Week|Day)?|agenda(Week|Day)?)$/.test(a)?(d.views[a]||(d.views[a]={}),d.views[a][b]=e):(c||(c={}),c[a]=e)}),c&&(d[b]=c)):d[b]=e)}),d}/* FullCalendar-specific DOM Utilities ----------------------------------------------------------------------------------------------------------------------*/ // Given the scrollbar widths of some other container, create borders/margins on rowEls in order to match the left // and right space that was offset by the scrollbars. A 1-pixel border first, then margin beyond that. function e(a,b){b.left&&a.css({"border-left-width":1,"margin-left":b.left-1}),b.right&&a.css({"border-right-width":1,"margin-right":b.right-1})} // Undoes compensateScroll and restores all borders/margins function f(a){a.css({"margin-left":"","margin-right":"","border-left-width":"","border-right-width":""})} // Make the mouse cursor express that an event is not allowed in the current area function g(){a("body").addClass("fc-not-allowed")} // Returns the mouse cursor to its original look function h(){a("body").removeClass("fc-not-allowed")} // Given a total available height to fill, have `els` (essentially child rows) expand to accomodate. // By default, all elements that are shorter than the recommended height are expanded uniformly, not considering // any other els that are already too tall. if `shouldRedistribute` is on, it considers these tall rows and // reduces the available height. function i(b,c,d){ // *FLOORING NOTE*: we floor in certain places because zoom can give inaccurate floating-point dimensions, // and it is better to be shorter than taller, to avoid creating unnecessary scrollbars. var e=Math.floor(c/b.length),f=Math.floor(c-e*(b.length-1)),g=[],h=[],i=[],k=0;j(b),// give all elements their natural height // find elements that are below the recommended height (expandable). // important to query for heights in a single first pass (to avoid reflow oscillation). b.each(function(c,d){var j=c===b.length-1?f:e,l=a(d).outerHeight(!0);j>l?(g.push(d),h.push(l),i.push(a(d).height())): // this element stretches past recommended height (non-expandable). mark the space as occupied. k+=l}), // readjust the recommended height to only consider the height available to non-maxed-out rows. d&&(c-=k,e=Math.floor(c/g.length),f=Math.floor(c-e*(g.length-1))), // assign heights to all expandable elements a(g).each(function(b,c){var d=b===g.length-1?f:e,j=h[b],k=i[b],l=d-(j-k);// subtract the margin/padding d>j&&// we check this again because redistribution might have changed things a(c).height(l)})} // Undoes distrubuteHeight, restoring all els to their natural height function j(a){a.height("")} // Given `els`, a jQuery set of 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 k(b){var c=0;// sometimes not accurate of width the text needs to stay on one line. insurance return b.find("> span").each(function(b,d){var e=a(d).outerWidth();e>c&&(c=e)}),c++,b.width(c),c} // Given one element that resides inside another, // Subtracts the height of the inner element from the outer element. function l(a,b){var c,d=a.add(b);// undo hack // effin' IE8/9/10/11 sometimes returns 0 for dimensions. this weird hack was the only thing that worked // grab the dimensions return d.css({position:"relative",// cause a reflow, which will force fresh dimension recalculation left:-1}),c=a.outerHeight()-b.outerHeight(),d.css({position:"",left:""}),c} // borrowed from https://github.com/jquery/jquery-ui/blob/1.11.0/ui/core.js#L51 function m(b){var c=b.css("position"),d=b.parents().filter(function(){var b=a(this);return/(auto|scroll)/.test(b.css("overflow")+b.css("overflow-y")+b.css("overflow-x"))}).eq(0);return"fixed"!==c&&d.length?d:a(b[0].ownerDocument||document)} // Queries the outer bounding area of a jQuery element. // Returns a rectangle with absolute coordinates: left, right (exclusive), top, bottom (exclusive). // Origin is optional. function n(a,b){var c=a.offset(),d=c.left-(b?b.left:0),e=c.top-(b?b.top:0);return{left:d,right:d+a.outerWidth(),top:e,bottom:e+a.outerHeight()}} // Queries the area within the margin/border/scrollbars of a jQuery element. Does not go within the padding. // Returns a rectangle with absolute coordinates: left, right (exclusive), top, bottom (exclusive). // Origin is optional. // NOTE: should use clientLeft/clientTop, but very unreliable cross-browser. function o(a,b){var c=a.offset(),d=q(a),e=c.left+t(a,"border-left-width")+d.left-(b?b.left:0),f=c.top+t(a,"border-top-width")+d.top-(b?b.top:0);return{left:e,right:e+a[0].clientWidth,// clientWidth includes padding but NOT scrollbars top:f,bottom:f+a[0].clientHeight}} // Queries the area within the margin/border/padding of a jQuery element. Assumed not to have scrollbars. // Returns a rectangle with absolute coordinates: left, right (exclusive), top, bottom (exclusive). // Origin is optional. function p(a,b){var c=a.offset(),d=c.left+t(a,"border-left-width")+t(a,"padding-left")-(b?b.left:0),e=c.top+t(a,"border-top-width")+t(a,"padding-top")-(b?b.top:0);return{left:d,right:d+a.width(),top:e,bottom:e+a.height()}} // Returns the computed left/right/top/bottom scrollbar widths for the given jQuery element. // NOTE: should use clientLeft/clientTop, but very unreliable cross-browser. function q(a){var b=a.innerWidth()-a[0].clientWidth,c={left:0,right:0,top:0,bottom:a.innerHeight()-a[0].clientHeight};// is the scrollbar on the left side? return r()&&"rtl"==a.css("direction")?c.left=b:c.right=b,c}function r(){// responsible for caching the computation return null===Wa&&(Wa=s()),Wa}function s(){// creates an offscreen test element, then removes it var b=a("
").css({position:"absolute",top:-1e3,left:0,border:0,padding:0,overflow:"scroll",direction:"rtl"}).appendTo("body"),c=b.children(),d=c.offset().left>b.offset().left;// is the inner div shifted to accommodate a left scrollbar? return b.remove(),d} // Retrieves a jQuery element's computed CSS value as a floating-point number. // If the queried value is non-numeric (ex: IE can return "medium" for border width), will just return zero. function t(a,b){return parseFloat(a.css(b))||0} // Returns a boolean whether this was a left mouse click and no ctrl key (which means right click on Mac) function u(a){return 1==a.which&&!a.ctrlKey}function v(a){if(void 0!==a.pageX)return a.pageX;var b=a.originalEvent.touches;return b?b[0].pageX:void 0}function w(a){if(void 0!==a.pageY)return a.pageY;var b=a.originalEvent.touches;return b?b[0].pageY:void 0}function x(a){return/^touch/.test(a.type)}function y(a){a.addClass("fc-unselectable").on("selectstart",z)} // Stops a mouse/touch event from doing it's native browser action function z(a){a.preventDefault()} // Returns a new rectangle that is the intersection of the two rectangles. If they don't intersect, returns false function A(a,b){var c={left:Math.max(a.left,b.left),right:Math.min(a.right,b.right),top:Math.max(a.top,b.top),bottom:Math.min(a.bottom,b.bottom)};return c.lefti&&j>g?(g>=i?(c=g.clone(),e=!0):(c=i.clone(),e=!1),j>=h?(d=h.clone(),f=!0):(d=j.clone(),f=!1),{start:c,end:d,isStart:e,isEnd:f}):void 0} // Diffs the two moments into a Duration where full-days are recorded first, then the remaining time. // Moments will have their timezones normalized. function J(a,c){return b.duration({days:a.clone().stripTime().diff(c.clone().stripTime(),"days"),ms:a.time()-c.time()})} // Diffs the two moments via their start-of-day (regardless of timezone). Produces whole-day durations. function K(a,c){return b.duration({days:a.clone().stripTime().diff(c.clone().stripTime(),"days")})} // Diffs two moments, producing a duration, made of a whole-unit-increment of the given unit. Uses rounding. function L(a,c,d){// returnFloat=true return b.duration(Math.round(a.diff(c,d,!0)),d)} // Computes the unit name of the largest whole-unit period of time. // For example, 48 hours will be "days" whereas 49 hours will be "hours". // Accepts start/end, a range object, or an original duration object. function M(a,b){var c,d,e;for(c=0;c=1&&fa(e)));c++);return d} // Computes the number of units (like "hours") in the given range. // Range can be a {start,end} object, separate start/end args, or a Duration. // Results are based on Moment's .as() and .diff() methods, so results can depend on internal handling // of month-diffing logic (which tends to vary from version to version). function N(a,c,d){return null!=d?d.diff(c,a,!0):b.isDuration(c)?c.as(a):c.end.diff(c.start,a,!0)} // Intelligently divides a range (specified by a start/end params) by a duration function O(a,b,c){var d;return R(c)?(b-a)/c:(d=c.asMonths(),Math.abs(d)>=1&&fa(d)?b.diff(a,"months",!0)/d:b.diff(a,"days",!0)/c.asDays())} // Intelligently divides one duration by another function P(a,b){var c,d;return R(a)||R(b)?a/b:(c=a.asMonths(),d=b.asMonths(),Math.abs(c)>=1&&fa(c)&&Math.abs(d)>=1&&fa(d)?c/d:a.asDays()/b.asDays())} // Intelligently multiplies a duration by a number function Q(a,c){var d;return R(a)?b.duration(a*c):(d=a.asMonths(),Math.abs(d)>=1&&fa(d)?b.duration({months:d*c}):b.duration({days:a.asDays()*c}))} // Returns a boolean about whether the given duration has any time parts (hours/minutes/seconds/ms) function R(a){return Boolean(a.hours()||a.minutes()||a.seconds()||a.milliseconds())}function S(a){return"[object Date]"===Object.prototype.toString.call(a)||a instanceof Date} // Returns a boolean about whether the given input is a time string, like "06:40:00" or "06:00" function T(a){return/^\d+\:\d+(?:\:\d+\.?(?:\d{3})?)?$/.test(a)} // Merges an array of objects into a single object. // The second argument allows for an array of property names who's object values will be merged together. function U(a,b){var c,d,e,f,g,h,i={};if(b)for(c=0;c=0;f--)if(g=a[f][d],"object"==typeof g)e.unshift(g);else if(void 0!==g){i[d]=g;// if there were no objects, this value will be used break} // if the trailing values were objects, use the merged value e.length&&(i[d]=U(e))} // copy values into the destination, going from last to first for(c=a.length-1;c>=0;c--){h=a[c];for(d in h)d in i||(// if already assigned by previous props or complex props, don't reassign i[d]=h[d])}return i} // Create an object that has the given prototype. Just like Object.create function V(a){var b=function(){};return b.prototype=a,new b}function W(a,b){for(var c in a)Y(a,c)&&(b[c]=a[c])} // Copies over certain methods with the same names as Object.prototype methods. Overcomes an IE<=8 bug: // https://developer.mozilla.org/en-US/docs/ECMAScript_DontEnum_attribute#JScript_DontEnum_Bug function X(a,b){var c,d,e=["constructor","toString","valueOf"];for(c=0;c/g,">").replace(/'/g,"'").replace(/"/g,""").replace(/\n/g,"
")}function ba(a){return a.replace(/&.*?;/g,"")} // Given a hash of CSS properties, returns a string of CSS. // Uses property names as-is (no camel-case conversion). Will not make statements for null/undefined values. function ca(b){var c=[];return a.each(b,function(a,b){null!=b&&c.push(a+":"+b)}),c.join(";")}function da(a){return a.charAt(0).toUpperCase()+a.slice(1)}function ea(a,b){// for .sort() return a-b}function fa(a){return a%1===0} // Returns a method bound to the given object context. // Just like one of the jQuery.proxy signatures, but without the undesired behavior of treating the same method with // different contexts as identical when binding/unbinding events. function ga(a,b){var c=a[b];return function(){return c.apply(a,arguments)}} // 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. If `immediate` is passed, trigger the function on the // leading edge, instead of the trailing. // https://github.com/jashkenas/underscore/blob/1.6.0/underscore.js#L714 function ha(a,b,c){var d,e,f,g,h,i=function(){var j=+new Date-g;b>j?d=setTimeout(i,b-j):(d=null,c||(h=a.apply(f,e),f=e=null))};return function(){f=this,e=arguments,g=+new Date;var j=c&&!d;return d||(d=setTimeout(i,b)),j&&(h=a.apply(f,e),f=e=null),h}} // Builds an enhanced moment 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 ia(c,d,e){var f,g,h,i,j=c[0],k=1==c.length&&"string"==typeof j;// flag for extended functionality // clone it // "parsing" is required // accept strings like '2014-05', but convert to the first of the month // for when we pass it on to moment's constructor // no time part? // arrays have no timezone information, so assume ambiguous zone // otherwise, probably a string with a format // let's record the inputted zone somehow return b.isMoment(j)?(i=b.apply(null,c),ka(j,i)):S(j)||void 0===j?i=b.apply(null,c):(f=!1,g=!1,k?bb.test(j)?(j+="-01",c=[j],f=!0,g=!0):(h=cb.exec(j))&&(f=!h[5],g=!0):a.isArray(j)&&(g=!0),i=d||f?b.utc.apply(b,c):b.apply(null,c),f?(i._ambigTime=!0,i._ambigZone=!0):e&&(g?i._ambigZone=!0:k&&(i.utcOffset?i.utcOffset(j):i.zone(j)))),i._fullCalendar=!0,i} // 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. // returns the original moments if no modifications are necessary. function ja(a,c){var d,e,f=!1,g=!1,h=a.length,i=[]; // parse inputs into real moments and query their ambig flags for(d=0;h>d;d++)e=a[d],b.isMoment(e)||(e=Ta.moment.parseZone(e)),f=f||e._ambigTime,g=g||e._ambigZone,i.push(e); // strip each moment down to lowest common ambiguity // use clones to avoid modifying the original moments for(d=0;h>d;d++)e=i[d],c||!f||e._ambigTime?g&&!e._ambigZone&&(i[d]=e.clone().stripZone()):i[d]=e.clone().stripTime();return i} // Transfers all the flags related to ambiguous time/zone from the `src` moment to the `dest` moment // TODO: look into moment.momentProperties for this. function ka(a,b){a._ambigTime?b._ambigTime=!0:b._ambigTime&&(b._ambigTime=!1),a._ambigZone?b._ambigZone=!0:b._ambigZone&&(b._ambigZone=!1)} // Sets the year/month/date/etc values of the moment from the given array. // Inefficient because it calls each individual setter. function la(a,b){a.year(b[0]||0).month(b[1]||0).date(b[2]||0).hours(b[3]||0).minutes(b[4]||0).seconds(b[5]||0).milliseconds(b[6]||0)} // Single Date Formatting // ------------------------------------------------------------------------------------------------- // call this if you want Moment's original format method to be used function ma(a,b){return eb.format.call(a,b)} // Formats `date` with a Moment formatting string, but allow our non-zero areas and // additional token. function na(a,b){return oa(a,ta(b))}function oa(a,b){var c,d="";for(c=0;c "MMMM D YYYY" // 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. return a=Ta.moment.parseZone(a),b=Ta.moment.parseZone(b),f=(a.localeData||a.lang).call(a),c=f.longDateFormat(c)||c,d=d||" - ",ra(a,b,ta(c),d,e)}// expose function ra(a,b,c,d,e){var f,g,h,i,j=a.clone().stripZone(),k=b.clone().stripZone(),l="",m="",n="",o="",p=""; // Start at the leftmost side of the formatting string and continue until you hit a token // that is not the same between dates. for(g=0;gg&&(f=sa(a,b,j,k,c[h]),f!==!1);h--)m=f+m; // The area in the middle is different for both of the dates. // Collect them distinctly so we can jam them together later. for(i=g;h>=i;i++)n+=pa(a,c[i]),o+=pa(b,c[i]);return(n||o)&&(p=e?o+d+n:n+d+o),l+p+m} // 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 sa(a,b,c,d,e){var f,g;return"string"==typeof e?e:(f=e.token)&&(g=gb[f.charAt(0)],g&&c.isSame(d,g))?ma(a,f):!1}function ta(a){return a in hb?hb[a]:hb[a]=ua(a)} // Break the formatting string into an array of chunks function ua(a){for(var b,c=[],d=/\[([^\]]*)\]|\(([^\)]*)\)|(LTS|LT|(\w)\4*o?)|([^\w\[\(]+)/g;b=d.exec(a);)b[1]?// a literal string inside [ ... ] c.push(b[1]):b[2]?// non-zero formatting inside ( ... ) c.push({maybe:ua(b[2])}):b[3]?// a formatting token c.push({token:b[3]}):b[5]&&// an unenclosed literal string c.push(b[5]);return c}// export // Class that all other classes will inherit from function va(){}function wa(a,b){var c; // ensure a constructor for the subclass, forwarding all arguments to the super-constructor if it doesn't exist // build the base prototype for the subclass, which is an new object chained to the superclass's prototype // copy each member variable/method onto the the subclass's prototype // hack for IE8 // copy over all class variables/methods to the subclass, such as `extend` and `mixin` return Y(b,"constructor")&&(c=b.constructor),"function"!=typeof c&&(c=b.constructor=function(){a.apply(this,arguments)}),c.prototype=V(a.prototype),W(b,c.prototype),X(b,c.prototype),W(a,c),c}function xa(a,b){W(b,a.prototype)} // Returns `true` if the hits are identically equal. `false` otherwise. Must be from the same component. // Two null values will be considered equal, as two "out of the component" states are the same. function ya(a,b){return a||b?a&&b?a.component===b.component&&za(a,b)&&za(b,a):!1:!0} // Returns true if all of subHit's non-standard properties are within superHit function za(a,b){for(var c in a)if(!/^(component|left|right|top|bottom)$/.test(c)&&a[c]!==b[c])return!1;return!0}/* Utilities ----------------------------------------------------------------------------------------------------------------------*/ function Aa(a){// returns true if background OR inverse-background var b=Ca(a);return"background"===b||"inverse-background"===b}// export function Ba(a){return"inverse-background"===Ca(a)}function Ca(a){return _((a.source||{}).rendering,a.rendering)}function Da(a){var b,c,d={};for(b=0;b=a.leftCol)return!0;return!1} // A cmp function for determining the leftmost event function Ha(a,b){return a.leftCol-b.leftCol} // 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 Ia(a){var b,c,d,e=[];for(b=0;bb.top&&a.top").prependTo(c),R=N.header=new Qa(N,O),S=R.render(),S&&c.prepend(S),i(O.defaultView),O.handleWindowResize&&(Y=ha(m,O.windowResizeDelay),a(window).resize(Y))}function g(){W&&W.removeElement(),R.removeElement(),T.remove(),c.removeClass("fc fc-touch fc-cursor fc-ltr fc-rtl fc-unthemed ui-widget"),Y&&a(window).unbind("resize",Y)}function h(){return c.is(":visible")} // View Rendering // ----------------------------------------------------------------------------------- // Renders a view because of a date change, view-type change, or for the first time. // If not given a viewType, keep the current view but render different dates. function i(b){ca++, // if viewType is changing, remove the old view's rendering W&&b&&W.type!==b&&(R.deactivateButton(W.type),H(),// prevent a scroll jump when view element is removed W.removeElement(),W=N.view=null), // if viewType changed, or the view was never created, create a fresh view !W&&b&&(W=N.view=ba[b]||(ba[b]=N.instantiateView(b)),W.setElement(a("
").appendTo(T)),R.activateButton(b)),W&&(Z=W.massageCurrentDate(Z),W.displaying&&Z.isWithin(W.intervalStart,W.intervalEnd)||h()&&(W.display(Z),I(),u(),v(),q())),I(),// undo any lone freezeContentHeight calls ca--}function j(a){// isResize=true. will poll getSuggestedViewHeight() and isHeightAuto() return h()?(a&&l(),ca++,W.updateSize(!0),ca--,!0):void 0}function k(){h()&&l()}function l(){// assumes elementVisible X="number"==typeof O.contentHeight?O.contentHeight:"number"==typeof O.height?O.height-(S?S.outerHeight(!0):0):Math.round(T.width()/Math.max(O.aspectRatio,.5))}function m(a){!ca&&a.target===window&&W.start&&j(!0)&&W.trigger("windowResize",aa)}/* Event Fetching/Rendering -----------------------------------------------------------------------------*/ // TODO: going forward, most of this stuff should be directly handled by the view function n(){// can be called as an API method p(),// so that events are cleared before user starts waiting for AJAX r()}function o(){// destroys old events if previously rendered h()&&(H(),W.displayEvents(da),I())}function p(){H(),W.clearEvents(),I()}function q(){!O.lazyFetching||$(W.start,W.end)?r():o()}function r(){_(W.start,W.end)} // called when event data arrives function s(a){da=a,o()} // called when a single event's data has been changed function t(){o()}/* Header Updating -----------------------------------------------------------------------------*/ function u(){R.updateTitle(W.title)}function v(){var a=N.getNow();a.isWithin(W.intervalStart,W.intervalEnd)?R.disableButton("today"):R.enableButton("today")}/* Selection -----------------------------------------------------------------------------*/ // this public method receives start/end dates in any format, with any timezone function w(a,b){W.select(N.buildSelectSpan.apply(N,arguments))}function x(){// safe to be called before renderView W&&W.unselect()}/* Date -----------------------------------------------------------------------------*/ function y(){Z=W.computePrevDate(Z),i()}function z(){Z=W.computeNextDate(Z),i()}function A(){Z.add(-1,"years"),i()}function B(){Z.add(1,"years"),i()}function C(){Z=N.getNow(),i()}function D(a){Z=N.moment(a).stripZone(),i()}function E(a){Z.add(b.duration(a)),i()} // Forces navigation to a view for the given date. // `viewType` can be a specific view name or a generic one like "week" or "day". function F(a,b){var c;b=b||"day",c=N.getViewSpec(b)||N.getUnitViewSpec(b),Z=a.clone(),i(c?c.type:null)} // for external API function G(){return N.applyTimezone(Z)}function H(){T.css({width:"100%",height:T.height(),overflow:"hidden"})}function I(){T.css({width:"",height:"",overflow:""})}/* Misc -----------------------------------------------------------------------------*/ function J(){return N}function K(){return W}function L(a,b){return void 0===b?O[a]:void("height"!=a&&"contentHeight"!=a&&"aspectRatio"!=a||(O[a]=b,j(!0)))}function M(a,b){// overrides the Emitter's trigger method :( var c=Array.prototype.slice.call(arguments,2);return b=b||aa,this.triggerWith(a,b,c),O[a]?O[a].apply(b,c):void 0}var N=this;N.initOptions(d||{});var O=this.options; // Exports // ----------------------------------------------------------------------------------- N.render=e,N.destroy=g,N.refetchEvents=n,N.reportEvents=s,N.reportEventChange=t,N.rerenderEvents=o,// `renderEvents` serves as a rerender. an API method N.changeView=i,// `renderView` will switch to another view N.select=w,N.unselect=x,N.prev=y,N.next=z,N.prevYear=A,N.nextYear=B,N.today=C,N.gotoDate=D,N.incrementDate=E,N.zoomTo=F,N.getDate=G,N.getCalendar=J,N.getView=K,N.option=L,N.trigger=M; // Language-data Internals // ----------------------------------------------------------------------------------- // Apply overrides to the current language's data var P=V(// make a cheap copy Pa(O.lang));if(O.monthNames&&(P._months=O.monthNames),O.monthNamesShort&&(P._monthsShort=O.monthNamesShort),O.dayNames&&(P._weekdays=O.dayNames),O.dayNamesShort&&(P._weekdaysShort=O.dayNamesShort),null!=O.firstDay){var Q=V(P._week);// _week: { dow: # } Q.dow=O.firstDay,P._week=Q} // assign a normalized value, to be used by our .week() moment extension P._fullCalendar_weekCalc=function(a){return"function"==typeof a?a:"local"===a?a:"iso"===a||"ISO"===a?"ISO":void 0}(O.weekNumberCalculation), // Calendar-specific Date Utilities // ----------------------------------------------------------------------------------- N.defaultAllDayEventDuration=b.duration(O.defaultAllDayEventDuration),N.defaultTimedEventDuration=b.duration(O.defaultTimedEventDuration), // Builds a moment using the settings of the current calendar: timezone and language. // Accepts anything the vanilla moment() constructor accepts. N.moment=function(){var a; // Force the moment to be local, because FC.moment doesn't guarantee it. // don't give ambiguously-timed moments a local zone // moment 2.8 and above // pre-moment-2.8 return"local"===O.timezone?(a=Ta.moment.apply(null,arguments),a.hasTime()&&a.local()):a="UTC"===O.timezone?Ta.moment.utc.apply(null,arguments):Ta.moment.parseZone.apply(null,arguments),"_locale"in a?a._locale=P:a._lang=P,a}, // Returns a boolean about whether or not the calendar knows how to calculate // the timezone offset of arbitrary dates in the current timezone. N.getIsAmbigTimezone=function(){return"local"!==O.timezone&&"UTC"!==O.timezone}, // Returns a copy of the given date in the current timezone. Has no effect on dates without times. N.applyTimezone=function(a){if(!a.hasTime())return a.clone();var b,c=N.moment(a.toArray()),d=a.time()-c.time(); // Safari sometimes has problems with this coersion when near DST. Adjust if necessary. (bug #2396) // is the time result different than expected? // add milliseconds // does it match perfectly now? return d&&(b=c.clone().add(d),a.time()-b.time()===0&&(c=b)),c}, // Returns a moment for the current date, as defined by the client's computer or from the `now` option. // Will return an moment with an ambiguous timezone. N.getNow=function(){var a=O.now;return"function"==typeof a&&(a=a()),N.moment(a).stripZone()}, // Get an event's normalized end date. If not present, calculate it from the defaults. N.getEventEnd=function(a){return a.end?a.end.clone():N.getDefaultEventEnd(a.allDay,a.start)}, // Given an event's allDay status and start date, return what its fallback end date should be. // TODO: rename to computeDefaultEventEnd N.getDefaultEventEnd=function(a,b){var c=b.clone();return a?c.stripTime().add(N.defaultAllDayEventDuration):c.add(N.defaultTimedEventDuration),N.getIsAmbigTimezone()&&c.stripZone(),c}, // Produces a human-readable string for the given duration. // Side-effect: changes the locale of the given duration. N.humanizeDuration=function(a){return(a.locale||a.lang).call(a,O.lang).humanize()}, // Imports // ----------------------------------------------------------------------------------- Ra.call(N,O);var R,S,T,U,W,X,Y,Z,$=N.isFetchNeeded,_=N.fetchEvents,aa=c[0],ba={},ca=0,da=[];// unzoned // Main Rendering // ----------------------------------------------------------------------------------- // compute the initial ambig-timezone date Z=null!=O.defaultDate?N.moment(O.defaultDate).stripZone():N.getNow(),N.getSuggestedViewHeight=function(){return void 0===X&&k(),X},N.isHeightAuto=function(){return"auto"===O.contentHeight||"auto"===O.height},N.freezeContentHeight=H,N.unfreezeContentHeight=I,N.initialize()}function Oa(b){a.each(zb,function(a,c){null==b[a]&&(b[a]=c(b))})} // Returns moment's internal locale data. If doesn't exist, returns English. // Works with moment-pre-2.8 function Pa(a){var c=b.localeData||b.langData;return c.call(b,a)||c.call(b,"en")}/* Top toolbar area with buttons and title ----------------------------------------------------------------------------------------------------------------------*/ // TODO: rename all header-related things to "toolbar" function Qa(b,c){function d(){var b=c.header;return n=c.theme?"ui":"fc",b?o=a("
").append(f("left")).append(f("right")).append(f("center")).append('
'):void 0}function e(){o.remove(),o=a()}function f(d){var e=a('
'),f=c.header[d];return f&&a.each(f.split(" "),function(d){var f,g=a(),h=!0;a.each(this.split(","),function(d,e){var f,i,j,k,l,m,o,q,r,s;// the element "title"==e?(g=g.add(a("

 

")),h=!1):((f=(b.options.customButtons||{})[e])?(j=function(a){f.click&&f.click.call(s[0],a)},k="",l=f.text):(i=b.getViewSpec(e))?(j=function(){b.changeView(e)},p.push(e),k=i.buttonTextOverride,l=i.buttonTextDefault):b[e]&&(j=function(){b[e]()},k=(b.overrides.buttonText||{})[e],l=c.buttonText[e]),j&&(m=f?f.themeIcon:c.themeButtonIcons[e],o=f?f.icon:c.buttonIcons[e],q=k?aa(k):m&&c.theme?"":o&&!c.theme?"":aa(l),r=["fc-"+e+"-button",n+"-button",n+"-state-default"],s=a('").click(function(a){s.hasClass(n+"-state-disabled")||(j(a),(s.hasClass(n+"-state-active")||s.hasClass(n+"-state-disabled"))&&s.removeClass(n+"-state-hover"))}).mousedown(function(){s.not("."+n+"-state-active").not("."+n+"-state-disabled").addClass(n+"-state-down")}).mouseup(function(){s.removeClass(n+"-state-down")}).hover(function(){s.not("."+n+"-state-active").not("."+n+"-state-disabled").addClass(n+"-state-hover")},function(){s.removeClass(n+"-state-hover").removeClass(n+"-state-down")}),g=g.add(s)))}),h&&g.first().addClass(n+"-corner-left").end().last().addClass(n+"-corner-right").end(),g.length>1?(f=a("
"),h&&f.addClass("fc-button-group"),f.append(g),e.append(f)):e.append(g)}),e}function g(a){o.find("h2").text(a)}function h(a){o.find(".fc-"+a+"-button").addClass(n+"-state-active")}function i(a){o.find(".fc-"+a+"-button").removeClass(n+"-state-active")}function j(a){o.find(".fc-"+a+"-button").attr("disabled","disabled").addClass(n+"-state-disabled")}function k(a){o.find(".fc-"+a+"-button").removeAttr("disabled").removeClass(n+"-state-disabled")}function l(){return p}var m=this; // exports m.render=d,m.removeElement=e,m.updateTitle=g,m.activateButton=h,m.deactivateButton=i,m.disableButton=j,m.enableButton=k,m.getViewsWithButtons=l; // locals var n,o=a(),p=[]}function Ra(c){/* Fetching -----------------------------------------------------------------------------*/ // start and end are assumed to be unzoned function d(a,b){// nothing has been fetched yet? return!I||I>a||b>M}function e(a,b){I=a,M=b,S=[];var c=++Q,d=P.length;R=d;for(var e=0;d>e;e++)f(P[e],c)}function f(b,c){g(b,function(d){var e,f,g,h=a.isArray(b.events);if(c==Q){if(d)for(e=0;e=c&&b.end<=d} // Does the event's date range intersect with the given range? // start/end already assumed to have stripped zones :( function G(a,b){var c=a.start.clone().stripZone(),d=H.getEventEnd(a).stripZone();return b.startc}// assumed to be a calendar var H=this; // exports H.isFetchNeeded=d,H.fetchEvents=e,H.addEventSource=h,H.removeEventSource=j,H.updateEvent=m,H.renderEvent=p,H.removeEvents=q,H.clientEvents=r,H.mutateEvent=x,H.normalizeEventDates=u,H.normalizeEventTimes=v; // imports var I,M,N=H.reportEvents,O={events:[]},P=[O],Q=0,R=0,S=[];// holds events that have already been expanded a.each((c.events?[c.events]:[]).concat(c.eventSources||[]),function(a,b){var c=i(b);c&&P.push(c)}),/* Business Hours -----------------------------------------------------------------------------------------*/ H.getBusinessHoursEvents=z,/* Overlapping / Constraining -----------------------------------------------------------------------------------------*/ H.isEventSpanAllowed=A,H.isExternalSpanAllowed=B,H.isSelectionSpanAllowed=C,H.getEventCache=function(){return S}} // updates the "backup" properties, which are preserved in order to compute diffs later on. function Sa(a){a._allDay=a.allDay,a._start=a.start.clone(),a._end=a.end?a.end.clone():null}var Ta=a.fullCalendar={version:"2.7.1",internalApiVersion:3},Ua=Ta.views={};Ta.isTouch="ontouchstart"in document,a.fn.fullCalendar=function(b){var c=Array.prototype.slice.call(arguments,1),d=this;// what this function will return (this jQuery object by default) return this.each(function(e,f){// loop each DOM element involved var g,h=a(f),i=h.data("fullCalendar");// the returned value of this single method call // a method call "string"==typeof b?i&&a.isFunction(i[b])&&(g=i[b].apply(i,c),e||(d=g),"destroy"===b&&h.removeData("fullCalendar")):i||(i=new vb(h,b),h.data("fullCalendar",i),i.render())}),d};var Va=[// names of options that are objects whose properties should be combined "header","buttonText","buttonIcons","themeButtonIcons"]; // exports Ta.intersectRanges=I,Ta.applyAll=$,Ta.debounce=ha,Ta.isInt=fa,Ta.htmlEscape=aa,Ta.cssToStr=ca,Ta.proxy=ga,Ta.capitaliseFirstLetter=da,/* Element Geom Utilities ----------------------------------------------------------------------------------------------------------------------*/ Ta.getOuterRect=n,Ta.getClientRect=o,Ta.getContentRect=p,Ta.getScrollbarWidths=q; // Logic for determining if, when the element is right-to-left, the scrollbar appears on the left side var Wa=null;/* Mouse / Touch Utilities ----------------------------------------------------------------------------------------------------------------------*/ Ta.preventDefault=z,/* General Geometry Utils ----------------------------------------------------------------------------------------------------------------------*/ Ta.intersectRects=A,/* Object Ordering by Field ----------------------------------------------------------------------------------------------------------------------*/ Ta.parseFieldSpecs=E,Ta.compareByFieldSpecs=F,Ta.compareByFieldSpec=G,Ta.flexibleCompare=H,/* Date Utilities ----------------------------------------------------------------------------------------------------------------------*/ Ta.computeIntervalUnit=M,Ta.divideRangeByDuration=O,Ta.divideDurationByDuration=P,Ta.multiplyDuration=Q,Ta.durationHasTime=R;var Xa=["sun","mon","tue","wed","thu","fri","sat"],Ya=["year","month","week","day","hour","minute","second","millisecond"];/* Logging and Debug ----------------------------------------------------------------------------------------------------------------------*/ Ta.log=function(){var a=window.console;return a&&a.log?a.log.apply(a,arguments):void 0},Ta.warn=function(){var a=window.console;return a&&a.warn?a.warn.apply(a,arguments):Ta.log.apply(Ta,arguments)};/* General Utilities ----------------------------------------------------------------------------------------------------------------------*/ var Za,$a,_a,ab={}.hasOwnProperty,bb=/^\s*\d{4}-\d\d$/,cb=/^\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+)?)?)?)?)?$/,db=b.fn,eb=a.extend({},db);// function defined below // Creating // ------------------------------------------------------------------------------------------------- // Creates a new moment, similar to the vanilla moment(...) constructor, but with // extra features (ambiguous time, enhanced formatting). When given 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. Ta.moment=function(){return ia(arguments)}, // Sames as FC.moment, but forces the resulting moment to be in the UTC timezone. Ta.moment.utc=function(){var a=ia(arguments,!0); // Force it into UTC because makeMoment doesn't guarantee it // (if given a pre-existing moment for example) // don't give ambiguously-timed moments a UTC zone return a.hasTime()&&a.utc(),a}, // 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. Ta.moment.parseZone=function(){return ia(arguments,!0,!0)}, // A clone method that works with the flags related to our enhanced functionality. // In the future, use moment.momentProperties db.clone=function(){var a=eb.clone.apply(this,arguments); // these flags weren't transfered with the clone return ka(this,a),this._fullCalendar&&(a._fullCalendar=!0),a}, // Week Number // ------------------------------------------------------------------------------------------------- // Returns the week number, considering the locale's custom week number calcuation // `weeks` is an alias for `week` db.week=db.weeks=function(a){var b=(this._locale||this._lang)._fullCalendar_weekCalc;return null==a&&"function"==typeof b?b(this):"ISO"===b?eb.isoWeek.apply(this,arguments):eb.week.apply(this,arguments)}, // 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. db.time=function(a){ // Fallback to the original method (if there is one) if this moment wasn't created via FullCalendar. // `time` is a generic enough method name where this precaution is necessary to avoid collisions w/ other plugins. if(!this._fullCalendar)return eb.time.apply(this,arguments);if(null==a)// getter return b.duration({hours:this.hours(),minutes:this.minutes(),seconds:this.seconds(),milliseconds:this.milliseconds()});// setter this._ambigTime=!1,// mark that the moment now has a time b.isDuration(a)||b.isMoment(a)||(a=b.duration(a)); // The day value should cause overflow (so 24 hours becomes 00:00:00 of next day). // Only for Duration times, not Moment times. var c=0; // We need to set the individual fields. // Can't use startOf('day') then add duration. In case of DST at start of day. return b.isDuration(a)&&(c=24*Math.floor(a.asDays())),this.hours(c+a.hours()).minutes(a.minutes()).seconds(a.seconds()).milliseconds(a.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. db.stripTime=function(){var a; // get the values before any conversion happens // array of y/m/d/h/m/s/ms // TODO: use keepLocalTime in the future // set the internal UTC flag (will clear the ambig flags) // set the year/month/date. time will be zero // Mark the time as ambiguous. This needs to happen after the .utc() call, which might call .utcOffset(), // which clears all ambig flags. Same with setUTCValues with moment-timezone. return this._ambigTime||(a=this.toArray(),this.utc(),$a(this,a.slice(0,3)),this._ambigTime=!0,this._ambigZone=!0),this}, // Returns if the moment has a non-ambiguous time (boolean) db.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. // TODO: look into Moment's keepLocalTime functionality db.stripZone=function(){var a,b; // get the values before any conversion happens // array of y/m/d/h/m/s/ms // set the internal UTC flag (might clear the ambig flags, depending on Moment internals) // will set the year/month/date/hours/minutes/seconds/ms // the above call to .utc()/.utcOffset() unfortunately might clear the ambig flags, so restore // Mark the zone as ambiguous. This needs to happen after the .utc() call, which might call .utcOffset(), // which clears the ambig flags. Same with setUTCValues with moment-timezone. return this._ambigZone||(a=this.toArray(),b=this._ambigTime,this.utc(),$a(this,a),this._ambigTime=b||!1,this._ambigZone=!0),this}, // Returns of the moment has a non-ambiguous timezone offset (boolean) db.hasZone=function(){return!this._ambigZone}, // this method implicitly marks a zone db.local=function(){var a=this.toArray(),b=this._ambigZone; // ensure non-ambiguous // this probably already happened via local() -> utcOffset(), but don't rely on Moment's internals // If the moment was ambiguously zoned, the date fields were stored as UTC. // We want to preserve these, but in local time. // TODO: look into Moment's keepLocalTime functionality return eb.local.apply(this,arguments),this._ambigTime=!1,this._ambigZone=!1,b&&_a(this,a),this}, // implicitly marks a zone db.utc=function(){ // ensure non-ambiguous // this probably already happened via utc() -> utcOffset(), but don't rely on Moment's internals return eb.utc.apply(this,arguments),this._ambigTime=!1,this._ambigZone=!1,this}, // methods for arbitrarily manipulating timezone offset. // should clear time/zone ambiguity when called. a.each(["zone",// only in moment-pre-2.9. deprecated afterwards "utcOffset"],function(a,b){eb[b]&&(// original method exists? // this method implicitly marks a zone (will probably get called upon .utc() and .local()) db[b]=function(a){// setter // these assignments needs to happen before the original zone method is called. // I forget why, something to do with a browser crash. return null!=a&&(this._ambigTime=!1,this._ambigZone=!1),eb[b].apply(this,arguments)})}), // Formatting // ------------------------------------------------------------------------------------------------- db.format=function(){return this._fullCalendar&&arguments[0]?na(this,arguments[0]):this._ambigTime?ma(this,"YYYY-MM-DD"):this._ambigZone?ma(this,"YYYY-MM-DD[T]HH:mm:ss"):eb.format.apply(this,arguments)},db.toISOString=function(){return this._ambigTime?ma(this,"YYYY-MM-DD"):this._ambigZone?ma(this,"YYYY-MM-DD[T]HH:mm:ss"):eb.toISOString.apply(this,arguments)}, // Querying // ------------------------------------------------------------------------------------------------- // Is the moment within the specified range? `end` is exclusive. // FYI, this method is not a standard Moment method, so always do our enhanced logic. db.isWithin=function(a,b){var c=ja([this,a,b]);return c[0]>=c[1]&&c[0]a;a++)b=arguments[a],c-1>a&&xa(this,b);return wa(this,b||{})}, // Adds new member variables/methods to the class's prototype. // Can be called with another class, or a plain object hash containing new members. va.mixin=function(a){xa(this,a)};var ib=Ta.EmitterMixin={callbackHash:null,on:function(a,b){return this.loopCallbacks(a,"add",[b]),this},off:function(a,b){return this.loopCallbacks(a,"remove",[b]),this},trigger:function(a){// args... var b=Array.prototype.slice.call(arguments,1);return this.triggerWith(a,this,b),this},triggerWith:function(a,b,c){return this.loopCallbacks(a,"fireWith",[b,c]),this},/* Given an event name string with possible namespaces, call the given methodName on all the internal Callback object with the given arguments. */ loopCallbacks:function(a,b,c){var d,e,f,g=a.split(".");for(d=0;d').addClass(c.className||"").css({ // position initially to the top left to avoid creating scrollbars top:0,left:0}).append(c.content).appendTo(c.parentEl), // when a click happens on anything inside with a 'fc-close' className, hide the popover this.el.on("click",".fc-close",function(){b.hide()}),c.autoHide&&this.listenTo(a(document),"mousedown",this.documentMousedown)}, // Triggered when the user clicks *anywhere* in the document, for the autoHide feature documentMousedown:function(b){ // only hide the popover if the click happened outside the popover this.el&&!a(b.target).closest(this.el).length&&this.hide()}, // Hides and unregisters any handlers removeElement:function(){this.hide(),this.el&&(this.el.remove(),this.el=null),this.stopListeningTo(a(document),"mousedown")}, // Positions the popover optimally, using the top/left/right options position:function(){var b,c,d,e,f,g=this.options,h=this.el.offsetParent().offset(),i=this.el.outerWidth(),j=this.el.outerHeight(),k=a(window),l=m(this.el);e=g.top||0,f=void 0!==g.left?g.left:void 0!==g.right?g.right-i:0,l.is(window)||l.is(document)?(l=k,b=0,c=0):(d=l.offset(),b=d.top,c=d.left),b+=k.scrollTop(),c+=k.scrollLeft(),g.viewportConstrain!==!1&&(e=Math.min(e,b+l.outerHeight()-j-this.margin),e=Math.max(e,b+this.margin),f=Math.min(f,c+l.outerWidth()-i-this.margin),f=Math.max(f,c+this.margin)),this.el.css({top:e-h.top,left:f-h.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(a){this.options[a]&&this.options[a].apply(this,Array.prototype.slice.call(arguments,1))}}),lb=Ta.CoordCache=va.extend({els:null,// jQuery set (assumed to be siblings) forcedOffsetParentEl:null,// options can override the natural offsetParent origin:null,// {left,top} position of offsetParent of els boundingRect:null,// constrain cordinates to this rectangle. {left,right,top,bottom} or null isHorizontal:!1,// whether to query for left/right/width isVertical:!1,// whether to query for top/bottom/height // arrays of coordinates (offsets from topleft of document) lefts:null,rights:null,tops:null,bottoms:null,constructor:function(b){this.els=a(b.els),this.isHorizontal=b.isHorizontal,this.isVertical=b.isVertical,this.forcedOffsetParentEl=b.offsetParent?a(b.offsetParent):null}, // Queries the els for coordinates and stores them. // Call this method before using and of the get* methods below. build:function(){var a=this.forcedOffsetParentEl||this.els.eq(0).offsetParent();this.origin=a.offset(),this.boundingRect=this.queryBoundingRect(),this.isHorizontal&&this.buildElHorizontals(),this.isVertical&&this.buildElVerticals()}, // Destroys all internal data about coordinates, freeing memory clear:function(){this.origin=null,this.boundingRect=null,this.lefts=null,this.rights=null,this.tops=null,this.bottoms=null}, // When called, if coord caches aren't built, builds them ensureBuilt:function(){this.origin||this.build()}, // Compute and return what the elements' bounding rectangle is, from the user's perspective. // Right now, only returns a rectangle if constrained by an overflow:scroll element. queryBoundingRect:function(){var a=m(this.els.eq(0));return a.is(document)?void 0:o(a)}, // Populates the left/right internal coordinate arrays buildElHorizontals:function(){var b=[],c=[];this.els.each(function(d,e){var f=a(e),g=f.offset().left,h=f.outerWidth();b.push(g),c.push(g+h)}),this.lefts=b,this.rights=c}, // Populates the top/bottom internal coordinate arrays buildElVerticals:function(){var b=[],c=[];this.els.each(function(d,e){var f=a(e),g=f.offset().top,h=f.outerHeight();b.push(g),c.push(g+h)}),this.tops=b,this.bottoms=c}, // Given a left offset (from document left), returns the index of the el that it horizontally intersects. // If no intersection is made, or outside of the boundingRect, returns undefined. getHorizontalIndex:function(a){this.ensureBuilt();var b,c=this.boundingRect,d=this.lefts,e=this.rights,f=d.length;if(!c||a>=c.left&&ab;b++)if(a>=d[b]&&a=c.top&&ab;b++)if(a>=d[b]&&a=e*e&&this.handleDistanceSurpassed(a)),this.isDragging&&this.handleDrag(c,d,a)}, // Called while the mouse is being moved and when we know a legitimate drag is taking place handleDrag:function(a,b,c){this.trigger("drag",a,b,c),this.updateAutoScroll(c)},endDrag:function(a){this.isDragging&&(this.isDragging=!1,this.handleDragEnd(a))},handleDragEnd:function(a){this.trigger("dragEnd",a),this.destroyHrefHack()}, // Delay // ----------------------------------------------------------------------------------------------------------------- startDelay:function(a){var b=this;this.delay?this.delayTimeoutId=setTimeout(function(){b.handleDelayEnd(a)},this.delay):this.handleDelayEnd(a)},handleDelayEnd:function(a){this.isDelayEnded=!0,this.isDistanceSurpassed&&this.startDrag(a)}, // Distance // ----------------------------------------------------------------------------------------------------------------- handleDistanceSurpassed:function(a){this.isDistanceSurpassed=!0,this.isDelayEnded&&this.startDrag(a)}, // Mouse / Touch // ----------------------------------------------------------------------------------------------------------------- handleTouchMove:function(a){ // prevent inertia and touchmove-scrolling while dragging this.isDragging&&a.preventDefault(),this.handleMove(a)},handleMouseMove:function(a){this.handleMove(a)}, // Scrolling (unrelated to auto-scroll) // ----------------------------------------------------------------------------------------------------------------- handleTouchScroll:function(a){ // if the drag is being initiated by touch, but a scroll happens before // the drag-initiating delay is over, cancel the drag this.isDragging||this.endInteraction(a)}, // HREF Hack // ----------------------------------------------------------------------------------------------------------------- initHrefHack:function(){var a=this.subjectEl; // remove a mousedown'd 's href so it is not visited (IE8 bug) (this.subjectHref=a?a.attr("href"):null)&&a.removeAttr("href")},destroyHrefHack:function(){var a=this.subjectEl,b=this.subjectHref; // restore a mousedown'd 's href (for IE8 bug) setTimeout(function(){// must be outside of the click's execution b&&a.attr("href",b)},0)}, // Utils // ----------------------------------------------------------------------------------------------------------------- // Triggers a callback. Calls a function in the option hash of the same name. // Arguments beyond the first `name` are forwarded on. trigger:function(a){this.options[a]&&this.options[a].apply(this,Array.prototype.slice.call(arguments,1)), // makes _methods callable by event name. TODO: kill this this["_"+a]&&this["_"+a].apply(this,Array.prototype.slice.call(arguments,1))}});/* this.scrollEl is set in DragListener */ mb.mixin({isAutoScroll:!1,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 // defaults scrollSensitivity:30,// pixels from edge for scrolling to start scrollSpeed:200,// pixels per second, at maximum speed scrollIntervalMs:50,// millisecond wait between scroll increment initAutoScroll:function(){var a=this.scrollEl;this.isAutoScroll=this.options.scroll&&a&&!a.is(window)&&!a.is(document),this.isAutoScroll&& // debounce makes sure rapid calls don't happen this.listenTo(a,"scroll",ha(this.handleDebouncedScroll,100))},destroyAutoScroll:function(){this.endAutoScroll(),// kill any animation loop // remove the scroll handler if there is a scrollEl this.isAutoScroll&&this.stopListeningTo(this.scrollEl,"scroll")}, // Computes and stores the bounding rectangle of scrollEl computeScrollBounds:function(){this.isAutoScroll&&(this.scrollBounds=n(this.scrollEl))}, // Called when the dragging is in progress and scrolling should be updated updateAutoScroll:function(a){var b,c,d,e,f=this.scrollSensitivity,g=this.scrollBounds,h=0,i=0;g&&(b=(f-(w(a)-g.top))/f,c=(f-(g.bottom-w(a)))/f,d=(f-(v(a)-g.left))/f,e=(f-(g.right-v(a)))/f,b>=0&&1>=b?h=b*this.scrollSpeed*-1:c>=0&&1>=c&&(h=c*this.scrollSpeed),d>=0&&1>=d?i=d*this.scrollSpeed*-1:e>=0&&1>=e&&(i=e*this.scrollSpeed)),this.setScrollVel(h,i)}, // Sets the speed-of-scrolling for the scrollEl setScrollVel:function(a,b){this.scrollTopVel=a,this.scrollLeftVel=b,this.constrainScrollVel(),// massages into realistic values // if there is non-zero velocity, and an animation loop hasn't already started, then START !this.scrollTopVel&&!this.scrollLeftVel||this.scrollIntervalId||(this.scrollIntervalId=setInterval(ga(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 a=this.scrollEl;this.scrollTopVel<0?// scrolling up? a.scrollTop()<=0&&(// already scrolled all the way up? this.scrollTopVel=0):this.scrollTopVel>0&&a.scrollTop()+a[0].clientHeight>=a[0].scrollHeight&&(// already scrolled all the way down? this.scrollTopVel=0),this.scrollLeftVel<0?// scrolling left? a.scrollLeft()<=0&&(// already scrolled all the left? this.scrollLeftVel=0):this.scrollLeftVel>0&&a.scrollLeft()+a[0].clientWidth>=a[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 a=this.scrollEl,b=this.scrollIntervalMs/1e3;// considering animation frequency, what the vel should be mult'd by // change the value of scrollEl's scroll this.scrollTopVel&&a.scrollTop(a.scrollTop()+this.scrollTopVel*b),this.scrollLeftVel&&a.scrollLeft(a.scrollLeft()+this.scrollLeftVel*b),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 this.scrollTopVel||this.scrollLeftVel||this.endAutoScroll()}, // Kills any existing scrolling animation loop endAutoScroll:function(){this.scrollIntervalId&&(clearInterval(this.scrollIntervalId),this.scrollIntervalId=null,this.handleScrollEnd())}, // Get called when the scrollEl is scrolled (NOTE: this is delayed via debounce) handleDebouncedScroll:function(){ // recompute all coordinates, but *only* if this is *not* part of our scrolling animation this.scrollIntervalId||this.handleScrollEnd()}, // Called when scrolling has stopped, whether through auto scroll, or the user scrolling handleScrollEnd:function(){}});/* Tracks mouse movements over a component and raises events about which hit the mouse is over. ------------------------------------------------------------------------------------------------------------------------ options: - subjectEl - subjectCenter */ var nb=mb.extend({component:null,// converts coordinates to hits // methods: prepareHits, releaseHits, queryHit origHit:null,// the hit the mouse was over when listening started hit:null,// the hit the mouse is over coordAdjust:null,// delta that will be added to the mouse coordinates when computing collisions constructor:function(a,b){mb.call(this,b),// call the super-constructor this.component=a}, // Called when drag listening starts (but a real drag has not necessarily began). // ev might be undefined if dragging was started manually. handleInteractionStart:function(a){var b,c,d,e=this.subjectEl;this.computeCoords(),a?(c={left:v(a),top:w(a)},d=c,e&&(b=n(e),d=B(d,b)),this.origHit=this.queryHit(d.left,d.top),e&&this.options.subjectCenter&&(this.origHit&&(b=A(this.origHit,b)||b),d=C(b)),this.coordAdjust=D(d,c)):(this.origHit=null,this.coordAdjust=null), // call the super-method. do it after origHit has been computed mb.prototype.handleInteractionStart.apply(this,arguments)}, // Recomputes the drag-critical positions of elements computeCoords:function(){this.component.prepareHits(),this.computeScrollBounds()}, // Called when the actual drag has started handleDragStart:function(a){var b;mb.prototype.handleDragStart.apply(this,arguments),b=this.queryHit(v(a),w(a)),b&&this.handleHitOver(b)}, // Called when the drag moves handleDrag:function(a,b,c){var d;mb.prototype.handleDrag.apply(this,arguments),d=this.queryHit(v(c),w(c)),ya(d,this.hit)||(this.hit&&this.handleHitOut(),d&&this.handleHitOver(d))}, // Called when dragging has been stopped handleDragEnd:function(){this.handleHitDone(),mb.prototype.handleDragEnd.apply(this,arguments)}, // Called when a the mouse has just moved over a new hit handleHitOver:function(a){var b=ya(a,this.origHit);this.hit=a,this.trigger("hitOver",this.hit,b,this.origHit)}, // Called when the mouse has just moved out of a hit handleHitOut:function(){this.hit&&(this.trigger("hitOut",this.hit),this.handleHitDone(),this.hit=null)}, // Called after a hitOut. Also called before a dragStop handleHitDone:function(){this.hit&&this.trigger("hitDone",this.hit)}, // Called when the interaction ends, whether there was a real drag or not handleInteractionEnd:function(){mb.prototype.handleInteractionEnd.apply(this,arguments),// call the super-method this.origHit=null,this.hit=null,this.component.releaseHits()}, // Called when scrolling has stopped, whether through auto scroll, or the user scrolling handleScrollEnd:function(){mb.prototype.handleScrollEnd.apply(this,arguments),// call the super-method this.computeCoords()}, // Gets the hit underneath the coordinates for the given mouse event queryHit:function(a,b){return this.coordAdjust&&(a+=this.coordAdjust.left,b+=this.coordAdjust.top),this.component.queryHit(a,b)}}),ob=va.extend(jb,{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 absolute coordinates of the initiating touch/mouse action y0:null,x0:null, // the number of pixels the mouse has moved from its initial position topDelta:null,leftDelta:null,isFollowing:!1,isHidden:!1,isAnimating:!1,// doing the revert animation? constructor:function(b,c){this.options=c=c||{},this.sourceEl=b,this.parentEl=c.parentEl?a(c.parentEl):b.parent()}, // Causes the element to start following the mouse start:function(b){this.isFollowing||(this.isFollowing=!0,this.y0=w(b),this.x0=v(b),this.topDelta=0,this.leftDelta=0,this.isHidden||this.updatePosition(),x(b)?this.listenTo(a(document),"touchmove",this.handleMove):this.listenTo(a(document),"mousemove",this.handleMove))}, // 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(b,c){function d(){this.isAnimating=!1,e.removeElement(),this.top0=this.left0=null,// reset state for future updatePosition calls c&&c()}var e=this,f=this.options.revertDuration;this.isFollowing&&!this.isAnimating&&(// disallow more than one stop animation at a time this.isFollowing=!1,this.stopListeningTo(a(document)),b&&f&&!this.isHidden?(// do a revert animation? this.isAnimating=!0,this.el.animate({top:this.top0,left:this.left0},{duration:f,complete:d})):d())}, // Gets the tracking element. Create it if necessary getEl:function(){var a=this.el;// hack to force IE8 to compute correct bounding box // we don't want long taps or any mouse interaction causing selection/menus. // would use preventSelection(), but that prevents selectstart, causing problems. return a||(this.sourceEl.width(),a=this.el=this.sourceEl.clone().addClass(this.options.additionalClass||"").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}),a.addClass("fc-unselectable"),a.appendTo(this.parentEl)),a}, // Removes the tracking element if it has already been created removeElement:function(){this.el&&(this.el.remove(),this.el=null)}, // Update the CSS position of the tracking element updatePosition:function(){var a,b;this.getEl(),// ensure this.el // make sure origin info was computed null===this.top0&&(this.sourceEl.width(),a=this.sourceEl.offset(),b=this.el.offsetParent().offset(),this.top0=a.top-b.top,this.left0=a.left-b.left),this.el.css({top:this.top0+this.topDelta,left:this.left0+this.leftDelta})}, // Gets called when the user moves the mouse handleMove:function(a){this.topDelta=w(a)-this.y0,this.leftDelta=v(a)-this.x0,this.isHidden||this.updatePosition()}, // Temporarily makes the tracking element invisible. Can be called before following starts hide:function(){this.isHidden||(this.isHidden=!0,this.el&&this.el.hide())}, // Show the tracking element after it has been temporarily hidden show:function(){this.isHidden&&(this.isHidden=!1,this.updatePosition(),this.getEl().show())}}),pb=Ta.Grid=va.extend(jb,{view:null,// a View object isRTL:null,// shortcut to the view's isRTL option start:null,end:null,el:null,// the containing element elsByFill:null,// a hash of jQuery element sets used for rendering each fill. Keyed by fill name. // derived from options eventTimeFormat:null,displayEventTime:null,displayEventEnd:null,minResizeDuration:null,// TODO: hack. set by subclasses. minumum event resize duration // if defined, holds the unit identified (ex: "year" or "month") that determines the level of granularity // of the date areas. if not defined, assumes to be day and time granularity. // TODO: port isTimeScale into same system? largeUnit:null,dayDragListener:null,segDragListener:null,segResizeListener:null,externalDragListener:null,constructor:function(a){this.view=a,this.isRTL=a.opt("isRTL"),this.elsByFill={}},/* Options ------------------------------------------------------------------------------------------------------------------*/ // Generates the format string used for event time text, if not explicitly defined by 'timeFormat' computeEventTimeFormat:function(){return this.view.opt("smallTimeFormat")}, // Determines whether events should have their end times displayed, if not explicitly defined by 'displayEventTime'. // Only applies to non-all-day events. computeDisplayEventTime:function(){return!0}, // Determines whether events should have their end times displayed, if not explicitly defined by 'displayEventEnd' computeDisplayEventEnd:function(){return!0},/* Dates ------------------------------------------------------------------------------------------------------------------*/ // Tells the grid about what period of time to display. // Any date-related internal data should be generated. setRange:function(a){this.start=a.start.clone(),this.end=a.end.clone(),this.rangeUpdated(),this.processRangeOptions()}, // Called when internal variables that rely on the range should be updated rangeUpdated:function(){}, // Updates values that rely on options and also relate to range processRangeOptions:function(){var a,b,c=this.view;this.eventTimeFormat=c.opt("eventTimeFormat")||c.opt("timeFormat")||// deprecated this.computeEventTimeFormat(),a=c.opt("displayEventTime"),null==a&&(a=this.computeDisplayEventTime()),b=c.opt("displayEventEnd"),null==b&&(b=this.computeDisplayEventEnd()),this.displayEventTime=a,this.displayEventEnd=b}, // Converts a span (has unzoned start/end and any other grid-specific location information) // into an array of segments (pieces of events whose format is decided by the grid). spanToSegs:function(a){}, // Diffs the two dates, returning a duration, based on granularity of the grid // TODO: port isTimeScale into this system? diffDates:function(a,b){return this.largeUnit?L(a,b,this.largeUnit):J(a,b)},/* Hit Area ------------------------------------------------------------------------------------------------------------------*/ // Called before one or more queryHit calls might happen. Should prepare any cached coordinates for queryHit prepareHits:function(){}, // Called when queryHit calls have subsided. Good place to clear any coordinate caches. releaseHits:function(){}, // Given coordinates from the topleft of the document, return data about the date-related area underneath. // Can return an object with arbitrary properties (although top/right/left/bottom are encouraged). // Must have a `grid` property, a reference to this current grid. TODO: avoid this // The returned object will be processed by getHitSpan and getHitEl. queryHit:function(a,b){}, // Given position-level information about a date-related area within the grid, // should return an object with at least a start/end date. Can provide other information as well. getHitSpan:function(a){}, // Given position-level information about a date-related area within the grid, // should return a jQuery element that best represents it. passed to dayClick callback. getHitEl:function(a){},/* Rendering ------------------------------------------------------------------------------------------------------------------*/ // Sets the container element that the grid should render inside of. // Does other DOM-related initializations. setElement:function(a){this.el=a,y(a),this.view.calendar.isTouch?this.bindDayHandler("touchstart",this.dayTouchStart):this.bindDayHandler("mousedown",this.dayMousedown), // attach event-element-related handlers. in Grid.events // same garbage collection note as above. this.bindSegHandlers(),this.bindGlobalHandlers()},bindDayHandler:function(b,c){var d=this; // attach a handler to the grid's root element. // jQuery will take care of unregistering them when removeElement gets called. this.el.on(b,function(b){return a(b.target).is(".fc-event-container *, .fc-more")||a(b.target).closest(".fc-popover").length?void 0:c.call(d,b)})}, // Removes the grid's container element from the DOM. Undoes any other DOM-related attachments. // DOES NOT remove any content beforehand (doesn't clear events or call unrenderDates), unlike View removeElement:function(){this.unbindGlobalHandlers(),this.clearDragListeners(),this.el.remove()}, // Renders the basic structure of grid view before any content is rendered renderSkeleton:function(){}, // Renders the grid's date-related content (like areas that represent days/times). // Assumes setRange has already been called and the skeleton has already been rendered. renderDates:function(){}, // Unrenders the grid's date-related content unrenderDates:function(){},/* Handlers ------------------------------------------------------------------------------------------------------------------*/ // Binds DOM handlers to elements that reside outside the grid, such as the document bindGlobalHandlers:function(){this.listenTo(a(document),{dragstart:this.externalDragStart,// jqui sortstart:this.externalDragStart})}, // Unbinds DOM handlers from elements that reside outside the grid unbindGlobalHandlers:function(){this.stopListeningTo(a(document))}, // Process a mousedown on an element that represents a day. For day clicking and selecting. dayMousedown:function(a){this.clearDragListeners(),this.buildDayDragListener().startInteraction(a,{})},dayTouchStart:function(a){this.clearDragListeners(),this.buildDayDragListener().startInteraction(a,{delay:this.view.opt("longPressDelay")})}, // Creates a listener that tracks the user's drag across day elements. // For day clicking and selecting. buildDayDragListener:function(){var a,b,c=this,d=this.view,e=d.opt("selectable"),f=this.dayDragListener=new nb(this,{scroll:d.opt("dragScroll"),interactionStart:function(){a=f.origHit},dragStart:function(){d.unselect()},hitOver:function(d,f,h){h&&(// click needs to have started on a hit // if user dragged to another cell at any point, it can no longer be a dayClick f||(a=null),e&&(b=c.computeSelection(c.getHitSpan(h),c.getHitSpan(d)),b?c.renderSelection(b):b===!1&&g()))},hitOut:function(){a=null,b=null,c.unrenderSelection(),h()},interactionEnd:function(e){a&&d.triggerDayClick(c.getHitSpan(a),c.getHitEl(a),e),b&& // the selection will already have been rendered. just report it d.reportSelection(b,e),h(),c.dayDragListener=null}});return f}, // Kills all in-progress dragging. // Useful for when public API methods that result in re-rendering are invoked during a drag. // Also useful for when touch devices misbehave and don't fire their touchend. clearDragListeners:function(){this.dayDragListener&&this.dayDragListener.endInteraction(),this.segDragListener&&this.segDragListener.endInteraction(),this.segResizeListener&&this.segResizeListener.endInteraction(),this.externalDragListener&&this.externalDragListener.endInteraction()},/* Event Helper ------------------------------------------------------------------------------------------------------------------*/ // TODO: should probably move this to Grid.events, like we did event dragging / resizing // Renders a mock event at the given event location, which contains zoned start/end properties. // Returns all mock event elements. renderEventLocationHelper:function(a,b){var c=this.fabricateHelperEvent(a,b);return this.renderHelper(c,b)}, // Builds a fake event given zoned event date properties and a segment is should be inspired from. // The range'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. fabricateHelperEvent:function(a,b){var c=b?V(b.event):{};// mask the original event object if possible // force it to be freshly computed by normalizeEventDates // this extra className will be useful for differentiating real events from mock events in CSS // if something external is being dragged in, don't render a resizer return c.start=a.start.clone(),c.end=a.end?a.end.clone():null,c.allDay=null,this.view.calendar.normalizeEventDates(c),c.className=(c.className||[]).concat("fc-helper"),b||(c.editable=!1),c}, // Renders a mock event. Given zoned event date properties. // Must return all mock event elements. renderHelper:function(a,b){}, // Unrenders a mock event unrenderHelper:function(){},/* Selection ------------------------------------------------------------------------------------------------------------------*/ // Renders a visual indication of a selection. Will highlight by default but can be overridden by subclasses. // Given a span (unzoned start/end and other misc data) renderSelection:function(a){this.renderHighlight(a)}, // Unrenders any visual indications of a selection. Will unrender a highlight by default. unrenderSelection:function(){this.unrenderHighlight()}, // Given the first and last date-spans of a selection, returns another date-span object. // Subclasses can override and provide additional data in the span object. Will be passed to renderSelection(). // Will return false if the selection is invalid and this should be indicated to the user. // Will return null/undefined if a selection invalid but no error should be reported. computeSelection:function(a,b){var c=this.computeSelectionSpan(a,b);return c&&!this.view.calendar.isSelectionSpanAllowed(c)?!1:c}, // Given two spans, must return the combination of the two. // TODO: do this separation of concerns (combining VS validation) for event dnd/resize too. computeSelectionSpan:function(a,b){var c=[a.start,a.end,b.start,b.end];// sorts chronologically. works with Moments return c.sort(ea),{start:c[0].clone(),end:c[3].clone()}},/* Highlight ------------------------------------------------------------------------------------------------------------------*/ // Renders an emphasis on the given date range. Given a span (unzoned start/end and other misc data) renderHighlight:function(a){this.renderFill("highlight",this.spanToSegs(a))}, // Unrenders the emphasis on a date range unrenderHighlight:function(){this.unrenderFill("highlight")}, // Generates an array of classNames for rendering the highlight. Used by the fill system. highlightSegClasses:function(){return["fc-highlight"]},/* Business Hours ------------------------------------------------------------------------------------------------------------------*/ renderBusinessHours:function(){},unrenderBusinessHours:function(){},/* Now Indicator ------------------------------------------------------------------------------------------------------------------*/ getNowIndicatorUnit:function(){},renderNowIndicator:function(a){},unrenderNowIndicator:function(){},/* Fill System (highlight, background events, business hours) -------------------------------------------------------------------------------------------------------------------- TODO: remove this system. like we did in TimeGrid */ // Renders a set of rectangles over the given segments of time. // MUST RETURN a subset of segs, the segs that were actually rendered. // Responsible for populating this.elsByFill. TODO: better API for expressing this requirement renderFill:function(a,b){}, // Unrenders a specific type of fill that is currently rendered on the grid unrenderFill:function(a){var b=this.elsByFill[a];b&&(b.remove(),delete this.elsByFill[a])}, // Renders and assigns an `el` property for each fill segment. Generic enough to work with different types. // Only returns segments that successfully rendered. // To be harnessed by renderFill (implemented by subclasses). // Analagous to renderFgSegEls. renderFillSegEls:function(b,c){var d,e=this,f=this[b+"SegEl"],g="",h=[];if(c.length){ // build a large concatenation of segment HTML for(d=0;d"},/* Generic rendering utilities for subclasses ------------------------------------------------------------------------------------------------------------------*/ // Computes HTML classNames for a single-day element getDayClasses:function(a){var b=this.view,c=b.calendar.getNow(),d=["fc-"+Xa[a.day()]];return 1==b.intervalDuration.as("months")&&a.month()!=b.intervalStart.month()&&d.push("fc-other-month"),a.isSame(c,"day")?d.push("fc-today",b.highlightStateClass):c>a?d.push("fc-past"):d.push("fc-future"),d}});/* Event-rendering and event-interaction methods for the abstract Grid class ----------------------------------------------------------------------------------------------------------------------*/ pb.mixin({mousedOverSeg:null,// the segment object the user's mouse is over. null if over nothing isDraggingSeg:!1,// is a segment being dragged? boolean isResizingSeg:!1,// is a segment being resized? boolean isDraggingExternal:!1,// jqui-dragging an external element? boolean segs:null,// the *event* segments currently rendered in the grid. TODO: rename to `eventSegs` // Renders the given events onto the grid renderEvents:function(a){var b,c=[],d=[];for(b=0;b *",function(b){var e=a(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 // grab segment data. put there by View::renderEvents // only call the handlers if there is not a drag/resize in progress return!e||d.isDraggingSeg||d.isResizingSeg?void 0:c.call(d,e,b)})},handleSegClick:function(a,b){return this.view.trigger("eventClick",a.el[0],a.event,b)}, // Updates internal state and triggers handlers for when an event element is moused over handleSegMouseover:function(a,b){this.mousedOverSeg||(this.mousedOverSeg=a,this.view.trigger("eventMouseover",a.el[0],a.event,b))}, // 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. handleSegMouseout:function(a,b){b=b||{},this.mousedOverSeg&&(a=a||this.mousedOverSeg,this.mousedOverSeg=null,this.view.trigger("eventMouseout",a.el[0],a.event,b))},handleSegTouchStart:function(a,b){var c,d=this.view,e=a.event,f=d.isEventSelected(e),g=d.isEventDraggable(e),h=d.isEventResizable(e),i=!1;f&&h&&( // only allow resizing of the event is selected i=this.startSegResize(a,b)),i||!g&&!h||(// allowed to be selected? this.clearDragListeners(),c=g?this.buildSegDragListener(a):new mb,c._dragStart=function(){f||d.selectEvent(e)},c.startInteraction(b,{delay:f?0:this.view.opt("longPressDelay")}))},handleSegMousedown:function(a,b){var c=this.startSegResize(a,b,{distance:5});!c&&this.view.isEventDraggable(a.event)&&(this.clearDragListeners(),this.buildSegDragListener(a).startInteraction(b,{distance:5}))}, // returns boolean whether resizing actually started or not. // assumes the seg allows resizing. // `dragOptions` are optional. startSegResize:function(b,c,d){return a(c.target).is(".fc-resizer")?(this.clearDragListeners(),this.buildSegResizeListener(b,a(c.target).is(".fc-start-resizer")).startInteraction(c,d),!0):!1},/* Event Dragging ------------------------------------------------------------------------------------------------------------------*/ // Builds a listener that will track user-dragging on an event segment. // Generic enough to work with any type of Grid. buildSegDragListener:function(a){var b,c,d,e=this,f=this.view,i=f.calendar,j=a.el,k=a.event,l=this.segDragListener=new nb(f,{scroll:f.opt("dragScroll"),subjectEl:j,subjectCenter:!0,interactionStart:function(d){b=!1,c=new ob(a.el,{additionalClass:"fc-dragging",parentEl:f.el,opacity:l.isTouch?null:f.opt("dragOpacity"),revertDuration:f.opt("dragRevertDuration"),zIndex:2}),c.hide(),c.start(d)},dragStart:function(c){b=!0,e.handleSegMouseout(a,c),e.segDragStart(a,c),f.hideEvent(k)},hitOver:function(b,h,j){var m; // starting hit could be forced (DayGrid.limit) a.hit&&(j=a.hit),d=e.computeEventDrop(j.component.getHitSpan(j),b.component.getHitSpan(b),k),d&&!i.isEventSpanAllowed(e.eventToSpan(d),k)&&(g(),d=null),d&&(m=f.renderDrag(d,a))?(m.addClass("fc-dragging"),l.isTouch||e.applyDragOpacity(m),c.hide()):c.show(),h&&(d=null)},hitOut:function(){// called before mouse moves to a different hit OR moved out of all hits f.unrenderDrag(),// unrender whatever was done in renderDrag c.show(),// show in case we are moving out of all hits d=null},hitDone:function(){// Called after a hitOut OR before a dragEnd h()},interactionEnd:function(g){ // do revert animation if hasn't changed. calls a callback when finished (whether animation or not) c.stop(!d,function(){b&&(f.unrenderDrag(),f.showEvent(k),e.segDragStop(a,g)),d&&f.reportEventDrop(k,d,this.largeUnit,j,g)}),e.segDragListener=null}});return l}, // Called before event segment dragging starts segDragStart:function(a,b){this.isDraggingSeg=!0,this.view.trigger("eventDragStart",a.el[0],a.event,b,{})}, // Called after event segment dragging stops segDragStop:function(a,b){this.isDraggingSeg=!1,this.view.trigger("eventDragStop",a.el[0],a.event,b,{})}, // Given the spans an event drag began, and the span event was dropped, calculates the new zoned start/end/allDay // values for the event. Subclasses may override and set additional properties to be used by renderDrag. // A falsy returned value indicates an invalid drop. // DOES NOT consider overlap/constraint. computeEventDrop:function(a,b,c){var d,e,f=this.view.calendar,g=a.start,h=b.start;// zoned event date properties // if an all-day event was in a timed area and it was dragged to a different time, // guarantee an end and adjust start/end to have times // if switching from day <-> timed, start should be reset to the dropped date, and the end cleared return g.hasTime()===h.hasTime()?(d=this.diffDates(h,g),c.allDay&&R(d)?(e={start:c.start.clone(),end:f.getEventEnd(c),allDay:!1},f.normalizeEventTimes(e)):e={start:c.start.clone(),end:c.end?c.end.clone():null,allDay:c.allDay},e.start.add(d),e.end&&e.end.add(d)):e={start:h.clone(),end:null,// end should be cleared allDay:!h.hasTime()},e}, // Utility for apply dragOpacity to a jQuery set applyDragOpacity:function(a){var b=this.view.opt("dragOpacity");null!=b&&a.each(function(a,c){ // Don't use jQuery (will set an IE filter), do it the old fashioned way. // In IE8, a helper element will disappears if there's a filter. c.style.opacity=b})},/* External Element Dragging ------------------------------------------------------------------------------------------------------------------*/ // Called when a jQuery UI drag is initiated anywhere in the DOM externalDragStart:function(b,c){var d,e,f=this.view;f.opt("droppable")&&(d=a((c?c.item:null)||b.target),e=f.opt("dropAccept"),(a.isFunction(e)?e.call(d[0],d):d.is(e))&&(this.isDraggingExternal||this.listenToExternalDrag(d,b,c)))}, // Called when a jQuery UI drag starts and it needs to be monitored for dropping listenToExternalDrag:function(a,b,c){var d,e=this,f=this.view.calendar,i=Fa(a),j=e.externalDragListener=new nb(this,{interactionStart:function(){e.isDraggingExternal=!0},hitOver:function(a){d=e.computeExternalDrop(a.component.getHitSpan(a),// since we are querying the parent view, might not belong to this grid i),d&&!f.isExternalSpanAllowed(e.eventToSpan(d),d,i.eventProps)&&(g(),d=null),d&&e.renderDrag(d)},hitOut:function(){d=null},hitDone:function(){// Called after a hitOut OR before a dragEnd h(),e.unrenderDrag()},interactionEnd:function(b){d&&// element was dropped on a valid hit e.view.reportExternalDrop(i,d,a,b,c),e.isDraggingExternal=!1,e.externalDragListener=null}});j.startDrag(b)}, // Given a hit to be dropped upon, and misc data associated with the jqui drag (guaranteed to be a plain object), // returns the zoned start/end dates for the event that would result from the hypothetical drop. end might be null. // Returning a null value signals an invalid drop hit. // DOES NOT consider overlap/constraint. computeExternalDrop:function(a,b){var c=this.view.calendar,d={start:c.applyTimezone(a.start),// simulate a zoned event start date end:null}; // if dropped on an all-day span, and element's metadata specified a time, set it return b.startTime&&!d.start.hasTime()&&d.start.time(b.startTime),b.duration&&(d.end=d.start.clone().add(b.duration)),d},/* Drag Rendering (for both events and an external elements) ------------------------------------------------------------------------------------------------------------------*/ // Renders a visual indication of an event or external element being dragged. // `dropLocation` contains hypothetical start/end/allDay values the event would have if dropped. end can be null. // `seg` is the internal segment object that is being dragged. If dragging an external element, `seg` is null. // A truthy returned value indicates this method has rendered a helper element. // Must return elements used for any mock events. renderDrag:function(a,b){}, // Unrenders a visual indication of an event or external element being dragged unrenderDrag:function(){},/* Resizing ------------------------------------------------------------------------------------------------------------------*/ // Creates a listener that tracks the user as they resize an event segment. // Generic enough to work with any type of Grid. buildSegResizeListener:function(a,b){var c,d,e=this,f=this.view,i=f.calendar,j=a.el,k=a.event,l=i.getEventEnd(k),m=this.segResizeListener=new nb(this,{scroll:f.opt("dragScroll"),subjectEl:j,interactionStart:function(){c=!1},dragStart:function(b){c=!0,e.handleSegMouseout(a,b),e.segResizeStart(a,b)},hitOver:function(c,h,j){var m=e.getHitSpan(j),n=e.getHitSpan(c);d=b?e.computeEventStartResize(m,n,k):e.computeEventEndResize(m,n,k),d&&(i.isEventSpanAllowed(e.eventToSpan(d),k)?d.start.isSame(k.start)&&d.end.isSame(l)&&(d=null):(g(),d=null)),d&&(f.hideEvent(k),e.renderEventResize(d,a))},hitOut:function(){// called before mouse moves to a different hit OR moved out of all hits d=null},hitDone:function(){// resets the rendering to show the original event e.unrenderEventResize(),f.showEvent(k),h()},interactionEnd:function(b){c&&e.segResizeStop(a,b),d&&// valid date to resize to? f.reportEventResize(k,d,this.largeUnit,j,b),e.segResizeListener=null}});return m}, // Called before event segment resizing starts segResizeStart:function(a,b){this.isResizingSeg=!0,this.view.trigger("eventResizeStart",a.el[0],a.event,b,{})}, // Called after event segment resizing stops segResizeStop:function(a,b){this.isResizingSeg=!1,this.view.trigger("eventResizeStop",a.el[0],a.event,b,{})}, // Returns new date-information for an event segment being resized from its start computeEventStartResize:function(a,b,c){return this.computeEventResize("start",a,b,c)}, // Returns new date-information for an event segment being resized from its end computeEventEndResize:function(a,b,c){return this.computeEventResize("end",a,b,c)}, // Returns new zoned date information for an event segment being resized from its start OR end // `type` is either 'start' or 'end'. // DOES NOT consider overlap/constraint. computeEventResize:function(a,b,c,d){var e,f,g=this.view.calendar,h=this.diffDates(c[a],b[a]); // build original values to work from, guaranteeing a start and end // if an all-day event was in a timed area and was resized to a time, adjust start/end to have times // apply delta to start or end // if the event was compressed too small, find a new reasonable duration for it // TODO: hack // resizing the start? // resizing the end? return e={start:d.start.clone(),end:g.getEventEnd(d),allDay:d.allDay},e.allDay&&R(h)&&(e.allDay=!1,g.normalizeEventTimes(e)),e[a].add(h),e.start.isBefore(e.end)||(f=this.minResizeDuration||(d.allDay?g.defaultAllDayEventDuration:g.defaultTimedEventDuration),"start"==a?e.start=e.end.clone().subtract(f):e.end=e.start.clone().add(f)),e}, // Renders a visual indication of an event being resized. // `range` has the updated dates of the event. `seg` is the original segment object involved in the drag. // Must return elements used for any mock events. renderEventResize:function(a,b){}, // Unrenders a visual indication of an event being resized. unrenderEventResize:function(){},/* Rendering Utils ------------------------------------------------------------------------------------------------------------------*/ // Compute the text that should be displayed on an event's element. // `range` can be the Event object itself, or something range-like, with at least a `start`. // If event times are disabled, or the event has no time, will return a blank string. // If not specified, formatStr will default to the eventTimeFormat setting, // and displayEnd will default to the displayEventEnd setting. getEventTimeText:function(a,b,c){return null==b&&(b=this.eventTimeFormat),null==c&&(c=this.displayEventEnd),this.displayEventTime&&a.start.hasTime()?c&&a.end?this.view.formatRange(a,b):a.start.format(b):""}, // Generic utility for generating the HTML classNames for an event segment's element getSegClasses:function(a,b,c){var d=this.view,e=a.event,f=["fc-event",a.isStart?"fc-start":"fc-not-start",a.isEnd?"fc-end":"fc-not-end"].concat(e.className,e.source?e.source.className:[]); // event is currently selected? attach a className. return b&&f.push("fc-draggable"),c&&f.push("fc-resizable"),d.isEventSelected(e)&&f.push("fc-selected"),f}, // Utility for generating event skin-related CSS properties getSegSkinCss:function(a){var b=a.event,c=this.view,d=b.source||{},e=b.color,f=d.color,g=c.opt("eventColor");return{"background-color":b.backgroundColor||e||d.backgroundColor||f||c.opt("eventBackgroundColor")||g,"border-color":b.borderColor||e||d.borderColor||f||c.opt("eventBorderColor")||g,color:b.textColor||d.textColor||c.opt("eventTextColor")}},/* Converting events -> eventRange -> eventSpan -> eventSegs ------------------------------------------------------------------------------------------------------------------*/ // Generates an array of segments for the given single event // Can accept an event "location" as well (which only has start/end and no allDay) eventToSegs:function(a){return this.eventsToSegs([a])},eventToSpan:function(a){return this.eventToSpans(a)[0]}, // Generates spans (always unzoned) for the given event. // Does not do any inverting for inverse-background events. // Can accept an event "location" as well (which only has start/end and no allDay) eventToSpans:function(a){var b=this.eventToRange(a);return this.eventRangeToSpans(b,a)}, // Converts an array of event objects into an array of event segment objects. // A custom `segSliceFunc` may be given for arbitrarily slicing up events. // Doesn't guarantee an order for the resulting array. eventsToSegs:function(b,c){var d=this,e=Da(b),f=[];return a.each(e,function(a,b){var e,g=[];for(e=0;eh&&g.push({start:h,end:c.start}),h=c.end; // add the span of time after the last event (if there is any) // compare millisecond time (skip any ambig logic) return f>h&&g.push({start:h,end:f}),g},sortEventSegs:function(a){a.sort(ga(this,"compareEventSegs"))}, // A cmp function for determining which segments should take visual priority compareEventSegs:function(a,b){// earlier events go first // tie? longer events go first // tie? put all-day events first (booleans cast to 0/1) return a.eventStartMS-b.eventStartMS||b.eventDurationMS-a.eventDurationMS||b.event.allDay-a.event.allDay||F(a.event,b.event,this.view.eventOrderSpecs)}}),Ta.isBgEvent=Aa,/* External-Dragging-Element Data ----------------------------------------------------------------------------------------------------------------------*/ // Require all HTML5 data-* attributes used by FullCalendar to have this prefix. // A value of '' will query attributes like data-event. A value of 'fc' will query attributes like data-fc-event. Ta.dataAttrPrefix="";/* A set of rendering and date-related methods for a visual component comprised of one or more rows of day columns. Prerequisite: the object being mixed into needs to be a *Grid* */ var qb=Ta.DayTableMixin={breakOnWeeks:!1,// should create a new row for each week? dayDates:null,// whole-day dates for each column. left to right dayIndices:null,// for each day from start, the offset daysPerRow:null,rowCnt:null,colCnt:null,colHeadFormat:null, // Populates internal variables used for date calculation and rendering updateDayTable:function(){for(var a,b,c,d=this.view,e=this.start.clone(),f=-1,g=[],h=[];e.isBefore(this.end);)// loop each day from start to end d.isHiddenDay(e)?g.push(f+.5):(f++,g.push(f),h.push(e.clone())),e.add(1,"days");if(this.breakOnWeeks){for(b=h[0].day(),a=1;ac?b[0]-1:c>=b.length?b[b.length-1]+1:b[c]},/* Options ------------------------------------------------------------------------------------------------------------------*/ // Computes a default column header formatting string if `colFormat` is not explicitly defined computeColHeadFormat:function(){ // if more than one week row, or if there are a lot of columns with not much space, // put just the day numbers will be in each cell // if more than one week row, or if there are a lot of columns with not much space, // put just the day numbers will be in each cell return this.rowCnt>1||this.colCnt>10?"ddd":this.colCnt>1?this.view.opt("dayOfMonthFormat"):"dddd"},/* Slicing ------------------------------------------------------------------------------------------------------------------*/ // Slices up a date range into a segment for every week-row it intersects with sliceRangeByRow:function(a){var b,c,d,e,f,g=this.daysPerRow,h=this.view.computeDayRange(a),i=this.getDateDayIndex(h.start),j=this.getDateDayIndex(h.end.clone().subtract(1,"days")),k=[];// inclusive day-index range for segment for(b=0;b=e&&k.push({row:b,firstRowDayIndex:e-c,lastRowDayIndex:f-c,isStart:e===i,isEnd:f===j});return k}, // Slices up a date range into a segment for every day-cell it intersects with. // TODO: make more DRY with sliceRangeByRow somehow. sliceRangeByDay:function(a){var b,c,d,e,f,g,h=this.daysPerRow,i=this.view.computeDayRange(a),j=this.getDateDayIndex(i.start),k=this.getDateDayIndex(i.end.clone().subtract(1,"days")),l=[];// inclusive day-index range for segment for(b=0;b=e;e++)f=Math.max(j,e),g=Math.min(k,e),f=Math.ceil(f),g=Math.floor(g),g>=f&&l.push({row:b,firstRowDayIndex:f-c,lastRowDayIndex:g-c,isStart:f===j,isEnd:g===k});return l},/* Header Rendering ------------------------------------------------------------------------------------------------------------------*/ renderHeadHtml:function(){var a=this.view;return'
'+this.renderHeadTrHtml()+"
"},renderHeadIntroHtml:function(){return this.renderIntroHtml()},renderHeadTrHtml:function(){return""+(this.isRTL?"":this.renderHeadIntroHtml())+this.renderHeadDateCellsHtml()+(this.isRTL?this.renderHeadIntroHtml():"")+""},renderHeadDateCellsHtml:function(){var a,b,c=[];for(a=0;a1?' colspan="'+b+'"':"")+(c?" "+c:"")+">"+aa(a.format(this.colHeadFormat))+""},/* Background Rendering ------------------------------------------------------------------------------------------------------------------*/ renderBgTrHtml:function(a){return""+(this.isRTL?"":this.renderBgIntroHtml(a))+this.renderBgCellsHtml(a)+(this.isRTL?this.renderBgIntroHtml(a):"")+""},renderBgIntroHtml:function(a){return this.renderIntroHtml()},renderBgCellsHtml:function(a){var b,c,d=[];for(b=0;b"},/* Generic ------------------------------------------------------------------------------------------------------------------*/ // Generates the default HTML intro for any row. User classes should override renderIntroHtml:function(){}, // TODO: a generic method for dealing with , RTL, intro // when increment internalApiVersion // wrapTr (scheduler) /* Utils ------------------------------------------------------------------------------------------------------------------*/ // Applies the generic "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. bookendCells:function(a){var b=this.renderIntroHtml();b&&(this.isRTL?a.append(b):a.prepend(b))}},rb=Ta.DayGrid=pb.extend(qb,{numbersVisible:!1,// should render a row for day/week numbers? set by outside view. TODO: make internal bottomCoordPadding:0,// hack for extending the hit area for the last row of the coordinate grid rowEls:null,// set of fake row elements cellEls:null,// set of whole-day elements comprising the row's background helperEls:null,// set of cell skeleton elements for rendering the mock event "helper" rowCoordCache:null,colCoordCache:null, // 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. renderDates:function(a){var b,c,d=this.view,e=this.rowCnt,f=this.colCnt,g="";for(b=0;e>b;b++)g+=this.renderDayRowHtml(b,a); // trigger dayRender with each cell's element for(this.el.html(g),this.rowEls=this.el.find(".fc-row"),this.cellEls=this.el.find(".fc-day"),this.rowCoordCache=new lb({els:this.rowEls,isVertical:!0}),this.colCoordCache=new lb({els:this.cellEls.slice(0,this.colCnt),// only the first row isHorizontal:!0}),b=0;e>b;b++)for(c=0;f>c;c++)d.trigger("dayRender",null,this.getCellDate(b,c),this.getCellEl(b,c))},unrenderDates:function(){this.removeSegPopover()},renderBusinessHours:function(){var a=this.view.calendar.getBusinessHoursEvents(!0),b=this.eventsToSegs(a);this.renderFill("businessHours",b,"bgevent")}, // Generates the HTML for a single row, which is a div that wraps a table. // `row` is the row number. renderDayRowHtml:function(a,b){var c=this.view,d=["fc-row","fc-week",c.widgetContentClass];return b&&d.push("fc-rigid"),'
'+this.renderBgTrHtml(a)+'
'+(this.numbersVisible?""+this.renderNumberTrHtml(a)+"":"")+"
"},/* Grid Number Rendering ------------------------------------------------------------------------------------------------------------------*/ renderNumberTrHtml:function(a){return""+(this.isRTL?"":this.renderNumberIntroHtml(a))+this.renderNumberCellsHtml(a)+(this.isRTL?this.renderNumberIntroHtml(a):"")+""},renderNumberIntroHtml:function(a){return this.renderIntroHtml()},renderNumberCellsHtml:function(a){var b,c,d=[];for(b=0;bs 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. renderNumberCellHtml:function(a){var b;return this.view.dayNumbersVisible?(b=this.getDayClasses(a),b.unshift("fc-day-number"),''+a.date()+""):""},/* Options ------------------------------------------------------------------------------------------------------------------*/ // Computes a default event time formatting string if `timeFormat` is not explicitly defined computeEventTimeFormat:function(){return this.view.opt("extraSmallTimeFormat")}, // Computes a default `displayEventEnd` value if one is not expliclty defined computeDisplayEventEnd:function(){return 1==this.colCnt},/* Dates ------------------------------------------------------------------------------------------------------------------*/ rangeUpdated:function(){this.updateDayTable()}, // Slices up the given span (unzoned start/end with other misc data) into an array of segments spanToSegs:function(a){var b,c,d=this.sliceRangeByRow(a);for(b=0;b');g=c&&c.row===b?c.el.position().top:h.find(".fc-content-skeleton tbody").position().top,i.css("top",g).find("table").append(d[b].tbodyEl),h.append(i),e.push(i[0])}),this.helperEls=a(e)}, // Unrenders any visual indication of a mock helper event unrenderHelper:function(){this.helperEls&&(this.helperEls.remove(),this.helperEls=null)},/* Fill System (highlight, background events, business hours) ------------------------------------------------------------------------------------------------------------------*/ fillSegTag:"td",// override the default tag name // Renders a set of rectangles over the given segments of days. // Only returns segments that successfully rendered. renderFill:function(b,c,d){var e,f,g,h=[];// assignes `.el` to each seg. returns successfully rendered segs for(c=this.renderFillSegEls(b,c),e=0;e
'),f=e.find("tr"),h>0&&f.append(''),f.append(c.el.attr("colspan",i-h)),g>i&&f.append(''),this.bookendCells(f),e}});/* Event-rendering methods for the DayGrid class ----------------------------------------------------------------------------------------------------------------------*/ rb.mixin({rowStructs:null,// an array of objects, each holding information about a row's foreground event-rendering // Unrenders all events currently rendered on the grid unrenderEvents:function(){this.removeSegPopover(),// removes the "more.." events popover pb.prototype.unrenderEvents.apply(this,arguments)}, // Retrieves all rendered segment objects currently rendered on the grid getEventSegs:function(){return pb.prototype.getEventSegs.call(this).concat(this.popoverSegs||[])}, // Renders the given background event segments onto the grid renderBgSegs:function(b){ // don't render timed background events var c=a.grep(b,function(a){return a.event.allDay});return pb.prototype.renderBgSegs.call(this,c)}, // Renders the given foreground event segments onto the grid renderFgSegs:function(b){var c; // render an `.el` on each seg // returns a subset of the segs. segs that were actually rendered // append to each row's content skeleton return b=this.renderFgSegEls(b),c=this.rowStructs=this.renderSegRows(b),this.rowEls.each(function(b,d){a(d).find(".fc-content-skeleton > table").append(c[b].tbodyEl)}),b}, // Unrenders all currently rendered foreground event segments unrenderFgSegs:function(){for(var a,b=this.rowStructs||[];a=b.pop();)a.tbodyEl.remove();this.rowStructs=null}, // Uses the given events array to generate elements that should be appended to each row's content skeleton. // Returns an array of rowStruct objects (see the bottom of `renderSegRow`). // PRECONDITION: each segment shoud already have a rendered and assigned `.el` renderSegRows:function(a){var b,c,d=[];// group into nested arrays // iterate each row of segment groupings for(b=this.groupSegRows(a),c=0;c'+aa(c)+"")),d=''+(aa(f.title||"")||" ")+"",'
'+(this.isRTL?d+" "+l:l+" "+d)+"
"+(h?'
':"")+(i?'
':"")+""}, // 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. // NOTE: modifies rowSegs renderSegRow:function(b,c){ // populates empty cells from the current column (`col`) to `endCol` function d(b){for(;b>g;)k=(r[e-1]||[])[g],k?k.attr("rowspan",parseInt(k.attr("rowspan")||1,10)+1):(k=a(""),h.append(k)),q[e][g]=k,r[e][g]=k,g++}var e,f,g,h,i,j,k,l=this.colCnt,m=this.buildSegLevels(c),n=Math.max(1,m.length),o=a(""),p=[],q=[],r=[];for(e=0;n>e;e++){ // levelCnt might be 1 even though there are no actual levels. protect against this. // this single empty row is useful for styling. if(f=m[e],g=0,h=a(""),p.push([]),q.push([]),r.push([]),f)for(i=0;i').append(j.el),j.leftCol!=j.rightCol?k.attr("colspan",j.rightCol-j.leftCol+1):// a single-column segment r[e][g]=k;g<=j.rightCol;)q[e][g]=k,p[e][g]=j,g++;h.append(k)}d(l),// finish off the row this.bookendCells(h),o.append(h)}return{// a "rowStruct" row:b,// the row number tbodyEl:o,cellMatrix:q,segMatrix:p,segLevels:m,segs:c}}, // Stacks a flat array of segments, which are all assumed to be in the same row, into subarrays of vertical levels. // NOTE: modifies segs buildSegLevels:function(a){var b,c,d,e=[];for( // Give preference to elements with certain criteria, so they have // a chance to be closer to the top. this.sortEventSegs(a),b=0;b at a time and stop when we find one out of bounds for(d=0;d td > :first-child").each(c),e.position().top+f>h)return d;return!1}, // 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(b,c){ // Iterates through empty level cells and places "more" links inside if need be function d(d){// goes from current `col` to `endCol` for(;d>w;)j=t.getCellSegs(b,w,c),j.length&&(m=f[c-1][w],s=t.renderMoreLink(b,w,j),r=a("
").append(s),m.append(r),v.push(r[0])),w++}var e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t=this,u=this.rowStructs[b],v=[],w=0;if(c&&c for each column the segment occupies. will be one for each colspan for(m=f[c-1][i.leftCol],n=m.attr("rowspan")||1,o=[],p=0;p').attr("rowspan",n),j=l[p],s=this.renderMoreLink(b,i.leftCol+p,[i].concat(j)),r=a("
").append(s),q.append(r),o.push(q[0]),v.push(q[0]);m.addClass("fc-limited").after(a(o)),// hide original and inject replacements g.push(m[0])}}d(this.colCnt),// finish off the level u.moreEls=a(v),// for easy undoing later u.limitedEls=a(g)}}, // Reveals all levels and removes all "more"-related elements for a grid's row. // `row` is a row number. unlimitRow:function(a){var b=this.rowStructs[a];b.moreEls&&(b.moreEls.remove(),b.moreEls=null),b.limitedEls&&(b.limitedEls.removeClass("fc-limited"),b.limitedEls=null)}, // Renders an element that represents hidden event element for a cell. // Responsible for attaching click handler as well. renderMoreLink:function(b,c,d){var e=this,f=this.view;return a('').text(this.getMoreLinkText(d.length)).on("click",function(g){var h=f.opt("eventLimitClick"),i=e.getCellDate(b,c),j=a(this),k=e.getCellEl(b,c),l=e.getCellSegs(b,c),m=e.resliceDaySegs(l,i),n=e.resliceDaySegs(d,i);"function"==typeof h&&( // the returned value can be an atomic option h=f.trigger("eventLimitClick",null,{date:i,dayEl:k,moreEl:j,segs:m,hiddenSegs:n},g)),"popover"===h?e.showSegPopover(b,c,j,m):"string"==typeof h&&// a view name f.calendar.zoomTo(i,h)})}, // Reveals the popover that displays all events within a cell showSegPopover:function(a,b,c,d){var e,f,g=this,h=this.view,i=c.parent();e=1==this.rowCnt?h.el:this.rowEls.eq(a),f={className:"fc-more-popover",content:this.renderSegPopoverContent(a,b,d),parentEl:this.el,top:e.offset().top,autoHide:!0,viewportConstrain:h.opt("popoverViewportConstrain"),hide:function(){g.segPopover.removeElement(),g.segPopover=null,g.popoverSegs=null}},this.isRTL?f.right=i.offset().left+i.outerWidth()+1:f.left=i.offset().left-1,this.segPopover=new kb(f),this.segPopover.show()}, // Builds the inner DOM contents of the segment popover renderSegPopoverContent:function(b,c,d){var e,f=this.view,g=f.opt("theme"),h=this.getCellDate(b,c).format(f.opt("dayPopoverFormat")),i=a('
'+aa(h)+'
'),j=i.find(".fc-event-container");for(d=this.renderFgSegEls(d,!0),this.popoverSegs=d,e=0;e'+this.renderBgTrHtml(0)+'
'+this.renderSlatRowHtml()+"
"}, // Generates the HTML for the horizontal "slats" that run width-wise. Has a time axis on a side. Depends on RTL. renderSlatRowHtml:function(){ // Calculate the time for each slot for(var a,c,d,e=this.view,f=this.isRTL,g="",h=b.duration(+this.minTime);h"+(c?""+aa(a.format(this.labelFormat))+"":"")+"",g+='"+(f?"":d)+''+(f?d:"")+"",h.add(this.slotDuration);return g},/* Options ------------------------------------------------------------------------------------------------------------------*/ // Parses various options into properties of this object processOptions:function(){var c,d=this.view,e=d.opt("slotDuration"),f=d.opt("snapDuration");e=b.duration(e),f=f?b.duration(f):e,this.slotDuration=e,this.snapDuration=f,this.snapsPerSlot=e/f,this.minResizeDuration=f,this.minTime=b.duration(d.opt("minTime")),this.maxTime=b.duration(d.opt("maxTime")),c=d.opt("slotLabelFormat"),a.isArray(c)&&(c=c[c.length-1]),this.labelFormat=c||d.opt("axisFormat")||d.opt("smallTimeFormat"),c=d.opt("slotLabelInterval"),this.labelInterval=c?b.duration(c):this.computeLabelInterval(e)}, // Computes an automatic value for slotLabelInterval computeLabelInterval:function(a){var c,d,e; // find the smallest stock label interval that results in more than one slots-per-label for(c=Jb.length-1;c>=0;c--)if(d=b.duration(Jb[c]),e=P(d,a),fa(e)&&e>1)return d;return b.duration(a)}, // Computes a default event time formatting string if `timeFormat` is not explicitly defined computeEventTimeFormat:function(){return this.view.opt("noMeridiemTimeFormat")}, // Computes a default `displayEventEnd` value if one is not expliclty defined computeDisplayEventEnd:function(){return!0},/* Hit System ------------------------------------------------------------------------------------------------------------------*/ prepareHits:function(){this.colCoordCache.build(),this.slatCoordCache.build()},releaseHits:function(){this.colCoordCache.clear()},queryHit:function(a,b){var c=this.snapsPerSlot,d=this.colCoordCache,e=this.slatCoordCache,f=d.getHorizontalIndex(a),g=e.getVerticalIndex(b);if(null!=f&&null!=g){var h=e.getTopOffset(g),i=e.getHeight(g),j=(b-h)/i,k=Math.floor(j*c),l=g*c+k,m=h+k/c*i,n=h+(k+1)/c*i;return{col:f,snap:l,component:this,// needed unfortunately :( left:d.getLeftOffset(f),right:d.getRightOffset(f),top:m,bottom:n}}},getHitSpan:function(a){var b,c=this.getCellDate(0,a.col),d=this.computeSnapTime(a.snap);return c.time(d),b=c.clone().add(this.snapDuration),{start:c,end:b}},getHitEl:function(a){return this.colEls.eq(a.col)},/* Dates ------------------------------------------------------------------------------------------------------------------*/ rangeUpdated:function(){this.updateDayTable()}, // Given a row number of the grid, representing a "snap", returns a time (Duration) from its start-of-day computeSnapTime:function(a){return b.duration(this.minTime+this.snapDuration*a)}, // Slices up the given span (unzoned start/end with other misc data) into an array of segments spanToSegs:function(a){var b,c=this.sliceRangeByTimes(a);for(b=0;b
').css("top",e).appendTo(this.colContainerEls.eq(d[c].col))[0]); // render an arrow over the axis d.length>0&&// is the current time in view? f.push(a('
').css("top",e).appendTo(this.el.find(".fc-content-skeleton"))[0]),this.nowIndicatorEls=a(f)},unrenderNowIndicator:function(){this.nowIndicatorEls&&(this.nowIndicatorEls.remove(),this.nowIndicatorEls=null)},/* Selection ------------------------------------------------------------------------------------------------------------------*/ // Renders a visual indication of a selection. Overrides the default, which was to simply render a highlight. renderSelection:function(a){this.view.opt("selectHelper")?// this setting signals that a mock helper event should be rendered // normally acceps an eventLocation, span has a start/end, which is good enough this.renderEventLocationHelper(a):this.renderHighlight(a)}, // Unrenders any visual indication of a selection unrenderSelection:function(){this.unrenderHelper(),this.unrenderHighlight()},/* Highlight ------------------------------------------------------------------------------------------------------------------*/ renderHighlight:function(a){this.renderHighlightSegs(this.spanToSegs(a))},unrenderHighlight:function(){this.unrenderHighlightSegs()}});/* Methods for rendering SEGMENTS, pieces of content that live on the view ( this file is no longer just for events ) ----------------------------------------------------------------------------------------------------------------------*/ sb.mixin({colContainerEls:null,// containers for each column // inner-containers for each column where different types of segs live fgContainerEls:null,bgContainerEls:null,helperContainerEls:null,highlightContainerEls:null,businessContainerEls:null, // arrays of different types of displayed segments fgSegs:null,bgSegs:null,helperSegs:null,highlightSegs:null,businessSegs:null, // Renders the DOM that the view's content will live in renderContentSkeleton:function(){var b,c,d="";for(b=0;b
';c=a('
'+d+"
"),this.colContainerEls=c.find(".fc-content-col"),this.helperContainerEls=c.find(".fc-helper-container"),this.fgContainerEls=c.find(".fc-event-container:not(.fc-helper-container)"),this.bgContainerEls=c.find(".fc-bgevent-container"),this.highlightContainerEls=c.find(".fc-highlight-container"),this.businessContainerEls=c.find(".fc-business-container"),this.bookendCells(c.find("tr")),this.el.append(c)},/* Foreground Events ------------------------------------------------------------------------------------------------------------------*/ renderFgSegs:function(a){return a=this.renderFgSegsIntoContainers(a,this.fgContainerEls),this.fgSegs=a,a},unrenderFgSegs:function(){this.unrenderNamedSegs("fgSegs")},/* Foreground Helper Events ------------------------------------------------------------------------------------------------------------------*/ renderHelperSegs:function(b,c){var d,e,f,g=[]; // Try to make the segment that is in the same row as sourceSeg look the same for(b=this.renderFgSegsIntoContainers(b,this.helperContainerEls),d=0;d' : '' ) + */ return k.unshift("fc-time-grid-event","fc-v-event"),f.isMultiDayEvent(g)?(a.isStart||a.isEnd)&&(c=this.getEventTimeText(a),d=this.getEventTimeText(a,"LT"),e=this.getEventTimeText(a,null,!1)):(c=this.getEventTimeText(g),d=this.getEventTimeText(g,"LT"),e=this.getEventTimeText(g,null,!1)),'
'+(c?'
'+aa(c)+"
":"")+(g.title?'
'+aa(g.title)+"
":"")+'
'+(j?'
':"")+""},/* Seg Position Utils ------------------------------------------------------------------------------------------------------------------*/ // Refreshes the CSS top/bottom coordinates for each segment element. // Works when called after initial render, after a window resize/zoom for example. updateSegVerticals:function(a){this.computeSegVerticals(a),this.assignSegVerticals(a)}, // For each segment in an array, computes and assigns its top and bottom properties computeSegVerticals:function(a){var b,c;for(b=0;b1?"ll":"LL"}, // Utility for formatting a range. Accepts a range object, formatting string, and optional separator. // Displays all-day ranges naturally, with an inclusive end. Takes the current isRTL into account. // The timezones of the dates within `range` will be respected. formatRange:function(a,b,c){var d=a.end;// all-day? return d.hasTime()||(d=d.clone().subtract(1)),qa(a.start,d,b,c,this.opt("isRTL"))},/* Rendering ------------------------------------------------------------------------------------------------------------------*/ // Sets the container element that the view should render inside of. // Does other DOM-related initializations. setElement:function(a){this.el=a,this.bindGlobalHandlers()}, // Removes the view's container element from the DOM, clearing any content beforehand. // Undoes any other DOM-related attachments. removeElement:function(){this.clear(),// clears all content // clean up the skeleton this.isSkeletonRendered&&(this.unrenderSkeleton(),this.isSkeletonRendered=!1),this.unbindGlobalHandlers(),this.el.remove()}, // Does everything necessary to display the view centered around the given unzoned date. // Does every type of rendering EXCEPT rendering events. // Is asychronous and returns a promise. display:function(b){var c=this,d=null;return this.displaying&&(d=this.queryScroll()),this.calendar.freezeContentHeight(),this.clear().then(function(){// clear the content first (async) return c.displaying=a.when(c.displayView(b)).then(function(){c.forceScroll(c.computeInitialScroll(d)),c.calendar.unfreezeContentHeight(),c.triggerRender()})})}, // Does everything necessary to clear the content of the view. // Clears dates and events. Does not clear the skeleton. // Is asychronous and returns a promise. clear:function(){var b=this,c=this.displaying;return c?c.then(function(){// wait for the display to finish return b.displaying=null,b.clearEvents(),b.clearView()}):a.when()}, // Displays the view's non-event content, such as date-related content or anything required by events. // Renders the view's non-content skeleton if necessary. // Can be asynchronous and return a promise. displayView:function(a){this.isSkeletonRendered||(this.renderSkeleton(),this.isSkeletonRendered=!0),a&&this.setDate(a),this.render&&this.render(),this.renderDates(),this.updateSize(),this.renderBusinessHours(),// might need coordinates, so should go after updateSize() this.startNowIndicator()}, // Unrenders the view content that was rendered in displayView. // Can be asynchronous and return a promise. clearView:function(){this.unselect(),this.stopNowIndicator(),this.triggerUnrender(),this.unrenderBusinessHours(),this.unrenderDates(),this.destroy&&this.destroy()}, // Renders the basic structure of the view before any content is rendered renderSkeleton:function(){}, // Unrenders the basic structure of the view unrenderSkeleton:function(){}, // Renders the view's date-related content. // Assumes setRange has already been called and the skeleton has already been rendered. renderDates:function(){}, // Unrenders the view's date-related content unrenderDates:function(){}, // Signals that the view's content has been rendered triggerRender:function(){this.trigger("viewRender",this,this,this.el)}, // Signals that the view's content is about to be unrendered triggerUnrender:function(){this.trigger("viewDestroy",this,this,this.el)}, // Binds DOM handlers to elements that reside outside the view container, such as the document bindGlobalHandlers:function(){this.listenTo(a(document),"mousedown",this.handleDocumentMousedown),this.listenTo(a(document),"touchstart",this.handleDocumentTouchStart),this.listenTo(a(document),"touchend",this.handleDocumentTouchEnd)}, // Unbinds DOM handlers from elements that reside outside the view container unbindGlobalHandlers:function(){this.stopListeningTo(a(document))}, // Initializes internal variables related to theming initThemingProps:function(){var a=this.opt("theme")?"ui":"fc";this.widgetHeaderClass=a+"-widget-header",this.widgetContentClass=a+"-widget-content",this.highlightStateClass=a+"-state-highlight"},/* Business Hours ------------------------------------------------------------------------------------------------------------------*/ // Renders business-hours onto the view. Assumes updateSize has already been called. renderBusinessHours:function(){}, // Unrenders previously-rendered business-hours unrenderBusinessHours:function(){},/* Now Indicator ------------------------------------------------------------------------------------------------------------------*/ // Immediately render the current time indicator and begins re-rendering it at an interval, // which is defined by this.getNowIndicatorUnit(). // TODO: somehow do this for the current whole day's background too startNowIndicator:function(){var a,c,d,e=this;// ms wait value this.opt("nowIndicator")&&(a=this.getNowIndicatorUnit(),a&&(c=ga(this,"updateNowIndicator"),this.initialNowDate=this.calendar.getNow(),this.initialNowQueriedMs=+new Date,this.renderNowIndicator(this.initialNowDate),this.isNowIndicatorRendered=!0,d=this.initialNowDate.clone().startOf(a).add(1,a)-this.initialNowDate,this.nowIndicatorTimeoutID=setTimeout(function(){e.nowIndicatorTimeoutID=null,c(),d=+b.duration(1,a),d=Math.max(100,d),e.nowIndicatorIntervalID=setInterval(c,d)},d)))}, // rerenders the now indicator, computing the new current time from the amount of time that has passed // since the initial getNow call. updateNowIndicator:function(){this.isNowIndicatorRendered&&(this.unrenderNowIndicator(),this.renderNowIndicator(this.initialNowDate.clone().add(new Date-this.initialNowQueriedMs)))}, // Immediately unrenders the view's current time indicator and stops any re-rendering timers. // Won't cause side effects if indicator isn't rendered. stopNowIndicator:function(){this.isNowIndicatorRendered&&(this.nowIndicatorTimeoutID&&(clearTimeout(this.nowIndicatorTimeoutID),this.nowIndicatorTimeoutID=null),this.nowIndicatorIntervalID&&(clearTimeout(this.nowIndicatorIntervalID),this.nowIndicatorIntervalID=null),this.unrenderNowIndicator(),this.isNowIndicatorRendered=!1)}, // Returns a string unit, like 'second' or 'minute' that defined how often the current time indicator // should be refreshed. If something falsy is returned, no time indicator is rendered at all. getNowIndicatorUnit:function(){}, // Renders a current time indicator at the given datetime renderNowIndicator:function(a){}, // Undoes the rendering actions from renderNowIndicator unrenderNowIndicator:function(){},/* Dimensions ------------------------------------------------------------------------------------------------------------------*/ // Refreshes anything dependant upon sizing of the container element of the grid updateSize:function(a){var b;a&&(b=this.queryScroll()),this.updateHeight(a),this.updateWidth(a),this.updateNowIndicator(),a&&this.setScroll(b)}, // Refreshes the horizontal dimensions of the calendar updateWidth:function(a){}, // Refreshes the vertical dimensions of the calendar updateHeight:function(a){var b=this.calendar;// we poll the calendar for height information this.setHeight(b.getSuggestedViewHeight(),b.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(a,b){},/* Scroller ------------------------------------------------------------------------------------------------------------------*/ // Computes the initial pre-configured scroll state prior to allowing the user to change it. // Given the scroll state from the previous rendering. If first time rendering, given null. computeInitialScroll:function(a){return 0}, // Retrieves the view's current natural scroll state. Can return an arbitrary format. queryScroll:function(){}, // Sets the view's scroll state. Will accept the same format computeInitialScroll and queryScroll produce. setScroll:function(a){}, // Sets the scroll state, making sure to overcome any predefined scroll value the browser has in mind forceScroll:function(a){var b=this;this.setScroll(a),setTimeout(function(){b.setScroll(a)},0)},/* Event Elements / Segments ------------------------------------------------------------------------------------------------------------------*/ // Does everything necessary to display the given events onto the current view displayEvents:function(a){var b=this.queryScroll();this.clearEvents(),this.renderEvents(a),this.isEventsRendered=!0,this.setScroll(b),this.triggerEventRender()}, // Does everything necessary to clear the view's currently-rendered events clearEvents:function(){var a;this.isEventsRendered&&(a=this.queryScroll(),this.triggerEventUnrender(),this.destroyEvents&&this.destroyEvents(),this.unrenderEvents(),this.setScroll(a),this.isEventsRendered=!1)}, // Renders the events onto the view. renderEvents:function(a){}, // Removes event elements from the view. unrenderEvents:function(){}, // Signals that all events have been rendered triggerEventRender:function(){this.renderedEventSegEach(function(a){this.trigger("eventAfterRender",a.event,a.event,a.el)}),this.trigger("eventAfterAllRender")}, // Signals that all event elements are about to be removed triggerEventUnrender:function(){this.renderedEventSegEach(function(a){this.trigger("eventDestroy",a.event,a.event,a.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(b,c){var d=this.trigger("eventRender",b,b,c);// means don't render at all return d===!1?c=null:d&&d!==!0&&(c=a(d)),c}, // Hides all rendered event segments linked to the given event showEvent:function(a){this.renderedEventSegEach(function(a){a.el.css("visibility","")},a)}, // Shows all rendered event segments linked to the given event hideEvent:function(a){this.renderedEventSegEach(function(a){a.el.css("visibility","hidden")},a)}, // Iterates through event segments that have been rendered (have an el). 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. renderedEventSegEach:function(a,b){var c,d=this.getEventSegs();for(c=0;c