Multiple checkbox filter problem

Hello,

I’ve been tasked with building a website for a project I’m working on and despite good progress with the site itself I’m struggling with the code for a multiple checkbox filter.

The site and page can be found here: www.routetoresilience.co.uk/services which shows each of the mental health services that are available locally.

I created a database collection for the services (#database1) and a page with a repeater (#repeater1) that displays the relevant information but would like visitors to be able to search the listing using the displayed checkboxes as filters (inspired by this website: https://www.kaltra.de/chillers) with the repeater repopulating each time a checkbox is (un)checked.

Each of the checkboxes (#checkbox1 to #checkbox7) refer to a boolean field on the database. So long as a checkbox is checked then any entries that have that field checked should be pushed through to the repeater. Entries can have multiple fields checked (i.e. a service can offer advice and help) but it’s important not to list the entry twice.

Bearing in mind I have little experience with coding in general (and none in Java) I’ve tried to adapt some code I found on the forums:

But it doesn’t work and I’m out of my depth as to being able to understand why.

Any support / guidance would be much appreciated!

Thanks

Adam

Page code as follows:

import wixData from ‘wix-data’;

let originalServicesInfo = ;

$w.onReady(function () {

// Query to get the information from the database

  wixData.query("Services") 
  	.find() 
  	.then((results) => { 
  		originalServicesInfo = results.items; 
  		$w('#repeater1').data = originalServicesInfo; 
  	}) 
  	.catch((err) => { 
  		let errorMsg = err; 
  	}); 

// Set the information to the repeater

  $w('#repeater1').onItemReady(($w, itemData) => { 

// Add here all the relevant elements of the repeater

  	 $w('#text17').text = itemData.title; 
  	 $w('#text20').text = itemData.website; 
  	 $w('#text19').text = itemData.description; 
     $w('#image1').src = itemData.image;   
  }); 

});

// Create a filter results function and use if statement for each of your filter fields

function filterServices(){
const filterArray = ;
if ($w(‘#checkbox1’).checked){
filterArray.push(‘gettingAdvice’);
}
if ($w(‘#checkbox2’).checked) {
filterArray.push(‘help’);
}
if ($w(‘#checkbox3’).checked) {
filterArray.push(‘moreHelp’);
}
if ($w(‘#checkbox4’).checked) {
filterArray.push(‘riskSupport’);
}
if ($w(‘#checkbox5’).checked) {
filterArray.push(‘childOrYoungPerson’);
}
if ($w(‘#checkbox6’).checked) {
filterArray.push(‘parentCarer’);
}
if ($w(‘#checkbox7’).checked) {
filterArray.push(‘educator’);
}
return;
}

// For each user input, create an onChange event, filter the results based on the value (call the filterServices function) and set the filtered information to the repeater

export function checkbox1_change(event, $w) {
//filtering the results
const filteredServices = filterServices(originalServicesInfo);
//setting the data
$w(‘#repeater1’).data = filteredServices;
//setting the repeater elements
$w(‘#repeater1’).onItemReady(($w, itemData) => {
console.log(itemData.title);
$w(‘#text17’).text = itemData.title;
$w(‘#text20’).text = itemData.website;
$w(‘#text19’).text = itemData.description;
$w(‘#image1’).src = itemData.image;
});
}

export function checkbox2_change(event, $w) {
//filtering the results
const filteredServices = filterServices(originalServicesInfo);
//setting the data
$w(‘#repeater1’).data = filteredServices;
//setting the repeater elements
$w(‘#repeater1’).onItemReady(($w, itemData) => {
console.log(itemData.title);
$w(‘#text17’).text = itemData.title;
$w(‘#text20’).text = itemData.website;
$w(‘#text19’).text = itemData.description;
$w(‘#image1’).src = itemData.image;
});
}

export function checkbox3_change(event, $w) {
//filtering the results
const filteredServices = filterServices(originalServicesInfo);
//setting the data
$w(‘#repeater1’).data = filteredServices;
//setting the repeater elements
$w(‘#repeater1’).onItemReady(($w, itemData) => {
console.log(itemData.title);
$w(‘#text17’).text = itemData.title;
$w(‘#text20’).text = itemData.website;
$w(‘#text19’).text = itemData.description;
$w(‘#image1’).src = itemData.image;
});
}

export function checkbox4_change(event, $w) {
//filtering the results
const filteredServices = filterServices(originalServicesInfo);
//setting the data
$w(‘#repeater1’).data = filteredServices;
//setting the repeater elements
$w(‘#repeater1’).onItemReady(($w, itemData) => {
console.log(itemData.title);
$w(‘#text17’).text = itemData.title;
$w(‘#text20’).text = itemData.website;
$w(‘#text19’).text = itemData.description;
$w(‘#image1’).src = itemData.image;
});
}

export function checkbox5_change(event, $w) {
//filtering the results
const filteredServices = filterServices(originalServicesInfo);
//setting the data
$w(‘#repeater1’).data = filteredServices;
//setting the repeater elements
$w(‘#repeater1’).onItemReady(($w, itemData) => {
console.log(itemData.title);
$w(‘#text17’).text = itemData.title;
$w(‘#text20’).text = itemData.website;
$w(‘#text19’).text = itemData.description;
$w(‘#image1’).src = itemData.image;
});
}

export function checkbox6_change(event, $w) {
//filtering the results
const filteredServices = filterServices(originalServicesInfo);
//setting the data
$w(‘#repeater1’).data = filteredServices;
//setting the repeater elements
$w(‘#repeater1’).onItemReady(($w, itemData) => {
console.log(itemData.title);
$w(‘#text17’).text = itemData.title;
$w(‘#text20’).text = itemData.website;
$w(‘#text19’).text = itemData.description;
$w(‘#image1’).src = itemData.image;
});
}

export function checkbox7_change(event, $w) {
//filtering the results
const filteredServices = filterServices(originalServicesInfo);
//setting the data
$w(‘#repeater1’).data = filteredServices;
//setting the repeater elements
$w(‘#repeater1’).onItemReady(($w, itemData) => {
console.log(itemData.title);
$w(‘#text17’).text = itemData.title;
$w(‘#text20’).text = itemData.website;
$w(‘#text19’).text = itemData.description;
$w(‘#image1’).src = itemData.image;
});
}

Hi Adam,

I took a peek at your site. One problem you have is that you’ve connected the repeater to a dataset, but in the code you set the repeater items.

So, first thing you need to do is to disconnect the repeater from the dataset.

Then you need the code…

Here’s a routine that sets up the query with filters for the checkboxes. Notice that I’ve used the .or function to create the desired filter behavior (although I’ve only done three of the checkboxes):

function fillRepeater() {
        wixData.query("services")
        .eq("gettingAdvice", $w("#checkbox1").checked)
        .or(
                wixData.query("services")
                .eq("help", $w("#checkbox2").checked)
        )
        .or(
                wixData.query("services")
                .eq("moreHelp", $w("#checkbox3").checked)
        )
        // add or statements for the other checkboxes      
        .find()
        .then((results) => {
                console.log(results.items);
                originalServicesInfo = results.items;
                console.log(originalServicesInfo.length);
                $w('#repeater1').data = originalServicesInfo;
        })
        .catch((err) => {
                let errorMsg = err;
                console.log(errorMsg);
        });
}

The onReady() function:

$w.onReady(function () {

    // Query to get the information from the database       
    fillRepeater();

    // Set the information to the repeater
    $w('#repeater1').onItemReady(($w, itemData) => {
        // Add here all the relevant elements of the repeater
        $w('#text17').text = itemData.title;
        $w('#text20').text = itemData.website;
        $w('#text19').text = itemData.description;
        $w('#image1').src = itemData.image;  
    });
});

And then connect the onClick() function of all of the Checkbox components to this function:

export function checkbox_changed(event, $w) {
    fillRepeater();
}

The rest of the code on the page you no longer need.

Try this out to get started. You’ll have to add all of the .or condition functions for the other fields/checkboxes, but this should get you going in the right direction.

Good luck,

Yisrael

Hi Yisrael,

Thank you for the fast response.

I’ve tried entering the code (adding in #checkbox4 to account for all the checkboxes in the first category titled ‘level of support’) but wanted to check a couple of things as it’s still not populating.

If the repeater is not connected to a dataset how does the code know where to look for the repeater items? The database is called ‘services’ so I assume wixData.query(“services”) does this?

Also, I’m not sure whether ‘originalServicesInfo’ is correct. I’d copied this from the example code in another forum post but am not sure of it’s purpose or what it should match up to.

Thanks again,

Adam

In case you check the site again I did disconnect the database from the repeater as suggested whilst testing but had to reattach it so the services displayed for visitors until the code is working. Hope that makes sense. Adam

Oops :grimacing:

Somehow I had a minor bug in the code I posted. Here’s the fix:

   .then((results) => {
        //console.log(results.items);
        let services = results.items;
        //console.log(services.length);
        $w('#repeater1').data = services; // fills the repeater
   })
  • Notice that I omitted the let from let services = results.items;

  • I would recommend commenting out the two console.log() statements. I had them there for debugging and unless you’re a big fan of tons of just appearing in the console, you don’t need those statements.

  • the last line populates the repeater, therefore you don’t need to connect the repeater to the dataset

Give it another try with the above lines of code. Keep in mind the filter won’t be totally complete since the conditions for the other fields haven’t been added yet.

Yisrael

Great, added ‘let’ into the code and added ‘checkbox_changed’ as the event handler in the properties of the #checkbox1 to #checkbox4 and the page populates on load.

Unfortunately, clicking on #checkbox2 to #checkbox4 doesn’t do anything and clicking #checkbox1 repopulates the list but not in the expected way i.e. you lose some services that are true under the advice field in the database. Is OR the right function to use taking into consideration that services may be one or more of those fields e.g. a service can be checked as true against advice and help?

I’ve also noticed that using code to populate the repeater elements has resulted in the HTML tags showing up (

). Do I need to change the following to populate as real text, and if so what’s the code?

$w(‘#text19’).text = itemData.description;
becomes
$w(‘#text19’).realtext = itemData.description;
?

The code seems OK to me. Keep in mind that the filtering might not be working as expected since the other checkboxes are not yet being used in the filter. Once that’s done, it will either work properly, or we’ll need to break our heads. :upside_down_face:

For the text field, you need to use the html property:

$w("#text19").html = itemData.description;

I’ve updated the code and published the site - https://www.routetoresilience.co.uk/services

I took out the OR code related to checkboxes 2, 3 and 4 to test and the advice checkbox now works i.e. if the box is checked then all those services with the advice field listed as true populate the repeater. However, if the box is unchecked then it populates the repeater with those services with the advice field listed as false which isn’t the desired outcome and might have caused some of the problems when adding the other checkboxes into the code.

The outcome should be that if the checkbox is unchecked then nothing is returned. I don’t know how to explain but can point to this website built using Wix as an example - https://www.kaltra.de/chillers

Three categories of filters (chillers / compressors / refrigerant similar to my level of support / service user) with the chillers a combination of air, water and / or free (similar to my advice / help / more help / risk support). With all boxes unchecked no results are returned unlike in our code which would return those entries with a false in the advice boolean field.

Also, is there an additional line of code I could use in the onReady to sort results alphabetically?

1 Like

I think that’s what the code I originally used was based on - if the checkbox is checked then pull through that information which is true for that field into the repeater - applied to the first 4 checkboxes. Not sure if that would result in duplicate entries in the repeater though where a service is listed as true for advice and help etc.

I’ve got no idea how to include the second category of filters (service user) at the same time!

I would like to bump this post because I believe I am having the same problem. Correct me Adam if I am wrong but this is my problem - let me know if it is the same as yours:

I have three checkboxes. I have used pretty much the same code Adam has used for his. When previewing my page, all of my checkboxes are checked by default (which is fine), and my repeater is populated with all info. If I uncheck one check box, the repeater changes appropriately. If I uncheck a second box, the repeater is populated with 100% of my data, including both unchecked and checked boxes. Unchecking any two boxes just repopulates the entirety of my data back into my repeater. I do not know why it does this. Does it have something to do with the .or code?

Then, if I uncheck the third box, instead of no results appearing ideally (because all boxes have been unchecked at this point), all of my data showing is in the repeater.

(I apologize Adam, for posting here, if this is not your problem as well. If it is not, I will create a new post on the forum.)

@Alisha, without your code, there is no way to know why the filter is behaving as it is.

@Adam, filters are built using the various condition functions: .eq, .contains, .or, and so on. This allows a high level of complexity - as well as confusion, so you need to carefully think through the logic required for the filter. For example, do you want results that meet condition1 and condition2 , or do you want results that meet condition1 or condition2 ?

In terms of logic (at least for the initial category / first four checkboxes):

If #checkbox1 (or 2 or 3 or 4) is checked, populate the repeater with those items where the associated boolean field is true.

This is what the code in my original post was based on, but the code did not work:

const filterArray = ;
if ($w(’ #checkbox1 ').checked){
filterArray.push(‘gettingAdvice’);
}


I think the .eq function causes the repeater to populate items checked / true and unchecked / false which isn’t required. If the checkbox is unchecked then the site visitor is uninterested in those items and that field can be ignored from the results.


Where can I add sort() to the code to fill the repeater alphabetically ascending?

Thank you.

Right at the beginning of the WixDataQuery API you can see an example of sort (ascending).

Aloha Adam,

I know that this is an old post but the solution you implemented on your website https://www.routetoresilience.co.uk/servicesfor for multiple checkboxes to filter more than one category is exactly what I need to do for my website. I currently have a test website that I’m using to see if I can make this happen. https://rsenoren.wixsite.com/mysite/test-store-page . The checkboxes work for one category but when I tried to add another way to filter the checkboxes stopped working.

This is the code that I am using that works for filtering multiple checkboxes in one category.

Any help would be appreciated! Mahalo!

Aloha!

We ended up getting some external support with our code and they came up with the following:

import wixData from ‘wix-data’;
var boxes = [“#checkbox1”,“#checkbox2”,“#checkbox3”,“#checkbox4”];
var queries = [“gettingAdvice”,“help”,“moreHelp”,“riskSupport”];
var boxes2 = [“#checkbox5”,“#checkbox6”,“#checkbox7”];
var queries2 = [“childOrYoungPerson”,“parentCarer”,“educator”];
var count=0;

/*function fillRepeater() {
var newData=;
var i, j;
for (i = 0; i < boxes.length; i++) {
for (j=0; j < boxes2.length; j++){
if ($w(boxes[i]).checked && $w(boxes2[j]).checked){
wixData.query(“services”)

                .eq(queries[i], true) 
                .eq(queries2[j], true) 

                .find() 

                .then((results) => { 
                    newData = newData.concat(results.items); 
                    //console.info(newData.toString()) 
                    $w('#repeater1').data = newData.sort(function(a, b) { 
                          var nameA = a.title.toUpperCase(); // ignore upper and lowercase 
                          var nameB = b.title.toUpperCase(); // ignore upper and lowercase 
                          if (nameA < nameB) { 
                            return -1; 
                          } 
                          if (nameA > nameB) { 
                            return 1; 
                          } 

                          // names must be equal 
                          return 0; 
                        }); // sorts data; 
                    console.info(newData.toString()); 
                    updateCount(); 
                }) 
                .catch((err) => { 
                    let errorMsg = err; 
                    console.log(errorMsg); 
                }); 
                console.info(newData.toString()); 
        } 

    } 

} 

}*/

function fillRepeater2() {
var newData=;
var temp=0;
var i, j;
for (i = 0; i < boxes.length; i++) {
for (j=0; j < boxes2.length; j++){
if ($w(boxes[i]).checked && $w(boxes2[j]).checked){
temp=wixData.query(“services”)

                .eq(queries[i],  **true** ) 
                .eq(queries2[j],  **true** ) 

                .find(); 

        } 

if (temp!==0)
newData=newData.concat(temp);

    } 

} 

Promise.all(newData) 
    .then((results) => { 

var merged = ;
var k;
for (k=0;k<results.length;k++){
merged=merged.concat(results[k].items);
}
$w(‘#repeater1’).data = merged.sort( function (a, b) {
var nameA = a.title.toUpperCase(); // ignore upper and lowercase
var nameB = b.title.toUpperCase(); // ignore upper and lowercase
if (nameA < nameB) {
return -1;
}
if (nameA > nameB) {
return 1;
}

// names must be equal
return 0;
}); // sorts data;;
updateCount();
})
. catch ((err) => {
let errorMsg = err;
console.log(errorMsg);

    }); 

}

function updateCount(){
count = $w(‘#repeater1’).data.length;
$w(‘#text21’).text=count.toString().concat(’ RESULTS FOUND’);
}

$w.onReady( function () {

// Query to get the information from the database
fillRepeater2();

// Set the information to the repeater
$w(‘#repeater1’).onItemReady(($w, itemData) => {
// Add here all the relevant elements of the repeater
$w(‘#text17’).text = itemData.title;
$w(‘#text20’).text = itemData.website;
$w(‘#text19’).html = itemData.description;
$w(‘#image1’).src = itemData.image;
});
});

export function checkbox_changed(event, $w) {
fillRepeater2();
}

It doesn’t work entirely as intended (e.g. if you select multiple checkboxes in quick sucession it doesn’t have enough time to refresh as intended) and it can get quite slow in populating with the information the more you have.

We’ve decided to remove this outright in the near future and present the information in another way until there’s some clear guidance on how to do this.

Adam

@adam-billson You can avoid the “quick succession” problem by using debounce on the event handlers (click, change, keypress, etc). See the article Give the TextInput onKeyPress Function some time for details. Although it discusses the TextInput component, the principle applies to any event handler that might be invoked multiple times in quick succession.

@adam-billson Hi Adam!

Thank you so much for your reply and help. I will definitely give this a try. I had to implement a workaround for my usability study using the out of the box shopping template/layout to get the ball rolling, however, there are definitely limitations to this layout especially with filtering. As content grows, I want a more stable way to house the information and the flexibility to change the look and feel. Your solution is the first step to this so thank you so much!

Much mahalo and aloha!
-Routhie

$w.onReady( function () {
//TODO: write your page related code here…

});
import wixData from ‘wix-data’;
function ProductFilter() {
wixData.query(“Tour_List”)
.eq(“seasonal”, $w(“#checkbox1”).checked)
.or(
wixData.query(“Tour_List”)
.eq(“cultureExperience”, $w(“#checkbox2”).checked)
)
.or(
wixData.query(“Tour_List”)
.eq(“gourmet”, $w(“#checkbox3”).checked)
)
.or(
wixData.query(“Tour_List”)
.eq(“family”, $w(“#checkbox4”).checked)
)
.or(
wixData.query(“Tour_List”)
.eq(“outdoorActivities”, $w(“#checkbox5”).checked)
)
// add or statements for the other checkboxes
.find()
.then((results) => {
console.log(results.items);
let Tour = results.items;
$w(‘#repeater1’).data = Tour;
})
. catch ((err) => {
let errorMsg = err;
console.log(errorMsg);
});

}

$w.onReady( function () {
ProductFilter();
$w(‘#repeater1’).onItemReady(($w, itemData) => {
// Add here all the relevant elements of the repeater
$w(‘#title’).text = itemData.title;
$w(‘#description’).text = itemData.description;
$w(‘#image4’).src = itemData.photo;
});
});

export function checkbox1_change(event, $w) {
ProductFilter()//Add your code for this event here:
}
export function checkbox2_change(event, $w) {
ProductFilter()//Add your code for this event here:
}
export function checkbox3_change(event, $w) {
ProductFilter()//Add your code for this event here:
}
export function checkbox4_change(event, $w) {
ProductFilter()//Add your code for this event here:
}
export function checkbox5_change(event,$w) {
ProductFilter()//Add your code for this event here:
}


I have tried the above code based on this page but there is always 1 result left even if i unchecked all the boxes. https://www.travelmaster.hk/Tours