【Rails+JavaScript】残り文字を表示させる[コピペOK]

## この記事の対象者

- JavaScriptをRailsに組み込んでみたい方
- CRUD処理は理解されている方
- 細かい説明が欲しい方

＊最終コードは最後に書いております。説明が必要ない方は「最終的なコード」をご覧ください。

## 今回作成するもの

今回作成するものは、画像のように「xxx/xxx文字」と残り文字数を伝えるものになります

<img width="647" alt="スクリーンショット 2021-04-30 16 01 20" src="https://user-images.githubusercontent.com/64491435/116660001-5f77ab00-a9cd-11eb-8853-e28ada3790a5.png">


## 今回の想定外

- 文字の色を変更すること
- 文字数を超えて入力できること
- CSSによる見た目の修正


## 準備すること

- フォームの作成
（サンプル）

```erb:_form.html.erb
<%= form_with model: @モデル名, local: true do |f| %>
  <%= f.text_area :content %>
<% end %>
```

*この記事では、`alert`で動作確認を行う記述があります。
確認できましたら、`alert`は削除ください。

## 目次

1. JSファイルを作成
2. 文字数を取得する
3. viewに表示させる
4. 関数の作成

## 1.JSファイルを作成

まずは、JSファイルを作成します。名前は任意で構いません。

今回は`countLength`とします。

```zsh:ターミナル
touch app/javascript/packs/countLength.js
```

```js:app/javascript/packs/countLength.js
+ document.addEventListener('turbolinks:load', () => {
+   alert('動作確認'); //確認用です。
+ })
```
次に、このファイルを読み込みます

```js:application.js
import "./countLength"
```
alertが表示されればOKです！（確認後は削除してくだい。）
これで、JSファイルの準備ができました！

## 2.文字数を取得する

次に、文字数を取得しましょう。

今回取得する必要がある文字数は、以下の3つです

- 最大文字数
- 現在入力している文字数
- 残り文字数

この3つを算出する必要があります。

1つずつ確認していきましょう。

### 1.最大文字数

**「フォームで入力できる最大文字数」**を取得します。

最大文字数は、自分でフォームに設定する必要があります

`id`と`maxlength`(最大文字数)を付与しましょう

```erb:_form.html.erb
例
<%= f.text_area :content %>
↓
<%= f.text_area :content, id: "textarea", maxlength:"1000" %>
```

これで、**最大1000文字までしか打てない**ように制限がかかりました。
実際に1000文字以上打てるか確認してください。

この`maxlength: 1000`をJSファイル（`countLength.js`）で取得していきます。

以下のコードを追加してください。

```js:app/javascript/packs/countLength.js
document.addEventListener('turbolinks:load', () => {

+ let textarea = document.getElementById('textarea'); //テキストエリアを取得
+ let maxLength = textarea.maxLength; //最大の文字数を取得
+  alert(maxLength); //確認用

})
```
`maxLength`が表示されればOKです。

まずは、**最大文字数を取得する**ことができるようになりました。

### 2.現在入力している文字数

次に、現在入力している文字数を取得します。
先ほど定義した変数`textarea`を用いて、`textarea.value.length`で取得できます。

```js:app/javascript/packs/countLength.js
document.addEventListener('turbolinks:load', () => {
  let textarea = document.getElementById('textarea');
  let maxLength = textarea.maxLength;
+ let currentLength = textarea.value.length; //テキストエリアに入力されている文字数を取得
+ alert(currentLength);//確認用

})
```

`currentLength`が表示されればOKです。(formにいくつか文字を入力しておくとわかりやすいです。）

これで、現在入力している文字数を取得できました。


### 3.残り文字数を取得

最後に、**残り何文字打てるか？**を取得します。

残り文字数は、言葉にすると以下のようになります。

`残り文字数　＝ 最大文字数 - 現在入力している文字数`

これを実際にJSファイルで実行しましょう！

```js:app/javascript/packs/countLength.js
document.addEventListener('turbolinks:load', () => {
  let textarea = document.getElementById('textarea');
  let maxLength = textarea.maxLength;
  let currentLength = textarea.value.length;

+ remainingLength = maxLength - currentLength //残り文字　= 最大文字数　- 現在入力している文字数
+ alert(remainingLength); //確認用
})
```

`remainingLength`が表示されればOKです。

これで、必要な3つの要素をJSファイルで取得することができました。



## 3.viewに表示させる

最後に、取得した文字数を表示させましょう！
まずは**文字数を表示させるdivタグ**を作成し、`id="message"`を付与します。

```erb:_form.html.erb

<%= form_with model: @モデル名, local: true do |f| %>
  <%= f.text_area :content, id: "textarea", maxlength:"1000" %>

+ <div id="message"></div>

<% end %>

```

そして、先ほど取得した`最大文字数`と`残りの文字数`を`<div id="message"></div>`に反映させます。

JavaScriptで`innnerHTMLメソッド`というメソッドを使用することで、値をview側に渡すことができます。
- [element.innerHTML](https://developer.mozilla.org/ja/docs/Web/API/Element/innerHTML)

```js:app/javascript/packs/countLength.js
document.addEventListener('turbolinks:load', () => {
  let textarea = document.getElementById('textarea');
  let maxLength = textarea.maxLength;
  let currentLength = textarea.value.length;
  remainingLength = maxLength - currentLength;

+ let message = document.getElementById('message'); //メッセージを表示する要素を取得
+ message.innerHTML = `${remainingLength}/${maxLength}文字`; //残りの文字/最大文字数を出力

})
```

これで、残り文字を出力することができます

以上で完成になりま・・・せん！

<img width="647" alt="スクリーンショット 2021-04-30 16 01 20のコピー2" src="https://user-images.githubusercontent.com/64491435/116662241-7b308080-a9d0-11eb-9198-1a8c3b5bbf12.png">


これでは、ページが表示された時の情報が入るため、後から入力しても数字が変わりません。
なので、関数を作成して、**入力したら文字数が反映される**仕様にします。

## 4.関数の作成

まず、どのような条件と動作になるか言語化しておきます

言語化
**「`id="textarea"`を持つテキストエリア」**に文字を入力した時、**「入力し終わった直後の残り文字を算出して、表示させる」**

という風に置き換えることができます。

それでは、**条件を満たす時に実行される関数**を作成します。

テキストエリアに文字を入力された時に動作するイベントとして、今回'keyup'というイベントを使用します。
- [Document: keyup イベント](https://developer.mozilla.org/ja/docs/Web/API/Document/keyup_event)

```js:app/javascript/packs/countLength.js
   //テキストエリアに文字をkeyupしたときに動作する関数を作成
    textarea.addEventListener('keyup', function(){
    　　//ここに動作を書いていきます。  
    })  
```

```js:app/javascript/packs/countLength.js
document.addEventListener('turbolinks:load', () => {
  let textarea = document.getElementById('textarea');
  let maxLength = textarea.maxLength;
  let currentLength = textarea.value.length;
  remainingLength = maxLength - currentLength;
  let message = document.getElementById('message');
  message.innerHTML = `${remainingLength}/${maxLength}文字`;

  //テキストエリアでkeyupイベントが発生した時に動作する関数を作成。  
+ textarea.addEventListener('keyup', function(){
+   alert('動作確認');
+ }) 

})
```


キーUPするたびにアラートが表示されればOKです！
これで、関数が動作する条件を設定することができました。


最後に中身を作成します

```js:app/javascript/packs/countLength.js
document.addEventListener('turbolinks:load', () => {
  let textarea = document.getElementById('textarea');
  let maxLength = textarea.maxLength;
  let currentLength = textarea.value.length;
  remainingLength = maxLength - currentLength;
  let message = document.getElementById('message');
  message.innerHTML = `${remainingLength}/${maxLength}文字`;

  textarea.addEventListener('keyup', function(){

    //現在の文字数を再取得する
+   let currentLength = textarea.value.length
    //現在の文字数を再出力する
+   message.innerHTML = `${maxLength - currentLength }/${maxLength}文字`
   }) 
})
```


これで、入力（keyup）があるたびに、**残り文字数を表示する関数**を実装できました！
<img width="647" alt="スクリーンショット 2021-04-30 16 01 20" src="https://user-images.githubusercontent.com/64491435/116662246-7c61ad80-a9d0-11eb-969b-0d8d1a1b2c16.png">

動作確認してみてください！

## 最終的なコード

```erb:_form.html.erb
<%= form_with model: @モデル名, local: true do |f| %>
  <%= f.text_area :content, id: "textarea", maxlength:"1000" %>
  <div id="message"></div>
<% end %>
```
```js:app/javascript/packs/countLength.js
document.addEventListener('turbolinks:load', () => {
  if (document.getElementById('textarea') !==  null) { 
    let textarea = document.getElementById('textarea');
    let maxLength = textarea.maxLength;
    let currentLength = textarea.value.length;
-   remainingLength = maxLength - currentLength; //リファクタリング
    let message = document.getElementById('message');
+   message.innerHTML = `${maxLength - currentLength}/${maxLength}文字`; //改善

    textarea.addEventListener('keyup', function(){
      let currentLength = textarea.value.length
      message.innerHTML = `${maxLength - currentLength}/${maxLength}文字`
   }) 
  }
})
```


## 今後
今後、以下を実装してみたいと思っています。追加できるようになれば、この記事に追加しようと思います。

- `<div id = "message"></div>`をJSファイルで作成する（view側で設定する必要をなくす）
- 残り文字数が少なくなった時、色を変化させる


## エラー対応
上記に説明がありませんが、`document.getElementById('textarea')`を取得できない場合、エラーが発生することがわかりました。

そのため、`document.getElementById('textarea')`で値を取得できた場合のみ、動作するように条件分岐させております。(最終的なコードに追加してあります。)

```js:app/javascript/packs/countLength.js
document.addEventListener('turbolinks:load', () => {
+ if (document.getElementById('textarea') !==  null) { 
    let textarea = document.getElementById('textarea');
    let maxLength = textarea.maxLength;
    let currentLength = textarea.value.length;
    let message = document.getElementById('message');
    message.innerHTML = `${maxLength - currentLength}/${maxLength}文字`;

    textarea.addEventListener('keyup', function(){
      let currentLength = textarea.value.length
      message.innerHTML = `${maxLength - currentLength}/${maxLength}文字`
   }) 
+ }
})
```