[ページ遷移しないWebシステム] JavaScriptによるURLの操作と、ページ遷移しないプログラム

　ページ遷移しないWebシステムを作成する場合、いくつかの問題点に遭遇します。今回はその一つである、ブラウザの戻るボタン問題について解決していきます。


####以前の内容
[ページ遷移しないWebシステム] JavaScriptによるWindow Framework
https://qiita.com/SoraKumo/items/18197b5b61cf8061ae5d
[ページ遷移しないWebシステム]Qiitaの投稿をページ遷移無しで表示してみる
https://qiita.com/SoraKumo/items/0e017195d4ad8a88d762


####関連リンク
サンプルプログラム　　https://mofon001.github.io/npage/
サンプルソース　　　　https://github.com/mofon001/npage
ページ遷移しないBlog　https://croud.jp/


# 戻れない戻るボタン
　たとえば、JavaScriptでページ遷移しないブログシステムがあったとします。Ajaxによってコンテンツを非同期で拾ってきます。ユーザは操作を行い、いくつかの記事を切り替えました。そして、ふと、一つ前の記事が読みたくなり、ブラウザの戻るボタンを押します。

###「うわぁぁ、別のサイトに切り替わったぁぁぁ！！！！」


　そうです、JavaScriptで画面を書き換えたページは、ブラウザの履歴には残らないのです。そして戻るボタンによって、一つ前に見ていたサイトに切り替わるのです。

# オーイ、ミズシマ、イッショニ、マエノキジヘカエロウ
　このネタが分かる人は少なそうです。それはどうでもいいとして、解決方法を考えます。実はJavaScriptには、履歴を操る方法が存在しているのです。

　重要なのは以下の三つです

- location.search
- history.pushState
- popstateイベント

　これらを利用して、理想的な戻るボタンの動きを実現します。

#ページの構築手順

　以下の手順に従えば、だいたいうまくいきます。

1. location.searchからページに関するパラメータを抽出
2. パラメータに従い画面を書き換える
3. ページ切り替えの操作が行われたら、history.pushStateで履歴をねつ造する
4. ブラウザの戻るや進むボタンが押された場合、popstateが呼び出されるのでlocation.searchのパラメータに従い画面を書き換える

　popstateで画面の書き換えを行う場合は、history.pushStateは呼び出してはいけません。既に履歴に入っているからです。


#サンプルプログラム
　御託は良いから、とっとと出すもの出せやといわれそうなので、出しますサンプル

```index.html
<!DOCTYPE html>
<html>
<head>
	<meta charset="UTF-8"/>
	<link rel="stylesheet" href="Main.css"/>
	<script type="text/javascript" src="Main.js"></script>
	<title>ページ遷移しないための履歴制御</title>
</head>
<body>
</body>
</html>
```
```Main.css
div:nth-child(1){
	background-color: #cccccc;
	padding: 10px;
}
div:nth-child(2){
	margin:10px 0px;
	background-color: #88ff88;
	padding: 10px;
}

div:nth-child(3){
	border: 1px solid;
	height: 300px;
	padding: 20px;
}
```
```Main.js
(function(){
//最初に実行するファンクションを設定
document.addEventListener("DOMContentLoaded",onLoad);

//ページが読み込まれた際に最初に呼び出される
function onLoad(){
	//--------------------------------------------
	//ページタイトルと経過時間表示場所の作成
	var header = document.createElement("div");
	document.body.appendChild(header);
	header.innerHTML = "<span>ページ遷移しないプログラム 経過時間:</span><span>0</span>";
	//ページが遷移していないことを確認するため、経過時間を表示
	var t = header.querySelector("span:nth-child(2)");
	setInterval(function(){t.textContent=parseInt(t.textContent)+1},1000);

	//--------------------------------------------
	//ページ切り替えボタンの作成
	var menu = document.createElement("div");
	document.body.appendChild(menu);
	menu.innerHTML = "<button>ページ0</button><button>ページ1</button><button>ページ2</button>";
	var buttons = menu.querySelectorAll("button");
	buttons.forEach(function(v,i){v.addEventListener("click",function(){changePage(i,true)})})

	//--------------------------------------------
	//メインメッセージ表示領域の作成
	var main = document.createElement("div");
	document.body.appendChild(main);

	//--------------------------------------------
	//URLのパラメータ部分から、表示ページを切り替え
	function goLocation(){
		//パラメータの読み出し
		var p = {};
	    location.search.substring(1).split('&').forEach(function(v){s=v.split('=');p[s[0]]=s[1];});
		//指定ページに飛ぶ
		changePage(p['p']>=0?p['p']:0,false);
	}

	//--------------------------------------------
	//ページ切り替えとブラウザの履歴管理
	function changePage(page,flag){
		//flagがtrueなら、ページの状態を履歴に保存
		if(flag)
			history.pushState(null,null,"?p="+page);
		//ページ内容を書き換え
		main.innerHTML = ["あいうえお","かきくけこ","さしすせそ"][page];
	}

	//ブラウザの「戻る」「進む」ボタンが押された場合のイベント処理
	addEventListener('popstate', function(){goLocation();}, false);
	//初期ページへ飛ぶ
	goLocation();
}

})();
```


　私がWeb系のプログラムを作る際は、ちょっとした例外を除けばBODYタグの中は空です。ページ遷移しないプログラムは、基本的にベタでHTMLを書きません。JavaScriptで生成するのが基本なのです。HTMLを書くのは一番最初にjsファイルを読み出す部分と、innerHTMLなどに突っ込む部分だけです。

　それから今回、DOMのノードを探す際、セレクターにclassやidを指定していません。使ってはいけない訳ではありませんが、それらに頼らないで目的のものが指定できるぐらいには、セレクターの指定方法は覚えておいた方が後々のためです。



#実行画面
![image.png](https://qiita-image-store.s3.amazonaws.com/0/267629/d75a2266-a4c7-8b91-26ab-0a979029692c.png)

　ボタンを押すと内容が書き換わり、ブラウザのURLが変化します。そしてページ遷移が起こっていないことを確認するため、経過時間を表示しています。ボタンを何回か押した後、ブラウザの戻るボタンや進むボタンを押してみてください。ページ遷移せずに、必要な場所だけ書き換わるはずです。



#SEO対策
　実は今回の内容は、ブラウザの戻るボタンだけの問題ではありません。SEO対策でも必要になるのです。サーチエンジンはURLが同一だと、違う内容が表示されていたとしても、別々のデータとしてキャッシュしてくれません。当たり前といえば当たり前です。今回のようにパラメータに意味を持たせれば、違うページとしてキャッシュしてくれるのです。

　また、Googleで確認した結果、JavaScriptで動的に作成したページでも、きちんと内容を読み取ってキャッシュされました。ただしURLにパラメータを与えるのは必須条件です。


#まとめ
　ページ遷移しないWebシステムは、ページのルーティングがサーバサイドにある限り実現できません。有名どころのフレームワークは壊滅です。

　ページ遷移しないフレームワークを作るならば、クライアントサイドでルーティングさせる必要があります。その部分を作るのは、それほど難しいことではありません。結局の所、コントローラに相当する機能をクライアントに持っていくことになるので、距離ができてしまうサーバサイドとのデータのやりとりを、いかに簡潔に記述できるかが重要になります。


　そんなこんなで何年か後には、ページ遷移しないことを前提としたフレームワークが当たり前の世の中になっていて欲しいところです。