Currently have some html and JS which I want to use to search through revision notes. My aim is that when the keywords in the parents of a ul/li equal the input, all children (like the all * selector in css) will be shown.
Purpose: if an li contains given input then show all of that li's children (e.g. example 1, 2, 3) - if an li next to the li that contains input do not contain input the hide that.
Examples of different input and desired output in style:
Example 1
Input = keyword1 or Input = Parent 1
Parent 1 keyword1 (style="display:block;")
Child keyword2 (style="display:block;")
"Grand" Child keyword3 (style="display:block;")
Child 2 keyword4 (style="display:block;")
"Grand" Child keyword5 (style="display:block;")
Parent 2 keyword6 (style="display:none;")
Child 2 keyword7 (style="display:none;")
"Grand" Child keyword8 (style="display:none;")
Child 3 keyword9 (style="display:none;")
"Grand" Child keyword10 (style="display:none;")
Example 2
Input = keyword2 or Input = keyword3
Parent 1 keyword1 (style="display:block;")
Child keyword2 (style="display:block;")
"Grand" Child keyword3 (style="display:block;")
Child 2 keyword4 (style="display:none;")
"Grand" Child keyword5 (style="display:none;")
Parent 2 keyword6 (style="display:none;")
Child 2 keyword7 (style="display:none;")
"Grand" Child keyword8 (style="display:none;")
Child 3 keyword9 (style="display:none;")
"Grand" Child keyword10 (style="display:none;")
Example 3
Input = keyword4 or Input = keyword5
Parent 1 keyword1 (style="display:block;")
Child keyword2 (style="display:none;")
"Grand" Child keyword3 (style="display:none;")
Child 2 keyword4 (style="display:block;")
"Grand" Child keyword5 (style="display:block;")
Parent 2 keyword6 (style="display:none;")
Child 2 keyword7 (style="display:none;")
"Grand" Child keyword8 (style="display:none;")
Child 3 keyword9 (style="display:none;")
"Grand" Child keyword10 (style="display:none;")
Currently when an input is entered the function only shows the exact li that matches - I want all children to be shown. So how? Thanks. See code below.
function searchTitle() {
var input, filter, ul, li, a, i;
input = document.getElementById("myInput");
filter = input.value.toUpperCase();
window.status = filter;
ul = document.getElementById("List");
li = ul.getElementsByTagName("li");
for (i = 0; i < li.length; i ) {
a = li[i];
if (a.textContent.toUpperCase().indexOf(filter) > -1) {
a.style.display = "";
} else {
a.style.display = "none";
}
}
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<body>
<input type="text" id="myInput" onkeyup="searchTitle()" placeholder="Search through notes" title="Type in a keyword">
<ul id="List">
<li>
Parent 1 keyword1
<ul>
<li>
Child keyword2
<ul>
<li>"Grand" Child keyword3</li>
</ul>
</li>
<li>
Child 2 keyword4
<ul>
<li>"Grand" Child keyword5</li>
</ul>
</li>
</ul>
</li>
<li>
Parent 2 keyword6
<ul>
<li>
Child 2 keyword7
<ul>
<li>"Grand" Child keyword8</li>
</ul>
</li>
<li>
Child 3 keyword9
<ul>
<li>"Grand" Child keyword10</li>
</ul>
</li>
</ul>
</li>
</ul>
</body>
</html>
CodePudding user response:
Your function is setting display: none
to every li
that does not match the search keyword. But since you use .textContent
, the parents are not affected (they match anyway).
So now for the childrens, I achieved it using a recursive function to loop through all the childNodes of a match , until there is none, and set the display: ""
on them if it is a LI
element.
I used a setTimeout
to ensure the recursion occurs when the loop of searchTitle
has finished. Notice the let a
instead of a var
. That is to make sure to pass the matching element.
function searchTitle() {
var input, filter, ul, li, i;
input = document.getElementById("myInput");
filter = input.value.toUpperCase();
window.status = filter;
ul = document.getElementById("List");
li = ul.getElementsByTagName("li");
for (i = 0; i < li.length; i ) {
let a = li[i];
if (a.textContent.toUpperCase().indexOf(filter) > -1) {
a.style.display = "";
setTimeout(()=>displayChilds(a),1) // Call the recusrsive function
} else {
a.style.display = "none";
}
}
}
// Recursive function called from a matching element
function displayChilds(el){
if(el.hasChildNodes){
el.childNodes.forEach(child => {
if(child.tagName == "LI"){
child.style.display = ""
}
displayChilds(child) // Recusion occurs here
})
}
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<body>
<input type="text" id="myInput" onkeyup="searchTitle()" placeholder="Search through notes" title="Type in a keyword">
<ul id="List">
<li>
Parent 1 keyword1
<ul>
<li>
Child keyword2
<ul>
<li>"Grand" Child keyword3</li>
</ul>
</li>
</ul>
</li>
<li>
Parent 2 keyword4
<ul>
<li>
Child keyword5
<ul>
<li>"Grand" Child keyword6</li>
</ul>
</li>
</ul>
</li>
</ul>
</body>
</html>
CodePudding user response:
const $input = document.querySelector("#myInput");
$input.addEventListener("input", (e) => {
const query = e.target.value;
const $ul = document.querySelector("ul");
const $lis = $ul.querySelectorAll("li");
const $matchedLis = [...$lis].filter(($li) => $li.textContent.match(query));
[...$lis].forEach(($li) => $li.classList.add("hide"));
[...$matchedLis].forEach(($li) => {
$li.classList.remove("hide");
[...$li.querySelectorAll("li")].map(($li) => $li.classList.remove("hide"));
});
});
https://jsfiddle.net/3bh2paow/
What it does:
Fires event on input. [Not keypress]
I use variables that begin with $
for readability, they imply dom elements.
I hide elements by adding hide class to element. [Not inline JS]
Steps of code: Filter all lis which match the query string (the value in input) Hide all LIs Unhide (by removing hide class) of elements which were filtered. Unhide all children elements of previous step.