diff --git a/build/karma.conf.js b/build/karma.conf.js index 74343d2..417c295 100644 --- a/build/karma.conf.js +++ b/build/karma.conf.js @@ -22,7 +22,6 @@ module.exports = function(config) { '../lib/moment/moment.js', '../lib/jquery/dist/jquery.js', - '../lib/jquery-ui/ui/jquery-ui.js', // for jquery simulate '../lib/jquery-simulate-ext/libs/bililiteRange.js', diff --git a/fullcalendar.jquery.json b/fullcalendar.jquery.json index 4433dc0..bea056f 100644 --- a/fullcalendar.jquery.json +++ b/fullcalendar.jquery.json @@ -10,9 +10,6 @@ "jquery": ">=1.7.1", "moment": ">=2.5.0" }, - "optionalDependencies": { - "jquery-ui": ">=1.8.17" - }, "title": "FullCalendar", "demo": "http://arshaw.com/fullcalendar/", diff --git a/lumbar.json b/lumbar.json index 0699fad..b0a36e9 100644 --- a/lumbar.json +++ b/lumbar.json @@ -22,27 +22,29 @@ "src/util.js", "src/moment-ext.js", "src/date-formatting.js", + "src/common/CoordMap.js", + "src/common/DragListener.js", + "src/common/MouseFollower.js", + "src/common/RowRenderer.js", + "src/common/Grid.js", + "src/common/Grid.events.js", + "src/common/DayGrid.js", + "src/common/DayGrid.events.js", + "src/common/TimeGrid.js", + "src/common/TimeGrid.events.js", + "src/common/View.js", + "src/basic/BasicView.js", "src/basic/MonthView.js", "src/basic/BasicWeekView.js", "src/basic/BasicDayView.js", - "src/basic/BasicView.js", - "src/basic/BasicEventRenderer.js", + "src/agenda/AgendaView.js", "src/agenda/AgendaWeekView.js", "src/agenda/AgendaDayView.js", - "src/agenda/AgendaView.js", - "src/agenda/AgendaEventRenderer.js", - "src/common/View.js", - "src/common/DayEventRenderer.js", - "src/common/SelectionManager.js", - "src/common/OverlayManager.js", - "src/common/CoordinateGrid.js", - "src/common/HoverListener.js", - "src/common/HorizontalPositionCache.js", "src/outro.js" ], "styles": [ - "src/main.css", "src/common/common.css", + "src/main.css", "src/basic/basic.css", "src/agenda/agenda.css" ] diff --git a/src/Calendar.js b/src/Calendar.js index a875653..5dea1cd 100644 --- a/src/Calendar.js +++ b/src/Calendar.js @@ -41,7 +41,7 @@ function Calendar(element, instanceOptions) { t.refetchEvents = refetchEvents; t.reportEvents = reportEvents; t.reportEventChange = reportEventChange; - t.rerenderEvents = rerenderEvents; + t.rerenderEvents = renderEvents; // `renderEvents` serves as a rerender. an API method t.changeView = changeView; t.select = select; t.unselect = unselect; @@ -244,13 +244,11 @@ function Calendar(element, instanceOptions) { var content; var tm; // for making theme classes var currentView; - var elementOuterWidth; var suggestedViewHeight; var resizeUID = 0; var ignoreWindowResize = 0; var date; var events = []; - var _dragElement; @@ -273,7 +271,7 @@ function Calendar(element, instanceOptions) { else if (elementVisible()) { // mainly for the public API calcSize(); - _renderView(inc); + renderView(inc); } } @@ -281,18 +279,22 @@ function Calendar(element, instanceOptions) { function initialRender() { tm = options.theme ? 'ui' : 'fc'; element.addClass('fc'); + if (options.isRTL) { element.addClass('fc-rtl'); } else { element.addClass('fc-ltr'); } + if (options.theme) { element.addClass('ui-widget'); } + else { + element.addClass('fc-unthemed'); + } - content = $("
") - .prependTo(element); + content = $("").prependTo(element); header = new Header(t, options); headerElement = header.render(); @@ -305,47 +307,20 @@ function Calendar(element, instanceOptions) { if (options.handleWindowResize) { $(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() { if (currentView) { - trigger('viewDestroy', currentView, currentView, currentView.element); - currentView.triggerEventDestroy(); - } - - $(window).unbind('resize', windowResize); - - if (options.droppable) { - $(document) - .off('dragstart', droppableDragStart) - .off('dragstop', droppableDragStop); - } - - if (currentView.selectionManagerDestroy) { - currentView.selectionManagerDestroy(); + currentView.destroy(); } header.destroy(); content.remove(); - element.removeClass('fc fc-ltr fc-rtl ui-widget'); + element.removeClass('fc fc-ltr fc-rtl fc-unthemed ui-widget'); + + $(window).unbind('resize', windowResize); } @@ -354,114 +329,122 @@ function Calendar(element, instanceOptions) { } - function bodyVisible() { - return $('body').is(':visible'); - } - - // View Rendering // ----------------------------------------------------------------------------------- - - function changeView(newViewName) { - if (!currentView || newViewName != currentView.name) { - _changeView(newViewName); - } + + function changeView(viewName) { + renderView(0, viewName); } - function _changeView(newViewName) { + // Renders a view because of a date change, view-type change, or for the first time + function renderView(delta, viewName) { ignoreWindowResize++; + // if viewName is changing, destroy the old view + if (currentView && viewName && currentView.name !== viewName) { + header.deactivateButton(currentView.name); + freezeContentHeight(); // prevent a scroll jump when view element is removed + if (currentView.start) { // rendered before? + currentView.destroy(); + } + currentView.el.remove(); + currentView = null; + } + + // if viewName changed, or the view was never created, create a fresh view + if (!currentView && viewName) { + currentView = new fcViews[viewName](t); + currentView.el = $("").appendTo(content); + header.activateButton(viewName); + } + if (currentView) { - trigger('viewDestroy', currentView, currentView, currentView.element); - unselect(); - currentView.triggerEventDestroy(); // trigger 'eventDestroy' for each event - freezeContentHeight(); - currentView.element.remove(); - header.deactivateButton(currentView.name); - } - header.activateButton(newViewName); + // let the view determine what the delta means + if (delta) { + date = currentView.incrementDate(date, delta); + } - currentView = new fcViews[newViewName]( - $("") - .appendTo(content), - t // the calendar object - ); + // render or rerender the view + if ( + !currentView.start || // never rendered before + delta || // explicit date window change + !date.isWithin(currentView.intervalStart, currentView.intervalEnd) // implicit date window change + ) { + if (elementVisible()) { - renderView(); - unfreezeContentHeight(); + freezeContentHeight(); + if (currentView.start) { // rendered before? + currentView.destroy(); + } + currentView.render(date); + unfreezeContentHeight(); - ignoreWindowResize--; - } + // need to do this after View::render, so dates are calculated + updateTitle(); + updateTodayButton(); - - function renderView(inc) { - if ( - !currentView.start || // never rendered before - inc || // explicit date window change - !date.isWithin(currentView.intervalStart, currentView.intervalEnd) // implicit date window change - ) { - if (elementVisible()) { - _renderView(inc); + getAndRenderEvents(); + } } } - } - - - function _renderView(inc) { // assumes elementVisible - ignoreWindowResize++; - - if (currentView.start) { // already been rendered? - trigger('viewDestroy', currentView, currentView, currentView.element); - unselect(); - clearEvents(); - } - - freezeContentHeight(); - if (inc) { - date = currentView.incrementDate(date, inc); - } - currentView.render(date.clone()); // the view's render method ONLY renders the skeleton, nothing else - setSize(); - unfreezeContentHeight(); - (currentView.afterRender || noop)(); - - updateTitle(); - updateTodayButton(); - - trigger('viewRender', currentView, currentView, currentView.element); + unfreezeContentHeight(); // undo any lone freezeContentHeight calls ignoreWindowResize--; - - getAndRenderEvents(); } // Resizing // ----------------------------------------------------------------------------------- - - - function updateSize() { - if (elementVisible()) { - unselect(); - clearEvents(); + + + t.getSuggestedViewHeight = function() { + if (suggestedViewHeight === undefined) { calcSize(); - setSize(); - renderEvents(); + } + return suggestedViewHeight; + }; + + + t.isHeightAuto = function() { + return options.contentHeight === 'auto' || options.height === 'auto'; + }; + + + function updateSize(shouldRecalc) { + if (elementVisible()) { + + if (shouldRecalc) { + _calcSize(); + } + + ignoreWindowResize++; + currentView.updateHeight(); // will poll getSuggestedViewHeight() and isHeightAuto() + currentView.updateWidth(); + ignoreWindowResize--; + + return true; // signal success + } + } + + + function calcSize() { + if (elementVisible()) { + _calcSize(); } } - function calcSize() { // assumes elementVisible - if (options.contentHeight) { + function _calcSize() { // assumes elementVisible + if (typeof options.contentHeight === 'number') { // exists and not 'auto' suggestedViewHeight = options.contentHeight; } - else if (options.height) { - suggestedViewHeight = options.height - (headerElement ? headerElement.height() : 0) - vsides(content); + else if (typeof options.height === 'number') { // exists and not 'auto' + suggestedViewHeight = options.height - (headerElement ? headerElement.outerHeight(true) : 0); } else { suggestedViewHeight = Math.round(content.width() / Math.max(options.aspectRatio, .5)); @@ -469,44 +452,20 @@ function Calendar(element, instanceOptions) { } - function setSize() { // assumes elementVisible - - if (suggestedViewHeight === undefined) { - calcSize(); // for first time - // NOTE: we don't want to recalculate on every renderView because - // it could result in oscillating heights due to scrollbars. - } - - ignoreWindowResize++; - currentView.setHeight(suggestedViewHeight); - currentView.setWidth(content.width()); - ignoreWindowResize--; - - elementOuterWidth = element.outerWidth(); - } - - function windowResize(ev) { if ( !ignoreWindowResize && - ev.target === window // so we don't process jqui "resize" events that have bubbled up + ev.target === window && // so we don't process jqui "resize" events that have bubbled up + currentView.start // view has already been rendered ) { - 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--; - } + var uid = ++resizeUID; + setTimeout(function() { // add a delay + if (uid == resizeUID && !ignoreWindowResize) { + if (updateSize(true)) { + currentView.trigger('windowResize', _element); } - }, options.windowResizeDelay); - }else{ - // calendar must have been initialized in a 0x0 iframe that has just been resized - lateRender(); - } + } + }, options.windowResizeDelay); } } @@ -518,29 +477,25 @@ function Calendar(element, instanceOptions) { function refetchEvents() { // can be called as an API method - clearEvents(); + destroyEvents(); // so that events are cleared before user starts waiting for AJAX fetchAndRenderEvents(); } - function rerenderEvents(modifiedEventID) { // can be called as an API method - clearEvents(); - renderEvents(modifiedEventID); - } - - - function renderEvents(modifiedEventID) { // TODO: remove modifiedEventID hack + function renderEvents() { // destroys old events if previously rendered if (elementVisible()) { - currentView.renderEvents(events, modifiedEventID); // actually render the DOM elements - currentView.trigger('eventAfterAllRender'); + freezeContentHeight(); + currentView.destroyEvents(); // no performance cost if never rendered + currentView.renderEvents(events); + unfreezeContentHeight(); } } - function clearEvents() { - currentView.triggerEventDestroy(); // trigger 'eventDestroy' for each event - currentView.clearEvents(); // actually remove the DOM elements - currentView.clearEventData(); // for View.js, TODO: unify with clearEvents + function destroyEvents() { + freezeContentHeight(); + currentView.destroyEvents(); + unfreezeContentHeight(); } @@ -569,8 +524,8 @@ function Calendar(element, instanceOptions) { // called when a single event's data has been changed - function reportEventChange(eventID) { - rerenderEvents(eventID); + function reportEventChange() { + renderEvents(); } @@ -601,6 +556,18 @@ function Calendar(element, instanceOptions) { function select(start, end) { + + start = t.moment(start); + if (end) { + end = t.moment(end); + } + else if (start.hasTime()) { + end = start.clone().add(t.defaultTimedEventDuration); + } + else { + end = start.clone().add(t.defaultAllDayEventDuration); + } + currentView.select(start, end); } @@ -706,7 +673,7 @@ function Calendar(element, instanceOptions) { } if (name == 'height' || name == 'contentHeight' || name == 'aspectRatio') { options[name] = value; - updateSize(); + updateSize(true); // true = allow recalculation of height } } @@ -719,38 +686,5 @@ function Calendar(element, instanceOptions) { ); } } - - - - /* External Dragging - ------------------------------------------------------------------------*/ - - if (options.droppable) { - // TODO: unbind on destroy - $(document) - .on('dragstart', droppableDragStart) - .on('dragstop', droppableDragStop); - // this is undone in destroy - } - - function droppableDragStart(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; - currentView.dragStart(_dragElement, ev, ui); - } - } - } - - function droppableDragStop(ev, ui) { - if (_dragElement) { - currentView.dragStop(_dragElement, ev, ui); - _dragElement = null; - } - } - } diff --git a/src/EventManager.js b/src/EventManager.js index d7c821e..43cb035 100644 --- a/src/EventManager.js +++ b/src/EventManager.js @@ -250,15 +250,21 @@ function EventManager(options) { // assumed to be a calendar } else if (typeof sourceInput === 'object') { source = $.extend({}, sourceInput); // shallow copy - - if (typeof source.className === 'string') { - // TODO: repeat code, same code for event classNames - source.className = source.className.split(/\s+/); - } } if (source) { + // TODO: repeat code, same code for event classNames + if (source.className) { + if (typeof source.className === 'string') { + source.className = source.className.split(/\s+/); + } + // otherwise, assumed to be an array + } + else { + source.className = []; + } + // for array sources, we convert to standard Event Objects up front if ($.isArray(source.events)) { source.events = $.map(source.events, function(eventInput) { diff --git a/src/Header.js b/src/Header.js index 1537f83..114c4ee 100644 --- a/src/Header.js +++ b/src/Header.js @@ -1,8 +1,11 @@ +/* Top toolbar area with buttons and title +----------------------------------------------------------------------------------------------------------------------*/ +// TODO: rename all header-related things to "toolbar" + function Header(calendar, options) { var t = this; - // exports t.render = render; t.destroy = destroy; @@ -12,54 +15,61 @@ function Header(calendar, options) { t.disableButton = disableButton; t.enableButton = enableButton; - // locals - var element = $([]); + var el = $(); var tm; - function render() { - tm = options.theme ? 'ui' : 'fc'; var sections = options.header; + + tm = options.theme ? 'ui' : 'fc'; + if (sections) { - element = $("| " + - ( - opt('allDayHTML') || - htmlEscape(opt('allDayText')) - ) + - " | " + - "" +
- " " +
- " | " +
- "" + - " |
|---|
| " + - ((!slotNormal || !minutes) ? - htmlEscape(formatDate(slotDate, opt('axisFormat'))) : - ' ' - ) + - " | " + - "" +
- " | " +
- "
|---|
| ' + + this.timeGrid.headHtml() + // render the day-of-week headers + ' | ' + + '
| ' +
+ (this.dayGrid ?
+ '' +
+ ' ' : + '' + ) + + ' ' +
+ '' +
+ ' ' +
+ ' | ' +
+ '
| ' + + this.dayGrid.headHtml() + // render the day-of-week headers + ' | ' + + '
| ' +
+ ' ' +
+ '' +
+ ' ' +
+ ' | ' +
+ '