[rails6] fullcalendar 攻略ガイド(予定追加、予定表示編)

# はじめに

この記事はjqueryの外部ライブラリである「fullcalendar」をrailsで使いこなそうという記事です。

前回の投稿をまだご覧になっていない方は下記の記事を一度ご覧になってからこちらの記事を読まれることをお勧めします。

### 過去の投稿

・　[[rails6] fullcalendar 攻略ガイド(導入、表示編)](https://qiita.com/rakko1124/items/faa93cb7e81e312b9ae3)

# 今回の内容の概要

今回は前回表示させたカレンダーの上に実際の予定を追加し、表示を実装してみたいと思います。

jqueryでajaxを使用するのでajaxの知識が必要になります。

### 前提知識

ajax,jbuilderの知識

### 参考

[Railsでajaxを実装する。基本の構造から、controller・jbuilderの書き方など。[Rails]](https://qiita.com/tanutanu/items/239abfe88bbbeec772bf)

#予定の追加

##実装の流れ

まず最初の流れを説明するので、迷ったらここをみてもらえれば今どこにいるかが分かってもらえるかと思います。

1. カレンダーの予定を追加したい日付の余白をクリックし、予定追加フォームが記載されたmodalを表示
2. modal内で追加操作、ボタンで確定
3. 追加成功modalを表示
4. カレンダーに予定が反映される

この流れでやっていきます。

##事前準備
### model設計

今回は```Event```という名前のモデルを作成します
カルムは予定の詳細(title)、開始時間(start)、終了時間(end)としました。

```console:console
 rails g model Event title:string start:datetime end:datetime
```

### validate

```ruby:models/event.rb
class Event < ApplictionRecord

   default_scope -> { order(start_time: :asc) }

   validate  :start_end_check

   #時間の矛盾を防ぐ
   def start_end_check
     if self.start_time.present? && self.end_time.present?
       errors.add(:end, "が開始時刻を上回っています。正しく記入してください。") if self.start > self.end 
     end
   end
end

   
```

## 1. カレンダーの予定を追加したい日付の余白をクリック、予定追加フォームが記載されたmodalを表示

### controller

controllerは```resources :events```としてnewでフォームを出していきます。

```ruby:events_controller.rb

def new
    @event = events.new
    render plain: render_to_string(partial: 'form_new', layout: false, locals: { event: @event })
    #views/eventsディレクトリのなかに_form_new.html.erb というファイルを作り
    #そのファイルの中のhtmlコードを文字として返してくれます。
    #これはjsの時のコーディングで紐づいてくるのでいったん先に進みます。
end
```

### view

次はmodal用のhtml,登録フォーム用のhtmlを追加します。

```erb:views/events/index.html.erb

<div id="calendar"></div>

<%# ここから追加 %>
<div id="modal" tabindex="-1" role="dialog">
    <div class="modal-dialog" role="document">
      <div class="modal-content">
        <div class="modal-header">
          <button type="button" class="close" data-dismiss="modal" aria-label="Close">
            <span aria-hidden="true">&times;</span>
          </button>
        </div>
        <div class="modal-body">
              
        </div>
      </div>
    </div>
  </div>
</div>  

<div id="response-modal" tabindex="0" role="dialog">
    <div class="modal-dialog" role="document">
      <div class="modal-content">
        <div class="modal-header">
          <button type="button" class="error" data-dismiss="modal" aria-label="Close">
            <span aria-hidden="true">&times;</span>
          </button>
        </div>
        <div class="response-modal-body">
        </div>
      </div>
    </div>
</div>

```

次は新規追加フォームのレイアウトです


```erb:views/events/_form_new.html.erb
<div class="row">
      <%= form_with(model: @event, url: events_path, remote: true) do |f| %>
        <%= render 'shared/error_messages', object: f.object %>
         
        <%= f.label :plan, "内容"%>
        <%= f.text_field :plan, class:'form-control mb-3', required: true %> 
        <%# form_with で　required: trueを追加すると空白登録を回避してくれます %> 

        <div class="form-inline text-left">
         <%= f.label :start,"開始時刻", {class:'control-label'}%>
         <%= f.datetime_select :start, {default: Date.today + 0.hours + 00.minutes, minute_step: 60}, class: 'form-control' %>
        </div>
         <br>
        <div class="form-inline text-left"> 
         <%= f.label :end,"終了時刻", {class:'control-label'}%>
         <%= f.datetime_select :end, {default: Date.today + 0.hours + 00.minutes, minute_step: 60}, class: 'form-control' %>
        </div>
         <%# 自動入力をするためにはdatetime_selectにしておく必要があります %>
   
        <%= f.submit "登録",   class: "btn btn-primary js-event-create-btn" %>
      <% end %>
</div>      
```

### calendarのjs

では最後にjs側で日付クリックでajaxする処理をしていきます

```javascript:app/javascript/packs/calendar/event.js
document.addEventListener('turbolinks:load', function() {
    var calendarEl = document.getElementById('calendar');
    
    var calendar = new Calendar(calendarEl, {
        plugins: [ monthGridPlugin, interactionPlugin, googleCalendarApi ],
        //~省略~//
       
        //---ここから---//
        //日付をクリックした時に発生させるイベント
        dateClick: function(info){
            //クリックした日付の情報を取得
            const year  = info.date.getFullYear();
            const month = (info.date.getMonth() + 1);
            const day   = info.date.getDate();

            //ajaxでevents/newを着火させ、htmlを受け取ります
            $.ajax({
                type: 'GET',
                url:  '/events/new',
            }).done(function (res) {
                // 成功処理
                // 受け取ったhtmlをさっき追加したmodalのbodyの中に挿入します
                $('.modal-body').html(res);

                //フォームの年、月、日を自動入力
                $('#event_start_1i').val(year);
                $('#event_start_2i').val(month);
                $('#event_start_3i').val(day);

                $('#event_end_1i').val(year);
                $('#event_end_2i').val(month);
                $('#event_end_3i').val(day);
      
                //ここのidはevents/newのurlにアクセスするとhtmlがコードとして表示されるので、
                //開始時間と終了時間のフォームを表しているところのidを確認してもらうことが確実です
                
                $('#modal').fadeIn();
                
            }).fail(function (result) {
                // 失敗処理
                alert("failed");
            });
        },

    });
});


```



## 2. modal内で追加操作、ボタンで確定

予定の追加はcreateで登録していきます

```ruby:events_controller.rb
def new
#省略
end

def create
    @event = events.new(params_event)
    if @event.save
      respond_to do |format|
        format.html { redirect_to root_path } 
        format.js  #create.js.erbを探してその中の処理を実行する
      end
    else
      respond_to do |format|
        format.js {render partial: "error" }
        #登録にエラーが起きたときはerror.js.erbを実行する
      end
    end
end

def params_event
    params.require(:event).permit(:title, :start, :finish)
end
```

## 3. 追加成功、失敗modalを表示

コメントにも記入したようにajaxでcreateを処理した後は

成功時に```create.js.erb(新規作成)```、失敗時に```error.js.erb(新規作成)```に処理が移るのでそこに成功、失敗を表すmodalを表示するようにします

```erb:views/events/create.js
$('#modal').fadeOut();
$('.response-modal-body').html('<h1>登録が完了しました！</h1>');
$('#response-modal').fadeIn();
<%# 一度フォームのmodalを閉じて、成功の文面をもう一つのmodalの中に入れてそのmodalを表示する %>
```

```erb:views/events/error.js
$('.response-modal-body').html('<h1>時間設定が間違っています！</h1>');
$('#response-modal').fadeIn();
<%# 登録フォームを残したまま、エラーの内容(今回の場合時間設定しかあり得ない仕様なので)を入れる %>
```



## 4. カレンダーに予定が反映される

ここまでできたら追加処理は完了です！
でも、、**予定が表示されてねぇ！**となっていると思います。

実はまだrails側とjquery側でデータのやりとりをする処理をしていないからです。
それを次でやって今度こそ完成です。


#予定の表示

rubyとjavascript間でデータを交換したりする際に立ちはだかるのが言語の壁です。
その言語の壁を取り払ってくれるのが```.json```というファイル形式です。

今回はこいつを使ってrails(ruby)側のデータをjquery(javascript)に送りたいと思います。

###controllerでデータの選定
まずはindexのアクション内で表示したい予定を選びましょう。
今回は無難に全てのデータを表示します。

```ruby:events_controller.rb
def index
   @events = Event.all 
   #変数名は絶対に「@events」にすること！！
end
```

###jsに追加

fullcalendarの内容を記載しているjsファイル内にデータの受信をする設定を記載します。

```javascript:app/javascript/packs/calendar/event.js
document.addEventListener('turbolinks:load', function() {
    var calendarEl = document.getElementById('calendar');
    
    var calendar = new Calendar(calendarEl, {
        plugins: [ monthGridPlugin, interactionPlugin, googleCalendarApi ],
        //~省略~//

        events: '/events.json', // <=これを追加
        // 書き方のルールとしては['/コントローラー名.json']としてください

    });

    calendar.render();
 
    //この下からも追加
    //成功、失敗modalを閉じたときに予定を再更新してくれます
    //これがないと追加しても自動更新されません
    $(".error").click(function(){
        calendar.refetchEvents();
    });
});
```

### jbuilder

次はrails側がデータを送信するファイルを追加します。
```view/events/```のなかに```index.json.jbuilder```というファイルを新規作成します。

※問題ないとは思いますが、gemのjbuilderが入っていることを一応確認しましょう。
ちなみにjbuilderは```.rb```を```.json```に変換してくれるものです。

では、その中に渡すデータの詳細を記載します

```ruby:view/events/index.json.jbuilder
   
   #さっきcontrollerに追加した@eventsから情報を配列化する
   json.array!(@events) do |event|
    json.id event.id
    json.title event.title
    json.start event.start  
    json.end event.end 
   end

  #json.〇〇は送るデータの型
  #event.〇〇はそれに対応するモデルのカルム
```

解説：```json.〇〇```側の```id,title,start,end```というのはfullcalendarで定められたeventsが持っているパラメータになります。それに該当する情報を当てはめていくといった形になります。

上記のjbuilderは必要最低限の情報だけを記載しました。
eventsが持つパラメータの詳細は下記のドキュメントをみていただければもっとたくさんの情報を送ることも可能です。

ということでこれでリロードしてjavascriptをコンパイルすれば
今度こそデータが反映されているはずです！！
お疲れ様でした！

# 次回！

次回は表示されているeventをクリックしたときに起こるアクションの解説をしたいと思います。
乞うご期待！

###参照

[公式ドキュメント(Event Object)](https://fullcalendar.io/docs/event-object)
[Railsでfullcalendarを使ってみる(Ajax通信でイベント登録)](https://qiita.com/denkikairo/items/2d46d4816006a5f68b06)