I'm having a hard time with some JS DOM traversal. I'm stuck with html that's something like this:
<h2>Header 1</h2>
<div class="some-content">
<div class="inner-content">
<h4 class="person-name">John Smith</h4>
</div>
</div>
<h2>Header 2</h2>
<div class="some-content">
<div class="inner-content">
<h4 class="person-name">Emily Jones</h4>
</div>
</div>
This is all hidden by default. I'm trying to use a text field so that if it matches an h4 person-name, it displays the some-content container, as well as the preceding h2. I can make it work for the some-content bit, but I'm having trouble targeting the h2 that's above it. I've tried various combinations of jQuery parent(), siblings(), and prev(). I do not have the ability to add additional class names.
Edit: here is the script I have for the text field event:
$('#text-field').keyup(function() {
var nameSearch = $(this).val().toUpperCase();
$('.person-name').each(function() {
var x = $(this).text().toUpperCase();
if (x.includes(nameSearch)) {
$(this).prev('h2').show();
$(this).closest('.some-content').show();
}
})
});
Edit 2:
I apologize, my code example was oversimplified. Some very good answers by the way. If for example there was a search done for Emily Jones in this bit, would there need to be something extra done?
<div class="container">
<h2>Header 1</h2>
<div class="some-content">
<div class="inner-content">
<h4 class="person-name">John Smith</h4>
</div>
<div class="inner-content">
<h4 class="person-name">Emily Jones</h4>
</div>
</div>
</div>
If the header/content is not nested withing a wrapping div, you will need to step over every two child nodes and toggle class.
const triggerEvent = (el, eventName) => {
var event = document.createEvent('HTMLEvents');
event.initEvent(eventName, true, false);
el.dispatchEvent(event);
};
const
search = document.querySelector('.search'),
container = document.querySelector('.container');
const onSearch = (e) => {
const
searchValue = e.target.value,
nodes = container.children;
for (let i = 0; i < nodes.length; i += 2) {
const
h2 = nodes[i],
someContent = nodes[i + 1],
matches = someContent.querySelector('.person-name').textContent === searchValue;
h2.classList.toggle('hidden', !matches);
someContent.classList.toggle('hidden', !matches);
}
};
search.addEventListener('change', onSearch);
triggerEvent(search, 'change');
.hidden {
color: #DDD; /* Replace with -: display: none */
}
<input type="text" class="search" value="Emily Jones" />
<div class="container">
<h2>Header 1</h2>
<div class="some-content">
<div class="inner-content">
<h4 class="person-name">John Smith</h4>
</div>
</div>
<h2>Header 2</h2>
<div class="some-content">
<div class="inner-content">
<h4 class="person-name">Emily Jones</h4>
</div>
</div>
</div>
Alternatively, you can start with the names and work your way back to the corresponding h2.
const h2 = child
.closest('.inner-content')
.closest('.some-content')
.previousElementSibling; // h2
const triggerEvent = (el, eventName) => {
var event = document.createEvent('HTMLEvents');
event.initEvent(eventName, true, false);
el.dispatchEvent(event);
};
const
search = document.querySelector('.search'),
container = document.querySelector('.container');
const onSearch = (e) => {
const searchValue = e.target.value;
[...container.querySelectorAll('.person-name')].forEach(child => {
const
matches = child.textContent === searchValue,
h2 = child.closest('.inner-content')
.closest('.some-content').previousElementSibling;
[child, h2].forEach(el => el.classList.toggle('hidden', !matches));
});
};
search.addEventListener('change', onSearch);
triggerEvent(search, 'change');
.hidden {
color: #DDD; /* Replace with -: display: none */
}
<input type="text" class="search" value="Emily Jones" />
<div class="container">
<h2>Header 1</h2>
<div class="some-content">
<div class="inner-content">
<h4 class="person-name">John Smith</h4>
</div>
</div>
<h2>Header 2</h2>
<div class="some-content">
<div class="inner-content">
<h4 class="person-name">Emily Jones</h4>
</div>
</div>
</div>
Edit
Here is an altered version of the first example. If you have multiple names within .some-content you will have to find all the names that match and keep the parent, if at least one child matches.
const triggerEvent = (el, eventName) => {
var event = document.createEvent('HTMLEvents');
event.initEvent(eventName, true, false);
el.dispatchEvent(event);
};
const
search = document.querySelector('.search'),
container = document.querySelector('.container');
const onSearch = (e) => {
const
searchValue = e.target.value,
nodes = container.children;
for (let i = 0; i < nodes.length; i += 2) {
const
h2 = nodes[i],
someContent = nodes[i + 1],
names = [...someContent.querySelectorAll('.person-name')],
found = names.filter(name => name.textContent === searchValue);
h2.classList.toggle('hidden', found.length === 0);
names.forEach(name => {
const matches = name.textContent === searchValue;
name.closest('.inner-content').classList.toggle('hidden', !matches);
});
}
};
search.addEventListener('change', onSearch);
triggerEvent(search, 'change');
.hidden {
color: #DDD; /* Replace with -: display: none */
}
<input type="text" class="search" value="Emily Jones" />
<div class="container">
<h2>Header 1</h2>
<div class="some-content">
<div class="inner-content">
<h4 class="person-name">John Smith</h4>
</div>
<div class="inner-content">
<h4 class="person-name">Emily Jones</h4>
</div>
</div>
<h2>Header 2</h2>
<div class="some-content">
<div class="inner-content">
<h4 class="person-name">John Doe</h4>
</div>
<div class="inner-content">
<h4 class="person-name">Erica Jones</h4>
</div>
</div>
</div>
Related
I recently started to study JS and wanted to do a clickable tab showing different text with forEach method, but unfortunately i'm struggling!
const buttons = document.querySelectorAll('.button');
const texts = document.querySelectorAll('.text');
buttons.forEach(button => {
button.addEventListener('click', (event) => {
//make buttons active
buttons.forEach((button) => {
button.classList.remove('active')
})
event.target.classList.add('active')
//show text
texts.forEach(text => {
text.classList.remove('text');
})
});
})
<button class="button">Tab1</button>
<button class="button">Tab2</button>
<button class="button">Tab3</button>
<div class="text-tabs">
<div class="text">
<h4>Text from tab1</h4>
</div>
<div class="text">
<h4>Text from tab2</h4>
</div>
<div class="text">
<h4>Text from tab3</h4>
</div>
The CSS already has the display: none and then block
a way to do it is to add a data attribute to your different tab (for sample an id or an index) <button class="button" data-tab="1">Tab1</button>
and recover this value when you click on element
event.target.dataset.tab
const buttons = document.querySelectorAll('.button');
const texts = document.querySelectorAll('.text');
buttons.forEach(button => {
button.addEventListener('click', (event) => {
//make buttons active
buttons.forEach((button) => {
button.classList.remove('active')
})
event.target.classList.add('active')
texts.forEach(text => text.classList.add('hidden'));
const tabToShow = document.querySelector(`.text:nth-child(${event.target.dataset.tab})`);
if (tabToShow) {
tabToShow.classList.remove('hidden');
}
});
})
.hidden {
display: none;
}
button.active {
background: red;
}
<button class="button" data-tab="1">Tab1</button>
<button class="button" data-tab="2">Tab2</button>
<button class="button" data-tab="3">Tab3</button>
<div class="text-tabs">
<div class="text hidden">
<h4>Text from tab1</h4>
</div>
<div class="text hidden">
<h4>Text from tab2</h4>
</div>
<div class="text hidden">
<h4>Text from tab3</h4>
</div>
</div>
I'm so new that I don't know how to make a scoring system in JavaScript. All I need is; if the drag1 is dropped on div1, 1 score must add up. Here's my index.php, css, and js files
function submit() {
document.getElementById('handler').style.display = 'block'
const quest1 = document.getElementById('div1')
const ans1 = document.getElementById('drag1')
const totalScore = document.getElementById('score')
const score = 0
totalScore.textContent = score;
}
<div id="drop_area">
<div id="div1 drop" ondrop="drop(event);" ondragover="allowDrop(event);" value="10000 Years"></div>
<div id="div2 drop" ondrop="drop(event);" ondragover="allowDrop(event);" value="Evil Queen"></div>
<div id="div3 drop" ondrop="drop(event);" ondragover="allowDrop(event);" value="12"></div>
<div id="div4 drop" ondrop="drop(event);" ondragover="allowDrop(event);" value="Nana"></div>
<div id="div5 drop" ondrop="drop(event);" ondragover="allowDrop(event);" value="Maurice"></div>
</div>
<div id="drag_options">
<div id="wrapper">
<div id="drag1 option" draggable="true" ondragstart="drag(event)">Evil Queen</div>
<div id="drag2 option" draggable="true" ondragstart="drag(event)">Maurice</div>
<div id="drag3 option" draggable="true" ondragstart="drag(event)">Nyla</div>
<div id="drag4 option" draggable="true" ondragstart="drag(event)">Cruela Devil</div>
<div id="drag5 option" draggable="true" ondragstart="drag(event)">4</div>
<div id="drag6 option" draggable="true" ondragstart="drag(event)">Moris</div>
<div id="drag7 option" draggable="true" ondragstart="drag(event)">10000 Years</div>
<div id="drag8 option" draggable="true" ondragstart="drag(event)">1000 Years</div>
<div id="drag9 option" draggable="true" ondragstart="drag(event)">12</div>
<div id="drag10 option" draggable="true" ondragstart="drag(event)">Nana</div>
</div>
</div>
<div id="submit_button">
<button type="submit" id="submit" onclick="submit()">Submit</button>
</div>
NEVER call anything in a form id or name=submit. Also do not call your function submit.
You do not have a form,so no need to use a submit button. Just have <button id="run" type="button">Run</button>.
Use eventListeners and delegation
Here is a full version for you to study
I took the drag/drop from MDN
I have not added error handling when the same answer is dropped twice.
I guessed a point calculation
window.addEventListener("DOMContentLoaded", () => { // when page has loaded
const target = document.getElementById("drop_area");
const source = document.getElementById("drag_options");
const resetBut = document.getElementById("reset");
const runBut = document.getElementById("run");
const answerDiv = document.getElementById("answers");
let dragged = null;
source.addEventListener("dragstart", (event) => {
// store a ref. on the dragged elem
dragged = event.target;
});
target.addEventListener("dragover", (event) => {
// prevent default to allow drop
event.preventDefault();
});
target.addEventListener("drop", (event) => {
// prevent default action (open as link for some elements)
event.preventDefault();
// move dragged element to the selected drop target
if (event.target.dataset.value) {
// dragged.parentNode.removeChild(dragged);
event.target.textContent = dragged.textContent;
}
});
const pointsPerCorrect = 5;
runBut.addEventListener("click", () => {
const correct = [...target.querySelectorAll("div")].filter(div => div.textContent === div.dataset.value); // run filter on the child divs
const numberOfCorrect = correct.length;
const answers = correct.map(div => div.textContent).join(", ");
answerDiv.innerHTML = `You had ${numberOfCorrect} answer${numberOfCorrect===1?"":"s"} correct.<br/> ${answers}`; // using template strings
});
resetBut.addEventListener("click", () => {
target.querySelectorAll("div").forEach((div,i) => div.textContent = (i+1));
});
});
#drop_area {
background-color: green;
}
#drag_options {
background-color: red;
}
#drop_area div {
padding: 2px;
border: 1px solid black;
}
#drag_options div {
padding: 2px;
border: 1px solid black;
}
<div id="drop_area">
<div data-value="10000 Years">1</div>
<div data-value="Evil Queen">2</div>
<div data-value="12">3</div>
<div data-value="Nana">4</div>
<div data-value="Maurice">5</div>
</div>
<hr/>
<div id="drag_options">
<div draggable="true">Evil Queen</div>
<div draggable="true">Maurice</div>
<div draggable="true">Nyla</div>
<div draggable="true">Cruella de Vil</div>
<div draggable="true">4</div>
<div draggable="true">Moris</div>
<div draggable="true">10000 Years</div>
<div draggable="true">1000 Years</div>
<div draggable="true">12</div>
<div draggable="true">Nana</div>
</div>
<div id="submit_button">
<button type="button" id="reset">Reset</button>
<button type="button" id="run">Submit</button>
</div>
<div id="handler">
<div id="score_board">
<div id="wrap">
<h1>SCORE</h1>
<div id="answers"></div>
</div>
</div>
</div>
I have been trying to search with multiple tags.
But for some reason, it isn't working, it shows just the h5 tags as results.
How would you change the code that it works?
Is it because of the NodeList? For a single tag it is working with Document.getElementsByTagName()
Can i convert a NodeList to a HTMLCollection?
const searchBar = document.forms['search-webinare'].querySelector('input');
searchBar.addEventListener('keyup', function(e) {
const term = e.target.value.toLocaleLowerCase();
const webinare = Array.prototype.slice.call(document.querySelectorAll('h3,h5'), 0);
var hasResults = false;
webinare.forEach(function(webinar) {
const title = webinar.textContent;
if (title.toLowerCase().indexOf(term) != -1) {
webinar.parentElement.parentElement.parentElement.parentElement.parentElement.parentElement.style.display = 'flex';
hasResults = true;
} else {
webinar.parentElement.parentElement.parentElement.parentElement.parentElement.parentElement.style.display = 'none';
}
});
});
<form id="search-webinare">
<input type="text" placeholder="Suchen Sie ein Webinar ... "/>
</form>
<div>
<div>
<div>
<div>
<div>
<div>
<h3>text1</h3>
<h5>text2</h5>
</div>
</div>
</div>
</div>
</div>
</div>
<div>
<div>
<div>
<div>
<div>
<div>
<h3>text3</h3>
<h5>text4</h5>
</div>
</div>
</div>
</div>
</div>
</div>
<div>
<div>
<div>
<div>
<div>
<div>
<h3>text5</h3>
<h5>text6</h5>
</div>
</div>
</div>
</div>
</div>
</div>
You can use forEach on a NodeList. (doc) An array is not needed.
Lars Flieger has a good explanation on why your code is not doing what your want but I think it's easier to loop through parent divs and then search for your term in children elements. (Note that finding parents of an element is also possible with the element.closest() function)
querySelector functions are also available on elements (read more)
So I propose to loop through parent divs and then try to find headings in each div which match your term.
If your term is not found anywhere then your can directly show or hide your parent div element.
Note that I added a class to parent divs to select them but other selectors can be used depending on there positions in the document.
const searchBar = document.querySelector('#search-webinare input');
searchBar.addEventListener('keyup', function(e) {
const term = e.target.value.toLocaleLowerCase();
const parentDivs = document.querySelectorAll('.parent');
parentDivs.forEach(function(parentDiv) {
const headings = parentDiv.querySelectorAll('h3, h5');
let match = false;
headings.forEach(function(heading) {
const title = heading.textContent.toLowerCase();
if (title.indexOf(term) > -1) {
match = true
}
});
if (match) {
parentDiv.style.display = 'flex';
} else {
parentDiv.style.display = 'none';
}
});
});
/* just for easier visualisation of parents */
.parent {
margin: 5px;
padding: 5px;
background: rgba(0, 0, 0, 0.1);
}
<form id="search-webinare">
<input type="text" placeholder="Suchen Sie ein Webinar ... "/>
</form>
<div class="parent">
<div>
<div>
<div>
<div>
<div>
<h3>text1</h3>
<h5>text2a</h5>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="parent">
<div>
<div>
<div>
<div>
<div>
<h3>text2b</h3>
<h5>text3a</h5>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="parent">
<div>
<div>
<div>
<div>
<div>
<h3>text3b</h3>
<h5>text4</h5>
</div>
</div>
</div>
</div>
</div>
</div>
Try this when you are able to modify the parent div of h3/h5:
const searchBar = document.forms['search-webinare'].querySelector('input');
searchBar.addEventListener('input', (e) => {
const term = e.target.value.toLocaleLowerCase()
const webinare = document.querySelectorAll('div.content')
let hasResults = false;
webinare.forEach(webinar => {
const titleOfh3 = webinar.querySelector('h3').textContent;
const titleOfh5 = webinar.querySelector('h5').textContent;
if (titleOfh3.toLowerCase().includes(term) || titleOfh5.toLowerCase().includes(term)) {
webinar.style.display = 'block';
hasResults = true;
} else {
webinar.style.display = 'none';
}
})
});
<form id="search-webinare">
<input type="text" placeholder="Suchen Sie ein Webinar ... " />
</form>
<div>
<div>
<div>
<div>
<div>
<div class="content">
<h3>text1</h3>
<h5>text2</h5>
</div>
</div>
</div>
</div>
</div>
</div>
<div>
<div>
<div>
<div>
<div>
<div class="content">
<h3>text3</h3>
<h5>text4</h5>
</div>
</div>
</div>
</div>
</div>
</div>
<div>
<div>
<div>
<div>
<div>
<div class="content">
<h3>text5</h3>
<h5>text6</h5>
</div>
</div>
</div>
</div>
</div>
</div>
Or this if not:
const searchBar = document.forms['search-webinare'].querySelector('input');
searchBar.addEventListener('input', (e) => {
const term = e.target.value.toLocaleLowerCase()
const webinare = document.querySelectorAll('h3, h5')
let hasResults = false;
for (let i = 0; i < webinare.length; i += 2) {
const titleOfh3 = webinare[i].textContent;
const titleOfh5 = webinare[i + 1].textContent;
if (titleOfh3.toLowerCase().includes(term) || titleOfh5.toLowerCase().includes(term)) {
webinare[i].parentElement.style.display = 'block';
webinare[i + 1].parentElement.style.display = 'block';
hasResults = true;
} else {
webinare[i].parentElement.style.display = 'none';
webinare[i + 1].parentElement.style.display = 'none';
}
}
});
<form id="search-webinare">
<input type="text" placeholder="Suchen Sie ein Webinar ... " />
</form>
<div>
<div>
<div>
<div>
<div>
<div>
<h3>text1</h3>
<h5>text2</h5>
</div>
</div>
</div>
</div>
</div>
</div>
<div>
<div>
<div>
<div>
<div>
<div>
<h3>text3</h3>
<h5>text4</h5>
</div>
</div>
</div>
</div>
</div>
</div>
<div>
<div>
<div>
<div>
<div>
<div>
<h3>text5</h3>
<h5>text6</h5>
</div>
</div>
</div>
</div>
</div>
</div>
What is the problem with your solution?
The problem with your code is the order you check if the term is matching: If you try to search for a h5:
The parent element gets displayed since the last operation of it was to set the display to block.
If a search result matches the first, the second will override the result with display none:
You can solve this issue by: Adding a class to the parent div (no there is no parent selector based on childs yet) and select this one and change the search logic to check the h3 and h5 in one step. (This is the simplest one and if you are able to add stuff to the parent div: I recommend you this.)
I am trying to create a filter system that uses data-attributes, I can get it to work if the items selected match the order of the elements data-attributes eg: items selected {fun, easy, cheap} and the elements attributes are in the same order but if I click {easy, cheap, fun} then I don't have any results returned.
Any help on solving this would be greatly appreciated.
var filterBtns = document.querySelectorAll('.request-btn'),
slider = document.querySelector('.slider'),
cardContainers = $('.cards-container'),
selectedFilters = [];
filterBtns.forEach(function(el) {
el.addEventListener('click', function() {
this.classList.toggle('clicked');
if (this.classList.contains('clicked')) {
selectedFilters.push(this.dataset.filter);
} else {
selectedFilters.splice(selectedFilters.indexOf(this.dataset.filter), 1);
}
updateCards();
});
});
slider.addEventListener('change', function() {
if (this.value === '0') {
selectedFilters.push('easy');
} else if (this.value === '1') {
selectedFilters.splice(selectedFilters.indexOf('easy', 'diy'), 1);
} else {
selectedFilters.push('diy');
}
updateCards();
});
var updateCards = function() {
cardContainers.removeClass('show').filter(function() {
var data = this.dataset;
var selectedFiltersValues = selectedFilters.join(' ');
return selectedFilters.length ? data.filter.includes(selectedFiltersValues) : true;
}).addClass('show');
}
.card {
width: 200px;
border: 1px solid coral;
margin: 15px;
padding: 15px;
float: left;
}
.cards-container {
display: none;
}
.show {
display: block;
}
.request-btn.clicked {
background-color: coral;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<div class="filter-container">
<div class="left-filter">
<p>EASY<input type="range" class="slider provider-complexity" min="0" max="2" value="1" step="1">DIY</p>
</div>
<div class="right-filter">
<button class="request-btn" data-filter="cheap">Cheap</button>
<button class="request-btn" data-filter="fun">fun</button>
<button class="request-btn" data-filter="green">green</button>
<button class="request-btn" data-filter="big">big</button>
</div>
</div>
<div class="cards-container show" data-filter="green">
<div class="card">1 the tag is - green</div>
</div>
<div class="cards-container show" data-filter="fun">
<div class="card">2 the tag is - fun</div>
</div>
<div class="cards-container show" data-filter="cheap">
<div class="card">3 the tag is - cheap</div>
</div>
<div class="cards-container show" data-filter="big">
<div class="card">4 the tag is - big</div>
</div>
<div class="cards-container show" data-filter="cheap big">
<div class="card">5 the tags are - cheap big</div>
</div>
<div class="cards-container show" data-filter="fun easy cheap">
<div class="card">6 the tags are - fun easy cheap</div>
</div>
<div class="cards-container show" data-filter="diy">
<div class="card">7 the tag is - diy</div>
</div>
<div class="cards-container show" data-filter="easy">
<div class="card">8 hthe tag is - easy</div>
</div>
<div class="cards-container show" data-filter="easy green">
<div class="card">9 the tags are - easy green</div>
</div>
Update based on comment
Based on the comment, the requirement is that each item must contain all of the filters. If "cheap" and "big" are selected, only items that have "cheap" and "big" can be returned.
The change:
// Get the individual item filters as an array
var itemData = data.filter.split(' ')
return selectedFilters.length
// match on every
? selectedFilters.every(function (val) {
return itemData.indexOf(val) > -1;
})
: true;
var filterBtns = document.querySelectorAll('.request-btn'),
slider = document.querySelector('.slider'),
cardContainers = $('.cards-container'),
selectedFilters = [];
filterBtns.forEach(function(el) {
el.addEventListener('click', function() {
this.classList.toggle('clicked');
if (this.classList.contains('clicked')) {
selectedFilters.push(this.dataset.filter);
} else {
selectedFilters.splice(selectedFilters.indexOf(this.dataset.filter), 1);
}
updateCards();
});
});
slider.addEventListener('change', function() {
if (this.value === '0') {
selectedFilters.push('easy');
} else if (this.value === '1') {
selectedFilters.splice(selectedFilters.indexOf('easy', 'diy'), 1);
} else {
selectedFilters.push('diy');
}
updateCards();
});
var updateCards = function() {
cardContainers.removeClass('show').filter(function() {
var itemData = data.filter.split(' ')
return selectedFilters.length
? selectedFilters.every(function (val) {
return itemData.indexOf(val) > -1;
})
: true;
}).addClass('show');
}
.card {
width: 200px;
border: 1px solid coral;
margin: 15px;
padding: 15px;
float: left;
}
.cards-container {
display: none;
}
.show {
display: block;
}
.request-btn.clicked {
background-color: coral;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<div class="filter-container">
<div class="left-filter">
<p>EASY<input type="range" class="slider provider-complexity" min="0" max="2" value="1" step="1">DIY</p>
</div>
<div class="right-filter">
<button class="request-btn" data-filter="cheap">Cheap</button>
<button class="request-btn" data-filter="fun">fun</button>
<button class="request-btn" data-filter="green">green</button>
<button class="request-btn" data-filter="big">big</button>
</div>
</div>
<div class="cards-container show" data-filter="green">
<div class="card">1 the tag is - green</div>
</div>
<div class="cards-container show" data-filter="fun">
<div class="card">2 the tag is - fun</div>
</div>
<div class="cards-container show" data-filter="cheap">
<div class="card">3 the tag is - cheap</div>
</div>
<div class="cards-container show" data-filter="big">
<div class="card">4 the tag is - big</div>
</div>
<div class="cards-container show" data-filter="cheap big">
<div class="card">5 the tags are - cheap big</div>
</div>
<div class="cards-container show" data-filter="fun easy cheap">
<div class="card">6 the tags are - fun easy cheap</div>
</div>
<div class="cards-container show" data-filter="diy">
<div class="card">7 the tag is - diy</div>
</div>
<div class="cards-container show" data-filter="easy">
<div class="card">8 hthe tag is - easy</div>
</div>
<div class="cards-container show" data-filter="easy green">
<div class="card">9 the tags are - easy green</div>
</div>
Original
You problem is that you've joined your selected filters, in your updateCards function you're then checking if the items data-filter includes cheap fun green.
The solution is to check if the array of selectedFilters includes the elements attribute(s).
The change
const itemData = data.filter.split(' ')
return selectedFilters.length
? selectedFilters.every(function (val) {
return itemData.indexOf(val) > -1;
})
: true;
I've updated your example below.
A note on the usage of .some It works across all major browsers, if you need to support i.e 6-8 for some hellish reason, you will need a polyfill. CanIUse
var filterBtns = document.querySelectorAll('.request-btn'),
slider = document.querySelector('.slider'),
cardContainers = $('.cards-container'),
selectedFilters = [];
filterBtns.forEach(function(el) {
el.addEventListener('click', function() {
this.classList.toggle('clicked');
if (this.classList.contains('clicked')) {
selectedFilters.push(this.dataset.filter);
} else {
selectedFilters.splice(selectedFilters.indexOf(this.dataset.filter), 1);
}
updateCards();
});
});
slider.addEventListener('change', function() {
if (this.value === '0') {
selectedFilters.push('easy');
} else if (this.value === '1') {
selectedFilters.splice(selectedFilters.indexOf('easy', 'diy'), 1);
} else {
selectedFilters.push('diy');
}
updateCards();
});
var updateCards = function() {
cardContainers.removeClass('show').filter(function() {
var data = this.dataset;
return selectedFilters.length
? selectedFilters.some(function (val) {
return data.filter.includes(val);
})
: true;
}).addClass('show');
}
.card {
width: 200px;
border: 1px solid coral;
margin: 15px;
padding: 15px;
float: left;
}
.cards-container {
display: none;
}
.show {
display: block;
}
.request-btn.clicked {
background-color: coral;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<div class="filter-container">
<div class="left-filter">
<p>EASY<input type="range" class="slider provider-complexity" min="0" max="2" value="1" step="1">DIY</p>
</div>
<div class="right-filter">
<button class="request-btn" data-filter="cheap">Cheap</button>
<button class="request-btn" data-filter="fun">fun</button>
<button class="request-btn" data-filter="green">green</button>
<button class="request-btn" data-filter="big">big</button>
</div>
</div>
<div class="cards-container show" data-filter="green">
<div class="card">1 the tag is - green</div>
</div>
<div class="cards-container show" data-filter="fun">
<div class="card">2 the tag is - fun</div>
</div>
<div class="cards-container show" data-filter="cheap">
<div class="card">3 the tag is - cheap</div>
</div>
<div class="cards-container show" data-filter="big">
<div class="card">4 the tag is - big</div>
</div>
<div class="cards-container show" data-filter="cheap big">
<div class="card">5 the tags are - cheap big</div>
</div>
<div class="cards-container show" data-filter="fun easy cheap">
<div class="card">6 the tags are - fun easy cheap</div>
</div>
<div class="cards-container show" data-filter="diy">
<div class="card">7 the tag is - diy</div>
</div>
<div class="cards-container show" data-filter="easy">
<div class="card">8 hthe tag is - easy</div>
</div>
<div class="cards-container show" data-filter="easy green">
<div class="card">9 the tags are - easy green</div>
</div>
function isArrayEqual(a, b) {
var arrA = a.join() // Array as String
var arrB = b.join() // Array as String
// Compare length of two string
if (arrA.length != arrB.length) {
return false
}
var totalA = 0
var totalB = 0
for (var i = 0; i < arrA.length; i++) {
totalA += arrA.charCodeAt(i)
totalB += arrB.charCodeAt(i)
}
// Array is same, if both total equal
if (totalA == totalB) {
return true
}
}
I found a solution that looks at the values of each array and then only shows cards that have that combination of values in it's data-attribute.
The problen with the original question, it only showed cards that had values in the same order, the first soultion showed any card that matched a value, not combination of values.
var checker = function(arr, target) {
return target.every(function(v){
return arr.includes(v);
});
}
return checker(dataArray, selectedFilters);
One final problem..how to dynamicaly change class name of same instruments by adding number?
<div class="score">
<div class="system">
<div class="stff_Flute"></div>
<div class="stff_Flute"></div>
<div class="stff_Clarinet"></div>
</div>
<div class="system">
<div class="stff_Flute"></div>
<div class="stff_Flute"></div>
<div class="stff_Clarinet"></div>
</div>
</div>
To this?...
<div class="score">
<div class="system">
<div class="stff_Flute_1"></div>
<div class="stff_Flute_2"></div>
<div class="stff_Clarinet"></div>
</div>
<div class="system">
<div class="stff_Flute_1"></div>
<div class="stff_Flute_2"></div>
<div class="stff_Clarinet"></div>
</div>
</div>
I have this code https://jsfiddle.net/7cLoxn29/1/ but something is wrong...
I'm not terribly fond of jQuery, so I created a vanilla JS solution for you (hopefully that's OK!):
let parents = document.querySelectorAll(".system")
parents.forEach((parent) => {
let children = parent.querySelectorAll("div")
children = Array.from(children).reduce((accumulator, current) => {
if (current.className in accumulator) {
accumulator[current.className].push(current)
} else {
accumulator[current.className] = [current]
}
return accumulator
}, {})
for (var key in children) {
if (children[key].length > 1) {
children[key].forEach((child, i, target) => {
child.className = `${child.className}_${i+1}`
})
}
}
})
Note that this is ES2015 JS code.
Here's an updated fiddle: https://jsfiddle.net/7cLoxn29/5/