[resolved] How to make publish/subscribe react to user set filters (with limited subscribe)

After I removed autopublish and set up infinite scrolling (from http://meteorpedia.com/read/Infinite_Scrolling), I have no idea how to get my filters to work how I want because of the limit on the subscribe.

Basically there is a collection of songs, each with a genre. Then there are checkboxes for each genre. Checked boxes are added to an array of selected genres stored in a Session.set which are used to filter in a Songs.find(). That way the user could see songs under multiple selected genres.

The problem is I limited how many items are subscribed to at once, for the sake of speed, so when I click the check box, it only filters among the subscribed items. For example, pretend the collection has 50 songs, 10 rock, 10 hip hop, 30 pop. It is initially subscribed to 10 songs using the code from infinite scrolling. 2 are rock, 2 are hip hop, the rest are pop. If I scroll down it will load more. When I select it to filter rock and hip hop, it will only display the 4 rock/hiphop songs that have already been subscribed.

I would like it to subscribe to another 5 for either category, making it a total of 10 displayed. How can I do this? I’m thinking of using a button that would change the publish/subscribe function to include the filter but I have no idea how that info would flow back to publish or subscribe.

Server:

	Meteor.publish('songs', function(limit) {
		return Songs.find({}}, {sort: {score: -1}, limit: limit });
	}); 

Iron Router:

	this.route('songs', {
			   path: '/songs/',    			   
			   subscriptions: function() {
					var ITEMS_INCREMENT = 10;
					Session.setDefault('SongsLimit', ITEMS_INCREMENT);
					Deps.autorun(function() {
						Meteor.subscribe('songs', Session.get('SongsLimit'));
					});
				   },
			   cache: true,
			   });

Helpers:

Template.songs.helpers({
'song': function(){
	if(Session.get('genres') == ""){
		return Songs.find({}, {sort: {score: -1}});
	} else {
		return Songs.find({
			"genre": {$in: Session.get('genres')}},
			{sort: {score: -1}}
		});
	}
});

Events:

Template.filtergenre.events({
	'click input[class=checkbox-genre]': function (ev, tpl) {
    		var genres = tpl.$('input:checked').map(function () {
		return $(this).val();
	});
  Session.set('genres', $.makeArray(genres));
}
});

I’m guessing there is some way to include [ “genre”: {$in: Session.get(‘genres’)} ] into the publish and subscribe but… yeah I don’t even know how to begin plugging a filter into publish in general, not to mention using information from the client.

Any advice would be appreciated! Thanks!

Hi buddy, it looks to me like here’s your problem:

Meteor.publish('songs', function(limit) {
  return Songs.find({}}, {sort: {score: -1}, limit: limit });
});

First and foremost your server has to return the right data. Ignore everything else and just think: "What data is my server giving me". You can’t paint with a red crayon, if the server is giving you a purple crayon. Make sure your server is giving you the right crayons. :wink: Pass in your filter to this publish.

Meteor.publish('songs', function(filters, limit) {
  return Songs.find({filters}}, {sort: {score: -1}, limit: limit });
});

Then on your client you can just subscribe to the already filtered out data. :slight_smile:

Template.songs.helpers({
  'song': function(){
    return Songs.find({});
  });
});

Does that make sense?

3 Likes

Sounds easy enough but I must be doing something wrong. I tried:

Meteor.publish('songs', function(filters, limit) {
  return Songs.find({genre: {$in: Session.get('genres')}}, {sort: {score: -1}, limit: limit });
});

but I get “Session is not defined”. Perhaps I’m misunderstanding what you mean.

Oh yeah well Session.get('genres') is already in the filters parameter.

So you would use:

return Songs.find({genre: {$in: filters}}, {sort: {score: -1}, limit: limit });

It should be noted that Meteor.publish is server only and Session is client only which is why it was returning ‘not defined’.

1 Like

When you say it’s already in the filter parameters, I’m guessing you’re saying “filters” is defined in the subscriptions part like how limit is?

Sorry, I’m completely new to javascript, I’m literally started with no knowledge and am learning as I go with Meteor. I tried to do what I think you meant by filter parameters and took the code that was originally in the helper and plugged it into the subscription… Am I on the right track or way off base?

Client

		   subscriptions: function() {
				var ITEMS_INCREMENT = 3;
				var filters = function(){
					if(Session.get('genres') == ""){
						return "";
					} else {
						return {$in: Session.get('genres')};
					}
				};
				Session.setDefault('SongsLimit', ITEMS_INCREMENT);
				Deps.autorun(function() {
					Meteor.subscribe('songs', filters, Session.get('SongsLimit'));
				});
			   },

Server

Meteor.publish('songs', function(filters, limit) {
	return Songs.find({genre: filters}, {sort: {score: -1}, limit: limit });
});   

I had tried {$in: filters} as you said but it would get an error saying it $in requires an array. I’m guessing this is because because my default is blank ( Session.setDefault(‘genres’, “”) )?

In short, the above code ran but it didn’t subscribe to anything. I have no idea if I’m going in the right direction.

I think I figured it out! So that was the general right track right? I ended up moving the if statements to the server side and it worked! Thank you so much!

This brings up a new question for me though. If you or someone else wouldn’t wouldn’t mind answering, in the example I only had genres, but if I have other filter categories, like release year for instance, then I need another if statement embedded in the existing one. Is it bad that the server does more work? Is it better to publish all of the Songs and let the client figure out what to subscribe? (Is that even an option?)

Or perhaps there’s a better alternate solution altogether?

1 Like

The server is there to do the work - don’t be afraid to make it work hard. :smile:

Now, you can publish all the songs, and do the filtering client side. It’s possible, but that means your clients will have all of the songs locally. If you have more than 5000 songs, it’s gonna get hairy quick.

Your server is designed to do the heavy lifting, use it!

Good to know! Thanks again Sergio!

This was an eye-opening statement @sergiotapia. I’m so glad you made that comment. I have a completely different perspective of how I should be designing my app. Thank you.

2 Likes

I was having exactly the same problem @sergiotapia (just read the last comment):


Gracias máquina!