RailsでFullCalendarを使って予定を表示するまで

# 始めに
RailsTutorialを終えてから自分でアプリを作ろうと思い、カレンダーを使う必要があったのでググりました。
カレンダー機能はSimpleCalendarとかFullCalendarといったのが使われてるみたい。
FullCalendarはjQueryのプラグインでその知識も多少ないと使いづらいと思いますが、ドキュメント見る限り色々できそうなのでとりあえず手を出してみました。

今回はScaffoldを使って簡単なEventモデルを作って、とりあえずここまでできれば後は自由に使えそうかなーと思うところまでをメモっておきます。(途中躓いたところの対策も)

練習なのでこんな感じの超簡単なアプリを作っていきます
![image.png](https://qiita-image-store.s3.amazonaws.com/0/304952/80d629e8-8e75-4f6c-c78d-98f9bf9d75a2.png)



## 環境
Ruby: 2.5.1
Rails: 2.5.1
OS: CentOS7

### Gemの追加
Gemfileに使うjemを追加

```
gem 'jquery-rails', '4.3.3'
gem 'fullcalendar-rails'
gem 'momentjs-rails'
```

`bundle install`を実行

### application.jsを弄る
`app/assets/javascript/application.js`内に次のコードを追加

```js:app/assets/javascript/application.js
//= require jquery
//= require moment
//= require fullcalendar
```

FullCalendarを正しく読み込むためにapplication.jsに二つの関数を作ります

```js:app/assets/javascript/application.js
$(function () {
    function eventCalendar() {
        return $('#calendar').fullCalendar({});
    };
    function clearCalendar() {
        $('#calendar').html('');
    };
});
```

一つ目の関数ではFullCalendarの設定を読み込み、二つ目の関数ではFullCalendarを削除します。
そして、RailsにはTurbolinksというツールを使ってページ遷移を高速にさせています。この機能がないと、Turbolinksを使っているときにカレンダーが複数回表示されることが起きてしまいます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/304952/6e7e4ddc-377c-c202-d29b-1297c687d42e.png)
こんな感じ

関数を追加したら、今度は呼び出すコードを（`function clearCalendar(){...};`の下に）書きます

```js:app/assets/javascript/application.js
$(document).on('turbolinks:load', function () {
    eventCalendar();
});
$(document).on('turbolinks:before-cache', clearCalendar);
```

### application.cssを弄る
`app/assets/stylesheets/application.css`内に`*= require fullcalendar`を追加

```css:app/assets/stylesheets/application.css
...
 *= require_tree .
 *= require_self
 *= require fullcalendar
 */
```
これで、あとはviewのerbファイルに`<div id="calendar"></div>`と書き込むだけでカレンダーが表示されます。(が、まだこれではイベント表示機能を付けた時に上手く表示されないときがあります。この後改修していきます。)
あとルーティングの設定も忘れずに！

###イベントを表示する
Fullcalendarにイベントの情報を表示するには、JSONファイルを使ってあげます。
イベントにはタイトル、説明、開始日、終了日が必要です。今回はscaffoldで簡単に作ります。
`rails generate scaffold Event title:string description:text start_date:datetime end_date:datetime`
`rails db:migrate`

JSONを渡すために、Railsのjbuilderというものを使っていきます。scaffoldによってjson.jbuilderファイルが自動で作られています。
`app\views\events\index.json.jbuilder`に以下のコードを追加

```ruby:app\views\events\index.json.jbuilder
json.array!(@events) do |event|
  json.extract! event, :id, :title, :description   
  json.start event.start_date   
  json.end event.end_date   
  json.url event_url(event, format: :html) 
end
```
こうすることで、`{"id":"1", "title":"タイトル", "description":"説明", "start":"日付1", "end":"日付2", "url":"some_address"}`みたいなjsonファイルが作られ?、カレンダーが読み込めるデータになります。
url項目があることで、カレンダーの予定にurlが埋め込まれ、クリックすると予定の詳細に飛ぶことができます。
[FullCarendarで使えるデータについては公式ドキュメント参照](https://fullcalendar.io/docs/event-object)

最後に、イベントを表示させるために`app/assets/javascript/application.js`に次のコードを追加します。

```js:app/assets/javascript/application.js
$('#calendar').fullCalendar({
    events: '/events.json'
});
```

最終的にapplication.jsはこうなってると思います。

```js:app/assets/javascript/application.js
...

$(function () {
    function eventCalendar() {
        return $('#calendar').fullCalendar({});
    };
    function clearCalendar() {
        $('#calendar').html('');
    };
    $(document).on('turbolinks:load', function () {
    eventCalendar();
    });
    $(document).on('turbolinks:before-cache', clearCalendar);

    $('#calendar').fullCalendar({
    events: '/events.json'
    });
});
```

ちなみにルーティングは今こうなっています。

```ruby:routes.rb
Rails.application.routes.draw do
  resources :events
  # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
  root 'events#index'
end

```

これで終わり！...ではありません。この状態でも動くは動くのですが、バグが残っています。

### バグの修正～完成！

####3/26追記
以下で行う作業をしなくても、bodyタグに`data-turbolinks="false"`属性を加えたら動作するようになりました。一応当時の対処法は残しておきます

仮に、こんな状態で予定があったとします。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/304952/f25bd150-4b01-5b44-40ef-ce571d18ec00.png)

ここからShowリンクをクリックして、ブラウザの戻るボタンを押すと、元のブラウザの画面に戻ります。このとき、TurbolinksによってjQueryが呼び出されないため、予定が表示されなくなってしまいます。
単純に`application.js`の`//= require turbolinks`を消してしまえば解決なのですが、今回はturbolinksを残したままバグを直します。

`apprication.js`内を修正

```js:app/assets/javascript/application.js
...

$(function () {
    // 画面遷移を検知
    $(document).on('turbolinks:load', function () {
        // lengthを呼び出すことで、#calendarが存在していた場合はtrueの処理がされ、無い場合はnillを返す
        if ($('#calendar').length) {
            function eventCalendar() {
                return $('#calendar').fullCalendar({
                });
            };
            function clearCalendar() {
                $('#calendar').html('');
            };

            $(document).on('turbolinks:load', function () {
                eventCalendar();
            });
            $(document).on('turbolinks:before-cache', clearCalendar);

            $('#calendar').fullCalendar({
                events: '/events.json'
            });
        }
    });
});
```

これで、ブラウザで戻るを利用しても予定が無事表示されるようになりました。

### カレンダーのオプション

これまでに作ったカレンダーが、↑にあったような写真のカレンダーと若干表示が違っているはずです。

`$('#calendar').fullCalendar({ });`の{　}内にオプションを加えることで、カレンダーのタイトルをYYYY年MM月のようにしたり、レイアウト、時間の表示を設定できます。参考として自分がざっと設定してみたものを置いておきます。

```js:application.js
$(function () {
    ...

            //events: '/events.json', 以下に追加
            $('#calendar').fullCalendar({
                events: '/events.json',
                //カレンダー上部を年月で表示させる
                titleFormat: 'YYYY年 M月',
                //曜日を日本語表示
                dayNamesShort: ['日', '月', '火', '水', '木', '金', '土'],
                //ボタンのレイアウト
                header: {
                    left: '',
                    center: 'title',
                    right: 'today prev,next'
                },
                //終了時刻がないイベントの表示間隔
                defaultTimedEventDuration: '03:00:00',
                buttonText: {
                    prev: '前',
                    next: '次',
                    prevYear: '前年',
                    nextYear: '翌年',
                    today: '今日',
                    month: '月',
                    week: '週',
                    day: '日'
                },
                //イベントの時間表示を２４時間に
                timeFormat: "HH:mm",
                //イベントの色を変える
                eventColor: '#63ceef',
                //イベントの文字色を変える
                eventTextColor: '#000000',
            });
        }
    });
});
```

設定できる内容はたくさんあるので詳しくはドキュメントを参照してください。
また、Codeopen Fullcalendar とかでググったら簡単にカスタマイズの練習ができます！！

### 参考サイト
[FullCalendarドキュメント](https://fullcalendar.io/)
https://kaede.jp/2014/08/30175019.html
https://medium.com/@a01700666/fullcalendar-in-ruby-on-rails-f98816950039
jbuilderについて↓
https://qiita.com/ryouzi/items/06cb0d4aa7b6527b3645
jQueryの要素の存在の判別方法↓
http://black-flag.net/jquery/20150324-5622.html