[ Index ]

PHP Cross Reference of vtigercrm-6.1.0

title

Body

[close]

/libraries/fullcalendar/ -> fullcalendar.js (source)

   1  /**
   2   * @preserve
   3   * FullCalendar v1.5.3
   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: Mon Feb 6 22:40:40 2012 -0800
  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.3" };
 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[13]) {
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          if (m[14]) {
1435              var offset = Number(m[16]) * 60 + (m[18] ? Number(m[18]) : 0);
1436              offset *= m[15] == '-' ? 1 : -1;
1437              date = new Date(+date + (offset * 60 * 1000));
1438          }
1439      }
1440      return date;
1441  }
1442  
1443  
1444  function parseTime(s) { // returns minutes since start of day
1445      if (typeof s == 'number') { // an hour
1446          return s * 60;
1447      }
1448      if (typeof s == 'object') { // a Date object
1449          return s.getHours() * 60 + s.getMinutes();
1450      }
1451      var m = s.match(/(\d+)(?::(\d+))?\s*(\w+)?/);
1452      if (m) {
1453          var h = parseInt(m[1], 10);
1454          if (m[3]) {
1455              h %= 12;
1456              if (m[3].toLowerCase().charAt(0) == 'p') {
1457                  h += 12;
1458              }
1459          }
1460          return h * 60 + (m[2] ? parseInt(m[2], 10) : 0);
1461      }
1462  }
1463  
1464  
1465  
1466  /* Date Formatting
1467  -----------------------------------------------------------------------------*/
1468  // TODO: use same function formatDate(date, [date2], format, [options])
1469  
1470  
1471  function formatDate(date, format, options) {
1472      return formatDates(date, null, format, options);
1473  }
1474  
1475  
1476  function formatDates(date1, date2, format, options) {
1477      options = options || defaults;
1478      var date = date1,
1479          otherDate = date2,
1480          i, len = format.length, c,
1481          i2, formatter,
1482          res = '';
1483      for (i=0; i<len; i++) {
1484          c = format.charAt(i);
1485          if (c == "'") {
1486              for (i2=i+1; i2<len; i2++) {
1487                  if (format.charAt(i2) == "'") {
1488                      if (date) {
1489                          if (i2 == i+1) {
1490                              res += "'";
1491                          }else{
1492                              res += format.substring(i+1, i2);
1493                          }
1494                          i = i2;
1495                      }
1496                      break;
1497                  }
1498              }
1499          }
1500          else if (c == '(') {
1501              for (i2=i+1; i2<len; i2++) {
1502                  if (format.charAt(i2) == ')') {
1503                      var subres = formatDate(date, format.substring(i+1, i2), options);
1504                      if (parseInt(subres.replace(/\D/, ''), 10)) {
1505                          res += subres;
1506                      }
1507                      i = i2;
1508                      break;
1509                  }
1510              }
1511          }
1512          else if (c == '[') {
1513              for (i2=i+1; i2<len; i2++) {
1514                  if (format.charAt(i2) == ']') {
1515                      var subformat = format.substring(i+1, i2);
1516                      var subres = formatDate(date, subformat, options);
1517                      if (subres != formatDate(otherDate, subformat, options)) {
1518                          res += subres;
1519                      }
1520                      i = i2;
1521                      break;
1522                  }
1523              }
1524          }
1525          else if (c == '{') {
1526              date = date2;
1527              otherDate = date1;
1528          }
1529          else if (c == '}') {
1530              date = date1;
1531              otherDate = date2;
1532          }
1533          else {
1534              for (i2=len; i2>i; i2--) {
1535                  if (formatter = dateFormatters[format.substring(i, i2)]) {
1536                      if (date) {
1537                          res += formatter(date, options);
1538                      }
1539                      i = i2 - 1;
1540                      break;
1541                  }
1542              }
1543              if (i2 == i) {
1544                  if (date) {
1545                      res += c;
1546                  }
1547              }
1548          }
1549      }
1550      return res;
1551  };
1552  
1553  
1554  var dateFormatters = {
1555      s    : function(d)    { return d.getSeconds() },
1556      ss    : function(d)    { return zeroPad(d.getSeconds()) },
1557      m    : function(d)    { return d.getMinutes() },
1558      mm    : function(d)    { return zeroPad(d.getMinutes()) },
1559      h    : function(d)    { return d.getHours() % 12 || 12 },
1560      hh    : function(d)    { return zeroPad(d.getHours() % 12 || 12) },
1561      H    : function(d)    { return d.getHours() },
1562      HH    : function(d)    { return zeroPad(d.getHours()) },
1563      d    : function(d)    { return d.getDate() },
1564      dd    : function(d)    { return zeroPad(d.getDate()) },
1565      ddd    : function(d,o)    { return o.dayNamesShort[d.getDay()] },
1566      dddd: function(d,o)    { return o.dayNames[d.getDay()] },
1567      M    : function(d)    { return d.getMonth() + 1 },
1568      MM    : function(d)    { return zeroPad(d.getMonth() + 1) },
1569      MMM    : function(d,o)    { return o.monthNamesShort[d.getMonth()] },
1570      MMMM: function(d,o)    { return o.monthNames[d.getMonth()] },
1571      yy    : function(d)    { return (d.getFullYear()+'').substring(2) },
1572      yyyy: function(d)    { return d.getFullYear() },
1573      t    : function(d)    { return d.getHours() < 12 ? 'a' : 'p' },
1574      tt    : function(d)    { return d.getHours() < 12 ? 'am' : 'pm' },
1575      T    : function(d)    { return d.getHours() < 12 ? 'A' : 'P' },
1576      TT    : function(d)    { return d.getHours() < 12 ? 'AM' : 'PM' },
1577      u    : function(d)    { return formatDate(d, "yyyy-MM-dd'T'HH:mm:ss'Z'") },
1578      S    : function(d)    {
1579          var date = d.getDate();
1580          if (date > 10 && date < 20) {
1581              return 'th';
1582          }
1583          return ['st', 'nd', 'rd'][date%10-1] || 'th';
1584      }
1585  };
1586  
1587  
1588  
1589  fc.applyAll = applyAll;
1590  
1591  
1592  /* Event Date Math
1593  -----------------------------------------------------------------------------*/
1594  
1595  
1596  function exclEndDay(event) {
1597      if (event.end) {
1598          return _exclEndDay(event.end, event.allDay);
1599      }else{
1600          return addDays(cloneDate(event.start), 1);
1601      }
1602  }
1603  
1604  
1605  function _exclEndDay(end, allDay) {
1606      end = cloneDate(end);
1607      return allDay || end.getHours() || end.getMinutes() ? addDays(end, 1) : clearTime(end);
1608  }
1609  
1610  
1611  function segCmp(a, b) {
1612      return (b.msLength - a.msLength) * 100 + (a.event.start - b.event.start);
1613  }
1614  
1615  
1616  function segsCollide(seg1, seg2) {
1617      return seg1.end > seg2.start && seg1.start < seg2.end;
1618  }
1619  
1620  
1621  
1622  /* Event Sorting
1623  -----------------------------------------------------------------------------*/
1624  
1625  
1626  // event rendering utilities
1627  function sliceSegs(events, visEventEnds, start, end) {
1628      var segs = [],
1629          i, len=events.length, event,
1630          eventStart, eventEnd,
1631          segStart, segEnd,
1632          isStart, isEnd;
1633      for (i=0; i<len; i++) {
1634          event = events[i];
1635          eventStart = event.start;
1636          eventEnd = visEventEnds[i];
1637          if (eventEnd > start && eventStart < end) {
1638              if (eventStart < start) {
1639                  segStart = cloneDate(start);
1640                  isStart = false;
1641              }else{
1642                  segStart = eventStart;
1643                  isStart = true;
1644              }
1645              if (eventEnd > end) {
1646                  segEnd = cloneDate(end);
1647                  isEnd = false;
1648              }else{
1649                  segEnd = eventEnd;
1650                  isEnd = true;
1651              }
1652              segs.push({
1653                  event: event,
1654                  start: segStart,
1655                  end: segEnd,
1656                  isStart: isStart,
1657                  isEnd: isEnd,
1658                  msLength: segEnd - segStart
1659              });
1660          }
1661      } 
1662      return segs.sort(segCmp);
1663  }
1664  
1665  
1666  // event rendering calculation utilities
1667  function stackSegs(segs) {
1668      var levels = [],
1669          i, len = segs.length, seg,
1670          j, collide, k;
1671      for (i=0; i<len; i++) {
1672          seg = segs[i];
1673          j = 0; // the level index where seg should belong
1674          while (true) {
1675              collide = false;
1676              if (levels[j]) {
1677                  for (k=0; k<levels[j].length; k++) {
1678                      if (segsCollide(levels[j][k], seg)) {
1679                          collide = true;
1680                          break;
1681                      }
1682                  }
1683              }
1684              if (collide) {
1685                  j++;
1686              }else{
1687                  break;
1688              }
1689          }
1690          if (levels[j]) {
1691              levels[j].push(seg);
1692          }else{
1693              levels[j] = [seg];
1694          }
1695      }
1696      return levels;
1697  }
1698  
1699  
1700  
1701  /* Event Element Binding
1702  -----------------------------------------------------------------------------*/
1703  
1704  
1705  function lazySegBind(container, segs, bindHandlers) {
1706      container.unbind('mouseover').mouseover(function(ev) {
1707          var parent=ev.target, e,
1708              i, seg;
1709          while (parent != this) {
1710              e = parent;
1711              parent = parent.parentNode;
1712          }
1713          if ((i = e._fci) !== undefined) {
1714              e._fci = undefined;
1715              seg = segs[i];
1716              bindHandlers(seg.event, seg.element, seg);
1717              $(ev.target).trigger(ev);
1718          }
1719          ev.stopPropagation();
1720      });
1721  }
1722  
1723  
1724  
1725  /* Element Dimensions
1726  -----------------------------------------------------------------------------*/
1727  
1728  
1729  function setOuterWidth(element, width, includeMargins) {
1730      for (var i=0, e; i<element.length; i++) {
1731          e = $(element[i]);
1732          e.width(Math.max(0, width - hsides(e, includeMargins)));
1733      }
1734  }
1735  
1736  
1737  function setOuterHeight(element, height, includeMargins) {
1738      for (var i=0, e; i<element.length; i++) {
1739          e = $(element[i]);
1740          e.height(Math.max(0, height - vsides(e, includeMargins)));
1741      }
1742  }
1743  
1744  
1745  // TODO: curCSS has been deprecated (jQuery 1.4.3 - 10/16/2010)
1746  
1747  
1748  function hsides(element, includeMargins) {
1749      return hpadding(element) + hborders(element) + (includeMargins ? hmargins(element) : 0);
1750  }
1751  
1752  
1753  function hpadding(element) {
1754      return (parseFloat($.curCSS(element[0], 'paddingLeft', true)) || 0) +
1755             (parseFloat($.curCSS(element[0], 'paddingRight', true)) || 0);
1756  }
1757  
1758  
1759  function hmargins(element) {
1760      return (parseFloat($.curCSS(element[0], 'marginLeft', true)) || 0) +
1761             (parseFloat($.curCSS(element[0], 'marginRight', true)) || 0);
1762  }
1763  
1764  
1765  function hborders(element) {
1766      return (parseFloat($.curCSS(element[0], 'borderLeftWidth', true)) || 0) +
1767             (parseFloat($.curCSS(element[0], 'borderRightWidth', true)) || 0);
1768  }
1769  
1770  
1771  function vsides(element, includeMargins) {
1772      return vpadding(element) +  vborders(element) + (includeMargins ? vmargins(element) : 0);
1773  }
1774  
1775  
1776  function vpadding(element) {
1777      return (parseFloat($.curCSS(element[0], 'paddingTop', true)) || 0) +
1778             (parseFloat($.curCSS(element[0], 'paddingBottom', true)) || 0);
1779  }
1780  
1781  
1782  function vmargins(element) {
1783      return (parseFloat($.curCSS(element[0], 'marginTop', true)) || 0) +
1784             (parseFloat($.curCSS(element[0], 'marginBottom', true)) || 0);
1785  }
1786  
1787  
1788  function vborders(element) {
1789      return (parseFloat($.curCSS(element[0], 'borderTopWidth', true)) || 0) +
1790             (parseFloat($.curCSS(element[0], 'borderBottomWidth', true)) || 0);
1791  }
1792  
1793  
1794  function setMinHeight(element, height) {
1795      height = (typeof height == 'number' ? height + 'px' : height);
1796      element.each(function(i, _element) {
1797          _element.style.cssText += ';min-height:' + height + ';_height:' + height;
1798          // why can't we just use .css() ? i forget
1799      });
1800  }
1801  
1802  
1803  
1804  /* Misc Utils
1805  -----------------------------------------------------------------------------*/
1806  
1807  
1808  //TODO: arraySlice
1809  //TODO: isFunction, grep ?
1810  
1811  
1812  function noop() { }
1813  
1814  
1815  function cmp(a, b) {
1816      return a - b;
1817  }
1818  
1819  
1820  function arrayMax(a) {
1821      return Math.max.apply(Math, a);
1822  }
1823  
1824  
1825  function zeroPad(n) {
1826      return (n < 10 ? '0' : '') + n;
1827  }
1828  
1829  
1830  function smartProperty(obj, name) { // get a camel-cased/namespaced property of an object
1831      if (obj[name] !== undefined) {
1832          return obj[name];
1833      }
1834      var parts = name.split(/(?=[A-Z])/),
1835          i=parts.length-1, res;
1836      for (; i>=0; i--) {
1837          res = obj[parts[i].toLowerCase()];
1838          if (res !== undefined) {
1839              return res;
1840          }
1841      }
1842      return obj[''];
1843  }
1844  
1845  
1846  function htmlEscape(s) {
1847      return s.replace(/&/g, '&amp;')
1848          .replace(/</g, '&lt;')
1849          .replace(/>/g, '&gt;')
1850          .replace(/'/g, '&#039;')
1851          .replace(/"/g, '&quot;')
1852          .replace(/\n/g, '<br />');
1853  }
1854  
1855  
1856  function cssKey(_element) {
1857      return _element.id + '/' + _element.className + '/' + _element.style.cssText.replace(/(^|;)\s*(top|left|width|height)\s*:[^;]*/ig, '');
1858  }
1859  
1860  
1861  function disableTextSelection(element) {
1862      element
1863          .attr('unselectable', 'on')
1864          .css('MozUserSelect', 'none')
1865          .bind('selectstart.ui', function() { return false; });
1866  }
1867  
1868  
1869  /*
1870  function enableTextSelection(element) {
1871      element
1872          .attr('unselectable', 'off')
1873          .css('MozUserSelect', '')
1874          .unbind('selectstart.ui');
1875  }
1876  */
1877  
1878  
1879  function markFirstLast(e) {
1880      e.children()
1881          .removeClass('fc-first fc-last')
1882          .filter(':first-child')
1883              .addClass('fc-first')
1884          .end()
1885          .filter(':last-child')
1886              .addClass('fc-last');
1887  }
1888  
1889  
1890  function setDayID(cell, date) {
1891      cell.each(function(i, _cell) {
1892          _cell.className = _cell.className.replace(/^fc-\w*/, 'fc-' + dayIDs[date.getDay()]);
1893          // TODO: make a way that doesn't rely on order of classes
1894      });
1895  }
1896  
1897  
1898  function getSkinCss(event, opt) {
1899      var source = event.source || {};
1900      var eventColor = event.color;
1901      var sourceColor = source.color;
1902      var optionColor = opt('eventColor');
1903      var backgroundColor =
1904          event.backgroundColor ||
1905          eventColor ||
1906          source.backgroundColor ||
1907          sourceColor ||
1908          opt('eventBackgroundColor') ||
1909          optionColor;
1910      var borderColor =
1911          event.borderColor ||
1912          eventColor ||
1913          source.borderColor ||
1914          sourceColor ||
1915          opt('eventBorderColor') ||
1916          optionColor;
1917      var textColor =
1918          event.textColor ||
1919          source.textColor ||
1920          opt('eventTextColor');
1921      var statements = [];
1922      if (backgroundColor) {
1923          statements.push('background-color:' + backgroundColor);
1924      }
1925      if (borderColor) {
1926          statements.push('border-color:' + borderColor);
1927      }
1928      if (textColor) {
1929          statements.push('color:' + textColor);
1930      }
1931      return statements.join(';');
1932  }
1933  
1934  
1935  function applyAll(functions, thisObj, args) {
1936      if ($.isFunction(functions)) {
1937          functions = [ functions ];
1938      }
1939      if (functions) {
1940          var i;
1941          var ret;
1942          for (i=0; i<functions.length; i++) {
1943              ret = functions[i].apply(thisObj, args) || ret;
1944          }
1945          return ret;
1946      }
1947  }
1948  
1949  
1950  function firstDefined() {
1951      for (var i=0; i<arguments.length; i++) {
1952          if (arguments[i] !== undefined) {
1953              return arguments[i];
1954          }
1955      }
1956  }
1957  
1958  
1959  
1960  fcViews.month = MonthView;
1961  
1962  function MonthView(element, calendar) {
1963      var t = this;
1964      
1965      
1966      // exports
1967      t.render = render;
1968      
1969      
1970      // imports
1971      BasicView.call(t, element, calendar, 'month');
1972      var opt = t.opt;
1973      var renderBasic = t.renderBasic;
1974      var formatDate = calendar.formatDate;
1975      
1976      
1977      
1978  	function render(date, delta) {
1979          if (delta) {
1980              addMonths(date, delta);
1981              date.setDate(1);
1982          }
1983          var start = cloneDate(date, true);
1984          start.setDate(1);
1985          var end = addMonths(cloneDate(start), 1);
1986          var visStart = cloneDate(start);
1987          var visEnd = cloneDate(end);
1988          var firstDay = opt('firstDay');
1989          var nwe = opt('weekends') ? 0 : 1;
1990          if (nwe) {
1991              skipWeekend(visStart);
1992              skipWeekend(visEnd, -1, true);
1993          }
1994          addDays(visStart, -((visStart.getDay() - Math.max(firstDay, nwe) + 7) % 7));
1995          addDays(visEnd, (7 - visEnd.getDay() + Math.max(firstDay, nwe)) % 7);
1996          var rowCnt = Math.round((visEnd - visStart) / (DAY_MS * 7));
1997          if (opt('weekMode') == 'fixed') {
1998              addDays(visEnd, (6 - rowCnt) * 7);
1999              rowCnt = 6;
2000          }
2001          t.title = formatDate(start, opt('titleFormat'));
2002          t.start = start;
2003          t.end = end;
2004          t.visStart = visStart;
2005          t.visEnd = visEnd;
2006          renderBasic(6, rowCnt, nwe ? 5 : 7, true);
2007      }
2008      
2009      
2010  }
2011  
2012  fcViews.basicWeek = BasicWeekView;
2013  
2014  function BasicWeekView(element, calendar) {
2015      var t = this;
2016      
2017      
2018      // exports
2019      t.render = render;
2020      
2021      
2022      // imports
2023      BasicView.call(t, element, calendar, 'basicWeek');
2024      var opt = t.opt;
2025      var renderBasic = t.renderBasic;
2026      var formatDates = calendar.formatDates;
2027      
2028      
2029      
2030  	function render(date, delta) {
2031          if (delta) {
2032              addDays(date, delta * 7);
2033          }
2034          var start = addDays(cloneDate(date), -((date.getDay() - opt('firstDay') + 7) % 7));
2035          var end = addDays(cloneDate(start), 7);
2036          var visStart = cloneDate(start);
2037          var visEnd = cloneDate(end);
2038          var weekends = opt('weekends');
2039          if (!weekends) {
2040              skipWeekend(visStart);
2041              skipWeekend(visEnd, -1, true);
2042          }
2043          t.title = formatDates(
2044              visStart,
2045              addDays(cloneDate(visEnd), -1),
2046              opt('titleFormat')
2047          );
2048          t.start = start;
2049          t.end = end;
2050          t.visStart = visStart;
2051          t.visEnd = visEnd;
2052          renderBasic(1, 1, weekends ? 7 : 5, false);
2053      }
2054      
2055      
2056  }
2057  
2058  fcViews.basicDay = BasicDayView;
2059  
2060  //TODO: when calendar's date starts out on a weekend, shouldn't happen
2061  
2062  
2063  function BasicDayView(element, calendar) {
2064      var t = this;
2065      
2066      
2067      // exports
2068      t.render = render;
2069      
2070      
2071      // imports
2072      BasicView.call(t, element, calendar, 'basicDay');
2073      var opt = t.opt;
2074      var renderBasic = t.renderBasic;
2075      var formatDate = calendar.formatDate;
2076      
2077      
2078      
2079  	function render(date, delta) {
2080          if (delta) {
2081              addDays(date, delta);
2082              if (!opt('weekends')) {
2083                  skipWeekend(date, delta < 0 ? -1 : 1);
2084              }
2085          }
2086          t.title = formatDate(date, opt('titleFormat'));
2087          t.start = t.visStart = cloneDate(date, true);
2088          t.end = t.visEnd = addDays(cloneDate(t.start), 1);
2089          renderBasic(1, 1, 1, false);
2090      }
2091      
2092      
2093  }
2094  
2095  setDefaults({
2096      weekMode: 'fixed'
2097  });
2098  
2099  
2100  function BasicView(element, calendar, viewName) {
2101      var t = this;
2102      
2103      
2104      // exports
2105      t.renderBasic = renderBasic;
2106      t.setHeight = setHeight;
2107      t.setWidth = setWidth;
2108      t.renderDayOverlay = renderDayOverlay;
2109      t.defaultSelectionEnd = defaultSelectionEnd;
2110      t.renderSelection = renderSelection;
2111      t.clearSelection = clearSelection;
2112      t.reportDayClick = reportDayClick; // for selection (kinda hacky)
2113      t.dragStart = dragStart;
2114      t.dragStop = dragStop;
2115      t.defaultEventEnd = defaultEventEnd;
2116      t.getHoverListener = function() { return hoverListener };
2117      t.colContentLeft = colContentLeft;
2118      t.colContentRight = colContentRight;
2119      t.dayOfWeekCol = dayOfWeekCol;
2120      t.dateCell = dateCell;
2121      t.cellDate = cellDate;
2122      t.cellIsAllDay = function() { return true };
2123      t.allDayRow = allDayRow;
2124      t.allDayBounds = allDayBounds;
2125      t.getRowCnt = function() { return rowCnt };
2126      t.getColCnt = function() { return colCnt };
2127      t.getColWidth = function() { return colWidth };
2128      t.getDaySegmentContainer = function() { return daySegmentContainer };
2129      
2130      
2131      // imports
2132      View.call(t, element, calendar, viewName);
2133      OverlayManager.call(t);
2134      SelectionManager.call(t);
2135      BasicEventRenderer.call(t);
2136      var opt = t.opt;
2137      var trigger = t.trigger;
2138      var clearEvents = t.clearEvents;
2139      var renderOverlay = t.renderOverlay;
2140      var clearOverlays = t.clearOverlays;
2141      var daySelectionMousedown = t.daySelectionMousedown;
2142      var formatDate = calendar.formatDate;
2143      
2144      
2145      // locals
2146      
2147      var head;
2148      var headCells;
2149      var body;
2150      var bodyRows;
2151      var bodyCells;
2152      var bodyFirstCells;
2153      var bodyCellTopInners;
2154      var daySegmentContainer;
2155      
2156      var viewWidth;
2157      var viewHeight;
2158      var colWidth;
2159      
2160      var rowCnt, colCnt;
2161      var coordinateGrid;
2162      var hoverListener;
2163      var colContentPositions;
2164      
2165      var rtl, dis, dit;
2166      var firstDay;
2167      var nwe;
2168      var tm;
2169      var colFormat;
2170      
2171      
2172      
2173      /* Rendering
2174      ------------------------------------------------------------*/
2175      
2176      
2177      disableTextSelection(element.addClass('fc-grid'));
2178      
2179      
2180  	function renderBasic(maxr, r, c, showNumbers) {
2181          rowCnt = r;
2182          colCnt = c;
2183          updateOptions();
2184          var firstTime = !body;
2185          if (firstTime) {
2186              buildSkeleton(maxr, showNumbers);
2187          }else{
2188              clearEvents();
2189          }
2190          updateCells(firstTime);
2191      }
2192      
2193      
2194      
2195  	function updateOptions() {
2196          rtl = opt('isRTL');
2197          if (rtl) {
2198              dis = -1;
2199              dit = colCnt - 1;
2200          }else{
2201              dis = 1;
2202              dit = 0;
2203          }
2204          firstDay = opt('firstDay');
2205          nwe = opt('weekends') ? 0 : 1;
2206          tm = opt('theme') ? 'ui' : 'fc';
2207          colFormat = opt('columnFormat');
2208      }
2209      
2210      
2211      
2212  	function buildSkeleton(maxRowCnt, showNumbers) {
2213          var s;
2214          var headerClass = tm + "-widget-header";
2215          var contentClass = tm + "-widget-content";
2216          var i, j;
2217          var table;
2218          
2219          s =
2220              "<table class='fc-border-separate' style='width:100%' cellspacing='0'>" +
2221              "<thead>" +
2222              "<tr>";
2223          for (i=0; i<colCnt; i++) {
2224              s +=
2225                  "<th class='fc- " + headerClass + "'/>"; // need fc- for setDayID
2226          }
2227          s +=
2228              "</tr>" +
2229              "</thead>" +
2230              "<tbody>";
2231          for (i=0; i<maxRowCnt; i++) {
2232              s +=
2233                  "<tr class='fc-week" + i + "'>";
2234              for (j=0; j<colCnt; j++) {
2235                  s +=
2236                      "<td class='fc- " + contentClass + " fc-day" + (i*colCnt+j) + "'>" + // need fc- for setDayID
2237                      "<div>" +
2238                      (showNumbers ?
2239                          "<div class='fc-day-number'/>" :
2240                          ''
2241                          ) +
2242                      "<div class='fc-day-content'>" +
2243                      "<div style='position:relative'>&nbsp;</div>" +
2244                      "</div>" +
2245                      "</div>" +
2246                      "</td>";
2247              }
2248              s +=
2249                  "</tr>";
2250          }
2251          s +=
2252              "</tbody>" +
2253              "</table>";
2254          table = $(s).appendTo(element);
2255          
2256          head = table.find('thead');
2257          headCells = head.find('th');
2258          body = table.find('tbody');
2259          bodyRows = body.find('tr');
2260          bodyCells = body.find('td');
2261          bodyFirstCells = bodyCells.filter(':first-child');
2262          bodyCellTopInners = bodyRows.eq(0).find('div.fc-day-content div');
2263          
2264          markFirstLast(head.add(head.find('tr'))); // marks first+last tr/th's
2265          markFirstLast(bodyRows); // marks first+last td's
2266          bodyRows.eq(0).addClass('fc-first'); // fc-last is done in updateCells
2267          
2268          dayBind(bodyCells);
2269          
2270          daySegmentContainer =
2271              $("<div style='position:absolute;z-index:8;top:0;left:0'/>")
2272                  .appendTo(element);
2273      }
2274      
2275      
2276      
2277  	function updateCells(firstTime) {
2278          var dowDirty = firstTime || rowCnt == 1; // could the cells' day-of-weeks need updating?
2279          var month = t.start.getMonth();
2280          var today = clearTime(new Date());
2281          var cell;
2282          var date;
2283          var row;
2284      
2285          if (dowDirty) {
2286              headCells.each(function(i, _cell) {
2287                  cell = $(_cell);
2288                  date = indexDate(i);
2289                  cell.html(formatDate(date, colFormat));
2290                  setDayID(cell, date);
2291              });
2292          }
2293          
2294          bodyCells.each(function(i, _cell) {
2295              cell = $(_cell);
2296              date = indexDate(i);
2297              if (date.getMonth() == month) {
2298                  cell.removeClass('fc-other-month');
2299              }else{
2300                  cell.addClass('fc-other-month');
2301              }
2302              if (+date == +today) {
2303                  cell.addClass(tm + '-state-highlight fc-today');
2304              }else{
2305                  cell.removeClass(tm + '-state-highlight fc-today');
2306              }
2307              cell.find('div.fc-day-number').text(date.getDate());
2308              if (dowDirty) {
2309                  setDayID(cell, date);
2310              }
2311          });
2312          
2313          bodyRows.each(function(i, _row) {
2314              row = $(_row);
2315              if (i < rowCnt) {
2316                  row.show();
2317                  if (i == rowCnt-1) {
2318                      row.addClass('fc-last');
2319                  }else{
2320                      row.removeClass('fc-last');
2321                  }
2322              }else{
2323                  row.hide();
2324              }
2325          });
2326      }
2327      
2328      
2329      
2330  	function setHeight(height) {
2331          viewHeight = height;
2332          
2333          var bodyHeight = viewHeight - head.height();
2334          var rowHeight;
2335          var rowHeightLast;
2336          var cell;
2337              
2338          if (opt('weekMode') == 'variable') {
2339              rowHeight = rowHeightLast = Math.floor(bodyHeight / (rowCnt==1 ? 2 : 6));
2340          }else{
2341              rowHeight = Math.floor(bodyHeight / rowCnt);
2342              rowHeightLast = bodyHeight - rowHeight * (rowCnt-1);
2343          }
2344          
2345          bodyFirstCells.each(function(i, _cell) {
2346              if (i < rowCnt) {
2347                  cell = $(_cell);
2348                  setMinHeight(
2349                      cell.find('> div'),
2350                      (i==rowCnt-1 ? rowHeightLast : rowHeight) - vsides(cell)
2351                  );
2352              }
2353          });
2354          
2355      }
2356      
2357      
2358  	function setWidth(width) {
2359          viewWidth = width;
2360          colContentPositions.clear();
2361          colWidth = Math.floor(viewWidth / colCnt);
2362          setOuterWidth(headCells.slice(0, -1), colWidth);
2363      }
2364      
2365      
2366      
2367      /* Day clicking and binding
2368      -----------------------------------------------------------*/
2369      
2370      
2371  	function dayBind(days) {
2372          days.click(dayClick)
2373              .mousedown(daySelectionMousedown);
2374      }
2375      
2376      
2377  	function dayClick(ev) {
2378          if (!opt('selectable')) { // if selectable, SelectionManager will worry about dayClick
2379              var index = parseInt(this.className.match(/fc\-day(\d+)/)[1]); // TODO: maybe use .data
2380              var date = indexDate(index);
2381              trigger('dayClick', this, date, true, ev);
2382          }
2383      }
2384      
2385      
2386      
2387      /* Semi-transparent Overlay Helpers
2388      ------------------------------------------------------*/
2389      
2390      
2391  	function renderDayOverlay(overlayStart, overlayEnd, refreshCoordinateGrid) { // overlayEnd is exclusive
2392          if (refreshCoordinateGrid) {
2393              coordinateGrid.build();
2394          }
2395          var rowStart = cloneDate(t.visStart);
2396          var rowEnd = addDays(cloneDate(rowStart), colCnt);
2397          for (var i=0; i<rowCnt; i++) {
2398              var stretchStart = new Date(Math.max(rowStart, overlayStart));
2399              var stretchEnd = new Date(Math.min(rowEnd, overlayEnd));
2400              if (stretchStart < stretchEnd) {
2401                  var colStart, colEnd;
2402                  if (rtl) {
2403                      colStart = dayDiff(stretchEnd, rowStart)*dis+dit+1;
2404                      colEnd = dayDiff(stretchStart, rowStart)*dis+dit+1;
2405                  }else{
2406                      colStart = dayDiff(stretchStart, rowStart);
2407                      colEnd = dayDiff(stretchEnd, rowStart);
2408                  }
2409                  dayBind(
2410                      renderCellOverlay(i, colStart, i, colEnd-1)
2411                  );
2412              }
2413              addDays(rowStart, 7);
2414              addDays(rowEnd, 7);
2415          }
2416      }
2417      
2418      
2419  	function renderCellOverlay(row0, col0, row1, col1) { // row1,col1 is inclusive
2420          var rect = coordinateGrid.rect(row0, col0, row1, col1, element);
2421          return renderOverlay(rect, element);
2422      }
2423      
2424      
2425      
2426      /* Selection
2427      -----------------------------------------------------------------------*/
2428      
2429      
2430  	function defaultSelectionEnd(startDate, allDay) {
2431          return cloneDate(startDate);
2432      }
2433      
2434      
2435  	function renderSelection(startDate, endDate, allDay) {
2436          renderDayOverlay(startDate, addDays(cloneDate(endDate), 1), true); // rebuild every time???
2437      }
2438      
2439      
2440  	function clearSelection() {
2441          clearOverlays();
2442      }
2443      
2444      
2445  	function reportDayClick(date, allDay, ev) {
2446          var cell = dateCell(date);
2447          var _element = bodyCells[cell.row*colCnt + cell.col];
2448          trigger('dayClick', _element, date, allDay, ev);
2449      }
2450      
2451      
2452      
2453      /* External Dragging
2454      -----------------------------------------------------------------------*/
2455      
2456      
2457  	function dragStart(_dragElement, ev, ui) {
2458          hoverListener.start(function(cell) {
2459              clearOverlays();
2460              if (cell) {
2461                  renderCellOverlay(cell.row, cell.col, cell.row, cell.col);
2462              }
2463          }, ev);
2464      }
2465      
2466      
2467  	function dragStop(_dragElement, ev, ui) {
2468          var cell = hoverListener.stop();
2469          clearOverlays();
2470          if (cell) {
2471              var d = cellDate(cell);
2472              trigger('drop', _dragElement, d, true, ev, ui);
2473          }
2474      }
2475      
2476      
2477      
2478      /* Utilities
2479      --------------------------------------------------------*/
2480      
2481      
2482  	function defaultEventEnd(event) {
2483          return cloneDate(event.start);
2484      }
2485      
2486      
2487      coordinateGrid = new CoordinateGrid(function(rows, cols) {
2488          var e, n, p;
2489          headCells.each(function(i, _e) {
2490              e = $(_e);
2491              n = e.offset().left;
2492              if (i) {
2493                  p[1] = n;
2494              }
2495              p = [n];
2496              cols[i] = p;
2497          });
2498          p[1] = n + e.outerWidth();
2499          bodyRows.each(function(i, _e) {
2500              if (i < rowCnt) {
2501                  e = $(_e);
2502                  n = e.offset().top;
2503                  if (i) {
2504                      p[1] = n;
2505                  }
2506                  p = [n];
2507                  rows[i] = p;
2508              }
2509          });
2510          p[1] = n + e.outerHeight();
2511      });
2512      
2513      
2514      hoverListener = new HoverListener(coordinateGrid);
2515      
2516      
2517      colContentPositions = new HorizontalPositionCache(function(col) {
2518          return bodyCellTopInners.eq(col);
2519      });
2520      
2521      
2522  	function colContentLeft(col) {
2523          return colContentPositions.left(col);
2524      }
2525      
2526      
2527  	function colContentRight(col) {
2528          return colContentPositions.right(col);
2529      }
2530      
2531      
2532      
2533      
2534  	function dateCell(date) {
2535          return {
2536              row: Math.floor(dayDiff(date, t.visStart) / 7),
2537              col: dayOfWeekCol(date.getDay())
2538          };
2539      }
2540      
2541      
2542  	function cellDate(cell) {
2543          return _cellDate(cell.row, cell.col);
2544      }
2545      
2546      
2547  	function _cellDate(row, col) {
2548          return addDays(cloneDate(t.visStart), row*7 + col*dis+dit);
2549          // what about weekends in middle of week?
2550      }
2551      
2552      
2553  	function indexDate(index) {
2554          return _cellDate(Math.floor(index/colCnt), index%colCnt);
2555      }
2556      
2557      
2558  	function dayOfWeekCol(dayOfWeek) {
2559          return ((dayOfWeek - Math.max(firstDay, nwe) + colCnt) % colCnt) * dis + dit;
2560      }
2561      
2562      
2563      
2564      
2565  	function allDayRow(i) {
2566          return bodyRows.eq(i);
2567      }
2568      
2569      
2570  	function allDayBounds(i) {
2571          return {
2572              left: 0,
2573              right: viewWidth
2574          };
2575      }
2576      
2577      
2578  }
2579  
2580  function BasicEventRenderer() {
2581      var t = this;
2582      
2583      
2584      // exports
2585      t.renderEvents = renderEvents;
2586      t.compileDaySegs = compileSegs; // for DayEventRenderer
2587      t.clearEvents = clearEvents;
2588      t.bindDaySeg = bindDaySeg;
2589      
2590      
2591      // imports
2592      DayEventRenderer.call(t);
2593      var opt = t.opt;
2594      var trigger = t.trigger;
2595      //var setOverflowHidden = t.setOverflowHidden;
2596      var isEventDraggable = t.isEventDraggable;
2597      var isEventResizable = t.isEventResizable;
2598      var reportEvents = t.reportEvents;
2599      var reportEventClear = t.reportEventClear;
2600      var eventElementHandlers = t.eventElementHandlers;
2601      var showEvents = t.showEvents;
2602      var hideEvents = t.hideEvents;
2603      var eventDrop = t.eventDrop;
2604      var getDaySegmentContainer = t.getDaySegmentContainer;
2605      var getHoverListener = t.getHoverListener;
2606      var renderDayOverlay = t.renderDayOverlay;
2607      var clearOverlays = t.clearOverlays;
2608      var getRowCnt = t.getRowCnt;
2609      var getColCnt = t.getColCnt;
2610      var renderDaySegs = t.renderDaySegs;
2611      var resizableDayEvent = t.resizableDayEvent;
2612      
2613      
2614      
2615      /* Rendering
2616      --------------------------------------------------------------------*/
2617      
2618      
2619  	function renderEvents(events, modifiedEventId) {
2620          reportEvents(events);
2621          renderDaySegs(compileSegs(events), modifiedEventId);
2622      }
2623      
2624      
2625  	function clearEvents() {
2626          reportEventClear();
2627          getDaySegmentContainer().empty();
2628      }
2629      
2630      
2631  	function compileSegs(events) {
2632          var rowCnt = getRowCnt(),
2633              colCnt = getColCnt(),
2634              d1 = cloneDate(t.visStart),
2635              d2 = addDays(cloneDate(d1), colCnt),
2636              visEventsEnds = $.map(events, exclEndDay),
2637              i, row,
2638              j, level,
2639              k, seg,
2640              segs=[];
2641          for (i=0; i<rowCnt; i++) {
2642              row = stackSegs(sliceSegs(events, visEventsEnds, d1, d2));
2643              for (j=0; j<row.length; j++) {
2644                  level = row[j];
2645                  for (k=0; k<level.length; k++) {
2646                      seg = level[k];
2647                      seg.row = i;
2648                      seg.level = j; // not needed anymore
2649                      segs.push(seg);
2650                  }
2651              }
2652              addDays(d1, 7);
2653              addDays(d2, 7);
2654          }
2655          return segs;
2656      }
2657      
2658      
2659  	function bindDaySeg(event, eventElement, seg) {
2660          if (isEventDraggable(event)) {
2661              draggableDayEvent(event, eventElement);
2662          }
2663          if (seg.isEnd && isEventResizable(event)) {
2664              resizableDayEvent(event, eventElement, seg);
2665          }
2666          eventElementHandlers(event, eventElement);
2667              // needs to be after, because resizableDayEvent might stopImmediatePropagation on click
2668      }
2669      
2670      
2671      
2672      /* Dragging
2673      ----------------------------------------------------------------------------*/
2674      
2675      
2676  	function draggableDayEvent(event, eventElement) {
2677          var hoverListener = getHoverListener();
2678          var dayDelta;
2679          eventElement.draggable({
2680              zIndex: 9,
2681              delay: 50,
2682              opacity: opt('dragOpacity'),
2683              revertDuration: opt('dragRevertDuration'),
2684              start: function(ev, ui) {
2685                  trigger('eventDragStart', eventElement, event, ev, ui);
2686                  hideEvents(event, eventElement);
2687                  hoverListener.start(function(cell, origCell, rowDelta, colDelta) {
2688                      eventElement.draggable('option', 'revert', !cell || !rowDelta && !colDelta);
2689                      clearOverlays();
2690                      if (cell) {
2691                          //setOverflowHidden(true);
2692                          dayDelta = rowDelta*7 + colDelta * (opt('isRTL') ? -1 : 1);
2693                          renderDayOverlay(
2694                              addDays(cloneDate(event.start), dayDelta),
2695                              addDays(exclEndDay(event), dayDelta)
2696                          );
2697                      }else{
2698                          //setOverflowHidden(false);
2699                          dayDelta = 0;
2700                      }
2701                  }, ev, 'drag');
2702              },
2703              stop: function(ev, ui) {
2704                  hoverListener.stop();
2705                  clearOverlays();
2706                  trigger('eventDragStop', eventElement, event, ev, ui);
2707                  if (dayDelta) {
2708                      eventDrop(this, event, dayDelta, 0, event.allDay, ev, ui);
2709                  }else{
2710                      eventElement.css('filter', ''); // clear IE opacity side-effects
2711                      showEvents(event, eventElement);
2712                  }
2713                  //setOverflowHidden(false);
2714              }
2715          });
2716      }
2717  
2718  
2719  }
2720  
2721  fcViews.agendaWeek = AgendaWeekView;
2722  
2723  function AgendaWeekView(element, calendar) {
2724      var t = this;
2725      
2726      
2727      // exports
2728      t.render = render;
2729      
2730      
2731      // imports
2732      AgendaView.call(t, element, calendar, 'agendaWeek');
2733      var opt = t.opt;
2734      var renderAgenda = t.renderAgenda;
2735      var formatDates = calendar.formatDates;
2736      
2737      
2738      
2739  	function render(date, delta) {
2740          if (delta) {
2741              addDays(date, delta * 7);
2742          }
2743          var start = addDays(cloneDate(date), -((date.getDay() - opt('firstDay') + 7) % 7));
2744          var end = addDays(cloneDate(start), 7);
2745          var visStart = cloneDate(start);
2746          var visEnd = cloneDate(end);
2747          var weekends = opt('weekends');
2748          if (!weekends) {
2749              skipWeekend(visStart);
2750              skipWeekend(visEnd, -1, true);
2751          }
2752          t.title = formatDates(
2753              visStart,
2754              addDays(cloneDate(visEnd), -1),
2755              opt('titleFormat')
2756          );
2757          t.start = start;
2758          t.end = end;
2759          t.visStart = visStart;
2760          t.visEnd = visEnd;
2761          renderAgenda(weekends ? 7 : 5);
2762      }
2763      
2764  
2765  }
2766  
2767  fcViews.agendaDay = AgendaDayView;
2768  
2769  function AgendaDayView(element, calendar) {
2770      var t = this;
2771      
2772      
2773      // exports
2774      t.render = render;
2775      
2776      
2777      // imports
2778      AgendaView.call(t, element, calendar, 'agendaDay');
2779      var opt = t.opt;
2780      var renderAgenda = t.renderAgenda;
2781      var formatDate = calendar.formatDate;
2782      
2783      
2784      
2785  	function render(date, delta) {
2786          if (delta) {
2787              addDays(date, delta);
2788              if (!opt('weekends')) {
2789                  skipWeekend(date, delta < 0 ? -1 : 1);
2790              }
2791          }
2792          var start = cloneDate(date, true);
2793          var end = addDays(cloneDate(start), 1);
2794          t.title = formatDate(date, opt('titleFormat'));
2795          t.start = t.visStart = start;
2796          t.end = t.visEnd = end;
2797          renderAgenda(1);
2798      }
2799      
2800  
2801  }
2802  
2803  setDefaults({
2804      allDaySlot: true,
2805      allDayText: 'all-day',
2806      firstHour: 6,
2807      slotMinutes: 30,
2808      defaultEventMinutes: 120,
2809      axisFormat: 'h(:mm)tt',
2810      timeFormat: {
2811          agenda: 'h:mm{ - h:mm}'
2812      },
2813      dragOpacity: {
2814          agenda: .5
2815      },
2816      minTime: 0,
2817      maxTime: 24
2818  });
2819  
2820  
2821  // TODO: make it work in quirks mode (event corners, all-day height)
2822  // TODO: test liquid width, especially in IE6
2823  
2824  
2825  function AgendaView(element, calendar, viewName) {
2826      var t = this;
2827      
2828      
2829      // exports
2830      t.renderAgenda = renderAgenda;
2831      t.setWidth = setWidth;
2832      t.setHeight = setHeight;
2833      t.beforeHide = beforeHide;
2834      t.afterShow = afterShow;
2835      t.defaultEventEnd = defaultEventEnd;
2836      t.timePosition = timePosition;
2837      t.dayOfWeekCol = dayOfWeekCol;
2838      t.dateCell = dateCell;
2839      t.cellDate = cellDate;
2840      t.cellIsAllDay = cellIsAllDay;
2841      t.allDayRow = getAllDayRow;
2842      t.allDayBounds = allDayBounds;
2843      t.getHoverListener = function() { return hoverListener };
2844      t.colContentLeft = colContentLeft;
2845      t.colContentRight = colContentRight;
2846      t.getDaySegmentContainer = function() { return daySegmentContainer };
2847      t.getSlotSegmentContainer = function() { return slotSegmentContainer };
2848      t.getMinMinute = function() { return minMinute };
2849      t.getMaxMinute = function() { return maxMinute };
2850      t.getBodyContent = function() { return slotContent }; // !!??
2851      t.getRowCnt = function() { return 1 };
2852      t.getColCnt = function() { return colCnt };
2853      t.getColWidth = function() { return colWidth };
2854      t.getSlotHeight = function() { return slotHeight };
2855      t.defaultSelectionEnd = defaultSelectionEnd;
2856      t.renderDayOverlay = renderDayOverlay;
2857      t.renderSelection = renderSelection;
2858      t.clearSelection = clearSelection;
2859      t.reportDayClick = reportDayClick; // selection mousedown hack
2860      t.dragStart = dragStart;
2861      t.dragStop = dragStop;
2862      
2863      
2864      // imports
2865      View.call(t, element, calendar, viewName);
2866      OverlayManager.call(t);
2867      SelectionManager.call(t);
2868      AgendaEventRenderer.call(t);
2869      var opt = t.opt;
2870      var trigger = t.trigger;
2871      var clearEvents = t.clearEvents;
2872      var renderOverlay = t.renderOverlay;
2873      var clearOverlays = t.clearOverlays;
2874      var reportSelection = t.reportSelection;
2875      var unselect = t.unselect;
2876      var daySelectionMousedown = t.daySelectionMousedown;
2877      var slotSegHtml = t.slotSegHtml;
2878      var formatDate = calendar.formatDate;
2879      
2880      
2881      // locals
2882      
2883      var dayTable;
2884      var dayHead;
2885      var dayHeadCells;
2886      var dayBody;
2887      var dayBodyCells;
2888      var dayBodyCellInners;
2889      var dayBodyFirstCell;
2890      var dayBodyFirstCellStretcher;
2891      var slotLayer;
2892      var daySegmentContainer;
2893      var allDayTable;
2894      var allDayRow;
2895      var slotScroller;
2896      var slotContent;
2897      var slotSegmentContainer;
2898      var slotTable;
2899      var slotTableFirstInner;
2900      var axisFirstCells;
2901      var gutterCells;
2902      var selectionHelper;
2903      
2904      var viewWidth;
2905      var viewHeight;
2906      var axisWidth;
2907      var colWidth;
2908      var gutterWidth;
2909      var slotHeight; // TODO: what if slotHeight changes? (see issue 650)
2910      var savedScrollTop;
2911      
2912      var colCnt;
2913      var slotCnt;
2914      var coordinateGrid;
2915      var hoverListener;
2916      var colContentPositions;
2917      var slotTopCache = {};
2918      
2919      var tm;
2920      var firstDay;
2921      var nwe;            // no weekends (int)
2922      var rtl, dis, dit;  // day index sign / translate
2923      var minMinute, maxMinute;
2924      var colFormat;
2925      
2926  
2927      
2928      /* Rendering
2929      -----------------------------------------------------------------------------*/
2930      
2931      
2932      disableTextSelection(element.addClass('fc-agenda'));
2933      
2934      
2935  	function renderAgenda(c) {
2936          colCnt = c;
2937          updateOptions();
2938          if (!dayTable) {
2939              buildSkeleton();
2940          }else{
2941              clearEvents();
2942          }
2943          updateCells();
2944      }
2945      
2946      
2947      
2948  	function updateOptions() {
2949          tm = opt('theme') ? 'ui' : 'fc';
2950          nwe = opt('weekends') ? 0 : 1;
2951          firstDay = opt('firstDay');
2952          if (rtl = opt('isRTL')) {
2953              dis = -1;
2954              dit = colCnt - 1;
2955          }else{
2956              dis = 1;
2957              dit = 0;
2958          }
2959          minMinute = parseTime(opt('minTime'));
2960          maxMinute = parseTime(opt('maxTime'));
2961          colFormat = opt('columnFormat');
2962      }
2963      
2964      
2965      
2966  	function buildSkeleton() {
2967          var headerClass = tm + "-widget-header";
2968          var contentClass = tm + "-widget-content";
2969          var s;
2970          var i;
2971          var d;
2972          var maxd;
2973          var minutes;
2974          var slotNormal = opt('slotMinutes') % 15 == 0;
2975          
2976          s =
2977              "<table style='width:100%' class='fc-agenda-days fc-border-separate' cellspacing='0'>" +
2978              "<thead>" +
2979              "<tr>" +
2980              "<th class='fc-agenda-axis " + headerClass + "'>&nbsp;</th>";
2981          for (i=0; i<colCnt; i++) {
2982              s +=
2983                  "<th class='fc- fc-col" + i + ' ' + headerClass + "'/>"; // fc- needed for setDayID
2984          }
2985          s +=
2986              "<th class='fc-agenda-gutter " + headerClass + "'>&nbsp;</th>" +
2987              "</tr>" +
2988              "</thead>" +
2989              "<tbody>" +
2990              "<tr>" +
2991              "<th class='fc-agenda-axis " + headerClass + "'>&nbsp;</th>";
2992          for (i=0; i<colCnt; i++) {
2993              s +=
2994                  "<td class='fc- fc-col" + i + ' ' + contentClass + "'>" + // fc- needed for setDayID
2995                  "<div>" +
2996                  "<div class='fc-day-content'>" +
2997                  "<div style='position:relative'>&nbsp;</div>" +
2998                  "</div>" +
2999                  "</div>" +
3000                  "</td>";
3001          }
3002          s +=
3003              "<td class='fc-agenda-gutter " + contentClass + "'>&nbsp;</td>" +
3004              "</tr>" +
3005              "</tbody>" +
3006              "</table>";
3007          dayTable = $(s).appendTo(element);
3008          dayHead = dayTable.find('thead');
3009          dayHeadCells = dayHead.find('th').slice(1, -1);
3010          dayBody = dayTable.find('tbody');
3011          dayBodyCells = dayBody.find('td').slice(0, -1);
3012          dayBodyCellInners = dayBodyCells.find('div.fc-day-content div');
3013          dayBodyFirstCell = dayBodyCells.eq(0);
3014          dayBodyFirstCellStretcher = dayBodyFirstCell.find('> div');
3015          
3016          markFirstLast(dayHead.add(dayHead.find('tr')));
3017          markFirstLast(dayBody.add(dayBody.find('tr')));
3018          
3019          axisFirstCells = dayHead.find('th:first');
3020          gutterCells = dayTable.find('.fc-agenda-gutter');
3021          
3022          slotLayer =
3023              $("<div style='position:absolute;z-index:2;left:0;width:100%'/>")
3024                  .appendTo(element);
3025                  
3026          if (opt('allDaySlot')) {
3027          
3028              daySegmentContainer =
3029                  $("<div style='position:absolute;z-index:8;top:0;left:0'/>")
3030                      .appendTo(slotLayer);
3031          
3032              s =
3033                  "<table style='width:100%' class='fc-agenda-allday' cellspacing='0'>" +
3034                  "<tr>" +
3035                  "<th class='" + headerClass + " fc-agenda-axis'>" + opt('allDayText') + "</th>" +
3036                  "<td>" +
3037                  "<div class='fc-day-content'><div style='position:relative'/></div>" +
3038                  "</td>" +
3039                  "<th class='" + headerClass + " fc-agenda-gutter'>&nbsp;</th>" +
3040                  "</tr>" +
3041                  "</table>";
3042              allDayTable = $(s).appendTo(slotLayer);
3043              allDayRow = allDayTable.find('tr');
3044              
3045              dayBind(allDayRow.find('td'));
3046              
3047              axisFirstCells = axisFirstCells.add(allDayTable.find('th:first'));
3048              gutterCells = gutterCells.add(allDayTable.find('th.fc-agenda-gutter'));
3049              
3050              slotLayer.append(
3051                  "<div class='fc-agenda-divider " + headerClass + "'>" +
3052                  "<div class='fc-agenda-divider-inner'/>" +
3053                  "</div>"
3054              );
3055              
3056          }else{
3057          
3058              daySegmentContainer = $([]); // in jQuery 1.4, we can just do $()
3059          
3060          }
3061          
3062          slotScroller =
3063              $("<div style='position:absolute;width:100%;overflow-x:hidden;overflow-y:auto'/>")
3064                  .appendTo(slotLayer);
3065                  
3066          slotContent =
3067              $("<div style='position:relative;width:100%;overflow:hidden'/>")
3068                  .appendTo(slotScroller);
3069                  
3070          slotSegmentContainer =
3071              $("<div style='position:absolute;z-index:8;top:0;left:0'/>")
3072                  .appendTo(slotContent);
3073          
3074          s =
3075              "<table class='fc-agenda-slots' style='width:100%' cellspacing='0'>" +
3076              "<tbody>";
3077          d = zeroDate();
3078          maxd = addMinutes(cloneDate(d), maxMinute);
3079          addMinutes(d, minMinute);
3080          slotCnt = 0;
3081          for (i=0; d < maxd; i++) {
3082              minutes = d.getMinutes();
3083              s +=
3084                  "<tr class='fc-slot" + i + ' ' + (!minutes ? '' : 'fc-minor') + "'>" +
3085                  "<th class='fc-agenda-axis " + headerClass + "'>" +
3086                  ((!slotNormal || !minutes) ? formatDate(d, opt('axisFormat')) : '&nbsp;') +
3087                  "</th>" +
3088                  "<td class='" + contentClass + "'>" +
3089                  "<div style='position:relative'>&nbsp;</div>" +
3090                  "</td>" +
3091                  "</tr>";
3092              addMinutes(d, opt('slotMinutes'));
3093              slotCnt++;
3094          }
3095          s +=
3096              "</tbody>" +
3097              "</table>";
3098          slotTable = $(s).appendTo(slotContent);
3099          slotTableFirstInner = slotTable.find('div:first');
3100          
3101          slotBind(slotTable.find('td'));
3102          
3103          axisFirstCells = axisFirstCells.add(slotTable.find('th:first'));
3104      }
3105      
3106      
3107      
3108  	function updateCells() {
3109          var i;
3110          var headCell;
3111          var bodyCell;
3112          var date;
3113          var today = clearTime(new Date());
3114          for (i=0; i<colCnt; i++) {
3115              date = colDate(i);
3116              headCell = dayHeadCells.eq(i);
3117              headCell.html(formatDate(date, colFormat));
3118              bodyCell = dayBodyCells.eq(i);
3119              if (+date == +today) {
3120                  bodyCell.addClass(tm + '-state-highlight fc-today');
3121              }else{
3122                  bodyCell.removeClass(tm + '-state-highlight fc-today');
3123              }
3124              setDayID(headCell.add(bodyCell), date);
3125          }
3126      }
3127      
3128      
3129      
3130  	function setHeight(height, dateChanged) {
3131          if (height === undefined) {
3132              height = viewHeight;
3133          }
3134          viewHeight = height;
3135          slotTopCache = {};
3136      
3137          var headHeight = dayBody.position().top;
3138          var allDayHeight = slotScroller.position().top; // including divider
3139          var bodyHeight = Math.min( // total body height, including borders
3140              height - headHeight,   // when scrollbars
3141              slotTable.height() + allDayHeight + 1 // when no scrollbars. +1 for bottom border
3142          );
3143          
3144          dayBodyFirstCellStretcher
3145              .height(bodyHeight - vsides(dayBodyFirstCell));
3146          
3147          slotLayer.css('top', headHeight);
3148          
3149          slotScroller.height(bodyHeight - allDayHeight - 1);
3150          
3151          slotHeight = slotTableFirstInner.height() + 1; // +1 for border
3152          
3153          if (dateChanged) {
3154              resetScroll();
3155          }
3156      }
3157      
3158      
3159      
3160  	function setWidth(width) {
3161          viewWidth = width;
3162          colContentPositions.clear();
3163          
3164          axisWidth = 0;
3165          setOuterWidth(
3166              axisFirstCells
3167                  .width('')
3168                  .each(function(i, _cell) {
3169                      axisWidth = Math.max(axisWidth, $(_cell).outerWidth());
3170                  }),
3171              axisWidth
3172          );
3173          
3174          var slotTableWidth = slotScroller[0].clientWidth; // needs to be done after axisWidth (for IE7)
3175          //slotTable.width(slotTableWidth);
3176          
3177          gutterWidth = slotScroller.width() - slotTableWidth;
3178          if (gutterWidth) {
3179              setOuterWidth(gutterCells, gutterWidth);
3180              gutterCells
3181                  .show()
3182                  .prev()
3183                  .removeClass('fc-last');
3184          }else{
3185              gutterCells
3186                  .hide()
3187                  .prev()
3188                  .addClass('fc-last');
3189          }
3190          
3191          colWidth = Math.floor((slotTableWidth - axisWidth) / colCnt);
3192          setOuterWidth(dayHeadCells.slice(0, -1), colWidth);
3193      }
3194      
3195  
3196  
3197  	function resetScroll() {
3198          var d0 = zeroDate();
3199          var scrollDate = cloneDate(d0);
3200          scrollDate.setHours(opt('firstHour'));
3201          var top = timePosition(d0, scrollDate) + 1; // +1 for the border
3202  		function scroll() {
3203              slotScroller.scrollTop(top);
3204          }
3205          scroll();
3206          setTimeout(scroll, 0); // overrides any previous scroll state made by the browser
3207      }
3208      
3209      
3210  	function beforeHide() {
3211          savedScrollTop = slotScroller.scrollTop();
3212      }
3213      
3214      
3215  	function afterShow() {
3216          slotScroller.scrollTop(savedScrollTop);
3217      }
3218      
3219      
3220      
3221      /* Slot/Day clicking and binding
3222      -----------------------------------------------------------------------*/
3223      
3224  
3225  	function dayBind(cells) {
3226          cells.click(slotClick)
3227              .mousedown(daySelectionMousedown);
3228      }
3229  
3230  
3231  	function slotBind(cells) {
3232          cells.click(slotClick)
3233              .mousedown(slotSelectionMousedown);
3234      }
3235      
3236      
3237  	function slotClick(ev) {
3238          if (!opt('selectable')) { // if selectable, SelectionManager will worry about dayClick
3239              var col = Math.min(colCnt-1, Math.floor((ev.pageX - dayTable.offset().left - axisWidth) / colWidth));
3240              var date = colDate(col);
3241              var rowMatch = this.parentNode.className.match(/fc-slot(\d+)/); // TODO: maybe use data
3242              if (rowMatch) {
3243                  var mins = parseInt(rowMatch[1]) * opt('slotMinutes');
3244                  var hours = Math.floor(mins/60);
3245                  date.setHours(hours);
3246                  date.setMinutes(mins%60 + minMinute);
3247                  trigger('dayClick', dayBodyCells[col], date, false, ev);
3248              }else{
3249                  trigger('dayClick', dayBodyCells[col], date, true, ev);
3250              }
3251          }
3252      }
3253      
3254      
3255      
3256      /* Semi-transparent Overlay Helpers
3257      -----------------------------------------------------*/
3258      
3259  
3260  	function renderDayOverlay(startDate, endDate, refreshCoordinateGrid) { // endDate is exclusive
3261          if (refreshCoordinateGrid) {
3262              coordinateGrid.build();
3263          }
3264          var visStart = cloneDate(t.visStart);
3265          var startCol, endCol;
3266          if (rtl) {
3267              startCol = dayDiff(endDate, visStart)*dis+dit+1;
3268              endCol = dayDiff(startDate, visStart)*dis+dit+1;
3269          }else{
3270              startCol = dayDiff(startDate, visStart);
3271              endCol = dayDiff(endDate, visStart);
3272          }
3273          startCol = Math.max(0, startCol);
3274          endCol = Math.min(colCnt, endCol);
3275          if (startCol < endCol) {
3276              dayBind(
3277                  renderCellOverlay(0, startCol, 0, endCol-1)
3278              );
3279          }
3280      }
3281      
3282      
3283  	function renderCellOverlay(row0, col0, row1, col1) { // only for all-day?
3284          var rect = coordinateGrid.rect(row0, col0, row1, col1, slotLayer);
3285          return renderOverlay(rect, slotLayer);
3286      }
3287      
3288  
3289  	function renderSlotOverlay(overlayStart, overlayEnd) {
3290          var dayStart = cloneDate(t.visStart);
3291          var dayEnd = addDays(cloneDate(dayStart), 1);
3292          for (var i=0; i<colCnt; i++) {
3293              var stretchStart = new Date(Math.max(dayStart, overlayStart));
3294              var stretchEnd = new Date(Math.min(dayEnd, overlayEnd));
3295              if (stretchStart < stretchEnd) {
3296                  var col = i*dis+dit;
3297                  var rect = coordinateGrid.rect(0, col, 0, col, slotContent); // only use it for horizontal coords
3298                  var top = timePosition(dayStart, stretchStart);
3299                  var bottom = timePosition(dayStart, stretchEnd);
3300                  rect.top = top;
3301                  rect.height = bottom - top;
3302                  slotBind(
3303                      renderOverlay(rect, slotContent)
3304                  );
3305              }
3306              addDays(dayStart, 1);
3307              addDays(dayEnd, 1);
3308          }
3309      }
3310      
3311      
3312      
3313      /* Coordinate Utilities
3314      -----------------------------------------------------------------------------*/
3315      
3316      
3317      coordinateGrid = new CoordinateGrid(function(rows, cols) {
3318          var e, n, p;
3319          dayHeadCells.each(function(i, _e) {
3320              e = $(_e);
3321              n = e.offset().left;
3322              if (i) {
3323                  p[1] = n;
3324              }
3325              p = [n];
3326              cols[i] = p;
3327          });
3328          p[1] = n + e.outerWidth();
3329          if (opt('allDaySlot')) {
3330              e = allDayRow;
3331              n = e.offset().top;
3332              rows[0] = [n, n+e.outerHeight()];
3333          }
3334          var slotTableTop = slotContent.offset().top;
3335          var slotScrollerTop = slotScroller.offset().top;
3336          var slotScrollerBottom = slotScrollerTop + slotScroller.outerHeight();
3337  		function constrain(n) {
3338              return Math.max(slotScrollerTop, Math.min(slotScrollerBottom, n));
3339          }
3340          for (var i=0; i<slotCnt; i++) {
3341              rows.push([
3342                  constrain(slotTableTop + slotHeight*i),
3343                  constrain(slotTableTop + slotHeight*(i+1))
3344              ]);
3345          }
3346      });
3347      
3348      
3349      hoverListener = new HoverListener(coordinateGrid);
3350      
3351      
3352      colContentPositions = new HorizontalPositionCache(function(col) {
3353          return dayBodyCellInners.eq(col);
3354      });
3355      
3356      
3357  	function colContentLeft(col) {
3358          return colContentPositions.left(col);
3359      }
3360      
3361      
3362  	function colContentRight(col) {
3363          return colContentPositions.right(col);
3364      }
3365      
3366      
3367      
3368      
3369  	function dateCell(date) { // "cell" terminology is now confusing
3370          return {
3371              row: Math.floor(dayDiff(date, t.visStart) / 7),
3372              col: dayOfWeekCol(date.getDay())
3373          };
3374      }
3375      
3376      
3377  	function cellDate(cell) {
3378          var d = colDate(cell.col);
3379          var slotIndex = cell.row;
3380          if (opt('allDaySlot')) {
3381              slotIndex--;
3382          }
3383          if (slotIndex >= 0) {
3384              addMinutes(d, minMinute + slotIndex * opt('slotMinutes'));
3385          }
3386          return d;
3387      }
3388      
3389      
3390  	function colDate(col) { // returns dates with 00:00:00
3391          return addDays(cloneDate(t.visStart), col*dis+dit);
3392      }
3393      
3394      
3395  	function cellIsAllDay(cell) {
3396          return opt('allDaySlot') && !cell.row;
3397      }
3398      
3399      
3400  	function dayOfWeekCol(dayOfWeek) {
3401          return ((dayOfWeek - Math.max(firstDay, nwe) + colCnt) % colCnt)*dis+dit;
3402      }
3403      
3404      
3405      
3406      
3407      // get the Y coordinate of the given time on the given day (both Date objects)
3408  	function timePosition(day, time) { // both date objects. day holds 00:00 of current day
3409          day = cloneDate(day, true);
3410          if (time < addMinutes(cloneDate(day), minMinute)) {
3411              return 0;
3412          }
3413          if (time >= addMinutes(cloneDate(day), maxMinute)) {
3414              return slotTable.height();
3415          }
3416          var slotMinutes = opt('slotMinutes'),
3417              minutes = time.getHours()*60 + time.getMinutes() - minMinute,
3418              slotI = Math.floor(minutes / slotMinutes),
3419              slotTop = slotTopCache[slotI];
3420          if (slotTop === undefined) {
3421              slotTop = slotTopCache[slotI] = slotTable.find('tr:eq(' + slotI + ') td div')[0].offsetTop; //.position().top; // need this optimization???
3422          }
3423          return Math.max(0, Math.round(
3424              slotTop - 1 + slotHeight * ((minutes % slotMinutes) / slotMinutes)
3425          ));
3426      }
3427      
3428      
3429  	function allDayBounds() {
3430          return {
3431              left: axisWidth,
3432              right: viewWidth - gutterWidth
3433          }
3434      }
3435      
3436      
3437  	function getAllDayRow(index) {
3438          return allDayRow;
3439      }
3440      
3441      
3442  	function defaultEventEnd(event) {
3443          var start = cloneDate(event.start);
3444          if (event.allDay) {
3445              return start;
3446          }
3447          return addMinutes(start, opt('defaultEventMinutes'));
3448      }
3449      
3450      
3451      
3452      /* Selection
3453      ---------------------------------------------------------------------------------*/
3454      
3455      
3456  	function defaultSelectionEnd(startDate, allDay) {
3457          if (allDay) {
3458              return cloneDate(startDate);
3459          }
3460          return addMinutes(cloneDate(startDate), opt('slotMinutes'));
3461      }
3462      
3463      
3464  	function renderSelection(startDate, endDate, allDay) { // only for all-day
3465          if (allDay) {
3466              if (opt('allDaySlot')) {
3467                  renderDayOverlay(startDate, addDays(cloneDate(endDate), 1), true);
3468              }
3469          }else{
3470              renderSlotSelection(startDate, endDate);
3471          }
3472      }
3473      
3474      
3475  	function renderSlotSelection(startDate, endDate) {
3476          var helperOption = opt('selectHelper');
3477          coordinateGrid.build();
3478          if (helperOption) {
3479              var col = dayDiff(startDate, t.visStart) * dis + dit;
3480              if (col >= 0 && col < colCnt) { // only works when times are on same day
3481                  var rect = coordinateGrid.rect(0, col, 0, col, slotContent); // only for horizontal coords
3482                  var top = timePosition(startDate, startDate);
3483                  var bottom = timePosition(startDate, endDate);
3484                  if (bottom > top) { // protect against selections that are entirely before or after visible range
3485                      rect.top = top;
3486                      rect.height = bottom - top;
3487                      rect.left += 2;
3488                      rect.width -= 5;
3489                      if ($.isFunction(helperOption)) {
3490                          var helperRes = helperOption(startDate, endDate);
3491                          if (helperRes) {
3492                              rect.position = 'absolute';
3493                              rect.zIndex = 8;
3494                              selectionHelper = $(helperRes)
3495                                  .css(rect)
3496                                  .appendTo(slotContent);
3497                          }
3498                      }else{
3499                          rect.isStart = true; // conside rect a "seg" now
3500                          rect.isEnd = true;   //
3501                          selectionHelper = $(slotSegHtml(
3502                              {
3503                                  title: '',
3504                                  start: startDate,
3505                                  end: endDate,
3506                                  className: ['fc-select-helper'],
3507                                  editable: false
3508                              },
3509                              rect
3510                          ));
3511                          selectionHelper.css('opacity', opt('dragOpacity'));
3512                      }
3513                      if (selectionHelper) {
3514                          slotBind(selectionHelper);
3515                          slotContent.append(selectionHelper);
3516                          setOuterWidth(selectionHelper, rect.width, true); // needs to be after appended
3517                          setOuterHeight(selectionHelper, rect.height, true);
3518                      }
3519                  }
3520              }
3521          }else{
3522              renderSlotOverlay(startDate, endDate);
3523          }
3524      }
3525      
3526      
3527  	function clearSelection() {
3528          clearOverlays();
3529          if (selectionHelper) {
3530              selectionHelper.remove();
3531              selectionHelper = null;
3532          }
3533      }
3534      
3535      
3536  	function slotSelectionMousedown(ev) {
3537          if (ev.which == 1 && opt('selectable')) { // ev.which==1 means left mouse button
3538              unselect(ev);
3539              var dates;
3540              hoverListener.start(function(cell, origCell) {
3541                  clearSelection();
3542                  if (cell && cell.col == origCell.col && !cellIsAllDay(cell)) {
3543                      var d1 = cellDate(origCell);
3544                      var d2 = cellDate(cell);
3545                      dates = [
3546                          d1,
3547                          addMinutes(cloneDate(d1), opt('slotMinutes')),
3548                          d2,
3549                          addMinutes(cloneDate(d2), opt('slotMinutes'))
3550                      ].sort(cmp);
3551                      renderSlotSelection(dates[0], dates[3]);
3552                  }else{
3553                      dates = null;
3554                  }
3555              }, ev);
3556              $(document).one('mouseup', function(ev) {
3557                  hoverListener.stop();
3558                  if (dates) {
3559                      if (+dates[0] == +dates[1]) {
3560                          reportDayClick(dates[0], false, ev);
3561                      }
3562                      reportSelection(dates[0], dates[3], false, ev);
3563                  }
3564              });
3565          }
3566      }
3567      
3568      
3569  	function reportDayClick(date, allDay, ev) {
3570          trigger('dayClick', dayBodyCells[dayOfWeekCol(date.getDay())], date, allDay, ev);
3571      }
3572      
3573      
3574      
3575      /* External Dragging
3576      --------------------------------------------------------------------------------*/
3577      
3578      
3579  	function dragStart(_dragElement, ev, ui) {
3580          hoverListener.start(function(cell) {
3581              clearOverlays();
3582              if (cell) {
3583                  if (cellIsAllDay(cell)) {
3584                      renderCellOverlay(cell.row, cell.col, cell.row, cell.col);
3585                  }else{
3586                      var d1 = cellDate(cell);
3587                      var d2 = addMinutes(cloneDate(d1), opt('defaultEventMinutes'));
3588                      renderSlotOverlay(d1, d2);
3589                  }
3590              }
3591          }, ev);
3592      }
3593      
3594      
3595  	function dragStop(_dragElement, ev, ui) {
3596          var cell = hoverListener.stop();
3597          clearOverlays();
3598          if (cell) {
3599              trigger('drop', _dragElement, cellDate(cell), cellIsAllDay(cell), ev, ui);
3600          }
3601      }
3602  
3603  
3604  }
3605  
3606  function AgendaEventRenderer() {
3607      var t = this;
3608      
3609      
3610      // exports
3611      t.renderEvents = renderEvents;
3612      t.compileDaySegs = compileDaySegs; // for DayEventRenderer
3613      t.clearEvents = clearEvents;
3614      t.slotSegHtml = slotSegHtml;
3615      t.bindDaySeg = bindDaySeg;
3616      
3617      
3618      // imports
3619      DayEventRenderer.call(t);
3620      var opt = t.opt;
3621      var trigger = t.trigger;
3622      //var setOverflowHidden = t.setOverflowHidden;
3623      var isEventDraggable = t.isEventDraggable;
3624      var isEventResizable = t.isEventResizable;
3625      var eventEnd = t.eventEnd;
3626      var reportEvents = t.reportEvents;
3627      var reportEventClear = t.reportEventClear;
3628      var eventElementHandlers = t.eventElementHandlers;
3629      var setHeight = t.setHeight;
3630      var getDaySegmentContainer = t.getDaySegmentContainer;
3631      var getSlotSegmentContainer = t.getSlotSegmentContainer;
3632      var getHoverListener = t.getHoverListener;
3633      var getMaxMinute = t.getMaxMinute;
3634      var getMinMinute = t.getMinMinute;
3635      var timePosition = t.timePosition;
3636      var colContentLeft = t.colContentLeft;
3637      var colContentRight = t.colContentRight;
3638      var renderDaySegs = t.renderDaySegs;
3639      var resizableDayEvent = t.resizableDayEvent; // TODO: streamline binding architecture
3640      var getColCnt = t.getColCnt;
3641      var getColWidth = t.getColWidth;
3642      var getSlotHeight = t.getSlotHeight;
3643      var getBodyContent = t.getBodyContent;
3644      var reportEventElement = t.reportEventElement;
3645      var showEvents = t.showEvents;
3646      var hideEvents = t.hideEvents;
3647      var eventDrop = t.eventDrop;
3648      var eventResize = t.eventResize;
3649      var renderDayOverlay = t.renderDayOverlay;
3650      var clearOverlays = t.clearOverlays;
3651      var calendar = t.calendar;
3652      var formatDate = calendar.formatDate;
3653      var formatDates = calendar.formatDates;
3654      
3655      
3656      
3657      /* Rendering
3658      ----------------------------------------------------------------------------*/
3659      
3660  
3661  	function renderEvents(events, modifiedEventId) {
3662          reportEvents(events);
3663          var i, len=events.length,
3664              dayEvents=[],
3665              slotEvents=[];
3666          for (i=0; i<len; i++) {
3667              if (events[i].allDay) {
3668                  dayEvents.push(events[i]);
3669              }else{
3670                  slotEvents.push(events[i]);
3671              }
3672          }
3673          if (opt('allDaySlot')) {
3674              renderDaySegs(compileDaySegs(dayEvents), modifiedEventId);
3675              setHeight(); // no params means set to viewHeight
3676          }
3677          renderSlotSegs(compileSlotSegs(slotEvents), modifiedEventId);
3678      }
3679      
3680      
3681  	function clearEvents() {
3682          reportEventClear();
3683          getDaySegmentContainer().empty();
3684          getSlotSegmentContainer().empty();
3685      }
3686      
3687      
3688  	function compileDaySegs(events) {
3689          var levels = stackSegs(sliceSegs(events, $.map(events, exclEndDay), t.visStart, t.visEnd)),
3690              i, levelCnt=levels.length, level,
3691              j, seg,
3692              segs=[];
3693          for (i=0; i<levelCnt; i++) {
3694              level = levels[i];
3695              for (j=0; j<level.length; j++) {
3696                  seg = level[j];
3697                  seg.row = 0;
3698                  seg.level = i; // not needed anymore
3699                  segs.push(seg);
3700              }
3701          }
3702          return segs;
3703      }
3704      
3705      
3706  	function compileSlotSegs(events) {
3707          var colCnt = getColCnt(),
3708              minMinute = getMinMinute(),
3709              maxMinute = getMaxMinute(),
3710              d = addMinutes(cloneDate(t.visStart), minMinute),
3711              visEventEnds = $.map(events, slotEventEnd),
3712              i, col,
3713              j, level,
3714              k, seg,
3715              segs=[];
3716          for (i=0; i<colCnt; i++) {
3717              col = stackSegs(sliceSegs(events, visEventEnds, d, addMinutes(cloneDate(d), maxMinute-minMinute)));
3718              countForwardSegs(col);
3719              for (j=0; j<col.length; j++) {
3720                  level = col[j];
3721                  for (k=0; k<level.length; k++) {
3722                      seg = level[k];
3723                      seg.col = i;
3724                      seg.level = j;
3725                      segs.push(seg);
3726                  }
3727              }
3728              addDays(d, 1, true);
3729          }
3730          return segs;
3731      }
3732      
3733      
3734  	function slotEventEnd(event) {
3735          if (event.end) {
3736              return cloneDate(event.end);
3737          }else{
3738              return addMinutes(cloneDate(event.start), opt('defaultEventMinutes'));
3739          }
3740      }
3741      
3742      
3743      // renders events in the 'time slots' at the bottom
3744      
3745  	function renderSlotSegs(segs, modifiedEventId) {
3746      
3747          var i, segCnt=segs.length, seg,
3748              event,
3749              classes,
3750              top, bottom,
3751              colI, levelI, forward,
3752              leftmost,
3753              availWidth,
3754              outerWidth,
3755              left,
3756              html='',
3757              eventElements,
3758              eventElement,
3759              triggerRes,
3760              vsideCache={},
3761              hsideCache={},
3762              key, val,
3763              contentElement,
3764              height,
3765              slotSegmentContainer = getSlotSegmentContainer(),
3766              rtl, dis, dit,
3767              colCnt = getColCnt();
3768              
3769          if (rtl = opt('isRTL')) {
3770              dis = -1;
3771              dit = colCnt - 1;
3772          }else{
3773              dis = 1;
3774              dit = 0;
3775          }
3776              
3777          // calculate position/dimensions, create html
3778          for (i=0; i<segCnt; i++) {
3779              seg = segs[i];
3780              event = seg.event;
3781              top = timePosition(seg.start, seg.start);
3782              bottom = timePosition(seg.start, seg.end);
3783              colI = seg.col;
3784              levelI = seg.level;
3785              forward = seg.forward || 0;
3786              leftmost = colContentLeft(colI*dis + dit);
3787              availWidth = colContentRight(colI*dis + dit) - leftmost;
3788              availWidth = Math.min(availWidth-6, availWidth*.95); // TODO: move this to CSS
3789              if (levelI) {
3790                  // indented and thin
3791                  outerWidth = availWidth / (levelI + forward + 1);
3792              }else{
3793                  if (forward) {
3794                      // moderately wide, aligned left still
3795                      outerWidth = ((availWidth / (forward + 1)) - (12/2)) * 2; // 12 is the predicted width of resizer =
3796                  }else{
3797                      // can be entire width, aligned left
3798                      outerWidth = availWidth;
3799                  }
3800              }
3801              left = leftmost +                                  // leftmost possible
3802                  (availWidth / (levelI + forward + 1) * levelI) // indentation
3803                  * dis + (rtl ? availWidth - outerWidth : 0);   // rtl
3804              seg.top = top;
3805              seg.left = left;
3806              seg.outerWidth = outerWidth;
3807              seg.outerHeight = bottom - top;
3808              html += slotSegHtml(event, seg);
3809          }
3810          slotSegmentContainer[0].innerHTML = html; // faster than html()
3811          eventElements = slotSegmentContainer.children();
3812          
3813          // retrieve elements, run through eventRender callback, bind event handlers
3814          for (i=0; i<segCnt; i++) {
3815              seg = segs[i];
3816              event = seg.event;
3817              eventElement = $(eventElements[i]); // faster than eq()
3818              triggerRes = trigger('eventRender', event, event, eventElement);
3819              if (triggerRes === false) {
3820                  eventElement.remove();
3821              }else{
3822                  if (triggerRes && triggerRes !== true) {
3823                      eventElement.remove();
3824                      eventElement = $(triggerRes)
3825                          .css({
3826                              position: 'absolute',
3827                              top: seg.top,
3828                              left: seg.left
3829                          })
3830                          .appendTo(slotSegmentContainer);
3831                  }
3832                  seg.element = eventElement;
3833                  if (event._id === modifiedEventId) {
3834                      bindSlotSeg(event, eventElement, seg);
3835                  }else{
3836                      eventElement[0]._fci = i; // for lazySegBind
3837                  }
3838                  reportEventElement(event, eventElement);
3839              }
3840          }
3841          
3842          lazySegBind(slotSegmentContainer, segs, bindSlotSeg);
3843          
3844          // record event sides and title positions
3845          for (i=0; i<segCnt; i++) {
3846              seg = segs[i];
3847              if (eventElement = seg.element) {
3848                  val = vsideCache[key = seg.key = cssKey(eventElement[0])];
3849                  seg.vsides = val === undefined ? (vsideCache[key] = vsides(eventElement, true)) : val;
3850                  val = hsideCache[key];
3851                  seg.hsides = val === undefined ? (hsideCache[key] = hsides(eventElement, true)) : val;
3852                  contentElement = eventElement.find('div.fc-event-content');
3853                  if (contentElement.length) {
3854                      seg.contentTop = contentElement[0].offsetTop;
3855                  }
3856              }
3857          }
3858          
3859          // set all positions/dimensions at once
3860          for (i=0; i<segCnt; i++) {
3861              seg = segs[i];
3862              if (eventElement = seg.element) {
3863                  eventElement[0].style.width = Math.max(0, seg.outerWidth - seg.hsides) + 'px';
3864                  height = Math.max(0, seg.outerHeight - seg.vsides);
3865                  eventElement[0].style.height = height + 'px';
3866                  event = seg.event;
3867                  if (seg.contentTop !== undefined && height - seg.contentTop < 10) {
3868                      // not enough room for title, put it in the time header
3869                      eventElement.find('div.fc-event-time')
3870                          .text(formatDate(event.start, opt('timeFormat')) + ' - ' + event.title);
3871                      eventElement.find('div.fc-event-title')
3872                          .remove();
3873                  }
3874                  trigger('eventAfterRender', event, event, eventElement);
3875              }
3876          }
3877                      
3878      }
3879      
3880      
3881  	function slotSegHtml(event, seg) {
3882          var html = "<";
3883          var url = event.url;
3884          var skinCss = getSkinCss(event, opt);
3885          var skinCssAttr = (skinCss ? " style='" + skinCss + "'" : '');
3886          var classes = ['fc-event', 'fc-event-skin', 'fc-event-vert'];
3887          if (isEventDraggable(event)) {
3888              classes.push('fc-event-draggable');
3889          }
3890          if (seg.isStart) {
3891              classes.push('fc-corner-top');
3892          }
3893          if (seg.isEnd) {
3894              classes.push('fc-corner-bottom');
3895          }
3896          classes = classes.concat(event.className);
3897          if (event.source) {
3898              classes = classes.concat(event.source.className || []);
3899          }
3900          if (url) {
3901              html += "a href='" + htmlEscape(event.url) + "'";
3902          }else{
3903              html += "div";
3904          }
3905          html +=
3906              " class='" + classes.join(' ') + "'" +
3907              " style='position:absolute;z-index:8;top:" + seg.top + "px;left:" + seg.left + "px;" + skinCss + "'" +
3908              ">" +
3909              "<div class='fc-event-inner fc-event-skin'" + skinCssAttr + ">" +
3910              "<div class='fc-event-head fc-event-skin'" + skinCssAttr + ">" +
3911              "<div class='fc-event-time'>" +
3912              htmlEscape(formatDates(event.start, event.end, opt('timeFormat'))) +
3913              "</div>" +
3914              "</div>" +
3915              "<div class='fc-event-content'>" +
3916              "<div class='fc-event-title'>" +
3917              htmlEscape(event.title) +
3918              "</div>" +
3919              "</div>" +
3920              "<div class='fc-event-bg'></div>" +
3921              "</div>"; // close inner
3922          if (seg.isEnd && isEventResizable(event)) {
3923              html +=
3924                  "<div class='ui-resizable-handle ui-resizable-s'>=</div>";
3925          }
3926          html +=
3927              "</" + (url ? "a" : "div") + ">";
3928          return html;
3929      }
3930      
3931      
3932  	function bindDaySeg(event, eventElement, seg) {
3933          if (isEventDraggable(event)) {
3934              draggableDayEvent(event, eventElement, seg.isStart);
3935          }
3936          if (seg.isEnd && isEventResizable(event)) {
3937              resizableDayEvent(event, eventElement, seg);
3938          }
3939          eventElementHandlers(event, eventElement);
3940              // needs to be after, because resizableDayEvent might stopImmediatePropagation on click
3941      }
3942      
3943      
3944  	function bindSlotSeg(event, eventElement, seg) {
3945          var timeElement = eventElement.find('div.fc-event-time');
3946          if (isEventDraggable(event)) {
3947              draggableSlotEvent(event, eventElement, timeElement);
3948          }
3949          if (seg.isEnd && isEventResizable(event)) {
3950              resizableSlotEvent(event, eventElement, timeElement);
3951          }
3952          eventElementHandlers(event, eventElement);
3953      }
3954      
3955      
3956      
3957      /* Dragging
3958      -----------------------------------------------------------------------------------*/
3959      
3960      
3961      // when event starts out FULL-DAY
3962      
3963  	function draggableDayEvent(event, eventElement, isStart) {
3964          var origWidth;
3965          var revert;
3966          var allDay=true;
3967          var dayDelta;
3968          var dis = opt('isRTL') ? -1 : 1;
3969          var hoverListener = getHoverListener();
3970          var colWidth = getColWidth();
3971          var slotHeight = getSlotHeight();
3972          var minMinute = getMinMinute();
3973          eventElement.draggable({
3974              zIndex: 9,
3975              opacity: opt('dragOpacity', 'month'), // use whatever the month view was using
3976              revertDuration: opt('dragRevertDuration'),
3977              start: function(ev, ui) {
3978                  trigger('eventDragStart', eventElement, event, ev, ui);
3979                  hideEvents(event, eventElement);
3980                  origWidth = eventElement.width();
3981                  hoverListener.start(function(cell, origCell, rowDelta, colDelta) {
3982                      clearOverlays();
3983                      if (cell) {
3984                          //setOverflowHidden(true);
3985                          revert = false;
3986                          dayDelta = colDelta * dis;
3987                          if (!cell.row) {
3988                              // on full-days
3989                              renderDayOverlay(
3990                                  addDays(cloneDate(event.start), dayDelta),
3991                                  addDays(exclEndDay(event), dayDelta)
3992                              );
3993                              resetElement();
3994                          }else{
3995                              // mouse is over bottom slots
3996                              if (isStart) {
3997                                  if (allDay) {
3998                                      // convert event to temporary slot-event
3999                                      eventElement.width(colWidth - 10); // don't use entire width
4000                                      setOuterHeight(
4001                                          eventElement,
4002                                          slotHeight * Math.round(
4003                                              (event.end ? ((event.end - event.start) / MINUTE_MS) : opt('defaultEventMinutes'))
4004                                              / opt('slotMinutes')
4005                                          )
4006                                      );
4007                                      eventElement.draggable('option', 'grid', [colWidth, 1]);
4008                                      allDay = false;
4009                                  }
4010                              }else{
4011                                  revert = true;
4012                              }
4013                          }
4014                          revert = revert || (allDay && !dayDelta);
4015                      }else{
4016                          resetElement();
4017                          //setOverflowHidden(false);
4018                          revert = true;
4019                      }
4020                      eventElement.draggable('option', 'revert', revert);
4021                  }, ev, 'drag');
4022              },
4023              stop: function(ev, ui) {
4024                  hoverListener.stop();
4025                  clearOverlays();
4026                  trigger('eventDragStop', eventElement, event, ev, ui);
4027                  if (revert) {
4028                      // hasn't moved or is out of bounds (draggable has already reverted)
4029                      resetElement();
4030                      eventElement.css('filter', ''); // clear IE opacity side-effects
4031                      showEvents(event, eventElement);
4032                  }else{
4033                      // changed!
4034                      var minuteDelta = 0;
4035                      if (!allDay) {
4036                          minuteDelta = Math.round((eventElement.offset().top - getBodyContent().offset().top) / slotHeight)
4037                              * opt('slotMinutes')
4038                              + minMinute
4039                              - (event.start.getHours() * 60 + event.start.getMinutes());
4040                      }
4041                      eventDrop(this, event, dayDelta, minuteDelta, allDay, ev, ui);
4042                  }
4043                  //setOverflowHidden(false);
4044              }
4045          });
4046  		function resetElement() {
4047              if (!allDay) {
4048                  eventElement
4049                      .width(origWidth)
4050                      .height('')
4051                      .draggable('option', 'grid', null);
4052                  allDay = true;
4053              }
4054          }
4055      }
4056      
4057      
4058      // when event starts out IN TIMESLOTS
4059      
4060  	function draggableSlotEvent(event, eventElement, timeElement) {
4061          var origPosition;
4062          var allDay=false;
4063          var dayDelta;
4064          var minuteDelta;
4065          var prevMinuteDelta;
4066          var dis = opt('isRTL') ? -1 : 1;
4067          var hoverListener = getHoverListener();
4068          var colCnt = getColCnt();
4069          var colWidth = getColWidth();
4070          var slotHeight = getSlotHeight();
4071          eventElement.draggable({
4072              zIndex: 9,
4073              scroll: false,
4074              grid: [colWidth, slotHeight],
4075              axis: colCnt==1 ? 'y' : false,
4076              opacity: opt('dragOpacity'),
4077              revertDuration: opt('dragRevertDuration'),
4078              start: function(ev, ui) {
4079                  trigger('eventDragStart', eventElement, event, ev, ui);
4080                  hideEvents(event, eventElement);
4081                  origPosition = eventElement.position();
4082                  minuteDelta = prevMinuteDelta = 0;
4083                  hoverListener.start(function(cell, origCell, rowDelta, colDelta) {
4084                      eventElement.draggable('option', 'revert', !cell);
4085                      clearOverlays();
4086                      if (cell) {
4087                          dayDelta = colDelta * dis;
4088                          if (opt('allDaySlot') && !cell.row) {
4089                              // over full days
4090                              if (!allDay) {
4091                                  // convert to temporary all-day event
4092                                  allDay = true;
4093                                  timeElement.hide();
4094                                  eventElement.draggable('option', 'grid', null);
4095                              }
4096                              renderDayOverlay(
4097                                  addDays(cloneDate(event.start), dayDelta),
4098                                  addDays(exclEndDay(event), dayDelta)
4099                              );
4100                          }else{
4101                              // on slots
4102                              resetElement();
4103                          }
4104                      }
4105                  }, ev, 'drag');
4106              },
4107              drag: function(ev, ui) {
4108                  minuteDelta = Math.round((ui.position.top - origPosition.top) / slotHeight) * opt('slotMinutes');
4109                  if (minuteDelta != prevMinuteDelta) {
4110                      if (!allDay) {
4111                          updateTimeText(minuteDelta);
4112                      }
4113                      prevMinuteDelta = minuteDelta;
4114                  }
4115              },
4116              stop: function(ev, ui) {
4117                  var cell = hoverListener.stop();
4118                  clearOverlays();
4119                  trigger('eventDragStop', eventElement, event, ev, ui);
4120                  if (cell && (dayDelta || minuteDelta || allDay)) {
4121                      // changed!
4122                      eventDrop(this, event, dayDelta, allDay ? 0 : minuteDelta, allDay, ev, ui);
4123                  }else{
4124                      // either no change or out-of-bounds (draggable has already reverted)
4125                      resetElement();
4126                      eventElement.css('filter', ''); // clear IE opacity side-effects
4127                      eventElement.css(origPosition); // sometimes fast drags make event revert to wrong position
4128                      updateTimeText(0);
4129                      showEvents(event, eventElement);
4130                  }
4131              }
4132          });
4133  		function updateTimeText(minuteDelta) {
4134              var newStart = addMinutes(cloneDate(event.start), minuteDelta);
4135              var newEnd;
4136              if (event.end) {
4137                  newEnd = addMinutes(cloneDate(event.end), minuteDelta);
4138              }
4139              timeElement.text(formatDates(newStart, newEnd, opt('timeFormat')));
4140          }
4141  		function resetElement() {
4142              // convert back to original slot-event
4143              if (allDay) {
4144                  timeElement.css('display', ''); // show() was causing display=inline
4145                  eventElement.draggable('option', 'grid', [colWidth, slotHeight]);
4146                  allDay = false;
4147              }
4148          }
4149      }
4150      
4151      
4152      
4153      /* Resizing
4154      --------------------------------------------------------------------------------------*/
4155      
4156      
4157  	function resizableSlotEvent(event, eventElement, timeElement) {
4158          var slotDelta, prevSlotDelta;
4159          var slotHeight = getSlotHeight();
4160          eventElement.resizable({
4161              handles: {
4162                  s: 'div.ui-resizable-s'
4163              },
4164              grid: slotHeight,
4165              start: function(ev, ui) {
4166                  slotDelta = prevSlotDelta = 0;
4167                  hideEvents(event, eventElement);
4168                  eventElement.css('z-index', 9);
4169                  trigger('eventResizeStart', this, event, ev, ui);
4170              },
4171              resize: function(ev, ui) {
4172                  // don't rely on ui.size.height, doesn't take grid into account
4173                  slotDelta = Math.round((Math.max(slotHeight, eventElement.height()) - ui.originalSize.height) / slotHeight);
4174                  if (slotDelta != prevSlotDelta) {
4175                      timeElement.text(
4176                          formatDates(
4177                              event.start,
4178                              (!slotDelta && !event.end) ? null : // no change, so don't display time range
4179                                  addMinutes(eventEnd(event), opt('slotMinutes')*slotDelta),
4180                              opt('timeFormat')
4181                          )
4182                      );
4183                      prevSlotDelta = slotDelta;
4184                  }
4185              },
4186              stop: function(ev, ui) {
4187                  trigger('eventResizeStop', this, event, ev, ui);
4188                  if (slotDelta) {
4189                      eventResize(this, event, 0, opt('slotMinutes')*slotDelta, ev, ui);
4190                  }else{
4191                      eventElement.css('z-index', 8);
4192                      showEvents(event, eventElement);
4193                      // BUG: if event was really short, need to put title back in span
4194                  }
4195              }
4196          });
4197      }
4198      
4199  
4200  }
4201  
4202  
4203  function countForwardSegs(levels) {
4204      var i, j, k, level, segForward, segBack;
4205      for (i=levels.length-1; i>0; i--) {
4206          level = levels[i];
4207          for (j=0; j<level.length; j++) {
4208              segForward = level[j];
4209              for (k=0; k<levels[i-1].length; k++) {
4210                  segBack = levels[i-1][k];
4211                  if (segsCollide(segForward, segBack)) {
4212                      segBack.forward = Math.max(segBack.forward||0, (segForward.forward||0)+1);
4213                  }
4214              }
4215          }
4216      }
4217  }
4218  
4219  
4220  
4221  
4222  function View(element, calendar, viewName) {
4223      var t = this;
4224      
4225      
4226      // exports
4227      t.element = element;
4228      t.calendar = calendar;
4229      t.name = viewName;
4230      t.opt = opt;
4231      t.trigger = trigger;
4232      //t.setOverflowHidden = setOverflowHidden;
4233      t.isEventDraggable = isEventDraggable;
4234      t.isEventResizable = isEventResizable;
4235      t.reportEvents = reportEvents;
4236      t.eventEnd = eventEnd;
4237      t.reportEventElement = reportEventElement;
4238      t.reportEventClear = reportEventClear;
4239      t.eventElementHandlers = eventElementHandlers;
4240      t.showEvents = showEvents;
4241      t.hideEvents = hideEvents;
4242      t.eventDrop = eventDrop;
4243      t.eventResize = eventResize;
4244      // t.title
4245      // t.start, t.end
4246      // t.visStart, t.visEnd
4247      
4248      
4249      // imports
4250      var defaultEventEnd = t.defaultEventEnd;
4251      var normalizeEvent = calendar.normalizeEvent; // in EventManager
4252      var reportEventChange = calendar.reportEventChange;
4253      
4254      
4255      // locals
4256      var eventsByID = {};
4257      var eventElements = [];
4258      var eventElementsByID = {};
4259      var options = calendar.options;
4260      
4261      
4262      
4263  	function opt(name, viewNameOverride) {
4264          var v = options[name];
4265          if (typeof v == 'object') {
4266              return smartProperty(v, viewNameOverride || viewName);
4267          }
4268          return v;
4269      }
4270  
4271      
4272  	function trigger(name, thisObj) {
4273          return calendar.trigger.apply(
4274              calendar,
4275              [name, thisObj || t].concat(Array.prototype.slice.call(arguments, 2), [t])
4276          );
4277      }
4278      
4279      
4280      /*
4281      function setOverflowHidden(bool) {
4282          element.css('overflow', bool ? 'hidden' : '');
4283      }
4284      */
4285      
4286      
4287  	function isEventDraggable(event) {
4288          return isEventEditable(event) && !opt('disableDragging');
4289      }
4290      
4291      
4292  	function isEventResizable(event) { // but also need to make sure the seg.isEnd == true
4293          return isEventEditable(event) && !opt('disableResizing');
4294      }
4295      
4296      
4297  	function isEventEditable(event) {
4298          return firstDefined(event.editable, (event.source || {}).editable, opt('editable'));
4299      }
4300      
4301      
4302      
4303      /* Event Data
4304      ------------------------------------------------------------------------------*/
4305      
4306      
4307      // report when view receives new events
4308  	function reportEvents(events) { // events are already normalized at this point
4309          eventsByID = {};
4310          var i, len=events.length, event;
4311          for (i=0; i<len; i++) {
4312              event = events[i];
4313              if (eventsByID[event._id]) {
4314                  eventsByID[event._id].push(event);
4315              }else{
4316                  eventsByID[event._id] = [event];
4317              }
4318          }
4319      }
4320      
4321      
4322      // returns a Date object for an event's end
4323  	function eventEnd(event) {
4324          return event.end ? cloneDate(event.end) : defaultEventEnd(event);
4325      }
4326      
4327      
4328      
4329      /* Event Elements
4330      ------------------------------------------------------------------------------*/
4331      
4332      
4333      // report when view creates an element for an event
4334  	function reportEventElement(event, element) {
4335          eventElements.push(element);
4336          if (eventElementsByID[event._id]) {
4337              eventElementsByID[event._id].push(element);
4338          }else{
4339              eventElementsByID[event._id] = [element];
4340          }
4341      }
4342      
4343      
4344  	function reportEventClear() {
4345          eventElements = [];
4346          eventElementsByID = {};
4347      }
4348      
4349      
4350      // attaches eventClick, eventMouseover, eventMouseout
4351  	function eventElementHandlers(event, eventElement) {
4352          eventElement
4353              .click(function(ev) {
4354                  if (!eventElement.hasClass('ui-draggable-dragging') &&
4355                      !eventElement.hasClass('ui-resizable-resizing')) {
4356                          return trigger('eventClick', this, event, ev);
4357                      }
4358              })
4359              .hover(
4360                  function(ev) {
4361                      trigger('eventMouseover', this, event, ev);
4362                  },
4363                  function(ev) {
4364                      trigger('eventMouseout', this, event, ev);
4365                  }
4366              );
4367          // TODO: don't fire eventMouseover/eventMouseout *while* dragging is occuring (on subject element)
4368          // TODO: same for resizing
4369      }
4370      
4371      
4372  	function showEvents(event, exceptElement) {
4373          eachEventElement(event, exceptElement, 'show');
4374      }
4375      
4376      
4377  	function hideEvents(event, exceptElement) {
4378          eachEventElement(event, exceptElement, 'hide');
4379      }
4380      
4381      
4382  	function eachEventElement(event, exceptElement, funcName) {
4383          var elements = eventElementsByID[event._id],
4384              i, len = elements.length;
4385          for (i=0; i<len; i++) {
4386              if (!exceptElement || elements[i][0] != exceptElement[0]) {
4387                  elements[i][funcName]();
4388              }
4389          }
4390      }
4391      
4392      
4393      
4394      /* Event Modification Reporting
4395      ---------------------------------------------------------------------------------*/
4396      
4397      
4398  	function eventDrop(e, event, dayDelta, minuteDelta, allDay, ev, ui) {
4399          var oldAllDay = event.allDay;
4400          var eventId = event._id;
4401          moveEvents(eventsByID[eventId], dayDelta, minuteDelta, allDay);
4402          trigger(
4403              'eventDrop',
4404              e,
4405              event,
4406              dayDelta,
4407              minuteDelta,
4408              allDay,
4409              function() {
4410                  // TODO: investigate cases where this inverse technique might not work
4411                  moveEvents(eventsByID[eventId], -dayDelta, -minuteDelta, oldAllDay);
4412                  reportEventChange(eventId);
4413              },
4414              ev,
4415              ui
4416          );
4417          reportEventChange(eventId);
4418      }
4419      
4420      
4421  	function eventResize(e, event, dayDelta, minuteDelta, ev, ui) {
4422          var eventId = event._id;
4423          elongateEvents(eventsByID[eventId], dayDelta, minuteDelta);
4424          trigger(
4425              'eventResize',
4426              e,
4427              event,
4428              dayDelta,
4429              minuteDelta,
4430              function() {
4431                  // TODO: investigate cases where this inverse technique might not work
4432                  elongateEvents(eventsByID[eventId], -dayDelta, -minuteDelta);
4433                  reportEventChange(eventId);
4434              },
4435              ev,
4436              ui
4437          );
4438          reportEventChange(eventId);
4439      }
4440      
4441      
4442      
4443      /* Event Modification Math
4444      ---------------------------------------------------------------------------------*/
4445      
4446      
4447  	function moveEvents(events, dayDelta, minuteDelta, allDay) {
4448          minuteDelta = minuteDelta || 0;
4449          for (var e, len=events.length, i=0; i<len; i++) {
4450              e = events[i];
4451              if (allDay !== undefined) {
4452                  e.allDay = allDay;
4453              }
4454              addMinutes(addDays(e.start, dayDelta, true), minuteDelta);
4455              if (e.end) {
4456                  e.end = addMinutes(addDays(e.end, dayDelta, true), minuteDelta);
4457              }
4458              normalizeEvent(e, options);
4459          }
4460      }
4461      
4462      
4463  	function elongateEvents(events, dayDelta, minuteDelta) {
4464          minuteDelta = minuteDelta || 0;
4465          for (var e, len=events.length, i=0; i<len; i++) {
4466              e = events[i];
4467              e.end = addMinutes(addDays(eventEnd(e), dayDelta, true), minuteDelta);
4468              normalizeEvent(e, options);
4469          }
4470      }
4471      
4472  
4473  }
4474  
4475  function DayEventRenderer() {
4476      var t = this;
4477  
4478      
4479      // exports
4480      t.renderDaySegs = renderDaySegs;
4481      t.resizableDayEvent = resizableDayEvent;
4482      
4483      
4484      // imports
4485      var opt = t.opt;
4486      var trigger = t.trigger;
4487      var isEventDraggable = t.isEventDraggable;
4488      var isEventResizable = t.isEventResizable;
4489      var eventEnd = t.eventEnd;
4490      var reportEventElement = t.reportEventElement;
4491      var showEvents = t.showEvents;
4492      var hideEvents = t.hideEvents;
4493      var eventResize = t.eventResize;
4494      var getRowCnt = t.getRowCnt;
4495      var getColCnt = t.getColCnt;
4496      var getColWidth = t.getColWidth;
4497      var allDayRow = t.allDayRow;
4498      var allDayBounds = t.allDayBounds;
4499      var colContentLeft = t.colContentLeft;
4500      var colContentRight = t.colContentRight;
4501      var dayOfWeekCol = t.dayOfWeekCol;
4502      var dateCell = t.dateCell;
4503      var compileDaySegs = t.compileDaySegs;
4504      var getDaySegmentContainer = t.getDaySegmentContainer;
4505      var bindDaySeg = t.bindDaySeg; //TODO: streamline this
4506      var formatDates = t.calendar.formatDates;
4507      var renderDayOverlay = t.renderDayOverlay;
4508      var clearOverlays = t.clearOverlays;
4509      var clearSelection = t.clearSelection;
4510      
4511      
4512      
4513      /* Rendering
4514      -----------------------------------------------------------------------------*/
4515      
4516      
4517  	function renderDaySegs(segs, modifiedEventId) {
4518          var segmentContainer = getDaySegmentContainer();
4519          var rowDivs;
4520          var rowCnt = getRowCnt();
4521          var colCnt = getColCnt();
4522          var i = 0;
4523          var rowI;
4524          var levelI;
4525          var colHeights;
4526          var j;
4527          var segCnt = segs.length;
4528          var seg;
4529          var top;
4530          var k;
4531          segmentContainer[0].innerHTML = daySegHTML(segs); // faster than .html()
4532          daySegElementResolve(segs, segmentContainer.children());
4533          daySegElementReport(segs);
4534          daySegHandlers(segs, segmentContainer, modifiedEventId);
4535          daySegCalcHSides(segs);
4536          daySegSetWidths(segs);
4537          daySegCalcHeights(segs);
4538          rowDivs = getRowDivs();
4539          // set row heights, calculate event tops (in relation to row top)
4540          for (rowI=0; rowI<rowCnt; rowI++) {
4541              levelI = 0;
4542              colHeights = [];
4543              for (j=0; j<colCnt; j++) {
4544                  colHeights[j] = 0;
4545              }
4546              while (i<segCnt && (seg = segs[i]).row == rowI) {
4547                  // loop through segs in a row
4548                  top = arrayMax(colHeights.slice(seg.startCol, seg.endCol));
4549                  seg.top = top;
4550                  top += seg.outerHeight;
4551                  for (k=seg.startCol; k<seg.endCol; k++) {
4552                      colHeights[k] = top;
4553                  }
4554                  i++;
4555              }
4556              rowDivs[rowI].height(arrayMax(colHeights));
4557          }
4558          daySegSetTops(segs, getRowTops(rowDivs));
4559      }
4560      
4561      
4562  	function renderTempDaySegs(segs, adjustRow, adjustTop) {
4563          var tempContainer = $("<div/>");
4564          var elements;
4565          var segmentContainer = getDaySegmentContainer();
4566          var i;
4567          var segCnt = segs.length;
4568          var element;
4569          tempContainer[0].innerHTML = daySegHTML(segs); // faster than .html()
4570          elements = tempContainer.children();
4571          segmentContainer.append(elements);
4572          daySegElementResolve(segs, elements);
4573          daySegCalcHSides(segs);
4574          daySegSetWidths(segs);
4575          daySegCalcHeights(segs);
4576          daySegSetTops(segs, getRowTops(getRowDivs()));
4577          elements = [];
4578          for (i=0; i<segCnt; i++) {
4579              element = segs[i].element;
4580              if (element) {
4581                  if (segs[i].row === adjustRow) {
4582                      element.css('top', adjustTop);
4583                  }
4584                  elements.push(element[0]);
4585              }
4586          }
4587          return $(elements);
4588      }
4589      
4590      
4591  	function daySegHTML(segs) { // also sets seg.left and seg.outerWidth
4592          var rtl = opt('isRTL');
4593          var i;
4594          var segCnt=segs.length;
4595          var seg;
4596          var event;
4597          var url;
4598          var classes;
4599          var bounds = allDayBounds();
4600          var minLeft = bounds.left;
4601          var maxLeft = bounds.right;
4602          var leftCol;
4603          var rightCol;
4604          var left;
4605          var right;
4606          var skinCss;
4607          var html = '';
4608          // calculate desired position/dimensions, create html
4609          for (i=0; i<segCnt; i++) {
4610              seg = segs[i];
4611              event = seg.event;
4612              classes = ['fc-event', 'fc-event-skin', 'fc-event-hori'];
4613              if (isEventDraggable(event)) {
4614                  classes.push('fc-event-draggable');
4615              }
4616              if (rtl) {
4617                  if (seg.isStart) {
4618                      classes.push('fc-corner-right');
4619                  }
4620                  if (seg.isEnd) {
4621                      classes.push('fc-corner-left');
4622                  }
4623                  leftCol = dayOfWeekCol(seg.end.getDay()-1);
4624                  rightCol = dayOfWeekCol(seg.start.getDay());
4625                  left = seg.isEnd ? colContentLeft(leftCol) : minLeft;
4626                  right = seg.isStart ? colContentRight(rightCol) : maxLeft;
4627              }else{
4628                  if (seg.isStart) {
4629                      classes.push('fc-corner-left');
4630                  }
4631                  if (seg.isEnd) {
4632                      classes.push('fc-corner-right');
4633                  }
4634                  leftCol = dayOfWeekCol(seg.start.getDay());
4635                  rightCol = dayOfWeekCol(seg.end.getDay()-1);
4636                  left = seg.isStart ? colContentLeft(leftCol) : minLeft;
4637                  right = seg.isEnd ? colContentRight(rightCol) : maxLeft;
4638              }
4639              classes = classes.concat(event.className);
4640              if (event.source) {
4641                  classes = classes.concat(event.source.className || []);
4642              }
4643              url = event.url;
4644              skinCss = getSkinCss(event, opt);
4645              if (url) {
4646                  html += "<a href='" + htmlEscape(url) + "'";
4647              }else{
4648                  html += "<div";
4649              }
4650              html +=
4651                  " class='" + classes.join(' ') + "'" +
4652                  " style='position:absolute;z-index:8;left:"+left+"px;" + skinCss + "'" +
4653                  ">" +
4654                  "<div" +
4655                  " class='fc-event-inner fc-event-skin'" +
4656                  (skinCss ? " style='" + skinCss + "'" : '') +
4657                  ">";
4658              if (!event.allDay && seg.isStart) {
4659                  html +=
4660                      "<span class='fc-event-time'>" +
4661                      htmlEscape(formatDates(event.start, event.end, opt('timeFormat'))) +
4662                      "</span>";
4663              }
4664              html +=
4665                  "<span class='fc-event-title'>" + htmlEscape(event.title) + "</span>" +
4666                  "</div>";
4667              if (seg.isEnd && isEventResizable(event)) {
4668                  html +=
4669                      "<div class='ui-resizable-handle ui-resizable-" + (rtl ? 'w' : 'e') + "'>" +
4670                      "&nbsp;&nbsp;&nbsp;" + // makes hit area a lot better for IE6/7
4671                      "</div>";
4672              }
4673              html +=
4674                  "</" + (url ? "a" : "div" ) + ">";
4675              seg.left = left;
4676              seg.outerWidth = right - left;
4677              seg.startCol = leftCol;
4678              seg.endCol = rightCol + 1; // needs to be exclusive
4679          }
4680          return html;
4681      }
4682      
4683      
4684  	function daySegElementResolve(segs, elements) { // sets seg.element
4685          var i;
4686          var segCnt = segs.length;
4687          var seg;
4688          var event;
4689          var element;
4690          var triggerRes;
4691          for (i=0; i<segCnt; i++) {
4692              seg = segs[i];
4693              event = seg.event;
4694              element = $(elements[i]); // faster than .eq()
4695              triggerRes = trigger('eventRender', event, event, element);
4696              if (triggerRes === false) {
4697                  element.remove();
4698              }else{
4699                  if (triggerRes && triggerRes !== true) {
4700                      triggerRes = $(triggerRes)
4701                          .css({
4702                              position: 'absolute',
4703                              left: seg.left
4704                          });
4705                      element.replaceWith(triggerRes);
4706                      element = triggerRes;
4707                  }
4708                  seg.element = element;
4709              }
4710          }
4711      }
4712      
4713      
4714  	function daySegElementReport(segs) {
4715          var i;
4716          var segCnt = segs.length;
4717          var seg;
4718          var element;
4719          for (i=0; i<segCnt; i++) {
4720              seg = segs[i];
4721              element = seg.element;
4722              if (element) {
4723                  reportEventElement(seg.event, element);
4724              }
4725          }
4726      }
4727      
4728      
4729  	function daySegHandlers(segs, segmentContainer, modifiedEventId) {
4730          var i;
4731          var segCnt = segs.length;
4732          var seg;
4733          var element;
4734          var event;
4735          // retrieve elements, run through eventRender callback, bind handlers
4736          for (i=0; i<segCnt; i++) {
4737              seg = segs[i];
4738              element = seg.element;
4739              if (element) {
4740                  event = seg.event;
4741                  if (event._id === modifiedEventId) {
4742                      bindDaySeg(event, element, seg);
4743                  }else{
4744                      element[0]._fci = i; // for lazySegBind
4745                  }
4746              }
4747          }
4748          lazySegBind(segmentContainer, segs, bindDaySeg);
4749      }
4750      
4751      
4752  	function daySegCalcHSides(segs) { // also sets seg.key
4753          var i;
4754          var segCnt = segs.length;
4755          var seg;
4756          var element;
4757          var key, val;
4758          var hsideCache = {};
4759          // record event horizontal sides
4760          for (i=0; i<segCnt; i++) {
4761              seg = segs[i];
4762              element = seg.element;
4763              if (element) {
4764                  key = seg.key = cssKey(element[0]);
4765                  val = hsideCache[key];
4766                  if (val === undefined) {
4767                      val = hsideCache[key] = hsides(element, true);
4768                  }
4769                  seg.hsides = val;
4770              }
4771          }
4772      }
4773      
4774      
4775  	function daySegSetWidths(segs) {
4776          var i;
4777          var segCnt = segs.length;
4778          var seg;
4779          var element;
4780          for (i=0; i<segCnt; i++) {
4781              seg = segs[i];
4782              element = seg.element;
4783              if (element) {
4784                  element[0].style.width = Math.max(0, seg.outerWidth - seg.hsides) + 'px';
4785              }
4786          }
4787      }
4788      
4789      
4790  	function daySegCalcHeights(segs) {
4791          var i;
4792          var segCnt = segs.length;
4793          var seg;
4794          var element;
4795          var key, val;
4796          var vmarginCache = {};
4797          // record event heights
4798          for (i=0; i<segCnt; i++) {
4799              seg = segs[i];
4800              element = seg.element;
4801              if (element) {
4802                  key = seg.key; // created in daySegCalcHSides
4803                  val = vmarginCache[key];
4804                  if (val === undefined) {
4805                      val = vmarginCache[key] = vmargins(element);
4806                  }
4807                  seg.outerHeight = element[0].offsetHeight + val;
4808              }
4809          }
4810      }
4811      
4812      
4813  	function getRowDivs() {
4814          var i;
4815          var rowCnt = getRowCnt();
4816          var rowDivs = [];
4817          for (i=0; i<rowCnt; i++) {
4818              rowDivs[i] = allDayRow(i)
4819                  .find('td:first div.fc-day-content > div'); // optimal selector?
4820          }
4821          return rowDivs;
4822      }
4823      
4824      
4825  	function getRowTops(rowDivs) {
4826          var i;
4827          var rowCnt = rowDivs.length;
4828          var tops = [];
4829          for (i=0; i<rowCnt; i++) {
4830              tops[i] = rowDivs[i][0].offsetTop; // !!?? but this means the element needs position:relative if in a table cell!!!!
4831          }
4832          return tops;
4833      }
4834      
4835      
4836  	function daySegSetTops(segs, rowTops) { // also triggers eventAfterRender
4837          var i;
4838          var segCnt = segs.length;
4839          var seg;
4840          var element;
4841          var event;
4842          for (i=0; i<segCnt; i++) {
4843              seg = segs[i];
4844              element = seg.element;
4845              if (element) {
4846                  element[0].style.top = rowTops[seg.row] + (seg.top||0) + 'px';
4847                  event = seg.event;
4848                  trigger('eventAfterRender', event, event, element);
4849              }
4850          }
4851      }
4852      
4853      
4854      
4855      /* Resizing
4856      -----------------------------------------------------------------------------------*/
4857      
4858      
4859  	function resizableDayEvent(event, element, seg) {
4860          var rtl = opt('isRTL');
4861          var direction = rtl ? 'w' : 'e';
4862          var handle = element.find('div.ui-resizable-' + direction);
4863          var isResizing = false;
4864          
4865          // TODO: look into using jquery-ui mouse widget for this stuff
4866          disableTextSelection(element); // prevent native <a> selection for IE
4867          element
4868              .mousedown(function(ev) { // prevent native <a> selection for others
4869                  ev.preventDefault();
4870              })
4871              .click(function(ev) {
4872                  if (isResizing) {
4873                      ev.preventDefault(); // prevent link from being visited (only method that worked in IE6)
4874                      ev.stopImmediatePropagation(); // prevent fullcalendar eventClick handler from being called
4875                                                     // (eventElementHandlers needs to be bound after resizableDayEvent)
4876                  }
4877              });
4878          
4879          handle.mousedown(function(ev) {
4880              if (ev.which != 1) {
4881                  return; // needs to be left mouse button
4882              }
4883              isResizing = true;
4884              var hoverListener = t.getHoverListener();
4885              var rowCnt = getRowCnt();
4886              var colCnt = getColCnt();
4887              var dis = rtl ? -1 : 1;
4888              var dit = rtl ? colCnt-1 : 0;
4889              var elementTop = element.css('top');
4890              var dayDelta;
4891              var helpers;
4892              var eventCopy = $.extend({}, event);
4893              var minCell = dateCell(event.start);
4894              clearSelection();
4895              $('body')
4896                  .css('cursor', direction + '-resize')
4897                  .one('mouseup', mouseup);
4898              trigger('eventResizeStart', this, event, ev);
4899              hoverListener.start(function(cell, origCell) {
4900                  if (cell) {
4901                      var r = Math.max(minCell.row, cell.row);
4902                      var c = cell.col;
4903                      if (rowCnt == 1) {
4904                          r = 0; // hack for all-day area in agenda views
4905                      }
4906                      if (r == minCell.row) {
4907                          if (rtl) {
4908                              c = Math.min(minCell.col, c);
4909                          }else{
4910                              c = Math.max(minCell.col, c);
4911                          }
4912                      }
4913                      dayDelta = (r*7 + c*dis+dit) - (origCell.row*7 + origCell.col*dis+dit);
4914                      var newEnd = addDays(eventEnd(event), dayDelta, true);
4915                      if (dayDelta) {
4916                          eventCopy.end = newEnd;
4917                          var oldHelpers = helpers;
4918                          helpers = renderTempDaySegs(compileDaySegs([eventCopy]), seg.row, elementTop);
4919                          helpers.find('*').css('cursor', direction + '-resize');
4920                          if (oldHelpers) {
4921                              oldHelpers.remove();
4922                          }
4923                          hideEvents(event);
4924                      }else{
4925                          if (helpers) {
4926                              showEvents(event);
4927                              helpers.remove();
4928                              helpers = null;
4929                          }
4930                      }
4931                      clearOverlays();
4932                      renderDayOverlay(event.start, addDays(cloneDate(newEnd), 1)); // coordinate grid already rebuild at hoverListener.start
4933                  }
4934              }, ev);
4935              
4936  			function mouseup(ev) {
4937                  trigger('eventResizeStop', this, event, ev);
4938                  $('body').css('cursor', '');
4939                  hoverListener.stop();
4940                  clearOverlays();
4941                  if (dayDelta) {
4942                      eventResize(this, event, dayDelta, 0, ev);
4943                      // event redraw will clear helpers
4944                  }
4945                  // otherwise, the drag handler already restored the old events
4946                  
4947                  setTimeout(function() { // make this happen after the element's click event
4948                      isResizing = false;
4949                  },0);
4950              }
4951              
4952          });
4953      }
4954      
4955  
4956  }
4957  
4958  //BUG: unselect needs to be triggered when events are dragged+dropped
4959  
4960  function SelectionManager() {
4961      var t = this;
4962      
4963      
4964      // exports
4965      t.select = select;
4966      t.unselect = unselect;
4967      t.reportSelection = reportSelection;
4968      t.daySelectionMousedown = daySelectionMousedown;
4969      
4970      
4971      // imports
4972      var opt = t.opt;
4973      var trigger = t.trigger;
4974      var defaultSelectionEnd = t.defaultSelectionEnd;
4975      var renderSelection = t.renderSelection;
4976      var clearSelection = t.clearSelection;
4977      
4978      
4979      // locals
4980      var selected = false;
4981  
4982  
4983  
4984      // unselectAuto
4985      if (opt('selectable') && opt('unselectAuto')) {
4986          $(document).mousedown(function(ev) {
4987              var ignore = opt('unselectCancel');
4988              if (ignore) {
4989                  if ($(ev.target).parents(ignore).length) { // could be optimized to stop after first match
4990                      return;
4991                  }
4992              }
4993              unselect(ev);
4994          });
4995      }
4996      
4997  
4998  	function select(startDate, endDate, allDay) {
4999          unselect();
5000          if (!endDate) {
5001              endDate = defaultSelectionEnd(startDate, allDay);
5002          }
5003          renderSelection(startDate, endDate, allDay);
5004          reportSelection(startDate, endDate, allDay);
5005      }
5006      
5007      
5008  	function unselect(ev) {
5009          if (selected) {
5010              selected = false;
5011              clearSelection();
5012              trigger('unselect', null, ev);
5013          }
5014      }
5015      
5016      
5017  	function reportSelection(startDate, endDate, allDay, ev) {
5018          selected = true;
5019          trigger('select', null, startDate, endDate, allDay, ev);
5020      }
5021      
5022      
5023  	function daySelectionMousedown(ev) { // not really a generic manager method, oh well
5024          var cellDate = t.cellDate;
5025          var cellIsAllDay = t.cellIsAllDay;
5026          var hoverListener = t.getHoverListener();
5027          var reportDayClick = t.reportDayClick; // this is hacky and sort of weird
5028          if (ev.which == 1 && opt('selectable')) { // which==1 means left mouse button
5029              unselect(ev);
5030              var _mousedownElement = this;
5031              var dates;
5032              hoverListener.start(function(cell, origCell) { // TODO: maybe put cellDate/cellIsAllDay info in cell
5033                  clearSelection();
5034                  if (cell && cellIsAllDay(cell)) {
5035                      dates = [ cellDate(origCell), cellDate(cell) ].sort(cmp);
5036                      renderSelection(dates[0], dates[1], true);
5037                  }else{
5038                      dates = null;
5039                  }
5040              }, ev);
5041              $(document).one('mouseup', function(ev) {
5042                  hoverListener.stop();
5043                  if (dates) {
5044                      if (+dates[0] == +dates[1]) {
5045                          reportDayClick(dates[0], true, ev);
5046                      }
5047                      reportSelection(dates[0], dates[1], true, ev);
5048                  }
5049              });
5050          }
5051      }
5052  
5053  
5054  }
5055   
5056  function OverlayManager() {
5057      var t = this;
5058      
5059      
5060      // exports
5061      t.renderOverlay = renderOverlay;
5062      t.clearOverlays = clearOverlays;
5063      
5064      
5065      // locals
5066      var usedOverlays = [];
5067      var unusedOverlays = [];
5068      
5069      
5070  	function renderOverlay(rect, parent) {
5071          var e = unusedOverlays.shift();
5072          if (!e) {
5073              e = $("<div class='fc-cell-overlay' style='position:absolute;z-index:3'/>");
5074          }
5075          if (e[0].parentNode != parent[0]) {
5076              e.appendTo(parent);
5077          }
5078          usedOverlays.push(e.css(rect).show());
5079          return e;
5080      }
5081      
5082  
5083  	function clearOverlays() {
5084          var e;
5085          while (e = usedOverlays.shift()) {
5086              unusedOverlays.push(e.hide().unbind());
5087          }
5088      }
5089  
5090  
5091  }
5092  
5093  function CoordinateGrid(buildFunc) {
5094  
5095      var t = this;
5096      var rows;
5097      var cols;
5098      
5099      
5100      t.build = function() {
5101          rows = [];
5102          cols = [];
5103          buildFunc(rows, cols);
5104      };
5105      
5106      
5107      t.cell = function(x, y) {
5108          var rowCnt = rows.length;
5109          var colCnt = cols.length;
5110          var i, r=-1, c=-1;
5111          for (i=0; i<rowCnt; i++) {
5112              if (y >= rows[i][0] && y < rows[i][1]) {
5113                  r = i;
5114                  break;
5115              }
5116          }
5117          for (i=0; i<colCnt; i++) {
5118              if (x >= cols[i][0] && x < cols[i][1]) {
5119                  c = i;
5120                  break;
5121              }
5122          }
5123          return (r>=0 && c>=0) ? { row:r, col:c } : null;
5124      };
5125      
5126      
5127      t.rect = function(row0, col0, row1, col1, originElement) { // row1,col1 is inclusive
5128          var origin = originElement.offset();
5129          return {
5130              top: rows[row0][0] - origin.top,
5131              left: cols[col0][0] - origin.left,
5132              width: cols[col1][1] - cols[col0][0],
5133              height: rows[row1][1] - rows[row0][0]
5134          };
5135      };
5136  
5137  }
5138  
5139  function HoverListener(coordinateGrid) {
5140  
5141  
5142      var t = this;
5143      var bindType;
5144      var change;
5145      var firstCell;
5146      var cell;
5147      
5148      
5149      t.start = function(_change, ev, _bindType) {
5150          change = _change;
5151          firstCell = cell = null;
5152          coordinateGrid.build();
5153          mouse(ev);
5154          bindType = _bindType || 'mousemove';
5155          $(document).bind(bindType, mouse);
5156      };
5157      
5158      
5159  	function mouse(ev) {
5160          _fixUIEvent(ev); // see below
5161          var newCell = coordinateGrid.cell(ev.pageX, ev.pageY);
5162          if (!newCell != !cell || newCell && (newCell.row != cell.row || newCell.col != cell.col)) {
5163              if (newCell) {
5164                  if (!firstCell) {
5165                      firstCell = newCell;
5166                  }
5167                  change(newCell, firstCell, newCell.row-firstCell.row, newCell.col-firstCell.col);
5168              }else{
5169                  change(newCell, firstCell);
5170              }
5171              cell = newCell;
5172          }
5173      }
5174      
5175      
5176      t.stop = function() {
5177          $(document).unbind(bindType, mouse);
5178          return cell;
5179      };
5180      
5181      
5182  }
5183  
5184  
5185  
5186  // this fix was only necessary for jQuery UI 1.8.16 (and jQuery 1.7 or 1.7.1)
5187  // upgrading to jQuery UI 1.8.17 (and using either jQuery 1.7 or 1.7.1) fixed the problem
5188  // but keep this in here for 1.8.16 users
5189  // and maybe remove it down the line
5190  
5191  function _fixUIEvent(event) { // for issue 1168
5192      if (event.pageX === undefined) {
5193          event.pageX = event.originalEvent.pageX;
5194          event.pageY = event.originalEvent.pageY;
5195      }
5196  }
5197  function HorizontalPositionCache(getElement) {
5198  
5199      var t = this,
5200          elements = {},
5201          lefts = {},
5202          rights = {};
5203          
5204      function e(i) {
5205          return elements[i] = elements[i] || getElement(i);
5206      }
5207      
5208      t.left = function(i) {
5209          return lefts[i] = lefts[i] === undefined ? e(i).position().left : lefts[i];
5210      };
5211      
5212      t.right = function(i) {
5213          return rights[i] = rights[i] === undefined ? t.left(i) + e(i).width() : rights[i];
5214      };
5215      
5216      t.clear = function() {
5217          elements = {};
5218          lefts = {};
5219          rights = {};
5220      };
5221      
5222  }
5223  
5224  })(jQuery);


Generated: Fri Nov 28 20:08:37 2014 Cross-referenced by PHPXref 0.7.1