Combine Multiple Filters with Velo

Question:
Combine Multiple Filters for a dataset with Velo

Product:
Wix Editor

What are you trying to achieve:
Combine (distinct) location filters (Country, State & City) with other simpler tag filters.

The location filtering is a major feature I’ve tried to implement for long but had to settle for poor workarounds. Until recently when the THE WIX WIZ posted a tutorial for that exact requirement, a proper conditional location filtering using the native capabilities of Wix’s Google Maps.
Despite successfully implementing this location filtering (& expanding it to have Country & City filters), it cost me the other filters I had (eg. no. of rooms, size etc), as I can’t combine them despite days of trying as a noncoder. So I can only use either the location filters together, or the other few filters together, each bundle filter the dataset separately.

What have you already tried:
@thewixwiz How to Design Real Estate Listings & Map with Wix CMS Collections, Repeater](https://youtu.be/aY2fy5l4Ooo?si=jbhaMrAMyFe7TNpV)

The other filters (no. of rooms, size etc) were great combinable filters Wix allows by default with no code & datasets. But I’ve tried & was not able to implement this with code either. Anyway, combining these filters with those robust location filters is what I’m trying.

I’ve checked forums & many YouTube vids but their coding approaches are very different to this tutorial so are hard to relate together.

Also, better if no button is used to apply all filters at once. Rather, each input selection ‘on change’ narrows filtering. (Specially as this was the way in the tutorial)

Complete Location filtering Velo Code:

import wixData from 'wix-data';
$w.onReady(async function () {
    await setupCountriesDropdown();
});

async function getCountries() {
    const listingsQueryResult = await wixData.query("Properties").distinct("mapLocation.country")
    const countries = listingsQueryResult.items;
    return countries;
}

async function setupCountriesDropdown() {
    const countries = await getCountries();
    $w("#countriesDropdown").options = countries.map((country) => ({
        label: country,
        value: country,
    }))
    $w("#countriesDropdown").onChange(filterByCountry);
}

async function filterByCountry() {
    const country = $w("#countriesDropdown").value;
    $w("#listingsDataset").setFilter(
        wixData.filter()
        .eq("mapLocation.country", country)
    )
    await setupStatesDropdown();
    $w('#statesDropdown, #cityDropdown').value = undefined;
}


async function getStates() {
    const country = $w("#countriesDropdown").value;
    const listingsQueryResult = await wixData.query("Properties").eq("mapLocation.country", country).limit(1000).distinct("mapLocation.subdivision")
    const states = listingsQueryResult.items;
    return states;
}

async function setupStatesDropdown() {
    const states = await getStates();
    $w("#statesDropdown").options = states.map((state) => ({
        label: state,
        value: state,
    }))
    $w("#statesDropdown").enable();
    $w("#statesDropdown").onChange(filterByState);
}

async function filterByState() {
    const state = $w("#statesDropdown").value;
    $w("#listingsDataset").setFilter(
        wixData.filter()
        .eq("mapLocation.subdivision", state)
    )
    await setupCitiesDropdown();
    $w('#cityDropdown').value = undefined;
}


async function getCities() {
    const state = $w("#statesDropdown").value;
    const listingsQueryResult = await wixData.query("Properties").eq("mapLocation.subdivision", state).limit(1000).distinct("mapLocation.city")
    const cities = listingsQueryResult.items;
    return cities;
}

async function setupCitiesDropdown() {
    const cities = await getCities();
    $w("#cityDropdown").options = cities.map((city)=>({
        label: city,
        value: city, 
    }))
    $w("#cityDropdown").enable();
    $w("#cityDropdown").onChange(filterByCity);
}

async function filterByCity() {
    const cities = $w("#cityDropdown").value;
    $w("#listingsDataset").setFilter(
        wixData.filter()
        .eq("mapLocation.city", cities)
    )
}

It all depends on the data for the rooms and size but something like this should work. may need adjusting to suit your exact requirements.

import wixData from 'wix-data';

$w.onReady(async function () {
    await setupCountriesDropdown();
    setupRoomsDropdown();
    setupSizeDropdown();
});

async function getCountries() {
    const listingsQueryResult = await wixData.query("Properties").distinct("mapLocation.country");
    return listingsQueryResult.items;
}

async function setupCountriesDropdown() {
    const countries = await getCountries();
    $w("#countriesDropdown").options = countries.map((country) => ({
        label: country,
        value: country,
    }));
    $w("#countriesDropdown").onChange(filterAndApply);
}

async function getStates() {
    const country = $w("#countriesDropdown").value;
    const listingsQueryResult = await wixData.query("Properties")
        .eq("mapLocation.country", country)
        .distinct("mapLocation.subdivision");
    return listingsQueryResult.items;
}

async function setupStatesDropdown() {
    const states = await getStates();
    $w("#statesDropdown").options = states.map((state) => ({
        label: state,
        value: state,
    }));
    $w("#statesDropdown").enable();
    $w("#statesDropdown").onChange(filterAndApply);
}

async function getCities() {
    const state = $w("#statesDropdown").value;
    const listingsQueryResult = await wixData.query("Properties")
        .eq("mapLocation.subdivision", state)
        .distinct("mapLocation.city");
    return listingsQueryResult.items;
}

async function setupCitiesDropdown() {
    const cities = await getCities();
    $w("#cityDropdown").options = cities.map((city) => ({
        label: city,
        value: city,
    }));
    $w("#cityDropdown").enable();
    $w("#cityDropdown").onChange(filterAndApply);
}

function setupRoomsDropdown() {
    const roomOptions = [
        { label: "1", value: "1" },
        { label: "2", value: "2" },
        { label: "3+", value: "3+" }
    ];
    $w("#roomsDropdown").options = roomOptions;
    $w("#roomsDropdown").onChange(filterAndApply);
}

function setupSizeDropdown() {
    const sizeOptions = [
        { label: "Small (0-500 sqft)", value: "small" },
        { label: "Medium (500-1000 sqft)", value: "medium" },
        { label: "Large (1000+ sqft)", value: "large" }
    ];
    $w("#sizeDropdown").options = sizeOptions;
    $w("#sizeDropdown").onChange(filterAndApply);
}

async function filterAndApply() {
    let filter = wixData.filter();

    
    const country = $w("#countriesDropdown").value;
    if (country) {
        filter = filter.eq("mapLocation.country", country);
        await setupStatesDropdown(); // Update dependent dropdowns
    }

    const state = $w("#statesDropdown").value;
    if (state) {
        filter = filter.eq("mapLocation.subdivision", state);
        await setupCitiesDropdown(); // Update dependent dropdowns
    }

    const city = $w("#cityDropdown").value;
    if (city) {
        filter = filter.eq("mapLocation.city", city);
    }

    const rooms = $w("#roomsDropdown").value;
    if (rooms) {
        if (rooms === "3+") {
            filter = filter.ge("rooms", 3);
        } else {
            filter = filter.eq("rooms", parseInt(rooms));
        }
    }

    const size = $w("#sizeDropdown").value;
    if (size) {
        switch (size) {
            case "small":
                filter = filter.lt("size", 500);
                break;
            case "medium":
                filter = filter.between("size", 500, 1000);
                break;
            case "large":
                filter = filter.gt("size", 1000);
                break;
        }
    }

    $w("#listingsDataset").setFilter(filter);
}

1 Like

You DID IT you amazing soul! Thank you so much!!

Couple issues though,

  1. Just found that the filter set by code, overwrites the filters set via dataset settings. How do I maintain this Boolean filter fixed always?

  1. These commands don’t work unlike before:
    $w(‘#statesDropdown, #cityDropdown’).value = undefined;
    $w(‘#cityDropdown’).value = undefined;

I placed them where you’ve instructed as well as in other places, but it doesn’t work as before. Where should I place them?

(These commands are to reset dependent DDs when a parent DD value changes)

  1. I think it would all need to be coded and remove the dataset filtering
  2. Where else did you add the code? Are there any errors in the code panel?

Maybe post up the entire code and I’ll have a look tomorrow.

(currently resting with a broken foot)

Wishing you speedy recovery! Wish I could help code it back to health

  1. I already tried w dataset filtering removed too
  2. no errors. Tried the code under setup…Dropdown

Re the constant filter, it’s a Boolean one of a different field (‘hide’, false) as shown in pic.
As a noncoder I scoured forums, API ref & tutorials on how to deploy this filter & tried .and(), & arranged the code in different ways, but can’t weave it in to the current code. When filtered, it removes valid results too. I feel so close to the solution, prally a very few lines of code is all that’s missing/misarranged. Your support would be a huge relief!:pray:

I’ll show pretty much all the code; 3 of my 7 dropdowns. As the rest are replications

import wixData from 'wix-data';

$w.onReady(async function () {
    await setupCountriesDropdown();
    setupSubjectDropdown();
    setupLevelDropdown();
    setupCapacityDropdown();
});

function setupSubjectDropdown() {
    $w("#subjectDropdown").onChange(filterAndApply);
}

function setupLevelDropdown() {
    $w("#levelDropdown").onChange(filterAndApply);
}

function setupCapacityDropdown() {
    $w("#capacityDropdown").onChange(filterAndApply);
}


async function filterAndApply() {

    let filter = wixData.filter();

    const subject = $w("#subjectDropdown").value;
    if (subject) {
        filter = filter.ge("subjects", subject);
    }

    const level = $w("#levelDropdown").value;
    if (level) {
        filter = filter.ge("level", level);
    }

    const capacity = $w("#capacityDropdown").value;
    if (capacity) {
        filter = filter.ge("capacity", capacity);
    }

    $w("#listingsDataset").setFilter(filter);
}

maybe adding something like this to the filterandapply function

filter = filter.and(
        wixData.filter()
            .ne("hide", true) // 'Hide' is not true
            .isNotEmpty("mapLocation") // 'Map Location' is not empty
    );

Out of all the forum answers I checked, this was the one that worked. Thank you! The solution was .ne
Can you please provide an answer to the 2nd part as well? (‘2. These commands don’t work unlike before…’)

How’s your foot?

Would need to see the whole code and do testing which would need to be charged for.
Feel free to message me directly for contact details and I can quote you.

Foot is still broken, swollen and sore.

I tried something & it worked. I couldn’t have done it without you. Hope you recover soon Dan


For 2. I just put this separately at the bottom which worked

$w('#countriesDropdown').onChange((event) => {
    $w('#statesDropdown, #cityDropdown').value = undefined; 
    $w("#cityDropdown").disable();        
})

$w('#statesDropdown').onChange((event) => {
    $w('#cityDropdown').value = undefined;     
})

Further clarifications on the solution for anyone from the future
Re 1. constant filter: I added it outside the optional filters, without using .and(), so it needn’t be repeated for each optional filter

async function filterAndApply() {

    let filter = wixData.filter();
    filter = filter.ne("hide", true);

I’m not a coder, this is what I did on top of coders’ help, which worked