简体   繁体   中英

Filter based on multiple conditions and multiple arrays?

I have a list of products in JSON like this:

{
    "products": [
        {
            "tags": ["filter-color-White", "filter-style-Low 1", "5", "6", "7", "8", "9", "10", "11", "12", "13"],
            "styles": ["filter-style-Low 1"],
            "colors": ["filter-color-White"],
            "sizes": ["5", "6", "7", "8", "9", "10", "11", "12", "13"]
        }, 
        {
            "tags": ["filter-color-Black", "filter-color-Red", "filter-color-Blue", "filter-style-Boot", "7", "8", "9", "12", "13"],
            "styles": ["filter-style-Boot"],
            "colors": ["filter-color-Black", "filter-color-Red", "filter-color-Blue"],
            "sizes": ["7", "8", "9", "12", "13"]
        }, 
        {
            "tags": ["filter-color-Black", "filter-color-Red", "filter-style-Gat", "7", "8", "9", "10", "11", "12", "13"],
            "styles": ["filter-style-Gat"],
            "colors": ["filter-color-Black", "filter-color-Red"],
            "sizes": ["7", "8", "9", "10", "11", "12", "13"]
        }
        ...
        ...
        ...
    ]
}

As you can see, there are styles , colors , and sizes . And there's also a tags item which is actually made up out of all three of those previous ones.

I need for someone to be able to filter based on multiple selections. As in, if someone selects the Low 1 style and then black, then show them black items in that style. But if they ALSO select white, then show them white OR black, or both. Same with size selections. Example: Low 1 items that are black, or white, AND size 5, or size 6, or both.

Lastly, multiple styles should be selectable at once and also colors and sizes should be selectable without a style being selected. So the above example, but then add in another style on top like the Boot style. It should then return products that match ALL the criteria.

Is this even doable?

Currently I'm doing it like this:

let filterOptions = this.selectedOptions;
this.products.forEach(product => {
  if (filterOptions.some(item => product.tags.includes(item))) {
    return product.visible = true;
  } else if(filterOptions.length == 0) {
    return product.visible = true;
  } else {
    return product.visible = false;
  }
})

where product.visible is just a simple bool that allows vuejs to show the item on the page or not and this.selectedOptions is an array that gets dynamically populated every time someone adds/removes an option inside the filter. Example of what it would like like:

this.selectedOptions = ["filter-style-Erving","filter-color-Black","filter-color-Brown","8","9"]

The above filtering code works but not reliably. It returns all items that match any of the criteria, irrespective of the other selected filters. If I change some to every the opposite happens where it then tries to only find items that have a red AND black color. Or a 5 AND 6 size, etc.

I'm more or less trying to replicate the filtering on https://www.everlane.com/collections/mens-tees

You might find this useful.

 Vue.config.productionTip = false; Vue.config.devtools = false; new Vue({ el: '#hook', template: '#app-template', data: () => ({ data: [{ styles: ["filter-style-Low 1"], colors: ["filter-color-White"], sizes: ["5", "6", "7", "8", "9", "10", "11", "12", "13"] }, { styles: ["filter-style-Boot"], colors: ["filter-color-Black", "filter-color-Red", "filter-color-Blue"], sizes: ["7", "8", "9", "12", "13"] }, { styles: ["filter-style-Gat"], colors: ["filter-color-Black", "filter-color-Red"], sizes: ["7", "8", "9", "10", "11", "12", "13"] } ], filters: { styles: [], colors: [], sizes: [] }, additiveFiltering: false }), computed: { products() { return this.data.map(product => ({ ...product, tags: this.tags(product) })) }, filteredProducts() { return this.products.filter( this.additiveFiltering ? p => this.x(this.filters.styles, p.styles).length || this.x(this.filters.colors, p.colors).length || this.x(this.filters.sizes, p.sizes).length : p => (!this.filters.styles.length || this.x(this.filters.styles, p.styles).length) && (!this.filters.colors.length || this.x(this.filters.colors, p.colors).length) && (!this.filters.sizes.length || this.x(this.filters.sizes, p.sizes).length) ) }, allStyles() { return this.getAll('styles') }, allColors() { return this.getAll('colors') }, allSizes() { return this.getAll('sizes') } }, methods: { tags(product) { return [].concat(product.styles, product.colors, product.sizes) }, logger(obj) { return JSON.stringify(obj, null, 2) }, getAll(prop) { return [ ...new Set([].concat.apply([], this.data.map(item => item[prop])))] }, x(arr1, arr2) { return arr1.filter(val => arr2.includes(val)) } } }) 
 ul>li>span { background-color: #333; color: white; padding: 0 5px 2px; margin: 0 5px 5px 0; border-radius: 3px; font-variant: all-petite-caps; font-family: monospace; } .filters { display: flex; } .filters > div { flex: 1; } .filters > div:last-child { columns: 2; } .filters > div:last-child div{ column-span: all; } .filters label { display: block; } ul { list-style-type: none; padding: 0; } li { margin: 5px; border: 1px solid transparent; padding: 1rem; } .selected { background-color: #f5f5f5; border-color: red; } 
 <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script> <script type="text/template" id="app-template"> <div id="app"> <div class="filters"> <div> <div>Style</div> <label v-for="style in allStyles" :key="style"> <input type="checkbox" v-model="filters['styles']" :value="style"> {{style}} </label> </div> <div> <div>Colors</div> <label v-for="color in allColors" :key="color"> <input type="checkbox" v-model="filters['colors']" :value="color"> {{color}} </label> </div> <div> <div>Sizes</div> <label v-for="size in allSizes" :key="size"> <input type="checkbox" v-model="filters['sizes']" :value="size"> {{size}} </label> </div> </div> Additive filtering: <input type="checkbox" v-model="additiveFiltering"> <h3>Products</h3> <ul> <li v-for="(product, key) in products" :key="key" :class="{selected: filteredProducts.includes(product)}"> <span v-for="tag in product.tags" :key="tag" v-text="tag"></span> </li> </ul> <pre v-text="{filters}"></pre> </div> </script> <div id="hook"></div> 

The relevant bit is the x method, which is an array intersection.

This filter is actually open.

But I think you are looking for something where some items are AND and others OR

So if user picks a size you want to filter those.

But color is an || option?

If so, I will correct to that model. Let me know.

 const p = { "products": [ { "tags": ["filter-color-White", "filter-style-Low 1", "5", "6", "7", "8", "9", "10", "11", "12", "13"], "styles": ["filter-style-Low 1"], "colors": ["filter-color-White"], "sizes": ["5", "6", "7", "8", "9", "10", "11", "12", "13"] }, { "tags": ["filter-color-Black", "filter-color-Red", "filter-color-Blue", "filter-style-Boot", "7", "8", "9", "12", "13"], "styles": ["filter-style-Boot"], "colors": ["filter-color-Black", "filter-color-Red", "filter-color-Blue"], "sizes": ["7", "8", "9", "12", "13"] }, { "tags": ["filter-color-Black", "filter-color-Red", "filter-style-Gat", "7", "8", "9", "10", "11", "12", "13"], "styles": ["filter-style-Gat"], "colors": ["filter-color-Black", "filter-color-Red"], "sizes": ["7", "8", "9", "10", "11", "12", "13"] } ] }; const getFiltered = ( ({products}, filterValues) => products.filter(p => Object.entries(p) .flatMap(([k, v]) => v) .some(entry => filterValues.includes(entry)))); console.log(getFiltered(p, ["5", "filter-style-Gat"])); 

Assuming we want some conditional:

 const p = { "products": [{ "tags": ["filter-color-White", "filter-style-Low 1", "5", "6", "7", "8", "9", "10", "11", "12", "13"], "styles": ["filter-style-Low 1"], "colors": ["filter-color-White"], "sizes": ["5", "6", "7", "8", "9", "10", "11", "12", "13"] }, { "tags": ["filter-color-Black", "filter-color-Red", "filter-color-Blue", "filter-style-Boot", "7", "8", "9", "12", "13"], "styles": ["filter-style-Boot"], "colors": ["filter-color-Black", "filter-color-Red", "filter-color-Blue"], "sizes": ["7", "8", "9", "12", "13"] }, { "tags": ["filter-color-Black", "filter-color-Red", "filter-style-Gat", "7", "8", "9", "10", "11", "12", "13"], "styles": ["filter-style-Gat"], "colors": ["filter-color-Black", "filter-color-Red"], "sizes": ["7", "8", "9", "10", "11", "12", "13"] } ] }; const getFiltered = (({ products }, { tags, styles, colors, sizes }) => { // Filter size fully. return products.filter(({ sizes: s }) => (sizes) ? s.some(size => sizes.includes(size)) : true) // Now color .filter(({ colors: c }) => (colors) ? c.some(color => colors.includes(color)) : true) // style etc. .filter(({ styles: s }) => (styles) ? s.some(style => styles.includes(style)) : true) }); const filter = { sizes: ["6", "7"], colors: ["filter-color-Red"] }; console.log(getFiltered(p, filter)); 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM