Index: fullcalendar.css
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/fullcalendar/fullcalendar.css,v
retrieving revision 1.3
diff -u -p -r1.3 fullcalendar.css
--- fullcalendar.css 11 Nov 2010 16:58:56 -0000 1.3
+++ fullcalendar.css 24 Nov 2010 16:34:58 -0000
@@ -1,14 +1,16 @@
-/* $Id: fullcalendar.css,v 1.3 2010/11/11 16:58:56 ablondeau Exp $ */
/*
- * FullCalendar v1.4.7 Stylesheet
+ * FullCalendar v1.4.9 Stylesheet
*
* Feel free to edit this file to customize the look of FullCalendar.
* When upgrading to newer versions, please upgrade this file as well,
* porting over any customizations afterwards.
*
- * Date: Mon Jul 5 16:07:40 2010 -0700
+ * Date: Fri Nov 19 22:45:44 2010 -0800
*
*/
+
+
+/* TODO: make font sizes look the same in all doctypes */
.fc,
@@ -33,12 +35,6 @@
}
-/* Custom for Drupal module - undo Drupal default theme link colors */
-.fc-content a:link, .fc-content a:visited {
-color:#FFFFFF;
-text-decoration:none;
-}
-
/* Header
------------------------------------------------------------------------*/
@@ -222,7 +218,7 @@ table.fc-header {
background: #ffc;
}
-.fc-content .fc-not-today {
+.fc-content .fc-not-today { /* override jq-ui highlight (TODO: ui-widget-content) */
background: none;
}
@@ -239,12 +235,14 @@ table.fc-header {
+
+
/* Global Event Styles
------------------------------------------------------------------------*/
-.fc-event-default,
-.fc-agenda .fc-event-default .fc-event-time,
-.fc-event-default a {
+.fc-event,
+.fc-agenda .fc-event-time,
+.fc-event a {
border-style: solid;
border-color: #36c; /* default BORDER color (probably the same as background-color) */
background-color: #36c; /* default BACKGROUND color */
@@ -300,7 +298,7 @@ table.fc-header {
/* resizable */
-.fc .ui-resizable-handle {
+.fc .ui-resizable-handle { /*** TODO: don't use ui-resizable anoymore, change class ***/
display: block;
position: absolute;
z-index: 99999;
@@ -365,6 +363,7 @@ table.fc-header {
}
+
/* Month View, Basic Week View, Basic Day View
------------------------------------------------------------------------*/
@@ -500,6 +499,12 @@ table.fc-header {
padding: 2px 2px 0; /* distance between events and day edges */
}
+/* vertical background columns */
+
+.fc .fc-agenda-bg .ui-state-highlight {
+ background-image: none; /* tall column, don't want repeating background image */
+ }
+
/* Vertical Events
Index: fullcalendar.js
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/fullcalendar/fullcalendar.js,v
retrieving revision 1.2
diff -u -p -r1.2 fullcalendar.js
--- fullcalendar.js 11 Nov 2010 17:09:21 -0000 1.2
+++ fullcalendar.js 24 Nov 2010 16:34:59 -0000
@@ -1,32 +1,23 @@
-/* $Id: fullcalendar.js,v 1.2 2010/11/11 17:09:21 ablondeau Exp $ */
/**
* @preserve
- * FullCalendar v1.4.7
+ * FullCalendar v1.4.9
* http://arshaw.com/fullcalendar/
*
* Use fullcalendar.css for basic styling.
- * For event drag & drop, required jQuery UI draggable.
+ * For event drag & drop, requires jQuery UI draggable.
* For event resizing, requires jQuery UI resizable.
*
- * Copyright (c) 2009 Adam Shaw
- * Dual licensed under the MIT and GPL licenses:
- * http://www.opensource.org/licenses/mit-license.php
- * http://www.gnu.org/licenses/gpl.html
+ * Copyright (c) 2010 Adam Shaw
+ * Dual licensed under the MIT and GPL licenses, located in
+ * MIT-LICENSE.txt and GPL-LICENSE.txt respectively.
*
- * Date: Mon Jul 5 16:07:40 2010 -0700
+ * Date: Fri Nov 19 22:45:44 2010 -0800
*
*/
(function($, undefined) {
-var fc = $.fullCalendar = {};
-var views = fc.views = {};
-
-
-/* Defaults
------------------------------------------------------------------------------*/
-
var defaults = {
// display
@@ -45,6 +36,7 @@ var defaults = {
//disableResizing: false,
allDayDefault: true,
+ ignoreTimezone: true,
// event ajax
lazyFetching: true,
@@ -117,31 +109,28 @@ var rtlDefaults = {
}
};
-// function for adding/overriding defaults
-var setDefaults = fc.setDefaults = function(d) {
- $.extend(true, defaults, d);
-};
+var fc = $.fullCalendar = { version: "1.4.9" };
+var fcViews = fc.views = {};
-/* .fullCalendar jQuery function
------------------------------------------------------------------------------*/
$.fn.fullCalendar = function(options) {
+
// method calling
if (typeof options == 'string') {
- var args = Array.prototype.slice.call(arguments, 1),
- res;
+ var args = Array.prototype.slice.call(arguments, 1);
+ var res;
this.each(function() {
- var data = $.data(this, 'fullCalendar');
- if (data) {
- var meth = data[options];
- if (meth) {
- var r = meth.apply(this, args);
- if (res === undefined) {
- res = r;
- }
+ var calendar = $.data(this, 'fullCalendar');
+ if (calendar && $.isFunction(calendar[options])) {
+ var r = calendar[options].apply(calendar, args);
+ if (res === undefined) {
+ res = r;
+ }
+ if (options == 'destroy') {
+ $.removeData(this, 'fullCalendar');
}
}
});
@@ -150,8 +139,9 @@ $.fn.fullCalendar = function(options) {
}
return this;
}
-
- // pluck the 'events' and 'eventSources' options
+
+
+ // would like to have this logic in EventManager, but needs to happen before options are recursively extended
var eventSources = options.eventSources || [];
delete options.eventSources;
if (options.events) {
@@ -159,950 +149,1202 @@ $.fn.fullCalendar = function(options) {
delete options.events;
}
- // first event source reserved for 'sticky' events
- eventSources.unshift([]);
-
- // initialize options
+
options = $.extend(true, {},
defaults,
(options.isRTL || options.isRTL===undefined && defaults.isRTL) ? rtlDefaults : {},
options
);
- var tm = options.theme ? 'ui' : 'fc'; // for making theme classes
- this.each(function() {
+ this.each(function(i, _element) {
+ var element = $(_element);
+ var calendar = new Calendar(element, options, eventSources);
+ element.data('fullCalendar', calendar); // TODO: look into memory leak implications
+ calendar.render();
+ });
- /* Instance Initialization
- -----------------------------------------------------------------------------*/
-
- // element
- var _element = this,
- element = $(_element).addClass('fc'),
- elementOuterWidth,
- content = $("
").prependTo(_element),
- suggestedViewHeight,
- resizeUID = 0,
- ignoreWindowResize = 0,
- date = new Date(),
- viewName, // the current view name (TODO: look into getting rid of)
- view, // the current view
- viewInstances = {},
- absoluteViewElement;
-
-
-
+ return this;
+
+};
+
+
+// function for adding/overriding defaults
+function setDefaults(d) {
+ $.extend(true, defaults, d);
+}
+
+
+
+
+function Calendar(element, options, eventSources) {
+ var t = this;
+
+
+ // exports
+ t.options = options;
+ t.render = render;
+ t.destroy = destroy;
+ t.refetchEvents = refetchEvents;
+ t.reportEvents = reportEvents;
+ t.reportEventChange = reportEventChange;
+ t.changeView = changeView;
+ t.select = select;
+ t.unselect = unselect;
+ t.prev = prev;
+ t.next = next;
+ t.prevYear = prevYear;
+ t.nextYear = nextYear;
+ t.today = today;
+ t.gotoDate = gotoDate;
+ t.incrementDate = incrementDate;
+ t.formatDate = function(format, date) { return formatDate(format, date, options) };
+ t.formatDates = function(format, date1, date2) { return formatDates(format, date1, date2, options) };
+ t.getDate = getDate;
+ t.getView = getView;
+ t.option = option;
+ t.trigger = trigger;
+
+
+ // imports
+ EventManager.call(t, options, eventSources);
+ var isFetchNeeded = t.isFetchNeeded;
+ var fetchEvents = t.fetchEvents;
+
+
+ // locals
+ var _element = element[0];
+ var header;
+ var headerElement;
+ var content;
+ var tm; // for making theme classes
+ var currentView;
+ var viewInstances = {};
+ var elementOuterWidth;
+ var suggestedViewHeight;
+ var absoluteViewElement;
+ var resizeUID = 0;
+ var ignoreWindowResize = 0;
+ var date = new Date();
+ var events = [];
+ var _dragElement;
+
+
+
+ /* Main Rendering
+ -----------------------------------------------------------------------------*/
+
+
+ setYMD(date, options.year, options.month, options.date);
+
+
+ function render(inc) {
+ if (!content) {
+ initialRender();
+ }else{
+ calcSize();
+ markSizesDirty();
+ markEventsDirty();
+ renderView(inc);
+ }
+ }
+
+
+ function initialRender() {
+ tm = options.theme ? 'ui' : 'fc';
+ element.addClass('fc');
if (options.isRTL) {
element.addClass('fc-rtl');
}
if (options.theme) {
element.addClass('ui-widget');
}
-
- setYMD(date, options.year, options.month, options.day);
-
-
-
- /* View Rendering
- -----------------------------------------------------------------------------*/
-
- function changeView(v) {
- if (v != viewName) {
- ignoreWindowResize++; // because setMinHeight might change the height before render (and subsequently setSize) is reached
-
- viewUnselect();
-
- var oldView = view,
- newViewElement;
-
- if (oldView) {
- if (oldView.eventsChanged) {
- eventsDirty();
- oldView.eventDirty = oldView.eventsChanged = false;
- }
- if (oldView.beforeHide) {
- oldView.beforeHide(); // called before changing min-height. if called after, scroll state is reset (in Opera)
- }
- setMinHeight(content, content.height());
- oldView.element.hide();
- }else{
- setMinHeight(content, 1); // needs to be 1 (not 0) for IE7, or else view dimensions miscalculated
- }
- content.css('overflow', 'hidden');
-
- if (viewInstances[v]) {
- (view = viewInstances[v]).element.show();
- }else{
- view = viewInstances[v] = fc.views[v](
- newViewElement = absoluteViewElement =
- $("")
- .appendTo(content),
- options,
- v // the view's name
- );
- }
-
- if (header) {
- // update 'active' view button
- header.find('div.fc-button-' + viewName).removeClass(tm + '-state-active');
- header.find('div.fc-button-' + v).addClass(tm + '-state-active');
- }
-
- viewName = v;
- render(); // after height has been set, will make absoluteViewElement's position=relative, then set to null
- content.css('overflow', '');
- if (oldView) {
- setMinHeight(content, 1);
- }
- if (!newViewElement && view.afterShow) {
- view.afterShow(); // called after setting min-height/overflow, so in final scroll state (for Opera)
- }
-
- ignoreWindowResize--;
- }
+ content = $("")
+ .prependTo(element);
+ header = new Header(t, options);
+ headerElement = header.render();
+ if (headerElement) {
+ element.prepend(headerElement);
}
-
-
- function render(inc) {
- if (elementVisible()) {
- ignoreWindowResize++; // because view.renderEvents might temporarily change the height before setSize is reached
+ changeView(options.defaultView);
+ $(window).resize(windowResize);
+ // needed for IE in a 0x0 iframe, b/c when it is resized, never triggers a windowResize
+ if (!bodyVisible()) {
+ lateRender();
+ }
+ }
+
+
+ // called when we know the calendar couldn't be rendered when it was initialized,
+ // but we think it's ready now
+ function lateRender() {
+ setTimeout(function() { // IE7 needs this so dimensions are calculated correctly
+ if (!currentView.start && bodyVisible()) { // !currentView.start makes sure this never happens more than once
+ renderView();
+ }
+ },0);
+ }
+
+
+ function destroy() {
+ $(window).unbind('resize', windowResize);
+ header.destroy();
+ content.remove();
+ element.removeClass('fc fc-rtl fc-ui-widget');
+ }
+
+
+
+ function elementVisible() {
+ return _element.offsetWidth !== 0;
+ }
+
+
+ function bodyVisible() {
+ return $('body')[0].offsetWidth !== 0;
+ }
+
+
+
+ /* View Rendering
+ -----------------------------------------------------------------------------*/
+
+
+ function changeView(newViewName) {
+ if (!currentView || newViewName != currentView.name) {
+ ignoreWindowResize++; // because setMinHeight might change the height before render (and subsequently setSize) is reached
- viewUnselect();
-
- if (suggestedViewHeight === undefined) {
- calcSize();
- }
-
- if (!view.start || inc || date < view.start || date >= view.end) {
- view.render(date, inc || 0); // responsible for clearing events
- setSize(true);
- if (!eventStart || !options.lazyFetching || view.visStart < eventStart || view.visEnd > eventEnd) {
- fetchAndRenderEvents();
- }else{
- view.renderEvents(events); // don't refetch
- }
- }
- else if (view.sizeDirty || view.eventsDirty || !options.lazyFetching) {
- view.clearEvents();
- if (view.sizeDirty) {
- setSize();
- }
- if (options.lazyFetching) {
- view.renderEvents(events); // don't refetch
- }else{
- fetchAndRenderEvents();
- }
- }
- elementOuterWidth = element.outerWidth();
- view.sizeDirty = false;
- view.eventsDirty = false;
-
- if (header) {
- // update title text
- header.find('h2.fc-header-title').html(view.title);
- // enable/disable 'today' button
- var today = new Date();
- if (today >= view.start && today < view.end) {
- header.find('div.fc-button-today').addClass(tm + '-state-disabled');
- }else{
- header.find('div.fc-button-today').removeClass(tm + '-state-disabled');
- }
- }
+ unselect();
+
+ var oldView = currentView;
+ var newViewElement;
- ignoreWindowResize--;
- view.trigger('viewDisplay', _element);
+ if (oldView) {
+ (oldView.beforeHide || noop)(); // called before changing min-height. if called after, scroll state is reset (in Opera)
+ setMinHeight(content, content.height());
+ oldView.element.hide();
+ }else{
+ setMinHeight(content, 1); // needs to be 1 (not 0) for IE7, or else view dimensions miscalculated
}
- }
-
-
- function elementVisible() {
- return _element.offsetWidth !== 0;
- }
-
- function bodyVisible() {
- return $('body')[0].offsetWidth !== 0;
- }
-
- function viewUnselect() {
- if (view) {
- view.unselect();
+ content.css('overflow', 'hidden');
+
+ currentView = viewInstances[newViewName];
+ if (currentView) {
+ currentView.element.show();
+ }else{
+ currentView = viewInstances[newViewName] = new fcViews[newViewName](
+ newViewElement = absoluteViewElement =
+ $("")
+ .appendTo(content),
+ t // the calendar object
+ );
}
- }
-
-
- // called when any event objects have been added/removed/changed, rerenders
- function eventsChanged() {
- eventsDirty();
- if (elementVisible()) {
- view.clearEvents();
- view.renderEvents(events);
- view.eventsDirty = false;
+
+ if (oldView) {
+ header.deactivateButton(oldView.name);
}
+ header.activateButton(newViewName);
+
+ renderView(); // after height has been set, will make absoluteViewElement's position=relative, then set to null
+
+ content.css('overflow', '');
+ if (oldView) {
+ setMinHeight(content, 1);
+ }
+
+ if (!newViewElement) {
+ (currentView.afterShow || noop)(); // called after setting min-height/overflow, so in final scroll state (for Opera)
+ }
+
+ ignoreWindowResize--;
}
-
- // marks other views' events as dirty
- function eventsDirty() {
- $.each(viewInstances, function() {
- this.eventsDirty = true;
- });
- }
-
- // called when we know the element size has changed
- function sizeChanged() {
- sizesDirty();
- if (elementVisible()) {
+ }
+
+
+
+ function renderView(inc) {
+ if (elementVisible()) {
+ ignoreWindowResize++; // because renderEvents might temporarily change the height before setSize is reached
+
+ unselect();
+
+ if (suggestedViewHeight === undefined) {
calcSize();
+ }
+
+ var forceEventRender = false;
+ if (!currentView.start || inc || date < currentView.start || date >= currentView.end) {
+ // view must render an entire new date range (and refetch/render events)
+ currentView.render(date, inc || 0); // responsible for clearing events
+ setSize(true);
+ forceEventRender = true;
+ }
+ else if (currentView.sizeDirty) {
+ // view must resize (and rerender events)
+ currentView.clearEvents();
setSize();
- viewUnselect();
- view.rerenderEvents();
- view.sizeDirty = false;
+ forceEventRender = true;
+ }
+ else if (currentView.eventsDirty) {
+ currentView.clearEvents();
+ forceEventRender = true;
+ }
+ currentView.sizeDirty = false;
+ currentView.eventsDirty = false;
+ updateEvents(forceEventRender);
+
+ elementOuterWidth = element.outerWidth();
+
+ header.updateTitle(currentView.title);
+ var today = new Date();
+ if (today >= currentView.start && today < currentView.end) {
+ header.disableButton('today');
+ }else{
+ header.enableButton('today');
}
+
+ ignoreWindowResize--;
+ currentView.trigger('viewDisplay', _element);
}
-
- // marks other views' sizes as dirty
- function sizesDirty() {
- $.each(viewInstances, function() {
- this.sizeDirty = true;
- });
+ }
+
+
+
+ /* Resizing
+ -----------------------------------------------------------------------------*/
+
+
+ function updateSize() {
+ markSizesDirty();
+ if (elementVisible()) {
+ calcSize();
+ setSize();
+ unselect();
+ currentView.clearEvents();
+ currentView.renderEvents(events);
+ currentView.sizeDirty = false;
}
-
-
-
-
- /* Event Sources and Fetching
- -----------------------------------------------------------------------------*/
-
- var events = [],
- eventStart, eventEnd;
-
- // Fetch from ALL sources. Clear 'events' array and populate
- function fetchEvents(callback) {
- events = [];
- eventStart = cloneDate(view.visStart);
- eventEnd = cloneDate(view.visEnd);
- var queued = eventSources.length,
- sourceDone = function() {
- if (!--queued) {
- if (callback) {
- callback(events);
+ }
+
+
+ function markSizesDirty() {
+ $.each(viewInstances, function(i, inst) {
+ inst.sizeDirty = true;
+ });
+ }
+
+
+ function calcSize() {
+ if (options.contentHeight) {
+ suggestedViewHeight = options.contentHeight;
+ }
+ else if (options.height) {
+ suggestedViewHeight = options.height - (headerElement ? headerElement.height() : 0) - vsides(content[0]);
+ }
+ else {
+ suggestedViewHeight = Math.round(content.width() / Math.max(options.aspectRatio, .5));
+ }
+ }
+
+
+ function setSize(dateChanged) { // todo: dateChanged?
+ ignoreWindowResize++;
+ currentView.setHeight(suggestedViewHeight, dateChanged);
+ if (absoluteViewElement) {
+ absoluteViewElement.css('position', 'relative');
+ absoluteViewElement = null;
+ }
+ currentView.setWidth(content.width(), dateChanged);
+ ignoreWindowResize--;
+ }
+
+
+ function windowResize() {
+ if (!ignoreWindowResize) {
+ if (currentView.start) { // view has already been rendered
+ var uid = ++resizeUID;
+ setTimeout(function() { // add a delay
+ if (uid == resizeUID && !ignoreWindowResize && elementVisible()) {
+ if (elementOuterWidth != (elementOuterWidth = element.outerWidth())) {
+ ignoreWindowResize++; // in case the windowResize callback changes the height
+ updateSize();
+ currentView.trigger('windowResize', _element);
+ ignoreWindowResize--;
}
}
- }, i=0;
- for (; i")
+ .append($("
")
+ .append($("")
+ .append(renderSection(sections.left)))
+ .append($("")
+ .append(renderSection(sections.center)))
+ .append($("")
+ .append(renderSection(sections.right))));
+ return element;
+ }
+ }
+
+
+ function destroy() {
+ element.remove();
+ }
+
+
+ function renderSection(buttonStr) {
+ if (buttonStr) {
+ var tr = $("
");
+ $.each(buttonStr.split(' '), function(i) {
+ if (i > 0) {
+ tr.append(" | ");
}
- render();
- },
-
- //
- // Event Manipulation
- //
-
- updateEvent: function(event) { // update an existing event
- var i, len = events.length, e,
- startDelta = event.start - event._start,
- endDelta = event.end ?
- (event.end - (event._end || view.defaultEventEnd(event))) // event._end would be null if event.end
- : 0; // was null and event was just resized
- for (i=0; i");
+ if (prevButton) {
+ prevButton.addClass(tm + '-corner-right');
}
- e.title = event.title;
- e.url = event.url;
- e.allDay = event.allDay;
- e.className = event.className;
- e.editable = event.editable;
- normalizeEvent(e, options);
- }
- }
- normalizeEvent(event, options);
- eventsChanged();
- },
-
- renderEvent: function(event, stick) { // render a new event
- normalizeEvent(event, options);
- if (!event.source) {
- if (stick) {
- (event.source = eventSources[0]).push(event);
- }
- events.push(event);
- }
- eventsChanged();
- },
-
- removeEvents: function(filter) {
- if (!filter) { // remove all
- events = [];
- // clear all array sources
- for (var i=0; i")
- .append($("
")
- .append($("").append(buildSection(sections.left)))
- .append($("").append(buildSection(sections.center)))
- .append($("").append(buildSection(sections.right))))
- .prependTo(element);
- }
- function buildSection(buttonStr) {
- if (buttonStr) {
- var tr = $("
");
- $.each(buttonStr.split(' '), function(i) {
- if (i > 0) {
- tr.append(" | ");
- }
- var prevButton;
- $.each(this.split(','), function(j, buttonName) {
- if (buttonName == 'title') {
- tr.append(" | ");
+ if (buttonClick) {
if (prevButton) {
- prevButton.addClass(tm + '-corner-right');
+ prevButton.addClass(tm + '-no-right');
}
- prevButton = null;
- }else{
- var buttonClick;
- if (publicMethods[buttonName]) {
- buttonClick = publicMethods[buttonName];
+ var button;
+ var icon = options.theme ? smartProperty(options.buttonIcons, buttonName) : null;
+ var text = smartProperty(options.buttonText, buttonName);
+ if (icon) {
+ button = $("");
}
- else if (views[buttonName]) {
- buttonClick = function() {
- button.removeClass(tm + '-state-hover');
- changeView(buttonName);
- };
+ else if (text) {
+ button = $("");
}
- if (buttonClick) {
- if (prevButton) {
- prevButton.addClass(tm + '-no-right');
- }
- var button,
- icon = options.theme ? smartProperty(options.buttonIcons, buttonName) : null,
- text = smartProperty(options.buttonText, buttonName);
- if (icon) {
- button = $("");
- }
- else if (text) {
- button = $("");
- }
- if (button) {
- button
- .click(function() {
- if (!button.hasClass(tm + '-state-disabled')) {
- buttonClick();
- }
- })
- .mousedown(function() {
+ if (button) {
+ button
+ .click(function() {
+ if (!button.hasClass(tm + '-state-disabled')) {
+ buttonClick();
+ }
+ })
+ .mousedown(function() {
+ button
+ .not('.' + tm + '-state-active')
+ .not('.' + tm + '-state-disabled')
+ .addClass(tm + '-state-down');
+ })
+ .mouseup(function() {
+ button.removeClass(tm + '-state-down');
+ })
+ .hover(
+ function() {
button
.not('.' + tm + '-state-active')
.not('.' + tm + '-state-disabled')
- .addClass(tm + '-state-down');
- })
- .mouseup(function() {
- button.removeClass(tm + '-state-down');
- })
- .hover(
- function() {
- button
- .not('.' + tm + '-state-active')
- .not('.' + tm + '-state-disabled')
- .addClass(tm + '-state-hover');
- },
- function() {
- button
- .removeClass(tm + '-state-hover')
- .removeClass(tm + '-state-down');
- }
- )
- .appendTo($(" | ").appendTo(tr));
- if (prevButton) {
- prevButton.addClass(tm + '-no-right');
- }else{
- button.addClass(tm + '-corner-left');
- }
- prevButton = button;
+ .addClass(tm + '-state-hover');
+ },
+ function() {
+ button
+ .removeClass(tm + '-state-hover')
+ .removeClass(tm + '-state-down');
+ }
+ )
+ .appendTo($(" | ").appendTo(tr));
+ if (prevButton) {
+ prevButton.addClass(tm + '-no-right');
+ }else{
+ button.addClass(tm + '-corner-left');
}
+ prevButton = button;
}
}
- });
- if (prevButton) {
- prevButton.addClass(tm + '-corner-right');
- }
- });
- return $("").append(tr);
- }
- }
-
-
-
- /* Resizing
- -----------------------------------------------------------------------------*/
-
-
- function calcSize() {
- if (options.contentHeight) {
- suggestedViewHeight = options.contentHeight;
- }
- else if (options.height) {
- suggestedViewHeight = options.height - (header ? header.height() : 0) - vsides(content[0]);
- }
- else {
- suggestedViewHeight = Math.round(content.width() / Math.max(options.aspectRatio, .5));
- }
- }
-
-
- function setSize(dateChanged) {
- ignoreWindowResize++;
- view.setHeight(suggestedViewHeight, dateChanged);
- if (absoluteViewElement) {
- absoluteViewElement.css('position', 'relative');
- absoluteViewElement = null;
- }
- view.setWidth(content.width(), dateChanged);
- ignoreWindowResize--;
- }
-
-
- function windowResize() {
- if (!ignoreWindowResize) {
- if (view.start) { // view has already been rendered
- var uid = ++resizeUID;
- setTimeout(function() { // add a delay
- if (uid == resizeUID && !ignoreWindowResize && elementVisible()) {
- if (elementOuterWidth != (elementOuterWidth = element.outerWidth())) {
- ignoreWindowResize++; // in case the windowResize callback changes the height
- sizeChanged();
- view.trigger('windowResize', _element);
- ignoreWindowResize--;
- }
- }
- }, 200);
- }else{
- // calendar must have been initialized in a 0x0 iframe that has just been resized
- lateRender();
- }
- }
- }
- $(window).resize(windowResize);
-
-
-
- /* External event dropping
- --------------------------------------------------------*/
-
- if (options.droppable) {
- var _dragElement;
- $(document)
- .bind('dragstart', function(ev, ui) {
- var _e = ev.target;
- var e = $(_e);
- if (!e.parents('.fc').length) { // not already inside a calendar
- var accept = options.dropAccept;
- if ($.isFunction(accept) ? accept.call(_e, e) : e.is(accept)) {
- _dragElement = _e;
- view.dragStart(_dragElement, ev, ui);
- }
- }
- })
- .bind('dragstop', function(ev, ui) {
- if (_dragElement) {
- view.dragStop(_dragElement, ev, ui);
- _dragElement = null;
}
});
- }
-
-
-
- // let's begin...
- changeView(options.defaultView);
-
-
- // needed for IE in a 0x0 iframe, b/c when it is resized, never triggers a windowResize
- if (!bodyVisible()) {
- lateRender();
- }
-
-
- // called when we know the calendar couldn't be rendered when it was initialized,
- // but we think it's ready now
- function lateRender() {
- setTimeout(function() { // IE7 needs this so dimensions are calculated correctly
- if (!view.start && bodyVisible()) { // !view.start makes sure this never happens more than once
- render();
+ if (prevButton) {
+ prevButton.addClass(tm + '-corner-right');
}
- },0);
+ });
+ return $("").append(tr);
}
-
-
- });
+ }
- return this;
-};
-
-
-
-/* Important Event Utilities
------------------------------------------------------------------------------*/
-
-var fakeID = 0;
-
-function normalizeEvent(event, options) {
- event._id = event._id || (event.id === undefined ? '_fc' + fakeID++ : event.id + '');
- if (event.date) {
- if (!event.start) {
- event.start = event.date;
- }
- delete event.date;
- }
- event._start = cloneDate(event.start = parseDate(event.start));
- event.end = parseDate(event.end);
- if (event.end && event.end <= event.start) {
- event.end = null;
- }
- event._end = event.end ? cloneDate(event.end) : null;
- if (event.allDay === undefined) {
- event.allDay = options.allDayDefault;
- }
- if (event.className) {
- if (typeof event.className == 'string') {
- event.className = event.className.split(/\s+/);
- }
- }else{
- event.className = [];
+ function updateTitle(html) {
+ element.find('h2.fc-header-title')
+ .html(html);
}
-}
-// TODO: if there is no start date, return false to indicate an invalid event
-
-
-/* Grid-based Views: month, basicWeek, basicDay
------------------------------------------------------------------------------*/
-
-setDefaults({
- weekMode: 'fixed'
-});
-
-views.month = function(element, options, viewName) {
- return new Grid(element, options, {
- render: function(date, delta) {
- if (delta) {
- addMonths(date, delta);
- date.setDate(1);
- }
- // start/end
- var start = this.start = cloneDate(date, true);
- start.setDate(1);
- this.end = addMonths(cloneDate(start), 1);
- // visStart/visEnd
- var visStart = this.visStart = cloneDate(start),
- visEnd = this.visEnd = cloneDate(this.end),
- nwe = options.weekends ? 0 : 1;
- if (nwe) {
- skipWeekend(visStart);
- skipWeekend(visEnd, -1, true);
- }
- addDays(visStart, -((visStart.getDay() - Math.max(options.firstDay, nwe) + 7) % 7));
- addDays(visEnd, (7 - visEnd.getDay() + Math.max(options.firstDay, nwe)) % 7);
- // row count
- var rowCnt = Math.round((visEnd - visStart) / (DAY_MS * 7));
- if (options.weekMode == 'fixed') {
- addDays(visEnd, (6 - rowCnt) * 7);
- rowCnt = 6;
- }
- // title
- this.title = formatDate(
- start,
- this.option('titleFormat'),
- options
- );
- // render
- this.renderGrid(
- rowCnt, options.weekends ? 7 : 5,
- this.option('columnFormat'),
- true
- );
- }
- }, viewName);
-};
-
-views.basicWeek = function(element, options, viewName) {
- return new Grid(element, options, {
- render: function(date, delta) {
- if (delta) {
- addDays(date, delta * 7);
- }
- var visStart = this.visStart = cloneDate(
- this.start = addDays(cloneDate(date), -((date.getDay() - options.firstDay + 7) % 7))
- ),
- visEnd = this.visEnd = cloneDate(
- this.end = addDays(cloneDate(visStart), 7)
- );
- if (!options.weekends) {
- skipWeekend(visStart);
- skipWeekend(visEnd, -1, true);
- }
- this.title = formatDates(
- visStart,
- addDays(cloneDate(visEnd), -1),
- this.option('titleFormat'),
- options
- );
- this.renderGrid(
- 1, options.weekends ? 7 : 5,
- this.option('columnFormat'),
- false
- );
- }
- }, viewName);
-};
-
-views.basicDay = function(element, options, viewName) {
- return new Grid(element, options, {
- render: function(date, delta) {
- if (delta) {
- addDays(date, delta);
- if (!options.weekends) {
- skipWeekend(date, delta < 0 ? -1 : 1);
- }
- }
- this.title = formatDate(date, this.option('titleFormat'), options);
- this.start = this.visStart = cloneDate(date, true);
- this.end = this.visEnd = addDays(cloneDate(this.start), 1);
- this.renderGrid(
- 1, 1,
- this.option('columnFormat'),
- false
- );
- }
- }, viewName);
-};
-
-
-// rendering bugs
-
-var tdHeightBug;
-
-
-function Grid(element, options, methods, viewName) {
- var tm, firstDay,
- nwe, // no weekends (int)
- rtl, dis, dit, // day index sign / translate
- viewWidth, viewHeight,
- rowCnt, colCnt,
- colWidth,
- thead, tbody,
- cachedEvents=[],
- segmentContainer,
- dayContentPositions = new HorizontalPositionCache(function(dayOfWeek) {
- return tbody.find('td:eq(' + ((dayOfWeek - Math.max(firstDay,nwe)+colCnt) % colCnt) + ') div div');
- }),
- // ...
-
- // initialize superclass
- view = $.extend(this, viewMethods, methods, {
- renderGrid: renderGrid,
- renderEvents: renderEvents,
- rerenderEvents: rerenderEvents,
- clearEvents: clearEvents,
- setHeight: setHeight,
- setWidth: setWidth,
- defaultEventEnd: function(event) { // calculates an end if event doesnt have one, mostly for resizing
- return cloneDate(event.start);
- }
- });
- view.name = viewName;
- view.init(element, options);
+ function activateButton(buttonName) {
+ element.find('div.fc-button-' + buttonName)
+ .addClass(tm + '-state-active');
+ }
- /* Grid Rendering
- -----------------------------------------------------------------------------*/
+ function deactivateButton(buttonName) {
+ element.find('div.fc-button-' + buttonName)
+ .removeClass(tm + '-state-active');
+ }
- disableTextSelection(element.addClass('fc-grid'));
+ function disableButton(buttonName) {
+ element.find('div.fc-button-' + buttonName)
+ .addClass(tm + '-state-disabled');
+ }
-
- function renderGrid(r, c, colFormat, showNumbers) {
- rowCnt = r;
+ function enableButton(buttonName) {
+ element.find('div.fc-button-' + buttonName)
+ .removeClass(tm + '-state-disabled');
+ }
+
+
+}
+
+var eventGUID = 1;
+
+function EventManager(options, sources) {
+ var t = this;
+
+
+ // exports
+ t.isFetchNeeded = isFetchNeeded;
+ t.fetchEvents = fetchEvents;
+ t.addEventSource = addEventSource;
+ t.removeEventSource = removeEventSource;
+ t.updateEvent = updateEvent;
+ t.renderEvent = renderEvent;
+ t.removeEvents = removeEvents;
+ t.clientEvents = clientEvents;
+ t.normalizeEvent = normalizeEvent;
+
+
+ // imports
+ var trigger = t.trigger;
+ var getView = t.getView;
+ var reportEvents = t.reportEvents;
+
+
+ // locals
+ var rangeStart, rangeEnd;
+ var currentFetchID = 0;
+ var pendingSourceCnt = 0;
+ var loadingLevel = 0;
+ var dynamicEventSource = [];
+ var cache = [];
+
+
+
+ /* Fetching
+ -----------------------------------------------------------------------------*/
+
+
+ function isFetchNeeded(start, end) {
+ return !rangeStart || start < rangeStart || end > rangeEnd;
+ }
+
+
+ function fetchEvents(start, end) {
+ rangeStart = start;
+ rangeEnd = end;
+ cache = [];
+ var fetchID = ++currentFetchID;
+ var len = sources.length;
+ pendingSourceCnt = len;
+ for (var i=0; i" + formatDate(d, colFormat, options) + "";
+ "'>" + formatDate(d, colFormat) + "";
addDays(d, 1);
if (nwe) {
skipWeekend(d);
@@ -1123,7 +1365,7 @@ function Grid(element, options, methods,
thead = $(s + "").appendTo(table);
s = "";
- d = cloneDate(view.visStart);
+ d = cloneDate(t.visStart);
for (i=0; i";
for (j=0; j").appendTo(table);
dayBind(tbody.find('td'));
- segmentContainer = $("").appendTo(element);
+ daySegmentContainer = $("").appendTo(element);
}else{ // NOT first time, reuse as many cells as possible
@@ -1181,7 +1423,7 @@ function Grid(element, options, methods,
dayBind(tbody.find('td.fc-new').removeClass('fc-new'));
// re-label and re-class existing cells
- d = cloneDate(view.visStart);
+ d = cloneDate(t.visStart);
tbody.find('td').each(function() {
var td = $(this);
if (rowCnt > 1) {
@@ -1210,10 +1452,10 @@ function Grid(element, options, methods,
if (rowCnt == 1) { // more changes likely (week or day view)
// redo column header text and class
- d = cloneDate(view.visStart);
- thead.find('th').each(function() {
- $(this).text(formatDate(d, colFormat, options));
- this.className = this.className.replace(/^fc-\w+(?= )/, 'fc-' + dayIDs[d.getDay()]);
+ d = cloneDate(t.visStart);
+ thead.find('th').each(function(i, th) {
+ $(th).text(formatDate(d, colFormat));
+ th.className = th.className.replace(/^fc-\w+(?= )/, 'fc-' + dayIDs[d.getDay()]);
addDays(d, 1);
if (nwe) {
skipWeekend(d);
@@ -1221,9 +1463,9 @@ function Grid(element, options, methods,
});
// redo cell day-of-weeks
- d = cloneDate(view.visStart);
- tbody.find('td').each(function() {
- this.className = this.className.replace(/^fc-\w+(?= )/, 'fc-' + dayIDs[d.getDay()]);
+ d = cloneDate(t.visStart);
+ tbody.find('td').each(function(i, td) {
+ td.className = td.className.replace(/^fc-\w+(?= )/, 'fc-' + dayIDs[d.getDay()]);
addDays(d, 1);
if (nwe) {
skipWeekend(d);
@@ -1237,13 +1479,12 @@ function Grid(element, options, methods,
}
-
function setHeight(height) {
viewHeight = height;
var leftTDs = tbody.find('tr td:first-child'),
tbodyHeight = viewHeight - thead.height(),
rowHeight1, rowHeight2;
- if (options.weekMode == 'variable') {
+ if (opt('weekMode') == 'variable') {
rowHeight1 = rowHeight2 = Math.floor(tbodyHeight / (rowCnt==1 ? 2 : 6));
}else{
rowHeight1 = Math.floor(tbodyHeight / rowCnt);
@@ -1268,167 +1509,131 @@ function Grid(element, options, methods,
function setWidth(width) {
viewWidth = width;
- dayContentPositions.clear();
- setOuterWidth(
- thead.find('th').slice(0, -1),
- colWidth = Math.floor(viewWidth / colCnt)
- );
+ colContentPositions.clear();
+ colWidth = Math.floor(viewWidth / colCnt);
+ setOuterWidth(thead.find('th').slice(0, -1), colWidth);
}
-
- /* Event Rendering
- -----------------------------------------------------------------------------*/
+
+ /* Day clicking and binding
+ -----------------------------------------------------------*/
- function renderEvents(events) {
- view.reportEvents(cachedEvents = events);
- renderSegs(compileSegs(events));
+ function dayBind(days) {
+ days.click(dayClick)
+ .mousedown(daySelectionMousedown);
}
- function rerenderEvents(modifiedEventId) {
- clearEvents();
- renderSegs(compileSegs(cachedEvents), modifiedEventId);
+ function dayClick(ev) {
+ if (!opt('selectable')) { // SelectionManager will worry about dayClick
+ var n = parseInt(this.className.match(/fc\-day(\d+)/)[1]),
+ date = addDays(
+ cloneDate(t.visStart),
+ Math.floor(n/colCnt) * 7 + n % colCnt
+ );
+ // TODO: what about weekends in middle of week?
+ trigger('dayClick', this, date, true, ev);
+ }
}
- function clearEvents() {
- view._clearEvents(); // only clears the hashes
- segmentContainer.empty();
- }
+ /* Semi-transparent Overlay Helpers
+ ------------------------------------------------------*/
- function compileSegs(events) {
- var d1 = cloneDate(view.visStart),
- d2 = addDays(cloneDate(d1), colCnt),
- visEventsEnds = $.map(events, exclEndDay),
- i, row,
- j, level,
- k, seg,
- segs=[];
- for (i=0; i" +
- "" +
- (!event.allDay && seg.isStart ?
- "" +
- htmlEscape(formatDates(event.start, event.end, view.option('timeFormat'), options)) +
- ""
- :'') +
- "" + htmlEscape(event.title) + "" +
- "" +
- ((event.editable || event.editable === undefined && options.editable) && !options.disableResizing && $.fn.resizable ?
- ""
- : '') +
- "";
- seg.left = left;
- seg.outerWidth = right - left;
+
+ function clearEvents() {
+ reportEventClear();
+ getDaySegmentContainer().empty();
}
- segmentContainer[0].innerHTML = html; // faster than html()
- eventElements = segmentContainer.children();
- // retrieve elements, run through eventRender callback, bind handlers
- for (i=0; i div') // optimal selector?
- .height(top + levelHeight);
}
+
+
+}
+
+fcViews.agendaWeek = AgendaWeekView;
+
+function AgendaWeekView(element, calendar) {
+ var t = this;
- // calculate row tops
- for (rowI=0; rowI" +
- "" +
+ "
" +
" | ";
for (i=0; i" + formatDate(d, colFormat, options) + "";
+ "'>" + formatDate(d, colFormat) + "";
addDays(d, dis);
if (nwe) {
skipWeekend(d, dis);
}
}
s += " |
";
- if (options.allDaySlot) {
+ if (opt('allDaySlot')) {
s += "" +
- "" + options.allDayText + " | " +
+ "" + opt('allDayText') + " | " +
"" +
" | " +
" | " +
@@ -1922,10 +2108,10 @@ function Agenda(element, options, method
s += "
" +
- ((!slotNormal || !minutes) ? formatDate(d, options.axisFormat) : ' ') +
+ ((!slotNormal || !minutes) ? formatDate(d, opt('axisFormat')) : ' ') +
" | |
";
- addMinutes(d, options.slotMinutes);
+ addMinutes(d, opt('slotMinutes'));
slotCnt++;
}
s += "";
@@ -1962,9 +2148,9 @@ function Agenda(element, options, method
clearEvents();
// redo column header text and class
- head.find('tr:first th').slice(1, -1).each(function() {
- $(this).text(formatDate(d, colFormat, options));
- this.className = this.className.replace(/^fc-\w+(?= )/, 'fc-' + dayIDs[d.getDay()]);
+ head.find('tr:first th').slice(1, -1).each(function(i, th) {
+ $(th).text(formatDate(d, colFormat));
+ th.className = th.className.replace(/^fc-\w+(?= )/, 'fc-' + dayIDs[d.getDay()]);
addDays(d, dis);
if (nwe) {
skipWeekend(d, dis);
@@ -1973,15 +2159,15 @@ function Agenda(element, options, method
// change classes of background stripes
d = cloneDate(d0);
- bg.find('td').each(function() {
- this.className = this.className.replace(/^fc-\w+(?= )/, 'fc-' + dayIDs[d.getDay()]);
+ bg.find('td').each(function(i, td) {
+ td.className = td.className.replace(/^fc-\w+(?= )/, 'fc-' + dayIDs[d.getDay()]);
if (+d == +today) {
- $(this)
+ $(td)
.removeClass('fc-not-today')
.addClass('fc-today')
.addClass(tm + '-state-highlight');
}else{
- $(this)
+ $(td)
.addClass('fc-not-today')
.removeClass('fc-today')
.removeClass(tm + '-state-highlight');
@@ -1997,75 +2183,105 @@ function Agenda(element, options, method
}
- function resetScroll() {
- var d0 = zeroDate(),
- scrollDate = cloneDate(d0);
- scrollDate.setHours(options.firstHour);
- var top = timePosition(d0, scrollDate) + 1, // +1 for the border
- scroll = function() {
- body.scrollTop(top);
- };
- scroll();
- setTimeout(scroll, 0); // overrides any previous scroll state made by the browser
- }
-
function setHeight(height, dateChanged) {
+
+ if (height === undefined) {
+ height = viewHeight;
+ }
+
viewHeight = height;
slotTopCache = {};
- body.height(height - head.height());
+ var bodyHeight = height - head.height();
+ bodyHeight = Math.min(bodyHeight, bodyTable.height()); // shrink to fit table
+ body.height(bodyHeight);
slotHeight = body.find('tr:first div').height() + 1;
- bg.css({
- top: head.find('tr').height(),
- height: height
- });
-
if (dateChanged) {
resetScroll();
}
}
+
function setWidth(width) {
viewWidth = width;
colContentPositions.clear();
- body.width(width);
+ body.width(width).css('overflow', 'auto');
bodyTable.width('');
var topTDs = head.find('tr:first th'),
+ allDayLastTH = head.find('tr.fc-all-day th:last'),
stripeTDs = bg.find('td'),
clientWidth = body[0].clientWidth;
bodyTable.width(clientWidth);
+ clientWidth = body[0].clientWidth; // in ie6, sometimes previous clientWidth was wrongly reported
+ bodyTable.width(clientWidth);
// time-axis width
axisWidth = 0;
setOuterWidth(
head.find('tr:lt(2) th:first').add(body.find('tr:first th'))
- .width('')
+ .width(1)
.each(function() {
axisWidth = Math.max(axisWidth, $(this).outerWidth());
}),
axisWidth
);
- // column width
+ // column width, except for last column
colWidth = Math.floor((clientWidth - axisWidth) / colCnt);
setOuterWidth(stripeTDs.slice(0, -1), colWidth);
setOuterWidth(topTDs.slice(1, -2), colWidth);
- setOuterWidth(topTDs.slice(-2, -1), clientWidth - axisWidth - colWidth*(colCnt-1));
+
+ // column width for last column
+ if (width != clientWidth) { // has scrollbar
+ setOuterWidth(topTDs.slice(-2, -1), clientWidth - axisWidth - colWidth*(colCnt-1));
+ topTDs.slice(-1).show();
+ allDayLastTH.show();
+ }else{
+ body.css('overflow', 'hidden');
+ topTDs.slice(-2, -1).width('');
+ topTDs.slice(-1).hide();
+ allDayLastTH.hide();
+ }
bg.css({
+ top: head.find('tr').height(),
left: axisWidth,
- width: clientWidth - axisWidth
+ width: clientWidth - axisWidth,
+ height: viewHeight
});
}
+ function resetScroll() {
+ var d0 = zeroDate(),
+ scrollDate = cloneDate(d0);
+ scrollDate.setHours(opt('firstHour'));
+ var top = timePosition(d0, scrollDate) + 1, // +1 for the border
+ scroll = function() {
+ body.scrollTop(top);
+ };
+ scroll();
+ setTimeout(scroll, 0); // overrides any previous scroll state made by the browser
+ }
+
+
+ function beforeHide() {
+ savedScrollTop = body.scrollTop();
+ }
+
+
+ function afterShow() {
+ body.scrollTop(savedScrollTop);
+ }
+
+
/* Slot/Day clicking and binding
-----------------------------------------------------------------------*/
@@ -2084,175 +2300,550 @@ function Agenda(element, options, method
function slotClick(ev) {
- if (!view.option('selectable')) { // SelectionManager will worry about dayClick
+ if (!opt('selectable')) { // SelectionManager will worry about dayClick
var col = Math.min(colCnt-1, Math.floor((ev.pageX - bg.offset().left) / colWidth)),
- date = addDays(cloneDate(view.visStart), col*dis+dit),
+ date = addDays(cloneDate(t.visStart), col*dis+dit),
rowMatch = this.className.match(/fc-slot(\d+)/);
if (rowMatch) {
- var mins = parseInt(rowMatch[1]) * options.slotMinutes,
+ var mins = parseInt(rowMatch[1]) * opt('slotMinutes'),
hours = Math.floor(mins/60);
date.setHours(hours);
date.setMinutes(mins%60 + minMinute);
- view.trigger('dayClick', this, date, false, ev);
+ trigger('dayClick', this, date, false, ev);
}else{
- view.trigger('dayClick', this, date, true, ev);
+ trigger('dayClick', this, date, true, ev);
}
}
}
- /* Event Rendering
- -----------------------------------------------------------------------------*/
+ /* Semi-transparent Overlay Helpers
+ -----------------------------------------------------*/
- function renderEvents(events, modifiedEventId) {
- view.reportEvents(cachedEvents = events);
- var i, len=events.length,
- dayEvents=[],
- slotEvents=[];
- for (i=0; i= addMinutes(cloneDate(day), maxMinute)) {
+ return bodyContent.height();
+ }
+ var slotMinutes = opt('slotMinutes'),
+ minutes = time.getHours()*60 + time.getMinutes() - minMinute,
+ slotI = Math.floor(minutes / slotMinutes),
+ slotTop = slotTopCache[slotI];
+ if (slotTop === undefined) {
+ slotTop = slotTopCache[slotI] = body.find('tr:eq(' + slotI + ') td div')[0].offsetTop;
+ }
+ return Math.max(0, Math.round(
+ slotTop - 1 + slotHeight * ((minutes % slotMinutes) / slotMinutes)
+ ));
}
- function compileSlotSegs(events) {
- var d = addMinutes(cloneDate(view.visStart), minMinute),
- visEventEnds = $.map(events, slotEventEnd),
- i, col,
- j, level,
- k, seg,
- segs=[];
- for (i=0; i= 0) {
+ addMinutes(d, minMinute + slotIndex*opt('slotMinutes'));
+ }
+ return d;
+ }
+
+
+ function cellIsAllDay(cell) {
+ return opt('allDaySlot') && !cell.row;
}
+ function allDayBounds() {
+ return {
+ left: axisWidth,
+ right: viewWidth
+ }
+ }
+
+ function allDayTR(index) {
+ return head.find('tr.fc-all-day');
+ }
- // renders 'all-day' events at the top
- function renderDaySegs(segs, modifiedEventId) {
- if (options.allDaySlot) {
- _renderDaySegs(
- segs,
- 1,
- view,
- axisWidth,
- viewWidth,
- function() {
- return head.find('tr.fc-all-day');
- },
- function(dayOfWeek) {
- return axisWidth + colContentPositions.left(dayOfWeekCol(dayOfWeek));
- },
- function(dayOfWeek) {
- return axisWidth + colContentPositions.right(dayOfWeekCol(dayOfWeek));
- },
- daySegmentContainer,
- daySegBind,
- modifiedEventId
- );
- setHeight(viewHeight); // might have pushed the body down, so resize
+ function defaultEventEnd(event) {
+ var start = cloneDate(event.start);
+ if (event.allDay) {
+ return start;
}
+ return addMinutes(start, opt('defaultEventMinutes'));
}
- // renders events in the 'time slots' at the bottom
+ /* Selection
+ ---------------------------------------------------------------------------------*/
- function renderSlotSegs(segs, modifiedEventId) {
- var i, segCnt=segs.length, seg,
- event,
- className,
- top, bottom,
- colI, levelI, forward,
- leftmost,
- availWidth,
- outerWidth,
- left,
- html='',
- eventElements,
- eventElement,
- triggerRes,
- vsideCache={},
- hsideCache={},
- key, val,
- titleSpan,
- height;
-
- // calculate position/dimensions, create html
- for (i=0; i= 0 && col < colCnt) { // only works when times are on same day
+ var rect = coordinateGrid.rect(0, col, 0, col, bodyContent); // only for horizontal coords
+ var top = timePosition(startDate, startDate);
+ var bottom = timePosition(startDate, endDate);
+ if (bottom > top) { // protect against selections that are entirely before or after visible range
+ rect.top = top;
+ rect.height = bottom - top;
+ rect.left += 2;
+ rect.width -= 5;
+ if ($.isFunction(helperOption)) {
+ var helperRes = helperOption(startDate, endDate);
+ if (helperRes) {
+ rect.position = 'absolute';
+ rect.zIndex = 8;
+ selectionHelper = $(helperRes)
+ .css(rect)
+ .appendTo(bodyContent);
+ }
+ }else{
+ selectionHelper = $(slotSegHtml(
+ {
+ title: '',
+ start: startDate,
+ end: endDate,
+ className: [],
+ editable: false
+ },
+ rect,
+ 'fc-event fc-event-vert fc-corner-top fc-corner-bottom '
+ ));
+ if ($.browser.msie) {
+ selectionHelper.find('span.fc-event-bg').hide(); // nested opacities mess up in IE, just hide
+ }
+ selectionHelper.css('opacity', opt('dragOpacity'));
+ }
+ if (selectionHelper) {
+ slotBind(selectionHelper);
+ bodyContent.append(selectionHelper);
+ setOuterWidth(selectionHelper, rect.width, true); // needs to be after appended
+ setOuterHeight(selectionHelper, rect.height, true);
+ }
+ }
+ }
+ }else{
+ renderSlotOverlay(startDate, endDate);
+ }
+ }
+
+
+ function clearSelection() {
+ clearOverlays();
+ if (selectionHelper) {
+ selectionHelper.remove();
+ selectionHelper = null;
+ }
+ }
+
+
+ function slotSelectionMousedown(ev) {
+ if (ev.which == 1 && opt('selectable')) { // ev.which==1 means left mouse button
+ unselect(ev);
+ var _mousedownElement = this;
+ var dates;
+ hoverListener.start(function(cell, origCell) {
+ clearSelection();
+ if (cell && cell.col == origCell.col && !cellIsAllDay(cell)) {
+ var d1 = cellDate(origCell);
+ var d2 = cellDate(cell);
+ dates = [
+ d1,
+ addMinutes(cloneDate(d1), opt('slotMinutes')),
+ d2,
+ addMinutes(cloneDate(d2), opt('slotMinutes'))
+ ].sort(cmp);
+ renderSlotSelection(dates[0], dates[3]);
+ }else{
+ dates = null;
+ }
+ }, ev);
+ $(document).one('mouseup', function(ev) {
+ hoverListener.stop();
+ if (dates) {
+ if (+dates[0] == +dates[1]) {
+ trigger('dayClick', _mousedownElement, dates[0], false, ev);
+ // BUG: _mousedownElement will sometimes be the overlay
+ }
+ reportSelection(dates[0], dates[3], false, ev);
+ }
+ });
+ }
+ }
+
+
+
+ /* External Dragging
+ --------------------------------------------------------------------------------*/
+
+
+ function dragStart(_dragElement, ev, ui) {
+ hoverListener.start(function(cell) {
+ clearOverlays();
+ if (cell) {
+ if (cellIsAllDay(cell)) {
+ renderCellOverlay(cell.row, cell.col, cell.row, cell.col);
+ }else{
+ var d1 = cellDate(cell);
+ var d2 = addMinutes(cloneDate(d1), opt('defaultEventMinutes'));
+ renderSlotOverlay(d1, d2);
+ }
+ }
+ }, ev);
+ }
+
+
+ function dragStop(_dragElement, ev, ui) {
+ var cell = hoverListener.stop();
+ clearOverlays();
+ if (cell) {
+ trigger('drop', _dragElement, cellDate(cell), cellIsAllDay(cell), ev, ui);
+ }
+ }
+
+
+}
+
+function AgendaEventRenderer() {
+ var t = this;
+
+
+ // exports
+ t.renderEvents = renderEvents;
+ t.compileDaySegs = compileDaySegs; // for DayEventRenderer
+ t.clearEvents = clearEvents;
+ t.slotSegHtml = slotSegHtml;
+ t.bindDaySeg = bindDaySeg;
+
+
+ // imports
+ DayEventRenderer.call(t);
+ var opt = t.opt;
+ var trigger = t.trigger;
+ var eventEnd = t.eventEnd;
+ var reportEvents = t.reportEvents;
+ var reportEventClear = t.reportEventClear;
+ var eventElementHandlers = t.eventElementHandlers;
+ var setHeight = t.setHeight;
+ var getDaySegmentContainer = t.getDaySegmentContainer;
+ var getSlotSegmentContainer = t.getSlotSegmentContainer;
+ var getHoverListener = t.getHoverListener;
+ var getMaxMinute = t.getMaxMinute;
+ var getMinMinute = t.getMinMinute;
+ var timePosition = t.timePosition;
+ var colContentLeft = t.colContentLeft;
+ var colContentRight = t.colContentRight;
+ var renderDaySegs = t.renderDaySegs;
+ var resizableDayEvent = t.resizableDayEvent; // TODO: streamline binding architecture
+ var getColCnt = t.getColCnt;
+ var getColWidth = t.getColWidth;
+ var getSlotHeight = t.getSlotHeight;
+ var getBodyContent = t.getBodyContent;
+ var reportEventElement = t.reportEventElement;
+ var showEvents = t.showEvents;
+ var hideEvents = t.hideEvents;
+ var eventDrop = t.eventDrop;
+ var eventResize = t.eventResize;
+ var renderDayOverlay = t.renderDayOverlay;
+ var clearOverlays = t.clearOverlays;
+ var calendar = t.calendar;
+ var formatDate = calendar.formatDate;
+ var formatDates = calendar.formatDates;
+
+
+
+ /* Rendering
+ ----------------------------------------------------------------------------*/
+
+
+ function renderEvents(events, modifiedEventId) {
+ reportEvents(events);
+ var i, len=events.length,
+ dayEvents=[],
+ slotEvents=[];
+ for (i=0; i" +
"" +
"" +
- "" + htmlEscape(formatDates(event.start, event.end, view.option('timeFormat'))) + "" +
+ "" + htmlEscape(formatDates(event.start, event.end, opt('timeFormat'))) + "" +
"" + htmlEscape(event.title) + "" +
"" +
- ((event.editable || event.editable === undefined && options.editable) && !options.disableResizing && $.fn.resizable ?
+ ((event.editable || event.editable === undefined && opt('editable')) && !opt('disableResizing') && $.fn.resizable ?
"=
"
: '') +
"";
}
-
- function daySegBind(event, eventElement, seg) {
- view.eventElementHandlers(event, eventElement);
- if (event.editable || event.editable === undefined && options.editable) {
+ function bindDaySeg(event, eventElement, seg) {
+ eventElementHandlers(event, eventElement);
+ if (event.editable || event.editable === undefined && opt('editable')) {
draggableDayEvent(event, eventElement, seg.isStart);
if (seg.isEnd) {
- view.resizableDayEvent(event, eventElement, colWidth);
+ resizableDayEvent(event, eventElement, seg);
}
}
}
-
- function slotSegBind(event, eventElement, seg) {
- view.eventElementHandlers(event, eventElement);
- if (event.editable || event.editable === undefined && options.editable) {
+ function bindSlotSeg(event, eventElement, seg) {
+ eventElementHandlers(event, eventElement);
+ if (event.editable || event.editable === undefined && opt('editable')) {
var timeElement = eventElement.find('span.fc-event-time');
draggableSlotEvent(event, eventElement, timeElement);
if (seg.isEnd) {
@@ -2381,33 +2972,36 @@ function Agenda(element, options, method
}
}
}
-
-
- /* Event Dragging
- -----------------------------------------------------------------------------*/
+ /* Dragging
+ -----------------------------------------------------------------------------------*/
// when event starts out FULL-DAY
function draggableDayEvent(event, eventElement, isStart) {
- if (!options.disableDragging && eventElement.draggable) {
+ if (!opt('disableDragging') && eventElement.draggable) {
var origWidth;
var allDay=true;
var dayDelta;
+ var dis = opt('isRTL') ? -1 : 1;
+ var hoverListener = getHoverListener();
+ var colWidth = getColWidth();
+ var slotHeight = getSlotHeight();
+ var minMinute = getMinMinute();
eventElement.draggable({
zIndex: 9,
- opacity: view.option('dragOpacity', 'month'), // use whatever the month view was using
- revertDuration: options.dragRevertDuration,
+ opacity: opt('dragOpacity', 'month'), // use whatever the month view was using
+ revertDuration: opt('dragRevertDuration'),
start: function(ev, ui) {
- view.trigger('eventDragStart', eventElement, event, ev, ui);
- view.hideEvents(event, eventElement);
+ trigger('eventDragStart', eventElement, event, ev, ui);
+ hideEvents(event, eventElement);
origWidth = eventElement.width();
hoverListener.start(function(cell, origCell, rowDelta, colDelta) {
eventElement.draggable('option', 'revert', !cell || !rowDelta && !colDelta);
- clearOverlay();
+ clearOverlays();
if (cell) {
dayDelta = colDelta * dis;
if (!cell.row) {
@@ -2424,8 +3018,8 @@ function Agenda(element, options, method
setOuterHeight(
eventElement.width(colWidth - 10), // don't use entire width
slotHeight * Math.round(
- (event.end ? ((event.end - event.start) / MINUTE_MS) : options.defaultEventMinutes)
- / options.slotMinutes
+ (event.end ? ((event.end - event.start) / MINUTE_MS) : opt('defaultEventMinutes'))
+ / opt('slotMinutes')
)
);
eventElement.draggable('option', 'grid', [colWidth, 1]);
@@ -2437,26 +3031,26 @@ function Agenda(element, options, method
},
stop: function(ev, ui) {
var cell = hoverListener.stop();
- clearOverlay();
- view.trigger('eventDragStop', eventElement, event, ev, ui);
+ clearOverlays();
+ trigger('eventDragStop', eventElement, event, ev, ui);
if (cell && (!allDay || dayDelta)) {
// changed!
eventElement.find('a').removeAttr('href'); // prevents safari from visiting the link
var minuteDelta = 0;
if (!allDay) {
- minuteDelta = Math.round((eventElement.offset().top - bodyContent.offset().top) / slotHeight)
- * options.slotMinutes
+ minuteDelta = Math.round((eventElement.offset().top - getBodyContent().offset().top) / slotHeight)
+ * opt('slotMinutes')
+ minMinute
- (event.start.getHours() * 60 + event.start.getMinutes());
}
- view.eventDrop(this, event, dayDelta, minuteDelta, allDay, ev, ui);
+ eventDrop(this, event, dayDelta, minuteDelta, allDay, ev, ui);
}else{
// hasn't moved or is out of bounds (draggable has already reverted)
resetElement();
if ($.browser.msie) {
eventElement.css('filter', ''); // clear IE opacity side-effects
}
- view.showEvents(event, eventElement);
+ showEvents(event, eventElement);
}
}
});
@@ -2473,26 +3067,30 @@ function Agenda(element, options, method
}
-
// when event starts out IN TIMESLOTS
function draggableSlotEvent(event, eventElement, timeElement) {
- if (!options.disableDragging && eventElement.draggable) {
+ if (!opt('disableDragging') && eventElement.draggable) {
var origPosition;
var allDay=false;
var dayDelta;
var minuteDelta;
var prevMinuteDelta;
+ var dis = opt('isRTL') ? -1 : 1;
+ var hoverListener = getHoverListener();
+ var colCnt = getColCnt();
+ var colWidth = getColWidth();
+ var slotHeight = getSlotHeight();
eventElement.draggable({
zIndex: 9,
scroll: false,
grid: [colWidth, slotHeight],
axis: colCnt==1 ? 'y' : false,
- opacity: view.option('dragOpacity'),
- revertDuration: options.dragRevertDuration,
+ opacity: opt('dragOpacity'),
+ revertDuration: opt('dragRevertDuration'),
start: function(ev, ui) {
- view.trigger('eventDragStart', eventElement, event, ev, ui);
- view.hideEvents(event, eventElement);
+ trigger('eventDragStart', eventElement, event, ev, ui);
+ hideEvents(event, eventElement);
if ($.browser.msie) {
eventElement.find('span.fc-event-bg').hide(); // nested opacities mess up in IE, just hide
}
@@ -2500,10 +3098,10 @@ function Agenda(element, options, method
minuteDelta = prevMinuteDelta = 0;
hoverListener.start(function(cell, origCell, rowDelta, colDelta) {
eventElement.draggable('option', 'revert', !cell);
- clearOverlay();
+ clearOverlays();
if (cell) {
dayDelta = colDelta * dis;
- if (options.allDaySlot && !cell.row) {
+ if (opt('allDaySlot') && !cell.row) {
// over full days
if (!allDay) {
// convert to temporary all-day event
@@ -2523,7 +3121,7 @@ function Agenda(element, options, method
}, ev, 'drag');
},
drag: function(ev, ui) {
- minuteDelta = Math.round((ui.position.top - origPosition.top) / slotHeight) * options.slotMinutes;
+ minuteDelta = Math.round((ui.position.top - origPosition.top) / slotHeight) * opt('slotMinutes');
if (minuteDelta != prevMinuteDelta) {
if (!allDay) {
updateTimeText(minuteDelta);
@@ -2533,11 +3131,11 @@ function Agenda(element, options, method
},
stop: function(ev, ui) {
var cell = hoverListener.stop();
- clearOverlay();
- view.trigger('eventDragStop', eventElement, event, ev, ui);
+ clearOverlays();
+ trigger('eventDragStop', eventElement, event, ev, ui);
if (cell && (dayDelta || minuteDelta || allDay)) {
// changed!
- view.eventDrop(this, event, dayDelta, allDay ? 0 : minuteDelta, allDay, ev, ui);
+ eventDrop(this, event, dayDelta, allDay ? 0 : minuteDelta, allDay, ev, ui);
}else{
// either no change or out-of-bounds (draggable has already reverted)
resetElement();
@@ -2549,7 +3147,7 @@ function Agenda(element, options, method
.find('span.fc-event-bg')
.css('display', ''); // .show() made display=inline
}
- view.showEvents(event, eventElement);
+ showEvents(event, eventElement);
}
}
});
@@ -2559,7 +3157,7 @@ function Agenda(element, options, method
if (event.end) {
newEnd = addMinutes(cloneDate(event.end), minuteDelta);
}
- timeElement.text(formatDates(newStart, newEnd, view.option('timeFormat')));
+ timeElement.text(formatDates(newStart, newEnd, opt('timeFormat')));
}
function resetElement() {
// convert back to original slot-event
@@ -2574,15 +3172,14 @@ function Agenda(element, options, method
+ /* Resizing
+ --------------------------------------------------------------------------------------*/
- /* Event Resizing
- -----------------------------------------------------------------------------*/
- // for TIMESLOT events
-
function resizableSlotEvent(event, eventElement, timeElement) {
- if (!options.disableResizing && eventElement.resizable) {
+ if (!opt('disableResizing') && eventElement.resizable) {
var slotDelta, prevSlotDelta;
+ var slotHeight = getSlotHeight();
eventElement.resizable({
handles: {
s: 'div.ui-resizable-s'
@@ -2590,375 +3187,46 @@ function Agenda(element, options, method
grid: slotHeight,
start: function(ev, ui) {
slotDelta = prevSlotDelta = 0;
- view.hideEvents(event, eventElement);
+ hideEvents(event, eventElement);
if ($.browser.msie && $.browser.version == '6.0') {
eventElement.css('overflow', 'hidden');
}
eventElement.css('z-index', 9);
- view.trigger('eventResizeStart', this, event, ev, ui);
+ trigger('eventResizeStart', this, event, ev, ui);
},
- resize: function(ev, ui) {
- // don't rely on ui.size.height, doesn't take grid into account
- slotDelta = Math.round((Math.max(slotHeight, eventElement.height()) - ui.originalSize.height) / slotHeight);
- if (slotDelta != prevSlotDelta) {
- timeElement.text(
- formatDates(
- event.start,
- (!slotDelta && !event.end) ? null : // no change, so don't display time range
- addMinutes(view.eventEnd(event), options.slotMinutes*slotDelta),
- view.option('timeFormat')
- )
- );
- prevSlotDelta = slotDelta;
- }
- },
- stop: function(ev, ui) {
- view.trigger('eventResizeStop', this, event, ev, ui);
- if (slotDelta) {
- view.eventResize(this, event, 0, options.slotMinutes*slotDelta, ev, ui);
- }else{
- eventElement.css('z-index', 8);
- view.showEvents(event, eventElement);
- // BUG: if event was really short, need to put title back in span
- }
- }
- });
- }
- }
-
-
-
-
- /* Coordinate Utilities
- -----------------------------------------------------------------------------*/
-
- var coordinateGrid = new CoordinateGrid(function(rows, cols) {
- var e, n, p;
- bg.find('td').each(function(i, _e) {
- e = $(_e);
- n = e.offset().left;
- if (i) {
- p[1] = n;
- }
- p = [n];
- cols[i] = p;
- });
- p[1] = n + e.outerWidth();
- if (options.allDaySlot) {
- e = head.find('td');
- n = e.offset().top;
- rows[0] = [n, n+e.outerHeight()];
- }
- var bodyContentTop = bodyContent.offset().top;
- var bodyTop = body.offset().top;
- var bodyBottom = bodyTop + body.outerHeight();
- function constrain(n) {
- return Math.max(bodyTop, Math.min(bodyBottom, n));
- }
- for (var i=0; i= addMinutes(cloneDate(day), maxMinute)) {
- return bodyContent.height();
- }
- var slotMinutes = options.slotMinutes,
- minutes = time.getHours()*60 + time.getMinutes() - minMinute,
- slotI = Math.floor(minutes / slotMinutes),
- slotTop = slotTopCache[slotI];
- if (slotTop === undefined) {
- slotTop = slotTopCache[slotI] = body.find('tr:eq(' + slotI + ') td div')[0].offsetTop;
- }
- return Math.max(0, Math.round(
- slotTop - 1 + slotHeight * ((minutes % slotMinutes) / slotMinutes)
- ));
- }
-
-
-
-
- /* Selecting
- -----------------------------------------------------------------------------*/
-
- var selected = false;
- var daySelectionMousedown = selection_dayMousedown(
- view, hoverListener, cellDate, cellIsAllDay, renderDayOverlay, clearOverlay, reportSelection, unselect
- );
-
- function slotSelectionMousedown(ev) {
- if (view.option('selectable')) {
- unselect(ev);
- var _mousedownElement = this;
- var dates;
- hoverListener.start(function(cell, origCell) {
- clearSelection();
- if (cell && cell.col == origCell.col && !cellIsAllDay(cell)) {
- var d1 = cellDate(origCell);
- var d2 = cellDate(cell);
- dates = [
- d1,
- addMinutes(cloneDate(d1), options.slotMinutes),
- d2,
- addMinutes(cloneDate(d2), options.slotMinutes)
- ].sort(cmp);
- renderSlotSelection(dates[0], dates[3]);
- }else{
- dates = null;
- }
- }, ev);
- $(document).one('mouseup', function(ev) {
- hoverListener.stop();
- if (dates) {
- if (+dates[0] == +dates[1]) {
- view.trigger('dayClick', _mousedownElement, dates[0], false, ev);
- // BUG: _mousedownElement will sometimes be the overlay
- }
- reportSelection(dates[0], dates[3], false, ev);
- }
- });
- }
- }
-
- view.select = function(startDate, endDate, allDay) {
- coordinateGrid.build();
- unselect();
- if (allDay) {
- if (options.allDaySlot) {
- if (!endDate) {
- endDate = cloneDate(startDate);
- }
- renderDayOverlay(startDate, addDays(cloneDate(endDate), 1));
- }
- }else{
- if (!endDate) {
- endDate = addMinutes(cloneDate(startDate), options.slotMinutes);
- }
- renderSlotSelection(startDate, endDate);
- }
- reportSelection(startDate, endDate, allDay);
- };
-
- function reportSelection(startDate, endDate, allDay, ev) {
- selected = true;
- view.trigger('select', view, startDate, endDate, allDay, ev);
- }
-
- function unselect(ev) {
- if (selected) {
- clearSelection();
- selected = false;
- view.trigger('unselect', view, ev);
- }
- }
- view.unselect = unselect;
-
- selection_unselectAuto(view, unselect);
-
-
-
-
- /* Selecting drawing utils
- -----------------------------------------------------------------------------*/
-
- var selectionHelper;
-
- function renderSlotSelection(startDate, endDate) {
- var helperOption = view.option('selectHelper');
- if (helperOption) {
- var col = dayDiff(startDate, view.visStart) * dis + dit;
- if (col >= 0 && col < colCnt) { // only works when times are on same day
- var rect = coordinateGrid.rect(0, col, 0, col, bodyContent); // only for horizontal coords
- var top = timePosition(startDate, startDate);
- var bottom = timePosition(startDate, endDate);
- if (bottom > top) { // protect against selections that are entirely before or after visible range
- rect.top = top;
- rect.height = bottom - top;
- rect.left += 2;
- rect.width -= 5;
- if ($.isFunction(helperOption)) {
- var helperRes = helperOption(startDate, endDate);
- if (helperRes) {
- rect.position = 'absolute';
- rect.zIndex = 8;
- selectionHelper = $(helperRes)
- .css(rect)
- .appendTo(bodyContent);
- }
- }else{
- selectionHelper = $(slotSegHtml(
- {
- title: '',
- start: startDate,
- end: endDate,
- className: [],
- editable: false
- },
- rect,
- 'fc-event fc-event-vert fc-corner-top fc-corner-bottom '
- ));
- if ($.browser.msie) {
- selectionHelper.find('span.fc-event-bg').hide(); // nested opacities mess up in IE, just hide
- }
- selectionHelper.css('opacity', view.option('dragOpacity'));
- }
- if (selectionHelper) {
- slotBind(selectionHelper);
- bodyContent.append(selectionHelper);
- setOuterWidth(selectionHelper, rect.width, true); // needs to be after appended
- setOuterHeight(selectionHelper, rect.height, true);
- }
- }
- }
- }else{
- renderSlotOverlay(startDate, endDate);
- }
- }
-
- function clearSelection() {
- clearOverlay();
- if (selectionHelper) {
- selectionHelper.remove();
- selectionHelper = null;
- }
- }
-
-
-
-
- /* Semi-transparent Overlay Helpers
- -----------------------------------------------------*/
-
- function renderDayOverlay(startDate, endDate) {
- var startCol, endCol;
- if (rtl) {
- startCol = dayDiff(endDate, view.visStart)*dis+dit+1;
- endCol = dayDiff(startDate, view.visStart)*dis+dit+1;
- }else{
- startCol = dayDiff(startDate, view.visStart);
- endCol = dayDiff(endDate, view.visStart);
- }
- startCol = Math.max(0, startCol);
- endCol = Math.min(colCnt, endCol);
- if (startCol < endCol) {
- dayBind(
- _renderDayOverlay(0, startCol, 0, endCol-1)
- );
- }
- }
-
- function _renderDayOverlay(col0, row0, col1, row1) {
- var rect = coordinateGrid.rect(col0, row0, col1, row1, head);
- return view.renderOverlay(rect, head);
- }
-
- function renderSlotOverlay(overlayStart, overlayEnd) {
- var dayStart = cloneDate(view.visStart);
- var dayEnd = addDays(cloneDate(dayStart), 1);
- for (var i=0; i= 0) {
- addMinutes(d, minMinute + slotIndex*options.slotMinutes);
+ resize: function(ev, ui) {
+ // don't rely on ui.size.height, doesn't take grid into account
+ slotDelta = Math.round((Math.max(slotHeight, eventElement.height()) - ui.originalSize.height) / slotHeight);
+ if (slotDelta != prevSlotDelta) {
+ timeElement.text(
+ formatDates(
+ event.start,
+ (!slotDelta && !event.end) ? null : // no change, so don't display time range
+ addMinutes(eventEnd(event), opt('slotMinutes')*slotDelta),
+ opt('timeFormat')
+ )
+ );
+ prevSlotDelta = slotDelta;
+ }
+ },
+ stop: function(ev, ui) {
+ trigger('eventResizeStop', this, event, ev, ui);
+ if (slotDelta) {
+ eventResize(this, event, 0, opt('slotMinutes')*slotDelta, ev, ui);
+ }else{
+ eventElement.css('z-index', 8);
+ showEvents(event, eventElement);
+ // BUG: if event was really short, need to put title back in span
+ }
+ }
+ });
}
- return d;
- }
-
- function cellIsAllDay(cell) {
- return options.allDaySlot && !cell.row;
}
-
}
-// count the number of colliding, higher-level segments (for event squishing)
-
function countForwardSegs(levels) {
var i, j, k, level, segForward, segBack;
for (i=levels.length-1; i>0; i--) {
@@ -2976,67 +3244,72 @@ function countForwardSegs(levels) {
}
-/* Methods & Utilities for All Views
------------------------------------------------------------------------------*/
-var viewMethods = {
- /*
- * Objects inheriting these methods must implement the following properties/methods:
- * - title
- * - start
- * - end
- * - visStart
- * - visEnd
- * - defaultEventEnd(event)
- * - render(events)
- * - rerenderEvents()
- *
- *
- * z-index reservations:
- * 3 - day-overlay
- * 8 - events
- * 9 - dragging/resizing events
- *
- */
-
+function View(element, calendar, viewName) {
+ var t = this;
-
- init: function(element, options) {
- this.element = element;
- this.options = options;
- this.eventsByID = {};
- this.eventElements = [];
- this.eventElementsByID = {};
- this.usedOverlays = [];
- this.unusedOverlays = [];
- },
+ // exports
+ t.element = element;
+ t.calendar = calendar;
+ t.name = viewName;
+ t.opt = opt;
+ t.trigger = trigger;
+ t.reportEvents = reportEvents;
+ t.eventEnd = eventEnd;
+ t.reportEventElement = reportEventElement;
+ t.reportEventClear = reportEventClear;
+ t.eventElementHandlers = eventElementHandlers;
+ t.showEvents = showEvents;
+ t.hideEvents = hideEvents;
+ t.eventDrop = eventDrop;
+ t.eventResize = eventResize;
+ // t.title
+ // t.start, t.end
+ // t.visStart, t.visEnd
+
+
+ // imports
+ var defaultEventEnd = t.defaultEventEnd;
+ var normalizeEvent = calendar.normalizeEvent; // in EventManager
+ var reportEventChange = calendar.reportEventChange;
+
+
+ // locals
+ var eventsByID = {};
+ var eventElements = [];
+ var eventElementsByID = {};
+ var options = calendar.options;
- // triggers an event handler, always append view as last arg
- trigger: function(name, thisObj) {
- if (this.options[name]) {
- return this.options[name].apply(thisObj || this, Array.prototype.slice.call(arguments, 2).concat([this]));
+ function opt(name, viewNameOverride) {
+ var v = options[name];
+ if (typeof v == 'object') {
+ return smartProperty(v, viewNameOverride || viewName);
}
- },
-
+ return v;
+ }
+
+ function trigger(name, thisObj) {
+ return calendar.trigger.apply(
+ calendar,
+ [name, thisObj || t].concat(Array.prototype.slice.call(arguments, 2), [t])
+ );
+ }
- // returns a Date object for an event's end
- eventEnd: function(event) {
- return event.end ? cloneDate(event.end) : this.defaultEventEnd(event); // TODO: make sure always using copies
- },
+ /* Event Data
+ ------------------------------------------------------------------------------*/
// report when view receives new events
-
- reportEvents: function(events) { // events are already normalized at this point
- var i, len=events.length, event,
- eventsByID = this.eventsByID = {};
+ function reportEvents(events) { // events are already normalized at this point
+ eventsByID = {};
+ var i, len=events.length, event;
for (i=0; i");
- }
- if (e[0].parentNode != parent[0]) {
- e.appendTo(parent);
- }
- this.usedOverlays.push(e.css(rect).show());
- return e;
- },
- clearOverlays: function() {
- var e;
- while (e = this.usedOverlays.shift()) {
- this.unusedOverlays.push(e.hide().unbind());
- }
- },
+}
+
+function DayEventRenderer() {
+ var t = this;
+
+ // exports
+ t.renderDaySegs = renderDaySegs;
+ t.resizableDayEvent = resizableDayEvent;
+
+
+ // imports
+ var opt = t.opt;
+ var trigger = t.trigger;
+ var eventEnd = t.eventEnd;
+ var reportEventElement = t.reportEventElement;
+ var showEvents = t.showEvents;
+ var hideEvents = t.hideEvents;
+ var eventResize = t.eventResize;
+ var getRowCnt = t.getRowCnt;
+ var getColCnt = t.getColCnt;
+ var getColWidth = t.getColWidth;
+ var allDayTR = t.allDayTR;
+ var allDayBounds = t.allDayBounds;
+ var colContentLeft = t.colContentLeft;
+ var colContentRight = t.colContentRight;
+ var dayOfWeekCol = t.dayOfWeekCol;
+ var dateCell = t.dateCell;
+ var compileDaySegs = t.compileDaySegs;
+ var getDaySegmentContainer = t.getDaySegmentContainer;
+ var bindDaySeg = t.bindDaySeg; //TODO: streamline this
+ var formatDates = t.calendar.formatDates;
+ var renderDayOverlay = t.renderDayOverlay;
+ var clearOverlays = t.clearOverlays;
+ var clearSelection = t.clearSelection;
- // common horizontal event resizing
-
- resizableDayEvent: function(event, eventElement, colWidth) {
- var view = this;
- if (!view.options.disableResizing && eventElement.resizable) {
- eventElement.resizable({
- handles: view.options.isRTL ? {w:'div.ui-resizable-w'} : {e:'div.ui-resizable-e'},
- grid: colWidth,
- minWidth: colWidth/2, // need this or else IE throws errors when too small
- containment: view.element.parent().parent(), // the main element...
- // ... a fix. wouldn't allow extending to last column in agenda views (jq ui bug?)
- start: function(ev, ui) {
- eventElement.css('z-index', 9);
- view.hideEvents(event, eventElement);
- view.trigger('eventResizeStart', this, event, ev, ui);
- },
- stop: function(ev, ui) {
- view.trigger('eventResizeStop', this, event, ev, ui);
- // ui.size.width wasn't working with grid correctly, use .width()
- var dayDelta = Math.round((eventElement.width() - ui.originalSize.width) / colWidth);
- if (dayDelta) {
- view.eventResize(this, event, dayDelta, 0, ev, ui);
- }else{
- eventElement.css('z-index', 8);
- view.showEvents(event, eventElement);
- }
- }
- });
- }
- },
+ /* Rendering
+ -----------------------------------------------------------------------------*/
+ function renderDaySegs(segs, modifiedEventId) {
+ var segmentContainer = getDaySegmentContainer();
+ var rowDivs;
+ var rowCnt = getRowCnt();
+ var colCnt = getColCnt();
+ var i = 0;
+ var rowI;
+ var levelI;
+ var colHeights;
+ var j;
+ var segCnt = segs.length;
+ var seg;
+ var top;
+ var k;
+ segmentContainer[0].innerHTML = daySegHTML(segs); // faster than .html()
+ daySegElementResolve(segs, segmentContainer.children());
+ daySegElementReport(segs);
+ daySegHandlers(segs, segmentContainer, modifiedEventId);
+ daySegCalcHSides(segs);
+ daySegSetWidths(segs);
+ daySegCalcHeights(segs);
+ rowDivs = getRowDivs();
+ // set row heights, calculate event tops (in relation to row top)
+ for (rowI=0; rowI");
+ var elements;
+ var segmentContainer = getDaySegmentContainer();
+ var i;
+ var segCnt = segs.length;
+ var element;
+ tempContainer[0].innerHTML = daySegHTML(segs); // faster than .html()
+ elements = tempContainer.children();
+ segmentContainer.append(elements);
+ daySegElementResolve(segs, elements);
+ daySegCalcHSides(segs);
+ daySegSetWidths(segs);
+ daySegCalcHeights(segs);
+ daySegSetTops(segs, getRowTops(getRowDivs()));
+ elements = [];
+ for (i=0; i" +
+ "" +
+ (!event.allDay && seg.isStart ?
+ "" +
+ htmlEscape(formatDates(event.start, event.end, opt('timeFormat'))) +
+ ""
+ :'') +
+ "" + htmlEscape(event.title) + "" +
+ "" +
+ (seg.isEnd && (event.editable || event.editable === undefined && opt('editable')) && !opt('disableResizing') ?
+ ""
+ : '') +
+ "";
+ seg.left = left;
+ seg.outerWidth = right - left;
+ cols.sort(cmp);
+ seg.startCol = cols[0];
+ seg.endCol = cols[1] + 1;
+ }
+ return html;
+ }
- // get a property from the 'options' object, using smart view naming
- option: function(name, viewName) {
- var v = this.options[name];
- if (typeof v == 'object') {
- return smartProperty(v, viewName || this.name);
+ function daySegElementResolve(segs, elements) { // sets seg.element
+ var i;
+ var segCnt = segs.length;
+ var seg;
+ var event;
+ var element;
+ var triggerRes;
+ for (i=0; i start && eventStart < end) {
- if (eventStart < start) {
- segStart = cloneDate(start);
- isStart = false;
- }else{
- segStart = eventStart;
- isStart = true;
- }
- if (eventEnd > end) {
- segEnd = cloneDate(end);
- isEnd = false;
+ function daySegHandlers(segs, segmentContainer, modifiedEventId) {
+ var i;
+ var segCnt = segs.length;
+ var seg;
+ var element;
+ var event;
+ // retrieve elements, run through eventRender callback, bind handlers
+ for (i=0; i div'); // optimal selector?
+ }
+ return rowDivs;
+ }
+
+
+ function getRowTops(rowDivs) {
+ var i;
+ var rowCnt = rowDivs.length;
+ var tops = [];
+ for (i=0; i seg2.start && seg1.start < seg2.end;
-}
+//BUG: unselect needs to be triggered when events are dragged+dropped
+function SelectionManager() {
+ var t = this;
+
+
+ // exports
+ t.select = select;
+ t.unselect = unselect;
+ t.reportSelection = reportSelection;
+ t.daySelectionMousedown = daySelectionMousedown;
+
+
+ // imports
+ var opt = t.opt;
+ var trigger = t.trigger;
+ var defaultSelectionEnd = t.defaultSelectionEnd;
+ var renderSelection = t.renderSelection;
+ var clearSelection = t.clearSelection;
+
+
+ // locals
+ var selected = false;
+ // unselectAuto
+ if (opt('selectable') && opt('unselectAuto')) {
+ $(document).mousedown(function(ev) {
+ var ignore = opt('unselectCancel');
+ if (ignore) {
+ if ($(ev.target).parents(ignore).length) { // could be optimized to stop after first match
+ return;
+ }
+ }
+ unselect(ev);
+ });
+ }
+
-function selection_dayMousedown(view, hoverListener, cellDate, cellIsAllDay, renderSelection, clearSelection, reportSelection, unselect) {
- return function(ev) {
- if (view.option('selectable')) {
+ function select(startDate, endDate, allDay) {
+ unselect();
+ if (!endDate) {
+ endDate = defaultSelectionEnd(startDate, allDay);
+ }
+ renderSelection(startDate, endDate, allDay);
+ reportSelection(startDate, endDate, allDay);
+ }
+
+
+ function unselect(ev) {
+ if (selected) {
+ selected = false;
+ clearSelection();
+ trigger('unselect', null, ev);
+ }
+ }
+
+
+ function reportSelection(startDate, endDate, allDay, ev) {
+ selected = true;
+ trigger('select', null, startDate, endDate, allDay, ev);
+ }
+
+
+ function daySelectionMousedown(ev) { // not really a generic manager method, oh well
+ var cellDate = t.cellDate;
+ var cellIsAllDay = t.cellIsAllDay;
+ var hoverListener = t.getHoverListener();
+ if (ev.which == 1 && opt('selectable')) { // which==1 means left mouse button
unselect(ev);
var _mousedownElement = this;
var dates;
- hoverListener.start(function(cell, origCell) {
+ hoverListener.start(function(cell, origCell) { // TODO: maybe put cellDate/cellIsAllDay info in cell
clearSelection();
if (cell && cellIsAllDay(cell)) {
dates = [ cellDate(origCell), cellDate(cell) ].sort(cmp);
- renderSelection(dates[0], addDays(cloneDate(dates[1]), 1), true);
+ renderSelection(dates[0], dates[1], true);
}else{
dates = null;
}
@@ -3371,7 +3993,7 @@ function selection_dayMousedown(view, ho
hoverListener.stop();
if (dates) {
if (+dates[0] == +dates[1]) {
- view.trigger('dayClick', _mousedownElement, dates[0], true, ev);
+ trigger('dayClick', _mousedownElement, dates[0], true, ev);
// BUG: _mousedownElement will sometimes be the overlay
}
reportSelection(dates[0], dates[1], true, ev);
@@ -3379,29 +4001,180 @@ function selection_dayMousedown(view, ho
});
}
}
+
+
+}
+
+function OverlayManager() {
+ var t = this;
+
+
+ // exports
+ t.renderOverlay = renderOverlay;
+ t.clearOverlays = clearOverlays;
+
+
+ // locals
+ var usedOverlays = [];
+ var unusedOverlays = [];
+
+
+ function renderOverlay(rect, parent) {
+ var e = unusedOverlays.shift();
+ if (!e) {
+ e = $("");
+ }
+ if (e[0].parentNode != parent[0]) {
+ e.appendTo(parent);
+ }
+ usedOverlays.push(e.css(rect).show());
+ return e;
+ }
+
+
+ function clearOverlays() {
+ var e;
+ while (e = usedOverlays.shift()) {
+ unusedOverlays.push(e.hide().unbind());
+ }
+ }
+
+
}
+function CoordinateGrid(buildFunc) {
-function selection_unselectAuto(view, unselect) {
- if (view.option('selectable') && view.option('unselectAuto')) {
- $(document).mousedown(function(ev) {
- var ignore = view.option('unselectCancel');
- if (ignore) {
- if ($(ev.target).parents(ignore).length) { // could be optimized to stop after first match
- return;
+ var t = this;
+ var rows;
+ var cols;
+
+ t.build = function() {
+ rows = [];
+ cols = [];
+ buildFunc(rows, cols);
+ };
+
+ t.cell = function(x, y) {
+ var rowCnt = rows.length;
+ var colCnt = cols.length;
+ var i, r=-1, c=-1;
+ for (i=0; i= rows[i][0] && y < rows[i][1]) {
+ r = i;
+ break;
+ }
+ }
+ for (i=0; i= cols[i][0] && x < cols[i][1]) {
+ c = i;
+ break;
+ }
+ }
+ return (r>=0 && c>=0) ? { row:r, col:c } : null;
+ };
+
+ t.rect = function(row0, col0, row1, col1, originElement) { // row1,col1 is inclusive
+ var origin = originElement.offset();
+ return {
+ top: rows[row0][0] - origin.top,
+ left: cols[col0][0] - origin.left,
+ width: cols[col1][1] - cols[col0][0],
+ height: rows[row1][1] - rows[row0][0]
+ };
+ };
+
+}
+
+function HoverListener(coordinateGrid) {
+
+
+ var t = this;
+ var bindType;
+ var change;
+ var firstCell;
+ var cell;
+
+
+ t.start = function(_change, ev, _bindType) {
+ change = _change;
+ firstCell = cell = null;
+ coordinateGrid.build();
+ mouse(ev);
+ bindType = _bindType || 'mousemove';
+ $(document).bind(bindType, mouse);
+ };
+
+
+ function mouse(ev) {
+ var newCell = coordinateGrid.cell(ev.pageX, ev.pageY);
+ if (!newCell != !cell || newCell && (newCell.row != cell.row || newCell.col != cell.col)) {
+ if (newCell) {
+ if (!firstCell) {
+ firstCell = newCell;
}
+ change(newCell, firstCell, newCell.row-firstCell.row, newCell.col-firstCell.col);
+ }else{
+ change(newCell, firstCell);
}
- unselect(ev);
- });
+ cell = newCell;
+ }
+ }
+
+
+ t.stop = function() {
+ $(document).unbind(bindType, mouse);
+ return cell;
+ };
+
+
+}
+
+function HorizontalPositionCache(getElement) {
+
+ var t = this,
+ elements = {},
+ lefts = {},
+ rights = {};
+
+ function e(i) {
+ return elements[i] = elements[i] || getElement(i);
}
+
+ t.left = function(i) {
+ return lefts[i] = lefts[i] === undefined ? e(i).position().left : lefts[i];
+ };
+
+ t.right = function(i) {
+ return rights[i] = rights[i] === undefined ? t.left(i) + e(i).width() : rights[i];
+ };
+
+ t.clear = function() {
+ elements = {};
+ lefts = {};
+ rights = {};
+ };
+
}
+
+fc.addDays = addDays;
+fc.cloneDate = cloneDate;
+fc.parseDate = parseDate;
+fc.parseISO8601 = parseISO8601;
+fc.parseTime = parseTime;
+fc.formatDate = formatDate;
+fc.formatDates = formatDates;
+
+
+
/* Date Math
-----------------------------------------------------------------------------*/
-var DAY_MS = 86400000,
+var dayIDs = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'],
+ DAY_MS = 86400000,
HOUR_MS = 3600000,
MINUTE_MS = 60000;
+
function addYears(d, n, keepTime) {
d.setFullYear(d.getFullYear() + n);
@@ -3411,6 +4184,7 @@ function addYears(d, n, keepTime) {
return d;
}
+
function addMonths(d, n, keepTime) { // prevents day overflow/underflow
if (+d) { // prevent infinite looping on invalid dates
var m = d.getMonth() + n,
@@ -3428,6 +4202,7 @@ function addMonths(d, n, keepTime) { //
return d;
}
+
function addDays(d, n, keepTime) { // deals with daylight savings
if (+d) {
var dd = d.getDate() + n,
@@ -3442,7 +4217,7 @@ function addDays(d, n, keepTime) { // de
}
return d;
}
-fc.addDays = addDays;
+
function fixDate(d, check) { // force d to be on check's YMD, for daylight savings purposes
if (+d) { // prevent infinite looping on invalid dates
@@ -3452,11 +4227,13 @@ function fixDate(d, check) { // force d
}
}
+
function addMinutes(d, n) {
d.setMinutes(d.getMinutes() + n);
return d;
}
+
function clearTime(d) {
d.setHours(0);
d.setMinutes(0);
@@ -3465,13 +4242,14 @@ function clearTime(d) {
return d;
}
+
function cloneDate(d, dontKeepTime) {
if (dontKeepTime) {
return clearTime(new Date(+d));
}
return new Date(+d);
}
-fc.cloneDate = cloneDate;
+
function zeroDate() { // returns a Date with time 00:00:00 and dateOfMonth=1
var i=0, d;
@@ -3481,6 +4259,7 @@ function zeroDate() { // returns a Date
return d;
}
+
function skipWeekend(date, inc, excl) {
inc = inc || 1;
while (!date.getDay() || (excl && date.getDay()==1 || !excl && date.getDay()==6)) {
@@ -3489,10 +4268,12 @@ function skipWeekend(date, inc, excl) {
return date;
}
+
function dayDiff(d1, d2) { // d1 - d2
return Math.round((cloneDate(d1, true) - cloneDate(d2, true)) / DAY_MS);
}
+
function setYMD(date, y, m, d) {
if (y !== undefined && y != date.getFullYear()) {
date.setDate(1);
@@ -3513,7 +4294,8 @@ function setYMD(date, y, m, d) {
/* Date Parsing
-----------------------------------------------------------------------------*/
-var parseDate = fc.parseDate = function(s) {
+
+function parseDate(s, ignoreTimezone) { // ignoreTimezone defaults to true
if (typeof s == 'object') { // already a Date object
return s;
}
@@ -3524,55 +4306,69 @@ var parseDate = fc.parseDate = function(
if (s.match(/^\d+$/)) { // a UNIX timestamp
return new Date(parseInt(s) * 1000);
}
- return parseISO8601(s, true) || (s ? new Date(s) : null);
+ if (ignoreTimezone === undefined) {
+ ignoreTimezone = true;
+ }
+ return parseISO8601(s, ignoreTimezone) || (s ? new Date(s) : null);
}
// TODO: never return invalid dates (like from new Date()), return null instead
return null;
-};
+}
+
-var parseISO8601 = fc.parseISO8601 = function(s, ignoreTimezone) {
+function parseISO8601(s, ignoreTimezone) { // ignoreTimezone defaults to false
// derived from http://delete.me.uk/2005/03/iso8601.html
// TODO: for a know glitch/feature, read tests/issue_206_parseDate_dst.html
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})))?)?)?)?$/);
if (!m) {
return null;
}
- var date = new Date(m[1], 0, 1),
- check = new Date(m[1], 0, 1, 9, 0),
- offset = 0;
- if (m[3]) {
- date.setMonth(m[3] - 1);
- check.setMonth(m[3] - 1);
- }
- if (m[5]) {
- date.setDate(m[5]);
- check.setDate(m[5]);
- }
- fixDate(date, check);
- if (m[7]) {
- date.setHours(m[7]);
- }
- if (m[8]) {
- date.setMinutes(m[8]);
- }
- if (m[10]) {
- date.setSeconds(m[10]);
- }
- if (m[12]) {
- date.setMilliseconds(Number("0." + m[12]) * 1000);
- }
- fixDate(date, check);
- if (!ignoreTimezone) {
- if (m[14]) {
- offset = Number(m[16]) * 60 + Number(m[17]);
- offset *= m[15] == '-' ? 1 : -1;
+ var date = new Date(m[1], 0, 1);
+ if (ignoreTimezone || !m[14]) {
+ var check = new Date(m[1], 0, 1, 9, 0);
+ if (m[3]) {
+ date.setMonth(m[3] - 1);
+ check.setMonth(m[3] - 1);
+ }
+ if (m[5]) {
+ date.setDate(m[5]);
+ check.setDate(m[5]);
+ }
+ fixDate(date, check);
+ if (m[7]) {
+ date.setHours(m[7]);
+ }
+ if (m[8]) {
+ date.setMinutes(m[8]);
}
- offset -= date.getTimezoneOffset();
+ if (m[10]) {
+ date.setSeconds(m[10]);
+ }
+ if (m[12]) {
+ date.setMilliseconds(Number("0." + m[12]) * 1000);
+ }
+ fixDate(date, check);
+ }else{
+ date.setUTCFullYear(
+ m[1],
+ m[3] ? m[3] - 1 : 0,
+ m[5] || 1
+ );
+ date.setUTCHours(
+ m[7] || 0,
+ m[8] || 0,
+ m[10] || 0,
+ m[12] ? Number("0." + m[12]) * 1000 : 0
+ );
+ var offset = Number(m[16]) * 60 + Number(m[17]);
+ offset *= m[15] == '-' ? 1 : -1;
+ date = new Date(+date + (offset * 60 * 1000));
}
- return new Date(+date + (offset * 60 * 1000));
-};
+ return date;
+}
-var parseTime = fc.parseTime = function(s) { // returns minutes since start of day
+
+function parseTime(s) { // returns minutes since start of day
if (typeof s == 'number') { // an hour
return s * 60;
}
@@ -3590,18 +4386,21 @@ var parseTime = fc.parseTime = function(
}
return h * 60 + (m[2] ? parseInt(m[2]) : 0);
}
-};
+}
/* Date Formatting
-----------------------------------------------------------------------------*/
+// TODO: use same function formatDate(date, [date2], format, [options])
+
-var formatDate = fc.formatDate = function(date, format, options) {
+function formatDate(date, format, options) {
return formatDates(date, null, format, options);
-};
+}
-var formatDates = fc.formatDates = function(date1, date2, format, options) {
+
+function formatDates(date1, date2, format, options) {
options = options || defaults;
var date = date1,
otherDate = date2,
@@ -3678,6 +4477,7 @@ var formatDates = fc.formatDates = funct
return res;
};
+
var dateFormatters = {
s : function(d) { return d.getSeconds() },
ss : function(d) { return zeroPad(d.getSeconds()) },
@@ -3713,51 +4513,191 @@ var dateFormatters = {
+
+/* Event Date Math
+-----------------------------------------------------------------------------*/
+
+
+function exclEndDay(event) {
+ if (event.end) {
+ return _exclEndDay(event.end, event.allDay);
+ }else{
+ return addDays(cloneDate(event.start), 1);
+ }
+}
+
+
+function _exclEndDay(end, allDay) {
+ end = cloneDate(end);
+ return allDay || end.getHours() || end.getMinutes() ? addDays(end, 1) : clearTime(end);
+}
+
+
+function segCmp(a, b) {
+ return (b.msLength - a.msLength) * 100 + (a.event.start - b.event.start);
+}
+
+
+function segsCollide(seg1, seg2) {
+ return seg1.end > seg2.start && seg1.start < seg2.end;
+}
+
+
+
+/* Event Sorting
+-----------------------------------------------------------------------------*/
+
+
+// event rendering utilities
+function sliceSegs(events, visEventEnds, start, end) {
+ var segs = [],
+ i, len=events.length, event,
+ eventStart, eventEnd,
+ segStart, segEnd,
+ isStart, isEnd;
+ for (i=0; i start && eventStart < end) {
+ if (eventStart < start) {
+ segStart = cloneDate(start);
+ isStart = false;
+ }else{
+ segStart = eventStart;
+ isStart = true;
+ }
+ if (eventEnd > end) {
+ segEnd = cloneDate(end);
+ isEnd = false;
+ }else{
+ segEnd = eventEnd;
+ isEnd = true;
+ }
+ segs.push({
+ event: event,
+ start: segStart,
+ end: segEnd,
+ isStart: isStart,
+ isEnd: isEnd,
+ msLength: segEnd - segStart
+ });
+ }
+ }
+ return segs.sort(segCmp);
+}
+
+
+// event rendering calculation utilities
+function stackSegs(segs) {
+ var levels = [],
+ i, len = segs.length, seg,
+ j, collide, k;
+ for (i=0; i= rows[i][0] && y < rows[i][1]) {
- r = i;
- break;
- }
- }
- for (i=0; i= cols[i][0] && x < cols[i][1]) {
- c = i;
- break;
- }
- }
- return (r>=0 && c>=0) ? { row:r, col:c } : null;
- };
-
- t.rect = function(row0, col0, row1, col1, originElement) { // row1,col1 is inclusive
- var origin = originElement.offset();
- return {
- top: rows[row0][0] - origin.top,
- left: cols[col0][0] - origin.left,
- width: cols[col1][1] - cols[col0][0],
- height: rows[row1][1] - rows[row0][0]
- };
- };
-
-}
+//TODO: arraySlice
+//TODO: isFunction, grep ?
-/* Hover Listener
------------------------------------------------------------------------------*/
+function noop() { }
-function HoverListener(coordinateGrid) {
- var t = this;
- var bindType;
- var change;
- var firstCell;
- var cell;
-
- t.start = function(_change, ev, _bindType) {
- change = _change;
- firstCell = cell = null;
- coordinateGrid.build();
- mouse(ev);
- bindType = _bindType || 'mousemove';
- $(document).bind(bindType, mouse);
- };
-
- function mouse(ev) {
- var newCell = coordinateGrid.cell(ev.pageX, ev.pageY);
- if (!newCell != !cell || newCell && (newCell.row != cell.row || newCell.col != cell.col)) {
- if (newCell) {
- if (!firstCell) {
- firstCell = newCell;
- }
- change(newCell, firstCell, newCell.row-firstCell.row, newCell.col-firstCell.col);
- }else{
- change(newCell, firstCell);
- }
- cell = newCell;
- }
- }
-
- t.stop = function() {
- $(document).unbind(bindType, mouse);
- return cell;
- };
-
+function cmp(a, b) {
+ return a - b;
}
+function arrayMax(a) {
+ return Math.max.apply(Math, a);
+}
-/* Misc Utils
------------------------------------------------------------------------------*/
-
-var dayIDs = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'];
function zeroPad(n) {
return (n < 10 ? '0' : '') + n;
}
+
function smartProperty(obj, name) { // get a camel-cased/namespaced property of an object
if (obj[name] !== undefined) {
return obj[name];
@@ -3906,6 +4771,7 @@ function smartProperty(obj, name) { // g
return obj[''];
}
+
function htmlEscape(s) {
return s.replace(/&/g, '&')
.replace(/