#31 Datepicker widget

50 000 | 2020-02-19 | Төлөв: Шинэ

Javascript + HTML + CSS

jQuery UI-тэй төстэй байдлаар ажилладаг Datepicker widget шаардлагатай.

Дараах кодыг сайжруулах / засварлах замаар гүйцэтгэнэ.

  • Байгаа CSS ашиглана. Edge, Safari дээр шалгах шаардлагатай. Алдаа байвал код дотроо тоочоод бичээрэй. Алдааг засах ШААРДЛАГАГҮЙ.
  • Хэрэггүй CSS код байвал арилгаарай.
  • Байгаа JS засварлана. Хэрэггүй JS хэсэг байвал арилгаарай.
  • Анх ороход зөвхөн input харагдана.
  • input focus болоход calendar харагдана
  • Calendar дээр дарахад сонгосон огноо input дээр харагдана.
  • Хуудас анх дуудагдахад огноо орсон байвал input focus болоход calendar харагдана. Calendar дээр тухайн огноо заагдсан байна.
  • Огноог зөвхөн YYYY-mm-dd форматаар л харуулна
  • Он бичих үед calendar дээр оны эхний сар руу шилжинэ
  • Он бичээд сар бичих үед тухайн сар руу шилжинэ
  • Өдөр бичих үед тухайн сарын өдөр сонгогдоно.
  • input blur болбол calendar хаагдана. 2020-1-1 гэс бичсэн бол засварлаад 2020-01-01 болгоно.

index.html

<div class="calendar">
    <div class="sidebar">
        <div class="help-button">?</div>
        <div class="help">
            <ul>
                <li>Өдөр дээр дарвал сонгогдоно</li>
                <li>Өдрүүдийг чирээд олныг сонгоно</li>
                <li>Сар дээр дарвал тухайн сар сонгогдоно</li>
                <li>Shift-тэй хамт дарвал нэмж сонгогдоно</li>
                <li>Эхний баганы гаригууд дээр дарвал сонгосон хэсгүүд арилна</li>
            </ul>
        </div>
        <ul class="weekdays">
            <li>Да</li>
            <li>Мя</li>
            <li>Лх</li>
            <li>Пү</li>
            <li>Ба</li>
            <li>Бя</li>
            <li>Ня</li>
        </ul>
    </div>

    <div class="calendar-container">
        <div class="months"></div>
        <div class="days"></div>
    </div>

    <div class="prev">&laquo;</div>
    <div class="next">&raquo;</div>
</div>

main.js

$(function(){
    /**
     * Triggers calendar-selected, calendar-ready events
     */
    var day_width = 24;
    var today = new Date();
    today.setHours(0, 0, 0, 0);  // Used for comparison without time

    function isToday(date) {
        var _date = new Date(date);
        return _date.getTime() == today.getTime();
    }

    function date2tomorrow(date) {
        date.setDate(date.getDate() + 1);
    }

    function getDateStart(num_weeks) {
        var fix_to_monday = 1 - (today.getDay() ? today.getDay() : 7);
        var day = new Date(today);
        day.setDate(today.getDate() - Math.floor(num_weeks / 2) * 7 + fix_to_monday);

        return day;
    }

    function formatDate(date) {
        var year = date.getFullYear();
        var month = date.getMonth() + 1;
        var day = date.getDate();
        month = month <= 9 ? '0' + month : month;
        day = day <= 9 ? '0' + day : day;
        return year + '-' + month + '-' + day;
    }

    function addDay(date, el_week) {
        var el_day = $('<div class="day">').appendTo(el_week);
        el_day.text(date.getDate());
        el_day.attr('title', formatDate(date));
        el_day.toggleClass('today', isToday(date));

        if (isLastOfMonth(date)) {
            el_day.addClass('last');
            el_week.find('.day').addClass('last-week')
        }
        if (isFirstOfMonth(date)) {
            el_day.addClass('first');
            el_week.addClass('first-week');
        }
    }

    function addMonths(container) {
        var width = 0;
        var el_months = container.find('.months');
        var el_weeks = container.find('.days .week');
        var el_month;

        function _addMonth(el_week) {
            var date = el_week.find(':last-child').attr('title');  // e.g: 2016-06-17
            var month = parseInt(date.substr(5, 2));
            var year = date.substr(0, 4);
            el_month = $('<div class="month">').appendTo(el_months);
            el_month.text((month == 1 ? year + ' он ' : '')  + month + ' сар');
            el_month.attr('title', date.substr(0, 7));
            width = -2;
        }

        el_weeks.each(function(i, _el_week){
            var el_week = $(_el_week);
            var el_monday = el_week.find('.day:first');

            var is_monday_first = el_monday.hasClass('first');
            var is_monday_last_week = el_monday.hasClass('last-week');

            if (i == 0) {
                _addMonth(el_week);
                //width = 0;  // margin fix for latter weeks
            } else if (is_monday_first) {
                //_addMonth(el_week);
            }

            width += el_week.width();
            el_month.css('width', width + 'px');

            if (is_monday_last_week) {
                var el_week_next = el_week.next();
                if (el_week_next.length) {
                    _addMonth(el_week.next());
                }
            }
        });
    }

    function isFirstOfMonth(date) {
        return date.getDate() == 1;
    }

    function isLastOfMonth(date) {
        var d = new Date(date);
        d.setDate(1);
        d.setMonth(d.getMonth() + 1);
        d.setDate(0);
        return date.getDate() == d.getDate();
    }

    function initCalendar(container, calendar) {
        var el_days = container.find('.days');
        var visible_weeks = Math.floor(el_days.width() / day_width);
        var number_of_days = visible_weeks * 7;
        var date = getDateStart(visible_weeks);
        var el_week = null;
        var el_months = container.find('.months');

        for (var day=0; day < number_of_days; ++day) {

            if (el_week === null || date.getDay() == 1) {
                el_week = $('<div class="week">').appendTo(el_days);
            }
            addDay(date, el_week);
            date2tomorrow(date);
        }
        addMonths(container);

        calendar.toggleClass = function toggleClass(date, cls, state) {
            var el_day = container.find('.day[title="' + date + '"]');
            var el_today = container.find('.day.today');
            if (!el_today.is(el_day)) {
                el_today.toggleClass('marked', !state);
            }

            el_day.toggleClass(cls, state);
        }
    }

    function selectRange(elements, el_start, el_end) {
        var is_el_start_found = false;
        var is_el_end_found = false;
        function _XOR(a, b) {
            return (a || b) && !(a && b);
        }
        elements.each(function(i, el) {
            if (el_end.is(el_start)) {
                el_end.toggleClass('selected');
                return false;
            } else if (el_end.is(el)) {
                is_el_end_found = true;
                $(el).addClass('selected');
            } else if (el_start.is(el)) {
                is_el_start_found = true;
                $(el).addClass('selected');
            } else if (_XOR(is_el_start_found, is_el_end_found)) {
                $(el).addClass('selected');
            }
        });
    }

    function initScroll(container, calendar) {
        var btn_prev = container.find('.prev');
        var btn_next = container.find('.next');
        var el_scroll = container.find('.calendar-container');

        function _snap(n) {
            return Math.round(n / day_width) * day_width;
        }

        function _getScroll() {
            if (el_scroll.data('scrollDest') !== undefined) {
                return el_scroll.data('scrollDest');
            } else {
                return el_scroll.scrollLeft();
            }
        }

        function _scroll(amount) {
            var scrollLeft = _snap(_getScroll() + amount);
            el_scroll.data('scrollDest', scrollLeft);
            el_scroll.animate({scrollLeft: scrollLeft}, 'fast');
        }

        var center_x = el_scroll.find('.day.today').position().left;
        el_scroll.scrollLeft(_snap(center_x - el_scroll.width() / 2));

        btn_prev.click(function() { _scroll(-day_width * 4); })
        btn_next.click(function() { _scroll(day_width * 4); })

        calendar.scrollTo = function scrollTo(date) {
            el_scroll.stop(true);

            var el_day = container.find('.day[title="' + date + '"]');
            var left = el_day.position().left - el_scroll.width() / 2;
            _scroll(left);
        }

        calendar.scrollSave = function scrollSave() {
            if (calendar.scroll === null) {
                calendar.scroll = _getScroll();
            }
        }
        calendar.scrollRestore = function scrollRestore() {
            if (calendar.scroll !== null) {
                _scroll(calendar.scroll - _getScroll());
                calendar.scroll = null;
            }
        }

        calendar.scroll = null;
    }

    function initSelection(container, calendar) {
        var el_start;

        function _highlightSelection(el_end, shiftKey) {
            if (!el_start) return;
            if (!shiftKey) {
                container.find('.day.selected').removeClass('selected');
            }
            selectRange(container.find('.day'), el_start, el_end);
        }
        function _triggerEvent() {
            var dates = container.find('.day.selected').map(function(i, el){
                return $(el).attr('title');
            });
            var event = $.Event("calendar-selected");
            event.dates = dates.toArray();
            container.trigger(event)
        }

        container.find('.day').mousedown(function(e) {
            el_start = $(e.target);
        });
        container.find('.day').mouseover(function(e) {
            _highlightSelection($(e.target), e.shiftKey);
        });
        container.find('.day').mouseup(function(e) {
            _highlightSelection($(e.target), e.shiftKey);
            if (!el_start) return;
            _triggerEvent();
            el_start = null;
        });
        container.find('.days').mouseleave(function() {
            if (!el_start) return;
            _triggerEvent();
            el_start = null;
        });
        container.find('.weekdays').click(function() {
            container.find('.day.selected').removeClass('selected');
            _triggerEvent();
        });
        container.find('.month').click(function(e) {
            if (!e.shiftKey) {
                container.find('.day.selected').removeClass('selected');
            }
            var year_month = $(e.target).attr('title');
            container.find('.day[title^=' + year_month + ']').addClass('selected');
            _triggerEvent();
        });

        calendar.selectDates = function selectDates(dates) {
            // reset and select given date
            container.find('.day').removeClass('selected');
            $.each(dates, function(idx, date){
                container.find('.day[title=' + date + ']').addClass('selected');
            });
        }
    }

    $('.calendar').each(function(i, _el){
        var el_calendar = $(_el);
        var calendar = {
            el: el_calendar
        };

        initCalendar(el_calendar, calendar);
        initScroll(el_calendar, calendar);
        initSelection(el_calendar, calendar);

        var event = $.Event("calendar-ready");
        event.calendar = calendar;
        el_calendar.trigger(event);
    });

});

main.css

.calendar {
    height: 192px;
    position: relative;
}

.calendar .sidebar {
    float: left;
    width: 24px;
}

.calendar-container {
    margin-left: 24px;
    overflow: hidden;
    position: relative;
    border-radius: 4px 4px 4px 0;
}

.calendar .prev,
.calendar .next {
    position: absolute;
    display: block;
    top: 0px;
    height: 22px;
    width: 22px;
    font-weight: bold;
    line-height: 17px;
    padding-left: 7px;
    color: #fff;
    background-color: #d9d9d9;
    transition: padding-left .3s ease-in-out;
    cursor: pointer;
}
.calendar .prev { left: 24px; border-top-left-radius: 4px; }
.calendar .next { right: 0px; border-top-right-radius: 4px; }
.calendar .prev:hover { padding-left: 1px }
.calendar .next:hover { padding-left: 12px }

.calendar .help-button {
    width: 16px;
    height: 16px;
    font-size: 13px;
    text-align: center;
    background: #ccc;
    border-radius: 8px;
    margin: 4px;
    color: #fff;
    text-shadow: 1px 1px 0 rgba(0,0,0,.2);
    font-weight: bold;
}

.calendar .help{
    position: absolute;
    top: 0px;
    background-color: #f5f5f5;
    font-size: 15px;
    padding: 5px 20px;
    border-radius: 4px;
    margin: 5px;
    box-shadow: 0 15px 31px rgba(0,0,0,.5);
    -moz-transition: opacity .5s;
    -webkit-transition: opacity .5s;
    transition: opacity .5s;
    opacity: 0;
    visibility: hidden;
    z-index: 2;
}
.calendar .sidebar > .help-button:hover + .help,
.calendar .sidebar .help:hover {
    opacity: 1;
    visibility: visible;
}

.calendar .weekdays {
    font-size: 13px;
}

.calendar .weekdays li {
    display: block;
    text-align: center;
    height: 24px;
    width: 22px;
    background-color: #d9d9d9;
}
.calendar .weekdays li:first-child{ border-radius: 4px 0 0 0; }
.calendar .weekdays li:last-child { border-radius: 0 0 0 4px; }

.calendar .months {
    margin-bottom: 2px;
    height: 22px;
}

.calendar .month {
    background: #d9d9d9;
    font-size: 13px;
    text-align: center;
    margin-right: 2px;
    height: 22px;
    display: inline-block;
    overflow: hidden;
}
.calendar .month:last-child { margin-right: 0; }


.calendar .months,
.calendar .days {
    width: 3500px;  /* roughly 6 months before and after */
}

.calendar .week {
    width: 24px;
    display: inline-block;
}

.calendar .day {
    height: 24px;
    text-align: center;
    font-size: 11px;
    padding-top: 1px;
    background-color: #fff;
}

.calendar .day:nth-child(7),
.calendar .day:nth-child(6) {
    color: #aaa;
}

.calendar .day.selected {
    background-color: #a0c6e8;
}
.calendar .day.selected:nth-child(7),
.calendar .day.selected:nth-child(6) {
    color: #fff;
}

.calendar .day.today{
    background-image: radial-gradient(circle, transparent 8px, rgba(0,0,0,0.15) 9px)
}

.calendar .day.marked:not(.today) {
    font-size: 19px;
    font-weight: bold;
    color: #1d9d74;
}
.calendar .day.marked.hover {
    font-size: 11px;
    font-weight: normal;
    color: #333;
    background-image: radial-gradient(circle, transparent 8px, #1d9d74 9px)
}
.calendar .day.today.marked{
    background-image: radial-gradient(circle, transparent 8px, #1d9d74 9px)
}

.calendar .week.first-week .day {
    width: 22px;
    margin-left: 2px;
}
.calendar .week.first-week .day.last-week{
    width: 22px;
    margin-left: 0;
    margin-right: 2px;
}
/*.calendar .day.first{ border-top-left-radius: 4px; }*/
/*.calendar .day.last{ border-bottom-right-radius: 4px; }*/
.calendar .day.last:not(:last-child){
    height: 22px;
    margin-bottom: 2px;
}
« буцах