【Vue.js】さ迷うハロオタがお誕生日カレンダーを作った

#はじめに

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/164405/c6b384f0-414d-2a6d-4551-472e37816948.jpeg" width="320px">
_アンジュルム元メンバー・室田瑞希_

私はハロープロジェクト所属(筆頭はモーニング娘。’20ですね)のアンジュルムというグループとメンバーの室田瑞希さんという方のファンで、ここ４年くらい足繁くライブやイベントに参加していました。
昨年の誕生日イベントや地元・千葉公演など良き思い出がたくさんあります。
[アンジュルム｜ハロー！プロジェクト オフィシャルサイト](http://www.helloproject.com/angerme/)
#悲劇は起きた
[アンジュルム室田瑞希が卒業を発表](https://natalie.mu/music/news/364220)

２０２０年1月、突然の卒業発表。
当時の職場でのシビアな話合いの直後に入ってきたニュースだったので
余計にダメージを喰らいました。
その後の卒業イベントのチケットのプレミア化、果てはコロナによる無観客での実施など、最後まで激動でしたね。私は今だに辛くて映像も見ておりません。
[アンジュルム・室田瑞希、「ひなフェス」無観客開催で卒業「約5年間、本当にありがとうございました！」](https://www.crank-in.net/news/75086/1)

#企画
私はアンジュルムの単推しの人間なので他のグループの方のお顔と名前以外はほとんど知りません。
新しい推しを探すためにもまずは所属メンバーのことを知ることだと思いました。
新たな推しが見つかるといいな！
今回はプロフィールには必ず載っている誕生日を一覧できるハロメン誕生日カレンダーの制作を試みました。

#APIを探そう
私は最近APIを叩くことに喜びを感じているので隙あらば今回も組み込んでやろうと鼻息荒くAPIを探しました。

```
https://api.helloproject.com/v2/artists
```

このようなURLで所属メンバーのプロフィールを取得するAPIがあるか探しました。

<strong>【結果】ありませんでした。</strong>

#自分で作ろう
![スクリーンショット 2020-08-05 17.42.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/164405/3b6e2fcb-77c3-d264-1293-d04e03b0e76a.png)

無ければ作れば良いと思いました。
SpreadSheetをデータベースの代わりにしてVue.jsでデータを受けとります。
現役でグループに所属しているメンバーの用情報を公式ホームページからピックアップしていきます。
[ハロー！プロジェクト オフィシャルサイト](http://www.helloproject.com/)
データ数は５3人分なので地道に作業をしています。スクレイピングでちゃっとやりたい気持ちもありますが（できるかは別として）この作業で各メンバーのプロフィールを読んでいる時が一番楽しかったことを告白します。

#構成
![Untitled Diagram (1).jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/164405/e227db6e-51e0-dcec-177c-57f8718124fe.jpeg)

フロントエンドのアプリケーションフレームワークにVue.jsを使用してカレンダーライブラリはFullCalendarを使用して作成しました。
Vue.jsで作成されたアプリケーションからリクエストを飛ばしてSpreadSheetのデータを取得、カレンダーに反映させます。
データの取得はこちらの記事が大変参考になりました。

[Google Spreadsheet のデータを JSON 形式で取得する Web API をサクッと作る](https://qiita.com/takatama/items/7aa1097aac453fff1d53)
## 使用ライブラリなど
[FullCalendar](https://fullcalendar.io/)
設定するためのコードを書くだけでカレンダーをブラウザに表示させることができます。
オプションも豊富です。
先輩の執筆された記事ですね、大変お世話になりました。
[Vue.js×FullCallendarでWEBカレンダー作成（花粉カレンダー作成①）](https://qiita.com/doikatsuyuki/items/344b61459977a7ee2eff)

[moment.js](https://momentjs.com/)
日付処理のライブラリです。
どんなプログラミング言語でも日付周りの操作は面倒なことが多いですが
moment.jsはすごく使いやすかったです。
減算加算、未来過去応用の効く使い方もできますし、かなりの好印象。

[BULMA](https://bulma.io/)
CSSフレームワークはbulmaを選択しました。
BootStrap以外触れたことが無かったのですが
FullCalendarにより画面はほぼ決まってしまっていたため、
ヘッダー、フッターなど一部パーツにしか使用できませんでした。
なので、いずれの機会にもう一度bulmaに挑戦したいと思います。

#制作物のデモ
こちらのURLで公開しております。
https://nostalgic-bhaskara-023640.netlify.app/
何度も確認しましたが間違いを見つけた同志の方がいらっしゃいましたら
教えてくださいね！

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">Vue.jsとFullCalendarを使ってハロメンお誕生日カレンダーを作りました。企画段階でも実装でも推し探しでもさ迷いましたが得るものも多かったです。<a href="https://twitter.com/hashtag/protoout?src=hash&amp;ref_src=twsrc%5Etfw">#protoout</a> <a href="https://t.co/aDuqv9Ht8F">pic.twitter.com/aDuqv9Ht8F</a></p>&mdash; ウーリズム (@Uh_rhythm) <a href="https://twitter.com/Uh_rhythm/status/1290960487350333443?ref_src=twsrc%5Etfw">August 5, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
UIはFullCalendarの力によってきちんとカレンダーの役割を果たしたものになっています。
![スクリーンショット 2020-08-05 19.16.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/164405/791b7f0f-1b6f-1086-b697-438ccf727b1b.png)
イベントの色はメンバーカラーが表示されるようにしました。
これはカラフルで良いと思います。
![スクリーンショット 2020-08-05 19.17.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/164405/ec15ba8e-53d6-7020-04b6-db1c382939a8.png)

イベントをマウスオーバーするとメンバーの写真が出現します。
皆さん本当に美しいです。
いつも元気をもらっております。
コードはFullCalendarの設定が主です。
ほとんど書いてないです。

```js
// ライブラリ関連の読み込みなど
const dayGridPlugin = window.FullCalendarDayGrid.default;
const FullCalendarInteraction = window.FullCalendarInteraction.default;
const VModal = window["vue-js-modal"].default
Vue.use(VModal);
// FullCalendar
Vue.component('calender', {
  template: '#calender',
  data: function() {
    return {
      name: "",
      imgUrl: "",
      id: "",
      calendarPlugins: [ 
        dayGridPlugin, 
        FullCalendarInteraction 
      ],
      events: [],
      // mouseOvernによるモーダルの呼び出し
      eventMouseEnter: function(event, jsEvent, view) {
        var title = event.event.title;
        app.name = title.substring(0,title.indexOf(" "))
        app.imgUrl = event.event.url
        app.id = event.event.id
        app.show()
      },
      header:{
        left:   'title',
        center: 'addPostButton',
        right:  'today prev,next'
      },
      buttonText: {
        today: '今日'
      },
    }
  },
  mounted: function(){
    this.getData();
  },
  // spreadSheetからデータを取得
  methods: {
      getData: async function (event) {
        let response;
        try {
          response = await axios.get(
            'https://script.google.com/macros/s/AKfycbwzkyHjVYKu53Hh-zS5PBGebOsT5b0kLNtnJWJ7X7s1bxcV3Me-/exec'
          );
          var this_year = moment().get('year');
          for (var i = 0; i < response.data.length; i++) {
            for (var j = 0; j < 5; j++) {
                var name = response.data[i].name;
                //  生まれ年を現在年に置換する
                var now = moment(response.data[i].date).set('year', this_year);
                // 5年後まで表示
                var target_date = moment(now, 'YYYY-MM-DD').add(j, 'year');
                var current_date = moment().add(j, 'years');
                var birth_date = moment(response.data[i].date);
                var age = this.calcAge(current_date, birth_date);
                var color = response.data[i].member_color;
                var url = response.data[i].image;
                var id = response.data[i].id;
                this.events.push({title: `${name} ${age}歳`, date: target_date.format('YYYY-MM-DD'), color: `${color}`, url: url, id: id });
             }
          }
        } catch (error) {
          console.error(error);
        }
      },
      // 年齢の計算
      calcAge: function(today, dateOfBirth) {  
        let baseAge = today.year() - dateOfBirth.year();
        let birthday = moment(
            new Date(
                today.year() + "-" + (dateOfBirth.month() + 1) + "-" + dateOfBirth.date()
            )
        );
        return baseAge;
      } 
  }
});  

let app = new Vue({
  el: '#app',
  data: function () {
    return {
        name: "",
        imgUrl: "",
        id: ""
    }
  },
  methods: {
    show : function() {
      this.$modal.show('my_modal');
    },
    hide : function () {
      this.$modal.hide('my_modal');
    },
  }
});
```


#結論
企画にも実装にも新たな推しメン探しにも、全てにさ迷った果てに、
出来上がったものに対しての一番の感想は、
<strong>これはGoogleCalendarで十分だな、ということです。</strong>
もう少し工夫を施さないと差別化は難しいなと感じます。
制作過程はとても楽しかったですが制作物でその楽しさをなかなか表現できませんでした。

#新たな推しは見つからなかった、だが・・・
見出しの通りですが新たな推しは見つかりませんでした。
５０人以上のプロフィールと写真を何度もチェックしたけど
やはり皆さん素敵でした。選べません。
ライブも再開し始めたし早く生で見たいですね！

さて、今回はWEBアプリを作成してみましたが企画段階ですごく時間がかかりました。
自分の思いついたものって大体世の中に存在することの方が多い。
obnizのように目新しいものを使えば組み合わせ次第で目を引くものは作れそうですがWEBだけで表現するのはとても難しいと感じました。

ただ今回のようにAPIの代わりとしてSpreadSheetを作ったことで解決策の新しいパターンができたこと、
制作後の結論として既存のアプリとの差別化がうまくいかなかったことなどが早いうちから分かるなど、学ぶことが多かったです。

久しぶりのWEBアプリの作成でしたが過去に何度か試みた時は環境構築を終えた時点で力尽きることばかりでした。
仮想環境を用意し、サーバーやフレームワーク、DBをインストールするなどなかなか面倒な工程をふまなければなりません。
今回の環境構築的な作業は全てCDNを使用、DBもスプレッドシートを用意するだけという、とても簡単なものでした。
限りある時間を企画→制作→発表に割き、制作物のブラッシュアップに取り組む、このサイクルはとても合理的で良い手法だと思います。
まだまだ頑張ります。