Rails6 カウントダウンタイマーの実装

#はじめに
ポートフォリオで実装予定のカウントダウンタイマーを実装する。
タイマーを実装するにはjQueryが必要と書いてある記事もあるが、今回はjQueryを利用せずに実装を進めていくことにする。

#作成順序
具体的な手順としては
①制限時間のカラムであるdeadlineを作成する
②タイマーを画面上に表示させるため、HTMLの記述をする
③制限時間を表示させたいので、javascriptを使って記述を行う


#カラムの作成

まずは、いつものように

```
$rails generate migration Addカラム名Toテーブル名　カラム名：データ型
```
でマイグレーションファイルを作成する。

今回の場合、投稿部分に制限時間を表示させたいのでdeadlineをカラム名として、

```
$rails generate migration AddDeadlineToMission deadline:datetime
``` 
とする。

```db/migrate/20200903084112_create_missions.rb
class CreateMissions < ActiveRecord::Migration[6.0]
  def change
    create_table :missions do |t|
      t.integer :user_id
      t.text    :content
      t.string  :penalty
      t.datetime   :deadline

      
      t.timestamps
    end
  end
end

```
上記のマイグレーションファイルの7行目に```t.datetime :deadline```を追加する。
そして、親の顔より見た

```
$ rails db:migrate
````
を実行して、データベースに保存する。

#タイマーの実装

![スクリーンショット 2020-09-22 0.33.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/650686/fc061546-8bc6-bcfa-ddfb-ca8e36fbb5ee.png)
##HTMLの記述
上記のような赤字のタイマーを設定するには、


```app/views/missions/new.html.erb
<p>
     <%= f.hidden_field :deadline, :id => "deadline.id" %>
        <input type="text" id="userYear" >年
        <input type="text" id="userMonth">月
        <input type="text" id="userDate" >日
        <input type="text" id="userHour" >時
        <input type="text" id="userMin"  >分
        <input type="text" id="userSec"  >秒
 </p>
       <p id="RealtimeCountdownArea" ></p> #ここにタイマーが表示される
```
とする。
ここで、```:id => deadline.id``` は後のjavascriptの記述において効果を発揮するため、記述している。

##javascriptの記述
今回は、```app/views/missions/new.html.erb```のscriptタグにjavascriptを記述することにする。

以下の通りである。

```app/views/missions/new.html.erb
<script>
    
function set2fig(num) {
   // 数値が1桁だったら2桁の文字列にして返す
   var ret;
   if( num < 10 ) { ret = "0" + num; }
   else { ret = num; }
   return ret;
}
function isNumOrZero(num) {
   // 数値でなかったら0にして返す
   if( isNaN(num) ) { return 0; }
   return num;
}
function showCountdown() {
   // 現在日時を数値(1970-01-01 00:00:00からのミリ秒)に変換
   var nowDate = new Date();
   var dnumNow = nowDate.getTime();
 
   // 指定日時を数値(1970-01-01 00:00:00からのミリ秒)に変換
   var inputYear  = document.getElementById("userYear").value;
   var inputMonth = document.getElementById("userMonth").value - 1;
   var inputDate  = document.getElementById("userDate").value;
   var inputHour  = document.getElementById("userHour").value;
   var inputMin   = document.getElementById("userMin").value;
   var inputSec   = document.getElementById("userSec").value;
   var targetDate = new Date( isNumOrZero(inputYear), isNumOrZero(inputMonth), isNumOrZero(inputDate), isNumOrZero(inputHour), isNumOrZero(inputMin), isNumOrZero(inputSec) );
   var dnumTarget = targetDate.getTime();
 
   // 表示を準備
   var dlYear  = targetDate.getFullYear();
   var dlMonth = targetDate.getMonth() + 1;
   var dlDate  = targetDate.getDate();
   var dlHour  = targetDate.getHours();
   var dlMin   = targetDate.getMinutes();
   var dlSec   = targetDate.getSeconds();
   var msg1 = "期限の" + dlYear + "/" + dlMonth + "/" + dlDate + " " + set2fig(dlHour) + ":" + set2fig(dlMin) + ":" + set2fig(dlSec);
 
   // 引き算して日数(ミリ秒)の差を計算
   var diff2Dates = dnumTarget - dnumNow;
   if( dnumTarget < dnumNow ) {
      // 期限が過ぎた場合は -1 を掛けて正の値に変換
      diff2Dates *= -1;
   }
 
   // 差のミリ秒を、日数・時間・分・秒に分割
   var dDays  = diff2Dates / ( 1000 * 60 * 60 * 24 );   // 日数
   diff2Dates = diff2Dates % ( 1000 * 60 * 60 * 24 );
   var dHour  = diff2Dates / ( 1000 * 60 * 60 );   // 時間
   diff2Dates = diff2Dates % ( 1000 * 60 * 60 );
   var dMin   = diff2Dates / ( 1000 * 60 );   // 分
   diff2Dates = diff2Dates % ( 1000 * 60 );
   var dSec   = diff2Dates / 1000;   // 秒
   var msg2 = Math.floor(dDays) + "日"
            + Math.floor(dHour) + "時間"
            + Math.floor(dMin) + "分"
            + Math.floor(dSec) + "秒";
 
   // 表示文字列の作成
   var msg;
   if( dnumTarget > dnumNow ) {
      // まだ期限が来ていない場合
      msg = msg1 + "までは、あと" + msg2 + "です。";
   }
   else {
      // 期限が過ぎた場合
      msg = msg1 + "は、既に" + msg2 + "前に過ぎました。";
   }
 
   // 作成した文字列を表示
   document.getElementById("RealtimeCountdownArea").innerHTML = msg;
   document.getElementById("deadline.id").value =  targetDate;　#最重要記述

}
// 1秒ごとに実行
setInterval('showCountdown()',1000);


</script>
```

ここで、先程の```:id => deadline.id``` が活きてくる。このような記述を追加することで初めて、
javascriptで処理された結果をvalue（値）として受け取り、それを画面上で表示させることができる。これでようやく、タイマーを実装することができる。

###タイマーだけを表示させたい場合
なお、日時の記入欄を表示させたくない場合も考えられる。下の写真のように期限とタイマーの残り時間だけを表示させたいという場合には、
![スクリーンショット 2020-09-22 0.49.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/650686/b9ae4336-da9b-2680-791b-76c2c5d058ec.png)

```app/views/missions/show.thml.erb
      <p>期限 <%= @mission.deadline %>
      <br>
        <input type="hidden" id="userYear" value = "<%= @mission.deadline.year %>"  > 
        <input type="hidden" id="userMonth"value = "<%= @mission.deadline.month %>" >
        <input type="hidden" id="userDate" value = "<%= @mission.deadline.day %>" >
        <input type="hidden" id="userHour" value = "<%= @mission.deadline.hour %>" >
        <input type="hidden" id="userMin"  value = "<%= @mission.deadline.min %>" >
        <input type="hidden" id="userSec"  value = "<%= @mission.deadline.sec %>" >
      </p>
      <p id="RealtimeCountdownArea" ></p>

<script>



function set2fig(num) {
   // 数値が1桁だったら2桁の文字列にして返す
   var ret;
   if( num < 10 ) { ret = "0" + num; }
   else { ret = num; }
   return ret;
}
function isNumOrZero(num) {
   // 数値でなかったら0にして返す
   if( isNaN(num) ) { return 0; }
   return num;
}
function showCountdown() {
   // 現在日時を数値(1970-01-01 00:00:00からのミリ秒)に変換
   var nowDate = new Date();
   var dnumNow = nowDate.getTime();
 
   // 指定日時を数値(1970-01-01 00:00:00からのミリ秒)に変換
   var inputYear  = document.getElementById("userYear").value;
   var inputMonth = document.getElementById("userMonth").value - 1;
   var inputDate  = document.getElementById("userDate").value;
   var inputHour  = document.getElementById("userHour").value;
   var inputMin   = document.getElementById("userMin").value;
   var inputSec   = document.getElementById("userSec").value;
   var targetDate = new Date( isNumOrZero(inputYear), isNumOrZero(inputMonth), isNumOrZero(inputDate), isNumOrZero(inputHour), isNumOrZero(inputMin), isNumOrZero(inputSec) );
   var dnumTarget = targetDate.getTime();
 
   // 表示を準備
   var dlYear  = targetDate.getFullYear();
   var dlMonth = targetDate.getMonth() + 1;
   var dlDate  = targetDate.getDate();
   var dlHour  = targetDate.getHours();
   var dlMin   = targetDate.getMinutes();
   var dlSec   = targetDate.getSeconds();
   var msg1 = "期限の" + dlYear + "/" + dlMonth + "/" + dlDate + " " + set2fig(dlHour) + ":" + set2fig(dlMin) + ":" + set2fig(dlSec);
 
   // 引き算して日数(ミリ秒)の差を計算
   var diff2Dates = dnumTarget - dnumNow;
   if( dnumTarget < dnumNow ) {
      // 期限が過ぎた場合は -1 を掛けて正の値に変換
      diff2Dates *= -1;
   }
 
   // 差のミリ秒を、日数・時間・分・秒に分割
   var dDays  = diff2Dates / ( 1000 * 60 * 60 * 24 );   // 日数
   diff2Dates = diff2Dates % ( 1000 * 60 * 60 * 24 );
   var dHour  = diff2Dates / ( 1000 * 60 * 60 );   // 時間
   diff2Dates = diff2Dates % ( 1000 * 60 * 60 );
   var dMin   = diff2Dates / ( 1000 * 60 );   // 分
   diff2Dates = diff2Dates % ( 1000 * 60 );
   var dSec   = diff2Dates / 1000;   // 秒
   var msg2 = Math.floor(dDays) + "日"
            + Math.floor(dHour) + "時間"
            + Math.floor(dMin) + "分"
            + Math.floor(dSec) + "秒";
 
   // 表示文字列の作成
   var msg;
   if( dnumTarget > dnumNow ) {
      // まだ期限が来ていない場合
      msg =  "Mission終了まで、あと" + msg2 ;
   }
   else {
      // 期限が過ぎた場合
      msg = msg1 + "は、既に" + msg2 + "前に過ぎました。";
   }
 
   // 作成した文字列を表示
   document.getElementById("RealtimeCountdownArea").innerHTML = msg;
   document.getElementById("deadline.id").value =  targetDate;

}
// 1秒ごとに実行
setInterval('showCountdown()',1000);

</script>


```
上記のように```input type = "hidden"```とすれば、記入欄が画面上に表示されないものの、```<input type = ・・・>```の６つが削除されている訳ではないためこれで正常に起動する。

　また、```<input type="hidden" id="userYear" value = "<%= @mission.deadline.year %>"  >```の
```value="<%= @mission.deadline.year %>"```の部分がなぜそのような記述となるかについて説明する。これは、deadlineはdatetimeというデータ型をとるカラムであるのだが、datetimeには年・月・日・時・分・秒といった日時の情報が保存されているからである。したがって、上記のような記述でnew.html.erbの部分で記述した期限の日時が取り出せることになる。

#まとめ
なかなか難易度が高かったが、達成感はすごかった。少しでも参考にしていただけたら幸いである。

参考記事
https://www.nishishi.com/javascript-tips/realtime-countdown-deadline.html