CakePHP ブログチュートリアル tag機能をJavaScriptで実装


#本章の内容

CakePHPブログチュートリアルに追加したタグ機能をJavaScriptで実装させる。

#JavaScriptで実装させる理由

CakePHPブログチュートリアルに、PHPのみでタグ機能を実装してしまうと、サーバーの通信を待たなければならない。
PHPのみで実装すると、新しいタグを追加する際に、記事の編集内容が保存されない。

そこで、JavaScriptによってView上で処理させることによって、タグ追加を実装させる。
これにより、サーバーの通信を待たずに処理ができるので、記事の内容に影響なく保存ができる。

注意 : JavaScriptの処理が大きくなりすぎると、ブラウザ上での処理が重くなってしまうので注意する。


#タグ機能を使用している場所と機能

- add.ctp、edit.ctp
    - タグの一覧表示
    - タグの選択
    - 新しいタグの追加
- index.ctp
    - タグの一覧表示
    - タグの選択

#CakePHP 2.xでJavaScriptを書く方法

[HtmlHelperで書く](https://book.cakephp.org/2/ja/core-libraries/helpers/html.html#HtmlHelper::script)

##JavaScriptファイルを作成する

`/app/webroot/js`のディレクトリの中にJavaScriptファイルを作成する。

Viewファイルで下記のように呼び出す。(パスは絶対パスを指定)

```php
echo $this->Html->script('script_file');
```

##Viewファイルに直接書く

下記のいずれかで`<script>`開始タグを生成する。

```php
$this->Html->scriptBlock($code, $options = array());

$this->Html->scriptStart($options = array());
```

`scriptBlock`は、変数`$code`を含めた開始タグである。

そして、下記のようにして`</script>`終了タグを生成する。

```php
$this->Html->scriptEnd();
```

上記の開始タグと終了タグの間に、JSの処理を書いていくのだが、JSHelperは、推奨されていない。

また、直接`<script></script>`を書いて動かすことも可能。


#調べたこと

##変数の受け渡し

`json_encode($value)`により、与えられた$valueをJSON形式にした文字列を返す。

これを使って、下記のようにすることで、PHPからJavaScriptに値を渡すことができる。

```javascript

var 変数 = <?php echo json_encode($value); ?>;
```

##連想配列から値だけを取り出す

下記のようにして、連想配列から値だけをリスト化することができる。

```javascript
var data = {a:1 ,b:2};

var data_list = Object.values(data);  // [1,2]
```

##配列の中に特定の値があるか判定

`リスト.indexOf(値)`で、リストの中にある特定の値のindex値を返す。リストの中に含まれていなければ、`-1`を返す。

下記のようにして、あるかないかを判定できる。

```javascript
if(list.indexOf(value) !== -1){
    //listにvalueが含まれているときの処理
}
```

##変数の型を調べる

`typeof(変数)`で型が調べられる。


##valueをJSで受け取る

下記のように`document.getElementById()`を使うことで、指定したidの要素を表すElementオブジェクトを返す。

```html:View側
<input id="id.name">
```

```javascript:JS側
var element = document.getElementById("id.name");
```

これを使い、下記のようにしてvalueを取得する。

```javascript:JS側
var value = document.getElementById("id.name").value;
```


##JSでHTML要素を生成する

`document.createElement("HTMLの要素")`を使って指定したHTML要素をJavaScript上で生成することができる。

また、`setAttribute()`を使うことで、指定した要素に新しい属性を追加することができる。


```javascript
const element = document.createElement("input");
element.setAttribute("id","id.name");
element.setAttribute("type","button");
```

上記の結果

```html
<input id="id.name" type="button">
```

##HTML要素を動的に追加する

上記の`createElement`や`setAttribute`を使って作成したHTML要素を、`appendChild()`を使って特定の親ノードの子ノードの末尾にノード（作成したHTML要素）を追加することができる。

```php
<script>
function add (){
    const Parent = document.getElementById("parent");
    const child = document.createElement("input");
    child.setAttribute("id","child.id");

    Parent.appendChild(child);
}
</script>

<input type="button" onclick="add()">  //このボタンを押すと下記のようになる
```

```html:View側
<div id="parent">
    <input id="child.id">
</div>
```

##ajaxについて(Asynchronous JavaScript + XML)

[参考資料](https://qiita.com/hisamura333/items/e3ea6ae549eb09b7efb9)

ajaxは、サーバーと非同期通信処理を行う。

**同期通信の場合**
webブラウザからサーバーにリクエストを通信して、レスポンスが返ってくる。
その際、全ての情報を通信するため、画面が白くなる。(リロード状態)

**非同期通信の場合**
webブラウザから一部の情報をリクエストするので、それ以外の部分は変わらない。
サーバーからのレスポンスが返ってこなくても他の作業ができる。
（リロードしなくても情報がとってこれる）


###ajaxの仕組み

- XMLHttpRequest

ブラウザ上でサーバーとHTTP通信(POSTとかGETとか)を行うためのAPI。

- JavaScript

XMLHttpRequestがjavascriptの組み込みオブジェクトのため、jsを用いる。

- DOM(Document Object Model)

Ajaxを使って動的なWebページを作成するときに、HTML,XML上のどの要素を変更するかを指定する。
DOMは、HTMLやXMLを「ツリー構造」として展開し、アプリケーション側に文章の情報を伝え、加工や変更をしやすくする。

- XML(Extensible Markup Language)

文書やデータの意味や構造を記述するためのマークアップ言語の一つ。


###ajaxを使ってタグ情報を取得する流れ

####タグ一覧

1. ページ上でイベント発生(get:ページを表示)
2. JS+XMLHttpRequestでサーバーに対してリクエストを送信（全てのTagの情報を取得。`find('list')`とか）
3. リクエストした情報取得

####タグ追加

1. ページ上でイベント発生(post:タグ追加ボタン)
2. JS+XMLHttpRequestでサーバーに対してリクエストを送信（新しいタグの追加`save?`）
3. サーバーからsaveが成功したってくる（saveした情報はこの時点では得られてない）
4. JS+XMLHttpRequestでサーバーに対してリクエストを送信（全てのTagの情報を取得。`find('list')`とか）
5. リクエストした情報取得

###ajaxの使い方

[データビューを有効にする](https://book.cakephp.org/2/ja/views/json-and-xml-views.html#id1)
app/Config/routes.phpに`Router::parseExtensions();`を追加。
Controllerのcomponentsに`RequestHandler`を追加。

続いて、下記のようにControllerにDBからデータを取ってきて、ajaxに送る。

```php:Controller
public function hoge(){
    $data = $this->MODEL->find('list');
    $this->set(compact('data'));
    $this->set('_serialize',array('data'));
}
```

```php:View
//省略

<?php echo $this->Html->script('javascript');?>

//省略
```

下記の`execAjax()`を実行すれば、データを取得することができる。

```javascript:javascript
function execAjax() {
    var xhr = new XMLHttpRequest();
 
    xhr.open('GET', '/****/hoge.json');
    xhr.send();
 
    xhr.onreadystatechange = function() {
        if(xhr.readyState === 4 && xhr.status === 200) {
        //データを取得後の処理を書く
            console.log(xhr.responseText);  //Controllerで送ったデータがjson形式で入っている。
        }
    }
}
```


##JSでjson形式のものをobjectとして受け取る

`JSON.parse(text)`メソッドは文字列(text)をJSONとして解析し、文字列によって記述されているJavaScriptの値やオブジェクトを構築する。

##ajax通信で値を送信する

###GET

URLの後ろに送りたいデータを付与して送る

`http://*****/**?パラメータ名=値`という感じで送る。
複数送るときは、`&`で区切る。

送るデータは、表示的にクライアントに見える。(URLに書かれているため)

データを取得したいとき、データベースの書き換えが必要ないときに使う。

###POST

`.send()`に送りたいデータを入れる。
`.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");`を指定しなければならない。
上記の例は、string型で送れるようにしている。
文字列で送るなら`.send("パラメータ名=値")`という感じ。

送るデータは、表示的にクライアントに見えないが、通信内容を見に行った場合見れる。

データの変更、ソート、更新、削除、新規登録などで使用するとき、
パスワードのような秘匿情報をURLに表示したくないとき、
送信するデータ量が多いとき、
バイナリデータを送信したいときなどに使う。


##ajax通信で送信したデータをControllerで受け取る

GETで送ったものは`$this->request->query`に入っているので、そこから取得する。
POSTで送ったものは、`$this->request->data`に入っているので、そこから取得する。


##findでjoinする方法

findのオプションで`joins`を使うことでテーブルをjoinしてデータを取ってくることができる。

```php
$options['joins'] = array(
    array('table' => 'テーブル名',
        'type' => 'JOINの種類',
        'conditions' => array(
            'モデル名.カラム名 = テーブル名.カラム名',
            'テーブル名.カラム名 = 値'
            )
        )
    );

$変数名 = $this->モデル名->find('all',$options);
```

##HTML要素の再構築

`cloneNode`にfalseを渡すことで、中身を空の状態にしたHTML要素をクローンすることができる。

```javascript

const Parent = document.getElementById("id.name");
const clone = Parent.cloneNode(false);
```

下記の`replaceChild()`を使うことで、「Parent」を「clone」として置き換えることができる。
それを`parentNode`でParentに渡すことでParentの中身を更新しているように見せている。

```javascript

Parent.parentNode.replaceChild(clone, Parent);
```