Skip to content

Instantly share code, notes, and snippets.

@begedin
Last active August 29, 2015 14:01
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save begedin/4dc2ffc3052a7e9c171a to your computer and use it in GitHub Desktop.
Save begedin/4dc2ffc3052a7e9c171a to your computer and use it in GitHub Desktop.
Simple implementation of paging on a collection in knockout.
function Paging(itemSet) {
/// <summary>Component used to apply paging on a provided collection. Requires knockout.</summary>
/// <param name="itemSet" type="ko.observableArray or Array">Collection to apply the paging on.</param>
var self = this;
self.page = ko.observable(1); // currently active page, starts from 1 for increased "user friendliness"
self.itemsPerPage = ko.observable(20); // currently selected number of items per page
self.itemsPerPageOptions = [10, 20, 50, 100]; // available options for number of items per page
self.total = ko.computed(function () {
/// <summary>Counts total number of items in the currently assigned collection.</summary>
return itemSet().length;
});
var totalPages = ko.computed(function () {
/// <summary>Returns total number of pages based on current collection size and page size setting</summary>
// how many full pages are there?
var maxPages = Math.floor(self.total() / parseInt(self.itemsPerPage(), 10));
// is there an extra "not full" page
if ((self.total() % parseInt(self.itemsPerPage(), 10)) > 0) maxPages++;
// if there's 0 items in the collection, a single empty page still should be shown
return maxPages > 0 ? maxPages : 1;
});
self.pageContent = ko.computed(function () {
/// <summary>Content of the current page. A total of <itemsPerPage> items</summary>
var startIndex = (self.page() - 1) * parseInt(self.itemsPerPage(), 10);
var endIndex = startIndex + parseInt(self.itemsPerPage(), 10);
// Filtered items do not need to be an observable array.
// A computed is in itself an observable.
var filteredItems = [];
var items = ko.utils.unwrapObservable(itemSet);
// The loop is only as long as a single page.
for (var i = startIndex; i < endIndex; i++) {
if (i < items.length) {
filteredItems.push(items[i]);
} else break;
}
return filteredItems;
});
self.itemsPerPage.subscribe(function (newValue) {
// when the itemsPerPage option changes, we need to see if the current page is
// larger than the total number of pages for the new option value and set it to the new max in that case
if (self.page() > totalPages()) self.page(totalPages());
});
totalPages.subscribe(function (newValue) {
// when the total number of pages changes, we need to see if the current page
// is larger than the new total and set it to the new total in that case
if (self.page() > newValue) self.page(newValue);
});
self.pageNavigation = ko.computed(function () {
/// <summary>Computes an array of "shortcuts" to navigate to specific page. The computed array is at most in range [currentPage - 4, currentPage + 5]</summary>
var pages = [], start = self.page() - 2;
if (start < 1) start = 1;
var end = start + 4;
if (end > totalPages()) end = totalPages();
while (start <= end) {
pages.push(start++);
};
return pages;
});
self.setPage = function (index) {
self.page(index);
}
self.nextPage = function () {
/// <summary>Moves to next page</summary>
self.page(self.page() + 1); // no need to do checks, we enable/disable the control that calls the function instead
};
self.previousPage = function () {
/// <summary>Moves to previous page</summary>
self.page(self.page() - 1); // no need to do checks, we enable/disable the control that calls the function instead
};
self.firstPage = function () {
/// <summary>Jumps to first page</summary>
self.page(1);
};
self.lastPage = function () {
/// <summary>Jumps to last page</summary>
self.page(totalPages());
};
self.isFirstPage = ko.computed(function () {
/// <summary>Check if the current page equals 1</summary>
return parseInt(self.page(), 10) === 1 ? true: false;
});
self.isLastPage = ko.computed(function () {
/// <summary>Checks if current page equals total pages</summary>
if (self.page() === totalPages()) { return true; }
else { return false; }
});
}
<div class="pager-control">
<input type="button" data-bind="click: paging.firstPage, enable: !paging.isFirstPage()" class="first" />
<input type="button" data-bind="click: paging.previousPage, enable: !paging.isFirstPage()" class="previous" />
<!-- ko foreach: paging.pageNavigation -->
<span class="page-number" data-bind="text: $data, css: { 'current-page': $data === $parent.paging.page() }, click: $parent.paging.setPage"></span>
<!-- /ko -->
<input type="button" data-bind="click: paging.nextPage, enable: !paging.isLastPage()" class="next" />
<input type="button" data-bind="click: paging.lastPage, enable: !paging.isLastPage()" class="last" />
<select data-bind="options: paging.itemsPerPageOptions, value: paging.itemsPerPage"></select>
</div>
<!-- to display the items, bind to "paging.pageContent"-->
/* basic example of styling for the pager control */
.pager-control {
display: inline-block;
vertical-align: middle;
line-height: 24px;
width: 100%;
position: absolute;
bottom: 0;
height: 30px;
background: white;
border-top: 2px solid #D9D6D5;
}
.pager-control input[type=button], .pager-control span, .pager-control select {
display: inline-block;
vertical-align: middle;
}
.pager-control select {
width: 60px;
text-align: center;
}
.pager-control input[type=button], .pager-control span.page-number {
height: 24px;
width: 24px;
}
.pager-control input[type=button] {
border: none;
}
.pager-control input[type=button]:disabled {
opacity: 0.5;
}
.pager-control span.page-number {
text-align: center;
line-height: 24px;
cursor: pointer;
}
.pager-control span.page-number.current-page{
color: #FF8101;
}
.pager-control span.page-number:hover {
text-decoration: underline;
}
.pager-control input[type=button].first {
background: url(../Images/Paging_start24.png);
}
.pager-control input[type=button].last {
background: url(../Images/Paging_end24.png);
}
.pager-control input[type=button].previous {
background: url(../Images/Paging_prev24.png);
}
.pager-control input[type=button].next {
background: url(../Images/Paging_next24.png);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment