No custom programming answer:
A plugin like https://facetwp.com/ will allow you to set up filters as you have described.
Custom programming answer:
You can create a shortcode that renders fields for the user to check and submit. Then, attach a javascript submit handler to the form so that when the user checks a box or two, the form submits to an ajax handler and hands the selected taxonomy over to the custom ajax function.
The custom ajax function formats the users selection properly into a WordPress query, runs a loop and renders HTML and passes it back to the javascript. The javascript then loads it into a container defined by your shortcode.
The following code block is a quick and dirty example of what I am describing above. It's rough, untested and needs polish but it will give you a head start. Hope this helps.
<?php
//Do you want non-logged-in users to use this? Then you need the "nopriv" action below as well as standard wp_ajax
add_action( 'wp_ajax_nopriv_erfan_get_results', 'wperfan_ajax' );
add_action( 'wp_ajax_erfan_get_results', 'wperfan_ajax' );
function wperfan_ajax() {
//Check security of ajax request
check_ajax_referer( 'erfan_security', 'my_security_param' );
$args = array();
//Need security here.
$custom_tax_a = ( isset( $_POST['fields']['custom_tax_a'] ) && is_array( $_POST['fields']['custom_tax_a'] ) ? $_POST['fields']['custom_tax_a'] : false );
$custom_tax_b = ( isset( $_POST['fields']['custom_tax_b'] ) && is_array( $_POST['fields']['custom_tax_b'] ) ? $_POST['fields']['custom_tax_b'] : false );
$tax_query = array();
if ( $custom_tax_a ) {
foreach ( $custom_tax_a as $slug => $tax_name ) {
$tax_query[] = array(
'taxonomy' => 'custom_tax_a', //REPLACE
'field' => 'slug',
'terms' => $slug,
);
}
}
if ( $custom_tax_b ) {
foreach ( $custom_tax_b as $slug => $tax_name ) {
$tax_query[] = array(
'taxonomy' => 'custom_tax_b', //REPLACE
'field' => 'slug',
'terms' => $slug,
);
}
}
$args['tax_query'] = array(
'relation' => "AND",
$custom_tax_a,
$custom_tax_b,
);
$args['post_type'] = 'MY_CUSTOM_POST_TYPE'; // REPLACE
$args['showposts'] = '10'; // REPLACE
//Add other args here... showposts, etc
$query = new WP_Query( $args );
$returndata = false;
if ( $query->have_posts() ) {
while ( $query->have_posts() ) {
$query->the_post();
// Need to customize your template here.
// Also a better way to do this, but for answer purposes:
$returndata .= sprintf( '<article id="item-%s" class="post-result"><h2>%s</h2><div>%s</div></article>', get_the_ID(), get_the_title(), get_the_content() );
}
}
if ( $returndata ) {
return json_encode( array( 'msg' => 'filtered', 'data' => $returndata ) );
}
return json_encode( array( 'msg' => 'no_filter', 'data' => $returndata ) );
}
add_shortcode( 'erfan_filters', 'erfan_filters_shortcode' );
function erfan_filters_shortcode() {
$custom_tax_a = get_terms( 'custom_tax_a', array( //REPLACE
'hide_empty' => true,
) );
$custom_tax_b = get_terms( 'custom_tax_b', array( //REPLACE
'hide_empty' => true,
) );
ob_start(); ?>
<div class="erfan-custom-filter">
<form action="">
<div class="filter-block custom-tax-one">
<?php foreach ( $custom_tax_a as $ctone ) {
echo sprintf( '<div>
<input type="checkbox" id="%s" name="%s" value="%s">
<label for="%s">%s</label>
</div>', $ctone->slug, 'custom_tax_a[]', $ctone->slug, $ctone->slug, $ctone->name );
} ?>
</div>
<div class="filter-block custom-tax-one">
<?php foreach ( $custom_tax_b as $cttwo ) {
echo sprintf( '<div>
<input type="checkbox" id="%s" name="%s" value="%s">
<label for="%s">%s</label>
</div>', $cttwo->slug, 'custom_tax_b[]', $cttwo->slug, $cttwo->slug, $cttwo->name );
} ?>
</div>
<?php wp_nonce_field( 'erfan_security', 'my_security_param', true, true ); ?>
<input type="submit" value="Submit"/>
</form>
</div>
<div class="erfan-results-container">
<div class="erfan-results">
<?php // The Custom Posts found will go here...
?>
</div>
</div>
<?php
echo ob_get_clean();
}
add_action( 'wp_footer', 'wperfan_footer_script' );
function wperfan_footer_script() {
ob_start();
?>
<script type="text/javascript">
/**
* jQuery serializeObject
* @copyright 2014, macek <[email protected]>
* @link https://github.com/macek/jquery-serialize-object
* @license BSD
* @version 2.5.0
*/
!function (e, i) {
if ("function" == typeof define && define.amd) define(["exports", "jquery"], function (e, r) {
return i(e, r)
}); else if ("undefined" != typeof exports) {
var r = require("jquery");
i(exports, r)
} else i(e, e.jQuery || e.Zepto || e.ender || e.$)
}(this, function (e, i) {
function r(e, r) {
function n(e, i, r) {
return e[i] = r, e
}
function a(e, i) {
for (var r, a = e.match(t.key); void 0 !== (r = a.pop());) if (t.push.test(r)) {
var u = s(e.replace(/\[\]$/, ""));
i = n([], u, i)
} else t.fixed.test(r) ? i = n([], r, i) : t.named.test(r) && (i = n({}, r, i));
return i
}
function s(e) {
return void 0 === h[e] && (h[e] = 0), h[e]++
}
function u(e) {
switch (i('[name="' + e.name + '"]', r).attr("type")) {
case"checkbox":
return "on" === e.value ? !0 : e.value;
default:
return e.value
}
}
function f(i) {
if (!t.validate.test(i.name)) return this;
var r = a(i.name, u(i));
return l = e.extend(!0, l, r), this
}
function d(i) {
if (!e.isArray(i)) throw new Error("formSerializer.addPairs expects an Array");
for (var r = 0, t = i.length; t > r; r++) this.addPair(i[r]);
return this
}
function o() {
return l
}
function c() {
return JSON.stringify(o())
}
var l = {}, h = {};
this.addPair = f, this.addPairs = d, this.serialize = o, this.serializeJSON = c
}
var t = {
validate: /^[a-z_][a-z0-9_]*(?:\[(?:\d*|[a-z0-9_]+)\])*$/i,
key: /[a-z0-9_]+|(?=\[\])/gi,
push: /^$/,
fixed: /^\d+$/,
named: /^[a-z0-9_]+$/i
};
return r.patterns = t, r.serializeObject = function () {
return new r(i, this).addPairs(this.serializeArray()).serialize()
}, r.serializeJSON = function () {
return new r(i, this).addPairs(this.serializeArray()).serializeJSON()
}, "undefined" != typeof i.fn && (i.fn.serializeObject = r.serializeObject, i.fn.serializeJSON = r.serializeJSON), e.FormSerializer = r, r
});
window.wperfan_filter = (function (window, document, $, undefined) {
'use strict';
var app = {};
app.init = function () {
app.the_form = $('.erfan-custom-filter form');
the_form.on('submit', app.process_form);
};
app.process_form = function (e) {
e.preventDefault();
var data = $(this).serializeObject();
var $filter_call = $.ajax({
url: ajaxurl,
type: "POST",
data: {
'action': 'erfan_get_results',
'security': data.my_security_param
'fields': data
}
});
$.when($filter_call).then(
function (response) {
if (response.msg === 'filtered') {
console.log('Filter worked');
}
if (response.msg === 'no_filter') {
console.log('Default results, no filter.');
}
$('.erfan-results').html(response.data);
},
function (error) {
console.log('ERROR');
console.log(error);
alert('An error occured on the server.') //REPLACE
});
return false;
};
$(document).ready(function () {
if ($('.erfan-custom-filter').length > 0) {
app.init();
//If you want the form to load data on page load, uncomment the following:
// app.the_form.trigger('submit');
}
});
})(window, document, jQuery);
</script>
<?php
echo ob_get_clean();
}
Resources: