JavaScriptによるカレンダー

これは何?

JavaScriptで書いた、Webページにカレンダーを表示するプログラムです。

GitHubにも置いてあります。

https://github.com/kmaebashi/calendar

実際に動くものを貼っておきます。以下です。


――Web画面に表示する「カレンダー」というのは、「自力で作れば大した苦労もなく作れるのに、なぜかみんな外部ライブラリを使いたがる」機能の代表格かと思います。まあ、それでもこれだけ簡単に作れるものなので、検索すると「作ってみました」系の記事は結構出てきます。ただ、「作ってみました」系のページにある実装だと、HTMLを文字列で組み立ててinnerHTMLに設定していたり(好みの問題かもしれませんが、ここはDOMを組み立てるべきだと思う)、あまりうまく部品化できていないように感じられるものが多かったりしたので、新たな実装を加えることにもいくらかは価値があるでしょう。

使い方

Calendarクラスとして提供しています。

  1. .jsと.cssを取り込みます。
    <script type="text/javascript" src="./calendar.js"></script>
    <link rel="stylesheet" type="text/css" href="calendar.css">
    
  2. カレンダーを貼るための要素(たいていはdiv要素でしょう)を必要な場所に貼ります。
    <div id="calendar"></div>
    
  3. 貼り付け先の要素と、デフォルトで表示したい日付のDateオブジェクトをコンストラクタの引数にして、Calendarnewします。
    const elem = document.getElementById("calendar");
    const calendar = new Calendar(elem, new Date());
    
  4. 日をクリックしたときに何か処理をしたいのであれば、setDatePickedCallback()メソッドを使用してコールバック関数を設定します。このコールバック関数の引数は、選択した日付を示すDateオブジェクトです。
    calendar.setDatePickedCallback(clicked);
    ...
    function clicked(date) {
      alert("日付がクリックされました.." + date.toString());
    }
    
  5. 任意のタイミングで、現在選択されている日付をカレンダーから取得する場合は、getCurrentDate()メソッドを使用します。このメソッドの戻り値はDate型のオブジェクトです。
    const date = calendar.getCurrentDate();
    

実際の例については、このページの末尾に貼ってあるtest.htmlか、このページ自体のHTMLを参照してください。

ソース

calendar.js

class Calendar {
  constructor(targetElement, date) {
    this.targetElement = targetElement;
    this.date = date;
    this.render();
  }

  setDatePickedCallback(callback) {
    this.datePickedCallback = callback;
  }

  getCurrentDate() {
    return this.date;
  }

  render() {
    while (this.targetElement.firstChild ){
      this.targetElement.removeChild(this.targetElement.firstChild);
    }
    const firstYoubi = Calendar.#getFirstYoubi(this.date);
    let nth = 0; // 月の中の何日目かを示す
    let lastNth = Calendar.#getLastNth(this.date);
    let endFlag = false;

    const tableElem = document.createElement("table");
    const headTr = document.createElement("tr");

    const leftArrowTd = document.createElement("td");
    leftArrowTd.innerText = "≪";
    leftArrowTd.classList.add("calendar-left-arrow");
    headTr.appendChild(leftArrowTd);

    const monthTd = document.createElement("td");
    monthTd.colSpan = 5;
    monthTd.innerText = this.date.getFullYear() + "年" + (this.date.getMonth() + 1) + "月";
    monthTd.classList.add("calendar-header-month");
    headTr.appendChild(monthTd);

    const rightArrowTd = document.createElement("td");
    rightArrowTd.innerText = "≫";
    rightArrowTd.classList.add("calendar-right-arrow");
    headTr.appendChild(rightArrowTd);
    tableElem.appendChild(headTr);

    leftArrowTd.onclick = this.leftArrowClicked.bind(this);
    rightArrowTd.onclick = this.rightArrowClicked.bind(this);

    for (;;) {
      const trElem = document.createElement("tr");
      tableElem.appendChild(trElem);
      for (let youbi = 0; youbi < 7; youbi++) {
        const tdElem = document.createElement("td");
        trElem.appendChild(tdElem);

        if (nth == 0 && youbi < firstYoubi) {
          ;
        } else if (nth <= lastNth) {
          if (nth == 0 && youbi == firstYoubi) {
            nth = 1;
          }
          tdElem.innerText = "" + nth;
          tdElem.setAttribute("data-date", nth);
          tdElem.classList.add("calendar-date");
          if (youbi == 0) {
            tdElem.classList.add("calendar-sunday");
          }
          if (youbi == 6) {
            tdElem.classList.add("calendar-saturday");
          }
          if (nth == this.date.getDate()) {
            tdElem.classList.add("calendar-target-date");
          }
          tdElem.onclick = this.dateClicked.bind(this);
          nth++;
          if (nth > lastNth) {
            endFlag = true;
          }
        } else {
          ;
        }
      }
      if (endFlag) {
        break;
      }
    }
    this.targetElement.appendChild(tableElem);
  }

  leftArrowClicked() {
    let newYear = this.date.getFullYear();
    let newMonth;
    let newDate;

    if (this.date.getMonth() == 0) {
      newMonth = 11;
      newYear--;
    } else {
      newMonth = this.date.getMonth() - 1;
    }
    newDate = Calendar.#fixLastDate(newYear, newMonth, this.date.getDate());
    this.date = new Date(newYear, newMonth, newDate);
    this.render();
  }

  rightArrowClicked() {
    let newYear = this.date.getFullYear();
    let newMonth;
    let newDate;

    if (this.date.getMonth() == 11) {
      newMonth = 0;
      newYear++;
    } else {
      newMonth = this.date.getMonth() + 1;
    }
    newDate = Calendar.#fixLastDate(newYear, newMonth, this.date.getDate());
    this.date = new Date(newYear, newMonth, newDate);
    this.render();
  }

  dateClicked(e) {
    const date = e.target.dataset.date;
    this.date.setDate(parseInt(date));
    this.render();
    if (this.datePickedCallback !== undefined && this.datePickedCallback !== null) {
      this.datePickedCallback(this.date);
    }
  }

  static #getLastNth(date) {
    const date2 = new Date(date.getTime());
    date2.setMonth(date.getMonth() + 1, 0);
    return date2.getDate();
  }

  static #getFirstYoubi(date) {
    const date2 = new Date(date.getFullYear(), date.getMonth(), date.getDate());
    date2.setDate(1);
    return date2.getDay();
  }

  static #fixLastDate(newYear, newMonth, oldDate) {
    const tempDate = new Date(newYear, newMonth, 1);
    const lastNth = Calendar.#getLastNth(tempDate);
    let newDate;

    if (oldDate > lastNth) {
      newDate = lastNth;
    } else {
      newDate = oldDate;
    }

    return newDate;
  }
}

解説……というほどのものでもありませんが。

デザインを規定しているCSSは以下。見た目を変えたければここをいじってください。

calendar.css

td.calendar-left-arrow {
  cursor: pointer;
  text-align: center;
}

td.calendar-header-month {
  text-align: center;
}

td.calendar-right-arrow {
  cursor: pointer;
  text-align: center;
}

td.calendar-date {
  cursor: pointer;
  text-align: center;
}

td.calendar-saturday {
  background-color: #ccccff;
}
td.calendar-sunday {
  background-color: #ffcccc;
}


td.calendar-target-date {
  background-color: #ffffcc;
}

JavaScript側を見てもわかるとおり、このカレンダーの「≪」や「YYYY年M月」や「≫」は、カレンダー本体のテーブル要素の一部です(YYYY年M月部分は、colspanで5列ぶち抜いています)。

テスト用のHTMLであるtest.htmlも一応載せておきます。

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>カレンダーのテスト</title>
<script type="text/javascript" src="./calendar.js"></script>
<link rel="stylesheet" type="text/css" href="calendar.css">
</head>
<body>
<div id="calendar"></div>
<p><button onclick="currentDate()">今の日付</button><span id="current-date"></span></p>
<script>
function clicked(date) {
  alert("日付がクリックされました.." + date.toString());
}

const elem = document.getElementById("calendar");
const calendar = new Calendar(elem, new Date());
calendar.setDatePickedCallback(clicked);

function currentDate() {
  const date = calendar.getCurrentDate();
  const span = document.getElementById("current-date");
  span.innerText = "今選ばれている日付.." + date.toString();
}
</script>
</body>
</html>

公開日: 2024/03/31


不具合等ありましたら、掲示板にご連絡願います。

ひとつ上のページに戻る