スケジュールカレンダー作成

スケジュールカレンダー作成
Image by Free-Photos from Pixabay

2021.02.08(更新日:2021.03.23)

カレンダーに「休業日」とか「スケジュール」なんぞ入れ込めるものが欲しいとの問い合わせ。
レスポンシブにするけどスマホ表示はどうする?と考えた場合、表組みだと難がある。
そこでブレイクポイント以下の場合はリスト表示がベストじゃないと勝手に思っている。
「日付はええっと…Date?なんだっけ?」今更ながら復習を兼ねて作ってみた。

この記事は1年以上経過しています。内容的に古い場合があります。

まずは html / CSS でレイアウトを決める

スケジュールの内容はDBから取得するとして、ローカル段階での作成は以下を目標とする。

  • PCでは表組み。スマホではリスト表示
  • 祝日表示
  • 翌月/次月操作
  • 複数設置可能
/* html */
<div class="cal"></div>

実際は想定した内容をモックアップとして作成した上でコードを書くんだけれど省略。
なのでhtml自体はjavascriptで吐き出しするためラッパーだけ設置。また複数設置も視野に入れ、classセレクタで。

/* CSS */
.calender_switcher,
.calender-head,
.calender-body {
  -webkit-box-sizing: border-box;
  box-sizing: border-box;
}
.calender_switcher *,
.calender-head *,
.calender-body * {
  -webkit-box-sizing: inherit;
  box-sizing: inherit;
}

.calender_switcher {
  display: table;
  table-layout: fixed;
  width: 100%;
  background: #f5f5f5;
  padding: 10px;
}
.calender_switcher li {
  display: table-cell;
  vertical-align: middle;
  text-align: center;
}
.calender_switcher li:first-child { text-align: left; }
.calender_switcher li:nth-child(2) { width: 70%; }
.calender_switcher li:last-child { text-align: right; }
.calender_switcher a {
  display: inline-block;
  width: 36px;
  height: 36px;
  background: #fff;
  text-align: center;
  cursor: pointer;
  position: relative;
}
.prevCal::before,
.nextCal::before {
  display: block;
  position: absolute;
  top: 50%;
  left: 50%;
  -webkit-transform: translate(-50%,-50%);
  transform: translate(-50%,-50%);
}
.prevCal::before { content: "<"; }
.nextCal:before { content: ">"; }

@media (max-width: 768px) {

  .calender_switcher { margin: 0 0 10px; }
  .calender-head,
  .calender-body li.sp { display: none; }
  .calender-body { border-top: 1px #ccc solid; }
  .calender-body li:not( .sp ) { 
    display: table;
    table-layout: fixed;
    width: 100%;
    border-bottom: 1px #ccc solid; 
    padding: 1em 0;
  }
  .calender-body li > * {
    display: table-cell;
    vertical-align: middle;
  }
  .calender-body li .num { width: 2em; }
  .calender-body li span { width: 2.5em; }

}

@media (min-width: 769px) {

  .calender-head,
  .calender-body {
    display: -webkit-box;
    display: -ms-flexbox;
    display: flex;
    -ms-flex-wrap: wrap;
    flex-wrap: wrap;
    border-left: 1px #ccc solid;
  }
  .calender-head {
    border-top: 1px #ccc solid;
  }
  .calender-head li,
  .calender-body li {
    width: 14.2857%;
    border-bottom: 1px #ccc solid;
    border-right: 1px #ccc solid;
    text-align: center;
    padding: 1em;
  }
  .calender-body li { min-height: 7em; }
  .calender-head li span,
  .calender-body li span { display: none; }
  .calender-body li .holiday-name { margin: 0 0 0 .5em; }
  .calender-body li em { display: block; }

}
  • .calender_switcher・・・前月、次月のボタンと現在の月表示
  • .calender-head・・・曜日
  • .calender-body・・・カレンダー本体

すべてリストタグ(ul)で実装。switcherとheadは単なる横並びで処理(スマホ時headは非表示)。
カレンダー本体のセル(li)は以下のようにした。

/* calender-body内セルのhtml抜粋 */
/* 表組みの際の空白のセル(スマホ時非表示) */
<li class="sp"></li> 
/* 通常の日のセル */
<li class="">
  <strong class="num">1</strong> /* 日付 */
  <span>Mon</span> /* スマホ用曜日 */
  <em></em> /* スケジュール */
</li> 
/* 祝日がある日のセル */
<li class="holiday">
  <strong class="num">23</strong>
  <span>Tue</span>
  <strong class="holiday-name">天皇誕生日</strong> /* 祝日名 */
  <em></em>
</li>

祝日をどうするか

調べるとGoogleのAPIから引っ張ってくるのが多いが、アカウントが必要になる。
もし受注案件だと登録させる手間が必要になるし、説明も何かと面倒なのでさらに検索。

祝日を取得出来る「holiday_jp-js

手軽に取得出来るのでこれに決定。感謝しつつ使用させていただきます。
ダウンロードして「holiday_jp.min.js」を読み込ませておく。
作者さんのサイト

javascriptでカレンダー形成

jQueryでやればもっと簡潔かも。苦手なネイティブでトライしてみた。

/* JavaScript */
var ScheduleStatecal = function( elem ){

  const elements = document.querySelectorAll( elem );

  elements.forEach( el => {
    let date = new Date();
    date.setDate( 1 );
    let current = el.querySelector('.currentYM');
    if( current !== null ){
      date = new Date( current.textContent + '/01' );
    }
    el.insertAdjacentHTML( 'beforeend', Schedulecal.setSwitcher( date ) );
    el.insertAdjacentHTML( 'beforeend', Schedulecal.weekHeader() );
    el.insertAdjacentHTML( 'beforeend', Schedulecal.creatCalender( date ) );
  });

  const prevCal = document.querySelectorAll( '.calender_switcher .prevCal' );
  const nextCal = document.querySelectorAll( '.calender_switcher .nextCal' );

  for( let i = 0; i < prevCal.length; i++ ){
    prevCal[ i ].onclick = function(){
      const cal = parentElem( prevCal[ i ] );
      const calBody = cal.querySelector('.calender-body');
      const current = cal.querySelector('.currentYM');
      const currentDate = setCurrent( current, current, -1 );
      calBody.remove();
      cal.insertAdjacentHTML( 'beforeend', Schedulecal.creatCalender( currentDate ) );
    }
  }

  for( let i = 0; i < nextCal.length; i++ ){
    nextCal[ i ].onclick = function(){
      const cal = parentElem( nextCal[ i ] );
      const calBody = cal.querySelector('.calender-body');
      const current = cal.querySelector('.currentYM');
      const currentDate = setCurrent( current, current, 1 );
      calBody.remove();
      cal.insertAdjacentHTML( 'beforeend', Schedulecal.creatCalender( currentDate ) );
    }
  }

  const parentElem = function( elem ){
    const parent = elem.closest( 'ul.calender_switcher' );
    const cal = parent.parentNode;
    return cal;
  }

  const setCurrent = function( elem, ym, num ){
    let currentDate = new Date( ym.textContent + '/01' );
    if( num > 0 ){
      currentDate.setMonth( currentDate.getMonth() + 1 );
    } else if( num < 0 ){
      currentDate.setMonth( currentDate.getMonth() - 1 );
    }
    elem.innerHTML = currentDate.getFullYear() + '/' + Schedulecal.zeroDays( currentDate.getMonth() + 1 );
    return currentDate;
  }

}

Schedulecal = {
  init: {
    week_name: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
  },

  setCalender: function( d ){
    return {
      year: d.getFullYear(),
month: d.getMonth() + 1 }; }, zeroDays: function( num ){ return ( '00' + num ).slice( -2 ); }, getHolidays: function( y, m, l ){ return holiday_jp.between( new Date( y + '-' + m + '-' + 1 ), new Date( y + '-' + m + '-' + l ) ); }, setSwitcher: function( d ){ let ym = this.setCalender( d ); let html = '<ul class="calender_switcher">\n'; html += '<li><a class="prevCal"></a></li>'; html += '<li class="currentYM">' + ym.year + '/' + this.zeroDays( ym.month ) + '</li>'; html += '<li><a class="nextCal"></a></li>'; html += '</ul>\n'; return html; }, weekHeader: function(){ let html = '<ul class="calender-head">\n'; for( let i = 0; i < this.init.week_name.length; i++ ) { html += '<li>' + this.init.week_name[ i ] + '</li>\n'; } html += '</ul>\n'; return html; }, creatCalender: function( d ){ let html = ''; let year = d.getFullYear(); let month = d.getMonth() + 1; let day = d.getDate(); let week = d.getDay(); html += '<ul class="calender-body">\n'; for( let i = 1; i <= week; i++ ) { html += '<li class="sp"></li>\n'; } d.setMonth( d.getMonth() + 1 ); d.setDate( 0 ); let lastday = d.getDate(); let lastweek = d.getDay(); let holidays = this.getHolidays( year, month, lastday ); for (var i = 1; i <= lastday; i++) { let the_date_num = year + '/' + month + '/' + i; let the_date = new Date(the_date_num); let holiday_name = ''; let addsele = ''; if( holidays ){ holidays.forEach(item => { let item_date = item['date']; if( this.sameDay( item_date, the_date ) === true ){ holiday_name = item['name']; addsele = 'holiday'; } }); } html += '<li class="' + addsele + '">'; html += '<strong class="num">' + i + '</strong>'; html += '<span>' + this.init.week_name[the_date.getDay()] + '</span>'; if( holiday_name ) { html += '<strong class="holiday-name">' + holiday_name + '</strong>'; } // input message html += '<em></em></li>\n'; } for( let i = 1; i < ( 7 - lastweek ); i++ ) { html += '<li class="sp"></li>\n'; } html += '</ul>\n'; return html; }, sameDay: function( d1, d2 ){ if( d1.getFullYear() === d2.getFullYear() && d1.getDate() === d2.getDate() && d1.getMonth() === d2.getMonth() ) { return true; } else { return false; } } }

136行目にスケジュールなりコメントなどをサーバーサイドから取得し入れる予定。
冗長なのか正解なのかもわからないが、動いたので良しとする。javascriptのオブジェクト指向は慣れないと難しい・・・

最後にセレクタの指定をして終わり。

/* htmlの最後にでも挿入 */
<script>
new ScheduleStatecal('.cal'); /* カレンダーを表示するセレクタを指定 */
</script>

今後はスケジュールなりコメントなど入れ込むなどまで書けたらと。
WordPressでプラグイン化して組み込む感じかなと思う。