Starting my blog – https://blog.cdswyda.com/post/2017121010

Why reinvent the wheel when there are so many calendar controls?

Because most calendar controls are used to select dates, there is a need to display a wide variety of content on the calendar. There are fewer and less satisfying calendar controls to try out.

So let’s create another wheel and take you through the process of developing a calendar control based on the component mechanism we completed earlier.

demand

Briefly summarize the requirements as follows:

  • On the view
  • Support for inserting arbitrary content into each day of the calendar
  • Related Click events
  • Gets the start and end dates of the current view of the calendar
  • Gets the selected date of the setting

Implementation analysis

First of all, let’s take a look at the calendar in the system to see what the characteristics of the calendar are.

There are 28 to 31 days in a month, but to keep the structure intact, the calendar has partial dates from the previous and next months, so that a month must show the full six weeks.

Just get the start date of the month and you can draw the calendar.

How do you calculate the start date in the calendar view of the month? The previous analysis shows a partial number of days in the previous month for completeness, so you just need to work backwards from the first of the month.

Start date = date of 1st of the month - week of 1st of the month

End Date = Start date + 42 days

Copy the code

This problem is clear, feel that there is no big obstacle to achieve such a calendar, start to work!

Necessary structure preparation

Start by building the basic structure shown below

Among them:

  • The left and right sides of the header are personalized areas for actual use to place arbitrary content. The middle is used to display the current month and the switch button
  • Draw the entire calendar in the main area
    • Thead draws the week from Monday to Sunday or From Sunday to Monday. This content will not change with the month and can be directly prepared
    • Tbody is used to draw variable dates, ready to leave the container empty.
  • The foot area is used to place any items for actual use
  • The Menu area is used for the panel that pops up when switching dates

Draw a calendar

After you initialize the calendar structure, you can begin to draw the calendar.

Calculate the start and end dates of the month

First complete the calculation of the start and end times

{
    // Initializes the start and end dates of the current month
    _initStartEnd: function () {
        // The first of the month
        var currMonth = moment(this.currMonth, 'YYYY-MM'),
            // The ISO Day of the week with 1 being Monday and 7 being Sunday is the 1st of the month.
            firstDay_weekday = currMonth.isoWeekday(),
            startDateOfMonth,
            endDateOfMonth;
        if (!this.dayStartFromSunday) {
            // Start on Monday and reduce the number of days in the week -1 is the start date
            startDateOfMonth = currMonth.subtract(firstDay_weekday - 1.'day');
        } else {
            // Start on Sunday and go straight to the previous day
            startDateOfMonth = currMonth.subtract(firstDay_weekday, 'day');
        }

        endDateOfMonth = startDateOfMonth.clone().add(41.'day');

        this.startDateOfMonth = startDateOfMonth;
        this.endDateOfMonth = endDateOfMonth; }}Copy the code

Because there are a lot of dates to process, and date processing in JavaScript varies greatly from browser to browser, you can use moment.js directly to process dates uniformly.

Due to different usage habits, the start of a week is not determined on Monday or Sunday, so it can be directly used as the configuration.

Draw dates in January

The start and end dates of the month have been calculated above, so just walk through the drawing.

Since we used a table implementation, we need to draw on a row basis.

The implementation is as follows:

{
    // Render the mutable part of the calendar
    _render: function () {
        this._initStartEnd();

        var weeks = 6,
            days = 7,
            curDate = this.startDateOfMonth.clone(),
            tr;

        var start = this.startDateOfMonth.format('YYYY-MM-DD'),
            end = this.endDateOfMonth.format('YYYY-MM-DD');

        // Empty and start a new rendering
        this._clearDays();
        this._renderTitle();

        for (var i = 0; i < weeks; ++i) {
            tr = document.createElement('tr');
            tr.className = 'ep-calendar-week';
            this._daysBody.appendChild(tr);

            for (var j = 0; j < days; ++j) {
                // Render a day and increment
                this._renderDay(curDate, tr);
                curDate.add(1.'day'); }}},// Daily render
    _renderDay: function (date, currTr) {
        var td = document.createElement('td'),
            tdInner = document.createElement('div'),
            text = document.createElement('span'),
            day = date.isoWeekday(),
            // The month returned is 0-11
            month = date.month() + 1;

        tdInner.appendChild(text);
        td.appendChild(tdInner);

        td.className = 'ep-calendar-date';
        tdInner.className = 'ep-calendar-date-inner';
        // Full date
        td.setAttribute('data-date', date.format('YYYY-MM-DD'));
        // The corresponding ISO week
        td.setAttribute('data-isoweekday', day);

        // The weekend tag text.classname
        if (day === 6 || day === 7) {
            td.className += ' ep-calenday-weekend';
        }
        // Not this month
        // Substr has a problem in IE8
        // if (month ! = parseInt(this.currMonth.substr(-2))) {
        if(month ! =parseInt(this.currMonth.substr(5), 10)) {
            td.className += ' ep-calendar-othermonth';
        }
        // Today marks
        if (this.today == date.format('YYYY-MM-DD')) {
            td.className += ' ep-calendar-today';
        }

        // The page has not yet been inserted when rendering every day
        var renderEvent = this.fire('cellRender', {
            // Full date of the day
            date: date.format('YYYY-MM-DD'),
            // The iso week of the day
            isoWeekday: day,
            Dom / / calendar
            el: this.el,
            // The current cell
            tdEl: td,
            // Date text
            dateText: date.date(),
            / / date class
            dateCls: 'ep-calendar-date-text'.// Additional HTML to inject
            extraHtml: ' '.isHeader: false
        });

        // Handle changes to dayText content and style
        text.innerText = renderEvent.dateText;
        text.className = renderEvent.dateCls;

        // Add new content
        if (renderEvent.extraHtml) {
            jQuery(renderEvent.extraHtml).appendTo(tdInner);
        }

        currTr.appendChild(renderEvent.tdEl);

        // Insert into the page every day after rendering
        this.fire('afterCellRender', {
            date: date.format('YYYY-MM-DD'),
            isoWeekday: day,
            el: this.el,
            tdEl: td,
            dateText: text.innerText,
            dateCls: text.className,
            extraHtml: renderEvent.extraHtml,
            isHeader: false}); }}Copy the code

Just draw 42 days from the start date.

In order to be flexible, different events are triggered at different times of drawing, and corresponding events can be bound to carry out personalized operations in them.

For convenience and flexibility, the date and week attributes are added to the DOM when the date is drawn.

In this process, you need to mark the date whether it is weekend, whether it is this month, whether it is selected, whether it is today, and so on.

Draw something else

In addition to the above, we also draw the date selection, title, and so on, which are actually changes to the existing DOM elements and are no longer expanded.

Switch the implementation of the month

Now that you’ve basically drawn a calendar, it’s actually easier to switch months. Just recalculate the start date according to the new month, clear the original content, and draw again.

{
    // Set the month
    setMonth: function (ym) {
        var date = moment(ym, 'YYYY-MM');

        if (date.isValid()) {
            var oldMonth = this.currMonth,
                aimMonth = date.format('YYYY-MM');

            // Before the month change
            this.fire('beforeMonthChange', {
                el: this.el,
                oldMonth: oldMonth,
                newMonth: aimMonth
            });

            this.currMonth = aimMonth;
            this.render();

            // After the month change
            this.fire('afterMonthChange', {
                el: this.el,
                oldMonth: oldMonth,
                newMonth: aimMonth
            });

        } else {
            throw new Error(ym + 'Is an invalid date'); }}}Copy the code

Event handling

There are a lot of events to deal with, and this is just a date click as an indication.

{
    // Initialize the event
    _initEvent: function () {
        var my = this;
        jQuery(this.el)
            // Date cell
            .on('click'.'.ep-calendar-date'.function (e) {
                var date = this.getAttribute('data-date'),
                    ev = my.fire('dayClick', {
                        ev: e,
                        date: date,
                        day: this.getAttribute('data-isoweekday'),
                        el: my.el,
                        tdEl: this
                    });

                // If cancel of the event object is changed to true, subsequent selections are not performed
                if(! ev.cancel) { my.setSelected(date); }}}})Copy the code

Since the DATE’s DOM element is always added and removed, binding events directly to the DATE’s DOM element makes it cumbersome to rebind events after each addition.

Use the event broker mechanism directly to bind events to the DOM of the entire calendar, so that events only need to be initialized once at creation time, simple, efficient, and memory saving.

use

The main purpose of this new control is to support drawing arbitrary content in the calendar, how to use it?

var testCalendar = epctrl.init('Calendar', {
    el: '#date'.// Events during resource loading need to be specified directly here
    events: {
        beforeSourceLoad: function (e) {
            // Add our skin style file before the resource is loaded
            e.cssUrl.push('./test-skin.css'); }}});// Dynamically fetching data is supported before rendering the date part
testCalendar.on('beforeDateRender'.function (e) {
    var startDate = e.startDate,
        endDate = e.endDate;
    // If you want to get data dynamically
    // Add ajax to the ajax property of the event object
    // The date render cellRender event will be executed after ajax successfully fetched the data
    e.ajax = $.ajax({
        url: 'getDateInfo.xxx'.// Pass the start and end times of the view for that month
        data: {
            start: startDate,
            end: endDate
        }
    });
});
// Control the rendering process to insert arbitrary content or modify the original content
testCalendar.on('cellRender'.function (e) {
    if(! e.isHeader) {// For example, Friday and Saturday are inserted into weekends; otherwise, weekdays are inserted into weekdays
        e.extraHtml = '<div>' + (e.isoWeekday > 5 ? 'the weekend': 'Working day') + '</div>'; }});Copy the code

conclusion

These are the core steps of the calendar control for a month view.

This calendar implementation is based on an extension of a control base class. The necessary functionality is only a set of event mechanisms. See implementing a custom event mechanism

The above analysis only the key steps, and the core code, in order to facilitate the use and scalability, the actual code to deal with many problems. Source code and documentation as follows, interested can read: monthly view calendar