JSFiddle - React, Tailwind, and code Playground
HTML
<script src="http://code.jquery.com/jquery-1.10.1.min.js"></script>
<link rel="stylesheet" href="http://www.cssreset.com/downloads/css-resets/html5-doctor-reset-stylesheet/html5-doctor-reset-stylesheet.min.css">
<!-- ページャ View 再利用可能なので WAF での部品化オススメ -->
<ul class="ko-pager" data-bind="with: pager">
<li class="btn btn-small" data-bind="click: goToFirst"> 最初 </li>
<li class="btn btn-small" data-bind="click: goToPrev"> 前ページ </li>
<li class="pager-input" onclick="$(this).find('input').focus();">
<form data-bind="submit: goToInputted">
<input type="text" onfocus="this.select();"
data-bind="value: current_input,
valueUpdate: 'afterkeydown'" />
/<span data-bind="text: pages"> </span>
</form>
</li>
<li class="btn btn-small" data-bind="click: goToNext"> 次ページ </li>
<li class="btn btn-small" data-bind="click: goToLast"> 最後 </li>
<li class="pager-summary" data-bind="visible: !isLoading()">
<!--ko if: count() > 0 -->
<span data-bind="text: count"> </span> 件中
<span data-bind="text: offset() + 1"> </span> ~
<span data-bind="text: offset() + items().length"> </span> 件目を表示中
<!--/ko-->
<!--ko if: count() == 0 -->
アイテムが登録されていません
<!--/ko-->
</li>
<li class="indicator" data-bind="visible: isLoading">
<!-- 実際はローディング画像を入れる -->
now loading ...
</li>
</ul>
<!-- リスト View -->
<table>
<thead>
<tr>
<th>ID</th>
<th>field_a</th>
<th>field_b</th>
</tr>
</thead>
<tbody data-bind="foreach: entries">
<tr>
<td data-bind="text: id"> </td>
<td data-bind="text: field_a"></td>
<td data-bind="text: field_b"></td>
</tr>
</tbody>
</table>
CSS
ul.ko-pager {
background-color: #ABE2EA;
width: 500px;
height: 24px; }
ul.ko-pager > li {
display: inline-block;
font-size: 14px;
padding: 0 4px 0 4px; }
ul.ko-pager > li.pager-input {
font-size: 13px;
margin: 4px 0;
color: #777;
border: solid 1px #aaa;
background-color: white;
padding: 0 2px;
box-shadow: inset 0 0 3px rgba(0, 0, 0, 0.3);
border-radius: 3px; }
ul.ko-pager > li.pager-input input {
width: 35px;
text-align: right;
background-color: transparent;
border: 0;
margin: 0;
padding: 0 2px;
vertical-align: baseline; }
ul.ko-pager > li.pager-summary {
color: #FFF;
text-shadow: 1px 1px 1px #000 }
ul.ko-pager > li.btn {
margin-top: -2px;
cursor: pointer; }
ul.ko-pager > li.btn:hover {
background-color: #bbb; }
table {
width: 500px; }
table td, table th {
padding: 4px;
border: solid 1px #ccc; }
TypeScript
/**
* ページャ ViewModel
* 再利用可能モジュール
*
* @param {ko.observableArray} observableItems アイテムを格納するリスト
* @param {number} [itemsLimit] ページ毎の最大アイテム数
* @constructor
*/
function PagerViewModel(observableItems, itemsLimit) {
var self = this;
self.isLoading = ko.observable(false); // 読み込み中か否か
self.count = ko.observable(0); // アイテム総数
self.current = ko.observable(1); // 現在のページ番号
self.current_input = ko.observable(1); // 現在のページ番号(入力値)
self.limit = ko.observable( itemsLimit || 20 ); // 1ページ中の最大アイテム数
self.pages = ko.computed(function() { // 総ページ数
return Math.ceil(self.count() / self.limit())
});
self.offset = ko.computed(function() { // 表示中のアイテムオフセット
return (self.current() - 1) * self.limit()
});
self.items = observableItems; // アイテムリスト
// ページ移動アクション
self.goTo = function(page) {
if (page < 1) page = 1;
if (page > self.pages()) page = self.pages();
self.current(page);
self.current_input(page);
};
self.goToFirst = function() { self.goTo(1) };
self.goToPrev = function() { self.goTo(self.current() - 1) };
self.goToNext = function() { self.goTo(self.current() + 1) };
self.goToLast = function() { self.goTo(self.pages()) };
self.goToInputted = function() { self.goTo(self.current_input()) };
}
/**
* アプリケーション ViewModel
* @constructor
*/
function AppViewModel() {
var self = this;
// fields
self.api = new DummyApi();
self.entries = ko.observableArray([]);
self.pager = new PagerViewModel(self.entries, 15);
// methods
self.reload = function() {
self.pager.isLoading(true);
self.api
.getEntries( self.pager.offset(), self.pager.limit() )
.then(function(response) {
if (response.result != 'success') {
alert(response.error);
return;
}
...