Guest User

fullcalendar

a guest
Jun 14th, 2011
346
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. /**
  2.  * @preserve
  3.  * FullCalendar v1.5.1
  4.  * http://arshaw.com/fullcalendar/
  5.  *
  6.  * Use fullcalendar.css for basic styling.
  7.  * For event drag & drop, requires jQuery UI draggable.
  8.  * For event resizing, requires jQuery UI resizable.
  9.  *
  10.  * Copyright (c) 2011 Adam Shaw
  11.  * Dual licensed under the MIT and GPL licenses, located in
  12.  * MIT-LICENSE.txt and GPL-LICENSE.txt respectively.
  13.  *
  14.  * Date: Sat Apr 9 14:09:51 2011 -0700
  15.  *
  16.  */
  17.  
  18. (function($, undefined) {
  19.  
  20.  
  21. var defaults = {
  22.  
  23.     // display
  24.     defaultView: 'month',
  25.     aspectRatio: 1.35,
  26.     header: {
  27.         left: 'title',
  28.         center: '',
  29.         right: 'today prev,next'
  30.     },
  31.     weekends: true,
  32.    
  33.     // editing
  34.     //editable: false,
  35.     //disableDragging: false,
  36.     //disableResizing: false,
  37.    
  38.     allDayDefault: true,
  39.     ignoreTimezone: true,
  40.    
  41.     // event ajax
  42.     lazyFetching: true,
  43.     startParam: 'start',
  44.     endParam: 'end',
  45.    
  46.     // time formats
  47.     titleFormat: {
  48.         month: 'MMMM yyyy',
  49.         week: "MMM d[ yyyy]{ '—'[ MMM] d yyyy}",
  50.         day: 'dddd, MMM d, yyyy'
  51.     },
  52.     columnFormat: {
  53.         month: 'ddd',
  54.         week: 'ddd M/d',
  55.         day: 'dddd M/d'
  56.     },
  57.     timeFormat: { // for event elements
  58.         '': 'h(:mm)t' // default
  59.     },
  60.    
  61.     // locale
  62.     isRTL: false,
  63.     firstDay: 0,
  64.     monthNames: ['January','February','March','April','May','June','July','August','September','October','November','December'],
  65.     monthNamesShort: ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'],
  66.     dayNames: ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'],
  67.     dayNamesShort: ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'],
  68.     buttonText: {
  69.         prev: ' ◄ ',
  70.         next: ' ► ',
  71.         prevYear: ' << ',
  72.         nextYear: ' >> ',
  73.         today: 'today',
  74.         month: 'month',
  75.         week: 'week',
  76.         day: 'day'
  77.     },
  78.    
  79.     // jquery-ui theming
  80.     theme: false,
  81.     buttonIcons: {
  82.         prev: 'circle-triangle-w',
  83.         next: 'circle-triangle-e'
  84.     },
  85.    
  86.     //selectable: false,
  87.     unselectAuto: true,
  88.    
  89.     dropAccept: '*'
  90.    
  91. };
  92.  
  93. // right-to-left defaults
  94. var rtlDefaults = {
  95.     header: {
  96.         left: 'next,prev today',
  97.         center: '',
  98.         right: 'title'
  99.     },
  100.     buttonText: {
  101.         prev: ' ► ',
  102.         next: ' ◄ ',
  103.         prevYear: ' >> ',
  104.         nextYear: ' << '
  105.     },
  106.     buttonIcons: {
  107.         prev: 'circle-triangle-e',
  108.         next: 'circle-triangle-w'
  109.     }
  110. };
  111.  
  112.  
  113.  
  114. var fc = $.fullCalendar = { version: "1.5.1" };
  115. var fcViews = fc.views = {};
  116.  
  117.  
  118. $.fn.fullCalendar = function(options) {
  119.  
  120.  
  121.     // method calling
  122.     if (typeof options == 'string') {
  123.         var args = Array.prototype.slice.call(arguments, 1);
  124.         var res;
  125.         this.each(function() {
  126.             var calendar = $.data(this, 'fullCalendar');
  127.             if (calendar && $.isFunction(calendar[options])) {
  128.                 var r = calendar[options].apply(calendar, args);
  129.                 if (res === undefined) {
  130.                     res = r;
  131.                 }
  132.                 if (options == 'destroy') {
  133.                     $.removeData(this, 'fullCalendar');
  134.                 }
  135.             }
  136.         });
  137.         if (res !== undefined) {
  138.             return res;
  139.         }
  140.         return this;
  141.     }
  142.    
  143.    
  144.     // would like to have this logic in EventManager, but needs to happen before options are recursively extended
  145.     var eventSources = options.eventSources || [];
  146.     delete options.eventSources;
  147.     if (options.events) {
  148.         eventSources.push(options.events);
  149.         delete options.events;
  150.     }
  151.    
  152.  
  153.     options = $.extend(true, {},
  154.         defaults,
  155.         (options.isRTL || options.isRTL===undefined && defaults.isRTL) ? rtlDefaults : {},
  156.         options
  157.     );
  158.    
  159.    
  160.     this.each(function(i, _element) {
  161.         var element = $(_element);
  162.         var calendar = new Calendar(element, options, eventSources);
  163.         element.data('fullCalendar', calendar); // TODO: look into memory leak implications
  164.         calendar.render();
  165.     });
  166.    
  167.    
  168.     return this;
  169.    
  170. };
  171.  
  172.  
  173. // function for adding/overriding defaults
  174. function setDefaults(d) {
  175.     $.extend(true, defaults, d);
  176. }
  177.  
  178.  
  179.  
  180.  
  181. function Calendar(element, options, eventSources) {
  182.     var t = this;
  183.    
  184.    
  185.     // exports
  186.     t.options = options;
  187.     t.render = render;
  188.     t.destroy = destroy;
  189.     t.refetchEvents = refetchEvents;
  190.     t.reportEvents = reportEvents;
  191.     t.reportEventChange = reportEventChange;
  192.     t.rerenderEvents = rerenderEvents;
  193.     t.changeView = changeView;
  194.     t.select = select;
  195.     t.unselect = unselect;
  196.     t.prev = prev;
  197.     t.next = next;
  198.     t.prevYear = prevYear;
  199.     t.nextYear = nextYear;
  200.     t.today = today;
  201.     t.gotoDate = gotoDate;
  202.     t.incrementDate = incrementDate;
  203.     t.formatDate = function(format, date) { return formatDate(format, date, options) };
  204.     t.formatDates = function(format, date1, date2) { return formatDates(format, date1, date2, options) };
  205.     t.getDate = getDate;
  206.     t.getView = getView;
  207.     t.option = option;
  208.     t.trigger = trigger;
  209.    
  210.    
  211.     // imports
  212.     EventManager.call(t, options, eventSources);
  213.     var isFetchNeeded = t.isFetchNeeded;
  214.     var fetchEvents = t.fetchEvents;
  215.    
  216.    
  217.     // locals
  218.     var _element = element[0];
  219.     var header;
  220.     var headerElement;
  221.     var content;
  222.     var tm; // for making theme classes
  223.     var currentView;
  224.     var viewInstances = {};
  225.     var elementOuterWidth;
  226.     var suggestedViewHeight;
  227.     var absoluteViewElement;
  228.     var resizeUID = 0;
  229.     var ignoreWindowResize = 0;
  230.     var date = new Date();
  231.     var events = [];
  232.     var _dragElement;
  233.    
  234.    
  235.    
  236.     /* Main Rendering
  237.     -----------------------------------------------------------------------------*/
  238.    
  239.    
  240.     setYMD(date, options.year, options.month, options.date);
  241.    
  242.    
  243.     function render(inc) {
  244.         if (!content) {
  245.             initialRender();
  246.         }else{
  247.             calcSize();
  248.             markSizesDirty();
  249.             markEventsDirty();
  250.             renderView(inc);
  251.         }
  252.     }
  253.    
  254.    
  255.     function initialRender() {
  256.         tm = options.theme ? 'ui' : 'fc';
  257.         element.addClass('fc');
  258.         if (options.isRTL) {
  259.             element.addClass('fc-rtl');
  260.         }
  261.         if (options.theme) {
  262.             element.addClass('ui-widget');
  263.         }
  264.         content = $("<div class='fc-content' style='position:relative'/>")
  265.             .prependTo(element);
  266.         header = new Header(t, options);
  267.         headerElement = header.render();
  268.         if (headerElement) {
  269.             element.prepend(headerElement);
  270.         }
  271.         changeView(options.defaultView);
  272.         $(window).resize(windowResize);
  273.         // needed for IE in a 0x0 iframe, b/c when it is resized, never triggers a windowResize
  274.         if (!bodyVisible()) {
  275.             lateRender();
  276.         }
  277.     }
  278.    
  279.    
  280.     // called when we know the calendar couldn't be rendered when it was initialized,
  281.     // but we think it's ready now
  282.     function lateRender() {
  283.         setTimeout(function() { // IE7 needs this so dimensions are calculated correctly
  284.             if (!currentView.start && bodyVisible()) { // !currentView.start makes sure this never happens more than once
  285.                 renderView();
  286.             }
  287.         },0);
  288.     }
  289.    
  290.    
  291.     function destroy() {
  292.         $(window).unbind('resize', windowResize);
  293.         header.destroy();
  294.         content.remove();
  295.         element.removeClass('fc fc-rtl ui-widget');
  296.     }
  297.    
  298.    
  299.    
  300.     function elementVisible() {
  301.         return _element.offsetWidth !== 0;
  302.     }
  303.    
  304.    
  305.     function bodyVisible() {
  306.         return $('body')[0].offsetWidth !== 0;
  307.     }
  308.    
  309.    
  310.    
  311.     /* View Rendering
  312.     -----------------------------------------------------------------------------*/
  313.    
  314.     // TODO: improve view switching (still weird transition in IE, and FF has whiteout problem)
  315.    
  316.     function changeView(newViewName) {
  317.         if (!currentView || newViewName != currentView.name) {
  318.             ignoreWindowResize++; // because setMinHeight might change the height before render (and subsequently setSize) is reached
  319.  
  320.             unselect();
  321.            
  322.             var oldView = currentView;
  323.             var newViewElement;
  324.                
  325.             if (oldView) {
  326.                 (oldView.beforeHide || noop)(); // called before changing min-height. if called after, scroll state is reset (in Opera)
  327.                 setMinHeight(content, content.height());
  328.                 oldView.element.hide();
  329.             }else{
  330.                 setMinHeight(content, 1); // needs to be 1 (not 0) for IE7, or else view dimensions miscalculated
  331.             }
  332.             content.css('overflow', 'hidden');
  333.            
  334.             currentView = viewInstances[newViewName];
  335.             if (currentView) {
  336.                 currentView.element.show();
  337.             }else{
  338.                 currentView = viewInstances[newViewName] = new fcViews[newViewName](
  339.                     newViewElement = absoluteViewElement =
  340.                         $("<div class='fc-view fc-view-" + newViewName + "' style='position:absolute'/>")
  341.                             .appendTo(content),
  342.                     t // the calendar object
  343.                 );
  344.             }
  345.            
  346.             if (oldView) {
  347.                 header.deactivateButton(oldView.name);
  348.             }
  349.             header.activateButton(newViewName);
  350.            
  351.             renderView(); // after height has been set, will make absoluteViewElement's position=relative, then set to null
  352.            
  353.             content.css('overflow', '');
  354.             if (oldView) {
  355.                 setMinHeight(content, 1);
  356.             }
  357.            
  358.             if (!newViewElement) {
  359.                 (currentView.afterShow || noop)(); // called after setting min-height/overflow, so in final scroll state (for Opera)
  360.             }
  361.            
  362.             ignoreWindowResize--;
  363.         }
  364.     }
  365.    
  366.    
  367.    
  368.     function renderView(inc) {
  369.         if (elementVisible()) {
  370.             ignoreWindowResize++; // because renderEvents might temporarily change the height before setSize is reached
  371.  
  372.             unselect();
  373.            
  374.             if (suggestedViewHeight === undefined) {
  375.                 calcSize();
  376.             }
  377.            
  378.             var forceEventRender = false;
  379.             if (!currentView.start || inc || date < currentView.start || date >= currentView.end) {
  380.                 // view must render an entire new date range (and refetch/render events)
  381.                 currentView.render(date, inc || 0); // responsible for clearing events
  382.                 setSize(true);
  383.                 forceEventRender = true;
  384.             }
  385.             else if (currentView.sizeDirty) {
  386.                 // view must resize (and rerender events)
  387.                 currentView.clearEvents();
  388.                 setSize();
  389.                 forceEventRender = true;
  390.             }
  391.             else if (currentView.eventsDirty) {
  392.                 currentView.clearEvents();
  393.                 forceEventRender = true;
  394.             }
  395.             currentView.sizeDirty = false;
  396.             currentView.eventsDirty = false;
  397.             updateEvents(forceEventRender);
  398.            
  399.             elementOuterWidth = element.outerWidth();
  400.            
  401.             header.updateTitle(currentView.title);
  402.             var today = new Date();
  403.             if (today >= currentView.start && today < currentView.end) {
  404.                 header.disableButton('today');
  405.             }else{
  406.                 header.enableButton('today');
  407.             }
  408.            
  409.             ignoreWindowResize--;
  410.             currentView.trigger('viewDisplay', _element);
  411.         }
  412.     }
  413.    
  414.    
  415.    
  416.     /* Resizing
  417.     -----------------------------------------------------------------------------*/
  418.    
  419.    
  420.     function updateSize() {
  421.         markSizesDirty();
  422.         if (elementVisible()) {
  423.             calcSize();
  424.             setSize();
  425.             unselect();
  426.             currentView.clearEvents();
  427.             currentView.renderEvents(events);
  428.             currentView.sizeDirty = false;
  429.         }
  430.     }
  431.    
  432.    
  433.     function markSizesDirty() {
  434.         $.each(viewInstances, function(i, inst) {
  435.             inst.sizeDirty = true;
  436.         });
  437.     }
  438.    
  439.    
  440.     function calcSize() {
  441.         if (options.contentHeight) {
  442.             suggestedViewHeight = options.contentHeight;
  443.         }
  444.         else if (options.height) {
  445.             suggestedViewHeight = options.height - (headerElement ? headerElement.height() : 0) - vsides(content);
  446.         }
  447.         else {
  448.             suggestedViewHeight = Math.round(content.width() / Math.max(options.aspectRatio, .5));
  449.         }
  450.     }
  451.    
  452.    
  453.     function setSize(dateChanged) { // todo: dateChanged?
  454.         ignoreWindowResize++;
  455.         currentView.setHeight(suggestedViewHeight, dateChanged);
  456.         if (absoluteViewElement) {
  457.             absoluteViewElement.css('position', 'relative');
  458.             absoluteViewElement = null;
  459.         }
  460.         currentView.setWidth(content.width(), dateChanged);
  461.         ignoreWindowResize--;
  462.     }
  463.    
  464.    
  465.     function windowResize() {
  466.         if (!ignoreWindowResize) {
  467.             if (currentView.start) { // view has already been rendered
  468.                 var uid = ++resizeUID;
  469.                 setTimeout(function() { // add a delay
  470.                     if (uid == resizeUID && !ignoreWindowResize && elementVisible()) {
  471.                         if (elementOuterWidth != (elementOuterWidth = element.outerWidth())) {
  472.                             ignoreWindowResize++; // in case the windowResize callback changes the height
  473.                             updateSize();
  474.                             currentView.trigger('windowResize', _element);
  475.                             ignoreWindowResize--;
  476.                         }
  477.                     }
  478.                 }, 200);
  479.             }else{
  480.                 // calendar must have been initialized in a 0x0 iframe that has just been resized
  481.                 lateRender();
  482.             }
  483.         }
  484.     }
  485.    
  486.    
  487.    
  488.     /* Event Fetching/Rendering
  489.     -----------------------------------------------------------------------------*/
  490.    
  491.    
  492.     // fetches events if necessary, rerenders events if necessary (or if forced)
  493.     function updateEvents(forceRender) {
  494.         if (!options.lazyFetching || isFetchNeeded(currentView.visStart, currentView.visEnd)) {
  495.             refetchEvents();
  496.         }
  497.         else if (forceRender) {
  498.             rerenderEvents();
  499.         }
  500.     }
  501.    
  502.    
  503.     function refetchEvents() {
  504.         fetchEvents(currentView.visStart, currentView.visEnd); // will call reportEvents
  505.     }
  506.    
  507.    
  508.     // called when event data arrives
  509.     function reportEvents(_events) {
  510.         events = _events;
  511.         rerenderEvents();
  512.     }
  513.    
  514.    
  515.     // called when a single event's data has been changed
  516.     function reportEventChange(eventID) {
  517.         rerenderEvents(eventID);
  518.     }
  519.    
  520.    
  521.     // attempts to rerenderEvents
  522.     function rerenderEvents(modifiedEventID) {
  523.         markEventsDirty();
  524.         if (elementVisible()) {
  525.             currentView.clearEvents();
  526.             currentView.renderEvents(events, modifiedEventID);
  527.             currentView.eventsDirty = false;
  528.         }
  529.     }
  530.    
  531.    
  532.     function markEventsDirty() {
  533.         $.each(viewInstances, function(i, inst) {
  534.             inst.eventsDirty = true;
  535.         });
  536.     }
  537.    
  538.  
  539.  
  540.     /* Selection
  541.     -----------------------------------------------------------------------------*/
  542.    
  543.  
  544.     function select(start, end, allDay) {
  545.         currentView.select(start, end, allDay===undefined ? true : allDay);
  546.     }
  547.    
  548.  
  549.     function unselect() { // safe to be called before renderView
  550.         if (currentView) {
  551.             currentView.unselect();
  552.         }
  553.     }
  554.    
  555.    
  556.    
  557.     /* Date
  558.     -----------------------------------------------------------------------------*/
  559.    
  560.    
  561.     function prev() {
  562.         renderView(-1);
  563.     }
  564.    
  565.    
  566.     function next() {
  567.         renderView(1);
  568.     }
  569.    
  570.    
  571.     function prevYear() {
  572.         addYears(date, -1);
  573.         renderView();
  574.     }
  575.    
  576.    
  577.     function nextYear() {
  578.         addYears(date, 1);
  579.         renderView();
  580.     }
  581.    
  582.    
  583.     function today() {
  584.         date = new Date();
  585.         renderView();
  586.     }
  587.    
  588.    
  589.     function gotoDate(year, month, dateOfMonth) {
  590.         if (year instanceof Date) {
  591.             date = cloneDate(year); // provided 1 argument, a Date
  592.         }else{
  593.             setYMD(date, year, month, dateOfMonth);
  594.         }
  595.         renderView();
  596.     }
  597.    
  598.    
  599.     function incrementDate(years, months, days) {
  600.         if (years !== undefined) {
  601.             addYears(date, years);
  602.         }
  603.         if (months !== undefined) {
  604.             addMonths(date, months);
  605.         }
  606.         if (days !== undefined) {
  607.             addDays(date, days);
  608.         }
  609.         renderView();
  610.     }
  611.    
  612.    
  613.     function getDate() {
  614.         return cloneDate(date);
  615.     }
  616.    
  617.    
  618.    
  619.     /* Misc
  620.     -----------------------------------------------------------------------------*/
  621.    
  622.    
  623.     function getView() {
  624.         return currentView;
  625.     }
  626.    
  627.    
  628.     function option(name, value) {
  629.         if (value === undefined) {
  630.             return options[name];
  631.         }
  632.         if (name == 'height' || name == 'contentHeight' || name == 'aspectRatio') {
  633.             options[name] = value;
  634.             updateSize();
  635.         }
  636.     }
  637.    
  638.    
  639.     function trigger(name, thisObj) {
  640.         if (options[name]) {
  641.             return options[name].apply(
  642.                 thisObj || _element,
  643.                 Array.prototype.slice.call(arguments, 2)
  644.             );
  645.         }
  646.     }
  647.    
  648.    
  649.    
  650.     /* External Dragging
  651.     ------------------------------------------------------------------------*/
  652.    
  653.     if (options.droppable) {
  654.         $(document)
  655.             .bind('dragstart', function(ev, ui) {
  656.                 var _e = ev.target;
  657.                 var e = $(_e);
  658.                 if (!e.parents('.fc').length) { // not already inside a calendar
  659.                     var accept = options.dropAccept;
  660.                     if ($.isFunction(accept) ? accept.call(_e, e) : e.is(accept)) {
  661.                         _dragElement = _e;
  662.                         currentView.dragStart(_dragElement, ev, ui);
  663.                     }
  664.                 }
  665.             })
  666.             .bind('dragstop', function(ev, ui) {
  667.                 if (_dragElement) {
  668.                     currentView.dragStop(_dragElement, ev, ui);
  669.                     _dragElement = null;
  670.                 }
  671.             });
  672.     }
  673.    
  674.  
  675. }
  676.  
  677. function Header(calendar, options) {
  678.     var t = this;
  679.    
  680.    
  681.     // exports
  682.     t.render = render;
  683.     t.destroy = destroy;
  684.     t.updateTitle = updateTitle;
  685.     t.activateButton = activateButton;
  686.     t.deactivateButton = deactivateButton;
  687.     t.disableButton = disableButton;
  688.     t.enableButton = enableButton;
  689.    
  690.    
  691.     // locals
  692.     var element = $([]);
  693.     var tm;
  694.    
  695.  
  696.  
  697.     function render() {
  698.         tm = options.theme ? 'ui' : 'fc';
  699.         var sections = options.header;
  700.         if (sections) {
  701.             element = $("<table class='fc-header' style='width:100%'/>")
  702.                 .append(
  703.                     $("<tr/>")
  704.                         .append(renderSection('left'))
  705.                         .append(renderSection('center'))
  706.                         .append(renderSection('right'))
  707.                 );
  708.             return element;
  709.         }
  710.     }
  711.    
  712.    
  713.     function destroy() {
  714.         element.remove();
  715.     }
  716.    
  717.    
  718.     function renderSection(position) {
  719.         var e = $("<td class='fc-header-" + position + "'/>");
  720.         var buttonStr = options.header[position];
  721.         if (buttonStr) {
  722.             $.each(buttonStr.split(' '), function(i) {
  723.                 if (i > 0) {
  724.                     e.append("<span class='fc-header-space'/>");
  725.                 }
  726.                 var prevButton;
  727.                 $.each(this.split(','), function(j, buttonName) {
  728.                     if (buttonName == 'title') {
  729.                         e.append("<span class='fc-header-title'><h2>&nbsp;</h2></span>");
  730.                         if (prevButton) {
  731.                             prevButton.addClass(tm + '-corner-right');
  732.                         }
  733.                         prevButton = null;
  734.                     }else{
  735.                         var buttonClick;
  736.                         if (calendar[buttonName]) {
  737.                             buttonClick = calendar[buttonName]; // calendar method
  738.                         }
  739.                         else if (fcViews[buttonName]) {
  740.                             buttonClick = function() {
  741.                                 button.removeClass(tm + '-state-hover'); // forget why
  742.                                 calendar.changeView(buttonName);
  743.                             };
  744.                         }
  745.                         if (buttonClick) {
  746.                             var icon = options.theme ? smartProperty(options.buttonIcons, buttonName) : null; // why are we using smartProperty here?
  747.                             var text = smartProperty(options.buttonText, buttonName); // why are we using smartProperty here?
  748.                             var button = $(
  749.                                 "<span class='fc-button fc-button-" + buttonName + " " + tm + "-state-default'>" +
  750.                                     "<span class='fc-button-inner'>" +
  751.                                         "<span class='fc-button-content'>" +
  752.                                             (icon ?
  753.                                                 "<span class='fc-icon-wrap'>" +
  754.                                                     "<span class='ui-icon ui-icon-" + icon + "'/>" +
  755.                                                 "</span>" :
  756.                                                 text
  757.                                                 ) +
  758.                                         "</span>" +
  759.                                         "<span class='fc-button-effect'><span></span></span>" +
  760.                                     "</span>" +
  761.                                 "</span>"
  762.                             );
  763.                             if (button) {
  764.                                 button
  765.                                     .click(function() {
  766.                                         if (!button.hasClass(tm + '-state-disabled')) {
  767.                                             buttonClick();
  768.                                         }
  769.                                     })
  770.                                     .mousedown(function() {
  771.                                         button
  772.                                             .not('.' + tm + '-state-active')
  773.                                             .not('.' + tm + '-state-disabled')
  774.                                             .addClass(tm + '-state-down');
  775.                                     })
  776.                                     .mouseup(function() {
  777.                                         button.removeClass(tm + '-state-down');
  778.                                     })
  779.                                     .hover(
  780.                                         function() {
  781.                                             button
  782.                                                 .not('.' + tm + '-state-active')
  783.                                                 .not('.' + tm + '-state-disabled')
  784.                                                 .addClass(tm + '-state-hover');
  785.                                         },
  786.                                         function() {
  787.                                             button
  788.                                                 .removeClass(tm + '-state-hover')
  789.                                                 .removeClass(tm + '-state-down');
  790.                                         }
  791.                                     )
  792.                                     .appendTo(e);
  793.                                 if (!prevButton) {
  794.                                     button.addClass(tm + '-corner-left');
  795.                                 }
  796.                                 prevButton = button;
  797.                             }
  798.                         }
  799.                     }
  800.                 });
  801.                 if (prevButton) {
  802.                     prevButton.addClass(tm + '-corner-right');
  803.                 }
  804.             });
  805.         }
  806.         return e;
  807.     }
  808.    
  809.    
  810.     function updateTitle(html) {
  811.         element.find('h2')
  812.             .html(html);
  813.     }
  814.    
  815.    
  816.     function activateButton(buttonName) {
  817.         element.find('span.fc-button-' + buttonName)
  818.             .addClass(tm + '-state-active');
  819.     }
  820.    
  821.    
  822.     function deactivateButton(buttonName) {
  823.         element.find('span.fc-button-' + buttonName)
  824.             .removeClass(tm + '-state-active');
  825.     }
  826.    
  827.    
  828.     function disableButton(buttonName) {
  829.         element.find('span.fc-button-' + buttonName)
  830.             .addClass(tm + '-state-disabled');
  831.     }
  832.    
  833.    
  834.     function enableButton(buttonName) {
  835.         element.find('span.fc-button-' + buttonName)
  836.             .removeClass(tm + '-state-disabled');
  837.     }
  838.  
  839.  
  840. }
  841.  
  842. fc.sourceNormalizers = [];
  843. fc.sourceFetchers = [];
  844.  
  845. var ajaxDefaults = {
  846.     dataType: 'json',
  847.     cache: false
  848. };
  849.  
  850. var eventGUID = 1;
  851.  
  852.  
  853. function EventManager(options, _sources) {
  854.     var t = this;
  855.    
  856.    
  857.     // exports
  858.     t.isFetchNeeded = isFetchNeeded;
  859.     t.fetchEvents = fetchEvents;
  860.     t.addEventSource = addEventSource;
  861.     t.removeEventSource = removeEventSource;
  862.     t.updateEvent = updateEvent;
  863.     t.renderEvent = renderEvent;
  864.     t.removeEvents = removeEvents;
  865.     t.clientEvents = clientEvents;
  866.     t.normalizeEvent = normalizeEvent;
  867.    
  868.    
  869.     // imports
  870.     var trigger = t.trigger;
  871.     var getView = t.getView;
  872.     var reportEvents = t.reportEvents;
  873.    
  874.    
  875.     // locals
  876.     var stickySource = { events: [] };
  877.     var sources = [ stickySource ];
  878.     var rangeStart, rangeEnd;
  879.     var currentFetchID = 0;
  880.     var pendingSourceCnt = 0;
  881.     var loadingLevel = 0;
  882.     var cache = [];
  883.    
  884.    
  885.     for (var i=0; i<_sources.length; i++) {
  886.         _addEventSource(_sources[i]);
  887.     }
  888.    
  889.    
  890.    
  891.     /* Fetching
  892.     -----------------------------------------------------------------------------*/
  893.    
  894.    
  895.     function isFetchNeeded(start, end) {
  896.         return !rangeStart || start < rangeStart || end > rangeEnd;
  897.     }
  898.    
  899.    
  900.     function fetchEvents(start, end) {
  901.         rangeStart = start;
  902.         rangeEnd = end;
  903.         cache = [];
  904.         var fetchID = ++currentFetchID;
  905.         var len = sources.length;
  906.         pendingSourceCnt = len;
  907.         for (var i=0; i<len; i++) {
  908.             fetchEventSource(sources[i], fetchID);
  909.         }
  910.     }
  911.    
  912.    
  913.     function fetchEventSource(source, fetchID) {
  914.         _fetchEventSource(source, function(events) {
  915.             if (fetchID == currentFetchID) {
  916.                 if (events) {
  917.                     for (var i=0; i<events.length; i++) {
  918.                         events[i].source = source;
  919.                         normalizeEvent(events[i]);
  920.                     }
  921.                     cache = cache.concat(events);
  922.                 }
  923.                 pendingSourceCnt--;
  924.                 if (!pendingSourceCnt) {
  925.                     reportEvents(cache);
  926.                 }
  927.             }
  928.         });
  929.     }
  930.    
  931.    
  932.     function _fetchEventSource(source, callback) {
  933.         var i;
  934.         var fetchers = fc.sourceFetchers;
  935.         var res;
  936.         for (i=0; i<fetchers.length; i++) {
  937.             res = fetchers[i](source, rangeStart, rangeEnd, callback);
  938.             if (res === true) {
  939.                 // the fetcher is in charge. made its own async request
  940.                 return;
  941.             }
  942.             else if (typeof res == 'object') {
  943.                 // the fetcher returned a new source. process it
  944.                 _fetchEventSource(res, callback);
  945.                 return;
  946.             }
  947.         }
  948.         var events = source.events;
  949.         if (events) {
  950.             if ($.isFunction(events)) {
  951.                 pushLoading();
  952.                 events(cloneDate(rangeStart), cloneDate(rangeEnd), function(events) {
  953.                     callback(events);
  954.                     popLoading();
  955.                 });
  956.             }
  957.             else if ($.isArray(events)) {
  958.                 callback(events);
  959.             }
  960.             else {
  961.                 callback();
  962.             }
  963.         }else{
  964.             var url = source.url;
  965.             if (url) {
  966.                 var success = source.success;
  967.                 var error = source.error;
  968.                 var complete = source.complete;
  969.                 var data = $.extend({}, source.data || {});
  970.                 var startParam = firstDefined(source.startParam, options.startParam);
  971.                 var endParam = firstDefined(source.endParam, options.endParam);
  972.                 if (startParam) {
  973.                     data[startParam] = Math.round(+rangeStart / 1000);
  974.                 }
  975.                 if (endParam) {
  976.                     data[endParam] = Math.round(+rangeEnd / 1000);
  977.                 }
  978.                 pushLoading();
  979.                 $.ajax($.extend({}, ajaxDefaults, source, {
  980.                     data: data,
  981.                     success: function(events) {
  982.                         events = events || [];
  983.                         var res = applyAll(success, this, arguments);
  984.                         if ($.isArray(res)) {
  985.                             events = res;
  986.                         }
  987.                         callback(events);
  988.                     },
  989.                     error: function() {
  990.                         applyAll(error, this, arguments);
  991.                         callback();
  992.                     },
  993.                     complete: function() {
  994.                         applyAll(complete, this, arguments);
  995.                         popLoading();
  996.                     }
  997.                 }));
  998.             }else{
  999.                 callback();
  1000.             }
  1001.         }
  1002.     }
  1003.    
  1004.    
  1005.    
  1006.     /* Sources
  1007.     -----------------------------------------------------------------------------*/
  1008.    
  1009.  
  1010.     function addEventSource(source) {
  1011.         source = _addEventSource(source);
  1012.         if (source) {
  1013.             pendingSourceCnt++;
  1014.             fetchEventSource(source, currentFetchID); // will eventually call reportEvents
  1015.         }
  1016.     }
  1017.    
  1018.    
  1019.     function _addEventSource(source) {
  1020.         if ($.isFunction(source) || $.isArray(source)) {
  1021.             source = { events: source };
  1022.         }
  1023.         else if (typeof source == 'string') {
  1024.             source = { url: source };
  1025.         }
  1026.         if (typeof source == 'object') {
  1027.             normalizeSource(source);
  1028.             sources.push(source);
  1029.             return source;
  1030.         }
  1031.     }
  1032.    
  1033.  
  1034.     function removeEventSource(source) {
  1035.         sources = $.grep(sources, function(src) {
  1036.             return !isSourcesEqual(src, source);
  1037.         });
  1038.         // remove all client events from that source
  1039.         cache = $.grep(cache, function(e) {
  1040.             return !isSourcesEqual(e.source, source);
  1041.         });
  1042.         reportEvents(cache);
  1043.     }
  1044.    
  1045.    
  1046.    
  1047.     /* Manipulation
  1048.     -----------------------------------------------------------------------------*/
  1049.    
  1050.    
  1051.     function updateEvent(event) { // update an existing event
  1052.         var i, len = cache.length, e,
  1053.             defaultEventEnd = getView().defaultEventEnd, // getView???
  1054.             startDelta = event.start - event._start,
  1055.             endDelta = event.end ?
  1056.                 (event.end - (event._end || defaultEventEnd(event))) // event._end would be null if event.end
  1057.                 : 0;                                                      // was null and event was just resized
  1058.         for (i=0; i<len; i++) {
  1059.             e = cache[i];
  1060.             if (e._id == event._id && e != event) {
  1061.                 e.start = new Date(+e.start + startDelta);
  1062.                 if (event.end) {
  1063.                     if (e.end) {
  1064.                         e.end = new Date(+e.end + endDelta);
  1065.                     }else{
  1066.                         e.end = new Date(+defaultEventEnd(e) + endDelta);
  1067.                     }
  1068.                 }else{
  1069.                     e.end = null;
  1070.                 }
  1071.                 e.title = event.title;
  1072.                 e.url = event.url;
  1073.                 e.allDay = event.allDay;
  1074.                 e.className = event.className;
  1075.                 e.editable = event.editable;
  1076.                 e.color = event.color;
  1077.                 e.backgroudColor = event.backgroudColor;
  1078.                 e.borderColor = event.borderColor;
  1079.                 e.textColor = event.textColor;
  1080.                 normalizeEvent(e);
  1081.             }
  1082.         }
  1083.         normalizeEvent(event);
  1084.         reportEvents(cache);
  1085.     }
  1086.    
  1087.    
  1088.     function renderEvent(event, stick) {
  1089.         normalizeEvent(event);
  1090.         if (!event.source) {
  1091.             if (stick) {
  1092.                 stickySource.events.push(event);
  1093.                 event.source = stickySource;
  1094.             }
  1095.             cache.push(event);
  1096.         }
  1097.         reportEvents(cache);
  1098.     }
  1099.    
  1100.    
  1101.     function removeEvents(filter) {
  1102.         if (!filter) { // remove all
  1103.             cache = [];
  1104.             // clear all array sources
  1105.             for (var i=0; i<sources.length; i++) {
  1106.                 if ($.isArray(sources[i].events)) {
  1107.                     sources[i].events = [];
  1108.                 }
  1109.             }
  1110.         }else{
  1111.             if (!$.isFunction(filter)) { // an event ID
  1112.                 var id = filter + '';
  1113.                 filter = function(e) {
  1114.                     return e._id == id;
  1115.                 };
  1116.             }
  1117.             cache = $.grep(cache, filter, true);
  1118.             // remove events from array sources
  1119.             for (var i=0; i<sources.length; i++) {
  1120.                 if ($.isArray(sources[i].events)) {
  1121.                     sources[i].events = $.grep(sources[i].events, filter, true);
  1122.                 }
  1123.             }
  1124.         }
  1125.         reportEvents(cache);
  1126.     }
  1127.    
  1128.    
  1129.     function clientEvents(filter) {
  1130.         if ($.isFunction(filter)) {
  1131.             return $.grep(cache, filter);
  1132.         }
  1133.         else if (filter) { // an event ID
  1134.             filter += '';
  1135.             return $.grep(cache, function(e) {
  1136.                 return e._id == filter;
  1137.             });
  1138.         }
  1139.         return cache; // else, return all
  1140.     }
  1141.    
  1142.    
  1143.    
  1144.     /* Loading State
  1145.     -----------------------------------------------------------------------------*/
  1146.    
  1147.    
  1148.     function pushLoading() {
  1149.         if (!loadingLevel++) {
  1150.             trigger('loading', null, true);
  1151.         }
  1152.     }
  1153.    
  1154.    
  1155.     function popLoading() {
  1156.         if (!--loadingLevel) {
  1157.             trigger('loading', null, false);
  1158.         }
  1159.     }
  1160.    
  1161.    
  1162.    
  1163.     /* Event Normalization
  1164.     -----------------------------------------------------------------------------*/
  1165.    
  1166.    
  1167.     function normalizeEvent(event) {
  1168.         var source = event.source || {};
  1169.         var ignoreTimezone = firstDefined(source.ignoreTimezone, options.ignoreTimezone);
  1170.         event._id = event._id || (event.id === undefined ? '_fc' + eventGUID++ : event.id + '');
  1171.         if (event.date) {
  1172.             if (!event.start) {
  1173.                 event.start = event.date;
  1174.             }
  1175.             delete event.date;
  1176.         }
  1177.         event._start = cloneDate(event.start = parseDate(event.start, ignoreTimezone));
  1178.         event.end = parseDate(event.end, ignoreTimezone);
  1179.         if (event.end && event.end <= event.start) {
  1180.             event.end = null;
  1181.         }
  1182.         event._end = event.end ? cloneDate(event.end) : null;
  1183.         if (event.allDay === undefined) {
  1184.             event.allDay = firstDefined(source.allDayDefault, options.allDayDefault);
  1185.         }
  1186.         if (event.className) {
  1187.             if (typeof event.className == 'string') {
  1188.                 event.className = event.className.split(/\s+/);
  1189.             }
  1190.         }else{
  1191.             event.className = [];
  1192.         }
  1193.         // TODO: if there is no start date, return false to indicate an invalid event
  1194.     }
  1195.    
  1196.    
  1197.    
  1198.     /* Utils
  1199.     ------------------------------------------------------------------------------*/
  1200.    
  1201.    
  1202.     function normalizeSource(source) {
  1203.         if (source.className) {
  1204.             // TODO: repeat code, same code for event classNames
  1205.             if (typeof source.className == 'string') {
  1206.                 source.className = source.className.split(/\s+/);
  1207.             }
  1208.         }else{
  1209.             source.className = [];
  1210.         }
  1211.         var normalizers = fc.sourceNormalizers;
  1212.         for (var i=0; i<normalizers.length; i++) {
  1213.             normalizers[i](source);
  1214.         }
  1215.     }
  1216.    
  1217.    
  1218.     function isSourcesEqual(source1, source2) {
  1219.         return source1 && source2 && getSourcePrimitive(source1) == getSourcePrimitive(source2);
  1220.     }
  1221.    
  1222.    
  1223.     function getSourcePrimitive(source) {
  1224.         return ((typeof source == 'object') ? (source.events || source.url) : '') || source;
  1225.     }
  1226.  
  1227.  
  1228. }
  1229.  
  1230.  
  1231. fc.addDays = addDays;
  1232. fc.cloneDate = cloneDate;
  1233. fc.parseDate = parseDate;
  1234. fc.parseISO8601 = parseISO8601;
  1235. fc.parseTime = parseTime;
  1236. fc.formatDate = formatDate;
  1237. fc.formatDates = formatDates;
  1238.  
  1239.  
  1240.  
  1241. /* Date Math
  1242. -----------------------------------------------------------------------------*/
  1243.  
  1244. var dayIDs = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'],
  1245.     DAY_MS = 86400000,
  1246.     HOUR_MS = 3600000,
  1247.     MINUTE_MS = 60000;
  1248.    
  1249.  
  1250. function addYears(d, n, keepTime) {
  1251.     d.setFullYear(d.getFullYear() + n);
  1252.     if (!keepTime) {
  1253.         clearTime(d);
  1254.     }
  1255.     return d;
  1256. }
  1257.  
  1258.  
  1259. function addMonths(d, n, keepTime) { // prevents day overflow/underflow
  1260.     if (+d) { // prevent infinite looping on invalid dates
  1261.         var m = d.getMonth() + n,
  1262.             check = cloneDate(d);
  1263.         check.setDate(1);
  1264.         check.setMonth(m);
  1265.         d.setMonth(m);
  1266.         if (!keepTime) {
  1267.             clearTime(d);
  1268.         }
  1269.         while (d.getMonth() != check.getMonth()) {
  1270.             d.setDate(d.getDate() + (d < check ? 1 : -1));
  1271.         }
  1272.     }
  1273.     return d;
  1274. }
  1275.  
  1276.  
  1277. function addDays(d, n, keepTime) { // deals with daylight savings
  1278.     if (+d) {
  1279.         var dd = d.getDate() + n,
  1280.             check = cloneDate(d);
  1281.         check.setHours(9); // set to middle of day
  1282.         check.setDate(dd);
  1283.         d.setDate(dd);
  1284.         if (!keepTime) {
  1285.             clearTime(d);
  1286.         }
  1287.         fixDate(d, check);
  1288.     }
  1289.     return d;
  1290. }
  1291.  
  1292.  
  1293. function fixDate(d, check) { // force d to be on check's YMD, for daylight savings purposes
  1294.     if (+d) { // prevent infinite looping on invalid dates
  1295.         while (d.getDate() != check.getDate()) {
  1296.             d.setTime(+d + (d < check ? 1 : -1) * HOUR_MS);
  1297.         }
  1298.     }
  1299. }
  1300.  
  1301.  
  1302. function addMinutes(d, n) {
  1303.     d.setMinutes(d.getMinutes() + n);
  1304.     return d;
  1305. }
  1306.  
  1307.  
  1308. function clearTime(d) {
  1309.     d.setHours(0);
  1310.     d.setMinutes(0);
  1311.     d.setSeconds(0);
  1312.     d.setMilliseconds(0);
  1313.     return d;
  1314. }
  1315.  
  1316.  
  1317. function cloneDate(d, dontKeepTime) {
  1318.     if (dontKeepTime) {
  1319.         return clearTime(new Date(+d));
  1320.     }
  1321.     return new Date(+d);
  1322. }
  1323.  
  1324.  
  1325. function zeroDate() { // returns a Date with time 00:00:00 and dateOfMonth=1
  1326.     var i=0, d;
  1327.     do {
  1328.         d = new Date(1970, i++, 1);
  1329.     } while (d.getHours()); // != 0
  1330.     return d;
  1331. }
  1332.  
  1333.  
  1334. function skipWeekend(date, inc, excl) {
  1335.     inc = inc || 1;
  1336.     while (!date.getDay() || (excl && date.getDay()==1 || !excl && date.getDay()==6)) {
  1337.         addDays(date, inc);
  1338.     }
  1339.     return date;
  1340. }
  1341.  
  1342.  
  1343. function dayDiff(d1, d2) { // d1 - d2
  1344.     return Math.round((cloneDate(d1, true) - cloneDate(d2, true)) / DAY_MS);
  1345. }
  1346.  
  1347.  
  1348. function setYMD(date, y, m, d) {
  1349.     if (y !== undefined && y != date.getFullYear()) {
  1350.         date.setDate(1);
  1351.         date.setMonth(0);
  1352.         date.setFullYear(y);
  1353.     }
  1354.     if (m !== undefined && m != date.getMonth()) {
  1355.         date.setDate(1);
  1356.         date.setMonth(m);
  1357.     }
  1358.     if (d !== undefined) {
  1359.         date.setDate(d);
  1360.     }
  1361. }
  1362.  
  1363.  
  1364.  
  1365. /* Date Parsing
  1366. -----------------------------------------------------------------------------*/
  1367.  
  1368.  
  1369. function parseDate(s, ignoreTimezone) { // ignoreTimezone defaults to true
  1370.     if (typeof s == 'object') { // already a Date object
  1371.         return s;
  1372.     }
  1373.     if (typeof s == 'number') { // a UNIX timestamp
  1374.         return new Date(s * 1000);
  1375.     }
  1376.     if (typeof s == 'string') {
  1377.         if (s.match(/^\d+(\.\d+)?$/)) { // a UNIX timestamp
  1378.             return new Date(parseFloat(s) * 1000);
  1379.         }
  1380.         if (ignoreTimezone === undefined) {
  1381.             ignoreTimezone = true;
  1382.         }
  1383.         return parseISO8601(s, ignoreTimezone) || (s ? new Date(s) : null);
  1384.     }
  1385.     // TODO: never return invalid dates (like from new Date(<string>)), return null instead
  1386.     return null;
  1387. }
  1388.  
  1389.  
  1390. function parseISO8601(s, ignoreTimezone) { // ignoreTimezone defaults to false
  1391.     // derived from http://delete.me.uk/2005/03/iso8601.html
  1392.     // TODO: for a know glitch/feature, read tests/issue_206_parseDate_dst.html
  1393.     var m = s.match(/^([0-9]{4})(-([0-9]{2})(-([0-9]{2})([T ]([0-9]{2}):([0-9]{2})(:([0-9]{2})(\.([0-9]+))?)?(Z|(([-+])([0-9]{2})(:?([0-9]{2}))?))?)?)?)?$/);
  1394.     if (!m) {
  1395.         return null;
  1396.     }
  1397.     var date = new Date(m[1], 0, 1);
  1398.     if (ignoreTimezone || !m[14]) {
  1399.         var check = new Date(m[1], 0, 1, 9, 0);
  1400.         if (m[3]) {
  1401.             date.setMonth(m[3] - 1);
  1402.             check.setMonth(m[3] - 1);
  1403.         }
  1404.         if (m[5]) {
  1405.             date.setDate(m[5]);
  1406.             check.setDate(m[5]);
  1407.         }
  1408.         fixDate(date, check);
  1409.         if (m[7]) {
  1410.             date.setHours(m[7]);
  1411.         }
  1412.         if (m[8]) {
  1413.             date.setMinutes(m[8]);
  1414.         }
  1415.         if (m[10]) {
  1416.             date.setSeconds(m[10]);
  1417.         }
  1418.         if (m[12]) {
  1419.             date.setMilliseconds(Number("0." + m[12]) * 1000);
  1420.         }
  1421.         fixDate(date, check);
  1422.     }else{
  1423.         date.setUTCFullYear(
  1424.             m[1],
  1425.             m[3] ? m[3] - 1 : 0,
  1426.             m[5] || 1
  1427.         );
  1428.         date.setUTCHours(
  1429.             m[7] || 0,
  1430.             m[8] || 0,
  1431.             m[10] || 0,
  1432.             m[12] ? Number("0." + m[12]) * 1000 : 0
  1433.         );
  1434.         var offset = Number(m[16]) * 60 + (m[18] ? Number(m[18]) : 0);
  1435.         offset *= m[15] == '-' ? 1 : -1;
  1436.         date = new Date(+date + (offset * 60 * 1000));
  1437.     }
  1438.     return date;
  1439. }
  1440.  
  1441.  
  1442. function parseTime(s) { // returns minutes since start of day
  1443.     if (typeof s == 'number') { // an hour
  1444.         return s * 60;
  1445.     }
  1446.     if (typeof s == 'object') { // a Date object
  1447.         return s.getHours() * 60 + s.getMinutes();
  1448.     }
  1449.     var m = s.match(/(\d+)(?::(\d+))?\s*(\w+)?/);
  1450.     if (m) {
  1451.         var h = parseInt(m[1], 10);
  1452.         if (m[3]) {
  1453.             h %= 12;
  1454.             if (m[3].toLowerCase().charAt(0) == 'p') {
  1455.                 h += 12;
  1456.             }
  1457.         }
  1458.         return h * 60 + (m[2] ? parseInt(m[2], 10) : 0);
  1459.     }
  1460. }
  1461.  
  1462.  
  1463.  
  1464. /* Date Formatting
  1465. -----------------------------------------------------------------------------*/
  1466. // TODO: use same function formatDate(date, [date2], format, [options])
  1467.  
  1468.  
  1469. function formatDate(date, format, options) {
  1470.     return formatDates(date, null, format, options);
  1471. }
  1472.  
  1473.  
  1474. function formatDates(date1, date2, format, options) {
  1475.     options = options || defaults;
  1476.     var date = date1,
  1477.         otherDate = date2,
  1478.         i, len = format.length, c,
  1479.         i2, formatter,
  1480.         res = '';
  1481.     for (i=0; i<len; i++) {
  1482.         c = format.charAt(i);
  1483.         if (c == "'") {
  1484.             for (i2=i+1; i2<len; i2++) {
  1485.                 if (format.charAt(i2) == "'") {
  1486.                     if (date) {
  1487.                         if (i2 == i+1) {
  1488.                             res += "'";
  1489.                         }else{
  1490.                             res += format.substring(i+1, i2);
  1491.                         }
  1492.                         i = i2;
  1493.                     }
  1494.                     break;
  1495.                 }
  1496.             }
  1497.         }
  1498.         else if (c == '(') {
  1499.             for (i2=i+1; i2<len; i2++) {
  1500.                 if (format.charAt(i2) == ')') {
  1501.                     var subres = formatDate(date, format.substring(i+1, i2), options);
  1502.                     if (parseInt(subres.replace(/\D/, ''), 10)) {
  1503.                         res += subres;
  1504.                     }
  1505.                     i = i2;
  1506.                     break;
  1507.                 }
  1508.             }
  1509.         }
  1510.         else if (c == '[') {
  1511.             for (i2=i+1; i2<len; i2++) {
  1512.                 if (format.charAt(i2) == ']') {
  1513.                     var subformat = format.substring(i+1, i2);
  1514.                     var subres = formatDate(date, subformat, options);
  1515.                     if (subres != formatDate(otherDate, subformat, options)) {
  1516.                         res += subres;
  1517.                     }
  1518.                     i = i2;
  1519.                     break;
  1520.                 }
  1521.             }
  1522.         }
  1523.         else if (c == '{') {
  1524.             date = date2;
  1525.             otherDate = date1;
  1526.         }
  1527.         else if (c == '}') {
  1528.             date = date1;
  1529.             otherDate = date2;
  1530.         }
  1531.         else {
  1532.             for (i2=len; i2>i; i2--) {
  1533.                 if (formatter = dateFormatters[format.substring(i, i2)]) {
  1534.                     if (date) {
  1535.                         res += formatter(date, options);
  1536.                     }
  1537.                     i = i2 - 1;
  1538.                     break;
  1539.                 }
  1540.             }
  1541.             if (i2 == i) {
  1542.                 if (date) {
  1543.                     res += c;
  1544.                 }
  1545.             }
  1546.         }
  1547.     }
  1548.     return res;
  1549. };
  1550.  
  1551.  
  1552. var dateFormatters = {
  1553.     s   : function(d)   { return d.getSeconds() },
  1554.     ss  : function(d)   { return zeroPad(d.getSeconds()) },
  1555.     m   : function(d)   { return d.getMinutes() },
  1556.     mm  : function(d)   { return zeroPad(d.getMinutes()) },
  1557.     h   : function(d)   { return d.getHours() % 12 || 12 },
  1558.     hh  : function(d)   { return zeroPad(d.getHours() % 12 || 12) },
  1559.     H   : function(d)   { return d.getHours() },
  1560.     HH  : function(d)   { return zeroPad(d.getHours()) },
  1561.     d   : function(d)   { return d.getDate() },
  1562.     dd  : function(d)   { return zeroPad(d.getDate()) },
  1563.     ddd : function(d,o) { return o.dayNamesShort[d.getDay()] },
  1564.     dddd: function(d,o) { return o.dayNames[d.getDay()] },
  1565.     M   : function(d)   { return d.getMonth() + 1 },
  1566.     MM  : function(d)   { return zeroPad(d.getMonth() + 1) },
  1567.     MMM : function(d,o) { return o.monthNamesShort[d.getMonth()] },
  1568.     MMMM: function(d,o) { return o.monthNames[d.getMonth()] },
  1569.     yy  : function(d)   { return (d.getFullYear()+'').substring(2) },
  1570.     yyyy: function(d)   { return d.getFullYear() },
  1571.     t   : function(d)   { return d.getHours() < 12 ? 'a' : 'p' },
  1572.     tt  : function(d)   { return d.getHours() < 12 ? 'am' : 'pm' },
  1573.     T   : function(d)   { return d.getHours() < 12 ? 'A' : 'P' },
  1574.     TT  : function(d)   { return d.getHours() < 12 ? 'AM' : 'PM' },
  1575.     u   : function(d)   { return formatDate(d, "yyyy-MM-dd'T'HH:mm:ss'Z'") },
  1576.     S   : function(d)   {
  1577.         var date = d.getDate();
  1578.         if (date > 10 && date < 20) {
  1579.             return 'th';
  1580.         }
  1581.         return ['st', 'nd', 'rd'][date%10-1] || 'th';
  1582.     }
  1583. };
  1584.  
  1585.  
  1586.  
  1587. fc.applyAll = applyAll;
  1588.  
  1589.  
  1590. /* Event Date Math
  1591. -----------------------------------------------------------------------------*/
  1592.  
  1593.  
  1594. function exclEndDay(event) {
  1595.     if (event.end) {
  1596.         return _exclEndDay(event.end, event.allDay);
  1597.     }else{
  1598.         return addDays(cloneDate(event.start), 1);
  1599.     }
  1600. }
  1601.  
  1602.  
  1603. function _exclEndDay(end, allDay) {
  1604.     end = cloneDate(end);
  1605.     return allDay || end.getHours() || end.getMinutes() ? addDays(end, 1) : clearTime(end);
  1606. }
  1607.  
  1608.  
  1609. function segCmp(a, b) {
  1610.     return (b.msLength - a.msLength) * 100 + (a.event.start - b.event.start);
  1611. }
  1612.  
  1613.  
  1614. function segsCollide(seg1, seg2) {
  1615.     return seg1.end > seg2.start && seg1.start < seg2.end;
  1616. }
  1617.  
  1618.  
  1619.  
  1620. /* Event Sorting
  1621. -----------------------------------------------------------------------------*/
  1622.  
  1623.  
  1624. // event rendering utilities
  1625. function sliceSegs(events, visEventEnds, start, end) {
  1626.     var segs = [],
  1627.         i, len=events.length, event,
  1628.         eventStart, eventEnd,
  1629.         segStart, segEnd,
  1630.         isStart, isEnd;
  1631.     for (i=0; i<len; i++) {
  1632.         event = events[i];
  1633.         eventStart = event.start;
  1634.         eventEnd = visEventEnds[i];
  1635.         if (eventEnd > start && eventStart < end) {
  1636.             if (eventStart < start) {
  1637.                 segStart = cloneDate(start);
  1638.                 isStart = false;
  1639.             }else{
  1640.                 segStart = eventStart;
  1641.                 isStart = true;
  1642.             }
  1643.             if (eventEnd > end) {
  1644.                 segEnd = cloneDate(end);
  1645.                 isEnd = false;
  1646.             }else{
  1647.                 segEnd = eventEnd;
  1648.                 isEnd = true;
  1649.             }
  1650.             segs.push({
  1651.                 event: event,
  1652.                 start: segStart,
  1653.                 end: segEnd,
  1654.                 isStart: isStart,
  1655.                 isEnd: isEnd,
  1656.                 msLength: segEnd - segStart
  1657.             });
  1658.         }
  1659.     }
  1660.     return segs.sort(segCmp);
  1661. }
  1662.  
  1663.  
  1664. // event rendering calculation utilities
  1665. function stackSegs(segs) {
  1666.     var levels = [],
  1667.         i, len = segs.length, seg,
  1668.         j, collide, k;
  1669.     for (i=0; i<len; i++) {
  1670.         seg = segs[i];
  1671.         j = 0; // the level index where seg should belong
  1672.         while (true) {
  1673.             collide = false;
  1674.             if (levels[j]) {
  1675.                 for (k=0; k<levels[j].length; k++) {
  1676.                     if (segsCollide(levels[j][k], seg)) {
  1677.                         collide = true;
  1678.                         break;
  1679.                     }
  1680.                 }
  1681.             }
  1682.             if (collide) {
  1683.                 j++;
  1684.             }else{
  1685.                 break;
  1686.             }
  1687.         }
  1688.         if (levels[j]) {
  1689.             levels[j].push(seg);
  1690.         }else{
  1691.             levels[j] = [seg];
  1692.         }
  1693.     }
  1694.     return levels;
  1695. }
  1696.  
  1697.  
  1698.  
  1699. /* Event Element Binding
  1700. -----------------------------------------------------------------------------*/
  1701.  
  1702.  
  1703. function lazySegBind(container, segs, bindHandlers) {
  1704.     container.unbind('mouseover').mouseover(function(ev) {
  1705.         var parent=ev.target, e,
  1706.             i, seg;
  1707.         while (parent != this) {
  1708.             e = parent;
  1709.             parent = parent.parentNode;
  1710.         }
  1711.         if ((i = e._fci) !== undefined) {
  1712.             e._fci = undefined;
  1713.             seg = segs[i];
  1714.             bindHandlers(seg.event, seg.element, seg);
  1715.             $(ev.target).trigger(ev);
  1716.         }
  1717.         ev.stopPropagation();
  1718.     });
  1719. }
  1720.  
  1721.  
  1722.  
  1723. /* Element Dimensions
  1724. -----------------------------------------------------------------------------*/
  1725.  
  1726.  
  1727. function setOuterWidth(element, width, includeMargins) {
  1728.     for (var i=0, e; i<element.length; i++) {
  1729.         e = $(element[i]);
  1730.         e.width(Math.max(0, width - hsides(e, includeMargins)));
  1731.     }
  1732. }
  1733.  
  1734.  
  1735. function setOuterHeight(element, height, includeMargins) {
  1736.     for (var i=0, e; i<element.length; i++) {
  1737.         e = $(element[i]);
  1738.         e.height(Math.max(0, height - vsides(e, includeMargins)));
  1739.     }
  1740. }
  1741.  
  1742.  
  1743. // TODO: curCSS has been deprecated (jQuery 1.4.3 - 10/16/2010)
  1744.  
  1745.  
  1746. function hsides(element, includeMargins) {
  1747.     return hpadding(element) + hborders(element) + (includeMargins ? hmargins(element) : 0);
  1748. }
  1749.  
  1750.  
  1751. function hpadding(element) {
  1752.     return (parseFloat($.curCSS(element[0], 'paddingLeft', true)) || 0) +
  1753.            (parseFloat($.curCSS(element[0], 'paddingRight', true)) || 0);
  1754. }
  1755.  
  1756.  
  1757. function hmargins(element) {
  1758.     return (parseFloat($.curCSS(element[0], 'marginLeft', true)) || 0) +
  1759.            (parseFloat($.curCSS(element[0], 'marginRight', true)) || 0);
  1760. }
  1761.  
  1762.  
  1763. function hborders(element) {
  1764.     return (parseFloat($.curCSS(element[0], 'borderLeftWidth', true)) || 0) +
  1765.            (parseFloat($.curCSS(element[0], 'borderRightWidth', true)) || 0);
  1766. }
  1767.  
  1768.  
  1769. function vsides(element, includeMargins) {
  1770.     return vpadding(element) +  vborders(element) + (includeMargins ? vmargins(element) : 0);
  1771. }
  1772.  
  1773.  
  1774. function vpadding(element) {
  1775.     return (parseFloat($.curCSS(element[0], 'paddingTop', true)) || 0) +
  1776.            (parseFloat($.curCSS(element[0], 'paddingBottom', true)) || 0);
  1777. }
  1778.  
  1779.  
  1780. function vmargins(element) {
  1781.     return (parseFloat($.curCSS(element[0], 'marginTop', true)) || 0) +
  1782.            (parseFloat($.curCSS(element[0], 'marginBottom', true)) || 0);
  1783. }
  1784.  
  1785.  
  1786. function vborders(element) {
  1787.     return (parseFloat($.curCSS(element[0], 'borderTopWidth', true)) || 0) +
  1788.            (parseFloat($.curCSS(element[0], 'borderBottomWidth', true)) || 0);
  1789. }
  1790.  
  1791.  
  1792. function setMinHeight(element, height) {
  1793.     height = (typeof height == 'number' ? height + 'px' : height);
  1794.     element.each(function(i, _element) {
  1795.         _element.style.cssText += ';min-height:' + height + ';_height:' + height;
  1796.         // why can't we just use .css() ? i forget
  1797.     });
  1798. }
  1799.  
  1800.  
  1801.  
  1802. /* Misc Utils
  1803. -----------------------------------------------------------------------------*/
  1804.  
  1805.  
  1806. //TODO: arraySlice
  1807. //TODO: isFunction, grep ?
  1808.  
  1809.  
  1810. function noop() { }
  1811.  
  1812.  
  1813. function cmp(a, b) {
  1814.     return a - b;
  1815. }
  1816.  
  1817.  
  1818. function arrayMax(a) {
  1819.     return Math.max.apply(Math, a);
  1820. }
  1821.  
  1822.  
  1823. function zeroPad(n) {
  1824.     return (n < 10 ? '0' : '') + n;
  1825. }
  1826.  
  1827.  
  1828. function smartProperty(obj, name) { // get a camel-cased/namespaced property of an object
  1829.     if (obj[name] !== undefined) {
  1830.         return obj[name];
  1831.     }
  1832.     var parts = name.split(/(?=[A-Z])/),
  1833.         i=parts.length-1, res;
  1834.     for (; i>=0; i--) {
  1835.         res = obj[parts[i].toLowerCase()];
  1836.         if (res !== undefined) {
  1837.             return res;
  1838.         }
  1839.     }
  1840.     return obj[''];
  1841. }
  1842.  
  1843.  
  1844. function htmlEscape(s) {
  1845.     return s.replace(/&/g, '&amp;')
  1846.         .replace(/</g, '&lt;')
  1847.         .replace(/>/g, '&gt;')
  1848.         .replace(/'/g, '&#039;')
  1849.         .replace(/"/g, '&quot;')
  1850.         .replace(/\n/g, '<br />');
  1851. }
  1852.  
  1853.  
  1854. function cssKey(_element) {
  1855.     return _element.id + '/' + _element.className + '/' + _element.style.cssText.replace(/(^|;)\s*(top|left|width|height)\s*:[^;]*/ig, '');
  1856. }
  1857.  
  1858.  
  1859. function disableTextSelection(element) {
  1860.     element
  1861.         .attr('unselectable', 'on')
  1862.         .css('MozUserSelect', 'none')
  1863.         .bind('selectstart.ui', function() { return false; });
  1864. }
  1865.  
  1866.  
  1867. /*
  1868. function enableTextSelection(element) {
  1869.     element
  1870.         .attr('unselectable', 'off')
  1871.         .css('MozUserSelect', '')
  1872.         .unbind('selectstart.ui');
  1873. }
  1874. */
  1875.  
  1876.  
  1877. function markFirstLast(e) {
  1878.     e.children()
  1879.         .removeClass('fc-first fc-last')
  1880.         .filter(':first-child')
  1881.             .addClass('fc-first')
  1882.         .end()
  1883.         .filter(':last-child')
  1884.             .addClass('fc-last');
  1885. }
  1886.  
  1887.  
  1888. function setDayID(cell, date) {
  1889.     cell.each(function(i, _cell) {
  1890.         _cell.className = _cell.className.replace(/^fc-\w*/, 'fc-' + dayIDs[date.getDay()]);
  1891.         // TODO: make a way that doesn't rely on order of classes
  1892.     });
  1893. }
  1894.  
  1895.  
  1896. function getSkinCss(event, opt) {
  1897.     var source = event.source || {};
  1898.     var eventColor = event.color;
  1899.     var sourceColor = source.color;
  1900.     var optionColor = opt('eventColor');
  1901.     var backgroundColor =
  1902.         event.backgroundColor ||
  1903.         eventColor ||
  1904.         source.backgroundColor ||
  1905.         sourceColor ||
  1906.         opt('eventBackgroundColor') ||
  1907.         optionColor;
  1908.     var borderColor =
  1909.         event.borderColor ||
  1910.         eventColor ||
  1911.         source.borderColor ||
  1912.         sourceColor ||
  1913.         opt('eventBorderColor') ||
  1914.         optionColor;
  1915.     var textColor =
  1916.         event.textColor ||
  1917.         source.textColor ||
  1918.         opt('eventTextColor');
  1919.     var statements = [];
  1920.     if (backgroundColor) {
  1921.         statements.push('background-color:' + backgroundColor);
  1922.     }
  1923.     if (borderColor) {
  1924.         statements.push('border-color:' + borderColor);
  1925.     }
  1926.     if (textColor) {
  1927.         statements.push('color:' + textColor);
  1928.     }
  1929.     return statements.join(';');
  1930. }
  1931.  
  1932.  
  1933. function applyAll(functions, thisObj, args) {
  1934.     if ($.isFunction(functions)) {
  1935.         functions = [ functions ];
  1936.     }
  1937.     if (functions) {
  1938.         var i;
  1939.         var ret;
  1940.         for (i=0; i<functions.length; i++) {
  1941.             ret = functions[i].apply(thisObj, args) || ret;
  1942.         }
  1943.         return ret;
  1944.     }
  1945. }
  1946.  
  1947.  
  1948. function firstDefined() {
  1949.     for (var i=0; i<arguments.length; i++) {
  1950.         if (arguments[i] !== undefined) {
  1951.             return arguments[i];
  1952.         }
  1953.     }
  1954. }
  1955.  
  1956.  
  1957.  
  1958. fcViews.month = MonthView;
  1959.  
  1960. function MonthView(element, calendar) {
  1961.     var t = this;
  1962.    
  1963.    
  1964.     // exports
  1965.     t.render = render;
  1966.    
  1967.    
  1968.     // imports
  1969.     BasicView.call(t, element, calendar, 'month');
  1970.     var opt = t.opt;
  1971.     var renderBasic = t.renderBasic;
  1972.     var formatDate = calendar.formatDate;
  1973.    
  1974.    
  1975.    
  1976.     function render(date, delta) {
  1977.         if (delta) {
  1978.             addMonths(date, delta);
  1979.             date.setDate(1);
  1980.         }
  1981.         var start = cloneDate(date, true);
  1982.         start.setDate(1);
  1983.         var end = addMonths(cloneDate(start), 1);
  1984.         var visStart = cloneDate(start);
  1985.         var visEnd = cloneDate(end);
  1986.         var firstDay = opt('firstDay');
  1987.         var nwe = opt('weekends') ? 0 : 1;
  1988.         if (nwe) {
  1989.             skipWeekend(visStart);
  1990.             skipWeekend(visEnd, -1, true);
  1991.         }
  1992.         addDays(visStart, -((visStart.getDay() - Math.max(firstDay, nwe) + 7) % 7));
  1993.         addDays(visEnd, (7 - visEnd.getDay() + Math.max(firstDay, nwe)) % 7);
  1994.         var rowCnt = Math.round((visEnd - visStart) / (DAY_MS * 7));
  1995.         if (opt('weekMode') == 'fixed') {
  1996.             addDays(visEnd, (6 - rowCnt) * 7);
  1997.             rowCnt = 6;
  1998.         }
  1999.         t.title = formatDate(start, opt('titleFormat'));
  2000.         t.start = start;
  2001.         t.end = end;
  2002.         t.visStart = visStart;
  2003.         t.visEnd = visEnd;
  2004.         renderBasic(6, rowCnt, nwe ? 5 : 7, true);
  2005.     }
  2006.    
  2007.    
  2008. }
  2009.  
  2010. fcViews.basicWeek = BasicWeekView;
  2011.  
  2012. function BasicWeekView(element, calendar) {
  2013.     var t = this;
  2014.    
  2015.    
  2016.     // exports
  2017.     t.render = render;
  2018.    
  2019.    
  2020.     // imports
  2021.     BasicView.call(t, element, calendar, 'basicWeek');
  2022.     var opt = t.opt;
  2023.     var renderBasic = t.renderBasic;
  2024.     var formatDates = calendar.formatDates;
  2025.    
  2026.    
  2027.    
  2028.     function render(date, delta) {
  2029.         if (delta) {
  2030.             addDays(date, delta * 7);
  2031.         }
  2032.         var start = addDays(cloneDate(date), -((date.getDay() - opt('firstDay') + 7) % 7));
  2033.         var end = addDays(cloneDate(start), 7);
  2034.         var visStart = cloneDate(start);
  2035.         var visEnd = cloneDate(end);
  2036.         var weekends = opt('weekends');
  2037.         if (!weekends) {
  2038.             skipWeekend(visStart);
  2039.             skipWeekend(visEnd, -1, true);
  2040.         }
  2041.         t.title = formatDates(
  2042.             visStart,
  2043.             addDays(cloneDate(visEnd), -1),
  2044.             opt('titleFormat')
  2045.         );
  2046.         t.start = start;
  2047.         t.end = end;
  2048.         t.visStart = visStart;
  2049.         t.visEnd = visEnd;
  2050.         renderBasic(1, 1, weekends ? 7 : 5, false);
  2051.     }
  2052.    
  2053.    
  2054. }
  2055.  
  2056. fcViews.basicDay = BasicDayView;
  2057.  
  2058. //TODO: when calendar's date starts out on a weekend, shouldn't happen
  2059.  
  2060.  
  2061. function BasicDayView(element, calendar) {
  2062.     var t = this;
  2063.    
  2064.    
  2065.     // exports
  2066.     t.render = render;
  2067.    
  2068.    
  2069.     // imports
  2070.     BasicView.call(t, element, calendar, 'basicDay');
  2071.     var opt = t.opt;
  2072.     var renderBasic = t.renderBasic;
  2073.     var formatDate = calendar.formatDate;
  2074.    
  2075.    
  2076.    
  2077.     function render(date, delta) {
  2078.         if (delta) {
  2079.             addDays(date, delta);
  2080.             if (!opt('weekends')) {
  2081.                 skipWeekend(date, delta < 0 ? -1 : 1);
  2082.             }
  2083.         }
  2084.         t.title = formatDate(date, opt('titleFormat'));
  2085.         t.start = t.visStart = cloneDate(date, true);
  2086.         t.end = t.visEnd = addDays(cloneDate(t.start), 1);
  2087.         renderBasic(1, 1, 1, false);
  2088.     }
  2089.    
  2090.    
  2091. }
  2092.  
  2093. setDefaults({
  2094.     weekMode: 'fixed'
  2095. });
  2096.  
  2097.  
  2098. function BasicView(element, calendar, viewName) {
  2099.     var t = this;
  2100.    
  2101.    
  2102.     // exports
  2103.     t.renderBasic = renderBasic;
  2104.     t.setHeight = setHeight;
  2105.     t.setWidth = setWidth;
  2106.     t.renderDayOverlay = renderDayOverlay;
  2107.     t.defaultSelectionEnd = defaultSelectionEnd;
  2108.     t.renderSelection = renderSelection;
  2109.     t.clearSelection = clearSelection;
  2110.     t.reportDayClick = reportDayClick; // for selection (kinda hacky)
  2111.     t.dragStart = dragStart;
  2112.     t.dragStop = dragStop;
  2113.     t.defaultEventEnd = defaultEventEnd;
  2114.     t.getHoverListener = function() { return hoverListener };
  2115.     t.colContentLeft = colContentLeft;
  2116.     t.colContentRight = colContentRight;
  2117.     t.dayOfWeekCol = dayOfWeekCol;
  2118.     t.dateCell = dateCell;
  2119.     t.cellDate = cellDate;
  2120.     t.cellIsAllDay = function() { return true };
  2121.     t.allDayRow = allDayRow;
  2122.     t.allDayBounds = allDayBounds;
  2123.     t.getRowCnt = function() { return rowCnt };
  2124.     t.getColCnt = function() { return colCnt };
  2125.     t.getColWidth = function() { return colWidth };
  2126.     t.getDaySegmentContainer = function() { return daySegmentContainer };
  2127.    
  2128.    
  2129.     // imports
  2130.     View.call(t, element, calendar, viewName);
  2131.     OverlayManager.call(t);
  2132.     SelectionManager.call(t);
  2133.     BasicEventRenderer.call(t);
  2134.     var opt = t.opt;
  2135.     var trigger = t.trigger;
  2136.     var clearEvents = t.clearEvents;
  2137.     var renderOverlay = t.renderOverlay;
  2138.     var clearOverlays = t.clearOverlays;
  2139.     var daySelectionMousedown = t.daySelectionMousedown;
  2140.     var formatDate = calendar.formatDate;
  2141.    
  2142.    
  2143.     // locals
  2144.    
  2145.     var head;
  2146.     var headCells;
  2147.     var body;
  2148.     var bodyRows;
  2149.     var bodyCells;
  2150.     var bodyFirstCells;
  2151.     var bodyCellTopInners;
  2152.     var daySegmentContainer;
  2153.    
  2154.     var viewWidth;
  2155.     var viewHeight;
  2156.     var colWidth;
  2157.    
  2158.     var rowCnt, colCnt;
  2159.     var coordinateGrid;
  2160.     var hoverListener;
  2161.     var colContentPositions;
  2162.    
  2163.     var rtl, dis, dit;
  2164.     var firstDay;
  2165.     var nwe;
  2166.     var tm;
  2167.     var colFormat;
  2168.    
  2169.    
  2170.    
  2171.     /* Rendering
  2172.     ------------------------------------------------------------*/
  2173.    
  2174.    
  2175.     disableTextSelection(element.addClass('fc-grid'));
  2176.    
  2177.    
  2178.     function renderBasic(maxr, r, c, showNumbers) {
  2179.         rowCnt = r;
  2180.         colCnt = c;
  2181.         updateOptions();
  2182.         var firstTime = !body;
  2183.         if (firstTime) {
  2184.             buildSkeleton(maxr, showNumbers);
  2185.         }else{
  2186.             clearEvents();
  2187.         }
  2188.         updateCells(firstTime);
  2189.     }
  2190.    
  2191.    
  2192.    
  2193.     function updateOptions() {
  2194.         rtl = opt('isRTL');
  2195.         if (rtl) {
  2196.             dis = -1;
  2197.             dit = colCnt - 1;
  2198.         }else{
  2199.             dis = 1;
  2200.             dit = 0;
  2201.         }
  2202.         firstDay = opt('firstDay');
  2203.         nwe = opt('weekends') ? 0 : 1;
  2204.         tm = opt('theme') ? 'ui' : 'fc';
  2205.         colFormat = opt('columnFormat');
  2206.     }
  2207.    
  2208.    
  2209.    
  2210.     function buildSkeleton(maxRowCnt, showNumbers) {
  2211.         var s;
  2212.         var headerClass = tm + "-widget-header";
  2213.         var contentClass = tm + "-widget-content";
  2214.         var i, j;
  2215.         var table;
  2216.        
  2217.         s =
  2218.             "<table class='fc-border-separate' style='width:100%' cellspacing='0'>" +
  2219.             "<thead>" +
  2220.             "<tr>";
  2221.         for (i=0; i<colCnt; i++) {
  2222.             s +=
  2223.                 "<th class='fc- " + headerClass + "'/>"; // need fc- for setDayID
  2224.         }
  2225.         s +=
  2226.             "</tr>" +
  2227.             "</thead>" +
  2228.             "<tbody>";
  2229.         for (i=0; i<maxRowCnt; i++) {
  2230.             s +=
  2231.                 "<tr class='fc-week" + i + "'>";
  2232.             for (j=0; j<colCnt; j++) {
  2233.                 s +=
  2234.                     "<td class='fc- " + contentClass + " fc-day" + (i*colCnt+j) + "'>" + // need fc- for setDayID
  2235.                     "<div>" +
  2236.                     (showNumbers ?
  2237.                         "<div class='fc-day-number'/>" :
  2238.                         ''
  2239.                         ) +
  2240.                     "<div class='fc-day-content'>" +
  2241.                     "<div style='position:relative'>&nbsp;</div>" +
  2242.                     "</div>" +
  2243.                     "</div>" +
  2244.                     "</td>";
  2245.             }
  2246.             s +=
  2247.                 "</tr>";
  2248.         }
  2249.         s +=
  2250.             "</tbody>" +
  2251.             "</table>";
  2252.         table = $(s).appendTo(element);
  2253.        
  2254.         head = table.find('thead');
  2255.         headCells = head.find('th');
  2256.         body = table.find('tbody');
  2257.         bodyRows = body.find('tr');
  2258.         bodyCells = body.find('td');
  2259.         bodyFirstCells = bodyCells.filter(':first-child');
  2260.         bodyCellTopInners = bodyRows.eq(0).find('div.fc-day-content div');
  2261.        
  2262.         markFirstLast(head.add(head.find('tr'))); // marks first+last tr/th's
  2263.         markFirstLast(bodyRows); // marks first+last td's
  2264.         bodyRows.eq(0).addClass('fc-first'); // fc-last is done in updateCells
  2265.        
  2266.         dayBind(bodyCells);
  2267.        
  2268.         daySegmentContainer =
  2269.             $("<div style='position:absolute;z-index:8;top:0;left:0'/>")
  2270.                 .appendTo(element);
  2271.     }
  2272.    
  2273.    
  2274.    
  2275.     function updateCells(firstTime) {
  2276.         var dowDirty = firstTime || rowCnt == 1; // could the cells' day-of-weeks need updating?
  2277.         var month = t.start.getMonth();
  2278.         var today = clearTime(new Date());
  2279.         var cell;
  2280.         var date;
  2281.         var row;
  2282.    
  2283.         if (dowDirty) {
  2284.             headCells.each(function(i, _cell) {
  2285.                 cell = $(_cell);
  2286.                 date = indexDate(i);
  2287.                 cell.html(formatDate(date, colFormat));
  2288.                 setDayID(cell, date);
  2289.             });
  2290.         }
  2291.        
  2292.         bodyCells.each(function(i, _cell) {
  2293.             cell = $(_cell);
  2294.             date = indexDate(i);
  2295.             if (date.getMonth() == month) {
  2296.                 cell.removeClass('fc-other-month');
  2297.             }else{
  2298.                 cell.addClass('fc-other-month');
  2299.             }
  2300.             if (+date == +today) {
  2301.                 cell.addClass(tm + '-state-highlight fc-today');
  2302.             }else{
  2303.                 cell.removeClass(tm + '-state-highlight fc-today');
  2304.             }
  2305.             cell.find('div.fc-day-number').text(date.getDate());
  2306.             if (dowDirty) {
  2307.                 setDayID(cell, date);
  2308.             }
  2309.         });
  2310.        
  2311.         bodyRows.each(function(i, _row) {
  2312.             row = $(_row);
  2313.             if (i < rowCnt) {
  2314.                 row.show();
  2315.                 if (i == rowCnt-1) {
  2316.                     row.addClass('fc-last');
  2317.                 }else{
  2318.                     row.removeClass('fc-last');
  2319.                 }
  2320.             }else{
  2321.                 row.hide();
  2322.             }
  2323.         });
  2324.     }
  2325.    
  2326.    
  2327.    
  2328.     function setHeight(height) {
  2329.         viewHeight = height;
  2330.        
  2331.         var bodyHeight = viewHeight - head.height();
  2332.         var rowHeight;
  2333.         var rowHeightLast;
  2334.         var cell;
  2335.            
  2336.         if (opt('weekMode') == 'variable') {
  2337.             rowHeight = rowHeightLast = Math.floor(bodyHeight / (rowCnt==1 ? 2 : 6));
  2338.         }else{
  2339.             rowHeight = Math.floor(bodyHeight / rowCnt);
  2340.             rowHeightLast = bodyHeight - rowHeight * (rowCnt-1);
  2341.         }
  2342.        
  2343.         bodyFirstCells.each(function(i, _cell) {
  2344.             if (i < rowCnt) {
  2345.                 cell = $(_cell);
  2346.                 setMinHeight(
  2347.                     cell.find('> div'),
  2348.                     (i==rowCnt-1 ? rowHeightLast : rowHeight) - vsides(cell)
  2349.                 );
  2350.             }
  2351.         });
  2352.        
  2353.     }
  2354.    
  2355.    
  2356.     function setWidth(width) {
  2357.         viewWidth = width;
  2358.         colContentPositions.clear();
  2359.         colWidth = Math.floor(viewWidth / colCnt);
  2360.         setOuterWidth(headCells.slice(0, -1), colWidth);
  2361.     }
  2362.    
  2363.    
  2364.    
  2365.     /* Day clicking and binding
  2366.     -----------------------------------------------------------*/
  2367.    
  2368.    
  2369.     function dayBind(days) {
  2370.         days.click(dayClick)
  2371.             .mousedown(daySelectionMousedown);
  2372.     }
  2373.    
  2374.    
  2375.     function dayClick(ev) {
  2376.         if (!opt('selectable')) { // if selectable, SelectionManager will worry about dayClick
  2377.             var index = parseInt(this.className.match(/fc\-day(\d+)/)[1]); // TODO: maybe use .data
  2378.             var date = indexDate(index);
  2379.             trigger('dayClick', this, date, true, ev);
  2380.         }
  2381.     }
  2382.    
  2383.    
  2384.    
  2385.     /* Semi-transparent Overlay Helpers
  2386.     ------------------------------------------------------*/
  2387.    
  2388.    
  2389.     function renderDayOverlay(overlayStart, overlayEnd, refreshCoordinateGrid) { // overlayEnd is exclusive
  2390.         if (refreshCoordinateGrid) {
  2391.             coordinateGrid.build();
  2392.         }
  2393.         var rowStart = cloneDate(t.visStart);
  2394.         var rowEnd = addDays(cloneDate(rowStart), colCnt);
  2395.         for (var i=0; i<rowCnt; i++) {
  2396.             var stretchStart = new Date(Math.max(rowStart, overlayStart));
  2397.             var stretchEnd = new Date(Math.min(rowEnd, overlayEnd));
  2398.             if (stretchStart < stretchEnd) {
  2399.                 var colStart, colEnd;
  2400.                 if (rtl) {
  2401.                     colStart = dayDiff(stretchEnd, rowStart)*dis+dit+1;
  2402.                     colEnd = dayDiff(stretchStart, rowStart)*dis+dit+1;
  2403.                 }else{
  2404.                     colStart = dayDiff(stretchStart, rowStart);
  2405.                     colEnd = dayDiff(stretchEnd, rowStart);
  2406.                 }
  2407.                 dayBind(
  2408.                     renderCellOverlay(i, colStart, i, colEnd-1)
  2409.                 );
  2410.             }
  2411.             addDays(rowStart, 7);
  2412.             addDays(rowEnd, 7);
  2413.         }
  2414.     }
  2415.    
  2416.    
  2417.     function renderCellOverlay(row0, col0, row1, col1) { // row1,col1 is inclusive
  2418.         var rect = coordinateGrid.rect(row0, col0, row1, col1, element);
  2419.         return renderOverlay(rect, element);
  2420.     }
  2421.    
  2422.    
  2423.    
  2424.     /* Selection
  2425.     -----------------------------------------------------------------------*/
  2426.    
  2427.    
  2428.     function defaultSelectionEnd(startDate, allDay) {
  2429.         return cloneDate(startDate);
  2430.     }
  2431.    
  2432.    
  2433.     function renderSelection(startDate, endDate, allDay) {
  2434.         renderDayOverlay(startDate, addDays(cloneDate(endDate), 1), true); // rebuild every time???
  2435.     }
  2436.    
  2437.    
  2438.     function clearSelection() {
  2439.         clearOverlays();
  2440.     }
  2441.    
  2442.    
  2443.     function reportDayClick(date, allDay, ev) {
  2444.         var cell = dateCell(date);
  2445.         var _element = bodyCells[cell.row*colCnt + cell.col];
  2446.         trigger('dayClick', _element, date, allDay, ev);
  2447.     }
  2448.    
  2449.    
  2450.    
  2451.     /* External Dragging
  2452.     -----------------------------------------------------------------------*/
  2453.    
  2454.    
  2455.     function dragStart(_dragElement, ev, ui) {
  2456.         hoverListener.start(function(cell) {
  2457.             clearOverlays();
  2458.             if (cell) {
  2459.                 renderCellOverlay(cell.row, cell.col, cell.row, cell.col);
  2460.             }
  2461.         }, ev);
  2462.     }
  2463.    
  2464.    
  2465.     function dragStop(_dragElement, ev, ui) {
  2466.         var cell = hoverListener.stop();
  2467.         clearOverlays();
  2468.         if (cell) {
  2469.             var d = cellDate(cell);
  2470.             trigger('drop', _dragElement, d, true, ev, ui);
  2471.         }
  2472.     }
  2473.    
  2474.    
  2475.    
  2476.     /* Utilities
  2477.     --------------------------------------------------------*/
  2478.    
  2479.    
  2480.     function defaultEventEnd(event) {
  2481.         return cloneDate(event.start);
  2482.     }
  2483.    
  2484.    
  2485.     coordinateGrid = new CoordinateGrid(function(rows, cols) {
  2486.         var e, n, p;
  2487.         headCells.each(function(i, _e) {
  2488.             e = $(_e);
  2489.             n = e.offset().left;
  2490.             if (i) {
  2491.                 p[1] = n;
  2492.             }
  2493.             p = [n];
  2494.             cols[i] = p;
  2495.         });
  2496.         p[1] = n + e.outerWidth();
  2497.         bodyRows.each(function(i, _e) {
  2498.             if (i < rowCnt) {
  2499.                 e = $(_e);
  2500.                 n = e.offset().top;
  2501.                 if (i) {
  2502.                     p[1] = n;
  2503.                 }
  2504.                 p = [n];
  2505.                 rows[i] = p;
  2506.             }
  2507.         });
  2508.         p[1] = n + e.outerHeight();
  2509.     });
  2510.    
  2511.    
  2512.     hoverListener = new HoverListener(coordinateGrid);
  2513.    
  2514.    
  2515.     colContentPositions = new HorizontalPositionCache(function(col) {
  2516.         return bodyCellTopInners.eq(col);
  2517.     });
  2518.    
  2519.    
  2520.     function colContentLeft(col) {
  2521.         return colContentPositions.left(col);
  2522.     }
  2523.    
  2524.    
  2525.     function colContentRight(col) {
  2526.         return colContentPositions.right(col);
  2527.     }
  2528.    
  2529.    
  2530.    
  2531.    
  2532.     function dateCell(date) {
  2533.         return {
  2534.             row: Math.floor(dayDiff(date, t.visStart) / 7),
  2535.             col: dayOfWeekCol(date.getDay())
  2536.         };
  2537.     }
  2538.    
  2539.    
  2540.     function cellDate(cell) {
  2541.         return _cellDate(cell.row, cell.col);
  2542.     }
  2543.    
  2544.    
  2545.     function _cellDate(row, col) {
  2546.         return addDays(cloneDate(t.visStart), row*7 + col*dis+dit);
  2547.         // what about weekends in middle of week?
  2548.     }
  2549.    
  2550.    
  2551.     function indexDate(index) {
  2552.         return _cellDate(Math.floor(index/colCnt), index%colCnt);
  2553.     }
  2554.    
  2555.    
  2556.     function dayOfWeekCol(dayOfWeek) {
  2557.         return ((dayOfWeek - Math.max(firstDay, nwe) + colCnt) % colCnt) * dis + dit;
  2558.     }
  2559.    
  2560.    
  2561.    
  2562.    
  2563.     function allDayRow(i) {
  2564.         return bodyRows.eq(i);
  2565.     }
  2566.    
  2567.    
  2568.     function allDayBounds(i) {
  2569.         return {
  2570.             left: 0,
  2571.             right: viewWidth
  2572.         };
  2573.     }
  2574.    
  2575.    
  2576. }
  2577.  
  2578. function BasicEventRenderer() {
  2579.     var t = this;
  2580.    
  2581.    
  2582.     // exports
  2583.     t.renderEvents = renderEvents;
  2584.     t.compileDaySegs = compileSegs; // for DayEventRenderer
  2585.     t.clearEvents = clearEvents;
  2586.     t.bindDaySeg = bindDaySeg;
  2587.    
  2588.    
  2589.     // imports
  2590.     DayEventRenderer.call(t);
  2591.     var opt = t.opt;
  2592.     var trigger = t.trigger;
  2593.     //var setOverflowHidden = t.setOverflowHidden;
  2594.     var isEventDraggable = t.isEventDraggable;
  2595.     var isEventResizable = t.isEventResizable;
  2596.     var reportEvents = t.reportEvents;
  2597.     var reportEventClear = t.reportEventClear;
  2598.     var eventElementHandlers = t.eventElementHandlers;
  2599.     var showEvents = t.showEvents;
  2600.     var hideEvents = t.hideEvents;
  2601.     var eventDrop = t.eventDrop;
  2602.     var getDaySegmentContainer = t.getDaySegmentContainer;
  2603.     var getHoverListener = t.getHoverListener;
  2604.     var renderDayOverlay = t.renderDayOverlay;
  2605.     var clearOverlays = t.clearOverlays;
  2606.     var getRowCnt = t.getRowCnt;
  2607.     var getColCnt = t.getColCnt;
  2608.     var renderDaySegs = t.renderDaySegs;
  2609.     var resizableDayEvent = t.resizableDayEvent;
  2610.    
  2611.    
  2612.    
  2613.     /* Rendering
  2614.     --------------------------------------------------------------------*/
  2615.    
  2616.    
  2617.     function renderEvents(events, modifiedEventId) {
  2618.         reportEvents(events);
  2619.         renderDaySegs(compileSegs(events), modifiedEventId);
  2620.     }
  2621.    
  2622.    
  2623.     function clearEvents() {
  2624.         reportEventClear();
  2625.         getDaySegmentContainer().empty();
  2626.     }
  2627.    
  2628.    
  2629.     function compileSegs(events) {
  2630.         var rowCnt = getRowCnt(),
  2631.             colCnt = getColCnt(),
  2632.             d1 = cloneDate(t.visStart),
  2633.             d2 = addDays(cloneDate(d1), colCnt),
  2634.             visEventsEnds = $.map(events, exclEndDay),
  2635.             i, row,
  2636.             j, level,
  2637.             k, seg,
  2638.             segs=[];
  2639.         for (i=0; i<rowCnt; i++) {
  2640.             row = stackSegs(sliceSegs(events, visEventsEnds, d1, d2));
  2641.             for (j=0; j<row.length; j++) {
  2642.                 level = row[j];
  2643.                 for (k=0; k<level.length; k++) {
  2644.                     seg = level[k];
  2645.                     seg.row = i;
  2646.                     seg.level = j; // not needed anymore
  2647.                     segs.push(seg);
  2648.                 }
  2649.             }
  2650.             addDays(d1, 7);
  2651.             addDays(d2, 7);
  2652.         }
  2653.         return segs;
  2654.     }
  2655.    
  2656.    
  2657.     function bindDaySeg(event, eventElement, seg) {
  2658.         if (isEventDraggable(event)) {
  2659.             draggableDayEvent(event, eventElement);
  2660.         }
  2661.         if (seg.isEnd && isEventResizable(event)) {
  2662.             resizableDayEvent(event, eventElement, seg);
  2663.         }
  2664.         eventElementHandlers(event, eventElement);
  2665.             // needs to be after, because resizableDayEvent might stopImmediatePropagation on click
  2666.     }
  2667.    
  2668.    
  2669.    
  2670.     /* Dragging
  2671.     ----------------------------------------------------------------------------*/
  2672.    
  2673.    
  2674.     function draggableDayEvent(event, eventElement) {
  2675.         var hoverListener = getHoverListener();
  2676.         var dayDelta;
  2677.         eventElement.draggable({
  2678.             zIndex: 9,
  2679.             delay: 50,
  2680.             opacity: opt('dragOpacity'),
  2681.             revertDuration: opt('dragRevertDuration'),
  2682.             start: function(ev, ui) {
  2683.                 trigger('eventDragStart', eventElement, event, ev, ui);
  2684.                 hideEvents(event, eventElement);
  2685.                 hoverListener.start(function(cell, origCell, rowDelta, colDelta) {
  2686.                     eventElement.draggable('option', 'revert', !cell || !rowDelta && !colDelta);
  2687.                     clearOverlays();
  2688.                     if (cell) {
  2689.                         //setOverflowHidden(true);
  2690.                         dayDelta = rowDelta*7 + colDelta * (opt('isRTL') ? -1 : 1);
  2691.                         renderDayOverlay(
  2692.                             addDays(cloneDate(event.start), dayDelta),
  2693.                             addDays(exclEndDay(event), dayDelta)
  2694.                         );
  2695.                     }else{
  2696.                         //setOverflowHidden(false);
  2697.                         dayDelta = 0;
  2698.                     }
  2699.                 }, ev, 'drag');
  2700.             },
  2701.             stop: function(ev, ui) {
  2702.                 hoverListener.stop();
  2703.                 clearOverlays();
  2704.                 trigger('eventDragStop', eventElement, event, ev, ui);
  2705.                 if (dayDelta) {
  2706.                     eventDrop(this, event, dayDelta, 0, event.allDay, ev, ui);
  2707.                 }else{
  2708.                     eventElement.css('filter', ''); // clear IE opacity side-effects
  2709.                     showEvents(event, eventElement);
  2710.                 }
  2711.                 //setOverflowHidden(false);
  2712.             }
  2713.         });
  2714.     }
  2715.  
  2716.  
  2717. }
  2718.  
  2719. fcViews.agendaWeek = AgendaWeekView;
  2720.  
  2721. function AgendaWeekView(element, calendar) {
  2722.     var t = this;
  2723.    
  2724.    
  2725.     // exports
  2726.     t.render = render;
  2727.    
  2728.    
  2729.     // imports
  2730.     AgendaView.call(t, element, calendar, 'agendaWeek');
  2731.     var opt = t.opt;
  2732.     var renderAgenda = t.renderAgenda;
  2733.     var formatDates = calendar.formatDates;
  2734.    
  2735.    
  2736.    
  2737.     function render(date, delta) {
  2738.         if (delta) {
  2739.             addDays(date, delta * 7);
  2740.         }
  2741.         var start = addDays(cloneDate(date), -((date.getDay() - opt('firstDay') + 7) % 7));
  2742.         var end = addDays(cloneDate(start), 7);
  2743.         var visStart = cloneDate(start);
  2744.         var visEnd = cloneDate(end);
  2745.         var weekends = opt('weekends');
  2746.         if (!weekends) {
  2747.             skipWeekend(visStart);
  2748.             skipWeekend(visEnd, -1, true);
  2749.         }
  2750.         t.title = formatDates(
  2751.             visStart,
  2752.             addDays(cloneDate(visEnd), -1),
  2753.             opt('titleFormat')
  2754.         );
  2755.         t.start = start;
  2756.         t.end = end;
  2757.         t.visStart = visStart;
  2758.         t.visEnd = visEnd;
  2759.         renderAgenda(weekends ? 7 : 5);
  2760.     }
  2761.    
  2762.  
  2763. }
  2764.  
  2765. fcViews.agendaDay = AgendaDayView;
  2766.  
  2767. function AgendaDayView(element, calendar) {
  2768.     var t = this;
  2769.    
  2770.    
  2771.     // exports
  2772.     t.render = render;
  2773.    
  2774.    
  2775.     // imports
  2776.     AgendaView.call(t, element, calendar, 'agendaDay');
  2777.     var opt = t.opt;
  2778.     var renderAgenda = t.renderAgenda;
  2779.     var formatDate = calendar.formatDate;
  2780.    
  2781.    
  2782.    
  2783.     function render(date, delta) {
  2784.         if (delta) {
  2785.             addDays(date, delta);
  2786.             if (!opt('weekends')) {
  2787.                 skipWeekend(date, delta < 0 ? -1 : 1);
  2788.             }
  2789.         }
  2790.         var start = cloneDate(date, true);
  2791.         var end = addDays(cloneDate(start), 1);
  2792.         t.title = formatDate(date, opt('titleFormat'));
  2793.         t.start = t.visStart = start;
  2794.         t.end = t.visEnd = end;
  2795.         renderAgenda(1);
  2796.     }
  2797.    
  2798.  
  2799. }
  2800.  
  2801. setDefaults({
  2802.     allDaySlot: true,
  2803.     allDayText: 'all-day',
  2804.     firstHour: 6,
  2805.     slotMinutes: 30,
  2806.     defaultEventMinutes: 120,
  2807.     axisFormat: 'h(:mm)tt',
  2808.     timeFormat: {
  2809.         agenda: 'h:mm{ - h:mm}'
  2810.     },
  2811.     dragOpacity: {
  2812.         agenda: .5
  2813.     },
  2814.     minTime: 0,
  2815.     maxTime: 24
  2816. });
  2817.  
  2818.  
  2819. // TODO: make it work in quirks mode (event corners, all-day height)
  2820. // TODO: test liquid width, especially in IE6
  2821.  
  2822.  
  2823. function AgendaView(element, calendar, viewName) {
  2824.     var t = this;
  2825.    
  2826.    
  2827.     // exports
  2828.     t.renderAgenda = renderAgenda;
  2829.     t.setWidth = setWidth;
  2830.     t.setHeight = setHeight;
  2831.     t.beforeHide = beforeHide;
  2832.     t.afterShow = afterShow;
  2833.     t.defaultEventEnd = defaultEventEnd;
  2834.     t.timePosition = timePosition;
  2835.     t.dayOfWeekCol = dayOfWeekCol;
  2836.     t.dateCell = dateCell;
  2837.     t.cellDate = cellDate;
  2838.     t.cellIsAllDay = cellIsAllDay;
  2839.     t.allDayRow = getAllDayRow;
  2840.     t.allDayBounds = allDayBounds;
  2841.     t.getHoverListener = function() { return hoverListener };
  2842.     t.colContentLeft = colContentLeft;
  2843.     t.colContentRight = colContentRight;
  2844.     t.getDaySegmentContainer = function() { return daySegmentContainer };
  2845.     t.getSlotSegmentContainer = function() { return slotSegmentContainer };
  2846.     t.getMinMinute = function() { return minMinute };
  2847.     t.getMaxMinute = function() { return maxMinute };
  2848.     t.getBodyContent = function() { return slotContent }; // !!??
  2849.     t.getRowCnt = function() { return 1 };
  2850.     t.getColCnt = function() { return colCnt };
  2851.     t.getColWidth = function() { return colWidth };
  2852.     t.getSlotHeight = function() { return slotHeight };
  2853.     t.defaultSelectionEnd = defaultSelectionEnd;
  2854.     t.renderDayOverlay = renderDayOverlay;
  2855.     t.renderSelection = renderSelection;
  2856.     t.clearSelection = clearSelection;
  2857.     t.reportDayClick = reportDayClick; // selection mousedown hack
  2858.     t.dragStart = dragStart;
  2859.     t.dragStop = dragStop;
  2860.    
  2861.    
  2862.     // imports
  2863.     View.call(t, element, calendar, viewName);
  2864.     OverlayManager.call(t);
  2865.     SelectionManager.call(t);
  2866.     AgendaEventRenderer.call(t);
  2867.     var opt = t.opt;
  2868.     var trigger = t.trigger;
  2869.     var clearEvents = t.clearEvents;
  2870.     var renderOverlay = t.renderOverlay;
  2871.     var clearOverlays = t.clearOverlays;
  2872.     var reportSelection = t.reportSelection;
  2873.     var unselect = t.unselect;
  2874.     var daySelectionMousedown = t.daySelectionMousedown;
  2875.     var slotSegHtml = t.slotSegHtml;
  2876.     var formatDate = calendar.formatDate;
  2877.    
  2878.    
  2879.     // locals
  2880.    
  2881.     var dayTable;
  2882.     var dayHead;
  2883.     var dayHeadCells;
  2884.     var dayBody;
  2885.     var dayBodyCells;
  2886.     var dayBodyCellInners;
  2887.     var dayBodyFirstCell;
  2888.     var dayBodyFirstCellStretcher;
  2889.     var slotLayer;
  2890.     var daySegmentContainer;
  2891.     var allDayTable;
  2892.     var allDayRow;
  2893.     var slotScroller;
  2894.     var slotContent;
  2895.     var slotSegmentContainer;
  2896.     var slotTable;
  2897.     var slotTableFirstInner;
  2898.     var axisFirstCells;
  2899.     var gutterCells;
  2900.     var selectionHelper;
  2901.    
  2902.     var viewWidth;
  2903.     var viewHeight;
  2904.     var axisWidth;
  2905.     var colWidth;
  2906.     var gutterWidth;
  2907.     var slotHeight; // TODO: what if slotHeight changes? (see issue 650)
  2908.     var savedScrollTop;
  2909.    
  2910.     var colCnt;
  2911.     var slotCnt;
  2912.     var coordinateGrid;
  2913.     var hoverListener;
  2914.     var colContentPositions;
  2915.     var slotTopCache = {};
  2916.    
  2917.     var tm;
  2918.     var firstDay;
  2919.     var nwe;            // no weekends (int)
  2920.     var rtl, dis, dit;  // day index sign / translate
  2921.     var minMinute, maxMinute;
  2922.     var colFormat;
  2923.    
  2924.  
  2925.    
  2926.     /* Rendering
  2927.     -----------------------------------------------------------------------------*/
  2928.    
  2929.    
  2930.     disableTextSelection(element.addClass('fc-agenda'));
  2931.    
  2932.    
  2933.     function renderAgenda(c) {
  2934.         colCnt = c;
  2935.         updateOptions();
  2936.         if (!dayTable) {
  2937.             buildSkeleton();
  2938.         }else{
  2939.             clearEvents();
  2940.         }
  2941.         updateCells();
  2942.     }
  2943.    
  2944.    
  2945.    
  2946.     function updateOptions() {
  2947.         tm = opt('theme') ? 'ui' : 'fc';
  2948.         nwe = opt('weekends') ? 0 : 1;
  2949.         firstDay = opt('firstDay');
  2950.         if (rtl = opt('isRTL')) {
  2951.             dis = -1;
  2952.             dit = colCnt - 1;
  2953.         }else{
  2954.             dis = 1;
  2955.             dit = 0;
  2956.         }
  2957.         minMinute = parseTime(opt('minTime'));
  2958.         maxMinute = parseTime(opt('maxTime'));
  2959.         colFormat = opt('columnFormat');
  2960.     }
  2961.    
  2962.    
  2963.    
  2964.     function buildSkeleton() {
  2965.         var headerClass = tm + "-widget-header";
  2966.         var contentClass = tm + "-widget-content";
  2967.         var s;
  2968.         var i;
  2969.         var d;
  2970.         var maxd;
  2971.         var minutes;
  2972.         var slotNormal = opt('slotMinutes') % 15 == 0;
  2973.        
  2974.         s =
  2975.             "<table style='width:100%' class='fc-agenda-days fc-border-separate' cellspacing='0'>" +
  2976.             "<thead>" +
  2977.             "<tr>" +
  2978.             "<th class='fc-agenda-axis " + headerClass + "'>&nbsp;</th>";
  2979.         for (i=0; i<colCnt; i++) {
  2980.             s +=
  2981.                 "<th class='fc- fc-col" + i + ' ' + headerClass + "'/>"; // fc- needed for setDayID
  2982.         }
  2983.         s +=
  2984.             "<th class='fc-agenda-gutter " + headerClass + "'>&nbsp;</th>" +
  2985.             "</tr>" +
  2986.             "</thead>" +
  2987.             "<tbody>" +
  2988.             "<tr>" +
  2989.             "<th class='fc-agenda-axis " + headerClass + "'>&nbsp;</th>";
  2990.         for (i=0; i<colCnt; i++) {
  2991.             s +=
  2992.                 "<td class='fc- fc-col" + i + ' ' + contentClass + "'>" + // fc- needed for setDayID
  2993.                 "<div>" +
  2994.                 "<div class='fc-day-content'>" +
  2995.                 "<div style='position:relative'>&nbsp;</div>" +
  2996.                 "</div>" +
  2997.                 "</div>" +
  2998.                 "</td>";
  2999.         }
  3000.         s +=
  3001.             "<td class='fc-agenda-gutter " + contentClass + "'>&nbsp;</td>" +
  3002.             "</tr>" +
  3003.             "</tbody>" +
  3004.             "</table>";
  3005.         dayTable = $(s).appendTo(element);
  3006.         dayHead = dayTable.find('thead');
  3007.         dayHeadCells = dayHead.find('th').slice(1, -1);
  3008.         dayBody = dayTable.find('tbody');
  3009.         dayBodyCells = dayBody.find('td').slice(0, -1);
  3010.         dayBodyCellInners = dayBodyCells.find('div.fc-day-content div');
  3011.         dayBodyFirstCell = dayBodyCells.eq(0);
  3012.         dayBodyFirstCellStretcher = dayBodyFirstCell.find('> div');
  3013.        
  3014.         markFirstLast(dayHead.add(dayHead.find('tr')));
  3015.         markFirstLast(dayBody.add(dayBody.find('tr')));
  3016.        
  3017.         axisFirstCells = dayHead.find('th:first');
  3018.         gutterCells = dayTable.find('.fc-agenda-gutter');
  3019.        
  3020.         slotLayer =
  3021.             $("<div style='position:absolute;z-index:2;left:0;width:100%'/>")
  3022.                 .appendTo(element);
  3023.                
  3024.         if (opt('allDaySlot')) {
  3025.        
  3026.             daySegmentContainer =
  3027.                 $("<div style='position:absolute;z-index:8;top:0;left:0'/>")
  3028.                     .appendTo(slotLayer);
  3029.        
  3030.             s =
  3031.                 "<table style='width:100%' class='fc-agenda-allday' cellspacing='0'>" +
  3032.                 "<tr>" +
  3033.                 "<th class='" + headerClass + " fc-agenda-axis'>" + opt('allDayText') + "</th>" +
  3034.                 "<td>" +
  3035.                 "<div class='fc-day-content'><div style='position:relative'/></div>" +
  3036.                 "</td>" +
  3037.                 "<th class='" + headerClass + " fc-agenda-gutter'>&nbsp;</th>" +
  3038.                 "</tr>" +
  3039.                 "</table>";
  3040.             allDayTable = $(s).appendTo(slotLayer);
  3041.             allDayRow = allDayTable.find('tr');
  3042.            
  3043.             dayBind(allDayRow.find('td'));
  3044.            
  3045.             axisFirstCells = axisFirstCells.add(allDayTable.find('th:first'));
  3046.             gutterCells = gutterCells.add(allDayTable.find('th.fc-agenda-gutter'));
  3047.            
  3048.             slotLayer.append(
  3049.                 "<div class='fc-agenda-divider " + headerClass + "'>" +
  3050.                 "<div class='fc-agenda-divider-inner'/>" +
  3051.                 "</div>"
  3052.             );
  3053.            
  3054.         }else{
  3055.        
  3056.             daySegmentContainer = $([]); // in jQuery 1.4, we can just do $()
  3057.        
  3058.         }
  3059.        
  3060.         slotScroller =
  3061.             $("<div style='position:absolute;width:100%;overflow-x:hidden;overflow-y:auto'/>")
  3062.                 .appendTo(slotLayer);
  3063.                
  3064.         slotContent =
  3065.             $("<div style='position:relative;width:100%;overflow:hidden'/>")
  3066.                 .appendTo(slotScroller);
  3067.                
  3068.         slotSegmentContainer =
  3069.             $("<div style='position:absolute;z-index:8;top:0;left:0'/>")
  3070.                 .appendTo(slotContent);
  3071.        
  3072.         s =
  3073.             "<table class='fc-agenda-slots' style='width:100%' cellspacing='0'>" +
  3074.             "<tbody>";
  3075.         d = zeroDate();
  3076.         maxd = addMinutes(cloneDate(d), maxMinute);
  3077.         addMinutes(d, minMinute);
  3078.         slotCnt = 0;
  3079.         for (i=0; d < maxd; i++) {
  3080.             minutes = d.getMinutes();
  3081.             s +=
  3082.                 "<tr class='fc-slot" + i + ' ' + (!minutes ? '' : 'fc-minor') + "'>" +
  3083.                 "<th class='fc-agenda-axis " + headerClass + "'>" +
  3084.                 ((!slotNormal || !minutes) ? formatDate(d, opt('axisFormat')) : '&nbsp;') +
  3085.                 "</th>" +
  3086.                 "<td class='" + contentClass + "'>" +
  3087.                 "<div style='position:relative'>&nbsp;</div>" +
  3088.                 "</td>" +
  3089.                 "</tr>";
  3090.             addMinutes(d, opt('slotMinutes'));
  3091.             slotCnt++;
  3092.         }
  3093.         s +=
  3094.             "</tbody>" +
  3095.             "</table>";
  3096.         slotTable = $(s).appendTo(slotContent);
  3097.         slotTableFirstInner = slotTable.find('div:first');
  3098.        
  3099.         slotBind(slotTable.find('td'));
  3100.        
  3101.         axisFirstCells = axisFirstCells.add(slotTable.find('th:first'));
  3102.     }
  3103.    
  3104.    
  3105.    
  3106.     function updateCells() {
  3107.         var i;
  3108.         var headCell;
  3109.         var bodyCell;
  3110.         var date;
  3111.         var today = clearTime(new Date());
  3112.         for (i=0; i<colCnt; i++) {
  3113.             date = colDate(i);
  3114.             headCell = dayHeadCells.eq(i);
  3115.             headCell.html(formatDate(date, colFormat));
  3116.             bodyCell = dayBodyCells.eq(i);
  3117.             if (+date == +today) {
  3118.                 bodyCell.addClass(tm + '-state-highlight fc-today');
  3119.             }else{
  3120.                 bodyCell.removeClass(tm + '-state-highlight fc-today');
  3121.             }
  3122.             setDayID(headCell.add(bodyCell), date);
  3123.         }
  3124.     }
  3125.    
  3126.    
  3127.    
  3128.     function setHeight(height, dateChanged) {
  3129.         if (height === undefined) {
  3130.             height = viewHeight;
  3131.         }
  3132.         viewHeight = height;
  3133.         slotTopCache = {};
  3134.    
  3135.         var headHeight = dayBody.position().top;
  3136.         var allDayHeight = slotScroller.position().top; // including divider
  3137.         var bodyHeight = Math.min( // total body height, including borders
  3138.             height - headHeight,   // when scrollbars
  3139.             slotTable.height() + allDayHeight + 1 // when no scrollbars. +1 for bottom border
  3140.         );
  3141.        
  3142.         dayBodyFirstCellStretcher
  3143.             .height(bodyHeight - vsides(dayBodyFirstCell));
  3144.        
  3145.         slotLayer.css('top', headHeight);
  3146.        
  3147.         slotScroller.height(bodyHeight - allDayHeight - 1);
  3148.        
  3149.         slotHeight = slotTableFirstInner.height() + 1; // +1 for border
  3150.        
  3151.         if (dateChanged) {
  3152.             resetScroll();
  3153.         }
  3154.     }
  3155.    
  3156.    
  3157.    
  3158.     function setWidth(width) {
  3159.         viewWidth = width;
  3160.         colContentPositions.clear();
  3161.        
  3162.         axisWidth = 0;
  3163.         setOuterWidth(
  3164.             axisFirstCells
  3165.                 .width('')
  3166.                 .each(function(i, _cell) {
  3167.                     axisWidth = Math.max(axisWidth, $(_cell).outerWidth());
  3168.                 }),
  3169.             axisWidth
  3170.         );
  3171.        
  3172.         var slotTableWidth = slotScroller[0].clientWidth; // needs to be done after axisWidth (for IE7)
  3173.         //slotTable.width(slotTableWidth);
  3174.        
  3175.         gutterWidth = slotScroller.width() - slotTableWidth;
  3176.         if (gutterWidth) {
  3177.             setOuterWidth(gutterCells, gutterWidth);
  3178.             gutterCells
  3179.                 .show()
  3180.                 .prev()
  3181.                 .removeClass('fc-last');
  3182.         }else{
  3183.             gutterCells
  3184.                 .hide()
  3185.                 .prev()
  3186.                 .addClass('fc-last');
  3187.         }
  3188.        
  3189.         colWidth = Math.floor((slotTableWidth - axisWidth) / colCnt);
  3190.         setOuterWidth(dayHeadCells.slice(0, -1), colWidth);
  3191.     }
  3192.    
  3193.  
  3194.  
  3195.     function resetScroll() {
  3196.         var d0 = zeroDate();
  3197.         var scrollDate = cloneDate(d0);
  3198.         scrollDate.setHours(opt('firstHour'));
  3199.         var top = timePosition(d0, scrollDate) + 1; // +1 for the border
  3200.         function scroll() {
  3201.             slotScroller.scrollTop(top);
  3202.         }
  3203.         scroll();
  3204.         setTimeout(scroll, 0); // overrides any previous scroll state made by the browser
  3205.     }
  3206.    
  3207.    
  3208.     function beforeHide() {
  3209.         savedScrollTop = slotScroller.scrollTop();
  3210.     }
  3211.    
  3212.    
  3213.     function afterShow() {
  3214.         slotScroller.scrollTop(savedScrollTop);
  3215.     }
  3216.    
  3217.    
  3218.    
  3219.     /* Slot/Day clicking and binding
  3220.     -----------------------------------------------------------------------*/
  3221.    
  3222.  
  3223.     function dayBind(cells) {
  3224.         cells.click(slotClick)
  3225.             .mousedown(daySelectionMousedown);
  3226.     }
  3227.  
  3228.  
  3229.     function slotBind(cells) {
  3230.         cells.click(slotClick)
  3231.             .mousedown(slotSelectionMousedown);
  3232.     }
  3233.    
  3234.    
  3235.     function slotClick(ev) {
  3236.         if (!opt('selectable')) { // if selectable, SelectionManager will worry about dayClick
  3237.             var col = Math.min(colCnt-1, Math.floor((ev.pageX - dayTable.offset().left - axisWidth) / colWidth));
  3238.             var date = colDate(col);
  3239.             var rowMatch = this.parentNode.className.match(/fc-slot(\d+)/); // TODO: maybe use data
  3240.             if (rowMatch) {
  3241.                 var mins = parseInt(rowMatch[1]) * opt('slotMinutes');
  3242.                 var hours = Math.floor(mins/60);
  3243.                 date.setHours(hours);
  3244.                 date.setMinutes(mins%60 + minMinute);
  3245.                 trigger('dayClick', dayBodyCells[col], date, false, ev);
  3246.             }else{
  3247.                 trigger('dayClick', dayBodyCells[col], date, true, ev);
  3248.             }
  3249.         }
  3250.     }
  3251.    
  3252.    
  3253.    
  3254.     /* Semi-transparent Overlay Helpers
  3255.     -----------------------------------------------------*/
  3256.    
  3257.  
  3258.     function renderDayOverlay(startDate, endDate, refreshCoordinateGrid) { // endDate is exclusive
  3259.         if (refreshCoordinateGrid) {
  3260.             coordinateGrid.build();
  3261.         }
  3262.         var visStart = cloneDate(t.visStart);
  3263.         var startCol, endCol;
  3264.         if (rtl) {
  3265.             startCol = dayDiff(endDate, visStart)*dis+dit+1;
  3266.             endCol = dayDiff(startDate, visStart)*dis+dit+1;
  3267.         }else{
  3268.             startCol = dayDiff(startDate, visStart);
  3269.             endCol = dayDiff(endDate, visStart);
  3270.         }
  3271.         startCol = Math.max(0, startCol);
  3272.         endCol = Math.min(colCnt, endCol);
  3273.         if (startCol < endCol) {
  3274.             dayBind(
  3275.                 renderCellOverlay(0, startCol, 0, endCol-1)
  3276.             );
  3277.         }
  3278.     }
  3279.    
  3280.    
  3281.     function renderCellOverlay(row0, col0, row1, col1) { // only for all-day?
  3282.         var rect = coordinateGrid.rect(row0, col0, row1, col1, slotLayer);
  3283.         return renderOverlay(rect, slotLayer);
  3284.     }
  3285.    
  3286.  
  3287.     function renderSlotOverlay(overlayStart, overlayEnd) {
  3288.         var dayStart = cloneDate(t.visStart);
  3289.         var dayEnd = addDays(cloneDate(dayStart), 1);
  3290.         for (var i=0; i<colCnt; i++) {
  3291.             var stretchStart = new Date(Math.max(dayStart, overlayStart));
  3292.             var stretchEnd = new Date(Math.min(dayEnd, overlayEnd));
  3293.             if (stretchStart < stretchEnd) {
  3294.                 var col = i*dis+dit;
  3295.                 var rect = coordinateGrid.rect(0, col, 0, col, slotContent); // only use it for horizontal coords
  3296.                 var top = timePosition(dayStart, stretchStart);
  3297.                 var bottom = timePosition(dayStart, stretchEnd);
  3298.                 rect.top = top;
  3299.                 rect.height = bottom - top;
  3300.                 slotBind(
  3301.                     renderOverlay(rect, slotContent)
  3302.                 );
  3303.             }
  3304.             addDays(dayStart, 1);
  3305.             addDays(dayEnd, 1);
  3306.         }
  3307.     }
  3308.    
  3309.    
  3310.    
  3311.     /* Coordinate Utilities
  3312.     -----------------------------------------------------------------------------*/
  3313.    
  3314.    
  3315.     coordinateGrid = new CoordinateGrid(function(rows, cols) {
  3316.         var e, n, p;
  3317.         dayHeadCells.each(function(i, _e) {
  3318.             e = $(_e);
  3319.             n = e.offset().left;
  3320.             if (i) {
  3321.                 p[1] = n;
  3322.             }
  3323.             p = [n];
  3324.             cols[i] = p;
  3325.         });
  3326.         p[1] = n + e.outerWidth();
  3327.         if (opt('allDaySlot')) {
  3328.             e = allDayRow;
  3329.             n = e.offset().top;
  3330.             rows[0] = [n, n+e.outerHeight()];
  3331.         }
  3332.         var slotTableTop = slotContent.offset().top;
  3333.         var slotScrollerTop = slotScroller.offset().top;
  3334.         var slotScrollerBottom = slotScrollerTop + slotScroller.outerHeight();
  3335.         function constrain(n) {
  3336.             return Math.max(slotScrollerTop, Math.min(slotScrollerBottom, n));
  3337.         }
  3338.         for (var i=0; i<slotCnt; i++) {
  3339.             rows.push([
  3340.                 constrain(slotTableTop + slotHeight*i),
  3341.                 constrain(slotTableTop + slotHeight*(i+1))
  3342.             ]);
  3343.         }
  3344.     });
  3345.    
  3346.    
  3347.     hoverListener = new HoverListener(coordinateGrid);
  3348.    
  3349.    
  3350.     colContentPositions = new HorizontalPositionCache(function(col) {
  3351.         return dayBodyCellInners.eq(col);
  3352.     });
  3353.    
  3354.    
  3355.     function colContentLeft(col) {
  3356.         return colContentPositions.left(col);
  3357.     }
  3358.    
  3359.    
  3360.     function colContentRight(col) {
  3361.         return colContentPositions.right(col);
  3362.     }
  3363.    
  3364.    
  3365.    
  3366.    
  3367.     function dateCell(date) { // "cell" terminology is now confusing
  3368.         return {
  3369.             row: Math.floor(dayDiff(date, t.visStart) / 7),
  3370.             col: dayOfWeekCol(date.getDay())
  3371.         };
  3372.     }
  3373.    
  3374.    
  3375.     function cellDate(cell) {
  3376.         var d = colDate(cell.col);
  3377.         var slotIndex = cell.row;
  3378.         if (opt('allDaySlot')) {
  3379.             slotIndex--;
  3380.         }
  3381.         if (slotIndex >= 0) {
  3382.             addMinutes(d, minMinute + slotIndex * opt('slotMinutes'));
  3383.         }
  3384.         return d;
  3385.     }
  3386.    
  3387.    
  3388.     function colDate(col) { // returns dates with 00:00:00
  3389.         return addDays(cloneDate(t.visStart), col*dis+dit);
  3390.     }
  3391.    
  3392.    
  3393.     function cellIsAllDay(cell) {
  3394.         return opt('allDaySlot') && !cell.row;
  3395.     }
  3396.    
  3397.    
  3398.     function dayOfWeekCol(dayOfWeek) {
  3399.         return ((dayOfWeek - Math.max(firstDay, nwe) + colCnt) % colCnt)*dis+dit;
  3400.     }
  3401.    
  3402.    
  3403.    
  3404.    
  3405.     // get the Y coordinate of the given time on the given day (both Date objects)
  3406.     function timePosition(day, time) { // both date objects. day holds 00:00 of current day
  3407.         day = cloneDate(day, true);
  3408.         if (time < addMinutes(cloneDate(day), minMinute)) {
  3409.             return 0;
  3410.         }
  3411.         if (time >= addMinutes(cloneDate(day), maxMinute)) {
  3412.             return slotTable.height();
  3413.         }
  3414.         var slotMinutes = opt('slotMinutes'),
  3415.             minutes = time.getHours()*60 + time.getMinutes() - minMinute,
  3416.             slotI = Math.floor(minutes / slotMinutes),
  3417.             slotTop = slotTopCache[slotI];
  3418.         if (slotTop === undefined) {
  3419.             slotTop = slotTopCache[slotI] = slotTable.find('tr:eq(' + slotI + ') td div')[0].offsetTop; //.position().top; // need this optimization???
  3420.         }
  3421.         return Math.max(0, Math.round(
  3422.             slotTop - 1 + slotHeight * ((minutes % slotMinutes) / slotMinutes)
  3423.         ));
  3424.     }
  3425.    
  3426.    
  3427.     function allDayBounds() {
  3428.         return {
  3429.             left: axisWidth,
  3430.             right: viewWidth - gutterWidth
  3431.         }
  3432.     }
  3433.    
  3434.    
  3435.     function getAllDayRow(index) {
  3436.         return allDayRow;
  3437.     }
  3438.    
  3439.    
  3440.     function defaultEventEnd(event) {
  3441.         var start = cloneDate(event.start);
  3442.         if (event.allDay) {
  3443.             return start;
  3444.         }
  3445.         return addMinutes(start, opt('defaultEventMinutes'));
  3446.     }
  3447.    
  3448.    
  3449.    
  3450.     /* Selection
  3451.     ---------------------------------------------------------------------------------*/
  3452.    
  3453.    
  3454.     function defaultSelectionEnd(startDate, allDay) {
  3455.         if (allDay) {
  3456.             return cloneDate(startDate);
  3457.         }
  3458.         return addMinutes(cloneDate(startDate), opt('slotMinutes'));
  3459.     }
  3460.    
  3461.    
  3462.     function renderSelection(startDate, endDate, allDay) { // only for all-day
  3463.         if (allDay) {
  3464.             if (opt('allDaySlot')) {
  3465.                 renderDayOverlay(startDate, addDays(cloneDate(endDate), 1), true);
  3466.             }
  3467.         }else{
  3468.             renderSlotSelection(startDate, endDate);
  3469.         }
  3470.     }
  3471.    
  3472.    
  3473.     function renderSlotSelection(startDate, endDate) {
  3474.         var helperOption = opt('selectHelper');
  3475.         coordinateGrid.build();
  3476.         if (helperOption) {
  3477.             var col = dayDiff(startDate, t.visStart) * dis + dit;
  3478.             if (col >= 0 && col < colCnt) { // only works when times are on same day
  3479.                 var rect = coordinateGrid.rect(0, col, 0, col, slotContent); // only for horizontal coords
  3480.                 var top = timePosition(startDate, startDate);
  3481.                 var bottom = timePosition(startDate, endDate);
  3482.                 if (bottom > top) { // protect against selections that are entirely before or after visible range
  3483.                     rect.top = top;
  3484.                     rect.height = bottom - top;
  3485.                     rect.left += 2;
  3486.                     rect.width -= 5;
  3487.                     if ($.isFunction(helperOption)) {
  3488.                         var helperRes = helperOption(startDate, endDate);
  3489.                         if (helperRes) {
  3490.                             rect.position = 'absolute';
  3491.                             rect.zIndex = 8;
  3492.                             selectionHelper = $(helperRes)
  3493.                                 .css(rect)
  3494.                                 .appendTo(slotContent);
  3495.                         }
  3496.                     }else{
  3497.                         rect.isStart = true; // conside rect a "seg" now
  3498.                         rect.isEnd = true;   //
  3499.                         selectionHelper = $(slotSegHtml(
  3500.                             {
  3501.                                 title: '',
  3502.                                 start: startDate,
  3503.                                 end: endDate,
  3504.                                 className: ['fc-select-helper'],
  3505.                                 editable: false
  3506.                             },
  3507.                             rect
  3508.                         ));
  3509.                         selectionHelper.css('opacity', opt('dragOpacity'));
  3510.                     }
  3511.                     if (selectionHelper) {
  3512.                         slotBind(selectionHelper);
  3513.                         slotContent.append(selectionHelper);
  3514.                         setOuterWidth(selectionHelper, rect.width, true); // needs to be after appended
  3515.                         setOuterHeight(selectionHelper, rect.height, true);
  3516.                     }
  3517.                 }
  3518.             }
  3519.         }else{
  3520.             renderSlotOverlay(startDate, endDate);
  3521.         }
  3522.     }
  3523.    
  3524.    
  3525.     function clearSelection() {
  3526.         clearOverlays();
  3527.         if (selectionHelper) {
  3528.             selectionHelper.remove();
  3529.             selectionHelper = null;
  3530.         }
  3531.     }
  3532.    
  3533.    
  3534.     function slotSelectionMousedown(ev) {
  3535.         if (ev.which == 1 && opt('selectable')) { // ev.which==1 means left mouse button
  3536.             unselect(ev);
  3537.             var dates;
  3538.             hoverListener.start(function(cell, origCell) {
  3539.                 clearSelection();
  3540.                 if (cell && cell.col == origCell.col && !cellIsAllDay(cell)) {
  3541.                     var d1 = cellDate(origCell);
  3542.                     var d2 = cellDate(cell);
  3543.                     dates = [
  3544.                         d1,
  3545.                         addMinutes(cloneDate(d1), opt('slotMinutes')),
  3546.                         d2,
  3547.                         addMinutes(cloneDate(d2), opt('slotMinutes'))
  3548.                     ].sort(cmp);
  3549.                     renderSlotSelection(dates[0], dates[3]);
  3550.                 }else{
  3551.                     dates = null;
  3552.                 }
  3553.             }, ev);
  3554.             $(document).one('mouseup', function(ev) {
  3555.                 hoverListener.stop();
  3556.                 if (dates) {
  3557.                     if (+dates[0] == +dates[1]) {
  3558.                         reportDayClick(dates[0], false, ev);
  3559.                     }
  3560.                     reportSelection(dates[0], dates[3], false, ev);
  3561.                 }
  3562.             });
  3563.         }
  3564.     }
  3565.    
  3566.    
  3567.     function reportDayClick(date, allDay, ev) {
  3568.         trigger('dayClick', dayBodyCells[dayOfWeekCol(date.getDay())], date, allDay, ev);
  3569.     }
  3570.    
  3571.    
  3572.    
  3573.     /* External Dragging
  3574.     --------------------------------------------------------------------------------*/
  3575.    
  3576.    
  3577.     function dragStart(_dragElement, ev, ui) {
  3578.         hoverListener.start(function(cell) {
  3579.             clearOverlays();
  3580.             if (cell) {
  3581.                 if (cellIsAllDay(cell)) {
  3582.                     renderCellOverlay(cell.row, cell.col, cell.row, cell.col);
  3583.                 }else{
  3584.                     var d1 = cellDate(cell);
  3585.                     var d2 = addMinutes(cloneDate(d1), opt('defaultEventMinutes'));
  3586.                     renderSlotOverlay(d1, d2);
  3587.                 }
  3588.             }
  3589.         }, ev);
  3590.     }
  3591.    
  3592.    
  3593.     function dragStop(_dragElement, ev, ui) {
  3594.         var cell = hoverListener.stop();
  3595.         clearOverlays();
  3596.         if (cell) {
  3597.             trigger('drop', _dragElement, cellDate(cell), cellIsAllDay(cell), ev, ui);
  3598.         }
  3599.     }
  3600.  
  3601.  
  3602. }
  3603.  
  3604. function AgendaEventRenderer() {
  3605.     var t = this;
  3606.    
  3607.    
  3608.     // exports
  3609.     t.renderEvents = renderEvents;
  3610.     t.compileDaySegs = compileDaySegs; // for DayEventRenderer
  3611.     t.clearEvents = clearEvents;
  3612.     t.slotSegHtml = slotSegHtml;
  3613.     t.bindDaySeg = bindDaySeg;
  3614.    
  3615.    
  3616.     // imports
  3617.     DayEventRenderer.call(t);
  3618.     var opt = t.opt;
  3619.     var trigger = t.trigger;
  3620.     //var setOverflowHidden = t.setOverflowHidden;
  3621.     var isEventDraggable = t.isEventDraggable;
  3622.     var isEventResizable = t.isEventResizable;
  3623.     var eventEnd = t.eventEnd;
  3624.     var reportEvents = t.reportEvents;
  3625.     var reportEventClear = t.reportEventClear;
  3626.     var eventElementHandlers = t.eventElementHandlers;
  3627.     var setHeight = t.setHeight;
  3628.     var getDaySegmentContainer = t.getDaySegmentContainer;
  3629.     var getSlotSegmentContainer = t.getSlotSegmentContainer;
  3630.     var getHoverListener = t.getHoverListener;
  3631.     var getMaxMinute = t.getMaxMinute;
  3632.     var getMinMinute = t.getMinMinute;
  3633.     var timePosition = t.timePosition;
  3634.     var colContentLeft = t.colContentLeft;
  3635.     var colContentRight = t.colContentRight;
  3636.     var renderDaySegs = t.renderDaySegs;
  3637.     var resizableDayEvent = t.resizableDayEvent; // TODO: streamline binding architecture
  3638.     var getColCnt = t.getColCnt;
  3639.     var getColWidth = t.getColWidth;
  3640.     var getSlotHeight = t.getSlotHeight;
  3641.     var getBodyContent = t.getBodyContent;
  3642.     var reportEventElement = t.reportEventElement;
  3643.     var showEvents = t.showEvents;
  3644.     var hideEvents = t.hideEvents;
  3645.     var eventDrop = t.eventDrop;
  3646.     var eventResize = t.eventResize;
  3647.     var renderDayOverlay = t.renderDayOverlay;
  3648.     var clearOverlays = t.clearOverlays;
  3649.     var calendar = t.calendar;
  3650.     var formatDate = calendar.formatDate;
  3651.     var formatDates = calendar.formatDates;
  3652.    
  3653.    
  3654.    
  3655.     /* Rendering
  3656.     ----------------------------------------------------------------------------*/
  3657.    
  3658.  
  3659.     function renderEvents(events, modifiedEventId) {
  3660.         reportEvents(events);
  3661.         var i, len=events.length,
  3662.             dayEvents=[],
  3663.             slotEvents=[];
  3664.         for (i=0; i<len; i++) {
  3665.             if (events[i].allDay) {
  3666.                 dayEvents.push(events[i]);
  3667.             }else{
  3668.                 slotEvents.push(events[i]);
  3669.             }
  3670.         }
  3671.         if (opt('allDaySlot')) {
  3672.             renderDaySegs(compileDaySegs(dayEvents), modifiedEventId);
  3673.             setHeight(); // no params means set to viewHeight
  3674.         }
  3675.         renderSlotSegs(compileSlotSegs(slotEvents), modifiedEventId);
  3676.     }
  3677.    
  3678.    
  3679.     function clearEvents() {
  3680.         reportEventClear();
  3681.         getDaySegmentContainer().empty();
  3682.         getSlotSegmentContainer().empty();
  3683.     }
  3684.    
  3685.    
  3686.     function compileDaySegs(events) {
  3687.         var levels = stackSegs(sliceSegs(events, $.map(events, exclEndDay), t.visStart, t.visEnd)),
  3688.             i, levelCnt=levels.length, level,
  3689.             j, seg,
  3690.             segs=[];
  3691.         for (i=0; i<levelCnt; i++) {
  3692.             level = levels[i];
  3693.             for (j=0; j<level.length; j++) {
  3694.                 seg = level[j];
  3695.                 seg.row = 0;
  3696.                 seg.level = i; // not needed anymore
  3697.                 segs.push(seg);
  3698.             }
  3699.         }
  3700.         return segs;
  3701.     }
  3702.    
  3703.    
  3704.     function compileSlotSegs(events) {
  3705.         var colCnt = getColCnt(),
  3706.             minMinute = getMinMinute(),
  3707.             maxMinute = getMaxMinute(),
  3708.             d = addMinutes(cloneDate(t.visStart), minMinute),
  3709.             visEventEnds = $.map(events, slotEventEnd),
  3710.             i, col,
  3711.             j, level,
  3712.             k, seg,
  3713.             segs=[];
  3714.         for (i=0; i<colCnt; i++) {
  3715.             col = stackSegs(sliceSegs(events, visEventEnds, d, addMinutes(cloneDate(d), maxMinute-minMinute)));
  3716.             countForwardSegs(col);
  3717.             for (j=0; j<col.length; j++) {
  3718.                 level = col[j];
  3719.                 for (k=0; k<level.length; k++) {
  3720.                     seg = level[k];
  3721.                     seg.col = i;
  3722.                     seg.level = j;
  3723.                     segs.push(seg);
  3724.                 }
  3725.             }
  3726.             addDays(d, 1, true);
  3727.         }
  3728.         return segs;
  3729.     }
  3730.    
  3731.    
  3732.     function slotEventEnd(event) {
  3733.         if (event.end) {
  3734.             return cloneDate(event.end);
  3735.         }else{
  3736.             return addMinutes(cloneDate(event.start), opt('defaultEventMinutes'));
  3737.         }
  3738.     }
  3739.    
  3740.    
  3741.     // renders events in the 'time slots' at the bottom
  3742.    
  3743.     function renderSlotSegs(segs, modifiedEventId) {
  3744.    
  3745.         var i, segCnt=segs.length, seg,
  3746.             event,
  3747.             classes,
  3748.             top, bottom,
  3749.             colI, levelI, forward,
  3750.             leftmost,
  3751.             availWidth,
  3752.             outerWidth,
  3753.             left,
  3754.             html='',
  3755.             eventElements,
  3756.             eventElement,
  3757.             triggerRes,
  3758.             vsideCache={},
  3759.             hsideCache={},
  3760.             key, val,
  3761.             contentElement,
  3762.             height,
  3763.             slotSegmentContainer = getSlotSegmentContainer(),
  3764.             rtl, dis, dit,
  3765.             colCnt = getColCnt();
  3766.            
  3767.         if (rtl = opt('isRTL')) {
  3768.             dis = -1;
  3769.             dit = colCnt - 1;
  3770.         }else{
  3771.             dis = 1;
  3772.             dit = 0;
  3773.         }
  3774.            
  3775.         // calculate position/dimensions, create html
  3776.         for (i=0; i<segCnt; i++) {
  3777.             seg = segs[i];
  3778.             event = seg.event;
  3779.             top = timePosition(seg.start, seg.start);
  3780.             bottom = timePosition(seg.start, seg.end);
  3781.             colI = seg.col;
  3782.             levelI = seg.level;
  3783.             forward = seg.forward || 0;
  3784.             leftmost = colContentLeft(colI*dis + dit);
  3785.             availWidth = colContentRight(colI*dis + dit) - leftmost;
  3786.             availWidth = Math.min(availWidth-6, availWidth*.95); // TODO: move this to CSS
  3787.             if (levelI) {
  3788.                 // indented and thin
  3789.                 outerWidth = availWidth / (levelI + forward + 1);
  3790.             }else{
  3791.                 if (forward) {
  3792.                     // moderately wide, aligned left still
  3793.                     outerWidth = ((availWidth / (forward + 1)) - (12/2)) * 2; // 12 is the predicted width of resizer =
  3794.                 }else{
  3795.                     // can be entire width, aligned left
  3796.                     outerWidth = availWidth;
  3797.                 }
  3798.             }
  3799.             left = leftmost +                                  // leftmost possible
  3800.                 (availWidth / (levelI + forward + 1) * levelI) // indentation
  3801.                 * dis + (rtl ? availWidth - outerWidth : 0);   // rtl
  3802.             seg.top = top;
  3803.             seg.left = left;
  3804.             seg.outerWidth = outerWidth;
  3805.             seg.outerHeight = bottom - top;
  3806.             html += slotSegHtml(event, seg);
  3807.         }
  3808.         slotSegmentContainer[0].innerHTML = html; // faster than html()
  3809.         eventElements = slotSegmentContainer.children();
  3810.        
  3811.         // retrieve elements, run through eventRender callback, bind event handlers
  3812.         for (i=0; i<segCnt; i++) {
  3813.             seg = segs[i];
  3814.             event = seg.event;
  3815.             eventElement = $(eventElements[i]); // faster than eq()
  3816.             triggerRes = trigger('eventRender', event, event, eventElement);
  3817.             if (triggerRes === false) {
  3818.                 eventElement.remove();
  3819.             }else{
  3820.                 if (triggerRes && triggerRes !== true) {
  3821.                     eventElement.remove();
  3822.                     eventElement = $(triggerRes)
  3823.                         .css({
  3824.                             position: 'absolute',
  3825.                             top: seg.top,
  3826.                             left: seg.left
  3827.                         })
  3828.                         .appendTo(slotSegmentContainer);
  3829.                 }
  3830.                 seg.element = eventElement;
  3831.                 if (event._id === modifiedEventId) {
  3832.                     bindSlotSeg(event, eventElement, seg);
  3833.                 }else{
  3834.                     eventElement[0]._fci = i; // for lazySegBind
  3835.                 }
  3836.                 reportEventElement(event, eventElement);
  3837.             }
  3838.         }
  3839.        
  3840.         lazySegBind(slotSegmentContainer, segs, bindSlotSeg);
  3841.        
  3842.         // record event sides and title positions
  3843.         for (i=0; i<segCnt; i++) {
  3844.             seg = segs[i];
  3845.             if (eventElement = seg.element) {
  3846.                 val = vsideCache[key = seg.key = cssKey(eventElement[0])];
  3847.                 seg.vsides = val === undefined ? (vsideCache[key] = vsides(eventElement, true)) : val;
  3848.                 val = hsideCache[key];
  3849.                 seg.hsides = val === undefined ? (hsideCache[key] = hsides(eventElement, true)) : val;
  3850.                 contentElement = eventElement.find('div.fc-event-content');
  3851.                 if (contentElement.length) {
  3852.                     seg.contentTop = contentElement[0].offsetTop;
  3853.                 }
  3854.             }
  3855.         }
  3856.        
  3857.         // set all positions/dimensions at once
  3858.         for (i=0; i<segCnt; i++) {
  3859.             seg = segs[i];
  3860.             if (eventElement = seg.element) {
  3861.                 eventElement[0].style.width = Math.max(0, seg.outerWidth - seg.hsides) + 'px';
  3862.                 height = Math.max(0, seg.outerHeight - seg.vsides);
  3863.                 eventElement[0].style.height = height + 'px';
  3864.                 event = seg.event;
  3865.                 if (seg.contentTop !== undefined && height - seg.contentTop < 10) {
  3866.                     // not enough room for title, put it in the time header
  3867.                     eventElement.find('div.fc-event-time')
  3868.                         .text(formatDate(event.start, opt('timeFormat')) + ' - ' + event.title);
  3869.                     eventElement.find('div.fc-event-title')
  3870.                         .remove();
  3871.                 }
  3872.                 trigger('eventAfterRender', event, event, eventElement);
  3873.             }
  3874.         }
  3875.                    
  3876.     }
  3877.    
  3878.    
  3879.     function slotSegHtml(event, seg) {
  3880.         var html = "<";
  3881.         var url = event.url;
  3882.         var skinCss = getSkinCss(event, opt);
  3883.         var skinCssAttr = (skinCss ? " style='" + skinCss + "'" : '');
  3884.         var classes = ['fc-event', 'fc-event-skin', 'fc-event-vert'];
  3885.         if (isEventDraggable(event)) {
  3886.             classes.push('fc-event-draggable');
  3887.         }
  3888.         if (seg.isStart) {
  3889.             classes.push('fc-corner-top');
  3890.         }
  3891.         if (seg.isEnd) {
  3892.             classes.push('fc-corner-bottom');
  3893.         }
  3894.         classes = classes.concat(event.className);
  3895.         if (event.source) {
  3896.             classes = classes.concat(event.source.className || []);
  3897.         }
  3898.         if (url) {
  3899.             html += "a href='" + htmlEscape(event.url) + "'";
  3900.         }else{
  3901.             html += "div";
  3902.         }
  3903.         html +=
  3904.             " class='" + classes.join(' ') + "'" +
  3905.             " style='position:absolute;z-index:8;top:" + seg.top + "px;left:" + seg.left + "px;" + skinCss + "'" +
  3906.             ">" +
  3907.             "<div class='fc-event-inner fc-event-skin'" + skinCssAttr + ">" +
  3908.             "<div class='fc-event-head fc-event-skin'" + skinCssAttr + ">" +
  3909.             "<div class='fc-event-time'>" +
  3910.             htmlEscape(formatDates(event.start, event.end, opt('timeFormat'))) +
  3911.             "</div>" +
  3912.             "</div>" +
  3913.             "<div class='fc-event-content'>" +
  3914.             "<div class='fc-event-title'>" +
  3915.             htmlEscape(event.title) +
  3916.             "</div>" +
  3917.             "</div>" +
  3918.             "<div class='fc-event-bg'></div>" +
  3919.             "</div>"; // close inner
  3920.         if (seg.isEnd && isEventResizable(event)) {
  3921.             html +=
  3922.                 "<div class='ui-resizable-handle ui-resizable-s'>=</div>";
  3923.         }
  3924.         html +=
  3925.             "</" + (url ? "a" : "div") + ">";
  3926.         return html;
  3927.     }
  3928.    
  3929.    
  3930.     function bindDaySeg(event, eventElement, seg) {
  3931.         if (isEventDraggable(event)) {
  3932.             draggableDayEvent(event, eventElement, seg.isStart);
  3933.         }
  3934.         if (seg.isEnd && isEventResizable(event)) {
  3935.             resizableDayEvent(event, eventElement, seg);
  3936.         }
  3937.         eventElementHandlers(event, eventElement);
  3938.             // needs to be after, because resizableDayEvent might stopImmediatePropagation on click
  3939.     }
  3940.    
  3941.    
  3942.     function bindSlotSeg(event, eventElement, seg) {
  3943.         var timeElement = eventElement.find('div.fc-event-time');
  3944.         if (isEventDraggable(event)) {
  3945.             draggableSlotEvent(event, eventElement, timeElement);
  3946.         }
  3947.         if (seg.isEnd && isEventResizable(event)) {
  3948.             resizableSlotEvent(event, eventElement, timeElement);
  3949.         }
  3950.         eventElementHandlers(event, eventElement);
  3951.     }
  3952.    
  3953.    
  3954.    
  3955.     /* Dragging
  3956.     -----------------------------------------------------------------------------------*/
  3957.    
  3958.    
  3959.     // when event starts out FULL-DAY
  3960.    
  3961.     function draggableDayEvent(event, eventElement, isStart) {
  3962.         var origWidth;
  3963.         var revert;
  3964.         var allDay=true;
  3965.         var dayDelta;
  3966.         var dis = opt('isRTL') ? -1 : 1;
  3967.         var hoverListener = getHoverListener();
  3968.         var colWidth = getColWidth();
  3969.         var slotHeight = getSlotHeight();
  3970.         var minMinute = getMinMinute();
  3971.         eventElement.draggable({
  3972.             zIndex: 9,
  3973.             opacity: opt('dragOpacity', 'month'), // use whatever the month view was using
  3974.             revertDuration: opt('dragRevertDuration'),
  3975.             start: function(ev, ui) {
  3976.                 trigger('eventDragStart', eventElement, event, ev, ui);
  3977.                 hideEvents(event, eventElement);
  3978.                 origWidth = eventElement.width();
  3979.                 hoverListener.start(function(cell, origCell, rowDelta, colDelta) {
  3980.                     clearOverlays();
  3981.                     if (cell) {
  3982.                         //setOverflowHidden(true);
  3983.                         revert = false;
  3984.                         dayDelta = colDelta * dis;
  3985.                         if (!cell.row) {
  3986.                             // on full-days
  3987.                             renderDayOverlay(
  3988.                                 addDays(cloneDate(event.start), dayDelta),
  3989.                                 addDays(exclEndDay(event), dayDelta)
  3990.                             );
  3991.                             resetElement();
  3992.                         }else{
  3993.                             // mouse is over bottom slots
  3994.                             if (isStart) {
  3995.                                 if (allDay) {
  3996.                                     // convert event to temporary slot-event
  3997.                                     eventElement.width(colWidth - 10); // don't use entire width
  3998.                                     setOuterHeight(
  3999.                                         eventElement,
  4000.                                         slotHeight * Math.round(
  4001.                                             (event.end ? ((event.end - event.start) / MINUTE_MS) : opt('defaultEventMinutes'))
  4002.                                             / opt('slotMinutes')
  4003.                                         )
  4004.                                     );
  4005.                                     eventElement.draggable('option', 'grid', [colWidth, 1]);
  4006.                                     allDay = false;
  4007.                                 }
  4008.                             }else{
  4009.                                 revert = true;
  4010.                             }
  4011.                         }
  4012.                         revert = revert || (allDay && !dayDelta);
  4013.                     }else{
  4014.                         resetElement();
  4015.                         //setOverflowHidden(false);
  4016.                         revert = true;
  4017.                     }
  4018.                     eventElement.draggable('option', 'revert', revert);
  4019.                 }, ev, 'drag');
  4020.             },
  4021.             stop: function(ev, ui) {
  4022.                 hoverListener.stop();
  4023.                 clearOverlays();
  4024.                 trigger('eventDragStop', eventElement, event, ev, ui);
  4025.                 if (revert) {
  4026.                     // hasn't moved or is out of bounds (draggable has already reverted)
  4027.                     resetElement();
  4028.                     eventElement.css('filter', ''); // clear IE opacity side-effects
  4029.                     showEvents(event, eventElement);
  4030.                 }else{
  4031.                     // changed!
  4032.                     var minuteDelta = 0;
  4033.                     if (!allDay) {
  4034.                         minuteDelta = Math.round((eventElement.offset().top - getBodyContent().offset().top) / slotHeight)
  4035.                             * opt('slotMinutes')
  4036.                             + minMinute
  4037.                             - (event.start.getHours() * 60 + event.start.getMinutes());
  4038.                     }
  4039.                     eventDrop(this, event, dayDelta, minuteDelta, allDay, ev, ui);
  4040.                 }
  4041.                 //setOverflowHidden(false);
  4042.             }
  4043.         });
  4044.         function resetElement() {
  4045.             if (!allDay) {
  4046.                 eventElement
  4047.                     .width(origWidth)
  4048.                     .height('')
  4049.                     .draggable('option', 'grid', null);
  4050.                 allDay = true;
  4051.             }
  4052.         }
  4053.     }
  4054.    
  4055.    
  4056.     // when event starts out IN TIMESLOTS
  4057.    
  4058.     function draggableSlotEvent(event, eventElement, timeElement) {
  4059.         var origPosition;
  4060.         var allDay=false;
  4061.         var dayDelta;
  4062.         var minuteDelta;
  4063.         var prevMinuteDelta;
  4064.         var dis = opt('isRTL') ? -1 : 1;
  4065.         var hoverListener = getHoverListener();
  4066.         var colCnt = getColCnt();
  4067.         var colWidth = getColWidth();
  4068.         var slotHeight = getSlotHeight();
  4069.         eventElement.draggable({
  4070.             zIndex: 9,
  4071.             scroll: false,
  4072.             grid: [colWidth, slotHeight],
  4073.             axis: colCnt==1 ? 'y' : false,
  4074.             opacity: opt('dragOpacity'),
  4075.             revertDuration: opt('dragRevertDuration'),
  4076.             start: function(ev, ui) {
  4077.                 trigger('eventDragStart', eventElement, event, ev, ui);
  4078.                 hideEvents(event, eventElement);
  4079.                 origPosition = eventElement.position();
  4080.                 minuteDelta = prevMinuteDelta = 0;
  4081.                 hoverListener.start(function(cell, origCell, rowDelta, colDelta) {
  4082.                     eventElement.draggable('option', 'revert', !cell);
  4083.                     clearOverlays();
  4084.                     if (cell) {
  4085.                         dayDelta = colDelta * dis;
  4086.                         if (opt('allDaySlot') && !cell.row) {
  4087.                             // over full days
  4088.                             if (!allDay) {
  4089.                                 // convert to temporary all-day event
  4090.                                 allDay = true;
  4091.                                 timeElement.hide();
  4092.                                 eventElement.draggable('option', 'grid', null);
  4093.                             }
  4094.                             renderDayOverlay(
  4095.                                 addDays(cloneDate(event.start), dayDelta),
  4096.                                 addDays(exclEndDay(event), dayDelta)
  4097.                             );
  4098.                         }else{
  4099.                             // on slots
  4100.                             resetElement();
  4101.                         }
  4102.                     }
  4103.                 }, ev, 'drag');
  4104.             },
  4105.             drag: function(ev, ui) {
  4106.                 minuteDelta = Math.round((ui.position.top - origPosition.top) / slotHeight) * opt('slotMinutes');
  4107.                 if (minuteDelta != prevMinuteDelta) {
  4108.                     if (!allDay) {
  4109.                         updateTimeText(minuteDelta);
  4110.                     }
  4111.                     prevMinuteDelta = minuteDelta;
  4112.                 }
  4113.             },
  4114.             stop: function(ev, ui) {
  4115.                 var cell = hoverListener.stop();
  4116.                 clearOverlays();
  4117.                 trigger('eventDragStop', eventElement, event, ev, ui);
  4118.                 if (cell && (dayDelta || minuteDelta || allDay)) {
  4119.                     // changed!
  4120.                     eventDrop(this, event, dayDelta, allDay ? 0 : minuteDelta, allDay, ev, ui);
  4121.                 }else{
  4122.                     // either no change or out-of-bounds (draggable has already reverted)
  4123.                     resetElement();
  4124.                     eventElement.css('filter', ''); // clear IE opacity side-effects
  4125.                     eventElement.css(origPosition); // sometimes fast drags make event revert to wrong position
  4126.                     updateTimeText(0);
  4127.                     showEvents(event, eventElement);
  4128.                 }
  4129.             }
  4130.         });
  4131.         function updateTimeText(minuteDelta) {
  4132.             var newStart = addMinutes(cloneDate(event.start), minuteDelta);
  4133.             var newEnd;
  4134.             if (event.end) {
  4135.                 newEnd = addMinutes(cloneDate(event.end), minuteDelta);
  4136.             }
  4137.             timeElement.text(formatDates(newStart, newEnd, opt('timeFormat')));
  4138.         }
  4139.         function resetElement() {
  4140.             // convert back to original slot-event
  4141.             if (allDay) {
  4142.                 timeElement.css('display', ''); // show() was causing display=inline
  4143.                 eventElement.draggable('option', 'grid', [colWidth, slotHeight]);
  4144.                 allDay = false;
  4145.             }
  4146.         }
  4147.     }
  4148.    
  4149.    
  4150.    
  4151.     /* Resizing
  4152.     --------------------------------------------------------------------------------------*/
  4153.    
  4154.    
  4155.     function resizableSlotEvent(event, eventElement, timeElement) {
  4156.         var slotDelta, prevSlotDelta;
  4157.         var slotHeight = getSlotHeight();
  4158.         eventElement.resizable({
  4159.             handles: {
  4160.                 s: 'div.ui-resizable-s'
  4161.             },
  4162.             grid: slotHeight,
  4163.             start: function(ev, ui) {
  4164.                 slotDelta = prevSlotDelta = 0;
  4165.                 hideEvents(event, eventElement);
  4166.                 eventElement.css('z-index', 9);
  4167.                 trigger('eventResizeStart', this, event, ev, ui);
  4168.             },
  4169.             resize: function(ev, ui) {
  4170.                 // don't rely on ui.size.height, doesn't take grid into account
  4171.                 slotDelta = Math.round((Math.max(slotHeight, eventElement.height()) - ui.originalSize.height) / slotHeight);
  4172.                 if (slotDelta != prevSlotDelta) {
  4173.                     timeElement.text(
  4174.                         formatDates(
  4175.                             event.start,
  4176.                             (!slotDelta && !event.end) ? null : // no change, so don't display time range
  4177.                                 addMinutes(eventEnd(event), opt('slotMinutes')*slotDelta),
  4178.                             opt('timeFormat')
  4179.                         )
  4180.                     );
  4181.                     prevSlotDelta = slotDelta;
  4182.                 }
  4183.             },
  4184.             stop: function(ev, ui) {
  4185.                 trigger('eventResizeStop', this, event, ev, ui);
  4186.                 if (slotDelta) {
  4187.                     eventResize(this, event, 0, opt('slotMinutes')*slotDelta, ev, ui);
  4188.                 }else{
  4189.                     eventElement.css('z-index', 8);
  4190.                     showEvents(event, eventElement);
  4191.                     // BUG: if event was really short, need to put title back in span
  4192.                 }
  4193.             }
  4194.         });
  4195.     }
  4196.    
  4197.  
  4198. }
  4199.  
  4200.  
  4201. function countForwardSegs(levels) {
  4202.     var i, j, k, level, segForward, segBack;
  4203.     for (i=levels.length-1; i>0; i--) {
  4204.         level = levels[i];
  4205.         for (j=0; j<level.length; j++) {
  4206.             segForward = level[j];
  4207.             for (k=0; k<levels[i-1].length; k++) {
  4208.                 segBack = levels[i-1][k];
  4209.                 if (segsCollide(segForward, segBack)) {
  4210.                     segBack.forward = Math.max(segBack.forward||0, (segForward.forward||0)+1);
  4211.                 }
  4212.             }
  4213.         }
  4214.     }
  4215. }
  4216.  
  4217.  
  4218.  
  4219.  
  4220. function View(element, calendar, viewName) {
  4221.     var t = this;
  4222.    
  4223.    
  4224.     // exports
  4225.     t.element = element;
  4226.     t.calendar = calendar;
  4227.     t.name = viewName;
  4228.     t.opt = opt;
  4229.     t.trigger = trigger;
  4230.     //t.setOverflowHidden = setOverflowHidden;
  4231.     t.isEventDraggable = isEventDraggable;
  4232.     t.isEventResizable = isEventResizable;
  4233.     t.reportEvents = reportEvents;
  4234.     t.eventEnd = eventEnd;
  4235.     t.reportEventElement = reportEventElement;
  4236.     t.reportEventClear = reportEventClear;
  4237.     t.eventElementHandlers = eventElementHandlers;
  4238.     t.showEvents = showEvents;
  4239.     t.hideEvents = hideEvents;
  4240.     t.eventDrop = eventDrop;
  4241.     t.eventResize = eventResize;
  4242.     // t.title
  4243.     // t.start, t.end
  4244.     // t.visStart, t.visEnd
  4245.    
  4246.    
  4247.     // imports
  4248.     var defaultEventEnd = t.defaultEventEnd;
  4249.     var normalizeEvent = calendar.normalizeEvent; // in EventManager
  4250.     var reportEventChange = calendar.reportEventChange;
  4251.    
  4252.    
  4253.     // locals
  4254.     var eventsByID = {};
  4255.     var eventElements = [];
  4256.     var eventElementsByID = {};
  4257.     var options = calendar.options;
  4258.    
  4259.    
  4260.    
  4261.     function opt(name, viewNameOverride) {
  4262.         var v = options[name];
  4263.         if (typeof v == 'object') {
  4264.             return smartProperty(v, viewNameOverride || viewName);
  4265.         }
  4266.         return v;
  4267.     }
  4268.  
  4269.    
  4270.     function trigger(name, thisObj) {
  4271.         return calendar.trigger.apply(
  4272.             calendar,
  4273.             [name, thisObj || t].concat(Array.prototype.slice.call(arguments, 2), [t])
  4274.         );
  4275.     }
  4276.    
  4277.    
  4278.     /*
  4279.     function setOverflowHidden(bool) {
  4280.         element.css('overflow', bool ? 'hidden' : '');
  4281.     }
  4282.     */
  4283.    
  4284.    
  4285.     function isEventDraggable(event) {
  4286.         return isEventEditable(event) && !opt('disableDragging');
  4287.     }
  4288.    
  4289.    
  4290.     function isEventResizable(event) { // but also need to make sure the seg.isEnd == true
  4291.         return isEventEditable(event) && !opt('disableResizing');
  4292.     }
  4293.    
  4294.    
  4295.     function isEventEditable(event) {
  4296.         return firstDefined(event.editable, (event.source || {}).editable, opt('editable'));
  4297.     }
  4298.    
  4299.    
  4300.    
  4301.     /* Event Data
  4302.     ------------------------------------------------------------------------------*/
  4303.    
  4304.    
  4305.     // report when view receives new events
  4306.     function reportEvents(events) { // events are already normalized at this point
  4307.         eventsByID = {};
  4308.         var i, len=events.length, event;
  4309.         for (i=0; i<len; i++) {
  4310.             event = events[i];
  4311.             if (eventsByID[event._id]) {
  4312.                 eventsByID[event._id].push(event);
  4313.             }else{
  4314.                 eventsByID[event._id] = [event];
  4315.             }
  4316.         }
  4317.     }
  4318.    
  4319.    
  4320.     // returns a Date object for an event's end
  4321.     function eventEnd(event) {
  4322.         return event.end ? cloneDate(event.end) : defaultEventEnd(event);
  4323.     }
  4324.    
  4325.    
  4326.    
  4327.     /* Event Elements
  4328.     ------------------------------------------------------------------------------*/
  4329.    
  4330.    
  4331.     // report when view creates an element for an event
  4332.     function reportEventElement(event, element) {
  4333.         eventElements.push(element);
  4334.         if (eventElementsByID[event._id]) {
  4335.             eventElementsByID[event._id].push(element);
  4336.         }else{
  4337.             eventElementsByID[event._id] = [element];
  4338.         }
  4339.     }
  4340.    
  4341.    
  4342.     function reportEventClear() {
  4343.         eventElements = [];
  4344.         eventElementsByID = {};
  4345.     }
  4346.    
  4347.    
  4348.     // attaches eventClick, eventMouseover, eventMouseout
  4349.     function eventElementHandlers(event, eventElement) {
  4350.         eventElement
  4351.             .click(function(ev) {
  4352.                 if (!eventElement.hasClass('ui-draggable-dragging') &&
  4353.                     !eventElement.hasClass('ui-resizable-resizing')) {
  4354.                         return trigger('eventClick', this, event, ev);
  4355.                     }
  4356.             })
  4357.             .hover(
  4358.                 function(ev) {
  4359.                     trigger('eventMouseover', this, event, ev);
  4360.                 },
  4361.                 function(ev) {
  4362.                     trigger('eventMouseout', this, event, ev);
  4363.                 }
  4364.             );
  4365.         // TODO: don't fire eventMouseover/eventMouseout *while* dragging is occuring (on subject element)
  4366.         // TODO: same for resizing
  4367.     }
  4368.    
  4369.    
  4370.     function showEvents(event, exceptElement) {
  4371.         eachEventElement(event, exceptElement, 'show');
  4372.     }
  4373.    
  4374.    
  4375.     function hideEvents(event, exceptElement) {
  4376.         eachEventElement(event, exceptElement, 'hide');
  4377.     }
  4378.    
  4379.    
  4380.     function eachEventElement(event, exceptElement, funcName) {
  4381.         var elements = eventElementsByID[event._id],
  4382.             i, len = elements.length;
  4383.         for (i=0; i<len; i++) {
  4384.             if (!exceptElement || elements[i][0] != exceptElement[0]) {
  4385.                 elements[i][funcName]();
  4386.             }
  4387.         }
  4388.     }
  4389.    
  4390.    
  4391.    
  4392.     /* Event Modification Reporting
  4393.     ---------------------------------------------------------------------------------*/
  4394.    
  4395.    
  4396.     function eventDrop(e, event, dayDelta, minuteDelta, allDay, ev, ui) {
  4397.         var oldAllDay = event.allDay;
  4398.         var eventId = event._id;
  4399.         moveEvents(eventsByID[eventId], dayDelta, minuteDelta, allDay);
  4400.         trigger(
  4401.             'eventDrop',
  4402.             e,
  4403.             event,
  4404.             dayDelta,
  4405.             minuteDelta,
  4406.             allDay,
  4407.             function() {
  4408.                 // TODO: investigate cases where this inverse technique might not work
  4409.                 moveEvents(eventsByID[eventId], -dayDelta, -minuteDelta, oldAllDay);
  4410.                 reportEventChange(eventId);
  4411.             },
  4412.             ev,
  4413.             ui
  4414.         );
  4415.         reportEventChange(eventId);
  4416.     }
  4417.    
  4418.    
  4419.     function eventResize(e, event, dayDelta, minuteDelta, ev, ui) {
  4420.         var eventId = event._id;
  4421.         elongateEvents(eventsByID[eventId], dayDelta, minuteDelta);
  4422.         trigger(
  4423.             'eventResize',
  4424.             e,
  4425.             event,
  4426.             dayDelta,
  4427.             minuteDelta,
  4428.             function() {
  4429.                 // TODO: investigate cases where this inverse technique might not work
  4430.                 elongateEvents(eventsByID[eventId], -dayDelta, -minuteDelta);
  4431.                 reportEventChange(eventId);
  4432.             },
  4433.             ev,
  4434.             ui
  4435.         );
  4436.         reportEventChange(eventId);
  4437.     }
  4438.    
  4439.    
  4440.    
  4441.     /* Event Modification Math
  4442.     ---------------------------------------------------------------------------------*/
  4443.    
  4444.    
  4445.     function moveEvents(events, dayDelta, minuteDelta, allDay) {
  4446.         minuteDelta = minuteDelta || 0;
  4447.         for (var e, len=events.length, i=0; i<len; i++) {
  4448.             e = events[i];
  4449.             if (allDay !== undefined) {
  4450.                 e.allDay = allDay;
  4451.             }
  4452.             addMinutes(addDays(e.start, dayDelta, true), minuteDelta);
  4453.             if (e.end) {
  4454.                 e.end = addMinutes(addDays(e.end, dayDelta, true), minuteDelta);
  4455.             }
  4456.             normalizeEvent(e, options);
  4457.         }
  4458.     }
  4459.    
  4460.    
  4461.     function elongateEvents(events, dayDelta, minuteDelta) {
  4462.         minuteDelta = minuteDelta || 0;
  4463.         for (var e, len=events.length, i=0; i<len; i++) {
  4464.             e = events[i];
  4465.             e.end = addMinutes(addDays(eventEnd(e), dayDelta, true), minuteDelta);
  4466.             normalizeEvent(e, options);
  4467.         }
  4468.     }
  4469.    
  4470.  
  4471. }
  4472.  
  4473. function DayEventRenderer() {
  4474.     var t = this;
  4475.  
  4476.    
  4477.     // exports
  4478.     t.renderDaySegs = renderDaySegs;
  4479.     t.resizableDayEvent = resizableDayEvent;
  4480.    
  4481.    
  4482.     // imports
  4483.     var opt = t.opt;
  4484.     var trigger = t.trigger;
  4485.     var isEventDraggable = t.isEventDraggable;
  4486.     var isEventResizable = t.isEventResizable;
  4487.     var eventEnd = t.eventEnd;
  4488.     var reportEventElement = t.reportEventElement;
  4489.     var showEvents = t.showEvents;
  4490.     var hideEvents = t.hideEvents;
  4491.     var eventResize = t.eventResize;
  4492.     var getRowCnt = t.getRowCnt;
  4493.     var getColCnt = t.getColCnt;
  4494.     var getColWidth = t.getColWidth;
  4495.     var allDayRow = t.allDayRow;
  4496.     var allDayBounds = t.allDayBounds;
  4497.     var colContentLeft = t.colContentLeft;
  4498.     var colContentRight = t.colContentRight;
  4499.     var dayOfWeekCol = t.dayOfWeekCol;
  4500.     var dateCell = t.dateCell;
  4501.     var compileDaySegs = t.compileDaySegs;
  4502.     var getDaySegmentContainer = t.getDaySegmentContainer;
  4503.     var bindDaySeg = t.bindDaySeg; //TODO: streamline this
  4504.     var formatDates = t.calendar.formatDates;
  4505.     var renderDayOverlay = t.renderDayOverlay;
  4506.     var clearOverlays = t.clearOverlays;
  4507.     var clearSelection = t.clearSelection;
  4508.    
  4509.    
  4510.    
  4511.     /* Rendering
  4512.     -----------------------------------------------------------------------------*/
  4513.    
  4514.    
  4515.     function renderDaySegs(segs, modifiedEventId) {
  4516.         var segmentContainer = getDaySegmentContainer();
  4517.         var rowDivs;
  4518.         var rowCnt = getRowCnt();
  4519.         var colCnt = getColCnt();
  4520.         var i = 0;
  4521.         var rowI;
  4522.         var levelI;
  4523.         var colHeights;
  4524.         var j;
  4525.         var segCnt = segs.length;
  4526.         var seg;
  4527.         var top;
  4528.         var k;
  4529.         segmentContainer[0].innerHTML = daySegHTML(segs); // faster than .html()
  4530.         daySegElementResolve(segs, segmentContainer.children());
  4531.         daySegElementReport(segs);
  4532.         daySegHandlers(segs, segmentContainer, modifiedEventId);
  4533.         daySegCalcHSides(segs);
  4534.         daySegSetWidths(segs);
  4535.         daySegCalcHeights(segs);
  4536.         rowDivs = getRowDivs();
  4537.         // set row heights, calculate event tops (in relation to row top)
  4538.         for (rowI=0; rowI<rowCnt; rowI++) {
  4539.             levelI = 0;
  4540.             colHeights = [];
  4541.             for (j=0; j<colCnt; j++) {
  4542.                 colHeights[j] = 0;
  4543.             }
  4544.             while (i<segCnt && (seg = segs[i]).row == rowI) {
  4545.                 // loop through segs in a row
  4546.                 top = arrayMax(colHeights.slice(seg.startCol, seg.endCol));
  4547.                 seg.top = top;
  4548.                 top += seg.outerHeight;
  4549.                 for (k=seg.startCol; k<seg.endCol; k++) {
  4550.                     colHeights[k] = top;
  4551.                 }
  4552.                 i++;
  4553.             }
  4554.             rowDivs[rowI].height(arrayMax(colHeights));
  4555.         }
  4556.         daySegSetTops(segs, getRowTops(rowDivs));
  4557.     }
  4558.    
  4559.    
  4560.     function renderTempDaySegs(segs, adjustRow, adjustTop) {
  4561.         var tempContainer = $("<div/>");
  4562.         var elements;
  4563.         var segmentContainer = getDaySegmentContainer();
  4564.         var i;
  4565.         var segCnt = segs.length;
  4566.         var element;
  4567.         tempContainer[0].innerHTML = daySegHTML(segs); // faster than .html()
  4568.         elements = tempContainer.children();
  4569.         segmentContainer.append(elements);
  4570.         daySegElementResolve(segs, elements);
  4571.         daySegCalcHSides(segs);
  4572.         daySegSetWidths(segs);
  4573.         daySegCalcHeights(segs);
  4574.         daySegSetTops(segs, getRowTops(getRowDivs()));
  4575.         elements = [];
  4576.         for (i=0; i<segCnt; i++) {
  4577.             element = segs[i].element;
  4578.             if (element) {
  4579.                 if (segs[i].row === adjustRow) {
  4580.                     element.css('top', adjustTop);
  4581.                 }
  4582.                 elements.push(element[0]);
  4583.             }
  4584.         }
  4585.         return $(elements);
  4586.     }
  4587.    
  4588.    
  4589.     function daySegHTML(segs) { // also sets seg.left and seg.outerWidth
  4590.         var rtl = opt('isRTL');
  4591.         var i;
  4592.         var segCnt=segs.length;
  4593.         var seg;
  4594.         var event;
  4595.         var url;
  4596.         var classes;
  4597.         var bounds = allDayBounds();
  4598.         var minLeft = bounds.left;
  4599.         var maxLeft = bounds.right;
  4600.         var leftCol;
  4601.         var rightCol;
  4602.         var left;
  4603.         var right;
  4604.         var skinCss;
  4605.         var html = '';
  4606.         // calculate desired position/dimensions, create html
  4607.         for (i=0; i<segCnt; i++) {
  4608.             seg = segs[i];
  4609.             event = seg.event;
  4610.             classes = ['fc-event', 'fc-event-skin', 'fc-event-hori'];
  4611.             if (isEventDraggable(event)) {
  4612.                 classes.push('fc-event-draggable');
  4613.             }
  4614.             if (rtl) {
  4615.                 if (seg.isStart) {
  4616.                     classes.push('fc-corner-right');
  4617.                 }
  4618.                 if (seg.isEnd) {
  4619.                     classes.push('fc-corner-left');
  4620.                 }
  4621.                 leftCol = dayOfWeekCol(seg.end.getDay()-1);
  4622.                 rightCol = dayOfWeekCol(seg.start.getDay());
  4623.                 left = seg.isEnd ? colContentLeft(leftCol) : minLeft;
  4624.                 right = seg.isStart ? colContentRight(rightCol) : maxLeft;
  4625.             }else{
  4626.                 if (seg.isStart) {
  4627.                     classes.push('fc-corner-left');
  4628.                 }
  4629.                 if (seg.isEnd) {
  4630.                     classes.push('fc-corner-right');
  4631.                 }
  4632.                 leftCol = dayOfWeekCol(seg.start.getDay());
  4633.                 rightCol = dayOfWeekCol(seg.end.getDay()-1);
  4634.                 left = seg.isStart ? colContentLeft(leftCol) : minLeft;
  4635.                 right = seg.isEnd ? colContentRight(rightCol) : maxLeft;
  4636.             }
  4637.             classes = classes.concat(event.className);
  4638.             if (event.source) {
  4639.                 classes = classes.concat(event.source.className || []);
  4640.             }
  4641.             url = event.url;
  4642.             skinCss = getSkinCss(event, opt);
  4643.             if (url) {
  4644.                 html += "<a href='" + htmlEscape(url) + "'";
  4645.             }else{
  4646.                 html += "<div";
  4647.             }
  4648.             html +=
  4649.                 " class='" + classes.join(' ') + "'" +
  4650.                 " style='position:absolute;z-index:8;left:"+left+"px;" + skinCss + "'" +
  4651.                 ">" +
  4652.                 "<div" +
  4653.                 " class='fc-event-inner fc-event-skin'" +
  4654.                 (skinCss ? " style='" + skinCss + "'" : '') +
  4655.                 ">";
  4656.             if (!event.allDay && seg.isStart) {
  4657.                 html +=
  4658.                     "<span class='fc-event-time'>" +
  4659.                     htmlEscape(formatDates(event.start, event.end, opt('timeFormat'))) +
  4660.                     "</span>";
  4661.             }
  4662.             html +=
  4663.                 "<span class='fc-event-title'>" + htmlEscape(event.title) + "</span>" +
  4664.                 "</div>";
  4665.             if (seg.isEnd && isEventResizable(event)) {
  4666.                 html +=
  4667.                     "<div class='ui-resizable-handle ui-resizable-" + (rtl ? 'w' : 'e') + "'>" +
  4668.                     "&nbsp;&nbsp;&nbsp;" + // makes hit area a lot better for IE6/7
  4669.                     "</div>";
  4670.             }
  4671.             html +=
  4672.                 "</" + (url ? "a" : "div" ) + ">";
  4673.             seg.left = left;
  4674.             seg.outerWidth = right - left;
  4675.             seg.startCol = leftCol;
  4676.             seg.endCol = rightCol + 1; // needs to be exclusive
  4677.         }
  4678.         return html;
  4679.     }
  4680.    
  4681.    
  4682.     function daySegElementResolve(segs, elements) { // sets seg.element
  4683.         var i;
  4684.         var segCnt = segs.length;
  4685.         var seg;
  4686.         var event;
  4687.         var element;
  4688.         var triggerRes;
  4689.         for (i=0; i<segCnt; i++) {
  4690.             seg = segs[i];
  4691.             event = seg.event;
  4692.             element = $(elements[i]); // faster than .eq()
  4693.             triggerRes = trigger('eventRender', event, event, element);
  4694.             if (triggerRes === false) {
  4695.                 element.remove();
  4696.             }else{
  4697.                 if (triggerRes && triggerRes !== true) {
  4698.                     triggerRes = $(triggerRes)
  4699.                         .css({
  4700.                             position: 'absolute',
  4701.                             left: seg.left
  4702.                         });
  4703.                     element.replaceWith(triggerRes);
  4704.                     element = triggerRes;
  4705.                 }
  4706.                 seg.element = element;
  4707.             }
  4708.         }
  4709.     }
  4710.    
  4711.    
  4712.     function daySegElementReport(segs) {
  4713.         var i;
  4714.         var segCnt = segs.length;
  4715.         var seg;
  4716.         var element;
  4717.         for (i=0; i<segCnt; i++) {
  4718.             seg = segs[i];
  4719.             element = seg.element;
  4720.             if (element) {
  4721.                 reportEventElement(seg.event, element);
  4722.             }
  4723.         }
  4724.     }
  4725.    
  4726.    
  4727.     function daySegHandlers(segs, segmentContainer, modifiedEventId) {
  4728.         var i;
  4729.         var segCnt = segs.length;
  4730.         var seg;
  4731.         var element;
  4732.         var event;
  4733.         // retrieve elements, run through eventRender callback, bind handlers
  4734.         for (i=0; i<segCnt; i++) {
  4735.             seg = segs[i];
  4736.             element = seg.element;
  4737.             if (element) {
  4738.                 event = seg.event;
  4739.                 if (event._id === modifiedEventId) {
  4740.                     bindDaySeg(event, element, seg);
  4741.                 }else{
  4742.                     element[0]._fci = i; // for lazySegBind
  4743.                 }
  4744.             }
  4745.         }
  4746.         lazySegBind(segmentContainer, segs, bindDaySeg);
  4747.     }
  4748.    
  4749.    
  4750.     function daySegCalcHSides(segs) { // also sets seg.key
  4751.         var i;
  4752.         var segCnt = segs.length;
  4753.         var seg;
  4754.         var element;
  4755.         var key, val;
  4756.         var hsideCache = {};
  4757.         // record event horizontal sides
  4758.         for (i=0; i<segCnt; i++) {
  4759.             seg = segs[i];
  4760.             element = seg.element;
  4761.             if (element) {
  4762.                 key = seg.key = cssKey(element[0]);
  4763.                 val = hsideCache[key];
  4764.                 if (val === undefined) {
  4765.                     val = hsideCache[key] = hsides(element, true);
  4766.                 }
  4767.                 seg.hsides = val;
  4768.             }
  4769.         }
  4770.     }
  4771.    
  4772.    
  4773.     function daySegSetWidths(segs) {
  4774.         var i;
  4775.         var segCnt = segs.length;
  4776.         var seg;
  4777.         var element;
  4778.         for (i=0; i<segCnt; i++) {
  4779.             seg = segs[i];
  4780.             element = seg.element;
  4781.             if (element) {
  4782.                 element[0].style.width = Math.max(0, seg.outerWidth - seg.hsides) + 'px';
  4783.             }
  4784.         }
  4785.     }
  4786.    
  4787.    
  4788.     function daySegCalcHeights(segs) {
  4789.         var i;
  4790.         var segCnt = segs.length;
  4791.         var seg;
  4792.         var element;
  4793.         var key, val;
  4794.         var vmarginCache = {};
  4795.         // record event heights
  4796.         for (i=0; i<segCnt; i++) {
  4797.             seg = segs[i];
  4798.             element = seg.element;
  4799.             if (element) {
  4800.                 key = seg.key; // created in daySegCalcHSides
  4801.                 val = vmarginCache[key];
  4802.                 if (val === undefined) {
  4803.                     val = vmarginCache[key] = vmargins(element);
  4804.                 }
  4805.                 seg.outerHeight = element[0].offsetHeight + val;
  4806.             }
  4807.         }
  4808.     }
  4809.    
  4810.    
  4811.     function getRowDivs() {
  4812.         var i;
  4813.         var rowCnt = getRowCnt();
  4814.         var rowDivs = [];
  4815.         for (i=0; i<rowCnt; i++) {
  4816.             rowDivs[i] = allDayRow(i)
  4817.                 .find('td:first div.fc-day-content > div'); // optimal selector?
  4818.         }
  4819.         return rowDivs;
  4820.     }
  4821.    
  4822.    
  4823.     function getRowTops(rowDivs) {
  4824.         var i;
  4825.         var rowCnt = rowDivs.length;
  4826.         var tops = [];
  4827.         for (i=0; i<rowCnt; i++) {
  4828.             tops[i] = rowDivs[i][0].offsetTop; // !!?? but this means the element needs position:relative if in a table cell!!!!
  4829.         }
  4830.         return tops;
  4831.     }
  4832.    
  4833.    
  4834.     function daySegSetTops(segs, rowTops) { // also triggers eventAfterRender
  4835.         var i;
  4836.         var segCnt = segs.length;
  4837.         var seg;
  4838.         var element;
  4839.         var event;
  4840.         for (i=0; i<segCnt; i++) {
  4841.             seg = segs[i];
  4842.             element = seg.element;
  4843.             if (element) {
  4844.                 element[0].style.top = rowTops[seg.row] + (seg.top||0) + 'px';
  4845.                 event = seg.event;
  4846.                 trigger('eventAfterRender', event, event, element);
  4847.             }
  4848.         }
  4849.     }
  4850.    
  4851.    
  4852.    
  4853.     /* Resizing
  4854.     -----------------------------------------------------------------------------------*/
  4855.    
  4856.    
  4857.     function resizableDayEvent(event, element, seg) {
  4858.         var rtl = opt('isRTL');
  4859.         var direction = rtl ? 'w' : 'e';
  4860.         var handle = element.find('div.ui-resizable-' + direction);
  4861.         var isResizing = false;
  4862.        
  4863.         // TODO: look into using jquery-ui mouse widget for this stuff
  4864.         disableTextSelection(element); // prevent native <a> selection for IE
  4865.         element
  4866.             .mousedown(function(ev) { // prevent native <a> selection for others
  4867.                 ev.preventDefault();
  4868.             })
  4869.             .click(function(ev) {
  4870.                 if (isResizing) {
  4871.                     ev.preventDefault(); // prevent link from being visited (only method that worked in IE6)
  4872.                     ev.stopImmediatePropagation(); // prevent fullcalendar eventClick handler from being called
  4873.                                                    // (eventElementHandlers needs to be bound after resizableDayEvent)
  4874.                 }
  4875.             });
  4876.        
  4877.         handle.mousedown(function(ev) {
  4878.             if (ev.which != 1) {
  4879.                 return; // needs to be left mouse button
  4880.             }
  4881.             isResizing = true;
  4882.             var hoverListener = t.getHoverListener();
  4883.             var rowCnt = getRowCnt();
  4884.             var colCnt = getColCnt();
  4885.             var dis = rtl ? -1 : 1;
  4886.             var dit = rtl ? colCnt-1 : 0;
  4887.             var elementTop = element.css('top');
  4888.             var dayDelta;
  4889.             var helpers;
  4890.             var eventCopy = $.extend({}, event);
  4891.             var minCell = dateCell(event.start);
  4892.             clearSelection();
  4893.             $('body')
  4894.                 .css('cursor', direction + '-resize')
  4895.                 .one('mouseup', mouseup);
  4896.             trigger('eventResizeStart', this, event, ev);
  4897.             hoverListener.start(function(cell, origCell) {
  4898.                 if (cell) {
  4899.                     var r = Math.max(minCell.row, cell.row);
  4900.                     var c = cell.col;
  4901.                     if (rowCnt == 1) {
  4902.                         r = 0; // hack for all-day area in agenda views
  4903.                     }
  4904.                     if (r == minCell.row) {
  4905.                         if (rtl) {
  4906.                             c = Math.min(minCell.col, c);
  4907.                         }else{
  4908.                             c = Math.max(minCell.col, c);
  4909.                         }
  4910.                     }
  4911.                     dayDelta = (r*7 + c*dis+dit) - (origCell.row*7 + origCell.col*dis+dit);
  4912.                     var newEnd = addDays(eventEnd(event), dayDelta, true);
  4913.                     if (dayDelta) {
  4914.                         eventCopy.end = newEnd;
  4915.                         var oldHelpers = helpers;
  4916.                         helpers = renderTempDaySegs(compileDaySegs([eventCopy]), seg.row, elementTop);
  4917.                         helpers.find('*').css('cursor', direction + '-resize');
  4918.                         if (oldHelpers) {
  4919.                             oldHelpers.remove();
  4920.                         }
  4921.                         hideEvents(event);
  4922.                     }else{
  4923.                         if (helpers) {
  4924.                             showEvents(event);
  4925.                             helpers.remove();
  4926.                             helpers = null;
  4927.                         }
  4928.                     }
  4929.                     clearOverlays();
  4930.                     renderDayOverlay(event.start, addDays(cloneDate(newEnd), 1)); // coordinate grid already rebuild at hoverListener.start
  4931.                 }
  4932.             }, ev);
  4933.            
  4934.             function mouseup(ev) {
  4935.                 trigger('eventResizeStop', this, event, ev);
  4936.                 $('body').css('cursor', '');
  4937.                 hoverListener.stop();
  4938.                 clearOverlays();
  4939.                 if (dayDelta) {
  4940.                     eventResize(this, event, dayDelta, 0, ev);
  4941.                     // event redraw will clear helpers
  4942.                 }
  4943.                 // otherwise, the drag handler already restored the old events
  4944.                
  4945.                 setTimeout(function() { // make this happen after the element's click event
  4946.                     isResizing = false;
  4947.                 },0);
  4948.             }
  4949.            
  4950.         });
  4951.     }
  4952.    
  4953.  
  4954. }
  4955.  
  4956. //BUG: unselect needs to be triggered when events are dragged+dropped
  4957.  
  4958. function SelectionManager() {
  4959.     var t = this;
  4960.    
  4961.    
  4962.     // exports
  4963.     t.select = select;
  4964.     t.unselect = unselect;
  4965.     t.reportSelection = reportSelection;
  4966.     t.daySelectionMousedown = daySelectionMousedown;
  4967.    
  4968.    
  4969.     // imports
  4970.     var opt = t.opt;
  4971.     var trigger = t.trigger;
  4972.     var defaultSelectionEnd = t.defaultSelectionEnd;
  4973.     var renderSelection = t.renderSelection;
  4974.     var clearSelection = t.clearSelection;
  4975.    
  4976.    
  4977.     // locals
  4978.     var selected = false;
  4979.  
  4980.  
  4981.  
  4982.     // unselectAuto
  4983.     if (opt('selectable') && opt('unselectAuto')) {
  4984.         $(document).mousedown(function(ev) {
  4985.             var ignore = opt('unselectCancel');
  4986.             if (ignore) {
  4987.                 if ($(ev.target).parents(ignore).length) { // could be optimized to stop after first match
  4988.                     return;
  4989.                 }
  4990.             }
  4991.             unselect(ev);
  4992.         });
  4993.     }
  4994.    
  4995.  
  4996.     function select(startDate, endDate, allDay) {
  4997.         unselect();
  4998.         if (!endDate) {
  4999.             endDate = defaultSelectionEnd(startDate, allDay);
  5000.         }
  5001.         renderSelection(startDate, endDate, allDay);
  5002.         reportSelection(startDate, endDate, allDay);
  5003.     }
  5004.    
  5005.    
  5006.     function unselect(ev) {
  5007.         if (selected) {
  5008.             selected = false;
  5009.             clearSelection();
  5010.             trigger('unselect', null, ev);
  5011.         }
  5012.     }
  5013.    
  5014.    
  5015.     function reportSelection(startDate, endDate, allDay, ev) {
  5016.         selected = true;
  5017.         trigger('select', null, startDate, endDate, allDay, ev);
  5018.     }
  5019.    
  5020.    
  5021.     function daySelectionMousedown(ev) { // not really a generic manager method, oh well
  5022.         var cellDate = t.cellDate;
  5023.         var cellIsAllDay = t.cellIsAllDay;
  5024.         var hoverListener = t.getHoverListener();
  5025.         var reportDayClick = t.reportDayClick; // this is hacky and sort of weird
  5026.         if (ev.which == 1 && opt('selectable')) { // which==1 means left mouse button
  5027.             unselect(ev);
  5028.             var _mousedownElement = this;
  5029.             var dates;
  5030.             hoverListener.start(function(cell, origCell) { // TODO: maybe put cellDate/cellIsAllDay info in cell
  5031.                 clearSelection();
  5032.                 if (cell && cellIsAllDay(cell)) {
  5033.                     dates = [ cellDate(origCell), cellDate(cell) ].sort(cmp);
  5034.                     renderSelection(dates[0], dates[1], true);
  5035.                 }else{
  5036.                     dates = null;
  5037.                 }
  5038.             }, ev);
  5039.             $(document).one('mouseup', function(ev) {
  5040.                 hoverListener.stop();
  5041.                 if (dates) {
  5042.                     if (+dates[0] == +dates[1]) {
  5043.                         reportDayClick(dates[0], true, ev);
  5044.                     }
  5045.                     reportSelection(dates[0], dates[1], true, ev);
  5046.                 }
  5047.             });
  5048.         }
  5049.     }
  5050.  
  5051.  
  5052. }
  5053.  
  5054. function OverlayManager() {
  5055.     var t = this;
  5056.    
  5057.    
  5058.     // exports
  5059.     t.renderOverlay = renderOverlay;
  5060.     t.clearOverlays = clearOverlays;
  5061.    
  5062.    
  5063.     // locals
  5064.     var usedOverlays = [];
  5065.     var unusedOverlays = [];
  5066.    
  5067.    
  5068.     function renderOverlay(rect, parent) {
  5069.         var e = unusedOverlays.shift();
  5070.         if (!e) {
  5071.             e = $("<div class='fc-cell-overlay' style='position:absolute;z-index:3'/>");
  5072.         }
  5073.         if (e[0].parentNode != parent[0]) {
  5074.             e.appendTo(parent);
  5075.         }
  5076.         usedOverlays.push(e.css(rect).show());
  5077.         return e;
  5078.     }
  5079.    
  5080.  
  5081.     function clearOverlays() {
  5082.         var e;
  5083.         while (e = usedOverlays.shift()) {
  5084.             unusedOverlays.push(e.hide().unbind());
  5085.         }
  5086.     }
  5087.  
  5088.  
  5089. }
  5090.  
  5091. function CoordinateGrid(buildFunc) {
  5092.  
  5093.     var t = this;
  5094.     var rows;
  5095.     var cols;
  5096.    
  5097.    
  5098.     t.build = function() {
  5099.         rows = [];
  5100.         cols = [];
  5101.         buildFunc(rows, cols);
  5102.     };
  5103.    
  5104.    
  5105.     t.cell = function(x, y) {
  5106.         var rowCnt = rows.length;
  5107.         var colCnt = cols.length;
  5108.         var i, r=-1, c=-1;
  5109.         for (i=0; i<rowCnt; i++) {
  5110.             if (y >= rows[i][0] && y < rows[i][1]) {
  5111.                 r = i;
  5112.                 break;
  5113.             }
  5114.         }
  5115.         for (i=0; i<colCnt; i++) {
  5116.             if (x >= cols[i][0] && x < cols[i][1]) {
  5117.                 c = i;
  5118.                 break;
  5119.             }
  5120.         }
  5121.         return (r>=0 && c>=0) ? { row:r, col:c } : null;
  5122.     };
  5123.    
  5124.    
  5125.     t.rect = function(row0, col0, row1, col1, originElement) { // row1,col1 is inclusive
  5126.         var origin = originElement.offset();
  5127.         return {
  5128.             top: rows[row0][0] - origin.top,
  5129.             left: cols[col0][0] - origin.left,
  5130.             width: cols[col1][1] - cols[col0][0],
  5131.             height: rows[row1][1] - rows[row0][0]
  5132.         };
  5133.     };
  5134.  
  5135. }
  5136.  
  5137. function HoverListener(coordinateGrid) {
  5138.  
  5139.  
  5140.     var t = this;
  5141.     var bindType;
  5142.     var change;
  5143.     var firstCell;
  5144.     var cell;
  5145.    
  5146.    
  5147.     t.start = function(_change, ev, _bindType) {
  5148.         change = _change;
  5149.         firstCell = cell = null;
  5150.         coordinateGrid.build();
  5151.         mouse(ev);
  5152.         bindType = _bindType || 'mousemove';
  5153.         $(document).bind(bindType, mouse);
  5154.     };
  5155.    
  5156.    
  5157.     function mouse(ev) {
  5158.         var newCell = coordinateGrid.cell(ev.pageX, ev.pageY);
  5159.         if (!newCell != !cell || newCell && (newCell.row != cell.row || newCell.col != cell.col)) {
  5160.             if (newCell) {
  5161.                 if (!firstCell) {
  5162.                     firstCell = newCell;
  5163.                 }
  5164.                 change(newCell, firstCell, newCell.row-firstCell.row, newCell.col-firstCell.col);
  5165.             }else{
  5166.                 change(newCell, firstCell);
  5167.             }
  5168.             cell = newCell;
  5169.         }
  5170.     }
  5171.    
  5172.    
  5173.     t.stop = function() {
  5174.         $(document).unbind(bindType, mouse);
  5175.         return cell;
  5176.     };
  5177.    
  5178.    
  5179. }
  5180.  
  5181. function HorizontalPositionCache(getElement) {
  5182.  
  5183.     var t = this,
  5184.         elements = {},
  5185.         lefts = {},
  5186.         rights = {};
  5187.        
  5188.     function e(i) {
  5189.         return elements[i] = elements[i] || getElement(i);
  5190.     }
  5191.    
  5192.     t.left = function(i) {
  5193.         return lefts[i] = lefts[i] === undefined ? e(i).position().left : lefts[i];
  5194.     };
  5195.    
  5196.     t.right = function(i) {
  5197.         return rights[i] = rights[i] === undefined ? t.left(i) + e(i).width() : rights[i];
  5198.     };
  5199.    
  5200.     t.clear = function() {
  5201.         elements = {};
  5202.         lefts = {};
  5203.         rights = {};
  5204.     };
  5205.    
  5206. }
  5207.  
  5208. })(jQuery);
Add Comment
Please, Sign In to add comment