How Can I create Multi Checkbox Filter similar to the Wix store prebuild filter?

Thank you in advance for helping me out with this. I have been searching and trying out codes from examples and the forum for 2 straight weeks without success. I am looking to build the same type of product filter similar to the Wix Store filter. The reason I opt out on Wix store is due to the lack of customization for its product pages. Really appreciate any help on this. Thank you again.

Currently my filter has two checkboxGroup with 5 to 6 values in each. Each checkboxGroup is set to query value from two individual Tag fields in the same database. From one contributors example, I was able to get closer to what I need and was able to query multiple items from one checkboxGroup. But once I select a check box from the second checkboxGroup the query would only match the second checkboxGroup and disregard the first checkboxGroup

The code for that looks like this

import wixData from 'wix-data';
 
const databaseName = 'StoneType';
const databaseField = 'stoneCategory'; 
const databaseField2 = 'stoneColor';

$w.onReady(function () {
 
   $w('#checkboxGroup1').onChange((event) => {
        addItemstoRepeater($w('#checkboxGroup1').value);
 });

   $w('#checkboxGroup2').onChange((event) => {
         addItemstoRepeater($w('#checkboxGroup2').value);
        
 });
 
    $w('#clearButton').onClick((event) => {
        $w("#checkboxGroup1").value = undefined;
        $w("#checkboxGroup2").value = undefined;
        addItemstoRepeater()
 
 }); 
});
 
//populates repeater
function addItemstoRepeater(selectedOption = []) {

 let dataQuery = wixData.query(databaseName);

    if (selectedOption.length > 0) {
        dataQuery = dataQuery.hasSome(databaseField2, selectedOption)
        .or (dataQuery.hasSome(databaseField, selectedOption));
       }

 dataQuery 
 .find()
 .then(results => {
  const filtereditemsReady = results.items;
  $w('#repeater1').data = filtereditemsReady;
  })
 }

Then after checking through the forum I found Velo Ninja’s code and tried it out. I was able to get closer to what I am looking for but the filter would only allow one check box to be selected from each checkboxGroup. When more than one is selected the whole checkbox disappears. Also the onclick reset function stops working with the code.


import wixData from 'wix-data';
 
const databaseName = 'StoneType';
const databaseField = 'stoneCategory'; 
const databaseField2 = 'stoneColor';

$w.onReady(function () {
 
   $w('#checkboxGroup1').onChange((event) => {
       addItemstoRepeater();
   });

   $w('#checkboxGroup2').onChange((event) => {
        addItemstoRepeater();
   });
 
    $w('#clearButton').onClick((event) => {
        $w("#checkboxGroup1").value = undefined;
        $w("#checkboxGroup2").value = undefined;
        addItemstoRepeater()
 }); 
});
 

function addItemstoRepeater(){

 let dataQuery = wixData.query(databaseName);
 let item1, item2

 if ($w('#checkboxGroup1').value[0]!==undefined) {item1 = $w('#checkboxGroup1').value} 
 if (item1!==undefined){ 
  for (var i=0; i < item1.length; i++) {
         dataQuery = dataQuery.hasSome(databaseField, item1[i]) 
  }
 }

 if ($w('#checkboxGroup2').value[0]!==undefined) {item2 = $w('#checkboxGroup2').value}
 if (item2!==undefined){ 
  for (var a=0; a < item2.length; a++) {
         dataQuery = dataQuery.contains(databaseField2, item2[a]) 
  }
 }
 
 dataQuery 
  .find()
  .then(results => {
   const filtereditemsReady = results.items;
   $w('#repeater1').data = filtereditemsReady;
  })
}

This is what my dataBase looks like

This is what my filter looks like

Take a look on this link…

I tried to reconstruct your issue… —> Load —> " DB-Preset-1 "


I must be honest, i solved it in my interactive-example in another way.
I did not use —> “hasSome” (it is ok to use hasSome), but i did it another way.

Your problem will sure be that you are using a kind of AND-FILTERING.
This will be surely the reason why your RESULTS gets lost, when using both CBGs (checkbox-groups).

You can test this little example and use …

  1. —> AND-FILTERING
  2. —> OR-FILTERING

You will find the SWITCH-OPTIONS here…

Did you read this post here???
https://www.wix.com/velo/forum/coding-with-velo/want-to-call-multiple-items-from-database-according-to-user-selection-can-you-help

Or the original INTRODUCTING-POST here…
https://www.wix.com/velo/forum/coding-with-velo/database-filtering-and-grouping-including-caching-of-results

You can do much more of your filtering-system.

What you are searching for will be a kind of OR-FILTERING-SYSTEM.

Select-A or Select-C or Select-D —> [A, C, D] <— RESULT-ARRAY after choice!

Another tip…
Try always to shorten your code as most as possible, for example by using short IDs, short function-names and so on.

The more you shorten your code, the better readable it will be. Of course you also should use plausible IDs for all of your used elements in your project.

How i would do it…

This is just an example to show you a better and more readable code-structure…

import wixData from 'wix-data';
 
const dbName = 'StoneType';
const dbField = 'stoneCategory'; 
const dbField2 = 'stoneColor';

//Options for CheckBox-1------------------------------
 $w('#CBG1').options = [
    {"label": "Porphury", "value": "porphury"},
    {"label": "Basalt", "value": "basalt"},
    {"label": "Granite", "value": "granite"},
    {"label": "Travertine", "value": "travertine"},
 ];

 //Options for CheckBox-2------------------------------
 $w('#CBG2').options = [
    {"label": "Red", "value": "red"},
    {"label": "Black", "value": "black"},
    {"label": "Grey", "value": "grey"},
    {"label": "Gold", "value": "gold"},
    {"label": "Blue", "value": "blue"},
    {"label": "White", "value": "white"},
 ];


//CODE-START....
$w.onReady(()=>{ console.log("START")
    $w('#CBG1').onChange(() => {start_Filter();});
    $w('#CBG2').onChange(() => {start_Filter();});
    $w('#btnClear').onClick(() => {
        $w("#CBG1").value = undefined;
        $w("#CBG2").value = undefined;
        start_Filter();
    }); 
});
 

function start_Filter(){
  let dataQuery = wixData.query(dbName);
  let item1, item2

  if ($w('#CBG1').value[0]!==undefined) {item1 = $w('#CBG1').value} 
  if (item1!==undefined){ 
     for (var i=0; i < item1.length; i++) {
            dataQuery = dataQuery.hasSome(dbField, item1[i]) 
     }
  }

 //-------------------------------------------
  if ($w('#CBG2').value[0]!==undefined) {item2 = $w('#CBG2').value}
  if (item2!==undefined){ 
     for (var a=0; a < item2.length; a++) {
            dataQuery = dataQuery.contains(dbField2, item2[a]) 
     }
  }
 
  dataQuery.find()
  .then(results => {
     const filtereditems = results.items;
     $w('#repeater1').data = filtereditems;
   })
}

BTW: Here…

…you will find an upgraded version of such an OR-FILTER :wink:

@russian-dima
Thank you for the above asnwer. I’ve actually went through all three post you’ve provide on top before posting here. I am not good with coding, so the best i could come up with was the code I have on top.

Can you point me in the right direction as to how to implement .or filter to the code i already have, and I will play around with it on my own to achieve the filter i wish to have.

For the mean time, I will also shorten my code so it is more readable and easier to work with like you have suggested.

Before i can show you the right direction, i want to know if the shown OR-filter is e exactly what you are searching for? Did you do some testings?

Or do you expect another functionality of your filter?

Thank you again for checking in @russian-dima

I tested the OR-filter on your website, and it is very close to what I need. I figure if you can point me in the right direction on how to code that section, I can play around with the code and eventually figure out what I am looking for exactly.

Basically, I am trying to replicate the filter provided by the Wix Shop because the shop itself has too many resitriction on layout and design, so I want to make one that would work on a regular page.

The filter I am looking for would like the below.

When I select one or multiple value from #CBG1 and nothing from #CBG2 , products that match selected #CBG1 values would populate on the repeater.

When I select one or multiple value from #CBG2 and nothing from #CBG1, products that match selected #CBG2 values would populate on the repeater.

When I select a single value from #CBG1 and single vaules from #CBG2, only products that matche both selected #CBG1 value and selected #CBG2 values will populate the repeater.

When I select a single value from #CBG1 and multiple vaules from #CBG2, only products that matche the single selected #CBG1 including any #CBG2 value will populate the repeater

When I select a single value from #CBG2 and multiple vaules from #CBG1, only product matches the single selected #CBG2 including any #CBG1 value will populate the repeater

When I select multiple values from #CBG1 and multiple values on #CBG2, all products that matches selected #CBG1 values and #CBG2 values will popoulate the repeater

not sure if this is clear. Basically the same as the store filter function from wix store

I tried to code it as most as possible the simple way…

User-Interface-------------------------------------------------------------
let VALUE, DATAFIELDS = []
let DATABASE = "StoneType"

DATAFIELDS[0] = "stoneCategory" //--> 1-st. CBG        
DATAFIELDS[1] = "stoneColor"    //--> 2-nd. CBG
//and so on.....
User-Interface-------------------------------------------------------------

$w.onReady(()=>{
   $w('CheckboxGroup').onChange(async(event)=>{
      let selectedElementID = event.target.id;
      let selectedINDEX = Number(event.target.id[event.target.id.length-1]);
      VALUE[selectedINDEX-1] = await $w('#'+selectedElementID).value;
      setTimeout(()=>{FILTER_ENGINE()},100);
   });
})


async function FILTER_ENGINE() {console.log("START_Filter-Engine")
 let query = await wixData.query(DATABASE)
 let uniqueItems = []
 
 if (VALUE) { 
 for (var a = 0; a < DATAFIELDS.length; a++) {
 if (VALUE[a]){
 for (var b = 0; b < VALUE[a].length; b++) {
                query.eq(DATAFIELDS[a], VALUE[a][b])
 //query.contains(DATAFIELDS[a], VALUE[a][b])
 .find()
 .then(async res => { 
 for (var i = 0; i < res.items.length; i++) {
 let ITEM = await res.items[i]
 let ID = await res.items[i]._id; console.log("ID = ",ID)

 // When Array do not include item, add the item to ARRAY.... (unique filtering).    
 if(myFilter.includes(ID)) {console.log("TRUE")}
 else {console.log("FALSE")
                            myFilter.push(ID)
                            uniqueItems.push(ITEM)
 //myArray.push({datafield: DATAFIELD[a], value: VALUE[a][b]})
 }
 }
                    console.log("myFilter= ", myFilter)
                    console.log("Unique-Items = " ,uniqueItems)
 })
 }
 }
 }
}

It’s just a part of the filtering process, you will have to expand the code and it’s functionality.

This looks exciting , let me try this out and get back to you on the result.

Just one quick question
Does this encompass all the checkboxgroup no matter how many I have? or I will need to replicate this for every checkboxGroup that I have?

$w('CheckboxGroup').onChange

You can add checkboxes as many as you want. The code should do the rest.

You can use lodash npm pack and you can create this via looking to this example = https://www.wix.com/velo/example/mega-search

@loeix Sounds interessting, which advantage do i have when using LODASH? Can you give an example.
Didn’t use LODASH yet.

Got it let me try it out

@loeix
I did try using mega serach from velo example before seeking help on the forum. However I was not able the recreate the same type of filter offer by wix sotre. The filter only allows one check box to be selected from each check box group. When I selected more than one the repeater disappears.

       $w('#checkboxGroup1').value.forEach((selectedOption) => {
        dataQuery = dataQuery.hasSome(databaseField, selectedOption)
       })
 
       $w('#checkboxGroup2').value.forEach((selectedOption) => {
        dataQuery = dataQuery.hasSome(databaseField2, selectedOption);
       }) 

I just tried out the code but myFilter cannot be found, so i added the below line to be able test the code. Not sure if this is correct

 let myFilter = []

Then the below happend when I tried to select a check box. I immediately received the following error from the console. "Cannot set property ‘0’ of undefined. "

no changes were applied to the repeater.

@Velo-Ninja
can you help me out again on this
Thank you again.

@megmortalwinlive
I will take a look on it later.

I still did not find enough time to inspect your code.

In the meanwhile, you perhaps take a look onto this one…

import wixData from 'wix-data';

var DATASET = "#dataset1"
//--------------------------------------- 
var REFERENCE0 = "datafield1" // modify to your own DATAFIELD-ID!
var REFERENCE1 = "datafield2" //modify further DATAFIELD here
var REFERENCE2 = "datafield3" //modify further DATAFIELD here
var REFERENCE3 = "datafield4" //modify further DATAFIELD here


export function start_SearchEngine(VALUE) {console.log("Search-Engine running...") 
 let filter =  wixData.filter() 

    filter=filter.contains(REFERENCE0, VALUE)
 .or(filter.contains(REFERENCE1, VALUE)) //you can add different logics --> hasSome()...
 .or(filter.contains(REFERENCE2, VALUE)) //you can add different logics --> between()...  
 .or(filter.eq(REFERENCE3, VALUE)); console.log(filter)

    $w(DATASET).setFilter(filter)
 .then(()=>{
 let count = $w(DATASET).getTotalCount().toString(); console.log("COUNT = " + count)
        $w("#count").text = 'Total Products: (' + count + ')';
 })
}

Modify all DATAFIELDS by your own.
It is also one of my own used or-filtering engines. Perhaps this one also can be used in your project.

@russian-dima
Sorry i don’t understand how export function works.
Do i have to call the function as I do a usual function?
how do i intergrated with my current code if i have other functions?
I know this is some standard basic question but I tried reading information online but was still not able to understnad it.

@megmortalwinlive
Just delete —> Export
And let it work like a normal function.
I will surely find some time tomorrow, to investigate your code a little bit

Ok, back to you, let’s see what we can do :grin:

Ok, like i always do → starting from SCRATCH!

The first thing what i would do in your situation, is to create a function which would FILL ALL MY CBGs (CheckBoxGroups) —> AUTOMATICALY <— directly out from my chosen DB.

"Velo-Ninja, WhatTheHell do you mean?":grin:

Take a look here…(this example is related to the following DATABASE…)
https://www.media-junkie.com/databases

import wixData from 'wix-data';

//-------- USER-INTERFACE -------------------------------------------------
var DBFIELDS = []
var DATABASE = "Team"
var DBLIMIT = 1000
//-----------------------------
DBFIELDS[0] = "sprache";    //<---simple adding new (TAG)-DATAFIELD here --> 1-st. CBG        
DBFIELDS[1] = "bundesland"; //<---simple adding new (TAG)-DATAFIELD here --> 2-nd. CBG
//-------- USER-INTERFACE -------------------------------------------------

$w.onReady(()=>{
   wixData.query(DATABASE)
  .limit(DBLIMIT)
  .find()
  .then(async(results)=>{console.log(results.items);
     let ITEMS = results.items
     get_CBGoptions(ITEMS, DBFIELDS[0], "CBG1"); //<---simple adding new CBG here 
     get_CBGoptions(ITEMS, DBFIELDS[1], "CBG2"); //<---simple adding new CBG here
  });
});

//----------- DO NOT TOUCH THIS FUNCTION !!! -------------------------------------
async function get_CBGoptions(ITEMS, DBFIELD, CBG){
 let uniqueTitles = await [...new Set(getxxx(ITEMS).flat())]; $w('#'+CBG).options = buildOptions(uniqueTitles);
 function getxxx(items) {const titlesOnly=items.map(item => item[DBFIELD]); return [...new Set(titlesOnly)];}
 function buildOptions(item) {return item.map(item => {return {label:item, value:item};});}
}
//----------- DO NOT TOUCH THIS FUNCTION !!! -------------------------------------

OK! First part of your PROJECT should be done!

But what is it for?
You surely have setted-up your CBG-Options, either directly in the Editor-Options, or you have connected the CBG-Options via a DATASET with a Collection to populate your CBGs with options. RIGHT ???

Or you even have a hardcoding for example like something like this one (either directly on your page-code-section, or in a separate public-js-file)…

$w('#CB1').options = [
   {"label": "Burgenland", "value": "Burgenland"},
   {"label": "Kärnten", "value": "Kärnten"},
   {"label": "Niederösterreich", "value": "Niederösterreich"},
   {"label": "Oberösterreich", "value": "Oberösterreich"},
   {"label": "Salzburg", "value": "Salzburg"},
   {"label": "Steiermark", "value": "Steiermark"},
   {"label": "Tirol", "value": "Tirol"},
   {"label": "Vorarlberg", "value": "Vorarlberg"},
   {"label": "Wien", "value": "Wien"},
];

 //Options for CheckBox-2------------------------------
$w('#CB2').options = [
   {"label": "Englisch", "value": "Englisch"},
   {"label": "Estnisch", "value": "Estnisch"},
   {"label": "Finnisch", "value": "Finnisch"},
   {"label": "Französisch", "value": "Französisch"},
   {"label": "Deutsch", "value": "Deutsch"},
   {"label": "Italienisch", "value": "Italienisch"},
   {"label": "Rumänisch", "value": "Rumänisch"},
   {"label": "Russisch", "value": "Russisch"},
   {"label": "Schwedisch", "value": "Schwedisch"},
   {"label": "Spanisch", "value": "Spanisch"},
];

…to populate your CBGs (DropDowns the same).

And EVERY-TIME you perhaps add a new ITEM in your DB, you also have to add a new VALUE in your CODE —> MANUALY !!!

You surely do NOT want to do this all the time!

So your first progress will be to populate the CBG-options → AUTOMATICALY!

…to be continued …
How to do the same for DropDowns, you will find here…
Want to "call" multiple items from database according to user selection. Can you help? - Ask a question - Wix Studio Community forum