Real estate website with Kirby

Hello !
I’d like to know if someone has some experience with developping a real estate website with Kirby. I’m quite new with Kirby, I think I understood the basics (and enjoyed the simplicity and power) but I wonder if I could run into some limitations when designing the property search and filter module, for instance?
Cheers,
Daniel

UPDATE:
This is the parameter search which I have announced:
http://smartphone-star.de/finder/category:smartphones

Check it out. : )


OLDER POST:
This is a small parameter search/filter for example. Works realy great:

http://uhren-uhu.de/uhren

I will design an other parameter search on http://smartphone-star.de for the device specifications. This will be a bit bigger. Partially it´s a bit tricky but it works…

Not real estate, but I designed a filtering/search system that includes filtering by location.

http://croissant.reviews/en

So, it is possible. Thanks tobias and sam for your real-world examples. Both of your websites are really inspiring! Now let’s build something…
Cheers,
Daniel

Keep us up to date about your project daniel. I´m interested in the result.

Looking forward to more complex Kirby sites! Let us know if you need help with any snags.

I worked with a Landscape Architecture firm to create this filtering system.

http://triadassociates.net/projects

The client loves using Kirby. After a quick demo, they were excited to get started and have had very few questions about how to use the CMS. They manage the content completely by them selves.

They have already uploaded more than 50 new projects. I actually didn’t anticipate them uploading so many projects and will probably have to go back in and add pagination.

Hope this helps,

Ian

Thanks, Ian. Very nice and clean website with great fonts. I’d also love to build something like that without relying (again) on WordPress.
Cheers,
Daniel

There’s a lot you can do using Kirby’s built-in filter() and filterBy() methods. I’m building a site right now that is a repository of a bunch of books, and browsing will be primarily through searches:

Then there are two ways I handle search queries - if a book contains a certain attribute (if it’s included in the Modern Library, for instance), and then, a basic filter. This allows for a lot of compound filtering.

The request is sent as a JSON object, via ajax, to a search script. This may be much more than what you’re looking for, but, I thought I’d post this here for anyone else to use / reference / critique:

<?php
define('DS', DIRECTORY_SEPARATOR);
require($_SERVER["DOCUMENT_ROOT"] . DS . 'kirby' . DS . 'bootstrap.php');
$kirby = kirby();
$site = $kirby->site();

$params = $_POST;

$books = $site->pages()->find('writers')->children()->visible()->children();

function contains($str, array $arr) {
  foreach($arr as $a) {
    if (stripos($str,$a) !== false || $a = "none" && strlen($str) == 0) return true;
  }
  return false;
}

foreach ($params as $param => $param_array) {
  switch($param) {
    case "regions":
      $books = $books->filter(function($book) use ($param_array) {
        if (contains((string)$book->region(), $param_array)) {
          return $book;
        }
      });
      break;
    case "languages":
      $books = $books->filter(function($book) use ($param_array) {
        if (contains((string)$book->language(), $param_array)) {
          return $book;
        }
      });
      break;
    case "collections":
      $books = $books->filter(function($book) use ($param_array) {        
        if (contains((string)$book->collections(), $param_array)) {
          return $book;
        }
      });
      break;
    case "filter":
      foreach($param_array as $filter => $filter_value) {
        switch ($filter) {
          case "min_pages":
            $books = $books->filterBy('page_count', '>', $filter_value);
            break;
          case "max_pages":
            $books = $books->filterBy('page_count', '<', $filter_value);
            break;  
          case "min_decade":
            $books = $books->filterBy('year_published', '>=', $filter_value);
            break;
          case "max_decade":
            $max = ($filter_value == 'max') ? 200000 : $filter_value;
            $books = $books->filterBy('year_published', '<=', $max);
            break;
        }
      }
      break;
  }
}
5 Likes

Thanks SQBiz for pointing me into the right direction and providing some code. I’m sure it will help other Kirby users, too.
Cheers,
Daniel

Can you show template part of code? To understand how this form is working… I have similar filter system to do, but don’t have enough skills in code…

Hi @wildthing,

The template end of this is built to work with some javascript to work over AJAX - it’s not a straight up form. I think that posting all of it here wouldn’t be much of a help - honestly, it’s kind of a mess and I’d do it differently anyway. Do you have any specific questions?

I got a DM from another user asking about this code – here it is. The site this was built for is www.ripsguidetoliterature.com. Like I mentioned, there is a lot I would do differently if I were to build this again. I’ve learned a lot since putting this together. I wouldn’t recommend any kind of copy + paste to get this working in another situation. But, dig through and maybe this can be of some help.

Template

	<div class="search-field cf slider-container">
		<div class="search-field-title">
			<p>Decade</p>
		</div>
		<input class="search-parameter search-filter" name="min_decade" type="hidden" value="1900">
		<input class="search-parameter search-filter" name="max_decade" type="hidden" value="max">
		<div class="slider-box">
			<div class="slider-tickmarks">
				<div class="tickmark"></div>
				<div class="tickmark"></div>
				<div class="tickmark"></div>
				<div class="tickmark"></div>
				<div class="tickmark"></div>
				<div class="tickmark"></div>
				<div class="tickmark"></div>
				<div class="tickmark"></div>
				<div class="tickmark"></div>
				<div class="tickmark"></div>
				<div class="tickmark"></div>
			</div>
			<div class="decade-slider"></div>
			<p class="slider-label decade-minmax"><span class="min-decade-label">1900</span> - <span class="max-decade-label">2000+</span></p>
		</div>
	</div>

	<div class="search-field cf slider-container">
		<div class="search-field-title">
			<p>Page Count</p>
		</div>
		<input class="search-parameter search-filter" name="min_pages" type="hidden" value="0">
		<input class="search-parameter search-filter" name="max_pages" type="hidden" value="max">
		<div class="slider-box">
			<div class="slider-tickmarks">
				<div class="tickmark"></div>
				<div class="tickmark"></div>
				<div class="tickmark"></div>
				<div class="tickmark"></div>
				<div class="tickmark"></div>
				<div class="tickmark"></div>	
			</div>
			<div class="page-slider"></div>			
			<p class="slider-label page-minmax"><span class="min-page-label">Any</span><span class="max-page-label"></span></p>		
		</div>
	</div>

	<div class="search-field cf">
		<div class="search-field-title"><p>Type</p></div>
		<div class="checkboxes">
			<div class="checkbox">
				<input id="novel" class="search-parameter search-booktype" type="checkbox" value="novel" checked="checked"/>
				<label class="checkbox-label" for="novel">Novel</label>
			</div>
			<div class="checkbox">
				<input id="poetry" class="search-parameter search-booktype" type="checkbox" value="poetry" checked="checked"/>
				<label class="checkbox-label" for="poetry">Poetry</label>
			</div>
			<div class="checkbox">
				<input id="criticism" class="search-parameter search-booktype" type="checkbox" value="criticism" checked="checked"/>
				<label class="checkbox-label" for="criticism">Criticism</label>
			</div>
			<div class="checkbox">
				<input id="short_stories" class="search-parameter search-booktype" type="checkbox" value="short_stories" checked="checked"/>
				<label class="checkbox-label" for="short_stories">Short Stories</label>
			</div>
			<div class="checkbox">
				<input id="theater" class="search-parameter search-booktype" type="checkbox" value="theater" checked="checked"/>
				<label class="checkbox-label" for="theater">Drama</label>
			</div>
			<div class="checkbox-tools">
				<img class="check-all" src="/assets/images/icons_check-all.png" alt="">
				<img class="uncheck-all" src="/assets/images/icons_uncheck-all.png" alt="">
			</div>
		</div>
	</div>

	<div class="search-field cf">
		<div class="search-field-title"><p>Difficulty</p></div>
		<div class="checkboxes">
			<div class="checkbox">
				<input id="intermediate" class="search-parameter search-difficulty" type="checkbox" value="intermediate" checked="checked"/>
				<label class="checkbox-label" for="intermediate">Intermediate</label>
			</div>
			<div class="checkbox">
				<input id="advanced" class="search-parameter search-difficulty" type="checkbox" value="advanced" checked="checked"/>
				<label class="checkbox-label" for="advanced">Advanced</label>
			</div>
			<div class="checkbox">
				<input id="difficult" class="search-parameter search-difficulty" type="checkbox" value="difficult" checked="checked"/>
				<label class="checkbox-label" for="difficult">Difficult</label>
			</div>
			<div class="checkbox">
				<input id="extreme" class="search-parameter search-difficulty" type="checkbox" value="extreme" checked="checked"/>
				<label class="checkbox-label" for="extreme">Extreme</label>
			</div>
			<div class="checkbox-tools">
				<img class="check-all" src="/assets/images/icons_check-all.png" alt="">
				<img class="uncheck-all" src="/assets/images/icons_uncheck-all.png" alt="">
			</div>
		</div>
	</div>



	<div class="search-field cf">
		<div class="search-field-title"><p>Region</p></div>
		<div class="checkboxes">
			<div class="checkbox">
				<input id="north_america" class="search-parameter search-region" type="checkbox" value="north_america" checked="checked"/>
				<label for="north_america">North America</label>
			</div>
			<div class="checkbox">
				<input id="western_europe" class="search-parameter search-region" type="checkbox" value="western_europe" checked="checked"/>
				<label for="western_europe">Western Europe</label>
			</div>
			<div class="checkbox">
				<input id="eastern_europe" class="search-parameter search-region" type="checkbox" value="eastern_europe" checked="checked"/>
				<label for="eastern_europe">Eastern Europe</label>
			</div>
			<div class="checkbox">
				<input id="africa" class="search-parameter search-region" type="checkbox" value="africa" checked="checked"/>
				<label for="africa">Africa</label>
			</div>
			<div class="checkbox">
				<input id="latin_america" class="search-parameter search-region" type="checkbox" value="latin_america" checked="checked"/>
				<label for="latin_america">Latin America</label>
			</div>
			<div class="checkbox">
				<input id="middle_east" class="search-parameter search-region" type="checkbox" value="middle_east" checked="checked"/>
				<label for="middle_east">Middle East</label>
			</div>
			<div class="checkbox">
				<input id="asia-pacific" class="search-parameter search-region" type="checkbox" value="asia-pacific" checked="checked"/>
				<label for="asia-pacific">Asia-Pacific</label>
			</div>

			<div class="checkbox-tools">
				<img class="check-all" src="/assets/images/icons_check-all.png" alt="">
				<img class="uncheck-all" src="/assets/images/icons_uncheck-all.png" alt="">
			</div>
		</div>
	</div>

	<div class="search-field cf">
		<div class="search-field-title">
			<p>Language</p>
		</div>
		<div class="checkboxes">
			<div class="checkbox">
				<input id="english" class="search-parameter search-language" type="checkbox" value="english" checked="checked"/>
				<label for="english">English</label>
			</div>
			<div class="checkbox">
				<input id="russian-slavic" class="search-parameter search-language" type="checkbox" value="russian-slavic" checked="checked"/>
				<label for="russian-slavic">Russian-Slavic</label>
			</div>
			<div class="checkbox">
				<input id="spanish" class="search-parameter search-language" type="checkbox" value="spanish" checked="checked"/>
				<label for="spanish">Spanish</label>
			</div>
			<div class="checkbox">
				<input id="french" class="search-parameter search-language" type="checkbox" value="french" checked="checked"/>
				<label for="french">French</label>
			</div>
			<div class="checkbox">
				<input id="german" class="search-parameter search-language" type="checkbox" value="german" checked="checked"/>
				<label for="german">German</label>
			</div>
			<div class="checkbox">
				<input id="japanese" class="search-parameter search-language" type="checkbox" value="japanese" checked="checked"/>
				<label for="japanese">Japanese</label>
			</div>
			<div class="checkbox">
				<input id="other" class="search-parameter search-language" type="checkbox" value="other" checked="checked"/>
				<label for="other">Other</label>
			</div>
			<div class="checkbox-tools">
				<img class="check-all" src="/assets/images/icons_check-all.png" alt="">
				<img class="uncheck-all" src="/assets/images/icons_uncheck-all.png" alt="">
			</div>
		</div>			
	</div>
	<div id="book-search" class="search-button-container do-search">
		<h4 class="">Show All</h4>
	</div>
</div>

Javascript

// Search Functions
$(".do-search").click(function() {
	console.log('clicked');
	if (!$(this).find('h4').hasClass('no-results')) {
		console.log('pass');
		params = getParams()
		params.results = true;
		searchingOnLoad = false;
		changeView('page-view');
		doSearch(params);
		$("#main-container").addClass('loading');
		$('body').removeClass('homepage');
	}
})


$(".search-parameter").change(function() {
	updateSearch();
})

function updateSearch() {
	params = getParams();
	params.results = false;
	console.log('params:', params);
	doSearch(params);
}

function getParams() {
	// Rebuild: auto-detect parameter categories
	var params = {};
	params.context = $("#search-containers").attr('data-view');
	var context = $('#' + params.context);
	params.filter = {};
	params.empty = false;
	$(context).find(".search-filter").each(function() {
		$(this).addClass('test');
		var param = $(this).attr('name');
		var value = $(this).val();
		if (value.length) {
			params.filter[param] = value;
		}
	})
	if (params.context == 'book-search') {
		params.booktype = [];
		$(".search-booktype:checked").each(function() {
			params.booktype.push($(this).val());
		})

		params.difficulty = [];
		$(".search-difficulty:checked").each(function() {
			params.difficulty.push($(this).val());
		})

		params.regions = [];
		$(".search-region:checked").each(function() {
			params.regions.push($(this).val());
		})

		params.languages = [];
		$(".search-language:checked").each(function() {
			params.languages.push($(this).val());
		})

		params.collections = [];
		$(".search-collection:checked").each(function() {
			params.collections.push($(this).val());
		})
	} else if (params.context == 'writer-search') {
		params.writer_type = [];
		$(".search-writertype:checked").each(function() {
			params.writer_type.push($(this).val());
		})

		params.gender = [];
		$(".search-gender:checked").each(function() {
			params.gender.push($(this).val());
		})

		params.mortality = [];
		$(".search-mortality:checked").each(function() {
			params.mortality.push($(this).val());
		})

		params.geography = [];
		$(".search-geography:checked").each(function() {
			params.geography.push($(this).val());
		})
	}
	context.find('.checkboxes').each(function() {
		if ($(this).find('.search-parameter:checked').length == 0) {
			params.empty = true;
		}
	})

	return params;
}


(function ($) {
    $.deserialize = function (str, options) {
        var pairs = str.split(/&amp;|&/i),
            h = {},
            options = options || {};
        for(var i = 0; i < pairs.length; i++) {
            var kv = pairs[i].split('=');
            kv[0] = decodeURIComponent(kv[0]);
            if(!options.except || options.except.indexOf(kv[0]) == -1) {
                if((/^\w+\[\w+\]$/).test(kv[0])) {
                    var matches = kv[0].match(/^(\w+)\[(\w+)\]$/);
                    if(typeof h[matches[1]] === 'undefined') {
                        h[matches[1]] = {};
                    }
                    h[matches[1]][matches[2]] = decodeURIComponent(kv[1]);
                } else {
                    h[kv[0]] = decodeURIComponent(kv[1]);
                }
            }
        }
        return h;
    };

    $.fn.deserialize = function (options) {
        return $.deserialize($(this).serialize(), options);
    };
})(jQuery);


function doSearch(params, noStateChange) {
	var search_button = $("#" + params.context).children('.search-button-container')
	
		var query = $.param(params);

		if (params.results && !noStateChange ) { history.pushState({}, "page 2", '/search?' + query); };
		$.ajax({
			type: "POST",
			url: "/assets/php/search_results.php",
			data: params,
			success: function (response) {
				console.log(response);
				// TODO end loading spinner

				var container_size = $("#main-container").attr('data-size');
				var load_container = $("#load-container");
				var main_container = $("#main-container");
				if (params.results) {
					//changeView('page-view');
					var decode = QueryStringToHash(query);
						// tracker handler
						var from_uri = window.location.pathname;
						var to_uri = '/search?' + query;
						var session_data = {};
						session_data.new_click = {};
						session_data.new_click.from_uri = from_uri;
						session_data.new_click.to_uri = to_uri;
						session_data.new_click.timestamp = timestamp();
						if (!searchingOnLoad) send_post('update', session_data);
						// ---
						console.log(response);
						showResults(response, params.context);
					
				} else {
					search_button.html(response);
				} 
			}
		})		
	
	// TODO start loading spinner. for both results / preview

}

function QueryStringToHash(query) {
  var query_string = {};
  var vars = query.split("&");
  for (var i=0;i<vars.length;i++) {
    var pair = vars[i].split("=");
    pair[0] = decodeURIComponent(pair[0]);
    pair[1] = decodeURIComponent(pair[1]);
    	// If first entry with this name
    if (typeof query_string[pair[0]] === "undefined") {
      query_string[pair[0]] = pair[1];
    	// If second entry with this name
    } else if (typeof query_string[pair[0]] === "string") {
      var arr = [ query_string[pair[0]], pair[1] ];
      query_string[pair[0]] = arr;
    	// If third or later entry with this name
    } else {
      query_string[pair[0]].push(pair[1]);
    }
  } 
  return query_string;
};

search_results.php

<?php


  define('DS', DIRECTORY_SEPARATOR);
  require($_SERVER["DOCUMENT_ROOT"] . DS . 'kirby' . DS . 'bootstrap.php');
  

  require($_SERVER["DOCUMENT_ROOT"] . '/assets/php/do_search.php'); 

  $params = $_POST;
  doSearch($params);

?>

do_search.php

<?php

function doSearch($params) {
	if (!isset($site)) {
		$site = kirby()->site();
	}

	$site->load('models');

	if ($params['empty'] == 'true') {
		echo "<h4 class='no-results'>No Results</h4>";
		return;
	}

  
	function array_trim(&$value) { 
	    $value = trim($value); 
	}

	function contains($str, array $arr) {
		if (strlen($str) < 1) {
			return false;
		}
		foreach($arr as $a) {
			if (stripos($str,$a) !== false || $a = "none" && strlen($str) == 0) return true;
		}
		return false;
	}

	// BOOK SEARCH
	if ($params['context'] == 'book-search') {

		$books = $site->pages()->find('writers')->children()->visible()->children()->visible();

		foreach ($params as $param => $param_value) {
			switch($param) {
				case "booktype":
					$books = $books->filter(function($book) use ($param_value) {
						if (contains((string)$book->book_type(), $param_value)) {
							return $book;
						}
					});
					break;
				case "difficulty":
					$books = $books->filter(function($book) use ($param_value) {
						if (contains((string)$book->difficulty(), $param_value)) {
							return $book;
						}
					});
					break;
				case "regions":
					$books = $books->filter(function($book) use ($param_value) {
						if (contains((string)$book->region(), $param_value)) {
							return $book;
						}
					});
					break;
				case "languages":
					$books = $books->filter(function($book) use ($param_value) {
						if (contains((string)$book->language(), $param_value)) {
							return $book;
						}
					});
					break;
				case "collections":
					$books = $books->filter(function($book) use ($param_value) {
						if (in_array("none", $param_value) && strlen($book->collections()) == 0) return $book;
						if (contains((string)$book->collections(), $param_value)) {
							return $book;
						}
					});
				 break;
				case "awards":
					$books = $books->filter(function($book) use ($param_value) {
						if (in_array("none", $param_value) && strlen($book->awards()) == 0) return $book;
						if (contains((string)$book->awards(), $param_value)) {
							return $book;
						}
					});
				 break;
				case "filter":
					foreach($param_value as $filter => $filter_value) {
						switch ($filter) {
							case "min_pages":
								$books = $books->filterBy('page_count', '>', $filter_value);
								break;
							case "max_pages":
								$max_pl = ((string)$filter_value == 'max') ? '200000' : $filter_value;
								$books = $books->filterBy('page_count', '<', $max_pl);
								break;  
							case "min_decade":
								$min = ((string)$filter_value == '1900') ? '1800' : $filter_value;
								$books = $books->filterBy('year_published', '>=', $min);
								break;
							case "max_decade":
								$max = ((string)$filter_value == 'max') ? '200000' : $filter_value;
								$books = $books->filterBy('year_published', '<=', $max);
								break;
						}
					}
					break;
			}
		}

		$filtered_results = $books;


	// WRITER SEARCH
	} else if ($params['context'] == 'writer-search') {

		$writers = $site->pages()->find('writers')->children()->visible();

		//$writers = $site->pages()->find('writers')->children()->find('albert-camus')->visible();

		foreach ($params as $param => $param_value) {
			switch ($param) {
				case "writer_type":
					$writers = $writers->filter(function($writer) use ($param_value) {
						// if this writer has a book of one of the submitted types
						if ($writer->children()->visible()->count() > 0) {
							foreach ($writer->children()->visible() as $book) {
								if (in_array((string)$book->book_type(), $param_value)) {
									return $writer;
								}
							}	
						}	
						
						// or, if the writer has been checked as a particular type
						$writer_type_arr = explode(',', (string)$writer->writer_type());
						array_walk($writer_type_arr, 'array_trim');
						if (count(array_intersect($writer_type_arr, $param_value))) {
							return $writer;
						}						
					});
					break;
				case "gender":
					$writers = $writers->filter(function($writer) use ($param_value) {
						if (in_array((string)$writer->gender(), $param_value)) {
							return $writer;
						}
					});
					break;
				case "mortality":
					$writers = $writers->filter(function($writer) use ($param_value) {
						if (in_array('living', $param_value)) {
							if (strlen($writer->date_of_death()) == 0) {
								return $writer;
							}              
						}
						if (in_array('deceased', $param_value)) {
							if (strlen($writer->date_of_death()) > 1) {
								return $writer;
							}              
						}
					});
					break;

				// fix geography!!

				case "geography":
					$writers = $writers->filter(function($writer) use ($param_value) {
						if (contains((string)$writer->geography(), $param_value)) {
							return $writer;
						}
						else if ((string)$writer->geography() == 'default') {
							return $writer;
						}
					});
					break;
				case "awards":
					$writers = $writers->filter(function($writer) use ($param_value) {
						if (contains((string)$writer->awards(), $param_value)) {
							return $writer;
						}
						else if ((string)$writer->awards() == 'default') {
							return $writer;
						}
					});
					break;
				case "filter":
						foreach($param_value as $filter => $filter_value) {
							switch ($filter) {
								case "min_decade":
									if ($filter_value == 1900) { $filter_value = 1000; };
									$writers = $writers->filter(function($writer) use ($filter_value) {
										if ($writer->children()->visible()->count() < 1) return $writer;
										foreach ($writer->children()->visible() as $book) {
											if ($filter_value < (string)$book->year_published() && (string)$book->year_published() != "") {
												return $writer;
												break;
											}
										}
									});
									break;
								case "max_decade":
									$max = ($filter_value == 'max') ? 200000 : $filter_value;
									$writers = $writers->filter(function($writer) use ($filter_value) {
										if ($writer->children()->visible()->count() < 1) return $writer;
										foreach ($writer->children()->visible() as $book) {
										// echo $filter_value . " / " . $book->year_published() . "<br>";
										 if ($filter_value > (string)$book->year_published() && (string)$book->year_published() != "") {
												return $writer;
												break;
											}
										}
									});
									break;
							}
						}
					break;
			}
		}

		$filtered_results = $writers;
	}

	// Make Cache for Reading Lists
	if (array_key_exists('cache', $params)) {
		$cache_arr = [];
		$i = 0;
		foreach ($filtered_results as $result) {
			$cache_arr[$i]['book'] = $result->uid();
			$i++;
		}
		return ($cache_arr);
	}
	// Return Results
	else {
		if ($params['results'] == "true") {
			// return full results

			$results = new stdClass;
			$results->items = array();

			// append each result item to array
			foreach ($filtered_results as $filtered_result) {
				$filtered_result_arr = [];
				$image_str = (string)$filtered_result->thumbnail_image();
				$fallback_str = (string)$filtered_result->cover_image();
				if (null !== $filtered_result->files()->find($image_str) && $filtered_result->files()->find($image_str)->size() > 0) {
					$cover = $filtered_result->files()->find($image_str);
				} else if (null !== $filtered_result->files()->find($fallback_str) && $filtered_result->files()->find($fallback_str)->size() > 0) {
					$cover = $filtered_result->files()->find($fallback_str);
				} else {
					$cover = $site->files()->find('default_thumbnail.jpg');
				}

				$thumb = thumb($cover, array('width' => '180', 'height' => '250', 'crop' => true))->filename();

				$filtered_result_arr['title'] = (string)$filtered_result->title();
				$filtered_result_arr['author'] = (string)$filtered_result->parent()->title();
				$filtered_result_arr['categories'] = get_categories($filtered_result);
				$filtered_result_arr['url'] = "/" . (string)$filtered_result->uri();
				$filtered_result_arr['thumb'] = "/thumbs/" . (string)$thumb;
				$results->items[] = $filtered_result_arr;
			}


			// make readable search string

			$results->readable_string = json_encode($params);

			print(json_encode($results));

		} else {
			// just return the count
			$count = $filtered_results->count();

			switch (TRUE) {
				case $count > 1:
					echo "<h4 class='results'>" . $count . " results &rarr;</h4>";
					break;
				case $count == 1:
					echo "<h4 class='results'>" . $count . " result &rarr;</h4>";
					break;
				case $count == 0:
					echo "<h4 class='no-results'>No Results</h4>";
					break;
			}
		}
	}
}

function get_categories($result) {

	if ($result->template() == 'writer') {
    $writer_types = array_map('trim',explode(",",$result->writer_type()));
    $book_types = array();
    foreach ($result->children()->visible() as $book) {
      $book_types[] = (string)$book->book_type();
    }
    // merge, make unique, sort, re-index
    $types = array_unique(array_merge($writer_types, $book_types));
    asort($types);
    $types = array_values($types);

    $str = "";
    $built_arr = array();

    for ($i = 0; $i < count($types); $i++) {
      switch ($types[$i]) {
        case ('theater'):
          $built_arr[$i] = "Playwright";
          break;
        case ('poetry'):
          $built_arr[$i] = "Poet";
          break;
        case ('novel'):
          $built_arr[$i] = "Novelist";
          break;
        case ('criticism'):
          $built_arr[$i] = "Critic";
          break;
        case ('short_stories'):
          $built_arr[$i] = "Short Story Writer";
          break;
      }
    }
    $built_str = "";
    $i = 0;
    foreach ($built_arr as $built_item) {
      $built_str .= $built_item;
      if ($i < count($built_arr) -1) $built_str .= ", ";
      $i++;
    }
    return($built_str);		
	} else if ($result->template() == 'book') {
		$str = $result->book_type();
		switch ($str) {
        case ('theater'):
          $str = "Theater";
          break;
        case ('poetry'):
          $str = "Poetry";
          break;
        case ('novel'):
          $str = "Novel";
          break;
        case ('criticism'):
          $str = "Criticism";
          break;
        case ('short_stories'):
          $str = "Short Stories";
          break;
		}
		return($str);
	}


}

?>
2 Likes

I have built “facets” with kirby: http://sioen-ppc.com/en/products

1 Like

Can you share, how do you create this filtering? It is looks great!!!

Can you share the code? How do you made multiple filtering?
Im stuck a little with this… :pleading_face::pleading_face::pleading_face:

I have Reviews controller

<?php

return function($site, $pages, $page) {
	
  $reviews = $page->children()->listed()->sortBy('rating')->flip();
    
    $params = params();
    
    /* Search */
    $search = urldecode(param('q'));
    
 	/* Categories */
    $category = urldecode(param('category'));
    
    /* Software */
    $software = urldecode(param('software'));
    
    /* License */
    $license = urldecode(param('license'));
    
    /* Currency */
    $currency = urldecode(param('currency'));
    
    /* Language */
    $language = urldecode(param('language'));
    
    /* Country */
    $country = urldecode(param('country'));
    
    /* Payment Methods */
    $payment = urldecode(param('payment'));
    
    
	if($search = get('q')) {
		$reviews = $reviews->search($search, 'title|text');
	}
	
	if ($category) {
		$reviews = $page->children()->listed()->filterBy('reviews_category', $category,',');
	}
  	
  	if ($software) {
		$reviews = $page->children()->listed()->filterBy('gaming_software', $software,',');
	}

  	if ($license) {
	  	$reviews = $page->children()->listed()->filterBy('licenses', $license,',');
	}
  	
  	if ($currency) {
	  	$reviews = $page->children()->listed()->filterBy('currencies', $currency,',');
	}
  	
  	if ($language) {
		$reviews = $page->children()->listed()->filterBy('languages', $language,',');
	}
  	
  	if ($country) {
		$reviews = $page->children()->listed()->filterBy('restricted_countries', $country,',');
  	}
  	
  	if ($payment) {
		$reviews = $page->children()->listed()->filterBy('payment_methods', $payment,',');
  	}
	
  

  return compact( 'reviews', 'search', 'category', 'license', 'currency', 'language', 'country', 'software', 'payment');

};

And have template…

<div class="container">
   <div class="row">
      <div class="col-lg-9 order-lg-2 mb-7">
         <!-- Reviews-->
         <div class="row reviews">
            <?php foreach($reviews as $review): ?>
				<?php snippet('items/review', compact('review')) ?>
			<?php endforeach ?>
         </div>
         <!-- End Games-->
         <div class="text-center align-items-center"><a class="btn btn-light" href="#">Load More</a></div>
      </div>
      <div class="col-lg-3 order-lg-1 mb-7">
         <!-- Sidebar-->
         <div>
	         
         </div>
          <div class="widget">
            <form class="search-form" role="search" action="<?= $page->url() ?>">
               <div class="input-group">
                  <input class="form-control" type="text" placeholder="Search" name="q" id="search">
                  <div class="input-group-append">
                     <button class="btn" type="submit" role="search"><i class="fa fa-search" aria-hidden="true"></i></button>
                  </div>
               </div>
            </form>
         </div>
         <div class="filter panel-group primary-bg-color widget" id="filters">
	         <?php if($category == urldecode(param('category'))): ?>
	         <?php $ccategory = $category; ?>
	         
<!-- 	         <a class="<?php e($category == urldecode(param('category')), 'active') ?>" href="<?= url($page->url(), ['params' => ['software' => $software]]) ?>"><?= $ccategory ?></a> -->
	         <?php endif ?>
	         <?php if($software == urldecode(param('software'))): ?>
	         <?php $csoftware = $software; ?>
	         <?php endif ?>
	         
	         <?php if($license == urldecode(param('license'))): ?>
	         <?php $clicense = $license; ?>
	         <?php endif ?>
	         
	         <?php if($payment == urldecode(param('payment'))): ?>
	         <?php $cpayment = $payment; ?>
	         <?php endif ?>
	         
	         <?php if($language == urldecode(param('language'))): ?>
	         <?php $clanguage = $language; ?>
	         <?php endif ?>
	         
	         <?php if($currency == urldecode(param('currency'))): ?>
	         <?php $ccurrency = $currency; ?>
	         <?php endif ?>
	         
	         
            <h5 class="widget-title"><a data-toggle="collapse" href="#category" aria-expanded="true" aria-controls="category">Category</a></h5>
            <ul id="category" class="border-list collapse show">
	           <?php foreach (page('reviews')->reviews_categories()->split() as $category): ?>
	           <li><a href="<?= url($page->url(), ['params' => ['category' => $category, 'software' => $csoftware, 'license' => $clicense, 'payment' => $cpayment, 'language' => $clanguage, 'currency' => $ccurrency]]) ?>"><?= $category ?></a></li>
               <?php endforeach ?>
            </ul>
            <h5 class="widget-title"><a data-toggle="collapse" href="#software" aria-expanded="true" aria-controls="software">Software</a></h5>
            <ul id="software" class="border-list collapse">
	           <?php foreach (page('games')->taxonomy_software()->split() as $software): ?>
               <li><a href="<?= url($page->url(), ['params' => ['category' => $ccategory, 'software' => $software, 'license' => $clicense, 'payment' => $cpayment, 'language' => $clanguage, 'currency' => $ccurrency]]) ?>"><?= $software ?></a></li>
               <?php endforeach ?>
            </ul>
            <h5 class="widget-title"><a data-toggle="collapse" href="#license" aria-expanded="true" aria-controls="license">License</a></h5>
            <ul id="license" class="border-list collapse">
               <?php foreach (page('reviews')->taxonomy_license()->split() as $license): ?>
               <li><a href="<?= url($page->url(), ['params' => ['category' => $ccategory, 'software' => $csoftware, 'license' => $license, 'payment' => $cpayment, 'language' => $clanguage, 'currency' => $ccurrency]]) ?>"><?= $license ?></a></li>
               <?php endforeach ?>
            </ul>
            <h5 class="widget-title"><a data-toggle="collapse" href="#payment" aria-expanded="true" aria-controls="payment">Payment Method</a></h5>
            <ul id="payment" class="border-list collapse">
               <?php foreach (page('reviews')->taxonomy_payment()->split() as $payment): ?>
               <li><a href="<?= url($page->url(), ['params' => ['category' => $ccategory, 'software' => $csoftware, 'license' => $clicense, 'payment' => $payment, 'language' => $clanguage, 'currency' => $ccurrency]]) ?>"><?= $payment ?></a></li>
               <?php endforeach ?>
            </ul>
            <h5 class="widget-title"><a data-toggle="collapse" href="#language" aria-expanded="true" aria-controls="language">Language</a></h5>
            <ul id="language" class="border-list collapse">
               <?php foreach (page('reviews')->taxonomy_language()->split() as $language): ?>
               <li><a href="<?= url($page->url(), ['params' => ['category' => $ccategory, 'software' => $csoftware, 'license' => $clicense, 'payment' => $cpayment, 'language' => $language, 'currency' => $ccurrency]]) ?>"><?= $language ?></a></li>
               <?php endforeach ?>
            </ul>
            <h5 class="widget-title"><a data-toggle="collapse" href="#currency" aria-expanded="true" aria-controls="currency">Currency</a></h5>
            <ul id="currency" class="border-list collapse">
               <?php foreach (page('reviews')->taxonomy_currency()->split() as $currency): ?>
               <li><a href="<?= url($page->url(), ['params' => ['category' => $ccategory, 'software' => $csoftware, 'license' => $clicense, 'payment' => $cpayment, 'language' => $clanguage, 'currency' => $currency]]) ?>"><?= $currency ?></a></li>
               <?php endforeach ?>
            </ul>
         </div>
         <!-- Sidebar End-->
      </div>
   </div>
</div>

On this stage I can generate urls
in this way:

/reviews/category:Popular/software:Amatic

But can’t teach script to filter based on all selected params?

If I try something like that in controller… it gives empty results

	$collection = $page->children()->listed()
		->filterBy('reviews_category', $category,',')
		->filterBy('gaming_software', $software,',')
		->filterBy('licenses', $license,',')
		->filterBy('currencies', $currency,',')
		->filterBy('languages', $language,',')
		->filterBy('restricted_countries', $country,',')
		->filterBy('payment_methods', $payment,',');
		
   	$reviews = $collection;

I think, that something like that should be in cookbook), because many sites needs to filtering content based on few params and fields.

I know, that already ask and have idea to made all using query, and it works, but with params it have much better URL.

UPD------>>>

Found solution for correct filtering

	$collection = pages();

if($search = get('q')) {
	$collection = $reviews->search($search, 'title|text');
	$reviews = $collection;
}


if ($category) {
	$collection = $reviews->filterBy('reviews_category', $category,',');
	$reviews = $collection;
	
}

if ($software) {
  	
	$collection = $reviews->filterBy('gaming_software', $software,',');
	$reviews = $collection;
	print_r($reviews);
}

if ($license) {
  	$collection = $reviews->filterBy('licenses', $license,',');
  	$reviews = $collection;
}

if ($currency) {
  	$collection = $reviews->filterBy('currencies', $currency,',');
  	$reviews = $collection;
}

if ($language) {
	$collection = $reviews->filterBy('languages', $language,',');
	$reviews = $collection;
}

if ($country) {
	$collection = $reviews->filterBy('restricted_countries', $country,',');
	$reviews = $collection;
}

if ($payment) {
	$collection = $reviews->filterBy('payment_methods', $payment,',');
	$reviews = $collection;
	
}