Как создать накопительную систему фильтров в svelte (например, боковую панель Amazon)

У меня есть стройная страница для отображения списка сообщений, мне нужно дать пользователю возможность фильтровать этот список по нескольким условиям, например: год, теги, категория и т. д. Все эти условия должны быть параллельными, как и на веб-сайте Amazon. боковая панель.

Используя производное хранилище, я получил один фильтр, работающий нормально, но все же мне не удается применить 2 или 3 фильтра одновременно.

Для фильтра «год» у меня есть следующий код:

import { readable, writable, derived } from "svelte/store";

export let data;
let posts = readable(data.posts);

const extractColumn = (arr, column) => arr.map((x) => x[column]);
const allYears = extractColumn(data.posts, "year");
const years = readable ([...new Set(allYears)]);
const filter = writable({ year: null });

const filteredPosts = derived([filter, posts], ([$filter, $posts]) => {
        if (!$filter.year) return $posts;
        return $posts.filter((x) => x.year === $filter.year);
    });

Итак, на основе списка сообщений я могу получить массив лет для фильтра, а затем с помощью простого выбора запустить действие фильтрации, и это работает довольно хорошо.

<select bind:value = {$filter.year}>
    {#each $years as year}
        <option value = {year}>{year}</option>
    {/each}
</select>

Как я сказал в начале, суть в том, как усложнить этот сценарий, чтобы превратить простой фильтр в систему с переменным количеством динамических накопительных фильтров.

Спасибо за любую идею.

🤔 А знаете ли вы, что...
JavaScript позволяет создавать асинхронные запросы к серверу с помощью технологии AJAX.


2
45
2

Ответы:

Вы можете перебрать реквизит объекта с помощью Object.entries Объедините это со скобками, чтобы прочитать реквизит obj[prop] Добавьте в массив сокращение, чтобы сделать его проще, и вы получите:

return Object.entries($filter)
  .reduce(
    (pre, [key, value]) => pre.filter(x => x[key] == value), 
    $posts
  );

В качестве дополнения вы можете вместо этого хранить массивы в своем фильтре, чтобы вы могли фильтровать несколько значений:

{
  "year": [2022],
  "colour": ['red', 'green']
}

В этом случае используйте pre.filter(x => values.includes(x[key])) вместо этого.


Решено

Чтобы сделать это более универсальным, я бы начал с «автоматического» создания параметров фильтра.

    const filterOptions = derived(posts, $posts => {

        // set the columns you'd like to have a filter for
        const columns = ['year', 'language']
        //  const columns = Object.keys($posts[0]) // or change to this to have a filter for every property

        return columns.reduce((filterOptions, column) => {
            filterOptions[column] = extractUniqueColumnValues($posts, column)
            return filterOptions
        }, {})
    })

    function extractUniqueColumnValues (arr, column) {
        return Array.from(new Set(arr.map((x) => x[column])))
    } 

и отображать их так

{#each Object.entries($filterOptions) as [column, values]}
{column}
<select bind:value = {$filter[column]}>
    <option value = {null} ></option>
    {#each values as value}
    <option value = {value}>{value}</option>
    {/each}
</select>
{/each}

filter может быть просто

const filter = writable({});

(Изначально установлено значение {year: null, language: null} с помощью привязок выбора значения)

Одним из способов фильтрации сообщений может быть это (или использование reduce, как показывает ответ Стефана Ванраеса)

const filteredPosts = derived([filter, posts], ([$filter, $posts]) => {
        Object.entries($filter).forEach(([key, value]) => {
            if (value !== null) $posts = $posts.filter(x => x[key] == value)
        })      
        return $posts
    });

Вот РЕПЛ, построенный на этом