Skip to content

Commit

Permalink
Add support for pages computed value. Closes ErikSchierboom#2
Browse files Browse the repository at this point in the history
  • Loading branch information
ErikSchierboom committed May 10, 2015
1 parent 3a79aac commit 928788b
Show file tree
Hide file tree
Showing 7 changed files with 691 additions and 172 deletions.
150 changes: 146 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ var target = ko.observableArray();
target.extend({ paged: {} });
```

After extending the observable array with the `paged` extender, the observable array will have observables, computed values and functions added to it.
After extending the observable array with the `paged` extender, the observable array will have new observables, computed values and functions added to it.

### Observables

Expand All @@ -30,15 +30,29 @@ target.pageSize(10);
target.pageNumber(3);
```

If you want to override the default values, just specify them as parameters when extending the observable array:
#### Defaults

If you want to override the default, initial values, for a single paged observable array, just specify them as parameters:

```js
target.extend({ paged: { pageNumber: 3, pageSize: 25 } });
```

You can also define global defaults in `ko.paging.defaults`:

```js
ko.paging.defaults.pageNumber = 2;
ko.paging.defaults.pageSize = 25;

// Create a paged observable array uses the global defaults
target.extend({ paged: {} });
target.pageNumber(); // Returns 2
target.pageNSize(); // Returns 25
```

### Computed values

The following read-only computed values are added:
The following *read-only* computed values are added:

* `pageItems`: the array items on the current page.
* `pageCount`: the total number of pages.
Expand All @@ -49,6 +63,7 @@ The following read-only computed values are added:
* `hasNextPage`: indicates if there is a next page.
* `isFirstPage`: indicates if the current page is the first page.
* `isLastPage`: indicates if the current page is the last page.
* `pages`: an array of pages.

The following example shows how these values can be used:

Expand All @@ -65,6 +80,7 @@ target.hasPreviousPage; // Returns false
target.hasNextPage; // Returns true
target.isFirstPage; // Returns true
target.isLastPage; // Returns false
target.pages; // Returns [1, 2, 3]
```

### Functions
Expand All @@ -89,6 +105,127 @@ target.toPreviousPage(); // pageNumber becomes 2
target.toFirstPage(); // pageNumber becomes 1
```

### Page generators

As we saw earlier, the `pages` computed observable returns an array of page numbers. It generates page through a concept known a page generators. Out of the box, there are two page generators you can use:

- Simple page generator: returns all pages.
- Sliding page generator: returns current page and some pages surrounding.

#### Simple page generator

The simple page generator simply returns all available pages. The following example shows how to use the *simple* page generator:

```js
var input = [1,2,3,4,5,6,7,8,9];

// Create a paged observable array using the 'simple' page generater
var simple = ko.observableArray(input).extend({
paged: {
pageSize: 2,
pageGenerator: 'simple'
}
});
```

The `simple` paged observable array will now use the simple page generator in its `pages` observable:

```js
// The returned pages are simply all available pages
simple.pages; // Returns [1, 2, 3, 4, 5]

// Changing the page number does not change the returned pages
simple.pageNumber(3);
simple.pages; // Returns [1, 2, 3, 4, 5]
```

#### Sliding page generator

Although the simple page generator is fine for a small number of pages, it becomes unwieldy when there are many pages. This is where the *sliding* page generator shines, as it only returns the current page and some pages surrounding it.

This example shows how to use the `sliding` page generator:

```js
var input = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20];

// Create a paged observable array using the 'sliding' page generater
var sliding = ko.observableArray(input).extend({
paged: {
pageSize: 2,
pageGenerator: 'sliding'
}
});
```

The `sliding` paged observable array will use the sliding page generator in its `pages` observable:

```js
// Returned pages is a window around the current page
sliding.pages; // Returns [1, 2, 3, 4, 5]

// Changing the page number changes the returned pages
sliding.pageNumber(7);
sliding.pages; // Returns [5, 6, 7, 8, 9]
```

As can be seen, the sliding page generator returns the current page number and some pages left and right of it. By default, the window size is equal to five, which means that two items to the left and two items to the right of the current page are also returned.

You can easily change the *sliding* generator's window size:

```js
sliding.pageNumber(7);
sliding.pageGenerator.windowSize(3);
sliding.pages; // Returns [6, 7, 8]
```

Note: as the sliding page generator is the default option, the following two statements are equivalent:

```js
ko.observableArray(input).extend({ paged: { pageGenerator: 'sliding' } });
ko.observableArray(input).extend({ paged: {} });
```

#### Custom page generators

You can also supply your own page generator. First, you define an object that has a `generate` function. This function takes a paged observable array as its parameter and should return an array of pages.

The following custom page generator is an adaptation of the simple page generator, but one that starts with zero:

```js
function CustomPageGenerator() {
this.generate = function(pagedObservable) {
return createRange(0, pagedObservable.pageCount() - 1);
}
}
```

To be able to use this custom generator, we have to add it to `ko.paging.generators`:

```js
ko.paging.generators['custom'] = new CustomPageGenerator();
```

The key in which you stored the page generator is what you should specify in the `pageGenerator` parameter when extending the observable array:

```js
var input = [1,2,3,4,5,6,7,8,9];

// Create a paged observable array using our 'custom' page generater
var custom = ko.observableArray(input).extend({
paged: {
pageSize: 2,
pageGenerator: 'custom'
}
});
```

The `custom` paged observable array will now use our custom page generator in its `pages` observable:

```js
// The returned pages are now zero-based
custom.pages; // Returns [0, 1, 2, 3, 4]
```

## Installation
The best way to install this library is using [Bower](http://bower.io/):

Expand All @@ -107,7 +244,12 @@ There is a [JSBin demo](http://jsbin.com/liruyo/) that shows how the `paged` ext
<th>Changes</th>
</tr>
<tr>
<td>2014-04-11</td>
<td>2014-05-09</td>
<td>0.2.0</td>
<td>Added page generator support.</td>
</tr>
<tr>
<td>2014-05-06</td>
<td>0.1.0</td>
<td>None. Initial version.</td>
</tr>
Expand Down
2 changes: 1 addition & 1 deletion bower.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "knockout-paging",
"main": "dist",
"version": "0.1.0",
"version": "0.2.0",
"homepage": "https://github.com/ErikSchierboom/knockout-paging",
"authors": [
"Erik Schierboom <erik_schierboom@hotmail.com>"
Expand Down
89 changes: 85 additions & 4 deletions dist/knockout-paging.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*!
Knockout paged extender v0.1.0
Knockout paged extender v0.2.0
By: Erik Schierboom (C) 2015
License: Apache 2
Expand All @@ -26,6 +26,78 @@ function isObservableArray(value) {
return ko.isObservable(value) && 'push' in value;
}

function createRange(min, max) {
var list = [];

for (var i = min; i <= max; i++) {
list.push(i);
}

return list;
}

// This page generator just returns a integer for all pages in the pages
// observable. For a large number of pages, this soon becomes impractical
// and you are better of with the sliding window page generator or your
// own, custom one.
function DefaultPageGenerator() {
this.generate = function(pagedObservable) {
return createRange(1, pagedObservable.pageCount());
}
}

// This page generators presents a sliding window that displays the current
// page and the windows around it. The default window displays 5 pages, but
// you can customize it through by settings the windowSize observable
function SlidingPageGenerator() {
var self = this;
self.windowSize = ko.observable(5);

self.generate = function(pagedObservable) {
var leftBasedStartIndex = pagedObservable.pageNumber() - Math.floor(self.windowSize() / 2),
rightBasedStartIndex = pagedObservable.pageCount() - self.windowSize() + 1,
startIndex = Math.max(1, Math.min(leftBasedStartIndex, rightBasedStartIndex)),
stopIndex = Math.min(pagedObservable.pageCount(), startIndex + self.windowSize() - 1);

return createRange(startIndex, stopIndex);
}
}

// This object contains global paging options as well as page generators.
//
// There are two default paging options:
// ko.paging.defaults.pageNumber: the default page number (1)
// ko.paging.defaults.pageSize: the default page size (50)
//
// The paging generators are stored as followed:
// ko.paging.generators.default: default page generator that returns all pages
// ko.paging.generators.sliding: sliding page generator that displays a sliding
// window with the current page and the pages around it
//
// You can extend the available page generators by simply adding a property to
// the generators object:
//
// // This generator uses a zero-based paged index
// ko.paging.generators.zeroBased = {
// generate: function(pagedObservable) {
// return [createRange(0, pagedObservable.pageCount() - 1)];
// }
// }
//
// You can then use this custom pager by supplying the name of the custom generator
// when extending the observable array:
// target.extend({ paged: { pageGenerator: 'zeroBased' } });
ko.paging = {
defaults: {
pageNumber: 1,
pageSize: 50
},
generators: {
'default': new DefaultPageGenerator(),
'sliding': new SlidingPageGenerator()
}
}

// This extender adds paging functionality to a Knockout observable array.
// The target must be an observable array, otherwise an error is thrown.
// The options parameter can contain two values: the page number and/or
Expand All @@ -43,10 +115,15 @@ ko.extenders.paged = function(target, options) {

if (options && options['pageSize'] < 1) {
throw new Error('The page size must be greater than zero.');
}
}

if (options && options['pageGenerator'] !== undefined && ko.paging.generators[options['pageGenerator']] === undefined) {
throw new Error('The page generator could not be found.');
}

target.pageNumber = ko.observable(options && options['pageNumber'] || 1);
target.pageSize = ko.observable(options && options['pageSize'] || 50);
target.pageNumber = ko.observable(options && options['pageNumber'] || ko.paging.defaults.pageNumber || 1);
target.pageSize = ko.observable(options && options['pageSize'] || ko.paging.defaults.pageSize || 50);
target.pageGenerator = ko.paging.generators[options && options['pageGenerator'] || 'default'];

target.pageItems = ko.pureComputed(function() {
return target().slice(target.firstItemOnPage() - 1, target.lastItemOnPage());
Expand Down Expand Up @@ -92,6 +169,10 @@ ko.extenders.paged = function(target, options) {
return target.pageNumber() == target.pageCount();
});

target.pages = ko.pureComputed(function() {
return target.pageGenerator.generate(target);
});

target.toNextPage = function() {
if (target.hasNextPage()) {
target.pageNumber(target.pageNumber() + 1);
Expand Down
2 changes: 1 addition & 1 deletion dist/knockout-paging.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 928788b

Please sign in to comment.