1

Currently in my application the user can search game titles by typing in the game name. As they start typing the results update using the filter method. I'm now trying to do the same using multiple checkboxes. So when the user checks playstation and or xbox for example the results show games only from these categories. As they uncheck, the results will refresh with the updated values.

At the moment I've successfully created change events for all checkboxes and pushing their values to the filters.categories array. The problem I'm having is I have no idea how to connect this array to my existing filter method. I need to somehow loop though the array and compare the current value with category.name in my stores object to see whether they match. Is this possible to do inside the filter method? Any ideas where I'm going wrong?

Thanks in advance.

The html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>

    <aside>
        <ul>
            <li>
                <label>
                    <input type="checkbox" value="playstation" class="category-cb">
                    <span>Playstation</span>
                </label>
                <li>
                    <label>
                        <input type="checkbox" value="xbox" class="category-cb">
                        <span>Xbox</span>
                    </label>
                </li>
                <li>
                    <label>
                        <input type="checkbox" value="nintendo" class="category-cb">
                        <span>Nintendo</span>
                    </label>
                </li>
            </li>
        </ul>
    </aside>
    <main>
        <input type="text" class="store-search" placeholder="Search">
        <ul class="grid"></ul>
    </main>

    <script src="app.js"></script>
</body>
</html>

The js:

/*----------------------------
    data
----------------------------*/

const stores = [{
    id: 0,
    name: 'The Last Of Us',
    studio: 'Naughtydog',
    thumbnail: '',
    category: {
        id: 0,
        name: 'Playstation'
    },
    price: 50.00
}, {
    id: 1,
    name: 'Animal Crossing',
    studio: 'Nintendo',
    thumbnail: '',
    category: {
        id: 1,
        name: 'Nintendo'
    },
    price: 60.00
}, {
    id: 2,
    name: 'Gears 5',
    studio: 'The Coalition',
    thumbnail: '',
    category: {
        id: 2,
        name: 'Xbox'
    },
    price: 10.00
}, {
    id: 3,
    name: 'DOOM Eternal',
    studio: 'id Software',
    thumbnail: '',
    category: {
        id: 3,
        name: 'Playstation'
    },
    price: 50.00
}, {
    id: 4,
    name: 'The Legend of Zelda: Link\'s Awakening',
    studio: 'Grezzo',
    thumbnail: '',
    category: {
        id: 4,
        name: 'Nintendo'
    },
    price: 35.00
}, {
    id: 5,
    name: 'Resident Evil 3',
    studio: 'Capcom',
    thumbnail: '',
    category: {
        id: 5,
        name: 'Playstation'
    },
    price: 40.00
}];

let filters = {
    searchText: '',
    categories: []
}

/*----------------------------
    functions
----------------------------*/

let filterStores = function (stores, filters) {

    let filteredStores = stores.filter(function (store) {
        let storeName = store.name.toLowerCase().includes(filters.searchText.toLowerCase());
        return storeName;
    });

    let grid = document.querySelector('.grid');
    grid.innerHTML = '';

    filteredStores.forEach(function (store) {
        let li = document.createElement('li');
        li.textContent = store.name;
        grid.appendChild(li);
    });
}

/*----------------------------
    events
----------------------------*/

document.addEventListener('DOMContentLoaded', function () {
    filterStores(stores, filters);

    document.querySelector('.store-search').addEventListener('input', function (event) {
        filters.searchText = event.target.value;
        filterStores(stores, filters);
    });

    document.querySelectorAll('.category-cb').forEach(function (checkbox) {
        checkbox.addEventListener('change', function (event) {
            if (checkbox.checked) {
                filters.categories.push(event.target.value);
                filterStores(stores, filters);
                console.log(filters.categories);
            }
        });
    });
});

https://jsfiddle.net/bjgp0ux4/

1 Answer 1

1

There are two main features to implement:

  1. Remove categories when they get unchecked

    I recommend you to use a Set as filters.categories, so you can easily check, add and remove entries by has, add and delete, respectively.

    //onchange event
    if (checkbox.checked) {
        filters.categories.add(event.target.value);
    } else {
        filters.categories.delete(event.target.value);
    }
    filterStores(stores, filters);
    
  2. Filter the stores based on categories as well

    Modify your .filter callback to this into account as well:

    store.name.toLowerCase().includes(filters.searchText.toLowerCase()) &&
    filters.categories.has(store.category.name.toLowerCase())
    

    Also, to show all when no categories are selected, do:

    store.name.toLowerCase().includes(filters.searchText.toLowerCase()) &&
    (
        filters.categories.has(store.category.name.toLowerCase()) ||
        filters.categories.size === 0
    )
    

All together:

/*----------------------------
    data
----------------------------*/

const stores = [{
    id: 0,
    name: 'The Last Of Us',
    studio: 'Naughtydog',
    thumbnail: '',
    category: {
        id: 0,
        name: 'Playstation'
    },
    price: 50.00
}, {
    id: 1,
    name: 'Animal Crossing',
    studio: 'Nintendo',
    thumbnail: '',
    category: {
        id: 1,
        name: 'Nintendo'
    },
    price: 60.00
}, {
    id: 2,
    name: 'Gears 5',
    studio: 'The Coalition',
    thumbnail: '',
    category: {
        id: 2,
        name: 'Xbox'
    },
    price: 10.00
}, {
    id: 3,
    name: 'DOOM Eternal',
    studio: 'id Software',
    thumbnail: '',
    category: {
        id: 3,
        name: 'Playstation'
    },
    price: 50.00
}, {
    id: 4,
    name: 'The Legend of Zelda: Link\'s Awakening',
    studio: 'Grezzo',
    thumbnail: '',
    category: {
        id: 4,
        name: 'Nintendo'
    },
    price: 35.00
}, {
    id: 5,
    name: 'Resident Evil 3',
    studio: 'Capcom',
    thumbnail: '',
    category: {
        id: 5,
        name: 'Playstation'
    },
    price: 40.00
}];

let filters = {
    searchText: '',
    categories: new Set
}

/*----------------------------
    functions
----------------------------*/

let filterStores = function (stores, filters) {

    let filteredStores = stores.filter(store => (
        store.name.toLowerCase().includes(filters.searchText.toLowerCase()) &&
        (
            filters.categories.has(store.category.name.toLowerCase()) ||
            filters.categories.size === 0 //No selected categories, show all
        )
    ));

    let grid = document.querySelector('.grid');
    grid.innerHTML = '';

    filteredStores.forEach(function (store) {
        let li = document.createElement('li');
        li.textContent = store.name;
        grid.appendChild(li);
    });
}

/*----------------------------
    events
----------------------------*/

document.addEventListener('DOMContentLoaded', function () {
    filterStores(stores, filters);

    document.querySelector('.store-search').addEventListener('input', function (event) {
        filters.searchText = event.target.value;
        filterStores(stores, filters);
    });

    document.querySelectorAll('.category-cb').forEach(function (checkbox) {
        checkbox.addEventListener('change', function (event) {
            if (checkbox.checked) {
                filters.categories.add(event.target.value);
            } else {
                filters.categories.delete(event.target.value);
            }
            filterStores(stores, filters);
        });
    });
});
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>

    <aside>
        <ul>
            <li>
                <label>
                    <input type="checkbox" value="playstation" class="category-cb">
                    <span>Playstation</span>
                </label>
            </li>
            <li>
                <label>
                    <input type="checkbox" value="xbox" class="category-cb">
                    <span>Xbox</span>
                </label>
            </li>
            <li>
                <label>
                    <input type="checkbox" value="nintendo" class="category-cb">
                    <span>Nintendo</span>
                </label>
            </li>
        </ul>
    </aside>
    <main>
        <input type="text" class="store-search" placeholder="Search">
        <ul class="grid"></ul>
    </main>

    <script src="app.js"></script>
</body>
</html>

You can also use arrays:

/*----------------------------
    data
----------------------------*/

const stores = [{
    id: 0,
    name: 'The Last Of Us',
    studio: 'Naughtydog',
    thumbnail: '',
    category: {
        id: 0,
        name: 'Playstation'
    },
    price: 50.00
}, {
    id: 1,
    name: 'Animal Crossing',
    studio: 'Nintendo',
    thumbnail: '',
    category: {
        id: 1,
        name: 'Nintendo'
    },
    price: 60.00
}, {
    id: 2,
    name: 'Gears 5',
    studio: 'The Coalition',
    thumbnail: '',
    category: {
        id: 2,
        name: 'Xbox'
    },
    price: 10.00
}, {
    id: 3,
    name: 'DOOM Eternal',
    studio: 'id Software',
    thumbnail: '',
    category: {
        id: 3,
        name: 'Playstation'
    },
    price: 50.00
}, {
    id: 4,
    name: 'The Legend of Zelda: Link\'s Awakening',
    studio: 'Grezzo',
    thumbnail: '',
    category: {
        id: 4,
        name: 'Nintendo'
    },
    price: 35.00
}, {
    id: 5,
    name: 'Resident Evil 3',
    studio: 'Capcom',
    thumbnail: '',
    category: {
        id: 5,
        name: 'Playstation'
    },
    price: 40.00
}];

let filters = {
    searchText: '',
    categories: []
}

/*----------------------------
    functions
----------------------------*/

let filterStores = function (stores, filters) {

    let filteredStores = stores.filter(store => (
        store.name.toLowerCase().includes(filters.searchText.toLowerCase()) &&
        (
            filters.categories.includes(store.category.name.toLowerCase()) ||
            filters.categories.length === 0 //No selected categories, show all
        )
    ));

    let grid = document.querySelector('.grid');
    grid.innerHTML = '';

    filteredStores.forEach(function (store) {
        let li = document.createElement('li');
        li.textContent = store.name;
        grid.appendChild(li);
    });
}

/*----------------------------
    events
----------------------------*/

document.addEventListener('DOMContentLoaded', function () {
    filterStores(stores, filters);

    document.querySelector('.store-search').addEventListener('input', function (event) {
        filters.searchText = event.target.value;
        filterStores(stores, filters);
    });

    document.querySelectorAll('.category-cb').forEach(function (checkbox) {
        checkbox.addEventListener('change', function (event) {
            if (checkbox.checked) {
                filters.categories.push(event.target.value);
            } else {
                filters.categories.splice(filters.categories.indexOf(event.target.value), 1);
            }
            filterStores(stores, filters);
        });
    });
});
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>

    <aside>
        <ul>
            <li>
                <label>
                    <input type="checkbox" value="playstation" class="category-cb">
                    <span>Playstation</span>
                </label>
            </li>
            <li>
                <label>
                    <input type="checkbox" value="xbox" class="category-cb">
                    <span>Xbox</span>
                </label>
            </li>
            <li>
                <label>
                    <input type="checkbox" value="nintendo" class="category-cb">
                    <span>Nintendo</span>
                </label>
            </li>
        </ul>
    </aside>
    <main>
        <input type="text" class="store-search" placeholder="Search">
        <ul class="grid"></ul>
    </main>

    <script src="app.js"></script>
</body>
</html>

Sign up to request clarification or add additional context in comments.

4 Comments

Hi, thanks for your prompt response. I'm pretty new to JavaScript and was wondering whether there's an alternative to Set, for learning purposes? I've also modified your checkbox else statement to the following: if (checkbox.checked) { filters.categories.push(event.target.value); } else { let checkboxValueIndex = filters.categories.indexOf(event.target.value); filters.categories.splice(checkboxValueIndex, 1); } I appreciate is long winded but I find it easier to understand. Thanks
You can use an array instead, if you like. Your example is OK as well :D A Set is an ES6 object, which is a collection of items, but doesn't have indexes or order. Any JS value may or may not be present in a Set. Values can be added by Set#add(), removed by Set#delete(), or checked using Set#has(). A Set can be created using the new Set() syntax. Read more here or here. (Note: Set#x() denotes that x is an instance method of the class Set)
Could you show me an example using an Array please? I'm struggling to see how to get the categories array into the existing filter. Thanks.
@user1321811 I've edited my answer, take a look at it now!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.