Air Datepicker, an easy and beautiful date picker

I want to share with you the experience of writing a date picker for a text field.



The result of the work can be found here: Air Datepicker .

Introduction


While working on the latest project, it became necessary to add a calendar to the application with the ability to select a specific month. All popular plugins provide such an opportunity, my choice is Zebra Datepicker - small, functional, everything is great. But some things were still missing:

  1. passing Date () objects to parameters instead of strings
  2. less cumbersome markup
  3. flexible element positioning
  4. animation on appearance

How much you did not have to work with the date, it was almost always stored in the unix format in the source data , and it remains a mystery to me why in many plugins when setting, for example, the minimum possible date, you need to pass a string: you need to get the date, then redo it into a string and only then pass it to the plugin, instead of just giving new Date (time) .

As for cumbersome markup, a table layout is also added to it, to the cells of which you can’t add position: relative; .

And finally, I still want to be able to add a little animation, but due to the fact that many popular calendars use the .show () method , which uses the propertydisplay , transitions are difficult to add.

Development


I divided the calendar into three parts:

// Основная часть
Datepicker
// Тело календаря
Datepicker.Body
// Навигация
Datepicker.Navigation

When any events in the body or navigation occur, they report this to the main part, and the calendar updates its state in accordance with these events. Getters and setters

helped me in this task . For example, when changing the month, a new displayed date with the changed month number is simply assigned, and inside the getter, the method of redrawing the body and calendar navigation is called. Despite the fact that it would be possible to do without them, this approach seems to me more beautiful. For example, this is how the transition to the next month, year or decade looks like, depending on the current view:

next: function() {
    var d = this.parsedDate;
    switch (this.view) {
        case 'days':
            this.date = new Date(d.year, d.month + 1, 1);
            break;
        case 'months':
            this.date = new Date(d.year + 1, d.month, 1);
            break;
        case 'years':
            this.date = new Date(d.year + 10, 0, 1);
            break;
    }
}

In turn, inside the getter, a call to draw calendar elements occurs (simplified):

set date (val) {
    this.currentDate = val;
    this.currentView._render();
    this.nav._render();
}

In the same way there is a transition to another view, very simple:

this.view = 'months';


Markup


The basis for the calendar is as follows:


Without tables and a hint of them. Cell is simple
...
, which makes it possible to add pseudo elements to them and position the content inside them as you want.

I do not see much point in dividing into rows of cells, since this is an additional unnecessary element. All dates go one after another, they have a relative width that allows you to jump to another line at the right time.

Calculating the total number of days in a month


To generate the correct HTML, you need to know how many days are in a month. To do this, a small trick is used with the transfer of the next month and zero date (in Date (), the date of the month starts from one).

Datepicker.getDaysCount = function (date) {
    // Например, нам нужно узнать сколько дней в декабре, передаем следующий месяц, получается январь.
    // Но из-за того, что вместо 1, мы передали 0, он указывает на последний день предыдущего месяца,
    // что в итоге и дает нам 31 число, или 31 день.
    return new Date(date.getFullYear(), date.getMonth() + 1, 0).getDate();
};

The formation of the names of the days




When you initialize the calendar, you can set the day on which the week starts. It seemed interesting to me to show how you can form a markup with the names of the days using recursion:

/**
* @param firstDay - День, с которого начинается неделя
* @param [curDay] - Текущий день, для которого формируется разметка
* @param [html] - Весь html доступный на данный момент
* @param [i] - Текущий номер дня недели
*/
_getDayNamesHtml: function (firstDay, curDay, html, i) {
    curDay = curDay != undefined ? curDay : firstDay;
    html = html ? html : '';
    i = i != undefined ? i : 0;
    // Если прошли все 7 дней, возвращаем готовый html
    if (i > 7) return html;
    // Если дошли до последнего дня недели, а общий счетчик еще не больше 7, начинаем с первого дня недели
    if (curDay == 7) return this._getDayNamesHtml(firstDay, 0, html, ++i);
    html += '
' + this.localization.daysMin[curDay] + '
'; return this._getDayNamesHtml(firstDay, ++curDay, html, ++i); },

Using flexbox


For positioning inside the calendar, I use flexbox . It easily allows you to center the content inside the cells, it will be centered in all browsers ( which support this technology ) and on different operating systems, unlike the technique of setting the height and the same line spacing.

Plus, it allows you to arrange elements at an equidistant distance from each other with just one line:

.datepicker--nav {
    justify-content: space-between;
}

No need to worry about different widths, everything will be calculated automatically.

You can also mention the buttons "Today" and "Clear":



If there are two, they occupy 50% of the entire width, if one, then it occupies the entire width. This can also be achieved in one line:

.datepicker--button {
    flex: 1;
}

This means that the element, if necessary, can both increase in size and decrease, but at the same time, the dimensions of all neighbors will be the same. When the button is one, it expands to its full width, when two, they are proportionally reduced and occupy 50% each, etc. You can add as many elements as you like, all of them will have the same size, and in total they will occupy the entire width of the parent.

As a result, we get the ease of positioning the content as when using tables, but at the same time we keep the layout clean and valid.

Positioning


Element position is set by two values:

  1. side on which the calendar will appear
  2. position on this side

If you need to place the calendar on the top right, then the value will look like:

{
    position: 'top left'
}

In order to add the animation of "drive up" to the text field, I added helper classes that say which side the animation should start from. In this case, this class would look like.-From-top- . For animation, css transition and css transform are responsible . This allows you to achieve smoothness, as well as add custom transitions.

Regarding Date ()


As I mentioned at the beginning, I don’t quite understand situations when instead of a date object I need to pass a string. Perhaps this is convenient for automatic initialization, when parameters need to be passed through data attributes, but for me it’s more convenient to just pass new Date (). Moreover, a record of the form new Date (2015, 11, 17) is not particularly complicated '2015-12-17'. Therefore, I have to pass new Date () in all the parameters where the date is set.

A few words about using


I like the practice of automatic initialization of plugins, so to initialize the calendar to the text field, just add the class 'datepicker-here' and it will work.


Options can be passed through data attributes.

Custom cell contents


Air Datepicker has the ability to completely change the contents of cells. This allows you to add, for example, event names or some auxiliary content to cells. To do this, use the onRenderCell () option :

$('#datepicker').datepicker({
    // Добавим свой контент во все ячейки с датой 31 декабря.
    onRenderCell: function (date, cellType) {
        if (cellType == 'day' && date.getDate() == 31 && date.getMonth() == 11) {
            return {
                classes: '-ny-',
                html: 'Новый год!'
            }
        }
    }
})

Conclusion


As a result, I can say that I got a good experience, improved my skills in working with dates and writing documentation. The calendar turned out to be small: only 20kb ( minified js file ), but quite functional, at least for me it performs its tasks. I would be glad if he or this article helps someone.

Thanks for attention.

Also popular now: