jiichan.com

PROGRAMMING

javascript
CSS
PHP
Java

ページャーを作る

サイトを構築していると必ずといっていいほどページャーが必要になってきます。
ブログなど日常的にコンテンツが増えるていくページは、ページ分けしなければ下にどんどん長くなり、非常に見ずらいものになります。
そこで、汎用性のあるページャーを作ってみました。

-----2015.02.06(修正)-----
ページ数が多くなると希望のページへの移動が難しくなるので、中央部のページを10件単位でプラスマイナスできるように修正しました。
後、不要なコードなどもありましたので整理しました。

-----2015.06.21(修正)-----
イベント登録のページ番号クリックで、先頭ページと最終ページをクリックした場合、中央のページ番号をそれぞれクリックした側に寄せられるように 修正しました。例えば、先頭ページ(1ページ)の場合はクリックしたら中央ページ番号が2ページから始まるように。(イベント登録 186行~199行追加)

MkenPagerのダウンロード(mkenPager.js)
mkenPager.zip

ページャー用関数を準備

最初にページャー用の関数を準備します。この関数にイベントやスタイルなどを追加していきます。

var MkenPager = function(tagId, totalRow, viewCount){
	// ページャーの表示されるタグ
	this.tagId = tagId;
	// データの総件数
	this.totalRow = Number(totalRow);
	// 1ページに表示する件数
	this.viewCount = Number(viewCount);
	//	現在選択されているページの先頭データの行番号
	this.currentRow = 1;
	// 最終ページ番号
	this.lastPageNum = Math.ceil(this.totalRow / this.viewCount);
};

引数を3個とる関数です。それぞれの引数の意味は、次のとおりです。
1. tagId:ページャーが表示されるHTMLタグのidです(例:<div id="paging"></div>) 2. totalRow:表示するデータの総件数です。 3. viewCount:1ページに表示したい件数です。

プロパティやメソッドの追加(各要素を生成)

前記の関数MkenPagerにプロパティやメソッドを追加していきます。プロトタイプ定義をオブジェクト形式で追加しています。
まずは各要素の生成です。

MkenPager.prototype = {
createHTML: function(){
	// ページャーを囲む枠
	var htmlStr = '<div id="' + this.tagId + 'Pager">';
	// 前へボタン(子の空のdivは三角)
	htmlStr += '<div id="' + this.tagId + 'Prev"><div></div></div>';
	// マイナスボタン(中央部のページを10件マイナスする)
	htmlStr += '<div id="' + this.tagId + 'Minus">-</div>';
	// ページ表示部
	htmlStr += '<ul id="' + this.tagId + 'PageNumGroup">';
	// ページ番号を作成
	var tmpTotalRow = this.totalRow;
	// リストの数は10個で固定
	for(var pageNum = 1; pageNum <= 10; pageNum++){
		if(tmpTotalRow > 0){
			if(pageNum == 10){
				htmlStr += '<li>' + this.lastPageNum + '</li>';
			}else{
				htmlStr += '<li>' + pageNum + '</li>';
			}
		}else{
			htmlStr += '<li class="' + this.tagId + 'nonePage">&nbsp;</li>';
		}
		tmpTotalRow = tmpTotalRow - this.viewCount; 
	}
	htmlStr += '</ul>';
	// プラスボタン(中央部のページを10件プラスする)
	htmlStr += '<div id="' + this.tagId + 'Plus">+</div>';
	// 次へボタン(子の空のdivは三角)
	htmlStr += '<div id="' + this.tagId + 'Next"><div></div></div>';
	htmlStr += '</div>';
	// フロートクリア
	htmlStr += '<div id="' + this.tagId + 'clear"></div>';
	
	var pagerContainer = document.getElementById(this.tagId);
	pagerContainer.innerHTML = htmlStr;
},

プロパティやメソッドの追加(スタイルの設定)

次はスタイルの設定です。長いのでスタイルシートでとも考えましたが、ファイルの扱いも考えコードに埋め込みました。

setStyle: function(){
	// 全体を囲むdiv
	var pager = document.getElementById(this.tagId + 'Pager');
	pager.style.display = "flex";	// floatを使用しない
	pager.style.width = "447px";
	pager.style.height = "36px";
	pager.style.margin = "30px";
	pager.style.border = "1px solid rgba(0,128,128,0.5)";
	pager.style.borderRadius = "15px";
	// 前へボタン
	var prev = document.getElementById(this.tagId + 'Prev');
	prev.style.width = "26px";
	prev.style.height = "26px";
	prev.style.margin = "3px 20px";
	prev.style.borderRadius = "13px";
	prev.style.backgroundColor = "#666666";
	prev.style.cursor = "default";
	prev.style.opacity = 0.5;
	// 前へボタンの三角
	var prevChild = prev.firstChild;
	prevChild.style.width = "0px";
	prevChild.style.height = "0px";
	prevChild.style.borderTop = "6px solid transparent";
	prevChild.style.borderRight = "10px solid #ffffff";
	prevChild.style.borderBottom = "6px solid transparent";
	prevChild.style.borderLeft = "10px solid transparent";
	prevChild.style.margin = "7px 0 0 -3px";
	// 次へボタン
	var next = document.getElementById(this.tagId + 'Next');
	next.style.width = "26px";
	next.style.height = "26px";
	next.style.margin = "3px 20px";
	next.style.borderRadius = "13px";
	next.style.backgroundColor = "#666666";
	next.style.cursor = "pointer";
	// 次へボタンの三角
	var nextChild = next.firstChild;
	nextChild.style.width = "0px";
	nextChild.style.height = "0px";
	nextChild.style.borderTop = "6px solid transparent";
	nextChild.style.borderRight = "10px solid transparent";
	nextChild.style.borderBottom = "6px solid transparent";
	nextChild.style.borderLeft = "10px solid #ffffff";
	nextChild.style.margin = "7px 0 0 9px";
	// マイナスボタン
	var minus = document.getElementById(this.tagId + 'Minus');
	minus.style.width = "23px";
	minus.style.height = "26px";
	minus.style.margin = "4px 0px";
	minus.style.textAlign = "center";
	minus.style.color = "rgba(0,102,153,0.5)";
	minus.style.backgroundColor = "#eee";
	minus.style.fontWeight = "bold";
	minus.style.fontSize = "14px";
	minus.style.border = "1px solid rgba(0,128,128,0.5)";
	minus.style.cursor = "default";
	// プラスボタン
	var plus = document.getElementById(this.tagId + 'Plus');
	plus.style.width = "23px";
	plus.style.height = "26px";
	plus.style.margin = "4px 0px";
	plus.style.textAlign = "center";
	plus.style.color = "rgba(0,102,153,0.5)";
	plus.style.backgroundColor = "#eee";
	plus.style.fontWeight = "bold";
	plus.style.fontSize = "14px";
	plus.style.border = "1px solid rgba(0,128,128,0.5)";
	if(this.lastPageNum < 11){
		plus.style.cursor = "default";
	}else{
		plus.style.cursor = "pointer";
	}
	// 中央部のUL(ページ番号のコンテナ)
	var pageNumGroup = document.getElementById(this.tagId + "PageNumGroup");
	pageNumGroup.style.width = "271px";
	pageNumGroup.style.height = "26px";
	pageNumGroup.style.margin = "4px 0";
	pageNumGroup.style.textAlign = "center";
	pageNumGroup.style.fontSize = "13px";
	// ul内のli (ページ番号部)
	var nodes = pageNumGroup.childNodes;
	for(var i = 0; i < nodes.length; i++){
		nodes[i].style.display = "inline-block";
		nodes[i].style.width = "23px";
		nodes[i].style.height = "26px";
		nodes[i].style.margin = "0 1px";
		nodes[i].style.paddingTop = "1px";
		nodes[i].style.border = "1px solid rgba(0,128,128,0.5)";
		nodes[i].style.color = "#006699";
		if(i === 0){
			nodes[i].style.cursor = "default";
			nodes[i].style.backgroundColor = "rgba(130,210,253,0.5)";
		}else{
			nodes[i].style.cursor = "pointer";
			nodes[i].style.backgroundColor = "rgba(130,210,253,0.1)";
		}
	}
	// 1ページ目と最終ページを外側にずらす
	nodes[0].style.marginRight = "5px";
	nodes[9].style.marginLeft = "5px";
	// ページが無い部分のスタイル
	var nonePage = document.getElementsByClassName(this.tagId + "nonePage");
	for(var i = 0; i < nonePage.length; i++){
		nonePage[i].style.borderColor = "#eeeeee";
		nonePage[i].style.cursor = "default";
	}
},

プロパティやメソッドの追加(イベント登録)

いよいよ難関のイベントです。この部分に何日もかかってしまいました。

setEvent: function(){
	// イベントのthisはインスタンスではなくイベントの発生元を指すので
	// インスタンスは一旦ローカル変数に代入して使用する
	var self = this;
	var lastIdx = Math.floor(self.totalRow / self.viewCount);
	// ページ番号用リストの最後のIndex
	if(lastIdx > 9){lastIdx = 9;}
	// 現在選択されているページ番号用リスト(0~9)
	var currentIdx = 0;
	// 現在選択されているページ番号
	var currentPageNum = 1;
	var prev = document.getElementById(this.tagId + 'Prev');
	var next = document.getElementById(this.tagId + 'Next');
	var minus = document.getElementById(this.tagId + 'Minus');
	var plus = document.getElementById(this.tagId + 'Plus');
	var pageNumGroup = document.getElementById(this.tagId + "PageNumGroup");

	var nodes = pageNumGroup.childNodes;
	// ページ番号クリック **********
	for(var i = 0; i < nodes.length; i++){
		this.addListener(nodes[i], "click", function(){
			// クリックされたインデックスを取得
			var count = 0;
			var node = this;
			// 直前の兄弟がある限りカウンターを増やすことで自分が何番目かがわかる
			while(node.previousSibling) {
				++count;
				node = node.previousSibling;
			}
			
			var oldIdx = currentIdx;	// 直前のliインデックス
			currentIdx = count;

			// 先頭ページの場合、中央のページ番号部分を先頭ページ側に寄せる
			if(currentIdx == 0){
				for(var j = 1; j <= 8; j++){
					nodes[j].childNodes[0].nodeValue = j + 1;
				}
			}
			// 最終ページの場合、中央のページ番号部分を最終ページ側に寄せる
			if(currentIdx == 9){
				var k = 8;
				for(var j = 1; j <= 8; j++){
					nodes[j].childNodes[0].nodeValue = self.lastPageNum - k;
					k--;
				}
			}

			// ページ番号のstyleを元に戻す
			defaultPageStyle(nodes[oldIdx]);
			// クリックされたページ番号のstyleを変える
			newPageStyle(this);

			// 現在のページ番号を取得
			getPageNum(this);
			// 先頭データ行番号を取得
			getTopRowNum();
			// ボタンスタイル変更
			btnStyle(prev, 0);
			btnStyle(next, lastIdx);
		});
	}

	// 前へボタンクリック **********
	this.addListener(prev, 'click', function(){
		if(currentIdx == 0){	// 先頭ページなら何もしない
			return;
		}else{
			// ページ番号のstyleを元に戻す
			defaultPageStyle(nodes[currentIdx]);
			// 最初と2番目リストのページ差が2ページ以上ある場合は
			// 最初と最後を除いた中央部のリストのページ番号を減らす
			if((currentIdx == 1) && ((currentPageNum - 1) >= 2)){
				for(var i = 1; i <= 8; i++){
					var pageNum = Number(nodes[i].childNodes[0].nodeValue);
					nodes[i].childNodes[0].nodeValue = pageNum - 1;
				}
			}else{
				// インデックスを減らす
				currentIdx--;
			}
			// ページ番号の新スタイル
			newPageStyle(nodes[currentIdx]);
			// 現在のページ番号を取得
			getPageNum(nodes[currentIdx]);
			// 先頭データ行番号を取得
			getTopRowNum();
		}
		// ボタンスタイル変更
		btnStyle(prev, 0);
		next.style.cursor = "pointer";
		next.style.opacity = 1;
		// プラスマイナスのカーソル変更
		plusMinusStyle();
	});

	// 次へボタンクリック **********
	this.addListener(next, 'click', function(){
		if(currentIdx == lastIdx){	// 最終ページなら何もしない
			return;
		}else{
			// ページ番号のstyleを元に戻す
			defaultPageStyle(nodes[currentIdx]);
			// 最初と最後を除いたリストのページ番号を増やす
			//(最後とその前のリストのページ差が2ページ以上の場合)
			if((currentIdx == 8) && ((self.lastPageNum - currentPageNum) >= 2)){
				for(var i = 1; i <= 8; i++){
					var pageNum = Number(nodes[i].childNodes[0].nodeValue);
					nodes[i].childNodes[0].nodeValue = pageNum + 1;
				}
			}else{
				// インデックスを増やす
				currentIdx++;
			}
			// ページ番号の新スタイル
			newPageStyle(nodes[currentIdx]);
			// 現在のページ番号を取得
			getPageNum(nodes[currentIdx]);
			// 先頭データ行番号を取得
			getTopRowNum();
		}
		// ボタンスタイル変更
		btnStyle(next, lastIdx);
		prev.style.cursor = "pointer";
		prev.style.opacity = 1;
		// プラスマイナスのカーソル変更
		plusMinusStyle();
	});

	// マイナスボタンクリック **********
	this.addListener(minus, 'click', function(){
		var node1Val = Number(nodes[1].childNodes[0].nodeValue);
		// 2番目のページ番号から10を差し引いた結果が2ページ未満の場合
		if((node1Val - 10) < 2){
			// 3ページから11ページならば中央部のページ番号を1ページ側にずらす
			if(node1Val > 2 && node1Val < (2 + 10)){
				for(var i = 1; i <= 8; i++){
					var pageNum = Number(nodes[i].childNodes[0].nodeValue);
					nodes[i].childNodes[0].nodeValue = pageNum - (node1Val - 2);
				}
			// それ以外は何もしない
			}else{
				return;
			}
		}else{
			// 最初と最後を除いた中央部のリストのページ番号を10づつ減らす
			for(var i = 1; i <= 8; i++){
				var pageNum = Number(nodes[i].childNodes[0].nodeValue);
				nodes[i].childNodes[0].nodeValue = pageNum - 10;
			}
		}
		// 現在のページ番号を取得
		getPageNum(nodes[currentIdx]);
		// 先頭データ行番号を取得
		getTopRowNum();
		// プラスマイナスのカーソル変更
		plusMinusStyle();
	});

	// プラスボタンクリック **********
	this.addListener(plus, 'click', function(){
		var node8Val = Number(nodes[8].childNodes[0].nodeValue);
		// ページ数が10に満たない場合
		if(lastIdx < 9){
			return;
		}
		// 9番目のページ番号に10を足した結果が最後から1ページ前のページ番号を超える場合
		if((node8Val + 10) > (self.lastPageNum - 1)){
			// 9番目のページ番号が(最終ページ番号-10)から(最終ページ-2)の間ならば
			// 中央部のページ番号を最終ページ側にずらす
			if(node8Val >= (self.lastPageNum - 10)
				&& node8Val <= (self.lastPageNum -2)){
				for(var i = 1; i <= 8; i++){
					var pageNum = Number(nodes[i].childNodes[0].nodeValue);
					nodes[i].childNodes[0].nodeValue = 
						pageNum + ((self.lastPageNum - 1) - node8Val);
				}
			// それ以外は何もしない
			}else{
				return;
			}
		}else{
			// 最初と最後を除いた中央部のリストのページ番号を10づつ増やす
			for(var i = 1; i <= 8; i++){
				var pageNum = Number(nodes[i].childNodes[0].nodeValue);
				nodes[i].childNodes[0].nodeValue = pageNum + 10;
			}
		}
		// 現在のページ番号を取得
		getPageNum(nodes[currentIdx]);
		// 先頭データ行番号を取得
		getTopRowNum();
		// プラスマイナスのカーソル変更
		plusMinusStyle();
	});

	//	関数:既定のページ番号のスタイル
	function defaultPageStyle(node) {
		node.style.cursor = "pointer";
		node.style.backgroundColor = "rgba(130,210,253,0.1)";
	}
	// 関数:新たに適用するページ番号のスタイル
	function newPageStyle(node) {
		node.style.cursor = "default";
		node.style.backgroundColor = "rgba(130,210,253,0.5)";
	}
	// 関数:ボタンスタイル変更
	function btnStyle(node, nodeIdx) {
		if(currentIdx == nodeIdx){
			node.style.cursor = "default";
			node.style.opacity = 0.5;
		}else{
			node.style.cursor = "pointer";
			node.style.opacity = 1;
		}
	}
	// 関数:プラスマイナスのカーソル変更
	function plusMinusStyle(){
		var node1 = Number(nodes[1].childNodes[0].nodeValue);
		var node8 = Number(nodes[8].childNodes[0].nodeValue);
		var node9 = Number(nodes[9].childNodes[0].nodeValue);
		if(node1 - 1 == 1){
			minus.style.cursor = "default";
		}else{
			minus.style.cursor = "pointer";
		}
		if(node9 - node8 == 1){
			plus.style.cursor = "default";
		}else{
			plus.style.cursor = "pointer";
		}
	}
	// 関数:現在のページ番号を取得
	function getPageNum(node) {
		var txtNode = node.childNodes[0];
		currentPageNum = Number(txtNode.nodeValue);
	}
	// 関数:先頭データ行番号を取得
	function getTopRowNum() {
		self.currentRow = 1 + (currentPageNum - 1) * self.viewCount;
	}
},

プロパティやメソッドの追加(イベントのクロスブラウザ対策)

おきまりのIE対策。

addListener: function(elm, ev, listener){
	if(elm.addEventListener) {		// IE以外
		elm.addEventListener(ev, listener, false);
	} else if(elm.attachEvent) {	// IE
		elm.attachEvent('on' + ev, listener);
	} else {
		throw new Error('イベントリスナーに未対応です。');
	}
},

プロパティやメソッドの追加(ページャー表示用メソッド)

いままでのメソッドを集めて、ひとつのページャー表示用メソッドにしてあります。
インスタンスの後にこのメソッドを呼び出してページャーを使用します。

showPager: function(){
	this.createHTML(); // 各要素を生成
	this.setStyle();   // スタイルをセット
	this.setEvent();   // イベントをセット
}

ページャーの使い方

このページのページャーサンプルではidがpagingのdivに表示させています。
データの件数は100件、1ページの表示件数は7件で呼び出ししています。

<head>
	<meta charset="UTF-8">
	<title>団塊爺ちゃんの備忘録</title>
	<link rel="stylesheet" href="index.css">
	<script src="jquery-1.8.0.min.js"></script>
	<script src="mkenPager.js"></script>
	<script src="index.js"></script>
</head>
<body>
	<div id="paging"></div> <!--ページャーの表示場所-->

var tagId = "paging";
// データの総件数が100件で1ページに7件づつ表示の場合
var pager = new MkenPager(tagId, 100, 7);
// ページャーの表示
pager.showPager();

// ここからページャー用イベント
// 各ページをクリック
$("#" + tagId + " li").on("click", function(){
	action();
});
// 「前へ」ボタンクリック
$("#" + tagId + "Prev").on("click", function(){
	action();
});
// 「次へ」ボタンクリック
$("#" + tagId + "Next").on("click", function(){
	action();
});
// マイナスボタンクリック
$("#" + tagId + "Minus").on("click", function(){
	action();
});
// プラスボタンクリック
$("#" + tagId + "Plus").on("click", function(){
	action();
});
function action(){
	var msg = 'このページの先頭の行番号は'+pager.currentRow +'行目です。';
	alert(msg);
}

7行目から下がページャーのイベントで、jQueryでイベントを追加しています。
アプリ側で必要なデータはページャーの各プロパティから取得します。
ページの先頭の行番号:pager.currentRow
などです。