【Laravel Sail × Editor.js】リッチテキストエディタのデータをAjaxで保存する

現在エンジニアを目指して勉強中です。

ポートフォリオ作品にEditor.jsを導入したので、実装の内容をまとめました。
[導入~出力](https://qiita.com/Oidon/items/4b25c81a9574d8a8ee36) / データ整形~保存 / [画面表示・再編集](https://qiita.com/Oidon/items/3dbb3f6840fd65bda701) / 画像プラグイン /
の4部作となっています。（現在進行中）

今回はデータ整形~保存までです。

## 環境

・macOS Monterey 12.6
・PHP 8.1.13
・Laravel 9.3.12
・MySQL 8.0.31
・Docker 20.10.2
・Laravel Sail

## おさらいと今回の概要

[前回の記事](https://qiita.com/Oidon/items/4b25c81a9574d8a8ee36)で、エディタに入力したデータが
JSON形式で出力されるところまで確認しました
```js
// 出力されるデータ
// 出力されるJSONオブジェクトは以下のように3つのプロパティを含んでいる
{time: 1672207762160, blocks: Array(2), version: '2.26.4'}

// blocksのなかにテキスト情報が含まれる
blocks:[
    {
     id  : "J4ZvhsriKq",
     type:"paragraph",
     data:{
            text:"ああああああ"
           }
    },
    {
     id  :"El6S5s-QoM",
     type:"list",
     data:{
            style:"ordered",
            items:[
                    "ほほおほほほおほほほほh<br>ほほほほほほほほほほほ",
                    "ほほおほほほほほほほほほ"
                   ]
           }
    }
]
```
今回はこの`Editor.js`のデータを`input`タグの情報と一緒に送信し、
保存するまでを実装していきます。

## データの整形
### 不要なプロパティの削除
タイムスタンプとバージョン情報は不要なので、落とします。
jsでは、オブジェクトから複数のプロパティを一括削除できる方法がないらしいので
[こちらの記事](https://www.web-dev-qa-db-ja.com/ja/javascript/javascript%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%81%AE%E8%A4%87%E6%95%B0%E3%81%AE%E3%82%AD%E3%83%BC%E3%82%92%E5%89%8A%E9%99%A4%E3%81%99%E3%82%8B%E3%81%9F%E3%82%81%E3%81%AE%E7%B5%84%E3%81%BF%E8%BE%BC%E3%81%BF%E9%96%A2%E6%95%B0%EF%BC%9F/1055732322/
)のやり方で実装。


```editor.js
editor.save()
  .then((outputData) => {
    console.log('Article data: ', outputData);

    // ここで不要なプロパティを削除
    ['time', 'version'].forEach(e => delete outputData[e]);
    console.log('Processed data: ', outputData);
  })
  .catch((error) => {console.log('Saving failed: ', error)});

// 出力
Article data:  {time: 1672212255124, blocks: Array(2), version: '2.26.4'}
Processed data:  {blocks: Array(2)}
```

### 文字列へ変換
MySQLではJSON型のテーブルも作れるようですが、
今回はtext型でデータを保存しておきます。

`JSON.stringify`メソッドで文字列型へ変換。
```editor.js
let text = '';

editor.save()
  .then((outputData) => {
      ['time', 'version'].forEach(e => delete outputData[e]);
      text = JSON.stringify(outputData);
  })
```
これで、`Editor.js`の`blocks`の内容が
JSON形式の文字列として`text`に格納されました。

### inputタグのデータ取得
今回保存は`input`タグの入力データも一緒に保存するので、
そのデータの取得も行います。

```editor.html
<!-- Laravelプロジェクトなので実際はeditor.blade.phpファイル -->
<input type="text" name="input1" class="js-get-input1">
<input type="text" name="input2" class="js-get-input2">

<!-- Editor.jsのデータ -->
<div id="editor" class="form_editor"></div>

<!-- ボタンを押すとイベント発火 -->
<button type="button" class="js-save-data">保存する</button>
```

```editor.js
$(function() {

  // 保存するデータ
  $('.js-save-data').on('click', function() {
    let input1 = $('.js-get-input1').val();
    let input2 = $('.js-get-input2').val();
    let text = '';

    editor.save()
      .then((outputData) => {
          ['time', 'version'].forEach(e => delete outputData[e]);
          text = JSON.stringify(outputData);
      })
      .catch((error) => {console.log('Saving failed: ', error)});
  });
});
```
## データベースへ送信
### db構造
以下のようなイメージです
| 名前 | タイプ |
|:-:|:-:|
| id | bigint |
| user_id | bigint |
| data1 | varchar(255) |
| data2 | int(11) |
| text | text |

### ajax通信

今回はajaxでデータを送信します
```editor.js
$(function() {
  $('.js-save-note').on('click', function() {
    let input1 = $('.js-get-input1').val();
    let input2 = $('.js-get-input2').val();
    let text = '';

    editor.save()
      .then((outputData) => {
          ['time', 'version'].forEach(e => delete outputData[e]);
          text = JSON.stringify(outputData);
          ///////////////////////////////
          // ここからajax通信
          $.ajax({
            method: 'POST',
            url: '/hoge',
            data: {data1: input1, data2: input2, text: text}
          })
          .done(function() {
            window.location = ('/home');
          })
          .fail(function() {
             console.log('失敗');
          });
      })
      .catch((error) => {console.log('Saving failed: ', error)});
  });
});

```

### Laravel側の設定
ここは、formタグで値を保存するときと同じ。
```app/Models/Hoge.php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Hoge extends Model
{
    use HasFactory;

    protected $fillable = [
      'user_id',
      'data1',
      'data2',
      'text'
    ];

    public function user() {
      return $this->belongsTo(User::class);
    }
}
```
```app/Http/Requests/Hoge/CreateRequest.php
namespace App\Http\Requests\Note;
use Illuminate\Foundation\Http\FormRequest;

class CreateRequest extends FormRequest
{
    public function authorize()
    {
        return true;
    }

    public function rules()
    {
    // ここがバリデーション
        return [
          'data1' => 'required|integer|max:47',
          'data2' => 'required|string|max:255',
          'text' => 'required|json'
        ];
    }
}
```
```app/Http/Controllers/HogeController.php
namespace App\Http\Controllers;

use App\Http\Requests\Hoge\CreateRequest;
use App\Models\Hoge;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;

class HogeController extends Controller
{
  public function store(CreateRequest $request) {
    $hoge = new Hoge;
    $hoge->user_id = Auth::id();    // user_idを格納
    $hoge->fill($request->all());   // ajaxのデータを格納
    $hoge->save();                  // 保存
  }
}
```
### エラー
ここまでを実装する中で、以下2つのエラーが発生。
`419 (unknown status)`
`422 (Unprocessable Content)`

解決のために3つのことを確認しました
1. csrfトークン
1. モデル内の`$fillable`の設定
1. ログイン状況

#### CSRFトークン
Laravelでpost送信をしようとすると求められる。
ajax通信を使う場合は、それ用の記載が必要
```editor.js
$.ajax({
/////////////////////////
// これがajax通信用のcsrfトークンの記載
headers: {
  'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
},
/////////////////////////

method: 'POST',
url: '',
data: {text: text}
})
.done(function() {
})
.fail(function() {
});
```

#### `$fillable`の設定
`$fillable`内に`user_id`を指定し忘れていました。。。
指定したカラムのみが操作できるようになるので、
`user_id`がnullになってしまいエラーが発生していた。

*fillableやguardedについては↓

https://qiita.com/toro_ponz/items/b33c757cb7ba5bb48ed4

#### ログイン状況
これは我ながらアホすぎるミスなのですが、
ログインしていない状態で作業していたため、
`user_id`が無限に取得できない状況になっていました。

開発段階でミドルウェアの設定をしてない時などはお気をつけください。。。

## おまけ：フラッシュメッセージの追加
ここからはプラスアルファですが、
登録が完了したら、それを通知するメッセージを表示したい。

Laravelにはフラッシュメッセージ用の[`flash`メソッド](https://readouble.com/laravel/9.x/ja/session.html)が用意されているので
それを使います。

### セッションにフラッシュメッセージを持たせる

```app/Http/Controllers/HogeController.php

class HogeController extends Controller
{
  public function store(CreateRequest $request) {
    $hoge = new Hoge;
    $hoge->user_id = Auth::id();
    $hoge->fill($request->all()); 
    $hoge->save();                  

    // フラッシュメッセージを追加
    session()->flash('session_success', '保存が完了しました'); 
  }
}
```

### フラッシュメッセージ表示のhtmlを追加
```layouts/app.html
<!-- Laravelプロジェクトなので実際はapp.blade.phpファイル -->
<!-- ここまでヘッダー -->

<!-- フラッシュメッセージがある時だけ表示したいので@if -->
@if(session('session_success'))
<!-- Session Message -->
<div class="flashMsg flashMsg--success js-show-flashMsg">
  <p class="flashMsg_text js-get-flashMsg">{{ session('session_success') }}</p>
</div>
@endif

<!-- ここからコンテンツ -->
```

### cssとjsで表示
cssは割愛しますが、`active`クラスの付け外しで
`transform: translateX`を適用させて動かします
```app.js
$(function () {

  let msgWindow = $('.js-show-flashMsg');

  if ($('.js-get-flashMsg').text()) {
    msgWindow.toggleClass('active');
    setTimeout(function(){
      msgWindow.toggleClass('active');
    }, 4000);
  }
});

```

## 参考

### 公式

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify

### その他

https://www.web-dev-qa-db-ja.com/ja/javascript/javascript%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%81%AE%E8%A4%87%E6%95%B0%E3%81%AE%E3%82%AD%E3%83%BC%E3%82%92%E5%89%8A%E9%99%A4%E3%81%99%E3%82%8B%E3%81%9F%E3%82%81%E3%81%AE%E7%B5%84%E3%81%BF%E8%BE%BC%E3%81%BF%E9%96%A2%E6%95%B0%EF%BC%9F/1055732322/

https://js.studio-kingdom.com/jquery/ajax/ajax

https://error-search.com/error-post/detail/283/Laravel5.5%2BAjax%25E3%2581%25A7419%25E3%2582%25A8%25E3%2583%25A9%25E3%2583%25BC%25E3%2581%258C%25E8%25BF%2594%25E3%2581%25A3%25E3%2581%25A6%25E3%2581%258F%25E3%2582%258B

https://qiita.com/usaginooheso/items/6a99e565f16de2f9ddf7