Commit 03cf7c38882c8b034e49719d3360dd5556ca8bc3

Authored by Swarn Singh
1 parent 22787c6693
Exists in master

minor changes due to ui updation and integrated schedular

app/css/custom.css
... ... @@ -185,7 +185,6 @@ font-size: 10px !important;
185 185 }
186 186 .customAccordianHeader > span{
187 187 font-size: 15px;
188   - margin-right: 20px;
189 188 line-height: 22px;
190 189 float: left;
191 190 }
... ... @@ -200,7 +199,6 @@ font-size: 10px !important;
200 199 width: 150px;
201 200 height: 26px;
202 201 padding: 0 6px;
203   - margin-right: 20px;
204 202 float: left;
205 203 }
206 204 .customAccordianHeader > select.form-control[disabled]{
... ... @@ -213,7 +211,7 @@ font-size: 10px !important;
213 211 width: auto;
214 212 }
215 213 .customAccordianHeader > input.form-control{
216   - width: 80px;
  214 + width: 70px;
217 215 height: 26px;
218 216 padding: 0 6px;
219 217 float: left;
... ...
... ... @@ -54,7 +54,7 @@
54 54 <script src='https://fullcalendar.io/js/fullcalendar-3.4.0/lib/moment.min.js'></script>
55 55 <script src='https://fullcalendar.io/js/fullcalendar-3.4.0/lib/jquery.min.js'></script>
56 56 <script src='https://fullcalendar.io/js/fullcalendar-3.4.0/lib/jquery-ui.min.js'></script> -->
57   -
  57 + <link rel="stylesheet" href="bower_components/fullcalendar/dist/fullcalendar.css"/>
58 58  
59 59 </head>
60 60 <body>
... ... @@ -94,9 +94,12 @@
94 94 <script src="bower_components/select2/select2.js"></script>
95 95 <script src="bower_components/angular-ui-select2/src/select2.js"></script>
96 96 <script src="bower_components/angular-ckeditor/angular-ckeditor.js"></script>
  97 + <script src="bower_components/angular-dragdrop/src/angular-dragdrop.min.js"></script>
97 98  
98 99 <!-- <script src="https://unpkg.com/ng-table@2.0.2/bundles/ng-table.min.js"></script> -->
99   -
  100 + <script type="text/javascript" src="bower_components/angular-ui-calendar/src/calendar.js"></script>
  101 + <!-- <script type="text/javascript" src="bower_components/fullcalendar/dist/fullcalendar.min.js"></script> -->
  102 +
100 103  
101 104 <!--
102 105 <script src="scripts/inspinia.js"></script>
... ... @@ -197,7 +200,9 @@
197 200 <script src="partials/enterFuelOrder/enterFuelOrder.service.js"></script>
198 201  
199 202 <script src="partials/main/main.service.js"></script>
200   - <script src='https://fullcalendar.io/js/fullcalendar-3.4.0/fullcalendar.min.js'></script>
  203 + <!-- <script src='https://fullcalendar.io/js/fullcalendar-3.4.0/fullcalendar.min.js'></script> -->
  204 + <script type="text/javascript" src="bower_components/fullcalendar/dist/fullcalendar.min.js"></script>
  205 + <script type="text/javascript" src="bower_components/fullcalendar/dist/gcal.js"></script>
201 206  
202 207 </body>
203 208 </html>
204 209 \ No newline at end of file
... ...
1 1 'use strict';
2 2  
3 3  
4   - angular.module('acufuel', ['ngCookies', 'ngResource', 'ui.router', 'ngAnimate', 'ui.bootstrap', 'xeditable', 'ui.toggle', 'ngTable', 'ui.select2', 'ckeditor'])
  4 + angular.module('acufuel', ['ngCookies', 'ngResource', 'ui.router', 'ngAnimate', 'ui.bootstrap', 'xeditable', 'ui.toggle', 'ngTable', 'ui.select2', 'ckeditor', 'ui.calendar', 'ngDragDrop'])
5 5  
6 6 .config(['$httpProvider', function($httpProvider) {
7 7 $httpProvider.defaults.withCredentials = true;
... ...
app/js/fullcalender.js
Changes suppressed. Click to show
1 1 /*!
2   - * FullCalendar v3.4.0
3   - * Docs & License: https://fullcalendar.io/
4   - * (c) 2017 Adam Shaw
  2 + * FullCalendar v2.3.1
  3 + * Docs & License: http://fullcalendar.io/
  4 + * (c) 2015 Adam Shaw
5 5 */
6   -!function(t){"function"==typeof define&&define.amd?define(["jquery","moment"],t):"object"==typeof exports?module.exports=t(require("jquery"),require("moment")):t(jQuery,moment)}(function(t,e){function n(t){return it(t,Qt)}function i(t,e){e.left&&t.css({"border-left-width":1,"margin-left":e.left-1}),e.right&&t.css({"border-right-width":1,"margin-right":e.right-1})}function r(t){t.css({"margin-left":"","margin-right":"","border-left-width":"","border-right-width":""})}function s(){t("body").addClass("fc-not-allowed")}function o(){t("body").removeClass("fc-not-allowed")}function a(e,n,i){var r=Math.floor(n/e.length),s=Math.floor(n-r*(e.length-1)),o=[],a=[],u=[],h=0;l(e),e.each(function(n,i){var l=n===e.length-1?s:r,c=t(i).outerHeight(!0);c<l?(o.push(i),a.push(c),u.push(t(i).height())):h+=c}),i&&(n-=h,r=Math.floor(n/o.length),s=Math.floor(n-r*(o.length-1))),t(o).each(function(e,n){var i=e===o.length-1?s:r,l=a[e],h=u[e],c=i-(l-h);l<i&&t(n).height(c)})}function l(t){t.height("")}function u(e){var n=0;return e.find("> *").each(function(e,i){var r=t(i).outerWidth();r>n&&(n=r)}),n++,e.width(n),n}function h(t,e){var n,i=t.add(e);return i.css({position:"relative",left:-1}),n=t.outerHeight()-e.outerHeight(),i.css({position:"",left:""}),n}function c(e){var n=e.css("position"),i=e.parents().filter(function(){var e=t(this);return/(auto|scroll)/.test(e.css("overflow")+e.css("overflow-y")+e.css("overflow-x"))}).eq(0);return"fixed"!==n&&i.length?i:t(e[0].ownerDocument||document)}function d(t,e){var n=t.offset(),i=n.left-(e?e.left:0),r=n.top-(e?e.top:0);return{left:i,right:i+t.outerWidth(),top:r,bottom:r+t.outerHeight()}}function f(t,e){var n=t.offset(),i=p(t),r=n.left+w(t,"border-left-width")+i.left-(e?e.left:0),s=n.top+w(t,"border-top-width")+i.top-(e?e.top:0);return{left:r,right:r+t[0].clientWidth,top:s,bottom:s+t[0].clientHeight}}function g(t,e){var n=t.offset(),i=n.left+w(t,"border-left-width")+w(t,"padding-left")-(e?e.left:0),r=n.top+w(t,"border-top-width")+w(t,"padding-top")-(e?e.top:0);return{left:i,right:i+t.width(),top:r,bottom:r+t.height()}}function p(t){var e,n=t[0].offsetWidth-t[0].clientWidth,i=t[0].offsetHeight-t[0].clientHeight;return n=v(n),i=v(i),e={left:0,right:0,top:0,bottom:i},m()&&"rtl"==t.css("direction")?e.left=n:e.right=n,e}function v(t){return t=Math.max(0,t),t=Math.round(t)}function m(){return null===Xt&&(Xt=y()),Xt}function y(){var e=t("<div><div/></div>").css({position:"absolute",top:-1e3,left:0,border:0,padding:0,overflow:"scroll",direction:"rtl"}).appendTo("body"),n=e.children(),i=n.offset().left>e.offset().left;return e.remove(),i}function w(t,e){return parseFloat(t.css(e))||0}function S(t){return 1==t.which&&!t.ctrlKey}function b(t){var e=t.originalEvent.touches;return e&&e.length?e[0].pageX:t.pageX}function E(t){var e=t.originalEvent.touches;return e&&e.length?e[0].pageY:t.pageY}function D(t){return/^touch/.test(t.type)}function T(t){t.addClass("fc-unselectable").on("selectstart",H)}function C(t){t.removeClass("fc-unselectable").off("selectstart",H)}function H(t){t.preventDefault()}function R(t,e){var n={left:Math.max(t.left,e.left),right:Math.min(t.right,e.right),top:Math.max(t.top,e.top),bottom:Math.min(t.bottom,e.bottom)};return n.left<n.right&&n.top<n.bottom&&n}function x(t,e){return{left:Math.min(Math.max(t.left,e.left),e.right),top:Math.min(Math.max(t.top,e.top),e.bottom)}}function I(t){return{left:(t.left+t.right)/2,top:(t.top+t.bottom)/2}}function k(t,e){return{left:t.left-e.left,top:t.top-e.top}}function M(e){var n,i,r=[],s=[];for("string"==typeof e?s=e.split(/\s*,\s*/):"function"==typeof e?s=[e]:t.isArray(e)&&(s=e),n=0;n<s.length;n++)i=s[n],"string"==typeof i?r.push("-"==i.charAt(0)?{field:i.substring(1),order:-1}:{field:i,order:1}):"function"==typeof i&&r.push({func:i});return r}function B(t,e,n){var i,r;for(i=0;i<n.length;i++)if(r=L(t,e,n[i]))return r;return 0}function L(t,e,n){return n.func?n.func(t,e):N(t[n.field],e[n.field])*(n.order||1)}function N(e,n){return e||n?null==n?-1:null==e?1:"string"===t.type(e)||"string"===t.type(n)?String(e).localeCompare(String(n)):e-n:0}function z(t,e){var n,i,r,s,o=t.start,a=t.end,l=e.start,u=e.end;if(a>l&&o<u)return o>=l?(n=o.clone(),r=!0):(n=l.clone(),r=!1),a<=u?(i=a.clone(),s=!0):(i=u.clone(),s=!1),{start:n,end:i,isStart:r,isEnd:s}}function F(t,n){return e.duration({days:t.clone().stripTime().diff(n.clone().stripTime(),"days"),ms:t.time()-n.time()})}function A(t,n){return e.duration({days:t.clone().stripTime().diff(n.clone().stripTime(),"days")})}function G(t,n,i){return e.duration(Math.round(t.diff(n,i,!0)),i)}function V(t,e){var n,i,r;for(n=0;n<Jt.length&&(i=Jt[n],!((r=P(i,t,e))>=1&&vt(r)));n++);return i}function O(t,e){var n=V(t);return"week"===n&&"object"==typeof e&&e.days&&(n="day"),n}function P(t,n,i){return null!=i?i.diff(n,t,!0):e.isDuration(n)?n.as(t):n.end.diff(n.start,t,!0)}function _(t,e,n){var i;return tt(n)?(e-t)/n:(i=n.asMonths(),Math.abs(i)>=1&&vt(i)?e.diff(t,"months",!0)/i:e.diff(t,"days",!0)/n.asDays())}function W(t,e){var n,i;return tt(t)||tt(e)?t/e:(n=t.asMonths(),i=e.asMonths(),Math.abs(n)>=1&&vt(n)&&Math.abs(i)>=1&&vt(i)?n/i:t.asDays()/e.asDays())}function Y(t,n){var i;return tt(t)?e.duration(t*n):(i=t.asMonths(),Math.abs(i)>=1&&vt(i)?e.duration({months:i*n}):e.duration({days:t.asDays()*n}))}function q(t){return{start:t.start.clone(),end:t.end.clone()}}function U(t,e){return t=q(t),e.start&&(t.start=j(t.start,e)),e.end&&(t.end=K(t.end,e.end)),t}function j(t,e){return t=t.clone(),e.start&&(t=J(t,e.start)),e.end&&t>=e.end&&(t=e.end.clone().subtract(1)),t}function Z(t,e){return(!e.start||t>=e.start)&&(!e.end||t<e.end)}function $(t,e){return(!e.start||t.end>=e.start)&&(!e.end||t.start<e.end)}function Q(t,e){return(!e.start||t.start>=e.start)&&(!e.end||t.end<=e.end)}function X(t,e){return(t.start&&e.start&&t.start.isSame(e.start)||!t.start&&!e.start)&&(t.end&&e.end&&t.end.isSame(e.end)||!t.end&&!e.end)}function K(t,e){return(t.isBefore(e)?t:e).clone()}function J(t,e){return(t.isAfter(e)?t:e).clone()}function tt(t){return Boolean(t.hours()||t.minutes()||t.seconds()||t.milliseconds())}function et(t){return"[object Date]"===Object.prototype.toString.call(t)||t instanceof Date}function nt(t){return/^\d+\:\d+(?:\:\d+\.?(?:\d{3})?)?$/.test(t)}function it(t,e){var n,i,r,s,o,a,l={};if(e)for(n=0;n<e.length;n++){for(i=e[n],r=[],s=t.length-1;s>=0;s--)if("object"==typeof(o=t[s][i]))r.unshift(o);else if(void 0!==o){l[i]=o;break}r.length&&(l[i]=it(r))}for(n=t.length-1;n>=0;n--){a=t[n];for(i in a)i in l||(l[i]=a[i])}return l}function rt(t){var e=function(){};return e.prototype=t,new e}function st(t,e){for(var n in t)ot(t,n)&&(e[n]=t[n])}function ot(t,e){return te.call(t,e)}function at(e){return/undefined|null|boolean|number|string/.test(t.type(e))}function lt(e,n,i){if(t.isFunction(e)&&(e=[e]),e){var r,s;for(r=0;r<e.length;r++)s=e[r].apply(n,i)||s;return s}}function ut(){for(var t=0;t<arguments.length;t++)if(void 0!==arguments[t])return arguments[t]}function ht(t){return(t+"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/'/g,"&#039;").replace(/"/g,"&quot;").replace(/\n/g,"<br />")}function ct(t){return t.replace(/&.*?;/g,"")}function dt(e){var n=[];return t.each(e,function(t,e){null!=e&&n.push(t+":"+e)}),n.join(";")}function ft(e){var n=[];return t.each(e,function(t,e){null!=e&&n.push(t+'="'+ht(e)+'"')}),n.join(" ")}function gt(t){return t.charAt(0).toUpperCase()+t.slice(1)}function pt(t,e){return t-e}function vt(t){return t%1==0}function mt(t,e){var n=t[e];return function(){return n.apply(t,arguments)}}function yt(t,e,n){var i,r,s,o,a,l=function(){var u=+new Date-o;u<e?i=setTimeout(l,e-u):(i=null,n||(a=t.apply(s,r),s=r=null))};return function(){s=this,r=arguments,o=+new Date;var u=n&&!i;return i||(i=setTimeout(l,e)),u&&(a=t.apply(s,r),s=r=null),a}}function wt(n,i,r){var s,o,a,l,u=n[0],h=1==n.length&&"string"==typeof u;return e.isMoment(u)||et(u)||void 0===u?l=e.apply(null,n):(s=!1,o=!1,h?ee.test(u)?(u+="-01",n=[u],s=!0,o=!0):(a=ne.exec(u))&&(s=!a[5],o=!0):t.isArray(u)&&(o=!0),l=i||s?e.utc.apply(e,n):e.apply(null,n),s?(l._ambigTime=!0,l._ambigZone=!0):r&&(o?l._ambigZone=!0:h&&l.utcOffset(u))),l._fullCalendar=!0,l}function St(t){return"en"!==t.locale()?t.clone().locale("en"):t}function bt(){}function Et(t,e){var n;return ot(e,"constructor")&&(n=e.constructor),"function"!=typeof n&&(n=e.constructor=function(){t.apply(this,arguments)}),n.prototype=rt(t.prototype),st(e,n.prototype),st(t,n),n}function Dt(t,e){st(e,t.prototype)}function Tt(t,e){t.then=function(n){return"function"==typeof n&&n(e),t}}function Ct(t){t.then=function(e,n){return"function"==typeof n&&n(),t}}function Ht(t,e){return!t&&!e||!(!t||!e)&&(t.component===e.component&&Rt(t,e)&&Rt(e,t))}function Rt(t,e){for(var n in t)if(!/^(component|left|right|top|bottom)$/.test(n)&&t[n]!==e[n])return!1;return!0}function xt(t){return{start:t.start.clone(),end:t.end?t.end.clone():null,allDay:t.allDay}}function It(t){var e=Mt(t);return"background"===e||"inverse-background"===e}function kt(t){return"inverse-background"===Mt(t)}function Mt(t){return ut((t.source||{}).rendering,t.rendering)}function Bt(t){var e,n,i={};for(e=0;e<t.length;e++)n=t[e],(i[n._id]||(i[n._id]=[])).push(n);return i}function Lt(t,e){return t.start-e.start}function Nt(n){var i,r,s,o,a=Zt.dataAttrPrefix;return a&&(a+="-"),i=n.data(a+"event")||null,i&&(i="object"==typeof i?t.extend({},i):{},r=i.start,null==r&&(r=i.time),s=i.duration,o=i.stick,delete i.start,delete i.time,delete i.duration,delete i.stick),null==r&&(r=n.data(a+"start")),null==r&&(r=n.data(a+"time")),null==s&&(s=n.data(a+"duration")),null==o&&(o=n.data(a+"stick")),r=null!=r?e.duration(r):null,s=null!=s?e.duration(s):null,o=Boolean(o),{eventProps:i,startTime:r,duration:s,stick:o}}function zt(t,e){var n,i;for(n=0;n<e.length;n++)if(i=e[n],i.leftCol<=t.rightCol&&i.rightCol>=t.leftCol)return!0;return!1}function Ft(t,e){return t.leftCol-e.leftCol}function At(t){var e,n,i,r=[];for(e=0;e<t.length;e++){for(n=t[e],i=0;i<r.length&&Ot(n,r[i]).length;i++);n.level=i,(r[i]||(r[i]=[])).push(n)}return r}function Gt(t){var e,n,i,r,s;for(e=0;e<t.length;e++)for(n=t[e],i=0;i<n.length;i++)for(r=n[i],r.forwardSegs=[],s=e+1;s<t.length;s++)Ot(r,t[s],r.forwardSegs)}function Vt(t){var e,n,i=t.forwardSegs,r=0;if(void 0===t.forwardPressure){for(e=0;e<i.length;e++)n=i[e],Vt(n),r=Math.max(r,1+n.forwardPressure);t.forwardPressure=r}}function Ot(t,e,n){n=n||[];for(var i=0;i<e.length;i++)Pt(t,e[i])&&n.push(e[i]);return n}function Pt(t,e){return t.bottom>e.top&&t.top<e.bottom}function _t(t){this.items=t||[]}function Wt(e,n){function i(t){n=t}function r(){var i=n.layout;p=e.opt("theme")?"ui":"fc",i?(g?g.empty():g=this.el=t("<div class='fc-toolbar "+n.extraClasses+"'/>"),g.append(o("left")).append(o("right")).append(o("center")).append('<div class="fc-clear"/>')):s()}function s(){g&&(g.remove(),g=f.el=null)}function o(i){var r=t('<div class="fc-'+i+'"/>'),s=n.layout[i],o=e.opt("customButtons")||{},a=e.opt("buttonText")||{};return s&&t.each(s.split(" "),function(n){var i,s=t(),l=!0;t.each(this.split(","),function(n,i){var r,u,h,c,d,f,g,m,y,w;"title"==i?(s=s.add(t("<h2>&nbsp;</h2>")),l=!1):((r=o[i])?(h=function(t){r.click&&r.click.call(w[0],t)},c="",d=r.text):(u=e.getViewSpec(i))?(h=function(){e.changeView(i)},v.push(i),c=u.buttonTextOverride,d=u.buttonTextDefault):e[i]&&(h=function(){e[i]()},c=(e.overrides.buttonText||{})[i],d=a[i]),h&&(f=r?r.themeIcon:e.opt("themeButtonIcons")[i],g=r?r.icon:e.opt("buttonIcons")[i],m=c?ht(c):f&&e.opt("theme")?"<span class='ui-icon ui-icon-"+f+"'></span>":g&&!e.opt("theme")?"<span class='fc-icon fc-icon-"+g+"'></span>":ht(d),y=["fc-"+i+"-button",p+"-button",p+"-state-default"],w=t('<button type="button" class="'+y.join(" ")+'">'+m+"</button>").click(function(t){w.hasClass(p+"-state-disabled")||(h(t),(w.hasClass(p+"-state-active")||w.hasClass(p+"-state-disabled"))&&w.removeClass(p+"-state-hover"))}).mousedown(function(){w.not("."+p+"-state-active").not("."+p+"-state-disabled").addClass(p+"-state-down")}).mouseup(function(){w.removeClass(p+"-state-down")}).hover(function(){w.not("."+p+"-state-active").not("."+p+"-state-disabled").addClass(p+"-state-hover")},function(){w.removeClass(p+"-state-hover").removeClass(p+"-state-down")}),s=s.add(w)))}),l&&s.first().addClass(p+"-corner-left").end().last().addClass(p+"-corner-right").end(),s.length>1?(i=t("<div/>"),l&&i.addClass("fc-button-group"),i.append(s),r.append(i)):r.append(s)}),r}function a(t){g&&g.find("h2").text(t)}function l(t){g&&g.find(".fc-"+t+"-button").addClass(p+"-state-active")}function u(t){g&&g.find(".fc-"+t+"-button").removeClass(p+"-state-active")}function h(t){g&&g.find(".fc-"+t+"-button").prop("disabled",!0).addClass(p+"-state-disabled")}function c(t){g&&g.find(".fc-"+t+"-button").prop("disabled",!1).removeClass(p+"-state-disabled")}function d(){return v}var f=this;f.setToolbarOptions=i,f.render=r,f.removeElement=s,f.updateTitle=a,f.activateButton=l,f.deactivateButton=u,f.disableButton=h,f.enableButton=c,f.getViewsWithButtons=d,f.el=null;var g,p,v=[]}function Yt(e){t.each(Me,function(t,n){null==e[t]&&(e[t]=n(e))})}function qt(t){return e.localeData(t)||e.localeData("en")}function Ut(){function n(t,e){return!q.opt("lazyFetching")||s(t,e)?o(t,e):he.resolve(Z)}function i(){Z=r(K),q.trigger("eventsReset",Z)}function r(t){var e,n,i=[];for(e=0;e<t.length;e++)n=t[e],n.start.clone().stripZone()<j&&q.getEventEnd(n).stripZone()>U&&i.push(n);return i}function s(t,e){return!U||t<U||e>j}function o(t,e){return U=t,j=e,a()}function a(){return u(Q,"reset")}function l(t){return u(b(t))}function u(t,e){var n,i;for("reset"===e?K=[]:"add"!==e&&(K=C(K,t)),n=0;n<t.length;n++)i=t[n],"pending"!==i._status&&X++,i._fetchId=(i._fetchId||0)+1,i._status="pending";for(n=0;n<t.length;n++)i=t[n],h(i,i._fetchId);return X?he.construct(function(t){q.one("eventsReceived",t)}):he.resolve(Z)}function h(e,n){f(e,function(i){var r,s,o,a=t.isArray(e.events);if(n===e._fetchId&&"rejected"!==e._status){if(e._status="resolved",i)for(r=0;r<i.length;r++)s=i[r],(o=a?s:z(s,e))&&K.push.apply(K,_(o));d()}})}function c(t){var e="pending"===t._status;t._status="rejected",e&&d()}function d(){--X||(i(K),q.trigger("eventsReceived",Z))}function f(e,n){var i,r,s=Zt.sourceFetchers;for(i=0;i<s.length;i++){if(!0===(r=s[i].call(q,e,U.clone(),j.clone(),q.opt("timezone"),n)))return;if("object"==typeof r)return void f(r,n)}var o=e.events;if(o)t.isFunction(o)?(q.pushLoading(),o.call(q,U.clone(),j.clone(),q.opt("timezone"),function(t){n(t),q.popLoading()})):t.isArray(o)?n(o):n();else{if(e.url){var a,l=e.success,u=e.error,h=e.complete;a=t.isFunction(e.data)?e.data():e.data;var c=t.extend({},a||{}),d=ut(e.startParam,q.opt("startParam")),g=ut(e.endParam,q.opt("endParam")),p=ut(e.timezoneParam,q.opt("timezoneParam"));d&&(c[d]=U.format()),g&&(c[g]=j.format()),q.opt("timezone")&&"local"!=q.opt("timezone")&&(c[p]=q.opt("timezone")),q.pushLoading(),t.ajax(t.extend({},Be,e,{data:c,success:function(e){e=e||[];var i=lt(l,this,arguments);t.isArray(i)&&(e=i),n(e)},error:function(){lt(u,this,arguments),n()},complete:function(){lt(h,this,arguments),q.popLoading()}}))}else n()}}function g(t){var e=p(t);e&&(Q.push(e),u([e],"add"))}function p(e){var n,i,r=Zt.sourceNormalizers;if(t.isFunction(e)||t.isArray(e)?n={events:e}:"string"==typeof e?n={url:e}:"object"==typeof e&&(n=t.extend({},e)),n){for(n.className?"string"==typeof n.className&&(n.className=n.className.split(/\s+/)):n.className=[],t.isArray(n.events)&&(n.origArray=n.events,n.events=t.map(n.events,function(t){return z(t,n)})),i=0;i<r.length;i++)r[i].call(q,n);return n}}function v(t){y(E(t))}function m(t){null==t?y(Q,!0):y(b(t))}function y(e,n){var r;for(r=0;r<e.length;r++)c(e[r]);n?(Q=[],K=[]):(Q=t.grep(Q,function(t){for(r=0;r<e.length;r++)if(t===e[r])return!1;return!0}),K=C(K,e)),i()}function w(){return Q.slice(1)}function S(e){return t.grep(Q,function(t){return t.id&&t.id===e})[0]}function b(e){e?t.isArray(e)||(e=[e]):e=[];var n,i=[];for(n=0;n<e.length;n++)i.push.apply(i,E(e[n]));return i}function E(e){var n,i;for(n=0;n<Q.length;n++)if((i=Q[n])===e)return[i];return i=S(e),i?[i]:t.grep(Q,function(t){return D(e,t)})}function D(t,e){return t&&e&&T(t)==T(e)}function T(t){return("object"==typeof t?t.origArray||t.googleCalendarId||t.url||t.events:null)||t}function C(e,n){return t.grep(e,function(t){for(var e=0;e<n.length;e++)if(t.source===n[e])return!1;return!0})}function H(t){R([t])}function R(t){var e,n;for(e=0;e<t.length;e++)n=t[e],n.start=q.moment(n.start),n.end?n.end=q.moment(n.end):n.end=null,W(n,x(n));i()}function x(e){var n={};return t.each(e,function(t,e){I(t)&&void 0!==e&&at(e)&&(n[t]=e)}),n}function I(t){return!/^_|^(id|allDay|start|end)$/.test(t)}function k(t,e){return M([t],e)}function M(t,e){var n,r,s,o,a,l=[];for(s=0;s<t.length;s++)if(r=z(t[s])){for(n=_(r),o=0;o<n.length;o++)a=n[o],a.source||(e&&($.events.push(a),a.source=$),K.push(a));l=l.concat(n)}return l.length&&i(),l}function B(e){var n,r;for(null==e?e=function(){return!0}:t.isFunction(e)||(n=e+"",e=function(t){return t._id==n}),K=t.grep(K,e,!0),r=0;r<Q.length;r++)t.isArray(Q[r].events)&&(Q[r].events=t.grep(Q[r].events,e,!0));i()}function L(e){return t.isFunction(e)?t.grep(K,e):null!=e?(e+="",t.grep(K,function(t){return t._id==e})):K}function N(t){t.start=q.moment(t.start),t.end&&(t.end=q.moment(t.end)),jt(t)}function z(n,i){var r,s,o,a=q.opt("eventDataTransform"),l={};if(a&&(n=a(n)),i&&i.eventDataTransform&&(n=i.eventDataTransform(n)),t.extend(l,n),i&&(l.source=i),l._id=n._id||(void 0===n.id?"_fc"+Le++:n.id+""),n.className?"string"==typeof n.className?l.className=n.className.split(/\s+/):l.className=n.className:l.className=[],r=n.start||n.date,s=n.end,nt(r)&&(r=e.duration(r)),nt(s)&&(s=e.duration(s)),n.dow||e.isDuration(r)||e.isDuration(s))l.start=r?e.duration(r):null,l.end=s?e.duration(s):null,l._recurring=!0;else{if(r&&(r=q.moment(r),!r.isValid()))return!1;s&&(s=q.moment(s),s.isValid()||(s=null)),o=n.allDay,void 0===o&&(o=ut(i?i.allDayDefault:void 0,q.opt("allDayDefault"))),V(r,s,o,l)}return q.normalizeEvent(l),l}function V(t,e,n,i){i.start=t,i.end=e,i.allDay=n,O(i),jt(i)}function O(t){P(t),t.end&&!t.end.isAfter(t.start)&&(t.end=null),t.end||(q.opt("forceEventDuration")?t.end=q.getDefaultEventEnd(t.allDay,t.start):t.end=null)}function P(t){null==t.allDay&&(t.allDay=!(t.start.hasTime()||t.end&&t.end.hasTime())),t.allDay?(t.start.stripTime(),t.end&&t.end.stripTime()):(t.start.hasTime()||(t.start=q.applyTimezone(t.start.time(0))),t.end&&!t.end.hasTime()&&(t.end=q.applyTimezone(t.end.time(0))))}function _(e,n,i){var r,s,o,a,l,u,h,c,d,f=[];if(n=n||U,i=i||j,e)if(e._recurring){if(s=e.dow)for(r={},o=0;o<s.length;o++)r[s[o]]=!0;for(a=n.clone().stripTime();a.isBefore(i);)r&&!r[a.day()]||(l=e.start,u=e.end,h=a.clone(),c=null,l&&(h=h.time(l)),u&&(c=a.clone().time(u)),d=t.extend({},e),V(h,c,!l&&!u,d),f.push(d)),a.add(1,"days")}else f.push(e);return f}function W(e,n,i){function r(t,e){return i?G(t,e,i):n.allDay?A(t,e):F(t,e)}var s,o,a,l,u,h,c={};return n=n||{},n.start||(n.start=e.start.clone()),void 0===n.end&&(n.end=e.end?e.end.clone():null),null==n.allDay&&(n.allDay=e.allDay),O(n),s={start:e._start.clone(),end:e._end?e._end.clone():q.getDefaultEventEnd(e._allDay,e._start),allDay:n.allDay},O(s),o=null!==e._end&&null===n.end,a=r(n.start,s.start),n.end?(l=r(n.end,s.end),u=l.subtract(a)):u=null,t.each(n,function(t,e){I(t)&&void 0!==e&&(c[t]=e)}),h=Y(L(e._id),o,n.allDay,a,u,c),{dateDelta:a,durationDelta:u,undo:h}}function Y(e,n,i,r,s,o){var a=q.getIsAmbigTimezone(),l=[];return r&&!r.valueOf()&&(r=null),s&&!s.valueOf()&&(s=null),t.each(e,function(e,u){var h,c;h={start:u.start.clone(),end:u.end?u.end.clone():null,allDay:u.allDay},t.each(o,function(t){h[t]=u[t]}),c={start:u._start,end:u._end,allDay:i},O(c),n?c.end=null:s&&!c.end&&(c.end=q.getDefaultEventEnd(c.allDay,c.start)),r&&(c.start.add(r),c.end&&c.end.add(r)),s&&c.end.add(s),a&&!c.allDay&&(r||s)&&(c.start.stripZone(),c.end&&c.end.stripZone()),t.extend(u,o,c),jt(u),l.push(function(){t.extend(u,h),jt(u)})}),function(){for(var t=0;t<l.length;t++)l[t]()}}var q=this;q.requestEvents=n,q.reportEventChange=i,q.isFetchNeeded=s,q.fetchEvents=o,q.fetchEventSources=u,q.refetchEvents=a,q.refetchEventSources=l,q.getEventSources=w,q.getEventSourceById=S,q.addEventSource=g,q.removeEventSource=v,q.removeEventSources=m,q.updateEvent=H,q.updateEvents=R,q.renderEvent=k,q.renderEvents=M,q.removeEvents=B,q.clientEvents=L,q.mutateEvent=W,q.normalizeEventDates=O,q.normalizeEventTimes=P;var U,j,Z,$={events:[]},Q=[$],X=0,K=[];t.each((q.opt("events")?[q.opt("events")]:[]).concat(q.opt("eventSources")||[]),function(t,e){var n=p(e);n&&Q.push(n)}),q.getEventCache=function(){return K},q.rezoneArrayEventSources=function(){var e,n,i;for(e=0;e<Q.length;e++)if(n=Q[e].events,t.isArray(n))for(i=0;i<n.length;i++)N(n[i])},q.buildEventFromInput=z,q.expandEvent=_}function jt(t){t._allDay=t.allDay,t._start=t.start.clone(),t._end=t.end?t.end.clone():null}var Zt=t.fullCalendar={version:"3.4.0",internalApiVersion:9},$t=Zt.views={};t.fn.fullCalendar=function(e){var n=Array.prototype.slice.call(arguments,1),i=this;return this.each(function(r,s){var o,a=t(s),l=a.data("fullCalendar");"string"==typeof e?l&&t.isFunction(l[e])&&(o=l[e].apply(l,n),r||(i=o),"destroy"===e&&a.removeData("fullCalendar")):l||(l=new Re(a,e),a.data("fullCalendar",l),l.render())}),i};var Qt=["header","footer","buttonText","buttonIcons","themeButtonIcons"];Zt.intersectRanges=z,Zt.applyAll=lt,Zt.debounce=yt,Zt.isInt=vt,Zt.htmlEscape=ht,Zt.cssToStr=dt,Zt.proxy=mt,Zt.capitaliseFirstLetter=gt,Zt.getOuterRect=d,Zt.getClientRect=f,Zt.getContentRect=g,Zt.getScrollbarWidths=p;var Xt=null;Zt.preventDefault=H,Zt.intersectRects=R,Zt.parseFieldSpecs=M,Zt.compareByFieldSpecs=B,Zt.compareByFieldSpec=L,Zt.flexibleCompare=N,Zt.computeGreatestUnit=V,Zt.divideRangeByDuration=_,Zt.divideDurationByDuration=W,Zt.multiplyDuration=Y,Zt.durationHasTime=tt;var Kt=["sun","mon","tue","wed","thu","fri","sat"],Jt=["year","month","week","day","hour","minute","second","millisecond"];Zt.log=function(){var t=window.console;if(t&&t.log)return t.log.apply(t,arguments)},Zt.warn=function(){var t=window.console;return t&&t.warn?t.warn.apply(t,arguments):Zt.log.apply(Zt,arguments)};var te={}.hasOwnProperty;Zt.createObject=rt;var ee=/^\s*\d{4}-\d\d$/,ne=/^\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+)?)?)?)?)?$/,ie=e.fn,re=t.extend({},ie),se=e.momentProperties;se.push("_fullCalendar"),se.push("_ambigTime"),se.push("_ambigZone"),Zt.moment=function(){return wt(arguments)},Zt.moment.utc=function(){var t=wt(arguments,!0);return t.hasTime()&&t.utc(),t},Zt.moment.parseZone=function(){return wt(arguments,!0,!0)},ie.week=ie.weeks=function(t){var e=this._locale._fullCalendar_weekCalc;return null==t&&"function"==typeof e?e(this):"ISO"===e?re.isoWeek.apply(this,arguments):re.week.apply(this,arguments)},ie.time=function(t){if(!this._fullCalendar)return re.time.apply(this,arguments);if(null==t)return e.duration({hours:this.hours(),minutes:this.minutes(),seconds:this.seconds(),milliseconds:this.milliseconds()});this._ambigTime=!1,e.isDuration(t)||e.isMoment(t)||(t=e.duration(t));var n=0;return e.isDuration(t)&&(n=24*Math.floor(t.asDays())),this.hours(n+t.hours()).minutes(t.minutes()).seconds(t.seconds()).milliseconds(t.milliseconds())},ie.stripTime=function(){return this._ambigTime||(this.utc(!0),this.set({hours:0,minutes:0,seconds:0,ms:0}),this._ambigTime=!0,this._ambigZone=!0),this},ie.hasTime=function(){return!this._ambigTime},ie.stripZone=function(){var t;return this._ambigZone||(t=this._ambigTime,this.utc(!0),this._ambigTime=t||!1,this._ambigZone=!0),this},ie.hasZone=function(){return!this._ambigZone},ie.local=function(t){return re.local.call(this,this._ambigZone||t),this._ambigTime=!1,this._ambigZone=!1,this},ie.utc=function(t){return re.utc.call(this,t),this._ambigTime=!1,this._ambigZone=!1,this},ie.utcOffset=function(t){return null!=t&&(this._ambigTime=!1,this._ambigZone=!1),re.utcOffset.apply(this,arguments)},ie.format=function(){return this._fullCalendar&&arguments[0]?oe(this,arguments[0]):this._ambigTime?le(St(this),"YYYY-MM-DD"):this._ambigZone?le(St(this),"YYYY-MM-DD[T]HH:mm:ss"):this._fullCalendar?le(St(this)):re.format.apply(this,arguments)},ie.toISOString=function(){return this._ambigTime?le(St(this),"YYYY-MM-DD"):this._ambigZone?le(St(this),"YYYY-MM-DD[T]HH:mm:ss"):this._fullCalendar?re.toISOString.apply(St(this),arguments):re.toISOString.apply(this,arguments)},function(){function t(t,e){return h(r(e).fakeFormatString,t)}function e(t,e){return re.format.call(t,e)}function n(t,e,n,s,o){var a;return t=Zt.moment.parseZone(t),e=Zt.moment.parseZone(e),a=t.localeData(),n=a.longDateFormat(n)||n,i(r(n),t,e,s||" - ",o)}function i(t,e,n,i,r){var s,o,a,l=t.sameUnits,u=e.clone().stripZone(),h=n.clone().stripZone(),f=c(t.fakeFormatString,e),g=c(t.fakeFormatString,n),p="",v="",m="",y="",w="";for(s=0;s<l.length&&(!l[s]||u.isSame(h,l[s]));s++)p+=f[s];for(o=l.length-1;o>s&&(!l[o]||u.isSame(h,l[o]))&&(o-1!==s||"."!==f[o]);o--)v=f[o]+v;for(a=s;a<=o;a++)m+=f[a],y+=g[a];return(m||y)&&(w=r?y+i+m:m+i+y),d(p+w+v)}function r(t){return S[t]||(S[t]=s(t))}function s(t){var e=o(t);return{fakeFormatString:l(e),sameUnits:u(e)}}function o(t){for(var e,n=[],i=/\[([^\]]*)\]|\(([^\)]*)\)|(LTS|LT|(\w)\4*o?)|([^\w\[\(]+)/g;e=i.exec(t);)e[1]?n.push.apply(n,a(e[1])):e[2]?n.push({maybe:o(e[2])}):e[3]?n.push({token:e[3]}):e[5]&&n.push.apply(n,a(e[5]));return n}function a(t){return". "===t?["."," "]:[t]}function l(t){var e,n,i=[];for(e=0;e<t.length;e++)n=t[e],"string"==typeof n?i.push("["+n+"]"):n.token?n.token in y?i.push(p+"["+n.token+"]"):i.push(n.token):n.maybe&&i.push(v+l(n.maybe)+v);return i.join(g)}function u(t){var e,n,i,r=[];for(e=0;e<t.length;e++)n=t[e],n.token?(i=w[n.token.charAt(0)],r.push(i?i.unit:"second")):n.maybe?r.push.apply(r,u(n.maybe)):r.push(null);return r}function h(t,e){return d(c(t,e).join(""))}function c(t,n){var i,r,s=[],o=e(n,t),a=o.split(g);for(i=0;i<a.length;i++)r=a[i],r.charAt(0)===p?s.push(y[r.substring(1)](n)):s.push(r);return s}function d(t){return t.replace(m,function(t,e){return e.match(/[1-9]/)?e:""})}function f(t){var e,n,i,r,s=o(t);for(e=0;e<s.length;e++)n=s[e],n.token&&(i=w[n.token.charAt(0)])&&(!r||i.value>r.value)&&(r=i);return r?r.unit:null}Zt.formatDate=t,Zt.formatRange=n,Zt.oldMomentFormat=e,Zt.queryMostGranularFormatUnit=f;var g="\v",p="",v="",m=new RegExp(v+"([^"+v+"]*)"+v,"g"),y={t:function(t){return e(t,"a").charAt(0)},T:function(t){return e(t,"A").charAt(0)}},w={Y:{value:1,unit:"year"},M:{value:2,unit:"month"},W:{value:3,unit:"week"},w:{value:3,unit:"week"},D:{value:4,unit:"day"},d:{value:4,unit:"day"}},S={}}();var oe=Zt.formatDate,ae=Zt.formatRange,le=Zt.oldMomentFormat;Zt.Class=bt,bt.extend=function(){var t,e,n=arguments.length;for(t=0;t<n;t++)e=arguments[t],t<n-1&&Dt(this,e);return Et(this,e||{})},bt.mixin=function(t){Dt(this,t)};var ue=bt.extend(fe,ge,{_props:null,_watchers:null,_globalWatchArgs:null,constructor:function(){this._watchers={},this._props={},this.applyGlobalWatchers()},applyGlobalWatchers:function(){var t,e=this._globalWatchArgs||[];for(t=0;t<e.length;t++)this.watch.apply(this,e[t])},has:function(t){return t in this._props},get:function(t){return void 0===t?this._props:this._props[t]},set:function(t,e){var n;"string"==typeof t?(n={},n[t]=void 0===e?null:e):n=t,this.setProps(n)},reset:function(t){var e,n=this._props,i={};for(e in n)i[e]=void 0;for(e in t)i[e]=t[e];this.setProps(i)},unset:function(t){var e,n,i={};for(e="string"==typeof t?[t]:t,n=0;n<e.length;n++)i[e[n]]=void 0;this.setProps(i)},setProps:function(t){var e,n,i={},r=0;for(e in t)"object"!=typeof(n=t[e])&&n===this._props[e]||(i[e]=n,r++);if(r){this.trigger("before:batchChange",i);for(e in i)n=i[e],this.trigger("before:change",e,n),this.trigger("before:change:"+e,n);for(e in i)n=i[e],void 0===n?delete this._props[e]:this._props[e]=n,this.trigger("change:"+e,n),this.trigger("change",e,n);this.trigger("batchChange",i)}},watch:function(t,e,n,i){var r=this;this.unwatch(t),this._watchers[t]=this._watchDeps(e,function(e){var i=n.call(r,e);i&&i.then?(r.unset(t),i.then(function(e){r.set(t,e)})):r.set(t,i)},function(){r.unset(t),i&&i.call(r)})},unwatch:function(t){var e=this._watchers[t];e&&(delete this._watchers[t],e.teardown())},_watchDeps:function(t,e,n){function i(t,e,i){1===++a&&u===l&&(d=!0,n(),d=!1)}function r(t,n,i){void 0===n?(i||void 0===h[t]||u--,delete h[t]):(i||void 0!==h[t]||u++,h[t]=n),--a||u===l&&(d||e(h))}function s(t,e){o.on(t,e),c.push([t,e])}var o=this,a=0,l=t.length,u=0,h={},c=[],d=!1;return t.forEach(function(t){var e=!1;"?"===t.charAt(0)&&(t=t.substring(1),e=!0),s("before:change:"+t,function(n){i(t,n,e)}),s("change:"+t,function(n){r(t,n,e)})}),t.forEach(function(t){var e=!1;"?"===t.charAt(0)&&(t=t.substring(1),e=!0),o.has(t)?(h[t]=o.get(t),u++):e&&u++}),u===l&&e(h),{teardown:function(){for(var t=0;t<c.length;t++)o.off(c[t][0],c[t][1]);c=null,u===l&&n()},flash:function(){u===l&&(n(),e(h))}}},flash:function(t){var e=this._watchers[t];e&&e.flash()}});ue.watch=function(){var t=this.prototype;t._globalWatchArgs||(t._globalWatchArgs=[]),t._globalWatchArgs.push(arguments)},Zt.Model=ue;var he={construct:function(e){var n=t.Deferred(),i=n.promise();return"function"==typeof e&&e(function(t){n.resolve(t),Tt(i,t)},function(){n.reject(),Ct(i)}),i},resolve:function(e){var n=t.Deferred().resolve(e),i=n.promise();return Tt(i,e),i},reject:function(){var e=t.Deferred().reject(),n=e.promise();return Ct(n),n}};Zt.Promise=he;var ce=bt.extend(fe,{q:null,isPaused:!1,isRunning:!1,constructor:function(){this.q=[]},queue:function(){this.q.push.apply(this.q,arguments),this.tryStart()},pause:function(){this.isPaused=!0},resume:function(){this.isPaused=!1,this.tryStart()},tryStart:function(){!this.isRunning&&this.canRunNext()&&(this.isRunning=!0,this.trigger("start"),this.runNext())},canRunNext:function(){return!this.isPaused&&this.q.length},runNext:function(){this.runTask(this.q.shift())},runTask:function(t){this.runTaskFunc(t)},runTaskFunc:function(t){function e(){n.canRunNext()?n.runNext():(n.isRunning=!1,n.trigger("stop"))}var n=this,i=t();i&&i.then?i.then(e):e()}});Zt.TaskQueue=ce;var de=ce.extend({waitsByNamespace:null,waitNamespace:null,waitId:null,constructor:function(t){ce.call(this),this.waitsByNamespace=t||{}},queue:function(t,e,n){var i,r={func:t,namespace:e,type:n};e&&(i=this.waitsByNamespace[e]),this.waitNamespace&&(e===this.waitNamespace&&null!=i?this.delayWait(i):(this.clearWait(),this.tryStart())),this.compoundTask(r)&&(this.waitNamespace||null==i?this.tryStart():this.startWait(e,i))},startWait:function(t,e){this.waitNamespace=t,this.spawnWait(e)},delayWait:function(t){clearTimeout(this.waitId),this.spawnWait(t)},spawnWait:function(t){var e=this;this.waitId=setTimeout(function(){e.waitNamespace=null,e.tryStart()},t)},clearWait:function(){this.waitNamespace&&(clearTimeout(this.waitId),this.waitId=null,this.waitNamespace=null)},canRunNext:function(){if(!ce.prototype.canRunNext.apply(this,arguments))return!1;if(this.waitNamespace){for(var t=this.q,e=0;e<t.length;e++)if(t[e].namespace!==this.waitNamespace)return!0;return!1}return!0},runTask:function(t){this.runTaskFunc(t.func)},compoundTask:function(t){var e,n,i=this.q,r=!0;if(t.namespace&&("destroy"===t.type||"init"===t.type)){for(e=i.length-1;e>=0;e--)n=i[e],n.namespace!==t.namespace||"add"!==n.type&&"remove"!==n.type||i.splice(e,1);"destroy"===t.type?i.length&&(n=i[i.length-1],n.namespace===t.namespace&&("init"===n.type?(r=!1,i.pop()):"destroy"===n.type&&(r=!1))):"init"===t.type&&i.length&&(n=i[i.length-1],n.namespace===t.namespace&&"init"===n.type&&i.pop())}return r&&i.push(t),r}});Zt.RenderQueue=de;var fe=Zt.EmitterMixin={on:function(e,n){return t(this).on(e,this._prepareIntercept(n)),this},one:function(e,n){return t(this).one(e,this._prepareIntercept(n)),this},_prepareIntercept:function(e){var n=function(t,n){return e.apply(n.context||this,n.args||[])};return e.guid||(e.guid=t.guid++),n.guid=e.guid,n},
7   -off:function(e,n){return t(this).off(e,n),this},trigger:function(e){var n=Array.prototype.slice.call(arguments,1);return t(this).triggerHandler(e,{args:n}),this},triggerWith:function(e,n,i){return t(this).triggerHandler(e,{context:n,args:i}),this}},ge=Zt.ListenerMixin=function(){var e=0;return{listenerId:null,listenTo:function(e,n,i){if("object"==typeof n)for(var r in n)n.hasOwnProperty(r)&&this.listenTo(e,r,n[r]);else"string"==typeof n&&e.on(n+"."+this.getListenerNamespace(),t.proxy(i,this))},stopListeningTo:function(t,e){t.off((e||"")+"."+this.getListenerNamespace())},getListenerNamespace:function(){return null==this.listenerId&&(this.listenerId=e++),"_listener"+this.listenerId}}}(),pe=bt.extend(ge,{isHidden:!0,options:null,el:null,margin:10,constructor:function(t){this.options=t||{}},show:function(){this.isHidden&&(this.el||this.render(),this.el.show(),this.position(),this.isHidden=!1,this.trigger("show"))},hide:function(){this.isHidden||(this.el.hide(),this.isHidden=!0,this.trigger("hide"))},render:function(){var e=this,n=this.options;this.el=t('<div class="fc-popover"/>').addClass(n.className||"").css({top:0,left:0}).append(n.content).appendTo(n.parentEl),this.el.on("click",".fc-close",function(){e.hide()}),n.autoHide&&this.listenTo(t(document),"mousedown",this.documentMousedown)},documentMousedown:function(e){this.el&&!t(e.target).closest(this.el).length&&this.hide()},removeElement:function(){this.hide(),this.el&&(this.el.remove(),this.el=null),this.stopListeningTo(t(document),"mousedown")},position:function(){var e,n,i,r,s,o=this.options,a=this.el.offsetParent().offset(),l=this.el.outerWidth(),u=this.el.outerHeight(),h=t(window),d=c(this.el);r=o.top||0,s=void 0!==o.left?o.left:void 0!==o.right?o.right-l:0,d.is(window)||d.is(document)?(d=h,e=0,n=0):(i=d.offset(),e=i.top,n=i.left),e+=h.scrollTop(),n+=h.scrollLeft(),!1!==o.viewportConstrain&&(r=Math.min(r,e+d.outerHeight()-u-this.margin),r=Math.max(r,e+this.margin),s=Math.min(s,n+d.outerWidth()-l-this.margin),s=Math.max(s,n+this.margin)),this.el.css({top:r-a.top,left:s-a.left})},trigger:function(t){this.options[t]&&this.options[t].apply(this,Array.prototype.slice.call(arguments,1))}}),ve=Zt.CoordCache=bt.extend({els:null,forcedOffsetParentEl:null,origin:null,boundingRect:null,isHorizontal:!1,isVertical:!1,lefts:null,rights:null,tops:null,bottoms:null,constructor:function(e){this.els=t(e.els),this.isHorizontal=e.isHorizontal,this.isVertical=e.isVertical,this.forcedOffsetParentEl=e.offsetParent?t(e.offsetParent):null},build:function(){var t=this.forcedOffsetParentEl;!t&&this.els.length>0&&(t=this.els.eq(0).offsetParent()),this.origin=t?t.offset():null,this.boundingRect=this.queryBoundingRect(),this.isHorizontal&&this.buildElHorizontals(),this.isVertical&&this.buildElVerticals()},clear:function(){this.origin=null,this.boundingRect=null,this.lefts=null,this.rights=null,this.tops=null,this.bottoms=null},ensureBuilt:function(){this.origin||this.build()},buildElHorizontals:function(){var e=[],n=[];this.els.each(function(i,r){var s=t(r),o=s.offset().left,a=s.outerWidth();e.push(o),n.push(o+a)}),this.lefts=e,this.rights=n},buildElVerticals:function(){var e=[],n=[];this.els.each(function(i,r){var s=t(r),o=s.offset().top,a=s.outerHeight();e.push(o),n.push(o+a)}),this.tops=e,this.bottoms=n},getHorizontalIndex:function(t){this.ensureBuilt();var e,n=this.lefts,i=this.rights,r=n.length;for(e=0;e<r;e++)if(t>=n[e]&&t<i[e])return e},getVerticalIndex:function(t){this.ensureBuilt();var e,n=this.tops,i=this.bottoms,r=n.length;for(e=0;e<r;e++)if(t>=n[e]&&t<i[e])return e},getLeftOffset:function(t){return this.ensureBuilt(),this.lefts[t]},getLeftPosition:function(t){return this.ensureBuilt(),this.lefts[t]-this.origin.left},getRightOffset:function(t){return this.ensureBuilt(),this.rights[t]},getRightPosition:function(t){return this.ensureBuilt(),this.rights[t]-this.origin.left},getWidth:function(t){return this.ensureBuilt(),this.rights[t]-this.lefts[t]},getTopOffset:function(t){return this.ensureBuilt(),this.tops[t]},getTopPosition:function(t){return this.ensureBuilt(),this.tops[t]-this.origin.top},getBottomOffset:function(t){return this.ensureBuilt(),this.bottoms[t]},getBottomPosition:function(t){return this.ensureBuilt(),this.bottoms[t]-this.origin.top},getHeight:function(t){return this.ensureBuilt(),this.bottoms[t]-this.tops[t]},queryBoundingRect:function(){var t;return this.els.length>0&&(t=c(this.els.eq(0)),!t.is(document))?f(t):null},isPointInBounds:function(t,e){return this.isLeftInBounds(t)&&this.isTopInBounds(e)},isLeftInBounds:function(t){return!this.boundingRect||t>=this.boundingRect.left&&t<this.boundingRect.right},isTopInBounds:function(t){return!this.boundingRect||t>=this.boundingRect.top&&t<this.boundingRect.bottom}}),me=Zt.DragListener=bt.extend(ge,{options:null,subjectEl:null,originX:null,originY:null,scrollEl:null,isInteracting:!1,isDistanceSurpassed:!1,isDelayEnded:!1,isDragging:!1,isTouch:!1,isGeneric:!1,delay:null,delayTimeoutId:null,minDistance:null,shouldCancelTouchScroll:!0,scrollAlwaysKills:!1,constructor:function(t){this.options=t||{}},startInteraction:function(e,n){if("mousedown"===e.type){if(we.get().shouldIgnoreMouse())return;if(!S(e))return;e.preventDefault()}this.isInteracting||(n=n||{},this.delay=ut(n.delay,this.options.delay,0),this.minDistance=ut(n.distance,this.options.distance,0),this.subjectEl=this.options.subjectEl,T(t("body")),this.isInteracting=!0,this.isTouch=D(e),this.isGeneric="dragstart"===e.type,this.isDelayEnded=!1,this.isDistanceSurpassed=!1,this.originX=b(e),this.originY=E(e),this.scrollEl=c(t(e.target)),this.bindHandlers(),this.initAutoScroll(),this.handleInteractionStart(e),this.startDelay(e),this.minDistance||this.handleDistanceSurpassed(e))},handleInteractionStart:function(t){this.trigger("interactionStart",t)},endInteraction:function(e,n){this.isInteracting&&(this.endDrag(e),this.delayTimeoutId&&(clearTimeout(this.delayTimeoutId),this.delayTimeoutId=null),this.destroyAutoScroll(),this.unbindHandlers(),this.isInteracting=!1,this.handleInteractionEnd(e,n),C(t("body")))},handleInteractionEnd:function(t,e){this.trigger("interactionEnd",t,e||!1)},bindHandlers:function(){var e=we.get();this.isGeneric?this.listenTo(t(document),{drag:this.handleMove,dragstop:this.endInteraction}):this.isTouch?this.listenTo(e,{touchmove:this.handleTouchMove,touchend:this.endInteraction,scroll:this.handleTouchScroll}):this.listenTo(e,{mousemove:this.handleMouseMove,mouseup:this.endInteraction}),this.listenTo(e,{selectstart:H,contextmenu:H})},unbindHandlers:function(){this.stopListeningTo(we.get()),this.stopListeningTo(t(document))},startDrag:function(t,e){this.startInteraction(t,e),this.isDragging||(this.isDragging=!0,this.handleDragStart(t))},handleDragStart:function(t){this.trigger("dragStart",t)},handleMove:function(t){var e=b(t)-this.originX,n=E(t)-this.originY,i=this.minDistance;this.isDistanceSurpassed||e*e+n*n>=i*i&&this.handleDistanceSurpassed(t),this.isDragging&&this.handleDrag(e,n,t)},handleDrag:function(t,e,n){this.trigger("drag",t,e,n),this.updateAutoScroll(n)},endDrag:function(t){this.isDragging&&(this.isDragging=!1,this.handleDragEnd(t))},handleDragEnd:function(t){this.trigger("dragEnd",t)},startDelay:function(t){var e=this;this.delay?this.delayTimeoutId=setTimeout(function(){e.handleDelayEnd(t)},this.delay):this.handleDelayEnd(t)},handleDelayEnd:function(t){this.isDelayEnded=!0,this.isDistanceSurpassed&&this.startDrag(t)},handleDistanceSurpassed:function(t){this.isDistanceSurpassed=!0,this.isDelayEnded&&this.startDrag(t)},handleTouchMove:function(t){this.isDragging&&this.shouldCancelTouchScroll&&t.preventDefault(),this.handleMove(t)},handleMouseMove:function(t){this.handleMove(t)},handleTouchScroll:function(t){this.isDragging&&!this.scrollAlwaysKills||this.endInteraction(t,!0)},trigger:function(t){this.options[t]&&this.options[t].apply(this,Array.prototype.slice.call(arguments,1)),this["_"+t]&&this["_"+t].apply(this,Array.prototype.slice.call(arguments,1))}});me.mixin({isAutoScroll:!1,scrollBounds:null,scrollTopVel:null,scrollLeftVel:null,scrollIntervalId:null,scrollSensitivity:30,scrollSpeed:200,scrollIntervalMs:50,initAutoScroll:function(){var t=this.scrollEl;this.isAutoScroll=this.options.scroll&&t&&!t.is(window)&&!t.is(document),this.isAutoScroll&&this.listenTo(t,"scroll",yt(this.handleDebouncedScroll,100))},destroyAutoScroll:function(){this.endAutoScroll(),this.isAutoScroll&&this.stopListeningTo(this.scrollEl,"scroll")},computeScrollBounds:function(){this.isAutoScroll&&(this.scrollBounds=d(this.scrollEl))},updateAutoScroll:function(t){var e,n,i,r,s=this.scrollSensitivity,o=this.scrollBounds,a=0,l=0;o&&(e=(s-(E(t)-o.top))/s,n=(s-(o.bottom-E(t)))/s,i=(s-(b(t)-o.left))/s,r=(s-(o.right-b(t)))/s,e>=0&&e<=1?a=e*this.scrollSpeed*-1:n>=0&&n<=1&&(a=n*this.scrollSpeed),i>=0&&i<=1?l=i*this.scrollSpeed*-1:r>=0&&r<=1&&(l=r*this.scrollSpeed)),this.setScrollVel(a,l)},setScrollVel:function(t,e){this.scrollTopVel=t,this.scrollLeftVel=e,this.constrainScrollVel(),!this.scrollTopVel&&!this.scrollLeftVel||this.scrollIntervalId||(this.scrollIntervalId=setInterval(mt(this,"scrollIntervalFunc"),this.scrollIntervalMs))},constrainScrollVel:function(){var t=this.scrollEl;this.scrollTopVel<0?t.scrollTop()<=0&&(this.scrollTopVel=0):this.scrollTopVel>0&&t.scrollTop()+t[0].clientHeight>=t[0].scrollHeight&&(this.scrollTopVel=0),this.scrollLeftVel<0?t.scrollLeft()<=0&&(this.scrollLeftVel=0):this.scrollLeftVel>0&&t.scrollLeft()+t[0].clientWidth>=t[0].scrollWidth&&(this.scrollLeftVel=0)},scrollIntervalFunc:function(){var t=this.scrollEl,e=this.scrollIntervalMs/1e3;this.scrollTopVel&&t.scrollTop(t.scrollTop()+this.scrollTopVel*e),this.scrollLeftVel&&t.scrollLeft(t.scrollLeft()+this.scrollLeftVel*e),this.constrainScrollVel(),this.scrollTopVel||this.scrollLeftVel||this.endAutoScroll()},endAutoScroll:function(){this.scrollIntervalId&&(clearInterval(this.scrollIntervalId),this.scrollIntervalId=null,this.handleScrollEnd())},handleDebouncedScroll:function(){this.scrollIntervalId||this.handleScrollEnd()},handleScrollEnd:function(){}});var ye=me.extend({component:null,origHit:null,hit:null,coordAdjust:null,constructor:function(t,e){me.call(this,e),this.component=t},handleInteractionStart:function(t){var e,n,i,r=this.subjectEl;this.component.hitsNeeded(),this.computeScrollBounds(),t?(n={left:b(t),top:E(t)},i=n,r&&(e=d(r),i=x(i,e)),this.origHit=this.queryHit(i.left,i.top),r&&this.options.subjectCenter&&(this.origHit&&(e=R(this.origHit,e)||e),i=I(e)),this.coordAdjust=k(i,n)):(this.origHit=null,this.coordAdjust=null),me.prototype.handleInteractionStart.apply(this,arguments)},handleDragStart:function(t){var e;me.prototype.handleDragStart.apply(this,arguments),(e=this.queryHit(b(t),E(t)))&&this.handleHitOver(e)},handleDrag:function(t,e,n){var i;me.prototype.handleDrag.apply(this,arguments),i=this.queryHit(b(n),E(n)),Ht(i,this.hit)||(this.hit&&this.handleHitOut(),i&&this.handleHitOver(i))},handleDragEnd:function(){this.handleHitDone(),me.prototype.handleDragEnd.apply(this,arguments)},handleHitOver:function(t){var e=Ht(t,this.origHit);this.hit=t,this.trigger("hitOver",this.hit,e,this.origHit)},handleHitOut:function(){this.hit&&(this.trigger("hitOut",this.hit),this.handleHitDone(),this.hit=null)},handleHitDone:function(){this.hit&&this.trigger("hitDone",this.hit)},handleInteractionEnd:function(){me.prototype.handleInteractionEnd.apply(this,arguments),this.origHit=null,this.hit=null,this.component.hitsNotNeeded()},handleScrollEnd:function(){me.prototype.handleScrollEnd.apply(this,arguments),this.isDragging&&(this.component.releaseHits(),this.component.prepareHits())},queryHit:function(t,e){return this.coordAdjust&&(t+=this.coordAdjust.left,e+=this.coordAdjust.top),this.component.queryHit(t,e)}});Zt.touchMouseIgnoreWait=500;var we=bt.extend(ge,fe,{isTouching:!1,mouseIgnoreDepth:0,handleScrollProxy:null,bind:function(){var e=this;this.listenTo(t(document),{touchstart:this.handleTouchStart,touchcancel:this.handleTouchCancel,touchend:this.handleTouchEnd,mousedown:this.handleMouseDown,mousemove:this.handleMouseMove,mouseup:this.handleMouseUp,click:this.handleClick,selectstart:this.handleSelectStart,contextmenu:this.handleContextMenu}),window.addEventListener("touchmove",this.handleTouchMoveProxy=function(n){e.handleTouchMove(t.Event(n))},{passive:!1}),window.addEventListener("scroll",this.handleScrollProxy=function(n){e.handleScroll(t.Event(n))},!0)},unbind:function(){this.stopListeningTo(t(document)),window.removeEventListener("touchmove",this.handleTouchMoveProxy),window.removeEventListener("scroll",this.handleScrollProxy,!0)},handleTouchStart:function(t){this.stopTouch(t,!0),this.isTouching=!0,this.trigger("touchstart",t)},handleTouchMove:function(t){this.isTouching&&this.trigger("touchmove",t)},handleTouchCancel:function(t){this.isTouching&&(this.trigger("touchcancel",t),this.stopTouch(t))},handleTouchEnd:function(t){this.stopTouch(t)},handleMouseDown:function(t){this.shouldIgnoreMouse()||this.trigger("mousedown",t)},handleMouseMove:function(t){this.shouldIgnoreMouse()||this.trigger("mousemove",t)},handleMouseUp:function(t){this.shouldIgnoreMouse()||this.trigger("mouseup",t)},handleClick:function(t){this.shouldIgnoreMouse()||this.trigger("click",t)},handleSelectStart:function(t){this.trigger("selectstart",t)},handleContextMenu:function(t){this.trigger("contextmenu",t)},handleScroll:function(t){this.trigger("scroll",t)},stopTouch:function(t,e){this.isTouching&&(this.isTouching=!1,this.trigger("touchend",t),e||this.startTouchMouseIgnore())},startTouchMouseIgnore:function(){var t=this,e=Zt.touchMouseIgnoreWait;e&&(this.mouseIgnoreDepth++,setTimeout(function(){t.mouseIgnoreDepth--},e))},shouldIgnoreMouse:function(){return this.isTouching||Boolean(this.mouseIgnoreDepth)}});!function(){var t=null,e=0;we.get=function(){return t||(t=new we,t.bind()),t},we.needed=function(){we.get(),e++},we.unneeded=function(){--e||(t.unbind(),t=null)}}();var Se=bt.extend(ge,{options:null,sourceEl:null,el:null,parentEl:null,top0:null,left0:null,y0:null,x0:null,topDelta:null,leftDelta:null,isFollowing:!1,isHidden:!1,isAnimating:!1,constructor:function(e,n){this.options=n=n||{},this.sourceEl=e,this.parentEl=n.parentEl?t(n.parentEl):e.parent()},start:function(e){this.isFollowing||(this.isFollowing=!0,this.y0=E(e),this.x0=b(e),this.topDelta=0,this.leftDelta=0,this.isHidden||this.updatePosition(),D(e)?this.listenTo(t(document),"touchmove",this.handleMove):this.listenTo(t(document),"mousemove",this.handleMove))},stop:function(e,n){function i(){r.isAnimating=!1,r.removeElement(),r.top0=r.left0=null,n&&n()}var r=this,s=this.options.revertDuration;this.isFollowing&&!this.isAnimating&&(this.isFollowing=!1,this.stopListeningTo(t(document)),e&&s&&!this.isHidden?(this.isAnimating=!0,this.el.animate({top:this.top0,left:this.left0},{duration:s,complete:i})):i())},getEl:function(){var t=this.el;return t||(t=this.el=this.sourceEl.clone().addClass(this.options.additionalClass||"").css({position:"absolute",visibility:"",display:this.isHidden?"none":"",margin:0,right:"auto",bottom:"auto",width:this.sourceEl.width(),height:this.sourceEl.height(),opacity:this.options.opacity||"",zIndex:this.options.zIndex}),t.addClass("fc-unselectable"),t.appendTo(this.parentEl)),t},removeElement:function(){this.el&&(this.el.remove(),this.el=null)},updatePosition:function(){var t,e;this.getEl(),null===this.top0&&(t=this.sourceEl.offset(),e=this.el.offsetParent().offset(),this.top0=t.top-e.top,this.left0=t.left-e.left),this.el.css({top:this.top0+this.topDelta,left:this.left0+this.leftDelta})},handleMove:function(t){this.topDelta=E(t)-this.y0,this.leftDelta=b(t)-this.x0,this.isHidden||this.updatePosition()},hide:function(){this.isHidden||(this.isHidden=!0,this.el&&this.el.hide())},show:function(){this.isHidden&&(this.isHidden=!1,this.updatePosition(),this.getEl().show())}}),be=Zt.Grid=bt.extend(ge,{hasDayInteractions:!0,view:null,isRTL:null,start:null,end:null,el:null,elsByFill:null,eventTimeFormat:null,displayEventTime:null,displayEventEnd:null,minResizeDuration:null,largeUnit:null,dayClickListener:null,daySelectListener:null,segDragListener:null,segResizeListener:null,externalDragListener:null,constructor:function(t){this.view=t,this.isRTL=t.opt("isRTL"),this.elsByFill={},this.dayClickListener=this.buildDayClickListener(),this.daySelectListener=this.buildDaySelectListener()},computeEventTimeFormat:function(){return this.view.opt("smallTimeFormat")},computeDisplayEventTime:function(){return!0},computeDisplayEventEnd:function(){return!0},setRange:function(t){this.start=t.start.clone(),this.end=t.end.clone(),this.rangeUpdated(),this.processRangeOptions()},rangeUpdated:function(){},processRangeOptions:function(){var t,e,n=this.view;this.eventTimeFormat=n.opt("eventTimeFormat")||n.opt("timeFormat")||this.computeEventTimeFormat(),t=n.opt("displayEventTime"),null==t&&(t=this.computeDisplayEventTime()),e=n.opt("displayEventEnd"),null==e&&(e=this.computeDisplayEventEnd()),this.displayEventTime=t,this.displayEventEnd=e},spanToSegs:function(t){},diffDates:function(t,e){return this.largeUnit?G(t,e,this.largeUnit):F(t,e)},hitsNeededDepth:0,hitsNeeded:function(){this.hitsNeededDepth++||this.prepareHits()},hitsNotNeeded:function(){this.hitsNeededDepth&&!--this.hitsNeededDepth&&this.releaseHits()},prepareHits:function(){},releaseHits:function(){},queryHit:function(t,e){},getSafeHitSpan:function(t){var e=this.getHitSpan(t);return Q(e,this.view.activeRange)?e:null},getHitSpan:function(t){},getHitEl:function(t){},setElement:function(t){this.el=t,this.hasDayInteractions&&(T(t),this.bindDayHandler("touchstart",this.dayTouchStart),this.bindDayHandler("mousedown",this.dayMousedown)),this.bindSegHandlers(),this.bindGlobalHandlers()},bindDayHandler:function(e,n){var i=this;this.el.on(e,function(e){if(!t(e.target).is(i.segSelector+","+i.segSelector+" *,.fc-more,a[data-goto]"))return n.call(i,e)})},removeElement:function(){this.unbindGlobalHandlers(),this.clearDragListeners(),this.el.remove()},renderSkeleton:function(){},renderDates:function(){},unrenderDates:function(){},bindGlobalHandlers:function(){this.listenTo(t(document),{dragstart:this.externalDragStart,sortstart:this.externalDragStart})},unbindGlobalHandlers:function(){this.stopListeningTo(t(document))},dayMousedown:function(t){var e=this.view;we.get().shouldIgnoreMouse()||(this.dayClickListener.startInteraction(t),e.opt("selectable")&&this.daySelectListener.startInteraction(t,{distance:e.opt("selectMinDistance")}))},dayTouchStart:function(t){var e,n=this.view;n.isSelected||n.selectedEvent||(e=n.opt("selectLongPressDelay"),null==e&&(e=n.opt("longPressDelay")),this.dayClickListener.startInteraction(t),n.opt("selectable")&&this.daySelectListener.startInteraction(t,{delay:e}))},buildDayClickListener:function(){var t,e=this,n=this.view,i=new ye(this,{scroll:n.opt("dragScroll"),interactionStart:function(){t=i.origHit},hitOver:function(e,n,i){n||(t=null)},hitOut:function(){t=null},interactionEnd:function(i,r){var s;!r&&t&&(s=e.getSafeHitSpan(t))&&n.triggerDayClick(s,e.getHitEl(t),i)}});return i.shouldCancelTouchScroll=!1,i.scrollAlwaysKills=!0,i},buildDaySelectListener:function(){var t,e=this,n=this.view;return new ye(this,{scroll:n.opt("dragScroll"),interactionStart:function(){t=null},dragStart:function(){n.unselect()},hitOver:function(n,i,r){var o,a;r&&(o=e.getSafeHitSpan(r),a=e.getSafeHitSpan(n),t=o&&a?e.computeSelection(o,a):null,t?e.renderSelection(t):!1===t&&s())},hitOut:function(){t=null,e.unrenderSelection()},hitDone:function(){o()},interactionEnd:function(e,i){!i&&t&&n.reportSelection(t,e)}})},clearDragListeners:function(){this.dayClickListener.endInteraction(),this.daySelectListener.endInteraction(),this.segDragListener&&this.segDragListener.endInteraction(),this.segResizeListener&&this.segResizeListener.endInteraction(),this.externalDragListener&&this.externalDragListener.endInteraction()},renderEventLocationHelper:function(t,e){var n=this.fabricateHelperEvent(t,e);return this.renderHelper(n,e)},fabricateHelperEvent:function(t,e){var n=e?rt(e.event):{};return n.start=t.start.clone(),n.end=t.end?t.end.clone():null,n.allDay=null,this.view.calendar.normalizeEventDates(n),n.className=(n.className||[]).concat("fc-helper"),e||(n.editable=!1),n},renderHelper:function(t,e){},unrenderHelper:function(){},renderSelection:function(t){this.renderHighlight(t)},unrenderSelection:function(){this.unrenderHighlight()},computeSelection:function(t,e){var n=this.computeSelectionSpan(t,e);return!(n&&!this.view.calendar.isSelectionSpanAllowed(n))&&n},computeSelectionSpan:function(t,e){var n=[t.start,t.end,e.start,e.end];return n.sort(pt),{start:n[0].clone(),end:n[3].clone()}},renderHighlight:function(t){this.renderFill("highlight",this.spanToSegs(t))},unrenderHighlight:function(){this.unrenderFill("highlight")},highlightSegClasses:function(){return["fc-highlight"]},renderBusinessHours:function(){},unrenderBusinessHours:function(){},getNowIndicatorUnit:function(){},renderNowIndicator:function(t){},unrenderNowIndicator:function(){},renderFill:function(t,e){},unrenderFill:function(t){var e=this.elsByFill[t];e&&(e.remove(),delete this.elsByFill[t])},renderFillSegEls:function(e,n){var i,r=this,s=this[e+"SegEl"],o="",a=[];if(n.length){for(i=0;i<n.length;i++)o+=this.fillSegHtml(e,n[i]);t(o).each(function(e,i){var o=n[e],l=t(i);s&&(l=s.call(r,o,l)),l&&(l=t(l),l.is(r.fillSegTag)&&(o.el=l,a.push(o)))})}return a},fillSegTag:"div",fillSegHtml:function(t,e){var n=this[t+"SegClasses"],i=this[t+"SegCss"],r=n?n.call(this,e):[],s=dt(i?i.call(this,e):{});return"<"+this.fillSegTag+(r.length?' class="'+r.join(" ")+'"':"")+(s?' style="'+s+'"':"")+" />"},getDayClasses:function(t,e){var n,i=this.view,r=[];return Z(t,i.activeRange)?(r.push("fc-"+Kt[t.day()]),1==i.currentRangeAs("months")&&t.month()!=i.currentRange.start.month()&&r.push("fc-other-month"),n=i.calendar.getNow(),t.isSame(n,"day")?(r.push("fc-today"),!0!==e&&r.push(i.highlightStateClass)):t<n?r.push("fc-past"):r.push("fc-future")):r.push("fc-disabled-day"),r}});be.mixin({segSelector:".fc-event-container > *",mousedOverSeg:null,isDraggingSeg:!1,isResizingSeg:!1,isDraggingExternal:!1,segs:null,renderEvents:function(t){var e,n=[],i=[];for(e=0;e<t.length;e++)(It(t[e])?n:i).push(t[e]);this.segs=[].concat(this.renderBgEvents(n),this.renderFgEvents(i))},renderBgEvents:function(t){var e=this.eventsToSegs(t);return this.renderBgSegs(e)||e},renderFgEvents:function(t){var e=this.eventsToSegs(t);return this.renderFgSegs(e)||e},unrenderEvents:function(){this.handleSegMouseout(),this.clearDragListeners(),this.unrenderFgSegs(),this.unrenderBgSegs(),this.segs=null},getEventSegs:function(){return this.segs||[]},renderFgSegs:function(t){},unrenderFgSegs:function(){},renderFgSegEls:function(e,n){var i,r=this.view,s="",o=[];if(e.length){for(i=0;i<e.length;i++)s+=this.fgSegHtml(e[i],n);t(s).each(function(n,i){var s=e[n],a=r.resolveEventEl(s.event,t(i));a&&(a.data("fc-seg",s),s.el=a,o.push(s))})}return o},fgSegHtml:function(t,e){},renderBgSegs:function(t){return this.renderFill("bgEvent",t)},unrenderBgSegs:function(){this.unrenderFill("bgEvent")},bgEventSegEl:function(t,e){return this.view.resolveEventEl(t.event,e)},bgEventSegClasses:function(t){var e=t.event,n=e.source||{};return["fc-bgevent"].concat(e.className,n.className||[])},bgEventSegCss:function(t){return{"background-color":this.getSegSkinCss(t)["background-color"]}},businessHoursSegClasses:function(t){return["fc-nonbusiness","fc-bgevent"]},buildBusinessHourSegs:function(t,e){return this.eventsToSegs(this.buildBusinessHourEvents(t,e))},buildBusinessHourEvents:function(e,n){var i,r=this.view.calendar;return null==n&&(n=r.opt("businessHours")),i=r.computeBusinessHourEvents(e,n),!i.length&&n&&(i=[t.extend({},Ne,{start:this.view.activeRange.end,end:this.view.activeRange.end,dow:null})]),i},bindSegHandlers:function(){this.bindSegHandlersToEl(this.el)},bindSegHandlersToEl:function(t){this.bindSegHandlerToEl(t,"touchstart",this.handleSegTouchStart),this.bindSegHandlerToEl(t,"mouseenter",this.handleSegMouseover),this.bindSegHandlerToEl(t,"mouseleave",this.handleSegMouseout),this.bindSegHandlerToEl(t,"mousedown",this.handleSegMousedown),this.bindSegHandlerToEl(t,"click",this.handleSegClick)},bindSegHandlerToEl:function(e,n,i){var r=this;e.on(n,this.segSelector,function(e){var n=t(this).data("fc-seg");if(n&&!r.isDraggingSeg&&!r.isResizingSeg)return i.call(r,n,e)})},handleSegClick:function(t,e){!1===this.view.publiclyTrigger("eventClick",t.el[0],t.event,e)&&e.preventDefault()},handleSegMouseover:function(t,e){we.get().shouldIgnoreMouse()||this.mousedOverSeg||(this.mousedOverSeg=t,this.view.isEventResizable(t.event)&&t.el.addClass("fc-allow-mouse-resize"),this.view.publiclyTrigger("eventMouseover",t.el[0],t.event,e))},handleSegMouseout:function(t,e){e=e||{},this.mousedOverSeg&&(t=t||this.mousedOverSeg,this.mousedOverSeg=null,this.view.isEventResizable(t.event)&&t.el.removeClass("fc-allow-mouse-resize"),this.view.publiclyTrigger("eventMouseout",t.el[0],t.event,e))},handleSegMousedown:function(t,e){!this.startSegResize(t,e,{distance:5})&&this.view.isEventDraggable(t.event)&&this.buildSegDragListener(t).startInteraction(e,{distance:5})},handleSegTouchStart:function(t,e){var n,i,r=this.view,s=t.event,o=r.isEventSelected(s),a=r.isEventDraggable(s),l=r.isEventResizable(s),u=!1;o&&l&&(u=this.startSegResize(t,e)),u||!a&&!l||(i=r.opt("eventLongPressDelay"),null==i&&(i=r.opt("longPressDelay")),n=a?this.buildSegDragListener(t):this.buildSegSelectListener(t),n.startInteraction(e,{delay:o?0:i}))},startSegResize:function(e,n,i){return!!t(n.target).is(".fc-resizer")&&(this.buildSegResizeListener(e,t(n.target).is(".fc-start-resizer")).startInteraction(n,i),!0)},buildSegDragListener:function(t){var e,n,i,r=this,a=this.view,l=t.el,u=t.event;if(this.segDragListener)return this.segDragListener;var h=this.segDragListener=new ye(a,{scroll:a.opt("dragScroll"),subjectEl:l,subjectCenter:!0,interactionStart:function(i){t.component=r,e=!1,n=new Se(t.el,{additionalClass:"fc-dragging",parentEl:a.el,opacity:h.isTouch?null:a.opt("dragOpacity"),revertDuration:a.opt("dragRevertDuration"),zIndex:2}),n.hide(),n.start(i)},dragStart:function(n){h.isTouch&&!a.isEventSelected(u)&&a.selectEvent(u),e=!0,r.handleSegMouseout(t,n),r.segDragStart(t,n),a.hideEvent(u)},hitOver:function(e,o,l){var c,d,f,g=!0;t.hit&&(l=t.hit),c=l.component.getSafeHitSpan(l),d=e.component.getSafeHitSpan(e),c&&d?(i=r.computeEventDrop(c,d,u),g=i&&r.isEventLocationAllowed(i,u)):g=!1,g||(i=null,s()),i&&(f=a.renderDrag(i,t))?(f.addClass("fc-dragging"),h.isTouch||r.applyDragOpacity(f),n.hide()):n.show(),o&&(i=null)},hitOut:function(){a.unrenderDrag(),n.show(),i=null},hitDone:function(){o()},interactionEnd:function(s){delete t.component,n.stop(!i,function(){e&&(a.unrenderDrag(),r.segDragStop(t,s)),i?a.reportSegDrop(t,i,r.largeUnit,l,s):a.showEvent(u)}),r.segDragListener=null}});return h},buildSegSelectListener:function(t){var e=this,n=this.view,i=t.event;if(this.segDragListener)return this.segDragListener;var r=this.segDragListener=new me({dragStart:function(t){r.isTouch&&!n.isEventSelected(i)&&n.selectEvent(i)},interactionEnd:function(t){e.segDragListener=null}});return r},segDragStart:function(t,e){this.isDraggingSeg=!0,this.view.publiclyTrigger("eventDragStart",t.el[0],t.event,e,{})},segDragStop:function(t,e){this.isDraggingSeg=!1,this.view.publiclyTrigger("eventDragStop",t.el[0],t.event,e,{})},computeEventDrop:function(t,e,n){var i,r,s=this.view.calendar,o=t.start,a=e.start;return o.hasTime()===a.hasTime()?(i=this.diffDates(a,o),n.allDay&&tt(i)?(r={start:n.start.clone(),end:s.getEventEnd(n),allDay:!1},s.normalizeEventTimes(r)):r=xt(n),r.start.add(i),r.end&&r.end.add(i)):r={start:a.clone(),end:null,allDay:!a.hasTime()},r},applyDragOpacity:function(t){var e=this.view.opt("dragOpacity");null!=e&&t.css("opacity",e)},externalDragStart:function(e,n){var i,r,s=this.view;s.opt("droppable")&&(i=t((n?n.item:null)||e.target),r=s.opt("dropAccept"),(t.isFunction(r)?r.call(i[0],i):i.is(r))&&(this.isDraggingExternal||this.listenToExternalDrag(i,e,n)))},listenToExternalDrag:function(t,e,n){var i,r=this,a=this.view,l=Nt(t);(r.externalDragListener=new ye(this,{interactionStart:function(){r.isDraggingExternal=!0},hitOver:function(t){var e=!0,n=t.component.getSafeHitSpan(t);n?(i=r.computeExternalDrop(n,l),e=i&&r.isExternalLocationAllowed(i,l.eventProps)):e=!1,e||(i=null,s()),i&&r.renderDrag(i)},hitOut:function(){i=null},hitDone:function(){o(),r.unrenderDrag()},interactionEnd:function(e){i&&a.reportExternalDrop(l,i,t,e,n),r.isDraggingExternal=!1,r.externalDragListener=null}})).startDrag(e)},computeExternalDrop:function(t,e){var n=this.view.calendar,i={start:n.applyTimezone(t.start),end:null};return e.startTime&&!i.start.hasTime()&&i.start.time(e.startTime),e.duration&&(i.end=i.start.clone().add(e.duration)),i},renderDrag:function(t,e){},unrenderDrag:function(){},buildSegResizeListener:function(t,e){var n,i,r=this,a=this.view,l=a.calendar,u=t.el,h=t.event,c=l.getEventEnd(h);return this.segResizeListener=new ye(this,{scroll:a.opt("dragScroll"),subjectEl:u,interactionStart:function(){n=!1},dragStart:function(e){n=!0,r.handleSegMouseout(t,e),r.segResizeStart(t,e)},hitOver:function(n,o,l){var u=!0,d=r.getSafeHitSpan(l),f=r.getSafeHitSpan(n);d&&f?(i=e?r.computeEventStartResize(d,f,h):r.computeEventEndResize(d,f,h),u=i&&r.isEventLocationAllowed(i,h)):u=!1,u?i.start.isSame(h.start.clone().stripZone())&&i.end.isSame(c.clone().stripZone())&&(i=null):(i=null,s()),i&&(a.hideEvent(h),r.renderEventResize(i,t))},hitOut:function(){i=null,a.showEvent(h)},hitDone:function(){r.unrenderEventResize(),o()},interactionEnd:function(e){n&&r.segResizeStop(t,e),i?a.reportSegResize(t,i,r.largeUnit,u,e):a.showEvent(h),r.segResizeListener=null}})},segResizeStart:function(t,e){this.isResizingSeg=!0,this.view.publiclyTrigger("eventResizeStart",t.el[0],t.event,e,{})},segResizeStop:function(t,e){this.isResizingSeg=!1,this.view.publiclyTrigger("eventResizeStop",t.el[0],t.event,e,{})},computeEventStartResize:function(t,e,n){return this.computeEventResize("start",t,e,n)},computeEventEndResize:function(t,e,n){return this.computeEventResize("end",t,e,n)},computeEventResize:function(t,e,n,i){var r,s,o=this.view.calendar,a=this.diffDates(n[t],e[t]);return r={start:i.start.clone(),end:o.getEventEnd(i),allDay:i.allDay},r.allDay&&tt(a)&&(r.allDay=!1,o.normalizeEventTimes(r)),r[t].add(a),r.start.isBefore(r.end)||(s=this.minResizeDuration||(i.allDay?o.defaultAllDayEventDuration:o.defaultTimedEventDuration),"start"==t?r.start=r.end.clone().subtract(s):r.end=r.start.clone().add(s)),r},renderEventResize:function(t,e){},unrenderEventResize:function(){},getEventTimeText:function(t,e,n){return null==e&&(e=this.eventTimeFormat),null==n&&(n=this.displayEventEnd),this.displayEventTime&&t.start.hasTime()?n&&t.end?this.view.formatRange(t,e):t.start.format(e):""},getSegClasses:function(t,e,n){var i=this.view,r=["fc-event",t.isStart?"fc-start":"fc-not-start",t.isEnd?"fc-end":"fc-not-end"].concat(this.getSegCustomClasses(t));return e&&r.push("fc-draggable"),n&&r.push("fc-resizable"),i.isEventSelected(t.event)&&r.push("fc-selected"),r},getSegCustomClasses:function(t){var e=t.event;return[].concat(e.className,e.source?e.source.className:[])},getSegSkinCss:function(t){return{"background-color":this.getSegBackgroundColor(t),"border-color":this.getSegBorderColor(t),color:this.getSegTextColor(t)}},getSegBackgroundColor:function(t){return t.event.backgroundColor||t.event.color||this.getSegDefaultBackgroundColor(t)},getSegDefaultBackgroundColor:function(t){var e=t.event.source||{};return e.backgroundColor||e.color||this.view.opt("eventBackgroundColor")||this.view.opt("eventColor")},getSegBorderColor:function(t){return t.event.borderColor||t.event.color||this.getSegDefaultBorderColor(t)},getSegDefaultBorderColor:function(t){var e=t.event.source||{};return e.borderColor||e.color||this.view.opt("eventBorderColor")||this.view.opt("eventColor")},getSegTextColor:function(t){
8   -return t.event.textColor||this.getSegDefaultTextColor(t)},getSegDefaultTextColor:function(t){return(t.event.source||{}).textColor||this.view.opt("eventTextColor")},isEventLocationAllowed:function(t,e){if(this.isEventLocationInRange(t)){var n,i=this.view.calendar,r=this.eventToSpans(t);if(r.length){for(n=0;n<r.length;n++)if(!i.isEventSpanAllowed(r[n],e))return!1;return!0}}return!1},isExternalLocationAllowed:function(t,e){if(this.isEventLocationInRange(t)){var n,i=this.view.calendar,r=this.eventToSpans(t);if(r.length){for(n=0;n<r.length;n++)if(!i.isExternalSpanAllowed(r[n],t,e))return!1;return!0}}return!1},isEventLocationInRange:function(t){return Q(this.eventToRawRange(t),this.view.validRange)},eventToSegs:function(t){return this.eventsToSegs([t])},eventToSpans:function(t){var e=this.eventToRange(t);return e?this.eventRangeToSpans(e,t):[]},eventsToSegs:function(e,n){var i=this,r=Bt(e),s=[];return t.each(r,function(t,e){var r,o,a=[],l=[];for(o=0;o<e.length;o++)(r=i.eventToRange(e[o]))&&(l.push(r),a.push(e[o]));if(kt(e[0]))for(l=i.invertRanges(l),o=0;o<l.length;o++)s.push.apply(s,i.eventRangeToSegs(l[o],e[0],n));else for(o=0;o<l.length;o++)s.push.apply(s,i.eventRangeToSegs(l[o],a[o],n))}),s},eventToRange:function(t){return this.refineRawEventRange(this.eventToRawRange(t))},refineRawEventRange:function(t){var e=this.view,n=e.calendar,i=z(t,e.activeRange);if(i)return n.localizeMoment(i.start),n.localizeMoment(i.end),i},eventToRawRange:function(t){var e=this.view.calendar;return{start:t.start.clone().stripZone(),end:(t.end?t.end.clone():e.getDefaultEventEnd(null!=t.allDay?t.allDay:!t.start.hasTime(),t.start)).stripZone()}},eventRangeToSegs:function(t,e,n){var i,r=this.eventRangeToSpans(t,e),s=[];for(i=0;i<r.length;i++)s.push.apply(s,this.eventSpanToSegs(r[i],e,n));return s},eventRangeToSpans:function(e,n){return[t.extend({},e)]},eventSpanToSegs:function(t,e,n){var i,r,s=n?n(t):this.spanToSegs(t);for(i=0;i<s.length;i++)r=s[i],t.isStart||(r.isStart=!1),t.isEnd||(r.isEnd=!1),r.event=e,r.eventStartMS=+t.start,r.eventDurationMS=t.end-t.start;return s},invertRanges:function(t){var e,n,i=this.view,r=i.activeRange.start.clone(),s=i.activeRange.end.clone(),o=[],a=r;for(t.sort(Lt),e=0;e<t.length;e++)n=t[e],n.start>a&&o.push({start:a,end:n.start}),n.end>a&&(a=n.end);return a<s&&o.push({start:a,end:s}),o},sortEventSegs:function(t){t.sort(mt(this,"compareEventSegs"))},compareEventSegs:function(t,e){return t.eventStartMS-e.eventStartMS||e.eventDurationMS-t.eventDurationMS||e.event.allDay-t.event.allDay||B(t.event,e.event,this.view.eventOrderSpecs)}}),Zt.pluckEventDateProps=xt,Zt.isBgEvent=It,Zt.dataAttrPrefix="";var Ee=Zt.DayTableMixin={breakOnWeeks:!1,dayDates:null,dayIndices:null,daysPerRow:null,rowCnt:null,colCnt:null,colHeadFormat:null,updateDayTable:function(){for(var t,e,n,i=this.view,r=this.start.clone(),s=-1,o=[],a=[];r.isBefore(this.end);)i.isHiddenDay(r)?o.push(s+.5):(s++,o.push(s),a.push(r.clone())),r.add(1,"days");if(this.breakOnWeeks){for(e=a[0].day(),t=1;t<a.length&&a[t].day()!=e;t++);n=Math.ceil(a.length/t)}else n=1,t=a.length;this.dayDates=a,this.dayIndices=o,this.daysPerRow=t,this.rowCnt=n,this.updateDayTableCols()},updateDayTableCols:function(){this.colCnt=this.computeColCnt(),this.colHeadFormat=this.view.opt("columnFormat")||this.computeColHeadFormat()},computeColCnt:function(){return this.daysPerRow},getCellDate:function(t,e){return this.dayDates[this.getCellDayIndex(t,e)].clone()},getCellRange:function(t,e){var n=this.getCellDate(t,e);return{start:n,end:n.clone().add(1,"days")}},getCellDayIndex:function(t,e){return t*this.daysPerRow+this.getColDayIndex(e)},getColDayIndex:function(t){return this.isRTL?this.colCnt-1-t:t},getDateDayIndex:function(t){var e=this.dayIndices,n=t.diff(this.start,"days");return n<0?e[0]-1:n>=e.length?e[e.length-1]+1:e[n]},computeColHeadFormat:function(){return this.rowCnt>1||this.colCnt>10?"ddd":this.colCnt>1?this.view.opt("dayOfMonthFormat"):"dddd"},sliceRangeByRow:function(t){var e,n,i,r,s,o=this.daysPerRow,a=this.view.computeDayRange(t),l=this.getDateDayIndex(a.start),u=this.getDateDayIndex(a.end.clone().subtract(1,"days")),h=[];for(e=0;e<this.rowCnt;e++)n=e*o,i=n+o-1,r=Math.max(l,n),s=Math.min(u,i),r=Math.ceil(r),s=Math.floor(s),r<=s&&h.push({row:e,firstRowDayIndex:r-n,lastRowDayIndex:s-n,isStart:r===l,isEnd:s===u});return h},sliceRangeByDay:function(t){var e,n,i,r,s,o,a=this.daysPerRow,l=this.view.computeDayRange(t),u=this.getDateDayIndex(l.start),h=this.getDateDayIndex(l.end.clone().subtract(1,"days")),c=[];for(e=0;e<this.rowCnt;e++)for(n=e*a,i=n+a-1,r=n;r<=i;r++)s=Math.max(u,r),o=Math.min(h,r),s=Math.ceil(s),o=Math.floor(o),s<=o&&c.push({row:e,firstRowDayIndex:s-n,lastRowDayIndex:o-n,isStart:s===u,isEnd:o===h});return c},renderHeadHtml:function(){return'<div class="fc-row '+this.view.widgetHeaderClass+'"><table><thead>'+this.renderHeadTrHtml()+"</thead></table></div>"},renderHeadIntroHtml:function(){return this.renderIntroHtml()},renderHeadTrHtml:function(){return"<tr>"+(this.isRTL?"":this.renderHeadIntroHtml())+this.renderHeadDateCellsHtml()+(this.isRTL?this.renderHeadIntroHtml():"")+"</tr>"},renderHeadDateCellsHtml:function(){var t,e,n=[];for(t=0;t<this.colCnt;t++)e=this.getCellDate(0,t),n.push(this.renderHeadDateCellHtml(e));return n.join("")},renderHeadDateCellHtml:function(t,e,n){var i=this.view,r=Z(t,i.activeRange),s=["fc-day-header",i.widgetHeaderClass],o=ht(t.format(this.colHeadFormat));return 1===this.rowCnt?s=s.concat(this.getDayClasses(t,!0)):s.push("fc-"+Kt[t.day()]),'<th class="'+s.join(" ")+'"'+(1===(r&&this.rowCnt)?' data-date="'+t.format("YYYY-MM-DD")+'"':"")+(e>1?' colspan="'+e+'"':"")+(n?" "+n:"")+">"+(r?i.buildGotoAnchorHtml({date:t,forceOff:this.rowCnt>1||1===this.colCnt},o):o)+"</th>"},renderBgTrHtml:function(t){return"<tr>"+(this.isRTL?"":this.renderBgIntroHtml(t))+this.renderBgCellsHtml(t)+(this.isRTL?this.renderBgIntroHtml(t):"")+"</tr>"},renderBgIntroHtml:function(t){return this.renderIntroHtml()},renderBgCellsHtml:function(t){var e,n,i=[];for(e=0;e<this.colCnt;e++)n=this.getCellDate(t,e),i.push(this.renderBgCellHtml(n));return i.join("")},renderBgCellHtml:function(t,e){var n=this.view,i=Z(t,n.activeRange),r=this.getDayClasses(t);return r.unshift("fc-day",n.widgetContentClass),'<td class="'+r.join(" ")+'"'+(i?' data-date="'+t.format("YYYY-MM-DD")+'"':"")+(e?" "+e:"")+"></td>"},renderIntroHtml:function(){},bookendCells:function(t){var e=this.renderIntroHtml();e&&(this.isRTL?t.append(e):t.prepend(e))}},De=Zt.DayGrid=be.extend(Ee,{numbersVisible:!1,bottomCoordPadding:0,rowEls:null,cellEls:null,helperEls:null,rowCoordCache:null,colCoordCache:null,renderDates:function(t){var e,n,i=this.view,r=this.rowCnt,s=this.colCnt,o="";for(e=0;e<r;e++)o+=this.renderDayRowHtml(e,t);for(this.el.html(o),this.rowEls=this.el.find(".fc-row"),this.cellEls=this.el.find(".fc-day, .fc-disabled-day"),this.rowCoordCache=new ve({els:this.rowEls,isVertical:!0}),this.colCoordCache=new ve({els:this.cellEls.slice(0,this.colCnt),isHorizontal:!0}),e=0;e<r;e++)for(n=0;n<s;n++)i.publiclyTrigger("dayRender",null,this.getCellDate(e,n),this.getCellEl(e,n))},unrenderDates:function(){this.removeSegPopover()},renderBusinessHours:function(){var t=this.buildBusinessHourSegs(!0);this.renderFill("businessHours",t,"bgevent")},unrenderBusinessHours:function(){this.unrenderFill("businessHours")},renderDayRowHtml:function(t,e){var n=this.view,i=["fc-row","fc-week",n.widgetContentClass];return e&&i.push("fc-rigid"),'<div class="'+i.join(" ")+'"><div class="fc-bg"><table>'+this.renderBgTrHtml(t)+'</table></div><div class="fc-content-skeleton"><table>'+(this.numbersVisible?"<thead>"+this.renderNumberTrHtml(t)+"</thead>":"")+"</table></div></div>"},renderNumberTrHtml:function(t){return"<tr>"+(this.isRTL?"":this.renderNumberIntroHtml(t))+this.renderNumberCellsHtml(t)+(this.isRTL?this.renderNumberIntroHtml(t):"")+"</tr>"},renderNumberIntroHtml:function(t){return this.renderIntroHtml()},renderNumberCellsHtml:function(t){var e,n,i=[];for(e=0;e<this.colCnt;e++)n=this.getCellDate(t,e),i.push(this.renderNumberCellHtml(n));return i.join("")},renderNumberCellHtml:function(t){var e,n,i=this.view,r="",s=Z(t,i.activeRange),o=i.dayNumbersVisible&&s;return o||i.cellWeekNumbersVisible?(e=this.getDayClasses(t),e.unshift("fc-day-top"),i.cellWeekNumbersVisible&&(n="ISO"===t._locale._fullCalendar_weekCalc?1:t._locale.firstDayOfWeek()),r+='<td class="'+e.join(" ")+'"'+(s?' data-date="'+t.format()+'"':"")+">",i.cellWeekNumbersVisible&&t.day()==n&&(r+=i.buildGotoAnchorHtml({date:t,type:"week"},{class:"fc-week-number"},t.format("w"))),o&&(r+=i.buildGotoAnchorHtml(t,{class:"fc-day-number"},t.date())),r+="</td>"):"<td/>"},computeEventTimeFormat:function(){return this.view.opt("extraSmallTimeFormat")},computeDisplayEventEnd:function(){return 1==this.colCnt},rangeUpdated:function(){this.updateDayTable()},spanToSegs:function(t){var e,n,i=this.sliceRangeByRow(t);for(e=0;e<i.length;e++)n=i[e],this.isRTL?(n.leftCol=this.daysPerRow-1-n.lastRowDayIndex,n.rightCol=this.daysPerRow-1-n.firstRowDayIndex):(n.leftCol=n.firstRowDayIndex,n.rightCol=n.lastRowDayIndex);return i},prepareHits:function(){this.colCoordCache.build(),this.rowCoordCache.build(),this.rowCoordCache.bottoms[this.rowCnt-1]+=this.bottomCoordPadding},releaseHits:function(){this.colCoordCache.clear(),this.rowCoordCache.clear()},queryHit:function(t,e){if(this.colCoordCache.isLeftInBounds(t)&&this.rowCoordCache.isTopInBounds(e)){var n=this.colCoordCache.getHorizontalIndex(t),i=this.rowCoordCache.getVerticalIndex(e);if(null!=i&&null!=n)return this.getCellHit(i,n)}},getHitSpan:function(t){return this.getCellRange(t.row,t.col)},getHitEl:function(t){return this.getCellEl(t.row,t.col)},getCellHit:function(t,e){return{row:t,col:e,component:this,left:this.colCoordCache.getLeftOffset(e),right:this.colCoordCache.getRightOffset(e),top:this.rowCoordCache.getTopOffset(t),bottom:this.rowCoordCache.getBottomOffset(t)}},getCellEl:function(t,e){return this.cellEls.eq(t*this.colCnt+e)},renderDrag:function(t,e){var n,i=this.eventToSpans(t);for(n=0;n<i.length;n++)this.renderHighlight(i[n]);if(e&&e.component!==this)return this.renderEventLocationHelper(t,e)},unrenderDrag:function(){this.unrenderHighlight(),this.unrenderHelper()},renderEventResize:function(t,e){var n,i=this.eventToSpans(t);for(n=0;n<i.length;n++)this.renderHighlight(i[n]);return this.renderEventLocationHelper(t,e)},unrenderEventResize:function(){this.unrenderHighlight(),this.unrenderHelper()},renderHelper:function(e,n){var i,r=[],s=this.eventToSegs(e);return s=this.renderFgSegEls(s),i=this.renderSegRows(s),this.rowEls.each(function(e,s){var o,a=t(s),l=t('<div class="fc-helper-skeleton"><table/></div>');o=n&&n.row===e?n.el.position().top:a.find(".fc-content-skeleton tbody").position().top,l.css("top",o).find("table").append(i[e].tbodyEl),a.append(l),r.push(l[0])}),this.helperEls=t(r)},unrenderHelper:function(){this.helperEls&&(this.helperEls.remove(),this.helperEls=null)},fillSegTag:"td",renderFill:function(e,n,i){var r,s,o,a=[];for(n=this.renderFillSegEls(e,n),r=0;r<n.length;r++)s=n[r],o=this.renderFillRow(e,s,i),this.rowEls.eq(s.row).append(o),a.push(o[0]);return this.elsByFill[e]=t(a),n},renderFillRow:function(e,n,i){var r,s,o=this.colCnt,a=n.leftCol,l=n.rightCol+1;return i=i||e.toLowerCase(),r=t('<div class="fc-'+i+'-skeleton"><table><tr/></table></div>'),s=r.find("tr"),a>0&&s.append('<td colspan="'+a+'"/>'),s.append(n.el.attr("colspan",l-a)),l<o&&s.append('<td colspan="'+(o-l)+'"/>'),this.bookendCells(s),r}});De.mixin({rowStructs:null,unrenderEvents:function(){this.removeSegPopover(),be.prototype.unrenderEvents.apply(this,arguments)},getEventSegs:function(){return be.prototype.getEventSegs.call(this).concat(this.popoverSegs||[])},renderBgSegs:function(e){var n=t.grep(e,function(t){return t.event.allDay});return be.prototype.renderBgSegs.call(this,n)},renderFgSegs:function(e){var n;return e=this.renderFgSegEls(e),n=this.rowStructs=this.renderSegRows(e),this.rowEls.each(function(e,i){t(i).find(".fc-content-skeleton > table").append(n[e].tbodyEl)}),e},unrenderFgSegs:function(){for(var t,e=this.rowStructs||[];t=e.pop();)t.tbodyEl.remove();this.rowStructs=null},renderSegRows:function(t){var e,n,i=[];for(e=this.groupSegRows(t),n=0;n<e.length;n++)i.push(this.renderSegRow(n,e[n]));return i},fgSegHtml:function(t,e){var n,i,r=this.view,s=t.event,o=r.isEventDraggable(s),a=!e&&s.allDay&&t.isStart&&r.isEventResizableFromStart(s),l=!e&&s.allDay&&t.isEnd&&r.isEventResizableFromEnd(s),u=this.getSegClasses(t,o,a||l),h=dt(this.getSegSkinCss(t)),c="";return u.unshift("fc-day-grid-event","fc-h-event"),t.isStart&&(n=this.getEventTimeText(s))&&(c='<span class="fc-time">'+ht(n)+"</span>"),i='<span class="fc-title">'+(ht(s.title||"")||"&nbsp;")+"</span>",'<a class="'+u.join(" ")+'"'+(s.url?' href="'+ht(s.url)+'"':"")+(h?' style="'+h+'"':"")+'><div class="fc-content">'+(this.isRTL?i+" "+c:c+" "+i)+"</div>"+(a?'<div class="fc-resizer fc-start-resizer" />':"")+(l?'<div class="fc-resizer fc-end-resizer" />':"")+"</a>"},renderSegRow:function(e,n){function i(e){for(;o<e;)h=(m[r-1]||[])[o],h?h.attr("rowspan",parseInt(h.attr("rowspan")||1,10)+1):(h=t("<td/>"),a.append(h)),v[r][o]=h,m[r][o]=h,o++}var r,s,o,a,l,u,h,c=this.colCnt,d=this.buildSegLevels(n),f=Math.max(1,d.length),g=t("<tbody/>"),p=[],v=[],m=[];for(r=0;r<f;r++){if(s=d[r],o=0,a=t("<tr/>"),p.push([]),v.push([]),m.push([]),s)for(l=0;l<s.length;l++){for(u=s[l],i(u.leftCol),h=t('<td class="fc-event-container"/>').append(u.el),u.leftCol!=u.rightCol?h.attr("colspan",u.rightCol-u.leftCol+1):m[r][o]=h;o<=u.rightCol;)v[r][o]=h,p[r][o]=u,o++;a.append(h)}i(c),this.bookendCells(a),g.append(a)}return{row:e,tbodyEl:g,cellMatrix:v,segMatrix:p,segLevels:d,segs:n}},buildSegLevels:function(t){var e,n,i,r=[];for(this.sortEventSegs(t),e=0;e<t.length;e++){for(n=t[e],i=0;i<r.length&&zt(n,r[i]);i++);n.level=i,(r[i]||(r[i]=[])).push(n)}for(i=0;i<r.length;i++)r[i].sort(Ft);return r},groupSegRows:function(t){var e,n=[];for(e=0;e<this.rowCnt;e++)n.push([]);for(e=0;e<t.length;e++)n[t[e].row].push(t[e]);return n}}),De.mixin({segPopover:null,popoverSegs:null,removeSegPopover:function(){this.segPopover&&this.segPopover.hide()},limitRows:function(t){var e,n,i=this.rowStructs||[];for(e=0;e<i.length;e++)this.unlimitRow(e),!1!==(n=!!t&&("number"==typeof t?t:this.computeRowLevelLimit(e)))&&this.limitRow(e,n)},computeRowLevelLimit:function(e){function n(e,n){s=Math.max(s,t(n).outerHeight())}var i,r,s,o=this.rowEls.eq(e),a=o.height(),l=this.rowStructs[e].tbodyEl.children();for(i=0;i<l.length;i++)if(r=l.eq(i).removeClass("fc-limited"),s=0,r.find("> td > :first-child").each(n),r.position().top+s>a)return i;return!1},limitRow:function(e,n){function i(i){for(;E<i;)u=w.getCellSegs(e,E,n),u.length&&(d=s[n-1][E],y=w.renderMoreLink(e,E,u),m=t("<div/>").append(y),d.append(m),b.push(m[0])),E++}var r,s,o,a,l,u,h,c,d,f,g,p,v,m,y,w=this,S=this.rowStructs[e],b=[],E=0;if(n&&n<S.segLevels.length){for(r=S.segLevels[n-1],s=S.cellMatrix,o=S.tbodyEl.children().slice(n).addClass("fc-limited").get(),a=0;a<r.length;a++){for(l=r[a],i(l.leftCol),c=[],h=0;E<=l.rightCol;)u=this.getCellSegs(e,E,n),c.push(u),h+=u.length,E++;if(h){for(d=s[n-1][l.leftCol],f=d.attr("rowspan")||1,g=[],p=0;p<c.length;p++)v=t('<td class="fc-more-cell"/>').attr("rowspan",f),u=c[p],y=this.renderMoreLink(e,l.leftCol+p,[l].concat(u)),m=t("<div/>").append(y),v.append(m),g.push(v[0]),b.push(v[0]);d.addClass("fc-limited").after(t(g)),o.push(d[0])}}i(this.colCnt),S.moreEls=t(b),S.limitedEls=t(o)}},unlimitRow:function(t){var e=this.rowStructs[t];e.moreEls&&(e.moreEls.remove(),e.moreEls=null),e.limitedEls&&(e.limitedEls.removeClass("fc-limited"),e.limitedEls=null)},renderMoreLink:function(e,n,i){var r=this,s=this.view;return t('<a class="fc-more"/>').text(this.getMoreLinkText(i.length)).on("click",function(o){var a=s.opt("eventLimitClick"),l=r.getCellDate(e,n),u=t(this),h=r.getCellEl(e,n),c=r.getCellSegs(e,n),d=r.resliceDaySegs(c,l),f=r.resliceDaySegs(i,l);"function"==typeof a&&(a=s.publiclyTrigger("eventLimitClick",null,{date:l,dayEl:h,moreEl:u,segs:d,hiddenSegs:f},o)),"popover"===a?r.showSegPopover(e,n,u,d):"string"==typeof a&&s.calendar.zoomTo(l,a)})},showSegPopover:function(t,e,n,i){var r,s,o=this,a=this.view,l=n.parent();r=1==this.rowCnt?a.el:this.rowEls.eq(t),s={className:"fc-more-popover",content:this.renderSegPopoverContent(t,e,i),parentEl:this.view.el,top:r.offset().top,autoHide:!0,viewportConstrain:a.opt("popoverViewportConstrain"),hide:function(){if(o.popoverSegs)for(var t,e=0;e<o.popoverSegs.length;++e)t=o.popoverSegs[e],a.publiclyTrigger("eventDestroy",t.event,t.event,t.el);o.segPopover.removeElement(),o.segPopover=null,o.popoverSegs=null}},this.isRTL?s.right=l.offset().left+l.outerWidth()+1:s.left=l.offset().left-1,this.segPopover=new pe(s),this.segPopover.show(),this.bindSegHandlersToEl(this.segPopover.el)},renderSegPopoverContent:function(e,n,i){var r,s=this.view,o=s.opt("theme"),a=this.getCellDate(e,n).format(s.opt("dayPopoverFormat")),l=t('<div class="fc-header '+s.widgetHeaderClass+'"><span class="fc-close '+(o?"ui-icon ui-icon-closethick":"fc-icon fc-icon-x")+'"></span><span class="fc-title">'+ht(a)+'</span><div class="fc-clear"/></div><div class="fc-body '+s.widgetContentClass+'"><div class="fc-event-container"></div></div>'),u=l.find(".fc-event-container");for(i=this.renderFgSegEls(i,!0),this.popoverSegs=i,r=0;r<i.length;r++)this.hitsNeeded(),i[r].hit=this.getCellHit(e,n),this.hitsNotNeeded(),u.append(i[r].el);return l},resliceDaySegs:function(e,n){var i=t.map(e,function(t){return t.event}),r=n.clone(),s=r.clone().add(1,"days"),o={start:r,end:s};return e=this.eventsToSegs(i,function(t){var e=z(t,o);return e?[e]:[]}),this.sortEventSegs(e),e},getMoreLinkText:function(t){var e=this.view.opt("eventLimitText");return"function"==typeof e?e(t):"+"+t+" "+e},getCellSegs:function(t,e,n){for(var i,r=this.rowStructs[t].segMatrix,s=n||0,o=[];s<r.length;)i=r[s][e],i&&o.push(i),s++;return o}});var Te=Zt.TimeGrid=be.extend(Ee,{slotDuration:null,snapDuration:null,snapsPerSlot:null,labelFormat:null,labelInterval:null,colEls:null,slatContainerEl:null,slatEls:null,nowIndicatorEls:null,colCoordCache:null,slatCoordCache:null,constructor:function(){be.apply(this,arguments),this.processOptions()},renderDates:function(){this.el.html(this.renderHtml()),this.colEls=this.el.find(".fc-day, .fc-disabled-day"),this.slatContainerEl=this.el.find(".fc-slats"),this.slatEls=this.slatContainerEl.find("tr"),this.colCoordCache=new ve({els:this.colEls,isHorizontal:!0}),this.slatCoordCache=new ve({els:this.slatEls,isVertical:!0}),this.renderContentSkeleton()},renderHtml:function(){return'<div class="fc-bg"><table>'+this.renderBgTrHtml(0)+'</table></div><div class="fc-slats"><table>'+this.renderSlatRowHtml()+"</table></div>"},renderSlatRowHtml:function(){for(var t,n,i,r=this.view,s=this.isRTL,o="",a=e.duration(+this.view.minTime);a<this.view.maxTime;)t=this.start.clone().time(a),n=vt(W(a,this.labelInterval)),i='<td class="fc-axis fc-time '+r.widgetContentClass+'" '+r.axisStyleAttr()+">"+(n?"<span>"+ht(t.format(this.labelFormat))+"</span>":"")+"</td>",o+='<tr data-time="'+t.format("HH:mm:ss")+'"'+(n?"":' class="fc-minor"')+">"+(s?"":i)+'<td class="'+r.widgetContentClass+'"/>'+(s?i:"")+"</tr>",a.add(this.slotDuration);return o},processOptions:function(){var n,i=this.view,r=i.opt("slotDuration"),s=i.opt("snapDuration");r=e.duration(r),s=s?e.duration(s):r,this.slotDuration=r,this.snapDuration=s,this.snapsPerSlot=r/s,this.minResizeDuration=s,n=i.opt("slotLabelFormat"),t.isArray(n)&&(n=n[n.length-1]),this.labelFormat=n||i.opt("smallTimeFormat"),n=i.opt("slotLabelInterval"),this.labelInterval=n?e.duration(n):this.computeLabelInterval(r)},computeLabelInterval:function(t){var n,i,r;for(n=_e.length-1;n>=0;n--)if(i=e.duration(_e[n]),r=W(i,t),vt(r)&&r>1)return i;return e.duration(t)},computeEventTimeFormat:function(){return this.view.opt("noMeridiemTimeFormat")},computeDisplayEventEnd:function(){return!0},prepareHits:function(){this.colCoordCache.build(),this.slatCoordCache.build()},releaseHits:function(){this.colCoordCache.clear()},queryHit:function(t,e){var n=this.snapsPerSlot,i=this.colCoordCache,r=this.slatCoordCache;if(i.isLeftInBounds(t)&&r.isTopInBounds(e)){var s=i.getHorizontalIndex(t),o=r.getVerticalIndex(e);if(null!=s&&null!=o){var a=r.getTopOffset(o),l=r.getHeight(o),u=(e-a)/l,h=Math.floor(u*n),c=o*n+h,d=a+h/n*l,f=a+(h+1)/n*l;return{col:s,snap:c,component:this,left:i.getLeftOffset(s),right:i.getRightOffset(s),top:d,bottom:f}}}},getHitSpan:function(t){var e,n=this.getCellDate(0,t.col),i=this.computeSnapTime(t.snap);return n.time(i),e=n.clone().add(this.snapDuration),{start:n,end:e}},getHitEl:function(t){return this.colEls.eq(t.col)},rangeUpdated:function(){this.updateDayTable()},computeSnapTime:function(t){return e.duration(this.view.minTime+this.snapDuration*t)},spanToSegs:function(t){var e,n=this.sliceRangeByTimes(t);for(e=0;e<n.length;e++)this.isRTL?n[e].col=this.daysPerRow-1-n[e].dayIndex:n[e].col=n[e].dayIndex;return n},sliceRangeByTimes:function(t){var e,n,i,r,s=[];for(n=0;n<this.daysPerRow;n++)i=this.dayDates[n].clone().time(0),r={start:i.clone().add(this.view.minTime),end:i.clone().add(this.view.maxTime)},(e=z(t,r))&&(e.dayIndex=n,s.push(e));return s},updateSize:function(t){this.slatCoordCache.build(),t&&this.updateSegVerticals([].concat(this.fgSegs||[],this.bgSegs||[],this.businessSegs||[]))},getTotalSlatHeight:function(){return this.slatContainerEl.outerHeight()},computeDateTop:function(t,n){return this.computeTimeTop(e.duration(t-n.clone().stripTime()))},computeTimeTop:function(t){var e,n,i=this.slatEls.length,r=(t-this.view.minTime)/this.slotDuration;return r=Math.max(0,r),r=Math.min(i,r),e=Math.floor(r),e=Math.min(e,i-1),n=r-e,this.slatCoordCache.getTopPosition(e)+this.slatCoordCache.getHeight(e)*n},renderDrag:function(t,e){var n,i;if(e)return this.renderEventLocationHelper(t,e);for(n=this.eventToSpans(t),i=0;i<n.length;i++)this.renderHighlight(n[i])},unrenderDrag:function(){this.unrenderHelper(),this.unrenderHighlight()},renderEventResize:function(t,e){return this.renderEventLocationHelper(t,e)},unrenderEventResize:function(){this.unrenderHelper()},renderHelper:function(t,e){return this.renderHelperSegs(this.eventToSegs(t),e)},unrenderHelper:function(){this.unrenderHelperSegs()},renderBusinessHours:function(){this.renderBusinessSegs(this.buildBusinessHourSegs())},unrenderBusinessHours:function(){this.unrenderBusinessSegs()},getNowIndicatorUnit:function(){return"minute"},renderNowIndicator:function(e){var n,i=this.spanToSegs({start:e,end:e}),r=this.computeDateTop(e,e),s=[];for(n=0;n<i.length;n++)s.push(t('<div class="fc-now-indicator fc-now-indicator-line"></div>').css("top",r).appendTo(this.colContainerEls.eq(i[n].col))[0]);i.length>0&&s.push(t('<div class="fc-now-indicator fc-now-indicator-arrow"></div>').css("top",r).appendTo(this.el.find(".fc-content-skeleton"))[0]),this.nowIndicatorEls=t(s)},unrenderNowIndicator:function(){this.nowIndicatorEls&&(this.nowIndicatorEls.remove(),this.nowIndicatorEls=null)},renderSelection:function(t){this.view.opt("selectHelper")?this.renderEventLocationHelper(t):this.renderHighlight(t)},unrenderSelection:function(){this.unrenderHelper(),this.unrenderHighlight()},renderHighlight:function(t){this.renderHighlightSegs(this.spanToSegs(t))},unrenderHighlight:function(){this.unrenderHighlightSegs()}});Te.mixin({colContainerEls:null,fgContainerEls:null,bgContainerEls:null,helperContainerEls:null,highlightContainerEls:null,businessContainerEls:null,fgSegs:null,bgSegs:null,helperSegs:null,highlightSegs:null,businessSegs:null,renderContentSkeleton:function(){var e,n,i="";for(e=0;e<this.colCnt;e++)i+='<td><div class="fc-content-col"><div class="fc-event-container fc-helper-container"></div><div class="fc-event-container"></div><div class="fc-highlight-container"></div><div class="fc-bgevent-container"></div><div class="fc-business-container"></div></div></td>';n=t('<div class="fc-content-skeleton"><table><tr>'+i+"</tr></table></div>"),this.colContainerEls=n.find(".fc-content-col"),this.helperContainerEls=n.find(".fc-helper-container"),this.fgContainerEls=n.find(".fc-event-container:not(.fc-helper-container)"),this.bgContainerEls=n.find(".fc-bgevent-container"),this.highlightContainerEls=n.find(".fc-highlight-container"),this.businessContainerEls=n.find(".fc-business-container"),this.bookendCells(n.find("tr")),this.el.append(n)},renderFgSegs:function(t){return t=this.renderFgSegsIntoContainers(t,this.fgContainerEls),this.fgSegs=t,t},unrenderFgSegs:function(){this.unrenderNamedSegs("fgSegs")},renderHelperSegs:function(e,n){var i,r,s,o=[];for(e=this.renderFgSegsIntoContainers(e,this.helperContainerEls),i=0;i<e.length;i++)r=e[i],n&&n.col===r.col&&(s=n.el,r.el.css({left:s.css("left"),right:s.css("right"),"margin-left":s.css("margin-left"),"margin-right":s.css("margin-right")})),o.push(r.el[0]);return this.helperSegs=e,t(o)},unrenderHelperSegs:function(){this.unrenderNamedSegs("helperSegs")},renderBgSegs:function(t){return t=this.renderFillSegEls("bgEvent",t),this.updateSegVerticals(t),this.attachSegsByCol(this.groupSegsByCol(t),this.bgContainerEls),this.bgSegs=t,t},unrenderBgSegs:function(){this.unrenderNamedSegs("bgSegs")},renderHighlightSegs:function(t){t=this.renderFillSegEls("highlight",t),this.updateSegVerticals(t),this.attachSegsByCol(this.groupSegsByCol(t),this.highlightContainerEls),this.highlightSegs=t},unrenderHighlightSegs:function(){this.unrenderNamedSegs("highlightSegs")},renderBusinessSegs:function(t){t=this.renderFillSegEls("businessHours",t),this.updateSegVerticals(t),this.attachSegsByCol(this.groupSegsByCol(t),this.businessContainerEls),this.businessSegs=t},unrenderBusinessSegs:function(){this.unrenderNamedSegs("businessSegs")},groupSegsByCol:function(t){var e,n=[];for(e=0;e<this.colCnt;e++)n.push([]);for(e=0;e<t.length;e++)n[t[e].col].push(t[e]);return n},attachSegsByCol:function(t,e){var n,i,r;for(n=0;n<this.colCnt;n++)for(i=t[n],r=0;r<i.length;r++)e.eq(n).append(i[r].el)},unrenderNamedSegs:function(t){var e,n=this[t];if(n){for(e=0;e<n.length;e++)n[e].el.remove();this[t]=null}},renderFgSegsIntoContainers:function(t,e){var n,i;for(t=this.renderFgSegEls(t),n=this.groupSegsByCol(t),i=0;i<this.colCnt;i++)this.updateFgSegCoords(n[i]);return this.attachSegsByCol(n,e),t},fgSegHtml:function(t,e){var n,i,r,s=this.view,o=t.event,a=s.isEventDraggable(o),l=!e&&t.isStart&&s.isEventResizableFromStart(o),u=!e&&t.isEnd&&s.isEventResizableFromEnd(o),h=this.getSegClasses(t,a,l||u),c=dt(this.getSegSkinCss(t));return h.unshift("fc-time-grid-event","fc-v-event"),s.isMultiDayEvent(o)?(t.isStart||t.isEnd)&&(n=this.getEventTimeText(t),i=this.getEventTimeText(t,"LT"),r=this.getEventTimeText(t,null,!1)):(n=this.getEventTimeText(o),i=this.getEventTimeText(o,"LT"),r=this.getEventTimeText(o,null,!1)),'<a class="'+h.join(" ")+'"'+(o.url?' href="'+ht(o.url)+'"':"")+(c?' style="'+c+'"':"")+'><div class="fc-content">'+(n?'<div class="fc-time" data-start="'+ht(r)+'" data-full="'+ht(i)+'"><span>'+ht(n)+"</span></div>":"")+(o.title?'<div class="fc-title">'+ht(o.title)+"</div>":"")+'</div><div class="fc-bg"/>'+(u?'<div class="fc-resizer fc-end-resizer" />':"")+"</a>"},updateSegVerticals:function(t){this.computeSegVerticals(t),this.assignSegVerticals(t)},computeSegVerticals:function(t){var e,n,i;for(e=0;e<t.length;e++)n=t[e],i=this.dayDates[n.dayIndex],n.top=this.computeDateTop(n.start,i),n.bottom=this.computeDateTop(n.end,i)},assignSegVerticals:function(t){var e,n;for(e=0;e<t.length;e++)n=t[e],n.el.css(this.generateSegVerticalCss(n))},generateSegVerticalCss:function(t){return{top:t.top,bottom:-t.bottom}},updateFgSegCoords:function(t){this.computeSegVerticals(t),this.computeFgSegHorizontals(t),this.assignSegVerticals(t),this.assignFgSegHorizontals(t)},computeFgSegHorizontals:function(t){var e,n,i;if(this.sortEventSegs(t),e=At(t),Gt(e),n=e[0]){for(i=0;i<n.length;i++)Vt(n[i]);for(i=0;i<n.length;i++)this.computeFgSegForwardBack(n[i],0,0)}},computeFgSegForwardBack:function(t,e,n){var i,r=t.forwardSegs;if(void 0===t.forwardCoord)for(r.length?(this.sortForwardSegs(r),this.computeFgSegForwardBack(r[0],e+1,n),t.forwardCoord=r[0].backwardCoord):t.forwardCoord=1,t.backwardCoord=t.forwardCoord-(t.forwardCoord-n)/(e+1),i=0;i<r.length;i++)this.computeFgSegForwardBack(r[i],0,t.forwardCoord)},sortForwardSegs:function(t){t.sort(mt(this,"compareForwardSegs"))},compareForwardSegs:function(t,e){return e.forwardPressure-t.forwardPressure||(t.backwardCoord||0)-(e.backwardCoord||0)||this.compareEventSegs(t,e)},assignFgSegHorizontals:function(t){var e,n;for(e=0;e<t.length;e++)n=t[e],n.el.css(this.generateFgSegHorizontalCss(n)),n.bottom-n.top<30&&n.el.addClass("fc-short")},generateFgSegHorizontalCss:function(t){var e,n,i=this.view.opt("slotEventOverlap"),r=t.backwardCoord,s=t.forwardCoord,o=this.generateSegVerticalCss(t);return i&&(s=Math.min(1,r+2*(s-r))),this.isRTL?(e=1-s,n=r):(e=r,n=1-s),o.zIndex=t.level+1,o.left=100*e+"%",o.right=100*n+"%",i&&t.forwardPressure&&(o[this.isRTL?"marginLeft":"marginRight"]=20),o}});var Ce=Zt.View=ue.extend({type:null,name:null,title:null,calendar:null,viewSpec:null,options:null,el:null,renderQueue:null,batchRenderDepth:0,isDatesRendered:!1,isEventsRendered:!1,isBaseRendered:!1,queuedScroll:null,isRTL:!1,isSelected:!1,selectedEvent:null,eventOrderSpecs:null,widgetHeaderClass:null,widgetContentClass:null,highlightStateClass:null,nextDayThreshold:null,isHiddenDayHash:null,isNowIndicatorRendered:null,initialNowDate:null,initialNowQueriedMs:null,nowIndicatorTimeoutID:null,nowIndicatorIntervalID:null,constructor:function(t,n){ue.prototype.constructor.call(this),this.calendar=t,this.viewSpec=n,this.type=n.type,this.options=n.options,this.name=this.type,this.nextDayThreshold=e.duration(this.opt("nextDayThreshold")),this.initThemingProps(),this.initHiddenDays(),this.isRTL=this.opt("isRTL"),this.eventOrderSpecs=M(this.opt("eventOrder")),this.renderQueue=this.buildRenderQueue(),this.initAutoBatchRender(),this.initialize()},buildRenderQueue:function(){var t=this,e=new de({event:this.opt("eventRenderWait")});return e.on("start",function(){t.freezeHeight(),t.addScroll(t.queryScroll())}),e.on("stop",function(){t.thawHeight(),t.popScroll()}),e},initAutoBatchRender:function(){var t=this;this.on("before:change",function(){t.startBatchRender()}),this.on("change",function(){t.stopBatchRender()})},startBatchRender:function(){this.batchRenderDepth++||this.renderQueue.pause()},stopBatchRender:function(){--this.batchRenderDepth||this.renderQueue.resume()},initialize:function(){},opt:function(t){return this.options[t]},publiclyTrigger:function(t,e){var n=this.calendar;return n.publiclyTrigger.apply(n,[t,e||this].concat(Array.prototype.slice.call(arguments,2),[this]))},updateTitle:function(){this.title=this.computeTitle(),this.calendar.setToolbarsTitle(this.title)},computeTitle:function(){var t;return t=/^(year|month)$/.test(this.currentRangeUnit)?this.currentRange:this.activeRange,this.formatRange({start:this.calendar.applyTimezone(t.start),end:this.calendar.applyTimezone(t.end)},this.opt("titleFormat")||this.computeTitleFormat(),this.opt("titleRangeSeparator"))},computeTitleFormat:function(){return"year"==this.currentRangeUnit?"YYYY":"month"==this.currentRangeUnit?this.opt("monthYearFormat"):this.currentRangeAs("days")>1?"ll":"LL"},formatRange:function(t,e,n){var i=t.end;return i.hasTime()||(i=i.clone().subtract(1)),ae(t.start,i,e,n,this.opt("isRTL"))},getAllDayHtml:function(){return this.opt("allDayHtml")||ht(this.opt("allDayText"))},buildGotoAnchorHtml:function(e,n,i){var r,s,o,a;return t.isPlainObject(e)?(r=e.date,s=e.type,o=e.forceOff):r=e,r=Zt.moment(r),a={date:r.format("YYYY-MM-DD"),type:s||"day"},"string"==typeof n&&(i=n,n=null),n=n?" "+ft(n):"",i=i||"",!o&&this.opt("navLinks")?"<a"+n+' data-goto="'+ht(JSON.stringify(a))+'">'+i+"</a>":"<span"+n+">"+i+"</span>"},setElement:function(t){this.el=t,this.bindGlobalHandlers(),this.bindBaseRenderHandlers(),this.renderSkeleton()},
9   -removeElement:function(){this.unsetDate(),this.unrenderSkeleton(),this.unbindGlobalHandlers(),this.unbindBaseRenderHandlers(),this.el.remove()},renderSkeleton:function(){},unrenderSkeleton:function(){},setDate:function(t){var e=this.get("dateProfile"),n=this.buildDateProfile(t,null,!0);return e&&X(e.activeRange,n.activeRange)||this.set("dateProfile",n),n.date},unsetDate:function(){this.unset("dateProfile")},requestDateRender:function(t){var e=this;this.renderQueue.queue(function(){e.executeDateRender(t)},"date","init")},requestDateUnrender:function(){var t=this;this.renderQueue.queue(function(){t.executeDateUnrender()},"date","destroy")},fetchInitialEvents:function(t){return this.calendar.requestEvents(t.activeRange.start,t.activeRange.end)},bindEventChanges:function(){this.listenTo(this.calendar,"eventsReset",this.resetEvents)},unbindEventChanges:function(){this.stopListeningTo(this.calendar,"eventsReset")},setEvents:function(t){this.set("currentEvents",t),this.set("hasEvents",!0)},unsetEvents:function(){this.unset("currentEvents"),this.unset("hasEvents")},resetEvents:function(t){this.startBatchRender(),this.unsetEvents(),this.setEvents(t),this.stopBatchRender()},requestEventsRender:function(t){var e=this;this.renderQueue.queue(function(){e.executeEventsRender(t)},"event","init")},requestEventsUnrender:function(){var t=this;this.renderQueue.queue(function(){t.executeEventsUnrender()},"event","destroy")},executeDateRender:function(t,e){this.setDateProfileForRendering(t),this.updateTitle(),this.calendar.updateToolbarButtons(),this.render&&this.render(),this.renderDates(),this.updateSize(),this.renderBusinessHours(),this.startNowIndicator(),e||this.addScroll(this.computeInitialDateScroll()),this.isDatesRendered=!0,this.trigger("datesRendered")},executeDateUnrender:function(){this.unselect(),this.stopNowIndicator(),this.trigger("before:datesUnrendered"),this.unrenderBusinessHours(),this.unrenderDates(),this.destroy&&this.destroy(),this.isDatesRendered=!1},renderDates:function(){},unrenderDates:function(){},bindBaseRenderHandlers:function(){var t=this;this.on("datesRendered.baseHandler",function(){t.onBaseRender()}),this.on("before:datesUnrendered.baseHandler",function(){t.onBeforeBaseUnrender()})},unbindBaseRenderHandlers:function(){this.off(".baseHandler")},onBaseRender:function(){this.applyScreenState(),this.publiclyTrigger("viewRender",this,this,this.el)},onBeforeBaseUnrender:function(){this.applyScreenState(),this.publiclyTrigger("viewDestroy",this,this,this.el)},bindGlobalHandlers:function(){this.listenTo(we.get(),{touchstart:this.processUnselect,mousedown:this.handleDocumentMousedown})},unbindGlobalHandlers:function(){this.stopListeningTo(we.get())},initThemingProps:function(){var t=this.opt("theme")?"ui":"fc";this.widgetHeaderClass=t+"-widget-header",this.widgetContentClass=t+"-widget-content",this.highlightStateClass=t+"-state-highlight"},renderBusinessHours:function(){},unrenderBusinessHours:function(){},startNowIndicator:function(){var t,n,i,r=this;this.opt("nowIndicator")&&(t=this.getNowIndicatorUnit())&&(n=mt(this,"updateNowIndicator"),this.initialNowDate=this.calendar.getNow(),this.initialNowQueriedMs=+new Date,this.renderNowIndicator(this.initialNowDate),this.isNowIndicatorRendered=!0,i=this.initialNowDate.clone().startOf(t).add(1,t)-this.initialNowDate,this.nowIndicatorTimeoutID=setTimeout(function(){r.nowIndicatorTimeoutID=null,n(),i=+e.duration(1,t),i=Math.max(100,i),r.nowIndicatorIntervalID=setInterval(n,i)},i))},updateNowIndicator:function(){this.isNowIndicatorRendered&&(this.unrenderNowIndicator(),this.renderNowIndicator(this.initialNowDate.clone().add(new Date-this.initialNowQueriedMs)))},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)},getNowIndicatorUnit:function(){},renderNowIndicator:function(t){},unrenderNowIndicator:function(){},updateSize:function(t){var e;t&&(e=this.queryScroll()),this.updateHeight(t),this.updateWidth(t),this.updateNowIndicator(),t&&this.applyScroll(e)},updateWidth:function(t){},updateHeight:function(t){var e=this.calendar;this.setHeight(e.getSuggestedViewHeight(),e.isHeightAuto())},setHeight:function(t,e){},addForcedScroll:function(e){this.addScroll(t.extend(e,{isForced:!0}))},addScroll:function(e){var n=this.queuedScroll||(this.queuedScroll={});n.isForced||t.extend(n,e)},popScroll:function(){this.applyQueuedScroll(),this.queuedScroll=null},applyQueuedScroll:function(){this.queuedScroll&&this.applyScroll(this.queuedScroll)},queryScroll:function(){var e={};return this.isDatesRendered&&t.extend(e,this.queryDateScroll()),e},applyScroll:function(t){this.isDatesRendered&&this.applyDateScroll(t)},computeInitialDateScroll:function(){return{}},queryDateScroll:function(){return{}},applyDateScroll:function(t){},freezeHeight:function(){this.calendar.freezeContentHeight()},thawHeight:function(){this.calendar.thawContentHeight()},executeEventsRender:function(t){this.renderEvents(t),this.isEventsRendered=!0,this.onEventsRender()},executeEventsUnrender:function(){this.onBeforeEventsUnrender(),this.destroyEvents&&this.destroyEvents(),this.unrenderEvents(),this.isEventsRendered=!1},onEventsRender:function(){this.applyScreenState(),this.renderedEventSegEach(function(t){this.publiclyTrigger("eventAfterRender",t.event,t.event,t.el)}),this.publiclyTrigger("eventAfterAllRender")},onBeforeEventsUnrender:function(){this.applyScreenState(),this.renderedEventSegEach(function(t){this.publiclyTrigger("eventDestroy",t.event,t.event,t.el)})},applyScreenState:function(){this.thawHeight(),this.freezeHeight(),this.applyQueuedScroll()},renderEvents:function(t){},unrenderEvents:function(){},resolveEventEl:function(e,n){var i=this.publiclyTrigger("eventRender",e,e,n);return!1===i?n=null:i&&!0!==i&&(n=t(i)),n},showEvent:function(t){this.renderedEventSegEach(function(t){t.el.css("visibility","")},t)},hideEvent:function(t){this.renderedEventSegEach(function(t){t.el.css("visibility","hidden")},t)},renderedEventSegEach:function(t,e){var n,i=this.getEventSegs();for(n=0;n<i.length;n++)e&&i[n].event._id!==e._id||i[n].el&&t.call(this,i[n])},getEventSegs:function(){return[]},isEventDraggable:function(t){return this.isEventStartEditable(t)},isEventStartEditable:function(t){return ut(t.startEditable,(t.source||{}).startEditable,this.opt("eventStartEditable"),this.isEventGenerallyEditable(t))},isEventGenerallyEditable:function(t){return ut(t.editable,(t.source||{}).editable,this.opt("editable"))},reportSegDrop:function(t,e,n,i,r){var s=this.calendar,o=s.mutateSeg(t,e,n),a=function(){o.undo(),s.reportEventChange()};this.triggerEventDrop(t.event,o.dateDelta,a,i,r),s.reportEventChange()},triggerEventDrop:function(t,e,n,i,r){this.publiclyTrigger("eventDrop",i[0],t,e,n,r,{})},reportExternalDrop:function(e,n,i,r,s){var o,a,l=e.eventProps;l&&(o=t.extend({},l,n),a=this.calendar.renderEvent(o,e.stick)[0]),this.triggerExternalDrop(a,n,i,r,s)},triggerExternalDrop:function(t,e,n,i,r){this.publiclyTrigger("drop",n[0],e.start,i,r),t&&this.publiclyTrigger("eventReceive",null,t)},renderDrag:function(t,e){},unrenderDrag:function(){},isEventResizableFromStart:function(t){return this.opt("eventResizableFromStart")&&this.isEventResizable(t)},isEventResizableFromEnd:function(t){return this.isEventResizable(t)},isEventResizable:function(t){var e=t.source||{};return ut(t.durationEditable,e.durationEditable,this.opt("eventDurationEditable"),t.editable,e.editable,this.opt("editable"))},reportSegResize:function(t,e,n,i,r){var s=this.calendar,o=s.mutateSeg(t,e,n),a=function(){o.undo(),s.reportEventChange()};this.triggerEventResize(t.event,o.durationDelta,a,i,r),s.reportEventChange()},triggerEventResize:function(t,e,n,i,r){this.publiclyTrigger("eventResize",i[0],t,e,n,r,{})},select:function(t,e){this.unselect(e),this.renderSelection(t),this.reportSelection(t,e)},renderSelection:function(t){},reportSelection:function(t,e){this.isSelected=!0,this.triggerSelect(t,e)},triggerSelect:function(t,e){this.publiclyTrigger("select",null,this.calendar.applyTimezone(t.start),this.calendar.applyTimezone(t.end),e)},unselect:function(t){this.isSelected&&(this.isSelected=!1,this.destroySelection&&this.destroySelection(),this.unrenderSelection(),this.publiclyTrigger("unselect",null,t))},unrenderSelection:function(){},selectEvent:function(t){this.selectedEvent&&this.selectedEvent===t||(this.unselectEvent(),this.renderedEventSegEach(function(t){t.el.addClass("fc-selected")},t),this.selectedEvent=t)},unselectEvent:function(){this.selectedEvent&&(this.renderedEventSegEach(function(t){t.el.removeClass("fc-selected")},this.selectedEvent),this.selectedEvent=null)},isEventSelected:function(t){return this.selectedEvent&&this.selectedEvent._id===t._id},handleDocumentMousedown:function(t){S(t)&&this.processUnselect(t)},processUnselect:function(t){this.processRangeUnselect(t),this.processEventUnselect(t)},processRangeUnselect:function(e){var n;this.isSelected&&this.opt("unselectAuto")&&((n=this.opt("unselectCancel"))&&t(e.target).closest(n).length||this.unselect(e))},processEventUnselect:function(e){this.selectedEvent&&(t(e.target).closest(".fc-selected").length||this.unselectEvent())},triggerDayClick:function(t,e,n){this.publiclyTrigger("dayClick",e,this.calendar.applyTimezone(t.start),n)},computeDayRange:function(t){var e,n=t.start.clone().stripTime(),i=t.end,r=null;return i&&(r=i.clone().stripTime(),(e=+i.time())&&e>=this.nextDayThreshold&&r.add(1,"days")),(!i||r<=n)&&(r=n.clone().add(1,"days")),{start:n,end:r}},isMultiDayEvent:function(t){var e=this.computeDayRange(t);return e.end.diff(e.start,"days")>1}});Ce.watch("displayingDates",["dateProfile"],function(t){this.requestDateRender(t.dateProfile)},function(){this.requestDateUnrender()}),Ce.watch("initialEvents",["dateProfile"],function(t){return this.fetchInitialEvents(t.dateProfile)}),Ce.watch("bindingEvents",["initialEvents"],function(t){this.setEvents(t.initialEvents),this.bindEventChanges()},function(){this.unbindEventChanges(),this.unsetEvents()}),Ce.watch("displayingEvents",["displayingDates","hasEvents"],function(){this.requestEventsRender(this.get("currentEvents"))},function(){this.requestEventsUnrender()}),Ce.mixin({currentRange:null,currentRangeUnit:null,renderRange:null,activeRange:null,validRange:null,dateIncrement:null,minTime:null,maxTime:null,usesMinMaxTime:!1,start:null,end:null,intervalStart:null,intervalEnd:null,setDateProfileForRendering:function(t){this.currentRange=t.currentRange,this.currentRangeUnit=t.currentRangeUnit,this.renderRange=t.renderRange,this.activeRange=t.activeRange,this.validRange=t.validRange,this.dateIncrement=t.dateIncrement,this.minTime=t.minTime,this.maxTime=t.maxTime,this.start=t.activeRange.start,this.end=t.activeRange.end,this.intervalStart=t.currentRange.start,this.intervalEnd=t.currentRange.end},buildPrevDateProfile:function(t){var e=t.clone().startOf(this.currentRangeUnit).subtract(this.dateIncrement);return this.buildDateProfile(e,-1)},buildNextDateProfile:function(t){var e=t.clone().startOf(this.currentRangeUnit).add(this.dateIncrement);return this.buildDateProfile(e,1)},buildDateProfile:function(t,n,i){var r,s,o,a,l=this.buildValidRange(),u=null,h=null;return i&&(t=j(t,l)),r=this.buildCurrentRangeInfo(t,n),s=this.buildRenderRange(r.range,r.unit),o=q(s),this.opt("showNonCurrentDates")||(o=U(o,r.range)),u=e.duration(this.opt("minTime")),h=e.duration(this.opt("maxTime")),this.adjustActiveRange(o,u,h),o=U(o,l),t=j(t,o),a=$(r.range,l),{validRange:l,currentRange:r.range,currentRangeUnit:r.unit,activeRange:o,renderRange:s,minTime:u,maxTime:h,isValid:a,date:t,dateIncrement:this.buildDateIncrement(r.duration)}},buildValidRange:function(){return this.getRangeOption("validRange",this.calendar.getNow())||{}},buildCurrentRangeInfo:function(t,e){var n,i=null,r=null,s=null;return this.viewSpec.duration?(i=this.viewSpec.duration,r=this.viewSpec.durationUnit,s=this.buildRangeFromDuration(t,e,i,r)):(n=this.opt("dayCount"))?(r="day",s=this.buildRangeFromDayCount(t,e,n)):(s=this.buildCustomVisibleRange(t))?r=V(s.start,s.end):(i=this.getFallbackDuration(),r=V(i),s=this.buildRangeFromDuration(t,e,i,r)),this.normalizeCurrentRange(s,r),{duration:i,unit:r,range:s}},getFallbackDuration:function(){return e.duration({days:1})},normalizeCurrentRange:function(t,e){/^(year|month|week|day)$/.test(e)?(t.start.stripTime(),t.end.stripTime()):(t.start.hasTime()||t.start.time(0),t.end.hasTime()||t.end.time(0))},adjustActiveRange:function(t,e,n){var i=!1;this.usesMinMaxTime&&(e<0&&(t.start.time(0).add(e),i=!0),n>864e5&&(t.end.time(n-864e5),i=!0),i&&(t.start.hasTime()||t.start.time(0),t.end.hasTime()||t.end.time(0)))},buildRangeFromDuration:function(t,n,i,r){var s,o,a,l=this.opt("dateAlignment"),u=t.clone();return i.as("days")<=1&&this.isHiddenDay(u)&&(u=this.skipHiddenDays(u,n),u.startOf("day")),l||(o=this.opt("dateIncrement"),o?(a=e.duration(o),l=a<i?O(a,o):r):l=r),u.startOf(l),s=u.clone().add(i),{start:u,end:s}},buildRangeFromDayCount:function(t,e,n){var i,r=this.opt("dateAlignment"),s=0,o=t.clone();r&&o.startOf(r),o.startOf("day"),o=this.skipHiddenDays(o,e),i=o.clone();do{i.add(1,"day"),this.isHiddenDay(i)||s++}while(s<n);return{start:o,end:i}},buildCustomVisibleRange:function(t){var e=this.getRangeOption("visibleRange",this.calendar.moment(t));return!e||e.start&&e.end?e:null},buildRenderRange:function(t,e){return this.trimHiddenDays(t)},buildDateIncrement:function(t){var n,i=this.opt("dateIncrement");return i?e.duration(i):(n=this.opt("dateAlignment"))?e.duration(1,n):t||e.duration({days:1})},trimHiddenDays:function(t){return{start:this.skipHiddenDays(t.start),end:this.skipHiddenDays(t.end,-1,!0)}},currentRangeAs:function(t){var e=this.currentRange;return e.end.diff(e.start,t,!0)},getRangeOption:function(t){var e=this.opt(t);if("function"==typeof e&&(e=e.apply(null,Array.prototype.slice.call(arguments,1))),e)return this.calendar.parseRange(e)},initHiddenDays:function(){var e,n=this.opt("hiddenDays")||[],i=[],r=0;for(!1===this.opt("weekends")&&n.push(0,6),e=0;e<7;e++)(i[e]=-1!==t.inArray(e,n))||r++;if(!r)throw"invalid hiddenDays";this.isHiddenDayHash=i},isHiddenDay:function(t){return e.isMoment(t)&&(t=t.day()),this.isHiddenDayHash[t]},skipHiddenDays:function(t,e,n){var i=t.clone();for(e=e||1;this.isHiddenDayHash[(i.day()+(n?e:0)+7)%7];)i.add(e,"days");return i}});var He=Zt.Scroller=bt.extend({el:null,scrollEl:null,overflowX:null,overflowY:null,constructor:function(t){t=t||{},this.overflowX=t.overflowX||t.overflow||"auto",this.overflowY=t.overflowY||t.overflow||"auto"},render:function(){this.el=this.renderEl(),this.applyOverflow()},renderEl:function(){return this.scrollEl=t('<div class="fc-scroller"></div>')},clear:function(){this.setHeight("auto"),this.applyOverflow()},destroy:function(){this.el.remove()},applyOverflow:function(){this.scrollEl.css({"overflow-x":this.overflowX,"overflow-y":this.overflowY})},lockOverflow:function(t){var e=this.overflowX,n=this.overflowY;t=t||this.getScrollbarWidths(),"auto"===e&&(e=t.top||t.bottom||this.scrollEl[0].scrollWidth-1>this.scrollEl[0].clientWidth?"scroll":"hidden"),"auto"===n&&(n=t.left||t.right||this.scrollEl[0].scrollHeight-1>this.scrollEl[0].clientHeight?"scroll":"hidden"),this.scrollEl.css({"overflow-x":e,"overflow-y":n})},setHeight:function(t){this.scrollEl.height(t)},getScrollTop:function(){return this.scrollEl.scrollTop()},setScrollTop:function(t){this.scrollEl.scrollTop(t)},getClientWidth:function(){return this.scrollEl[0].clientWidth},getClientHeight:function(){return this.scrollEl[0].clientHeight},getScrollbarWidths:function(){return p(this.scrollEl)}});_t.prototype.proxyCall=function(t){var e=Array.prototype.slice.call(arguments,1),n=[];return this.items.forEach(function(i){n.push(i[t].apply(i,e))}),n};var Re=Zt.Calendar=bt.extend(fe,{view:null,viewsByType:null,currentDate:null,loadingLevel:0,constructor:function(t,e){we.needed(),this.el=t,this.viewsByType={},this.viewSpecCache={},this.initOptionsInternals(e),this.initMomentInternals(),this.initCurrentDate(),Ut.call(this),this.initialize()},initialize:function(){},getCalendar:function(){return this},getView:function(){return this.view},publiclyTrigger:function(t,e){var n=Array.prototype.slice.call(arguments,2),i=this.opt(t);if(e=e||this.el[0],this.triggerWith(t,e,n),i)return i.apply(e,n)},instantiateView:function(t){var e=this.getViewSpec(t);return new e.class(this,e)},isValidViewType:function(t){return Boolean(this.getViewSpec(t))},changeView:function(t,e){e&&(e.start&&e.end?this.recordOptionOverrides({visibleRange:e}):this.currentDate=this.moment(e).stripZone()),this.renderView(t)},zoomTo:function(t,e){var n;e=e||"day",n=this.getViewSpec(e)||this.getUnitViewSpec(e),this.currentDate=t.clone(),this.renderView(n?n.type:null)},initCurrentDate:function(){var t=this.opt("defaultDate");this.currentDate=null!=t?this.moment(t).stripZone():this.getNow()},prev:function(){var t=this.view.buildPrevDateProfile(this.currentDate);t.isValid&&(this.currentDate=t.date,this.renderView())},next:function(){var t=this.view.buildNextDateProfile(this.currentDate);t.isValid&&(this.currentDate=t.date,this.renderView())},prevYear:function(){this.currentDate.add(-1,"years"),this.renderView()},nextYear:function(){this.currentDate.add(1,"years"),this.renderView()},today:function(){this.currentDate=this.getNow(),this.renderView()},gotoDate:function(t){this.currentDate=this.moment(t).stripZone(),this.renderView()},incrementDate:function(t){this.currentDate.add(e.duration(t)),this.renderView()},getDate:function(){return this.applyTimezone(this.currentDate)},pushLoading:function(){this.loadingLevel++||this.publiclyTrigger("loading",null,!0,this.view)},popLoading:function(){--this.loadingLevel||this.publiclyTrigger("loading",null,!1,this.view)},select:function(t,e){this.view.select(this.buildSelectSpan.apply(this,arguments))},unselect:function(){this.view&&this.view.unselect()},buildSelectSpan:function(t,e){var n,i=this.moment(t).stripZone();return n=e?this.moment(e).stripZone():i.hasTime()?i.clone().add(this.defaultTimedEventDuration):i.clone().add(this.defaultAllDayEventDuration),{start:i,end:n}},parseRange:function(t){var e=null,n=null;return t.start&&(e=this.moment(t.start).stripZone()),t.end&&(n=this.moment(t.end).stripZone()),e||n?e&&n&&n.isBefore(e)?null:{start:e,end:n}:null},rerenderEvents:function(){this.elementVisible()&&this.reportEventChange()}});Re.mixin({dirDefaults:null,localeDefaults:null,overrides:null,dynamicOverrides:null,optionsModel:null,initOptionsInternals:function(e){this.overrides=t.extend({},e),this.dynamicOverrides={},this.optionsModel=new ue,this.populateOptionsHash()},option:function(t,e){var n;if("string"==typeof t){if(void 0===e)return this.optionsModel.get(t);n={},n[t]=e,this.setOptions(n)}else"object"==typeof t&&this.setOptions(t)},opt:function(t){return this.optionsModel.get(t)},setOptions:function(t){var e,n=0;this.recordOptionOverrides(t);for(e in t)n++;if(1===n){if("height"===e||"contentHeight"===e||"aspectRatio"===e)return void this.updateSize(!0);if("defaultDate"===e)return;if("businessHours"===e)return void(this.view&&(this.view.unrenderBusinessHours(),this.view.renderBusinessHours()));if("timezone"===e)return this.rezoneArrayEventSources(),void this.refetchEvents()}this.renderHeader(),this.renderFooter(),this.viewsByType={},this.reinitView()},populateOptionsHash:function(){var t,e,i,r,s;t=ut(this.dynamicOverrides.locale,this.overrides.locale),e=xe[t],e||(t=Re.defaults.locale,e=xe[t]||{}),i=ut(this.dynamicOverrides.isRTL,this.overrides.isRTL,e.isRTL,Re.defaults.isRTL),r=i?Re.rtlDefaults:{},this.dirDefaults=r,this.localeDefaults=e,s=n([Re.defaults,r,e,this.overrides,this.dynamicOverrides]),Yt(s),this.optionsModel.reset(s)},recordOptionOverrides:function(t){var e;for(e in t)this.dynamicOverrides[e]=t[e];this.viewSpecCache={},this.populateOptionsHash()}}),Re.mixin({defaultAllDayEventDuration:null,defaultTimedEventDuration:null,localeData:null,initMomentInternals:function(){var t=this;this.defaultAllDayEventDuration=e.duration(this.opt("defaultAllDayEventDuration")),this.defaultTimedEventDuration=e.duration(this.opt("defaultTimedEventDuration")),this.optionsModel.watch("buildingMomentLocale",["?locale","?monthNames","?monthNamesShort","?dayNames","?dayNamesShort","?firstDay","?weekNumberCalculation"],function(e){var n,i=e.weekNumberCalculation,r=e.firstDay;"iso"===i&&(i="ISO");var s=rt(qt(e.locale));e.monthNames&&(s._months=e.monthNames),e.monthNamesShort&&(s._monthsShort=e.monthNamesShort),e.dayNames&&(s._weekdays=e.dayNames),e.dayNamesShort&&(s._weekdaysShort=e.dayNamesShort),null==r&&"ISO"===i&&(r=1),null!=r&&(n=rt(s._week),n.dow=r,s._week=n),"ISO"!==i&&"local"!==i&&"function"!=typeof i||(s._fullCalendar_weekCalc=i),t.localeData=s,t.currentDate&&t.localizeMoment(t.currentDate)})},moment:function(){var t;return"local"===this.opt("timezone")?(t=Zt.moment.apply(null,arguments),t.hasTime()&&t.local()):t="UTC"===this.opt("timezone")?Zt.moment.utc.apply(null,arguments):Zt.moment.parseZone.apply(null,arguments),this.localizeMoment(t),t},localizeMoment:function(t){t._locale=this.localeData},getIsAmbigTimezone:function(){return"local"!==this.opt("timezone")&&"UTC"!==this.opt("timezone")},applyTimezone:function(t){if(!t.hasTime())return t.clone();var e,n=this.moment(t.toArray()),i=t.time()-n.time();return i&&(e=n.clone().add(i),t.time()-e.time()==0&&(n=e)),n},getNow:function(){var t=this.opt("now");return"function"==typeof t&&(t=t()),this.moment(t).stripZone()},humanizeDuration:function(t){return t.locale(this.opt("locale")).humanize()},getEventEnd:function(t){return t.end?t.end.clone():this.getDefaultEventEnd(t.allDay,t.start)},getDefaultEventEnd:function(t,e){var n=e.clone();return t?n.stripTime().add(this.defaultAllDayEventDuration):n.add(this.defaultTimedEventDuration),this.getIsAmbigTimezone()&&n.stripZone(),n}}),Re.mixin({viewSpecCache:null,getViewSpec:function(t){var e=this.viewSpecCache;return e[t]||(e[t]=this.buildViewSpec(t))},getUnitViewSpec:function(e){var n,i,r;if(-1!=t.inArray(e,Jt))for(n=this.header.getViewsWithButtons(),t.each(Zt.views,function(t){n.push(t)}),i=0;i<n.length;i++)if((r=this.getViewSpec(n[i]))&&r.singleUnit==e)return r},buildViewSpec:function(t){for(var i,r,s,o,a,l=this.overrides.views||{},u=[],h=[],c=[],d=t;d;)i=$t[d],r=l[d],d=null,"function"==typeof i&&(i={class:i}),i&&(u.unshift(i),h.unshift(i.defaults||{}),s=s||i.duration,d=d||i.type),r&&(c.unshift(r),s=s||r.duration,d=d||r.type);return i=it(u),i.type=t,!!i.class&&(s=s||this.dynamicOverrides.duration||this.overrides.duration,s&&(o=e.duration(s),o.valueOf()&&(a=O(o,s),i.duration=o,i.durationUnit=a,1===o.as(a)&&(i.singleUnit=a,c.unshift(l[a]||{})))),i.defaults=n(h),i.overrides=n(c),this.buildViewSpecOptions(i),this.buildViewSpecButtonText(i,t),i)},buildViewSpecOptions:function(t){t.options=n([Re.defaults,t.defaults,this.dirDefaults,this.localeDefaults,this.overrides,t.overrides,this.dynamicOverrides]),Yt(t.options)},buildViewSpecButtonText:function(t,e){function n(n){var i=n.buttonText||{};return i[e]||(t.buttonTextKey?i[t.buttonTextKey]:null)||(t.singleUnit?i[t.singleUnit]:null)}t.buttonTextOverride=n(this.dynamicOverrides)||n(this.overrides)||t.overrides.buttonText,t.buttonTextDefault=n(this.localeDefaults)||n(this.dirDefaults)||t.defaults.buttonText||n(Re.defaults)||(t.duration?this.humanizeDuration(t.duration):null)||e}}),Re.mixin({el:null,contentEl:null,suggestedViewHeight:null,windowResizeProxy:null,ignoreWindowResize:0,render:function(){this.contentEl?this.elementVisible()&&(this.calcSize(),this.renderView()):this.initialRender()},initialRender:function(){var e=this,n=this.el;n.addClass("fc"),n.on("click.fc","a[data-goto]",function(n){var i=t(this),r=i.data("goto"),s=e.moment(r.date),o=r.type,a=e.view.opt("navLink"+gt(o)+"Click");"function"==typeof a?a(s,n):("string"==typeof a&&(o=a),e.zoomTo(s,o))}),this.optionsModel.watch("applyingThemeClasses",["?theme"],function(t){n.toggleClass("ui-widget",t.theme),n.toggleClass("fc-unthemed",!t.theme)}),this.optionsModel.watch("applyingDirClasses",["?isRTL","?locale"],function(t){n.toggleClass("fc-ltr",!t.isRTL),n.toggleClass("fc-rtl",t.isRTL)}),this.contentEl=t("<div class='fc-view-container'/>").prependTo(n),this.initToolbars(),this.renderHeader(),this.renderFooter(),this.renderView(this.opt("defaultView")),this.opt("handleWindowResize")&&t(window).resize(this.windowResizeProxy=yt(this.windowResize.bind(this),this.opt("windowResizeDelay")))},destroy:function(){this.view&&this.view.removeElement(),this.toolbarsManager.proxyCall("removeElement"),this.contentEl.remove(),this.el.removeClass("fc fc-ltr fc-rtl fc-unthemed ui-widget"),this.el.off(".fc"),this.windowResizeProxy&&(t(window).unbind("resize",this.windowResizeProxy),this.windowResizeProxy=null),we.unneeded()},elementVisible:function(){return this.el.is(":visible")},renderView:function(e,n){this.ignoreWindowResize++;var i=this.view&&e&&this.view.type!==e;i&&(this.freezeContentHeight(),this.clearView()),!this.view&&e&&(this.view=this.viewsByType[e]||(this.viewsByType[e]=this.instantiateView(e)),this.view.setElement(t("<div class='fc-view fc-"+e+"-view' />").appendTo(this.contentEl)),this.toolbarsManager.proxyCall("activateButton",e)),this.view&&(n&&this.view.addForcedScroll(n),this.elementVisible()&&(this.currentDate=this.view.setDate(this.currentDate))),i&&this.thawContentHeight(),this.ignoreWindowResize--},clearView:function(){this.toolbarsManager.proxyCall("deactivateButton",this.view.type),this.view.removeElement(),this.view=null},reinitView:function(){this.ignoreWindowResize++,this.freezeContentHeight();var t=this.view.type,e=this.view.queryScroll();this.clearView(),this.calcSize(),this.renderView(t,e),this.thawContentHeight(),this.ignoreWindowResize--},getSuggestedViewHeight:function(){return null===this.suggestedViewHeight&&this.calcSize(),this.suggestedViewHeight},isHeightAuto:function(){return"auto"===this.opt("contentHeight")||"auto"===this.opt("height")},updateSize:function(t){if(this.elementVisible())return t&&this._calcSize(),this.ignoreWindowResize++,this.view.updateSize(!0),this.ignoreWindowResize--,!0},calcSize:function(){this.elementVisible()&&this._calcSize()},_calcSize:function(){var t=this.opt("contentHeight"),e=this.opt("height");this.suggestedViewHeight="number"==typeof t?t:"function"==typeof t?t():"number"==typeof e?e-this.queryToolbarsHeight():"function"==typeof e?e()-this.queryToolbarsHeight():"parent"===e?this.el.parent().height()-this.queryToolbarsHeight():Math.round(this.contentEl.width()/Math.max(this.opt("aspectRatio"),.5))},windowResize:function(t){!this.ignoreWindowResize&&t.target===window&&this.view.renderRange&&this.updateSize(!0)&&this.view.publiclyTrigger("windowResize",this.el[0])},freezeContentHeight:function(){this.contentEl.css({width:"100%",height:this.contentEl.height(),overflow:"hidden"})},thawContentHeight:function(){this.contentEl.css({width:"",height:"",overflow:""})}}),Re.mixin({header:null,footer:null,toolbarsManager:null,initToolbars:function(){this.header=new Wt(this,this.computeHeaderOptions()),this.footer=new Wt(this,this.computeFooterOptions()),this.toolbarsManager=new _t([this.header,this.footer])},computeHeaderOptions:function(){return{extraClasses:"fc-header-toolbar",layout:this.opt("header")}},computeFooterOptions:function(){return{extraClasses:"fc-footer-toolbar",layout:this.opt("footer")}},renderHeader:function(){var t=this.header;t.setToolbarOptions(this.computeHeaderOptions()),t.render(),t.el&&this.el.prepend(t.el)},renderFooter:function(){var t=this.footer;t.setToolbarOptions(this.computeFooterOptions()),t.render(),t.el&&this.el.append(t.el)},setToolbarsTitle:function(t){this.toolbarsManager.proxyCall("updateTitle",t)},updateToolbarButtons:function(){var t=this.getNow(),e=this.view,n=e.buildDateProfile(t),i=e.buildPrevDateProfile(this.currentDate),r=e.buildNextDateProfile(this.currentDate);this.toolbarsManager.proxyCall(n.isValid&&!Z(t,e.currentRange)?"enableButton":"disableButton","today"),this.toolbarsManager.proxyCall(i.isValid?"enableButton":"disableButton","prev"),this.toolbarsManager.proxyCall(r.isValid?"enableButton":"disableButton","next")},queryToolbarsHeight:function(){return this.toolbarsManager.items.reduce(function(t,e){return t+(e.el?e.el.outerHeight(!0):0)},0)}}),Re.defaults={titleRangeSeparator:" รขโ‚ฌโ€œ ",monthYearFormat:"MMMM YYYY",defaultTimedEventDuration:"02:00:00",defaultAllDayEventDuration:{days:1},forceEventDuration:!1,nextDayThreshold:"09:00:00",defaultView:"month",aspectRatio:1.35,header:{left:"title",center:"",right:"today prev,next"},weekends:!0,weekNumbers:!1,weekNumberTitle:"W",weekNumberCalculation:"local",scrollTime:"06:00:00",minTime:"00:00:00",maxTime:"24:00:00",showNonCurrentDates:!0,lazyFetching:!0,startParam:"start",endParam:"end",timezoneParam:"timezone",timezone:!1,isRTL:!1,buttonText:{prev:"prev",next:"next",prevYear:"prev year",nextYear:"next year",year:"year",today:"today",month:"month",week:"week",day:"day"},buttonIcons:{prev:"left-single-arrow",next:"right-single-arrow",prevYear:"left-double-arrow",nextYear:"right-double-arrow"},allDayText:"all-day",theme:!1,themeButtonIcons:{prev:"circle-triangle-w",next:"circle-triangle-e",prevYear:"seek-prev",nextYear:"seek-next"},dragOpacity:.75,dragRevertDuration:500,dragScroll:!0,unselectAuto:!0,dropAccept:"*",eventOrder:"title",eventLimit:!1,eventLimitText:"more",eventLimitClick:"popover",dayPopoverFormat:"LL",handleWindowResize:!0,windowResizeDelay:100,longPressDelay:1e3},Re.englishDefaults={dayPopoverFormat:"dddd, MMMM D"},Re.rtlDefaults={header:{left:"next,prev today",center:"",right:"title"},buttonIcons:{prev:"right-single-arrow",next:"left-single-arrow",prevYear:"right-double-arrow",nextYear:"left-double-arrow"},themeButtonIcons:{prev:"circle-triangle-e",next:"circle-triangle-w",nextYear:"seek-prev",prevYear:"seek-next"}};var xe=Zt.locales={};Zt.datepickerLocale=function(e,n,i){var r=xe[e]||(xe[e]={});r.isRTL=i.isRTL,r.weekNumberTitle=i.weekHeader,t.each(Ie,function(t,e){r[t]=e(i)}),t.datepicker&&(t.datepicker.regional[n]=t.datepicker.regional[e]=i,t.datepicker.regional.en=t.datepicker.regional[""],t.datepicker.setDefaults(i))},Zt.locale=function(e,i){var r,s;r=xe[e]||(xe[e]={}),i&&(r=xe[e]=n([r,i])),s=qt(e),t.each(ke,function(t,e){null==r[t]&&(r[t]=e(s,r))}),Re.defaults.locale=e};var Ie={buttonText:function(t){return{prev:ct(t.prevText),next:ct(t.nextText),today:ct(t.currentText)}},monthYearFormat:function(t){return t.showMonthAfterYear?"YYYY["+t.yearSuffix+"] MMMM":"MMMM YYYY["+t.yearSuffix+"]"}},ke={dayOfMonthFormat:function(t,e){var n=t.longDateFormat("l");return n=n.replace(/^Y+[^\w\s]*|[^\w\s]*Y+$/g,""),e.isRTL?n+=" ddd":n="ddd "+n,n},mediumTimeFormat:function(t){return t.longDateFormat("LT").replace(/\s*a$/i,"a")},smallTimeFormat:function(t){return t.longDateFormat("LT").replace(":mm","(:mm)").replace(/(\Wmm)$/,"($1)").replace(/\s*a$/i,"a")},extraSmallTimeFormat:function(t){return t.longDateFormat("LT").replace(":mm","(:mm)").replace(/(\Wmm)$/,"($1)").replace(/\s*a$/i,"t")},hourFormat:function(t){return t.longDateFormat("LT").replace(":mm","").replace(/(\Wmm)$/,"").replace(/\s*a$/i,"a")},noMeridiemTimeFormat:function(t){return t.longDateFormat("LT").replace(/\s*a$/i,"")}},Me={smallDayDateFormat:function(t){return t.isRTL?"D dd":"dd D"},weekFormat:function(t){return t.isRTL?"w[ "+t.weekNumberTitle+"]":"["+t.weekNumberTitle+" ]w"},smallWeekFormat:function(t){return t.isRTL?"w["+t.weekNumberTitle+"]":"["+t.weekNumberTitle+"]w"}};Zt.locale("en",Re.englishDefaults),Zt.sourceNormalizers=[],Zt.sourceFetchers=[];var Be={dataType:"json",cache:!1},Le=1;Re.prototype.mutateSeg=function(t,e){return this.mutateEvent(t.event,e)},Re.prototype.normalizeEvent=function(t){},Re.prototype.spanContainsSpan=function(t,e){var n=t.start.clone().stripZone(),i=this.getEventEnd(t).stripZone()
10   -;return e.start>=n&&e.end<=i},Re.prototype.getPeerEvents=function(t,e){var n,i,r=this.getEventCache(),s=[];for(n=0;n<r.length;n++)i=r[n],e&&e._id===i._id||s.push(i);return s},Re.prototype.isEventSpanAllowed=function(t,e){var n=e.source||{},i=this.opt("eventAllow"),r=ut(e.constraint,n.constraint,this.opt("eventConstraint")),s=ut(e.overlap,n.overlap,this.opt("eventOverlap"));return this.isSpanAllowed(t,r,s,e)&&(!i||!1!==i(t,e))},Re.prototype.isExternalSpanAllowed=function(e,n,i){var r,s;return i&&(r=t.extend({},i,n),s=this.expandEvent(this.buildEventFromInput(r))[0]),s?this.isEventSpanAllowed(e,s):this.isSelectionSpanAllowed(e)},Re.prototype.isSelectionSpanAllowed=function(t){var e=this.opt("selectAllow");return this.isSpanAllowed(t,this.opt("selectConstraint"),this.opt("selectOverlap"))&&(!e||!1!==e(t))},Re.prototype.isSpanAllowed=function(t,e,n,i){var r,s,o,a,l,u;if(null!=e&&(r=this.constraintToEvents(e))){for(s=!1,a=0;a<r.length;a++)if(this.spanContainsSpan(r[a],t)){s=!0;break}if(!s)return!1}for(o=this.getPeerEvents(t,i),a=0;a<o.length;a++)if(l=o[a],this.eventIntersectsRange(l,t)){if(!1===n)return!1;if("function"==typeof n&&!n(l,i))return!1;if(i){if(!1===(u=ut(l.overlap,(l.source||{}).overlap)))return!1;if("function"==typeof u&&!u(i,l))return!1}}return!0},Re.prototype.constraintToEvents=function(t){return"businessHours"===t?this.getCurrentBusinessHourEvents():"object"==typeof t?null!=t.start?this.expandEvent(this.buildEventFromInput(t)):null:this.clientEvents(t)},Re.prototype.eventIntersectsRange=function(t,e){var n=t.start.clone().stripZone(),i=this.getEventEnd(t).stripZone();return e.start<i&&e.end>n};var Ne={id:"_fcBusinessHours",start:"09:00",end:"17:00",dow:[1,2,3,4,5],rendering:"inverse-background"};Re.prototype.getCurrentBusinessHourEvents=function(t){return this.computeBusinessHourEvents(t,this.opt("businessHours"))},Re.prototype.computeBusinessHourEvents=function(e,n){return!0===n?this.expandBusinessHourEvents(e,[{}]):t.isPlainObject(n)?this.expandBusinessHourEvents(e,[n]):t.isArray(n)?this.expandBusinessHourEvents(e,n,!0):[]},Re.prototype.expandBusinessHourEvents=function(e,n,i){var r,s,o=this.getView(),a=[];for(r=0;r<n.length;r++)s=n[r],i&&!s.dow||(s=t.extend({},Ne,s),e&&(s.start=null,s.end=null),a.push.apply(a,this.expandEvent(this.buildEventFromInput(s),o.activeRange.start,o.activeRange.end)));return a};var ze=Zt.BasicView=Ce.extend({scroller:null,dayGridClass:De,dayGrid:null,dayNumbersVisible:!1,colWeekNumbersVisible:!1,cellWeekNumbersVisible:!1,weekNumberWidth:null,headContainerEl:null,headRowEl:null,initialize:function(){this.dayGrid=this.instantiateDayGrid(),this.scroller=new He({overflowX:"hidden",overflowY:"auto"})},instantiateDayGrid:function(){return new(this.dayGridClass.extend(Fe))(this)},buildRenderRange:function(t,e){var n=Ce.prototype.buildRenderRange.apply(this,arguments);return/^(year|month)$/.test(e)&&(n.start.startOf("week"),n.end.weekday()&&n.end.add(1,"week").startOf("week")),this.trimHiddenDays(n)},renderDates:function(){this.dayGrid.breakOnWeeks=/year|month|week/.test(this.currentRangeUnit),this.dayGrid.setRange(this.renderRange),this.dayNumbersVisible=this.dayGrid.rowCnt>1,this.opt("weekNumbers")&&(this.opt("weekNumbersWithinDays")?(this.cellWeekNumbersVisible=!0,this.colWeekNumbersVisible=!1):(this.cellWeekNumbersVisible=!1,this.colWeekNumbersVisible=!0)),this.dayGrid.numbersVisible=this.dayNumbersVisible||this.cellWeekNumbersVisible||this.colWeekNumbersVisible,this.el.addClass("fc-basic-view").html(this.renderSkeletonHtml()),this.renderHead(),this.scroller.render();var e=this.scroller.el.addClass("fc-day-grid-container"),n=t('<div class="fc-day-grid" />').appendTo(e);this.el.find(".fc-body > tr > td").append(e),this.dayGrid.setElement(n),this.dayGrid.renderDates(this.hasRigidRows())},renderHead:function(){this.headContainerEl=this.el.find(".fc-head-container").html(this.dayGrid.renderHeadHtml()),this.headRowEl=this.headContainerEl.find(".fc-row")},unrenderDates:function(){this.dayGrid.unrenderDates(),this.dayGrid.removeElement(),this.scroller.destroy()},renderBusinessHours:function(){this.dayGrid.renderBusinessHours()},unrenderBusinessHours:function(){this.dayGrid.unrenderBusinessHours()},renderSkeletonHtml:function(){return'<table><thead class="fc-head"><tr><td class="fc-head-container '+this.widgetHeaderClass+'"></td></tr></thead><tbody class="fc-body"><tr><td class="'+this.widgetContentClass+'"></td></tr></tbody></table>'},weekNumberStyleAttr:function(){return null!==this.weekNumberWidth?'style="width:'+this.weekNumberWidth+'px"':""},hasRigidRows:function(){var t=this.opt("eventLimit");return t&&"number"!=typeof t},updateWidth:function(){this.colWeekNumbersVisible&&(this.weekNumberWidth=u(this.el.find(".fc-week-number")))},setHeight:function(t,e){var n,s,o=this.opt("eventLimit");this.scroller.clear(),r(this.headRowEl),this.dayGrid.removeSegPopover(),o&&"number"==typeof o&&this.dayGrid.limitRows(o),n=this.computeScrollerHeight(t),this.setGridHeight(n,e),o&&"number"!=typeof o&&this.dayGrid.limitRows(o),e||(this.scroller.setHeight(n),s=this.scroller.getScrollbarWidths(),(s.left||s.right)&&(i(this.headRowEl,s),n=this.computeScrollerHeight(t),this.scroller.setHeight(n)),this.scroller.lockOverflow(s))},computeScrollerHeight:function(t){return t-h(this.el,this.scroller.el)},setGridHeight:function(t,e){e?l(this.dayGrid.rowEls):a(this.dayGrid.rowEls,t,!0)},computeInitialDateScroll:function(){return{top:0}},queryDateScroll:function(){return{top:this.scroller.getScrollTop()}},applyDateScroll:function(t){void 0!==t.top&&this.scroller.setScrollTop(t.top)},hitsNeeded:function(){this.dayGrid.hitsNeeded()},hitsNotNeeded:function(){this.dayGrid.hitsNotNeeded()},prepareHits:function(){this.dayGrid.prepareHits()},releaseHits:function(){this.dayGrid.releaseHits()},queryHit:function(t,e){return this.dayGrid.queryHit(t,e)},getHitSpan:function(t){return this.dayGrid.getHitSpan(t)},getHitEl:function(t){return this.dayGrid.getHitEl(t)},renderEvents:function(t){this.dayGrid.renderEvents(t),this.updateHeight()},getEventSegs:function(){return this.dayGrid.getEventSegs()},unrenderEvents:function(){this.dayGrid.unrenderEvents()},renderDrag:function(t,e){return this.dayGrid.renderDrag(t,e)},unrenderDrag:function(){this.dayGrid.unrenderDrag()},renderSelection:function(t){this.dayGrid.renderSelection(t)},unrenderSelection:function(){this.dayGrid.unrenderSelection()}}),Fe={renderHeadIntroHtml:function(){var t=this.view;return t.colWeekNumbersVisible?'<th class="fc-week-number '+t.widgetHeaderClass+'" '+t.weekNumberStyleAttr()+"><span>"+ht(t.opt("weekNumberTitle"))+"</span></th>":""},renderNumberIntroHtml:function(t){var e=this.view,n=this.getCellDate(t,0);return e.colWeekNumbersVisible?'<td class="fc-week-number" '+e.weekNumberStyleAttr()+">"+e.buildGotoAnchorHtml({date:n,type:"week",forceOff:1===this.colCnt},n.format("w"))+"</td>":""},renderBgIntroHtml:function(){var t=this.view;return t.colWeekNumbersVisible?'<td class="fc-week-number '+t.widgetContentClass+'" '+t.weekNumberStyleAttr()+"></td>":""},renderIntroHtml:function(){var t=this.view;return t.colWeekNumbersVisible?'<td class="fc-week-number" '+t.weekNumberStyleAttr()+"></td>":""}},Ae=Zt.MonthView=ze.extend({buildRenderRange:function(){var t,e=ze.prototype.buildRenderRange.apply(this,arguments);return this.isFixedWeeks()&&(t=Math.ceil(e.end.diff(e.start,"weeks",!0)),e.end.add(6-t,"weeks")),e},setGridHeight:function(t,e){e&&(t*=this.rowCnt/6),a(this.dayGrid.rowEls,t,!e)},isFixedWeeks:function(){return this.opt("fixedWeekCount")}});$t.basic={class:ze},$t.basicDay={type:"basic",duration:{days:1}},$t.basicWeek={type:"basic",duration:{weeks:1}},$t.month={class:Ae,duration:{months:1},defaults:{fixedWeekCount:!0}};var Ge=Zt.AgendaView=Ce.extend({scroller:null,timeGridClass:Te,timeGrid:null,dayGridClass:De,dayGrid:null,axisWidth:null,headContainerEl:null,noScrollRowEls:null,bottomRuleEl:null,usesMinMaxTime:!0,initialize:function(){this.timeGrid=this.instantiateTimeGrid(),this.opt("allDaySlot")&&(this.dayGrid=this.instantiateDayGrid()),this.scroller=new He({overflowX:"hidden",overflowY:"auto"})},instantiateTimeGrid:function(){return new(this.timeGridClass.extend(Ve))(this)},instantiateDayGrid:function(){return new(this.dayGridClass.extend(Oe))(this)},renderDates:function(){this.timeGrid.setRange(this.renderRange),this.dayGrid&&this.dayGrid.setRange(this.renderRange),this.el.addClass("fc-agenda-view").html(this.renderSkeletonHtml()),this.renderHead(),this.scroller.render();var e=this.scroller.el.addClass("fc-time-grid-container"),n=t('<div class="fc-time-grid" />').appendTo(e);this.el.find(".fc-body > tr > td").append(e),this.timeGrid.setElement(n),this.timeGrid.renderDates(),this.bottomRuleEl=t('<hr class="fc-divider '+this.widgetHeaderClass+'"/>').appendTo(this.timeGrid.el),this.dayGrid&&(this.dayGrid.setElement(this.el.find(".fc-day-grid")),this.dayGrid.renderDates(),this.dayGrid.bottomCoordPadding=this.dayGrid.el.next("hr").outerHeight()),this.noScrollRowEls=this.el.find(".fc-row:not(.fc-scroller *)")},renderHead:function(){this.headContainerEl=this.el.find(".fc-head-container").html(this.timeGrid.renderHeadHtml())},unrenderDates:function(){this.timeGrid.unrenderDates(),this.timeGrid.removeElement(),this.dayGrid&&(this.dayGrid.unrenderDates(),this.dayGrid.removeElement()),this.scroller.destroy()},renderSkeletonHtml:function(){return'<table><thead class="fc-head"><tr><td class="fc-head-container '+this.widgetHeaderClass+'"></td></tr></thead><tbody class="fc-body"><tr><td class="'+this.widgetContentClass+'">'+(this.dayGrid?'<div class="fc-day-grid"/><hr class="fc-divider '+this.widgetHeaderClass+'"/>':"")+"</td></tr></tbody></table>"},axisStyleAttr:function(){return null!==this.axisWidth?'style="width:'+this.axisWidth+'px"':""},renderBusinessHours:function(){this.timeGrid.renderBusinessHours(),this.dayGrid&&this.dayGrid.renderBusinessHours()},unrenderBusinessHours:function(){this.timeGrid.unrenderBusinessHours(),this.dayGrid&&this.dayGrid.unrenderBusinessHours()},getNowIndicatorUnit:function(){return this.timeGrid.getNowIndicatorUnit()},renderNowIndicator:function(t){this.timeGrid.renderNowIndicator(t)},unrenderNowIndicator:function(){this.timeGrid.unrenderNowIndicator()},updateSize:function(t){this.timeGrid.updateSize(t),Ce.prototype.updateSize.call(this,t)},updateWidth:function(){this.axisWidth=u(this.el.find(".fc-axis"))},setHeight:function(t,e){var n,s,o;this.bottomRuleEl.hide(),this.scroller.clear(),r(this.noScrollRowEls),this.dayGrid&&(this.dayGrid.removeSegPopover(),n=this.opt("eventLimit"),n&&"number"!=typeof n&&(n=Pe),n&&this.dayGrid.limitRows(n)),e||(s=this.computeScrollerHeight(t),this.scroller.setHeight(s),o=this.scroller.getScrollbarWidths(),(o.left||o.right)&&(i(this.noScrollRowEls,o),s=this.computeScrollerHeight(t),this.scroller.setHeight(s)),this.scroller.lockOverflow(o),this.timeGrid.getTotalSlatHeight()<s&&this.bottomRuleEl.show())},computeScrollerHeight:function(t){return t-h(this.el,this.scroller.el)},computeInitialDateScroll:function(){var t=e.duration(this.opt("scrollTime")),n=this.timeGrid.computeTimeTop(t);return n=Math.ceil(n),n&&n++,{top:n}},queryDateScroll:function(){return{top:this.scroller.getScrollTop()}},applyDateScroll:function(t){void 0!==t.top&&this.scroller.setScrollTop(t.top)},hitsNeeded:function(){this.timeGrid.hitsNeeded(),this.dayGrid&&this.dayGrid.hitsNeeded()},hitsNotNeeded:function(){this.timeGrid.hitsNotNeeded(),this.dayGrid&&this.dayGrid.hitsNotNeeded()},prepareHits:function(){this.timeGrid.prepareHits(),this.dayGrid&&this.dayGrid.prepareHits()},releaseHits:function(){this.timeGrid.releaseHits(),this.dayGrid&&this.dayGrid.releaseHits()},queryHit:function(t,e){var n=this.timeGrid.queryHit(t,e);return!n&&this.dayGrid&&(n=this.dayGrid.queryHit(t,e)),n},getHitSpan:function(t){return t.component.getHitSpan(t)},getHitEl:function(t){return t.component.getHitEl(t)},renderEvents:function(t){var e,n=[],i=[];for(e=0;e<t.length;e++)t[e].allDay?n.push(t[e]):i.push(t[e]);this.timeGrid.renderEvents(i),this.dayGrid&&this.dayGrid.renderEvents(n),this.updateHeight()},getEventSegs:function(){return this.timeGrid.getEventSegs().concat(this.dayGrid?this.dayGrid.getEventSegs():[])},unrenderEvents:function(){this.timeGrid.unrenderEvents(),this.dayGrid&&this.dayGrid.unrenderEvents()},renderDrag:function(t,e){return t.start.hasTime()?this.timeGrid.renderDrag(t,e):this.dayGrid?this.dayGrid.renderDrag(t,e):void 0},unrenderDrag:function(){this.timeGrid.unrenderDrag(),this.dayGrid&&this.dayGrid.unrenderDrag()},renderSelection:function(t){t.start.hasTime()||t.end.hasTime()?this.timeGrid.renderSelection(t):this.dayGrid&&this.dayGrid.renderSelection(t)},unrenderSelection:function(){this.timeGrid.unrenderSelection(),this.dayGrid&&this.dayGrid.unrenderSelection()}}),Ve={renderHeadIntroHtml:function(){var t,e=this.view;return e.opt("weekNumbers")?(t=this.start.format(e.opt("smallWeekFormat")),'<th class="fc-axis fc-week-number '+e.widgetHeaderClass+'" '+e.axisStyleAttr()+">"+e.buildGotoAnchorHtml({date:this.start,type:"week",forceOff:this.colCnt>1},ht(t))+"</th>"):'<th class="fc-axis '+e.widgetHeaderClass+'" '+e.axisStyleAttr()+"></th>"},renderBgIntroHtml:function(){var t=this.view;return'<td class="fc-axis '+t.widgetContentClass+'" '+t.axisStyleAttr()+"></td>"},renderIntroHtml:function(){return'<td class="fc-axis" '+this.view.axisStyleAttr()+"></td>"}},Oe={renderBgIntroHtml:function(){var t=this.view;return'<td class="fc-axis '+t.widgetContentClass+'" '+t.axisStyleAttr()+"><span>"+t.getAllDayHtml()+"</span></td>"},renderIntroHtml:function(){return'<td class="fc-axis" '+this.view.axisStyleAttr()+"></td>"}},Pe=5,_e=[{hours:1},{minutes:30},{minutes:15},{seconds:30},{seconds:15}];$t.agenda={class:Ge,defaults:{allDaySlot:!0,slotDuration:"00:30:00",slotEventOverlap:!0}},$t.agendaDay={type:"agenda",duration:{days:1}},$t.agendaWeek={type:"agenda",duration:{weeks:1}};var We=Ce.extend({grid:null,scroller:null,initialize:function(){this.grid=new Ye(this),this.scroller=new He({overflowX:"hidden",overflowY:"auto"})},renderSkeleton:function(){this.el.addClass("fc-list-view "+this.widgetContentClass),this.scroller.render(),this.scroller.el.appendTo(this.el),this.grid.setElement(this.scroller.scrollEl)},unrenderSkeleton:function(){this.scroller.destroy()},setHeight:function(t,e){this.scroller.setHeight(this.computeScrollerHeight(t))},computeScrollerHeight:function(t){return t-h(this.el,this.scroller.el)},renderDates:function(){this.grid.setRange(this.renderRange)},renderEvents:function(t){this.grid.renderEvents(t)},unrenderEvents:function(){this.grid.unrenderEvents()},isEventResizable:function(t){return!1},isEventDraggable:function(t){return!1}}),Ye=be.extend({segSelector:".fc-list-item",hasDayInteractions:!1,spanToSegs:function(t){for(var e,n=this.view,i=n.renderRange.start.clone().time(0),r=0,s=[];i<n.renderRange.end;)if(e=z(t,{start:i,end:i.clone().add(1,"day")}),e&&(e.dayIndex=r,s.push(e)),i.add(1,"day"),r++,e&&!e.isEnd&&t.end.hasTime()&&t.end<i.clone().add(this.view.nextDayThreshold)){e.end=t.end.clone(),e.isEnd=!0;break}return s},computeEventTimeFormat:function(){return this.view.opt("mediumTimeFormat")},handleSegClick:function(e,n){var i;be.prototype.handleSegClick.apply(this,arguments),t(n.target).closest("a[href]").length||(i=e.event.url)&&!n.isDefaultPrevented()&&(window.location.href=i)},renderFgSegs:function(t){return t=this.renderFgSegEls(t),t.length?this.renderSegList(t):this.renderEmptyMessage(),t},renderEmptyMessage:function(){this.el.html('<div class="fc-list-empty-wrap2"><div class="fc-list-empty-wrap1"><div class="fc-list-empty">'+ht(this.view.opt("noEventsMessage"))+"</div></div></div>")},renderSegList:function(e){var n,i,r,s=this.groupSegsByDay(e),o=t('<table class="fc-list-table"><tbody/></table>'),a=o.find("tbody");for(n=0;n<s.length;n++)if(i=s[n])for(a.append(this.dayHeaderHtml(this.view.renderRange.start.clone().add(n,"days"))),this.sortEventSegs(i),r=0;r<i.length;r++)a.append(i[r].el);this.el.empty().append(o)},groupSegsByDay:function(t){var e,n,i=[];for(e=0;e<t.length;e++)n=t[e],(i[n.dayIndex]||(i[n.dayIndex]=[])).push(n);return i},dayHeaderHtml:function(t){var e=this.view,n=e.opt("listDayFormat"),i=e.opt("listDayAltFormat");return'<tr class="fc-list-heading" data-date="'+t.format("YYYY-MM-DD")+'"><td class="'+e.widgetHeaderClass+'" colspan="3">'+(n?e.buildGotoAnchorHtml(t,{class:"fc-list-heading-main"},ht(t.format(n))):"")+(i?e.buildGotoAnchorHtml(t,{class:"fc-list-heading-alt"},ht(t.format(i))):"")+"</td></tr>"},fgSegHtml:function(t){var e,n=this.view,i=["fc-list-item"].concat(this.getSegCustomClasses(t)),r=this.getSegBackgroundColor(t),s=t.event,o=s.url;return e=s.allDay?n.getAllDayHtml():n.isMultiDayEvent(s)?t.isStart||t.isEnd?ht(this.getEventTimeText(t)):n.getAllDayHtml():ht(this.getEventTimeText(s)),o&&i.push("fc-has-url"),'<tr class="'+i.join(" ")+'">'+(this.displayEventTime?'<td class="fc-list-item-time '+n.widgetContentClass+'">'+(e||"")+"</td>":"")+'<td class="fc-list-item-marker '+n.widgetContentClass+'"><span class="fc-event-dot"'+(r?' style="background-color:'+r+'"':"")+'></span></td><td class="fc-list-item-title '+n.widgetContentClass+'"><a'+(o?' href="'+ht(o)+'"':"")+">"+ht(t.event.title||"")+"</a></td></tr>"}});return $t.list={class:We,buttonTextKey:"list",defaults:{buttonText:"list",listDayFormat:"LL",noEventsMessage:"No events to display"}},$t.listDay={type:"list",duration:{days:1},defaults:{listDayFormat:"dddd"}},$t.listWeek={type:"list",duration:{weeks:1},defaults:{listDayFormat:"dddd",listDayAltFormat:"LL"}},$t.listMonth={type:"list",duration:{month:1},defaults:{listDayAltFormat:"dddd"}},$t.listYear={type:"list",duration:{year:1},defaults:{listDayAltFormat:"dddd"}},Zt});
11 6 \ No newline at end of file
  7 +
  8 +(function(factory) {
  9 + if (typeof define === 'function' && define.amd) {
  10 + define([ 'jquery', 'moment' ], factory);
  11 + }
  12 + else if (typeof exports === 'object') { // Node/CommonJS
  13 + module.exports = factory(require('jquery'), require('moment'));
  14 + }
  15 + else {
  16 + factory(jQuery, moment);
  17 + }
  18 +})(function($, moment) {
  19 +
  20 +;;
  21 +
  22 +var fc = $.fullCalendar = { version: "2.3.1" };
  23 +var fcViews = fc.views = {};
  24 +
  25 +
  26 +$.fn.fullCalendar = function(options) {
  27 + var args = Array.prototype.slice.call(arguments, 1); // for a possible method call
  28 + var res = this; // what this function will return (this jQuery object by default)
  29 +
  30 + this.each(function(i, _element) { // loop each DOM element involved
  31 + var element = $(_element);
  32 + var calendar = element.data('fullCalendar'); // get the existing calendar object (if any)
  33 + var singleRes; // the returned value of this single method call
  34 +
  35 + // a method call
  36 + if (typeof options === 'string') {
  37 + if (calendar && $.isFunction(calendar[options])) {
  38 + singleRes = calendar[options].apply(calendar, args);
  39 + if (!i) {
  40 + res = singleRes; // record the first method call result
  41 + }
  42 + if (options === 'destroy') { // for the destroy method, must remove Calendar object data
  43 + element.removeData('fullCalendar');
  44 + }
  45 + }
  46 + }
  47 + // a new calendar initialization
  48 + else if (!calendar) { // don't initialize twice
  49 + calendar = new fc.CalendarBase(element, options);
  50 + element.data('fullCalendar', calendar);
  51 + calendar.render();
  52 + }
  53 + });
  54 +
  55 + return res;
  56 +};
  57 +
  58 +
  59 +var complexOptions = [ // names of options that are objects whose properties should be combined
  60 + 'header',
  61 + 'buttonText',
  62 + 'buttonIcons',
  63 + 'themeButtonIcons'
  64 +];
  65 +
  66 +
  67 +// Recursively combines all passed-in option-hash arguments into a new single option-hash.
  68 +// Given option-hashes are ordered from lowest to highest priority.
  69 +function mergeOptions() {
  70 + var chain = Array.prototype.slice.call(arguments); // convert to a real array
  71 + var complexVals = {}; // hash for each complex option's combined values
  72 + var i, name;
  73 + var combinedVal;
  74 + var j;
  75 + var val;
  76 +
  77 + // for each complex option, loop through each option-hash and accumulate the combined values
  78 + for (i = 0; i < complexOptions.length; i++) {
  79 + name = complexOptions[i];
  80 + combinedVal = null; // an object holding the merge of all the values
  81 +
  82 + for (j = 0; j < chain.length; j++) {
  83 + val = chain[j][name];
  84 +
  85 + if ($.isPlainObject(val)) {
  86 + combinedVal = $.extend(combinedVal || {}, val); // merge new properties
  87 + }
  88 + else if (val != null) { // a non-null non-undefined atomic option
  89 + combinedVal = null; // signal to use the atomic value
  90 + }
  91 + }
  92 +
  93 + // if not null, the final value was a combination of other objects. record it
  94 + if (combinedVal !== null) {
  95 + complexVals[name] = combinedVal;
  96 + }
  97 + }
  98 +
  99 + chain.unshift({}); // $.extend will mutate this with the result
  100 + chain.push(complexVals); // computed complex values are applied last
  101 + return $.extend.apply($, chain); // combine
  102 +}
  103 +
  104 +
  105 +// Given options specified for the calendar's constructor, massages any legacy options into a non-legacy form.
  106 +// Converts View-Option-Hashes into the View-Specific-Options format.
  107 +function massageOverrides(input) {
  108 + var overrides = { views: input.views || {} }; // the output. ensure a `views` hash
  109 + var subObj;
  110 +
  111 + // iterate through all option override properties (except `views`)
  112 + $.each(input, function(name, val) {
  113 + if (name != 'views') {
  114 +
  115 + // could the value be a legacy View-Option-Hash?
  116 + if (
  117 + $.isPlainObject(val) &&
  118 + !/(time|duration|interval)$/i.test(name) && // exclude duration options. might be given as objects
  119 + $.inArray(name, complexOptions) == -1 // complex options aren't allowed to be View-Option-Hashes
  120 + ) {
  121 + subObj = null;
  122 +
  123 + // iterate through the properties of this possible View-Option-Hash value
  124 + $.each(val, function(subName, subVal) {
  125 +
  126 + // is the property targeting a view?
  127 + if (/^(month|week|day|default|basic(Week|Day)?|agenda(Week|Day)?)$/.test(subName)) {
  128 + if (!overrides.views[subName]) { // ensure the view-target entry exists
  129 + overrides.views[subName] = {};
  130 + }
  131 + overrides.views[subName][name] = subVal; // record the value in the `views` object
  132 + }
  133 + else { // a non-View-Option-Hash property
  134 + if (!subObj) {
  135 + subObj = {};
  136 + }
  137 + subObj[subName] = subVal; // accumulate these unrelated values for later
  138 + }
  139 + });
  140 +
  141 + if (subObj) { // non-View-Option-Hash properties? transfer them as-is
  142 + overrides[name] = subObj;
  143 + }
  144 + }
  145 + else {
  146 + overrides[name] = val; // transfer normal options as-is
  147 + }
  148 + }
  149 + });
  150 +
  151 + return overrides;
  152 +}
  153 +
  154 +;;
  155 +
  156 +// exports
  157 +fc.intersectionToSeg = intersectionToSeg;
  158 +fc.applyAll = applyAll;
  159 +fc.debounce = debounce;
  160 +fc.isInt = isInt;
  161 +fc.htmlEscape = htmlEscape;
  162 +fc.cssToStr = cssToStr;
  163 +fc.proxy = proxy;
  164 +
  165 +
  166 +/* FullCalendar-specific DOM Utilities
  167 +----------------------------------------------------------------------------------------------------------------------*/
  168 +
  169 +
  170 +// Given the scrollbar widths of some other container, create borders/margins on rowEls in order to match the left
  171 +// and right space that was offset by the scrollbars. A 1-pixel border first, then margin beyond that.
  172 +function compensateScroll(rowEls, scrollbarWidths) {
  173 + if (scrollbarWidths.left) {
  174 + rowEls.css({
  175 + 'border-left-width': 1,
  176 + 'margin-left': scrollbarWidths.left - 1
  177 + });
  178 + }
  179 + if (scrollbarWidths.right) {
  180 + rowEls.css({
  181 + 'border-right-width': 1,
  182 + 'margin-right': scrollbarWidths.right - 1
  183 + });
  184 + }
  185 +}
  186 +
  187 +
  188 +// Undoes compensateScroll and restores all borders/margins
  189 +function uncompensateScroll(rowEls) {
  190 + rowEls.css({
  191 + 'margin-left': '',
  192 + 'margin-right': '',
  193 + 'border-left-width': '',
  194 + 'border-right-width': ''
  195 + });
  196 +}
  197 +
  198 +
  199 +// Make the mouse cursor express that an event is not allowed in the current area
  200 +function disableCursor() {
  201 + $('body').addClass('fc-not-allowed');
  202 +}
  203 +
  204 +
  205 +// Returns the mouse cursor to its original look
  206 +function enableCursor() {
  207 + $('body').removeClass('fc-not-allowed');
  208 +}
  209 +
  210 +
  211 +// Given a total available height to fill, have `els` (essentially child rows) expand to accomodate.
  212 +// By default, all elements that are shorter than the recommended height are expanded uniformly, not considering
  213 +// any other els that are already too tall. if `shouldRedistribute` is on, it considers these tall rows and
  214 +// reduces the available height.
  215 +function distributeHeight(els, availableHeight, shouldRedistribute) {
  216 +
  217 + // *FLOORING NOTE*: we floor in certain places because zoom can give inaccurate floating-point dimensions,
  218 + // and it is better to be shorter than taller, to avoid creating unnecessary scrollbars.
  219 +
  220 + var minOffset1 = Math.floor(availableHeight / els.length); // for non-last element
  221 + var minOffset2 = Math.floor(availableHeight - minOffset1 * (els.length - 1)); // for last element *FLOORING NOTE*
  222 + var flexEls = []; // elements that are allowed to expand. array of DOM nodes
  223 + var flexOffsets = []; // amount of vertical space it takes up
  224 + var flexHeights = []; // actual css height
  225 + var usedHeight = 0;
  226 +
  227 + undistributeHeight(els); // give all elements their natural height
  228 +
  229 + // find elements that are below the recommended height (expandable).
  230 + // important to query for heights in a single first pass (to avoid reflow oscillation).
  231 + els.each(function(i, el) {
  232 + var minOffset = i === els.length - 1 ? minOffset2 : minOffset1;
  233 + var naturalOffset = $(el).outerHeight(true);
  234 +
  235 + if (naturalOffset < minOffset) {
  236 + flexEls.push(el);
  237 + flexOffsets.push(naturalOffset);
  238 + flexHeights.push($(el).height());
  239 + }
  240 + else {
  241 + // this element stretches past recommended height (non-expandable). mark the space as occupied.
  242 + usedHeight += naturalOffset;
  243 + }
  244 + });
  245 +
  246 + // readjust the recommended height to only consider the height available to non-maxed-out rows.
  247 + if (shouldRedistribute) {
  248 + availableHeight -= usedHeight;
  249 + minOffset1 = Math.floor(availableHeight / flexEls.length);
  250 + minOffset2 = Math.floor(availableHeight - minOffset1 * (flexEls.length - 1)); // *FLOORING NOTE*
  251 + }
  252 +
  253 + // assign heights to all expandable elements
  254 + $(flexEls).each(function(i, el) {
  255 + var minOffset = i === flexEls.length - 1 ? minOffset2 : minOffset1;
  256 + var naturalOffset = flexOffsets[i];
  257 + var naturalHeight = flexHeights[i];
  258 + var newHeight = minOffset - (naturalOffset - naturalHeight); // subtract the margin/padding
  259 +
  260 + if (naturalOffset < minOffset) { // we check this again because redistribution might have changed things
  261 + $(el).height(newHeight);
  262 + }
  263 + });
  264 +}
  265 +
  266 +
  267 +// Undoes distrubuteHeight, restoring all els to their natural height
  268 +function undistributeHeight(els) {
  269 + els.height('');
  270 +}
  271 +
  272 +
  273 +// Given `els`, a jQuery set of <td> cells, find the cell with the largest natural width and set the widths of all the
  274 +// cells to be that width.
  275 +// PREREQUISITE: if you want a cell to take up width, it needs to have a single inner element w/ display:inline
  276 +function matchCellWidths(els) {
  277 + var maxInnerWidth = 0;
  278 +
  279 + els.find('> *').each(function(i, innerEl) {
  280 + var innerWidth = $(innerEl).outerWidth();
  281 + if (innerWidth > maxInnerWidth) {
  282 + maxInnerWidth = innerWidth;
  283 + }
  284 + });
  285 +
  286 + maxInnerWidth++; // sometimes not accurate of width the text needs to stay on one line. insurance
  287 +
  288 + els.width(maxInnerWidth);
  289 +
  290 + return maxInnerWidth;
  291 +}
  292 +
  293 +
  294 +// Turns a container element into a scroller if its contents is taller than the allotted height.
  295 +// Returns true if the element is now a scroller, false otherwise.
  296 +// NOTE: this method is best because it takes weird zooming dimensions into account
  297 +function setPotentialScroller(containerEl, height) {
  298 + containerEl.height(height).addClass('fc-scroller');
  299 +
  300 + // are scrollbars needed?
  301 + if (containerEl[0].scrollHeight - 1 > containerEl[0].clientHeight) { // !!! -1 because IE is often off-by-one :(
  302 + return true;
  303 + }
  304 +
  305 + unsetScroller(containerEl); // undo
  306 + return false;
  307 +}
  308 +
  309 +
  310 +// Takes an element that might have been a scroller, and turns it back into a normal element.
  311 +function unsetScroller(containerEl) {
  312 + containerEl.height('').removeClass('fc-scroller');
  313 +}
  314 +
  315 +
  316 +/* General DOM Utilities
  317 +----------------------------------------------------------------------------------------------------------------------*/
  318 +
  319 +fc.getClientRect = getClientRect;
  320 +fc.getContentRect = getContentRect;
  321 +fc.getScrollbarWidths = getScrollbarWidths;
  322 +
  323 +
  324 +// borrowed from https://github.com/jquery/jquery-ui/blob/1.11.0/ui/core.js#L51
  325 +function getScrollParent(el) {
  326 + var position = el.css('position'),
  327 + scrollParent = el.parents().filter(function() {
  328 + var parent = $(this);
  329 + return (/(auto|scroll)/).test(
  330 + parent.css('overflow') + parent.css('overflow-y') + parent.css('overflow-x')
  331 + );
  332 + }).eq(0);
  333 +
  334 + return position === 'fixed' || !scrollParent.length ? $(el[0].ownerDocument || document) : scrollParent;
  335 +}
  336 +
  337 +
  338 +// Queries the outer bounding area of a jQuery element.
  339 +// Returns a rectangle with absolute coordinates: left, right (exclusive), top, bottom (exclusive).
  340 +function getOuterRect(el) {
  341 + var offset = el.offset();
  342 +
  343 + return {
  344 + left: offset.left,
  345 + right: offset.left + el.outerWidth(),
  346 + top: offset.top,
  347 + bottom: offset.top + el.outerHeight()
  348 + };
  349 +}
  350 +
  351 +
  352 +// Queries the area within the margin/border/scrollbars of a jQuery element. Does not go within the padding.
  353 +// Returns a rectangle with absolute coordinates: left, right (exclusive), top, bottom (exclusive).
  354 +// NOTE: should use clientLeft/clientTop, but very unreliable cross-browser.
  355 +function getClientRect(el) {
  356 + var offset = el.offset();
  357 + var scrollbarWidths = getScrollbarWidths(el);
  358 + var left = offset.left + getCssFloat(el, 'border-left-width') + scrollbarWidths.left;
  359 + var top = offset.top + getCssFloat(el, 'border-top-width') + scrollbarWidths.top;
  360 +
  361 + return {
  362 + left: left,
  363 + right: left + el[0].clientWidth, // clientWidth includes padding but NOT scrollbars
  364 + top: top,
  365 + bottom: top + el[0].clientHeight // clientHeight includes padding but NOT scrollbars
  366 + };
  367 +}
  368 +
  369 +
  370 +// Queries the area within the margin/border/padding of a jQuery element. Assumed not to have scrollbars.
  371 +// Returns a rectangle with absolute coordinates: left, right (exclusive), top, bottom (exclusive).
  372 +function getContentRect(el) {
  373 + var offset = el.offset(); // just outside of border, margin not included
  374 + var left = offset.left + getCssFloat(el, 'border-left-width') + getCssFloat(el, 'padding-left');
  375 + var top = offset.top + getCssFloat(el, 'border-top-width') + getCssFloat(el, 'padding-top');
  376 +
  377 + return {
  378 + left: left,
  379 + right: left + el.width(),
  380 + top: top,
  381 + bottom: top + el.height()
  382 + };
  383 +}
  384 +
  385 +
  386 +// Returns the computed left/right/top/bottom scrollbar widths for the given jQuery element.
  387 +// NOTE: should use clientLeft/clientTop, but very unreliable cross-browser.
  388 +function getScrollbarWidths(el) {
  389 + var leftRightWidth = el.innerWidth() - el[0].clientWidth; // the paddings cancel out, leaving the scrollbars
  390 + var widths = {
  391 + left: 0,
  392 + right: 0,
  393 + top: 0,
  394 + bottom: el.innerHeight() - el[0].clientHeight // the paddings cancel out, leaving the bottom scrollbar
  395 + };
  396 +
  397 + if (getIsLeftRtlScrollbars() && el.css('direction') == 'rtl') { // is the scrollbar on the left side?
  398 + widths.left = leftRightWidth;
  399 + }
  400 + else {
  401 + widths.right = leftRightWidth;
  402 + }
  403 +
  404 + return widths;
  405 +}
  406 +
  407 +
  408 +// Logic for determining if, when the element is right-to-left, the scrollbar appears on the left side
  409 +
  410 +var _isLeftRtlScrollbars = null;
  411 +
  412 +function getIsLeftRtlScrollbars() { // responsible for caching the computation
  413 + if (_isLeftRtlScrollbars === null) {
  414 + _isLeftRtlScrollbars = computeIsLeftRtlScrollbars();
  415 + }
  416 + return _isLeftRtlScrollbars;
  417 +}
  418 +
  419 +function computeIsLeftRtlScrollbars() { // creates an offscreen test element, then removes it
  420 + var el = $('<div><div/></div>')
  421 + .css({
  422 + position: 'absolute',
  423 + top: -1000,
  424 + left: 0,
  425 + border: 0,
  426 + padding: 0,
  427 + overflow: 'scroll',
  428 + direction: 'rtl'
  429 + })
  430 + .appendTo('body');
  431 + var innerEl = el.children();
  432 + var res = innerEl.offset().left > el.offset().left; // is the inner div shifted to accommodate a left scrollbar?
  433 + el.remove();
  434 + return res;
  435 +}
  436 +
  437 +
  438 +// Retrieves a jQuery element's computed CSS value as a floating-point number.
  439 +// If the queried value is non-numeric (ex: IE can return "medium" for border width), will just return zero.
  440 +function getCssFloat(el, prop) {
  441 + return parseFloat(el.css(prop)) || 0;
  442 +}
  443 +
  444 +
  445 +// Returns a boolean whether this was a left mouse click and no ctrl key (which means right click on Mac)
  446 +function isPrimaryMouseButton(ev) {
  447 + return ev.which == 1 && !ev.ctrlKey;
  448 +}
  449 +
  450 +
  451 +/* Geometry
  452 +----------------------------------------------------------------------------------------------------------------------*/
  453 +
  454 +
  455 +// Returns a new rectangle that is the intersection of the two rectangles. If they don't intersect, returns false
  456 +function intersectRects(rect1, rect2) {
  457 + var res = {
  458 + left: Math.max(rect1.left, rect2.left),
  459 + right: Math.min(rect1.right, rect2.right),
  460 + top: Math.max(rect1.top, rect2.top),
  461 + bottom: Math.min(rect1.bottom, rect2.bottom)
  462 + };
  463 +
  464 + if (res.left < res.right && res.top < res.bottom) {
  465 + return res;
  466 + }
  467 + return false;
  468 +}
  469 +
  470 +
  471 +// Returns a new point that will have been moved to reside within the given rectangle
  472 +function constrainPoint(point, rect) {
  473 + return {
  474 + left: Math.min(Math.max(point.left, rect.left), rect.right),
  475 + top: Math.min(Math.max(point.top, rect.top), rect.bottom)
  476 + };
  477 +}
  478 +
  479 +
  480 +// Returns a point that is the center of the given rectangle
  481 +function getRectCenter(rect) {
  482 + return {
  483 + left: (rect.left + rect.right) / 2,
  484 + top: (rect.top + rect.bottom) / 2
  485 + };
  486 +}
  487 +
  488 +
  489 +// Subtracts point2's coordinates from point1's coordinates, returning a delta
  490 +function diffPoints(point1, point2) {
  491 + return {
  492 + left: point1.left - point2.left,
  493 + top: point1.top - point2.top
  494 + };
  495 +}
  496 +
  497 +
  498 +/* FullCalendar-specific Misc Utilities
  499 +----------------------------------------------------------------------------------------------------------------------*/
  500 +
  501 +
  502 +// Creates a basic segment with the intersection of the two ranges. Returns undefined if no intersection.
  503 +// Expects all dates to be normalized to the same timezone beforehand.
  504 +// TODO: move to date section?
  505 +function intersectionToSeg(subjectRange, constraintRange) {
  506 + var subjectStart = subjectRange.start;
  507 + var subjectEnd = subjectRange.end;
  508 + var constraintStart = constraintRange.start;
  509 + var constraintEnd = constraintRange.end;
  510 + var segStart, segEnd;
  511 + var isStart, isEnd;
  512 +
  513 + if (subjectEnd > constraintStart && subjectStart < constraintEnd) { // in bounds at all?
  514 +
  515 + if (subjectStart >= constraintStart) {
  516 + segStart = subjectStart.clone();
  517 + isStart = true;
  518 + }
  519 + else {
  520 + segStart = constraintStart.clone();
  521 + isStart = false;
  522 + }
  523 +
  524 + if (subjectEnd <= constraintEnd) {
  525 + segEnd = subjectEnd.clone();
  526 + isEnd = true;
  527 + }
  528 + else {
  529 + segEnd = constraintEnd.clone();
  530 + isEnd = false;
  531 + }
  532 +
  533 + return {
  534 + start: segStart,
  535 + end: segEnd,
  536 + isStart: isStart,
  537 + isEnd: isEnd
  538 + };
  539 + }
  540 +}
  541 +
  542 +
  543 +/* Date Utilities
  544 +----------------------------------------------------------------------------------------------------------------------*/
  545 +
  546 +fc.computeIntervalUnit = computeIntervalUnit;
  547 +fc.durationHasTime = durationHasTime;
  548 +
  549 +var dayIDs = [ 'sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat' ];
  550 +var intervalUnits = [ 'year', 'month', 'week', 'day', 'hour', 'minute', 'second', 'millisecond' ];
  551 +
  552 +
  553 +// Diffs the two moments into a Duration where full-days are recorded first, then the remaining time.
  554 +// Moments will have their timezones normalized.
  555 +function diffDayTime(a, b) {
  556 + return moment.duration({
  557 + days: a.clone().stripTime().diff(b.clone().stripTime(), 'days'),
  558 + ms: a.time() - b.time() // time-of-day from day start. disregards timezone
  559 + });
  560 +}
  561 +
  562 +
  563 +// Diffs the two moments via their start-of-day (regardless of timezone). Produces whole-day durations.
  564 +function diffDay(a, b) {
  565 + return moment.duration({
  566 + days: a.clone().stripTime().diff(b.clone().stripTime(), 'days')
  567 + });
  568 +}
  569 +
  570 +
  571 +// Diffs two moments, producing a duration, made of a whole-unit-increment of the given unit. Uses rounding.
  572 +function diffByUnit(a, b, unit) {
  573 + return moment.duration(
  574 + Math.round(a.diff(b, unit, true)), // returnFloat=true
  575 + unit
  576 + );
  577 +}
  578 +
  579 +
  580 +// Computes the unit name of the largest whole-unit period of time.
  581 +// For example, 48 hours will be "days" whereas 49 hours will be "hours".
  582 +// Accepts start/end, a range object, or an original duration object.
  583 +function computeIntervalUnit(start, end) {
  584 + var i, unit;
  585 + var val;
  586 +
  587 + for (i = 0; i < intervalUnits.length; i++) {
  588 + unit = intervalUnits[i];
  589 + val = computeRangeAs(unit, start, end);
  590 +
  591 + if (val >= 1 && isInt(val)) {
  592 + break;
  593 + }
  594 + }
  595 +
  596 + return unit; // will be "milliseconds" if nothing else matches
  597 +}
  598 +
  599 +
  600 +// Computes the number of units (like "hours") in the given range.
  601 +// Range can be a {start,end} object, separate start/end args, or a Duration.
  602 +// Results are based on Moment's .as() and .diff() methods, so results can depend on internal handling
  603 +// of month-diffing logic (which tends to vary from version to version).
  604 +function computeRangeAs(unit, start, end) {
  605 +
  606 + if (end != null) { // given start, end
  607 + return end.diff(start, unit, true);
  608 + }
  609 + else if (moment.isDuration(start)) { // given duration
  610 + return start.as(unit);
  611 + }
  612 + else { // given { start, end } range object
  613 + return start.end.diff(start.start, unit, true);
  614 + }
  615 +}
  616 +
  617 +
  618 +// Returns a boolean about whether the given duration has any time parts (hours/minutes/seconds/ms)
  619 +function durationHasTime(dur) {
  620 + return Boolean(dur.hours() || dur.minutes() || dur.seconds() || dur.milliseconds());
  621 +}
  622 +
  623 +
  624 +function isNativeDate(input) {
  625 + return Object.prototype.toString.call(input) === '[object Date]' || input instanceof Date;
  626 +}
  627 +
  628 +
  629 +// Returns a boolean about whether the given input is a time string, like "06:40:00" or "06:00"
  630 +function isTimeString(str) {
  631 + return /^\d+\:\d+(?:\:\d+\.?(?:\d{3})?)?$/.test(str);
  632 +}
  633 +
  634 +
  635 +/* General Utilities
  636 +----------------------------------------------------------------------------------------------------------------------*/
  637 +
  638 +var hasOwnPropMethod = {}.hasOwnProperty;
  639 +
  640 +
  641 +// Create an object that has the given prototype. Just like Object.create
  642 +function createObject(proto) {
  643 + var f = function() {};
  644 + f.prototype = proto;
  645 + return new f();
  646 +}
  647 +
  648 +
  649 +function copyOwnProps(src, dest) {
  650 + for (var name in src) {
  651 + if (hasOwnProp(src, name)) {
  652 + dest[name] = src[name];
  653 + }
  654 + }
  655 +}
  656 +
  657 +
  658 +// Copies over certain methods with the same names as Object.prototype methods. Overcomes an IE<=8 bug:
  659 +// https://developer.mozilla.org/en-US/docs/ECMAScript_DontEnum_attribute#JScript_DontEnum_Bug
  660 +function copyNativeMethods(src, dest) {
  661 + var names = [ 'constructor', 'toString', 'valueOf' ];
  662 + var i, name;
  663 +
  664 + for (i = 0; i < names.length; i++) {
  665 + name = names[i];
  666 +
  667 + if (src[name] !== Object.prototype[name]) {
  668 + dest[name] = src[name];
  669 + }
  670 + }
  671 +}
  672 +
  673 +
  674 +function hasOwnProp(obj, name) {
  675 + return hasOwnPropMethod.call(obj, name);
  676 +}
  677 +
  678 +
  679 +// Is the given value a non-object non-function value?
  680 +function isAtomic(val) {
  681 + return /undefined|null|boolean|number|string/.test($.type(val));
  682 +}
  683 +
  684 +
  685 +function applyAll(functions, thisObj, args) {
  686 + if ($.isFunction(functions)) {
  687 + functions = [ functions ];
  688 + }
  689 + if (functions) {
  690 + var i;
  691 + var ret;
  692 + for (i=0; i<functions.length; i++) {
  693 + ret = functions[i].apply(thisObj, args) || ret;
  694 + }
  695 + return ret;
  696 + }
  697 +}
  698 +
  699 +
  700 +function firstDefined() {
  701 + for (var i=0; i<arguments.length; i++) {
  702 + if (arguments[i] !== undefined) {
  703 + return arguments[i];
  704 + }
  705 + }
  706 +}
  707 +
  708 +
  709 +function htmlEscape(s) {
  710 + return (s + '').replace(/&/g, '&amp;')
  711 + .replace(/</g, '&lt;')
  712 + .replace(/>/g, '&gt;')
  713 + .replace(/'/g, '&#039;')
  714 + .replace(/"/g, '&quot;')
  715 + .replace(/\n/g, '<br />');
  716 +}
  717 +
  718 +
  719 +function stripHtmlEntities(text) {
  720 + return text.replace(/&.*?;/g, '');
  721 +}
  722 +
  723 +
  724 +// Given a hash of CSS properties, returns a string of CSS.
  725 +// Uses property names as-is (no camel-case conversion). Will not make statements for null/undefined values.
  726 +function cssToStr(cssProps) {
  727 + var statements = [];
  728 +
  729 + $.each(cssProps, function(name, val) {
  730 + if (val != null) {
  731 + statements.push(name + ':' + val);
  732 + }
  733 + });
  734 +
  735 + return statements.join(';');
  736 +}
  737 +
  738 +
  739 +function capitaliseFirstLetter(str) {
  740 + return str.charAt(0).toUpperCase() + str.slice(1);
  741 +}
  742 +
  743 +
  744 +function compareNumbers(a, b) { // for .sort()
  745 + return a - b;
  746 +}
  747 +
  748 +
  749 +function isInt(n) {
  750 + return n % 1 === 0;
  751 +}
  752 +
  753 +
  754 +// Returns a method bound to the given object context.
  755 +// Just like one of the jQuery.proxy signatures, but without the undesired behavior of treating the same method with
  756 +// different contexts as identical when binding/unbinding events.
  757 +function proxy(obj, methodName) {
  758 + var method = obj[methodName];
  759 +
  760 + return function() {
  761 + return method.apply(obj, arguments);
  762 + };
  763 +}
  764 +
  765 +
  766 +// Returns a function, that, as long as it continues to be invoked, will not
  767 +// be triggered. The function will be called after it stops being called for
  768 +// N milliseconds.
  769 +// https://github.com/jashkenas/underscore/blob/1.6.0/underscore.js#L714
  770 +function debounce(func, wait) {
  771 + var timeoutId;
  772 + var args;
  773 + var context;
  774 + var timestamp; // of most recent call
  775 + var later = function() {
  776 + var last = +new Date() - timestamp;
  777 + if (last < wait && last > 0) {
  778 + timeoutId = setTimeout(later, wait - last);
  779 + }
  780 + else {
  781 + timeoutId = null;
  782 + func.apply(context, args);
  783 + if (!timeoutId) {
  784 + context = args = null;
  785 + }
  786 + }
  787 + };
  788 +
  789 + return function() {
  790 + context = this;
  791 + args = arguments;
  792 + timestamp = +new Date();
  793 + if (!timeoutId) {
  794 + timeoutId = setTimeout(later, wait);
  795 + }
  796 + };
  797 +}
  798 +
  799 +;;
  800 +
  801 +var ambigDateOfMonthRegex = /^\s*\d{4}-\d\d$/;
  802 +var ambigTimeOrZoneRegex =
  803 + /^\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+)?)?)?)?)?$/;
  804 +var newMomentProto = moment.fn; // where we will attach our new methods
  805 +var oldMomentProto = $.extend({}, newMomentProto); // copy of original moment methods
  806 +var allowValueOptimization;
  807 +var setUTCValues; // function defined below
  808 +var setLocalValues; // function defined below
  809 +
  810 +
  811 +// Creating
  812 +// -------------------------------------------------------------------------------------------------
  813 +
  814 +// Creates a new moment, similar to the vanilla moment(...) constructor, but with
  815 +// extra features (ambiguous time, enhanced formatting). When given an existing moment,
  816 +// it will function as a clone (and retain the zone of the moment). Anything else will
  817 +// result in a moment in the local zone.
  818 +fc.moment = function() {
  819 + return makeMoment(arguments);
  820 +};
  821 +
  822 +// Sames as fc.moment, but forces the resulting moment to be in the UTC timezone.
  823 +fc.moment.utc = function() {
  824 + var mom = makeMoment(arguments, true);
  825 +
  826 + // Force it into UTC because makeMoment doesn't guarantee it
  827 + // (if given a pre-existing moment for example)
  828 + if (mom.hasTime()) { // don't give ambiguously-timed moments a UTC zone
  829 + mom.utc();
  830 + }
  831 +
  832 + return mom;
  833 +};
  834 +
  835 +// Same as fc.moment, but when given an ISO8601 string, the timezone offset is preserved.
  836 +// ISO8601 strings with no timezone offset will become ambiguously zoned.
  837 +fc.moment.parseZone = function() {
  838 + return makeMoment(arguments, true, true);
  839 +};
  840 +
  841 +// Builds an enhanced moment from args. When given an existing moment, it clones. When given a
  842 +// native Date, or called with no arguments (the current time), the resulting moment will be local.
  843 +// Anything else needs to be "parsed" (a string or an array), and will be affected by:
  844 +// parseAsUTC - if there is no zone information, should we parse the input in UTC?
  845 +// parseZone - if there is zone information, should we force the zone of the moment?
  846 +function makeMoment(args, parseAsUTC, parseZone) {
  847 + var input = args[0];
  848 + var isSingleString = args.length == 1 && typeof input === 'string';
  849 + var isAmbigTime;
  850 + var isAmbigZone;
  851 + var ambigMatch;
  852 + var mom;
  853 +
  854 + if (moment.isMoment(input)) {
  855 + mom = moment.apply(null, args); // clone it
  856 + transferAmbigs(input, mom); // the ambig flags weren't transfered with the clone
  857 + }
  858 + else if (isNativeDate(input) || input === undefined) {
  859 + mom = moment.apply(null, args); // will be local
  860 + }
  861 + else { // "parsing" is required
  862 + isAmbigTime = false;
  863 + isAmbigZone = false;
  864 +
  865 + if (isSingleString) {
  866 + if (ambigDateOfMonthRegex.test(input)) {
  867 + // accept strings like '2014-05', but convert to the first of the month
  868 + input += '-01';
  869 + args = [ input ]; // for when we pass it on to moment's constructor
  870 + isAmbigTime = true;
  871 + isAmbigZone = true;
  872 + }
  873 + else if ((ambigMatch = ambigTimeOrZoneRegex.exec(input))) {
  874 + isAmbigTime = !ambigMatch[5]; // no time part?
  875 + isAmbigZone = true;
  876 + }
  877 + }
  878 + else if ($.isArray(input)) {
  879 + // arrays have no timezone information, so assume ambiguous zone
  880 + isAmbigZone = true;
  881 + }
  882 + // otherwise, probably a string with a format
  883 +
  884 + if (parseAsUTC || isAmbigTime) {
  885 + mom = moment.utc.apply(moment, args);
  886 + }
  887 + else {
  888 + mom = moment.apply(null, args);
  889 + }
  890 +
  891 + if (isAmbigTime) {
  892 + mom._ambigTime = true;
  893 + mom._ambigZone = true; // ambiguous time always means ambiguous zone
  894 + }
  895 + else if (parseZone) { // let's record the inputted zone somehow
  896 + if (isAmbigZone) {
  897 + mom._ambigZone = true;
  898 + }
  899 + else if (isSingleString) {
  900 + if (mom.utcOffset) {
  901 + mom.utcOffset(input); // if not a valid zone, will assign UTC
  902 + }
  903 + else {
  904 + mom.zone(input); // for moment-pre-2.9
  905 + }
  906 + }
  907 + }
  908 + }
  909 +
  910 + mom._fullCalendar = true; // flag for extended functionality
  911 +
  912 + return mom;
  913 +}
  914 +
  915 +
  916 +// A clone method that works with the flags related to our enhanced functionality.
  917 +// In the future, use moment.momentProperties
  918 +newMomentProto.clone = function() {
  919 + var mom = oldMomentProto.clone.apply(this, arguments);
  920 +
  921 + // these flags weren't transfered with the clone
  922 + transferAmbigs(this, mom);
  923 + if (this._fullCalendar) {
  924 + mom._fullCalendar = true;
  925 + }
  926 +
  927 + return mom;
  928 +};
  929 +
  930 +
  931 +// Week Number
  932 +// -------------------------------------------------------------------------------------------------
  933 +
  934 +
  935 +// Returns the week number, considering the locale's custom week number calcuation
  936 +// `weeks` is an alias for `week`
  937 +newMomentProto.week = newMomentProto.weeks = function(input) {
  938 + var weekCalc = (this._locale || this._lang) // works pre-moment-2.8
  939 + ._fullCalendar_weekCalc;
  940 +
  941 + if (input == null && typeof weekCalc === 'function') { // custom function only works for getter
  942 + return weekCalc(this);
  943 + }
  944 + else if (weekCalc === 'ISO') {
  945 + return oldMomentProto.isoWeek.apply(this, arguments); // ISO getter/setter
  946 + }
  947 +
  948 + return oldMomentProto.week.apply(this, arguments); // local getter/setter
  949 +};
  950 +
  951 +
  952 +// Time-of-day
  953 +// -------------------------------------------------------------------------------------------------
  954 +
  955 +// GETTER
  956 +// Returns a Duration with the hours/minutes/seconds/ms values of the moment.
  957 +// If the moment has an ambiguous time, a duration of 00:00 will be returned.
  958 +//
  959 +// SETTER
  960 +// You can supply a Duration, a Moment, or a Duration-like argument.
  961 +// When setting the time, and the moment has an ambiguous time, it then becomes unambiguous.
  962 +newMomentProto.time = function(time) {
  963 +
  964 + // Fallback to the original method (if there is one) if this moment wasn't created via FullCalendar.
  965 + // `time` is a generic enough method name where this precaution is necessary to avoid collisions w/ other plugins.
  966 + if (!this._fullCalendar) {
  967 + return oldMomentProto.time.apply(this, arguments);
  968 + }
  969 +
  970 + if (time == null) { // getter
  971 + return moment.duration({
  972 + hours: this.hours(),
  973 + minutes: this.minutes(),
  974 + seconds: this.seconds(),
  975 + milliseconds: this.milliseconds()
  976 + });
  977 + }
  978 + else { // setter
  979 +
  980 + this._ambigTime = false; // mark that the moment now has a time
  981 +
  982 + if (!moment.isDuration(time) && !moment.isMoment(time)) {
  983 + time = moment.duration(time);
  984 + }
  985 +
  986 + // The day value should cause overflow (so 24 hours becomes 00:00:00 of next day).
  987 + // Only for Duration times, not Moment times.
  988 + var dayHours = 0;
  989 + if (moment.isDuration(time)) {
  990 + dayHours = Math.floor(time.asDays()) * 24;
  991 + }
  992 +
  993 + // We need to set the individual fields.
  994 + // Can't use startOf('day') then add duration. In case of DST at start of day.
  995 + return this.hours(dayHours + time.hours())
  996 + .minutes(time.minutes())
  997 + .seconds(time.seconds())
  998 + .milliseconds(time.milliseconds());
  999 + }
  1000 +};
  1001 +
  1002 +// Converts the moment to UTC, stripping out its time-of-day and timezone offset,
  1003 +// but preserving its YMD. A moment with a stripped time will display no time
  1004 +// nor timezone offset when .format() is called.
  1005 +newMomentProto.stripTime = function() {
  1006 + var a;
  1007 +
  1008 + if (!this._ambigTime) {
  1009 +
  1010 + // get the values before any conversion happens
  1011 + a = this.toArray(); // array of y/m/d/h/m/s/ms
  1012 +
  1013 + // TODO: use keepLocalTime in the future
  1014 + this.utc(); // set the internal UTC flag (will clear the ambig flags)
  1015 + setUTCValues(this, a.slice(0, 3)); // set the year/month/date. time will be zero
  1016 +
  1017 + // Mark the time as ambiguous. This needs to happen after the .utc() call, which might call .utcOffset(),
  1018 + // which clears all ambig flags. Same with setUTCValues with moment-timezone.
  1019 + this._ambigTime = true;
  1020 + this._ambigZone = true; // if ambiguous time, also ambiguous timezone offset
  1021 + }
  1022 +
  1023 + return this; // for chaining
  1024 +};
  1025 +
  1026 +// Returns if the moment has a non-ambiguous time (boolean)
  1027 +newMomentProto.hasTime = function() {
  1028 + return !this._ambigTime;
  1029 +};
  1030 +
  1031 +
  1032 +// Timezone
  1033 +// -------------------------------------------------------------------------------------------------
  1034 +
  1035 +// Converts the moment to UTC, stripping out its timezone offset, but preserving its
  1036 +// YMD and time-of-day. A moment with a stripped timezone offset will display no
  1037 +// timezone offset when .format() is called.
  1038 +// TODO: look into Moment's keepLocalTime functionality
  1039 +newMomentProto.stripZone = function() {
  1040 + var a, wasAmbigTime;
  1041 +
  1042 + if (!this._ambigZone) {
  1043 +
  1044 + // get the values before any conversion happens
  1045 + a = this.toArray(); // array of y/m/d/h/m/s/ms
  1046 + wasAmbigTime = this._ambigTime;
  1047 +
  1048 + this.utc(); // set the internal UTC flag (might clear the ambig flags, depending on Moment internals)
  1049 + setUTCValues(this, a); // will set the year/month/date/hours/minutes/seconds/ms
  1050 +
  1051 + // the above call to .utc()/.utcOffset() unfortunately might clear the ambig flags, so restore
  1052 + this._ambigTime = wasAmbigTime || false;
  1053 +
  1054 + // Mark the zone as ambiguous. This needs to happen after the .utc() call, which might call .utcOffset(),
  1055 + // which clears the ambig flags. Same with setUTCValues with moment-timezone.
  1056 + this._ambigZone = true;
  1057 + }
  1058 +
  1059 + return this; // for chaining
  1060 +};
  1061 +
  1062 +// Returns of the moment has a non-ambiguous timezone offset (boolean)
  1063 +newMomentProto.hasZone = function() {
  1064 + return !this._ambigZone;
  1065 +};
  1066 +
  1067 +
  1068 +// this method implicitly marks a zone
  1069 +newMomentProto.local = function() {
  1070 + var a = this.toArray(); // year,month,date,hours,minutes,seconds,ms as an array
  1071 + var wasAmbigZone = this._ambigZone;
  1072 +
  1073 + oldMomentProto.local.apply(this, arguments);
  1074 +
  1075 + // ensure non-ambiguous
  1076 + // this probably already happened via local() -> utcOffset(), but don't rely on Moment's internals
  1077 + this._ambigTime = false;
  1078 + this._ambigZone = false;
  1079 +
  1080 + if (wasAmbigZone) {
  1081 + // If the moment was ambiguously zoned, the date fields were stored as UTC.
  1082 + // We want to preserve these, but in local time.
  1083 + // TODO: look into Moment's keepLocalTime functionality
  1084 + setLocalValues(this, a);
  1085 + }
  1086 +
  1087 + return this; // for chaining
  1088 +};
  1089 +
  1090 +
  1091 +// implicitly marks a zone
  1092 +newMomentProto.utc = function() {
  1093 + oldMomentProto.utc.apply(this, arguments);
  1094 +
  1095 + // ensure non-ambiguous
  1096 + // this probably already happened via utc() -> utcOffset(), but don't rely on Moment's internals
  1097 + this._ambigTime = false;
  1098 + this._ambigZone = false;
  1099 +
  1100 + return this;
  1101 +};
  1102 +
  1103 +
  1104 +// methods for arbitrarily manipulating timezone offset.
  1105 +// should clear time/zone ambiguity when called.
  1106 +$.each([
  1107 + 'zone', // only in moment-pre-2.9. deprecated afterwards
  1108 + 'utcOffset'
  1109 +], function(i, name) {
  1110 + if (oldMomentProto[name]) { // original method exists?
  1111 +
  1112 + // this method implicitly marks a zone (will probably get called upon .utc() and .local())
  1113 + newMomentProto[name] = function(tzo) {
  1114 +
  1115 + if (tzo != null) { // setter
  1116 + // these assignments needs to happen before the original zone method is called.
  1117 + // I forget why, something to do with a browser crash.
  1118 + this._ambigTime = false;
  1119 + this._ambigZone = false;
  1120 + }
  1121 +
  1122 + return oldMomentProto[name].apply(this, arguments);
  1123 + };
  1124 + }
  1125 +});
  1126 +
  1127 +
  1128 +// Formatting
  1129 +// -------------------------------------------------------------------------------------------------
  1130 +
  1131 +newMomentProto.format = function() {
  1132 + if (this._fullCalendar && arguments[0]) { // an enhanced moment? and a format string provided?
  1133 + return formatDate(this, arguments[0]); // our extended formatting
  1134 + }
  1135 + if (this._ambigTime) {
  1136 + return oldMomentFormat(this, 'YYYY-MM-DD');
  1137 + }
  1138 + if (this._ambigZone) {
  1139 + return oldMomentFormat(this, 'YYYY-MM-DD[T]HH:mm:ss');
  1140 + }
  1141 + return oldMomentProto.format.apply(this, arguments);
  1142 +};
  1143 +
  1144 +newMomentProto.toISOString = function() {
  1145 + if (this._ambigTime) {
  1146 + return oldMomentFormat(this, 'YYYY-MM-DD');
  1147 + }
  1148 + if (this._ambigZone) {
  1149 + return oldMomentFormat(this, 'YYYY-MM-DD[T]HH:mm:ss');
  1150 + }
  1151 + return oldMomentProto.toISOString.apply(this, arguments);
  1152 +};
  1153 +
  1154 +
  1155 +// Querying
  1156 +// -------------------------------------------------------------------------------------------------
  1157 +
  1158 +// Is the moment within the specified range? `end` is exclusive.
  1159 +// FYI, this method is not a standard Moment method, so always do our enhanced logic.
  1160 +newMomentProto.isWithin = function(start, end) {
  1161 + var a = commonlyAmbiguate([ this, start, end ]);
  1162 + return a[0] >= a[1] && a[0] < a[2];
  1163 +};
  1164 +
  1165 +// When isSame is called with units, timezone ambiguity is normalized before the comparison happens.
  1166 +// If no units specified, the two moments must be identically the same, with matching ambig flags.
  1167 +newMomentProto.isSame = function(input, units) {
  1168 + var a;
  1169 +
  1170 + // only do custom logic if this is an enhanced moment
  1171 + if (!this._fullCalendar) {
  1172 + return oldMomentProto.isSame.apply(this, arguments);
  1173 + }
  1174 +
  1175 + if (units) {
  1176 + a = commonlyAmbiguate([ this, input ], true); // normalize timezones but don't erase times
  1177 + return oldMomentProto.isSame.call(a[0], a[1], units);
  1178 + }
  1179 + else {
  1180 + input = fc.moment.parseZone(input); // normalize input
  1181 + return oldMomentProto.isSame.call(this, input) &&
  1182 + Boolean(this._ambigTime) === Boolean(input._ambigTime) &&
  1183 + Boolean(this._ambigZone) === Boolean(input._ambigZone);
  1184 + }
  1185 +};
  1186 +
  1187 +// Make these query methods work with ambiguous moments
  1188 +$.each([
  1189 + 'isBefore',
  1190 + 'isAfter'
  1191 +], function(i, methodName) {
  1192 + newMomentProto[methodName] = function(input, units) {
  1193 + var a;
  1194 +
  1195 + // only do custom logic if this is an enhanced moment
  1196 + if (!this._fullCalendar) {
  1197 + return oldMomentProto[methodName].apply(this, arguments);
  1198 + }
  1199 +
  1200 + a = commonlyAmbiguate([ this, input ]);
  1201 + return oldMomentProto[methodName].call(a[0], a[1], units);
  1202 + };
  1203 +});
  1204 +
  1205 +
  1206 +// Misc Internals
  1207 +// -------------------------------------------------------------------------------------------------
  1208 +
  1209 +// given an array of moment-like inputs, return a parallel array w/ moments similarly ambiguated.
  1210 +// for example, of one moment has ambig time, but not others, all moments will have their time stripped.
  1211 +// set `preserveTime` to `true` to keep times, but only normalize zone ambiguity.
  1212 +// returns the original moments if no modifications are necessary.
  1213 +function commonlyAmbiguate(inputs, preserveTime) {
  1214 + var anyAmbigTime = false;
  1215 + var anyAmbigZone = false;
  1216 + var len = inputs.length;
  1217 + var moms = [];
  1218 + var i, mom;
  1219 +
  1220 + // parse inputs into real moments and query their ambig flags
  1221 + for (i = 0; i < len; i++) {
  1222 + mom = inputs[i];
  1223 + if (!moment.isMoment(mom)) {
  1224 + mom = fc.moment.parseZone(mom);
  1225 + }
  1226 + anyAmbigTime = anyAmbigTime || mom._ambigTime;
  1227 + anyAmbigZone = anyAmbigZone || mom._ambigZone;
  1228 + moms.push(mom);
  1229 + }
  1230 +
  1231 + // strip each moment down to lowest common ambiguity
  1232 + // use clones to avoid modifying the original moments
  1233 + for (i = 0; i < len; i++) {
  1234 + mom = moms[i];
  1235 + if (!preserveTime && anyAmbigTime && !mom._ambigTime) {
  1236 + moms[i] = mom.clone().stripTime();
  1237 + }
  1238 + else if (anyAmbigZone && !mom._ambigZone) {
  1239 + moms[i] = mom.clone().stripZone();
  1240 + }
  1241 + }
  1242 +
  1243 + return moms;
  1244 +}
  1245 +
  1246 +// Transfers all the flags related to ambiguous time/zone from the `src` moment to the `dest` moment
  1247 +// TODO: look into moment.momentProperties for this.
  1248 +function transferAmbigs(src, dest) {
  1249 + if (src._ambigTime) {
  1250 + dest._ambigTime = true;
  1251 + }
  1252 + else if (dest._ambigTime) {
  1253 + dest._ambigTime = false;
  1254 + }
  1255 +
  1256 + if (src._ambigZone) {
  1257 + dest._ambigZone = true;
  1258 + }
  1259 + else if (dest._ambigZone) {
  1260 + dest._ambigZone = false;
  1261 + }
  1262 +}
  1263 +
  1264 +
  1265 +// Sets the year/month/date/etc values of the moment from the given array.
  1266 +// Inefficient because it calls each individual setter.
  1267 +function setMomentValues(mom, a) {
  1268 + mom.year(a[0] || 0)
  1269 + .month(a[1] || 0)
  1270 + .date(a[2] || 0)
  1271 + .hours(a[3] || 0)
  1272 + .minutes(a[4] || 0)
  1273 + .seconds(a[5] || 0)
  1274 + .milliseconds(a[6] || 0);
  1275 +}
  1276 +
  1277 +// Can we set the moment's internal date directly?
  1278 +allowValueOptimization = '_d' in moment() && 'updateOffset' in moment;
  1279 +
  1280 +// Utility function. Accepts a moment and an array of the UTC year/month/date/etc values to set.
  1281 +// Assumes the given moment is already in UTC mode.
  1282 +setUTCValues = allowValueOptimization ? function(mom, a) {
  1283 + // simlate what moment's accessors do
  1284 + mom._d.setTime(Date.UTC.apply(Date, a));
  1285 + moment.updateOffset(mom, false); // keepTime=false
  1286 +} : setMomentValues;
  1287 +
  1288 +// Utility function. Accepts a moment and an array of the local year/month/date/etc values to set.
  1289 +// Assumes the given moment is already in local mode.
  1290 +setLocalValues = allowValueOptimization ? function(mom, a) {
  1291 + // simlate what moment's accessors do
  1292 + mom._d.setTime(+new Date( // FYI, there is now way to apply an array of args to a constructor
  1293 + a[0] || 0,
  1294 + a[1] || 0,
  1295 + a[2] || 0,
  1296 + a[3] || 0,
  1297 + a[4] || 0,
  1298 + a[5] || 0,
  1299 + a[6] || 0
  1300 + ));
  1301 + moment.updateOffset(mom, false); // keepTime=false
  1302 +} : setMomentValues;
  1303 +
  1304 +;;
  1305 +
  1306 +// Single Date Formatting
  1307 +// -------------------------------------------------------------------------------------------------
  1308 +
  1309 +
  1310 +// call this if you want Moment's original format method to be used
  1311 +function oldMomentFormat(mom, formatStr) {
  1312 + return oldMomentProto.format.call(mom, formatStr); // oldMomentProto defined in moment-ext.js
  1313 +}
  1314 +
  1315 +
  1316 +// Formats `date` with a Moment formatting string, but allow our non-zero areas and
  1317 +// additional token.
  1318 +function formatDate(date, formatStr) {
  1319 + return formatDateWithChunks(date, getFormatStringChunks(formatStr));
  1320 +}
  1321 +
  1322 +
  1323 +function formatDateWithChunks(date, chunks) {
  1324 + var s = '';
  1325 + var i;
  1326 +
  1327 + for (i=0; i<chunks.length; i++) {
  1328 + s += formatDateWithChunk(date, chunks[i]);
  1329 + }
  1330 +
  1331 + return s;
  1332 +}
  1333 +
  1334 +
  1335 +// addition formatting tokens we want recognized
  1336 +var tokenOverrides = {
  1337 + t: function(date) { // "a" or "p"
  1338 + return oldMomentFormat(date, 'a').charAt(0);
  1339 + },
  1340 + T: function(date) { // "A" or "P"
  1341 + return oldMomentFormat(date, 'A').charAt(0);
  1342 + }
  1343 +};
  1344 +
  1345 +
  1346 +function formatDateWithChunk(date, chunk) {
  1347 + var token;
  1348 + var maybeStr;
  1349 +
  1350 + if (typeof chunk === 'string') { // a literal string
  1351 + return chunk;
  1352 + }
  1353 + else if ((token = chunk.token)) { // a token, like "YYYY"
  1354 + if (tokenOverrides[token]) {
  1355 + return tokenOverrides[token](date); // use our custom token
  1356 + }
  1357 + return oldMomentFormat(date, token);
  1358 + }
  1359 + else if (chunk.maybe) { // a grouping of other chunks that must be non-zero
  1360 + maybeStr = formatDateWithChunks(date, chunk.maybe);
  1361 + if (maybeStr.match(/[1-9]/)) {
  1362 + return maybeStr;
  1363 + }
  1364 + }
  1365 +
  1366 + return '';
  1367 +}
  1368 +
  1369 +
  1370 +// Date Range Formatting
  1371 +// -------------------------------------------------------------------------------------------------
  1372 +// TODO: make it work with timezone offset
  1373 +
  1374 +// Using a formatting string meant for a single date, generate a range string, like
  1375 +// "Sep 2 - 9 2013", that intelligently inserts a separator where the dates differ.
  1376 +// If the dates are the same as far as the format string is concerned, just return a single
  1377 +// rendering of one date, without any separator.
  1378 +function formatRange(date1, date2, formatStr, separator, isRTL) {
  1379 + var localeData;
  1380 +
  1381 + date1 = fc.moment.parseZone(date1);
  1382 + date2 = fc.moment.parseZone(date2);
  1383 +
  1384 + localeData = (date1.localeData || date1.lang).call(date1); // works with moment-pre-2.8
  1385 +
  1386 + // Expand localized format strings, like "LL" -> "MMMM D YYYY"
  1387 + formatStr = localeData.longDateFormat(formatStr) || formatStr;
  1388 + // BTW, this is not important for `formatDate` because it is impossible to put custom tokens
  1389 + // or non-zero areas in Moment's localized format strings.
  1390 +
  1391 + separator = separator || ' - ';
  1392 +
  1393 + return formatRangeWithChunks(
  1394 + date1,
  1395 + date2,
  1396 + getFormatStringChunks(formatStr),
  1397 + separator,
  1398 + isRTL
  1399 + );
  1400 +}
  1401 +fc.formatRange = formatRange; // expose
  1402 +
  1403 +
  1404 +function formatRangeWithChunks(date1, date2, chunks, separator, isRTL) {
  1405 + var chunkStr; // the rendering of the chunk
  1406 + var leftI;
  1407 + var leftStr = '';
  1408 + var rightI;
  1409 + var rightStr = '';
  1410 + var middleI;
  1411 + var middleStr1 = '';
  1412 + var middleStr2 = '';
  1413 + var middleStr = '';
  1414 +
  1415 + // Start at the leftmost side of the formatting string and continue until you hit a token
  1416 + // that is not the same between dates.
  1417 + for (leftI=0; leftI<chunks.length; leftI++) {
  1418 + chunkStr = formatSimilarChunk(date1, date2, chunks[leftI]);
  1419 + if (chunkStr === false) {
  1420 + break;
  1421 + }
  1422 + leftStr += chunkStr;
  1423 + }
  1424 +
  1425 + // Similarly, start at the rightmost side of the formatting string and move left
  1426 + for (rightI=chunks.length-1; rightI>leftI; rightI--) {
  1427 + chunkStr = formatSimilarChunk(date1, date2, chunks[rightI]);
  1428 + if (chunkStr === false) {
  1429 + break;
  1430 + }
  1431 + rightStr = chunkStr + rightStr;
  1432 + }
  1433 +
  1434 + // The area in the middle is different for both of the dates.
  1435 + // Collect them distinctly so we can jam them together later.
  1436 + for (middleI=leftI; middleI<=rightI; middleI++) {
  1437 + middleStr1 += formatDateWithChunk(date1, chunks[middleI]);
  1438 + middleStr2 += formatDateWithChunk(date2, chunks[middleI]);
  1439 + }
  1440 +
  1441 + if (middleStr1 || middleStr2) {
  1442 + if (isRTL) {
  1443 + middleStr = middleStr2 + separator + middleStr1;
  1444 + }
  1445 + else {
  1446 + middleStr = middleStr1 + separator + middleStr2;
  1447 + }
  1448 + }
  1449 +
  1450 + return leftStr + middleStr + rightStr;
  1451 +}
  1452 +
  1453 +
  1454 +var similarUnitMap = {
  1455 + Y: 'year',
  1456 + M: 'month',
  1457 + D: 'day', // day of month
  1458 + d: 'day', // day of week
  1459 + // prevents a separator between anything time-related...
  1460 + A: 'second', // AM/PM
  1461 + a: 'second', // am/pm
  1462 + T: 'second', // A/P
  1463 + t: 'second', // a/p
  1464 + H: 'second', // hour (24)
  1465 + h: 'second', // hour (12)
  1466 + m: 'second', // minute
  1467 + s: 'second' // second
  1468 +};
  1469 +// TODO: week maybe?
  1470 +
  1471 +
  1472 +// Given a formatting chunk, and given that both dates are similar in the regard the
  1473 +// formatting chunk is concerned, format date1 against `chunk`. Otherwise, return `false`.
  1474 +function formatSimilarChunk(date1, date2, chunk) {
  1475 + var token;
  1476 + var unit;
  1477 +
  1478 + if (typeof chunk === 'string') { // a literal string
  1479 + return chunk;
  1480 + }
  1481 + else if ((token = chunk.token)) {
  1482 + unit = similarUnitMap[token.charAt(0)];
  1483 + // are the dates the same for this unit of measurement?
  1484 + if (unit && date1.isSame(date2, unit)) {
  1485 + return oldMomentFormat(date1, token); // would be the same if we used `date2`
  1486 + // BTW, don't support custom tokens
  1487 + }
  1488 + }
  1489 +
  1490 + return false; // the chunk is NOT the same for the two dates
  1491 + // BTW, don't support splitting on non-zero areas
  1492 +}
  1493 +
  1494 +
  1495 +// Chunking Utils
  1496 +// -------------------------------------------------------------------------------------------------
  1497 +
  1498 +
  1499 +var formatStringChunkCache = {};
  1500 +
  1501 +
  1502 +function getFormatStringChunks(formatStr) {
  1503 + if (formatStr in formatStringChunkCache) {
  1504 + return formatStringChunkCache[formatStr];
  1505 + }
  1506 + return (formatStringChunkCache[formatStr] = chunkFormatString(formatStr));
  1507 +}
  1508 +
  1509 +
  1510 +// Break the formatting string into an array of chunks
  1511 +function chunkFormatString(formatStr) {
  1512 + var chunks = [];
  1513 + var chunker = /\[([^\]]*)\]|\(([^\)]*)\)|(LTS|LT|(\w)\4*o?)|([^\w\[\(]+)/g; // TODO: more descrimination
  1514 + var match;
  1515 +
  1516 + while ((match = chunker.exec(formatStr))) {
  1517 + if (match[1]) { // a literal string inside [ ... ]
  1518 + chunks.push(match[1]);
  1519 + }
  1520 + else if (match[2]) { // non-zero formatting inside ( ... )
  1521 + chunks.push({ maybe: chunkFormatString(match[2]) });
  1522 + }
  1523 + else if (match[3]) { // a formatting token
  1524 + chunks.push({ token: match[3] });
  1525 + }
  1526 + else if (match[5]) { // an unenclosed literal string
  1527 + chunks.push(match[5]);
  1528 + }
  1529 + }
  1530 +
  1531 + return chunks;
  1532 +}
  1533 +
  1534 +;;
  1535 +
  1536 +fc.Class = Class; // export
  1537 +
  1538 +// class that all other classes will inherit from
  1539 +function Class() { }
  1540 +
  1541 +// called upon a class to create a subclass
  1542 +Class.extend = function(members) {
  1543 + var superClass = this;
  1544 + var subClass;
  1545 +
  1546 + members = members || {};
  1547 +
  1548 + // ensure a constructor for the subclass, forwarding all arguments to the super-constructor if it doesn't exist
  1549 + if (hasOwnProp(members, 'constructor')) {
  1550 + subClass = members.constructor;
  1551 + }
  1552 + if (typeof subClass !== 'function') {
  1553 + subClass = members.constructor = function() {
  1554 + superClass.apply(this, arguments);
  1555 + };
  1556 + }
  1557 +
  1558 + // build the base prototype for the subclass, which is an new object chained to the superclass's prototype
  1559 + subClass.prototype = createObject(superClass.prototype);
  1560 +
  1561 + // copy each member variable/method onto the the subclass's prototype
  1562 + copyOwnProps(members, subClass.prototype);
  1563 + copyNativeMethods(members, subClass.prototype); // hack for IE8
  1564 +
  1565 + // copy over all class variables/methods to the subclass, such as `extend` and `mixin`
  1566 + copyOwnProps(superClass, subClass);
  1567 +
  1568 + return subClass;
  1569 +};
  1570 +
  1571 +// adds new member variables/methods to the class's prototype.
  1572 +// can be called with another class, or a plain object hash containing new members.
  1573 +Class.mixin = function(members) {
  1574 + copyOwnProps(members.prototype || members, this.prototype);
  1575 +};
  1576 +;;
  1577 +
  1578 +/* A rectangular panel that is absolutely positioned over other content
  1579 +------------------------------------------------------------------------------------------------------------------------
  1580 +Options:
  1581 + - className (string)
  1582 + - content (HTML string or jQuery element set)
  1583 + - parentEl
  1584 + - top
  1585 + - left
  1586 + - right (the x coord of where the right edge should be. not a "CSS" right)
  1587 + - autoHide (boolean)
  1588 + - show (callback)
  1589 + - hide (callback)
  1590 +*/
  1591 +
  1592 +var Popover = Class.extend({
  1593 +
  1594 + isHidden: true,
  1595 + options: null,
  1596 + el: null, // the container element for the popover. generated by this object
  1597 + documentMousedownProxy: null, // document mousedown handler bound to `this`
  1598 + margin: 10, // the space required between the popover and the edges of the scroll container
  1599 +
  1600 +
  1601 + constructor: function(options) {
  1602 + this.options = options || {};
  1603 + },
  1604 +
  1605 +
  1606 + // Shows the popover on the specified position. Renders it if not already
  1607 + show: function() {
  1608 + if (this.isHidden) {
  1609 + if (!this.el) {
  1610 + this.render();
  1611 + }
  1612 + this.el.show();
  1613 + this.position();
  1614 + this.isHidden = false;
  1615 + this.trigger('show');
  1616 + }
  1617 + },
  1618 +
  1619 +
  1620 + // Hides the popover, through CSS, but does not remove it from the DOM
  1621 + hide: function() {
  1622 + if (!this.isHidden) {
  1623 + this.el.hide();
  1624 + this.isHidden = true;
  1625 + this.trigger('hide');
  1626 + }
  1627 + },
  1628 +
  1629 +
  1630 + // Creates `this.el` and renders content inside of it
  1631 + render: function() {
  1632 + var _this = this;
  1633 + var options = this.options;
  1634 +
  1635 + this.el = $('<div class="fc-popover"/>')
  1636 + .addClass(options.className || '')
  1637 + .css({
  1638 + // position initially to the top left to avoid creating scrollbars
  1639 + top: 0,
  1640 + left: 0
  1641 + })
  1642 + .append(options.content)
  1643 + .appendTo(options.parentEl);
  1644 +
  1645 + // when a click happens on anything inside with a 'fc-close' className, hide the popover
  1646 + this.el.on('click', '.fc-close', function() {
  1647 + _this.hide();
  1648 + });
  1649 +
  1650 + if (options.autoHide) {
  1651 + $(document).on('mousedown', this.documentMousedownProxy = proxy(this, 'documentMousedown'));
  1652 + }
  1653 + },
  1654 +
  1655 +
  1656 + // Triggered when the user clicks *anywhere* in the document, for the autoHide feature
  1657 + documentMousedown: function(ev) {
  1658 + // only hide the popover if the click happened outside the popover
  1659 + if (this.el && !$(ev.target).closest(this.el).length) {
  1660 + this.hide();
  1661 + }
  1662 + },
  1663 +
  1664 +
  1665 + // Hides and unregisters any handlers
  1666 + destroy: function() {
  1667 + this.hide();
  1668 +
  1669 + if (this.el) {
  1670 + this.el.remove();
  1671 + this.el = null;
  1672 + }
  1673 +
  1674 + $(document).off('mousedown', this.documentMousedownProxy);
  1675 + },
  1676 +
  1677 +
  1678 + // Positions the popover optimally, using the top/left/right options
  1679 + position: function() {
  1680 + var options = this.options;
  1681 + var origin = this.el.offsetParent().offset();
  1682 + var width = this.el.outerWidth();
  1683 + var height = this.el.outerHeight();
  1684 + var windowEl = $(window);
  1685 + var viewportEl = getScrollParent(this.el);
  1686 + var viewportTop;
  1687 + var viewportLeft;
  1688 + var viewportOffset;
  1689 + var top; // the "position" (not "offset") values for the popover
  1690 + var left; //
  1691 +
  1692 + // compute top and left
  1693 + top = options.top || 0;
  1694 + if (options.left !== undefined) {
  1695 + left = options.left;
  1696 + }
  1697 + else if (options.right !== undefined) {
  1698 + left = options.right - width; // derive the left value from the right value
  1699 + }
  1700 + else {
  1701 + left = 0;
  1702 + }
  1703 +
  1704 + if (viewportEl.is(window) || viewportEl.is(document)) { // normalize getScrollParent's result
  1705 + viewportEl = windowEl;
  1706 + viewportTop = 0; // the window is always at the top left
  1707 + viewportLeft = 0; // (and .offset() won't work if called here)
  1708 + }
  1709 + else {
  1710 + viewportOffset = viewportEl.offset();
  1711 + viewportTop = viewportOffset.top;
  1712 + viewportLeft = viewportOffset.left;
  1713 + }
  1714 +
  1715 + // if the window is scrolled, it causes the visible area to be further down
  1716 + viewportTop += windowEl.scrollTop();
  1717 + viewportLeft += windowEl.scrollLeft();
  1718 +
  1719 + // constrain to the view port. if constrained by two edges, give precedence to top/left
  1720 + if (options.viewportConstrain !== false) {
  1721 + top = Math.min(top, viewportTop + viewportEl.outerHeight() - height - this.margin);
  1722 + top = Math.max(top, viewportTop + this.margin);
  1723 + left = Math.min(left, viewportLeft + viewportEl.outerWidth() - width - this.margin);
  1724 + left = Math.max(left, viewportLeft + this.margin);
  1725 + }
  1726 +
  1727 + this.el.css({
  1728 + top: top - origin.top,
  1729 + left: left - origin.left
  1730 + });
  1731 + },
  1732 +
  1733 +
  1734 + // Triggers a callback. Calls a function in the option hash of the same name.
  1735 + // Arguments beyond the first `name` are forwarded on.
  1736 + // TODO: better code reuse for this. Repeat code
  1737 + trigger: function(name) {
  1738 + if (this.options[name]) {
  1739 + this.options[name].apply(this, Array.prototype.slice.call(arguments, 1));
  1740 + }
  1741 + }
  1742 +
  1743 +});
  1744 +
  1745 +;;
  1746 +
  1747 +/* A "coordinate map" converts pixel coordinates into an associated cell, which has an associated date
  1748 +------------------------------------------------------------------------------------------------------------------------
  1749 +Common interface:
  1750 +
  1751 + CoordMap.prototype = {
  1752 + build: function() {},
  1753 + getCell: function(x, y) {}
  1754 + };
  1755 +
  1756 +*/
  1757 +
  1758 +/* Coordinate map for a grid component
  1759 +----------------------------------------------------------------------------------------------------------------------*/
  1760 +
  1761 +var GridCoordMap = Class.extend({
  1762 +
  1763 + grid: null, // reference to the Grid
  1764 + rowCoords: null, // array of {top,bottom} objects
  1765 + colCoords: null, // array of {left,right} objects
  1766 +
  1767 + containerEl: null, // container element that all coordinates are constrained to. optionally assigned
  1768 + bounds: null,
  1769 +
  1770 +
  1771 + constructor: function(grid) {
  1772 + this.grid = grid;
  1773 + },
  1774 +
  1775 +
  1776 + // Queries the grid for the coordinates of all the cells
  1777 + build: function() {
  1778 + this.rowCoords = this.grid.computeRowCoords();
  1779 + this.colCoords = this.grid.computeColCoords();
  1780 + this.computeBounds();
  1781 + },
  1782 +
  1783 +
  1784 + // Clears the coordinates data to free up memory
  1785 + clear: function() {
  1786 + this.rowCoords = null;
  1787 + this.colCoords = null;
  1788 + },
  1789 +
  1790 +
  1791 + // Given a coordinate of the document, gets the associated cell. If no cell is underneath, returns null
  1792 + getCell: function(x, y) {
  1793 + var rowCoords = this.rowCoords;
  1794 + var rowCnt = rowCoords.length;
  1795 + var colCoords = this.colCoords;
  1796 + var colCnt = colCoords.length;
  1797 + var hitRow = null;
  1798 + var hitCol = null;
  1799 + var i, coords;
  1800 + var cell;
  1801 +
  1802 + if (this.inBounds(x, y)) {
  1803 +
  1804 + for (i = 0; i < rowCnt; i++) {
  1805 + coords = rowCoords[i];
  1806 + if (y >= coords.top && y < coords.bottom) {
  1807 + hitRow = i;
  1808 + break;
  1809 + }
  1810 + }
  1811 +
  1812 + for (i = 0; i < colCnt; i++) {
  1813 + coords = colCoords[i];
  1814 + if (x >= coords.left && x < coords.right) {
  1815 + hitCol = i;
  1816 + break;
  1817 + }
  1818 + }
  1819 +
  1820 + if (hitRow !== null && hitCol !== null) {
  1821 +
  1822 + cell = this.grid.getCell(hitRow, hitCol); // expected to return a fresh object we can modify
  1823 + cell.grid = this.grid; // for CellDragListener's isCellsEqual. dragging between grids
  1824 +
  1825 + // make the coordinates available on the cell object
  1826 + $.extend(cell, rowCoords[hitRow], colCoords[hitCol]);
  1827 +
  1828 + return cell;
  1829 + }
  1830 + }
  1831 +
  1832 + return null;
  1833 + },
  1834 +
  1835 +
  1836 + // If there is a containerEl, compute the bounds into min/max values
  1837 + computeBounds: function() {
  1838 + this.bounds = this.containerEl ?
  1839 + getClientRect(this.containerEl) : // area within scrollbars
  1840 + null;
  1841 + },
  1842 +
  1843 +
  1844 + // Determines if the given coordinates are in bounds. If no `containerEl`, always true
  1845 + inBounds: function(x, y) {
  1846 + var bounds = this.bounds;
  1847 +
  1848 + if (bounds) {
  1849 + return x >= bounds.left && x < bounds.right && y >= bounds.top && y < bounds.bottom;
  1850 + }
  1851 +
  1852 + return true;
  1853 + }
  1854 +
  1855 +});
  1856 +
  1857 +
  1858 +/* Coordinate map that is a combination of multiple other coordinate maps
  1859 +----------------------------------------------------------------------------------------------------------------------*/
  1860 +
  1861 +var ComboCoordMap = Class.extend({
  1862 +
  1863 + coordMaps: null, // an array of CoordMaps
  1864 +
  1865 +
  1866 + constructor: function(coordMaps) {
  1867 + this.coordMaps = coordMaps;
  1868 + },
  1869 +
  1870 +
  1871 + // Builds all coordMaps
  1872 + build: function() {
  1873 + var coordMaps = this.coordMaps;
  1874 + var i;
  1875 +
  1876 + for (i = 0; i < coordMaps.length; i++) {
  1877 + coordMaps[i].build();
  1878 + }
  1879 + },
  1880 +
  1881 +
  1882 + // Queries all coordMaps for the cell underneath the given coordinates, returning the first result
  1883 + getCell: function(x, y) {
  1884 + var coordMaps = this.coordMaps;
  1885 + var cell = null;
  1886 + var i;
  1887 +
  1888 + for (i = 0; i < coordMaps.length && !cell; i++) {
  1889 + cell = coordMaps[i].getCell(x, y);
  1890 + }
  1891 +
  1892 + return cell;
  1893 + },
  1894 +
  1895 +
  1896 + // Clears all coordMaps
  1897 + clear: function() {
  1898 + var coordMaps = this.coordMaps;
  1899 + var i;
  1900 +
  1901 + for (i = 0; i < coordMaps.length; i++) {
  1902 + coordMaps[i].clear();
  1903 + }
  1904 + }
  1905 +
  1906 +});
  1907 +
  1908 +;;
  1909 +
  1910 +/* Tracks a drag's mouse movement, firing various handlers
  1911 +----------------------------------------------------------------------------------------------------------------------*/
  1912 +
  1913 +var DragListener = fc.DragListener = Class.extend({
  1914 +
  1915 + options: null,
  1916 +
  1917 + isListening: false,
  1918 + isDragging: false,
  1919 +
  1920 + // coordinates of the initial mousedown
  1921 + originX: null,
  1922 + originY: null,
  1923 +
  1924 + // handler attached to the document, bound to the DragListener's `this`
  1925 + mousemoveProxy: null,
  1926 + mouseupProxy: null,
  1927 +
  1928 + // for IE8 bug-fighting behavior, for now
  1929 + subjectEl: null, // the element being draged. optional
  1930 + subjectHref: null,
  1931 +
  1932 + scrollEl: null,
  1933 + scrollBounds: null, // { top, bottom, left, right }
  1934 + scrollTopVel: null, // pixels per second
  1935 + scrollLeftVel: null, // pixels per second
  1936 + scrollIntervalId: null, // ID of setTimeout for scrolling animation loop
  1937 + scrollHandlerProxy: null, // this-scoped function for handling when scrollEl is scrolled
  1938 +
  1939 + scrollSensitivity: 30, // pixels from edge for scrolling to start
  1940 + scrollSpeed: 200, // pixels per second, at maximum speed
  1941 + scrollIntervalMs: 50, // millisecond wait between scroll increment
  1942 +
  1943 +
  1944 + constructor: function(options) {
  1945 + options = options || {};
  1946 + this.options = options;
  1947 + this.subjectEl = options.subjectEl;
  1948 + },
  1949 +
  1950 +
  1951 + // Call this when the user does a mousedown. Will probably lead to startListening
  1952 + mousedown: function(ev) {
  1953 + if (isPrimaryMouseButton(ev)) {
  1954 +
  1955 + ev.preventDefault(); // prevents native selection in most browsers
  1956 +
  1957 + this.startListening(ev);
  1958 +
  1959 + // start the drag immediately if there is no minimum distance for a drag start
  1960 + if (!this.options.distance) {
  1961 + this.startDrag(ev);
  1962 + }
  1963 + }
  1964 + },
  1965 +
  1966 +
  1967 + // Call this to start tracking mouse movements
  1968 + startListening: function(ev) {
  1969 + var scrollParent;
  1970 +
  1971 + if (!this.isListening) {
  1972 +
  1973 + // grab scroll container and attach handler
  1974 + if (ev && this.options.scroll) {
  1975 + scrollParent = getScrollParent($(ev.target));
  1976 + if (!scrollParent.is(window) && !scrollParent.is(document)) {
  1977 + this.scrollEl = scrollParent;
  1978 +
  1979 + // scope to `this`, and use `debounce` to make sure rapid calls don't happen
  1980 + this.scrollHandlerProxy = debounce(proxy(this, 'scrollHandler'), 100);
  1981 + this.scrollEl.on('scroll', this.scrollHandlerProxy);
  1982 + }
  1983 + }
  1984 +
  1985 + $(document)
  1986 + .on('mousemove', this.mousemoveProxy = proxy(this, 'mousemove'))
  1987 + .on('mouseup', this.mouseupProxy = proxy(this, 'mouseup'))
  1988 + .on('selectstart', this.preventDefault); // prevents native selection in IE<=8
  1989 +
  1990 + if (ev) {
  1991 + this.originX = ev.pageX;
  1992 + this.originY = ev.pageY;
  1993 + }
  1994 + else {
  1995 + // if no starting information was given, origin will be the topleft corner of the screen.
  1996 + // if so, dx/dy in the future will be the absolute coordinates.
  1997 + this.originX = 0;
  1998 + this.originY = 0;
  1999 + }
  2000 +
  2001 + this.isListening = true;
  2002 + this.listenStart(ev);
  2003 + }
  2004 + },
  2005 +
  2006 +
  2007 + // Called when drag listening has started (but a real drag has not necessarily began)
  2008 + listenStart: function(ev) {
  2009 + this.trigger('listenStart', ev);
  2010 + },
  2011 +
  2012 +
  2013 + // Called when the user moves the mouse
  2014 + mousemove: function(ev) {
  2015 + var dx = ev.pageX - this.originX;
  2016 + var dy = ev.pageY - this.originY;
  2017 + var minDistance;
  2018 + var distanceSq; // current distance from the origin, squared
  2019 +
  2020 + if (!this.isDragging) { // if not already dragging...
  2021 + // then start the drag if the minimum distance criteria is met
  2022 + minDistance = this.options.distance || 1;
  2023 + distanceSq = dx * dx + dy * dy;
  2024 + if (distanceSq >= minDistance * minDistance) { // use pythagorean theorem
  2025 + this.startDrag(ev);
  2026 + }
  2027 + }
  2028 +
  2029 + if (this.isDragging) {
  2030 + this.drag(dx, dy, ev); // report a drag, even if this mousemove initiated the drag
  2031 + }
  2032 + },
  2033 +
  2034 +
  2035 + // Call this to initiate a legitimate drag.
  2036 + // This function is called internally from this class, but can also be called explicitly from outside
  2037 + startDrag: function(ev) {
  2038 +
  2039 + if (!this.isListening) { // startDrag must have manually initiated
  2040 + this.startListening();
  2041 + }
  2042 +
  2043 + if (!this.isDragging) {
  2044 + this.isDragging = true;
  2045 + this.dragStart(ev);
  2046 + }
  2047 + },
  2048 +
  2049 +
  2050 + // Called when the actual drag has started (went beyond minDistance)
  2051 + dragStart: function(ev) {
  2052 + var subjectEl = this.subjectEl;
  2053 +
  2054 + this.trigger('dragStart', ev);
  2055 +
  2056 + // remove a mousedown'd <a>'s href so it is not visited (IE8 bug)
  2057 + if ((this.subjectHref = subjectEl ? subjectEl.attr('href') : null)) {
  2058 + subjectEl.removeAttr('href');
  2059 + }
  2060 + },
  2061 +
  2062 +
  2063 + // Called while the mouse is being moved and when we know a legitimate drag is taking place
  2064 + drag: function(dx, dy, ev) {
  2065 + this.trigger('drag', dx, dy, ev);
  2066 + this.updateScroll(ev); // will possibly cause scrolling
  2067 + },
  2068 +
  2069 +
  2070 + // Called when the user does a mouseup
  2071 + mouseup: function(ev) {
  2072 + this.stopListening(ev);
  2073 + },
  2074 +
  2075 +
  2076 + // Called when the drag is over. Will not cause listening to stop however.
  2077 + // A concluding 'cellOut' event will NOT be triggered.
  2078 + stopDrag: function(ev) {
  2079 + if (this.isDragging) {
  2080 + this.stopScrolling();
  2081 + this.dragStop(ev);
  2082 + this.isDragging = false;
  2083 + }
  2084 + },
  2085 +
  2086 +
  2087 + // Called when dragging has been stopped
  2088 + dragStop: function(ev) {
  2089 + var _this = this;
  2090 +
  2091 + this.trigger('dragStop', ev);
  2092 +
  2093 + // restore a mousedown'd <a>'s href (for IE8 bug)
  2094 + setTimeout(function() { // must be outside of the click's execution
  2095 + if (_this.subjectHref) {
  2096 + _this.subjectEl.attr('href', _this.subjectHref);
  2097 + }
  2098 + }, 0);
  2099 + },
  2100 +
  2101 +
  2102 + // Call this to stop listening to the user's mouse events
  2103 + stopListening: function(ev) {
  2104 + this.stopDrag(ev); // if there's a current drag, kill it
  2105 +
  2106 + if (this.isListening) {
  2107 +
  2108 + // remove the scroll handler if there is a scrollEl
  2109 + if (this.scrollEl) {
  2110 + this.scrollEl.off('scroll', this.scrollHandlerProxy);
  2111 + this.scrollHandlerProxy = null;
  2112 + }
  2113 +
  2114 + $(document)
  2115 + .off('mousemove', this.mousemoveProxy)
  2116 + .off('mouseup', this.mouseupProxy)
  2117 + .off('selectstart', this.preventDefault);
  2118 +
  2119 + this.mousemoveProxy = null;
  2120 + this.mouseupProxy = null;
  2121 +
  2122 + this.isListening = false;
  2123 + this.listenStop(ev);
  2124 + }
  2125 + },
  2126 +
  2127 +
  2128 + // Called when drag listening has stopped
  2129 + listenStop: function(ev) {
  2130 + this.trigger('listenStop', ev);
  2131 + },
  2132 +
  2133 +
  2134 + // Triggers a callback. Calls a function in the option hash of the same name.
  2135 + // Arguments beyond the first `name` are forwarded on.
  2136 + trigger: function(name) {
  2137 + if (this.options[name]) {
  2138 + this.options[name].apply(this, Array.prototype.slice.call(arguments, 1));
  2139 + }
  2140 + },
  2141 +
  2142 +
  2143 + // Stops a given mouse event from doing it's native browser action. In our case, text selection.
  2144 + preventDefault: function(ev) {
  2145 + ev.preventDefault();
  2146 + },
  2147 +
  2148 +
  2149 + /* Scrolling
  2150 + ------------------------------------------------------------------------------------------------------------------*/
  2151 +
  2152 +
  2153 + // Computes and stores the bounding rectangle of scrollEl
  2154 + computeScrollBounds: function() {
  2155 + var el = this.scrollEl;
  2156 +
  2157 + this.scrollBounds = el ? getOuterRect(el) : null;
  2158 + // TODO: use getClientRect in future. but prevents auto scrolling when on top of scrollbars
  2159 + },
  2160 +
  2161 +
  2162 + // Called when the dragging is in progress and scrolling should be updated
  2163 + updateScroll: function(ev) {
  2164 + var sensitivity = this.scrollSensitivity;
  2165 + var bounds = this.scrollBounds;
  2166 + var topCloseness, bottomCloseness;
  2167 + var leftCloseness, rightCloseness;
  2168 + var topVel = 0;
  2169 + var leftVel = 0;
  2170 +
  2171 + if (bounds) { // only scroll if scrollEl exists
  2172 +
  2173 + // compute closeness to edges. valid range is from 0.0 - 1.0
  2174 + topCloseness = (sensitivity - (ev.pageY - bounds.top)) / sensitivity;
  2175 + bottomCloseness = (sensitivity - (bounds.bottom - ev.pageY)) / sensitivity;
  2176 + leftCloseness = (sensitivity - (ev.pageX - bounds.left)) / sensitivity;
  2177 + rightCloseness = (sensitivity - (bounds.right - ev.pageX)) / sensitivity;
  2178 +
  2179 + // translate vertical closeness into velocity.
  2180 + // mouse must be completely in bounds for velocity to happen.
  2181 + if (topCloseness >= 0 && topCloseness <= 1) {
  2182 + topVel = topCloseness * this.scrollSpeed * -1; // negative. for scrolling up
  2183 + }
  2184 + else if (bottomCloseness >= 0 && bottomCloseness <= 1) {
  2185 + topVel = bottomCloseness * this.scrollSpeed;
  2186 + }
  2187 +
  2188 + // translate horizontal closeness into velocity
  2189 + if (leftCloseness >= 0 && leftCloseness <= 1) {
  2190 + leftVel = leftCloseness * this.scrollSpeed * -1; // negative. for scrolling left
  2191 + }
  2192 + else if (rightCloseness >= 0 && rightCloseness <= 1) {
  2193 + leftVel = rightCloseness * this.scrollSpeed;
  2194 + }
  2195 + }
  2196 +
  2197 + this.setScrollVel(topVel, leftVel);
  2198 + },
  2199 +
  2200 +
  2201 + // Sets the speed-of-scrolling for the scrollEl
  2202 + setScrollVel: function(topVel, leftVel) {
  2203 +
  2204 + this.scrollTopVel = topVel;
  2205 + this.scrollLeftVel = leftVel;
  2206 +
  2207 + this.constrainScrollVel(); // massages into realistic values
  2208 +
  2209 + // if there is non-zero velocity, and an animation loop hasn't already started, then START
  2210 + if ((this.scrollTopVel || this.scrollLeftVel) && !this.scrollIntervalId) {
  2211 + this.scrollIntervalId = setInterval(
  2212 + proxy(this, 'scrollIntervalFunc'), // scope to `this`
  2213 + this.scrollIntervalMs
  2214 + );
  2215 + }
  2216 + },
  2217 +
  2218 +
  2219 + // Forces scrollTopVel and scrollLeftVel to be zero if scrolling has already gone all the way
  2220 + constrainScrollVel: function() {
  2221 + var el = this.scrollEl;
  2222 +
  2223 + if (this.scrollTopVel < 0) { // scrolling up?
  2224 + if (el.scrollTop() <= 0) { // already scrolled all the way up?
  2225 + this.scrollTopVel = 0;
  2226 + }
  2227 + }
  2228 + else if (this.scrollTopVel > 0) { // scrolling down?
  2229 + if (el.scrollTop() + el[0].clientHeight >= el[0].scrollHeight) { // already scrolled all the way down?
  2230 + this.scrollTopVel = 0;
  2231 + }
  2232 + }
  2233 +
  2234 + if (this.scrollLeftVel < 0) { // scrolling left?
  2235 + if (el.scrollLeft() <= 0) { // already scrolled all the left?
  2236 + this.scrollLeftVel = 0;
  2237 + }
  2238 + }
  2239 + else if (this.scrollLeftVel > 0) { // scrolling right?
  2240 + if (el.scrollLeft() + el[0].clientWidth >= el[0].scrollWidth) { // already scrolled all the way right?
  2241 + this.scrollLeftVel = 0;
  2242 + }
  2243 + }
  2244 + },
  2245 +
  2246 +
  2247 + // This function gets called during every iteration of the scrolling animation loop
  2248 + scrollIntervalFunc: function() {
  2249 + var el = this.scrollEl;
  2250 + var frac = this.scrollIntervalMs / 1000; // considering animation frequency, what the vel should be mult'd by
  2251 +
  2252 + // change the value of scrollEl's scroll
  2253 + if (this.scrollTopVel) {
  2254 + el.scrollTop(el.scrollTop() + this.scrollTopVel * frac);
  2255 + }
  2256 + if (this.scrollLeftVel) {
  2257 + el.scrollLeft(el.scrollLeft() + this.scrollLeftVel * frac);
  2258 + }
  2259 +
  2260 + this.constrainScrollVel(); // since the scroll values changed, recompute the velocities
  2261 +
  2262 + // if scrolled all the way, which causes the vels to be zero, stop the animation loop
  2263 + if (!this.scrollTopVel && !this.scrollLeftVel) {
  2264 + this.stopScrolling();
  2265 + }
  2266 + },
  2267 +
  2268 +
  2269 + // Kills any existing scrolling animation loop
  2270 + stopScrolling: function() {
  2271 + if (this.scrollIntervalId) {
  2272 + clearInterval(this.scrollIntervalId);
  2273 + this.scrollIntervalId = null;
  2274 +
  2275 + // when all done with scrolling, recompute positions since they probably changed
  2276 + this.scrollStop();
  2277 + }
  2278 + },
  2279 +
  2280 +
  2281 + // Get called when the scrollEl is scrolled (NOTE: this is delayed via debounce)
  2282 + scrollHandler: function() {
  2283 + // recompute all coordinates, but *only* if this is *not* part of our scrolling animation
  2284 + if (!this.scrollIntervalId) {
  2285 + this.scrollStop();
  2286 + }
  2287 + },
  2288 +
  2289 +
  2290 + // Called when scrolling has stopped, whether through auto scroll, or the user scrolling
  2291 + scrollStop: function() {
  2292 + }
  2293 +
  2294 +});
  2295 +
  2296 +;;
  2297 +
  2298 +/* Tracks mouse movements over a CoordMap and raises events about which cell the mouse is over.
  2299 +------------------------------------------------------------------------------------------------------------------------
  2300 +options:
  2301 +- subjectEl
  2302 +- subjectCenter
  2303 +*/
  2304 +
  2305 +var CellDragListener = DragListener.extend({
  2306 +
  2307 + coordMap: null, // converts coordinates to date cells
  2308 + origCell: null, // the cell the mouse was over when listening started
  2309 + cell: null, // the cell the mouse is over
  2310 + coordAdjust: null, // delta that will be added to the mouse coordinates when computing collisions
  2311 +
  2312 +
  2313 + constructor: function(coordMap, options) {
  2314 + DragListener.prototype.constructor.call(this, options); // call the super-constructor
  2315 +
  2316 + this.coordMap = coordMap;
  2317 + },
  2318 +
  2319 +
  2320 + // Called when drag listening starts (but a real drag has not necessarily began).
  2321 + // ev might be undefined if dragging was started manually.
  2322 + listenStart: function(ev) {
  2323 + var subjectEl = this.subjectEl;
  2324 + var subjectRect;
  2325 + var origPoint;
  2326 + var point;
  2327 +
  2328 + DragListener.prototype.listenStart.apply(this, arguments); // call the super-method
  2329 +
  2330 + this.computeCoords();
  2331 +
  2332 + if (ev) {
  2333 + origPoint = { left: ev.pageX, top: ev.pageY };
  2334 + point = origPoint;
  2335 +
  2336 + // constrain the point to bounds of the element being dragged
  2337 + if (subjectEl) {
  2338 + subjectRect = getOuterRect(subjectEl); // used for centering as well
  2339 + point = constrainPoint(point, subjectRect);
  2340 + }
  2341 +
  2342 + this.origCell = this.getCell(point.left, point.top);
  2343 +
  2344 + // treat the center of the subject as the collision point?
  2345 + if (subjectEl && this.options.subjectCenter) {
  2346 +
  2347 + // only consider the area the subject overlaps the cell. best for large subjects
  2348 + if (this.origCell) {
  2349 + subjectRect = intersectRects(this.origCell, subjectRect) ||
  2350 + subjectRect; // in case there is no intersection
  2351 + }
  2352 +
  2353 + point = getRectCenter(subjectRect);
  2354 + }
  2355 +
  2356 + this.coordAdjust = diffPoints(point, origPoint); // point - origPoint
  2357 + }
  2358 + else {
  2359 + this.origCell = null;
  2360 + this.coordAdjust = null;
  2361 + }
  2362 + },
  2363 +
  2364 +
  2365 + // Recomputes the drag-critical positions of elements
  2366 + computeCoords: function() {
  2367 + this.coordMap.build();
  2368 + this.computeScrollBounds();
  2369 + },
  2370 +
  2371 +
  2372 + // Called when the actual drag has started
  2373 + dragStart: function(ev) {
  2374 + var cell;
  2375 +
  2376 + DragListener.prototype.dragStart.apply(this, arguments); // call the super-method
  2377 +
  2378 + cell = this.getCell(ev.pageX, ev.pageY); // might be different from this.origCell if the min-distance is large
  2379 +
  2380 + // report the initial cell the mouse is over
  2381 + // especially important if no min-distance and drag starts immediately
  2382 + if (cell) {
  2383 + this.cellOver(cell);
  2384 + }
  2385 + },
  2386 +
  2387 +
  2388 + // Called when the drag moves
  2389 + drag: function(dx, dy, ev) {
  2390 + var cell;
  2391 +
  2392 + DragListener.prototype.drag.apply(this, arguments); // call the super-method
  2393 +
  2394 + cell = this.getCell(ev.pageX, ev.pageY);
  2395 +
  2396 + if (!isCellsEqual(cell, this.cell)) { // a different cell than before?
  2397 + if (this.cell) {
  2398 + this.cellOut();
  2399 + }
  2400 + if (cell) {
  2401 + this.cellOver(cell);
  2402 + }
  2403 + }
  2404 + },
  2405 +
  2406 +
  2407 + // Called when dragging has been stopped
  2408 + dragStop: function() {
  2409 + this.cellDone();
  2410 + DragListener.prototype.dragStop.apply(this, arguments); // call the super-method
  2411 + },
  2412 +
  2413 +
  2414 + // Called when a the mouse has just moved over a new cell
  2415 + cellOver: function(cell) {
  2416 + this.cell = cell;
  2417 + this.trigger('cellOver', cell, isCellsEqual(cell, this.origCell), this.origCell);
  2418 + },
  2419 +
  2420 +
  2421 + // Called when the mouse has just moved out of a cell
  2422 + cellOut: function() {
  2423 + if (this.cell) {
  2424 + this.trigger('cellOut', this.cell);
  2425 + this.cellDone();
  2426 + this.cell = null;
  2427 + }
  2428 + },
  2429 +
  2430 +
  2431 + // Called after a cellOut. Also called before a dragStop
  2432 + cellDone: function() {
  2433 + if (this.cell) {
  2434 + this.trigger('cellDone', this.cell);
  2435 + }
  2436 + },
  2437 +
  2438 +
  2439 + // Called when drag listening has stopped
  2440 + listenStop: function() {
  2441 + DragListener.prototype.listenStop.apply(this, arguments); // call the super-method
  2442 +
  2443 + this.origCell = this.cell = null;
  2444 + this.coordMap.clear();
  2445 + },
  2446 +
  2447 +
  2448 + // Called when scrolling has stopped, whether through auto scroll, or the user scrolling
  2449 + scrollStop: function() {
  2450 + DragListener.prototype.scrollStop.apply(this, arguments); // call the super-method
  2451 +
  2452 + this.computeCoords(); // cells' absolute positions will be in new places. recompute
  2453 + },
  2454 +
  2455 +
  2456 + // Gets the cell underneath the coordinates for the given mouse event
  2457 + getCell: function(left, top) {
  2458 +
  2459 + if (this.coordAdjust) {
  2460 + left += this.coordAdjust.left;
  2461 + top += this.coordAdjust.top;
  2462 + }
  2463 +
  2464 + return this.coordMap.getCell(left, top);
  2465 + }
  2466 +
  2467 +});
  2468 +
  2469 +
  2470 +// Returns `true` if the cells are identically equal. `false` otherwise.
  2471 +// They must have the same row, col, and be from the same grid.
  2472 +// Two null values will be considered equal, as two "out of the grid" states are the same.
  2473 +function isCellsEqual(cell1, cell2) {
  2474 +
  2475 + if (!cell1 && !cell2) {
  2476 + return true;
  2477 + }
  2478 +
  2479 + if (cell1 && cell2) {
  2480 + return cell1.grid === cell2.grid &&
  2481 + cell1.row === cell2.row &&
  2482 + cell1.col === cell2.col;
  2483 + }
  2484 +
  2485 + return false;
  2486 +}
  2487 +
  2488 +;;
  2489 +
  2490 +/* Creates a clone of an element and lets it track the mouse as it moves
  2491 +----------------------------------------------------------------------------------------------------------------------*/
  2492 +
  2493 +var MouseFollower = Class.extend({
  2494 +
  2495 + options: null,
  2496 +
  2497 + sourceEl: null, // the element that will be cloned and made to look like it is dragging
  2498 + el: null, // the clone of `sourceEl` that will track the mouse
  2499 + parentEl: null, // the element that `el` (the clone) will be attached to
  2500 +
  2501 + // the initial position of el, relative to the offset parent. made to match the initial offset of sourceEl
  2502 + top0: null,
  2503 + left0: null,
  2504 +
  2505 + // the initial position of the mouse
  2506 + mouseY0: null,
  2507 + mouseX0: null,
  2508 +
  2509 + // the number of pixels the mouse has moved from its initial position
  2510 + topDelta: null,
  2511 + leftDelta: null,
  2512 +
  2513 + mousemoveProxy: null, // document mousemove handler, bound to the MouseFollower's `this`
  2514 +
  2515 + isFollowing: false,
  2516 + isHidden: false,
  2517 + isAnimating: false, // doing the revert animation?
  2518 +
  2519 + constructor: function(sourceEl, options) {
  2520 + this.options = options = options || {};
  2521 + this.sourceEl = sourceEl;
  2522 + this.parentEl = options.parentEl ? $(options.parentEl) : sourceEl.parent(); // default to sourceEl's parent
  2523 + },
  2524 +
  2525 +
  2526 + // Causes the element to start following the mouse
  2527 + start: function(ev) {
  2528 + if (!this.isFollowing) {
  2529 + this.isFollowing = true;
  2530 +
  2531 + this.mouseY0 = ev.pageY;
  2532 + this.mouseX0 = ev.pageX;
  2533 + this.topDelta = 0;
  2534 + this.leftDelta = 0;
  2535 +
  2536 + if (!this.isHidden) {
  2537 + this.updatePosition();
  2538 + }
  2539 +
  2540 + $(document).on('mousemove', this.mousemoveProxy = proxy(this, 'mousemove'));
  2541 + }
  2542 + },
  2543 +
  2544 +
  2545 + // Causes the element to stop following the mouse. If shouldRevert is true, will animate back to original position.
  2546 + // `callback` gets invoked when the animation is complete. If no animation, it is invoked immediately.
  2547 + stop: function(shouldRevert, callback) {
  2548 + var _this = this;
  2549 + var revertDuration = this.options.revertDuration;
  2550 +
  2551 + function complete() {
  2552 + this.isAnimating = false;
  2553 + _this.destroyEl();
  2554 +
  2555 + this.top0 = this.left0 = null; // reset state for future updatePosition calls
  2556 +
  2557 + if (callback) {
  2558 + callback();
  2559 + }
  2560 + }
  2561 +
  2562 + if (this.isFollowing && !this.isAnimating) { // disallow more than one stop animation at a time
  2563 + this.isFollowing = false;
  2564 +
  2565 + $(document).off('mousemove', this.mousemoveProxy);
  2566 +
  2567 + if (shouldRevert && revertDuration && !this.isHidden) { // do a revert animation?
  2568 + this.isAnimating = true;
  2569 + this.el.animate({
  2570 + top: this.top0,
  2571 + left: this.left0
  2572 + }, {
  2573 + duration: revertDuration,
  2574 + complete: complete
  2575 + });
  2576 + }
  2577 + else {
  2578 + complete();
  2579 + }
  2580 + }
  2581 + },
  2582 +
  2583 +
  2584 + // Gets the tracking element. Create it if necessary
  2585 + getEl: function() {
  2586 + var el = this.el;
  2587 +
  2588 + if (!el) {
  2589 + this.sourceEl.width(); // hack to force IE8 to compute correct bounding box
  2590 + el = this.el = this.sourceEl.clone()
  2591 + .css({
  2592 + position: 'absolute',
  2593 + visibility: '', // in case original element was hidden (commonly through hideEvents())
  2594 + display: this.isHidden ? 'none' : '', // for when initially hidden
  2595 + margin: 0,
  2596 + right: 'auto', // erase and set width instead
  2597 + bottom: 'auto', // erase and set height instead
  2598 + width: this.sourceEl.width(), // explicit height in case there was a 'right' value
  2599 + height: this.sourceEl.height(), // explicit width in case there was a 'bottom' value
  2600 + opacity: this.options.opacity || '',
  2601 + zIndex: this.options.zIndex
  2602 + })
  2603 + .appendTo(this.parentEl);
  2604 + }
  2605 +
  2606 + return el;
  2607 + },
  2608 +
  2609 +
  2610 + // Removes the tracking element if it has already been created
  2611 + destroyEl: function() {
  2612 + if (this.el) {
  2613 + this.el.remove();
  2614 + this.el = null;
  2615 + }
  2616 + },
  2617 +
  2618 +
  2619 + // Update the CSS position of the tracking element
  2620 + updatePosition: function() {
  2621 + var sourceOffset;
  2622 + var origin;
  2623 +
  2624 + this.getEl(); // ensure this.el
  2625 +
  2626 + // make sure origin info was computed
  2627 + if (this.top0 === null) {
  2628 + this.sourceEl.width(); // hack to force IE8 to compute correct bounding box
  2629 + sourceOffset = this.sourceEl.offset();
  2630 + origin = this.el.offsetParent().offset();
  2631 + this.top0 = sourceOffset.top - origin.top;
  2632 + this.left0 = sourceOffset.left - origin.left;
  2633 + }
  2634 +
  2635 + this.el.css({
  2636 + top: this.top0 + this.topDelta,
  2637 + left: this.left0 + this.leftDelta
  2638 + });
  2639 + },
  2640 +
  2641 +
  2642 + // Gets called when the user moves the mouse
  2643 + mousemove: function(ev) {
  2644 + this.topDelta = ev.pageY - this.mouseY0;
  2645 + this.leftDelta = ev.pageX - this.mouseX0;
  2646 +
  2647 + if (!this.isHidden) {
  2648 + this.updatePosition();
  2649 + }
  2650 + },
  2651 +
  2652 +
  2653 + // Temporarily makes the tracking element invisible. Can be called before following starts
  2654 + hide: function() {
  2655 + if (!this.isHidden) {
  2656 + this.isHidden = true;
  2657 + if (this.el) {
  2658 + this.el.hide();
  2659 + }
  2660 + }
  2661 + },
  2662 +
  2663 +
  2664 + // Show the tracking element after it has been temporarily hidden
  2665 + show: function() {
  2666 + if (this.isHidden) {
  2667 + this.isHidden = false;
  2668 + this.updatePosition();
  2669 + this.getEl().show();
  2670 + }
  2671 + }
  2672 +
  2673 +});
  2674 +
  2675 +;;
  2676 +
  2677 +/* A utility class for rendering <tr> rows.
  2678 +----------------------------------------------------------------------------------------------------------------------*/
  2679 +// It leverages methods of the subclass and the View to determine custom rendering behavior for each row "type"
  2680 +// (such as highlight rows, day rows, helper rows, etc).
  2681 +
  2682 +var RowRenderer = Class.extend({
  2683 +
  2684 + view: null, // a View object
  2685 + isRTL: null, // shortcut to the view's isRTL option
  2686 + cellHtml: '<td/>', // plain default HTML used for a cell when no other is available
  2687 +
  2688 +
  2689 + constructor: function(view) {
  2690 + this.view = view;
  2691 + this.isRTL = view.opt('isRTL');
  2692 + },
  2693 +
  2694 +
  2695 + // Renders the HTML for a row, leveraging custom cell-HTML-renderers based on the `rowType`.
  2696 + // Also applies the "intro" and "outro" cells, which are specified by the subclass and views.
  2697 + // `row` is an optional row number.
  2698 + rowHtml: function(rowType, row) {
  2699 + var renderCell = this.getHtmlRenderer('cell', rowType);
  2700 + var rowCellHtml = '';
  2701 + var col;
  2702 + var cell;
  2703 +
  2704 + row = row || 0;
  2705 +
  2706 + for (col = 0; col < this.colCnt; col++) {
  2707 + cell = this.getCell(row, col);
  2708 + rowCellHtml += renderCell(cell);
  2709 + }
  2710 +
  2711 + rowCellHtml = this.bookendCells(rowCellHtml, rowType, row); // apply intro and outro
  2712 +
  2713 + return '<tr>' + rowCellHtml + '</tr>';
  2714 + },
  2715 +
  2716 +
  2717 + // Applies the "intro" and "outro" HTML to the given cells.
  2718 + // Intro means the leftmost cell when the calendar is LTR and the rightmost cell when RTL. Vice-versa for outro.
  2719 + // `cells` can be an HTML string of <td>'s or a jQuery <tr> element
  2720 + // `row` is an optional row number.
  2721 + bookendCells: function(cells, rowType, row) {
  2722 + var intro = this.getHtmlRenderer('intro', rowType)(row || 0);
  2723 + var outro = this.getHtmlRenderer('outro', rowType)(row || 0);
  2724 + var prependHtml = this.isRTL ? outro : intro;
  2725 + var appendHtml = this.isRTL ? intro : outro;
  2726 +
  2727 + if (typeof cells === 'string') {
  2728 + return prependHtml + cells + appendHtml;
  2729 + }
  2730 + else { // a jQuery <tr> element
  2731 + return cells.prepend(prependHtml).append(appendHtml);
  2732 + }
  2733 + },
  2734 +
  2735 +
  2736 + // Returns an HTML-rendering function given a specific `rendererName` (like cell, intro, or outro) and a specific
  2737 + // `rowType` (like day, eventSkeleton, helperSkeleton), which is optional.
  2738 + // If a renderer for the specific rowType doesn't exist, it will fall back to a generic renderer.
  2739 + // We will query the View object first for any custom rendering functions, then the methods of the subclass.
  2740 + getHtmlRenderer: function(rendererName, rowType) {
  2741 + var view = this.view;
  2742 + var generalName; // like "cellHtml"
  2743 + var specificName; // like "dayCellHtml". based on rowType
  2744 + var provider; // either the View or the RowRenderer subclass, whichever provided the method
  2745 + var renderer;
  2746 +
  2747 + generalName = rendererName + 'Html';
  2748 + if (rowType) {
  2749 + specificName = rowType + capitaliseFirstLetter(rendererName) + 'Html';
  2750 + }
  2751 +
  2752 + if (specificName && (renderer = view[specificName])) {
  2753 + provider = view;
  2754 + }
  2755 + else if (specificName && (renderer = this[specificName])) {
  2756 + provider = this;
  2757 + }
  2758 + else if ((renderer = view[generalName])) {
  2759 + provider = view;
  2760 + }
  2761 + else if ((renderer = this[generalName])) {
  2762 + provider = this;
  2763 + }
  2764 +
  2765 + if (typeof renderer === 'function') {
  2766 + return function() {
  2767 + return renderer.apply(provider, arguments) || ''; // use correct `this` and always return a string
  2768 + };
  2769 + }
  2770 +
  2771 + // the rendered can be a plain string as well. if not specified, always an empty string.
  2772 + return function() {
  2773 + return renderer || '';
  2774 + };
  2775 + }
  2776 +
  2777 +});
  2778 +
  2779 +;;
  2780 +
  2781 +/* An abstract class comprised of a "grid" of cells that each represent a specific datetime
  2782 +----------------------------------------------------------------------------------------------------------------------*/
  2783 +
  2784 +var Grid = fc.Grid = RowRenderer.extend({
  2785 +
  2786 + start: null, // the date of the first cell
  2787 + end: null, // the date after the last cell
  2788 +
  2789 + rowCnt: 0, // number of rows
  2790 + colCnt: 0, // number of cols
  2791 + rowData: null, // array of objects, holding misc data for each row
  2792 + colData: null, // array of objects, holding misc data for each column
  2793 +
  2794 + el: null, // the containing element
  2795 + coordMap: null, // a GridCoordMap that converts pixel values to datetimes
  2796 + elsByFill: null, // a hash of jQuery element sets used for rendering each fill. Keyed by fill name.
  2797 +
  2798 + externalDragStartProxy: null, // binds the Grid's scope to externalDragStart (in DayGrid.events)
  2799 +
  2800 + // derived from options
  2801 + colHeadFormat: null, // TODO: move to another class. not applicable to all Grids
  2802 + eventTimeFormat: null,
  2803 + displayEventTime: null,
  2804 + displayEventEnd: null,
  2805 +
  2806 + // if all cells are the same length of time, the duration they all share. optional.
  2807 + // when defined, allows the computeCellRange shortcut, as well as improved resizing behavior.
  2808 + cellDuration: null,
  2809 +
  2810 + // if defined, holds the unit identified (ex: "year" or "month") that determines the level of granularity
  2811 + // of the date cells. if not defined, assumes to be day and time granularity.
  2812 + largeUnit: null,
  2813 +
  2814 +
  2815 + constructor: function() {
  2816 + RowRenderer.apply(this, arguments); // call the super-constructor
  2817 +
  2818 + this.coordMap = new GridCoordMap(this);
  2819 + this.elsByFill = {};
  2820 + this.externalDragStartProxy = proxy(this, 'externalDragStart');
  2821 + },
  2822 +
  2823 +
  2824 + /* Options
  2825 + ------------------------------------------------------------------------------------------------------------------*/
  2826 +
  2827 +
  2828 + // Generates the format string used for the text in column headers, if not explicitly defined by 'columnFormat'
  2829 + // TODO: move to another class. not applicable to all Grids
  2830 + computeColHeadFormat: function() {
  2831 + // subclasses must implement if they want to use headHtml()
  2832 + },
  2833 +
  2834 +
  2835 + // Generates the format string used for event time text, if not explicitly defined by 'timeFormat'
  2836 + computeEventTimeFormat: function() {
  2837 + return this.view.opt('smallTimeFormat');
  2838 + },
  2839 +
  2840 +
  2841 + // Determines whether events should have their end times displayed, if not explicitly defined by 'displayEventTime'.
  2842 + // Only applies to non-all-day events.
  2843 + computeDisplayEventTime: function() {
  2844 + return true;
  2845 + },
  2846 +
  2847 +
  2848 + // Determines whether events should have their end times displayed, if not explicitly defined by 'displayEventEnd'
  2849 + computeDisplayEventEnd: function() {
  2850 + return true;
  2851 + },
  2852 +
  2853 +
  2854 + /* Dates
  2855 + ------------------------------------------------------------------------------------------------------------------*/
  2856 +
  2857 +
  2858 + // Tells the grid about what period of time to display. Grid will subsequently compute dates for cell system.
  2859 + setRange: function(range) {
  2860 + var view = this.view;
  2861 + var displayEventTime;
  2862 + var displayEventEnd;
  2863 +
  2864 + this.start = range.start.clone();
  2865 + this.end = range.end.clone();
  2866 +
  2867 + this.rowData = [];
  2868 + this.colData = [];
  2869 + this.updateCells();
  2870 +
  2871 + // Populate option-derived settings. Look for override first, then compute if necessary.
  2872 + this.colHeadFormat = view.opt('columnFormat') || this.computeColHeadFormat();
  2873 +
  2874 + this.eventTimeFormat =
  2875 + view.opt('eventTimeFormat') ||
  2876 + view.opt('timeFormat') || // deprecated
  2877 + this.computeEventTimeFormat();
  2878 +
  2879 + displayEventTime = view.opt('displayEventTime');
  2880 + if (displayEventTime == null) {
  2881 + displayEventTime = this.computeDisplayEventTime(); // might be based off of range
  2882 + }
  2883 +
  2884 + displayEventEnd = view.opt('displayEventEnd');
  2885 + if (displayEventEnd == null) {
  2886 + displayEventEnd = this.computeDisplayEventEnd(); // might be based off of range
  2887 + }
  2888 +
  2889 + this.displayEventTime = displayEventTime;
  2890 + this.displayEventEnd = displayEventEnd;
  2891 + },
  2892 +
  2893 +
  2894 + // Responsible for setting rowCnt/colCnt and any other row/col data
  2895 + updateCells: function() {
  2896 + // subclasses must implement
  2897 + },
  2898 +
  2899 +
  2900 + // Converts a range with an inclusive `start` and an exclusive `end` into an array of segment objects
  2901 + rangeToSegs: function(range) {
  2902 + // subclasses must implement
  2903 + },
  2904 +
  2905 +
  2906 + // Diffs the two dates, returning a duration, based on granularity of the grid
  2907 + diffDates: function(a, b) {
  2908 + if (this.largeUnit) {
  2909 + return diffByUnit(a, b, this.largeUnit);
  2910 + }
  2911 + else {
  2912 + return diffDayTime(a, b);
  2913 + }
  2914 + },
  2915 +
  2916 +
  2917 + /* Cells
  2918 + ------------------------------------------------------------------------------------------------------------------*/
  2919 + // NOTE: columns are ordered left-to-right
  2920 +
  2921 +
  2922 + // Gets an object containing row/col number, misc data, and range information about the cell.
  2923 + // Accepts row/col values, an object with row/col properties, or a single-number offset from the first cell.
  2924 + getCell: function(row, col) {
  2925 + var cell;
  2926 +
  2927 + if (col == null) {
  2928 + if (typeof row === 'number') { // a single-number offset
  2929 + col = row % this.colCnt;
  2930 + row = Math.floor(row / this.colCnt);
  2931 + }
  2932 + else { // an object with row/col properties
  2933 + col = row.col;
  2934 + row = row.row;
  2935 + }
  2936 + }
  2937 +
  2938 + cell = { row: row, col: col };
  2939 +
  2940 + $.extend(cell, this.getRowData(row), this.getColData(col));
  2941 + $.extend(cell, this.computeCellRange(cell));
  2942 +
  2943 + return cell;
  2944 + },
  2945 +
  2946 +
  2947 + // Given a cell object with index and misc data, generates a range object
  2948 + // If the grid is leveraging cellDuration, this doesn't need to be defined. Only computeCellDate does.
  2949 + // If being overridden, should return a range with reference-free date copies.
  2950 + computeCellRange: function(cell) {
  2951 + var date = this.computeCellDate(cell);
  2952 +
  2953 + return {
  2954 + start: date,
  2955 + end: date.clone().add(this.cellDuration)
  2956 + };
  2957 + },
  2958 +
  2959 +
  2960 + // Given a cell, returns its start date. Should return a reference-free date copy.
  2961 + computeCellDate: function(cell) {
  2962 + // subclasses can implement
  2963 + },
  2964 +
  2965 +
  2966 + // Retrieves misc data about the given row
  2967 + getRowData: function(row) {
  2968 + return this.rowData[row] || {};
  2969 + },
  2970 +
  2971 +
  2972 + // Retrieves misc data baout the given column
  2973 + getColData: function(col) {
  2974 + return this.colData[col] || {};
  2975 + },
  2976 +
  2977 +
  2978 + // Retrieves the element representing the given row
  2979 + getRowEl: function(row) {
  2980 + // subclasses should implement if leveraging the default getCellDayEl() or computeRowCoords()
  2981 + },
  2982 +
  2983 +
  2984 + // Retrieves the element representing the given column
  2985 + getColEl: function(col) {
  2986 + // subclasses should implement if leveraging the default getCellDayEl() or computeColCoords()
  2987 + },
  2988 +
  2989 +
  2990 + // Given a cell object, returns the element that represents the cell's whole-day
  2991 + getCellDayEl: function(cell) {
  2992 + return this.getColEl(cell.col) || this.getRowEl(cell.row);
  2993 + },
  2994 +
  2995 +
  2996 + /* Cell Coordinates
  2997 + ------------------------------------------------------------------------------------------------------------------*/
  2998 +
  2999 +
  3000 + // Computes the top/bottom coordinates of all rows.
  3001 + // By default, queries the dimensions of the element provided by getRowEl().
  3002 + computeRowCoords: function() {
  3003 + var items = [];
  3004 + var i, el;
  3005 + var top;
  3006 +
  3007 + for (i = 0; i < this.rowCnt; i++) {
  3008 + el = this.getRowEl(i);
  3009 + top = el.offset().top;
  3010 + items.push({
  3011 + top: top,
  3012 + bottom: top + el.outerHeight()
  3013 + });
  3014 + }
  3015 +
  3016 + return items;
  3017 + },
  3018 +
  3019 +
  3020 + // Computes the left/right coordinates of all rows.
  3021 + // By default, queries the dimensions of the element provided by getColEl(). Columns can be LTR or RTL.
  3022 + computeColCoords: function() {
  3023 + var items = [];
  3024 + var i, el;
  3025 + var left;
  3026 +
  3027 + for (i = 0; i < this.colCnt; i++) {
  3028 + el = this.getColEl(i);
  3029 + left = el.offset().left;
  3030 + items.push({
  3031 + left: left,
  3032 + right: left + el.outerWidth()
  3033 + });
  3034 + }
  3035 +
  3036 + return items;
  3037 + },
  3038 +
  3039 +
  3040 + /* Rendering
  3041 + ------------------------------------------------------------------------------------------------------------------*/
  3042 +
  3043 +
  3044 + // Sets the container element that the grid should render inside of.
  3045 + // Does other DOM-related initializations.
  3046 + setElement: function(el) {
  3047 + var _this = this;
  3048 +
  3049 + this.el = el;
  3050 +
  3051 + // attach a handler to the grid's root element.
  3052 + // jQuery will take care of unregistering them when removeElement gets called.
  3053 + el.on('mousedown', function(ev) {
  3054 + if (
  3055 + !$(ev.target).is('.fc-event-container *, .fc-more') && // not an an event element, or "more.." link
  3056 + !$(ev.target).closest('.fc-popover').length // not on a popover (like the "more.." events one)
  3057 + ) {
  3058 + _this.dayMousedown(ev);
  3059 + }
  3060 + });
  3061 +
  3062 + // attach event-element-related handlers. in Grid.events
  3063 + // same garbage collection note as above.
  3064 + this.bindSegHandlers();
  3065 +
  3066 + this.bindGlobalHandlers();
  3067 + },
  3068 +
  3069 +
  3070 + // Removes the grid's container element from the DOM. Undoes any other DOM-related attachments.
  3071 + // DOES NOT remove any content before hand (doens't clear events or call destroyDates), unlike View
  3072 + removeElement: function() {
  3073 + this.unbindGlobalHandlers();
  3074 +
  3075 + this.el.remove();
  3076 +
  3077 + // NOTE: we don't null-out this.el for the same reasons we don't do it within View::removeElement
  3078 + },
  3079 +
  3080 +
  3081 + // Renders the basic structure of grid view before any content is rendered
  3082 + renderSkeleton: function() {
  3083 + // subclasses should implement
  3084 + },
  3085 +
  3086 +
  3087 + // Renders the grid's date-related content (like cells that represent days/times).
  3088 + // Assumes setRange has already been called and the skeleton has already been rendered.
  3089 + renderDates: function() {
  3090 + // subclasses should implement
  3091 + },
  3092 +
  3093 +
  3094 + // Unrenders the grid's date-related content
  3095 + destroyDates: function() {
  3096 + // subclasses should implement
  3097 + },
  3098 +
  3099 +
  3100 + /* Handlers
  3101 + ------------------------------------------------------------------------------------------------------------------*/
  3102 +
  3103 +
  3104 + // Binds DOM handlers to elements that reside outside the grid, such as the document
  3105 + bindGlobalHandlers: function() {
  3106 + $(document).on('dragstart sortstart', this.externalDragStartProxy); // jqui
  3107 + },
  3108 +
  3109 +
  3110 + // Unbinds DOM handlers from elements that reside outside the grid
  3111 + unbindGlobalHandlers: function() {
  3112 + $(document).off('dragstart sortstart', this.externalDragStartProxy); // jqui
  3113 + },
  3114 +
  3115 +
  3116 + // Process a mousedown on an element that represents a day. For day clicking and selecting.
  3117 + dayMousedown: function(ev) {
  3118 + var _this = this;
  3119 + var view = this.view;
  3120 + var isSelectable = view.opt('selectable');
  3121 + var dayClickCell; // null if invalid dayClick
  3122 + var selectionRange; // null if invalid selection
  3123 +
  3124 + // this listener tracks a mousedown on a day element, and a subsequent drag.
  3125 + // if the drag ends on the same day, it is a 'dayClick'.
  3126 + // if 'selectable' is enabled, this listener also detects selections.
  3127 + var dragListener = new CellDragListener(this.coordMap, {
  3128 + //distance: 5, // needs more work if we want dayClick to fire correctly
  3129 + scroll: view.opt('dragScroll'),
  3130 + dragStart: function() {
  3131 + view.unselect(); // since we could be rendering a new selection, we want to clear any old one
  3132 + },
  3133 + cellOver: function(cell, isOrig, origCell) {
  3134 + if (origCell) { // click needs to have started on a cell
  3135 + dayClickCell = isOrig ? cell : null; // single-cell selection is a day click
  3136 + if (isSelectable) {
  3137 + selectionRange = _this.computeSelection(origCell, cell);
  3138 + if (selectionRange) {
  3139 + _this.renderSelection(selectionRange);
  3140 + }
  3141 + else {
  3142 + disableCursor();
  3143 + }
  3144 + }
  3145 + }
  3146 + },
  3147 + cellOut: function(cell) {
  3148 + dayClickCell = null;
  3149 + selectionRange = null;
  3150 + _this.destroySelection();
  3151 + enableCursor();
  3152 + },
  3153 + listenStop: function(ev) {
  3154 + if (dayClickCell) {
  3155 + view.trigger('dayClick', _this.getCellDayEl(dayClickCell), dayClickCell.start, ev);
  3156 + }
  3157 + if (selectionRange) {
  3158 + // the selection will already have been rendered. just report it
  3159 + view.reportSelection(selectionRange, ev);
  3160 + }
  3161 + enableCursor();
  3162 + }
  3163 + });
  3164 +
  3165 + dragListener.mousedown(ev); // start listening, which will eventually initiate a dragStart
  3166 + },
  3167 +
  3168 +
  3169 + /* Event Helper
  3170 + ------------------------------------------------------------------------------------------------------------------*/
  3171 + // TODO: should probably move this to Grid.events, like we did event dragging / resizing
  3172 +
  3173 +
  3174 + // Renders a mock event over the given range
  3175 + renderRangeHelper: function(range, sourceSeg) {
  3176 + var fakeEvent = this.fabricateHelperEvent(range, sourceSeg);
  3177 +
  3178 + this.renderHelper(fakeEvent, sourceSeg); // do the actual rendering
  3179 + },
  3180 +
  3181 +
  3182 + // Builds a fake event given a date range it should cover, and a segment is should be inspired from.
  3183 + // The range's end can be null, in which case the mock event that is rendered will have a null end time.
  3184 + // `sourceSeg` is the internal segment object involved in the drag. If null, something external is dragging.
  3185 + fabricateHelperEvent: function(range, sourceSeg) {
  3186 + var fakeEvent = sourceSeg ? createObject(sourceSeg.event) : {}; // mask the original event object if possible
  3187 +
  3188 + fakeEvent.start = range.start.clone();
  3189 + fakeEvent.end = range.end ? range.end.clone() : null;
  3190 + fakeEvent.allDay = null; // force it to be freshly computed by normalizeEventRange
  3191 + this.view.calendar.normalizeEventRange(fakeEvent);
  3192 +
  3193 + // this extra className will be useful for differentiating real events from mock events in CSS
  3194 + fakeEvent.className = (fakeEvent.className || []).concat('fc-helper');
  3195 +
  3196 + // if something external is being dragged in, don't render a resizer
  3197 + if (!sourceSeg) {
  3198 + fakeEvent.editable = false;
  3199 + }
  3200 +
  3201 + return fakeEvent;
  3202 + },
  3203 +
  3204 +
  3205 + // Renders a mock event
  3206 + renderHelper: function(event, sourceSeg) {
  3207 + // subclasses must implement
  3208 + },
  3209 +
  3210 +
  3211 + // Unrenders a mock event
  3212 + destroyHelper: function() {
  3213 + // subclasses must implement
  3214 + },
  3215 +
  3216 +
  3217 + /* Selection
  3218 + ------------------------------------------------------------------------------------------------------------------*/
  3219 +
  3220 +
  3221 + // Renders a visual indication of a selection. Will highlight by default but can be overridden by subclasses.
  3222 + renderSelection: function(range) {
  3223 + this.renderHighlight(range);
  3224 + },
  3225 +
  3226 +
  3227 + // Unrenders any visual indications of a selection. Will unrender a highlight by default.
  3228 + destroySelection: function() {
  3229 + this.destroyHighlight();
  3230 + },
  3231 +
  3232 +
  3233 + // Given the first and last cells of a selection, returns a range object.
  3234 + // Will return something falsy if the selection is invalid (when outside of selectionConstraint for example).
  3235 + // Subclasses can override and provide additional data in the range object. Will be passed to renderSelection().
  3236 + computeSelection: function(firstCell, lastCell) {
  3237 + var dates = [
  3238 + firstCell.start,
  3239 + firstCell.end,
  3240 + lastCell.start,
  3241 + lastCell.end
  3242 + ];
  3243 + var range;
  3244 +
  3245 + dates.sort(compareNumbers); // sorts chronologically. works with Moments
  3246 +
  3247 + range = {
  3248 + start: dates[0].clone(),
  3249 + end: dates[3].clone()
  3250 + };
  3251 +
  3252 + if (!this.view.calendar.isSelectionRangeAllowed(range)) {
  3253 + return null;
  3254 + }
  3255 +
  3256 + return range;
  3257 + },
  3258 +
  3259 +
  3260 + /* Highlight
  3261 + ------------------------------------------------------------------------------------------------------------------*/
  3262 +
  3263 +
  3264 + // Renders an emphasis on the given date range. `start` is inclusive. `end` is exclusive.
  3265 + renderHighlight: function(range) {
  3266 + this.renderFill('highlight', this.rangeToSegs(range));
  3267 + },
  3268 +
  3269 +
  3270 + // Unrenders the emphasis on a date range
  3271 + destroyHighlight: function() {
  3272 + this.destroyFill('highlight');
  3273 + },
  3274 +
  3275 +
  3276 + // Generates an array of classNames for rendering the highlight. Used by the fill system.
  3277 + highlightSegClasses: function() {
  3278 + return [ 'fc-highlight' ];
  3279 + },
  3280 +
  3281 +
  3282 + /* Fill System (highlight, background events, business hours)
  3283 + ------------------------------------------------------------------------------------------------------------------*/
  3284 +
  3285 +
  3286 + // Renders a set of rectangles over the given segments of time.
  3287 + // Returns a subset of segs, the segs that were actually rendered.
  3288 + // Responsible for populating this.elsByFill. TODO: better API for expressing this requirement
  3289 + renderFill: function(type, segs) {
  3290 + // subclasses must implement
  3291 + },
  3292 +
  3293 +
  3294 + // Unrenders a specific type of fill that is currently rendered on the grid
  3295 + destroyFill: function(type) {
  3296 + var el = this.elsByFill[type];
  3297 +
  3298 + if (el) {
  3299 + el.remove();
  3300 + delete this.elsByFill[type];
  3301 + }
  3302 + },
  3303 +
  3304 +
  3305 + // Renders and assigns an `el` property for each fill segment. Generic enough to work with different types.
  3306 + // Only returns segments that successfully rendered.
  3307 + // To be harnessed by renderFill (implemented by subclasses).
  3308 + // Analagous to renderFgSegEls.
  3309 + renderFillSegEls: function(type, segs) {
  3310 + var _this = this;
  3311 + var segElMethod = this[type + 'SegEl'];
  3312 + var html = '';
  3313 + var renderedSegs = [];
  3314 + var i;
  3315 +
  3316 + if (segs.length) {
  3317 +
  3318 + // build a large concatenation of segment HTML
  3319 + for (i = 0; i < segs.length; i++) {
  3320 + html += this.fillSegHtml(type, segs[i]);
  3321 + }
  3322 +
  3323 + // Grab individual elements from the combined HTML string. Use each as the default rendering.
  3324 + // Then, compute the 'el' for each segment.
  3325 + $(html).each(function(i, node) {
  3326 + var seg = segs[i];
  3327 + var el = $(node);
  3328 +
  3329 + // allow custom filter methods per-type
  3330 + if (segElMethod) {
  3331 + el = segElMethod.call(_this, seg, el);
  3332 + }
  3333 +
  3334 + if (el) { // custom filters did not cancel the render
  3335 + el = $(el); // allow custom filter to return raw DOM node
  3336 +
  3337 + // correct element type? (would be bad if a non-TD were inserted into a table for example)
  3338 + if (el.is(_this.fillSegTag)) {
  3339 + seg.el = el;
  3340 + renderedSegs.push(seg);
  3341 + }
  3342 + }
  3343 + });
  3344 + }
  3345 +
  3346 + return renderedSegs;
  3347 + },
  3348 +
  3349 +
  3350 + fillSegTag: 'div', // subclasses can override
  3351 +
  3352 +
  3353 + // Builds the HTML needed for one fill segment. Generic enought o work with different types.
  3354 + fillSegHtml: function(type, seg) {
  3355 +
  3356 + // custom hooks per-type
  3357 + var classesMethod = this[type + 'SegClasses'];
  3358 + var cssMethod = this[type + 'SegCss'];
  3359 +
  3360 + var classes = classesMethod ? classesMethod.call(this, seg) : [];
  3361 + var css = cssToStr(cssMethod ? cssMethod.call(this, seg) : {});
  3362 +
  3363 + return '<' + this.fillSegTag +
  3364 + (classes.length ? ' class="' + classes.join(' ') + '"' : '') +
  3365 + (css ? ' style="' + css + '"' : '') +
  3366 + ' />';
  3367 + },
  3368 +
  3369 +
  3370 + /* Generic rendering utilities for subclasses
  3371 + ------------------------------------------------------------------------------------------------------------------*/
  3372 +
  3373 +
  3374 + // Renders a day-of-week header row.
  3375 + // TODO: move to another class. not applicable to all Grids
  3376 + headHtml: function() {
  3377 + return '' +
  3378 + '<div class="fc-row ' + this.view.widgetHeaderClass + '">' +
  3379 + '<table>' +
  3380 + '<thead>' +
  3381 + this.rowHtml('head') + // leverages RowRenderer
  3382 + '</thead>' +
  3383 + '</table>' +
  3384 + '</div>';
  3385 + },
  3386 +
  3387 +
  3388 + // Used by the `headHtml` method, via RowRenderer, for rendering the HTML of a day-of-week header cell
  3389 + // TODO: move to another class. not applicable to all Grids
  3390 + headCellHtml: function(cell) {
  3391 + var view = this.view;
  3392 + var date = cell.start;
  3393 +
  3394 + return '' +
  3395 + '<th class="fc-day-header ' + view.widgetHeaderClass + ' fc-' + dayIDs[date.day()] + '">' +
  3396 + htmlEscape(date.format(this.colHeadFormat)) +
  3397 + '</th>';
  3398 + },
  3399 +
  3400 +
  3401 + // Renders the HTML for a single-day background cell
  3402 + bgCellHtml: function(cell) {
  3403 + var view = this.view;
  3404 + var date = cell.start;
  3405 + var classes = this.getDayClasses(date);
  3406 +
  3407 + classes.unshift('fc-day', view.widgetContentClass);
  3408 +
  3409 + return '<td class="' + classes.join(' ') + '"' +
  3410 + ' data-date="' + date.format('YYYY-MM-DD') + '"' + // if date has a time, won't format it
  3411 + '></td>';
  3412 + },
  3413 +
  3414 +
  3415 + // Computes HTML classNames for a single-day cell
  3416 + getDayClasses: function(date) {
  3417 + var view = this.view;
  3418 + var today = view.calendar.getNow().stripTime();
  3419 + var classes = [ 'fc-' + dayIDs[date.day()] ];
  3420 +
  3421 + if (
  3422 + view.intervalDuration.as('months') == 1 &&
  3423 + date.month() != view.intervalStart.month()
  3424 + ) {
  3425 + classes.push('fc-other-month');
  3426 + }
  3427 +
  3428 + if (date.isSame(today, 'day')) {
  3429 + classes.push(
  3430 + 'fc-today',
  3431 + view.highlightStateClass
  3432 + );
  3433 + }
  3434 + else if (date < today) {
  3435 + classes.push('fc-past');
  3436 + }
  3437 + else {
  3438 + classes.push('fc-future');
  3439 + }
  3440 +
  3441 + return classes;
  3442 + }
  3443 +
  3444 +});
  3445 +
  3446 +;;
  3447 +
  3448 +/* Event-rendering and event-interaction methods for the abstract Grid class
  3449 +----------------------------------------------------------------------------------------------------------------------*/
  3450 +
  3451 +Grid.mixin({
  3452 +
  3453 + mousedOverSeg: null, // the segment object the user's mouse is over. null if over nothing
  3454 + isDraggingSeg: false, // is a segment being dragged? boolean
  3455 + isResizingSeg: false, // is a segment being resized? boolean
  3456 + isDraggingExternal: false, // jqui-dragging an external element? boolean
  3457 + segs: null, // the event segments currently rendered in the grid
  3458 +
  3459 +
  3460 + // Renders the given events onto the grid
  3461 + renderEvents: function(events) {
  3462 + var segs = this.eventsToSegs(events);
  3463 + var bgSegs = [];
  3464 + var fgSegs = [];
  3465 + var i, seg;
  3466 +
  3467 + for (i = 0; i < segs.length; i++) {
  3468 + seg = segs[i];
  3469 +
  3470 + if (isBgEvent(seg.event)) {
  3471 + bgSegs.push(seg);
  3472 + }
  3473 + else {
  3474 + fgSegs.push(seg);
  3475 + }
  3476 + }
  3477 +
  3478 + // Render each different type of segment.
  3479 + // Each function may return a subset of the segs, segs that were actually rendered.
  3480 + bgSegs = this.renderBgSegs(bgSegs) || bgSegs;
  3481 + fgSegs = this.renderFgSegs(fgSegs) || fgSegs;
  3482 +
  3483 + this.segs = bgSegs.concat(fgSegs);
  3484 + },
  3485 +
  3486 +
  3487 + // Unrenders all events currently rendered on the grid
  3488 + destroyEvents: function() {
  3489 + this.triggerSegMouseout(); // trigger an eventMouseout if user's mouse is over an event
  3490 +
  3491 + this.destroyFgSegs();
  3492 + this.destroyBgSegs();
  3493 +
  3494 + this.segs = null;
  3495 + },
  3496 +
  3497 +
  3498 + // Retrieves all rendered segment objects currently rendered on the grid
  3499 + getEventSegs: function() {
  3500 + return this.segs || [];
  3501 + },
  3502 +
  3503 +
  3504 + /* Foreground Segment Rendering
  3505 + ------------------------------------------------------------------------------------------------------------------*/
  3506 +
  3507 +
  3508 + // Renders foreground event segments onto the grid. May return a subset of segs that were rendered.
  3509 + renderFgSegs: function(segs) {
  3510 + // subclasses must implement
  3511 + },
  3512 +
  3513 +
  3514 + // Unrenders all currently rendered foreground segments
  3515 + destroyFgSegs: function() {
  3516 + // subclasses must implement
  3517 + },
  3518 +
  3519 +
  3520 + // Renders and assigns an `el` property for each foreground event segment.
  3521 + // Only returns segments that successfully rendered.
  3522 + // A utility that subclasses may use.
  3523 + renderFgSegEls: function(segs, disableResizing) {
  3524 + var view = this.view;
  3525 + var html = '';
  3526 + var renderedSegs = [];
  3527 + var i;
  3528 +
  3529 + if (segs.length) { // don't build an empty html string
  3530 +
  3531 + // build a large concatenation of event segment HTML
  3532 + for (i = 0; i < segs.length; i++) {
  3533 + html += this.fgSegHtml(segs[i], disableResizing);
  3534 + }
  3535 +
  3536 + // Grab individual elements from the combined HTML string. Use each as the default rendering.
  3537 + // Then, compute the 'el' for each segment. An el might be null if the eventRender callback returned false.
  3538 + $(html).each(function(i, node) {
  3539 + var seg = segs[i];
  3540 + var el = view.resolveEventEl(seg.event, $(node));
  3541 +
  3542 + if (el) {
  3543 + el.data('fc-seg', seg); // used by handlers
  3544 + seg.el = el;
  3545 + renderedSegs.push(seg);
  3546 + }
  3547 + });
  3548 + }
  3549 +
  3550 + return renderedSegs;
  3551 + },
  3552 +
  3553 +
  3554 + // Generates the HTML for the default rendering of a foreground event segment. Used by renderFgSegEls()
  3555 + fgSegHtml: function(seg, disableResizing) {
  3556 + // subclasses should implement
  3557 + },
  3558 +
  3559 +
  3560 + /* Background Segment Rendering
  3561 + ------------------------------------------------------------------------------------------------------------------*/
  3562 +
  3563 +
  3564 + // Renders the given background event segments onto the grid.
  3565 + // Returns a subset of the segs that were actually rendered.
  3566 + renderBgSegs: function(segs) {
  3567 + return this.renderFill('bgEvent', segs);
  3568 + },
  3569 +
  3570 +
  3571 + // Unrenders all the currently rendered background event segments
  3572 + destroyBgSegs: function() {
  3573 + this.destroyFill('bgEvent');
  3574 + },
  3575 +
  3576 +
  3577 + // Renders a background event element, given the default rendering. Called by the fill system.
  3578 + bgEventSegEl: function(seg, el) {
  3579 + return this.view.resolveEventEl(seg.event, el); // will filter through eventRender
  3580 + },
  3581 +
  3582 +
  3583 + // Generates an array of classNames to be used for the default rendering of a background event.
  3584 + // Called by the fill system.
  3585 + bgEventSegClasses: function(seg) {
  3586 + var event = seg.event;
  3587 + var source = event.source || {};
  3588 +
  3589 + return [ 'fc-bgevent' ].concat(
  3590 + event.className,
  3591 + source.className || []
  3592 + );
  3593 + },
  3594 +
  3595 +
  3596 + // Generates a semicolon-separated CSS string to be used for the default rendering of a background event.
  3597 + // Called by the fill system.
  3598 + // TODO: consolidate with getEventSkinCss?
  3599 + bgEventSegCss: function(seg) {
  3600 + var view = this.view;
  3601 + var event = seg.event;
  3602 + var source = event.source || {};
  3603 +
  3604 + return {
  3605 + 'background-color':
  3606 + event.backgroundColor ||
  3607 + event.color ||
  3608 + source.backgroundColor ||
  3609 + source.color ||
  3610 + view.opt('eventBackgroundColor') ||
  3611 + view.opt('eventColor')
  3612 + };
  3613 + },
  3614 +
  3615 +
  3616 + // Generates an array of classNames to be used for the rendering business hours overlay. Called by the fill system.
  3617 + businessHoursSegClasses: function(seg) {
  3618 + return [ 'fc-nonbusiness', 'fc-bgevent' ];
  3619 + },
  3620 +
  3621 +
  3622 + /* Handlers
  3623 + ------------------------------------------------------------------------------------------------------------------*/
  3624 +
  3625 +
  3626 + // Attaches event-element-related handlers to the container element and leverage bubbling
  3627 + bindSegHandlers: function() {
  3628 + var _this = this;
  3629 + var view = this.view;
  3630 +
  3631 + $.each(
  3632 + {
  3633 + mouseenter: function(seg, ev) {
  3634 + _this.triggerSegMouseover(seg, ev);
  3635 + },
  3636 + mouseleave: function(seg, ev) {
  3637 + _this.triggerSegMouseout(seg, ev);
  3638 + },
  3639 + click: function(seg, ev) {
  3640 + return view.trigger('eventClick', this, seg.event, ev); // can return `false` to cancel
  3641 + },
  3642 + mousedown: function(seg, ev) {
  3643 + if ($(ev.target).is('.fc-resizer') && view.isEventResizable(seg.event)) {
  3644 + _this.segResizeMousedown(seg, ev, $(ev.target).is('.fc-start-resizer'));
  3645 + }
  3646 + else if (view.isEventDraggable(seg.event)) {
  3647 + _this.segDragMousedown(seg, ev);
  3648 + }
  3649 + }
  3650 + },
  3651 + function(name, func) {
  3652 + // attach the handler to the container element and only listen for real event elements via bubbling
  3653 + _this.el.on(name, '.fc-event-container > *', function(ev) {
  3654 + var seg = $(this).data('fc-seg'); // grab segment data. put there by View::renderEvents
  3655 +
  3656 + // only call the handlers if there is not a drag/resize in progress
  3657 + if (seg && !_this.isDraggingSeg && !_this.isResizingSeg) {
  3658 + return func.call(this, seg, ev); // `this` will be the event element
  3659 + }
  3660 + });
  3661 + }
  3662 + );
  3663 + },
  3664 +
  3665 +
  3666 + // Updates internal state and triggers handlers for when an event element is moused over
  3667 + triggerSegMouseover: function(seg, ev) {
  3668 + if (!this.mousedOverSeg) {
  3669 + this.mousedOverSeg = seg;
  3670 + this.view.trigger('eventMouseover', seg.el[0], seg.event, ev);
  3671 + }
  3672 + },
  3673 +
  3674 +
  3675 + // Updates internal state and triggers handlers for when an event element is moused out.
  3676 + // Can be given no arguments, in which case it will mouseout the segment that was previously moused over.
  3677 + triggerSegMouseout: function(seg, ev) {
  3678 + ev = ev || {}; // if given no args, make a mock mouse event
  3679 +
  3680 + if (this.mousedOverSeg) {
  3681 + seg = seg || this.mousedOverSeg; // if given no args, use the currently moused-over segment
  3682 + this.mousedOverSeg = null;
  3683 + this.view.trigger('eventMouseout', seg.el[0], seg.event, ev);
  3684 + }
  3685 + },
  3686 +
  3687 +
  3688 + /* Event Dragging
  3689 + ------------------------------------------------------------------------------------------------------------------*/
  3690 +
  3691 +
  3692 + // Called when the user does a mousedown on an event, which might lead to dragging.
  3693 + // Generic enough to work with any type of Grid.
  3694 + segDragMousedown: function(seg, ev) {
  3695 + var _this = this;
  3696 + var view = this.view;
  3697 + var calendar = view.calendar;
  3698 + var el = seg.el;
  3699 + var event = seg.event;
  3700 + var dropLocation;
  3701 +
  3702 + // A clone of the original element that will move with the mouse
  3703 + var mouseFollower = new MouseFollower(seg.el, {
  3704 + parentEl: view.el,
  3705 + opacity: view.opt('dragOpacity'),
  3706 + revertDuration: view.opt('dragRevertDuration'),
  3707 + zIndex: 2 // one above the .fc-view
  3708 + });
  3709 +
  3710 + // Tracks mouse movement over the *view's* coordinate map. Allows dragging and dropping between subcomponents
  3711 + // of the view.
  3712 + var dragListener = new CellDragListener(view.coordMap, {
  3713 + distance: 5,
  3714 + scroll: view.opt('dragScroll'),
  3715 + subjectEl: el,
  3716 + subjectCenter: true,
  3717 + listenStart: function(ev) {
  3718 + mouseFollower.hide(); // don't show until we know this is a real drag
  3719 + mouseFollower.start(ev);
  3720 + },
  3721 + dragStart: function(ev) {
  3722 + _this.triggerSegMouseout(seg, ev); // ensure a mouseout on the manipulated event has been reported
  3723 + _this.segDragStart(seg, ev);
  3724 + view.hideEvent(event); // hide all event segments. our mouseFollower will take over
  3725 + },
  3726 + cellOver: function(cell, isOrig, origCell) {
  3727 +
  3728 + // starting cell could be forced (DayGrid.limit)
  3729 + if (seg.cell) {
  3730 + origCell = seg.cell;
  3731 + }
  3732 +
  3733 + dropLocation = _this.computeEventDrop(origCell, cell, event);
  3734 +
  3735 + if (dropLocation && !calendar.isEventRangeAllowed(dropLocation, event)) {
  3736 + disableCursor();
  3737 + dropLocation = null;
  3738 + }
  3739 +
  3740 + // if a valid drop location, have the subclass render a visual indication
  3741 + if (dropLocation && view.renderDrag(dropLocation, seg)) {
  3742 + mouseFollower.hide(); // if the subclass is already using a mock event "helper", hide our own
  3743 + }
  3744 + else {
  3745 + mouseFollower.show(); // otherwise, have the helper follow the mouse (no snapping)
  3746 + }
  3747 +
  3748 + if (isOrig) {
  3749 + dropLocation = null; // needs to have moved cells to be a valid drop
  3750 + }
  3751 + },
  3752 + cellOut: function() { // called before mouse moves to a different cell OR moved out of all cells
  3753 + view.destroyDrag(); // unrender whatever was done in renderDrag
  3754 + mouseFollower.show(); // show in case we are moving out of all cells
  3755 + dropLocation = null;
  3756 + },
  3757 + cellDone: function() { // Called after a cellOut OR before a dragStop
  3758 + enableCursor();
  3759 + },
  3760 + dragStop: function(ev) {
  3761 + // do revert animation if hasn't changed. calls a callback when finished (whether animation or not)
  3762 + mouseFollower.stop(!dropLocation, function() {
  3763 + view.destroyDrag();
  3764 + view.showEvent(event);
  3765 + _this.segDragStop(seg, ev);
  3766 +
  3767 + if (dropLocation) {
  3768 + view.reportEventDrop(event, dropLocation, this.largeUnit, el, ev);
  3769 + }
  3770 + });
  3771 + },
  3772 + listenStop: function() {
  3773 + mouseFollower.stop(); // put in listenStop in case there was a mousedown but the drag never started
  3774 + }
  3775 + });
  3776 +
  3777 + dragListener.mousedown(ev); // start listening, which will eventually lead to a dragStart
  3778 + },
  3779 +
  3780 +
  3781 + // Called before event segment dragging starts
  3782 + segDragStart: function(seg, ev) {
  3783 + this.isDraggingSeg = true;
  3784 + this.view.trigger('eventDragStart', seg.el[0], seg.event, ev, {}); // last argument is jqui dummy
  3785 + },
  3786 +
  3787 +
  3788 + // Called after event segment dragging stops
  3789 + segDragStop: function(seg, ev) {
  3790 + this.isDraggingSeg = false;
  3791 + this.view.trigger('eventDragStop', seg.el[0], seg.event, ev, {}); // last argument is jqui dummy
  3792 + },
  3793 +
  3794 +
  3795 + // Given the cell an event drag began, and the cell event was dropped, calculates the new start/end/allDay
  3796 + // values for the event. Subclasses may override and set additional properties to be used by renderDrag.
  3797 + // A falsy returned value indicates an invalid drop.
  3798 + computeEventDrop: function(startCell, endCell, event) {
  3799 + var calendar = this.view.calendar;
  3800 + var dragStart = startCell.start;
  3801 + var dragEnd = endCell.start;
  3802 + var delta;
  3803 + var dropLocation;
  3804 +
  3805 + if (dragStart.hasTime() === dragEnd.hasTime()) {
  3806 + delta = this.diffDates(dragEnd, dragStart);
  3807 +
  3808 + // if an all-day event was in a timed area and it was dragged to a different time,
  3809 + // guarantee an end and adjust start/end to have times
  3810 + if (event.allDay && durationHasTime(delta)) {
  3811 + dropLocation = {
  3812 + start: event.start.clone(),
  3813 + end: calendar.getEventEnd(event), // will be an ambig day
  3814 + allDay: false // for normalizeEventRangeTimes
  3815 + };
  3816 + calendar.normalizeEventRangeTimes(dropLocation);
  3817 + }
  3818 + // othewise, work off existing values
  3819 + else {
  3820 + dropLocation = {
  3821 + start: event.start.clone(),
  3822 + end: event.end ? event.end.clone() : null,
  3823 + allDay: event.allDay // keep it the same
  3824 + };
  3825 + }
  3826 +
  3827 + dropLocation.start.add(delta);
  3828 + if (dropLocation.end) {
  3829 + dropLocation.end.add(delta);
  3830 + }
  3831 + }
  3832 + else {
  3833 + // if switching from day <-> timed, start should be reset to the dropped date, and the end cleared
  3834 + dropLocation = {
  3835 + start: dragEnd.clone(),
  3836 + end: null, // end should be cleared
  3837 + allDay: !dragEnd.hasTime()
  3838 + };
  3839 + }
  3840 +
  3841 + return dropLocation;
  3842 + },
  3843 +
  3844 +
  3845 + // Utility for apply dragOpacity to a jQuery set
  3846 + applyDragOpacity: function(els) {
  3847 + var opacity = this.view.opt('dragOpacity');
  3848 +
  3849 + if (opacity != null) {
  3850 + els.each(function(i, node) {
  3851 + // Don't use jQuery (will set an IE filter), do it the old fashioned way.
  3852 + // In IE8, a helper element will disappears if there's a filter.
  3853 + node.style.opacity = opacity;
  3854 + });
  3855 + }
  3856 + },
  3857 +
  3858 +
  3859 + /* External Element Dragging
  3860 + ------------------------------------------------------------------------------------------------------------------*/
  3861 +
  3862 +
  3863 + // Called when a jQuery UI drag is initiated anywhere in the DOM
  3864 + externalDragStart: function(ev, ui) {
  3865 + var view = this.view;
  3866 + var el;
  3867 + var accept;
  3868 +
  3869 + if (view.opt('droppable')) { // only listen if this setting is on
  3870 + el = $((ui ? ui.item : null) || ev.target);
  3871 +
  3872 + // Test that the dragged element passes the dropAccept selector or filter function.
  3873 + // FYI, the default is "*" (matches all)
  3874 + accept = view.opt('dropAccept');
  3875 + if ($.isFunction(accept) ? accept.call(el[0], el) : el.is(accept)) {
  3876 + if (!this.isDraggingExternal) { // prevent double-listening if fired twice
  3877 + this.listenToExternalDrag(el, ev, ui);
  3878 + }
  3879 + }
  3880 + }
  3881 + },
  3882 +
  3883 +
  3884 + // Called when a jQuery UI drag starts and it needs to be monitored for cell dropping
  3885 + listenToExternalDrag: function(el, ev, ui) {
  3886 + var _this = this;
  3887 + var meta = getDraggedElMeta(el); // extra data about event drop, including possible event to create
  3888 + var dragListener;
  3889 + var dropLocation; // a null value signals an unsuccessful drag
  3890 +
  3891 + // listener that tracks mouse movement over date-associated pixel regions
  3892 + dragListener = new CellDragListener(this.coordMap, {
  3893 + listenStart: function() {
  3894 + _this.isDraggingExternal = true;
  3895 + },
  3896 + cellOver: function(cell) {
  3897 + dropLocation = _this.computeExternalDrop(cell, meta);
  3898 + if (dropLocation) {
  3899 + _this.renderDrag(dropLocation); // called without a seg parameter
  3900 + }
  3901 + else { // invalid drop cell
  3902 + disableCursor();
  3903 + }
  3904 + },
  3905 + cellOut: function() {
  3906 + dropLocation = null; // signal unsuccessful
  3907 + _this.destroyDrag();
  3908 + enableCursor();
  3909 + },
  3910 + dragStop: function() {
  3911 + _this.destroyDrag();
  3912 + enableCursor();
  3913 +
  3914 + if (dropLocation) { // element was dropped on a valid date/time cell
  3915 + _this.view.reportExternalDrop(meta, dropLocation, el, ev, ui);
  3916 + }
  3917 + },
  3918 + listenStop: function() {
  3919 + _this.isDraggingExternal = false;
  3920 + }
  3921 + });
  3922 +
  3923 + dragListener.startDrag(ev); // start listening immediately
  3924 + },
  3925 +
  3926 +
  3927 + // Given a cell to be dropped upon, and misc data associated with the jqui drag (guaranteed to be a plain object),
  3928 + // returns start/end dates for the event that would result from the hypothetical drop. end might be null.
  3929 + // Returning a null value signals an invalid drop cell.
  3930 + computeExternalDrop: function(cell, meta) {
  3931 + var dropLocation = {
  3932 + start: cell.start.clone(),
  3933 + end: null
  3934 + };
  3935 +
  3936 + // if dropped on an all-day cell, and element's metadata specified a time, set it
  3937 + if (meta.startTime && !dropLocation.start.hasTime()) {
  3938 + dropLocation.start.time(meta.startTime);
  3939 + }
  3940 +
  3941 + if (meta.duration) {
  3942 + dropLocation.end = dropLocation.start.clone().add(meta.duration);
  3943 + }
  3944 +
  3945 + if (!this.view.calendar.isExternalDropRangeAllowed(dropLocation, meta.eventProps)) {
  3946 + return null;
  3947 + }
  3948 +
  3949 + return dropLocation;
  3950 + },
  3951 +
  3952 +
  3953 +
  3954 + /* Drag Rendering (for both events and an external elements)
  3955 + ------------------------------------------------------------------------------------------------------------------*/
  3956 +
  3957 +
  3958 + // Renders a visual indication of an event or external element being dragged.
  3959 + // `dropLocation` contains hypothetical start/end/allDay values the event would have if dropped. end can be null.
  3960 + // `seg` is the internal segment object that is being dragged. If dragging an external element, `seg` is null.
  3961 + // A truthy returned value indicates this method has rendered a helper element.
  3962 + renderDrag: function(dropLocation, seg) {
  3963 + // subclasses must implement
  3964 + },
  3965 +
  3966 +
  3967 + // Unrenders a visual indication of an event or external element being dragged
  3968 + destroyDrag: function() {
  3969 + // subclasses must implement
  3970 + },
  3971 +
  3972 +
  3973 + /* Resizing
  3974 + ------------------------------------------------------------------------------------------------------------------*/
  3975 +
  3976 +
  3977 + // Called when the user does a mousedown on an event's resizer, which might lead to resizing.
  3978 + // Generic enough to work with any type of Grid.
  3979 + segResizeMousedown: function(seg, ev, isStart) {
  3980 + var _this = this;
  3981 + var view = this.view;
  3982 + var calendar = view.calendar;
  3983 + var el = seg.el;
  3984 + var event = seg.event;
  3985 + var eventEnd = calendar.getEventEnd(event);
  3986 + var dragListener;
  3987 + var resizeLocation; // falsy if invalid resize
  3988 +
  3989 + // Tracks mouse movement over the *grid's* coordinate map
  3990 + dragListener = new CellDragListener(this.coordMap, {
  3991 + distance: 5,
  3992 + scroll: view.opt('dragScroll'),
  3993 + subjectEl: el,
  3994 + dragStart: function(ev) {
  3995 + _this.triggerSegMouseout(seg, ev); // ensure a mouseout on the manipulated event has been reported
  3996 + _this.segResizeStart(seg, ev);
  3997 + },
  3998 + cellOver: function(cell, isOrig, origCell) {
  3999 + resizeLocation = isStart ?
  4000 + _this.computeEventStartResize(origCell, cell, event) :
  4001 + _this.computeEventEndResize(origCell, cell, event);
  4002 +
  4003 + if (resizeLocation) {
  4004 + if (!calendar.isEventRangeAllowed(resizeLocation, event)) {
  4005 + disableCursor();
  4006 + resizeLocation = null;
  4007 + }
  4008 + // no change? (TODO: how does this work with timezones?)
  4009 + else if (resizeLocation.start.isSame(event.start) && resizeLocation.end.isSame(eventEnd)) {
  4010 + resizeLocation = null;
  4011 + }
  4012 + }
  4013 +
  4014 + if (resizeLocation) {
  4015 + view.hideEvent(event);
  4016 + _this.renderEventResize(resizeLocation, seg);
  4017 + }
  4018 + },
  4019 + cellOut: function() { // called before mouse moves to a different cell OR moved out of all cells
  4020 + resizeLocation = null;
  4021 + },
  4022 + cellDone: function() { // resets the rendering to show the original event
  4023 + _this.destroyEventResize();
  4024 + view.showEvent(event);
  4025 + enableCursor();
  4026 + },
  4027 + dragStop: function(ev) {
  4028 + _this.segResizeStop(seg, ev);
  4029 +
  4030 + if (resizeLocation) { // valid date to resize to?
  4031 + view.reportEventResize(event, resizeLocation, this.largeUnit, el, ev);
  4032 + }
  4033 + }
  4034 + });
  4035 +
  4036 + dragListener.mousedown(ev); // start listening, which will eventually lead to a dragStart
  4037 + },
  4038 +
  4039 +
  4040 + // Called before event segment resizing starts
  4041 + segResizeStart: function(seg, ev) {
  4042 + this.isResizingSeg = true;
  4043 + this.view.trigger('eventResizeStart', seg.el[0], seg.event, ev, {}); // last argument is jqui dummy
  4044 + },
  4045 +
  4046 +
  4047 + // Called after event segment resizing stops
  4048 + segResizeStop: function(seg, ev) {
  4049 + this.isResizingSeg = false;
  4050 + this.view.trigger('eventResizeStop', seg.el[0], seg.event, ev, {}); // last argument is jqui dummy
  4051 + },
  4052 +
  4053 +
  4054 + // Returns new date-information for an event segment being resized from its start
  4055 + computeEventStartResize: function(startCell, endCell, event) {
  4056 + return this.computeEventResize('start', startCell, endCell, event);
  4057 + },
  4058 +
  4059 +
  4060 + // Returns new date-information for an event segment being resized from its end
  4061 + computeEventEndResize: function(startCell, endCell, event) {
  4062 + return this.computeEventResize('end', startCell, endCell, event);
  4063 + },
  4064 +
  4065 +
  4066 + // Returns new date-information for an event segment being resized from its start OR end
  4067 + // `type` is either 'start' or 'end'
  4068 + computeEventResize: function(type, startCell, endCell, event) {
  4069 + var calendar = this.view.calendar;
  4070 + var delta = this.diffDates(endCell[type], startCell[type]);
  4071 + var range;
  4072 + var defaultDuration;
  4073 +
  4074 + // build original values to work from, guaranteeing a start and end
  4075 + range = {
  4076 + start: event.start.clone(),
  4077 + end: calendar.getEventEnd(event),
  4078 + allDay: event.allDay
  4079 + };
  4080 +
  4081 + // if an all-day event was in a timed area and was resized to a time, adjust start/end to have times
  4082 + if (range.allDay && durationHasTime(delta)) {
  4083 + range.allDay = false;
  4084 + calendar.normalizeEventRangeTimes(range);
  4085 + }
  4086 +
  4087 + range[type].add(delta); // apply delta to start or end
  4088 +
  4089 + // if the event was compressed too small, find a new reasonable duration for it
  4090 + if (!range.start.isBefore(range.end)) {
  4091 +
  4092 + defaultDuration = event.allDay ?
  4093 + calendar.defaultAllDayEventDuration :
  4094 + calendar.defaultTimedEventDuration;
  4095 +
  4096 + // between the cell's duration and the event's default duration, use the smaller of the two.
  4097 + // example: if year-length slots, and compressed to one slot, we don't want the event to be a year long
  4098 + if (this.cellDuration && this.cellDuration < defaultDuration) {
  4099 + defaultDuration = this.cellDuration;
  4100 + }
  4101 +
  4102 + if (type == 'start') { // resizing the start?
  4103 + range.start = range.end.clone().subtract(defaultDuration);
  4104 + }
  4105 + else { // resizing the end?
  4106 + range.end = range.start.clone().add(defaultDuration);
  4107 + }
  4108 + }
  4109 +
  4110 + return range;
  4111 + },
  4112 +
  4113 +
  4114 + // Renders a visual indication of an event being resized.
  4115 + // `range` has the updated dates of the event. `seg` is the original segment object involved in the drag.
  4116 + renderEventResize: function(range, seg) {
  4117 + // subclasses must implement
  4118 + },
  4119 +
  4120 +
  4121 + // Unrenders a visual indication of an event being resized.
  4122 + destroyEventResize: function() {
  4123 + // subclasses must implement
  4124 + },
  4125 +
  4126 +
  4127 + /* Rendering Utils
  4128 + ------------------------------------------------------------------------------------------------------------------*/
  4129 +
  4130 +
  4131 + // Compute the text that should be displayed on an event's element.
  4132 + // `range` can be the Event object itself, or something range-like, with at least a `start`.
  4133 + // If event times are disabled, or the event has no time, will return a blank string.
  4134 + // If not specified, formatStr will default to the eventTimeFormat setting,
  4135 + // and displayEnd will default to the displayEventEnd setting.
  4136 + getEventTimeText: function(range, formatStr, displayEnd) {
  4137 +
  4138 + if (formatStr == null) {
  4139 + formatStr = this.eventTimeFormat;
  4140 + }
  4141 +
  4142 + if (displayEnd == null) {
  4143 + displayEnd = this.displayEventEnd;
  4144 + }
  4145 +
  4146 + if (this.displayEventTime && range.start.hasTime()) {
  4147 + if (displayEnd && range.end) {
  4148 + return this.view.formatRange(range, formatStr);
  4149 + }
  4150 + else {
  4151 + return range.start.format(formatStr);
  4152 + }
  4153 + }
  4154 +
  4155 + return '';
  4156 + },
  4157 +
  4158 +
  4159 + // Generic utility for generating the HTML classNames for an event segment's element
  4160 + getSegClasses: function(seg, isDraggable, isResizable) {
  4161 + var event = seg.event;
  4162 + var classes = [
  4163 + 'fc-event',
  4164 + seg.isStart ? 'fc-start' : 'fc-not-start',
  4165 + seg.isEnd ? 'fc-end' : 'fc-not-end'
  4166 + ].concat(
  4167 + event.className,
  4168 + event.source ? event.source.className : []
  4169 + );
  4170 +
  4171 + if (isDraggable) {
  4172 + classes.push('fc-draggable');
  4173 + }
  4174 + if (isResizable) {
  4175 + classes.push('fc-resizable');
  4176 + }
  4177 +
  4178 + return classes;
  4179 + },
  4180 +
  4181 +
  4182 + // Utility for generating event skin-related CSS properties
  4183 + getEventSkinCss: function(event) {
  4184 + var view = this.view;
  4185 + var source = event.source || {};
  4186 + var eventColor = event.color;
  4187 + var sourceColor = source.color;
  4188 + var optionColor = view.opt('eventColor');
  4189 +
  4190 + return {
  4191 + 'background-color':
  4192 + event.backgroundColor ||
  4193 + eventColor ||
  4194 + source.backgroundColor ||
  4195 + sourceColor ||
  4196 + view.opt('eventBackgroundColor') ||
  4197 + optionColor,
  4198 + 'border-color':
  4199 + event.borderColor ||
  4200 + eventColor ||
  4201 + source.borderColor ||
  4202 + sourceColor ||
  4203 + view.opt('eventBorderColor') ||
  4204 + optionColor,
  4205 + color:
  4206 + event.textColor ||
  4207 + source.textColor ||
  4208 + view.opt('eventTextColor')
  4209 + };
  4210 + },
  4211 +
  4212 +
  4213 + /* Converting events -> ranges -> segs
  4214 + ------------------------------------------------------------------------------------------------------------------*/
  4215 +
  4216 +
  4217 + // Converts an array of event objects into an array of event segment objects.
  4218 + // A custom `rangeToSegsFunc` may be given for arbitrarily slicing up events.
  4219 + // Doesn't guarantee an order for the resulting array.
  4220 + eventsToSegs: function(events, rangeToSegsFunc) {
  4221 + var eventRanges = this.eventsToRanges(events);
  4222 + var segs = [];
  4223 + var i;
  4224 +
  4225 + for (i = 0; i < eventRanges.length; i++) {
  4226 + segs.push.apply(
  4227 + segs,
  4228 + this.eventRangeToSegs(eventRanges[i], rangeToSegsFunc)
  4229 + );
  4230 + }
  4231 +
  4232 + return segs;
  4233 + },
  4234 +
  4235 +
  4236 + // Converts an array of events into an array of "range" objects.
  4237 + // A "range" object is a plain object with start/end properties denoting the time it covers. Also an event property.
  4238 + // For "normal" events, this will be identical to the event's start/end, but for "inverse-background" events,
  4239 + // will create an array of ranges that span the time *not* covered by the given event.
  4240 + // Doesn't guarantee an order for the resulting array.
  4241 + eventsToRanges: function(events) {
  4242 + var _this = this;
  4243 + var eventsById = groupEventsById(events);
  4244 + var ranges = [];
  4245 +
  4246 + // group by ID so that related inverse-background events can be rendered together
  4247 + $.each(eventsById, function(id, eventGroup) {
  4248 + if (eventGroup.length) {
  4249 + ranges.push.apply(
  4250 + ranges,
  4251 + isInverseBgEvent(eventGroup[0]) ?
  4252 + _this.eventsToInverseRanges(eventGroup) :
  4253 + _this.eventsToNormalRanges(eventGroup)
  4254 + );
  4255 + }
  4256 + });
  4257 +
  4258 + return ranges;
  4259 + },
  4260 +
  4261 +
  4262 + // Converts an array of "normal" events (not inverted rendering) into a parallel array of ranges
  4263 + eventsToNormalRanges: function(events) {
  4264 + var calendar = this.view.calendar;
  4265 + var ranges = [];
  4266 + var i, event;
  4267 + var eventStart, eventEnd;
  4268 +
  4269 + for (i = 0; i < events.length; i++) {
  4270 + event = events[i];
  4271 +
  4272 + // make copies and normalize by stripping timezone
  4273 + eventStart = event.start.clone().stripZone();
  4274 + eventEnd = calendar.getEventEnd(event).stripZone();
  4275 +
  4276 + ranges.push({
  4277 + event: event,
  4278 + start: eventStart,
  4279 + end: eventEnd,
  4280 + eventStartMS: +eventStart,
  4281 + eventDurationMS: eventEnd - eventStart
  4282 + });
  4283 + }
  4284 +
  4285 + return ranges;
  4286 + },
  4287 +
  4288 +
  4289 + // Converts an array of events, with inverse-background rendering, into an array of range objects.
  4290 + // The range objects will cover all the time NOT covered by the events.
  4291 + eventsToInverseRanges: function(events) {
  4292 + var view = this.view;
  4293 + var viewStart = view.start.clone().stripZone(); // normalize timezone
  4294 + var viewEnd = view.end.clone().stripZone(); // normalize timezone
  4295 + var normalRanges = this.eventsToNormalRanges(events); // will give us normalized dates we can use w/o copies
  4296 + var inverseRanges = [];
  4297 + var event0 = events[0]; // assign this to each range's `.event`
  4298 + var start = viewStart; // the end of the previous range. the start of the new range
  4299 + var i, normalRange;
  4300 +
  4301 + // ranges need to be in order. required for our date-walking algorithm
  4302 + normalRanges.sort(compareNormalRanges);
  4303 +
  4304 + for (i = 0; i < normalRanges.length; i++) {
  4305 + normalRange = normalRanges[i];
  4306 +
  4307 + // add the span of time before the event (if there is any)
  4308 + if (normalRange.start > start) { // compare millisecond time (skip any ambig logic)
  4309 + inverseRanges.push({
  4310 + event: event0,
  4311 + start: start,
  4312 + end: normalRange.start
  4313 + });
  4314 + }
  4315 +
  4316 + start = normalRange.end;
  4317 + }
  4318 +
  4319 + // add the span of time after the last event (if there is any)
  4320 + if (start < viewEnd) { // compare millisecond time (skip any ambig logic)
  4321 + inverseRanges.push({
  4322 + event: event0,
  4323 + start: start,
  4324 + end: viewEnd
  4325 + });
  4326 + }
  4327 +
  4328 + return inverseRanges;
  4329 + },
  4330 +
  4331 +
  4332 + // Slices the given event range into one or more segment objects.
  4333 + // A `rangeToSegsFunc` custom slicing function can be given.
  4334 + eventRangeToSegs: function(eventRange, rangeToSegsFunc) {
  4335 + var segs;
  4336 + var i, seg;
  4337 +
  4338 + if (rangeToSegsFunc) {
  4339 + segs = rangeToSegsFunc(eventRange);
  4340 + }
  4341 + else {
  4342 + segs = this.rangeToSegs(eventRange); // defined by the subclass
  4343 + }
  4344 +
  4345 + for (i = 0; i < segs.length; i++) {
  4346 + seg = segs[i];
  4347 + seg.event = eventRange.event;
  4348 + seg.eventStartMS = eventRange.eventStartMS;
  4349 + seg.eventDurationMS = eventRange.eventDurationMS;
  4350 + }
  4351 +
  4352 + return segs;
  4353 + }
  4354 +
  4355 +});
  4356 +
  4357 +
  4358 +/* Utilities
  4359 +----------------------------------------------------------------------------------------------------------------------*/
  4360 +
  4361 +
  4362 +function isBgEvent(event) { // returns true if background OR inverse-background
  4363 + var rendering = getEventRendering(event);
  4364 + return rendering === 'background' || rendering === 'inverse-background';
  4365 +}
  4366 +
  4367 +
  4368 +function isInverseBgEvent(event) {
  4369 + return getEventRendering(event) === 'inverse-background';
  4370 +}
  4371 +
  4372 +
  4373 +function getEventRendering(event) {
  4374 + return firstDefined((event.source || {}).rendering, event.rendering);
  4375 +}
  4376 +
  4377 +
  4378 +function groupEventsById(events) {
  4379 + var eventsById = {};
  4380 + var i, event;
  4381 +
  4382 + for (i = 0; i < events.length; i++) {
  4383 + event = events[i];
  4384 + (eventsById[event._id] || (eventsById[event._id] = [])).push(event);
  4385 + }
  4386 +
  4387 + return eventsById;
  4388 +}
  4389 +
  4390 +
  4391 +// A cmp function for determining which non-inverted "ranges" (see above) happen earlier
  4392 +function compareNormalRanges(range1, range2) {
  4393 + return range1.eventStartMS - range2.eventStartMS; // earlier ranges go first
  4394 +}
  4395 +
  4396 +
  4397 +// A cmp function for determining which segments should take visual priority
  4398 +// DOES NOT WORK ON INVERTED BACKGROUND EVENTS because they have no eventStartMS/eventDurationMS
  4399 +function compareSegs(seg1, seg2) {
  4400 + return seg1.eventStartMS - seg2.eventStartMS || // earlier events go first
  4401 + seg2.eventDurationMS - seg1.eventDurationMS || // tie? longer events go first
  4402 + seg2.event.allDay - seg1.event.allDay || // tie? put all-day events first (booleans cast to 0/1)
  4403 + (seg1.event.title || '').localeCompare(seg2.event.title); // tie? alphabetically by title
  4404 +}
  4405 +
  4406 +fc.compareSegs = compareSegs; // export
  4407 +
  4408 +
  4409 +/* External-Dragging-Element Data
  4410 +----------------------------------------------------------------------------------------------------------------------*/
  4411 +
  4412 +// Require all HTML5 data-* attributes used by FullCalendar to have this prefix.
  4413 +// A value of '' will query attributes like data-event. A value of 'fc' will query attributes like data-fc-event.
  4414 +fc.dataAttrPrefix = '';
  4415 +
  4416 +// Given a jQuery element that might represent a dragged FullCalendar event, returns an intermediate data structure
  4417 +// to be used for Event Object creation.
  4418 +// A defined `.eventProps`, even when empty, indicates that an event should be created.
  4419 +function getDraggedElMeta(el) {
  4420 + var prefix = fc.dataAttrPrefix;
  4421 + var eventProps; // properties for creating the event, not related to date/time
  4422 + var startTime; // a Duration
  4423 + var duration;
  4424 + var stick;
  4425 +
  4426 + if (prefix) { prefix += '-'; }
  4427 + eventProps = el.data(prefix + 'event') || null;
  4428 +
  4429 + if (eventProps) {
  4430 + if (typeof eventProps === 'object') {
  4431 + eventProps = $.extend({}, eventProps); // make a copy
  4432 + }
  4433 + else { // something like 1 or true. still signal event creation
  4434 + eventProps = {};
  4435 + }
  4436 +
  4437 + // pluck special-cased date/time properties
  4438 + startTime = eventProps.start;
  4439 + if (startTime == null) { startTime = eventProps.time; } // accept 'time' as well
  4440 + duration = eventProps.duration;
  4441 + stick = eventProps.stick;
  4442 + delete eventProps.start;
  4443 + delete eventProps.time;
  4444 + delete eventProps.duration;
  4445 + delete eventProps.stick;
  4446 + }
  4447 +
  4448 + // fallback to standalone attribute values for each of the date/time properties
  4449 + if (startTime == null) { startTime = el.data(prefix + 'start'); }
  4450 + if (startTime == null) { startTime = el.data(prefix + 'time'); } // accept 'time' as well
  4451 + if (duration == null) { duration = el.data(prefix + 'duration'); }
  4452 + if (stick == null) { stick = el.data(prefix + 'stick'); }
  4453 +
  4454 + // massage into correct data types
  4455 + startTime = startTime != null ? moment.duration(startTime) : null;
  4456 + duration = duration != null ? moment.duration(duration) : null;
  4457 + stick = Boolean(stick);
  4458 +
  4459 + return { eventProps: eventProps, startTime: startTime, duration: duration, stick: stick };
  4460 +}
  4461 +
  4462 +
  4463 +;;
  4464 +
  4465 +/* A component that renders a grid of whole-days that runs horizontally. There can be multiple rows, one per week.
  4466 +----------------------------------------------------------------------------------------------------------------------*/
  4467 +
  4468 +var DayGrid = Grid.extend({
  4469 +
  4470 + numbersVisible: false, // should render a row for day/week numbers? set by outside view. TODO: make internal
  4471 + bottomCoordPadding: 0, // hack for extending the hit area for the last row of the coordinate grid
  4472 + breakOnWeeks: null, // should create a new row for each week? set by outside view
  4473 +
  4474 + cellDates: null, // flat chronological array of each cell's dates
  4475 + dayToCellOffsets: null, // maps days offsets from grid's start date, to cell offsets
  4476 +
  4477 + rowEls: null, // set of fake row elements
  4478 + dayEls: null, // set of whole-day elements comprising the row's background
  4479 + helperEls: null, // set of cell skeleton elements for rendering the mock event "helper"
  4480 +
  4481 +
  4482 + constructor: function() {
  4483 + Grid.apply(this, arguments);
  4484 +
  4485 + this.cellDuration = moment.duration(1, 'day'); // for Grid system
  4486 + },
  4487 +
  4488 +
  4489 + // Renders the rows and columns into the component's `this.el`, which should already be assigned.
  4490 + // isRigid determins whether the individual rows should ignore the contents and be a constant height.
  4491 + // Relies on the view's colCnt and rowCnt. In the future, this component should probably be self-sufficient.
  4492 + renderDates: function(isRigid) {
  4493 + var view = this.view;
  4494 + var rowCnt = this.rowCnt;
  4495 + var colCnt = this.colCnt;
  4496 + var cellCnt = rowCnt * colCnt;
  4497 + var html = '';
  4498 + var row;
  4499 + var i, cell;
  4500 +
  4501 + for (row = 0; row < rowCnt; row++) {
  4502 + html += this.dayRowHtml(row, isRigid);
  4503 + }
  4504 + this.el.html(html);
  4505 +
  4506 + this.rowEls = this.el.find('.fc-row');
  4507 + this.dayEls = this.el.find('.fc-day');
  4508 +
  4509 + // trigger dayRender with each cell's element
  4510 + for (i = 0; i < cellCnt; i++) {
  4511 + cell = this.getCell(i);
  4512 + view.trigger('dayRender', null, cell.start, this.dayEls.eq(i));
  4513 + }
  4514 + },
  4515 +
  4516 +
  4517 + destroyDates: function() {
  4518 + this.destroySegPopover();
  4519 + },
  4520 +
  4521 +
  4522 + renderBusinessHours: function() {
  4523 + var events = this.view.calendar.getBusinessHoursEvents(true); // wholeDay=true
  4524 + var segs = this.eventsToSegs(events);
  4525 +
  4526 + this.renderFill('businessHours', segs, 'bgevent');
  4527 + },
  4528 +
  4529 +
  4530 + // Generates the HTML for a single row. `row` is the row number.
  4531 + dayRowHtml: function(row, isRigid) {
  4532 + var view = this.view;
  4533 + var classes = [ 'fc-row', 'fc-week', view.widgetContentClass ];
  4534 +
  4535 + if (isRigid) {
  4536 + classes.push('fc-rigid');
  4537 + }
  4538 +
  4539 + return '' +
  4540 + '<div class="' + classes.join(' ') + '">' +
  4541 + '<div class="fc-bg">' +
  4542 + '<table>' +
  4543 + this.rowHtml('day', row) + // leverages RowRenderer. calls dayCellHtml()
  4544 + '</table>' +
  4545 + '</div>' +
  4546 + '<div class="fc-content-skeleton">' +
  4547 + '<table>' +
  4548 + (this.numbersVisible ?
  4549 + '<thead>' +
  4550 + this.rowHtml('number', row) + // leverages RowRenderer. View will define render method
  4551 + '</thead>' :
  4552 + ''
  4553 + ) +
  4554 + '</table>' +
  4555 + '</div>' +
  4556 + '</div>';
  4557 + },
  4558 +
  4559 +
  4560 + // Renders the HTML for a whole-day cell. Will eventually end up in the day-row's background.
  4561 + // We go through a 'day' row type instead of just doing a 'bg' row type so that the View can do custom rendering
  4562 + // specifically for whole-day rows, whereas a 'bg' might also be used for other purposes (TimeGrid bg for example).
  4563 + dayCellHtml: function(cell) {
  4564 + return this.bgCellHtml(cell);
  4565 + },
  4566 +
  4567 +
  4568 + /* Options
  4569 + ------------------------------------------------------------------------------------------------------------------*/
  4570 +
  4571 +
  4572 + // Computes a default column header formatting string if `colFormat` is not explicitly defined
  4573 + computeColHeadFormat: function() {
  4574 + if (this.rowCnt > 1) { // more than one week row. day numbers will be in each cell
  4575 + return 'ddd'; // "Sat"
  4576 + }
  4577 + else if (this.colCnt > 1) { // multiple days, so full single date string WON'T be in title text
  4578 + return this.view.opt('dayOfMonthFormat'); // "Sat 12/10"
  4579 + }
  4580 + else { // single day, so full single date string will probably be in title text
  4581 + return 'dddd'; // "Saturday"
  4582 + }
  4583 + },
  4584 +
  4585 +
  4586 + // Computes a default event time formatting string if `timeFormat` is not explicitly defined
  4587 + computeEventTimeFormat: function() {
  4588 + return this.view.opt('extraSmallTimeFormat'); // like "6p" or "6:30p"
  4589 + },
  4590 +
  4591 +
  4592 + // Computes a default `displayEventEnd` value if one is not expliclty defined
  4593 + computeDisplayEventEnd: function() {
  4594 + return this.colCnt == 1; // we'll likely have space if there's only one day
  4595 + },
  4596 +
  4597 +
  4598 + /* Cell System
  4599 + ------------------------------------------------------------------------------------------------------------------*/
  4600 +
  4601 +
  4602 + // Initializes row/col information
  4603 + updateCells: function() {
  4604 + var cellDates;
  4605 + var firstDay;
  4606 + var rowCnt;
  4607 + var colCnt;
  4608 +
  4609 + this.updateCellDates(); // populates cellDates and dayToCellOffsets
  4610 + cellDates = this.cellDates;
  4611 +
  4612 + if (this.breakOnWeeks) {
  4613 + // count columns until the day-of-week repeats
  4614 + firstDay = cellDates[0].day();
  4615 + for (colCnt = 1; colCnt < cellDates.length; colCnt++) {
  4616 + if (cellDates[colCnt].day() == firstDay) {
  4617 + break;
  4618 + }
  4619 + }
  4620 + rowCnt = Math.ceil(cellDates.length / colCnt);
  4621 + }
  4622 + else {
  4623 + rowCnt = 1;
  4624 + colCnt = cellDates.length;
  4625 + }
  4626 +
  4627 + this.rowCnt = rowCnt;
  4628 + this.colCnt = colCnt;
  4629 + },
  4630 +
  4631 +
  4632 + // Populates cellDates and dayToCellOffsets
  4633 + updateCellDates: function() {
  4634 + var view = this.view;
  4635 + var date = this.start.clone();
  4636 + var dates = [];
  4637 + var offset = -1;
  4638 + var offsets = [];
  4639 +
  4640 + while (date.isBefore(this.end)) { // loop each day from start to end
  4641 + if (view.isHiddenDay(date)) {
  4642 + offsets.push(offset + 0.5); // mark that it's between offsets
  4643 + }
  4644 + else {
  4645 + offset++;
  4646 + offsets.push(offset);
  4647 + dates.push(date.clone());
  4648 + }
  4649 + date.add(1, 'days');
  4650 + }
  4651 +
  4652 + this.cellDates = dates;
  4653 + this.dayToCellOffsets = offsets;
  4654 + },
  4655 +
  4656 +
  4657 + // Given a cell object, generates its start date. Returns a reference-free copy.
  4658 + computeCellDate: function(cell) {
  4659 + var colCnt = this.colCnt;
  4660 + var index = cell.row * colCnt + (this.isRTL ? colCnt - cell.col - 1 : cell.col);
  4661 +
  4662 + return this.cellDates[index].clone();
  4663 + },
  4664 +
  4665 +
  4666 + // Retrieves the element representing the given row
  4667 + getRowEl: function(row) {
  4668 + return this.rowEls.eq(row);
  4669 + },
  4670 +
  4671 +
  4672 + // Retrieves the element representing the given column
  4673 + getColEl: function(col) {
  4674 + return this.dayEls.eq(col);
  4675 + },
  4676 +
  4677 +
  4678 + // Gets the whole-day element associated with the cell
  4679 + getCellDayEl: function(cell) {
  4680 + return this.dayEls.eq(cell.row * this.colCnt + cell.col);
  4681 + },
  4682 +
  4683 +
  4684 + // Overrides Grid's method for when row coordinates are computed
  4685 + computeRowCoords: function() {
  4686 + var rowCoords = Grid.prototype.computeRowCoords.call(this); // call the super-method
  4687 +
  4688 + // hack for extending last row (used by AgendaView)
  4689 + rowCoords[rowCoords.length - 1].bottom += this.bottomCoordPadding;
  4690 +
  4691 + return rowCoords;
  4692 + },
  4693 +
  4694 +
  4695 + /* Dates
  4696 + ------------------------------------------------------------------------------------------------------------------*/
  4697 +
  4698 +
  4699 + // Slices up a date range by row into an array of segments
  4700 + rangeToSegs: function(range) {
  4701 + var isRTL = this.isRTL;
  4702 + var rowCnt = this.rowCnt;
  4703 + var colCnt = this.colCnt;
  4704 + var segs = [];
  4705 + var first, last; // inclusive cell-offset range for given range
  4706 + var row;
  4707 + var rowFirst, rowLast; // inclusive cell-offset range for current row
  4708 + var isStart, isEnd;
  4709 + var segFirst, segLast; // inclusive cell-offset range for segment
  4710 + var seg;
  4711 +
  4712 + range = this.view.computeDayRange(range); // make whole-day range, considering nextDayThreshold
  4713 + first = this.dateToCellOffset(range.start);
  4714 + last = this.dateToCellOffset(range.end.subtract(1, 'days')); // offset of inclusive end date
  4715 +
  4716 + for (row = 0; row < rowCnt; row++) {
  4717 + rowFirst = row * colCnt;
  4718 + rowLast = rowFirst + colCnt - 1;
  4719 +
  4720 + // intersect segment's offset range with the row's
  4721 + segFirst = Math.max(rowFirst, first);
  4722 + segLast = Math.min(rowLast, last);
  4723 +
  4724 + // deal with in-between indices
  4725 + segFirst = Math.ceil(segFirst); // in-between starts round to next cell
  4726 + segLast = Math.floor(segLast); // in-between ends round to prev cell
  4727 +
  4728 + if (segFirst <= segLast) { // was there any intersection with the current row?
  4729 +
  4730 + // must be matching integers to be the segment's start/end
  4731 + isStart = segFirst === first;
  4732 + isEnd = segLast === last;
  4733 +
  4734 + // translate offsets to be relative to start-of-row
  4735 + segFirst -= rowFirst;
  4736 + segLast -= rowFirst;
  4737 +
  4738 + seg = { row: row, isStart: isStart, isEnd: isEnd };
  4739 + if (isRTL) {
  4740 + seg.leftCol = colCnt - segLast - 1;
  4741 + seg.rightCol = colCnt - segFirst - 1;
  4742 + }
  4743 + else {
  4744 + seg.leftCol = segFirst;
  4745 + seg.rightCol = segLast;
  4746 + }
  4747 + segs.push(seg);
  4748 + }
  4749 + }
  4750 +
  4751 + return segs;
  4752 + },
  4753 +
  4754 +
  4755 + // Given a date, returns its chronolocial cell-offset from the first cell of the grid.
  4756 + // If the date lies between cells (because of hiddenDays), returns a floating-point value between offsets.
  4757 + // If before the first offset, returns a negative number.
  4758 + // If after the last offset, returns an offset past the last cell offset.
  4759 + // Only works for *start* dates of cells. Will not work for exclusive end dates for cells.
  4760 + dateToCellOffset: function(date) {
  4761 + var offsets = this.dayToCellOffsets;
  4762 + var day = date.diff(this.start, 'days');
  4763 +
  4764 + if (day < 0) {
  4765 + return offsets[0] - 1;
  4766 + }
  4767 + else if (day >= offsets.length) {
  4768 + return offsets[offsets.length - 1] + 1;
  4769 + }
  4770 + else {
  4771 + return offsets[day];
  4772 + }
  4773 + },
  4774 +
  4775 +
  4776 + /* Event Drag Visualization
  4777 + ------------------------------------------------------------------------------------------------------------------*/
  4778 + // TODO: move to DayGrid.event, similar to what we did with Grid's drag methods
  4779 +
  4780 +
  4781 + // Renders a visual indication of an event or external element being dragged.
  4782 + // The dropLocation's end can be null. seg can be null. See Grid::renderDrag for more info.
  4783 + renderDrag: function(dropLocation, seg) {
  4784 +
  4785 + // always render a highlight underneath
  4786 + this.renderHighlight(
  4787 + this.view.calendar.ensureVisibleEventRange(dropLocation) // needs to be a proper range
  4788 + );
  4789 +
  4790 + // if a segment from the same calendar but another component is being dragged, render a helper event
  4791 + if (seg && !seg.el.closest(this.el).length) {
  4792 +
  4793 + this.renderRangeHelper(dropLocation, seg);
  4794 + this.applyDragOpacity(this.helperEls);
  4795 +
  4796 + return true; // a helper has been rendered
  4797 + }
  4798 + },
  4799 +
  4800 +
  4801 + // Unrenders any visual indication of a hovering event
  4802 + destroyDrag: function() {
  4803 + this.destroyHighlight();
  4804 + this.destroyHelper();
  4805 + },
  4806 +
  4807 +
  4808 + /* Event Resize Visualization
  4809 + ------------------------------------------------------------------------------------------------------------------*/
  4810 +
  4811 +
  4812 + // Renders a visual indication of an event being resized
  4813 + renderEventResize: function(range, seg) {
  4814 + this.renderHighlight(range);
  4815 + this.renderRangeHelper(range, seg);
  4816 + },
  4817 +
  4818 +
  4819 + // Unrenders a visual indication of an event being resized
  4820 + destroyEventResize: function() {
  4821 + this.destroyHighlight();
  4822 + this.destroyHelper();
  4823 + },
  4824 +
  4825 +
  4826 + /* Event Helper
  4827 + ------------------------------------------------------------------------------------------------------------------*/
  4828 +
  4829 +
  4830 + // Renders a mock "helper" event. `sourceSeg` is the associated internal segment object. It can be null.
  4831 + renderHelper: function(event, sourceSeg) {
  4832 + var helperNodes = [];
  4833 + var segs = this.eventsToSegs([ event ]);
  4834 + var rowStructs;
  4835 +
  4836 + segs = this.renderFgSegEls(segs); // assigns each seg's el and returns a subset of segs that were rendered
  4837 + rowStructs = this.renderSegRows(segs);
  4838 +
  4839 + // inject each new event skeleton into each associated row
  4840 + this.rowEls.each(function(row, rowNode) {
  4841 + var rowEl = $(rowNode); // the .fc-row
  4842 + var skeletonEl = $('<div class="fc-helper-skeleton"><table/></div>'); // will be absolutely positioned
  4843 + var skeletonTop;
  4844 +
  4845 + // If there is an original segment, match the top position. Otherwise, put it at the row's top level
  4846 + if (sourceSeg && sourceSeg.row === row) {
  4847 + skeletonTop = sourceSeg.el.position().top;
  4848 + }
  4849 + else {
  4850 + skeletonTop = rowEl.find('.fc-content-skeleton tbody').position().top;
  4851 + }
  4852 +
  4853 + skeletonEl.css('top', skeletonTop)
  4854 + .find('table')
  4855 + .append(rowStructs[row].tbodyEl);
  4856 +
  4857 + rowEl.append(skeletonEl);
  4858 + helperNodes.push(skeletonEl[0]);
  4859 + });
  4860 +
  4861 + this.helperEls = $(helperNodes); // array -> jQuery set
  4862 + },
  4863 +
  4864 +
  4865 + // Unrenders any visual indication of a mock helper event
  4866 + destroyHelper: function() {
  4867 + if (this.helperEls) {
  4868 + this.helperEls.remove();
  4869 + this.helperEls = null;
  4870 + }
  4871 + },
  4872 +
  4873 +
  4874 + /* Fill System (highlight, background events, business hours)
  4875 + ------------------------------------------------------------------------------------------------------------------*/
  4876 +
  4877 +
  4878 + fillSegTag: 'td', // override the default tag name
  4879 +
  4880 +
  4881 + // Renders a set of rectangles over the given segments of days.
  4882 + // Only returns segments that successfully rendered.
  4883 + renderFill: function(type, segs, className) {
  4884 + var nodes = [];
  4885 + var i, seg;
  4886 + var skeletonEl;
  4887 +
  4888 + segs = this.renderFillSegEls(type, segs); // assignes `.el` to each seg. returns successfully rendered segs
  4889 +
  4890 + for (i = 0; i < segs.length; i++) {
  4891 + seg = segs[i];
  4892 + skeletonEl = this.renderFillRow(type, seg, className);
  4893 + this.rowEls.eq(seg.row).append(skeletonEl);
  4894 + nodes.push(skeletonEl[0]);
  4895 + }
  4896 +
  4897 + this.elsByFill[type] = $(nodes);
  4898 +
  4899 + return segs;
  4900 + },
  4901 +
  4902 +
  4903 + // Generates the HTML needed for one row of a fill. Requires the seg's el to be rendered.
  4904 + renderFillRow: function(type, seg, className) {
  4905 + var colCnt = this.colCnt;
  4906 + var startCol = seg.leftCol;
  4907 + var endCol = seg.rightCol + 1;
  4908 + var skeletonEl;
  4909 + var trEl;
  4910 +
  4911 + className = className || type.toLowerCase();
  4912 +
  4913 + skeletonEl = $(
  4914 + '<div class="fc-' + className + '-skeleton">' +
  4915 + '<table><tr/></table>' +
  4916 + '</div>'
  4917 + );
  4918 + trEl = skeletonEl.find('tr');
  4919 +
  4920 + if (startCol > 0) {
  4921 + trEl.append('<td colspan="' + startCol + '"/>');
  4922 + }
  4923 +
  4924 + trEl.append(
  4925 + seg.el.attr('colspan', endCol - startCol)
  4926 + );
  4927 +
  4928 + if (endCol < colCnt) {
  4929 + trEl.append('<td colspan="' + (colCnt - endCol) + '"/>');
  4930 + }
  4931 +
  4932 + this.bookendCells(trEl, type);
  4933 +
  4934 + return skeletonEl;
  4935 + }
  4936 +
  4937 +});
  4938 +
  4939 +;;
  4940 +
  4941 +/* Event-rendering methods for the DayGrid class
  4942 +----------------------------------------------------------------------------------------------------------------------*/
  4943 +
  4944 +DayGrid.mixin({
  4945 +
  4946 + rowStructs: null, // an array of objects, each holding information about a row's foreground event-rendering
  4947 +
  4948 +
  4949 + // Unrenders all events currently rendered on the grid
  4950 + destroyEvents: function() {
  4951 + this.destroySegPopover(); // removes the "more.." events popover
  4952 + Grid.prototype.destroyEvents.apply(this, arguments); // calls the super-method
  4953 + },
  4954 +
  4955 +
  4956 + // Retrieves all rendered segment objects currently rendered on the grid
  4957 + getEventSegs: function() {
  4958 + return Grid.prototype.getEventSegs.call(this) // get the segments from the super-method
  4959 + .concat(this.popoverSegs || []); // append the segments from the "more..." popover
  4960 + },
  4961 +
  4962 +
  4963 + // Renders the given background event segments onto the grid
  4964 + renderBgSegs: function(segs) {
  4965 +
  4966 + // don't render timed background events
  4967 + var allDaySegs = $.grep(segs, function(seg) {
  4968 + return seg.event.allDay;
  4969 + });
  4970 +
  4971 + return Grid.prototype.renderBgSegs.call(this, allDaySegs); // call the super-method
  4972 + },
  4973 +
  4974 +
  4975 + // Renders the given foreground event segments onto the grid
  4976 + renderFgSegs: function(segs) {
  4977 + var rowStructs;
  4978 +
  4979 + // render an `.el` on each seg
  4980 + // returns a subset of the segs. segs that were actually rendered
  4981 + segs = this.renderFgSegEls(segs);
  4982 +
  4983 + rowStructs = this.rowStructs = this.renderSegRows(segs);
  4984 +
  4985 + // append to each row's content skeleton
  4986 + this.rowEls.each(function(i, rowNode) {
  4987 + $(rowNode).find('.fc-content-skeleton > table').append(
  4988 + rowStructs[i].tbodyEl
  4989 + );
  4990 + });
  4991 +
  4992 + return segs; // return only the segs that were actually rendered
  4993 + },
  4994 +
  4995 +
  4996 + // Unrenders all currently rendered foreground event segments
  4997 + destroyFgSegs: function() {
  4998 + var rowStructs = this.rowStructs || [];
  4999 + var rowStruct;
  5000 +
  5001 + while ((rowStruct = rowStructs.pop())) {
  5002 + rowStruct.tbodyEl.remove();
  5003 + }
  5004 +
  5005 + this.rowStructs = null;
  5006 + },
  5007 +
  5008 +
  5009 + // Uses the given events array to generate <tbody> elements that should be appended to each row's content skeleton.
  5010 + // Returns an array of rowStruct objects (see the bottom of `renderSegRow`).
  5011 + // PRECONDITION: each segment shoud already have a rendered and assigned `.el`
  5012 + renderSegRows: function(segs) {
  5013 + var rowStructs = [];
  5014 + var segRows;
  5015 + var row;
  5016 +
  5017 + segRows = this.groupSegRows(segs); // group into nested arrays
  5018 +
  5019 + // iterate each row of segment groupings
  5020 + for (row = 0; row < segRows.length; row++) {
  5021 + rowStructs.push(
  5022 + this.renderSegRow(row, segRows[row])
  5023 + );
  5024 + }
  5025 +
  5026 + return rowStructs;
  5027 + },
  5028 +
  5029 +
  5030 + // Builds the HTML to be used for the default element for an individual segment
  5031 + fgSegHtml: function(seg, disableResizing) {
  5032 + var view = this.view;
  5033 + var event = seg.event;
  5034 + var isDraggable = view.isEventDraggable(event);
  5035 + var isResizableFromStart = !disableResizing && event.allDay &&
  5036 + seg.isStart && view.isEventResizableFromStart(event);
  5037 + var isResizableFromEnd = !disableResizing && event.allDay &&
  5038 + seg.isEnd && view.isEventResizableFromEnd(event);
  5039 + var classes = this.getSegClasses(seg, isDraggable, isResizableFromStart || isResizableFromEnd);
  5040 + var skinCss = cssToStr(this.getEventSkinCss(event));
  5041 + var timeHtml = '';
  5042 + var timeText;
  5043 + var titleHtml;
  5044 +
  5045 + classes.unshift('fc-day-grid-event', 'fc-h-event');
  5046 +
  5047 + // Only display a timed events time if it is the starting segment
  5048 + if (seg.isStart) {
  5049 + timeText = this.getEventTimeText(event);
  5050 + if (timeText) {
  5051 + timeHtml = '<span class="fc-time">' + htmlEscape(timeText) + '</span>';
  5052 + }
  5053 + }
  5054 +
  5055 + titleHtml =
  5056 + '<span class="fc-title">' +
  5057 + (htmlEscape(event.title || '') || '&nbsp;') + // we always want one line of height
  5058 + '</span>';
  5059 +
  5060 + return '<a class="' + classes.join(' ') + '"' +
  5061 + (event.url ?
  5062 + ' href="' + htmlEscape(event.url) + '"' :
  5063 + ''
  5064 + ) +
  5065 + (skinCss ?
  5066 + ' style="' + skinCss + '"' :
  5067 + ''
  5068 + ) +
  5069 + '>' +
  5070 + '<div class="fc-content">' +
  5071 + (this.isRTL ?
  5072 + titleHtml + ' ' + timeHtml : // put a natural space in between
  5073 + timeHtml + ' ' + titleHtml //
  5074 + ) +
  5075 + '</div>' +
  5076 + (isResizableFromStart ?
  5077 + '<div class="fc-resizer fc-start-resizer" />' :
  5078 + ''
  5079 + ) +
  5080 + (isResizableFromEnd ?
  5081 + '<div class="fc-resizer fc-end-resizer" />' :
  5082 + ''
  5083 + ) +
  5084 + '</a>';
  5085 + },
  5086 +
  5087 +
  5088 + // Given a row # and an array of segments all in the same row, render a <tbody> element, a skeleton that contains
  5089 + // the segments. Returns object with a bunch of internal data about how the render was calculated.
  5090 + // NOTE: modifies rowSegs
  5091 + renderSegRow: function(row, rowSegs) {
  5092 + var colCnt = this.colCnt;
  5093 + var segLevels = this.buildSegLevels(rowSegs); // group into sub-arrays of levels
  5094 + var levelCnt = Math.max(1, segLevels.length); // ensure at least one level
  5095 + var tbody = $('<tbody/>');
  5096 + var segMatrix = []; // lookup for which segments are rendered into which level+col cells
  5097 + var cellMatrix = []; // lookup for all <td> elements of the level+col matrix
  5098 + var loneCellMatrix = []; // lookup for <td> elements that only take up a single column
  5099 + var i, levelSegs;
  5100 + var col;
  5101 + var tr;
  5102 + var j, seg;
  5103 + var td;
  5104 +
  5105 + // populates empty cells from the current column (`col`) to `endCol`
  5106 + function emptyCellsUntil(endCol) {
  5107 + while (col < endCol) {
  5108 + // try to grab a cell from the level above and extend its rowspan. otherwise, create a fresh cell
  5109 + td = (loneCellMatrix[i - 1] || [])[col];
  5110 + if (td) {
  5111 + td.attr(
  5112 + 'rowspan',
  5113 + parseInt(td.attr('rowspan') || 1, 10) + 1
  5114 + );
  5115 + }
  5116 + else {
  5117 + td = $('<td/>');
  5118 + tr.append(td);
  5119 + }
  5120 + cellMatrix[i][col] = td;
  5121 + loneCellMatrix[i][col] = td;
  5122 + col++;
  5123 + }
  5124 + }
  5125 +
  5126 + for (i = 0; i < levelCnt; i++) { // iterate through all levels
  5127 + levelSegs = segLevels[i];
  5128 + col = 0;
  5129 + tr = $('<tr/>');
  5130 +
  5131 + segMatrix.push([]);
  5132 + cellMatrix.push([]);
  5133 + loneCellMatrix.push([]);
  5134 +
  5135 + // levelCnt might be 1 even though there are no actual levels. protect against this.
  5136 + // this single empty row is useful for styling.
  5137 + if (levelSegs) {
  5138 + for (j = 0; j < levelSegs.length; j++) { // iterate through segments in level
  5139 + seg = levelSegs[j];
  5140 +
  5141 + emptyCellsUntil(seg.leftCol);
  5142 +
  5143 + // create a container that occupies or more columns. append the event element.
  5144 + td = $('<td class="fc-event-container"/>').append(seg.el);
  5145 + if (seg.leftCol != seg.rightCol) {
  5146 + td.attr('colspan', seg.rightCol - seg.leftCol + 1);
  5147 + }
  5148 + else { // a single-column segment
  5149 + loneCellMatrix[i][col] = td;
  5150 + }
  5151 +
  5152 + while (col <= seg.rightCol) {
  5153 + cellMatrix[i][col] = td;
  5154 + segMatrix[i][col] = seg;
  5155 + col++;
  5156 + }
  5157 +
  5158 + tr.append(td);
  5159 + }
  5160 + }
  5161 +
  5162 + emptyCellsUntil(colCnt); // finish off the row
  5163 + this.bookendCells(tr, 'eventSkeleton');
  5164 + tbody.append(tr);
  5165 + }
  5166 +
  5167 + return { // a "rowStruct"
  5168 + row: row, // the row number
  5169 + tbodyEl: tbody,
  5170 + cellMatrix: cellMatrix,
  5171 + segMatrix: segMatrix,
  5172 + segLevels: segLevels,
  5173 + segs: rowSegs
  5174 + };
  5175 + },
  5176 +
  5177 +
  5178 + // Stacks a flat array of segments, which are all assumed to be in the same row, into subarrays of vertical levels.
  5179 + // NOTE: modifies segs
  5180 + buildSegLevels: function(segs) {
  5181 + var levels = [];
  5182 + var i, seg;
  5183 + var j;
  5184 +
  5185 + // Give preference to elements with certain criteria, so they have
  5186 + // a chance to be closer to the top.
  5187 + segs.sort(compareSegs);
  5188 +
  5189 + for (i = 0; i < segs.length; i++) {
  5190 + seg = segs[i];
  5191 +
  5192 + // loop through levels, starting with the topmost, until the segment doesn't collide with other segments
  5193 + for (j = 0; j < levels.length; j++) {
  5194 + if (!isDaySegCollision(seg, levels[j])) {
  5195 + break;
  5196 + }
  5197 + }
  5198 + // `j` now holds the desired subrow index
  5199 + seg.level = j;
  5200 +
  5201 + // create new level array if needed and append segment
  5202 + (levels[j] || (levels[j] = [])).push(seg);
  5203 + }
  5204 +
  5205 + // order segments left-to-right. very important if calendar is RTL
  5206 + for (j = 0; j < levels.length; j++) {
  5207 + levels[j].sort(compareDaySegCols);
  5208 + }
  5209 +
  5210 + return levels;
  5211 + },
  5212 +
  5213 +
  5214 + // Given a flat array of segments, return an array of sub-arrays, grouped by each segment's row
  5215 + groupSegRows: function(segs) {
  5216 + var segRows = [];
  5217 + var i;
  5218 +
  5219 + for (i = 0; i < this.rowCnt; i++) {
  5220 + segRows.push([]);
  5221 + }
  5222 +
  5223 + for (i = 0; i < segs.length; i++) {
  5224 + segRows[segs[i].row].push(segs[i]);
  5225 + }
  5226 +
  5227 + return segRows;
  5228 + }
  5229 +
  5230 +});
  5231 +
  5232 +
  5233 +// Computes whether two segments' columns collide. They are assumed to be in the same row.
  5234 +function isDaySegCollision(seg, otherSegs) {
  5235 + var i, otherSeg;
  5236 +
  5237 + for (i = 0; i < otherSegs.length; i++) {
  5238 + otherSeg = otherSegs[i];
  5239 +
  5240 + if (
  5241 + otherSeg.leftCol <= seg.rightCol &&
  5242 + otherSeg.rightCol >= seg.leftCol
  5243 + ) {
  5244 + return true;
  5245 + }
  5246 + }
  5247 +
  5248 + return false;
  5249 +}
  5250 +
  5251 +
  5252 +// A cmp function for determining the leftmost event
  5253 +function compareDaySegCols(a, b) {
  5254 + return a.leftCol - b.leftCol;
  5255 +}
  5256 +
  5257 +;;
  5258 +
  5259 +/* Methods relate to limiting the number events for a given day on a DayGrid
  5260 +----------------------------------------------------------------------------------------------------------------------*/
  5261 +// NOTE: all the segs being passed around in here are foreground segs
  5262 +
  5263 +DayGrid.mixin({
  5264 +
  5265 + segPopover: null, // the Popover that holds events that can't fit in a cell. null when not visible
  5266 + popoverSegs: null, // an array of segment objects that the segPopover holds. null when not visible
  5267 +
  5268 +
  5269 + destroySegPopover: function() {
  5270 + if (this.segPopover) {
  5271 + this.segPopover.hide(); // will trigger destruction of `segPopover` and `popoverSegs`
  5272 + }
  5273 + },
  5274 +
  5275 +
  5276 + // Limits the number of "levels" (vertically stacking layers of events) for each row of the grid.
  5277 + // `levelLimit` can be false (don't limit), a number, or true (should be computed).
  5278 + limitRows: function(levelLimit) {
  5279 + var rowStructs = this.rowStructs || [];
  5280 + var row; // row #
  5281 + var rowLevelLimit;
  5282 +
  5283 + for (row = 0; row < rowStructs.length; row++) {
  5284 + this.unlimitRow(row);
  5285 +
  5286 + if (!levelLimit) {
  5287 + rowLevelLimit = false;
  5288 + }
  5289 + else if (typeof levelLimit === 'number') {
  5290 + rowLevelLimit = levelLimit;
  5291 + }
  5292 + else {
  5293 + rowLevelLimit = this.computeRowLevelLimit(row);
  5294 + }
  5295 +
  5296 + if (rowLevelLimit !== false) {
  5297 + this.limitRow(row, rowLevelLimit);
  5298 + }
  5299 + }
  5300 + },
  5301 +
  5302 +
  5303 + // Computes the number of levels a row will accomodate without going outside its bounds.
  5304 + // Assumes the row is "rigid" (maintains a constant height regardless of what is inside).
  5305 + // `row` is the row number.
  5306 + computeRowLevelLimit: function(row) {
  5307 + var rowEl = this.rowEls.eq(row); // the containing "fake" row div
  5308 + var rowHeight = rowEl.height(); // TODO: cache somehow?
  5309 + var trEls = this.rowStructs[row].tbodyEl.children();
  5310 + var i, trEl;
  5311 + var trHeight;
  5312 +
  5313 + function iterInnerHeights(i, childNode) {
  5314 + trHeight = Math.max(trHeight, $(childNode).outerHeight());
  5315 + }
  5316 +
  5317 + // Reveal one level <tr> at a time and stop when we find one out of bounds
  5318 + for (i = 0; i < trEls.length; i++) {
  5319 + trEl = trEls.eq(i).removeClass('fc-limited'); // reset to original state (reveal)
  5320 +
  5321 + // with rowspans>1 and IE8, trEl.outerHeight() would return the height of the largest cell,
  5322 + // so instead, find the tallest inner content element.
  5323 + trHeight = 0;
  5324 + trEl.find('> td > :first-child').each(iterInnerHeights);
  5325 +
  5326 + if (trEl.position().top + trHeight > rowHeight) {
  5327 + return i;
  5328 + }
  5329 + }
  5330 +
  5331 + return false; // should not limit at all
  5332 + },
  5333 +
  5334 +
  5335 + // Limits the given grid row to the maximum number of levels and injects "more" links if necessary.
  5336 + // `row` is the row number.
  5337 + // `levelLimit` is a number for the maximum (inclusive) number of levels allowed.
  5338 + limitRow: function(row, levelLimit) {
  5339 + var _this = this;
  5340 + var rowStruct = this.rowStructs[row];
  5341 + var moreNodes = []; // array of "more" <a> links and <td> DOM nodes
  5342 + var col = 0; // col #, left-to-right (not chronologically)
  5343 + var cell;
  5344 + var levelSegs; // array of segment objects in the last allowable level, ordered left-to-right
  5345 + var cellMatrix; // a matrix (by level, then column) of all <td> jQuery elements in the row
  5346 + var limitedNodes; // array of temporarily hidden level <tr> and segment <td> DOM nodes
  5347 + var i, seg;
  5348 + var segsBelow; // array of segment objects below `seg` in the current `col`
  5349 + var totalSegsBelow; // total number of segments below `seg` in any of the columns `seg` occupies
  5350 + var colSegsBelow; // array of segment arrays, below seg, one for each column (offset from segs's first column)
  5351 + var td, rowspan;
  5352 + var segMoreNodes; // array of "more" <td> cells that will stand-in for the current seg's cell
  5353 + var j;
  5354 + var moreTd, moreWrap, moreLink;
  5355 +
  5356 + // Iterates through empty level cells and places "more" links inside if need be
  5357 + function emptyCellsUntil(endCol) { // goes from current `col` to `endCol`
  5358 + while (col < endCol) {
  5359 + cell = _this.getCell(row, col);
  5360 + segsBelow = _this.getCellSegs(cell, levelLimit);
  5361 + if (segsBelow.length) {
  5362 + td = cellMatrix[levelLimit - 1][col];
  5363 + moreLink = _this.renderMoreLink(cell, segsBelow);
  5364 + moreWrap = $('<div/>').append(moreLink);
  5365 + td.append(moreWrap);
  5366 + moreNodes.push(moreWrap[0]);
  5367 + }
  5368 + col++;
  5369 + }
  5370 + }
  5371 +
  5372 + if (levelLimit && levelLimit < rowStruct.segLevels.length) { // is it actually over the limit?
  5373 + levelSegs = rowStruct.segLevels[levelLimit - 1];
  5374 + cellMatrix = rowStruct.cellMatrix;
  5375 +
  5376 + limitedNodes = rowStruct.tbodyEl.children().slice(levelLimit) // get level <tr> elements past the limit
  5377 + .addClass('fc-limited').get(); // hide elements and get a simple DOM-nodes array
  5378 +
  5379 + // iterate though segments in the last allowable level
  5380 + for (i = 0; i < levelSegs.length; i++) {
  5381 + seg = levelSegs[i];
  5382 + emptyCellsUntil(seg.leftCol); // process empty cells before the segment
  5383 +
  5384 + // determine *all* segments below `seg` that occupy the same columns
  5385 + colSegsBelow = [];
  5386 + totalSegsBelow = 0;
  5387 + while (col <= seg.rightCol) {
  5388 + cell = this.getCell(row, col);
  5389 + segsBelow = this.getCellSegs(cell, levelLimit);
  5390 + colSegsBelow.push(segsBelow);
  5391 + totalSegsBelow += segsBelow.length;
  5392 + col++;
  5393 + }
  5394 +
  5395 + if (totalSegsBelow) { // do we need to replace this segment with one or many "more" links?
  5396 + td = cellMatrix[levelLimit - 1][seg.leftCol]; // the segment's parent cell
  5397 + rowspan = td.attr('rowspan') || 1;
  5398 + segMoreNodes = [];
  5399 +
  5400 + // make a replacement <td> for each column the segment occupies. will be one for each colspan
  5401 + for (j = 0; j < colSegsBelow.length; j++) {
  5402 + moreTd = $('<td class="fc-more-cell"/>').attr('rowspan', rowspan);
  5403 + segsBelow = colSegsBelow[j];
  5404 + cell = this.getCell(row, seg.leftCol + j);
  5405 + moreLink = this.renderMoreLink(cell, [ seg ].concat(segsBelow)); // count seg as hidden too
  5406 + moreWrap = $('<div/>').append(moreLink);
  5407 + moreTd.append(moreWrap);
  5408 + segMoreNodes.push(moreTd[0]);
  5409 + moreNodes.push(moreTd[0]);
  5410 + }
  5411 +
  5412 + td.addClass('fc-limited').after($(segMoreNodes)); // hide original <td> and inject replacements
  5413 + limitedNodes.push(td[0]);
  5414 + }
  5415 + }
  5416 +
  5417 + emptyCellsUntil(this.colCnt); // finish off the level
  5418 + rowStruct.moreEls = $(moreNodes); // for easy undoing later
  5419 + rowStruct.limitedEls = $(limitedNodes); // for easy undoing later
  5420 + }
  5421 + },
  5422 +
  5423 +
  5424 + // Reveals all levels and removes all "more"-related elements for a grid's row.
  5425 + // `row` is a row number.
  5426 + unlimitRow: function(row) {
  5427 + var rowStruct = this.rowStructs[row];
  5428 +
  5429 + if (rowStruct.moreEls) {
  5430 + rowStruct.moreEls.remove();
  5431 + rowStruct.moreEls = null;
  5432 + }
  5433 +
  5434 + if (rowStruct.limitedEls) {
  5435 + rowStruct.limitedEls.removeClass('fc-limited');
  5436 + rowStruct.limitedEls = null;
  5437 + }
  5438 + },
  5439 +
  5440 +
  5441 + // Renders an <a> element that represents hidden event element for a cell.
  5442 + // Responsible for attaching click handler as well.
  5443 + renderMoreLink: function(cell, hiddenSegs) {
  5444 + var _this = this;
  5445 + var view = this.view;
  5446 +
  5447 + return $('<a class="fc-more"/>')
  5448 + .text(
  5449 + this.getMoreLinkText(hiddenSegs.length)
  5450 + )
  5451 + .on('click', function(ev) {
  5452 + var clickOption = view.opt('eventLimitClick');
  5453 + var date = cell.start;
  5454 + var moreEl = $(this);
  5455 + var dayEl = _this.getCellDayEl(cell);
  5456 + var allSegs = _this.getCellSegs(cell);
  5457 +
  5458 + // rescope the segments to be within the cell's date
  5459 + var reslicedAllSegs = _this.resliceDaySegs(allSegs, date);
  5460 + var reslicedHiddenSegs = _this.resliceDaySegs(hiddenSegs, date);
  5461 +
  5462 + if (typeof clickOption === 'function') {
  5463 + // the returned value can be an atomic option
  5464 + clickOption = view.trigger('eventLimitClick', null, {
  5465 + date: date,
  5466 + dayEl: dayEl,
  5467 + moreEl: moreEl,
  5468 + segs: reslicedAllSegs,
  5469 + hiddenSegs: reslicedHiddenSegs
  5470 + }, ev);
  5471 + }
  5472 +
  5473 + if (clickOption === 'popover') {
  5474 + _this.showSegPopover(cell, moreEl, reslicedAllSegs);
  5475 + }
  5476 + else if (typeof clickOption === 'string') { // a view name
  5477 + view.calendar.zoomTo(date, clickOption);
  5478 + }
  5479 + });
  5480 + },
  5481 +
  5482 +
  5483 + // Reveals the popover that displays all events within a cell
  5484 + showSegPopover: function(cell, moreLink, segs) {
  5485 + var _this = this;
  5486 + var view = this.view;
  5487 + var moreWrap = moreLink.parent(); // the <div> wrapper around the <a>
  5488 + var topEl; // the element we want to match the top coordinate of
  5489 + var options;
  5490 +
  5491 + if (this.rowCnt == 1) {
  5492 + topEl = view.el; // will cause the popover to cover any sort of header
  5493 + }
  5494 + else {
  5495 + topEl = this.rowEls.eq(cell.row); // will align with top of row
  5496 + }
  5497 +
  5498 + options = {
  5499 + className: 'fc-more-popover',
  5500 + content: this.renderSegPopoverContent(cell, segs),
  5501 + parentEl: this.el,
  5502 + top: topEl.offset().top,
  5503 + autoHide: true, // when the user clicks elsewhere, hide the popover
  5504 + viewportConstrain: view.opt('popoverViewportConstrain'),
  5505 + hide: function() {
  5506 + // destroy everything when the popover is hidden
  5507 + _this.segPopover.destroy();
  5508 + _this.segPopover = null;
  5509 + _this.popoverSegs = null;
  5510 + }
  5511 + };
  5512 +
  5513 + // Determine horizontal coordinate.
  5514 + // We use the moreWrap instead of the <td> to avoid border confusion.
  5515 + if (this.isRTL) {
  5516 + options.right = moreWrap.offset().left + moreWrap.outerWidth() + 1; // +1 to be over cell border
  5517 + }
  5518 + else {
  5519 + options.left = moreWrap.offset().left - 1; // -1 to be over cell border
  5520 + }
  5521 +
  5522 + this.segPopover = new Popover(options);
  5523 + this.segPopover.show();
  5524 + },
  5525 +
  5526 +
  5527 + // Builds the inner DOM contents of the segment popover
  5528 + renderSegPopoverContent: function(cell, segs) {
  5529 + var view = this.view;
  5530 + var isTheme = view.opt('theme');
  5531 + var title = cell.start.format(view.opt('dayPopoverFormat'));
  5532 + var content = $(
  5533 + '<div class="fc-header ' + view.widgetHeaderClass + '">' +
  5534 + '<span class="fc-close ' +
  5535 + (isTheme ? 'ui-icon ui-icon-closethick' : 'fc-icon fc-icon-x') +
  5536 + '"></span>' +
  5537 + '<span class="fc-title">' +
  5538 + htmlEscape(title) +
  5539 + '</span>' +
  5540 + '<div class="fc-clear"/>' +
  5541 + '</div>' +
  5542 + '<div class="fc-body ' + view.widgetContentClass + '">' +
  5543 + '<div class="fc-event-container"></div>' +
  5544 + '</div>'
  5545 + );
  5546 + var segContainer = content.find('.fc-event-container');
  5547 + var i;
  5548 +
  5549 + // render each seg's `el` and only return the visible segs
  5550 + segs = this.renderFgSegEls(segs, true); // disableResizing=true
  5551 + this.popoverSegs = segs;
  5552 +
  5553 + for (i = 0; i < segs.length; i++) {
  5554 +
  5555 + // because segments in the popover are not part of a grid coordinate system, provide a hint to any
  5556 + // grids that want to do drag-n-drop about which cell it came from
  5557 + segs[i].cell = cell;
  5558 +
  5559 + segContainer.append(segs[i].el);
  5560 + }
  5561 +
  5562 + return content;
  5563 + },
  5564 +
  5565 +
  5566 + // Given the events within an array of segment objects, reslice them to be in a single day
  5567 + resliceDaySegs: function(segs, dayDate) {
  5568 +
  5569 + // build an array of the original events
  5570 + var events = $.map(segs, function(seg) {
  5571 + return seg.event;
  5572 + });
  5573 +
  5574 + var dayStart = dayDate.clone().stripTime();
  5575 + var dayEnd = dayStart.clone().add(1, 'days');
  5576 + var dayRange = { start: dayStart, end: dayEnd };
  5577 +
  5578 + // slice the events with a custom slicing function
  5579 + segs = this.eventsToSegs(
  5580 + events,
  5581 + function(range) {
  5582 + var seg = intersectionToSeg(range, dayRange); // undefind if no intersection
  5583 + return seg ? [ seg ] : []; // must return an array of segments
  5584 + }
  5585 + );
  5586 +
  5587 + // force an order because eventsToSegs doesn't guarantee one
  5588 + segs.sort(compareSegs);
  5589 +
  5590 + return segs;
  5591 + },
  5592 +
  5593 +
  5594 + // Generates the text that should be inside a "more" link, given the number of events it represents
  5595 + getMoreLinkText: function(num) {
  5596 + var opt = this.view.opt('eventLimitText');
  5597 +
  5598 + if (typeof opt === 'function') {
  5599 + return opt(num);
  5600 + }
  5601 + else {
  5602 + return '+' + num + ' ' + opt;
  5603 + }
  5604 + },
  5605 +
  5606 +
  5607 + // Returns segments within a given cell.
  5608 + // If `startLevel` is specified, returns only events including and below that level. Otherwise returns all segs.
  5609 + getCellSegs: function(cell, startLevel) {
  5610 + var segMatrix = this.rowStructs[cell.row].segMatrix;
  5611 + var level = startLevel || 0;
  5612 + var segs = [];
  5613 + var seg;
  5614 +
  5615 + while (level < segMatrix.length) {
  5616 + seg = segMatrix[level][cell.col];
  5617 + if (seg) {
  5618 + segs.push(seg);
  5619 + }
  5620 + level++;
  5621 + }
  5622 +
  5623 + return segs;
  5624 + }
  5625 +
  5626 +});
  5627 +
  5628 +;;
  5629 +
  5630 +/* A component that renders one or more columns of vertical time slots
  5631 +----------------------------------------------------------------------------------------------------------------------*/
  5632 +
  5633 +var TimeGrid = Grid.extend({
  5634 +
  5635 + slotDuration: null, // duration of a "slot", a distinct time segment on given day, visualized by lines
  5636 + snapDuration: null, // granularity of time for dragging and selecting
  5637 +
  5638 + minTime: null, // Duration object that denotes the first visible time of any given day
  5639 + maxTime: null, // Duration object that denotes the exclusive visible end time of any given day
  5640 +
  5641 + axisFormat: null, // formatting string for times running along vertical axis
  5642 +
  5643 + dayEls: null, // cells elements in the day-row background
  5644 + slatEls: null, // elements running horizontally across all columns
  5645 +
  5646 + slatTops: null, // an array of top positions, relative to the container. last item holds bottom of last slot
  5647 +
  5648 + helperEl: null, // cell skeleton element for rendering the mock event "helper"
  5649 +
  5650 + businessHourSegs: null,
  5651 +
  5652 +
  5653 + constructor: function() {
  5654 + Grid.apply(this, arguments); // call the super-constructor
  5655 + this.processOptions();
  5656 + },
  5657 +
  5658 +
  5659 + // Renders the time grid into `this.el`, which should already be assigned.
  5660 + // Relies on the view's colCnt. In the future, this component should probably be self-sufficient.
  5661 + renderDates: function() {
  5662 + this.el.html(this.renderHtml());
  5663 + this.dayEls = this.el.find('.fc-day');
  5664 + this.slatEls = this.el.find('.fc-slats tr');
  5665 + },
  5666 +
  5667 +
  5668 + renderBusinessHours: function() {
  5669 + var events = this.view.calendar.getBusinessHoursEvents();
  5670 + this.businessHourSegs = this.renderFill('businessHours', this.eventsToSegs(events), 'bgevent');
  5671 + },
  5672 +
  5673 +
  5674 + // Renders the basic HTML skeleton for the grid
  5675 + renderHtml: function() {
  5676 + return '' +
  5677 + '<div class="fc-bg">' +
  5678 + '<table>' +
  5679 + this.rowHtml('slotBg') + // leverages RowRenderer, which will call slotBgCellHtml
  5680 + '</table>' +
  5681 + '</div>' +
  5682 + '<div class="fc-slats">' +
  5683 + '<table>' +
  5684 + this.slatRowHtml() +
  5685 + '</table>' +
  5686 + '</div>';
  5687 + },
  5688 +
  5689 +
  5690 + // Renders the HTML for a vertical background cell behind the slots.
  5691 + // This method is distinct from 'bg' because we wanted a new `rowType` so the View could customize the rendering.
  5692 + slotBgCellHtml: function(cell) {
  5693 + return this.bgCellHtml(cell);
  5694 + },
  5695 +
  5696 +
  5697 + // Generates the HTML for the horizontal "slats" that run width-wise. Has a time axis on a side. Depends on RTL.
  5698 + slatRowHtml: function() {
  5699 + var view = this.view;
  5700 + var isRTL = this.isRTL;
  5701 + var html = '';
  5702 + var slotNormal = this.slotDuration.asMinutes() % 15 === 0;
  5703 + var slotTime = moment.duration(+this.minTime); // wish there was .clone() for durations
  5704 + var slotDate; // will be on the view's first day, but we only care about its time
  5705 + var minutes;
  5706 + var axisHtml;
  5707 +
  5708 + // Calculate the time for each slot
  5709 + while (slotTime < this.maxTime) {
  5710 + slotDate = this.start.clone().time(slotTime); // will be in UTC but that's good. to avoid DST issues
  5711 + minutes = slotDate.minutes();
  5712 +
  5713 + axisHtml =
  5714 + '<td class="fc-axis fc-time ' + view.widgetContentClass + '" ' + view.axisStyleAttr() + '>' +
  5715 + ((!slotNormal || !minutes) ? // if irregular slot duration, or on the hour, then display the time
  5716 + '<span>' + // for matchCellWidths
  5717 + htmlEscape(slotDate.format(this.axisFormat)) +
  5718 + '</span>' :
  5719 + ''
  5720 + ) +
  5721 + '</td>';
  5722 +
  5723 + html +=
  5724 + '<tr ' + (!minutes ? '' : 'class="fc-minor"') + '>' +
  5725 + (!isRTL ? axisHtml : '') +
  5726 + '<td class="' + view.widgetContentClass + '"/>' +
  5727 + (isRTL ? axisHtml : '') +
  5728 + "</tr>";
  5729 +
  5730 + slotTime.add(this.slotDuration);
  5731 + }
  5732 +
  5733 + return html;
  5734 + },
  5735 +
  5736 +
  5737 + /* Options
  5738 + ------------------------------------------------------------------------------------------------------------------*/
  5739 +
  5740 +
  5741 + // Parses various options into properties of this object
  5742 + processOptions: function() {
  5743 + var view = this.view;
  5744 + var slotDuration = view.opt('slotDuration');
  5745 + var snapDuration = view.opt('snapDuration');
  5746 +
  5747 + slotDuration = moment.duration(slotDuration);
  5748 + snapDuration = snapDuration ? moment.duration(snapDuration) : slotDuration;
  5749 +
  5750 + this.slotDuration = slotDuration;
  5751 + this.snapDuration = snapDuration;
  5752 + this.cellDuration = snapDuration; // for Grid system
  5753 +
  5754 + this.minTime = moment.duration(view.opt('minTime'));
  5755 + this.maxTime = moment.duration(view.opt('maxTime'));
  5756 +
  5757 + this.axisFormat = view.opt('axisFormat') || view.opt('smallTimeFormat');
  5758 + },
  5759 +
  5760 +
  5761 + // Computes a default column header formatting string if `colFormat` is not explicitly defined
  5762 + computeColHeadFormat: function() {
  5763 + if (this.colCnt > 1) { // multiple days, so full single date string WON'T be in title text
  5764 + return this.view.opt('dayOfMonthFormat'); // "Sat 12/10"
  5765 + }
  5766 + else { // single day, so full single date string will probably be in title text
  5767 + return 'dddd'; // "Saturday"
  5768 + }
  5769 + },
  5770 +
  5771 +
  5772 + // Computes a default event time formatting string if `timeFormat` is not explicitly defined
  5773 + computeEventTimeFormat: function() {
  5774 + return this.view.opt('noMeridiemTimeFormat'); // like "6:30" (no AM/PM)
  5775 + },
  5776 +
  5777 +
  5778 + // Computes a default `displayEventEnd` value if one is not expliclty defined
  5779 + computeDisplayEventEnd: function() {
  5780 + return true;
  5781 + },
  5782 +
  5783 +
  5784 + /* Cell System
  5785 + ------------------------------------------------------------------------------------------------------------------*/
  5786 +
  5787 +
  5788 + // Initializes row/col information
  5789 + updateCells: function() {
  5790 + var view = this.view;
  5791 + var colData = [];
  5792 + var date;
  5793 +
  5794 + date = this.start.clone();
  5795 + while (date.isBefore(this.end)) {
  5796 + colData.push({
  5797 + day: date.clone()
  5798 + });
  5799 + date.add(1, 'day');
  5800 + date = view.skipHiddenDays(date);
  5801 + }
  5802 +
  5803 + if (this.isRTL) {
  5804 + colData.reverse();
  5805 + }
  5806 +
  5807 + this.colData = colData;
  5808 + this.colCnt = colData.length;
  5809 + this.rowCnt = Math.ceil((this.maxTime - this.minTime) / this.snapDuration); // # of vertical snaps
  5810 + },
  5811 +
  5812 +
  5813 + // Given a cell object, generates its start date. Returns a reference-free copy.
  5814 + computeCellDate: function(cell) {
  5815 + var time = this.computeSnapTime(cell.row);
  5816 +
  5817 + return this.view.calendar.rezoneDate(cell.day).time(time);
  5818 + },
  5819 +
  5820 +
  5821 + // Retrieves the element representing the given column
  5822 + getColEl: function(col) {
  5823 + return this.dayEls.eq(col);
  5824 + },
  5825 +
  5826 +
  5827 + /* Dates
  5828 + ------------------------------------------------------------------------------------------------------------------*/
  5829 +
  5830 +
  5831 + // Given a row number of the grid, representing a "snap", returns a time (Duration) from its start-of-day
  5832 + computeSnapTime: function(row) {
  5833 + return moment.duration(this.minTime + this.snapDuration * row);
  5834 + },
  5835 +
  5836 +
  5837 + // Slices up a date range by column into an array of segments
  5838 + rangeToSegs: function(range) {
  5839 + var colCnt = this.colCnt;
  5840 + var segs = [];
  5841 + var seg;
  5842 + var col;
  5843 + var colDate;
  5844 + var colRange;
  5845 +
  5846 + // normalize :(
  5847 + range = {
  5848 + start: range.start.clone().stripZone(),
  5849 + end: range.end.clone().stripZone()
  5850 + };
  5851 +
  5852 + for (col = 0; col < colCnt; col++) {
  5853 + colDate = this.colData[col].day; // will be ambig time/timezone
  5854 + colRange = {
  5855 + start: colDate.clone().time(this.minTime),
  5856 + end: colDate.clone().time(this.maxTime)
  5857 + };
  5858 + seg = intersectionToSeg(range, colRange); // both will be ambig timezone
  5859 + if (seg) {
  5860 + seg.col = col;
  5861 + segs.push(seg);
  5862 + }
  5863 + }
  5864 +
  5865 + return segs;
  5866 + },
  5867 +
  5868 +
  5869 + /* Coordinates
  5870 + ------------------------------------------------------------------------------------------------------------------*/
  5871 +
  5872 +
  5873 + updateSize: function(isResize) { // NOT a standard Grid method
  5874 + this.computeSlatTops();
  5875 +
  5876 + if (isResize) {
  5877 + this.updateSegVerticals();
  5878 + }
  5879 + },
  5880 +
  5881 +
  5882 + // Computes the top/bottom coordinates of each "snap" rows
  5883 + computeRowCoords: function() {
  5884 + var originTop = this.el.offset().top;
  5885 + var items = [];
  5886 + var i;
  5887 + var item;
  5888 +
  5889 + for (i = 0; i < this.rowCnt; i++) {
  5890 + item = {
  5891 + top: originTop + this.computeTimeTop(this.computeSnapTime(i))
  5892 + };
  5893 + if (i > 0) {
  5894 + items[i - 1].bottom = item.top;
  5895 + }
  5896 + items.push(item);
  5897 + }
  5898 + item.bottom = item.top + this.computeTimeTop(this.computeSnapTime(i));
  5899 +
  5900 + return items;
  5901 + },
  5902 +
  5903 +
  5904 + // Computes the top coordinate, relative to the bounds of the grid, of the given date.
  5905 + // A `startOfDayDate` must be given for avoiding ambiguity over how to treat midnight.
  5906 + computeDateTop: function(date, startOfDayDate) {
  5907 + return this.computeTimeTop(
  5908 + moment.duration(
  5909 + date.clone().stripZone() - startOfDayDate.clone().stripTime()
  5910 + )
  5911 + );
  5912 + },
  5913 +
  5914 +
  5915 + // Computes the top coordinate, relative to the bounds of the grid, of the given time (a Duration).
  5916 + computeTimeTop: function(time) {
  5917 + var slatCoverage = (time - this.minTime) / this.slotDuration; // floating-point value of # of slots covered
  5918 + var slatIndex;
  5919 + var slatRemainder;
  5920 + var slatTop;
  5921 + var slatBottom;
  5922 +
  5923 + // constrain. because minTime/maxTime might be customized
  5924 + slatCoverage = Math.max(0, slatCoverage);
  5925 + slatCoverage = Math.min(this.slatEls.length, slatCoverage);
  5926 +
  5927 + slatIndex = Math.floor(slatCoverage); // an integer index of the furthest whole slot
  5928 + slatRemainder = slatCoverage - slatIndex;
  5929 + slatTop = this.slatTops[slatIndex]; // the top position of the furthest whole slot
  5930 +
  5931 + if (slatRemainder) { // time spans part-way into the slot
  5932 + slatBottom = this.slatTops[slatIndex + 1];
  5933 + return slatTop + (slatBottom - slatTop) * slatRemainder; // part-way between slots
  5934 + }
  5935 + else {
  5936 + return slatTop;
  5937 + }
  5938 + },
  5939 +
  5940 +
  5941 + // Queries each `slatEl` for its position relative to the grid's container and stores it in `slatTops`.
  5942 + // Includes the the bottom of the last slat as the last item in the array.
  5943 + computeSlatTops: function() {
  5944 + var tops = [];
  5945 + var top;
  5946 +
  5947 + this.slatEls.each(function(i, node) {
  5948 + top = $(node).position().top;
  5949 + tops.push(top);
  5950 + });
  5951 +
  5952 + tops.push(top + this.slatEls.last().outerHeight()); // bottom of the last slat
  5953 +
  5954 + this.slatTops = tops;
  5955 + },
  5956 +
  5957 +
  5958 + /* Event Drag Visualization
  5959 + ------------------------------------------------------------------------------------------------------------------*/
  5960 +
  5961 +
  5962 + // Renders a visual indication of an event being dragged over the specified date(s).
  5963 + // dropLocation's end might be null, as well as `seg`. See Grid::renderDrag for more info.
  5964 + // A returned value of `true` signals that a mock "helper" event has been rendered.
  5965 + renderDrag: function(dropLocation, seg) {
  5966 +
  5967 + if (seg) { // if there is event information for this drag, render a helper event
  5968 + this.renderRangeHelper(dropLocation, seg);
  5969 + this.applyDragOpacity(this.helperEl);
  5970 +
  5971 + return true; // signal that a helper has been rendered
  5972 + }
  5973 + else {
  5974 + // otherwise, just render a highlight
  5975 + this.renderHighlight(
  5976 + this.view.calendar.ensureVisibleEventRange(dropLocation) // needs to be a proper range
  5977 + );
  5978 + }
  5979 + },
  5980 +
  5981 +
  5982 + // Unrenders any visual indication of an event being dragged
  5983 + destroyDrag: function() {
  5984 + this.destroyHelper();
  5985 + this.destroyHighlight();
  5986 + },
  5987 +
  5988 +
  5989 + /* Event Resize Visualization
  5990 + ------------------------------------------------------------------------------------------------------------------*/
  5991 +
  5992 +
  5993 + // Renders a visual indication of an event being resized
  5994 + renderEventResize: function(range, seg) {
  5995 + this.renderRangeHelper(range, seg);
  5996 + },
  5997 +
  5998 +
  5999 + // Unrenders any visual indication of an event being resized
  6000 + destroyEventResize: function() {
  6001 + this.destroyHelper();
  6002 + },
  6003 +
  6004 +
  6005 + /* Event Helper
  6006 + ------------------------------------------------------------------------------------------------------------------*/
  6007 +
  6008 +
  6009 + // Renders a mock "helper" event. `sourceSeg` is the original segment object and might be null (an external drag)
  6010 + renderHelper: function(event, sourceSeg) {
  6011 + var segs = this.eventsToSegs([ event ]);
  6012 + var tableEl;
  6013 + var i, seg;
  6014 + var sourceEl;
  6015 +
  6016 + segs = this.renderFgSegEls(segs); // assigns each seg's el and returns a subset of segs that were rendered
  6017 + tableEl = this.renderSegTable(segs);
  6018 +
  6019 + // Try to make the segment that is in the same row as sourceSeg look the same
  6020 + for (i = 0; i < segs.length; i++) {
  6021 + seg = segs[i];
  6022 + if (sourceSeg && sourceSeg.col === seg.col) {
  6023 + sourceEl = sourceSeg.el;
  6024 + seg.el.css({
  6025 + left: sourceEl.css('left'),
  6026 + right: sourceEl.css('right'),
  6027 + 'margin-left': sourceEl.css('margin-left'),
  6028 + 'margin-right': sourceEl.css('margin-right')
  6029 + });
  6030 + }
  6031 + }
  6032 +
  6033 + this.helperEl = $('<div class="fc-helper-skeleton"/>')
  6034 + .append(tableEl)
  6035 + .appendTo(this.el);
  6036 + },
  6037 +
  6038 +
  6039 + // Unrenders any mock helper event
  6040 + destroyHelper: function() {
  6041 + if (this.helperEl) {
  6042 + this.helperEl.remove();
  6043 + this.helperEl = null;
  6044 + }
  6045 + },
  6046 +
  6047 +
  6048 + /* Selection
  6049 + ------------------------------------------------------------------------------------------------------------------*/
  6050 +
  6051 +
  6052 + // Renders a visual indication of a selection. Overrides the default, which was to simply render a highlight.
  6053 + renderSelection: function(range) {
  6054 + if (this.view.opt('selectHelper')) { // this setting signals that a mock helper event should be rendered
  6055 + this.renderRangeHelper(range);
  6056 + }
  6057 + else {
  6058 + this.renderHighlight(range);
  6059 + }
  6060 + },
  6061 +
  6062 +
  6063 + // Unrenders any visual indication of a selection
  6064 + destroySelection: function() {
  6065 + this.destroyHelper();
  6066 + this.destroyHighlight();
  6067 + },
  6068 +
  6069 +
  6070 + /* Fill System (highlight, background events, business hours)
  6071 + ------------------------------------------------------------------------------------------------------------------*/
  6072 +
  6073 +
  6074 + // Renders a set of rectangles over the given time segments.
  6075 + // Only returns segments that successfully rendered.
  6076 + renderFill: function(type, segs, className) {
  6077 + var segCols;
  6078 + var skeletonEl;
  6079 + var trEl;
  6080 + var col, colSegs;
  6081 + var tdEl;
  6082 + var containerEl;
  6083 + var dayDate;
  6084 + var i, seg;
  6085 +
  6086 + if (segs.length) {
  6087 +
  6088 + segs = this.renderFillSegEls(type, segs); // assignes `.el` to each seg. returns successfully rendered segs
  6089 + segCols = this.groupSegCols(segs); // group into sub-arrays, and assigns 'col' to each seg
  6090 +
  6091 + className = className || type.toLowerCase();
  6092 + skeletonEl = $(
  6093 + '<div class="fc-' + className + '-skeleton">' +
  6094 + '<table><tr/></table>' +
  6095 + '</div>'
  6096 + );
  6097 + trEl = skeletonEl.find('tr');
  6098 +
  6099 + for (col = 0; col < segCols.length; col++) {
  6100 + colSegs = segCols[col];
  6101 + tdEl = $('<td/>').appendTo(trEl);
  6102 +
  6103 + if (colSegs.length) {
  6104 + containerEl = $('<div class="fc-' + className + '-container"/>').appendTo(tdEl);
  6105 + dayDate = this.colData[col].day;
  6106 +
  6107 + for (i = 0; i < colSegs.length; i++) {
  6108 + seg = colSegs[i];
  6109 + containerEl.append(
  6110 + seg.el.css({
  6111 + top: this.computeDateTop(seg.start, dayDate),
  6112 + bottom: -this.computeDateTop(seg.end, dayDate) // the y position of the bottom edge
  6113 + })
  6114 + );
  6115 + }
  6116 + }
  6117 + }
  6118 +
  6119 + this.bookendCells(trEl, type);
  6120 +
  6121 + this.el.append(skeletonEl);
  6122 + this.elsByFill[type] = skeletonEl;
  6123 + }
  6124 +
  6125 + return segs;
  6126 + }
  6127 +
  6128 +});
  6129 +
  6130 +;;
  6131 +
  6132 +/* Event-rendering methods for the TimeGrid class
  6133 +----------------------------------------------------------------------------------------------------------------------*/
  6134 +
  6135 +TimeGrid.mixin({
  6136 +
  6137 + eventSkeletonEl: null, // has cells with event-containers, which contain absolutely positioned event elements
  6138 +
  6139 +
  6140 + // Renders the given foreground event segments onto the grid
  6141 + renderFgSegs: function(segs) {
  6142 + segs = this.renderFgSegEls(segs); // returns a subset of the segs. segs that were actually rendered
  6143 +
  6144 + this.el.append(
  6145 + this.eventSkeletonEl = $('<div class="fc-content-skeleton"/>')
  6146 + .append(this.renderSegTable(segs))
  6147 + );
  6148 +
  6149 + return segs; // return only the segs that were actually rendered
  6150 + },
  6151 +
  6152 +
  6153 + // Unrenders all currently rendered foreground event segments
  6154 + destroyFgSegs: function(segs) {
  6155 + if (this.eventSkeletonEl) {
  6156 + this.eventSkeletonEl.remove();
  6157 + this.eventSkeletonEl = null;
  6158 + }
  6159 + },
  6160 +
  6161 +
  6162 + // Renders and returns the <table> portion of the event-skeleton.
  6163 + // Returns an object with properties 'tbodyEl' and 'segs'.
  6164 + renderSegTable: function(segs) {
  6165 + var tableEl = $('<table><tr/></table>');
  6166 + var trEl = tableEl.find('tr');
  6167 + var segCols;
  6168 + var i, seg;
  6169 + var col, colSegs;
  6170 + var containerEl;
  6171 +
  6172 + segCols = this.groupSegCols(segs); // group into sub-arrays, and assigns 'col' to each seg
  6173 +
  6174 + this.computeSegVerticals(segs); // compute and assign top/bottom
  6175 +
  6176 + for (col = 0; col < segCols.length; col++) { // iterate each column grouping
  6177 + colSegs = segCols[col];
  6178 + placeSlotSegs(colSegs); // compute horizontal coordinates, z-index's, and reorder the array
  6179 +
  6180 + containerEl = $('<div class="fc-event-container"/>');
  6181 +
  6182 + // assign positioning CSS and insert into container
  6183 + for (i = 0; i < colSegs.length; i++) {
  6184 + seg = colSegs[i];
  6185 + seg.el.css(this.generateSegPositionCss(seg));
  6186 +
  6187 + // if the height is short, add a className for alternate styling
  6188 + if (seg.bottom - seg.top < 30) {
  6189 + seg.el.addClass('fc-short');
  6190 + }
  6191 +
  6192 + containerEl.append(seg.el);
  6193 + }
  6194 +
  6195 + trEl.append($('<td/>').append(containerEl));
  6196 + }
  6197 +
  6198 + this.bookendCells(trEl, 'eventSkeleton');
  6199 +
  6200 + return tableEl;
  6201 + },
  6202 +
  6203 +
  6204 + // Refreshes the CSS top/bottom coordinates for each segment element. Probably after a window resize/zoom.
  6205 + // Repositions business hours segs too, so not just for events. Maybe shouldn't be here.
  6206 + updateSegVerticals: function() {
  6207 + var allSegs = (this.segs || []).concat(this.businessHourSegs || []);
  6208 + var i;
  6209 +
  6210 + this.computeSegVerticals(allSegs);
  6211 +
  6212 + for (i = 0; i < allSegs.length; i++) {
  6213 + allSegs[i].el.css(
  6214 + this.generateSegVerticalCss(allSegs[i])
  6215 + );
  6216 + }
  6217 + },
  6218 +
  6219 +
  6220 + // For each segment in an array, computes and assigns its top and bottom properties
  6221 + computeSegVerticals: function(segs) {
  6222 + var i, seg;
  6223 +
  6224 + for (i = 0; i < segs.length; i++) {
  6225 + seg = segs[i];
  6226 + seg.top = this.computeDateTop(seg.start, seg.start);
  6227 + seg.bottom = this.computeDateTop(seg.end, seg.start);
  6228 + }
  6229 + },
  6230 +
  6231 +
  6232 + // Renders the HTML for a single event segment's default rendering
  6233 + fgSegHtml: function(seg, disableResizing) {
  6234 + var view = this.view;
  6235 + var event = seg.event;
  6236 + var isDraggable = view.isEventDraggable(event);
  6237 + var isResizableFromStart = !disableResizing && seg.isStart && view.isEventResizableFromStart(event);
  6238 + var isResizableFromEnd = !disableResizing && seg.isEnd && view.isEventResizableFromEnd(event);
  6239 + var classes = this.getSegClasses(seg, isDraggable, isResizableFromStart || isResizableFromEnd);
  6240 + var skinCss = cssToStr(this.getEventSkinCss(event));
  6241 + var timeText;
  6242 + var fullTimeText; // more verbose time text. for the print stylesheet
  6243 + var startTimeText; // just the start time text
  6244 +
  6245 + classes.unshift('fc-time-grid-event', 'fc-v-event');
  6246 +
  6247 + if (view.isMultiDayEvent(event)) { // if the event appears to span more than one day...
  6248 + // Don't display time text on segments that run entirely through a day.
  6249 + // That would appear as midnight-midnight and would look dumb.
  6250 + // Otherwise, display the time text for the *segment's* times (like 6pm-midnight or midnight-10am)
  6251 + if (seg.isStart || seg.isEnd) {
  6252 + timeText = this.getEventTimeText(seg);
  6253 + fullTimeText = this.getEventTimeText(seg, 'LT');
  6254 + startTimeText = this.getEventTimeText(seg, null, false); // displayEnd=false
  6255 + }
  6256 + } else {
  6257 + // Display the normal time text for the *event's* times
  6258 + timeText = this.getEventTimeText(event);
  6259 + fullTimeText = this.getEventTimeText(event, 'LT');
  6260 + startTimeText = this.getEventTimeText(event, null, false); // displayEnd=false
  6261 + }
  6262 +
  6263 + return '<a class="' + classes.join(' ') + '"' +
  6264 + (event.url ?
  6265 + ' href="' + htmlEscape(event.url) + '"' :
  6266 + ''
  6267 + ) +
  6268 + (skinCss ?
  6269 + ' style="' + skinCss + '"' :
  6270 + ''
  6271 + ) +
  6272 + '>' +
  6273 + '<div class="fc-content">' +
  6274 + (timeText ?
  6275 + '<div class="fc-time"' +
  6276 + ' data-start="' + htmlEscape(startTimeText) + '"' +
  6277 + ' data-full="' + htmlEscape(fullTimeText) + '"' +
  6278 + '>' +
  6279 + '<span>' + htmlEscape(timeText) + '</span>' +
  6280 + '</div>' :
  6281 + ''
  6282 + ) +
  6283 + (event.title ?
  6284 + '<div class="fc-title">' +
  6285 + htmlEscape(event.title) +
  6286 + '</div>' :
  6287 + ''
  6288 + ) +
  6289 + '</div>' +
  6290 + '<div class="fc-bg"/>' +
  6291 + /* TODO: write CSS for this
  6292 + (isResizableFromStart ?
  6293 + '<div class="fc-resizer fc-start-resizer" />' :
  6294 + ''
  6295 + ) +
  6296 + */
  6297 + (isResizableFromEnd ?
  6298 + '<div class="fc-resizer fc-end-resizer" />' :
  6299 + ''
  6300 + ) +
  6301 + '</a>';
  6302 + },
  6303 +
  6304 +
  6305 + // Generates an object with CSS properties/values that should be applied to an event segment element.
  6306 + // Contains important positioning-related properties that should be applied to any event element, customized or not.
  6307 + generateSegPositionCss: function(seg) {
  6308 + var shouldOverlap = this.view.opt('slotEventOverlap');
  6309 + var backwardCoord = seg.backwardCoord; // the left side if LTR. the right side if RTL. floating-point
  6310 + var forwardCoord = seg.forwardCoord; // the right side if LTR. the left side if RTL. floating-point
  6311 + var props = this.generateSegVerticalCss(seg); // get top/bottom first
  6312 + var left; // amount of space from left edge, a fraction of the total width
  6313 + var right; // amount of space from right edge, a fraction of the total width
  6314 +
  6315 + if (shouldOverlap) {
  6316 + // double the width, but don't go beyond the maximum forward coordinate (1.0)
  6317 + forwardCoord = Math.min(1, backwardCoord + (forwardCoord - backwardCoord) * 2);
  6318 + }
  6319 +
  6320 + if (this.isRTL) {
  6321 + left = 1 - forwardCoord;
  6322 + right = backwardCoord;
  6323 + }
  6324 + else {
  6325 + left = backwardCoord;
  6326 + right = 1 - forwardCoord;
  6327 + }
  6328 +
  6329 + props.zIndex = seg.level + 1; // convert from 0-base to 1-based
  6330 + props.left = left * 100 + '%';
  6331 + props.right = right * 100 + '%';
  6332 +
  6333 + if (shouldOverlap && seg.forwardPressure) {
  6334 + // add padding to the edge so that forward stacked events don't cover the resizer's icon
  6335 + props[this.isRTL ? 'marginLeft' : 'marginRight'] = 10 * 2; // 10 is a guesstimate of the icon's width
  6336 + }
  6337 +
  6338 + return props;
  6339 + },
  6340 +
  6341 +
  6342 + // Generates an object with CSS properties for the top/bottom coordinates of a segment element
  6343 + generateSegVerticalCss: function(seg) {
  6344 + return {
  6345 + top: seg.top,
  6346 + bottom: -seg.bottom // flipped because needs to be space beyond bottom edge of event container
  6347 + };
  6348 + },
  6349 +
  6350 +
  6351 + // Given a flat array of segments, return an array of sub-arrays, grouped by each segment's col
  6352 + groupSegCols: function(segs) {
  6353 + var segCols = [];
  6354 + var i;
  6355 +
  6356 + for (i = 0; i < this.colCnt; i++) {
  6357 + segCols.push([]);
  6358 + }
  6359 +
  6360 + for (i = 0; i < segs.length; i++) {
  6361 + segCols[segs[i].col].push(segs[i]);
  6362 + }
  6363 +
  6364 + return segCols;
  6365 + }
  6366 +
  6367 +});
  6368 +
  6369 +
  6370 +// Given an array of segments that are all in the same column, sets the backwardCoord and forwardCoord on each.
  6371 +// NOTE: Also reorders the given array by date!
  6372 +function placeSlotSegs(segs) {
  6373 + var levels;
  6374 + var level0;
  6375 + var i;
  6376 +
  6377 + segs.sort(compareSegs); // order by date
  6378 + levels = buildSlotSegLevels(segs);
  6379 + computeForwardSlotSegs(levels);
  6380 +
  6381 + if ((level0 = levels[0])) {
  6382 +
  6383 + for (i = 0; i < level0.length; i++) {
  6384 + computeSlotSegPressures(level0[i]);
  6385 + }
  6386 +
  6387 + for (i = 0; i < level0.length; i++) {
  6388 + computeSlotSegCoords(level0[i], 0, 0);
  6389 + }
  6390 + }
  6391 +}
  6392 +
  6393 +
  6394 +// Builds an array of segments "levels". The first level will be the leftmost tier of segments if the calendar is
  6395 +// left-to-right, or the rightmost if the calendar is right-to-left. Assumes the segments are already ordered by date.
  6396 +function buildSlotSegLevels(segs) {
  6397 + var levels = [];
  6398 + var i, seg;
  6399 + var j;
  6400 +
  6401 + for (i=0; i<segs.length; i++) {
  6402 + seg = segs[i];
  6403 +
  6404 + // go through all the levels and stop on the first level where there are no collisions
  6405 + for (j=0; j<levels.length; j++) {
  6406 + if (!computeSlotSegCollisions(seg, levels[j]).length) {
  6407 + break;
  6408 + }
  6409 + }
  6410 +
  6411 + seg.level = j;
  6412 +
  6413 + (levels[j] || (levels[j] = [])).push(seg);
  6414 + }
  6415 +
  6416 + return levels;
  6417 +}
  6418 +
  6419 +
  6420 +// For every segment, figure out the other segments that are in subsequent
  6421 +// levels that also occupy the same vertical space. Accumulate in seg.forwardSegs
  6422 +function computeForwardSlotSegs(levels) {
  6423 + var i, level;
  6424 + var j, seg;
  6425 + var k;
  6426 +
  6427 + for (i=0; i<levels.length; i++) {
  6428 + level = levels[i];
  6429 +
  6430 + for (j=0; j<level.length; j++) {
  6431 + seg = level[j];
  6432 +
  6433 + seg.forwardSegs = [];
  6434 + for (k=i+1; k<levels.length; k++) {
  6435 + computeSlotSegCollisions(seg, levels[k], seg.forwardSegs);
  6436 + }
  6437 + }
  6438 + }
  6439 +}
  6440 +
  6441 +
  6442 +// Figure out which path forward (via seg.forwardSegs) results in the longest path until
  6443 +// the furthest edge is reached. The number of segments in this path will be seg.forwardPressure
  6444 +function computeSlotSegPressures(seg) {
  6445 + var forwardSegs = seg.forwardSegs;
  6446 + var forwardPressure = 0;
  6447 + var i, forwardSeg;
  6448 +
  6449 + if (seg.forwardPressure === undefined) { // not already computed
  6450 +
  6451 + for (i=0; i<forwardSegs.length; i++) {
  6452 + forwardSeg = forwardSegs[i];
  6453 +
  6454 + // figure out the child's maximum forward path
  6455 + computeSlotSegPressures(forwardSeg);
  6456 +
  6457 + // either use the existing maximum, or use the child's forward pressure
  6458 + // plus one (for the forwardSeg itself)
  6459 + forwardPressure = Math.max(
  6460 + forwardPressure,
  6461 + 1 + forwardSeg.forwardPressure
  6462 + );
  6463 + }
  6464 +
  6465 + seg.forwardPressure = forwardPressure;
  6466 + }
  6467 +}
  6468 +
  6469 +
  6470 +// Calculate seg.forwardCoord and seg.backwardCoord for the segment, where both values range
  6471 +// from 0 to 1. If the calendar is left-to-right, the seg.backwardCoord maps to "left" and
  6472 +// seg.forwardCoord maps to "right" (via percentage). Vice-versa if the calendar is right-to-left.
  6473 +//
  6474 +// The segment might be part of a "series", which means consecutive segments with the same pressure
  6475 +// who's width is unknown until an edge has been hit. `seriesBackwardPressure` is the number of
  6476 +// segments behind this one in the current series, and `seriesBackwardCoord` is the starting
  6477 +// coordinate of the first segment in the series.
  6478 +function computeSlotSegCoords(seg, seriesBackwardPressure, seriesBackwardCoord) {
  6479 + var forwardSegs = seg.forwardSegs;
  6480 + var i;
  6481 +
  6482 + if (seg.forwardCoord === undefined) { // not already computed
  6483 +
  6484 + if (!forwardSegs.length) {
  6485 +
  6486 + // if there are no forward segments, this segment should butt up against the edge
  6487 + seg.forwardCoord = 1;
  6488 + }
  6489 + else {
  6490 +
  6491 + // sort highest pressure first
  6492 + forwardSegs.sort(compareForwardSlotSegs);
  6493 +
  6494 + // this segment's forwardCoord will be calculated from the backwardCoord of the
  6495 + // highest-pressure forward segment.
  6496 + computeSlotSegCoords(forwardSegs[0], seriesBackwardPressure + 1, seriesBackwardCoord);
  6497 + seg.forwardCoord = forwardSegs[0].backwardCoord;
  6498 + }
  6499 +
  6500 + // calculate the backwardCoord from the forwardCoord. consider the series
  6501 + seg.backwardCoord = seg.forwardCoord -
  6502 + (seg.forwardCoord - seriesBackwardCoord) / // available width for series
  6503 + (seriesBackwardPressure + 1); // # of segments in the series
  6504 +
  6505 + // use this segment's coordinates to computed the coordinates of the less-pressurized
  6506 + // forward segments
  6507 + for (i=0; i<forwardSegs.length; i++) {
  6508 + computeSlotSegCoords(forwardSegs[i], 0, seg.forwardCoord);
  6509 + }
  6510 + }
  6511 +}
  6512 +
  6513 +
  6514 +// Find all the segments in `otherSegs` that vertically collide with `seg`.
  6515 +// Append into an optionally-supplied `results` array and return.
  6516 +function computeSlotSegCollisions(seg, otherSegs, results) {
  6517 + results = results || [];
  6518 +
  6519 + for (var i=0; i<otherSegs.length; i++) {
  6520 + if (isSlotSegCollision(seg, otherSegs[i])) {
  6521 + results.push(otherSegs[i]);
  6522 + }
  6523 + }
  6524 +
  6525 + return results;
  6526 +}
  6527 +
  6528 +
  6529 +// Do these segments occupy the same vertical space?
  6530 +function isSlotSegCollision(seg1, seg2) {
  6531 + return seg1.bottom > seg2.top && seg1.top < seg2.bottom;
  6532 +}
  6533 +
  6534 +
  6535 +// A cmp function for determining which forward segment to rely on more when computing coordinates.
  6536 +function compareForwardSlotSegs(seg1, seg2) {
  6537 + // put higher-pressure first
  6538 + return seg2.forwardPressure - seg1.forwardPressure ||
  6539 + // put segments that are closer to initial edge first (and favor ones with no coords yet)
  6540 + (seg1.backwardCoord || 0) - (seg2.backwardCoord || 0) ||
  6541 + // do normal sorting...
  6542 + compareSegs(seg1, seg2);
  6543 +}
  6544 +
  6545 +;;
  6546 +
  6547 +/* An abstract class from which other views inherit from
  6548 +----------------------------------------------------------------------------------------------------------------------*/
  6549 +
  6550 +var View = fc.View = Class.extend({
  6551 +
  6552 + type: null, // subclass' view name (string)
  6553 + name: null, // deprecated. use `type` instead
  6554 + title: null, // the text that will be displayed in the header's title
  6555 +
  6556 + calendar: null, // owner Calendar object
  6557 + options: null, // hash containing all options. already merged with view-specific-options
  6558 + coordMap: null, // a CoordMap object for converting pixel regions to dates
  6559 + el: null, // the view's containing element. set by Calendar
  6560 +
  6561 + isDisplayed: false,
  6562 + isSkeletonRendered: false,
  6563 + isEventsRendered: false,
  6564 +
  6565 + // range the view is actually displaying (moments)
  6566 + start: null,
  6567 + end: null, // exclusive
  6568 +
  6569 + // range the view is formally responsible for (moments)
  6570 + // may be different from start/end. for example, a month view might have 1st-31st, excluding padded dates
  6571 + intervalStart: null,
  6572 + intervalEnd: null, // exclusive
  6573 + intervalDuration: null,
  6574 + intervalUnit: null, // name of largest unit being displayed, like "month" or "week"
  6575 +
  6576 + isSelected: false, // boolean whether a range of time is user-selected or not
  6577 +
  6578 + // subclasses can optionally use a scroll container
  6579 + scrollerEl: null, // the element that will most likely scroll when content is too tall
  6580 + scrollTop: null, // cached vertical scroll value
  6581 +
  6582 + // classNames styled by jqui themes
  6583 + widgetHeaderClass: null,
  6584 + widgetContentClass: null,
  6585 + highlightStateClass: null,
  6586 +
  6587 + // for date utils, computed from options
  6588 + nextDayThreshold: null,
  6589 + isHiddenDayHash: null,
  6590 +
  6591 + // document handlers, bound to `this` object
  6592 + documentMousedownProxy: null, // TODO: doesn't work with touch
  6593 +
  6594 +
  6595 + constructor: function(calendar, type, options, intervalDuration) {
  6596 +
  6597 + this.calendar = calendar;
  6598 + this.type = this.name = type; // .name is deprecated
  6599 + this.options = options;
  6600 + this.intervalDuration = intervalDuration || moment.duration(1, 'day');
  6601 +
  6602 + this.nextDayThreshold = moment.duration(this.opt('nextDayThreshold'));
  6603 + this.initThemingProps();
  6604 + this.initHiddenDays();
  6605 +
  6606 + this.documentMousedownProxy = proxy(this, 'documentMousedown');
  6607 +
  6608 + this.initialize();
  6609 + },
  6610 +
  6611 +
  6612 + // A good place for subclasses to initialize member variables
  6613 + initialize: function() {
  6614 + // subclasses can implement
  6615 + },
  6616 +
  6617 +
  6618 + // Retrieves an option with the given name
  6619 + opt: function(name) {
  6620 + return this.options[name];
  6621 + },
  6622 +
  6623 +
  6624 + // Triggers handlers that are view-related. Modifies args before passing to calendar.
  6625 + trigger: function(name, thisObj) { // arguments beyond thisObj are passed along
  6626 + var calendar = this.calendar;
  6627 +
  6628 + return calendar.trigger.apply(
  6629 + calendar,
  6630 + [name, thisObj || this].concat(
  6631 + Array.prototype.slice.call(arguments, 2), // arguments beyond thisObj
  6632 + [ this ] // always make the last argument a reference to the view. TODO: deprecate
  6633 + )
  6634 + );
  6635 + },
  6636 +
  6637 +
  6638 + /* Dates
  6639 + ------------------------------------------------------------------------------------------------------------------*/
  6640 +
  6641 +
  6642 + // Updates all internal dates to center around the given current date
  6643 + setDate: function(date) {
  6644 + this.setRange(this.computeRange(date));
  6645 + },
  6646 +
  6647 +
  6648 + // Updates all internal dates for displaying the given range.
  6649 + // Expects all values to be normalized (like what computeRange does).
  6650 + setRange: function(range) {
  6651 + $.extend(this, range);
  6652 + this.updateTitle();
  6653 + },
  6654 +
  6655 +
  6656 + // Given a single current date, produce information about what range to display.
  6657 + // Subclasses can override. Must return all properties.
  6658 + computeRange: function(date) {
  6659 + var intervalUnit = computeIntervalUnit(this.intervalDuration);
  6660 + var intervalStart = date.clone().startOf(intervalUnit);
  6661 + var intervalEnd = intervalStart.clone().add(this.intervalDuration);
  6662 + var start, end;
  6663 +
  6664 + // normalize the range's time-ambiguity
  6665 + if (/year|month|week|day/.test(intervalUnit)) { // whole-days?
  6666 + intervalStart.stripTime();
  6667 + intervalEnd.stripTime();
  6668 + }
  6669 + else { // needs to have a time?
  6670 + if (!intervalStart.hasTime()) {
  6671 + intervalStart = this.calendar.rezoneDate(intervalStart); // convert to current timezone, with 00:00
  6672 + }
  6673 + if (!intervalEnd.hasTime()) {
  6674 + intervalEnd = this.calendar.rezoneDate(intervalEnd); // convert to current timezone, with 00:00
  6675 + }
  6676 + }
  6677 +
  6678 + start = intervalStart.clone();
  6679 + start = this.skipHiddenDays(start);
  6680 + end = intervalEnd.clone();
  6681 + end = this.skipHiddenDays(end, -1, true); // exclusively move backwards
  6682 +
  6683 + return {
  6684 + intervalUnit: intervalUnit,
  6685 + intervalStart: intervalStart,
  6686 + intervalEnd: intervalEnd,
  6687 + start: start,
  6688 + end: end
  6689 + };
  6690 + },
  6691 +
  6692 +
  6693 + // Computes the new date when the user hits the prev button, given the current date
  6694 + computePrevDate: function(date) {
  6695 + return this.massageCurrentDate(
  6696 + date.clone().startOf(this.intervalUnit).subtract(this.intervalDuration), -1
  6697 + );
  6698 + },
  6699 +
  6700 +
  6701 + // Computes the new date when the user hits the next button, given the current date
  6702 + computeNextDate: function(date) {
  6703 + return this.massageCurrentDate(
  6704 + date.clone().startOf(this.intervalUnit).add(this.intervalDuration)
  6705 + );
  6706 + },
  6707 +
  6708 +
  6709 + // Given an arbitrarily calculated current date of the calendar, returns a date that is ensured to be completely
  6710 + // visible. `direction` is optional and indicates which direction the current date was being
  6711 + // incremented or decremented (1 or -1).
  6712 + massageCurrentDate: function(date, direction) {
  6713 + if (this.intervalDuration.as('days') <= 1) { // if the view displays a single day or smaller
  6714 + if (this.isHiddenDay(date)) {
  6715 + date = this.skipHiddenDays(date, direction);
  6716 + date.startOf('day');
  6717 + }
  6718 + }
  6719 +
  6720 + return date;
  6721 + },
  6722 +
  6723 +
  6724 + /* Title and Date Formatting
  6725 + ------------------------------------------------------------------------------------------------------------------*/
  6726 +
  6727 +
  6728 + // Sets the view's title property to the most updated computed value
  6729 + updateTitle: function() {
  6730 + this.title = this.computeTitle();
  6731 + },
  6732 +
  6733 +
  6734 + // Computes what the title at the top of the calendar should be for this view
  6735 + computeTitle: function() {
  6736 + return this.formatRange(
  6737 + { start: this.intervalStart, end: this.intervalEnd },
  6738 + this.opt('titleFormat') || this.computeTitleFormat(),
  6739 + this.opt('titleRangeSeparator')
  6740 + );
  6741 + },
  6742 +
  6743 +
  6744 + // Generates the format string that should be used to generate the title for the current date range.
  6745 + // Attempts to compute the most appropriate format if not explicitly specified with `titleFormat`.
  6746 + computeTitleFormat: function() {
  6747 + if (this.intervalUnit == 'year') {
  6748 + return 'YYYY';
  6749 + }
  6750 + else if (this.intervalUnit == 'month') {
  6751 + return this.opt('monthYearFormat'); // like "September 2014"
  6752 + }
  6753 + else if (this.intervalDuration.as('days') > 1) {
  6754 + return 'll'; // multi-day range. shorter, like "Sep 9 - 10 2014"
  6755 + }
  6756 + else {
  6757 + return 'LL'; // one day. longer, like "September 9 2014"
  6758 + }
  6759 + },
  6760 +
  6761 +
  6762 + // Utility for formatting a range. Accepts a range object, formatting string, and optional separator.
  6763 + // Displays all-day ranges naturally, with an inclusive end. Takes the current isRTL into account.
  6764 + formatRange: function(range, formatStr, separator) {
  6765 + var end = range.end;
  6766 +
  6767 + if (!end.hasTime()) { // all-day?
  6768 + end = end.clone().subtract(1); // convert to inclusive. last ms of previous day
  6769 + }
  6770 +
  6771 + return formatRange(range.start, end, formatStr, separator, this.opt('isRTL'));
  6772 + },
  6773 +
  6774 +
  6775 + /* Rendering
  6776 + ------------------------------------------------------------------------------------------------------------------*/
  6777 +
  6778 +
  6779 + // Sets the container element that the view should render inside of.
  6780 + // Does other DOM-related initializations.
  6781 + setElement: function(el) {
  6782 + this.el = el;
  6783 + this.bindGlobalHandlers();
  6784 + },
  6785 +
  6786 +
  6787 + // Removes the view's container element from the DOM, clearing any content beforehand.
  6788 + // Undoes any other DOM-related attachments.
  6789 + removeElement: function() {
  6790 + this.clear(); // clears all content
  6791 +
  6792 + // clean up the skeleton
  6793 + if (this.isSkeletonRendered) {
  6794 + this.destroySkeleton();
  6795 + this.isSkeletonRendered = false;
  6796 + }
  6797 +
  6798 + this.unbindGlobalHandlers();
  6799 +
  6800 + this.el.remove();
  6801 +
  6802 + // NOTE: don't null-out this.el in case the View was destroyed within an API callback.
  6803 + // We don't null-out the View's other jQuery element references upon destroy, so why should we kill this.el?
  6804 + },
  6805 +
  6806 +
  6807 + // Does everything necessary to display the view centered around the given date.
  6808 + // Does every type of rendering EXCEPT rendering events.
  6809 + display: function(date) {
  6810 + var scrollState = null;
  6811 +
  6812 + if (this.isDisplayed) {
  6813 + scrollState = this.queryScroll();
  6814 + }
  6815 +
  6816 + this.clear(); // clear the old content
  6817 + this.setDate(date);
  6818 + this.render();
  6819 + this.updateSize();
  6820 + this.renderBusinessHours(); // might need coordinates, so should go after updateSize()
  6821 + this.isDisplayed = true;
  6822 +
  6823 + scrollState = this.computeInitialScroll(scrollState);
  6824 + this.forceScroll(scrollState);
  6825 +
  6826 + this.triggerRender();
  6827 + },
  6828 +
  6829 +
  6830 + // Does everything necessary to clear the content of the view.
  6831 + // Clears dates and events. Does not clear the skeleton.
  6832 + clear: function() { // clears the view of *content* but not the skeleton
  6833 + if (this.isDisplayed) {
  6834 + this.unselect();
  6835 + this.clearEvents();
  6836 + this.triggerDestroy();
  6837 + this.destroyBusinessHours();
  6838 + this.destroy();
  6839 + this.isDisplayed = false;
  6840 + }
  6841 + },
  6842 +
  6843 +
  6844 + // Renders the view's date-related content, rendering the view's non-content skeleton if necessary
  6845 + render: function() {
  6846 + if (!this.isSkeletonRendered) {
  6847 + this.renderSkeleton();
  6848 + this.isSkeletonRendered = true;
  6849 + }
  6850 + this.renderDates();
  6851 + },
  6852 +
  6853 +
  6854 + // Unrenders the view's date-related content.
  6855 + // Call this instead of destroyDates directly in case the View subclass wants to use a render/destroy pattern
  6856 + // where both the skeleton and the content always get rendered/unrendered together.
  6857 + destroy: function() {
  6858 + this.destroyDates();
  6859 + },
  6860 +
  6861 +
  6862 + // Renders the basic structure of the view before any content is rendered
  6863 + renderSkeleton: function() {
  6864 + // subclasses should implement
  6865 + },
  6866 +
  6867 +
  6868 + // Unrenders the basic structure of the view
  6869 + destroySkeleton: function() {
  6870 + // subclasses should implement
  6871 + },
  6872 +
  6873 +
  6874 + // Renders the view's date-related content (like cells that represent days/times).
  6875 + // Assumes setRange has already been called and the skeleton has already been rendered.
  6876 + renderDates: function() {
  6877 + // subclasses should implement
  6878 + },
  6879 +
  6880 +
  6881 + // Unrenders the view's date-related content
  6882 + destroyDates: function() {
  6883 + // subclasses should override
  6884 + },
  6885 +
  6886 +
  6887 + // Renders business-hours onto the view. Assumes updateSize has already been called.
  6888 + renderBusinessHours: function() {
  6889 + // subclasses should implement
  6890 + },
  6891 +
  6892 +
  6893 + // Unrenders previously-rendered business-hours
  6894 + destroyBusinessHours: function() {
  6895 + // subclasses should implement
  6896 + },
  6897 +
  6898 +
  6899 + // Signals that the view's content has been rendered
  6900 + triggerRender: function() {
  6901 + this.trigger('viewRender', this, this, this.el);
  6902 + },
  6903 +
  6904 +
  6905 + // Signals that the view's content is about to be unrendered
  6906 + triggerDestroy: function() {
  6907 + this.trigger('viewDestroy', this, this, this.el);
  6908 + },
  6909 +
  6910 +
  6911 + // Binds DOM handlers to elements that reside outside the view container, such as the document
  6912 + bindGlobalHandlers: function() {
  6913 + $(document).on('mousedown', this.documentMousedownProxy);
  6914 + },
  6915 +
  6916 +
  6917 + // Unbinds DOM handlers from elements that reside outside the view container
  6918 + unbindGlobalHandlers: function() {
  6919 + $(document).off('mousedown', this.documentMousedownProxy);
  6920 + },
  6921 +
  6922 +
  6923 + // Initializes internal variables related to theming
  6924 + initThemingProps: function() {
  6925 + var tm = this.opt('theme') ? 'ui' : 'fc';
  6926 +
  6927 + this.widgetHeaderClass = tm + '-widget-header';
  6928 + this.widgetContentClass = tm + '-widget-content';
  6929 + this.highlightStateClass = tm + '-state-highlight';
  6930 + },
  6931 +
  6932 +
  6933 + /* Dimensions
  6934 + ------------------------------------------------------------------------------------------------------------------*/
  6935 +
  6936 +
  6937 + // Refreshes anything dependant upon sizing of the container element of the grid
  6938 + updateSize: function(isResize) {
  6939 + var scrollState;
  6940 +
  6941 + if (isResize) {
  6942 + scrollState = this.queryScroll();
  6943 + }
  6944 +
  6945 + this.updateHeight();
  6946 + this.updateWidth();
  6947 +
  6948 + if (isResize) {
  6949 + this.setScroll(scrollState);
  6950 + }
  6951 + },
  6952 +
  6953 +
  6954 + // Refreshes the horizontal dimensions of the calendar
  6955 + updateWidth: function() {
  6956 + // subclasses should implement
  6957 + },
  6958 +
  6959 +
  6960 + // Refreshes the vertical dimensions of the calendar
  6961 + updateHeight: function() {
  6962 + var calendar = this.calendar; // we poll the calendar for height information
  6963 +
  6964 + this.setHeight(
  6965 + calendar.getSuggestedViewHeight(),
  6966 + calendar.isHeightAuto()
  6967 + );
  6968 + },
  6969 +
  6970 +
  6971 + // Updates the vertical dimensions of the calendar to the specified height.
  6972 + // if `isAuto` is set to true, height becomes merely a suggestion and the view should use its "natural" height.
  6973 + setHeight: function(height, isAuto) {
  6974 + // subclasses should implement
  6975 + },
  6976 +
  6977 +
  6978 + /* Scroller
  6979 + ------------------------------------------------------------------------------------------------------------------*/
  6980 +
  6981 +
  6982 + // Given the total height of the view, return the number of pixels that should be used for the scroller.
  6983 + // Utility for subclasses.
  6984 + computeScrollerHeight: function(totalHeight) {
  6985 + var scrollerEl = this.scrollerEl;
  6986 + var both;
  6987 + var otherHeight; // cumulative height of everything that is not the scrollerEl in the view (header+borders)
  6988 +
  6989 + both = this.el.add(scrollerEl);
  6990 +
  6991 + // fuckin IE8/9/10/11 sometimes returns 0 for dimensions. this weird hack was the only thing that worked
  6992 + both.css({
  6993 + position: 'relative', // cause a reflow, which will force fresh dimension recalculation
  6994 + left: -1 // ensure reflow in case the el was already relative. negative is less likely to cause new scroll
  6995 + });
  6996 + otherHeight = this.el.outerHeight() - scrollerEl.height(); // grab the dimensions
  6997 + both.css({ position: '', left: '' }); // undo hack
  6998 +
  6999 + return totalHeight - otherHeight;
  7000 + },
  7001 +
  7002 +
  7003 + // Computes the initial pre-configured scroll state prior to allowing the user to change it.
  7004 + // Given the scroll state from the previous rendering. If first time rendering, given null.
  7005 + computeInitialScroll: function(previousScrollState) {
  7006 + return 0;
  7007 + },
  7008 +
  7009 +
  7010 + // Retrieves the view's current natural scroll state. Can return an arbitrary format.
  7011 + queryScroll: function() {
  7012 + if (this.scrollerEl) {
  7013 + return this.scrollerEl.scrollTop(); // operates on scrollerEl by default
  7014 + }
  7015 + },
  7016 +
  7017 +
  7018 + // Sets the view's scroll state. Will accept the same format computeInitialScroll and queryScroll produce.
  7019 + setScroll: function(scrollState) {
  7020 + if (this.scrollerEl) {
  7021 + return this.scrollerEl.scrollTop(scrollState); // operates on scrollerEl by default
  7022 + }
  7023 + },
  7024 +
  7025 +
  7026 + // Sets the scroll state, making sure to overcome any predefined scroll value the browser has in mind
  7027 + forceScroll: function(scrollState) {
  7028 + var _this = this;
  7029 +
  7030 + this.setScroll(scrollState);
  7031 + setTimeout(function() {
  7032 + _this.setScroll(scrollState);
  7033 + }, 0);
  7034 + },
  7035 +
  7036 +
  7037 + /* Event Elements / Segments
  7038 + ------------------------------------------------------------------------------------------------------------------*/
  7039 +
  7040 +
  7041 + // Does everything necessary to display the given events onto the current view
  7042 + displayEvents: function(events) {
  7043 + var scrollState = this.queryScroll();
  7044 +
  7045 + this.clearEvents();
  7046 + this.renderEvents(events);
  7047 + this.isEventsRendered = true;
  7048 + this.setScroll(scrollState);
  7049 + this.triggerEventRender();
  7050 + },
  7051 +
  7052 +
  7053 + // Does everything necessary to clear the view's currently-rendered events
  7054 + clearEvents: function() {
  7055 + if (this.isEventsRendered) {
  7056 + this.triggerEventDestroy();
  7057 + this.destroyEvents();
  7058 + this.isEventsRendered = false;
  7059 + }
  7060 + },
  7061 +
  7062 +
  7063 + // Renders the events onto the view.
  7064 + renderEvents: function(events) {
  7065 + // subclasses should implement
  7066 + },
  7067 +
  7068 +
  7069 + // Removes event elements from the view.
  7070 + destroyEvents: function() {
  7071 + // subclasses should implement
  7072 + },
  7073 +
  7074 +
  7075 + // Signals that all events have been rendered
  7076 + triggerEventRender: function() {
  7077 + this.renderedEventSegEach(function(seg) {
  7078 + this.trigger('eventAfterRender', seg.event, seg.event, seg.el);
  7079 + });
  7080 + this.trigger('eventAfterAllRender');
  7081 + },
  7082 +
  7083 +
  7084 + // Signals that all event elements are about to be removed
  7085 + triggerEventDestroy: function() {
  7086 + this.renderedEventSegEach(function(seg) {
  7087 + this.trigger('eventDestroy', seg.event, seg.event, seg.el);
  7088 + });
  7089 + },
  7090 +
  7091 +
  7092 + // Given an event and the default element used for rendering, returns the element that should actually be used.
  7093 + // Basically runs events and elements through the eventRender hook.
  7094 + resolveEventEl: function(event, el) {
  7095 + var custom = this.trigger('eventRender', event, event, el);
  7096 +
  7097 + if (custom === false) { // means don't render at all
  7098 + el = null;
  7099 + }
  7100 + else if (custom && custom !== true) {
  7101 + el = $(custom);
  7102 + }
  7103 +
  7104 + return el;
  7105 + },
  7106 +
  7107 +
  7108 + // Hides all rendered event segments linked to the given event
  7109 + showEvent: function(event) {
  7110 + this.renderedEventSegEach(function(seg) {
  7111 + seg.el.css('visibility', '');
  7112 + }, event);
  7113 + },
  7114 +
  7115 +
  7116 + // Shows all rendered event segments linked to the given event
  7117 + hideEvent: function(event) {
  7118 + this.renderedEventSegEach(function(seg) {
  7119 + seg.el.css('visibility', 'hidden');
  7120 + }, event);
  7121 + },
  7122 +
  7123 +
  7124 + // Iterates through event segments that have been rendered (have an el). Goes through all by default.
  7125 + // If the optional `event` argument is specified, only iterates through segments linked to that event.
  7126 + // The `this` value of the callback function will be the view.
  7127 + renderedEventSegEach: function(func, event) {
  7128 + var segs = this.getEventSegs();
  7129 + var i;
  7130 +
  7131 + for (i = 0; i < segs.length; i++) {
  7132 + if (!event || segs[i].event._id === event._id) {
  7133 + if (segs[i].el) {
  7134 + func.call(this, segs[i]);
  7135 + }
  7136 + }
  7137 + }
  7138 + },
  7139 +
  7140 +
  7141 + // Retrieves all the rendered segment objects for the view
  7142 + getEventSegs: function() {
  7143 + // subclasses must implement
  7144 + return [];
  7145 + },
  7146 +
  7147 +
  7148 + /* Event Drag-n-Drop
  7149 + ------------------------------------------------------------------------------------------------------------------*/
  7150 +
  7151 +
  7152 + // Computes if the given event is allowed to be dragged by the user
  7153 + isEventDraggable: function(event) {
  7154 + var source = event.source || {};
  7155 +
  7156 + return firstDefined(
  7157 + event.startEditable,
  7158 + source.startEditable,
  7159 + this.opt('eventStartEditable'),
  7160 + event.editable,
  7161 + source.editable,
  7162 + this.opt('editable')
  7163 + );
  7164 + },
  7165 +
  7166 +
  7167 + // Must be called when an event in the view is dropped onto new location.
  7168 + // `dropLocation` is an object that contains the new start/end/allDay values for the event.
  7169 + reportEventDrop: function(event, dropLocation, largeUnit, el, ev) {
  7170 + var calendar = this.calendar;
  7171 + var mutateResult = calendar.mutateEvent(event, dropLocation, largeUnit);
  7172 + var undoFunc = function() {
  7173 + mutateResult.undo();
  7174 + calendar.reportEventChange();
  7175 + };
  7176 +
  7177 + this.triggerEventDrop(event, mutateResult.dateDelta, undoFunc, el, ev);
  7178 + calendar.reportEventChange(); // will rerender events
  7179 + },
  7180 +
  7181 +
  7182 + // Triggers event-drop handlers that have subscribed via the API
  7183 + triggerEventDrop: function(event, dateDelta, undoFunc, el, ev) {
  7184 + this.trigger('eventDrop', el[0], event, dateDelta, undoFunc, ev, {}); // {} = jqui dummy
  7185 + },
  7186 +
  7187 +
  7188 + /* External Element Drag-n-Drop
  7189 + ------------------------------------------------------------------------------------------------------------------*/
  7190 +
  7191 +
  7192 + // Must be called when an external element, via jQuery UI, has been dropped onto the calendar.
  7193 + // `meta` is the parsed data that has been embedded into the dragging event.
  7194 + // `dropLocation` is an object that contains the new start/end/allDay values for the event.
  7195 + reportExternalDrop: function(meta, dropLocation, el, ev, ui) {
  7196 + var eventProps = meta.eventProps;
  7197 + var eventInput;
  7198 + var event;
  7199 +
  7200 + // Try to build an event object and render it. TODO: decouple the two
  7201 + if (eventProps) {
  7202 + eventInput = $.extend({}, eventProps, dropLocation);
  7203 + event = this.calendar.renderEvent(eventInput, meta.stick)[0]; // renderEvent returns an array
  7204 + }
  7205 +
  7206 + this.triggerExternalDrop(event, dropLocation, el, ev, ui);
  7207 + },
  7208 +
  7209 +
  7210 + // Triggers external-drop handlers that have subscribed via the API
  7211 + triggerExternalDrop: function(event, dropLocation, el, ev, ui) {
  7212 +
  7213 + // trigger 'drop' regardless of whether element represents an event
  7214 + this.trigger('drop', el[0], dropLocation.start, ev, ui);
  7215 +
  7216 + if (event) {
  7217 + this.trigger('eventReceive', null, event); // signal an external event landed
  7218 + }
  7219 + },
  7220 +
  7221 +
  7222 + /* Drag-n-Drop Rendering (for both events and external elements)
  7223 + ------------------------------------------------------------------------------------------------------------------*/
  7224 +
  7225 +
  7226 + // Renders a visual indication of a event or external-element drag over the given drop zone.
  7227 + // If an external-element, seg will be `null`
  7228 + renderDrag: function(dropLocation, seg) {
  7229 + // subclasses must implement
  7230 + },
  7231 +
  7232 +
  7233 + // Unrenders a visual indication of an event or external-element being dragged.
  7234 + destroyDrag: function() {
  7235 + // subclasses must implement
  7236 + },
  7237 +
  7238 +
  7239 + /* Event Resizing
  7240 + ------------------------------------------------------------------------------------------------------------------*/
  7241 +
  7242 +
  7243 + // Computes if the given event is allowed to be resized from its starting edge
  7244 + isEventResizableFromStart: function(event) {
  7245 + return this.opt('eventResizableFromStart') && this.isEventResizable(event);
  7246 + },
  7247 +
  7248 +
  7249 + // Computes if the given event is allowed to be resized from its ending edge
  7250 + isEventResizableFromEnd: function(event) {
  7251 + return this.isEventResizable(event);
  7252 + },
  7253 +
  7254 +
  7255 + // Computes if the given event is allowed to be resized by the user at all
  7256 + isEventResizable: function(event) {
  7257 + var source = event.source || {};
  7258 +
  7259 + return firstDefined(
  7260 + event.durationEditable,
  7261 + source.durationEditable,
  7262 + this.opt('eventDurationEditable'),
  7263 + event.editable,
  7264 + source.editable,
  7265 + this.opt('editable')
  7266 + );
  7267 + },
  7268 +
  7269 +
  7270 + // Must be called when an event in the view has been resized to a new length
  7271 + reportEventResize: function(event, resizeLocation, largeUnit, el, ev) {
  7272 + var calendar = this.calendar;
  7273 + var mutateResult = calendar.mutateEvent(event, resizeLocation, largeUnit);
  7274 + var undoFunc = function() {
  7275 + mutateResult.undo();
  7276 + calendar.reportEventChange();
  7277 + };
  7278 +
  7279 + this.triggerEventResize(event, mutateResult.durationDelta, undoFunc, el, ev);
  7280 + calendar.reportEventChange(); // will rerender events
  7281 + },
  7282 +
  7283 +
  7284 + // Triggers event-resize handlers that have subscribed via the API
  7285 + triggerEventResize: function(event, durationDelta, undoFunc, el, ev) {
  7286 + this.trigger('eventResize', el[0], event, durationDelta, undoFunc, ev, {}); // {} = jqui dummy
  7287 + },
  7288 +
  7289 +
  7290 + /* Selection
  7291 + ------------------------------------------------------------------------------------------------------------------*/
  7292 +
  7293 +
  7294 + // Selects a date range on the view. `start` and `end` are both Moments.
  7295 + // `ev` is the native mouse event that begin the interaction.
  7296 + select: function(range, ev) {
  7297 + this.unselect(ev);
  7298 + this.renderSelection(range);
  7299 + this.reportSelection(range, ev);
  7300 + },
  7301 +
  7302 +
  7303 + // Renders a visual indication of the selection
  7304 + renderSelection: function(range) {
  7305 + // subclasses should implement
  7306 + },
  7307 +
  7308 +
  7309 + // Called when a new selection is made. Updates internal state and triggers handlers.
  7310 + reportSelection: function(range, ev) {
  7311 + this.isSelected = true;
  7312 + this.trigger('select', null, range.start, range.end, ev);
  7313 + },
  7314 +
  7315 +
  7316 + // Undoes a selection. updates in the internal state and triggers handlers.
  7317 + // `ev` is the native mouse event that began the interaction.
  7318 + unselect: function(ev) {
  7319 + if (this.isSelected) {
  7320 + this.isSelected = false;
  7321 + this.destroySelection();
  7322 + this.trigger('unselect', null, ev);
  7323 + }
  7324 + },
  7325 +
  7326 +
  7327 + // Unrenders a visual indication of selection
  7328 + destroySelection: function() {
  7329 + // subclasses should implement
  7330 + },
  7331 +
  7332 +
  7333 + // Handler for unselecting when the user clicks something and the 'unselectAuto' setting is on
  7334 + documentMousedown: function(ev) {
  7335 + var ignore;
  7336 +
  7337 + // is there a selection, and has the user made a proper left click?
  7338 + if (this.isSelected && this.opt('unselectAuto') && isPrimaryMouseButton(ev)) {
  7339 +
  7340 + // only unselect if the clicked element is not identical to or inside of an 'unselectCancel' element
  7341 + ignore = this.opt('unselectCancel');
  7342 + if (!ignore || !$(ev.target).closest(ignore).length) {
  7343 + this.unselect(ev);
  7344 + }
  7345 + }
  7346 + },
  7347 +
  7348 +
  7349 + /* Date Utils
  7350 + ------------------------------------------------------------------------------------------------------------------*/
  7351 +
  7352 +
  7353 + // Initializes internal variables related to calculating hidden days-of-week
  7354 + initHiddenDays: function() {
  7355 + var hiddenDays = this.opt('hiddenDays') || []; // array of day-of-week indices that are hidden
  7356 + var isHiddenDayHash = []; // is the day-of-week hidden? (hash with day-of-week-index -> bool)
  7357 + var dayCnt = 0;
  7358 + var i;
  7359 +
  7360 + if (this.opt('weekends') === false) {
  7361 + hiddenDays.push(0, 6); // 0=sunday, 6=saturday
  7362 + }
  7363 +
  7364 + for (i = 0; i < 7; i++) {
  7365 + if (
  7366 + !(isHiddenDayHash[i] = $.inArray(i, hiddenDays) !== -1)
  7367 + ) {
  7368 + dayCnt++;
  7369 + }
  7370 + }
  7371 +
  7372 + if (!dayCnt) {
  7373 + throw 'invalid hiddenDays'; // all days were hidden? bad.
  7374 + }
  7375 +
  7376 + this.isHiddenDayHash = isHiddenDayHash;
  7377 + },
  7378 +
  7379 +
  7380 + // Is the current day hidden?
  7381 + // `day` is a day-of-week index (0-6), or a Moment
  7382 + isHiddenDay: function(day) {
  7383 + if (moment.isMoment(day)) {
  7384 + day = day.day();
  7385 + }
  7386 + return this.isHiddenDayHash[day];
  7387 + },
  7388 +
  7389 +
  7390 + // Incrementing the current day until it is no longer a hidden day, returning a copy.
  7391 + // If the initial value of `date` is not a hidden day, don't do anything.
  7392 + // Pass `isExclusive` as `true` if you are dealing with an end date.
  7393 + // `inc` defaults to `1` (increment one day forward each time)
  7394 + skipHiddenDays: function(date, inc, isExclusive) {
  7395 + var out = date.clone();
  7396 + inc = inc || 1;
  7397 + while (
  7398 + this.isHiddenDayHash[(out.day() + (isExclusive ? inc : 0) + 7) % 7]
  7399 + ) {
  7400 + out.add(inc, 'days');
  7401 + }
  7402 + return out;
  7403 + },
  7404 +
  7405 +
  7406 + // Returns the date range of the full days the given range visually appears to occupy.
  7407 + // Returns a new range object.
  7408 + computeDayRange: function(range) {
  7409 + var startDay = range.start.clone().stripTime(); // the beginning of the day the range starts
  7410 + var end = range.end;
  7411 + var endDay = null;
  7412 + var endTimeMS;
  7413 +
  7414 + if (end) {
  7415 + endDay = end.clone().stripTime(); // the beginning of the day the range exclusively ends
  7416 + endTimeMS = +end.time(); // # of milliseconds into `endDay`
  7417 +
  7418 + // If the end time is actually inclusively part of the next day and is equal to or
  7419 + // beyond the next day threshold, adjust the end to be the exclusive end of `endDay`.
  7420 + // Otherwise, leaving it as inclusive will cause it to exclude `endDay`.
  7421 + if (endTimeMS && endTimeMS >= this.nextDayThreshold) {
  7422 + endDay.add(1, 'days');
  7423 + }
  7424 + }
  7425 +
  7426 + // If no end was specified, or if it is within `startDay` but not past nextDayThreshold,
  7427 + // assign the default duration of one day.
  7428 + if (!end || endDay <= startDay) {
  7429 + endDay = startDay.clone().add(1, 'days');
  7430 + }
  7431 +
  7432 + return { start: startDay, end: endDay };
  7433 + },
  7434 +
  7435 +
  7436 + // Does the given event visually appear to occupy more than one day?
  7437 + isMultiDayEvent: function(event) {
  7438 + var range = this.computeDayRange(event); // event is range-ish
  7439 +
  7440 + return range.end.diff(range.start, 'days') > 1;
  7441 + }
  7442 +
  7443 +});
  7444 +
  7445 +;;
  7446 +
  7447 +var Calendar = fc.Calendar = fc.CalendarBase = Class.extend({
  7448 +
  7449 + dirDefaults: null, // option defaults related to LTR or RTL
  7450 + langDefaults: null, // option defaults related to current locale
  7451 + overrides: null, // option overrides given to the fullCalendar constructor
  7452 + options: null, // all defaults combined with overrides
  7453 + viewSpecCache: null, // cache of view definitions
  7454 + view: null, // current View object
  7455 + header: null,
  7456 +
  7457 +
  7458 + // a lot of this class' OOP logic is scoped within this constructor function,
  7459 + // but in the future, write individual methods on the prototype.
  7460 + constructor: Calendar_constructor,
  7461 +
  7462 +
  7463 + // Initializes `this.options` and other important options-related objects
  7464 + initOptions: function(overrides) {
  7465 + var lang, langDefaults;
  7466 + var isRTL, dirDefaults;
  7467 +
  7468 + // converts legacy options into non-legacy ones.
  7469 + // in the future, when this is removed, don't use `overrides` reference. make a copy.
  7470 + overrides = massageOverrides(overrides);
  7471 +
  7472 + lang = overrides.lang;
  7473 + langDefaults = langOptionHash[lang];
  7474 + if (!langDefaults) {
  7475 + lang = Calendar.defaults.lang;
  7476 + langDefaults = langOptionHash[lang] || {};
  7477 + }
  7478 +
  7479 + isRTL = firstDefined(
  7480 + overrides.isRTL,
  7481 + langDefaults.isRTL,
  7482 + Calendar.defaults.isRTL
  7483 + );
  7484 + dirDefaults = isRTL ? Calendar.rtlDefaults : {};
  7485 +
  7486 + this.dirDefaults = dirDefaults;
  7487 + this.langDefaults = langDefaults;
  7488 + this.overrides = overrides;
  7489 + this.options = mergeOptions( // merge defaults and overrides. lowest to highest precedence
  7490 + Calendar.defaults, // global defaults
  7491 + dirDefaults,
  7492 + langDefaults,
  7493 + overrides
  7494 + );
  7495 + populateInstanceComputableOptions(this.options);
  7496 +
  7497 + this.viewSpecCache = {}; // somewhat unrelated
  7498 + },
  7499 +
  7500 +
  7501 + // Gets information about how to create a view. Will use a cache.
  7502 + getViewSpec: function(viewType) {
  7503 + var cache = this.viewSpecCache;
  7504 +
  7505 + return cache[viewType] || (cache[viewType] = this.buildViewSpec(viewType));
  7506 + },
  7507 +
  7508 +
  7509 + // Given a duration singular unit, like "week" or "day", finds a matching view spec.
  7510 + // Preference is given to views that have corresponding buttons.
  7511 + getUnitViewSpec: function(unit) {
  7512 + var viewTypes;
  7513 + var i;
  7514 + var spec;
  7515 +
  7516 + if ($.inArray(unit, intervalUnits) != -1) {
  7517 +
  7518 + // put views that have buttons first. there will be duplicates, but oh well
  7519 + viewTypes = this.header.getViewsWithButtons();
  7520 + $.each(fc.views, function(viewType) { // all views
  7521 + viewTypes.push(viewType);
  7522 + });
  7523 +
  7524 + for (i = 0; i < viewTypes.length; i++) {
  7525 + spec = this.getViewSpec(viewTypes[i]);
  7526 + if (spec) {
  7527 + if (spec.singleUnit == unit) {
  7528 + return spec;
  7529 + }
  7530 + }
  7531 + }
  7532 + }
  7533 + },
  7534 +
  7535 +
  7536 + // Builds an object with information on how to create a given view
  7537 + buildViewSpec: function(requestedViewType) {
  7538 + var viewOverrides = this.overrides.views || {};
  7539 + var defaultsChain = []; // for the view. lowest to highest priority
  7540 + var overridesChain = []; // for the view. lowest to highest priority
  7541 + var viewType = requestedViewType;
  7542 + var viewClass;
  7543 + var defaults; // for the view
  7544 + var overrides; // for the view
  7545 + var duration;
  7546 + var unit;
  7547 + var spec;
  7548 +
  7549 + // iterate from the specific view definition to a more general one until we hit an actual View class
  7550 + while (viewType && !viewClass) {
  7551 + defaults = fcViews[viewType] || {};
  7552 + overrides = viewOverrides[viewType] || {};
  7553 + duration = duration || overrides.duration || defaults.duration;
  7554 + viewType = overrides.type || defaults.type; // for next iteration
  7555 +
  7556 + if (typeof defaults === 'function') { // a class
  7557 + viewClass = defaults;
  7558 + defaultsChain.unshift(viewClass.defaults || {});
  7559 + }
  7560 + else { // an options object
  7561 + defaultsChain.unshift(defaults);
  7562 + }
  7563 + overridesChain.unshift(overrides);
  7564 + }
  7565 +
  7566 + if (viewClass) {
  7567 + spec = { 'class': viewClass, type: requestedViewType };
  7568 +
  7569 + if (duration) {
  7570 + duration = moment.duration(duration);
  7571 + if (!duration.valueOf()) { // invalid?
  7572 + duration = null;
  7573 + }
  7574 + }
  7575 + if (duration) {
  7576 + spec.duration = duration;
  7577 + unit = computeIntervalUnit(duration);
  7578 +
  7579 + // view is a single-unit duration, like "week" or "day"
  7580 + // incorporate options for this. lowest priority
  7581 + if (duration.as(unit) === 1) {
  7582 + spec.singleUnit = unit;
  7583 + overridesChain.unshift(viewOverrides[unit] || {});
  7584 + }
  7585 + }
  7586 +
  7587 + // collapse into single objects
  7588 + spec.defaults = mergeOptions.apply(null, defaultsChain);
  7589 + spec.overrides = mergeOptions.apply(null, overridesChain);
  7590 +
  7591 + this.buildViewSpecOptions(spec);
  7592 + this.buildViewSpecButtonText(spec, requestedViewType);
  7593 +
  7594 + return spec;
  7595 + }
  7596 + },
  7597 +
  7598 +
  7599 + // Builds and assigns a view spec's options object from its already-assigned defaults and overrides
  7600 + buildViewSpecOptions: function(spec) {
  7601 + spec.options = mergeOptions( // lowest to highest priority
  7602 + Calendar.defaults, // global defaults
  7603 + spec.defaults, // view's defaults (from ViewSubclass.defaults)
  7604 + this.dirDefaults,
  7605 + this.langDefaults, // locale and dir take precedence over view's defaults!
  7606 + this.overrides, // calendar's overrides (options given to constructor)
  7607 + spec.overrides // view's overrides (view-specific options)
  7608 + );
  7609 + populateInstanceComputableOptions(spec.options);
  7610 + },
  7611 +
  7612 +
  7613 + // Computes and assigns a view spec's buttonText-related options
  7614 + buildViewSpecButtonText: function(spec, requestedViewType) {
  7615 +
  7616 + // given an options object with a possible `buttonText` hash, lookup the buttonText for the
  7617 + // requested view, falling back to a generic unit entry like "week" or "day"
  7618 + function queryButtonText(options) {
  7619 + var buttonText = options.buttonText || {};
  7620 + return buttonText[requestedViewType] ||
  7621 + (spec.singleUnit ? buttonText[spec.singleUnit] : null);
  7622 + }
  7623 +
  7624 + // highest to lowest priority
  7625 + spec.buttonTextOverride =
  7626 + queryButtonText(this.overrides) || // constructor-specified buttonText lookup hash takes precedence
  7627 + spec.overrides.buttonText; // `buttonText` for view-specific options is a string
  7628 +
  7629 + // highest to lowest priority. mirrors buildViewSpecOptions
  7630 + spec.buttonTextDefault =
  7631 + queryButtonText(this.langDefaults) ||
  7632 + queryButtonText(this.dirDefaults) ||
  7633 + spec.defaults.buttonText || // a single string. from ViewSubclass.defaults
  7634 + queryButtonText(Calendar.defaults) ||
  7635 + (spec.duration ? this.humanizeDuration(spec.duration) : null) || // like "3 days"
  7636 + requestedViewType; // fall back to given view name
  7637 + },
  7638 +
  7639 +
  7640 + // Given a view name for a custom view or a standard view, creates a ready-to-go View object
  7641 + instantiateView: function(viewType) {
  7642 + var spec = this.getViewSpec(viewType);
  7643 +
  7644 + return new spec['class'](this, viewType, spec.options, spec.duration);
  7645 + },
  7646 +
  7647 +
  7648 + // Returns a boolean about whether the view is okay to instantiate at some point
  7649 + isValidViewType: function(viewType) {
  7650 + return Boolean(this.getViewSpec(viewType));
  7651 + }
  7652 +
  7653 +});
  7654 +
  7655 +
  7656 +function Calendar_constructor(element, overrides) {
  7657 + var t = this;
  7658 +
  7659 +
  7660 + t.initOptions(overrides || {});
  7661 + var options = this.options;
  7662 +
  7663 +
  7664 + // Exports
  7665 + // -----------------------------------------------------------------------------------
  7666 +
  7667 + t.render = render;
  7668 + t.destroy = destroy;
  7669 + t.refetchEvents = refetchEvents;
  7670 + t.reportEvents = reportEvents;
  7671 + t.reportEventChange = reportEventChange;
  7672 + t.rerenderEvents = renderEvents; // `renderEvents` serves as a rerender. an API method
  7673 + t.changeView = renderView; // `renderView` will switch to another view
  7674 + t.select = select;
  7675 + t.unselect = unselect;
  7676 + t.prev = prev;
  7677 + t.next = next;
  7678 + t.prevYear = prevYear;
  7679 + t.nextYear = nextYear;
  7680 + t.today = today;
  7681 + t.gotoDate = gotoDate;
  7682 + t.incrementDate = incrementDate;
  7683 + t.zoomTo = zoomTo;
  7684 + t.getDate = getDate;
  7685 + t.getCalendar = getCalendar;
  7686 + t.getView = getView;
  7687 + t.option = option;
  7688 + t.trigger = trigger;
  7689 +
  7690 +
  7691 +
  7692 + // Language-data Internals
  7693 + // -----------------------------------------------------------------------------------
  7694 + // Apply overrides to the current language's data
  7695 +
  7696 +
  7697 + var localeData = createObject( // make a cheap copy
  7698 + getMomentLocaleData(options.lang) // will fall back to en
  7699 + );
  7700 +
  7701 + if (options.monthNames) {
  7702 + localeData._months = options.monthNames;
  7703 + }
  7704 + if (options.monthNamesShort) {
  7705 + localeData._monthsShort = options.monthNamesShort;
  7706 + }
  7707 + if (options.dayNames) {
  7708 + localeData._weekdays = options.dayNames;
  7709 + }
  7710 + if (options.dayNamesShort) {
  7711 + localeData._weekdaysShort = options.dayNamesShort;
  7712 + }
  7713 + if (options.firstDay != null) {
  7714 + var _week = createObject(localeData._week); // _week: { dow: # }
  7715 + _week.dow = options.firstDay;
  7716 + localeData._week = _week;
  7717 + }
  7718 +
  7719 + // assign a normalized value, to be used by our .week() moment extension
  7720 + localeData._fullCalendar_weekCalc = (function(weekCalc) {
  7721 + if (typeof weekCalc === 'function') {
  7722 + return weekCalc;
  7723 + }
  7724 + else if (weekCalc === 'local') {
  7725 + return weekCalc;
  7726 + }
  7727 + else if (weekCalc === 'iso' || weekCalc === 'ISO') {
  7728 + return 'ISO';
  7729 + }
  7730 + })(options.weekNumberCalculation);
  7731 +
  7732 +
  7733 +
  7734 + // Calendar-specific Date Utilities
  7735 + // -----------------------------------------------------------------------------------
  7736 +
  7737 +
  7738 + t.defaultAllDayEventDuration = moment.duration(options.defaultAllDayEventDuration);
  7739 + t.defaultTimedEventDuration = moment.duration(options.defaultTimedEventDuration);
  7740 +
  7741 +
  7742 + // Builds a moment using the settings of the current calendar: timezone and language.
  7743 + // Accepts anything the vanilla moment() constructor accepts.
  7744 + t.moment = function() {
  7745 + var mom;
  7746 +
  7747 + if (options.timezone === 'local') {
  7748 + mom = fc.moment.apply(null, arguments);
  7749 +
  7750 + // Force the moment to be local, because fc.moment doesn't guarantee it.
  7751 + if (mom.hasTime()) { // don't give ambiguously-timed moments a local zone
  7752 + mom.local();
  7753 + }
  7754 + }
  7755 + else if (options.timezone === 'UTC') {
  7756 + mom = fc.moment.utc.apply(null, arguments); // process as UTC
  7757 + }
  7758 + else {
  7759 + mom = fc.moment.parseZone.apply(null, arguments); // let the input decide the zone
  7760 + }
  7761 +
  7762 + if ('_locale' in mom) { // moment 2.8 and above
  7763 + mom._locale = localeData;
  7764 + }
  7765 + else { // pre-moment-2.8
  7766 + mom._lang = localeData;
  7767 + }
  7768 +
  7769 + return mom;
  7770 + };
  7771 +
  7772 +
  7773 + // Returns a boolean about whether or not the calendar knows how to calculate
  7774 + // the timezone offset of arbitrary dates in the current timezone.
  7775 + t.getIsAmbigTimezone = function() {
  7776 + return options.timezone !== 'local' && options.timezone !== 'UTC';
  7777 + };
  7778 +
  7779 +
  7780 + // Returns a copy of the given date in the current timezone of it is ambiguously zoned.
  7781 + // This will also give the date an unambiguous time.
  7782 + t.rezoneDate = function(date) {
  7783 + return t.moment(date.toArray());
  7784 + };
  7785 +
  7786 +
  7787 + // Returns a moment for the current date, as defined by the client's computer,
  7788 + // or overridden by the `now` option.
  7789 + t.getNow = function() {
  7790 + var now = options.now;
  7791 + if (typeof now === 'function') {
  7792 + now = now();
  7793 + }
  7794 + return t.moment(now);
  7795 + };
  7796 +
  7797 +
  7798 + // Get an event's normalized end date. If not present, calculate it from the defaults.
  7799 + t.getEventEnd = function(event) {
  7800 + if (event.end) {
  7801 + return event.end.clone();
  7802 + }
  7803 + else {
  7804 + return t.getDefaultEventEnd(event.allDay, event.start);
  7805 + }
  7806 + };
  7807 +
  7808 +
  7809 + // Given an event's allDay status and start date, return swhat its fallback end date should be.
  7810 + t.getDefaultEventEnd = function(allDay, start) { // TODO: rename to computeDefaultEventEnd
  7811 + var end = start.clone();
  7812 +
  7813 + if (allDay) {
  7814 + end.stripTime().add(t.defaultAllDayEventDuration);
  7815 + }
  7816 + else {
  7817 + end.add(t.defaultTimedEventDuration);
  7818 + }
  7819 +
  7820 + if (t.getIsAmbigTimezone()) {
  7821 + end.stripZone(); // we don't know what the tzo should be
  7822 + }
  7823 +
  7824 + return end;
  7825 + };
  7826 +
  7827 +
  7828 + // Produces a human-readable string for the given duration.
  7829 + // Side-effect: changes the locale of the given duration.
  7830 + t.humanizeDuration = function(duration) {
  7831 + return (duration.locale || duration.lang).call(duration, options.lang) // works moment-pre-2.8
  7832 + .humanize();
  7833 + };
  7834 +
  7835 +
  7836 +
  7837 + // Imports
  7838 + // -----------------------------------------------------------------------------------
  7839 +
  7840 +
  7841 + EventManager.call(t, options);
  7842 + var isFetchNeeded = t.isFetchNeeded;
  7843 + var fetchEvents = t.fetchEvents;
  7844 +
  7845 +
  7846 +
  7847 + // Locals
  7848 + // -----------------------------------------------------------------------------------
  7849 +
  7850 +
  7851 + var _element = element[0];
  7852 + var header;
  7853 + var headerElement;
  7854 + var content;
  7855 + var tm; // for making theme classes
  7856 + var currentView; // NOTE: keep this in sync with this.view
  7857 + var viewsByType = {}; // holds all instantiated view instances, current or not
  7858 + var suggestedViewHeight;
  7859 + var windowResizeProxy; // wraps the windowResize function
  7860 + var ignoreWindowResize = 0;
  7861 + var date;
  7862 + var events = [];
  7863 +
  7864 +
  7865 +
  7866 + // Main Rendering
  7867 + // -----------------------------------------------------------------------------------
  7868 +
  7869 +
  7870 + if (options.defaultDate != null) {
  7871 + date = t.moment(options.defaultDate);
  7872 + }
  7873 + else {
  7874 + date = t.getNow();
  7875 + }
  7876 +
  7877 +
  7878 + function render() {
  7879 + if (!content) {
  7880 + initialRender();
  7881 + }
  7882 + else if (elementVisible()) {
  7883 + // mainly for the public API
  7884 + calcSize();
  7885 + renderView();
  7886 + }
  7887 + }
  7888 +
  7889 +
  7890 + function initialRender() {
  7891 + tm = options.theme ? 'ui' : 'fc';
  7892 + element.addClass('fc');
  7893 +
  7894 + if (options.isRTL) {
  7895 + element.addClass('fc-rtl');
  7896 + }
  7897 + else {
  7898 + element.addClass('fc-ltr');
  7899 + }
  7900 +
  7901 + if (options.theme) {
  7902 + element.addClass('ui-widget');
  7903 + }
  7904 + else {
  7905 + element.addClass('fc-unthemed');
  7906 + }
  7907 +
  7908 + content = $("<div class='fc-view-container'/>").prependTo(element);
  7909 +
  7910 + header = t.header = new Header(t, options);
  7911 + headerElement = header.render();
  7912 + if (headerElement) {
  7913 + element.prepend(headerElement);
  7914 + }
  7915 +
  7916 + renderView(options.defaultView);
  7917 +
  7918 + if (options.handleWindowResize) {
  7919 + windowResizeProxy = debounce(windowResize, options.windowResizeDelay); // prevents rapid calls
  7920 + $(window).resize(windowResizeProxy);
  7921 + }
  7922 + }
  7923 +
  7924 +
  7925 + function destroy() {
  7926 +
  7927 + if (currentView) {
  7928 + currentView.removeElement();
  7929 +
  7930 + // NOTE: don't null-out currentView/t.view in case API methods are called after destroy.
  7931 + // It is still the "current" view, just not rendered.
  7932 + }
  7933 +
  7934 + header.destroy();
  7935 + content.remove();
  7936 + element.removeClass('fc fc-ltr fc-rtl fc-unthemed ui-widget');
  7937 +
  7938 + if (windowResizeProxy) {
  7939 + $(window).unbind('resize', windowResizeProxy);
  7940 + }
  7941 + }
  7942 +
  7943 +
  7944 + function elementVisible() {
  7945 + return element.is(':visible');
  7946 + }
  7947 +
  7948 +
  7949 +
  7950 + // View Rendering
  7951 + // -----------------------------------------------------------------------------------
  7952 +
  7953 +
  7954 + // Renders a view because of a date change, view-type change, or for the first time.
  7955 + // If not given a viewType, keep the current view but render different dates.
  7956 + function renderView(viewType) {
  7957 + ignoreWindowResize++;
  7958 +
  7959 + // if viewType is changing, destroy the old view
  7960 + if (currentView && viewType && currentView.type !== viewType) {
  7961 + header.deactivateButton(currentView.type);
  7962 + freezeContentHeight(); // prevent a scroll jump when view element is removed
  7963 + currentView.removeElement();
  7964 + currentView = t.view = null;
  7965 + }
  7966 +
  7967 + // if viewType changed, or the view was never created, create a fresh view
  7968 + if (!currentView && viewType) {
  7969 + currentView = t.view =
  7970 + viewsByType[viewType] ||
  7971 + (viewsByType[viewType] = t.instantiateView(viewType));
  7972 +
  7973 + currentView.setElement(
  7974 + $("<div class='fc-view fc-" + viewType + "-view' />").appendTo(content)
  7975 + );
  7976 + header.activateButton(viewType);
  7977 + }
  7978 +
  7979 + if (currentView) {
  7980 +
  7981 + // in case the view should render a period of time that is completely hidden
  7982 + date = currentView.massageCurrentDate(date);
  7983 +
  7984 + // render or rerender the view
  7985 + if (
  7986 + !currentView.isDisplayed ||
  7987 + !date.isWithin(currentView.intervalStart, currentView.intervalEnd) // implicit date window change
  7988 + ) {
  7989 + if (elementVisible()) {
  7990 +
  7991 + freezeContentHeight();
  7992 + currentView.display(date);
  7993 + unfreezeContentHeight();
  7994 +
  7995 + // need to do this after View::render, so dates are calculated
  7996 + updateHeaderTitle();
  7997 + updateTodayButton();
  7998 +
  7999 + getAndRenderEvents();
  8000 + }
  8001 + }
  8002 + }
  8003 +
  8004 + unfreezeContentHeight(); // undo any lone freezeContentHeight calls
  8005 + ignoreWindowResize--;
  8006 + }
  8007 +
  8008 +
  8009 +
  8010 + // Resizing
  8011 + // -----------------------------------------------------------------------------------
  8012 +
  8013 +
  8014 + t.getSuggestedViewHeight = function() {
  8015 + if (suggestedViewHeight === undefined) {
  8016 + calcSize();
  8017 + }
  8018 + return suggestedViewHeight;
  8019 + };
  8020 +
  8021 +
  8022 + t.isHeightAuto = function() {
  8023 + return options.contentHeight === 'auto' || options.height === 'auto';
  8024 + };
  8025 +
  8026 +
  8027 + function updateSize(shouldRecalc) {
  8028 + if (elementVisible()) {
  8029 +
  8030 + if (shouldRecalc) {
  8031 + _calcSize();
  8032 + }
  8033 +
  8034 + ignoreWindowResize++;
  8035 + currentView.updateSize(true); // isResize=true. will poll getSuggestedViewHeight() and isHeightAuto()
  8036 + ignoreWindowResize--;
  8037 +
  8038 + return true; // signal success
  8039 + }
  8040 + }
  8041 +
  8042 +
  8043 + function calcSize() {
  8044 + if (elementVisible()) {
  8045 + _calcSize();
  8046 + }
  8047 + }
  8048 +
  8049 +
  8050 + function _calcSize() { // assumes elementVisible
  8051 + if (typeof options.contentHeight === 'number') { // exists and not 'auto'
  8052 + suggestedViewHeight = options.contentHeight;
  8053 + }
  8054 + else if (typeof options.height === 'number') { // exists and not 'auto'
  8055 + suggestedViewHeight = options.height - (headerElement ? headerElement.outerHeight(true) : 0);
  8056 + }
  8057 + else {
  8058 + suggestedViewHeight = Math.round(content.width() / Math.max(options.aspectRatio, .5));
  8059 + }
  8060 + }
  8061 +
  8062 +
  8063 + function windowResize(ev) {
  8064 + if (
  8065 + !ignoreWindowResize &&
  8066 + ev.target === window && // so we don't process jqui "resize" events that have bubbled up
  8067 + currentView.start // view has already been rendered
  8068 + ) {
  8069 + if (updateSize(true)) {
  8070 + currentView.trigger('windowResize', _element);
  8071 + }
  8072 + }
  8073 + }
  8074 +
  8075 +
  8076 +
  8077 + /* Event Fetching/Rendering
  8078 + -----------------------------------------------------------------------------*/
  8079 + // TODO: going forward, most of this stuff should be directly handled by the view
  8080 +
  8081 +
  8082 + function refetchEvents() { // can be called as an API method
  8083 + destroyEvents(); // so that events are cleared before user starts waiting for AJAX
  8084 + fetchAndRenderEvents();
  8085 + }
  8086 +
  8087 +
  8088 + function renderEvents() { // destroys old events if previously rendered
  8089 + if (elementVisible()) {
  8090 + freezeContentHeight();
  8091 + currentView.displayEvents(events);
  8092 + unfreezeContentHeight();
  8093 + }
  8094 + }
  8095 +
  8096 +
  8097 + function destroyEvents() {
  8098 + freezeContentHeight();
  8099 + currentView.clearEvents();
  8100 + unfreezeContentHeight();
  8101 + }
  8102 +
  8103 +
  8104 + function getAndRenderEvents() {
  8105 + if (!options.lazyFetching || isFetchNeeded(currentView.start, currentView.end)) {
  8106 + fetchAndRenderEvents();
  8107 + }
  8108 + else {
  8109 + renderEvents();
  8110 + }
  8111 + }
  8112 +
  8113 +
  8114 + function fetchAndRenderEvents() {
  8115 + fetchEvents(currentView.start, currentView.end);
  8116 + // ... will call reportEvents
  8117 + // ... which will call renderEvents
  8118 + }
  8119 +
  8120 +
  8121 + // called when event data arrives
  8122 + function reportEvents(_events) {
  8123 + events = _events;
  8124 + renderEvents();
  8125 + }
  8126 +
  8127 +
  8128 + // called when a single event's data has been changed
  8129 + function reportEventChange() {
  8130 + renderEvents();
  8131 + }
  8132 +
  8133 +
  8134 +
  8135 + /* Header Updating
  8136 + -----------------------------------------------------------------------------*/
  8137 +
  8138 +
  8139 + function updateHeaderTitle() {
  8140 + header.updateTitle(currentView.title);
  8141 + }
  8142 +
  8143 +
  8144 + function updateTodayButton() {
  8145 + var now = t.getNow();
  8146 + if (now.isWithin(currentView.intervalStart, currentView.intervalEnd)) {
  8147 + header.disableButton('today');
  8148 + }
  8149 + else {
  8150 + header.enableButton('today');
  8151 + }
  8152 + }
  8153 +
  8154 +
  8155 +
  8156 + /* Selection
  8157 + -----------------------------------------------------------------------------*/
  8158 +
  8159 +
  8160 + function select(start, end) {
  8161 +
  8162 + start = t.moment(start);
  8163 + if (end) {
  8164 + end = t.moment(end);
  8165 + }
  8166 + else if (start.hasTime()) {
  8167 + end = start.clone().add(t.defaultTimedEventDuration);
  8168 + }
  8169 + else {
  8170 + end = start.clone().add(t.defaultAllDayEventDuration);
  8171 + }
  8172 +
  8173 + currentView.select({ start: start, end: end }); // accepts a range
  8174 + }
  8175 +
  8176 +
  8177 + function unselect() { // safe to be called before renderView
  8178 + if (currentView) {
  8179 + currentView.unselect();
  8180 + }
  8181 + }
  8182 +
  8183 +
  8184 +
  8185 + /* Date
  8186 + -----------------------------------------------------------------------------*/
  8187 +
  8188 +
  8189 + function prev() {
  8190 + date = currentView.computePrevDate(date);
  8191 + renderView();
  8192 + }
  8193 +
  8194 +
  8195 + function next() {
  8196 + date = currentView.computeNextDate(date);
  8197 + renderView();
  8198 + }
  8199 +
  8200 +
  8201 + function prevYear() {
  8202 + date.add(-1, 'years');
  8203 + renderView();
  8204 + }
  8205 +
  8206 +
  8207 + function nextYear() {
  8208 + date.add(1, 'years');
  8209 + renderView();
  8210 + }
  8211 +
  8212 +
  8213 + function today() {
  8214 + date = t.getNow();
  8215 + renderView();
  8216 + }
  8217 +
  8218 +
  8219 + function gotoDate(dateInput) {
  8220 + date = t.moment(dateInput);
  8221 + renderView();
  8222 + }
  8223 +
  8224 +
  8225 + function incrementDate(delta) {
  8226 + date.add(moment.duration(delta));
  8227 + renderView();
  8228 + }
  8229 +
  8230 +
  8231 + // Forces navigation to a view for the given date.
  8232 + // `viewType` can be a specific view name or a generic one like "week" or "day".
  8233 + function zoomTo(newDate, viewType) {
  8234 + var spec;
  8235 +
  8236 + viewType = viewType || 'day'; // day is default zoom
  8237 + spec = t.getViewSpec(viewType) || t.getUnitViewSpec(viewType);
  8238 +
  8239 + date = newDate;
  8240 + renderView(spec ? spec.type : null);
  8241 + }
  8242 +
  8243 +
  8244 + function getDate() {
  8245 + return date.clone();
  8246 + }
  8247 +
  8248 +
  8249 +
  8250 + /* Height "Freezing"
  8251 + -----------------------------------------------------------------------------*/
  8252 + // TODO: move this into the view
  8253 +
  8254 +
  8255 + function freezeContentHeight() {
  8256 + content.css({
  8257 + width: '100%',
  8258 + height: content.height(),
  8259 + overflow: 'hidden'
  8260 + });
  8261 + }
  8262 +
  8263 +
  8264 + function unfreezeContentHeight() {
  8265 + content.css({
  8266 + width: '',
  8267 + height: '',
  8268 + overflow: ''
  8269 + });
  8270 + }
  8271 +
  8272 +
  8273 +
  8274 + /* Misc
  8275 + -----------------------------------------------------------------------------*/
  8276 +
  8277 +
  8278 + function getCalendar() {
  8279 + return t;
  8280 + }
  8281 +
  8282 +
  8283 + function getView() {
  8284 + return currentView;
  8285 + }
  8286 +
  8287 +
  8288 + function option(name, value) {
  8289 + if (value === undefined) {
  8290 + return options[name];
  8291 + }
  8292 + if (name == 'height' || name == 'contentHeight' || name == 'aspectRatio') {
  8293 + options[name] = value;
  8294 + updateSize(true); // true = allow recalculation of height
  8295 + }
  8296 + }
  8297 +
  8298 +
  8299 + function trigger(name, thisObj) {
  8300 + if (options[name]) {
  8301 + return options[name].apply(
  8302 + thisObj || _element,
  8303 + Array.prototype.slice.call(arguments, 2)
  8304 + );
  8305 + }
  8306 + }
  8307 +
  8308 +}
  8309 +
  8310 +;;
  8311 +
  8312 +Calendar.defaults = {
  8313 +
  8314 + titleRangeSeparator: ' \u2014 ', // emphasized dash
  8315 + monthYearFormat: 'MMMM YYYY', // required for en. other languages rely on datepicker computable option
  8316 +
  8317 + defaultTimedEventDuration: '02:00:00',
  8318 + defaultAllDayEventDuration: { days: 1 },
  8319 + forceEventDuration: false,
  8320 + nextDayThreshold: '09:00:00', // 9am
  8321 +
  8322 + // display
  8323 + defaultView: 'month',
  8324 + aspectRatio: 1.35,
  8325 + header: {
  8326 + left: 'title',
  8327 + center: '',
  8328 + right: 'today prev,next'
  8329 + },
  8330 + weekends: true,
  8331 + weekNumbers: false,
  8332 +
  8333 + weekNumberTitle: 'W',
  8334 + weekNumberCalculation: 'local',
  8335 +
  8336 + //editable: false,
  8337 +
  8338 + // event ajax
  8339 + lazyFetching: true,
  8340 + startParam: 'start',
  8341 + endParam: 'end',
  8342 + timezoneParam: 'timezone',
  8343 +
  8344 + timezone: false,
  8345 +
  8346 + //allDayDefault: undefined,
  8347 +
  8348 + // locale
  8349 + isRTL: false,
  8350 + buttonText: {
  8351 + prev: "prev",
  8352 + next: "next",
  8353 + prevYear: "prev year",
  8354 + nextYear: "next year",
  8355 + year: 'year', // TODO: locale files need to specify this
  8356 + today: 'today',
  8357 + month: 'month',
  8358 + week: 'week',
  8359 + day: 'day'
  8360 + },
  8361 +
  8362 + buttonIcons: {
  8363 + prev: 'left-single-arrow',
  8364 + next: 'right-single-arrow',
  8365 + prevYear: 'left-double-arrow',
  8366 + nextYear: 'right-double-arrow'
  8367 + },
  8368 +
  8369 + // jquery-ui theming
  8370 + theme: false,
  8371 + themeButtonIcons: {
  8372 + prev: 'circle-triangle-w',
  8373 + next: 'circle-triangle-e',
  8374 + prevYear: 'seek-prev',
  8375 + nextYear: 'seek-next'
  8376 + },
  8377 +
  8378 + //eventResizableFromStart: false,
  8379 + dragOpacity: .75,
  8380 + dragRevertDuration: 500,
  8381 + dragScroll: true,
  8382 +
  8383 + //selectable: false,
  8384 + unselectAuto: true,
  8385 +
  8386 + dropAccept: '*',
  8387 +
  8388 + eventLimit: false,
  8389 + eventLimitText: 'more',
  8390 + eventLimitClick: 'popover',
  8391 + dayPopoverFormat: 'LL',
  8392 +
  8393 + handleWindowResize: true,
  8394 + windowResizeDelay: 200 // milliseconds before an updateSize happens
  8395 +
  8396 +};
  8397 +
  8398 +
  8399 +Calendar.englishDefaults = { // used by lang.js
  8400 + dayPopoverFormat: 'dddd, MMMM D'
  8401 +};
  8402 +
  8403 +
  8404 +Calendar.rtlDefaults = { // right-to-left defaults
  8405 + header: { // TODO: smarter solution (first/center/last ?)
  8406 + left: 'next,prev today',
  8407 + center: '',
  8408 + right: 'title'
  8409 + },
  8410 + buttonIcons: {
  8411 + prev: 'right-single-arrow',
  8412 + next: 'left-single-arrow',
  8413 + prevYear: 'right-double-arrow',
  8414 + nextYear: 'left-double-arrow'
  8415 + },
  8416 + themeButtonIcons: {
  8417 + prev: 'circle-triangle-e',
  8418 + next: 'circle-triangle-w',
  8419 + nextYear: 'seek-prev',
  8420 + prevYear: 'seek-next'
  8421 + }
  8422 +};
  8423 +
  8424 +;;
  8425 +
  8426 +var langOptionHash = fc.langs = {}; // initialize and expose
  8427 +
  8428 +
  8429 +// TODO: document the structure and ordering of a FullCalendar lang file
  8430 +// TODO: rename everything "lang" to "locale", like what the moment project did
  8431 +
  8432 +
  8433 +// Initialize jQuery UI datepicker translations while using some of the translations
  8434 +// Will set this as the default language for datepicker.
  8435 +fc.datepickerLang = function(langCode, dpLangCode, dpOptions) {
  8436 +
  8437 + // get the FullCalendar internal option hash for this language. create if necessary
  8438 + var fcOptions = langOptionHash[langCode] || (langOptionHash[langCode] = {});
  8439 +
  8440 + // transfer some simple options from datepicker to fc
  8441 + fcOptions.isRTL = dpOptions.isRTL;
  8442 + fcOptions.weekNumberTitle = dpOptions.weekHeader;
  8443 +
  8444 + // compute some more complex options from datepicker
  8445 + $.each(dpComputableOptions, function(name, func) {
  8446 + fcOptions[name] = func(dpOptions);
  8447 + });
  8448 +
  8449 + // is jQuery UI Datepicker is on the page?
  8450 + if ($.datepicker) {
  8451 +
  8452 + // Register the language data.
  8453 + // FullCalendar and MomentJS use language codes like "pt-br" but Datepicker
  8454 + // does it like "pt-BR" or if it doesn't have the language, maybe just "pt".
  8455 + // Make an alias so the language can be referenced either way.
  8456 + $.datepicker.regional[dpLangCode] =
  8457 + $.datepicker.regional[langCode] = // alias
  8458 + dpOptions;
  8459 +
  8460 + // Alias 'en' to the default language data. Do this every time.
  8461 + $.datepicker.regional.en = $.datepicker.regional[''];
  8462 +
  8463 + // Set as Datepicker's global defaults.
  8464 + $.datepicker.setDefaults(dpOptions);
  8465 + }
  8466 +};
  8467 +
  8468 +
  8469 +// Sets FullCalendar-specific translations. Will set the language as the global default.
  8470 +fc.lang = function(langCode, newFcOptions) {
  8471 + var fcOptions;
  8472 + var momOptions;
  8473 +
  8474 + // get the FullCalendar internal option hash for this language. create if necessary
  8475 + fcOptions = langOptionHash[langCode] || (langOptionHash[langCode] = {});
  8476 +
  8477 + // provided new options for this language? merge them in
  8478 + if (newFcOptions) {
  8479 + fcOptions = langOptionHash[langCode] = mergeOptions(fcOptions, newFcOptions);
  8480 + }
  8481 +
  8482 + // compute language options that weren't defined.
  8483 + // always do this. newFcOptions can be undefined when initializing from i18n file,
  8484 + // so no way to tell if this is an initialization or a default-setting.
  8485 + momOptions = getMomentLocaleData(langCode); // will fall back to en
  8486 + $.each(momComputableOptions, function(name, func) {
  8487 + if (fcOptions[name] == null) {
  8488 + fcOptions[name] = func(momOptions, fcOptions);
  8489 + }
  8490 + });
  8491 +
  8492 + // set it as the default language for FullCalendar
  8493 + Calendar.defaults.lang = langCode;
  8494 +};
  8495 +
  8496 +
  8497 +// NOTE: can't guarantee any of these computations will run because not every language has datepicker
  8498 +// configs, so make sure there are English fallbacks for these in the defaults file.
  8499 +var dpComputableOptions = {
  8500 +
  8501 + buttonText: function(dpOptions) {
  8502 + return {
  8503 + // the translations sometimes wrongly contain HTML entities
  8504 + prev: stripHtmlEntities(dpOptions.prevText),
  8505 + next: stripHtmlEntities(dpOptions.nextText),
  8506 + today: stripHtmlEntities(dpOptions.currentText)
  8507 + };
  8508 + },
  8509 +
  8510 + // Produces format strings like "MMMM YYYY" -> "September 2014"
  8511 + monthYearFormat: function(dpOptions) {
  8512 + return dpOptions.showMonthAfterYear ?
  8513 + 'YYYY[' + dpOptions.yearSuffix + '] MMMM' :
  8514 + 'MMMM YYYY[' + dpOptions.yearSuffix + ']';
  8515 + }
  8516 +
  8517 +};
  8518 +
  8519 +var momComputableOptions = {
  8520 +
  8521 + // Produces format strings like "ddd M/D" -> "Fri 9/15"
  8522 + dayOfMonthFormat: function(momOptions, fcOptions) {
  8523 + var format = momOptions.longDateFormat('l'); // for the format like "M/D/YYYY"
  8524 +
  8525 + // strip the year off the edge, as well as other misc non-whitespace chars
  8526 + format = format.replace(/^Y+[^\w\s]*|[^\w\s]*Y+$/g, '');
  8527 +
  8528 + if (fcOptions.isRTL) {
  8529 + format += ' ddd'; // for RTL, add day-of-week to end
  8530 + }
  8531 + else {
  8532 + format = 'ddd ' + format; // for LTR, add day-of-week to beginning
  8533 + }
  8534 + return format;
  8535 + },
  8536 +
  8537 + // Produces format strings like "h:mma" -> "6:00pm"
  8538 + mediumTimeFormat: function(momOptions) { // can't be called `timeFormat` because collides with option
  8539 + return momOptions.longDateFormat('LT')
  8540 + .replace(/\s*a$/i, 'a'); // convert AM/PM/am/pm to lowercase. remove any spaces beforehand
  8541 + },
  8542 +
  8543 + // Produces format strings like "h(:mm)a" -> "6pm" / "6:30pm"
  8544 + smallTimeFormat: function(momOptions) {
  8545 + return momOptions.longDateFormat('LT')
  8546 + .replace(':mm', '(:mm)')
  8547 + .replace(/(\Wmm)$/, '($1)') // like above, but for foreign langs
  8548 + .replace(/\s*a$/i, 'a'); // convert AM/PM/am/pm to lowercase. remove any spaces beforehand
  8549 + },
  8550 +
  8551 + // Produces format strings like "h(:mm)t" -> "6p" / "6:30p"
  8552 + extraSmallTimeFormat: function(momOptions) {
  8553 + return momOptions.longDateFormat('LT')
  8554 + .replace(':mm', '(:mm)')
  8555 + .replace(/(\Wmm)$/, '($1)') // like above, but for foreign langs
  8556 + .replace(/\s*a$/i, 't'); // convert to AM/PM/am/pm to lowercase one-letter. remove any spaces beforehand
  8557 + },
  8558 +
  8559 + // Produces format strings like "ha" / "H" -> "6pm" / "18"
  8560 + hourFormat: function(momOptions) {
  8561 + return momOptions.longDateFormat('LT')
  8562 + .replace(':mm', '')
  8563 + .replace(/(\Wmm)$/, '') // like above, but for foreign langs
  8564 + .replace(/\s*a$/i, 'a'); // convert AM/PM/am/pm to lowercase. remove any spaces beforehand
  8565 + },
  8566 +
  8567 + // Produces format strings like "h:mm" -> "6:30" (with no AM/PM)
  8568 + noMeridiemTimeFormat: function(momOptions) {
  8569 + return momOptions.longDateFormat('LT')
  8570 + .replace(/\s*a$/i, ''); // remove trailing AM/PM
  8571 + }
  8572 +
  8573 +};
  8574 +
  8575 +
  8576 +// options that should be computed off live calendar options (considers override options)
  8577 +var instanceComputableOptions = { // TODO: best place for this? related to lang?
  8578 +
  8579 + // Produces format strings for results like "Mo 16"
  8580 + smallDayDateFormat: function(options) {
  8581 + return options.isRTL ?
  8582 + 'D dd' :
  8583 + 'dd D';
  8584 + },
  8585 +
  8586 + // Produces format strings for results like "Wk 5"
  8587 + weekFormat: function(options) {
  8588 + return options.isRTL ?
  8589 + 'w[ ' + options.weekNumberTitle + ']' :
  8590 + '[' + options.weekNumberTitle + ' ]w';
  8591 + },
  8592 +
  8593 + // Produces format strings for results like "Wk5"
  8594 + smallWeekFormat: function(options) {
  8595 + return options.isRTL ?
  8596 + 'w[' + options.weekNumberTitle + ']' :
  8597 + '[' + options.weekNumberTitle + ']w';
  8598 + }
  8599 +
  8600 +};
  8601 +
  8602 +function populateInstanceComputableOptions(options) {
  8603 + $.each(instanceComputableOptions, function(name, func) {
  8604 + if (options[name] == null) {
  8605 + options[name] = func(options);
  8606 + }
  8607 + });
  8608 +}
  8609 +
  8610 +
  8611 +// Returns moment's internal locale data. If doesn't exist, returns English.
  8612 +// Works with moment-pre-2.8
  8613 +function getMomentLocaleData(langCode) {
  8614 + var func = moment.localeData || moment.langData;
  8615 + return func.call(moment, langCode) ||
  8616 + func.call(moment, 'en'); // the newer localData could return null, so fall back to en
  8617 +}
  8618 +
  8619 +
  8620 +// Initialize English by forcing computation of moment-derived options.
  8621 +// Also, sets it as the default.
  8622 +fc.lang('en', Calendar.englishDefaults);
  8623 +
  8624 +;;
  8625 +
  8626 +/* Top toolbar area with buttons and title
  8627 +----------------------------------------------------------------------------------------------------------------------*/
  8628 +// TODO: rename all header-related things to "toolbar"
  8629 +
  8630 +function Header(calendar, options) {
  8631 + var t = this;
  8632 +
  8633 + // exports
  8634 + t.render = render;
  8635 + t.destroy = destroy;
  8636 + t.updateTitle = updateTitle;
  8637 + t.activateButton = activateButton;
  8638 + t.deactivateButton = deactivateButton;
  8639 + t.disableButton = disableButton;
  8640 + t.enableButton = enableButton;
  8641 + t.getViewsWithButtons = getViewsWithButtons;
  8642 +
  8643 + // locals
  8644 + var el = $();
  8645 + var viewsWithButtons = [];
  8646 + var tm;
  8647 +
  8648 +
  8649 + function render() {
  8650 + var sections = options.header;
  8651 +
  8652 + tm = options.theme ? 'ui' : 'fc';
  8653 +
  8654 + if (sections) {
  8655 + el = $("<div class='fc-toolbar'/>")
  8656 + .append(renderSection('left'))
  8657 + .append(renderSection('right'))
  8658 + .append(renderSection('center'))
  8659 + .append('<div class="fc-clear"/>');
  8660 +
  8661 + return el;
  8662 + }
  8663 + }
  8664 +
  8665 +
  8666 + function destroy() {
  8667 + el.remove();
  8668 + }
  8669 +
  8670 +
  8671 + function renderSection(position) {
  8672 + var sectionEl = $('<div class="fc-' + position + '"/>');
  8673 + var buttonStr = options.header[position];
  8674 +
  8675 + if (buttonStr) {
  8676 + $.each(buttonStr.split(' '), function(i) {
  8677 + var groupChildren = $();
  8678 + var isOnlyButtons = true;
  8679 + var groupEl;
  8680 +
  8681 + $.each(this.split(','), function(j, buttonName) {
  8682 + var viewSpec;
  8683 + var buttonClick;
  8684 + var overrideText; // text explicitly set by calendar's constructor options. overcomes icons
  8685 + var defaultText;
  8686 + var themeIcon;
  8687 + var normalIcon;
  8688 + var innerHtml;
  8689 + var classes;
  8690 + var button;
  8691 +
  8692 + if (buttonName == 'title') {
  8693 + groupChildren = groupChildren.add($('<h2>&nbsp;</h2>')); // we always want it to take up height
  8694 + isOnlyButtons = false;
  8695 + }
  8696 + else {
  8697 + viewSpec = calendar.getViewSpec(buttonName);
  8698 +
  8699 + if (viewSpec) {
  8700 + buttonClick = function() {
  8701 + calendar.changeView(buttonName);
  8702 + };
  8703 + viewsWithButtons.push(buttonName);
  8704 + overrideText = viewSpec.buttonTextOverride;
  8705 + defaultText = viewSpec.buttonTextDefault;
  8706 + }
  8707 + else if (calendar[buttonName]) { // a calendar method
  8708 + buttonClick = function() {
  8709 + calendar[buttonName]();
  8710 + };
  8711 + overrideText = (calendar.overrides.buttonText || {})[buttonName];
  8712 + defaultText = options.buttonText[buttonName]; // everything else is considered default
  8713 + }
  8714 +
  8715 + if (buttonClick) {
  8716 +
  8717 + themeIcon = options.themeButtonIcons[buttonName];
  8718 + normalIcon = options.buttonIcons[buttonName];
  8719 +
  8720 + if (overrideText) {
  8721 + innerHtml = htmlEscape(overrideText);
  8722 + }
  8723 + else if (themeIcon && options.theme) {
  8724 + innerHtml = "<span class='ui-icon ui-icon-" + themeIcon + "'></span>";
  8725 + }
  8726 + else if (normalIcon && !options.theme) {
  8727 + innerHtml = "<span class='fc-icon fc-icon-" + normalIcon + "'></span>";
  8728 + }
  8729 + else {
  8730 + innerHtml = htmlEscape(defaultText);
  8731 + }
  8732 +
  8733 + classes = [
  8734 + 'fc-' + buttonName + '-button',
  8735 + tm + '-button',
  8736 + tm + '-state-default'
  8737 + ];
  8738 +
  8739 + button = $( // type="button" so that it doesn't submit a form
  8740 + '<button type="button" class="' + classes.join(' ') + '">' +
  8741 + innerHtml +
  8742 + '</button>'
  8743 + )
  8744 + .click(function() {
  8745 + // don't process clicks for disabled buttons
  8746 + if (!button.hasClass(tm + '-state-disabled')) {
  8747 +
  8748 + buttonClick();
  8749 +
  8750 + // after the click action, if the button becomes the "active" tab, or disabled,
  8751 + // it should never have a hover class, so remove it now.
  8752 + if (
  8753 + button.hasClass(tm + '-state-active') ||
  8754 + button.hasClass(tm + '-state-disabled')
  8755 + ) {
  8756 + button.removeClass(tm + '-state-hover');
  8757 + }
  8758 + }
  8759 + })
  8760 + .mousedown(function() {
  8761 + // the *down* effect (mouse pressed in).
  8762 + // only on buttons that are not the "active" tab, or disabled
  8763 + button
  8764 + .not('.' + tm + '-state-active')
  8765 + .not('.' + tm + '-state-disabled')
  8766 + .addClass(tm + '-state-down');
  8767 + })
  8768 + .mouseup(function() {
  8769 + // undo the *down* effect
  8770 + button.removeClass(tm + '-state-down');
  8771 + })
  8772 + .hover(
  8773 + function() {
  8774 + // the *hover* effect.
  8775 + // only on buttons that are not the "active" tab, or disabled
  8776 + button
  8777 + .not('.' + tm + '-state-active')
  8778 + .not('.' + tm + '-state-disabled')
  8779 + .addClass(tm + '-state-hover');
  8780 + },
  8781 + function() {
  8782 + // undo the *hover* effect
  8783 + button
  8784 + .removeClass(tm + '-state-hover')
  8785 + .removeClass(tm + '-state-down'); // if mouseleave happens before mouseup
  8786 + }
  8787 + );
  8788 +
  8789 + groupChildren = groupChildren.add(button);
  8790 + }
  8791 + }
  8792 + });
  8793 +
  8794 + if (isOnlyButtons) {
  8795 + groupChildren
  8796 + .first().addClass(tm + '-corner-left').end()
  8797 + .last().addClass(tm + '-corner-right').end();
  8798 + }
  8799 +
  8800 + if (groupChildren.length > 1) {
  8801 + groupEl = $('<div/>');
  8802 + if (isOnlyButtons) {
  8803 + groupEl.addClass('fc-button-group');
  8804 + }
  8805 + groupEl.append(groupChildren);
  8806 + sectionEl.append(groupEl);
  8807 + }
  8808 + else {
  8809 + sectionEl.append(groupChildren); // 1 or 0 children
  8810 + }
  8811 + });
  8812 + }
  8813 +
  8814 + return sectionEl;
  8815 + }
  8816 +
  8817 +
  8818 + function updateTitle(text) {
  8819 + el.find('h2').text(text);
  8820 + }
  8821 +
  8822 +
  8823 + function activateButton(buttonName) {
  8824 + el.find('.fc-' + buttonName + '-button')
  8825 + .addClass(tm + '-state-active');
  8826 + }
  8827 +
  8828 +
  8829 + function deactivateButton(buttonName) {
  8830 + el.find('.fc-' + buttonName + '-button')
  8831 + .removeClass(tm + '-state-active');
  8832 + }
  8833 +
  8834 +
  8835 + function disableButton(buttonName) {
  8836 + el.find('.fc-' + buttonName + '-button')
  8837 + .attr('disabled', 'disabled')
  8838 + .addClass(tm + '-state-disabled');
  8839 + }
  8840 +
  8841 +
  8842 + function enableButton(buttonName) {
  8843 + el.find('.fc-' + buttonName + '-button')
  8844 + .removeAttr('disabled')
  8845 + .removeClass(tm + '-state-disabled');
  8846 + }
  8847 +
  8848 +
  8849 + function getViewsWithButtons() {
  8850 + return viewsWithButtons;
  8851 + }
  8852 +
  8853 +}
  8854 +
  8855 +;;
  8856 +
  8857 +fc.sourceNormalizers = [];
  8858 +fc.sourceFetchers = [];
  8859 +
  8860 +var ajaxDefaults = {
  8861 + dataType: 'json',
  8862 + cache: false
  8863 +};
  8864 +
  8865 +var eventGUID = 1;
  8866 +
  8867 +
  8868 +function EventManager(options) { // assumed to be a calendar
  8869 + var t = this;
  8870 +
  8871 +
  8872 + // exports
  8873 + t.isFetchNeeded = isFetchNeeded;
  8874 + t.fetchEvents = fetchEvents;
  8875 + t.addEventSource = addEventSource;
  8876 + t.removeEventSource = removeEventSource;
  8877 + t.updateEvent = updateEvent;
  8878 + t.renderEvent = renderEvent;
  8879 + t.removeEvents = removeEvents;
  8880 + t.clientEvents = clientEvents;
  8881 + t.mutateEvent = mutateEvent;
  8882 + t.normalizeEventRange = normalizeEventRange;
  8883 + t.normalizeEventRangeTimes = normalizeEventRangeTimes;
  8884 + t.ensureVisibleEventRange = ensureVisibleEventRange;
  8885 +
  8886 +
  8887 + // imports
  8888 + var trigger = t.trigger;
  8889 + var getView = t.getView;
  8890 + var reportEvents = t.reportEvents;
  8891 +
  8892 +
  8893 + // locals
  8894 + var stickySource = { events: [] };
  8895 + var sources = [ stickySource ];
  8896 + var rangeStart, rangeEnd;
  8897 + var currentFetchID = 0;
  8898 + var pendingSourceCnt = 0;
  8899 + var loadingLevel = 0;
  8900 + var cache = []; // holds events that have already been expanded
  8901 +
  8902 +
  8903 + $.each(
  8904 + (options.events ? [ options.events ] : []).concat(options.eventSources || []),
  8905 + function(i, sourceInput) {
  8906 + var source = buildEventSource(sourceInput);
  8907 + if (source) {
  8908 + sources.push(source);
  8909 + }
  8910 + }
  8911 + );
  8912 +
  8913 +
  8914 +
  8915 + /* Fetching
  8916 + -----------------------------------------------------------------------------*/
  8917 +
  8918 +
  8919 + function isFetchNeeded(start, end) {
  8920 + return !rangeStart || // nothing has been fetched yet?
  8921 + // or, a part of the new range is outside of the old range? (after normalizing)
  8922 + start.clone().stripZone() < rangeStart.clone().stripZone() ||
  8923 + end.clone().stripZone() > rangeEnd.clone().stripZone();
  8924 + }
  8925 +
  8926 +
  8927 + function fetchEvents(start, end) {
  8928 + rangeStart = start;
  8929 + rangeEnd = end;
  8930 + cache = [];
  8931 + var fetchID = ++currentFetchID;
  8932 + var len = sources.length;
  8933 + pendingSourceCnt = len;
  8934 + for (var i=0; i<len; i++) {
  8935 + fetchEventSource(sources[i], fetchID);
  8936 + }
  8937 + }
  8938 +
  8939 +
  8940 + function fetchEventSource(source, fetchID) {
  8941 + _fetchEventSource(source, function(eventInputs) {
  8942 + var isArraySource = $.isArray(source.events);
  8943 + var i, eventInput;
  8944 + var abstractEvent;
  8945 +
  8946 + if (fetchID == currentFetchID) {
  8947 +
  8948 + if (eventInputs) {
  8949 + for (i = 0; i < eventInputs.length; i++) {
  8950 + eventInput = eventInputs[i];
  8951 +
  8952 + if (isArraySource) { // array sources have already been convert to Event Objects
  8953 + abstractEvent = eventInput;
  8954 + }
  8955 + else {
  8956 + abstractEvent = buildEventFromInput(eventInput, source);
  8957 + }
  8958 +
  8959 + if (abstractEvent) { // not false (an invalid event)
  8960 + cache.push.apply(
  8961 + cache,
  8962 + expandEvent(abstractEvent) // add individual expanded events to the cache
  8963 + );
  8964 + }
  8965 + }
  8966 + }
  8967 +
  8968 + pendingSourceCnt--;
  8969 + if (!pendingSourceCnt) {
  8970 + reportEvents(cache);
  8971 + }
  8972 + }
  8973 + });
  8974 + }
  8975 +
  8976 +
  8977 + function _fetchEventSource(source, callback) {
  8978 + var i;
  8979 + var fetchers = fc.sourceFetchers;
  8980 + var res;
  8981 +
  8982 + for (i=0; i<fetchers.length; i++) {
  8983 + res = fetchers[i].call(
  8984 + t, // this, the Calendar object
  8985 + source,
  8986 + rangeStart.clone(),
  8987 + rangeEnd.clone(),
  8988 + options.timezone,
  8989 + callback
  8990 + );
  8991 +
  8992 + if (res === true) {
  8993 + // the fetcher is in charge. made its own async request
  8994 + return;
  8995 + }
  8996 + else if (typeof res == 'object') {
  8997 + // the fetcher returned a new source. process it
  8998 + _fetchEventSource(res, callback);
  8999 + return;
  9000 + }
  9001 + }
  9002 +
  9003 + var events = source.events;
  9004 + if (events) {
  9005 + if ($.isFunction(events)) {
  9006 + pushLoading();
  9007 + events.call(
  9008 + t, // this, the Calendar object
  9009 + rangeStart.clone(),
  9010 + rangeEnd.clone(),
  9011 + options.timezone,
  9012 + function(events) {
  9013 + callback(events);
  9014 + popLoading();
  9015 + }
  9016 + );
  9017 + }
  9018 + else if ($.isArray(events)) {
  9019 + callback(events);
  9020 + }
  9021 + else {
  9022 + callback();
  9023 + }
  9024 + }else{
  9025 + var url = source.url;
  9026 + if (url) {
  9027 + var success = source.success;
  9028 + var error = source.error;
  9029 + var complete = source.complete;
  9030 +
  9031 + // retrieve any outbound GET/POST $.ajax data from the options
  9032 + var customData;
  9033 + if ($.isFunction(source.data)) {
  9034 + // supplied as a function that returns a key/value object
  9035 + customData = source.data();
  9036 + }
  9037 + else {
  9038 + // supplied as a straight key/value object
  9039 + customData = source.data;
  9040 + }
  9041 +
  9042 + // use a copy of the custom data so we can modify the parameters
  9043 + // and not affect the passed-in object.
  9044 + var data = $.extend({}, customData || {});
  9045 +
  9046 + var startParam = firstDefined(source.startParam, options.startParam);
  9047 + var endParam = firstDefined(source.endParam, options.endParam);
  9048 + var timezoneParam = firstDefined(source.timezoneParam, options.timezoneParam);
  9049 +
  9050 + if (startParam) {
  9051 + data[startParam] = rangeStart.format();
  9052 + }
  9053 + if (endParam) {
  9054 + data[endParam] = rangeEnd.format();
  9055 + }
  9056 + if (options.timezone && options.timezone != 'local') {
  9057 + data[timezoneParam] = options.timezone;
  9058 + }
  9059 +
  9060 + pushLoading();
  9061 + $.ajax($.extend({}, ajaxDefaults, source, {
  9062 + data: data,
  9063 + success: function(events) {
  9064 + events = events || [];
  9065 + var res = applyAll(success, this, arguments);
  9066 + if ($.isArray(res)) {
  9067 + events = res;
  9068 + }
  9069 + callback(events);
  9070 + },
  9071 + error: function() {
  9072 + applyAll(error, this, arguments);
  9073 + callback();
  9074 + },
  9075 + complete: function() {
  9076 + applyAll(complete, this, arguments);
  9077 + popLoading();
  9078 + }
  9079 + }));
  9080 + }else{
  9081 + callback();
  9082 + }
  9083 + }
  9084 + }
  9085 +
  9086 +
  9087 +
  9088 + /* Sources
  9089 + -----------------------------------------------------------------------------*/
  9090 +
  9091 +
  9092 + function addEventSource(sourceInput) {
  9093 + var source = buildEventSource(sourceInput);
  9094 + if (source) {
  9095 + sources.push(source);
  9096 + pendingSourceCnt++;
  9097 + fetchEventSource(source, currentFetchID); // will eventually call reportEvents
  9098 + }
  9099 + }
  9100 +
  9101 +
  9102 + function buildEventSource(sourceInput) { // will return undefined if invalid source
  9103 + var normalizers = fc.sourceNormalizers;
  9104 + var source;
  9105 + var i;
  9106 +
  9107 + if ($.isFunction(sourceInput) || $.isArray(sourceInput)) {
  9108 + source = { events: sourceInput };
  9109 + }
  9110 + else if (typeof sourceInput === 'string') {
  9111 + source = { url: sourceInput };
  9112 + }
  9113 + else if (typeof sourceInput === 'object') {
  9114 + source = $.extend({}, sourceInput); // shallow copy
  9115 + }
  9116 +
  9117 + if (source) {
  9118 +
  9119 + // TODO: repeat code, same code for event classNames
  9120 + if (source.className) {
  9121 + if (typeof source.className === 'string') {
  9122 + source.className = source.className.split(/\s+/);
  9123 + }
  9124 + // otherwise, assumed to be an array
  9125 + }
  9126 + else {
  9127 + source.className = [];
  9128 + }
  9129 +
  9130 + // for array sources, we convert to standard Event Objects up front
  9131 + if ($.isArray(source.events)) {
  9132 + source.origArray = source.events; // for removeEventSource
  9133 + source.events = $.map(source.events, function(eventInput) {
  9134 + return buildEventFromInput(eventInput, source);
  9135 + });
  9136 + }
  9137 +
  9138 + for (i=0; i<normalizers.length; i++) {
  9139 + normalizers[i].call(t, source);
  9140 + }
  9141 +
  9142 + return source;
  9143 + }
  9144 + }
  9145 +
  9146 +
  9147 + function removeEventSource(source) {
  9148 + sources = $.grep(sources, function(src) {
  9149 + return !isSourcesEqual(src, source);
  9150 + });
  9151 + // remove all client events from that source
  9152 + cache = $.grep(cache, function(e) {
  9153 + return !isSourcesEqual(e.source, source);
  9154 + });
  9155 + reportEvents(cache);
  9156 + }
  9157 +
  9158 +
  9159 + function isSourcesEqual(source1, source2) {
  9160 + return source1 && source2 && getSourcePrimitive(source1) == getSourcePrimitive(source2);
  9161 + }
  9162 +
  9163 +
  9164 + function getSourcePrimitive(source) {
  9165 + return (
  9166 + (typeof source === 'object') ? // a normalized event source?
  9167 + (source.origArray || source.googleCalendarId || source.url || source.events) : // get the primitive
  9168 + null
  9169 + ) ||
  9170 + source; // the given argument *is* the primitive
  9171 + }
  9172 +
  9173 +
  9174 +
  9175 + /* Manipulation
  9176 + -----------------------------------------------------------------------------*/
  9177 +
  9178 +
  9179 + // Only ever called from the externally-facing API
  9180 + function updateEvent(event) {
  9181 +
  9182 + // massage start/end values, even if date string values
  9183 + event.start = t.moment(event.start);
  9184 + if (event.end) {
  9185 + event.end = t.moment(event.end);
  9186 + }
  9187 + else {
  9188 + event.end = null;
  9189 + }
  9190 +
  9191 + mutateEvent(event, getMiscEventProps(event)); // will handle start/end/allDay normalization
  9192 + reportEvents(cache); // reports event modifications (so we can redraw)
  9193 + }
  9194 +
  9195 +
  9196 + // Returns a hash of misc event properties that should be copied over to related events.
  9197 + function getMiscEventProps(event) {
  9198 + var props = {};
  9199 +
  9200 + $.each(event, function(name, val) {
  9201 + if (isMiscEventPropName(name)) {
  9202 + if (val !== undefined && isAtomic(val)) { // a defined non-object
  9203 + props[name] = val;
  9204 + }
  9205 + }
  9206 + });
  9207 +
  9208 + return props;
  9209 + }
  9210 +
  9211 + // non-date-related, non-id-related, non-secret
  9212 + function isMiscEventPropName(name) {
  9213 + return !/^_|^(id|allDay|start|end)$/.test(name);
  9214 + }
  9215 +
  9216 +
  9217 + // returns the expanded events that were created
  9218 + function renderEvent(eventInput, stick) {
  9219 + var abstractEvent = buildEventFromInput(eventInput);
  9220 + var events;
  9221 + var i, event;
  9222 +
  9223 + if (abstractEvent) { // not false (a valid input)
  9224 + events = expandEvent(abstractEvent);
  9225 +
  9226 + for (i = 0; i < events.length; i++) {
  9227 + event = events[i];
  9228 +
  9229 + if (!event.source) {
  9230 + if (stick) {
  9231 + stickySource.events.push(event);
  9232 + event.source = stickySource;
  9233 + }
  9234 + cache.push(event);
  9235 + }
  9236 + }
  9237 +
  9238 + reportEvents(cache);
  9239 +
  9240 + return events;
  9241 + }
  9242 +
  9243 + return [];
  9244 + }
  9245 +
  9246 +
  9247 + function removeEvents(filter) {
  9248 + var eventID;
  9249 + var i;
  9250 +
  9251 + if (filter == null) { // null or undefined. remove all events
  9252 + filter = function() { return true; }; // will always match
  9253 + }
  9254 + else if (!$.isFunction(filter)) { // an event ID
  9255 + eventID = filter + '';
  9256 + filter = function(event) {
  9257 + return event._id == eventID;
  9258 + };
  9259 + }
  9260 +
  9261 + // Purge event(s) from our local cache
  9262 + cache = $.grep(cache, filter, true); // inverse=true
  9263 +
  9264 + // Remove events from array sources.
  9265 + // This works because they have been converted to official Event Objects up front.
  9266 + // (and as a result, event._id has been calculated).
  9267 + for (i=0; i<sources.length; i++) {
  9268 + if ($.isArray(sources[i].events)) {
  9269 + sources[i].events = $.grep(sources[i].events, filter, true);
  9270 + }
  9271 + }
  9272 +
  9273 + reportEvents(cache);
  9274 + }
  9275 +
  9276 +
  9277 + function clientEvents(filter) {
  9278 + if ($.isFunction(filter)) {
  9279 + return $.grep(cache, filter);
  9280 + }
  9281 + else if (filter != null) { // not null, not undefined. an event ID
  9282 + filter += '';
  9283 + return $.grep(cache, function(e) {
  9284 + return e._id == filter;
  9285 + });
  9286 + }
  9287 + return cache; // else, return all
  9288 + }
  9289 +
  9290 +
  9291 +
  9292 + /* Loading State
  9293 + -----------------------------------------------------------------------------*/
  9294 +
  9295 +
  9296 + function pushLoading() {
  9297 + if (!(loadingLevel++)) {
  9298 + trigger('loading', null, true, getView());
  9299 + }
  9300 + }
  9301 +
  9302 +
  9303 + function popLoading() {
  9304 + if (!(--loadingLevel)) {
  9305 + trigger('loading', null, false, getView());
  9306 + }
  9307 + }
  9308 +
  9309 +
  9310 +
  9311 + /* Event Normalization
  9312 + -----------------------------------------------------------------------------*/
  9313 +
  9314 +
  9315 + // Given a raw object with key/value properties, returns an "abstract" Event object.
  9316 + // An "abstract" event is an event that, if recurring, will not have been expanded yet.
  9317 + // Will return `false` when input is invalid.
  9318 + // `source` is optional
  9319 + function buildEventFromInput(input, source) {
  9320 + var out = {};
  9321 + var start, end;
  9322 + var allDay;
  9323 +
  9324 + if (options.eventDataTransform) {
  9325 + input = options.eventDataTransform(input);
  9326 + }
  9327 + if (source && source.eventDataTransform) {
  9328 + input = source.eventDataTransform(input);
  9329 + }
  9330 +
  9331 + // Copy all properties over to the resulting object.
  9332 + // The special-case properties will be copied over afterwards.
  9333 + $.extend(out, input);
  9334 +
  9335 + if (source) {
  9336 + out.source = source;
  9337 + }
  9338 +
  9339 + out._id = input._id || (input.id === undefined ? '_fc' + eventGUID++ : input.id + '');
  9340 +
  9341 + if (input.className) {
  9342 + if (typeof input.className == 'string') {
  9343 + out.className = input.className.split(/\s+/);
  9344 + }
  9345 + else { // assumed to be an array
  9346 + out.className = input.className;
  9347 + }
  9348 + }
  9349 + else {
  9350 + out.className = [];
  9351 + }
  9352 +
  9353 + start = input.start || input.date; // "date" is an alias for "start"
  9354 + end = input.end;
  9355 +
  9356 + // parse as a time (Duration) if applicable
  9357 + if (isTimeString(start)) {
  9358 + start = moment.duration(start);
  9359 + }
  9360 + if (isTimeString(end)) {
  9361 + end = moment.duration(end);
  9362 + }
  9363 +
  9364 + if (input.dow || moment.isDuration(start) || moment.isDuration(end)) {
  9365 +
  9366 + // the event is "abstract" (recurring) so don't calculate exact start/end dates just yet
  9367 + out.start = start ? moment.duration(start) : null; // will be a Duration or null
  9368 + out.end = end ? moment.duration(end) : null; // will be a Duration or null
  9369 + out._recurring = true; // our internal marker
  9370 + }
  9371 + else {
  9372 +
  9373 + if (start) {
  9374 + start = t.moment(start);
  9375 + if (!start.isValid()) {
  9376 + return false;
  9377 + }
  9378 + }
  9379 +
  9380 + if (end) {
  9381 + end = t.moment(end);
  9382 + if (!end.isValid()) {
  9383 + end = null; // let defaults take over
  9384 + }
  9385 + }
  9386 +
  9387 + allDay = input.allDay;
  9388 + if (allDay === undefined) { // still undefined? fallback to default
  9389 + allDay = firstDefined(
  9390 + source ? source.allDayDefault : undefined,
  9391 + options.allDayDefault
  9392 + );
  9393 + // still undefined? normalizeEventRange will calculate it
  9394 + }
  9395 +
  9396 + assignDatesToEvent(start, end, allDay, out);
  9397 + }
  9398 +
  9399 + return out;
  9400 + }
  9401 +
  9402 +
  9403 + // Normalizes and assigns the given dates to the given partially-formed event object.
  9404 + // NOTE: mutates the given start/end moments. does not make a copy.
  9405 + function assignDatesToEvent(start, end, allDay, event) {
  9406 + event.start = start;
  9407 + event.end = end;
  9408 + event.allDay = allDay;
  9409 + normalizeEventRange(event);
  9410 + backupEventDates(event);
  9411 + }
  9412 +
  9413 +
  9414 + // Ensures proper values for allDay/start/end. Accepts an Event object, or a plain object with event-ish properties.
  9415 + // NOTE: Will modify the given object.
  9416 + function normalizeEventRange(props) {
  9417 +
  9418 + normalizeEventRangeTimes(props);
  9419 +
  9420 + if (props.end && !props.end.isAfter(props.start)) {
  9421 + props.end = null;
  9422 + }
  9423 +
  9424 + if (!props.end) {
  9425 + if (options.forceEventDuration) {
  9426 + props.end = t.getDefaultEventEnd(props.allDay, props.start);
  9427 + }
  9428 + else {
  9429 + props.end = null;
  9430 + }
  9431 + }
  9432 + }
  9433 +
  9434 +
  9435 + // Ensures the allDay property exists and the timeliness of the start/end dates are consistent
  9436 + function normalizeEventRangeTimes(range) {
  9437 + if (range.allDay == null) {
  9438 + range.allDay = !(range.start.hasTime() || (range.end && range.end.hasTime()));
  9439 + }
  9440 +
  9441 + if (range.allDay) {
  9442 + range.start.stripTime();
  9443 + if (range.end) {
  9444 + // TODO: consider nextDayThreshold here? If so, will require a lot of testing and adjustment
  9445 + range.end.stripTime();
  9446 + }
  9447 + }
  9448 + else {
  9449 + if (!range.start.hasTime()) {
  9450 + range.start = t.rezoneDate(range.start); // will assign a 00:00 time
  9451 + }
  9452 + if (range.end && !range.end.hasTime()) {
  9453 + range.end = t.rezoneDate(range.end); // will assign a 00:00 time
  9454 + }
  9455 + }
  9456 + }
  9457 +
  9458 +
  9459 + // If `range` is a proper range with a start and end, returns the original object.
  9460 + // If missing an end, computes a new range with an end, computing it as if it were an event.
  9461 + // TODO: make this a part of the event -> eventRange system
  9462 + function ensureVisibleEventRange(range) {
  9463 + var allDay;
  9464 +
  9465 + if (!range.end) {
  9466 +
  9467 + allDay = range.allDay; // range might be more event-ish than we think
  9468 + if (allDay == null) {
  9469 + allDay = !range.start.hasTime();
  9470 + }
  9471 +
  9472 + range = $.extend({}, range); // make a copy, copying over other misc properties
  9473 + range.end = t.getDefaultEventEnd(allDay, range.start);
  9474 + }
  9475 + return range;
  9476 + }
  9477 +
  9478 +
  9479 + // If the given event is a recurring event, break it down into an array of individual instances.
  9480 + // If not a recurring event, return an array with the single original event.
  9481 + // If given a falsy input (probably because of a failed buildEventFromInput call), returns an empty array.
  9482 + // HACK: can override the recurring window by providing custom rangeStart/rangeEnd (for businessHours).
  9483 + function expandEvent(abstractEvent, _rangeStart, _rangeEnd) {
  9484 + var events = [];
  9485 + var dowHash;
  9486 + var dow;
  9487 + var i;
  9488 + var date;
  9489 + var startTime, endTime;
  9490 + var start, end;
  9491 + var event;
  9492 +
  9493 + _rangeStart = _rangeStart || rangeStart;
  9494 + _rangeEnd = _rangeEnd || rangeEnd;
  9495 +
  9496 + if (abstractEvent) {
  9497 + if (abstractEvent._recurring) {
  9498 +
  9499 + // make a boolean hash as to whether the event occurs on each day-of-week
  9500 + if ((dow = abstractEvent.dow)) {
  9501 + dowHash = {};
  9502 + for (i = 0; i < dow.length; i++) {
  9503 + dowHash[dow[i]] = true;
  9504 + }
  9505 + }
  9506 +
  9507 + // iterate through every day in the current range
  9508 + date = _rangeStart.clone().stripTime(); // holds the date of the current day
  9509 + while (date.isBefore(_rangeEnd)) {
  9510 +
  9511 + if (!dowHash || dowHash[date.day()]) { // if everyday, or this particular day-of-week
  9512 +
  9513 + startTime = abstractEvent.start; // the stored start and end properties are times (Durations)
  9514 + endTime = abstractEvent.end; // "
  9515 + start = date.clone();
  9516 + end = null;
  9517 +
  9518 + if (startTime) {
  9519 + start = start.time(startTime);
  9520 + }
  9521 + if (endTime) {
  9522 + end = date.clone().time(endTime);
  9523 + }
  9524 +
  9525 + event = $.extend({}, abstractEvent); // make a copy of the original
  9526 + assignDatesToEvent(
  9527 + start, end,
  9528 + !startTime && !endTime, // allDay?
  9529 + event
  9530 + );
  9531 + events.push(event);
  9532 + }
  9533 +
  9534 + date.add(1, 'days');
  9535 + }
  9536 + }
  9537 + else {
  9538 + events.push(abstractEvent); // return the original event. will be a one-item array
  9539 + }
  9540 + }
  9541 +
  9542 + return events;
  9543 + }
  9544 +
  9545 +
  9546 +
  9547 + /* Event Modification Math
  9548 + -----------------------------------------------------------------------------------------*/
  9549 +
  9550 +
  9551 + // Modifies an event and all related events by applying the given properties.
  9552 + // Special date-diffing logic is used for manipulation of dates.
  9553 + // If `props` does not contain start/end dates, the updated values are assumed to be the event's current start/end.
  9554 + // All date comparisons are done against the event's pristine _start and _end dates.
  9555 + // Returns an object with delta information and a function to undo all operations.
  9556 + // For making computations in a granularity greater than day/time, specify largeUnit.
  9557 + // NOTE: The given `newProps` might be mutated for normalization purposes.
  9558 + function mutateEvent(event, newProps, largeUnit) {
  9559 + var miscProps = {};
  9560 + var oldProps;
  9561 + var clearEnd;
  9562 + var startDelta;
  9563 + var endDelta;
  9564 + var durationDelta;
  9565 + var undoFunc;
  9566 +
  9567 + // diffs the dates in the appropriate way, returning a duration
  9568 + function diffDates(date1, date0) { // date1 - date0
  9569 + if (largeUnit) {
  9570 + return diffByUnit(date1, date0, largeUnit);
  9571 + }
  9572 + else if (newProps.allDay) {
  9573 + return diffDay(date1, date0);
  9574 + }
  9575 + else {
  9576 + return diffDayTime(date1, date0);
  9577 + }
  9578 + }
  9579 +
  9580 + newProps = newProps || {};
  9581 +
  9582 + // normalize new date-related properties
  9583 + if (!newProps.start) {
  9584 + newProps.start = event.start.clone();
  9585 + }
  9586 + if (newProps.end === undefined) {
  9587 + newProps.end = event.end ? event.end.clone() : null;
  9588 + }
  9589 + if (newProps.allDay == null) { // is null or undefined?
  9590 + newProps.allDay = event.allDay;
  9591 + }
  9592 + normalizeEventRange(newProps);
  9593 +
  9594 + // create normalized versions of the original props to compare against
  9595 + // need a real end value, for diffing
  9596 + oldProps = {
  9597 + start: event._start.clone(),
  9598 + end: event._end ? event._end.clone() : t.getDefaultEventEnd(event._allDay, event._start),
  9599 + allDay: newProps.allDay // normalize the dates in the same regard as the new properties
  9600 + };
  9601 + normalizeEventRange(oldProps);
  9602 +
  9603 + // need to clear the end date if explicitly changed to null
  9604 + clearEnd = event._end !== null && newProps.end === null;
  9605 +
  9606 + // compute the delta for moving the start date
  9607 + startDelta = diffDates(newProps.start, oldProps.start);
  9608 +
  9609 + // compute the delta for moving the end date
  9610 + if (newProps.end) {
  9611 + endDelta = diffDates(newProps.end, oldProps.end);
  9612 + durationDelta = endDelta.subtract(startDelta);
  9613 + }
  9614 + else {
  9615 + durationDelta = null;
  9616 + }
  9617 +
  9618 + // gather all non-date-related properties
  9619 + $.each(newProps, function(name, val) {
  9620 + if (isMiscEventPropName(name)) {
  9621 + if (val !== undefined) {
  9622 + miscProps[name] = val;
  9623 + }
  9624 + }
  9625 + });
  9626 +
  9627 + // apply the operations to the event and all related events
  9628 + undoFunc = mutateEvents(
  9629 + clientEvents(event._id), // get events with this ID
  9630 + clearEnd,
  9631 + newProps.allDay,
  9632 + startDelta,
  9633 + durationDelta,
  9634 + miscProps
  9635 + );
  9636 +
  9637 + return {
  9638 + dateDelta: startDelta,
  9639 + durationDelta: durationDelta,
  9640 + undo: undoFunc
  9641 + };
  9642 + }
  9643 +
  9644 +
  9645 + // Modifies an array of events in the following ways (operations are in order):
  9646 + // - clear the event's `end`
  9647 + // - convert the event to allDay
  9648 + // - add `dateDelta` to the start and end
  9649 + // - add `durationDelta` to the event's duration
  9650 + // - assign `miscProps` to the event
  9651 + //
  9652 + // Returns a function that can be called to undo all the operations.
  9653 + //
  9654 + // TODO: don't use so many closures. possible memory issues when lots of events with same ID.
  9655 + //
  9656 + function mutateEvents(events, clearEnd, allDay, dateDelta, durationDelta, miscProps) {
  9657 + var isAmbigTimezone = t.getIsAmbigTimezone();
  9658 + var undoFunctions = [];
  9659 +
  9660 + // normalize zero-length deltas to be null
  9661 + if (dateDelta && !dateDelta.valueOf()) { dateDelta = null; }
  9662 + if (durationDelta && !durationDelta.valueOf()) { durationDelta = null; }
  9663 +
  9664 + $.each(events, function(i, event) {
  9665 + var oldProps;
  9666 + var newProps;
  9667 +
  9668 + // build an object holding all the old values, both date-related and misc.
  9669 + // for the undo function.
  9670 + oldProps = {
  9671 + start: event.start.clone(),
  9672 + end: event.end ? event.end.clone() : null,
  9673 + allDay: event.allDay
  9674 + };
  9675 + $.each(miscProps, function(name) {
  9676 + oldProps[name] = event[name];
  9677 + });
  9678 +
  9679 + // new date-related properties. work off the original date snapshot.
  9680 + // ok to use references because they will be thrown away when backupEventDates is called.
  9681 + newProps = {
  9682 + start: event._start,
  9683 + end: event._end,
  9684 + allDay: allDay // normalize the dates in the same regard as the new properties
  9685 + };
  9686 + normalizeEventRange(newProps); // massages start/end/allDay
  9687 +
  9688 + // strip or ensure the end date
  9689 + if (clearEnd) {
  9690 + newProps.end = null;
  9691 + }
  9692 + else if (durationDelta && !newProps.end) { // the duration translation requires an end date
  9693 + newProps.end = t.getDefaultEventEnd(newProps.allDay, newProps.start);
  9694 + }
  9695 +
  9696 + if (dateDelta) {
  9697 + newProps.start.add(dateDelta);
  9698 + if (newProps.end) {
  9699 + newProps.end.add(dateDelta);
  9700 + }
  9701 + }
  9702 +
  9703 + if (durationDelta) {
  9704 + newProps.end.add(durationDelta); // end already ensured above
  9705 + }
  9706 +
  9707 + // if the dates have changed, and we know it is impossible to recompute the
  9708 + // timezone offsets, strip the zone.
  9709 + if (
  9710 + isAmbigTimezone &&
  9711 + !newProps.allDay &&
  9712 + (dateDelta || durationDelta)
  9713 + ) {
  9714 + newProps.start.stripZone();
  9715 + if (newProps.end) {
  9716 + newProps.end.stripZone();
  9717 + }
  9718 + }
  9719 +
  9720 + $.extend(event, miscProps, newProps); // copy over misc props, then date-related props
  9721 + backupEventDates(event); // regenerate internal _start/_end/_allDay
  9722 +
  9723 + undoFunctions.push(function() {
  9724 + $.extend(event, oldProps);
  9725 + backupEventDates(event); // regenerate internal _start/_end/_allDay
  9726 + });
  9727 + });
  9728 +
  9729 + return function() {
  9730 + for (var i = 0; i < undoFunctions.length; i++) {
  9731 + undoFunctions[i]();
  9732 + }
  9733 + };
  9734 + }
  9735 +
  9736 +
  9737 + /* Business Hours
  9738 + -----------------------------------------------------------------------------------------*/
  9739 +
  9740 + t.getBusinessHoursEvents = getBusinessHoursEvents;
  9741 +
  9742 +
  9743 + // Returns an array of events as to when the business hours occur in the given view.
  9744 + // Abuse of our event system :(
  9745 + function getBusinessHoursEvents(wholeDay) {
  9746 + var optionVal = options.businessHours;
  9747 + var defaultVal = {
  9748 + className: 'fc-nonbusiness',
  9749 + start: '09:00',
  9750 + end: '17:00',
  9751 + dow: [ 1, 2, 3, 4, 5 ], // monday - friday
  9752 + rendering: 'inverse-background'
  9753 + };
  9754 + var view = t.getView();
  9755 + var eventInput;
  9756 +
  9757 + if (optionVal) { // `true` (which means "use the defaults") or an override object
  9758 + eventInput = $.extend(
  9759 + {}, // copy to a new object in either case
  9760 + defaultVal,
  9761 + typeof optionVal === 'object' ? optionVal : {} // override the defaults
  9762 + );
  9763 + }
  9764 +
  9765 + if (eventInput) {
  9766 +
  9767 + // if a whole-day series is requested, clear the start/end times
  9768 + if (wholeDay) {
  9769 + eventInput.start = null;
  9770 + eventInput.end = null;
  9771 + }
  9772 +
  9773 + return expandEvent(
  9774 + buildEventFromInput(eventInput),
  9775 + view.start,
  9776 + view.end
  9777 + );
  9778 + }
  9779 +
  9780 + return [];
  9781 + }
  9782 +
  9783 +
  9784 + /* Overlapping / Constraining
  9785 + -----------------------------------------------------------------------------------------*/
  9786 +
  9787 + t.isEventRangeAllowed = isEventRangeAllowed;
  9788 + t.isSelectionRangeAllowed = isSelectionRangeAllowed;
  9789 + t.isExternalDropRangeAllowed = isExternalDropRangeAllowed;
  9790 +
  9791 +
  9792 + function isEventRangeAllowed(range, event) {
  9793 + var source = event.source || {};
  9794 + var constraint = firstDefined(
  9795 + event.constraint,
  9796 + source.constraint,
  9797 + options.eventConstraint
  9798 + );
  9799 + var overlap = firstDefined(
  9800 + event.overlap,
  9801 + source.overlap,
  9802 + options.eventOverlap
  9803 + );
  9804 +
  9805 + range = ensureVisibleEventRange(range); // ensure a proper range with an end for isRangeAllowed
  9806 +
  9807 + return isRangeAllowed(range, constraint, overlap, event);
  9808 + }
  9809 +
  9810 +
  9811 + function isSelectionRangeAllowed(range) {
  9812 + return isRangeAllowed(range, options.selectConstraint, options.selectOverlap);
  9813 + }
  9814 +
  9815 +
  9816 + // when `eventProps` is defined, consider this an event.
  9817 + // `eventProps` can contain misc non-date-related info about the event.
  9818 + function isExternalDropRangeAllowed(range, eventProps) {
  9819 + var eventInput;
  9820 + var event;
  9821 +
  9822 + // note: very similar logic is in View's reportExternalDrop
  9823 + if (eventProps) {
  9824 + eventInput = $.extend({}, eventProps, range);
  9825 + event = expandEvent(buildEventFromInput(eventInput))[0];
  9826 + }
  9827 +
  9828 + if (event) {
  9829 + return isEventRangeAllowed(range, event);
  9830 + }
  9831 + else { // treat it as a selection
  9832 +
  9833 + range = ensureVisibleEventRange(range); // ensure a proper range with an end for isSelectionRangeAllowed
  9834 +
  9835 + return isSelectionRangeAllowed(range);
  9836 + }
  9837 + }
  9838 +
  9839 +
  9840 + // Returns true if the given range (caused by an event drop/resize or a selection) is allowed to exist
  9841 + // according to the constraint/overlap settings.
  9842 + // `event` is not required if checking a selection.
  9843 + function isRangeAllowed(range, constraint, overlap, event) {
  9844 + var constraintEvents;
  9845 + var anyContainment;
  9846 + var peerEvents;
  9847 + var i, peerEvent;
  9848 + var peerOverlap;
  9849 +
  9850 + // normalize. fyi, we're normalizing in too many places :(
  9851 + range = $.extend({}, range); // copy all properties in case there are misc non-date properties
  9852 + range.start = range.start.clone().stripZone();
  9853 + range.end = range.end.clone().stripZone();
  9854 +
  9855 + // the range must be fully contained by at least one of produced constraint events
  9856 + if (constraint != null) {
  9857 +
  9858 + // not treated as an event! intermediate data structure
  9859 + // TODO: use ranges in the future
  9860 + constraintEvents = constraintToEvents(constraint);
  9861 +
  9862 + anyContainment = false;
  9863 + for (i = 0; i < constraintEvents.length; i++) {
  9864 + if (eventContainsRange(constraintEvents[i], range)) {
  9865 + anyContainment = true;
  9866 + break;
  9867 + }
  9868 + }
  9869 +
  9870 + if (!anyContainment) {
  9871 + return false;
  9872 + }
  9873 + }
  9874 +
  9875 + peerEvents = t.getPeerEvents(event, range);
  9876 +
  9877 + for (i = 0; i < peerEvents.length; i++) {
  9878 + peerEvent = peerEvents[i];
  9879 +
  9880 + // there needs to be an actual intersection before disallowing anything
  9881 + if (eventIntersectsRange(peerEvent, range)) {
  9882 +
  9883 + // evaluate overlap for the given range and short-circuit if necessary
  9884 + if (overlap === false) {
  9885 + return false;
  9886 + }
  9887 + // if the event's overlap is a test function, pass the peer event in question as the first param
  9888 + else if (typeof overlap === 'function' && !overlap(peerEvent, event)) {
  9889 + return false;
  9890 + }
  9891 +
  9892 + // if we are computing if the given range is allowable for an event, consider the other event's
  9893 + // EventObject-specific or Source-specific `overlap` property
  9894 + if (event) {
  9895 + peerOverlap = firstDefined(
  9896 + peerEvent.overlap,
  9897 + (peerEvent.source || {}).overlap
  9898 + // we already considered the global `eventOverlap`
  9899 + );
  9900 + if (peerOverlap === false) {
  9901 + return false;
  9902 + }
  9903 + // if the peer event's overlap is a test function, pass the subject event as the first param
  9904 + if (typeof peerOverlap === 'function' && !peerOverlap(event, peerEvent)) {
  9905 + return false;
  9906 + }
  9907 + }
  9908 + }
  9909 + }
  9910 +
  9911 + return true;
  9912 + }
  9913 +
  9914 +
  9915 + // Given an event input from the API, produces an array of event objects. Possible event inputs:
  9916 + // 'businessHours'
  9917 + // An event ID (number or string)
  9918 + // An object with specific start/end dates or a recurring event (like what businessHours accepts)
  9919 + function constraintToEvents(constraintInput) {
  9920 +
  9921 + if (constraintInput === 'businessHours') {
  9922 + return getBusinessHoursEvents();
  9923 + }
  9924 +
  9925 + if (typeof constraintInput === 'object') {
  9926 + return expandEvent(buildEventFromInput(constraintInput));
  9927 + }
  9928 +
  9929 + return clientEvents(constraintInput); // probably an ID
  9930 + }
  9931 +
  9932 +
  9933 + // Does the event's date range fully contain the given range?
  9934 + // start/end already assumed to have stripped zones :(
  9935 + function eventContainsRange(event, range) {
  9936 + var eventStart = event.start.clone().stripZone();
  9937 + var eventEnd = t.getEventEnd(event).stripZone();
  9938 +
  9939 + return range.start >= eventStart && range.end <= eventEnd;
  9940 + }
  9941 +
  9942 +
  9943 + // Does the event's date range intersect with the given range?
  9944 + // start/end already assumed to have stripped zones :(
  9945 + function eventIntersectsRange(event, range) {
  9946 + var eventStart = event.start.clone().stripZone();
  9947 + var eventEnd = t.getEventEnd(event).stripZone();
  9948 +
  9949 + return range.start < eventEnd && range.end > eventStart;
  9950 + }
  9951 +
  9952 +
  9953 + t.getEventCache = function() {
  9954 + return cache;
  9955 + };
  9956 +
  9957 +}
  9958 +
  9959 +
  9960 +// Returns a list of events that the given event should be compared against when being considered for a move to
  9961 +// the specified range. Attached to the Calendar's prototype because EventManager is a mixin for a Calendar.
  9962 +Calendar.prototype.getPeerEvents = function(event, range) {
  9963 + var cache = this.getEventCache();
  9964 + var peerEvents = [];
  9965 + var i, otherEvent;
  9966 +
  9967 + for (i = 0; i < cache.length; i++) {
  9968 + otherEvent = cache[i];
  9969 + if (
  9970 + !event ||
  9971 + event._id !== otherEvent._id // don't compare the event to itself or other related [repeating] events
  9972 + ) {
  9973 + peerEvents.push(otherEvent);
  9974 + }
  9975 + }
  9976 +
  9977 + return peerEvents;
  9978 +};
  9979 +
  9980 +
  9981 +// updates the "backup" properties, which are preserved in order to compute diffs later on.
  9982 +function backupEventDates(event) {
  9983 + event._allDay = event.allDay;
  9984 + event._start = event.start.clone();
  9985 + event._end = event.end ? event.end.clone() : null;
  9986 +}
  9987 +
  9988 +;;
  9989 +
  9990 +/* An abstract class for the "basic" views, as well as month view. Renders one or more rows of day cells.
  9991 +----------------------------------------------------------------------------------------------------------------------*/
  9992 +// It is a manager for a DayGrid subcomponent, which does most of the heavy lifting.
  9993 +// It is responsible for managing width/height.
  9994 +
  9995 +var BasicView = fcViews.basic = View.extend({
  9996 +
  9997 + dayGrid: null, // the main subcomponent that does most of the heavy lifting
  9998 +
  9999 + dayNumbersVisible: false, // display day numbers on each day cell?
  10000 + weekNumbersVisible: false, // display week numbers along the side?
  10001 +
  10002 + weekNumberWidth: null, // width of all the week-number cells running down the side
  10003 +
  10004 + headRowEl: null, // the fake row element of the day-of-week header
  10005 +
  10006 +
  10007 + initialize: function() {
  10008 + this.dayGrid = new DayGrid(this);
  10009 + this.coordMap = this.dayGrid.coordMap; // the view's date-to-cell mapping is identical to the subcomponent's
  10010 + },
  10011 +
  10012 +
  10013 + // Sets the display range and computes all necessary dates
  10014 + setRange: function(range) {
  10015 + View.prototype.setRange.call(this, range); // call the super-method
  10016 +
  10017 + this.dayGrid.breakOnWeeks = /year|month|week/.test(this.intervalUnit); // do before setRange
  10018 + this.dayGrid.setRange(range);
  10019 + },
  10020 +
  10021 +
  10022 + // Compute the value to feed into setRange. Overrides superclass.
  10023 + computeRange: function(date) {
  10024 + var range = View.prototype.computeRange.call(this, date); // get value from the super-method
  10025 +
  10026 + // year and month views should be aligned with weeks. this is already done for week
  10027 + if (/year|month/.test(range.intervalUnit)) {
  10028 + range.start.startOf('week');
  10029 + range.start = this.skipHiddenDays(range.start);
  10030 +
  10031 + // make end-of-week if not already
  10032 + if (range.end.weekday()) {
  10033 + range.end.add(1, 'week').startOf('week');
  10034 + range.end = this.skipHiddenDays(range.end, -1, true); // exclusively move backwards
  10035 + }
  10036 + }
  10037 +
  10038 + return range;
  10039 + },
  10040 +
  10041 +
  10042 + // Renders the view into `this.el`, which should already be assigned
  10043 + render: function() {
  10044 +
  10045 + this.dayNumbersVisible = this.dayGrid.rowCnt > 1; // TODO: make grid responsible
  10046 + this.weekNumbersVisible = this.opt('weekNumbers');
  10047 + this.dayGrid.numbersVisible = this.dayNumbersVisible || this.weekNumbersVisible;
  10048 +
  10049 + this.el.addClass('fc-basic-view').html(this.renderHtml());
  10050 +
  10051 + this.headRowEl = this.el.find('thead .fc-row');
  10052 +
  10053 + this.scrollerEl = this.el.find('.fc-day-grid-container');
  10054 + this.dayGrid.coordMap.containerEl = this.scrollerEl; // constrain clicks/etc to the dimensions of the scroller
  10055 +
  10056 + this.dayGrid.setElement(this.el.find('.fc-day-grid'));
  10057 + this.dayGrid.renderDates(this.hasRigidRows());
  10058 + },
  10059 +
  10060 +
  10061 + // Unrenders the content of the view. Since we haven't separated skeleton rendering from date rendering,
  10062 + // always completely kill the dayGrid's rendering.
  10063 + destroy: function() {
  10064 + this.dayGrid.destroyDates();
  10065 + this.dayGrid.removeElement();
  10066 + },
  10067 +
  10068 +
  10069 + renderBusinessHours: function() {
  10070 + this.dayGrid.renderBusinessHours();
  10071 + },
  10072 +
  10073 +
  10074 + // Builds the HTML skeleton for the view.
  10075 + // The day-grid component will render inside of a container defined by this HTML.
  10076 + renderHtml: function() {
  10077 + return '' +
  10078 + '<table>' +
  10079 + '<thead class="fc-head">' +
  10080 + '<tr>' +
  10081 + '<td class="' + this.widgetHeaderClass + '">' +
  10082 + this.dayGrid.headHtml() + // render the day-of-week headers
  10083 + '</td>' +
  10084 + '</tr>' +
  10085 + '</thead>' +
  10086 + '<tbody class="fc-body">' +
  10087 + '<tr>' +
  10088 + '<td class="' + this.widgetContentClass + '">' +
  10089 + '<div class="fc-day-grid-container">' +
  10090 + '<div class="fc-day-grid"/>' +
  10091 + '</div>' +
  10092 + '</td>' +
  10093 + '</tr>' +
  10094 + '</tbody>' +
  10095 + '</table>';
  10096 + },
  10097 +
  10098 +
  10099 + // Generates the HTML that will go before the day-of week header cells.
  10100 + // Queried by the DayGrid subcomponent when generating rows. Ordering depends on isRTL.
  10101 + headIntroHtml: function() {
  10102 + if (this.weekNumbersVisible) {
  10103 + return '' +
  10104 + '<th class="fc-week-number ' + this.widgetHeaderClass + '" ' + this.weekNumberStyleAttr() + '>' +
  10105 + '<span>' + // needed for matchCellWidths
  10106 + htmlEscape(this.opt('weekNumberTitle')) +
  10107 + '</span>' +
  10108 + '</th>';
  10109 + }
  10110 + },
  10111 +
  10112 +
  10113 + // Generates the HTML that will go before content-skeleton cells that display the day/week numbers.
  10114 + // Queried by the DayGrid subcomponent. Ordering depends on isRTL.
  10115 + numberIntroHtml: function(row) {
  10116 + if (this.weekNumbersVisible) {
  10117 + return '' +
  10118 + '<td class="fc-week-number" ' + this.weekNumberStyleAttr() + '>' +
  10119 + '<span>' + // needed for matchCellWidths
  10120 + this.dayGrid.getCell(row, 0).start.format('w') +
  10121 + '</span>' +
  10122 + '</td>';
  10123 + }
  10124 + },
  10125 +
  10126 +
  10127 + // Generates the HTML that goes before the day bg cells for each day-row.
  10128 + // Queried by the DayGrid subcomponent. Ordering depends on isRTL.
  10129 + dayIntroHtml: function() {
  10130 + if (this.weekNumbersVisible) {
  10131 + return '<td class="fc-week-number ' + this.widgetContentClass + '" ' +
  10132 + this.weekNumberStyleAttr() + '></td>';
  10133 + }
  10134 + },
  10135 +
  10136 +
  10137 + // Generates the HTML that goes before every other type of row generated by DayGrid. Ordering depends on isRTL.
  10138 + // Affects helper-skeleton and highlight-skeleton rows.
  10139 + introHtml: function() {
  10140 + if (this.weekNumbersVisible) {
  10141 + return '<td class="fc-week-number" ' + this.weekNumberStyleAttr() + '></td>';
  10142 + }
  10143 + },
  10144 +
  10145 +
  10146 + // Generates the HTML for the <td>s of the "number" row in the DayGrid's content skeleton.
  10147 + // The number row will only exist if either day numbers or week numbers are turned on.
  10148 + numberCellHtml: function(cell) {
  10149 + var date = cell.start;
  10150 + var classes;
  10151 +
  10152 + if (!this.dayNumbersVisible) { // if there are week numbers but not day numbers
  10153 + return '<td/>'; // will create an empty space above events :(
  10154 + }
  10155 +
  10156 + classes = this.dayGrid.getDayClasses(date);
  10157 + classes.unshift('fc-day-number');
  10158 +
  10159 + return '' +
  10160 + '<td class="' + classes.join(' ') + '" data-date="' + date.format() + '">' +
  10161 + date.date() +
  10162 + '</td>';
  10163 + },
  10164 +
  10165 +
  10166 + // Generates an HTML attribute string for setting the width of the week number column, if it is known
  10167 + weekNumberStyleAttr: function() {
  10168 + if (this.weekNumberWidth !== null) {
  10169 + return 'style="width:' + this.weekNumberWidth + 'px"';
  10170 + }
  10171 + return '';
  10172 + },
  10173 +
  10174 +
  10175 + // Determines whether each row should have a constant height
  10176 + hasRigidRows: function() {
  10177 + var eventLimit = this.opt('eventLimit');
  10178 + return eventLimit && typeof eventLimit !== 'number';
  10179 + },
  10180 +
  10181 +
  10182 + /* Dimensions
  10183 + ------------------------------------------------------------------------------------------------------------------*/
  10184 +
  10185 +
  10186 + // Refreshes the horizontal dimensions of the view
  10187 + updateWidth: function() {
  10188 + if (this.weekNumbersVisible) {
  10189 + // Make sure all week number cells running down the side have the same width.
  10190 + // Record the width for cells created later.
  10191 + this.weekNumberWidth = matchCellWidths(
  10192 + this.el.find('.fc-week-number')
  10193 + );
  10194 + }
  10195 + },
  10196 +
  10197 +
  10198 + // Adjusts the vertical dimensions of the view to the specified values
  10199 + setHeight: function(totalHeight, isAuto) {
  10200 + var eventLimit = this.opt('eventLimit');
  10201 + var scrollerHeight;
  10202 +
  10203 + // reset all heights to be natural
  10204 + unsetScroller(this.scrollerEl);
  10205 + uncompensateScroll(this.headRowEl);
  10206 +
  10207 + this.dayGrid.destroySegPopover(); // kill the "more" popover if displayed
  10208 +
  10209 + // is the event limit a constant level number?
  10210 + if (eventLimit && typeof eventLimit === 'number') {
  10211 + this.dayGrid.limitRows(eventLimit); // limit the levels first so the height can redistribute after
  10212 + }
  10213 +
  10214 + scrollerHeight = this.computeScrollerHeight(totalHeight);
  10215 + this.setGridHeight(scrollerHeight, isAuto);
  10216 +
  10217 + // is the event limit dynamically calculated?
  10218 + if (eventLimit && typeof eventLimit !== 'number') {
  10219 + this.dayGrid.limitRows(eventLimit); // limit the levels after the grid's row heights have been set
  10220 + }
  10221 +
  10222 + if (!isAuto && setPotentialScroller(this.scrollerEl, scrollerHeight)) { // using scrollbars?
  10223 +
  10224 + compensateScroll(this.headRowEl, getScrollbarWidths(this.scrollerEl));
  10225 +
  10226 + // doing the scrollbar compensation might have created text overflow which created more height. redo
  10227 + scrollerHeight = this.computeScrollerHeight(totalHeight);
  10228 + this.scrollerEl.height(scrollerHeight);
  10229 + }
  10230 + },
  10231 +
  10232 +
  10233 + // Sets the height of just the DayGrid component in this view
  10234 + setGridHeight: function(height, isAuto) {
  10235 + if (isAuto) {
  10236 + undistributeHeight(this.dayGrid.rowEls); // let the rows be their natural height with no expanding
  10237 + }
  10238 + else {
  10239 + distributeHeight(this.dayGrid.rowEls, height, true); // true = compensate for height-hogging rows
  10240 + }
  10241 + },
  10242 +
  10243 +
  10244 + /* Events
  10245 + ------------------------------------------------------------------------------------------------------------------*/
  10246 +
  10247 +
  10248 + // Renders the given events onto the view and populates the segments array
  10249 + renderEvents: function(events) {
  10250 + this.dayGrid.renderEvents(events);
  10251 +
  10252 + this.updateHeight(); // must compensate for events that overflow the row
  10253 + },
  10254 +
  10255 +
  10256 + // Retrieves all segment objects that are rendered in the view
  10257 + getEventSegs: function() {
  10258 + return this.dayGrid.getEventSegs();
  10259 + },
  10260 +
  10261 +
  10262 + // Unrenders all event elements and clears internal segment data
  10263 + destroyEvents: function() {
  10264 + this.dayGrid.destroyEvents();
  10265 +
  10266 + // we DON'T need to call updateHeight() because:
  10267 + // A) a renderEvents() call always happens after this, which will eventually call updateHeight()
  10268 + // B) in IE8, this causes a flash whenever events are rerendered
  10269 + },
  10270 +
  10271 +
  10272 + /* Dragging (for both events and external elements)
  10273 + ------------------------------------------------------------------------------------------------------------------*/
  10274 +
  10275 +
  10276 + // A returned value of `true` signals that a mock "helper" event has been rendered.
  10277 + renderDrag: function(dropLocation, seg) {
  10278 + return this.dayGrid.renderDrag(dropLocation, seg);
  10279 + },
  10280 +
  10281 +
  10282 + destroyDrag: function() {
  10283 + this.dayGrid.destroyDrag();
  10284 + },
  10285 +
  10286 +
  10287 + /* Selection
  10288 + ------------------------------------------------------------------------------------------------------------------*/
  10289 +
  10290 +
  10291 + // Renders a visual indication of a selection
  10292 + renderSelection: function(range) {
  10293 + this.dayGrid.renderSelection(range);
  10294 + },
  10295 +
  10296 +
  10297 + // Unrenders a visual indications of a selection
  10298 + destroySelection: function() {
  10299 + this.dayGrid.destroySelection();
  10300 + }
  10301 +
  10302 +});
  10303 +
  10304 +;;
  10305 +
  10306 +/* A month view with day cells running in rows (one-per-week) and columns
  10307 +----------------------------------------------------------------------------------------------------------------------*/
  10308 +
  10309 +var MonthView = fcViews.month = BasicView.extend({
  10310 +
  10311 + // Produces information about what range to display
  10312 + computeRange: function(date) {
  10313 + var range = BasicView.prototype.computeRange.call(this, date); // get value from super-method
  10314 + var rowCnt;
  10315 +
  10316 + // ensure 6 weeks
  10317 + if (this.isFixedWeeks()) {
  10318 + rowCnt = Math.ceil(range.end.diff(range.start, 'weeks', true)); // could be partial weeks due to hiddenDays
  10319 + range.end.add(6 - rowCnt, 'weeks');
  10320 + }
  10321 +
  10322 + return range;
  10323 + },
  10324 +
  10325 +
  10326 + // Overrides the default BasicView behavior to have special multi-week auto-height logic
  10327 + setGridHeight: function(height, isAuto) {
  10328 +
  10329 + isAuto = isAuto || this.opt('weekMode') === 'variable'; // LEGACY: weekMode is deprecated
  10330 +
  10331 + // if auto, make the height of each row the height that it would be if there were 6 weeks
  10332 + if (isAuto) {
  10333 + height *= this.rowCnt / 6;
  10334 + }
  10335 +
  10336 + distributeHeight(this.dayGrid.rowEls, height, !isAuto); // if auto, don't compensate for height-hogging rows
  10337 + },
  10338 +
  10339 +
  10340 + isFixedWeeks: function() {
  10341 + var weekMode = this.opt('weekMode'); // LEGACY: weekMode is deprecated
  10342 + if (weekMode) {
  10343 + return weekMode === 'fixed'; // if any other type of weekMode, assume NOT fixed
  10344 + }
  10345 +
  10346 + return this.opt('fixedWeekCount');
  10347 + }
  10348 +
  10349 +});
  10350 +
  10351 +MonthView.duration = { months: 1 }; // important for prev/next
  10352 +
  10353 +MonthView.defaults = {
  10354 + fixedWeekCount: true
  10355 +};
  10356 +;;
  10357 +
  10358 +/* A week view with simple day cells running horizontally
  10359 +----------------------------------------------------------------------------------------------------------------------*/
  10360 +
  10361 +fcViews.basicWeek = {
  10362 + type: 'basic',
  10363 + duration: { weeks: 1 }
  10364 +};
  10365 +;;
  10366 +
  10367 +/* A view with a single simple day cell
  10368 +----------------------------------------------------------------------------------------------------------------------*/
  10369 +
  10370 +fcViews.basicDay = {
  10371 + type: 'basic',
  10372 + duration: { days: 1 }
  10373 +};
  10374 +;;
  10375 +
  10376 +/* An abstract class for all agenda-related views. Displays one more columns with time slots running vertically.
  10377 +----------------------------------------------------------------------------------------------------------------------*/
  10378 +// Is a manager for the TimeGrid subcomponent and possibly the DayGrid subcomponent (if allDaySlot is on).
  10379 +// Responsible for managing width/height.
  10380 +
  10381 +var AGENDA_DEFAULTS = {
  10382 + allDaySlot: true,
  10383 + allDayText: 'all-day',
  10384 + scrollTime: '06:00:00',
  10385 + slotDuration: '00:30:00',
  10386 + minTime: '00:00:00',
  10387 + maxTime: '24:00:00',
  10388 + slotEventOverlap: true // a bad name. confused with overlap/constraint system
  10389 +};
  10390 +
  10391 +var AGENDA_ALL_DAY_EVENT_LIMIT = 5;
  10392 +
  10393 +var AgendaView = fcViews.agenda = View.extend({
  10394 +
  10395 + timeGrid: null, // the main time-grid subcomponent of this view
  10396 + dayGrid: null, // the "all-day" subcomponent. if all-day is turned off, this will be null
  10397 +
  10398 + axisWidth: null, // the width of the time axis running down the side
  10399 +
  10400 + noScrollRowEls: null, // set of fake row elements that must compensate when scrollerEl has scrollbars
  10401 +
  10402 + // when the time-grid isn't tall enough to occupy the given height, we render an <hr> underneath
  10403 + bottomRuleEl: null,
  10404 + bottomRuleHeight: null,
  10405 +
  10406 +
  10407 + initialize: function() {
  10408 + this.timeGrid = new TimeGrid(this);
  10409 +
  10410 + if (this.opt('allDaySlot')) { // should we display the "all-day" area?
  10411 + this.dayGrid = new DayGrid(this); // the all-day subcomponent of this view
  10412 +
  10413 + // the coordinate grid will be a combination of both subcomponents' grids
  10414 + this.coordMap = new ComboCoordMap([
  10415 + this.dayGrid.coordMap,
  10416 + this.timeGrid.coordMap
  10417 + ]);
  10418 + }
  10419 + else {
  10420 + this.coordMap = this.timeGrid.coordMap;
  10421 + }
  10422 + },
  10423 +
  10424 +
  10425 + /* Rendering
  10426 + ------------------------------------------------------------------------------------------------------------------*/
  10427 +
  10428 +
  10429 + // Sets the display range and computes all necessary dates
  10430 + setRange: function(range) {
  10431 + View.prototype.setRange.call(this, range); // call the super-method
  10432 +
  10433 + this.timeGrid.setRange(range);
  10434 + if (this.dayGrid) {
  10435 + this.dayGrid.setRange(range);
  10436 + }
  10437 + },
  10438 +
  10439 +
  10440 + // Renders the view into `this.el`, which has already been assigned
  10441 + render: function() {
  10442 +
  10443 + this.el.addClass('fc-agenda-view').html(this.renderHtml());
  10444 +
  10445 + // the element that wraps the time-grid that will probably scroll
  10446 + this.scrollerEl = this.el.find('.fc-time-grid-container');
  10447 + this.timeGrid.coordMap.containerEl = this.scrollerEl; // don't accept clicks/etc outside of this
  10448 +
  10449 + this.timeGrid.setElement(this.el.find('.fc-time-grid'));
  10450 + this.timeGrid.renderDates();
  10451 +
  10452 + // the <hr> that sometimes displays under the time-grid
  10453 + this.bottomRuleEl = $('<hr class="fc-divider ' + this.widgetHeaderClass + '"/>')
  10454 + .appendTo(this.timeGrid.el); // inject it into the time-grid
  10455 +
  10456 + if (this.dayGrid) {
  10457 + this.dayGrid.setElement(this.el.find('.fc-day-grid'));
  10458 + this.dayGrid.renderDates();
  10459 +
  10460 + // have the day-grid extend it's coordinate area over the <hr> dividing the two grids
  10461 + this.dayGrid.bottomCoordPadding = this.dayGrid.el.next('hr').outerHeight();
  10462 + }
  10463 +
  10464 + this.noScrollRowEls = this.el.find('.fc-row:not(.fc-scroller *)'); // fake rows not within the scroller
  10465 + },
  10466 +
  10467 +
  10468 + // Unrenders the content of the view. Since we haven't separated skeleton rendering from date rendering,
  10469 + // always completely kill each grid's rendering.
  10470 + destroy: function() {
  10471 + this.timeGrid.destroyDates();
  10472 + this.timeGrid.removeElement();
  10473 +
  10474 + if (this.dayGrid) {
  10475 + this.dayGrid.destroyDates();
  10476 + this.dayGrid.removeElement();
  10477 + }
  10478 + },
  10479 +
  10480 +
  10481 + renderBusinessHours: function() {
  10482 + this.timeGrid.renderBusinessHours();
  10483 +
  10484 + if (this.dayGrid) {
  10485 + this.dayGrid.renderBusinessHours();
  10486 + }
  10487 + },
  10488 +
  10489 +
  10490 + // Builds the HTML skeleton for the view.
  10491 + // The day-grid and time-grid components will render inside containers defined by this HTML.
  10492 + renderHtml: function() {
  10493 + return '' +
  10494 + '<table>' +
  10495 + '<thead class="fc-head">' +
  10496 + '<tr>' +
  10497 + '<td class="' + this.widgetHeaderClass + '">' +
  10498 + this.timeGrid.headHtml() + // render the day-of-week headers
  10499 + '</td>' +
  10500 + '</tr>' +
  10501 + '</thead>' +
  10502 + '<tbody class="fc-body">' +
  10503 + '<tr>' +
  10504 + '<td class="' + this.widgetContentClass + '">' +
  10505 + (this.dayGrid ?
  10506 + '<div class="fc-day-grid"/>' +
  10507 + '<hr class="fc-divider ' + this.widgetHeaderClass + '"/>' :
  10508 + ''
  10509 + ) +
  10510 + '<div class="fc-time-grid-container">' +
  10511 + '<div class="fc-time-grid"/>' +
  10512 + '</div>' +
  10513 + '</td>' +
  10514 + '</tr>' +
  10515 + '</tbody>' +
  10516 + '</table>';
  10517 + },
  10518 +
  10519 +
  10520 + // Generates the HTML that will go before the day-of week header cells.
  10521 + // Queried by the TimeGrid subcomponent when generating rows. Ordering depends on isRTL.
  10522 + headIntroHtml: function() {
  10523 + var date;
  10524 + var weekText;
  10525 +
  10526 + if (this.opt('weekNumbers')) {
  10527 + date = this.timeGrid.getCell(0).start;
  10528 + weekText = date.format(this.opt('smallWeekFormat'));
  10529 +
  10530 + return '' +
  10531 + '<th class="fc-axis fc-week-number ' + this.widgetHeaderClass + '" ' + this.axisStyleAttr() + '>' +
  10532 + '<span>' + // needed for matchCellWidths
  10533 + htmlEscape(weekText) +
  10534 + '</span>' +
  10535 + '</th>';
  10536 + }
  10537 + else {
  10538 + return '<th class="fc-axis ' + this.widgetHeaderClass + '" ' + this.axisStyleAttr() + '></th>';
  10539 + }
  10540 + },
  10541 +
  10542 +
  10543 + // Generates the HTML that goes before the all-day cells.
  10544 + // Queried by the DayGrid subcomponent when generating rows. Ordering depends on isRTL.
  10545 + dayIntroHtml: function() {
  10546 + return '' +
  10547 + '<td class="fc-axis ' + this.widgetContentClass + '" ' + this.axisStyleAttr() + '>' +
  10548 + '<span>' + // needed for matchCellWidths
  10549 + (this.opt('allDayHtml') || htmlEscape(this.opt('allDayText'))) +
  10550 + '</span>' +
  10551 + '</td>';
  10552 + },
  10553 +
  10554 +
  10555 + // Generates the HTML that goes before the bg of the TimeGrid slot area. Long vertical column.
  10556 + slotBgIntroHtml: function() {
  10557 + return '<td class="fc-axis ' + this.widgetContentClass + '" ' + this.axisStyleAttr() + '></td>';
  10558 + },
  10559 +
  10560 +
  10561 + // Generates the HTML that goes before all other types of cells.
  10562 + // Affects content-skeleton, helper-skeleton, highlight-skeleton for both the time-grid and day-grid.
  10563 + // Queried by the TimeGrid and DayGrid subcomponents when generating rows. Ordering depends on isRTL.
  10564 + introHtml: function() {
  10565 + return '<td class="fc-axis" ' + this.axisStyleAttr() + '></td>';
  10566 + },
  10567 +
  10568 +
  10569 + // Generates an HTML attribute string for setting the width of the axis, if it is known
  10570 + axisStyleAttr: function() {
  10571 + if (this.axisWidth !== null) {
  10572 + return 'style="width:' + this.axisWidth + 'px"';
  10573 + }
  10574 + return '';
  10575 + },
  10576 +
  10577 +
  10578 + /* Dimensions
  10579 + ------------------------------------------------------------------------------------------------------------------*/
  10580 +
  10581 +
  10582 + updateSize: function(isResize) {
  10583 + this.timeGrid.updateSize(isResize);
  10584 +
  10585 + View.prototype.updateSize.call(this, isResize); // call the super-method
  10586 + },
  10587 +
  10588 +
  10589 + // Refreshes the horizontal dimensions of the view
  10590 + updateWidth: function() {
  10591 + // make all axis cells line up, and record the width so newly created axis cells will have it
  10592 + this.axisWidth = matchCellWidths(this.el.find('.fc-axis'));
  10593 + },
  10594 +
  10595 +
  10596 + // Adjusts the vertical dimensions of the view to the specified values
  10597 + setHeight: function(totalHeight, isAuto) {
  10598 + var eventLimit;
  10599 + var scrollerHeight;
  10600 +
  10601 + if (this.bottomRuleHeight === null) {
  10602 + // calculate the height of the rule the very first time
  10603 + this.bottomRuleHeight = this.bottomRuleEl.outerHeight();
  10604 + }
  10605 + this.bottomRuleEl.hide(); // .show() will be called later if this <hr> is necessary
  10606 +
  10607 + // reset all dimensions back to the original state
  10608 + this.scrollerEl.css('overflow', '');
  10609 + unsetScroller(this.scrollerEl);
  10610 + uncompensateScroll(this.noScrollRowEls);
  10611 +
  10612 + // limit number of events in the all-day area
  10613 + if (this.dayGrid) {
  10614 + this.dayGrid.destroySegPopover(); // kill the "more" popover if displayed
  10615 +
  10616 + eventLimit = this.opt('eventLimit');
  10617 + if (eventLimit && typeof eventLimit !== 'number') {
  10618 + eventLimit = AGENDA_ALL_DAY_EVENT_LIMIT; // make sure "auto" goes to a real number
  10619 + }
  10620 + if (eventLimit) {
  10621 + this.dayGrid.limitRows(eventLimit);
  10622 + }
  10623 + }
  10624 +
  10625 + if (!isAuto) { // should we force dimensions of the scroll container, or let the contents be natural height?
  10626 +
  10627 + scrollerHeight = this.computeScrollerHeight(totalHeight);
  10628 + if (setPotentialScroller(this.scrollerEl, scrollerHeight)) { // using scrollbars?
  10629 +
  10630 + // make the all-day and header rows lines up
  10631 + compensateScroll(this.noScrollRowEls, getScrollbarWidths(this.scrollerEl));
  10632 +
  10633 + // the scrollbar compensation might have changed text flow, which might affect height, so recalculate
  10634 + // and reapply the desired height to the scroller.
  10635 + scrollerHeight = this.computeScrollerHeight(totalHeight);
  10636 + this.scrollerEl.height(scrollerHeight);
  10637 + }
  10638 + else { // no scrollbars
  10639 + // still, force a height and display the bottom rule (marks the end of day)
  10640 + this.scrollerEl.height(scrollerHeight).css('overflow', 'hidden'); // in case <hr> goes outside
  10641 + this.bottomRuleEl.show();
  10642 + }
  10643 + }
  10644 + },
  10645 +
  10646 +
  10647 + // Computes the initial pre-configured scroll state prior to allowing the user to change it
  10648 + computeInitialScroll: function() {
  10649 + var scrollTime = moment.duration(this.opt('scrollTime'));
  10650 + var top = this.timeGrid.computeTimeTop(scrollTime);
  10651 +
  10652 + // zoom can give weird floating-point values. rather scroll a little bit further
  10653 + top = Math.ceil(top);
  10654 +
  10655 + if (top) {
  10656 + top++; // to overcome top border that slots beyond the first have. looks better
  10657 + }
  10658 +
  10659 + return top;
  10660 + },
  10661 +
  10662 +
  10663 + /* Events
  10664 + ------------------------------------------------------------------------------------------------------------------*/
  10665 +
  10666 +
  10667 + // Renders events onto the view and populates the View's segment array
  10668 + renderEvents: function(events) {
  10669 + var dayEvents = [];
  10670 + var timedEvents = [];
  10671 + var daySegs = [];
  10672 + var timedSegs;
  10673 + var i;
  10674 +
  10675 + // separate the events into all-day and timed
  10676 + for (i = 0; i < events.length; i++) {
  10677 + if (events[i].allDay) {
  10678 + dayEvents.push(events[i]);
  10679 + }
  10680 + else {
  10681 + timedEvents.push(events[i]);
  10682 + }
  10683 + }
  10684 +
  10685 + // render the events in the subcomponents
  10686 + timedSegs = this.timeGrid.renderEvents(timedEvents);
  10687 + if (this.dayGrid) {
  10688 + daySegs = this.dayGrid.renderEvents(dayEvents);
  10689 + }
  10690 +
  10691 + // the all-day area is flexible and might have a lot of events, so shift the height
  10692 + this.updateHeight();
  10693 + },
  10694 +
  10695 +
  10696 + // Retrieves all segment objects that are rendered in the view
  10697 + getEventSegs: function() {
  10698 + return this.timeGrid.getEventSegs().concat(
  10699 + this.dayGrid ? this.dayGrid.getEventSegs() : []
  10700 + );
  10701 + },
  10702 +
  10703 +
  10704 + // Unrenders all event elements and clears internal segment data
  10705 + destroyEvents: function() {
  10706 +
  10707 + // destroy the events in the subcomponents
  10708 + this.timeGrid.destroyEvents();
  10709 + if (this.dayGrid) {
  10710 + this.dayGrid.destroyEvents();
  10711 + }
  10712 +
  10713 + // we DON'T need to call updateHeight() because:
  10714 + // A) a renderEvents() call always happens after this, which will eventually call updateHeight()
  10715 + // B) in IE8, this causes a flash whenever events are rerendered
  10716 + },
  10717 +
  10718 +
  10719 + /* Dragging (for events and external elements)
  10720 + ------------------------------------------------------------------------------------------------------------------*/
  10721 +
  10722 +
  10723 + // A returned value of `true` signals that a mock "helper" event has been rendered.
  10724 + renderDrag: function(dropLocation, seg) {
  10725 + if (dropLocation.start.hasTime()) {
  10726 + return this.timeGrid.renderDrag(dropLocation, seg);
  10727 + }
  10728 + else if (this.dayGrid) {
  10729 + return this.dayGrid.renderDrag(dropLocation, seg);
  10730 + }
  10731 + },
  10732 +
  10733 +
  10734 + destroyDrag: function() {
  10735 + this.timeGrid.destroyDrag();
  10736 + if (this.dayGrid) {
  10737 + this.dayGrid.destroyDrag();
  10738 + }
  10739 + },
  10740 +
  10741 +
  10742 + /* Selection
  10743 + ------------------------------------------------------------------------------------------------------------------*/
  10744 +
  10745 +
  10746 + // Renders a visual indication of a selection
  10747 + renderSelection: function(range) {
  10748 + if (range.start.hasTime() || range.end.hasTime()) {
  10749 + this.timeGrid.renderSelection(range);
  10750 + }
  10751 + else if (this.dayGrid) {
  10752 + this.dayGrid.renderSelection(range);
  10753 + }
  10754 + },
  10755 +
  10756 +
  10757 + // Unrenders a visual indications of a selection
  10758 + destroySelection: function() {
  10759 + this.timeGrid.destroySelection();
  10760 + if (this.dayGrid) {
  10761 + this.dayGrid.destroySelection();
  10762 + }
  10763 + }
  10764 +
  10765 +});
  10766 +
  10767 +AgendaView.defaults = AGENDA_DEFAULTS;
  10768 +
  10769 +;;
  10770 +
  10771 +/* A week view with an all-day cell area at the top, and a time grid below
  10772 +----------------------------------------------------------------------------------------------------------------------*/
  10773 +
  10774 +fcViews.agendaWeek = {
  10775 + type: 'agenda',
  10776 + duration: { weeks: 1 }
  10777 +};
  10778 +;;
  10779 +
  10780 +/* A day view with an all-day cell area at the top, and a time grid below
  10781 +----------------------------------------------------------------------------------------------------------------------*/
  10782 +
  10783 +fcViews.agendaDay = {
  10784 + type: 'agenda',
  10785 + duration: { days: 1 }
  10786 +};
  10787 +;;
  10788 +
  10789 +return fc; // export for Node/CommonJS
  10790 +});
12 10791 \ No newline at end of file
... ...
app/partials/scheduler/scheduler.controller.js
... ... @@ -3,80 +3,59 @@
3 3 //Load controller
4 4 angular.module('acufuel')
5 5  
6   - .controller('schedulerController', ['$scope', function($scope) {
  6 + .controller('schedulerController', ['$scope','$compile', 'uiCalendarConfig', function($scope, $compile, uiCalendarConfig) {
7 7  
8 8 $scope.test = "Testing...";
  9 +
  10 + var date = new Date();
  11 + var d = date.getDate();
  12 + var m = date.getMonth();
  13 + var y = date.getFullYear();
  14 +
9 15  
10   -
11   - /*$('#calendar').fullCalendar({
12   - header: {
13   - left: 'prev,next today',
14   - center: 'title',
15   - right: 'month,agendaWeek,agendaDay,listWeek'
16   - },
17   - defaultDate: '2017-05-12',
18   - navLinks: true, // can click day/week names to navigate views
19   - editable: true,
20   - eventLimit: true, // allow "more" link when too many events
21   - events: [
22   - {
23   - title: 'All Day Event',
24   - start: '2017-05-01'
25   - },
26   - {
27   - title: 'Long Event',
28   - start: '2017-05-07',
29   - end: '2017-05-10'
30   - },
31   - {
32   - id: 999,
33   - title: 'Repeating Event',
34   - start: '2017-05-09T16:00:00'
35   - },
36   - {
37   - id: 999,
38   - title: 'Repeating Event',
39   - start: '2017-05-16T16:00:00'
40   - },
41   - {
42   - title: 'Conference',
43   - start: '2017-05-11',
44   - end: '2017-05-13'
45   - },
46   - {
47   - title: 'Meeting',
48   - start: '2017-05-12T10:30:00',
49   - end: '2017-05-12T12:30:00'
50   - },
51   - {
52   - title: 'Lunch',
53   - start: '2017-05-12T12:00:00'
54   - },
55   - {
56   - title: 'Meeting',
57   - start: '2017-05-12T14:30:00'
58   - },
59   - {
60   - title: 'Happy Hour',
61   - start: '2017-05-12T17:30:00'
62   - },
63   - {
64   - title: 'Dinner',
65   - start: '2017-05-12T20:00:00'
66   - },
67   - {
68   - title: 'Birthday Party',
69   - start: '2017-05-13T07:00:00'
70   - },
71   - {
72   - title: 'Click for Google',
73   - start: '2017-05-28'
  16 + $scope.eventList=[
  17 + {title:'Event 1'},
  18 + {title:'Event 2'},
  19 + {title:'Event 3'},
  20 + {title:'Event 4'}
  21 + ];
  22 +
  23 + $scope.eventSources=[];
  24 +
  25 + $scope.events = [
  26 + {title: 'All Day Event', start: new Date(y, m, 1)},
  27 + {title: 'Birthday Party', start: new Date(y, m, d + 1, 19, 0), end: new Date(y, m, d + 1, 22, 30), allDay: false},
  28 + {title: 'Click for Google', start: new Date(y, m, 28), end: new Date(y, m, 29)}
  29 + ];
  30 +
  31 + $scope.uiConfig = {
  32 + calendar:{
  33 + height: 450,
  34 + editable: true,
  35 + droppable: true,
  36 + drop: function (date, allDay, jsEvent, ui) {
  37 + console.log('Here ,but where is the object?');
  38 + },
  39 + header:{
  40 + left: 'title',
  41 + center: '',
  42 + right: 'today prev,next'
  43 + },
  44 + eventResize: true,
74 45 }
75   - ]
76   - });
77   -
78   - });*/
  46 + };
79 47  
  48 + $scope.eventSources = [$scope.events];
  49 + //$scope.eventSources = [];
  50 + //$scope.eventSources.push($scope.events);
  51 +
  52 + $scope.addEvent = function(index) {
  53 + console.log('INDEX', index);
  54 + console.log('EVENTS', $scope.eventSources);
  55 + //$scope.events.push($scope.eventList[index]);
  56 + }
  57 +
  58 + console.log($scope.eventSources);
80 59  
81 60 }]);
82 61  
... ...
app/partials/scheduler/scheduler.html
... ... @@ -65,31 +65,34 @@
65 65 <div class="container">
66 66 <div class="row">
67 67 <div class="col-xs-12">
68   - <!-- <div id='wrap'>
  68 + <div id='wrap'>
69 69  
70   - <div id='external-events'>
  70 + <div id='external-events' class="col-xs-12 col-md-3">
71 71 <h4>Draggable Events</h4>
72   - <div class='fc-event'>My Event 1</div>
73   - <div class='fc-event'>My Event 2</div>
74   - <div class='fc-event'>My Event 3</div>
75   - <div class='fc-event'>My Event 4</div>
76   - <div class='fc-event'>My Event 5</div>
77   - <p>
  72 + <label ng-repeat="item in eventList" style="width: 100%;">
  73 + <div class="fc-event" data-drag="true" data-jqyoui-options="{revert: 'invalid'}" jqyoui-draggable="{index: {{$index}},placeholder:true,animate:true}">
  74 + {{item.title}}
  75 + </div>
  76 + </label>
  77 + <!-- <p>
78 78 <input type='checkbox' id='drop-remove' />
79 79 <label for='drop-remove'>remove after drop</label>
80   - </p>
  80 + </p> -->
81 81 </div>
82 82  
83   - <div id='calendar'></div>
  83 + <div ui-calendar="uiConfig.calendar" ng-model="eventSources" class="col-xs-12 col-md-9" data-drop="true" jqyoui-droppable="{multiple:true, onDrop: 'addEvent($index)'}"></div>
84 84  
85 85 <div style='clear:both'></div>
86 86  
87   - </div> -->
  87 + </div>
  88 +
  89 +
88 90  
89   -
90 91 </div>
91 92  
92 93 </div>
  94 + <div>
  95 + </div>
93 96 <!-- /row -->
94 97 </div>
95 98 <!-- /container -->
... ...
app/partials/updateFuelManager/updateFuelManager.controller.js
... ... @@ -584,10 +584,11 @@
584 584 $scope.newFuelPricing[i].futureFuelPricing.deployDate = $scope.newFuelPricing[i].futureFuelPricing.deployDate.getTime();
585 585 }
586 586  
587   - $scope.newFuelPricing[i].futureFuelPricing.papTotal = parseFloat($scope.newFuelPricing[i].futureFuelPricing.cost) + parseFloat($scope.newFuelPricing[i].fuelPricing.papMargin);
  587 + $scope.newFuelPricing[i].futureFuelPricing.papTotal = parseFloat($scope.newFuelPricing[i].futureFuelPricing.cost) + parseFloat($scope.newFuelPricing[i].futureFuelPricing.papMargin);
  588 + //$scope.newFuelPricing[i].futureFuelPricing.papTotal;
588 589 $scope.updateFutureFuelPricing.futureFuelPricingList.push({
589 590 'cost': $scope.newFuelPricing[i].futureFuelPricing.cost,
590   - 'papMargin': $scope.newFuelPricing[i].fuelPricing.papMargin,
  591 + 'papMargin': $scope.newFuelPricing[i].futureFuelPricing.papMargin,
591 592 //'papTotal': $scope.newFuelPricing[i].futureFuelPricing.papTotal,
592 593 'papTotal': $scope.newFuelPricing[i].futureFuelPricing.papTotal,
593 594 'expirationDate': $scope.newFuelPricing[i].futureFuelPricing.nextExpiration,
... ... @@ -604,6 +605,7 @@
604 605 $scope.newFuelPricing[i].futureFuelPricing.deployDate = '';*/
605 606 }
606 607 }
  608 + //console.log('$scope.updateFutureFuelPricing', $scope.updateFutureFuelPricing);
607 609 updateFuelManagerService.updateFutureFuelPricing($scope.updateFutureFuelPricing).then(function(result) {
608 610 toastr.success('Successfully Updated', {
609 611 closeButton: true
... ...
app/partials/updateFuelManager/updateFuelManager.html
... ... @@ -8,7 +8,7 @@
8 8 <div class="myLoader" ng-show="showLoader">
9 9 <img src="../img/hourglass.gif" width="50px;">
10 10 </div>
11   -<div style="width: 90%; margin-left: 5%;">
  11 +<div style="width: 96%; margin-left: 2%;">
12 12 <div class="row">
13 13  
14 14 <div class="col-md-6">
... ... @@ -53,7 +53,7 @@
53 53 <input type="text" class="form-control" datepicker ng-disabled="fuelPricing.futureFuelPricing.cost == undefined || fuelPricing.futureFuelPricing.cost == null || fuelPricing.futureFuelPricing.cost == ''" ng-model="fuelPricing.futureFuelPricing.nextExpiration" style="height:31px; width: 80px; padding: 6px 6px;">
54 54 </td>
55 55 <td>
56   - <span style="line-height: 31px; color: #1ab394;">$ {{fuelPricing.futureFuelPricing.cost -- fuelPricing.fuelPricing.papMargin | number : 2}}</span>
  56 + <span style="line-height: 31px; color: #1ab394;">$ {{fuelPricing.futureFuelPricing.cost -- fuelPricing.futureFuelPricing.papMargin | number : 2}}</span>
57 57 </td>
58 58 </tr>
59 59 </tbody>
... ... @@ -104,7 +104,7 @@
104 104 <button class="btn btn-success" style="display: none; background-image: none; background-color: #f3f3f3; color: #333; border:0;" ng-click="closeAccordian(jets)">Close</button>
105 105 <button class="btn btn-success" style="display: none;" ng-click="saveJetAccordian(jets)">Save</button>
106 106 <button class="btn btn-danger" style="display: none;" ng-click="deleteJetAccordian(jets.id)">Delete</button>
107   - <button type="button" class="btn btn-primary" ng-model="" ng-click="emailPricingForMargin(jets.id)" style= "font-weight: normal; text-align: center; font-size:12px">Email Pricing for this Margin</button>
  107 + <button type="button" class="btn btn-primary" ng-click="emailPricingForMargin(jets.id)" style= "font-weight: normal; text-align: center; font-size:12px">Email Pricing for this Margin</button>
108 108 <button class="btn btn-default" ng-click="toggleJestAccordian(jets.id, $index)" style= "text-align: center; font-size:12px">Edit</button>
109 109 </div>
110 110 <div class="clearfix"></div>
... ...
... ... @@ -36,6 +36,7 @@
36 36 "angular-bootstrap-toggle": "~0.1.2",
37 37 "angular-ui-select2": "^0.0.5",
38 38 "angular-ckeditor": "^1.0.3",
39   - "angular-ui-calendar": "^1.0.2"
  39 + "angular-ui-calendar": "^1.0.2",
  40 + "angular-dragdrop": "^1.0.13"
40 41 }
41 42 }
... ...