Source: calendar/days.js

/**
 * A View displaying days in a month. It's one of the child views of ludo.calendar.Calendar.
 * @namespace calendar
 * @class ludo.calendar.Days
 * @param {Object} config
 * @param {String|Date} value Selected date
 * @fires ludo.calendar.Days#setDate - Arguments Date and ludo.View(the view firing the event)
 */

ludo.calendar.Days = new Class({
    Extends:ludo.calendar.Base,
    layout:{
        weight:1
    },
    date:undefined,
    colWidthFirst:16,
    colWidthRest:12,
    showWeeks:true,
    sundayFirst:false,
    overflow:'hidden',
    week:undefined,

    value:undefined,
    touch:{
        enabled:false
    },

    __rendered:function () {
        this.parent();
        this.createCalendarHeader();
        this.createCalendarView();
    },
    ludoDOM:function () {
        this.parent();
        this.getBody().addClass('ludo-calendar-view');
    },
    ludoEvents:function () {
        this.parent();
        if (ludo.util.isTabletOrMobile()) {
            var el = this.getBody();
            el.on('touchstart', this.touchStart.bind(this));
            this.getEventEl().on('touchmove', this.touchMove.bind(this));
            this.getEventEl().on('touchend', this.touchEnd.bind(this));
        }
    },

    touchStart:function (e) {
        this.touch = {
            enabled:true,
            x1:e.pageX, x2:e.pageX,
            y1:e.pageY, y2:e.pageY
        };

        if (e.target.tagName.toLowerCase() == 'window') {
            return false;
        }
        return undefined;
    },
    touchMove:function (e) {
        if (this.touch.enabled) {
            this.touch.x2 = e.pageX;
            this.touch.y2 = e.pageY;

            var left = this.touch.x2 - this.touch.x1;
            var top = this.touch.y2 - this.touch.y1;

            if (Math.abs(left) > Math.abs(top)) {
                if (left > 100)left = 100;
                if (left < -100) left = -100;
                this.els.monthView.css('left',  left + 'px');
                this.els.monthView.css('top',  '0px');
            } else {
                if (top > 100)top = 100;
                if (top < -100) top = -100;
                this.els.monthView.css('top',  top + 'px');
                this.els.monthView.css('left',  '0px');
            }
            return false;
        }
        return undefined;
    },
    touchEnd:function () {
        if (this.touch.enabled) {
            this.touch.enabled = false;
            var diffX = this.touch.x2 - this.touch.x1;
            var diffY = this.touch.y2 - this.touch.y1;

            var absX = Math.abs(diffX);
            var absY = Math.abs(diffY);

            if (absX > 100 || absY > 100) {
                if (absX > absY) {
                    this.data[diffX > 0 ? 'decrement' : 'increment']('month', 1);
                } else {
                    this.data[diffY > 0 ? 'decrement' : 'increment']('year', 1);
                }
                this.sendSetDateEvent();
                this.showMonth();
            }
            this.els.monthView.css('left',  '0');
            this.els.monthView.css('top',  '0');
        }

    },
    createCalendarHeader:function () {
        var el = this.els.daysHeader = $('<div>');
        el.addClass('ludo-calendar-header');
        this.getBody().append(el);

        var html = ['<table ', 'cellpadding="0" cellspacing="0" border="0" width="100%">'];
        html.push(this.getColGroup().join(''));
        html.push('<tr>');
        var headers = this.getTextForHeader();
        for (var i = 0; i < headers.length; i++) {
            html.push('<td>' + headers[i] + '</td>');
        }
        html.push('</tr>');
        html.push('</table>');
        el.html( html.join(''));
    },

    resizeDOM:function () {
        if (!this.els.daysContainer) {
            this.resizeDOM.delay(10, this);
            return;
        }
        this.parent();
        var b = this.getBody();
        var h = this.els.daysHeader;
        var size = { x:b.width(),y:b.height() };

        var height = size.y - ludo.dom.getBH(b) - ludo.dom.getPH(b) - h.height() + ludo.dom.getMH(h);

        var c = this.els.daysContainer;
        height -= (ludo.dom.getMH(c) + ludo.dom.getPH(c) + ludo.dom.getBH(c));
        c.css('height',  height + 'px');
    },

    getColGroup:function () {
        var html = [];
        html.push('<colgroup>');
        html.push('<col ' + 'width="' + this.colWidthFirst + '%" />');
        for (var i = 0; i < 7; i++) {
            html.push('<col ' + 'width="' + this.colWidthRest + '%" />');
        }
        html.push('</colgroup>');
        return html;
    },

    getTextForHeader:function () {
        var ret = [this.headerWeek];
        if (this.sundayFirst) {
            ret.push(this.days[this.days.length - 1]);
            ret = ret.append(this.days.slice(0, this.days.length - 1));

        } else {
            ret = ret.append(this.days);
        }
        return ret;
    },

    createCalendarView:function () {
        var el = this.els.daysContainer = $('<div>');
        el.addClass('ludo-calendar-container-days');
        el.css({
            position:'relative',
            width:'100%',
            overflow:'hidden',
            left:0,
            top:0
        });
        el.on('mousemove', this.mouseOverDays.bind(this));
        el.on('mouseleave', this.removeClsFromMouseOverDay.bind(this));
        this.getBody().append(el);
    },
    showMonth:function () {
        if (this.els.monthView) {
            this.els.monthView.remove();
        }

        var el = this.els.monthView = $('<div>');
        el.on('click', this.selectDay.bind(this));
        el.addClass('ludo-calendar-body-days');
        this.resizeMonthView();
        el.css('position', 'absolute');

        this.els.daysContainer.append(el);

        var html = ['<table ', 'cellpadding="0" cellspacing="0" border="0" width="100%" style="height:100%">'];
        html.push(this.getColGroup().join(''));

        var days = this.getDaysForView();
        var cls = '';

        var selectedDay = 0;
        if (this.isValueMonth()) {
            selectedDay = this.value.get('date');
        }
        var today = 0;
        if (this.isOnTodaysMonth()) {
            today = new Date().get('date');
        }
        var thisMonthStarted = false;
        var nextMonthStarted = false;
        for (var i = 0; i < days.length; i++) {
            cls = '';
            if (i % 8 == 0) {
                if (i)html.push('</tr>');
                html.push('<tr>');
                cls = 'calendar-week';
            } else {
                if (days[i] == 1 && thisMonthStarted)nextMonthStarted = true;
                if (days[i] == 1)thisMonthStarted = true;
                cls = 'calendar-day';
                if (!thisMonthStarted || nextMonthStarted) {
                    cls = cls + ' calendar-day-inactive';
                } else {
                    if ((this.sundayFirst && (i - 1) % 8 == 0) || (!this.sundayFirst && (i + 1) % 8 == 0)) {
                        cls = cls + ' calendar-sunday';
                    }
                    if (days[i] == selectedDay) {
                        cls = cls + ' calendar-day-selected';
                    }
                    if (days[i] == today) {
                        cls = cls + ' calendar-day-today';
                    }
                    cls = cls + ' calendar-day-' + days[i];
                }
            }

            html.push('<td class="' + cls + '">' + days[i] + '</td>');
        }

        html.push('</table>');
        el.html( html.join(''));
        this.resizeMonthView.delay(20, this);

    },

    mouseOverDays:function (e) {
        var el = $(e.target);
        this.removeClsFromMouseOverDay();
        if (!el.hasClass('calendar-day') || el.hasClass('calendar-day-inactive')) {
            return;
        }
        el.addClass('calendar-day-mouse-over');
        this.els.mouseOverDay = el;
    },

    removeClsFromMouseOverDay:function () {
        if (this.els.mouseOverDay) {
            this.els.mouseOverDay.removeClass('calendar-day-mouse-over');
            this.els.mouseOverDay = undefined;
        }
    },

    isValueMonth:function () {

        return this.value ? this.value.get('month') == this.date.get('month') && this.value.get('year') == this.date.get('year') : false;
    },

    isOnTodaysMonth:function () {
        var today = new Date();
        return today.get('month') == this.date.get('month') && today.get('year') == this.date.get('year');
    },
    resizeMonthView:function () {
        this.els.monthView.css('width',  '100%');
    },

    getDaysForView:function () {
        var ret = [];
        var thisMonth = this.date.clone();
        thisMonth.setDate('1');

        var lastMonth = thisMonth.clone().decrement('day', 1);
        var daysInMonth = thisMonth.getLastDayOfMonth();

        var dayOfWeek = thisMonth.getUTCDay();

        if (this.sundayFirst) {
            dayOfWeek++;
            if (dayOfWeek > 6)dayOfWeek = 0;
        }
        var i;
        if (dayOfWeek > 0 || daysInMonth < 31) {
            this.setStartWeek(lastMonth.get('week'));
            var daysInLastMonth = lastMonth.getLastDayOfMonth();
            var count = dayOfWeek || 7;
            ret.push(this.getNextWeek());
            for (i = count - 1; i > 0; i--) {
                ret.push(daysInLastMonth - i);
            }
            if (ret.length % 8 == 0) {
                ret.push(this.getNextWeek());
            }
        } else {
            this.setStartWeek(thisMonth.get('week'));
            ret.push(this.getNextWeek());
        }

        for (i = 1; i <= daysInMonth; i++) {
            ret.push(i);
            if (ret.length % 8 == 0) {
                ret.push(this.getNextWeek());
            }
        }
        var len = ret.length;
        for (i = ret.length; i < 48; i++) {
            if (ret.length < 48) {
                if (ret.length % 8 == 0) {
                    ret.push(this.getNextWeek());
                }
                ret.push(i - len + 1);
            }
        }
        return ret;
    },

    selectDay:function (e) {
        var el = $(e.target);
        if (!el.hasClass('calendar-day')) {
            el = el.closest('.calendar-day');
            if (!el)return;
        }
        if (el.hasClass('calendar-day-inactive')) {
            return;
        }
        this.removeClsFromSelectedDay();

        el.addClass('calendar-day-selected');
        this.value = this.date.clone();
        this.value.set('date', el.html());
        this.fireEvent('change', [this.value, this]);
    },

    removeClsFromSelectedDay:function () {
        if (this.els.monthView && this.isValueMonth()) {
            var el = this.els.monthView.find('.calendar-day-' + this.value.get('date'));
            if (el)el.removeClass('calendar-day-selected');
        }
    },

    addClsForSelectedDay:function () {
        if (this.els.monthView && this.isValueMonth()) {
            var el = this.els.monthView.find('.calendar-day-' + this.value.get('date'));
            if (el)el.addClass('calendar-day-selected');
        }
    },

    setStartWeek:function (week) {
        this.week = week;
    },

    getNextWeek:function () {
        var ret = this.week;
        this.week++;
        if (this.week > 53 || (this.week > 50 && this.date.get('month') == 0)) {
            this.week = 1;
        }
        return ret;
    },
    /**
     * Set currently viewed month
     * @function setDate
     * @param {Object} date
     * @return void
     * @memberof ludo.calendar.Days.prototype
     */
    setDate:function (date) {
        this.date = date;
        this.showMonth();
    },
    /**
     * Set selected date
     * @function setValue
     * @param {Date} date
     * @memberof ludo.calendar.Days.prototype
     */
    setValue:function (date) {
        this.removeClsFromSelectedDay();
        this.value = date.clone();
        this.addClsForSelectedDay();
    }
});