- Leng
- May 13, 2006
-
One song / Glory
One song before I go / Glory
One song to leave behind
No other road
No other way
No day but today
|
Hi thread! I'm pretty embarrassed to be posting this because I'm positive that I'm overlooking something so stupidly simple that I'm an idiot for not being able to see it. This is my first real coding attempt in something like 12+ years so I've forgotten most everything and have been trying to cobble stuff together.
The project:
I'm trying to build a filterable gallery using the base code provided here: https://www.codingnepalweb.com/filterable-image-gallery-html-bootstrap/
However, instead of just one group of filters, I have multiple groups of filters and some of them should be single selection only while others are multiple selection.
Single selection filters:
- type of book (e.g. standalone, 1st in series)
- ebook availability (Kindle Unlimited, Kobo Plus, Google Play, Nook, etc)
- intended audience (all ages, YA, adult, etc)
Multiple selection filters:
- length (e.g. short, medium, long)
- genre (e.g. mystery, action, romance)
Toggling the filters on/off should update the results in real time. So if a user selects "standalone", "Kindle Unlimited", "YA", "short", "mystery" and "action", the only books that show should be short standalone books intended for a YA audience that are available on Kindle Unlimited and are either in the mystery or action genres.
Current status:
I've been able to get the visibility of whether a button is selected or deselected working mostly right so I'm using const selectedFilters = document.querySelectorAll("#filter-buttons .active"); to then figure out what should/shouldn't be showing. I'm then splitting those into two separate arrays, selectedFilterLimits to represent the single selection filters and selectedFilterParams to represent the multiple selection filters.
Somewhere along the way of trying to get the results to display as I want them to, I screwed up and now the filters don't deselect properly.
.
My fail code, which has numerous redundant bits in it from abandoned previous attempts at getting something to work, as well as a couple of parts where I'm SURE it's the culprit but my eyes are totally crossing and I can't figure out the fix:
JavaScript code:// Select relevant HTML elements
const filterReset = document.querySelector("#filter-reset");
const filterButtons = document.querySelectorAll("#filter-buttons .filter-param");
const filterScope = document.querySelectorAll("#filter-scope .filter-param");
const filterAudience = document.querySelectorAll("#filter-audience .filter-param");
const filterLength = document.querySelectorAll("#filter-length .filter-param");
const filterMood = document.querySelectorAll("#filter-mood .filter-param");
const filterEbook = document.querySelectorAll("#filter-ebook .filter-param");
const filterFormat = document.querySelectorAll("#filter-format .filter-param");
const filterRetailer = document.querySelectorAll("#filter-retailer .filter-param");
const filterableCards = document.querySelectorAll("#filterable-cards .card");
const limitScopeTo = [];
const activeFilters = [];
const activeFilterGroups = [];
// Define what filter options there are
const filterScopeOptions = ["standalone","1st"];
const filterEbookOptions = ["ku","kobo","koboplus","nook","googleplay","apple","smashwords"];
const singleSelectFilters = ["scope","ebook"]
// Define what filters are selected
const selectedFilterParams = [];
const selectedFilterLimits = [];
// Function to filter cards based on filter buttons
const filterCards = (e) => {
// define previous states to button press
let previousFilterSelection = activeFilters;
let previousFilterGroups = activeFilterGroups;
let previousScopeSelection = limitScopeTo;
console.log("Previous filter selection is: " + previousFilterSelection);
console.log("Previously selected filter groups: " + previousFilterGroups);
console.log("Previous scope limited to: " + previousFilterGroups);
// current state
let filterSelection = e.target.dataset.filter;
let filterName = e.target.dataset.filtername;
let filterChoice = e.target.dataset.filterchoice;
let currentFilterGroup = filterName;
let filterGroupSelected = document.querySelectorAll("#filter-" + e.target.dataset.filtername + " .filter-param");
let currentState = activeFilters;
// which button has been pressed?
console.log("Filter pressed: " + filterSelection);
console.log("Filter name: " + filterName);
console.log("Filter allows: " + filterChoice + " selection");
console.log("Current active filters: " + currentState);
// if button is already selected, then deselect it
if (activeFilters.includes(filterSelection)) {
console.log("Removing the filter " + filterSelection);
e.target.classList.remove("active");
activeFilters.splice(activeFilters.indexOf(filterSelection));
console.log("Current active filter/s: " + activeFilters);
console.log("Removing the scope " + filterSelection);
limitScopeTo.splice(limitScopeTo.indexOf(filterName));
console.log("Current scope is limited to: " + limitScopeTo);
// if there are no active filters, show everything is reset
console.log("No filters selected, showing all options.");
if (activeFilters.length === 0) {
filterReset.classList.add("active");
}
} else {
if (filterChoice === "single") {
// deselect the other options within the scope filter group
console.log(filterSelection + " is single selection only. Deselecting other filters in " + filterName);
filterGroupSelected.forEach(button => button.classList.remove("active"));
if (activeFilters.length === 0){
console.log("Filters available for removal: none.");
} else {
activeFilters.splice(activeFilters.indexOf(filterSelection));
}
}
// if the filter is multi selection, leave previous filters selected
if (e.target.dataset.filterchoice === "multi") {
console.log(filterName + " is multi selection. Leaving other filters in " + filterName);
}
// select the button and add it to the list of active filters
console.log("Adding " + filterSelection + " to active filters.");
e.target.classList.add("active");
currentState.push(filterSelection);
const selectedFilters = document.querySelectorAll("#filter-buttons .active");
console.log("Selected filters:")
console.log(selectedFilters);
console.log("Active filters:")
console.log(activeFilters);
selectedFilters.forEach(button => {
if (button.dataset.filter === "") {
console.log("Return nothing");
} else {
console.log("Returning value: " + button.dataset.filter);
}
if (button.dataset.filterchoice === "single") {
// add to filter limits only if it's not a null value or already included
if (button.dataset.filter === "" || selectedFilterLimits.includes(button.dataset.filter)) {
console.log("Skipped as either null value or already selected.")
} else {
// clear previous filter and add new to filter limits
// THIS IS THE PART WITH THE PROBLEMS
selectedFilterLimits.push(button.dataset.filter);
}} else {
if (selectedFilterParams.includes(button.dataset.filter)) {
console.log("Skipped as already in filter params");
} else {
selectedFilterParams.push(button.dataset.filter);
}
activeFilters.push(button.dataset.filter);
}
console.log("Currently limiting scope to: " + selectedFilterLimits);
console.log("Current active filter params: " + selectedFilterParams);
console.log("Current state at end of button press: " + activeFilters);
// WHY IS EVERYTHING BEING ADDED TWICE?????? Is it because of the line 6 lines up?
// deselect the reset button
filterReset.classList.remove("active");
});
}
If anyone could point me in the right direction, I would be very grateful for the help!
|
#
¿
Feb 22, 2024 04:31
|
|
- Adbot
-
ADBOT LOVES YOU
|
|
#
¿
May 13, 2024 15:34
|
|
- Leng
- May 13, 2006
-
One song / Glory
One song before I go / Glory
One song to leave behind
No other road
No other way
No day but today
|
I do not find these indents-not-matching-brackets to be well-organized or easy to read, and I suspect without having done much inspection that the problem lies in a condition being in the wrong place from this. Run it through an auto-formatter! (Though I also would be suspicious of a block that says it removes something calling Array.push which is an action that doesn't remove anything.)
From skimming the code:
You seem to be using a non-checkbox element like a checkbox, you could try using checkboxes and trigger your filtering with the "input" event.
You also seem to be doing both the active/not active toggling and filtering in the same pass. I would do the toggling in one function, then run a separate function that just gathers the current selection and filters on that.
You are both 100% on the money.
I am the stupid. I indeed messed up the conditions and also tripped myself up by not separating out my functions. I managed to impose on two others outside of this thread to take a detailed look and they very kindly turned my travesty into this elegant script:
JavaScript code:// Select relevant HTML elements
const filterReset = document.querySelector("#filter-reset");
const filterButtons = document.querySelectorAll("#filter-buttons .filter-param");
const filterableCards = document.querySelectorAll("#filterable-cards .card");
const single_filters_map = new Map()
const multi_filters_map = new Map()
const showCards = () => {
filterableCards.forEach(card => {
//console.log(card.dataset)
//console.log(activeFilters)
const cardFilters = card.dataset.name.split(' ');
const matchesSingleFilters = single_filters_map.length === 0 || Array.from(single_filters_map.values()).every(filter => cardFilters.includes(filter));
const matchesMultiFilters = multi_filters_map.length === 0 || Array.from(multi_filters_map.values()).every(filterArray => filterArray.length === 0 || filterArray.some(filter => cardFilters.includes(filter)));
const isVisible = matchesSingleFilters && matchesMultiFilters;
if (isVisible) {
card.style.display = "block";
} else {
card.style.display = "none";
}
});
console.log("Filters now:");
console.log(single_filters_map);
console.log(multi_filters_map);
}
const clearRow = (filterName) => {
document.querySelector("#filter-" + filterName).querySelectorAll(".filter-param").forEach(child => {
if (child.classList.contains("active")) {
child.classList.remove("active");
}
})
}
// Function to handle filter button click
const handleButtonClick = (button, filterType, filterValue, filterName) => {
console.log("Filter of type:", filterType)
console.log("Filter to value:", filterValue)
if (button.classList.contains("active")) {
button.classList.remove("active")
if (filterType === "single") {
single_filters_map.delete(filterName)
} else {
const arr = multi_filters_map.get(filterName)
arr.splice(arr.indexOf(filterValue), 1)
}
} else {
if (filterType === "single") {
clearRow(filterName)
button.classList.add("active")
single_filters_map.set(filterName, filterValue)
} else {
button.classList.add("active")
if (multi_filters_map.has(filterName)){
multi_filters_map.get(filterName).push(filterValue)
} else {
multi_filters_map.set(filterName, [filterValue])
}
}
}
showCards();
}
// Add event listeners to filter buttons
filterButtons.forEach(button => {
button.addEventListener("click", () => {
const filterType = button.dataset.filterchoice;
const filterValue = button.dataset.filter;
const filterName = button.dataset.filtername;
handleButtonClick(button, filterType, filterValue, filterName);
});
});
// Function to reset filters and show all cards
const resetFilters = () => {
single_filters_map.clear();
multi_filters_map.clear();
filterButtons.forEach(button => {
button.classList.remove("active");
});
showCards();
}
// Add event listener to reset button
filterReset.addEventListener("click", resetFilters);
That saying about using a hammer on everything because it's the only tool you know has never felt more applicable. I'm gonna be chewing over this solution for weeks, and probably off to go read some docs to fill in the holes of my ignorance in the hopes that maybe the next time I write some code, it won't be several hundred lines of brain vomit.
Thank you all for taking a look!
|
#
¿
Feb 23, 2024 01:26
|
|