I'm creating a pagination observable object. I instantiate it with a collection, and it has a derived paginatedList
, and sometime later I update it's master list (say I've filtered the data). Obviously I want the derived list to recognize that and re-render the view, but it doesn't. Something must not be bound right.
Here's a fiddle.
var Pagination = can.Map.extend({
pageNumber: 1,
pageSize: 10,
isFirstPage: can.compute(function() {
return this.attr('pageNumber') === 1;
}),
isLastPage: can.compute(function() {
return this.attr('pageNumber') === this.attr('lastPage');
}),
lastPage: can.compute(function() {
return Math.ceil(this.attr('list').attr('length') / this.attr('pageSize'));
}),
paginatedList: can.compute(function() {
var pgn = this.attr('pageNumber'),
pgs = this.attr('pageSize'),
start = (pgn-1) * pgs,
end = start + pgs;
return this.attr('list').slice(start, end);
}),
prevPage: function() {
if (!this.attr('isFirstPage'))
this.attr('pageNumber', this.pageNumber-1);
},
nextPage: function() {
if (!this.attr('isLastPage'))
this.attr('pageNumber', this.pageNumber+1);
},
toPage: function(pageNumber) {
this.attr('pageNumber', pageNumber);
}
});
var paginated = new Pagination({ list: collection });
...and the template would look something like...
<table>
{{#paginated.paginatedList}}
<tr>
....
</tr>
{{/paginated.paginatedList}}
</table>
Then, sometime later, the user filters the list...
paginated.attr('list').replace(collection.filter(function() {
// return filtered collection
}))
But the view does not re-render. What isn't bound?
So first, an attempt to explain what I believe is happening in your example:
Understanding how the can.compute is figuring out how to publish/emit it has changed is maybe not obvious, but in your example the compute paginatedList
, will emit the fact that it has changed when any of the properties pageNumber
, pageSize
, or list
change, you can see this within the compute by which properties are accessed using the .attr()
accessor method.
The replace
method in a can.List replaces the items within a list not the list itself, so in
this example, the property accessed by .attr('list')
does not actually change (nor does
pageNumber
or pageSize
), it is still the same instance of a list it just has different items
in it now, so the computed value does not update, which in turn does not update the template.
As a simple/naive fix to the example you could just replace:
paginated.attr('list').replace(collection.filter(function() {
return Math.random() < 0.5;
}));
with
paginated.attr('list', collection.filter(function() {
return Math.random() < 0.5;
}));
...ie, replacing the whole list, not just the items within the list.
Here's a fiddle with that change http://jsfiddle.net/phx1jgq1/1/
Of course this might not be what you are looking for, maybe you actually want to bind to a list where the items may change.
The problem here is that this.attr('list').slice(start, end);
doesn't really bind to all the individual items of the list, but something like:
this.attr('list').filter(function(item, index){
return index >= start && index < end;
});
...does, so you could just replace that line to have it work like you expect.
Here's a fiddle with that change: http://jsfiddle.net/n2tygdud/1/
Of course if filtering through all the items doesn't feel right, you probably want to look into the define plugin that let's you map out advanced and specific behaviours to can.Maps like getters, setters and type conversion, and use that to bind to a lists add/remove events and update a computed property accordingly.