概要
次の一連の動作後の詳細ページから、ブラウザバックをした時に、スクロールで表示させた目的の明細が表示領域にある状態にしたい。
- データ一覧をAjaxでページングする
- 目的のデータまでスクロールする
- 目的のデータのリンクをクリックして詳細ページを開く
目的
次の動作を行った時、データ一覧ページにて、クリックしたデータ行が、表示領域にある事
- 明細ページからブラウザバックした時
- 明細ページから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はリクエストするなど、数多
- 画面遷移のデータ保持に、サーバーのセッションを使ったり、ローカルストレージ使用すると、複数タブで作業した時に片方がもう片方に影響を与えるので、リクエストパラメータだけで処理した
(追記:コメント頂いた通りセッションストレージは問題なし)