I am having issues with pagination I've implemented. Pagination works based on server-sided publish skip & limit filter.
Issue #1.
If I perform a specific user search the very first page will be blank.
At that state skip is set to 0, limit is always 20.
If I perform a find().fetch() I get 20 elements, but they are all for a different user.
Issue #2 Me going to next page (skip+10) gives me a few more elements
Doing it again gives even more results
and finally being out of data, and going to next page just removes 10 results, leaving 10 shown
This is a very odd behavior.
Server-sided publish
Meteor.publish('overtime', function(opt){
if (!Roles.userIsInRole(this.userId, 'admin')) {
return Overtime.find({userId: this.userId}, opt);
} else {
return Overtime.find({}, opt);
}
});
Client-sided subscription
var defaultSkipStep = 10;
var defaultLimit = 20;
Template.triphtml.onCreated(function(){
var instance = this;
Session.set('limit',defaultLimit);
instance.autorun(function(){
instance.subscribe('overtime', {skip:(Session.get('overSkip')||0), limit:(Session.get('limit')||defaultLimit), sort: {createdAt: -1}});
instance.subscribe('trips', {skip:(Session.get('tripSkip')||0), limit:(Session.get('limit')||defaultLimit), sort: {createdAt: -1}});
});
Next page click event
"click .nxtpage_over": function(event, template){
Session.set('overSkip', (Session.get('overSkip') || 0) + defaultSkipStep);
Session.set('limit', 20);
},
Submit event
https://pastebin.com/btYCSQBD
Query that user sees
main.js (client)
https://pastebin.com/tWakPDT1
main.html
https://pastebin.com/4uMVFsNG
Any idea how to make it so that when I perform search for a specific user I get all 20 results just for that user, and next page gives me NEXT 20 elements, not showing any one the 20 I've just seen.
Use below code for pagination. It is very ideal code, easy to understand and implement.
SERVER PUBLISH:
Meteor.publish('Students', function (str, toSkip, tillLimit) {
var regex = new RegExp(str, 'i');
var data = Students.find( {
$or:[
{"_id": {'$regex' : regex}},
{"username": {'$regex' : regex}}
]
},
{sort: {'time': -1}, skip: toSkip, limit : tillLimit});
return data;
});
Meteor.publish('StudentsTotalCount', function (str) {
var regex = new RegExp(str, 'i');
Counts.publish(this, "StudentsTotalCount", Students.find({
$or:[
{"_id": {'$regex' : regex}},
{"username": {'$regex' : regex}}
]
});
);
});
str
is global search text from client side. Now, Since the user will click on next
and previous
button frequently. On the client side the Subscription must be reactive. For that you can you below code.
CLIENT SUBSCRIBE:
Inside some App_ShowStudents.js
file, you can create subscription as below;
Template.App_ShowStudents.onCreated(function(){
this.userId = new ReactiveVar("");
this.filterCriteria = new ReactiveVar("");
//PAGINATION
this.enablePrevious = new ReactiveVar(false);
this.enableNext = new ReactiveVar(true);
this.skip = new ReactiveVar(0);
this.diff = new ReactiveVar(0);
this.total = new ReactiveVar(0);
this.limit = new ReactiveVar(10);
this.autorun(() => {
Meteor.subscribe('Students', this.filterCriteria.get(), this.skip.get(), this.limit.get());
Meteor.subscribe('StudentsTotalCount', this.filterCriteria.get(), this.role.get());
});
});
HELPERS:
Template.App_ShowStudents.helpers({
students(){
return Students.findOne({}); // this will always be less or equal to 10.
},
skip(){
return Template.instance().skip.get();
},
diff(){
var instance = Template.instance();
var add = instance.skip.get() + instance.limit.get();
var total = instance.total.get();
var diff = (add >= total ? total : add);
instance.diff.set(diff);
return instance.diff.get();
},
total(){
Template.instance().total.set(Counts.get('StudentsTotalCount'));
return Template.instance().total.get();
}
});
total
actually must be reactive and should give only count as per your search criteria, so we had a seperate publish on server.
EVENTS:
Template.App_ShowStudents.events({
'click .previous': function (event, template) {
event.preventDefault();
if(template.diff.get() > template.limit.get()){
template.skip.set(template.skip.get() - template.limit.get());
}
},
'click .next': function (event, template) {
event.preventDefault();
if(template.diff.get() < template.total.get()){
template.skip.set(template.skip.get() + template.limit.get());
}
},
'input #searchData': function( event, template ) {
if ( $( event.target ).val()) { // has some value.
template.filterCriteria.set( $( event.target ).val() );
} else {
template.filterCriteria.set( "" );
}
console.log(template.filterCriteria.get());
},
});
App_ShowStudents.html
<template name="App_ShowStudents">
{{#if Template.subscriptionsReady}}
<body>
<input type="text" id="searchData" class="form-control input-lg" placeholder="Search ID, Username" />
Showing {{skip}} - {{diff}} of {{total}}
{{#each students}}
...
{{/each}}
<ul>
<li><a href="#" role="button" class="previous">Previous</a></li>
<li><a href="#" role="button" class="next">Next</a></li>
</ul>
</body>
{{else}}
Please wait...
{{/if}}
</template>