This is the first day of my participation in Gwen Challenge

I used to write the VUE version of the calendar, last night I wrote the React version. Support for defining calendar emission order, starting with the day of the week. The diagram below:

  • Rodchen-king. Github. IO /react-calen…
  • Source code: github.com/rodchen-kin…






Design (in the most commonly used month by month calendar)

In fact, we are very familiar with the calendar, all the design is from the function, this is fundamental. The function of the calendar is divided into two parts.

  • Calendar header: current year/month.
  • Calendar body: Specific date information for the current month.
  • The number of rows in the body of the calendar: Now we see a calendar that is basically 6 rows, since a month is 31 days at most, assuming that the first day of the current month is the last day of the last week of the previous month. Five lines of data show only 29 days, which is why six lines of data are displayed.


The function point

  • Calendar initial render date is the current month
  • Slide the head left and right, the calendar data needs to display the corresponding month information
  • You can start with week *, Sunday or Monday, depending on the weekly data that you call this calendar.


The core problem

How do I get the year and month of the current date

// Calender/lib/utils.ts
/** * Obtain the calendar header in the format of **** year ** month *@param {*} date* /
export const getHeaderContent = function (date: Date) {
  let _date = new Date(date);

  return dateFormat(_date, 'YYYY');
};
Copy the code


How to obtain 42 pieces of data (6*7) that need to be displayed in the current month, what are these 42 pieces of data?

The heart of the question is: What is the first day of the 42 pieces of data displayed in the current month?

The first day of the current month is the last day of the last week of the previous month. Then the first day of the 42 data items should be based on the day of the week in which the first day of the current month is.

For example, 2019-02-01 is the first day of February, which is Friday. Therefore, the first day of the current calendar is 2019−02−01− 52019-02-01-52019 −02−01−5

var date = new Date()
date.setDate(date.getDate() - date.getDay() + 1) // get the first day of the current month as 2019-01-28
Copy the code


What’s the problem here?

The above code logic is to assume that the order of the calendar is yiwei zhou the first (if your calendar is also put on Sunday in the first day of the calendar, no problem, but in China is put on the last day on Sunday), means that the realization of the front of the calendar also need to consider to place order, because the calendar is in accordance with the ordinary Monday to Sunday, or on Sunday to Monday, The calendar we get for the first day of the month is different. So the code above also depends on the order of the calendar.

The emission order here will be the first parameter of the calendar component that can be controlled by the caller. My assumption here is to match the passed value of this parameter with date.getday ().

  • 0: Sunday
  • 1: on Monday
  • .
  • 5: on Friday
  • 6: on Saturday,

So the formula above is: Date. SetDate (date, getDate () – date. GetDay () + x) the date the setDate (date, getDate () – date. GetDay () + X) date. SetDate (date, getDate () – date. GetDay () + x)

But if the x value here is greater than the first day of the current month, then you need to subtract 7 days from the current date, which is not obvious.

/** * gets the first day of the current month calendar *@param {*} date* /
export const getFirstDayOfCalendar = function (
  date: Date,
  weekLabelIndex: number.) {
  let _date = new Date(date);
  _date = new Date(
    _date.setDate(_date.getDate() - _date.getDay() + weekLabelIndex),
  );
  // If the current date is greater than the first day of the current month, subtract 7 days
  if (_date > date) {
    _date = new Date(_date.setDate(_date.getDate() - 7));
  }

  return _date;
};

Copy the code

And then we’re done. We just add 1 to the current date to get the next date.

How to set the month switch

The above design is based on today as the calculation of the initial value, how to design the initial value of left and right switch? The first reaction is to add or subtract 1 from the month of the current date. This is not possible, because if today is the 31st, then when next month is only 30, it will hit next month, and directly switch two months. Not to mention the month of February, when the number of days is not fixed. So here’s another problem.

My solution is: when the month clicks to switch, the initial calculation value is designed to be the first day of the current month.

/** * gets the first day of the next month * based on the passed argument@param {*} firstDayOfCurrentMonth* /
export const getFirstDayOfNextMonth = function (firstDayOfCurrentMonth: Date) {
  return new Date(
    firstDayOfCurrentMonth.getFullYear(),
    firstDayOfCurrentMonth.getMonth() + 1.1,); };/** * gets the date of the first day of the last month * using the passed argument as a baseline@param {*} firstDayOfCurrentMonth* /
export const getFirstDayOfPrevMonth = function (firstDayOfCurrentMonth: Date) {
  return new Date(
    firstDayOfCurrentMonth.getFullYear(),
    firstDayOfCurrentMonth.getMonth() - 1.1,); };Copy the code


Switch monthly data transfer mode (Observer mode)

As for the calendar component itself, header and body are siblings of the same parent component, so data transfer can depend on the parent component for transmission. Here I use the observer mode implementation.

/* * Subject * Creates three methods internally and maintains an ObserverList internally. * /

export class Subject {
  private _observers = new ObserverList();

  // addObserver: calls the internally maintained add method of ObserverList
  public addObserver(observer: Observer) {
    this._observers.add(observer);
  }

  // removeObserver: Calls the removeat method of the internally maintained ObserverList
  public removeObserver(observer: Observer) {
    this._observers.removeAt(this._observers.indexOf(observer, 0));
  }

  // notify: notify the observer and execute the update function. Update is a method that implements the interface and is a notification trigger method.
  public notify(context: any) {
    let observerCount = this._observers.count();
    for (let i = 0; i < observerCount; i++) {
      (<Observer>this._observers.get(i)).update(context); }}}/* * ObserverList * Maintains an array internally, and four methods are used to manipulate the array. The relevant stuff here is subject again, because ObserverList is designed to decouple subject from observers. * /
class ObserverList {
  private _observerList: Observer[] = [];

  public add(obj: Observer) {
    return this._observerList.push(obj);
  }

  public count() {
    return this._observerList.length;
  }

  public get(index: number) {
    if (index > -1 && index < this._observerList.length) {
      return this._observerList[index];
    }

    throw new Error(`_observerList ${index}Unknown to null `);
  }

  public indexOf(obj: Observer, startIndex: number) {
    let i = startIndex;

    while (i < this._observerList.length) {
      if (this._observerList[i] === obj) {
        return i;
      }
      i++;
    }

    return -1;
  }

  public removeAt(index: number) {
    this._observerList.splice(index, 1); }}export class Observer {
  public update: Function = () = > {};
}

Copy the code


CalendarBody observer registration



CalendarHeader Notification messages





File structure

├─ ├─ bass Exercises, ├─ less ├─ bass Exercises, ├─ bass Exercises, less ├─ Bass Exercises │ ├─ ├─ class ├─ class ├─ class ├─ class ├─ class ├─ class ├─ class ├─ class ├─ class ├─ class ├─ class ├─ class ├─ class ├─ class ├─ class ├─ class ├─ classCopy the code


All code files

// index.ts
import React from 'react';
import CalendarBody from './components/CalendarBody';
import CalendarHeader from './components/CalendarHeader';
import { initObserver } from './lib/utils';
import { Subject } from './lib/subject';

export default ({ weekLabelIndex = 1 }: { weekLabelIndex: number= > {})let calendarObserver: Subject = initObserver();

  return (
    <div>
      <CalendarHeader observer={calendarObserver} />
      <CalendarBody
        observer={calendarObserver}
        weekLabelIndex={weekLabelIndex}
      />
    </div>
  );
};
Copy the code
// lib/subject.ts
export class Subject {
  private _observers = new ObserverList();

  // addObserver: calls the internally maintained add method of ObserverList
  public addObserver(observer: Observer) {
    this._observers.add(observer);
  }

  // removeObserver: Calls the removeat method of the internally maintained ObserverList
  public removeObserver(observer: Observer) {
    this._observers.removeAt(this._observers.indexOf(observer, 0));
  }

  // notify: notify the observer and execute the update function. Update is a method that implements the interface and is a notification trigger method.
  public notify(context: any) {
    let observerCount = this._observers.count();
    for (let i = 0; i < observerCount; i++) {
      (<Observer>this._observers.get(i)).update(context); }}}/* * ObserverList * Maintains an array internally, and four methods are used to manipulate the array. The relevant stuff here is subject again, because ObserverList is designed to decouple subject from observers. * /
class ObserverList {
  private _observerList: Observer[] = [];

  public add(obj: Observer) {
    return this._observerList.push(obj);
  }

  public count() {
    return this._observerList.length;
  }

  public get(index: number) {
    if (index > -1 && index < this._observerList.length) {
      return this._observerList[index];
    }

    throw new Error(`_observerList ${index}Unknown to null `);
  }

  public indexOf(obj: Observer, startIndex: number) {
    let i = startIndex;

    while (i < this._observerList.length) {
      if (this._observerList[i] === obj) {
        return i;
      }
      i++;
    }

    return -1;
  }

  public removeAt(index: number) {
    this._observerList.splice(index, 1); }}export class Observer {
  public update: Function = () = > {};
}

Copy the code
// lib/utils.ts
import { Subject } from './Subject';

let transfer = function (this: any, fmt: string) {
  let o: {
    [k: string] :string | number;
  } = {
    'M+': this.getMonth() + 1./ / in
    'd+': this.getDate(), / /,
    'h+': this.getHours(), / / hour
    'm+': this.getMinutes(), / / points
    's+': this.getSeconds(), / / SEC.
    'q+': Math.floor((this.getMonth() + 3) / 3), / / quarter
    S: this.getMilliseconds(), / / ms
  };

  if (/(y+)/.test(fmt)) {
    fmt = fmt.replace(
      RegExp. $1,this.getFullYear() + ' ').substr(4 - RegExp.$1.length),
    );
  }
  for (let k in o) {
    if (new RegExp('(' + k + ') ').test(fmt)) {
      fmt = fmt.replace(
        RegExp. $1,RegExp.$1.length === 1
          ? o[k] + ' '
          : ('00' + o[k]).substr((' '+ o[k]).length), ); }}return fmt;
};

/** * for format date format *@param {*} timeSpan
 * @param {*} fmt
 * @param {*} formatDateNullValue* /
export const dateFormat = function (
  timeSpan: Date,
  fmt: string, formatDateNullValue? :string.) {
  if(! timeSpan) {if (formatDateNullValue) {
      return formatDateNullValue;
    }
    return 'no';
  }

  let date = new Date(timeSpan);

  return transfer.call(date, fmt);
};

/** * Obtain the calendar header in the format of **** year ** month *@param {*} date* /
export const getHeaderContent = function (date: Date) {
  let _date = new Date(date);

  return dateFormat(_date, 'YYYY');
};

/** * gets the first day of the current month *@param {*} date* /
export const getFirstDayOfMonth = function (date: Date) {
  let _date = new Date(date);
  _date.setDate(1);

  return _date;
};

/** * gets the first day of the current month calendar *@param {*} date* /
export const getFirstDayOfCalendar = function (
  date: Date,
  weekLabelIndex: number.) {
  let _date = new Date(date);
  _date = new Date(
    _date.setDate(_date.getDate() - _date.getDay() + weekLabelIndex),
  );
  // If the current date is greater than the first day of the current month, subtract 7 days
  if (_date > date) {
    _date = new Date(_date.setDate(_date.getDate() - 7));
  }

  return _date;
};

/** * Specifies the order in which weekLabels are passed to index *@param {*} weekIndexOfFirstWeekDay* /
export const getWeekLabelList = function (weekIndexOfFirstWeekDay: number) {
  let weekLabelArray: string[] = [
    'Sunday'.'Monday'.'on Tuesday'.'on Wednesday'.'on Thursday.'on Friday'.'on Saturday,];for (let index = 0; index < weekIndexOfFirstWeekDay; index++) {
    let weekLabel = weekLabelArray.shift() || ' ';
    weekLabelArray.push(weekLabel);
  }

  return weekLabelArray;
};

/** * starts observer mode and initializes */
export const initObserver = function () {
  let subject = new Subject();

  return subject;
};

/** * Format the date to two words, for example, '1' in the format of '01' *@param {*} dateNumber* /
export const formatDayWithTwoWords = function (dateNumber: number) {
  if (dateNumber < 10) {
    return '0' + dateNumber;
  }

  return dateNumber;
};

/** * Compares whether the current date is the date of this month to highlight the data of this month *@param {*} firstDayOfMonth
 * @param {*} date* /
export const isCurrentMonth = function (firstDayOfMonth: Date, date: Date) {
  return firstDayOfMonth.getMonth() === date.getMonth();
};

/** * Compares whether the current date is the current system date *@param {*} date* /
export const isCurrentDay = function (date: Date) {
  let _date = new Date(a);return (
    date.getFullYear() === _date.getFullYear() &&
    date.getMonth() === _date.getMonth() &&
    date.getDate() === _date.getDate()
  );
};

/** * gets the first day of the next month * based on the passed argument@param {*} firstDayOfCurrentMonth* /
export const getFirstDayOfNextMonth = function (firstDayOfCurrentMonth: Date) {
  return new Date(
    firstDayOfCurrentMonth.getFullYear(),
    firstDayOfCurrentMonth.getMonth() + 1.1,); };/** * gets the date of the first day of the last month * using the passed argument as a baseline@param {*} firstDayOfCurrentMonth* /
export const getFirstDayOfPrevMonth = function (firstDayOfCurrentMonth: Date) {
  return new Date(
    firstDayOfCurrentMonth.getFullYear(),
    firstDayOfCurrentMonth.getMonth() - 1.1,); };Copy the code
// Components/CalendarHeader.tsx
import React, { useEffect, useCallback, useState } from 'react';
import { Subject } from '.. /lib/subject';
import {
  getHeaderContent,
  getFirstDayOfMonth,
  getFirstDayOfNextMonth,
  getFirstDayOfPrevMonth,
} from '.. /lib/utils';
import './calenderHeader.less';

export default ({ observer }: { observer: Subject }) => {
  // page binding data
  const [headerContent, setHeaderContent] = useState<string>(' ');
  const [firstDayOfMonth, setFirstDayOfMonth] = useState<Date> (new Date());

  let leftArrow = '<';
  let rightArrow = '>';

  useEffect(() = > {
    setHeaderContent(getHeaderContent(new Date()));
    setFirstDayOfMonth(new Date()); } []);/** * The theme publishes information to inform the observer */
  const observerNotify = (currentFirstDayOfMonth: Date) = > {
    setHeaderContent(getHeaderContent(currentFirstDayOfMonth));
    observer.notify(currentFirstDayOfMonth);
  };

  /** * page operation */
  const goPrev = () = > {
    const preFirstDayOfMonth = getFirstDayOfPrevMonth(firstDayOfMonth);
    setFirstDayOfMonth(preFirstDayOfMonth);
    observerNotify(preFirstDayOfMonth);
  };

  const goNext = () = > {
    const nextFirstDayOfMonth = getFirstDayOfNextMonth(firstDayOfMonth);

    setFirstDayOfMonth(nextFirstDayOfMonth);
    observerNotify(nextFirstDayOfMonth);
  };

  return (
    <div className="calendar-header">
      <div className="header-center">
        <span className="prev-month" onClick={goPrev}>
          {leftArrow}
        </span>
        <span className="title">{headerContent}</span>
        <span className="next-month" onClick={goNext}>
          {rightArrow}
        </span>
      </div>
    </div>
  );
};

Copy the code
// Components/CalendarBody.tsx
import React, { useEffect, useCallback, useState } from 'react';
import { Subject } from '.. /lib/subject';
import {
  getFirstDayOfMonth,
  getFirstDayOfCalendar,
  formatDayWithTwoWords,
  isCurrentMonth,
  isCurrentDay,
  getWeekLabelList,
} from '.. /lib/utils';
import './calenderBody.less';

interface DayItem {
  date: Date;
  monthDay: number | string;
  isCurrentMonth: boolean;
  isCurrentDay: boolean;
}

export default ({
  observer,
  weekLabelIndex = 1,}, {observer: Subject; weekLabelIndex? : number; = > {})const [firstDayOfMonth, setFirstDayOfMonth] = useState(new Date());
  const [weekList, setWeekList] = useState<DayItem[][]>([]);
  const [weekLabelArray, setWeekLabelArray] = useState<string[]>([]);

  useEffect(() = > {
    // Register the observer object
    observer.addObserver({
      update: update,
    });

    // Set the first day of the current month to start data and determine whether the date is the current month
    setFirstDayOfMonth(getFirstDayOfMonth(new Date()));

    // Set weekly label data
    setWeekLabelArray(getWeekLabelList(weekLabelIndex));

    // Set the current calendar data initially
    setWeekListValue(getFirstDayOfMonth(new Date())); } []);/** * calendar method */
  // Click calendar
  const onClickDay = (dayItem: DayItem) = > {
    // this.$emit('dayClick', dayItem)
  };

  // Set the weekList value
  const setWeekListValue = (firstDayOfmonth: Date) = > {
    let newWeekList = [];
    let dayOfCalendar = getFirstDayOfCalendar(firstDayOfmonth, weekLabelIndex);

    // The number of traversals is 6 because the calendar shows that the current month has 6 rows
    for (let weekIndex = 0; weekIndex < 6; weekIndex++) {
      let weekItem = [];
      // Each week is 7 days
      for (let dayIndex = 0; dayIndex < 7; dayIndex++) {
        let dayItem: DayItem = {
          date: dayOfCalendar,
          monthDay: formatDayWithTwoWords(dayOfCalendar.getDate()),
          isCurrentMonth: isCurrentMonth(firstDayOfMonth, dayOfCalendar),
          isCurrentDay: isCurrentDay(dayOfCalendar),
        };
        weekItem.push(dayItem);

        // Add 1 to the current date, and so on to get 42 records
        dayOfCalendar.setDate(dayOfCalendar.getDate() + 1); } newWeekList.push(weekItem); setWeekList(newWeekList); }};/** * Observer mode correlation method */
  // Switch months to update body data
  const update = (content: Date) = > {
    setFirstDayOfMonth(content);
    setWeekListValue(content);
  };

  /** * tool method */
  // Saturday/Sunday logo red font
  const isShowRedColorForWeekLable = (index: number) = > {
    return (
      index + weekLabelIndex === 6 ||
      index + weekLabelIndex === 7 ||
      (index === 0 && weekLabelIndex === 0)); };return (
    <div className="calendar-body">{/ *<! -- Calendar week label -->* /}<div className="calendar-body-week-label">
        {weekLabelArray.map((item, index) => (
          <div
            className={`calendar-body-week-label-dayThe ${isShowRedColorForWeekLable(index)? 'red-font' :"'} `} >
            <span>{item}</span>
          </div>
        ))}
      </div>{/ *<! -- Calendar data, traverses the calendar array, get the data for each week --> */}
      {weekList.map((weekItem: DayItem[]) => (
        <div className="calendar-body-week">{/ *<! -- Iterate over data for each week --> */}
          {weekItem.map((dayItem: DayItem, index: number) => (
            <div
              className={`calendar-body-week-dayThe ${dayItem.isCurrentMonth ? 'calendar-body-current-month' :'${}dayItem.isCurrentDay ? 'calendar-body-current-day' :'${}isShowRedColorForWeekLable(index)? 'red-font' :"'} `}onClick={()= > onClickDay(dayItem)}
            >
              <span>{dayItem.monthDay}</span>
            </div>
          ))}
        </div>
      ))}
    </div>
  );
};

Copy the code