Ajaxの仕組み


##プレーンテキストを扱う

###1. クライアントからリクエスト(GETかPOST)
①XMLHttpRequestオブジェクトを生成
②HTTPリクエストの初期化
③サーバーに要求データを送信する

###2. サーバーはテキストデータを生成しクライアントに返す
④以下のサンプルではPHPで記述

###3. クライアントは通信の状態の変化により処理を行う
⑤onreadystatechangeイベントハンドラを定義する
⑥テキストデータを取得(responseTextメソッド)
⑦応答したテキストデータ(responseTextで取得)はそのままHTMLに埋め込むことができる

```html:helloAjax.html
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Hello, World!</title>
<script type="text/javascript" src="helloAjax.js"></script>
</head>
<body>
<form name="fm">
<label>名前：
<input type="text" name="name" size="15" /></label>
<input id="btn" type="button" name="submit" value="送信" />
</form>
<div id="result"></div>
</body>
</html>
```

```php:helloAjax.php
<?php
sleep(3)
// ④テキストを出力
print('こんにちは、' . $_REQUEST['name'] . ' さん! ' );
```

```javascript:helloAjax.js
// XMLHttpRequestオブジェクトを生成の為の関数定義
function getXHR() {
  var req;
  try {
  	// Internet Explorer7.0以降/それ以外のブラウザ
    req = new XMLHttpRequest();
  } catch(e) {
    try {
    　// Internet Explorer6.0
      req = new ActiveXObject('Msxml2.XMLHTTP');
    } catch (e) {
    　// Internet Explorer5.5以前
      req = new ActiveXObject('Microsoft.XMLHTTP');
    }
  }
  return req;
}

window.onload = function() {
  getElementById('btn').onclick= function(){
  　// ①XMLHttpRequestオブジェクトを生成
    var req = getXHR();
    // ⑤通信の状態が変化したタイミングで呼ばれるイベントハンドラ作成
    req.onreadystatechange = function() {
      var result = document.getElementById('result');
      // 非同期通信が完了したか?
      if (req.readyState == 4) {
        // サーバーからの応答は?(ステータスコード)
        if (req.status == 200) {
          // ⑥⑦テキストデータを取得
          result.innerHTML = req.responseText;
        } else {
          result.innerHTML = "サーバエラーが発生しました。";
        }
      } else {
        result.innerHTML = "通信中...";
      }
    }
    // ②HTTPリクエストの初期化
    req.open('GET', 'helloAjax.php?name=' + 
      encodeURIComponent(document.fm.name.value), true);
    // ③サーバーに要求データを送信する
    req.send(null);
  }
}
```

##XMLデータを扱う(プロキシ経由)

###利用するwebAPI:Yahoo!ウェブ検索webサービス

###1. クライアントからリクエスト(GETかPOST)
①XMLHttpRequestオブジェクトを生成
②HTTPリクエストの初期化
③サーバーに要求データを送信する

###2. サーバー(プロキシ)は別のサーバー(webAPI)に問い合わせ
④以下のサンプルではPHPで記述

###3. 問い合わせ結果(XML形式)をそのままクライアントに返す
⑤以下のサンプルではPHPで記述
###4. クライアントは通信の状態が変化により処理を行う
⑥onreadystatechangeイベントハンドラを定義する
⑦XMLデータを取得(responseXMLメソッド)
⑧応答したXMLデータ(responseXMLで取得)はDOMDocumentオブジェクトとして扱う

```html:yahoo.html
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Yahoo! ウェブ検索Webサービス</title>
<script type="text/javascript" src="yahoo.js"></script>
</head>
<body>
<form name="fm">
<label>キーワード：
  <input type="text" name="keywd" size="15" /></label>
<input type="button" value="検索" id="btn" />
<hr />
<div id="result"></div>
</form>
</body>
</html>
```

```php:yahoo.php
<?php
// 応答のコンテンツタイプを宣言
header('Content-Type: text/xml;charset=UTF-8');
// Yahoo!ウェブ検索webサービスへの問い合わせの為のクエリ情報を作成
$params = array(
  'appid'  => 'wings-project',
  'query'  => $_REQUEST['keywd'],
  'results'=> 20
);
$url = 'http://search.yahooapis.jp/WebSearchService/V2/webSearch?'.http_build_query($params);
//④⑤問い合わせ結果をそのまま出力
print(file_get_contents($url));
```

```javascript:yahoo.js
// XMLHttpRequestオブジェクトを生成の為の関数定義
function getXHR() {
  var req;
  try {
  	// Internet Explorer7.0以降/それ以外のブラウザ
    req = new XMLHttpRequest();
  } catch(e) {
    try {
    　// Internet Explorer6.0
      req = new ActiveXObject('Msxml2.XMLHTTP');
    } catch (e) {
    　// Internet Explorer5.5以前
      req = new ActiveXObject('Microsoft.XMLHTTP');
    }
  }
  return req;
}

window.onload = function() {
  getElementById('btn').onclick= function(){
  　// ①XMLHttpRequestオブジェクトを生成
    var req = getXHR();
    // ⑥通信の状態が変化したタイミングで呼ばれるイベントハンドラ作成
    req.onreadystatechange = function() {
      var result = document.getElementById('result');
      // 非同期通信が完了したか?
      if (req.readyState == 4) {
        // サーバーからの応答は?(ステータスコード)
        if (req.status == 200) {
          // ⑦XMLデータを取得
          var doc = req.responseXML;
          // ⑧DOMDocumentオブジェクトとして扱う
          var nodes = doc.getElementsByTagName('Result');
          if (nodes.length == 0) {
            result.innerHTML = '合致するサイトはありませんでした。';
          } else {
            var ul = document.createElement('ul');
            for (var i = 0; i < nodes.length; i++) {
              var node = nodes.item(i);
              var li = document.createElement('li');
              var anchor = document.createElement('a');
              anchor.href = getNodeValue(node, 'Url');
              anchor.target = '_blank';
              var link = document.createTextNode(getNodeValue(node, 'Title'));
              anchor.appendChild(link);
              li.appendChild(anchor);
              ul.appendChild(li);
			  }
			}
		   result.replaceChild(ul, result.firstChild);
        } else {
          result.innerHTML = 'サーバエラーが発生しました。';
        }
      } else {
        result.innerHTML = "通信中...";
      }
    }
    // ②HTTPリクエストの初期化
    req.open('GET', 'yahoo.php?name=' + 
      encodeURIComponent(document.fm.name.value), true);
    // ③サーバーに要求データを送信する
    req.send(null);
  }
  
  // 要素ノードcurrent配下に含まれる要素nameのテキスト値を取得する関数
  function getNodeValue(current ,name) {
	var node = current.getElementsByTagName(name).item(0);
	return node.firstChild.nodeValue;
  }
}
```

##JSONデータを扱う(プロキシ経由)

###上記と同様のwebAPIをJSONに変換して扱う

###1. クライアントからリクエスト(GETかPOST)
①XMLHttpRequestオブジェクトを生成
②HTTPリクエストの初期化
③サーバーに要求データを送信する

###2. サーバー(プロキシ)は別のサーバー(webAPI)に問い合わせXMLを取得
④以下のサンプルではPHPで記述

###3. 問い合わせ結果(XML形式)をJSONに変換しクライアントに返す
⑤以下のサンプルではPHPで記述
###4. クライアントは通信の状態が変化により処理を行う
⑥onreadystatechangeイベントハンドラを定義する
⑦JSONデータを取得(responseTextメソッド)し解析する為eval関数に引き渡す
⑧取得したJSONデータはJavaScriptオブジェクト(連想配列)として扱う

###補足
eval関数は引数のコードを解析/実行する為の関数
JSONフォーマットを解析することによってJavaScriptのコード内で実行できる
この際、レスポンス全体をカッコで括らないと、正しく解析されない

```html:yahooJson.html
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Yahoo! ウェブ検索Webサービス</title>
<script type="text/javascript" src="yahooJson.js"></script>
</head>
<body>
<form name="fm">
<label>キーワード：
  <input type="text" name="keywd" size="15" /></label>
<input type="button" value="検索" id="btn" />
<hr />
<div id="result"></div>
</form>
</body>
</html>
```

```php:yahooJson.php
<?php
// 応答のコンテンツタイプを宣言
header('Content-Type: text/json;charset=UTF-8');
// Yahoo!ウェブ検索webサービスへの問い合わせの為のクエリ情報を作成
$params = array(
  'appid'  => 'wings-project',
  'query'  => $_REQUEST['keywd'],
  'results'=> 20
);
$url = 'http://search.yahooapis.jp/WebSearchService/V2/webSearch?'.http_build_query($params);
//④webAPIに問い合わせしXMLを取得
$doc = simplexml_load_string(file_get_contents($url));
//⑤取得したXMLをJSONに変換
print(json_encode($doc));
```

```javascript:yahooJson.js
// XMLHttpRequestオブジェクトを生成の為の関数定義
function getXHR() {
  var req;
  try {
  	// Internet Explorer7.0以降/それ以外のブラウザ
    req = new XMLHttpRequest();
  } catch(e) {
    try {
    　// Internet Explorer6.0
      req = new ActiveXObject('Msxml2.XMLHTTP');
    } catch (e) {
    　// Internet Explorer5.5以前
      req = new ActiveXObject('Microsoft.XMLHTTP');
    }
  }
  return req;
}

window.onload = function() {
  getElementById('btn').onclick= function(){
  　// ①XMLHttpRequestオブジェクトを生成
    var req = getXHR();
    // ⑥通信の状態が変化したタイミングで呼ばれるイベントハンドラ作成
    req.onreadystatechange = function() {
      var result = document.getElementById('result');
      // 非同期通信が完了したか?
      if (req.readyState == 4) {
        // サーバーからの応答は?(ステータスコード)
        if (req.status == 200) {
          // ⑦JSONデータを取得
          var data = eval('(' + req.responseText + ')');
          // ⑧取得したJSONデータはJavaScriptオブジェクトとして扱う
        　var nodes = data.Result;
        　if (nodes.length == 0) {
          　result.innerHTML = '合致するサイトはありませんでした。';
        　} else {
          　var ul = document.createElement('ul');
          　for (var i = 0; i < nodes.length; i++) {
            　var li = document.createElement('li');
           　 var anchor = document.createElement('a');
            　anchor.href = nodes[i].Url;
            　anchor.target = '_blank';
            　var link = document.createTextNode(nodes[i].Title);
            　anchor.appendChild(link);
            　li.appendChild(anchor);
            　ul.appendChild(li);
          　}
        　}
        　result.replaceChild(ul, result.firstChild);
        } else {
          result.innerHTML = 'サーバエラーが発生しました。';
        }
      } else {
        result.innerHTML = "通信中...";
      }
    }
    // ②HTTPリクエストの初期化
    req.open('GET', 'yahoo.php?name=' + 
      encodeURIComponent(document.fm.name.value), true);
    // ③サーバーに要求データを送信する
    req.send(null);
  }
  
  // 要素ノードcurrent配下に含まれる要素nameのテキスト値を取得する関数
  function getNodeValue(current ,name) {
	var node = current.getElementsByTagName(name).item(0);
	return node.firstChild.nodeValue;
  }
}
```

###JQueryで書き換える

###1. クライアントからリクエスト(GETかPOST)
①$.ajaxメソッドにより通信を開始
($.ajaxメソッドの引数にはオブジェクトリテラルの形式でパラメータを書く)
②送信するデータは$,ajaxのパラメータで記述(ハッシュ形式)

###2. サーバー(プロキシ)は別のサーバー(webAPI)に問い合わせXMLを取得
③上と同じ

###3. 問い合わせ結果(XML形式)をJSONに変換しクライアントに返す
④上と同じ

###4. クライアントは通信の状態が変化により処理を行う
⑤通信開始前の処理は$.ajaxメソッドの前に書く
⑥$.ajaxメソッドの引数によって通信終了時の処理を書く
⑦応答データ(JSON)はsuccessパラメーターのコールバック関数の引数に格納される
⑧取得したJSONデータはJavaScriptオブジェクト(連想配列)として扱う

###補足
eval関数は不要

```javascript:yahooJson.js
$(
  function(){
	$('#btn').click(
	  function() {
	    // ⑤通信前の処理
		$('#result').html('通信中...');
		// ①$.ajaxメソッドにより通信を開始
		$.ajax({
		  url: 'yahooJson.php',
		  type: 'GET',
		  //cache: false,
		  dataType: 'json',
		  // ②フォームの内容をハッシュ形式に変換
		  data: $('form').serializeArray(),
		  timeout: 5000,
		  // ⑥Ajax通信成功時の処理
		  // ⑦応答データ(JSON)は引数dataに格納されている
		  success: function(data){
		    $('#result').empty();
		    // ⑧取得したJSONデータ(data)はJavaScriptのコード内で実行できる
		    for(var i = 0; i < data.Result.length; i++){
		      $('#result').append(
			    $('<li></li>').append(
			    $('<a></a>')
			      .attr('href', data.Result[i].ClickUrl)
			      .html(data.Result[i].Title)
			    )
			  );
            }
		  },
		  // ⑥Ajax通信失敗時の処理
		  error: function(){
		    $('#result').html('サーバエラーが発生しました。');
		  }
	   });
	  }
    );
  }
);
```

##JSONPを利用する

###利用するwebAPI:はてなブックマークエントリー情報取得API
webAPIに送る情報
・クエリ情報callback → 後から呼び出すJavaScript関数の名前
・url → 被ブックマーク数を取得したいURL

###1. 事前にコールバック関数を定義
①外部サービス(コールバック関数)を処理する為の関数を定義する

###2. イベントが発生したタイミングで外部からスクリプトをダウンロード
②イベントハンドラに\<script>タグを生成するコードを書く

###3. 外部サービスを実行(コールバック関数を実行)
③\<script>タグによりwebAPIの関数を実行
(関数の定義はクライアント側)

###補足
JSONPが利用できるAPIはサーバーからの応答が
コールバック関数名(JSONデータ)
であることを期待している
それによりクライアント側で
function コールバック関数名(data) {
}
を書くことによりJSONデータを受け取ったかのような処理を記述できる


```html:jsonp.html
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>JSONP</title>
<script type="text/javascript" src="helloAjax.js"></script>
</head>
<body>
<form name="fm">
<label>URL：
  <input type="text" name="url" size="50"
    value="http://www.wings.msn.to/" /></label>
<input type="button" id="disp" value="検索" onclick="search()" />
</form>
<hr />
<span id="count">-</span>件、ブックマークされています。
<div id="result"></div>
</body>
</html>
```

```javascript:jsonp.js
// ②イベントハンドラに<script>タグを生成するコードを書く
window.onload = function() {
  getElementById('disp').onclick= function(){
    // webAPIへの問い合わせURLを生成
    var url = 'http://b.hatena.ne.jp/entry/jsonlite/?callback=show&url='
    + encodeURIComponent(document.fm.url.value);
    // <script>タグを生成
    var scr = document.createElement('script');
    scr.type = 'text/javascript';
    scr.src = url;
    document.getElementsByTagName('body').item(0).appendChild(scr);
  }
}

// ①外部サービス(コールバック関数)を処理する為の関数を定義する
function show(data) {
  var count = document.getElementById('count');
  var result = document.getElementById('result');
  result.innerHTML = '';
  if (data == null) {
    count.innerHTML = 0;
  } else {
    count.innerHTML = data.count;	
    var ul = document.createElement('ul');
    for (var i = 0; i < data.bookmarks.length; i++) {
      var bm = data.bookmarks[i];
      var li = document.createElement('li');
      var anchor = document.createElement('a');
      anchor.href = 'http://b.hatena.ne.jp/' + bm.user
      var text = document.createTextNode(bm.user + ' ' + bm.timestamp);
      anchor.appendChild(text);
      li.appendChild(anchor);
      ul.appendChild(li);
    }
    var result = document.getElementById('result');
    result.appendChild(ul);
  }
}
```
###JQueryで書き換える

###1. 事前にコールバック関数を定義
①外部サービス(コールバック関数)を処理する為の関数を定義する

###2. イベントが発生したタイミングで外部からスクリプトをダウンロード
②$.getJSONメソッドを使うことにより外部のスクリプトを読み込む

###3. 外部サービスを実行(コールバック関数を実行)
③\<script>タグによりwebAPIの関数を実行

###補足
$.getJSONメソッドは以下のように書く
$.getJSON(リクエスト先のURL [, リクエストパラメーター] [,通信完了時のコールバック関数]
また、リクエスト先のURLの末尾に?callback=?を入れることにより
コールバックを無名関数のように書ける

```javascript:jsonp.js
$(function() {
  $('#disp').click(function() {
    // ②外部のスクリプトを読み込む
    $.getJSON(
      // 末尾に?callback=?を入れる
      'http://b.hatena.ne.jp/entry/jsonlite/?callback=?',
      { url: $('#url').val() },
      // ①外部サービス(コールバック関数)を処理する為の関数を定義する
      function(data) {
        $('#count').html(data.count);
        for (var i = 0; i < data.bookmarks.length; i++) {
          var bm = data.bookmarks[i];
          $('#result').append(
            $('<li></li>').append(
              $('<a></a>')
                .attr('href', 'http://b.hatena.ne.jp/' + bm.user)
                .html(bm.user + ' ' + bm.timestamp)
            )
          );
        }
      }
    );
  });
});
```

##RailsでAjax処理を行う

以下のページで詳しく解説されている
http://qiita.com/ka215/items/dfa602f1ccc652cf2888
http://www.gotealeaf.com/blog/the-detailed-guide-on-how-ajax-works-with-ruby-on-rails



##参考
JavaScript本格入門8章・9章