Ajax処理とMySQLを使って、ページを更新せずにデータべ―スの読み書き

#動作の流れ
まず今回実現したい動作について説明します。

1. HTMLのフォームに入力してEnterキーを押す
![image.png](https://qiita-image-store.s3.amazonaws.com/0/298418/d912f168-e0b7-7298-fe99-fa1b8b13edd9.png)
2. 入力内容をデータベースに挿入
![image.png](https://qiita-image-store.s3.amazonaws.com/0/298418/7ae775e5-4f33-dcc1-02f7-9b121de47796.png)
4. 入力内容をHTML要素として挿入
![image.png](https://qiita-image-store.s3.amazonaws.com/0/298418/241e9348-fe72-85e4-b87c-83ff0fd80880.png)

#フォームの内容を入力したときのイベント
1のフォームに入力してEnterキーを押したときのイベント処理を考えていきます。

##onメソッド
`セレクタ.on('イベント',関数);`とすると、
セレクタで指定したHTML要素に第1引数のイベントが発生したときに、第2引数の関数が実行されます。

`セレクタ.イベント(関数);`で、
clickイベントなどはonとだいたい同じように使うことができます。
しかし、このようなイベント名のメソッドは、onメソッドと違って、jQueryなどで動的に作成したHTMLタグに対しては動作してくれません。

今回はフォームの内容をリスト上で動的に追加するので、onメソッドで統一しておきます。

##changeとsubmit
入力フォームに内容を入力してEnterキーを押したときに、2～4の処理を実行したいわけですが、どのイベントを使ったらいいのでしょうか。

フォームに関するイベントには
・changeイベント
・submitイベント
があります。

changeイベントはフォーム部品の状態が変化したときに発生します。
一方submitはフォームが送信されたときに発生します。

今回のページでは入力フォームが1つだけなので、Enterを押したらフォームが送信されます。
ですから、submitイベントがよさそうです。

##ページ遷移したくない！
とりあえず、onメソッドでsubmitイベントがちゃんと発生するか調べてみます。
但し、"\#new_todo_form"はform要素につけたIDです。


```javascript:todo.js
$('#new_todo_form').on('submit', function(){
console.log("success");
/* 2~4の処理 */
});
```
実行すると
![image.png](https://qiita-image-store.s3.amazonaws.com/0/298418/d912f168-e0b7-7298-fe99-fa1b8b13edd9.png)

↓

![image.png](https://qiita-image-store.s3.amazonaws.com/0/298418/0cc7a512-d127-67bd-adb2-ff1e15ee0392.png)

ページが更新されてしまいました！
formタグでページの遷移先は指定していないので、同じページが再読み込みされたのでしょう。

「新しいページにしなくていいから、そのままでいて・・・。」

そう思って、私はchangeメソッドでもいろいろ試したりしていたわけですが、実はたった1行書き加えるだけでよかったんですよね・・・。

**return false;**

これをonメソッドの第2引数の関数の末尾に書けば、ページ遷移を防ぐことができるのです。

#データベースに挿入
onメソッドの第2引数の関数を作っていきます。
サーバーのデータベースに非同期でアクセスしたいので,
Ajax処理を利用していきます。

```javascript:todo.js

  $('#new_todo_form').on('submit', function(){
    // idを取得
    var title = $('#new_todo').val();
    //ajax処理
    $.post('_ajax.php',{
      title: title,
      mode: 'create',
      token: $('#token').val()
    },function(res){
    /*3の処理*/
    });
  });

```
`$.post(’データの送信先’,{key:data},functoin(res){});`
とすると、formタグのmethod属性をpostにしたときと同じように
’データの送信先に’dataを送れます。
また、送信先の_ajax.phpで、echoなどで出力された内容が、送信先の処理が終わった後に実行される関数の引数resで受け取ることができます。


##_ajax.phpの作成

```php:_ajax.php

$todoApp = new \MyApp\Todo();

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
  try {
    $res = $todoApp->post();
    header('Content-Type: application/json');
    echo json_encode($res);
    exit;
  } catch (Exception $e) {
    header($_SERVER['SERVER_PROTOCOL'] . ' 500 Internal Server Error', true, 500);
    echo $e->getMessage();
    exit;
  }
}
```
Todo.phpに記述したTodoクラスのメソッドpost()を呼び出すと、mode:createの場合に、データベースに新しくデータを挿入するcreateメソッドを呼ぶようにしています。
返す値は扱いやすいようにjsonコードにしておきます。

##createメソッドの作成

```php:todo.php

  private function _create(){
    if(!isset($_POST['title'])|| isset($_POST['title']) === ''){
      throw new \Exception('[create] title not set!');
    }

    $sql = "insert into todos (title) values (:title)";
    $stmt = $this->_db->prepare($sql);
    $stmt->execute([':title' => $_POST['title']]);

    return ['id' => $this->_db->lastInsertId()];
  }

```
postで送信したデータを`$_POST['key']`で呼び出します。
フォームの内容が空じゃないかどうか調べて、throwで例外処理を発生させます。

フォームから受信したデータが空じゃなかったら、
insert文を使ってデータを挿入していきます。
プレースホルダーを使って出力します。
これはSQLインジェクション対策なのですが、[XSS対策とSQLインジェクション対策](https://qiita.com/yukako_nishimura/items/614533263de0ac835e6b)で少し説明しているので、参考にしていただければ幸いです。

最後に追加した内容のIDを取得して、連想配列で返します。
これは_ajax.php上でjson形式に変換されます。

#テンプレートをcloneメソッドで複製
入力データをHTML要素として挿入する時には、テンプレートがあって、属性値や、タグの中身にデータを入れるだけにしておくと便利そうですよね。

そこで、idex.php上にこのようなテンプレートを作っておきます。

```html:index.php
 <li id="todo_templete" data-id="">
         <input type="checkbox" class="update_todo">
         <span class="todo_title"></span>
         <div class="delete_todo">x</div>
 </li>
```

テンプレートをセレクタで指定して、cloneメソッドを使うと、複製することができます。
複製した要素にattrメソッドやdataメソッド、textメソッドなどをデータを挿入していきます。
そして、prependメソッドでリストの先頭に複製した要素を挿しこみます。


```js:todo.js
   function(res){
      //liを追加 index.phpのテンプレ―を複製
      var $li = $('#todo_templete').clone();
      $li
      .attr('id','todo_' + res.id)
      .data('id',res.id)
      .find('.todo_title').text(title);
      $('#todos').prepend($li.fadeIn());
      $('#new_todo').val('').focus(); //空にしてfocusを当てて連続で入力可能に
   }
```

フォームに連続して入力できるようにしたいので、val('')でフォームの中身を空にして、focusを当てておきました。