I am fairly new to Hubl.
NOTE: If this module exists, please share links. It seems like a function that other people would want.
We are making a native plant library, and need a module that filters based on selected columns for our HubDB.
(https://content.gardenforwildlife.com/learn/plant-directory)
The list of plants is a module. and we would love a separate module for filter and sort. This is something I have seen in Blogs, would expect there to be something similar for Hubdb.
I have looked into using Isotope, but cannot seem to get it to work correctly.
I have also tried using hubl to loop/generate the filter options, but since some of the columns are boolean, and some are text, and some are multiselect, it outputs a garbled mess.
I’m at a loss, and have been going in circles for several weeks. Any assistance is appreciated!
Thank you!
Hi @GFW-MillerM this is definitely possible, I’ve created many directories with filters for clients using HubDB and would be happy to refer you to a developer if you need hands-on help.
The video is helpful, unfortunately, I’m having issues applying this onto the hubd output.
one of the elements I’m trying to build is a filter loop that will output all the options of selected columns.
##### NOT THE ACTUAL CODE, JUST A SKETCH OF WHAT I'm GOING FOR ######{% for item in module.column_name %} <h3>{{ item.column_name.label }}</h3> <ul> {% for listitems in {{ item.column_name }}(same as the one entered in the module) %} <li>{{ listitme }}</li> {# no repeats #} {% endfor %} </ul>{% endfor %}
is this too ambitious? the end goal is the use the {{ listitem }}
as a filter tag as well. but that’s another challenge.
let $container = $(".your-grid");
// Function to handle filter clicks
$('.data-filter').on('click', function (event) {
var group = $(this).closest('fieldset').attr('data-group'),
checkbox = $(this).find("input"),
value = checkbox.attr('data-filter'),
hasFilter = false;
var filterGroup = filters[group];
if (!filterGroup) { filterGroup = filters[group] = []; }
if (checkbox.attr("checked") && value) {
checkbox.attr("checked", false);
$(this).removeClass("active");
checkbox.closest("li").removeClass("active");
if (value.indexOf(" ") > -1) {
var value_array = value.split(" ");
for (let x = 0; x < value_array.length; x++) {
var this_index = filterGroup.indexOf(value_array[x]);
filterGroup.splice(this_index, 1);
}
} else {
var index = filterGroup.indexOf(value);
filterGroup.splice(index, 1);
}
} else if (value) {
checkbox.attr("checked", true);
$(this).addClass("active");
if (single_select) {
$(this).siblings().removeClass('active').find("input").attr("checked", false);
}
if (value.indexOf(" ") > -1) {
var value_array = value.split(" ");
for (let i = 0; i < value_array.length; i++) {
filterGroup.push(value_array[i]);
}
} else {
filterGroup.push(value);
}
}
var comboFilter = getComboFilter();
allFilters = comboFilter;
$container.isotope({ filter: comboFilter });
comboFilter = "";
});
So sorry to hear that it’s been giving you trouble for weeks! I have definitely been there, so I hope this helps! Let me know if you have any questions.
This is BRILLIANT! Thank you! Macros were the bit that I was missing. I’m not familiar with writing them, so it looks like that’s the key that I need to read up on.
Thank you again for your assistance! it has been most helpful. The two last remaining issues that I can’t work out are:
1. the hubdb fields that are TEXT and BOOLEAN are not populating.
2. the filter is not joining the queries. the results are OR instead of AND.
- I tried to apply the Isotope js for combining the filters but it just broke all the filter code.
Guidence is appreciated!
thank you
{% set this_table = 7977074 %}{% set all_items = hubdb_table_rows(this_table) %}{% set filter_columns = ["classification", "light_requirements", "soil_moisture", "grows_to", "animal_resistance", "attracts", "soil_description"] %}{# MACROS create code-friendly slugs for valuesrender HTML for filters#}{% macro slugify(string) %}{{ string|string|regex_replace("[^a-zA-Z0-9]", "-")|replace(" ","-")|replace(",","-")|replace("$","-")|replace(":","-")|replace("&","and")|lower|trim }}{% endmacro %}{# MACRO to make single filter and value #}{% macro render_filter(value, option) %}{% if type == 'boolean' %}<li class="data-filter checkbox-select" id="{{ value|trim }}" tabindex="0"><label for="{{ value|trim }}"><input type="checkbox" name="{{ value|trim }}" data-filter=".{{ value|trim }}" /><span>{{ option.name or option.label or option.value or option }}</span></label></li>{% elif type == 'text' %}<li class="data-filter checkbox-select" id="{{ value|trim }}" tabindex="0"><label for="{{ value|trim }}"><input type="checkbox" name="{{ value|trim }}" data-filter=".{{ value|trim }}" /><span>{{ option.name or option.label or option.value or option }}</span></label></li>{% else %}<li class="data-filter checkbox-select" id="{{ value|trim }}" tabindex="0"><label for="{{ value|trim }}"><input type="checkbox" name="{{ value|trim }}" data-filter=".{{ value|trim }}" /><span>{{ option.name or option.label or option }}</span></label></li>{% endif %}{% endmacro %}{% macro render_filter_group(name, filters) %}<div class="filter-group pf-accordion-item" data-filter-group="{{ name }}"><label class="pf-accordion-header"><h4>{{ name|replace('_', ' ')|lower }}<span></span></h4></label><div class="pf-accordion-content" aria-expanded="true"><fieldset class="listing-filters__group" data-group="{{ slugify(name)|trim }}" aria-expanded="true"><ul aria-hidden="false">{% for filter in filters %}{{ filter }}{% endfor %}</ul></fieldset></div></div>{% endmacro %}{# Create your filters #}<div class="filters pf-accordion">{% for column in filter_columns %}{% set options = hubdb_table_column(this_table, column).options %}{% set filters = [] %}{% for option in options %}{% set filter = render_filter(slugify(option.label), option) %}{% do filters.append(filter) %}{% endfor %}{{ render_filter_group(column, filters) }}{% endfor %}</div>{% require_js %}<script>document.addEventListener('DOMContentLoaded', function() {// Initialize Isotopevar grid = document.querySelector('.plant-card-list');var iso = new Isotope(grid, {// optionsitemSelector: '.grid-item',layoutMode: 'fitRows'});// Filter items on checkbox changevar checkboxes = document.querySelectorAll('.data-filter input[type="checkbox"]');checkboxes.forEach(function(checkbox) {checkbox.addEventListener('change', function() {var filters = [];checkboxes.forEach(function(cb) {if (cb.checked) {filters.push(cb.getAttribute('data-filter'));}});var filterValue = filters.length ? filters.join(', ') : '*';iso.arrange({ filter: filterValue });});});});// store filter for each groupvar filters = {};$demo.on( 'click', '.checkbox-select', function() {var $this = $(this);// get group keyvar $buttonGroup = $this.parents('.filter-group');var filterGroup = $buttonGroup.attr('data-filter-group');// set filter for groupfilters[ filterGroup ] = $this.attr('data-filter');// combine filtersvar filterValue = concatValues( filters );$grid.isotope({ filter: filterValue });});// flatten object by concatting valuesfunction concatValues( obj ) {var value = '';for ( var prop in obj ) {value += obj[ prop ];}return value;}// store filter for each groupvar filters = {};$demo.on( 'click', '.checkbox-select', function() {var $this = $(this);// get group keyvar $buttonGroup = $this.parents('.filter-group');var filterGroup = $buttonGroup.attr('data-filter-group');// set filter for groupfilters[ filterGroup ] = $this.attr('data-filter');// combine filtersvar filterValue = concatValues( filters );$grid.isotope({ filter: filterValue });});// flatten object by concatting valuesfunction concatValues( obj ) {var value = '';for ( var prop in obj ) {value += obj[ prop ];}return value;}</script>{% end_require_js %}
I’m so glad this has been helpful!
For the text and boolean fields try modifying the append_this to fallback on the main this_value variable.
{% if is_array %}
{% for val in this_value %}
{% set append_this = val.label or val.name %}
{% do these_options.append(append_this) %}
{% endfor %}
{% else %}
{# change this next line to include this_value #}
{% set append_this = this_value.label or this_value.name or this_value %}
{% do these_options.append(append_this) %}
{% endif %}
For the JS, try using the following code to create your filter. With Isotope, the goal is for that variable to equal the classes that should select your items. So essentially, you need to combine everything into a string like “.class-one.class-two” in order to get AND filtering.
function getComboFilter() {
var combo = [];
for ( var prop in filters ) {
var group = filters[ prop ];
if ( !group.length ) {
combo.push( "*" );
continue;
}
if ( !combo.length ) {
combo = group.slice(0);
continue;
}
var nextCombo = [];
for ( var i=0; i < combo.length; i++ ) {
for ( var j=0; j < group.length; j++ ) {
var item = combo[i] + group[j];
nextCombo.push( item );
}
}
combo = nextCombo;
}
// this is where you join them all together to make the class string
var comboFilter = combo.join('');
return comboFilter;
}
I also just realized that I never gave you the search & sort options! Here’s the JS for that:
// helper function to throttle keydown function
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 );
};
}
// searches as they type!
var $quicksearch = $(`.listing__search input`).keyup( debounce( function() {
qsRegex = new RegExp( $quicksearch.val(), 'gi' );
init_grid();
}) );
// sorting on button click
$(".sort-button").on("click", function() {
let sortBy = $(this).attr("data-sort"),
ascending = true;
if ($(this).is(".active")) {
container.isotope({ sortBy : 'original-order' });
$(this).removeClass("active");
} else {
container.isotope({ sortBy : sortBy, sortAscending: ascending });
$(this).addClass("active");
}
});
Hello, can u please tell where i should call getComboFilter() function? I believe this is for the AND condition of the filters. I cannot see this function calling anywhere