Skip to content

Instantly share code, notes, and snippets.

@thisbit
Last active October 29, 2022 05:40
Show Gist options
  • Save thisbit/70887b3fc4e5e1283b6314f1fb23d504 to your computer and use it in GitHub Desktop.
Save thisbit/70887b3fc4e5e1283b6314f1fb23d504 to your computer and use it in GitHub Desktop.
Single query multiple loop with php an js filtering feature, with layouts with generatepress
<?php /**
* Enqueue the assets for the filtering staff feature
*/
function apuri_staff_filtering_assets() {
if ( is_page( array( 'nastavnici', 'lecturers' ) ) ) :
wp_enqueue_style( 'apuri-staff-filtering-assets', 'your plugin root' . 'assets/filter_and_search.css', false, '3.0.0.', 'all' );
wp_enqueue_script( 'apuri-staff-filtering-assets', 'your plugin root' . 'assets/filtering_and_search.js', false, '3.0.0.', 'all' );
endif;
}
add_action( 'wp_enqueue_scripts', 'apuri_staff_filtering_assets' );
<?php
/**
* Template Name: Apuri Replace Content Template
* @package WordPress
* @subpackage GeneratePress
* Template Post Type: page
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
get_header();
do_action( 'apuri_replace_content' );
get_footer();
.is-hidden {
display: none !important;
}
.grid {
margin-left: -2rem;
}
.grid h2 {
margin-left: 2rem;
}
.grid div {
display: flex;
flex-wrap: wrap;
justify-content: flex-start;
box-sizing: border-box;
}
.staff {
margin-bottom: 2rem;
}
.hentry {
margin-left: 2rem;
max-width: calc(25% - 2rem);
box-sizing: border-box;
}
.dynamic-featured-image {
width: 100%;
object-fit: cover;
}
.post-image {
min-width: 300px;
min-height: 300px;
}
.post-image img {
width: 100%;
height: 100%;
object-fit: cover;
}
.filter, .reset-filters {
cursor: pointer;
user-select: none;
display: flex;
width: 100%;
justify-content: space-between;
align-items: center;
padding: 1rem 0;
}
.filter::after,
.reset-filters::after {
content: "";
display: block;
height: .8rem;
width: .8rem;
background-color: #222;
border: 3px solid #222;
border-radius: .4rem;
}
.filter:hover::after,
.reset-filters:hover::after,
.filter.active::after,
.reset-filters.active::after {
background-color: #fff;
border: 3px solid #FF5E5E;
}
.search-form.vanilla {
flex-wrap: wrap;
}
.search-form.vanilla .search-reset {
flex: 1;
margin-top: 1em;
}
/**
* Script Name: JS Live Search and Filter feature
* Description: This script provides Vanilla JS filtering and search capability, for a CPT listing Page. It has a fallback to PhP filter for browsers that do not support JS. The JS filter has no Depemndencies but it is limited to filtering and searching elements allreadz in the DOM. It does not fetch anything from DB. The advantage of this filter is speed and UX friendlyness.
* Original idea and code is from here @link https://codepen.io/hilmanski/pen/XWgZYYp?editors=1010 which I extended with fallback and filtering.
*
* TOC
* - DOM ELEMENTS
* -- utils
* - FUNCTION DEFINITIONS
* -- Toggle Section Headers
* -- Live search
* -- Filters
* - FUNCTION EXCUTIONS
* -- Filters
* -- Search
*/
window.addEventListener( "DOMContentLoaded", () => { // wait for DOM loaded
/******************** DOM ELEMENTS *******************/
const jsOrNot = Array.from(document.querySelectorAll( ".js-or-not" ));
const skipLink = document.querySelector( ".skip-link" );
const primaryNavLinks = document.querySelectorAll( ".inside-navigation a" );
const homeLinks = document.querySelectorAll( ".site-branding a" ); // ovo cemo maknuti na pravom sajtu
const cards = document.querySelectorAll( ".hentry" );
const searchInput = document.getElementById( "searchbox" );
const searchReset = document.querySelector( ".search-reset" );
const filterButtons = document.querySelectorAll( ".filter" );
const resetFilters = document.querySelector( ".reset-filters" );
// utils
jsOrNot.forEach( element => element.classList.toggle( "is-hidden" ) );
skipLink.setAttribute( "tabindex", 1 );
homeLinks.forEach( element => element.setAttribute( "tabindex", 1 ) ); // maknuti na pravom sajtu
primaryNavLinks.forEach( element => element.setAttribute( "tabindex", 1 ) );
/******************** FUNCTION DEFINITIONS *******************/
/**
* Toggle Section Headers based on having or not having Content in
*/
function toggleSectionHeadings() {
const sections = document.querySelectorAll( ".staff" );
sections.forEach( (section) => {
if ( section.querySelectorAll(".cards .is-visible").length <= 0 ) {
section.classList.add( "is-hidden" );
} else {
section.classList.remove( "is-hidden" );
}
}, false );
}
/**
* Live search shows and hides cards
*/
function liveSearch() {
let searchQuery = document.getElementById( "searchbox" ).value;
//Use innerText if all contents are visible
//Use textContent for including hidden elements
for ( let i = 0; i < cards.length; i++ ) {
if ( cards[i].textContent.toLowerCase().includes(searchQuery.toLowerCase()) ) {
cards[i].classList.remove( "is-hidden" );
cards[i].classList.add( "is-visible" );
toggleSectionHeadings()
} else {
cards[i].classList.add( "is-hidden" );
cards[i].classList.remove( "is-visible" );
toggleSectionHeadings()
}
}
}
/**
* Filters show and hide the cards too
*/
function filterCards( katedra ) {
for (let i = 0; i < cards.length; i++) {
if ( cards[i].classList.contains( katedra ) ) { // if you belong to my collection
cards[i].classList.remove( "is-hidden" );
cards[i].classList.add( "is-visible" );// do not be
toggleSectionHeadings();
} else if ( ! cards[i].classList.contains( katedra ) ) { // if you are not in my collection
cards[i].classList.add( "is-hidden" ); // please hide
cards[i].classList.remove( "is-visible" );
toggleSectionHeadings();
}
// if one clicks on filter reset, reveal all cards
resetFilters.addEventListener( "click", () => {
cards[i].classList.remove( "is-hidden" );
cards[i].classList.add( "is-visible" );
toggleSectionHeadings();
}, false );
resetFilters.addEventListener( "keyup", ( event ) => {
if ( event.key === "Enter" ) {
cards[i].classList.remove( "is-hidden" );
cards[i].classList.add( "is-visible" );
toggleSectionHeadings();
}
}, false );
}
}
/******************** FUNCTION EXCUTIONS *******************/
// Filters
for ( let i = 0; i < filterButtons.length; i++ ) {
filterButtons[i].addEventListener( "click", () => {
filterCards( "apuri_katedra-" + filterButtons[i].id );
searchInput.value = ""; // also clean search text when using filters
let ifActive = Array.from(document.querySelectorAll( ".active" ));
ifActive.forEach( element => element.classList.remove( "active" ));
filterButtons[i].classList.toggle('active');
}, false );
filterButtons[i].addEventListener( "keyup", ( event ) => {
if ( event.key === "Enter" ) {
filterCards( "apuri_katedra-" + filterButtons[i].id );
searchInput.value = ""; // also clean search text when using filters
let ifActive = Array.from(document.querySelectorAll( ".active" ));
ifActive.forEach( element => element.classList.remove( "active" ) );
filterButtons[i].classList.toggle('active');
}
}, false );
}
// Search
let typingTimer;
let typeInterval = 500;
searchInput.addEventListener( "keyup", () => {
clearTimeout( typingTimer );
typingTimer = setTimeout( liveSearch, typeInterval );
});
searchReset.addEventListener( "keyup", () => { searchInput.value = ""; });
}, false ); // end the on DOM load eventlistener
<?php
/**
* Grid of cpts with filtering, js filter is the better, but there is fallback to php filters
* @notice this markup is optimized for GeneratePress and should be adapted for use with other themes
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
function apuri_staff_filters_and_loops() {
ob_start();
// Get katedra terms
$katedra_terms = get_terms(
array(
'taxonomy' => 'apuri_katedra',
'hide_empty' => true,
)
);
// fallback filters
if ( isset($_POST['katedra']) && ! empty( $_POST['katedra'] ) ) {
$katedra = esc_html__( $_POST['katedra'] );
} else {
$katedra = ( get_locale() == 'en_US' ) ? array( 'printmaking', 'painting', 'intermedia', 'sculpture', 'theory', 'drawing', 'acting' ) : array( 'grafika', 'slikarstvo', 'intermedija', 'kiparstvo', 'teorija', 'crtanje', 'gluma' );
} if ( isset($_POST['order']) && ! empty( $_POST['order'] ) ) {
$order = esc_html__( $_POST['order'] );
} else {
$order = esc_html__( 'ASC' );
}
?>
<div class="widget-area sidebar is-left-sidebar" id="lef-sidebar">
<div class="inside-left-sidebar">
<!-- FALLBACK FILTERS FORM -->
<form class="js-or-not widget inner-padding widget_block widget_search" action="<?php esc_url( the_permalink() ); ?>" method="post" id="nastavnici-fallback-filter" onchange="document.getElementById('nastavnici-fallback-filter').submit();"> <fieldset id="filters-2">
<label><?php _e( 'Filtriraj' ); ?></label>
<a href="<?php esc_url( the_permalink() ); ?>"><?php _e('sve') ?></a>
<?php
foreach ( $katedra_terms as $katedra_term ) :
// escaping
$kslug = esc_html__( $katedra_term->slug );
$kname = esc_html__( $katedra_term->name );
if ( ! empty( $kslug ) ) : ?>
<label for="<?php echo $kslug; ?>"><input type="radio" name="katedra" id="<?php echo $kslug; ?>" value="<?php echo $kname; ?>" <?php
echo ( $kslug == $katedra ) ? 'checked' : '';
?> /> <?php echo $kname; ?></label>
<?php
endif;
endforeach;
?>
<select name="order" id="order" >
<option value="ASC"
<?php echo ( 'ASC' == $order ) ? 'selected="selected"' : ''; ?>
onchange="document.getElementById('nastavnici-fallback-filter').submit()" >ASC</option>
<option value="DESC"
<?php echo ( 'DESC' == $order ) ? 'selected="selected"' : ''; ?>
onchange="document.getElementById('nastavnici-fallback-filter').submit()" >DESC</option>
</select>
<input type="hidden" value="filter" >
</fieldset>
</form>
<!-- END OF FALLBACK FILTERS FORM -->
<!-- JS SEARCH AND FILTERS BLOCK -->
<nav class="js-or-not is-hidden widget inner-padding widget_block widget_search" aria-label="<?php _e( 'Sekundarni izbornik' ); ?>" role="navigation" itemtype="https://schema.org/SiteNavigationElement" itemscope>
<ul class="secondary-menu">
<li>
<a role="link" tabindex="2" class="reset-filters active" id="reset" ><?php _e( 'Sve' ); ?></a>
</li>
<?php
foreach ( $katedra_terms as $katedra_term ) {
$kslug = $katedra_term->slug;
$kname = $katedra_term->name;
if ( ! empty( $kslug ) ) { ?>
<li><a
tabindex="2"
class="filter"
role="link"
id="<?php echo $kslug; ?>"
/> <?php echo $kname; ?></a></li>
<?php
}
}
?><li >
<form class="search-form vanilla" role="search" aria-label="<?php _e( 'Lokalna pretraga stranice' ); ?>" >
<input class='input' tabindex="2" type="search" id="searchbox" placeholder="<?php _e( 'Traži' ); ?>">
<input type="hidden" tabindex="2" class="search-reset" value="<?php _e( 'Poništi' ); ?>">
</form>
</li>
</ul>
</nav>
<!-- JS SEARCH AND FILTERS BLOCK -->
</div>
</div>
<!-- JS QUERY AND LOOPS BLOCK -->
<div <?php generate_do_attr( 'content' ); ?>>
<main <?php generate_do_attr( 'main' ); ?>>
<div class="inside-article">
<?php
$args = array( // single args of the single query
'post_type' => array ( 'apuri_osoblje' ),
'order' => $order,
'orderby' => 'title',
'tax_query' => array(
array(
'taxonomy' => 'apuri_katedra',
'field' => 'slug',
'terms' => $katedra,
'operator' => 'IN',
),
),
);
// global $post;
$zaposlenici = 'da';
$query = new WP_query ( $args ); // single query
$theme = wp_get_theme(); // we test for theme used so we can control templates differently
?>
<section class="zaposlanici staff grid">
<h2 class="section-heading"><?php _e('Zaposlenici'); ?></h2>
<div class="cards">
<?php
if ( $query->have_posts() ) : // master if
// loop one
while ( $query->have_posts() ) : $query->the_post();
if ( get_post_type() === 'apuri_osoblje' && has_term( $zaposlenici, 'apuri_zaposlenici' ) ) : ?>
<article id="post-<?php the_ID(); ?>" <?php post_class(); ?> <?php generate_do_microdata( 'article' ); ?>>
<?php do_action( 'apuri_staff_card' ); ?>
</article>
<?php
endif;
endwhile;
rewind_posts();
?>
</div>
</section>
<section class="suradnici staff grid">
<h2 class="section-heading"><?php _e('Suradnici'); ?></h2>
<div class="cards">
<?php
// loop two
while ( $query->have_posts() ) : $query->the_post();
if ( get_post_type() === 'apuri_osoblje' && ! has_term( $zaposlenici, 'apuri_zaposlenici' ) ) : ?>
<article id="post-<?php the_ID(); ?>" <?php post_class(); ?> <?php generate_do_microdata( 'article' ); ?>>
<?php do_action( 'apuri_staff_card' ); ?>
</article>
<?php
endif;
endwhile;
rewind_posts(); // this is crucial
?>
</div>
</section>
<section class="static-content">
<?php
the_post();
the_content();
?>
</section>
</div>
</main>
</div>
<?php endif; // end master if
$output = ob_get_clean();
return $output;
}
function apuri_add_staff_grid_to_page() {
echo $nastavnici = ( is_page( array( 'nastavnici', 'lecturers' ) ) ) ? apuri_staff_filters_and_loops() : '';
}
/**
* We inject the staff content filtering page in a dedicated template using a custom hook
* @custom_hook do_action( 'apuri_replace_content' );
* @custom_page_template should have this only "get_header(); do_action( 'apuri_replace_content' ); get_footer();"
*/
add_action( 'apuri_replace_content', 'apuri_add_staff_grid_to_page');
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment