Tips

【Vue.js】モバイルアプリ用カレンダーを作ってみる

【Vue.js】モバイルアプリ用カレンダーを作ってみる

Cordova + Vue でハイブリッドアプリ(スマホアプリ)を作る際に、
日付選択用のカレンダーが欲しいなぁ・・と思い、作ってみました。

↓ 完成品ですが、以下のようなものをイメージして作成を始めました。

筆者はVue自体初心者なので、その勉強も兼ねて試行錯誤したメモとなります。

↓ 完成品のソースはこちら (githubリンク)
https://github.com/sie-morishita/vue_calendar

ちなみに、以下のようなカレンダーライブラリもあります。
最初はこちらを使用していましたが、自作の方が都合が良さそうだったので作成した次第です。
V-Calendar(外部リンク)
FullCalendar(外部リンク)


1. プロジェクト作成

「vue_calendar」という名前でプロジェクトを作成しました。

 vue create vue_calendar

2. コンポーネント作成

・Calendar.vueを作成(/src/components配下)
確認用に、とりあえず以下を記述しました。

<template>
  <div>
    <h1>カレンダーサンプル</h1>
  </div>
</template>

・App.vueを変更
App.vueで読み込むように設定します。
templateとscriptを以下に変更しましょう。

<template>
  <div id="app">
    <Calendar />
  </div>
</template>

<script>
import Calendar from './components/Calendar.vue'

export default {
  name: 'App',
  components: {
    Calendar
  }
}
</script>

・ブラウザで確認
読み込めているかブラウザで確認します。

npm run serve

その後、以下のアドレスにアクセスして表示を確認しましょう。
http://localhost:8080/

以下の画面が表示されていれば、ひとまず初期設定はOKです。

3.カレンダーデータを作成(2020年3月固定Ver.)

カレンダーデータを作成していきます。
本当は指定した月に対応したデータに変わるようにしたいですが、
まずは2020年3月のデータを固定で用意してみます。

<script>
export default {
    data(){
        return{
            weekdays: ['日', '月', '火', '水', '木', '金', '土'],
            year:2020,
            month:3,
            day:-1,
            today: ''
        }
    },
    computed: {
        calData: function () {
            console.log(this.year + "-" + this.month + "のデータ作成")
            var calData = []

            calData[0] = [ 1, 2, 3, 4, 5, 6, 7]
            calData[1] = [ 8, 9,10,11,12,13,14]
            calData[2] = [15,16,17,18,19,20,21]
            calData[3] = [22,23,24,25,26,27,28]
            calData[4] = [29,30,31,"","","",""]
            
            return calData
        }
    }
}
</script>

computedでcalDataというデータを用意しました。
2次元の配列になっていて、各週ごとの配列の中に日付データが入っています。
最終週の今月以降の分は、今回は空文字列としています。

今は2020年3月で固定となっていますが、
指定した年月(dataのyearとmonth)のデータがこのフォーマットで出力されるように後程変更します。

dataの中にあるweekdaysは、曜日を表示させるためのデータです。

4.カレンダー表示部分の作成

先ほど用意したweekdaysおよびcalDataを画面に表示させてみます。

・templateを変更
先ほど記述したh1タグは削除して、以下の様にテーブルを作成します。

<template>
  <div>
    <table id="cal-main">
      <!-- 曜日を表示させる(テーブルヘッダ) -->
      <thead>
        <th v-for="(dayname,index) in weekdays" :key="index">{{dayname}}</th>
      </thead>
      <!-- 日付を表示させる(テーブルボディ) -->
      <tbody>
        <tr v-for="(weekData,index) in calData" :key="index">
          <td class="cal-day" v-for="(dayNum,index) in weekData" :key="index" >
            <span>{{dayNum}}</span>
          </td>
        </tr>
      </tbody>
    </table>
  </div>
</template>

・styleを追加
cssを以下の様に当ててください。

<style scoped>
/*---------------------------------------
カレンダーのcss
---------------------------------------*/
#cal-main {
  font-size:14px;
  line-height:20px;
  table-layout: fixed;
  width: 100%;
  margin-bottom: 1rem;
  color: #212529;
  border-bottom: 1px solid #ddd;
  border-collapse: collapse;
}
#cal-main th {
  padding: 0;
  text-align: center;
  vertical-align: middle;
  font-weight: normal;
  color: #999;
}
#cal-main td {
  padding: 8px;
  text-align: center;
  vertical-align: middle;
  border-top: 1px solid #ddd;
}
.cal-today {
  background-color: #fcf8e3;
}
.cal-day.active {
  background-color: #ffc9d7;
}
</style>

・App.vueのstyleを変更
ついでに、App.vueのcssを以下の様に変更してください。
bodyのmarginを0にするのと、#appのmargin-topを削除します。

<style>
body {
  margin: 0;
}
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  /* margin-top: 60px; */
}
</style>

・表示確認
改めてブラウザで確認してみましょう。
開発者ツールでモバイルの表示に切り替えると、以下の様になります。

5. カレンダーヘッダを作成

・ヘッダのhtmlを追加
カレンダー上部のヘッダ部分を作成し、「2020年3月」のように表示させます。
先ほどのテーブルの上部に、以下の様にヘッダ部分を追加してください。

<template>
  <div>
    <!-- カレンダーヘッダ -->
    <div id="cal-header">
      <span class="header-arrow" v-on:touchstart="setLastMonth"><</span>
      <span class="selected-month">{{year}}年{{month}}月</span>
      <span class="header-arrow" v-on:touchstart="setNextMonth">></span>
    </div>

    <!-- カレンダーメイン -->
    <table id="cal-main">
        ~ 省略 ~
    </table>
  </div>
</template>

・ヘッダのcssを追加
併せて、以下の通りcssを追加してください。

/*---------------------------------------
ヘッダのcss
---------------------------------------*/
#cal-header {
  font-size: 24px;
  padding: 0;
  text-align: center;
  margin-bottom: 10px; 
  background-color: darkorange;
  border-bottom: 1px solid #ddd;
  display:flex;
  justify-content: space-between;
}
#cal-header span{
  padding:15px 20px;
  color: white;
  display: inline-block;
}

・月変更のメソッド
ヘッダ部分に記述した、「setLastMonth」と「setNextMonth」はクリックしたときに実行されるメソッドです。
それぞれ前の月、次の月に表示が変わるようにします。

<script>
export default {
    data(){
        ~ 省略 ~
    },
    methods: {
        /**
         * 先月のカレンダーを取得
         */
        setLastMonth: function () {
            if (this.month === 1) {
                this.year--
                this.month = 12
            } else {
                this.month--
            }
            this.day = -1
        },
        /**
         * 翌月のカレンダーを取得
         */
        setNextMonth: function () {
            if (this.month === 12) {
                this.year++
                this.month = 1
            } else {
                this.month++
            }
            this.day = -1
        }
    },
    computed: {
        ~ 省略 ~
    }
}
</script>

・表示確認
ブラウザで確認すると以下の様になります。
「<」や「>」をクリックして、月の表示が変わることを確認してください。
(カレンダーの中身は変わりません。)

6. カレンダーデータを作成(完成Ver.)

手順3. のカレンダーデータが、選択した月に合わせて変わるよう変更します。

・calDataの処理を変更
以下の様に作成しましたが、手順3.のフォーマットで生成されていれば何でもOKです。

    computed: {
        calData: function () {
            console.log(this.year + "-" + this.month + "のデータ作成")
            var calData = []

            // 初日の曜日を取得
            var firstWeekDay = new Date(this.year, this.month - 1, 1).getDay()

            // 月の日数
            var lastDay = new Date(this.year, this.month, 0).getDate()

            // 日数カウント用
            var dayNum = 1

            // 週ごとのデータを作成して、calDateにpush
            while (dayNum <= lastDay) {
                var weekData = []
                
                // 日曜~土曜の日付データを配列で作成
                for (var i = 0; i <= 6; i++) {
                    if (calData.length === 0 && i < firstWeekDay) {
                        // 初週の1日以前の曜日は空文字
                        weekData[i] = ''
                    } else if (lastDay < dayNum) {
                        // 最終日以降の曜日は空文字
                        weekData[i] = ''
                    } else {
                        // 通常の日付入力
                        weekData[i] = dayNum
                        dayNum++
                    }
                }
                calData.push(weekData)
            }
            return calData
        }
    }

・ブラウザで確認
再度ブラウザで確認して、月の変更に合わせてカレンダー表示が変わっていればOKです。

7. 日付クリック時の処理

日付をクリックした際に、選択中のセルの背景が変わるようにします。

・templateを修正
templateの、tdタグに以下の二つを追加しています。
①「v-on:touchstart=”dateClick(dayNum)」
⇒ dayNumを、dataのdayに設定する。
②「:class=”{‘cal-today’: isToday(dayNum), active: day === dayNum}”」
⇒ 今日の日付なら「cal-today」クラス、選択中なら「active」クラスを付与。
③「spanをif-elseで分岐させる」
⇒ 今日の場合は「今日」、それ以外は日付を表示します。

<template>
  ~ 省略 ~
  <td class="cal-day" v-for="(dayNum,index) in weekData" :key="index" 
    v-on:touchstart="dateClick(dayNum)"
    :class="{'cal-today': isToday(dayNum), active: day === dayNum}"
  >
    <span v-if="isToday(dayNum)">今日</span>
    <span v-else>{{dayNum}}</span>
  </td>
  ~ 省略 ~
</template>

・dateClickメソッドをisTodayメソッドを追加
以下の様に実装しましょう。

    methods: {
        /**
         * カレンダー日付クリック時の処理
         */
        dateClick: function (dayNum) {
            if (dayNum !== '') {
                this.day = dayNum
            }
        },
        /**
         * 今日かどうかの判定
         * 年、月は現在選択しているページ
         * 日は引数
         */
        isToday: function (day) {
            var date = this.year + "-" + this.month.toFixed(2) + "-" + day
            if(this.today === date){
                return true
            }
            return false
        },
        ~ 省略 ~
    },

・mountedを追加
最後に、moutedの処理として以下を追加しておきましょう
・yearとmonthに今月の年、月を設定する。
・todayに今日の日付を設定する。(yyyy-mm-ddの形式で)

<script>
export default {
    data(){
    ~ 省略 ~
    },
    mounted(){
      var date = new Date()
      var y = date.getFullYear()
      var m = ('0' + (date.getMonth() + 1)).slice(-2)
      var d = ('0' + date.getDate()).slice(-2)
      
      // yearとmonthを設定
      this.year = y
      this.month = Number(m)

      // 今日の日付を設定
      this.today = y + '-' + m + '-' + d
    },
    ~ 省略 ~
}
</script>

・ブラウザで確認
今日の日付とクリックした日付がそれぞれ以下の様に表示されます。


以上、ザックリとカレンダーを作成するまでの流れでした。
vueの様々な機能を触れたので、勉強するのにちょうどよかったと思います。

※cssに関しては適当に調整しただけなので、端末によってはズレたりするかもしれません。
※細かいバグとかは有ると思いますが、サンプルなので精査していません。

HTML・CSSリファレンス 連載はこちら!

HTML・CSSリファレンス 連載目次

Recent News

Recent Tips

Tag Search