Better Pattern for Filtering Collections

2020-07-27 02:30发布

问题:

I have a collection named Games and I want users to filter through the collection using checkboxes. Every time a checkbox is checked/unchecked, a meteor subscription is called to display the appropriate Games

Here's an example of what a subscription might look like (Not proper mongo code):

{region: ['east','west','eu'], skill: ['casual','amatuer','pro'], gamemode: ['ctf','dm','etc']}

In this particular game you can filter by Region, Skill and Gamemode, with each having multiple combinations.

Edit:

What I need is to create the appropriate selector, depending on which checkboxes are checked.

First, I'm grabbing the checked checkboxes - for each group - and throwing them into an array and then setting that array as a session variable.

// Gamemode Group
'click .mode input[type=checkbox]': function(ev, tpl) {
    var mode = tpl.$('.mode input:checked').map(function () {
        return $(this).val();
    });

    var modeArray = $.makeArray(mode);
    Session.set('dotaMode', modeArray);
    returnFilterQuery();
},

Then I'm checking each groups array to see if they are populated, and if they are, add them to the final selector (query) which I will pass into the subscription. My problem is I have to check for each possible scenario and its nothing but if statements, and once I start adding more groups (gamemode, region, skill, etc.) to search by, there will be so many more combinations.

function returnFilterQuery(){
    //check all fields and create a query object
    var query = { game: 'dota' };
    var gamemode = Session.get('dotaMode');
    var region = Session.get('dotaRegion');
    var skill = Session.get('dotaSkill');

    // if no region or skill is selected, show user all
    if (region.length && skill.length && gamemode.length) {
        query.region = { $in: region };
        query.skill = { $in: skill };
        query.gamemode = { $in: gamemode };
    } else if (region.length && !skill.length && gamemode.length) {
        query.region = { $in: region };
        query.gamemode = { $in: gamemode };
    } else if (!region.length && skill.length && gamemode.length) {
        query.skill = { $in: skill };
        query.gamemode = { $in: gamemode };
    } else if (!region.length && !skill.length && gamemode.length) {
        query.gamemode = { $in: gamemode };
    } else if (region.length && skill.length && !gamemode.length) {
        query.region = { $in: region };
        query.skill = { $in: skill };
    } else if (region.length && !skill.length && !gamemode.length) {
        query.region = { $in: region };
    } else if (!region.length && skill.length && !gamemode.length) {
        query.skill = { $in: skill };
    }

    return Session.set('dotaFilter', query);
};

Hopefully this all makes sense. I'm sure there has to be a better way, I'm just not able to think of something right now. Thanks.

回答1:

I'm feeling particularly inspired to answer this because of the DotA reference. :)

So if I understand the problem correctly, you should not need to check every combination, but rather repeatedly add keys to the query object based on the contents of three session variables. I think the following is logically equivalent to your returnFilterQuery function above.

var returnFilterQuery = function() {
  var query = {game: 'dota'};

  var modifyQueryIfArray = function(key, sessionKey) {
    var value = Session.get(sessionKey);
    if (!_.isEmpty(value))
      query[key] = {$in: value};
  };

  modifyQueryIfArray('gamemode', 'dotaMode');
  modifyQueryIfArray('region', 'dotaRegion');
  modifyQueryIfArray('skill', 'dotaSkill');

  return Session.set('dotaFilter', query);
};

Note that isEmpty may be a safer check than length in case the session variable isn't an array.