Skip to content
This repository was archived by the owner on Nov 9, 2017. It is now read-only.
This repository was archived by the owner on Nov 9, 2017. It is now read-only.

Fatal error: Uncaught exception 'JSMinException' with message 'Unterminated regular expression literal #11

@DWboutin

Description

@DWboutin

I got this error... Don't know from where when i minify this file below

$(document).ready(function () {
/**
* FullCalendar v1.5.1
* http://arshaw.com/fullcalendar/
*
* Use fullcalendar.css for basic styling.
* For event drag & drop, requires jQuery UI draggable.
* For event resizing, requires jQuery UI resizable.
*
* Copyright (c) 2011 Adam Shaw
* Dual licensed under the MIT and GPL licenses, located in
* MIT-LICENSE.txt and GPL-LICENSE.txt respectively.
*
* Date: Sat Apr 9 14:09:51 2011 -0700
*
*/

(function($, undefined) {


var defaults = {

    // display
    defaultView: 'month',
    aspectRatio: 1.35,
    header: {
        left: 'title',
        center: '',
        right: 'today prev,next'
    },
    weekends: true,

    // editing
    //editable: false,
    //disableDragging: false,
    //disableResizing: false,

    allDayDefault: true,
    ignoreTimezone: true,

    // event ajax
    lazyFetching: true,
    startParam: 'start',
    endParam: 'end',

    // time formats
    titleFormat: {
        month: 'MMMM yyyy',
        week: "MMM d[ yyyy]{ '—'[ MMM] d yyyy}",
        day: 'dddd, MMM d, yyyy'
    },
    columnFormat: {
        month: 'ddd',
        week: 'ddd M/d',
        day: 'dddd M/d'
    },
    timeFormat: { // for event elements
        '': 'h(:mm)t' // default
    },

    // locale
    isRTL: false,
    firstDay: 0,
    monthNames: ['January','February','March','April','May','June','July','August','September','October','November','December'],
    monthNamesShort: ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'],
    dayNames: ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'],
    dayNamesShort: ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'],
    buttonText: {
        prev: ' ◄ ',
        next: ' ► ',
        prevYear: ' << ',
        nextYear: ' >> ',
        today: 'today',
        month: 'month',
        week: 'week',
        day: 'day'
    },

    // jquery-ui theming
    theme: false,
    buttonIcons: {
        prev: 'circle-triangle-w',
        next: 'circle-triangle-e'
    },

    //selectable: false,
    unselectAuto: true,

    dropAccept: '*'

};

// right-to-left defaults
var rtlDefaults = {
    header: {
        left: 'next,prev today',
        center: '',
        right: 'title'
    },
    buttonText: {
        prev: ' ► ',
        next: ' ◄ ',
        prevYear: ' >> ',
        nextYear: ' << '
    },
    buttonIcons: {
        prev: 'circle-triangle-e',
        next: 'circle-triangle-w'
    }
};



var fc = $.fullCalendar = { version: "1.5.1" };
var fcViews = fc.views = {};


$.fn.fullCalendar = function(options) {


    // method calling
    if (typeof options == 'string') {
        var args = Array.prototype.slice.call(arguments, 1);
        var res;
        this.each(function() {
            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');
                }
            }
        });
        if (res !== undefined) {
            return res;
        }
        return this;
    }


    // 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) {
        eventSources.push(options.events);
        delete options.events;
    }


    options = $.extend(true, {},
        defaults,
        (options.isRTL || options.isRTL===undefined && defaults.isRTL) ? rtlDefaults : {},
        options
    );


    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();
    });


    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.rerenderEvents = rerenderEvents;
    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');
        }
        content = $("<div class='fc-content' style='position:relative'/>")
            .prependTo(element);
        header = new Header(t, options);
        headerElement = header.render();
        if (headerElement) {
            element.prepend(headerElement);
        }
        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 ui-widget');
    }



    function elementVisible() {
        return _element.offsetWidth !== 0;
    }


    function bodyVisible() {
        return $('body')[0].offsetWidth !== 0;
    }



    /* View Rendering
    -----------------------------------------------------------------------------*/

    // TODO: improve view switching (still weird transition in IE, and FF has whiteout problem)

    function changeView(newViewName) {
        if (!currentView || newViewName != currentView.name) {
            ignoreWindowResize++; // because setMinHeight might change the height before render (and subsequently setSize) is reached

            unselect();

            var oldView = currentView;
            var newViewElement;

            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
            }
            content.css('overflow', 'hidden');

            currentView = viewInstances[newViewName];
            if (currentView) {
                currentView.element.show();
            }else{
                currentView = viewInstances[newViewName] = new fcViews[newViewName](
                    newViewElement = absoluteViewElement =
                        $("<div class='fc-view fc-view-" + newViewName + "' style='position:absolute'/>")
                            .appendTo(content),
                    t // the calendar object
                );
            }

            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--;
        }
    }



    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();
                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);
        }
    }



    /* Resizing
    -----------------------------------------------------------------------------*/


    function updateSize() {
        markSizesDirty();
        if (elementVisible()) {
            calcSize();
            setSize();
            unselect();
            currentView.clearEvents();
            currentView.renderEvents(events);
            currentView.sizeDirty = false;
        }
    }


    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);
        }
        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--;
                        }
                    }
                }, 200);
            }else{
                // calendar must have been initialized in a 0x0 iframe that has just been resized
                lateRender();
            }
        }
    }



    /* Event Fetching/Rendering
    -----------------------------------------------------------------------------*/


    // fetches events if necessary, rerenders events if necessary (or if forced)
    function updateEvents(forceRender) {
        if (!options.lazyFetching || isFetchNeeded(currentView.visStart, currentView.visEnd)) {
            refetchEvents();
        }
        else if (forceRender) {
            rerenderEvents();
        }
    }


    function refetchEvents() {
        fetchEvents(currentView.visStart, currentView.visEnd); // will call reportEvents
    }


    // called when event data arrives
    function reportEvents(_events) {
        events = _events;
        rerenderEvents();
    }


    // called when a single event's data has been changed
    function reportEventChange(eventID) {
        rerenderEvents(eventID);
    }


    // attempts to rerenderEvents
    function rerenderEvents(modifiedEventID) {
        markEventsDirty();
        if (elementVisible()) {
            currentView.clearEvents();
            currentView.renderEvents(events, modifiedEventID);
            currentView.eventsDirty = false;
        }
    }


    function markEventsDirty() {
        $.each(viewInstances, function(i, inst) {
            inst.eventsDirty = true;
        });
    }



    /* Selection
    -----------------------------------------------------------------------------*/


    function select(start, end, allDay) {
        currentView.select(start, end, allDay===undefined ? true : allDay);
    }


    function unselect() { // safe to be called before renderView
        if (currentView) {
            currentView.unselect();
        }
    }



    /* Date
    -----------------------------------------------------------------------------*/


    function prev() {
        renderView(-1);
    }


    function next() {
        renderView(1);
    }


    function prevYear() {
        addYears(date, -1);
        renderView();
    }


    function nextYear() {
        addYears(date, 1);
        renderView();
    }


    function today() {
        date = new Date();
        renderView();
    }


    function gotoDate(year, month, dateOfMonth) {
        if (year instanceof Date) {
            date = cloneDate(year); // provided 1 argument, a Date
        }else{
            setYMD(date, year, month, dateOfMonth);
        }
        renderView();
    }


    function incrementDate(years, months, days) {
        if (years !== undefined) {
            addYears(date, years);
        }
        if (months !== undefined) {
            addMonths(date, months);
        }
        if (days !== undefined) {
            addDays(date, days);
        }
        renderView();
    }


    function getDate() {
        return cloneDate(date);
    }



    /* Misc
    -----------------------------------------------------------------------------*/


    function getView() {
        return currentView;
    }


    function option(name, value) {
        if (value === undefined) {
            return options[name];
        }
        if (name == 'height' || name == 'contentHeight' || name == 'aspectRatio') {
            options[name] = value;
            updateSize();
        }
    }


    function trigger(name, thisObj) {
        if (options[name]) {
            return options[name].apply(
                thisObj || _element,
                Array.prototype.slice.call(arguments, 2)
            );
        }
    }



    /* External Dragging
    ------------------------------------------------------------------------*/

    if (options.droppable) {
        $(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;
                        currentView.dragStart(_dragElement, ev, ui);
                    }
                }
            })
            .bind('dragstop', function(ev, ui) {
                if (_dragElement) {
                    currentView.dragStop(_dragElement, ev, ui);
                    _dragElement = null;
                }
            });
    }


}

function Header(calendar, options) {
    var t = this;


    // exports
    t.render = render;
    t.destroy = destroy;
    t.updateTitle = updateTitle;
    t.activateButton = activateButton;
    t.deactivateButton = deactivateButton;
    t.disableButton = disableButton;
    t.enableButton = enableButton;


    // locals
    var element = $([]);
    var tm;



    function render() {
        tm = options.theme ? 'ui' : 'fc';
        var sections = options.header;
        if (sections) {
            element = $("<table class='fc-header' style='width:100%'/>")
                .append(
                    $("<tr/>")
                        .append(renderSection('left'))
                        .append(renderSection('center'))
                        .append(renderSection('right'))
                );
            return element;
        }
    }


    function destroy() {
        element.remove();
    }


    function renderSection(position) {
        var e = $("<td class='fc-header-" + position + "'/>");
        var buttonStr = options.header[position];
        if (buttonStr) {
            $.each(buttonStr.split(' '), function(i) {
                if (i > 0) {
                    e.append("<span class='fc-header-space'/>");
                }
                var prevButton;
                $.each(this.split(','), function(j, buttonName) {
                    if (buttonName == 'title') {
                        e.append("<span class='fc-header-title'><h2>&nbsp;</h2></span>");
                        if (prevButton) {
                            prevButton.addClass(tm + '-corner-right');
                        }
                        prevButton = null;
                    }else{
                        var buttonClick;
                        if (calendar[buttonName]) {
                            buttonClick = calendar[buttonName]; // calendar method
                        }
                        else if (fcViews[buttonName]) {
                            buttonClick = function() {
                                button.removeClass(tm + '-state-hover'); // forget why
                                calendar.changeView(buttonName);
                            };
                        }
                        if (buttonClick) {
                            var icon = options.theme ? smartProperty(options.buttonIcons, buttonName) : null; // why are we using smartProperty here?
                            var text = smartProperty(options.buttonText, buttonName); // why are we using smartProperty here?
                            var button = $(
                                "<span class='fc-button fc-button-" + buttonName + " " + tm + "-state-default'>" +
                                    "<span class='fc-button-inner'>" +
                                        "<span class='fc-button-content'>" +
                                            (icon ?
                                                "<span class='fc-icon-wrap'>" +
                                                    "<span class='ui-icon ui-icon-" + icon + "'/>" +
                                                "</span>" :
                                                text
                                                ) +
                                        "</span>" +
                                        "<span class='fc-button-effect'><span></span></span>" +
                                    "</span>" +
                                "</span>"
                            );
                            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-hover');
                                        },
                                        function() {
                                            button
                                                .removeClass(tm + '-state-hover')
                                                .removeClass(tm + '-state-down');
                                        }
                                    )
                                    .appendTo(e);
                                if (!prevButton) {
                                    button.addClass(tm + '-corner-left');
                                }
                                prevButton = button;
                            }
                        }
                    }
                });
                if (prevButton) {
                    prevButton.addClass(tm + '-corner-right');
                }
            });
        }
        return e;
    }


    function updateTitle(html) {
        element.find('h2')
            .html(html);
    }


    function activateButton(buttonName) {
        element.find('span.fc-button-' + buttonName)
            .addClass(tm + '-state-active');
    }


    function deactivateButton(buttonName) {
        element.find('span.fc-button-' + buttonName)
            .removeClass(tm + '-state-active');
    }


    function disableButton(buttonName) {
        element.find('span.fc-button-' + buttonName)
            .addClass(tm + '-state-disabled');
    }


    function enableButton(buttonName) {
        element.find('span.fc-button-' + buttonName)
            .removeClass(tm + '-state-disabled');
    }


}

fc.sourceNormalizers = [];
fc.sourceFetchers = [];

var ajaxDefaults = {
    dataType: 'json',
    cache: false
};

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 stickySource = { events: [] };
    var sources = [ stickySource ];
    var rangeStart, rangeEnd;
    var currentFetchID = 0;
    var pendingSourceCnt = 0;
    var loadingLevel = 0;
    var cache = [];


    for (var i=0; i<_sources.length; i++) {
        _addEventSource(_sources[i]);
    }



    /* 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<len; i++) {
            fetchEventSource(sources[i], fetchID);
        }
    }


    function fetchEventSource(source, fetchID) {
        _fetchEventSource(source, function(events) {
            if (fetchID == currentFetchID) {
                if (events) {
                    for (var i=0; i<events.length; i++) {
                        events[i].source = source;
                        normalizeEvent(events[i]);
                    }
                    cache = cache.concat(events);
                }
                pendingSourceCnt--;
                if (!pendingSourceCnt) {
                    reportEvents(cache);
                }
            }
        });
    }


    function _fetchEventSource(source, callback) {
        var i;
        var fetchers = fc.sourceFetchers;
        var res;
        for (i=0; i<fetchers.length; i++) {
            res = fetchers[i](source, rangeStart, rangeEnd, callback);
            if (res === true) {
                // the fetcher is in charge. made its own async request
                return;
            }
            else if (typeof res == 'object') {
                // the fetcher returned a new source. process it
                _fetchEventSource(res, callback);
                return;
            }
        }
        var events = source.events;
        if (events) {
            if ($.isFunction(events)) {
                pushLoading();
                events(cloneDate(rangeStart), cloneDate(rangeEnd), function(events) {
                    callback(events);
                    popLoading();
                });
            }
            else if ($.isArray(events)) {
                callback(events);
            }
            else {
                callback();
            }
        }else{
            var url = source.url;
            if (url) {
                var success = source.success;
                var error = source.error;
                var complete = source.complete;
                var data = $.extend({}, source.data || {});
                var startParam = firstDefined(source.startParam, options.startParam);
                var endParam = firstDefined(source.endParam, options.endParam);
                if (startParam) {
                    data[startParam] = Math.round(+rangeStart / 1000);
                }
                if (endParam) {
                    data[endParam] = Math.round(+rangeEnd / 1000);
                }
                pushLoading();
                $.ajax($.extend({}, ajaxDefaults, source, {
                    data: data,
                    success: function(events) {
                        events = events || [];
                        var res = applyAll(success, this, arguments);
                        if ($.isArray(res)) {
                            events = res;
                        }
                        callback(events);
                    },
                    error: function() {
                        applyAll(error, this, arguments);
                        callback();
                    },
                    complete: function() {
                        applyAll(complete, this, arguments);
                        popLoading();
                    }
                }));
            }else{
                callback();
            }
        }
    }



    /* Sources
    -----------------------------------------------------------------------------*/


    function addEventSource(source) {
        source = _addEventSource(source);
        if (source) {
            pendingSourceCnt++;
            fetchEventSource(source, currentFetchID); // will eventually call reportEvents
        }
    }


    function _addEventSource(source) {
        if ($.isFunction(source) || $.isArray(source)) {
            source = { events: source };
        }
        else if (typeof source == 'string') {
            source = { url: source };
        }
        if (typeof source == 'object') {
            normalizeSource(source);
            sources.push(source);
            return source;
        }
    }


    function removeEventSource(source) {
        sources = $.grep(sources, function(src) {
            return !isSourcesEqual(src, source);
        });
        // remove all client events from that source
        cache = $.grep(cache, function(e) {
            return !isSourcesEqual(e.source, source);
        });
        reportEvents(cache);
    }



    /* Manipulation
    -----------------------------------------------------------------------------*/


    function updateEvent(event) { // update an existing event
        var i, len = cache.length, e,
            defaultEventEnd = getView().defaultEventEnd, // getView???
            startDelta = event.start - event._start,
            endDelta = event.end ?
                (event.end - (event._end || defaultEventEnd(event))) // event._end would be null if event.end
                : 0;                                                      // was null and event was just resized
        for (i=0; i<len; i++) {
            e = cache[i];
            if (e._id == event._id && e != event) {
                e.start = new Date(+e.start + startDelta);
                if (event.end) {
                    if (e.end) {
                        e.end = new Date(+e.end + endDelta);
                    }else{
                        e.end = new Date(+defaultEventEnd(e) + endDelta);
                    }
                }else{
                    e.end = null;
                }
                e.title = event.title;
                e.url = event.url;
                e.allDay = event.allDay;
                e.className = event.className;
                e.editable = event.editable;
                e.color = event.color;
                e.backgroudColor = event.backgroudColor;
                e.borderColor = event.borderColor;
                e.textColor = event.textColor;
                normalizeEvent(e);
            }
        }
        normalizeEvent(event);
        reportEvents(cache);
    }


    function renderEvent(event, stick) {
        normalizeEvent(event);
        if (!event.source) {
            if (stick) {
                stickySource.events.push(event);
                event.source = stickySource;
            }
            cache.push(event);
        }
        reportEvents(cache);
    }


    function removeEvents(filter) {
        if (!filter) { // remove all
            cache = [];
            // clear all array sources
            for (var i=0; i<sources.length; i++) {
                if ($.isArray(sources[i].events)) {
                    sources[i].events = [];
                }
            }
        }else{
            if (!$.isFunction(filter)) { // an event ID
                var id = filter + '';
                filter = function(e) {
                    return e._id == id;
                };
            }
            cache = $.grep(cache, filter, true);
            // remove events from array sources
            for (var i=0; i<sources.length; i++) {
                if ($.isArray(sources[i].events)) {
                    sources[i].events = $.grep(sources[i].events, filter, true);
                }
            }
        }
        reportEvents(cache);
    }


    function clientEvents(filter) {
        if ($.isFunction(filter)) {
            return $.grep(cache, filter);
        }
        else if (filter) { // an event ID
            filter += '';
            return $.grep(cache, function(e) {
                return e._id == filter;
            });
        }
        return cache; // else, return all
    }



    /* Loading State
    -----------------------------------------------------------------------------*/


    function pushLoading() {
        if (!loadingLevel++) {
            trigger('loading', null, true);
        }
    }


    function popLoading() {
        if (!--loadingLevel) {
            trigger('loading', null, false);
        }
    }



    /* Event Normalization
    -----------------------------------------------------------------------------*/


    function normalizeEvent(event) {
        var source = event.source || {};
        var ignoreTimezone = firstDefined(source.ignoreTimezone, options.ignoreTimezone);
        event._id = event._id || (event.id === undefined ? '_fc' + eventGUID++ : event.id + '');
        if (event.date) {
            if (!event.start) {
                event.start = event.date;
            }
            delete event.date;
        }
        event._start = cloneDate(event.start = parseDate(event.start, ignoreTimezone));
        event.end = parseDate(event.end, ignoreTimezone);
        if (event.end && event.end <= event.start) {
            event.end = null;
        }
        event._end = event.end ? cloneDate(event.end) : null;
        if (event.allDay === undefined) {
            event.allDay = firstDefined(source.allDayDefault, options.allDayDefault);
        }
        if (event.className) {
            if (typeof event.className == 'string') {
                event.className = event.className.split(/\s+/);
            }
        }else{
            event.className = [];
        }
        // TODO: if there is no start date, return false to indicate an invalid event
    }



    /* Utils
    ------------------------------------------------------------------------------*/


    function normalizeSource(source) {
        if (source.className) {
            // TODO: repeat code, same code for event classNames
            if (typeof source.className == 'string') {
                source.className = source.className.split(/\s+/);
            }
        }else{
            source.className = [];
        }
        var normalizers = fc.sourceNormalizers;
        for (var i=0; i<normalizers.length; i++) {
            normalizers[i](source);
        }
    }


    function isSourcesEqual(source1, source2) {
        return source1 && source2 && getSourcePrimitive(source1) == getSourcePrimitive(source2);
    }


    function getSourcePrimitive(source) {
        return ((typeof source == 'object') ? (source.events || source.url) : '') || source;
    }


}


fc.addDays = addDays;
fc.cloneDate = cloneDate;
fc.parseDate = parseDate;
fc.parseISO8601 = parseISO8601;
fc.parseTime = parseTime;
fc.formatDate = formatDate;
fc.formatDates = formatDates;



/* Date Math
-----------------------------------------------------------------------------*/

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);
    if (!keepTime) {
        clearTime(d);
    }
    return d;
}


function addMonths(d, n, keepTime) { // prevents day overflow/underflow
    if (+d) { // prevent infinite looping on invalid dates
        var m = d.getMonth() + n,
            check = cloneDate(d);
        check.setDate(1);
        check.setMonth(m);
        d.setMonth(m);
        if (!keepTime) {
            clearTime(d);
        }
        while (d.getMonth() != check.getMonth()) {
            d.setDate(d.getDate() + (d < check ? 1 : -1));
        }
    }
    return d;
}


function addDays(d, n, keepTime) { // deals with daylight savings
    if (+d) {
        var dd = d.getDate() + n,
            check = cloneDate(d);
        check.setHours(9); // set to middle of day
        check.setDate(dd);
        d.setDate(dd);
        if (!keepTime) {
            clearTime(d);
        }
        fixDate(d, check);
    }
    return d;
}


function fixDate(d, check) { // force d to be on check's YMD, for daylight savings purposes
    if (+d) { // prevent infinite looping on invalid dates
        while (d.getDate() != check.getDate()) {
            d.setTime(+d + (d < check ? 1 : -1) * HOUR_MS);
        }
    }
}


function addMinutes(d, n) {
    d.setMinutes(d.getMinutes() + n);
    return d;
}


function clearTime(d) {
    d.setHours(0);
    d.setMinutes(0);
    d.setSeconds(0); 
    d.setMilliseconds(0);
    return d;
}


function cloneDate(d, dontKeepTime) {
    if (dontKeepTime) {
        return clearTime(new Date(+d));
    }
    return new Date(+d);
}


function zeroDate() { // returns a Date with time 00:00:00 and dateOfMonth=1
    var i=0, d;
    do {
        d = new Date(1970, i++, 1);
    } while (d.getHours()); // != 0
    return d;
}


function skipWeekend(date, inc, excl) {
    inc = inc || 1;
    while (!date.getDay() || (excl && date.getDay()==1 || !excl && date.getDay()==6)) {
        addDays(date, inc);
    }
    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);
        date.setMonth(0);
        date.setFullYear(y);
    }
    if (m !== undefined && m != date.getMonth()) {
        date.setDate(1);
        date.setMonth(m);
    }
    if (d !== undefined) {
        date.setDate(d);
    }
}



/* Date Parsing
-----------------------------------------------------------------------------*/


function parseDate(s, ignoreTimezone) { // ignoreTimezone defaults to true
    if (typeof s == 'object') { // already a Date object
        return s;
    }
    if (typeof s == 'number') { // a UNIX timestamp
        return new Date(s * 1000);
    }
    if (typeof s == 'string') {
        if (s.match(/^\d+(\.\d+)?$/)) { // a UNIX timestamp
            return new Date(parseFloat(s) * 1000);
        }
        if (ignoreTimezone === undefined) {
            ignoreTimezone = true;
        }
        return parseISO8601(s, ignoreTimezone) || (s ? new Date(s) : null);
    }

    // TODO: never return invalid dates (like from new Date(<string>)), return null instead
    return null;
}


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);
    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]);
        }
        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 + (m[18] ? Number(m[18]) : 0);
        offset *= m[15] == '-' ? 1 : -1;
        date = new Date(+date + (offset * 60 * 1000));
    }
    return date;
}


function parseTime(s) { // returns minutes since start of day
    if (typeof s == 'number') { // an hour
        return s * 60;
    }
    if (typeof s == 'object') { // a Date object
        return s.getHours() * 60 + s.getMinutes();
    }
    var m = s.match(/(\d+)(?::(\d+))?\s*(\w+)?/);
    if (m) {
        var h = parseInt(m[1], 10);
        if (m[3]) {
            h %= 12;
            if (m[3].toLowerCase().charAt(0) == 'p') {
                h += 12;
            }
        }
        return h * 60 + (m[2] ? parseInt(m[2], 10) : 0);
    }
}



/* Date Formatting
-----------------------------------------------------------------------------*/
// TODO: use same function formatDate(date, [date2], format, [options])


function formatDate(date, format, options) {
    return formatDates(date, null, format, options);
}


function formatDates(date1, date2, format, options) {
    options = options || defaults;
    var date = date1,
        otherDate = date2,
        i, len = format.length, c,
        i2, formatter,
        res = '';
    for (i=0; i<len; i++) {
        c = format.charAt(i);
        if (c == "'") {
            for (i2=i+1; i2<len; i2++) {
                if (format.charAt(i2) == "'") {
                    if (date) {
                        if (i2 == i+1) {
                            res += "'";
                        }else{
                            res += format.substring(i+1, i2);
                        }
                        i = i2;
                    }
                    break;
                }
            }
        }
        else if (c == '(') {
            for (i2=i+1; i2<len; i2++) {
                if (format.charAt(i2) == ')') {
                    var subres = formatDate(date, format.substring(i+1, i2), options);
                    if (parseInt(subres.replace(/\D/, ''), 10)) {
                        res += subres;
                    }
                    i = i2;
                    break;
                }
            }
        }
        else if (c == '[') {
            for (i2=i+1; i2<len; i2++) {
                if (format.charAt(i2) == ']') {
                    var subformat = format.substring(i+1, i2);
                    var subres = formatDate(date, subformat, options);
                    if (subres != formatDate(otherDate, subformat, options)) {
                        res += subres;
                    }
                    i = i2;
                    break;
                }
            }
        }
        else if (c == '{') {
            date = date2;
            otherDate = date1;
        }
        else if (c == '}') {
            date = date1;
            otherDate = date2;
        }
        else {
            for (i2=len; i2>i; i2--) {
                if (formatter = dateFormatters[format.substring(i, i2)]) {
                    if (date) {
                        res += formatter(date, options);
                    }
                    i = i2 - 1;
                    break;
                }
            }
            if (i2 == i) {
                if (date) {
                    res += c;
                }
            }
        }
    }
    return res;
};


var dateFormatters = {
    s   : function(d)   { return d.getSeconds() },
    ss  : function(d)   { return zeroPad(d.getSeconds()) },
    m   : function(d)   { return d.getMinutes() },
    mm  : function(d)   { return zeroPad(d.getMinutes()) },
    h   : function(d)   { return d.getHours() % 12 || 12 },
    hh  : function(d)   { return zeroPad(d.getHours() % 12 || 12) },
    H   : function(d)   { return d.getHours() },
    HH  : function(d)   { return zeroPad(d.getHours()) },
    d   : function(d)   { return d.getDate() },
    dd  : function(d)   { return zeroPad(d.getDate()) },
    ddd : function(d,o) { return o.dayNamesShort[d.getDay()] },
    dddd: function(d,o) { return o.dayNames[d.getDay()] },
    M   : function(d)   { return d.getMonth() + 1 },
    MM  : function(d)   { return zeroPad(d.getMonth() + 1) },
    MMM : function(d,o) { return o.monthNamesShort[d.getMonth()] },
    MMMM: function(d,o) { return o.monthNames[d.getMonth()] },
    yy  : function(d)   { return (d.getFullYear()+'').substring(2) },
    yyyy: function(d)   { return d.getFullYear() },
    t   : function(d)   { return d.getHours() < 12 ? 'a' : 'p' },
    tt  : function(d)   { return d.getHours() < 12 ? 'am' : 'pm' },
    T   : function(d)   { return d.getHours() < 12 ? 'A' : 'P' },
    TT  : function(d)   { return d.getHours() < 12 ? 'AM' : 'PM' },
    u   : function(d)   { return formatDate(d, "yyyy-MM-dd'T'HH:mm:ss'Z'") },
    S   : function(d)   {
        var date = d.getDate();
        if (date > 10 && date < 20) {
            return 'th';
        }
        return ['st', 'nd', 'rd'][date%10-1] || 'th';
    }
};



fc.applyAll = applyAll;


/* 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<len; i++) {
        event = events[i];
        eventStart = event.start;
        eventEnd = visEventEnds[i];
        if (eventEnd > 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<len; i++) {
        seg = segs[i];
        j = 0; // the level index where seg should belong
        while (true) {
            collide = false;
            if (levels[j]) {
                for (k=0; k<levels[j].length; k++) {
                    if (segsCollide(levels[j][k], seg)) {
                        collide = true;
                        break;
                    }
                }
            }
            if (collide) {
                j++;
            }else{
                break;
            }
        }
        if (levels[j]) {
            levels[j].push(seg);
        }else{
            levels[j] = [seg];
        }
    }
    return levels;
}



/* Event Element Binding
-----------------------------------------------------------------------------*/


function lazySegBind(container, segs, bindHandlers) {
    container.unbind('mouseover').mouseover(function(ev) {
        var parent=ev.target, e,
            i, seg;
        while (parent != this) {
            e = parent;
            parent = parent.parentNode;
        }
        if ((i = e._fci) !== undefined) {
            e._fci = undefined;
            seg = segs[i];
            bindHandlers(seg.event, seg.element, seg);
            $(ev.target).trigger(ev);
        }
        ev.stopPropagation();
    });
}



/* Element Dimensions
-----------------------------------------------------------------------------*/


function setOuterWidth(element, width, includeMargins) {
    for (var i=0, e; i<element.length; i++) {
        e = $(element[i]);
        e.width(Math.max(0, width - hsides(e, includeMargins)));
    }
}


function setOuterHeight(element, height, includeMargins) {
    for (var i=0, e; i<element.length; i++) {
        e = $(element[i]);
        e.height(Math.max(0, height - vsides(e, includeMargins)));
    }
}


// TODO: curCSS has been deprecated (jQuery 1.4.3 - 10/16/2010)


function hsides(element, includeMargins) {
    return hpadding(element) + hborders(element) + (includeMargins ? hmargins(element) : 0);
}


function hpadding(element) {
    return (parseFloat($.curCSS(element[0], 'paddingLeft', true)) || 0) +
           (parseFloat($.curCSS(element[0], 'paddingRight', true)) || 0);
}


function hmargins(element) {
    return (parseFloat($.curCSS(element[0], 'marginLeft', true)) || 0) +
           (parseFloat($.curCSS(element[0], 'marginRight', true)) || 0);
}


function hborders(element) {
    return (parseFloat($.curCSS(element[0], 'borderLeftWidth', true)) || 0) +
           (parseFloat($.curCSS(element[0], 'borderRightWidth', true)) || 0);
}


function vsides(element, includeMargins) {
    return vpadding(element) +  vborders(element) + (includeMargins ? vmargins(element) : 0);
}


function vpadding(element) {
    return (parseFloat($.curCSS(element[0], 'paddingTop', true)) || 0) +
           (parseFloat($.curCSS(element[0], 'paddingBottom', true)) || 0);
}


function vmargins(element) {
    return (parseFloat($.curCSS(element[0], 'marginTop', true)) || 0) +
           (parseFloat($.curCSS(element[0], 'marginBottom', true)) || 0);
}


function vborders(element) {
    return (parseFloat($.curCSS(element[0], 'borderTopWidth', true)) || 0) +
           (parseFloat($.curCSS(element[0], 'borderBottomWidth', true)) || 0);
}


function setMinHeight(element, height) {
    height = (typeof height == 'number' ? height + 'px' : height);
    element.each(function(i, _element) {
        _element.style.cssText += ';min-height:' + height + ';_height:' + height;
        // why can't we just use .css() ? i forget
    });
}



/* Misc Utils
-----------------------------------------------------------------------------*/


//TODO: arraySlice
//TODO: isFunction, grep ?


function noop() { }


function cmp(a, b) {
    return a - b;
}


function arrayMax(a) {
    return Math.max.apply(Math, a);
}


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];
    }
    var parts = name.split(/(?=[A-Z])/),
        i=parts.length-1, res;
    for (; i>=0; i--) {
        res = obj[parts[i].toLowerCase()];
        if (res !== undefined) {
            return res;
        }
    }
    return obj[''];
}


function htmlEscape(s) {
    return s.replace(/&/g, '&amp;')
        .replace(/</g, '&lt;')
        .replace(/>/g, '&gt;')
        .replace(/'/g, '&#039;')
        .replace(/"/g, '&quot;')
        .replace(/\n/g, '<br />');
}


function cssKey(_element) {
    return _element.id + '/' + _element.className + '/' + _element.style.cssText.replace(/(^|;)\s*(top|left|width|height)\s*:[^;]*/ig, '');
}


function disableTextSelection(element) {
    element
        .attr('unselectable', 'on')
        .css('MozUserSelect', 'none')
        .bind('selectstart.ui', function() { return false; });
}


/*
function enableTextSelection(element) {
    element
        .attr('unselectable', 'off')
        .css('MozUserSelect', '')
        .unbind('selectstart.ui');
}
*/


function markFirstLast(e) {
    e.children()
        .removeClass('fc-first fc-last')
        .filter(':first-child')
            .addClass('fc-first')
        .end()
        .filter(':last-child')
            .addClass('fc-last');
}


function setDayID(cell, date) {
    cell.each(function(i, _cell) {
        _cell.className = _cell.className.replace(/^fc-\w*/, 'fc-' + dayIDs[date.getDay()]);
        // TODO: make a way that doesn't rely on order of classes
    });
}


function getSkinCss(event, opt) {
    var source = event.source || {};
    var eventColor = event.color;
    var sourceColor = source.color;
    var optionColor = opt('eventColor');
    var backgroundColor =
        event.backgroundColor ||
        eventColor ||
        source.backgroundColor ||
        sourceColor ||
        opt('eventBackgroundColor') ||
        optionColor;
    var borderColor =
        event.borderColor ||
        eventColor ||
        source.borderColor ||
        sourceColor ||
        opt('eventBorderColor') ||
        optionColor;
    var textColor =
        event.textColor ||
        source.textColor ||
        opt('eventTextColor');
    var statements = [];
    if (backgroundColor) {
        statements.push('background-color:' + backgroundColor);
    }
    if (borderColor) {
        statements.push('border-color:' + borderColor);
    }
    if (textColor) {
        statements.push('color:' + textColor);
    }
    return statements.join(';');
}


function applyAll(functions, thisObj, args) {
    if ($.isFunction(functions)) {
        functions = [ functions ];
    }
    if (functions) {
        var i;
        var ret;
        for (i=0; i<functions.length; i++) {
            ret = functions[i].apply(thisObj, args) || ret;
        }
        return ret;
    }
}


function firstDefined() {
    for (var i=0; i<arguments.length; i++) {
        if (arguments[i] !== undefined) {
            return arguments[i];
        }
    }
}



fcViews.month = MonthView;

function MonthView(element, calendar) {
    var t = this;


    // exports
    t.render = render;


    // imports
    BasicView.call(t, element, calendar, 'month');
    var opt = t.opt;
    var renderBasic = t.renderBasic;
    var formatDate = calendar.formatDate;



    function render(date, delta) {
        if (delta) {
            addMonths(date, delta);
            date.setDate(1);
        }
        var start = cloneDate(date, true);
        start.setDate(1);
        var end = addMonths(cloneDate(start), 1);
        var visStart = cloneDate(start);
        var visEnd = cloneDate(end);
        var firstDay = opt('firstDay');
        var nwe = opt('weekends') ? 0 : 1;
        if (nwe) {
            skipWeekend(visStart);
            skipWeekend(visEnd, -1, true);
        }
        addDays(visStart, -((visStart.getDay() - Math.max(firstDay, nwe) + 7) % 7));
        addDays(visEnd, (7 - visEnd.getDay() + Math.max(firstDay, nwe)) % 7);
        var rowCnt = Math.round((visEnd - visStart) / (DAY_MS * 7));
        if (opt('weekMode') == 'fixed') {
            addDays(visEnd, (6 - rowCnt) * 7);
            rowCnt = 6;
        }
        t.title = formatDate(start, opt('titleFormat'));
        t.start = start;
        t.end = end;
        t.visStart = visStart;
        t.visEnd = visEnd;
        renderBasic(6, rowCnt, nwe ? 5 : 7, true);
    }


}

fcViews.basicWeek = BasicWeekView;

function BasicWeekView(element, calendar) {
    var t = this;


    // exports
    t.render = render;


    // imports
    BasicView.call(t, element, calendar, 'basicWeek');
    var opt = t.opt;
    var renderBasic = t.renderBasic;
    var formatDates = calendar.formatDates;



    function render(date, delta) {
        if (delta) {
            addDays(date, delta * 7);
        }
        var start = addDays(cloneDate(date), -((date.getDay() - opt('firstDay') + 7) % 7));
        var end = addDays(cloneDate(start), 7);
        var visStart = cloneDate(start);
        var visEnd = cloneDate(end);
        var weekends = opt('weekends');
        if (!weekends) {
            skipWeekend(visStart);
            skipWeekend(visEnd, -1, true);
        }
        t.title = formatDates(
            visStart,
            addDays(cloneDate(visEnd), -1),
            opt('titleFormat')
        );
        t.start = start;
        t.end = end;
        t.visStart = visStart;
        t.visEnd = visEnd;
        renderBasic(1, 1, weekends ? 7 : 5, false);
    }


}

fcViews.basicDay = BasicDayView;

//TODO: when calendar's date starts out on a weekend, shouldn't happen


function BasicDayView(element, calendar) {
    var t = this;


    // exports
    t.render = render;


    // imports
    BasicView.call(t, element, calendar, 'basicDay');
    var opt = t.opt;
    var renderBasic = t.renderBasic;
    var formatDate = calendar.formatDate;



    function render(date, delta) {
        if (delta) {
            addDays(date, delta);
            if (!opt('weekends')) {
                skipWeekend(date, delta < 0 ? -1 : 1);
            }
        }
        t.title = formatDate(date, opt('titleFormat'));
        t.start = t.visStart = cloneDate(date, true);
        t.end = t.visEnd = addDays(cloneDate(t.start), 1);
        renderBasic(1, 1, 1, false);
    }


}

setDefaults({
    weekMode: 'fixed'
});


function BasicView(element, calendar, viewName) {
    var t = this;


    // exports
    t.renderBasic = renderBasic;
    t.setHeight = setHeight;
    t.setWidth = setWidth;
    t.renderDayOverlay = renderDayOverlay;
    t.defaultSelectionEnd = defaultSelectionEnd;
    t.renderSelection = renderSelection;
    t.clearSelection = clearSelection;
    t.reportDayClick = reportDayClick; // for selection (kinda hacky)
    t.dragStart = dragStart;
    t.dragStop = dragStop;
    t.defaultEventEnd = defaultEventEnd;
    t.getHoverListener = function() { return hoverListener };
    t.colContentLeft = colContentLeft;
    t.colContentRight = colContentRight;
    t.dayOfWeekCol = dayOfWeekCol;
    t.dateCell = dateCell;
    t.cellDate = cellDate;
    t.cellIsAllDay = function() { return true };
    t.allDayRow = allDayRow;
    t.allDayBounds = allDayBounds;
    t.getRowCnt = function() { return rowCnt };
    t.getColCnt = function() { return colCnt };
    t.getColWidth = function() { return colWidth };
    t.getDaySegmentContainer = function() { return daySegmentContainer };


    // imports
    View.call(t, element, calendar, viewName);
    OverlayManager.call(t);
    SelectionManager.call(t);
    BasicEventRenderer.call(t);
    var opt = t.opt;
    var trigger = t.trigger;
    var clearEvents = t.clearEvents;
    var renderOverlay = t.renderOverlay;
    var clearOverlays = t.clearOverlays;
    var daySelectionMousedown = t.daySelectionMousedown;
    var formatDate = calendar.formatDate;


    // locals

    var head;
    var headCells;
    var body;
    var bodyRows;
    var bodyCells;
    var bodyFirstCells;
    var bodyCellTopInners;
    var daySegmentContainer;

    var viewWidth;
    var viewHeight;
    var colWidth;

    var rowCnt, colCnt;
    var coordinateGrid;
    var hoverListener;
    var colContentPositions;

    var rtl, dis, dit;
    var firstDay;
    var nwe;
    var tm;
    var colFormat;



    /* Rendering
    ------------------------------------------------------------*/


    disableTextSelection(element.addClass('fc-grid'));


    function renderBasic(maxr, r, c, showNumbers) {
        rowCnt = r;
        colCnt = c;
        updateOptions();
        var firstTime = !body;
        if (firstTime) {
            buildSkeleton(maxr, showNumbers);
        }else{
            clearEvents();
        }
        updateCells(firstTime);
    }



    function updateOptions() {
        rtl = opt('isRTL');
        if (rtl) {
            dis = -1;
            dit = colCnt - 1;
        }else{
            dis = 1;
            dit = 0;
        }
        firstDay = opt('firstDay');
        nwe = opt('weekends') ? 0 : 1;
        tm = opt('theme') ? 'ui' : 'fc';
        colFormat = opt('columnFormat');
    }



    function buildSkeleton(maxRowCnt, showNumbers) {
        var s;
        var headerClass = tm + "-widget-header";
        var contentClass = tm + "-widget-content";
        var i, j;
        var table;

        s =
            "<table class='fc-border-separate' style='width:100%' cellspacing='0'>" +
            "<thead>" +
            "<tr>";
        for (i=0; i<colCnt; i++) {
            s +=
                "<th class='fc- " + headerClass + "'/>"; // need fc- for setDayID
        }
        s +=
            "</tr>" +
            "</thead>" +
            "<tbody>";
        for (i=0; i<maxRowCnt; i++) {
            s +=
                "<tr class='fc-week" + i + "'>";
            for (j=0; j<colCnt; j++) {
                s +=
                    "<td class='fc- " + contentClass + " fc-day" + (i*colCnt+j) + "'>" + // need fc- for setDayID
                    "<div>" +
                    (showNumbers ?
                        "<div class='fc-day-number'/>" :
                        ''
                        ) +
                    "<div class='fc-day-content'>" +
                    "<div style='position:relative'>&nbsp;</div>" +
                    "</div>" +
                    "</div>" +
                    "</td>";
            }
            s +=
                "</tr>";
        }
        s +=
            "</tbody>" +
            "</table>";
        table = $(s).appendTo(element);

        head = table.find('thead');
        headCells = head.find('th');
        body = table.find('tbody');
        bodyRows = body.find('tr');
        bodyCells = body.find('td');
        bodyFirstCells = bodyCells.filter(':first-child');
        bodyCellTopInners = bodyRows.eq(0).find('div.fc-day-content div');

        markFirstLast(head.add(head.find('tr'))); // marks first+last tr/th's
        markFirstLast(bodyRows); // marks first+last td's
        bodyRows.eq(0).addClass('fc-first'); // fc-last is done in updateCells

        dayBind(bodyCells);

        daySegmentContainer =
            $("<div style='position:absolute;z-index:8;top:0;left:0'/>")
                .appendTo(element);
    }



    function updateCells(firstTime) {
        var dowDirty = firstTime || rowCnt == 1; // could the cells' day-of-weeks need updating?
        var month = t.start.getMonth();
        var today = clearTime(new Date());
        var cell;
        var date;
        var row;

        if (dowDirty) {
            headCells.each(function(i, _cell) {
                cell = $(_cell);
                date = indexDate(i);
                cell.html(formatDate(date, colFormat));
                setDayID(cell, date);
            });
        }

        bodyCells.each(function(i, _cell) {
            cell = $(_cell);
            date = indexDate(i);
            if (date.getMonth() == month) {
                cell.removeClass('fc-other-month');
            }else{
                cell.addClass('fc-other-month');
            }
            if (+date == +today) {
                cell.addClass(tm + '-state-highlight fc-today');
            }else{
                cell.removeClass(tm + '-state-highlight fc-today');
            }
            cell.find('div.fc-day-number').text(date.getDate());
            if (dowDirty) {
                setDayID(cell, date);
            }
        });

        bodyRows.each(function(i, _row) {
            row = $(_row);
            if (i < rowCnt) {
                row.show();
                if (i == rowCnt-1) {
                    row.addClass('fc-last');
                }else{
                    row.removeClass('fc-last');
                }
            }else{
                row.hide();
            }
        });
    }



    function setHeight(height) {
        viewHeight = height;

        var bodyHeight = viewHeight - head.height();
        var rowHeight;
        var rowHeightLast;
        var cell;

        if (opt('weekMode') == 'variable') {
            rowHeight = rowHeightLast = Math.floor(bodyHeight / (rowCnt==1 ? 2 : 6));
        }else{
            rowHeight = Math.floor(bodyHeight / rowCnt);
            rowHeightLast = bodyHeight - rowHeight * (rowCnt-1);
        }

        bodyFirstCells.each(function(i, _cell) {
            if (i < rowCnt) {
                cell = $(_cell);
                setMinHeight(
                    cell.find('> div'),
                    (i==rowCnt-1 ? rowHeightLast : rowHeight) - vsides(cell)
                );
            }
        });

    }


    function setWidth(width) {
        viewWidth = width;
        colContentPositions.clear();
        colWidth = Math.floor(viewWidth / colCnt);
        setOuterWidth(headCells.slice(0, -1), colWidth);
    }



    /* Day clicking and binding
    -----------------------------------------------------------*/


    function dayBind(days) {
        days.click(dayClick)
            .mousedown(daySelectionMousedown);
    }


    function dayClick(ev) {
        if (!opt('selectable')) { // if selectable, SelectionManager will worry about dayClick
            var index = parseInt(this.className.match(/fc\-day(\d+)/)[1]); // TODO: maybe use .data
            var date = indexDate(index);
            trigger('dayClick', this, date, true, ev);
        }
    }



    /* Semi-transparent Overlay Helpers
    ------------------------------------------------------*/


    function renderDayOverlay(overlayStart, overlayEnd, refreshCoordinateGrid) { // overlayEnd is exclusive
        if (refreshCoordinateGrid) {
            coordinateGrid.build();
        }
        var rowStart = cloneDate(t.visStart);
        var rowEnd = addDays(cloneDate(rowStart), colCnt);
        for (var i=0; i<rowCnt; i++) {
            var stretchStart = new Date(Math.max(rowStart, overlayStart));
            var stretchEnd = new Date(Math.min(rowEnd, overlayEnd));
            if (stretchStart < stretchEnd) {
                var colStart, colEnd;
                if (rtl) {
                    colStart = dayDiff(stretchEnd, rowStart)*dis+dit+1;
                    colEnd = dayDiff(stretchStart, rowStart)*dis+dit+1;
                }else{
                    colStart = dayDiff(stretchStart, rowStart);
                    colEnd = dayDiff(stretchEnd, rowStart);
                }
                dayBind(
                    renderCellOverlay(i, colStart, i, colEnd-1)
                );
            }
            addDays(rowStart, 7);
            addDays(rowEnd, 7);
        }
    }


    function renderCellOverlay(row0, col0, row1, col1) { // row1,col1 is inclusive
        var rect = coordinateGrid.rect(row0, col0, row1, col1, element);
        return renderOverlay(rect, element);
    }



    /* Selection
    -----------------------------------------------------------------------*/


    function defaultSelectionEnd(startDate, allDay) {
        return cloneDate(startDate);
    }


    function renderSelection(startDate, endDate, allDay) {
        renderDayOverlay(startDate, addDays(cloneDate(endDate), 1), true); // rebuild every time???
    }


    function clearSelection() {
        clearOverlays();
    }


    function reportDayClick(date, allDay, ev) {
        var cell = dateCell(date);
        var _element = bodyCells[cell.row*colCnt + cell.col];
        trigger('dayClick', _element, date, allDay, ev);
    }



    /* External Dragging
    -----------------------------------------------------------------------*/


    function dragStart(_dragElement, ev, ui) {
        hoverListener.start(function(cell) {
            clearOverlays();
            if (cell) {
                renderCellOverlay(cell.row, cell.col, cell.row, cell.col);
            }
        }, ev);
    }


    function dragStop(_dragElement, ev, ui) {
        var cell = hoverListener.stop();
        clearOverlays();
        if (cell) {
            var d = cellDate(cell);
            trigger('drop', _dragElement, d, true, ev, ui);
        }
    }



    /* Utilities
    --------------------------------------------------------*/


    function defaultEventEnd(event) {
        return cloneDate(event.start);
    }


    coordinateGrid = new CoordinateGrid(function(rows, cols) {
        var e, n, p;
        headCells.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();
        bodyRows.each(function(i, _e) {
            if (i < rowCnt) {
                e = $(_e);
                n = e.offset().top;
                if (i) {
                    p[1] = n;
                }
                p = [n];
                rows[i] = p;
            }
        });
        p[1] = n + e.outerHeight();
    });


    hoverListener = new HoverListener(coordinateGrid);


    colContentPositions = new HorizontalPositionCache(function(col) {
        return bodyCellTopInners.eq(col);
    });


    function colContentLeft(col) {
        return colContentPositions.left(col);
    }


    function colContentRight(col) {
        return colContentPositions.right(col);
    }




    function dateCell(date) {
        return {
            row: Math.floor(dayDiff(date, t.visStart) / 7),
            col: dayOfWeekCol(date.getDay())
        };
    }


    function cellDate(cell) {
        return _cellDate(cell.row, cell.col);
    }


    function _cellDate(row, col) {
        return addDays(cloneDate(t.visStart), row*7 + col*dis+dit);
        // what about weekends in middle of week?
    }


    function indexDate(index) {
        return _cellDate(Math.floor(index/colCnt), index%colCnt);
    }


    function dayOfWeekCol(dayOfWeek) {
        return ((dayOfWeek - Math.max(firstDay, nwe) + colCnt) % colCnt) * dis + dit;
    }




    function allDayRow(i) {
        return bodyRows.eq(i);
    }


    function allDayBounds(i) {
        return {
            left: 0,
            right: viewWidth
        };
    }


}

function BasicEventRenderer() {
    var t = this;


    // exports
    t.renderEvents = renderEvents;
    t.compileDaySegs = compileSegs; // for DayEventRenderer
    t.clearEvents = clearEvents;
    t.bindDaySeg = bindDaySeg;


    // imports
    DayEventRenderer.call(t);
    var opt = t.opt;
    var trigger = t.trigger;
    //var setOverflowHidden = t.setOverflowHidden;
    var isEventDraggable = t.isEventDraggable;
    var isEventResizable = t.isEventResizable;
    var reportEvents = t.reportEvents;
    var reportEventClear = t.reportEventClear;
    var eventElementHandlers = t.eventElementHandlers;
    var showEvents = t.showEvents;
    var hideEvents = t.hideEvents;
    var eventDrop = t.eventDrop;
    var getDaySegmentContainer = t.getDaySegmentContainer;
    var getHoverListener = t.getHoverListener;
    var renderDayOverlay = t.renderDayOverlay;
    var clearOverlays = t.clearOverlays;
    var getRowCnt = t.getRowCnt;
    var getColCnt = t.getColCnt;
    var renderDaySegs = t.renderDaySegs;
    var resizableDayEvent = t.resizableDayEvent;



    /* Rendering
    --------------------------------------------------------------------*/


    function renderEvents(events, modifiedEventId) {
        reportEvents(events);
        renderDaySegs(compileSegs(events), modifiedEventId);
    }


    function clearEvents() {
        reportEventClear();
        getDaySegmentContainer().empty();
    }


    function compileSegs(events) {
        var rowCnt = getRowCnt(),
            colCnt = getColCnt(),
            d1 = cloneDate(t.visStart),
            d2 = addDays(cloneDate(d1), colCnt),
            visEventsEnds = $.map(events, exclEndDay),
            i, row,
            j, level,
            k, seg,
            segs=[];
        for (i=0; i<rowCnt; i++) {
            row = stackSegs(sliceSegs(events, visEventsEnds, d1, d2));
            for (j=0; j<row.length; j++) {
                level = row[j];
                for (k=0; k<level.length; k++) {
                    seg = level[k];
                    seg.row = i;
                    seg.level = j; // not needed anymore
                    segs.push(seg);
                }
            }
            addDays(d1, 7);
            addDays(d2, 7);
        }
        return segs;
    }


    function bindDaySeg(event, eventElement, seg) {
        if (isEventDraggable(event)) {
            draggableDayEvent(event, eventElement);
        }
        if (seg.isEnd && isEventResizable(event)) {
            resizableDayEvent(event, eventElement, seg);
        }
        eventElementHandlers(event, eventElement);
            // needs to be after, because resizableDayEvent might stopImmediatePropagation on click
    }



    /* Dragging
    ----------------------------------------------------------------------------*/


    function draggableDayEvent(event, eventElement) {
        var hoverListener = getHoverListener();
        var dayDelta;
        eventElement.draggable({
            zIndex: 9,
            delay: 50,
            opacity: opt('dragOpacity'),
            revertDuration: opt('dragRevertDuration'),
            start: function(e

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions