Reputation: 2675
I'm working on building a component for a current and future projects that handles pagination on a knockout component level. I've based the idea on the accepted answer from here. Also reference the Knockout component page here.
Here is my component thus far. I am having trouble getting Any of the data to actually display, let alone with a dynamic table heading.
ko.components.register("jPage", {
viewModel: function (params) {
var self = this;
self.Items = params.Items;
self.Keys = Object.keys(self.Items);
self.PerPage = params.PerPage;
self.Page = 1;
self.PagesTotal = Math.ceil(self.Items.length / self.PerPage);
self.ItemsVisible = ko.computed(function () {
first = (self.Page * self.PerPage) - self.PerPage;
return self.Items.slice(first, first + self.PerPage);
})
self.ChangePage = function (_page) {
if (_page < 1 || _page > self.PagesTotal) return;
this.Page(_page);
}.bind(this);
},
template:
'<div data-bind="foreach: ItemsVisible"> \
Display Table headers and data here <br>\
<span data-bind="text: $data[0]"> </span>\
</div>\
<nav aria-label="Page navigation">\
<ul class="pagination">\
<li data-bind="css:{disabled: Page <= 1} ">\
<a aria-label="Previous" data-bind="click: ChangePage( Page - 1 )">\
<span aria-hidden="true">«</span>\
</a>\
</li>\
<li><a data-bind="text: Page -2, click: ChangePage( Page - 2 ), visible: Page > 2 "> </a></li>\
<li><a data-bind="text: Page -1, click: ChangePage( Page - 1 ), visible: Page > 1 "> </a></li>\
<li class="active"><a data-bind="text: Page"> </a></li>\
<li><a data-bind="text: Page +1, click: ChangePage( Page + 1 ), visible: (PagesTotal > Page +0) "></a></li>\
<li><a data-bind="text: Page +2, click: ChangePage( Page + 2 ), visible: (PagesTotal > Page +1)"></a></li>\
<li data-bind="css: {disabled: (PagesTotal < Page )}">\
<a aria-label="Next" data-bind="click: ChangePage( Page + 1 )">\
<span aria-hidden="true">»</span>\
</a>\
</li>\
</ul>\
</nav>\
'
})
This is how the component would be called:
<div data-bind="component: { name: 'jPage', params: { Items: CustomerList(), PerPage: 25} }">
CustomerList
would look like:
[
{FirstName: "John", Email: "[email protected]"},
{FirstName: "Steve", Email: "[email protected]"},
{FirstName: "Jim", Email: "[email protected]"}
]
What variables or context am I missing to access Items
keys/values?
Extra Notes. I do not have any required form of data as I can manipulate it without consequence.
Upvotes: 0
Views: 1954
Reputation: 2675
Alright. It took me 6 months to figure this out. But hopefully someone else will find this as valuable as I have.
The trick I found was not to use a component, but rather a template for only the HTML. Though I guess a component might work too.
Below is code for a view model for pagination and a small example of how it works. It only requires 1 input, an array, and then you simply bind the PerPage
, and bind the output, PaginatedArray
, and tah dah.
function MainViewModel() {
var self = this;
self.ListOfStuff = ko.observableArray();
self.Pagination = ko.observable(new PaginationVM(self.ListOfStuff));
self.PopulateArray = function() {
for (var i = 0; i < 100; i++) {
self.ListOfStuff.push("Item " + i);
}
}
self.PopulateArray();
}
function PaginationVM(input) {
var self = this;
self.InputArray = input;
self.PerPage = ko.observable();
self.Page = ko.observable(1);
self.PageTotal = ko.computed(function() {
var totalPages = Math.ceil(self.InputArray().length / parseInt(self.PerPage()));
if (self.Page() > totalPages && totalPages > 0) {
self.Page(totalPages);
}
return totalPages
})
self.PaginatedArray = ko.computed(function() {
var perPage = parseInt(self.PerPage())
var start = self.Page() * perPage - perPage;
return self.InputArray().slice(start, start + perPage);
})
self.ChangePageBy = function(offset) {
return function() {
self.Page(self.Page() + offset);
}
}
self.ChangePageTo = function(newPage) {
return function() {
switch (newPage) {
case "last":
self.Page(self.PageTotal());
break;
case "first":
self.Page(1);
break;
default:
self.Page(newPage);
}
}
}
}
ko.applyBindings(new MainViewModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<!-- This is the template needed for the view model but you can style it as necessary
Its bootstrap right now.-->
<script type="text/html" id="PaginationTemplate">
<nav class="text-center" aria-label="Page navigation">
<ul class="pagination" style="cursor:pointer">
<li data-bind="css:{disabled: Page() <= 1} ">
<a aria-label="Previous" data-bind="click: ChangePageTo('first')">
<span aria-hidden="true">«</span>
</a>
</li>
<li>
<a data-bind="text: Page() -2, click: ChangePageBy(-2), visible: Page() > 2 "> </a>
</li>
<li>
<a data-bind="text: Page() -1, click: ChangePageBy(-1), visible: Page() > 1 "> </a>
</li>
<li class="active">
<a data-bind="text: Page() "> </a>
</li>
<li>
<a data-bind="text: Page() +1, click: ChangePageBy(1), visible: (PageTotal() > Page() +0)"></a>
</li>
<li>
<a data-bind="text: Page() +2, click: ChangePageBy(2), visible: (PageTotal() > Page() +1)"></a>
</li>
<li data-bind="css: {disabled: (PageTotal() < Page() +1)}">
<a aria-label="Next" data-bind="click: ChangePageTo('last')">
<span aria-hidden="true">»</span>
</a>
</li>
</ul>
</nav>
</script>
<!-- This is what it looks like without pagination.
<div data-bind="foreach: ListOfStuff">
<span data-bind="text: $data"> </span><br>
</div>-->
<select data-bind="value: Pagination().PerPage">
<option value="10">10</option>
<option value="25">25</option>
<option value="50">50</option>
<option value="100">100</option>
</select>
<div data-bind="foreach: Pagination().PaginatedArray">
<span data-bind="text: $data"></span><br>
</div>
<div data-bind="template: { name : 'PaginationTemplate', data: Pagination }">
Upvotes: 4