search
LoginSignup
12

posted at

updated at

Ajaxでページングした一覧ページから詳細ページへ遷移後にブラウザバックで一覧に戻っても、GETで一覧に戻っても、POSTで一覧に戻っても、F5でリロードしてもクリックした明細が画面内に表示されるサンプル

概要

次の一連の動作後の詳細ページから、ブラウザバックをした時に、スクロールで表示させた目的の明細が表示領域にある状態にしたい。

  1. データ一覧をAjaxでページングする
  2. 目的のデータまでスクロールする
  3. 目的のデータのリンクをクリックして詳細ページを開く

目的

次の動作を行った時、データ一覧ページにて、クリックしたデータ行が、表示領域にある事

  • 明細ページからブラウザバックした時
  • 明細ページからGETやPOSTのサブミットで一覧ページに戻った時
  • 一覧ページでF5でリロードした時
  • 一覧ページでCtrl+F5でキャッシュ削除リロードした時
  • 一覧ページでブラウザバックした時に前に表示していたページを表示とともに、前回選択したデータ行にフォーカス
  • 複数のタブで開いても、独立して処理される事。別のタブに影響を与えない事。サーバーのセッションやローカルストレージを使用しない事。(追記:セッションストレージは使える)

サンプルのURL

Ajaxでページングして正しく表示領域の更新と履歴の更新をする

サンプルプログラム

index.php
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>Ajaxでページングして正しく表示領域の更新と履歴の更新をする</title>
  <script src="//ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
  <script>

      /**
       * Get the URL parameter value
       *
       * @see https://www-creators.com/archives/4463
       * @param  name {string} パラメータのキー文字列
       * @return  url {url} 対象のURL文字列(任意)
       */
      function getParam(name, url) {
          if (! url) url = window.location.href;
          name = name.replace(/[\[\]]/g, "\\$&");
          var regex = new RegExp("[?&]"+name+"(=([^&#]*)|&|#|$)"),
              results = regex.exec(url);
          if (! results) return null;
          if (! results[2]) return '';
          return decodeURIComponent(results[2].replace(/\+/g, " "));
      }

      window.addEventListener('pageshow', function (e) {
          const row_no = getParam('row_no');
          if (row_no) {
              forcusAndHighlightRow(row_no);
          }
          else {
              forcusAndHighlightRow($('input[name="row_no"]').val());
          }
      });

      window.addEventListener('popstate', function (e) {
          if (window.history && window.history.pushState) {
              const state = e.state;
              if (state) {
                  openPage(state);
              }
              else {
                  openPage({'page_no': 0, 'row_no': 0});
              }
          }
      });

      window.addEventListener('DOMContentLoaded', function () {

          // 詳細ページクリックへのバインド
          bindLinkDetail();

          // ページング番号クリックへのバインド
          $('.change-page').unbind('click').bind('click', function () {
              const page_no = $(this).attr('page_no');
              const state = {
                  'page_no': page_no,
                  'row_no': 0
              };
              openPage(state);
              if (window.history && window.history.pushState) {
                  const url = 'index.php?page_no='+state.page_no;
                  window.history.pushState(state, '', url);
              }
          });
      });

      /**
       *
       * @param state
       */
      function openPage(state) {
          if (state.page_no < 1) {
              return;
          }
          $.get("table.php", {'page_no': state.page_no}, function (html) {
              html = '<div>Ajaxで取得したコンテンツ</div>'+html;
              $('#page-list').html(html);
              // 詳細ページクリックへのバインド
              bindLinkDetail();
              forcusAndHighlightRow(state.row_no);
          });
      }

      /**
       * 詳細ページクリックへのバインド
       */
      function bindLinkDetail() {
          $('.a-detail').unbind('click').bind('click', function () {

              $('#page-list').find('tr').removeClass('current');
              $(this).closest('tr').addClass('current');

              const page_no = $(this).attr('page_no');
              $('input[name="page_no"]').val(page_no);

              const row_no = $(this).attr('row_no');
              $('input[name="row_no"]').val(row_no);

              if (window.history && window.history.pushState) {
                  const state = {
                      'page_no': page_no,
                      'row_no': row_no
                  };
                  const url = 'index.php?page_no='+page_no+'&row_no='+row_no;
                  window.history.pushState(state, '', url);
              }
              $(this).closest('form').submit();
              return false;
          });
      }

      /**
       *
       * @param row_no
       */
      function forcusAndHighlightRow(row_no) {
          if (row_no < 1) {
              $('#page-list').scrollTop(0);
              return;
          }
          $('.page-list').find('tr').removeClass('current');
          const row = $('#row'+row_no);
          row.addClass('current');
          const div = $('#page-list');
          div.scrollTop(row.position().top-(div.height() / 2)+row.height());
      }
  </script>
  <style>
   .current td {
    border: 3px solid blue;
   }
  </style>
</head>
<body>
<form method="POST" action="detail.php">
  <input type="hidden" name="page_no" value="<?php echo (array_key_exists('page_no', $_REQUEST)) ? $_REQUEST['page_no'] : ''; ?>"/>
  <input type="hidden" name="row_no" value="<?php echo (array_key_exists('row_no', $_REQUEST)) ? $_REQUEST['row_no'] : ''; ?>"/>
  <a href="javascript:void(0);" class="change-page" page_no="1">1ページ目</a>
  <a href="javascript:void(0);" class="change-page" page_no="2">2ページ目</a>
  <a href="javascript:void(0);" class="change-page" page_no="3">3ページ目</a>
  <a href="javascript:void(0);" class="change-page" page_no="4">4ページ目</a>
  <a href="javascript:void(0);" class="change-page" page_no="5">5ページ目</a>

  <div id="page-list" style="height:500px;overflow:auto;border:1px solid #000;">
    <div>初期表示のコンテンツ</div>
      <?php
      // F5押下時の初期表示処理
      if (array_key_exists('page_no', $_REQUEST) && 0 < $_REQUEST['page_no']) {
          require './table.php';
      }
      ?>
  </div>
</form>
</body>
table.php
<table>
    <?php for ($i = 1; $i <= 100; $i++): ?>
      <tr id="row<?php echo $i; ?>">
        <td>
          <a class="a-detail"
             page_no="<?php echo $_REQUEST['page_no']; ?>"
             row_no="<?php echo $i; ?>"
             href="detail.php?page_no=<?php echo $_REQUEST['page_no']; ?>&row_no=<?php echo $i; ?>">
              <?php echo $_REQUEST['page_no']; ?>ページ目の<?php echo $i; ?>番の詳細
          </a>
        </td>
      </tr>
    <?php endfor; ?>
</table>
detail.php
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>明細ページ</title>
</head>
<body>

このページは<?php echo $_REQUEST['page_no']; ?>ページ目の<?php echo $_REQUEST['row_no']; ?>の詳細ページです。
<br>
ブラウザバックで戻った時の動作とフォームのPOSTで戻った時の動作確認用

<fieldset>
  <legend>method="GET"</legend>
  <form method="GET" action="index.php">
    <input type="hidden" name="page_no" value="<?php echo $_REQUEST['page_no']; ?>"/>
    <input type="hidden" name="row_no" value="<?php echo $_REQUEST['row_no']; ?>"/>
    <input type="submit" value="GETサブミット"/>
  </form>
</fieldset>

<fieldset>
  <legend>method="POST"</legend>
  <form method="POST" action="index.php">
    <input type="hidden" name="page_no" value="<?php echo $_REQUEST['page_no']; ?>"/>
    <input type="hidden" name="row_no" value="<?php echo $_REQUEST['row_no']; ?>"/>
    <input type="submit" value="POSTサブミット"/>
  </form>
</fieldset>

</body>
</html>

ハマりどころ

  • ブラウザバックでFirefoxはキャッシュ表示、Chromeはリクエストするなど、数多
  • 画面遷移のデータ保持に、サーバーのセッションを使ったり、ローカルストレージ使用すると、複数タブで作業した時に片方がもう片方に影響を与えるので、リクエストパラメータだけで処理した
    (追記:コメント頂いた通りセッションストレージは問題なし)

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
12