jquery ajax を使って更新を自動的に行う

2014年1月26日

特定のページに記述してある情報を他のページでも使用している場合に、元のページの情報を更新すると他のページの情報も自動的に更新されるようにする方法のメモ。

概要

例えば、スケジュールが記述されている「schedule.html」のサマリーをトップページなどにも表示する場合、内容を更新するにはそれぞれのページを更新しなければならない。

これを jQuery の load() を利用して「schedule.html」を更新すると自動的に他のページも更新されるようにしてみる。

schedule.html 抜粋

・・・省略・・・
<table id="schedule">
<tr>
<th>DATE</th>
<th>VENUE / LOCATION</th>
</tr>
<tr>
<td class="date">1/3 (Thurs)</td>
<td class="info"><span class="venue">Cleopatra's Needle</span> ....</td>
</tr>
<tr>
<td class="date">1/6 (Sun)</td>
<td class="info"><span class="venue">Small's</span>....</td>
</tr>
・・・省略・・・
<td class="date">1/19 (Wed)</td>
<td class="info"><span class="venue">St. Peter's Church</span>....</td>
</tr>
</table>
・・・省略・・・

上記の schedule.html の「table id=”schedule”」の概要を他のページにも表示する。

  • この例では schedule.html の <td class=”date”> と <span class=”venue”> を抽出して index.html に表示。
  • 表示する際は、「class=”date”」の情報は dt 要素、「class=”venue”」の情報は dd 要素を使って表示。

index.html 抜粋

<h2>スケジュール</h2>
     <div id="live_summary">
     <!--この部分に概要を表示する-->
     </div><!--end of #live_summary-->

jQuery の記述

追加情報

以下の方法では、load() を使っているのに、コールバック関数の中で、応答テキストの response を使って無駄な処理をしています。効率の悪い例として残しておきますが、こちらのコードの方を参考にしてください。

  • 読み込む要素(index.html の #live_summary)で load() を使ってスケジュールのページ(schedule.html)の「#schedule」部分をロード
  • コールバックで exec() を使って必要な情報を取得して、配列に格納
  • exec() では、文字列中の全てのマッチを調べるので正規表現に gフラグを指定
  • 読み込む要素(#live_summary)の HTML を初期化($(this).html(”))
  • 配列に入っている情報を取り出し、dt 要素、dd 要素として書き出し

jQuery

jQuery(function($){
    $('#live_summary').load('en/schedule.html #schedule', function( response, status, xhr ) {  
    var date = /<td class="date">(.+)<\/td>/g;
    var venue = /<span class="venue">(.+)<\/span>/g;
    var result_date;
    var result_venue;
    $(this).html('');
    var date_array = new Array();
    var venue_array = new Array();
    while((result_date = date.exec(response)) != null){
      date_array.push(result_date[1]);
    }
    while((result_venue = venue.exec(response)) != null){
      venue_array.push(result_venue[1]);
    }
    var my_html = '';
    for(var i = 0; i < date_array.length; i++){
      my_html += '<dl><dt>' + date_array[i] + "</dt>\n" + '<dd>' + venue_array[i] + "</dd>\n" + '</dl>';
    }
    $(this).html(my_html);  
  });
});

HTML コメントアウト部分のの削除

もし、schedule.html にユーザーのためにコメントアウトを使って以下のような使用法が記述してあると、この部分の td 要素も対象になってしまい不要な部分が出力されてしまう。

<!--
行を追加する場合は以下をコピーして使用してください。

<tr>
<td class="date">日付 (曜日)</td>
<td class="info"><span class="venue">場所</span><br>住所等</td>
</tr>

-->
<table id="schedule">
<tr>
<th>DATE</th>
<th>VENUE / LOCATION</th>
</tr>
<tr>
<td class="date">1/3 (Thurs)</td>
<td class="info"><span class="venue">Cleopatra's Needle</span> ....</td>
</tr>

そのような場合は、replace() を使って HTML のコメントアウト部分を削除する。

text.replace(/<!--([^-]|-[^-]|--[^>])*-->/g, ""); 

ロードしたデータから全てのコメントアウトを削除する場合の例

jQuery

jQuery(function($){
  $('#live_summary').load('en/schedule.html #schedule', function( response, status, xhr ) {
  var date = /<td class="date">(.+)<\/td>/g;
  var venue = /<span class="venue">(.+)<\/span>/g;
  var result_date;
  var result_venue;
  $(this).html('');
  var date_array = new Array();
  var venue_array = new Array();
  //コメントアウト部分を削除
  response = response.replace(/<!--([^-]|-[^-]|--[^>])*-->/g, ""); 
  while((result_date = date.exec(response)) != null){
    date_array.push(result_date[1]);
  }
  while((result_venue = venue.exec(response)) != null){
    venue_array.push(result_venue[1]);
  }
  var my_html = '';
  for(var i = 0; i < date_array.length; i++){
    my_html += '<dl><dt>' + date_array[i] + "</dt>\n" + '<dd>' + venue_array[i] + "</dd>\n" + '</dl>';
  }
  $(this).html(my_html);	
  });
});  

効率的な方法

せっかく load() を使っているのだから、コールバック関数の中で $(this) を使用すれば、フィルタした要素(この例の場合は、id が schedule の table 要素)を操作できる。

jQuery

jQuery(function($){
  $('#live_summary').load('en/schedule.html #schedule', function() {  
    var table$ = $(this);
    var date$ = table$.find('td.date');
    var venue$ = table$.find('td.info span.venue');
    var html = "";
    for(var i = 0; i < date$.length; i ++) {
     html += "<dl><dt>" + date$.eq(i).text() + "</dt>\n<dd>" + venue$.eq(i).text() + "</dd></dl>";
    }
    $(this).html(html);
  });
});

コールバック関数は、マッチした要素に取得したデータが挿入された後に呼び出されるので、$(this) にはすでに、table 要素 <table id="schedule">~</table> の jQuery オブジェクトが入っています。それをまず変数(table$)に格納します。

そして find() を使って、 <td class=”date”> と <span class=”venue”> の要素を抽出して、それらを変数(date$, venue$)に格納します。

変数 html は、 live_summary という id 名の div 要素に挿入する HTML で、まず空文字列で初期化しておきます。

for 文を使って、要素の数だけ繰り返し処理を行います。繰り返しの数は、date$.length または、venue$.length で取得できます。

繰り返し処理の中では、date$ 及び venue$ から eq() と text() を使ってそれぞれの要素のテキストを取得して、それを dt 及び dd 要素の内容として html に追加しています。

繰り返し処理が終了したら、全ての要素の内容を html に代入できているので、それを $(this) つまり、$(‘#live_summary’) の HTML に設定します。

load()

関連ページ:「jQuery の load()

load(url, parameters, callback)

url (String)
要求を送信するサーバ側リソース(読む込むデータ)のURL。
オプションとして jQuery セレクタを使える 。
parameters (String|Object|Array) 
オプション。
リクエストと一緒に送信するデータ(要求のパラメータとして渡すべきデータ)を指定。
オブジェクトまたは配列で指定した場合、要求には POST メソッドが使用される。
省略した場合と、文字列で指定した場合は、GET メソッドが使われる。
callback (Function) 
オプション。
応答データ(取得したデータ)がロードされた後に呼び出されるコールバック関数。
この関数に渡されるパラメータは、
・response:応答テキスト(取得したデータ)、
・status:ステータス文字列(success、 error、notmodified、timeout、parsererror)、
・xhr:XMLHttpRequest インスタンス。
この関数はマッチしている各要素それぞれで呼び出され、ターゲットの要素集合(それぞれの DOM 要素)が関数のコンテクスト(this)になる。
戻り値
ラップ集合

セレクタを指定するには、URLの後に、1個のスペースに続けてセレクタを書く。例えば、応答要素(取得するデータ)にフィルタをかけて div インスタンスだけを注入したい場合は、次のように書く。

$('.injectMe').load('en/schedule.html div');

exec()

RegExp.exec() メソッド:

汎用のパターンマッチング。exec()は文字列を引数にとる RegExp のメソッド

書式
RegExp.exec(string)
引数
検索対象の文字列。これを指定しないと、代わりに RegExp.input が検索される。
戻り値
パターンマッチングの結果を含む文字列。マッチしない場合は null が返される。
  • exec() メソッドは、指定された文字列を検索し、一致する文字列が見つからない場合は null を返す。
  • 一致する文字が見つかった場合は、配列を返す。これはグローバル指定なしの match() が返す配列と同じ。
  • 配列の要素 0 には、正規表現に一致した文字列が格納される。
  • 続く配列の要素(要素1, 要素2…)には、括弧で囲まれた部分表現に一致した文字列が格納される。
  • 配列の length プロパティは、通常とおり配列内の要素の数を表す。
  • 配列要素と length プロパティに加えて、exec() メソッドが返す値にはほかの2つのプロパティ(index, input)が含まれる。
  • index プロパティは最初に一致した文字位置が入り、input プロパティは検索された文字列を参照。

RegExp がグローバルな正規表現の場合

  • gフラグのついた正規表現に対して exec() メソッドを使用すると、一致した文字列の直後の文字位置を RegExp オブジェクトの lastIndex プロパティに設定する。
  • 同じ RegExp オブジェクトで exec() をもう一度呼び出すと、lastIndex プロパティが 示す文字位置から検索を開始する。
  • 一致が見つからなかった場合、exec()メソッドは null を返し、lastIndex プロパティに 0 を設定する。
  • while などを使って、exec() を繰り返し呼びだすことで、文字列の全てのマッチを調べられる。(終了/継続の判定は戻り値が null かどうか)
var pattern = /Java/g;
var text = "JavaScript is more fun than Java!";
var result;
while((result = pattern.exec(text)) != null){
  console.log("Matched '" + result[0] + "'" + " at position " + 
    result.index + "; next search begins at " + pattern.lastIndex);
}

console.log() の出力

Matched ‘Java’ at position 0; next search begins at 4
Matched ‘Java’ at position 28; next search begins at 32

NetworkError: 404 Not Found

問題なく表示されるが、コンソールを見ると以下のようなエラーが出ていた。

GET http://localhost/images/header_page.jpg 404 (Not Found) jquery-1.10.1.js:6590

問題なく表示されるので、無視してもいいような気がするが取りあえずエラーが出ないようにしてみた。

load() でセレクタを指定するには URLの後に 1個のスペースに続けてセレクタを書くが、調べて見ると実際には指定したページの HTML 全てが最初にロードされその後セレクタでフィルタされているみたい。

このため、ロードしたページの画像へのパスが異なった階層として読み込まれているようなので、$.ajaxSetup() の dataFilter を使って以下を追加したところ、エラーが消えた。但し、以下の方法はこのサンプル特有な方法なので汎用的ではない。

load() は内部的には $.ajax() を使っているようなので、 $.ajaxSetup() で Ajax リクエストのデフォルト設定を変更。(Ajax リクエストのデフォルト設定の変更はそのサイトの環境により判断する必要があると思うので注意が必要)

  • load() を使用している(階層が異なる)他のページで確認すると同様のエラーが出ていたので、それぞれのページで「images」フォルダの位置(階層)を取得する「return_img_path_depth()」を作成。
  • 「return_img_path_depth()」では、全てのページにあるヘッダー用画像の src 属性を元に「../」の数を取得して階層の深さを返す。
  • $.ajaxSetup() のオプション dataFilter で「return_img_path_depth()」を使ってロードしたコンテンツの画像へのパスを変更
  • 変更がすぐに反映されるようにオプションの「cache」を「false」に設定
function return_img_path_depth() {
  var src = $('#header_img img').attr('src');
  var result = src.match(/(\.\.\/)/g);
  if(result != null) {
    return result.length;
  }else{
    return 0;
  }
}
  
$.ajaxSetup({
  dataFilter: function (response) {
  var parent_path = '';
  var path_depth_count = return_img_path_depth();
  for(var i = 0; i < path_depth_count ; i++){
    parent_path += '../';
  }
  return response.replace(/<img src="(..\/)*images/g, '<img src="' + parent_path + 'images');
  },
  //変更がすぐに反映されるよう「false」に設定
  cache: false
});

match()

書式
string.match(regexp)
引数
マッチングのパターンを表すRegExp オブジェクト
戻り値
マッチングの結果を含む配列。配列の内容は、regexp が g 属性を伴うグローバルな正規表現かどうかによって異なる
  • 引数に正規表現を指定すると、一致した結果を格納した配列が返される。引数に正規表現以外を指定すると、RegExp() コンストラクタに渡して正規表現に変換した上で比較が行われる。
  • 正規表現に g 属性があると一致した全ての文字列を含む配列を返す。
  • g フラグがない場合は、最初に一致した文字列だけを返すが、配列として返される。配列の1番目の要素は一致した文字列、 残りの要素は正規表現の括弧に囲まれた部分表現となる。
  • match() メソッドから返される配列には、他の配列と同じように lenght プロパティがある。
  • グローバル指定をしない(g フラグなし)で呼び出した場合は、length のほかに一致した文字列の開始位置を示す index プロパティと、対象となる文字列のコピーを格納した input プロパティがある。

replace()

文字列で regexp にマッチするサブストリングを探し、replacement でそれらを置換する(文字列に対して検索と置換を行う)。

書式
string.replace(regexp, replacement)
引数
regexp : マッチングのパターンを表すRegExp オブジェクト
replacement : 置換テキストを表す文字列。ドル記号($)はこの置換文字列では特殊な意味を持つ。
戻り値
最初またはすべてのマッチングで replacement によって置換された新しい文字列
  • グローバル検索の g 属性が設定された regexp の場合は、replace() メソッドはすべてのマッチするサブストリングを置換する。
  • g 属性が設定されていない場合は、最初にマッチするサブストリングのみを置換する。
  • ドル記号($)は replacement 文字列では特殊な意味を持つ。
  • 置換文字列の中でドル記号を文字として使用したい場合は、エスケープ文字のバックスラッシュを使用する必要がある(\\$)
  • 以下の表のように、ドル記号($)は置換で使われるパターンマッチングの文字列を示す。
文字 置換
$1,$2, … $9 regexp で最初から9番目までのカッコ付きサブ表現のテキスト
$+ regexp で最後にマッチしたカッコ付きサブ表現
$ regexp にマッチしたサブストリング
$` マッチした文字列の左側のテキスト
$’ マッチしたテキストの右側の文字列
\\$ 文字としてのドル記号

「Davis, Miles」という名前を「Miles Davis」という形式に置換するには、以下のようにする。

name.replace(/(\w+)\s*,\s*(\w+)/, "$2 $1");