0

I have a dataset containing product models, and I want to make a filter that filters through them by reducing the data set based on array filters. I have some radio inputs that perform a filter function to reduce the data set, but they reset the data on each click event.

I want them to check if one radio button has already been selected, and further reduce the data from there.

Here's a breakdown: I have data like this:

const stratPickups = {
    "Model 1": {
        name: "Clean Model",
        design: "Single Coil",
        output: 4,
        look: "traditional"
    },
    "Model 2": {
        name: "Balanced Model",
        design: "Single Coil",
        output: 5,
        look: "traditional"
    },
    "Model 3": {
        name: "Balanced Model 2",
        design: "Single Coil",
        output: 6,
        look: "traditional"
    },
    "Model 4": {
        name: "High Output Model",
        design: "Single Coil",
        output: 8,
        look: "traditional"
    },
}

I also have a change event that loads the data like this:

if (e.target.value === "strat") {
      data = Object.values(stratPickups);
      refreshView(data);

Which calls a function refreshView(data) which populates the screen with "cards" like so:

const refreshView = (dataSet) => {
    
    dataSet.forEach((p) => {
      output.innerHTML += `
    
    <div class="card pickup-result" data-output="${p.output}" data-tone="${p.tone}">
      <div class="card-body">
        <div class="card-heading">
          <h4>${p.name}</h4> 
        </div>
        <hr>
        <h5>Specs:</h5>
          <p><strong>Output: </strong> ${p.output}</p>
          <p><strong>Description:</strong> ${p.description}</p>
        <div class="pickup-info">
          <a class="link-button" target="blank" href="${p.url}">See Product</a> 
        </div>
      </div>
    </div>
    `;
    });
  };

Lastly, I have radio inputs that are supposed to filter the array of data based on appearance and design. It looks like this:

 const filterByTrad = () => {

  if (pickupType.value === "strat") {
      data = Object.values(stratPickups).filter((p) => p.look === "traditional");
    } else if (pickupType.value === "tele") {
      data = Object.values(telePickups).filter((p) => p.look === "traditional");
    }

   //  There are a lot more value types to be loaded.


    let traditionalData = data;

    refreshView(traditionalData) // Refresh the view with the filtered data

}
 const filterByUni = () => {
    
    if (pickupType.value === "strat") {
      data = Object.values(stratPickups).filter((p) => p.look === "unique");
    } else if (pickupType.value === "tele") {
      data = Object.values(telePickups).filter((p) => p.look === "unique");
    } ...

    let uniqueData = data;

    refreshView(uniqueData) // Refresh the view with the filtered data
}

const filterBySingleCoil = () => {
   
    if (pickupType.value === "strat") {
      data = Object.values(stratPickups).filter((p) => p.design === "single-coil");
    } else if (pickupType.value === "tele") {
      data = Object.values(telePickups).filter((p) => p.design === "single-coil");
    }

    let singleCoilData = data
    refreshView(singleCoilData)
}

const filterByHumCancelling = () => {
   
    if (pickupType.value === "strat") {
      data = Object.values(stratPickups).filter((p) => p.design === "hc");
    } else if (pickupType.value === "tele") {
      data = Object.values(telePickups).filter((p) => p.design === "hc");
    }

    let hcData = data
    refreshView(hcData)
}



traditionalAppearance.addEventListener("click", filterByTrad);
uniqueAppearance.addEventListener("click", filterByUni);
singleCoil.addEventListener("click", filterBySingleCoil);
hc.addEventListener("click", filterByHumCancelling);

Problem: The above works, but on every click, it loads the filtered data set. It DOES NOT take into account if any other checkboxes are selected and filter from there. My question is, how would you structure this filter function to take these into account?

Here's a CodePen:CodePen

To reproduce:

  1. Click Stratocaster Pickups from Pickup Type
  2. Click "Unique Appearance" to filter more, notice result
  3. Click "Single Coil" from second set of filters and notice that the dataset gets reloaded.

Update

I have created an object called filters:

const filters = {
  traditional: false,
  unique: false,
  singleCoil: false,
  humCancelling: false,
};

Also added a function called addEventListeners() which controls the true / false value of each object key.

const addEventListeners = () => {
    traditionalAppearance.addEventListener("click", function () {
      filters.traditional = this.checked;
      if (this.checked == true) {
        filterByTrad(data);
      }
    });
    uniqueAppearance.addEventListener("click", function () {
      filters.unique = this.checked;
      if (this.checked == true) {
        filterByUni(data);
      }
    });
    singleCoil.addEventListener("click", function () {
      filters.singleCoil = this.checked;
      if (this.checked == true) {
        filterBysingleCoil(data);
      }
    });
    hc.addEventListener("click", function () {
      filters.humCancelling = this.checked;
      if (this.checked == true) {
        filterByHumCancelling(data);
      }
    }); 
  };
  addEventListeners();

Lastly, I have made my functions Pure like so:

const filterByTrad = (array) => {
    data = Object.values(array).filter((p) => p.look === "traditional");
    refreshWithFilters(data);
    return data;
  };

  const filterByUni = (array) => {
    data = Object.values(array).filter((p) => p.look === "unique");
    refreshWithFilters(data);
  };

  const filterBysingleCoil = (array) => {
    data = Object.values(array).filter((p) => p.design === "single-coil");
    refreshWithFilters(data);
  };

  const filterByHumCancelling = (array) => {
    data = Object.values(array).filter((p) => p.design === "hc");
    refreshWithFilters(data);
  };

  const refreshWithFilters = (data) => {
    refreshView(data);
  };

Now I'm struggling with the next steps, based on the comment below. Is there anytihng you would do differently?

Sackadelic
  • 945
  • 1
  • 11
  • 21

1 Answers1

1

First store an array/object of the status of the inputs (should be checkboxes not radio buttons so they are not exclusive), like

let filters = {single: false, singleCoil: false, unique: true ...}

Then split out your filtering functions to be Pure, so they just take in a list and return a list with that filter having been applied.

Then have a refreshWithFilters method that will select the right tele/strat object, then go through the filters-status object, and apply all the correct filters, before refreshing the view.

Then your input event listeners need to just toggle the status of their associated boolean in the filter-status object, and then call that common filter+refresh method.


Update with code samples

Those filters are doing a side-effect of refreshing, so they are not pure. Input is an array, output is a filtered array, doesn't do anything complex.

const filterByTrad = (array) => Object.values(array).filter((p) => p.look === "traditional");
const filterByUni = (array) => Object.values(array).filter((p) => p.look === "unique");

then for event listeners, there's no point wrapping it all up in a method then calling the method, that doesn't do anything, just have a set of these:

traditionalAppearance.addEventListener("click", function () {
    filters.traditional = this.checked;
    refreshWithFilters()
});
uniqueAppearance.addEventListener("click", function () {
    filters.unique = this.checked;
    refreshWithFilters()
});

They toggle their own filter, then call the big shared function to update it all.

Then that big updatey function will get the original data, go through all the filters to make sure they are all applied, then refresh the data:

const refreshWithFilters = () => {
  let data = (pickupType.value === "strat") ? stratPickups : telePickups;
  data = Object.values(data);

  if (filters.traditional) data = filterByTrad(data);
  if (filters.unique) data = filterByUni (data);

  refreshView(data);

This way you have nice seperated testable filters, all being selectively applied in order, in one large non-repeated shared method, attached to all the click handlers.

Luke Storry
  • 6,032
  • 1
  • 9
  • 22
  • So to clarify, instead of taking in `if(pickupType.value ==="strat") { use list }`, you would recommend taking in a parameter like `function filterBySingleCoil(array) {data = Object.values(array).filter((p) => p.design === "single-coil");}` and pass in the array from there? Also, I'm a little confused on storing the status of the inputs. Can you please elaborate or provide an example? Lastly, why would you use checkboxes over radio inputs? Thanks for your help. – Sackadelic Jul 27 '20 at 17:02
  • Hey Luke, I have updated my answer. I'm still struggling with where to go next. Can you offer some advice? – Sackadelic Jul 28 '20 at 11:40
  • 1
    Sorry id I didn't explain my original architecture suggestion, I've giveen more code samples now – Luke Storry Jul 28 '20 at 13:23
  • Thank you. This is helpful. I have the bare bones working now, but I have a question about checkboxes over radio buttons. Both of these filters are exclusive to each other, for instance, a user would select either single-coil OR hum-canceling, not both. Could the same action work if I changed the checkboxes to Radio buttons? At very least, unchecking a checkbox restores the original data set. – Sackadelic Jul 28 '20 at 14:24
  • For accessibility and UX reasons, stick to the convention: radio buttons you can only select one of many options, checkboxes you can select multiple. – Luke Storry Jul 28 '20 at 16:19
  • 1
    In your original question you said "I want them to check if one radio button has already been selected, and further reduce the data from there." so that sounds like multiple successive filters? – Luke Storry Jul 28 '20 at 16:19
  • Yes, that's exactly what I'm looking for, and I haven't worked with arrays/filters before so this is a bit of a challenge for me. I appreciate your help. I have two sets of radios: 1.) Single Coil / Hum Cancelling (filter by design) and 2.) Traditional Appearance / Unique Appearance (filter by look) – Sackadelic Jul 28 '20 at 16:55
  • So if radio buttons are grouped, they will deselect themselves if another in the group is set, saving you some legwork in your code! See here for how to set that up: https://stackoverflow.com/questions/28543752/multiple-radio-button-groups-in-one-form – Luke Storry Jul 28 '20 at 17:01
  • It might just work, but you might have to disable the hum one when you enable the single coil one if that makes sense, you can do that in the individual change handlers. Throw a `console.log(filters)` at the top of `refreshWithFilters` though, it might just work with the radios. – Luke Storry Jul 28 '20 at 17:03