This tutorial provides the PHP & JS codes that can be pasted in order to filter your markers on a map each time you digit a letter in search field inside the Bricks Builder.
Table of Contents
Requirements
Bricks structure
Let’s try to apply a filter to both the store list and the map based on a quick search field. In order to achieve it, we’ll have to load the Isotope JS library (included as a core library of Bricks) and slightly modify our Bricks structure by adding a dedicated wrapper:
Add a class isotope-wrapper
to it:
And finally add a 100% width to this container:
Let’s move on to our new Code element and add our search input by adding the following HTML:
<input type="text" class="quicksearch" placeholder="Search" />
Now let’s jump on the Store Header element and add a new data-attribute called data-store
and let’s add post_id
as the value:
Finally let’s open our map canvas and add a new data-attribute called data-leaflet-max-zoom
with value 12 (feel free to change it to fits your needs):
And that should be it for the settings in Bricks. Let’s code some PHP and JavaScript!
The PHP
First of all, let’s enqueue the Isotope JS script and do some changes to our previous code, so we can dynamically add and remove our markers based on the input file. Replace the previous code with this one:
add_action( 'wp_enqueue_scripts', function() {
// Enqueue your files on the canvas & frontend, not the builder panel. Otherwise custom CSS might affect builder)
if ( ! bricks_is_builder_main() ) {
// Isotope JS
if ( get_field( 'activate_isotopejs' ) ) {
wp_enqueue_script( 'bricks-isotope' );
}
// Leaflet
if ( get_field( 'activate_leaflet' ) ) {
wp_enqueue_script( 'leaflet', get_stylesheet_directory_uri() . '/js/leaflet/leaflet.js', array(), filemtime( get_stylesheet_directory() . '/js/leaflet/leaflet.js' ) );
wp_enqueue_style( 'leaflet', get_stylesheet_directory_uri() . '/css/leaflet/leaflet.css', filemtime( get_stylesheet_directory() . '/css/leaflet/leaflet.css' ) );
wp_enqueue_script( 'leaflet_init', get_stylesheet_directory_uri() . '/js/leaflet_init.js', array('leaflet'), filemtime( get_stylesheet_directory() . '/js/leaflet_init.js' ) );
$cities = "var stores = [];var boundsList = []; var cities = L.layerGroup();";
// CUSTOM QUERY
$args = array(
'post_type' => 'stores',
'posts_per_page' => '-1',
'post_status' => 'publish'
);
$query = new WP_Query( $args );
// GET POST SETTINGS
if ( $query->have_posts() ) :
while ( $query->have_posts() ):
$query->the_post();
$id = get_the_ID();
$title = get_field( 'store_name' );
$address = get_field('store_address');
$lat = get_field( 'store_lattitude' );
$long = get_field( 'store_longitude' );
$cities .= "stores[" . $id . "] = L.marker([" . esc_attr( $lat ) . "," . esc_attr( $long ) . "]).bindPopup(`<a href=" . get_permalink() . "><h4>" . esc_attr( $title ) . "</h4></a><br>" . $address . "`);boundsList.push( new L.LatLng( ". $lat .", ". $long ." ) );";
endwhile;
$cities .= "stores.forEach( store => store.addTo( cities ) )";
endif;
wp_add_inline_script( 'leaflet_init', $cities, 'before' );
}
}
});
The JavaScript
Replace the previous script by this one:
window.addEventListener('load', () => {
const canvas = document.querySelector('#map');
if (!document.body.classList.contains('bricks-is-frontend') || canvas === null ){
return;
}
var mbAttr = 'Map data © <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, Imagery © <a href="https://www.mapbox.com/">Mapbox</a>';
var mbUrl = 'https://api.mapbox.com/styles/v1/{id}/tiles/{z}/{x}/{y}?access_token=pk.eyJ1IjoibWFwYm94IiwiYSI6ImNpejY4NXVycTA2emYycXBndHRqcmZ3N3gifQ.rJcFIG214AriISLbB6B5aw';
var streets = L.tileLayer(mbUrl, {id: 'mapbox/streets-v11', tileSize: 512, zoomOffset: -1, attribution: mbAttr});
var osm = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 19,
attribution: '© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
});
var satellite = L.tileLayer(mbUrl, {id: 'mapbox/satellite-v9', tileSize: 512, zoomOffset: -1, attribution: mbAttr});
var map = L.map('map', {
center: [canvas.dataset.leafletLat, canvas.dataset.leafletLong],
zoom: canvas.dataset.leafletZoom,
maxZoom: canvas.dataset.leafletMaxZoom,
layers: [osm, cities]
});
var baseLayers = {
'OpenStreetMap': osm,
'Streets': streets,
'Satellite': satellite
};
var layerControl = L.control.layers(baseLayers).addTo(map);
// Jump to map on click
const storeHeadersLink = document.querySelectorAll('.store-header-link');
if (storeHeadersLink.length > 0){
storeHeadersLink.forEach( (link) => {
link.addEventListener('click', (e) => {
e.preventDefault();
let lat = e.target.dataset.leafletLat;
let long = e.target.dataset.leafletLong;
map.flyTo([lat,long], 17);
});
});
};
// Filter search input with IsotopeJS
// quick search regex
var qsRegex;
// init Isotope
var iso = new Isotope( '.isotope-wrapper', {
itemSelector: '.store-wrapper',
layoutMode: 'fitRows',
filter: function( itemElem ) {
return qsRegex ? itemElem.textContent.match( qsRegex ) : true;
},
});
// use value of search field to filter
var quicksearch = document.querySelector('.quicksearch');
quicksearch.addEventListener( 'keyup', debounce( function() {
qsRegex = new RegExp( quicksearch.value, 'gi' );
// delete the current markers
cities.clearLayers();
//filter the store list
iso.arrange();
//add the new filtered markers to the map
boundsList = [];
var filteredItems = iso.filteredItems;
filteredItems.forEach(elm => {
elmt = elm.element;
let header = elmt.querySelector('.store-header')
let storeId = header.dataset.store;
let lat = header.dataset.leafletLat;
let long = header.dataset.leafletLong;
stores[storeId].addTo(cities);
boundsList.push(new L.LatLng(lat, long) );
} );
map.fitBounds(boundsList, {padding: [50, 50]});
}, 200 ) );
// debounce so filtering doesn't happen every millisecond
function debounce( fn, threshold ) {
var timeout;
threshold = threshold || 100;
return function debounced() {
clearTimeout( timeout );
var args = arguments;
var _this = this;
function delayed() {
fn.apply( _this, args );
}
timeout = setTimeout( delayed, threshold );
};
}
});
We’ve added a new maxZoom property to our map to avoid zooming too far into the map when we filter single elements:
var map = L.map('map', {
center: [canvas.dataset.leafletLat, canvas.dataset.leafletLong],
zoom: canvas.dataset.leafletZoom,
maxZoom: canvas.dataset.leafletMaxZoom,
layers: [osm, cities]
});
We added the following script to initialize Isotope JS and match the search input as our filter:
// Filter search input with IsotopeJS
// quick search regex
var qsRegex;
// init Isotope
var iso = new Isotope( '.isotope-wrapper', {
itemSelector: '.store-wrapper',
layoutMode: 'fitRows',
filter: function( itemElem ) {
return qsRegex ? itemElem.textContent.match( qsRegex ) : true;
},
});
// use value of search field to filter
var quicksearch = document.querySelector('.quicksearch');
quicksearch.addEventListener( 'keyup', debounce( function() {
qsRegex = new RegExp( quicksearch.value, 'gi' );
// delete the current markers
cities.clearLayers();
//filter the store list
iso.arrange();
//add the new filtered markers to the map
boundsList = [];
var filteredItems = iso.filteredItems;
filteredItems.forEach(elm => {
elmt = elm.element;
let header = elmt.querySelector('.store-header')
let storeId = header.dataset.store;
let lat = header.dataset.leafletLat;
let long = header.dataset.leafletLong;
stores[storeId].addTo(cities);
boundsList.push(new L.LatLng(lat, long) );
} );
// Bound the filtered markers to the map
map.fitBounds(boundsList, {padding: [50, 50]});
}, 200 ) );
Note that we also added an eventListener
on the quick search input so each time the user will digit a character inside the input, the script will populate the map with the filtered markers and calculate the correct bounds so all the markers are visible on the resulting map.
Finally, we added a setTimeout function to delay the results by 200ms between the moment the character is typed and the results are shown – that will avoid excessive CPU usage and give a better UX experience:
// debounce so filtering doesn't happen every millisecond
function debounce( fn, threshold ) {
var timeout;
threshold = threshold || 200;
return function debounced() {
clearTimeout( timeout );
var args = arguments;
var _this = this;
function delayed() {
fn.apply( _this, args );
}
timeout = setTimeout( delayed, threshold );
};
}
Conclusion
If everything is working correctly, you should be now able to filter your store list with a quick search input:
Pretty cool isn’t?
4 comments
amir
If the markers is hidden after writing in the search box, add .store-header classe to Store header element.
Shahi
Your code only work in Firefox when we are logout but if login it show the undefined error. I guess your code has some issue with either with bricks or isotop or the JS code has element that is not properly defined for for Firefox compatibility.
Shahi
Hi Maxime Beguin,
I got stuck on search box. It is not working. Would like look at the site please. In case I did something wrong. I can't figure out.
dev.bloomdirect.co.uk/find-a-florist/
shahi
Hi Maxime Beguin,
I got stuck on search box. It is not working. Would like look at the site please. In case I did something wrong. I can't figure out.
https://dev.bloomdirect.co.uk/find-a-florist/