smartinmedia/cunity

View on GitHub
lib/plugins/calendar/js/calendar.min.js

Summary

Maintainability
D
1 day
Test Coverage
/**
 * Bootstrap based calendar full view.
 *
 * https://github.com/Serhioromano/bootstrap-calendar
 *
 * User: Sergey Romanov <serg4172@mail.ru>
 * 
 * ****
 * Edited by Cunity to display only month view without event-list
 */
"use strict";

Date.prototype.getWeek = function() {
    var onejan = new Date(this.getFullYear(), 0, 1);
    return Math.ceil((((this.getTime() - onejan.getTime()) / 86400000) + onejan.getDay() + 1) / 7);
};
Date.prototype.getMonthFormatted = function() {
    var month = this.getMonth() + 1;
    return month < 10 ? '0' + month : month;
};
Date.prototype.getDateFormatted = function() {
    var date = this.getDate();
    return date < 10 ? '0' + date : date;
};
if (!String.prototype.format) {
    String.prototype.format = function() {
        var args = arguments;
        return this.replace(/{(\d+)}/g, function(match, number) {
            return typeof args[number] != 'undefined' ? args[number] : match;
        });
    };
}
if (!String.prototype.formatNum) {
    String.prototype.formatNum = function(decimal) {
        var r = "" + this;
        while (r.length < decimal)
            r = "0" + r;
        return r;
    };
}

(function($) {

    var defaults = {
        // Width of the calendar
        width: '100%',
        // Initial view (can be 'month', 'week', 'day')
        view: 'month',
        // Initial date. No matter month, week or day this will be a starting point. Can be 'now' or a date in format 'yyyy-mm-dd'
        day: 'now',
        // Day Start time and end time with time intervals. Time split 10, 15 or 30.
        time_start: '06:00',
        time_end: '22:00',
        time_split: '30',
        // Source of events data. It can be one of the following:
        // - URL to return JSON list of events in special format.
        //   {success:1, result: [....]} or for error {success:0, error:'Something terrible happened'}
        //   events: [...] as described in events property description
        //   The start and end variables will be sent to this url
        // - A function that received the start and end date, and that
        //   returns an array of events (as described in events property description)
        // - An array containing the events
        events_source: '',
        // Path to templates should end with slash /. It can be as relative
        // /component/bootstrap-calendar/tmpls/
        // or absolute
        // http://localhost/component/bootstrap-calendar/tmpls/
        tmpl_path: 'tmpls/',
        tmpl_cache: true,
        classes: {
            months: {
                inmonth: 'cal-day-inmonth',
                outmonth: 'cal-day-outmonth',
                saturday: 'cal-day-weekend',
                sunday: 'cal-day-weekend',
                today: 'cal-day-today'
            },
            week: {
                workday: 'cal-day-workday',
                saturday: 'cal-day-weekend',
                sunday: 'cal-day-weekend',
                today: 'cal-day-today'
            }
        },
        views: {
            month: {
                slide_events: 1,
                enable: 1
            }
        },
        // ------------------------------------------------------------
        // CALLBACKS. Events triggered by calendar class. You can use
        // those to affect you UI
        // ------------------------------------------------------------
        onAfterEventsLoad: function(events) {
            // Inside this function 'this' is the calendar instance
        },
        onBeforeEventsLoad: function(next) {
            // Inside this function 'this' is the calendar instance
            next();
        },
        onAfterViewLoad: function(view) {
            // Inside this function 'this' is the calendar instance
        },
        // -------------------------------------------------------------
        // INTERNAL USE ONLY. DO NOT ASSIGN IT WILL BE OVERRIDDEN ANYWAY
        // -------------------------------------------------------------
        events: [],
        templates: {
            year: '',
            month: '',
            week: '',
            day: ''
        },
        stop_cycling: false
    };

    var defaults_extended = {
        first_day: 2
    };

    var strings = {
        error_noview: 'Calendar: View {0} not found',
        error_dateformat: 'Calendar: Wrong date format {0}. Should be either "now" or "yyyy-mm-dd"',
        error_loadurl: 'Calendar: Event URL is not set',
        error_where: 'Calendar: Wrong navigation direction {0}. Can be only "next" or "prev" or "today"',
        error_timedevide: 'Calendar: Time split parameter should divide 60 without decimals. Something like 10, 15, 30',
        no_events_in_day: 'No events in this day.',
        title_year: '{0}',
        title_month: '{0} {1}',
        title_week: 'week {0} of {1}',
        title_day: '{0} {1} {2}, {3}',
        week: 'Week {0}',
        all_day: 'All day',
        time: 'Time',
        events: 'Events',
        before_time: 'Ends before timeline',
        after_time: 'Starts after timeline',
        m0: 'January',
        m1: 'February',
        m2: 'March',
        m3: 'April',
        m4: 'May',
        m5: 'June',
        m6: 'July',
        m7: 'August',
        m8: 'September',
        m9: 'October',
        m10: 'November',
        m11: 'December',
        ms0: 'Jan',
        ms1: 'Feb',
        ms2: 'Mar',
        ms3: 'Apr',
        ms4: 'May',
        ms5: 'Jun',
        ms6: 'Jul',
        ms7: 'Aug',
        ms8: 'Sep',
        ms9: 'Oct',
        ms10: 'Nov',
        ms11: 'Dec',
        d0: 'Sunday',
        d1: 'Monday',
        d2: 'Tuesday',
        d3: 'Wednesday',
        d4: 'Thursday',
        d5: 'Friday',
        d6: 'Saturday'
    };

    var browser_timezone = '';
    try {
        if ($.type(window.jstz) == 'object' && $.type(jstz.determine) == 'function') {
            browser_timezone = jstz.determine().name();
            if ($.type(browser_timezone) !== 'string') {
                browser_timezone = '';
            }
        }
    }
    catch (e) {
    }

    function buildEventsUrl(events_url, data) {
        var separator, key, url;
        url = events_url;
        separator = (events_url.indexOf('?') < 0) ? '?' : '&';
        for (key in data) {
            url += separator + key + '=' + encodeURIComponent(data[key]);
            separator = '&';
        }
        return url;
    }

    function getExtentedOption(cal, option_name) {
        var fromOptions = (cal.options[option_name] != null) ? cal.options[option_name] : null;
        var fromLanguage = (cal.locale[option_name] != null) ? cal.locale[option_name] : null;
        if (fromOptions != null) {
            return fromOptions;
        }
        if (fromLanguage != null) {
            return fromLanguage;
        }
        return defaults_extended[option_name];

    }

    function warn(message) {
        if ($.type(window.console) == 'object' && $.type(window.console.warn) == 'function') {
            window.console.warn('[Bootstrap-Calendar] ' + message);
        }
    }

    function Calendar(params, context) {
        this.options = $.extend(true, {position: {start: new Date(), end: new Date()}}, defaults, params);
        this.setLanguage(this.options.language);
        this.context = context;

        context.css('width', this.options.width).addClass('cal-context');

        this.view();
        return this;
    }

    Calendar.prototype.setOptions = function(object) {
        $.extend(this.options, object);
        if ('language' in object) {
            this.setLanguage(object.language);
        }
    }

    Calendar.prototype.setLanguage = function(lang) {
        if (window.calendar_languages && (lang in window.calendar_languages)) {
            this.locale = $.extend(true, {}, strings, calendar_languages[lang]);
            this.options.language = lang;
        } else {
            this.locale = strings;
            delete this.options.language;
        }
    }

    Calendar.prototype._render = function() {
        this.context.html('');
        this._loadTemplate(this.options.view);
        this.stop_cycling = false;

        var data = {};
        data.cal = this;
        data.day = 1;

        // Getting list of days in a week in correct order. Works for month and week views
        if (getExtentedOption(this, 'first_day') == 1) {
            data.months = [this.locale.d1, this.locale.d2, this.locale.d3, this.locale.d4, this.locale.d5, this.locale.d6, this.locale.d0]
        } else {
            data.months = [this.locale.d0, this.locale.d1, this.locale.d2, this.locale.d3, this.locale.d4, this.locale.d5, this.locale.d6]
        }

        // Get all events between start and end
        var start = parseInt(this.options.position.start.getTime());
        var end = parseInt(this.options.position.end.getTime());

        data.events = this.getEventsBetween(start, end);

        data.start = new Date(this.options.position.start.getTime());
        data.lang = this.locale;
        this.context.append(this.options.templates[this.options.view](data));
        this._update();
    };

    Calendar.prototype._month = function(month) {
        this._loadTemplate('year-month');

        var t = {cal: this};
        var newmonth = month + 1;
        t.data_day = this.options.position.start.getFullYear() + '-' + (newmonth < 10 ? '0' + newmonth : newmonth) + '-' + '01';
        t.month_name = this.locale['m' + month];

        var curdate = new Date(this.options.position.start.getFullYear(), month, 1, 0, 0, 0);
        t.start = parseInt(curdate.getTime());
        t.end = parseInt(new Date(this.options.position.start.getFullYear(), month + 1, 1, 0, 0, 0).getTime());
        t.events = this.getEventsBetween(t.start, t.end);
        return this.options.templates['year-month'](t);
    }

    Calendar.prototype._day = function(week, day) {
        this._loadTemplate('month-day');

        var t = {tooltip: '', cal: this};
        var cls = this.options.classes.months.outmonth;

        var firstday = this.options.position.start.getDay();
        if (getExtentedOption(this, 'first_day') == 2) {
            firstday++;
        } else {
            firstday = (firstday == 0 ? 7 : firstday);
        }

        day = (day - firstday) + 1;
        var curdate = new Date(this.options.position.start.getFullYear(), this.options.position.start.getMonth(), day, 0, 0, 0);

        // if day of the current month
        if (day > 0) {
            cls = this.options.classes.months.inmonth;
        }
        // stop cycling table rows;
        var daysinmonth = (new Date(this.options.position.end.getTime() - 1)).getDate();
        if ((day + 1) > daysinmonth) {
            this.stop_cycling = true;
        }
        // if day of the next month
        if (day > daysinmonth) {
            day = day - daysinmonth;
            cls = this.options.classes.months.outmonth;
        }

        cls = $.trim(cls + " " + this._getDayClass("months", curdate));

        if (day <= 0) {
            var daysinprevmonth = (new Date(this.options.position.start.getFullYear(), this.options.position.start.getMonth(), 0)).getDate();
            day = daysinprevmonth - Math.abs(day);
            cls += ' cal-month-first-row';
        }
        t.data_day = curdate.getFullYear() + '-' + curdate.getMonthFormatted() + '-' + (day < 10 ? '0' + day : day);
        t.cls = cls;
        t.day = day;

        t.start = parseInt(curdate.getTime());
        t.end = parseInt(t.start + 86400000);
        t.events = this.getEventsBetween(t.start, t.end);
        return this.options.templates['month-day'](t);
    }

    Calendar.prototype._getDayClass = function(class_group, date) {
        var self = this;
        var addClass = function(which, to) {
            var cls;
            cls = (self.options.classes && (class_group in self.options.classes) && (which in self.options.classes[class_group])) ? self.options.classes[class_group][which] : "";
            if ((typeof (cls) == "string") && cls.length) {
                to.push(cls);
            }
        };
        var classes = [];
        if (date.toDateString() == (new Date()).toDateString()) {
            addClass("today", classes);
        }
        switch (date.getDay()) {
            case 0:
                addClass("sunday", classes);
                break;
            case 6:
                addClass("saturday", classes);
                break;
        }

        addClass(date.toDateString(), classes);

        return classes.join(" ");
    };

    Calendar.prototype.view = function(view) {
        this._init_position();
        this._loadEvents();
        this._render();

        this.options.onAfterViewLoad.call(this, this.options.view);
    };

    Calendar.prototype.navigate = function(where, next) {
        var to = $.extend({}, this.options.position);
        if (where == 'next')
            to.start.setMonth(this.options.position.start.getMonth() + 1);

        else if (where == 'prev')
            to.start.setMonth(this.options.position.start.getMonth() - 1);

        else if (where == 'today')
            to.start.setTime(new Date().getTime());

        else
            $.error(this.locale.error_where.format(where))

        this.options.day = to.start.getFullYear() + '-' + to.start.getMonthFormatted() + '-' + to.start.getDateFormatted();
        this.view();
        if (_.isFunction(next))
            next();

    };

    Calendar.prototype._init_position = function() {
        var year, month, day;

        if (this.options.day == 'now') {
            var date = new Date();
            year = date.getFullYear();
            month = date.getMonth();
            day = date.getDate();
        } else if (this.options.day.match(/^\d{4}-\d{2}-\d{2}$/g)) {
            var list = this.options.day.split('-');
            year = parseInt(list[0], 10);
            month = parseInt(list[1], 10) - 1;
            day = parseInt(list[2], 10);
        }
        else {
            $.error(this.locale.error_dateformat.format(this.options.day));
        }

        this.options.position.start.setTime(new Date(year, month, 1).getTime());
        this.options.position.end.setTime(new Date(year, month + 1, 1).getTime());

        return this;
    };

    Calendar.prototype.getTitle = function() {
        var p = this.options.position.start;
        return this.locale.title_month.format(this.locale['m' + p.getMonth()], p.getFullYear());
    };

    Calendar.prototype.isToday = function() {
        var now = new Date().getTime();

        return ((now > this.options.position.start) && (now < this.options.position.end));
    }

    Calendar.prototype.getStartDate = function() {
        return this.options.position.start;
    }

    Calendar.prototype.getEndDate = function() {
        return this.options.position.end;
    }

    Calendar.prototype._loadEvents = function() {
        var self = this;
        var source = null;
        if ('events_source' in this.options && this.options.events_source !== '') {
            source = this.options.events_source;
        }
        else if ('events_url' in this.options) {
            source = this.options.events_url;
            warn('The events_url option is DEPRECATED and it will be REMOVED in near future. Please use events_source instead.');
        }
        var loader;
        if (source.length) {
            loader = function() {
                var events = [];
                var params = {from: self.options.position.start.getTime(), to: self.options.position.end.getTime()};
                if (browser_timezone.length) {
                    params.browser_timezone = browser_timezone;
                }
                $.ajax({
                    url: buildEventsUrl(source, params),
                    dataType: 'json',
                    type: 'GET',
                    async: false
                }).done(function(json) {
                    if (!json.success) {
                        $.error(json.error);
                    }
                    if (json.result) {
                        for (var i = 0; i < json.result.length; i++) {
                            json.result[i].time = moment(parseInt(json.result[i].start));
                        }
                        events = json.result;
                    }
                });
                return events;
            };
        }
        if (!loader) {
            $.error(this.locale.error_loadurl);
        }
        this.options.onBeforeEventsLoad.call(this, function() {
            self.options.events = loader();
            self.options.events.sort(function(a, b) {
                var delta;
                delta = a.start - b.start;
                if (delta == 0) {
                    delta = a.end - b.end;
                }
                return delta;
            });
            self.options.onAfterEventsLoad.call(self, self.options.events);
        });
    };

    Calendar.prototype._loadTemplate = function(name) {
        if (this.options.templates[name]) {
            return;
        }
        var self = this;
        $.ajax({
            url: this.options.tmpl_path + name + '.html',
            dataType: 'html',
            type: 'GET',
            async: false,
            cache: this.options.tmpl_cache
        }).done(function(html) {
            self.options.templates[name] = _.template(html);
        });
    };


    Calendar.prototype._update = function() {
        var self = this;

        $('*[data-toggle="tooltip"]').tooltip({container: 'body'});
        this['_update_' + this.options.view]();
    };

    Calendar.prototype._update_month = function() {
        var self = this;
        $('a.event').mouseenter(function() {
            $('a[data-event-id="' + $(this).data('event-id') + '"]').closest('.cal-cell1').addClass('day-highlight dh-' + $(this).data('event-class'));
        });
        $('a.event').mouseleave(function() {
            $('div.cal-cell1').removeClass('day-highlight dh-' + $(this).data('event-class'));
        });
    };

    Calendar.prototype.getEventsBetween = function(start, end) {
        var events = [];
        $.each(this.options.events, function() {
            if ((parseInt(this.start) < end || this.start == null) && (parseInt(this.end) >= start || this.end == null)) {
                events.push(this);
            }
        });
        return events;
    };

    $.fn.calendar = function(params) {
        return new Calendar(params, this);
    }
}(jQuery));