Is it possible to create multi filter search based on filterBy?

Hi to all,

I have some search controller that filtering loop by filterBy :confused:

  // get all articles and add pagination
  $articles = page('home')->children()->visible()->paginate(100);

  // add a tag filter
  if($rating = param('rating')) {
    $articles = $articles->filterBy('rating', $rating, ',');
  }
  if($colors = param('colors')) {
    $articles = $articles->filterBy('colors', $colors, ',');
  }
  
  // create a shortcut for pagination
  $pagination = $articles->pagination();

  // pass $articles and $pagination to the template
  return compact('articles', 'pagination');

And I have template

<form>
  	<div class="row filter">
		<div class="col-md-6">
			<h6>Rating</h6>
		    <select name="rating" class="selectpicker" multiple>
				<option data-content="<i class='fa fa-star color9'></i><i class='fa fa-star color9'></i><i class='fa fa-star color9'></i><i class='fa fa-star color9'></i><i class='fa fa-star color9'></i>" value="five">5 Stars</option>
				<option data-content="<i class='fa fa-star color9'></i><i class='fa fa-star color9'></i><i class='fa fa-star color9'></i><i class='fa fa-star color9'></i>" value="four">4 Stars</option>
				<option data-content="<i class='fa fa-star color9'></i><i class='fa fa-star color9'></i><i class='fa fa-star color9'></i>" value="three">3 Stars</option>
				<option data-content="<i class='fa fa-star color9'></i><i class='fa fa-star color9'></i>" value="two">2 Stars</option>
				<option data-content="<i class='fa fa-star color9'></i>" value="one">1 Star</option>
			</select>
		</div>
		<div class="col-md-6">
			<h6>Colors</h6>
		    <select name="colors" class="selectpicker" data-live-search="true" multiple>
				<option value="white">Sportsbook</option>
				<option value="red">Red</option>
				<option value="black">Black</option>
			</select>
		</div> 
		<div class="col-md-12 padding-t-20">
			<button type="submit" class="btn btn-default btn-block font-w-700 btn-lg"><i class="fa fa-search"></i> Search</button>
		</div> 
	</div>
  	</form>

But when I try to filter URL goes like ?rating=five&?color=red

  1. How can I can make it like /color:red/rating:five ?
  2. How can I use bootstrap multiple option for select fields to select more that one rating or color fields?
  3. How can I remember selected fields on results page? (users must see what they filtered)

This is example of what I need, I need 6 select parameters to filtered by submit button.
Or maybe its better to made all this with search->

It be great to have some sample like this one http://nimbus-lighting.com/en/products/search but with submit button.

Thanks

Check out this thread

Just saw this after seeing your post in the other thread. Replying here because it’s more relevant to the example link you provided. But, this is a solution that isn’t Kirby-specific, and doesn’t use filterBy.

If you are filtering results that are already in the DOM (like the nimbus lighting page), you’d be doing this with javascript. I have a similar filter in another project that works this way - I have a list of attorneys that can be filtered by position, location, and areas of practice. You can dig into the code to see how it’s working, but one thing I can point out which might help, are these lines in the template:

<option class="filter-button" value="any">Any Position</option>
<option class="filter-button" value="partner" <?= (param('p') == 'partner' ? 'selected="selected"' : ''); ?>>Partner</option>
<option class="filter-button" value="associate" <?= (param('p') == 'associate' ? 'selected="selected"' : ''); ?>>Associate</option>
<option class="filter-button" value="counsel" <?= (param('p') == 'counsel' ? 'selected="selected"' : ''); ?>>Counsel</option>

You can mark an option in a select menu as selected="selected".

When this page loads, it runs get_filters(). When the page /attorneys/ is loaded, all of the options are set to “any”, and it runs the filtering function but nothing gets filtered out. But /attorneys/p:associate will mark the Associate select option as selected, the function will pick up on this, and apply the filter.

More posted below ~ if you have any specific questions, let me know.

// Template

<section id="filter" class="all-attorneys">
		<div class="attorney-filter cf">
			<h4 class="filter-categories-title">Filter results by:</h4>
			<div class="filter-categories">
				<div class="filter-category" tabindex="10">
					<select name="position-filter" id="position-filter" data-filter="position" class="filter-menu">
						<option class="filter-button" value="any">Any Position</option>
						<option class="filter-button" value="partner" <?= (param('p') == 'partner' ? 'selected="selected"' : ''); ?>>Partner</option>
						<option class="filter-button" value="associate" <?= (param('p') == 'associate' ? 'selected="selected"' : ''); ?>>Associate</option>
						<option class="filter-button" value="counsel" <?= (param('p') == 'counsel' ? 'selected="selected"' : ''); ?>>Counsel</option>
					</select>
				</div>
				<div class="filter-category" tabindex="11">
					<select name="location-filter" id="location-filter" data-filter="location" class="filter-menu">
						<option class="filter-button" value="any">Any Location</option>
						<option class="filter-button" value="los_angeles">Los Angeles</option>
						<option class="filter-button" value="newport">Newport Beach</option>
					</select>
				</div>
				<div class="filter-category" tabindex="12">
					<select name="area-filter" id="area-filter" data-filter="areas" class="filter-menu">
						<option class="filter-button" value="any">Any Area of Practice</option>
						<option class="filter-button" value="antitrust">Antitrust</option>
						<option class="filter-button" value="class_action">Class Action Defense</option>
						<option class="filter-button" value="commercial_litigation">Commercial Litigation</option>
						<option class="filter-button" value="entertainment">Entertainment</option>
						<option class="filter-button" value="environment">Environment</option>
						<option class="filter-button" value="financial">Financial Industry & Securities</option>
						<option class="filter-button" value="health">Health Care</option>
						<option class="filter-button" value="intellectual">Intellectual Property</option>
						<option class="filter-button" value="pro_bono">Pro Bono</option>
						<option class="filter-button" value="white_collar">White Collar Defense</option>
					</select>
				</div>
				
			</div>
		</div>

		<div class="attorney-list">
			<div class="list-header">
				<p class="filter-first-name"><img src="/assets/images/icons_sort-toggle.png" alt="" class="sort-toggle" data-sort="first-name" data-sort-direction="asc"></p>
		    <p class="filter-last-name"><img src="/assets/images/icons_sort-toggle.png" alt="" class="sort-toggle last-clicked" data-sort="last-name" data-sort-direction="asc"></p>
		    <p class="filter-position"><img src="/assets/images/icons_sort-toggle.png" alt="" class="sort-toggle" data-sort="position" data-sort-direction="asc"></p>
		    <p class="filter-location"><img src="/assets/images/icons_sort-toggle.png" alt="" class="sort-toggle" data-sort="location" data-sort-direction="asc"></p>
		    <p class="filter-phone"></p>
		    <p class="filter-email"></a></p>
		    <p class="filter-vcard"></p>  
			</div>
			<?php foreach ($pages->find('attorneys')->children()->visible() as $attorney): ?>
				<div class="attorney-filter-item visible"
						data-first-name="<?= $attorney->first_name() ?>"
						data-last-name="<?= $attorney->last_name() ?>"
				    data-position="<?= $attorney->position() ?>"
				    data-areas="<?= str_replace(',', '', $attorney->areas())?>"
				    data-location="<?= str_replace(',', '', (string)$attorney->locations()) ?>">
				  <p class="filter-first-name"><a href="<?= $attorney->url() ?>"><?= $attorney->first_name() ?></a></p>
			    <p class="filter-last-name"><a href="<?= $attorney->url() ?>"><?= $attorney->last_name() ?></a></p>
			    <p class="filter-position"><?= ucwords($attorney->position()) ?></p>
			    <p class="filter-location"><?= $attorney->print_locations() ?></p>
			    <p class="filter-phone"><?= $attorney->phone() ?></p>
			    <p class="filter-email"><a href="mailto:<?= $attorney->email() ?>"><?= $attorney->email() ?></a></p>
			    <p class="filter-vcard"><a href="<?= $attorney->get_vcf() ?>"><img src="/assets/images/icons_filter-vcard.png" alt="" class="sidebar-icon"></a></p>  
				</div>
			<?php endforeach ?>
		</div>
</section>

// Javascript

$(document).ready(function() {
	var container = $('#filter');
	var attorneys = container.find('.attorney-filter-item');
	var attorney_filters = container.find('.filter-category');
	var menus = attorney_filters.find('.filter-menu');
	var filters = [];
	get_filters();
	menus.change(function() {
		get_filters();
	})

	function get_filters() {
		filters = [];
		menus.each(function() {
			var new_filter = {};
			new_filter.context = $(this).attr('data-filter');
			new_filter.value = $(this).val();
			filters.push(new_filter);
		})
		apply_filters();
	}

	function apply_filters() {
		attorneys.addClass('visible');
		$.each(filters, function() {
			var filter = $(this)[0];
			if (filter.value == "any") return;
			attorneys.each(function(index) {
			//	console.log($(this).attr('data-' + filter.context).indexOf(filter.value));
				if ($(this).attr('data-' + filter.context).indexOf(filter.value) < 0) {
					$(this).removeClass('visible');
				};
			})
		})
	}

	function sort_by(field, direction) {
		console.log(direction);
		jQuery.fn.reverse = [].reverse;
		attorneys.sort(function(a, b) {
		   return $(a).attr(field).localeCompare($(b).attr(field));
		})
		if (direction == 'desc') attorneys.reverse();
		$.each(attorneys, function(idx, itm) { container.append(itm); });
	}

	$(".to-filter").click(function() {
		var filter = $(this).attr('data-filter');
		console.log(filter);
		$("#position-filter").val(filter);
		get_filters();
	})
})

I think its to hard template :confused: and not what I need it… sorry

I’m almost done simple controller

return function($site, $pages, $page) {

  // get all articles and add pagination
  $articles = page('article')->children()->visible()->paginate(100);

  if($query_rating = get('rating')) {
  		$articles = $articles->filterBy('rating', '*=', $query_rating);
	}
  if($query_color = get('market')) {
  		$articles = $articles->filterBy('color', '*=', $query_color);
	}
  
  
  // create a shortcut for pagination
  $pagination = $articles->pagination();

  // pass $articles and $pagination to the template
  return compact('articles', 'query_color', 'query_rating', 'pagination');

};

This is works fine, but only one thing isn’t works, when I have multiply one field
?color=red (WORK)
?color=red&rating=five (WORK)
?color=red&color=white (DIDN’T WORK filters only last color=white)
Is it possible to filter two variables together?

You can’t do that with filterBy(), you would have to use a filter with callback …

Can you write a little sample where I need make changes? :cry:
Thanks

It’s possible!!!
Just need to add to name=“color” in select field ( name=“color”) and filter works just fine with multiple options.

Yes, that way you can post an array of colors, but how do you want to filter by multiple colors using the filterBy() method? It only accepts one key => value pair. For what you want to achieve, you want to filter either by color red or by color blue etc, so you need to return the result if it is in the array of posted colors.

You would need a solution like the one posted here: Filter Pages by multiple fields - #4 by distantnative

It was examples :slight_smile: Monosnap here how it will work. I mean it work with filterBy and search by 2 keys… and I think it will works with needed 6 keys (multiple).
As you see at video.

Would you mind sharing your solution then?

return function($site, $pages, $page) {

  // get all articles and add pagination
  $bookmakers = page('bookmaker')->children()->visible()->paginate(100);
  
  if($query_rating = $_GET['rating']) {
  		$bookmakers = $bookmakers->search($query_rating, 'rating');
	}
  if($query_market = $_GET['market']) {
  		$bookmakers = $bookmakers->search($query_market, 'market');
	}
  
   // create a shortcut for pagination
  $pagination = $bookmakers->pagination();

  // pass $articles and $pagination to the template
  return compact('bookmakers', 'query_market', 'query_rating', 'pagination');

};

and in html name=market with

<select name="market[]" class="selectpicker" data-live-search="true" data-size="5" multiple>
<option value="sportsbook">Sportsbook</option>
<option value="racebook">Racebook</option>
<option value="financial_betting">Financial Betting</option>
<option value="scratch_card">...</option>
</select>

for clean URL I think it will work with $_POST (not tested)

1 Like

Thanks for sharing :slightly_smiling:

Now you are using the search() method, not the filterBy method; all I was saying that $filterBy does not work with an array of options, while search() does.

1 Like

Is it possible to use search() for filed that iside structure? I have bluprint:

  bonus:
    label: Bonuses
    type: structure
    entry: >
      <b>{{bonus_name}}</b><br/>{{bonus_rules}}<br/>
    fields:
      bonus_name:
        label: Bonus Name
        type: text
...
      bonus_type:
        label: Bonus Type
        type: multiselect
        options:
         freebet: Freebet
         welcome_bonus: Welcome Bonus
         refund_bonus: Refund Bonus
         referral_bonus: Referral Bonus
         loyalty_vip_program: Loyalty/VIP program
...

And try to filter bonus_type field like was written above, but this isn’t work… I think that search isn’t filter inside structure field… Any ideas?

This is controller for page

return function($site, $pages, $page) {

  // get all articles and add pagination
  $bonuses = page('bookmaker')->children()->visible()->paginate(99);
  

  if($query_bookmaker = get('bookmaker')) {
	  	$bonuses = $bonuses->search($query_bookmaker, 'uid');
	}
  if($query_type = get('type')) {
	    $bonuses = $bonuses->search($query_type, 'bonus_type');
	}
  if($query_country = get('country')) {
	    $bonuses = $bonuses->search($query_country, 'accepted_countries');
	}
 
  // create a shortcut for pagination
  $pagination = $bonuses->pagination();

  // pass $articles and $pagination to the template
  return compact('bonuses', 'query_bookmaker', 'query_type', 'query_country', 'pagination');
};

If I use bonus() field this works and filter all fine, but return all structure items not just one where is searched item placed. So, if all bookmakers will have just one bonus, that will works, but if some bookmaker will have 2-3-5 bonuses filter will not work promptly.

So, I need some hint how can I search nested fields of sturcture field :confused: