Skip to content

Instantly share code, notes, and snippets.

@jmabbas
Last active September 23, 2026 12:04
Show Gist options
  • Select an option

  • Save jmabbas/a0c73a2d4a10f8802e83cbf79770d586 to your computer and use it in GitHub Desktop.

Select an option

Save jmabbas/a0c73a2d4a10f8802e83cbf79770d586 to your computer and use it in GitHub Desktop.
GoTrek - Search box update as per input count
/**
* --------------------------------------------------------------------------
* GoTrek: Dynamic Hotel Filter by Location, Adults, and Children
* --------------------------------------------------------------------------
*/
// 1. Output hotel data attributes inside each product item in the loop
add_action( 'woocommerce_before_shop_loop_item', 'gotrek_output_hotel_filter_meta', 1 );
add_action( 'woocommerce_shop_loop_item_title', 'gotrek_output_hotel_filter_meta', 1 );
if ( ! function_exists( 'gotrek_output_hotel_filter_meta' ) ) {
function gotrek_output_hotel_filter_meta() {
static $rendered_products = array();
global $product;
if ( ! $product ) {
return;
}
$product_id = $product->get_id();
if ( in_array( $product_id, $rendered_products, true ) ) {
return;
}
$rendered_products[] = $product_id;
// Fetch hotel group
$hotel_group = function_exists( 'gotrek_get_field' )
? gotrek_get_field( 'hotel_group', $product_id )
: ( function_exists( 'get_field' ) ? get_field( 'hotel_group', $product_id ) : null );
$location = '';
if ( ! empty( $hotel_group['location'] ) ) {
$location = $hotel_group['location'];
} elseif ( ! empty( $hotel_group['location_map']['address'] ) ) {
$location = $hotel_group['location_map']['address'];
}
// Find both HIGHEST single-room capacity and TOTAL hotel capacity across all rooms
$top_adults = 0;
$top_children = 0;
$total_adults = 0;
$total_children = 0;
$rooms_count = 0;
if ( ! empty( $hotel_group['rooms'] ) && is_array( $hotel_group['rooms'] ) ) {
$rooms_count = count( $hotel_group['rooms'] );
foreach ( $hotel_group['rooms'] as $room ) {
$room_id = is_object( $room ) ? $room->ID : absint( $room );
$room_group = function_exists( 'gotrek_get_field' )
? gotrek_get_field( 'room_group', $room_id )
: ( function_exists( 'get_field' ) ? get_field( 'room_group', $room_id ) : null );
$r_adults = isset( $room_group['max_adult'] ) ? absint( $room_group['max_adult'] ) : 0;
$r_children = isset( $room_group['max_children'] ) ? absint( $room_group['max_children'] ) : 0;
// Total capacity of hotel
$total_adults += $r_adults;
$total_children += $r_children;
// Maximum single room capacity
if ( $r_adults > $top_adults ) {
$top_adults = $r_adults;
}
if ( $r_children > $top_children ) {
$top_children = $r_children;
}
}
} else {
// Direct room product or simple product
$room_group = function_exists( 'gotrek_get_field' )
? gotrek_get_field( 'room_group', $product_id )
: ( function_exists( 'get_field' ) ? get_field( 'room_group', $product_id ) : null );
if ( ! empty( $room_group ) ) {
$top_adults = isset( $room_group['max_adult'] ) ? absint( $room_group['max_adult'] ) : 0;
$top_children = isset( $room_group['max_children'] ) ? absint( $room_group['max_children'] ) : 0;
$total_adults = $top_adults;
$total_children = $top_children;
$rooms_count = 1;
}
}
printf(
'<span class="gotrek-hotel-filter-meta" style="display:none;" data-location="%s" data-max-adults="%d" data-max-children="%d" data-total-adults="%d" data-total-children="%d" data-rooms="%d"></span>',
esc_attr( strtolower( trim( $location ) ) ),
$top_adults,
$top_children,
$total_adults,
$total_children,
$rooms_count
);
}
}
// 2. Client-Side Filter Script
add_action( 'wp_footer', 'gotrek_hotel_filter_script', 99 );
if ( ! function_exists( 'gotrek_hotel_filter_script' ) ) {
function gotrek_hotel_filter_script() {
?>
<script type="text/javascript">
(function() {
'use strict';
function cleanStr(str) {
return (str || '')
.toString()
.toLowerCase()
.normalize('NFD')
.replace(/[\u0300-\u036f]/g, '')
.trim();
}
function getCountValue(type) {
// type: 'adult', 'child', 'room'
var paramKey = (type === 'adult' ? 'adults' : (type === 'child' ? 'children' : 'rooms'));
// 1. Check visible counter elements (updates immediately on click)
var countEls = document.querySelectorAll(".js-counter[data-value-change*='" + type + "'] .js-count, .js-counter[data-value-change*='" + type + "'] .js-result");
for (var i = 0; i < countEls.length; i++) {
var txt = countEls[i].textContent.trim();
if (txt !== '') {
var num = parseInt(txt, 10);
if (!isNaN(num)) return num;
}
}
// 2. Check input fields
var inputEls = document.querySelectorAll("input.js-count-" + type + ", input[name='" + paramKey + "'], input.input-" + (type === 'child' ? 'children' : type) + "-value");
for (var j = 0; j < inputEls.length; j++) {
if (inputEls[j].value !== '') {
var num = parseInt(inputEls[j].value, 10);
if (!isNaN(num)) return num;
}
}
// 3. Check text spans (e.g. "1 child" or "0 children")
var spanEls = document.querySelectorAll("span.js-count-" + type);
for (var k = 0; k < spanEls.length; k++) {
var match = spanEls[k].textContent.match(/\d+/);
if (match) {
var num = parseInt(match[0], 10);
if (!isNaN(num)) return num;
}
}
// 4. Fallback to URL params
var urlParams = new URLSearchParams(window.location.search);
if (urlParams.has(paramKey)) {
var num = parseInt(urlParams.get(paramKey), 10);
if (!isNaN(num)) return num;
}
return 0;
}
function getLocationValue() {
var locInput = document.querySelector("input.search, input.location-map, input.location-acf-field, .search-location input[type='text']");
if (locInput && locInput.value.trim() !== '') {
return cleanStr(locInput.value);
}
var urlParams = new URLSearchParams(window.location.search);
if (urlParams.has('location') && urlParams.get('location').trim() !== '') {
return cleanStr(urlParams.get('location'));
}
return '';
}
function filterHotels() {
var searchLoc = getLocationValue();
var searchAdults = getCountValue('adult');
var searchChildren = getCountValue('child');
var searchRooms = getCountValue('room');
var metas = document.querySelectorAll('.gotrek-hotel-filter-meta');
if (!metas.length) return;
var visibleCount = 0;
metas.forEach(function(meta) {
var li = meta.closest('li');
if (!li) return;
var hotelLoc = cleanStr(meta.getAttribute('data-location') || '');
var maxAdults = parseInt(meta.getAttribute('data-max-adults') || '0', 10);
var maxChildren = parseInt(meta.getAttribute('data-max-children') || '0', 10);
var totalAdults = parseInt(meta.getAttribute('data-total-adults') || maxAdults.toString(), 10);
var totalChildren = parseInt(meta.getAttribute('data-total-children') || maxChildren.toString(), 10);
var roomsCount = parseInt(meta.getAttribute('data-rooms') || '1', 10);
var effectiveMaxAdults = Math.max(maxAdults, totalAdults);
var effectiveMaxChildren = Math.max(maxChildren, totalChildren);
// 0. Always hide products with 0 guest/room capacity
if (effectiveMaxAdults === 0 && roomsCount === 0) {
li.style.setProperty('display', 'none', 'important');
return;
}
// 1. Location Check
var locationMatch = true;
if (searchLoc !== '') {
var sLoc = searchLoc === 'bucharest' ? 'bucurest' : searchLoc;
var pLoc = hotelLoc === 'bucharest' ? 'bucurest' : hotelLoc;
locationMatch = (pLoc.indexOf(sLoc) !== -1 || sLoc.indexOf(pLoc) !== -1);
}
// 2. Capacity Check (Hide if requested count exceeds hotel capacity)
var exceedsAdults = (searchAdults > 0 && searchAdults > effectiveMaxAdults);
var exceedsChildren = (searchChildren > 0 && searchChildren > effectiveMaxChildren);
var exceedsRooms = (searchRooms > 0 && roomsCount > 0 && searchRooms > roomsCount);
var isMatch = locationMatch && !exceedsAdults && !exceedsChildren && !exceedsRooms;
// 3. Apply Visibility
if (isMatch) {
li.style.removeProperty('display');
li.style.display = '';
// Clear any hardcoded or lingering display:none in style attribute
if (li.getAttribute('style') && li.getAttribute('style').indexOf('display') !== -1) {
var cleanedStyle = li.getAttribute('style')
.replace(/display\s*:\s*none\s*!important\s*;?/gi, '')
.replace(/display\s*:\s*none\s*;?/gi, '')
.trim();
if (cleanedStyle) {
li.setAttribute('style', cleanedStyle);
} else {
li.removeAttribute('style');
}
}
visibleCount++;
} else {
li.style.setProperty('display', 'none', 'important');
}
});
// No results message handling
var noResultsEl = document.querySelector('.gotrek-js-no-products');
var container = document.querySelector('.wp-block-woocommerce-product-collection, ul.products, .wp-block-query');
if (visibleCount === 0 && (searchLoc !== '' || searchAdults > 0 || searchChildren > 0 || searchRooms > 0)) {
if (!noResultsEl && container) {
noResultsEl = document.createElement('p');
noResultsEl.className = 'gotrek-js-no-products text-center my-4 font-weight-bold';
noResultsEl.textContent = 'No products found matching your selected location and guest capacity.';
container.parentNode.insertBefore(noResultsEl, container.nextSibling);
}
} else if (noResultsEl) {
noResultsEl.remove();
}
}
// Initial runs
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', filterHotels);
} else {
filterHotels();
}
window.addEventListener('load', filterHotels);
// Debounce / delayed execution helper
var timer = null;
function triggerFilter() {
filterHotels();
clearTimeout(timer);
timer = setTimeout(filterHotels, 30);
setTimeout(filterHotels, 100);
setTimeout(filterHotels, 250);
}
// 1. Live click events on plus/minus buttons & counters
['click', 'mouseup', 'touchend'].forEach(function(evt) {
document.addEventListener(evt, function(e) {
if (e.target.closest('.js-plus, .js-minus, .js-up, .js-down, .js-counter, .counter-button-wrap, .searchMenu-guests, .search-submit')) {
triggerFilter();
}
});
});
// 2. Input / Change events on text fields and hidden inputs
document.addEventListener('input', triggerFilter);
document.addEventListener('change', triggerFilter);
document.addEventListener('keyup', triggerFilter);
// 3. MutationObserver on the guest counters and DOM
var guestContainer = document.querySelector('.searchMenu-guests, .js-form-counters, .search-wrap');
if (guestContainer && window.MutationObserver) {
var observer = new MutationObserver(function() {
triggerFilter();
});
observer.observe(guestContainer, {
childList: true,
subtree: true,
characterData: true,
attributes: true
});
}
})();
</script>
<?php
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment