Pastebin launched a little side project called VERYVIRAL.com, check it out ;-) Want more features on Pastebin? Sign Up, it's FREE!
Guest

fullcalendar

By: a guest on Jun 14th, 2011  |  syntax: JavaScript  |  size: 126.43 KB  |  views: 225  |  expires: Never
download  |  raw  |  embed  |  report abuse  |  print
Text below is selected. Please press Ctrl+C to copy to your clipboard. (⌘+C on Mac)
  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.