#本章の内容
CakePHPブログチュートリアルに追加したタグ機能をJavaScriptで実装させる。
#JavaScriptで実装させる理由
CakePHPブログチュートリアルに、PHPのみでタグ機能を実装してしまうと、サーバーの通信を待たなければならない。
PHPのみで実装すると、新しいタグを追加する際に、記事の編集内容が保存されない。
そこで、JavaScriptによってView上で処理させることによって、タグ追加を実装させる。
これにより、サーバーの通信を待たずに処理ができるので、記事の内容に影響なく保存ができる。
注意 : JavaScriptの処理が大きくなりすぎると、ブラウザ上での処理が重くなってしまうので注意する。
#タグ機能を使用している場所と機能
- add.ctp、edit.ctp
- タグの一覧表示
- タグの選択
- 新しいタグの追加
- index.ctp
- タグの一覧表示
- タグの選択
#CakePHP 2.xでJavaScriptを書く方法
##JavaScriptファイルを作成する
/app/webroot/js
のディレクトリの中にJavaScriptファイルを作成する。
Viewファイルで下記のように呼び出す。(パスは絶対パスを指定)
echo $this->Html->script('script_file');
##Viewファイルに直接書く
下記のいずれかで<script>
開始タグを生成する。
$this->Html->scriptBlock($code, $options = array());
$this->Html->scriptStart($options = array());
scriptBlock
は、変数$code
を含めた開始タグである。
そして、下記のようにして</script>
終了タグを生成する。
$this->Html->scriptEnd();
上記の開始タグと終了タグの間に、JSの処理を書いていくのだが、JSHelperは、推奨されていない。
また、直接<script></script>
を書いて動かすことも可能。
#調べたこと
##変数の受け渡し
json_encode($value)
により、与えられた$valueをJSON形式にした文字列を返す。
これを使って、下記のようにすることで、PHPからJavaScriptに値を渡すことができる。
var 変数 = <?php echo json_encode($value); ?>;
##連想配列から値だけを取り出す
下記のようにして、連想配列から値だけをリスト化することができる。
var data = {a:1 ,b:2};
var data_list = Object.values(data); // [1,2]
##配列の中に特定の値があるか判定
リスト.indexOf(値)
で、リストの中にある特定の値のindex値を返す。リストの中に含まれていなければ、-1
を返す。
下記のようにして、あるかないかを判定できる。
if(list.indexOf(value) !== -1){
//listにvalueが含まれているときの処理
}
##変数の型を調べる
typeof(変数)
で型が調べられる。
##valueをJSで受け取る
下記のようにdocument.getElementById()
を使うことで、指定したidの要素を表すElementオブジェクトを返す。
<input id="id.name">
var element = document.getElementById("id.name");
これを使い、下記のようにしてvalueを取得する。
var value = document.getElementById("id.name").value;
##JSでHTML要素を生成する
document.createElement("HTMLの要素")
を使って指定したHTML要素をJavaScript上で生成することができる。
また、setAttribute()
を使うことで、指定した要素に新しい属性を追加することができる。
const element = document.createElement("input");
element.setAttribute("id","id.name");
element.setAttribute("type","button");
上記の結果
<input id="id.name" type="button">
##HTML要素を動的に追加する
上記のcreateElement
やsetAttribute
を使って作成したHTML要素を、appendChild()
を使って特定の親ノードの子ノードの末尾にノード(作成したHTML要素)を追加することができる。
<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()"> //このボタンを押すと下記のようになる
<div id="parent">
<input id="child.id">
</div>
##ajaxについて(Asynchronous JavaScript + XML)
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を使ってタグ情報を取得する流れ
####タグ一覧
- ページ上でイベント発生(get:ページを表示)
- JS+XMLHttpRequestでサーバーに対してリクエストを送信(全てのTagの情報を取得。
find('list')
とか) - リクエストした情報取得
####タグ追加
- ページ上でイベント発生(post:タグ追加ボタン)
- JS+XMLHttpRequestでサーバーに対してリクエストを送信(新しいタグの追加
save?
) - サーバーからsaveが成功したってくる(saveした情報はこの時点では得られてない)
- JS+XMLHttpRequestでサーバーに対してリクエストを送信(全てのTagの情報を取得。
find('list')
とか) - リクエストした情報取得
###ajaxの使い方
データビューを有効にする
app/Config/routes.phpにRouter::parseExtensions();
を追加。
ControllerのcomponentsにRequestHandler
を追加。
続いて、下記のようにControllerにDBからデータを取ってきて、ajaxに送る。
public function hoge(){
$data = $this->MODEL->find('list');
$this->set(compact('data'));
$this->set('_serialize',array('data'));
}
//省略
<?php echo $this->Html->script('javascript');?>
//省略
下記のexecAjax()
を実行すれば、データを取得することができる。
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してデータを取ってくることができる。
$options['joins'] = array(
array('table' => 'テーブル名',
'type' => 'JOINの種類',
'conditions' => array(
'モデル名.カラム名 = テーブル名.カラム名',
'テーブル名.カラム名 = 値'
)
)
);
$変数名 = $this->モデル名->find('all',$options);
##HTML要素の再構築
cloneNode
にfalseを渡すことで、中身を空の状態にしたHTML要素をクローンすることができる。
const Parent = document.getElementById("id.name");
const clone = Parent.cloneNode(false);
下記のreplaceChild()
を使うことで、「Parent」を「clone」として置き換えることができる。
それをparentNode
でParentに渡すことでParentの中身を更新しているように見せている。
Parent.parentNode.replaceChild(clone, Parent);