JavaScript

非同期(jQuery AJAX)でページ移動した時の履歴とかのメモ

「検索結果の次のページ」とか、AJAX使ってリンク先URLの内容を読み込んで表示する時に、「ブラウザの履歴とかアドレスバーをリンク先のものに書き換える」ためのメモです。window.history.pushState()を使います。

「検索結果の次のページ」の例で言えば、ページの内容が1ページ目から2ページ目に変更されたら、ブラウザのアドレスバーやページのタイトルも2ページ目のものにして、履歴も「1ページ目 › 2ページ目」となるようにします。

ブラウザの履歴を追加するには、back()とかでよく使うhistory()にあるメソッドpushState()を使います。

var
$ = jQuery,
d = document;


$(d).ready(function () {
  if ( window.history && window.history.pushState ) {

    $( 'a' ).on('click', function(e) {
      e.preventDefault();
      _reflesh( $(this).attr('href') );
      return false;
    });

  }
});


function _reflesh (url) {
  var $content = $('#content').html( '<p>読み込み中</p>' );

  $.ajax({
    'url': url,
    'dataType': 'html',
    'cache': false
  })
  .then(function(data) {
    var $nextcontent = $(data).find('#content');
    $content.html( ( $nextcontent.size() ) ? $nextcontent.html() : '<p>内容がありません。</p>' );
    window.history.pushState(null, null, url);
  },function(xhr, txtstat) {
    $content.html( '<p>エラーのため表示できません。</p>' );
  })
}

リンクがクリックされたら_reflesh()関数を呼んで非同期でリンク先のコンテンツを取得します。成功したら<div id="content" />の内容を置き換えるようにしてwindow.history.pushState()で履歴を追加するようにします。

この簡単な例では$('a')にリスナ登録してますが、実際には自サイト内へのリンクだけに限定する必要もあるでしょう。その他、ローディング表示するとか、タイトルも変更するとかもあったほうが良いと思いますがここでやるとややこしいので割愛します。

history.pushState()は、第三引数に履歴に追加する新しいURLを指定します。第一、第二の引数はnullのまま使うことが多いので、詳しい内容はMDNとかで確認してください。

ブラウザの履歴を操作する - Web developer guide | MDN

ちなみに、自身もど忘れしてハマって地味に腹立つことあるんですが、$.ajaxで通信完了後に取得するHTMLをうまく利用できない時があります。上の例だと、通信成功時に実行する関数(thenの最初の関数)で取得するdataが、テキストでは<div id="content" />の存在を確認できるのに$(data).find('#content')とかだと要素が見つからなかったりします。

〜
  .then(function(data) {
    var $nextcontent = $(data).find('#content'); // < うまく取得できないことがある
    var $nextcontent = $('#content', $.parseHTML(data)); // < そんな時は何やってもダメ
〜

これ、$(data).find('#content')<body>直下にあったりするとうまくいかないことがあるんで

<!DOCTYPE html>
<html lang="ja">
<head>
<title>タイトル</title>
</head>
<body>
<div id="content">
<p>内容</p>
</div>
</body>
</html>
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8" />
<title>タイトル</title>
</head>
<body><div id="wrapper"><!-- body直下に #wrapper を追加 -->
<div id="content">
<p>内容</p>
</div>
</div></body>
</html>

みたいにして、一つ階層増やしてやると直ることがあります。原因は同じだと思うんですが、<title>とか<head>直下の要素もうまく取得できないことがあったので、マッチングさせて拾ったりしてました。

// ページタイトル
  data.match(/<title>(.*?)<\/title>/);
  document.title = RegExp.$1;

もしjQueryのajaxで要素の取得に失敗するときはお試し下さい。