observeableArray
にバインドするように設定されたdivがありますが、そのobserveableArray
から最大50項目のみを表示したい場合があります。ユーザーがコレクションのアイテムのページを循環できるように、ページのインデックスと一緒に「前へ」ボタンと「次へ」ボタンでページ分割してこれを処理したいと思います。
私はおそらくcomputedObservable
とカスタムデータバインディングでこれを行うことができることを知っていますが、それを行う方法がわかりません(私はまだノックアウトの初心者です)。
誰かが私を正しい方向に向けることができますか?
これが私のコードです(JSはTypeScriptにあります):
<div class="container-fluid">
<div class="row-fluid">
<div class="span12">
<%=
if params[:q]
render 'active_search.html.erb'
else
render 'passive_search.html.erb'
end
%>
<%= form_tag("/search", method: "get", :class => "form-search form-inline") do %>
<%= label_tag(:q, "Search for:") %>
<%= text_field_tag(:q, nil, class:"input-medium search-query") %>
<%= submit_tag("Search", :class=>"btn") %>
<% end %>
<div class="media" data-bind="foreach: tweetsArray">
<%= image_tag('Twitter-icon.svg', :class=>"Tweet_img", :style=>"display:inline;") %>
<div class="media-body" style="display:inline;">
<h4 class="media-heading" data-bind="text: user.screen_name" style="display:inline;"></h4>
<span data-bind="text:text" style="display:inline;"></span> <br />
<span data-bind="text:'Created at '+created_at"></span> <br />
</div>
</div>
<div class="pagination pagination-centered">
<ul>
<li>
<a href="#">Prev</a>
</li>
<li>
<a href="#">1</a>
</li>
<li>
<a href="#">Next</a>
</li>
</ul>
</div>
</div>
</div>
</div>
<script>
var viewModel = new twitterResearch.TweetViewModel();
ko.applyBindings(viewModel);
//TODO: notes to self, use custom binding for pagination along with a computed observable to determine where at in the list you are
//document.onReady callback function
$(function() {
$.getJSON('Twitter', {}, function(data) {
viewModel.pushTweet(data);
console.log(data.user);
});
});
</script>
declare var $: any;
declare var ko: any;
module twitterResearch {
class Tweet {
text: string;
created_at: string;
coordinates: string;
user: string;
entities: string;
id: number;
id_str: string;
constructor(_text: string, _created_at: string, _coordinates: any, _user: any,
_entities: any, _id_str: string, _id: number){
this.text = _text;
this.created_at = _created_at;
this.coordinates = _coordinates;
this.user = _user;
this.entities = _entities;
this.id_str = _id_str;
this.id = _id;
}
}
export class TweetViewModel{
tweetsArray: any;
constructor()
{
this.tweetsArray = ko.observableArray([]);
}
//Tweet is going to be the JSON Tweet we return
//from the server
pushTweet(Tweet)
{
var _Tweet = new Tweet(tweet.text, Tweet.created_at, Tweet.coordinates,
Tweet.user, Tweet.entities, Tweet.id_str, Tweet.id);
this.tweetsArray.Push(_Tweet);
this.tweetsArray.valueHasMutated();
}
}
}
Knockoutを使用すると、ページ分割は非常に簡単です。私は個人的にそれをこの方法で達成します:
そのため、現在のページをインクリメント(次へ)またはデクリメント(前へ)する関数を追加できます。
ここに簡単な例があります:
var Model = function() {
var self = this;
this.all = ko.observableArray([]);
this.pageNumber = ko.observable(0);
this.nbPerPage = 25;
this.totalPages = ko.computed(function() {
var div = Math.floor(self.all().length / self.nbPerPage);
div += self.all().length % self.nbPerPage > 0 ? 1 : 0;
return div - 1;
});
this.paginated = ko.computed(function() {
var first = self.pageNumber() * self.nbPerPage;
return self.all.slice(first, first + self.nbPerPage);
});
this.hasPrevious = ko.computed(function() {
return self.pageNumber() !== 0;
});
this.hasNext = ko.computed(function() {
return self.pageNumber() !== self.totalPages();
});
this.next = function() {
if(self.pageNumber() < self.totalPages()) {
self.pageNumber(self.pageNumber() + 1);
}
}
this.previous = function() {
if(self.pageNumber() != 0) {
self.pageNumber(self.pageNumber() - 1);
}
}
}
シンプルで完全な例がここにあります: http://jsfiddle.net/LAbCv/ (少しバグがあるかもしれませんが、アイデアはそこにあります)。
実際、私は多くのテーブルがあるWebサイトで作業しています(ほとんどはページングが必要です)。
実際には、ページングが必要なすべてのケースで使用するために、ページングに_reusable-component
_が必要でした。
また、この質問への回答で認められているよりも高度な機能が必要でした。
そこで、この問題を解決するための独自のコンポーネントを開発しました。
さらに詳細については、引き続き読んでください(ここからではなく、GitHubからコードを取得することを検討してください。GitHubコードは、ここに配置してから更新および拡張されているためです)
JavaScript
_function PagingVM(options) {
var self = this;
self.PageSize = ko.observable(options.pageSize);
self.CurrentPage = ko.observable(1);
self.TotalCount = ko.observable(options.totalCount);
self.PageCount = ko.pureComputed(function () {
return Math.ceil(self.TotalCount() / self.PageSize());
});
self.SetCurrentPage = function (page) {
if (page < self.FirstPage)
page = self.FirstPage;
if (page > self.LastPage())
page = self.LastPage();
self.CurrentPage(page);
};
self.FirstPage = 1;
self.LastPage = ko.pureComputed(function () {
return self.PageCount();
});
self.NextPage = ko.pureComputed(function () {
var next = self.CurrentPage() + 1;
if (next > self.LastPage())
return null;
return next;
});
self.PreviousPage = ko.pureComputed(function () {
var previous = self.CurrentPage() - 1;
if (previous < self.FirstPage)
return null;
return previous;
});
self.NeedPaging = ko.pureComputed(function () {
return self.PageCount() > 1;
});
self.NextPageActive = ko.pureComputed(function () {
return self.NextPage() != null;
});
self.PreviousPageActive = ko.pureComputed(function () {
return self.PreviousPage() != null;
});
self.LastPageActive = ko.pureComputed(function () {
return (self.LastPage() != self.CurrentPage());
});
self.FirstPageActive = ko.pureComputed(function () {
return (self.FirstPage != self.CurrentPage());
});
// this should be odd number always
var maxPageCount = 7;
self.generateAllPages = function () {
var pages = [];
for (var i = self.FirstPage; i <= self.LastPage() ; i++)
pages.Push(i);
return pages;
};
self.generateMaxPage = function () {
var current = self.CurrentPage();
var pageCount = self.PageCount();
var first = self.FirstPage;
var upperLimit = current + parseInt((maxPageCount - 1) / 2);
var downLimit = current - parseInt((maxPageCount - 1) / 2);
while (upperLimit > pageCount) {
upperLimit--;
if (downLimit > first)
downLimit--;
}
while (downLimit < first) {
downLimit++;
if (upperLimit < pageCount)
upperLimit++;
}
var pages = [];
for (var i = downLimit; i <= upperLimit; i++) {
pages.Push(i);
}
return pages;
};
self.GetPages = ko.pureComputed(function () {
self.CurrentPage();
self.TotalCount();
if (self.PageCount() <= maxPageCount) {
return ko.observableArray(self.generateAllPages());
} else {
return ko.observableArray(self.generateMaxPage());
}
});
self.Update = function (e) {
self.TotalCount(e.TotalCount);
self.PageSize(e.PageSize);
self.SetCurrentPage(e.CurrentPage);
};
self.GoToPage = function (page) {
if (page >= self.FirstPage && page <= self.LastPage())
self.SetCurrentPage(page);
}
self.GoToFirst = function () {
self.SetCurrentPage(self.FirstPage);
};
self.GoToPrevious = function () {
var previous = self.PreviousPage();
if (previous != null)
self.SetCurrentPage(previous);
};
self.GoToNext = function () {
var next = self.NextPage();
if (next != null)
self.SetCurrentPage(next);
};
self.GoToLast = function () {
self.SetCurrentPage(self.LastPage());
};
}
_
[〜#〜] html [〜#〜]
_<ul data-bind="visible: NeedPaging" class="pagination pagination-sm">
<li data-bind="css: { disabled: !FirstPageActive() }">
<a data-bind="click: GoToFirst">First</a>
</li>
<li data-bind="css: { disabled: !PreviousPageActive() }">
<a data-bind="click: GoToPrevious">Previous</a>
</li>
<!-- ko foreach: GetPages() -->
<li data-bind="css: { active: $parent.CurrentPage() === $data }">
<a data-bind="click: $parent.GoToPage, text: $data"></a>
</li>
<!-- /ko -->
<li data-bind="css: { disabled: !NextPageActive() }">
<a data-bind="click: GoToNext">Next</a>
</li>
<li data-bind="css: { disabled: !LastPageActive() }">
<a data-bind="click: GoToLast">Last</a>
</li>
</ul>
_
特徴
必要に応じて表示
ページングがまったく必要ない場合(たとえば、ページサイズより小さく表示する必要があるアイテムなど)、HTML
コンポーネントは表示されなくなります。
これは、statement _data-bind="visible: NeedPaging"
_によって確立されます。
必要に応じて無効にする
たとえば、すでに最後のページを選択している場合、なぜ_last page
_またはNext
ボタンを押すことができるはずなのですか?
私はこれを処理しており、その場合、次のバインディングを適用してこれらのボタンを無効にしますdata-bind="css: { disabled: !PreviousPageActive() }"
選択したページを区別します
特別なクラス(この場合はactive
クラスと呼ばれます)が選択されたページに適用され、ユーザーが現在どのページにいるかがわかります。
これはバインディングによって確立されますdata-bind="css: { active: $parent.CurrentPage() === $data }"
最後と最初
最初と最後のページへの移動は、専用の簡単なボタンでも実行できます。
表示されるボタンの制限
1000ページなど、多くのページがあるとすると、どうなりますか?それらをすべてユーザーに表示しますか? 絶対にありません現在のページに従ってそれらのいくつかだけを表示する必要があります。たとえば、選択したページの前の3ページと後の3ページを表示します。
このケースはここで処理されました<!-- ko foreach: GetPages() -->
すべてのページを表示する必要があるかどうかを判断するために単純なアルゴリズムを適用するGetPages
関数(ページ数がしきい値を下回っており、簡単に判断できる)、または一部のみを表示するボタン。maxPageCount
変数の値を変更することで、しきい値を決定できます
今、次の_var maxPageCount = 7;
_として割り当てました。つまり、ユーザー(SelectedPageの前に3つ、Selected Pageの後に3つ)とSelected Page自体に表示できるボタンは7つまでです。
疑問に思うかもしれませんが、現在のページの前に[〜#〜]または[〜#〜]の後に十分なページがなかった場合表示する? 心配しないでください私はこれをアルゴリズムで処理しています
たとえば、_11 pages
_があり、_maxPageCount = 7
_と現在の_selected page is 10
_がある場合、次のページが表示されます5,6,7,8,9,10(selected page),11
そのため、選択したページの前に_5
_ページを表示し、選択したページの後に_1
_ページを表示する前の例では、常にmaxPageCount
を階層化しています。
選択されたページの検証
ユーザーが選択したページを決定するCurrentPage
オブザーバブルのすべてのセット操作は、関数SetCurrentPage
を通過します。この関数でのみ、このオブザーバブルを設定し、コードからわかるように、値を設定する前に検証操作を行って、ページの使用可能なページを超えないようにします。
すでにきれい
私はpureComputed
プロパティのみを使用しており、computed
プロパティは使用していません。つまり、これらのプロパティのクリーニングや破棄に煩わされる必要はありません。 ただし、以下の例でわかるように、コンポーネント自体の外部にある他のサブスクリプションを破棄する必要があります
注1
このコンポーネントでいくつかのbootstrap
クラスを使用していることに気付くかもしれませんが、これは私に適していますが、、もちろん、を使用できますbootstrapクラスではなく、独自のクラス。
ここで使用したbootstrapクラスは、pagination
、_pagination-sm
_、active
およびdisabled
です。
必要に応じて自由に変更してください。
注2
コンポーネントを紹介しました。それがどのように機能するかを確認する時が来ました。
このようにして、このコンポーネントをメインのViewModelに統合します。
_function MainVM() {
var self = this;
self.PagingComponent = ko.observable(new Paging({
pageSize: 10, // how many items you would show in one page
totalCount: 100, // how many ALL the items do you have.
}));
self.currentPageSubscription = self.PagingComponent().CurrentPage.subscribe(function (newPage) {
// here is the code which will be executed when the user changes the page.
// you can handle this in the way you need.
// for example, in my case, I am requesting the data from the server again by making an ajax request
// and then updating the component
var data = /*bring data from server , for example*/
self.PagingComponent().Update({
// we need to set this again, why? because we could apply some other search criteria in the bringing data from the server,
// so the total count of all the items could change, and this will affect the paging
TotalCount: data.TotalCount,
// in most cases we will not change the PageSize after we bring data from the server
// but the component allows us to do that.
PageSize: self.PagingComponent().PageSize(),
// use this statement for now as it is, or you have to made some modifications on the 'Update' function.
CurrentPage: self.PagingComponent().CurrentPage(),
});
});
self.dispose = function () {
// you need to dispose the manual created subscription, you have created before.
self.currentPageSubscription.dispose();
}
}
_
最後ですが重要です、HTMLコンポーネントのバインディングを特別なviewModelに従って変更することを忘れないでください、またはこのようにすべてのコンポーネントを_with binding
_でラップしてください
_<div data-bind="with: PagingComponent()">
<!-- put the component here -->
</div>
_
乾杯
この質問は、依然として「ノックアウトページネーション」の上位の検索の1つなので、ノックアウト拡張機能 knockout-paging ( git )は言及する価値があります。
これは、ko.observableArray
。十分に文書化されており、使いやすいです。
使用例は here です。