Demand: we often see some calendars in some travel, booking, hotel pages, these calendars have Gregorian calendar, lunar calendar, holidays, non-holidays (days off) mark, as well as product business information, such as ticket prices, tickets and so on. Now it’s time to build the wheel. First, the component should generate a basic calendar and hang some extended information on each day’s objects. It might look something like ⤵, or more complicated.



GitHub | NPM | Demo

Note: This is a UI independent component, all we need to do is how to build such a calendar object.

Design components

The presentation of a calendar is usually a table. We need to put the information of each day into the table to produce a complete calendar for a month or several months.

  1. Example Query the number of days in the current month
  2. Calculate how many days are left before the first day of the month (consider the starting point of the calendar, starting Monday or Sunday)
  3. If available, put in the days to skip
  4. Generate data for every day with data
  5. Put the data object in the month table

Example Query the number of days in a month

This function is very simple. It takes a parameter, date, and the variable number of days is February.

export function getMonthDays(date) {
    const year = date.getFullYear()
    const month = date.getMonth()
    constleapYear = ! (year %4) && ((year % 100) | |! (year %400))
    const days = [31, leapYear ? 29 : 28.31.30.31.30.31.31.30.31.30.31]
    return days[month]
}
Copy the code

A leap in four years, a hundred years not leap, four hundred years to leap.

Please note that the date of the parameter here is likely to be new date (), for example, the date is 2018-08-31. When the user gets the normal data of this month, then directly set the month. If the user turns up to June, there will be problems, because there are no 31 days in June. In this case, the date will be July 1, which is different from the expected date. So be sure to set the date to 1 after the date operation.

const days = utils.getMonthDays(date)
date.setDate(1)
Copy the code

Create messages for the day

The daily information should contain some common attributes from the date object and some extended fields from the user. Here I’m using a class to construct this object.

class Day {
    constructor(dateObj, extension = {}) {
        this.year = dateObj.getFullYear()
        this.month = dateObj.getMonth()
        this.date = dateObj.getDate()
        this.day = dateObj.getDay()
        this.dateText = utils.getChinaStandard(dateObj)
        this.past = this.toDay.getTime() > dateObj.getTime()
        this.today = utils.getChinaStandard(new Date()) = = =this.dateText
        this.timestamp = dateObj.getTime()
        const _self = this
        Object.keys(extension).forEach(key= > {
            _self[key] = extension[key]
        })
    }

    get toDay() {
        const date = new Date()
        date.setHours(0)
        date.setMinutes(0)
        date.setSeconds(0)
        date.setMilliseconds(0)
        return date
    }
}
Copy the code

Note:

  • The past attribute may appear in a ticket scenario, and no ticket can be purchased for a past time
  • Today is today
  • DateText generates a date in string format
  • The toDay() function is used to flatten hours and below

Generate a table for one month

This object is in the form of a two-dimensional array, and for several weeks of the month, each week’s array holds the objects of the day

The monthly method is designed as a static method to facilitate the caller to quickly build a data object for a particular month through kalendar.monthly (),

parameter

  • Date Of the monthdateobject
  • Mount needs to extend that information
  • WeekStart Specifies the start day of a week. The default is Sunday
  • UnifiedMount all dates require extended information
static monthly({date, mount = {}, weekStart = 0, unifiedMount = {}}) {
        const monthTable = []
        const days = utils.getMonthDays(date)
        date.setDate(1)
        const day = date.getDay()
        let skip = 0
        if(day ! == weekStart) skip = day - weekStartfor (let i = 0; i < days + skip; i += 7) {
            const week = []
            let num = 7
            if(! i && skip) {for (let k = 0; k < skip; k++) week.push(null)
                num -= skip
            }
            for (let j = 0; j < num; j++) {
                const dateText = utils.getChinaStandard(date)
                week.push(new Day(date, Object.assign({}, unifiedMount, mount[dateText])))
                if (date.getDate() >= days) break
                date.setDate(date.getDate() + 1)
            }
            monthTable.push(week)
        }
        return monthTable
    }
Copy the code

The ticket price, or some default data, should be added to all unifiedMount dates

    var unifiedMount = {
        total: 1000.price: 550.bg: 'info'
    }
Copy the code

Mount is date specific data, such as a day that is sold out and should be disabled for that day

    var mount = {
        '2018-08-30': {
            total: 0.price: 550
        },
        '2018-08-31': {
            total: 10.price: 750}},Copy the code

Build a long-term calendar object

In some mobile scenarios, it is often a one-time access for three months or longer, sliding display effect.

I returned the _create() function in the constructor of my Kalendar class. The _create() function generates information for many months.

class Kalendar {
    constructor({start, end, unifiedMount = {}, mount = {}, weekStart = 0{} = {})this.startTime = start
        this.endTime = end
        this.unifiedMount = unifiedMount
        this.mount = mount
        this.weekStart = weekStart
        return this._create()
    }
    
    ......
    
    _create() {
        const {mount, weekStart, unifiedMount} = this
        const table = {}
        let count = (this.endDate.getFullYear() * 12 + this.endDate.getMonth() + 1)
            - (this.startDate.getFullYear() * 12 + this.startDate.getMonth() + 1)
        if (count < 0) return null
        let idx = 0
        do {
            const date = this.startDate
            date.setMonth(date.getMonth() + idx)
            const monthTable = Kalendar.monthly({date, mount, weekStart, unifiedMount})
            table[utils.getChinaStandard(date, true)] = monthTable
            count--
            idx++
        } while (count > 0)
        return table
    }
    
    static monthly({date, mount = {}, weekStart = 0, unifiedMount = {}}) { ...... }}Copy the code

Instead of generating a month of data, the constructor needs to set the start and end values. After calculating the month difference, the constructor calls the monthly function in a loop to avoid the startTime and endTime being the same month. While produces at least a month’s worth of data.

Package and release

For smaller volume, I used Rollup to build the component code and used the MIT protocol NPM Publish.

GitHub | NPM | Demo