Giter VIP home page Giter VIP logo

jsmin-php's Introduction

jsmin-php

This project is unmaintained. I stopped using it years ago. You shouldn't use it. You shouldn't use any version of JSMin. There are much better tools available now.

Here are some of them:

jsmin-php's People

Contributors

aag avatar andrerom avatar gerhobbelt avatar rgrove avatar tedivm avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

jsmin-php's Issues

JSMin-php gives error when parsing a specific string

Hi,

We are currently experiencing problems with using JSMin. The javascript code that causes us problems is this:

var pattern=/^(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])$/;

The usage is this:
$jsmin_php = JSMin::minify(file_get_contents("error.js"));
echo $jsmin_php;

The error thrown is this:
Fatal error: Uncaught exception 'JSMinUnterminatedStringException' with message 'Unterminated String: ''+/=?^`{|}~-]+)|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|[\x01-\x09\x0b\x0c\x0e-\x7f])")@(?:(?:a-z0-9?.)+a-z0-9?|[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?).){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-][a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|[\x01-\x09\x0b\x0c\x0e-\x7f])+)])$/;'' in /home/kasper/kaspergrubbe.dk/htdocs/JSMin.php:151 Stack trace: #0 /home/kasper/kaspergrubbe.dk/htdocs/JSMin.php(123): JSMin->action(1) #1 /home/kasper/kaspergrubbe.dk/htdocs/JSMin.php(80): JSMin->min() #2 /home/kasper/kaspergrubbe.dk/htdocs/JSMin.php(53): JSMin::minify('var pattern=/^(...') #3 {main} thrown in /home/kasper/kaspergrubbe.dk/htdocs/JSMin.php on line 151

Feedback on License comments

For some of the files I merge, and then minify as well as pack/compress, I had a friend note that (as an example) JQuery requires there license header be left in for commercial uses of it.
/*! jQuery v1.7 jquery.com | jquery.org/license */

I don't know if its widely supported to ignore this type of comment or not, or subsequently put it back in. Just thought I'd toss it out there since currently this script removes all comments.

Issue with + ++ (increment)

The following js snippet (note the sequence plus, space, plus, plus "+ ++"):

var n=0;
alert("test"+ ++n); //alert test1, n=1 at the end

is erroneously minified to (note the sequence plus,plus,plus):

var n=0;alert("test"+++n);

that produces an error.

Error when minify the jquery.mobile-1.0a3.js

I try to minify the latest release of JQuery Mobile (jquery.mobile-1.0a3.js) and this is the error detected:

Fatal error: Uncaught exception 'JSMinException' with message 'Unterminated regu
lar expression literal.' in /home/sanz/workspace/saltos/code/lib/jsmin/jsmin-1.1
.1.php:123
Stack trace:
-0: /home/sanz/workspace/saltos/code/lib/jsmin/jsmin-1.1.1.php(235): JSMin->actio
n(1)
-1: /home/sanz/workspace/saltos/code/lib/jsmin/jsmin-1.1.1.php(64): JSMin->min()
-2: /home/sanz/workspace/saltos/code/php/action/cache.php(62): JSMin::minify('/*!
? * jQuery M...')
-3: /home/sanz/workspace/saltos/code/xml.php(77): include('/home/sanz/work...')
-4: {main}
thrown in /home/sanz/workspace/saltos/code/lib/jsmin/jsmin-1.1.1.php on line 1
23

Josep.

1+ ++i jsminified incorrectly

this line is not jsminified properly

a = 1 + ++i

jsmin-php delete the space between the first + and ++ resulting javascript interpretation error.

In Llamlab XPath (http://www.llamalab.com/js/xpath/) library the author use 2 or 3 times this syntax ...

I bypass the problem put ++i between parenthesis

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

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

GPL Compatibility and initial jsmin.c license - "Good not Evil" clause

Dear,

We evaluated the license of Mediawiki and found that jsmin is used but there is a slight issue with the terms of the initial C version of jsmin (jsmin.c).

https://github.com/rgrove/jsmin-php/blob/master/jsmin.php#L28

"The Software shall be used for Good, not Evil."

As you can see, this is excluding the use of the software in some case and rending the license non-free. By so, this license is incompatible with GNU General Public License used
by Mediawiki. I know this looks like a small issue as the rest of terms is a classical free MIT/X license but just by adding this sentence, this is transforming this in a non-free license.

Would it possible to clarify this with the initial jsmin.c author? and remove the above mentioned terms.

This would clarify for any use of your excellent library in free software.

Thank you for your help,

Preserve important comments e.g. copyright/ license information

JSMin should preserve important comments e.g. copyright or license info.
Otherwise webmasters could unknowingly be infringing on copyright when including 3rd party code.

YUI compressor gets around this by checking for important comments e.g:
/*!
Somthing very important
*/

See more about this issue:
http://zoompf.com/blog/2009/11/jsmin-important-comments-and-copyright-violations

And a fork of JSmin that has solved this problem:
http://fmarcia.info/jsmin/test.html

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.