I'm building a simple filter in Vanilla Javascript and CSS.
When the user click a category, the articles not related with the category have the class .hide
. I also need to remove all these articles with the class .hide
from the DOM, and append them again when the user click the category related to them.
The filter works fine adding and removing the class .hide
. But the issue is, once the hidden articles have been removed, they don't appear again.
The JS:
const filterBox = document.querySelectorAll('.js-article');
document.querySelector('.js-filter-list').addEventListener('click', (event) => {
if (event.target.tagName !== 'BUTTON') return false;
let filterClass = event.target.dataset['filter'];
filterBox.forEach(elem => {
elem.classList.remove('hide');
elem.parentNode.appendChild(elem);
if (!elem.classList.contains(filterClass) && filterClass !== 'all') {
elem.classList.add('hide');
elem.parentNode.removeChild(elem);
}
});
});
The HTML:
<div class="c-filter__list js-filter-list">
<button class="c-filter__list-item is-active js-filter-item" data-filter="all">All</button>
<button class="c-filter__list-item o-font-title js-filter-item" data-filter="cat-1">Cat 1</button>
<button class="c-filter__list-item o-font-title js-filter-item" data-filter="cat-2">Cat 2</button>
<button class="c-filter__list-item o-font-title js-filter-item" data-filter="cat-3">Cat 3</button>
<button class="c-filter__list-item o-font-title js-filter-item" data-filter="cat-4">Cat 4</button>
</div>
<article class="js-article cat-1"></article>
<article class="js-article cat-2"></article>
<article class="js-article cat-2 cat-3"></article>
<article class="js-article cat-1" ></article>
<article class="js-article cat-4"></article>
<article class="js-article cat-3 cat-4"></article>
...
Thank you.
HTMLElement and TextNode references can easily live as part of every JavaScript object without any connection to the DOM.
Thus, the OP should initially fill an array with the references of all available article nodes where they later can be (constantly) filtered from according to the selected filter-name.
The re-render process following a filter-change is as follows ...
Other improvements are ...
function emptyElementNode(elmNode) {
const listOfNodes = [...elmNode.childNodes];
let node;
while (node = listOfNodes.pop()) {
elmNode.removeChild(node);
}
return elmNode;
}
function renderFilterRelatedArticles(filterName) {
const articlesRoot = emptyElementNode(
document.querySelector('.article-overview')
);
const listOfFilteredNodes = (filterName === 'all')
// ... either the initial array of every available article node.
&& listOfArticleNodes
// ... or a filtered version of this array (by filter name).
|| listOfArticleNodes
.filter(({ classList }) => classList.contains(filterName));
listOfFilteredNodes
.forEach(elmNode => articlesRoot.appendChild(elmNode));
}
function handleFilterChange({ target }) {
// make use of event delegation.
target = target.closest('[data-filter]');
if (target !== null) {
// get the filter name from `dataset.filter`
const filterName = target.dataset.filter ?? null;
if (filterName !== null && !target.classList.contains('selected')) {
// handle the selected states of all the filter buttons.
target
.closest('.js-filter-list')
.querySelectorAll('[data-filter]')
.forEach(elm =>
elm.classList.remove('selected')
);
target.classList.add('selected');
// re-render the articles according to the passed filer name.
renderFilterRelatedArticles(filterName);
}
}
}
const listOfArticleNodes = [];
function main() {
document
.querySelector('.js-filter-list')
.addEventListener('click', handleFilterChange);
// initially fill an array with all the available article nodes.
listOfArticleNodes
.push(
...document.querySelectorAll('article.js-article')
);
}
main();
[data-filter].selected {
scale: 1.1;
color: #eee;
background-color: #333;
}
.article-overview {
margin: 10px 0;
}
<div class="js-filter-list">
<button type="button" data-filter="all">All</button>
<button type="button" data-filter="cat-1">Cat 1</button>
<button type="button" data-filter="cat-2">Cat 2</button>
<button type="button" data-filter="cat-3">Cat 3</button>
<button type="button" data-filter="cat-4">Cat 4</button>
</div>
<div class="article-overview">
<article class="js-article cat-1">cat-1</article>
<article class="js-article cat-2">cat-2</article>
<article class="js-article cat-2 cat-3">cat-2 cat-3</article>
<article class="js-article cat-1" >cat-1</article>
<article class="js-article cat-4">cat-4</article>
<article class="js-article cat-3 cat-4">cat-3 cat-4</article>
</div>