Register a SA Forums Account here!
JOINING THE SA FORUMS WILL REMOVE THIS BIG AD, THE ANNOYING UNDERLINED ADS, AND STUPID INTERSTITIAL ADS!!!

You can: log in, read the tech support FAQ, or request your lost password. This dumb message (and those ads) will appear on every screen until you register! Get rid of this crap by registering your own SA Forums Account and joining roughly 150,000 Goons, for the one-time price of $9.95! We charge money because it costs us money per month for bills, and since we don't believe in showing ads to our users, we try to make the money back through forum registrations.
 
  • Post
  • Reply
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.

. :negative:

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!

Adbot
ADBOT LOVES YOU

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

roomforthetuna posted:

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.)

Wheany posted:

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.

:ughh:

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!

  • 1
  • 2
  • 3
  • 4
  • 5
  • Post
  • Reply