Chaining vanilla javascript traversal? - javascript

My HTML looks like this:
<div class="panel">
<div class="panel-heading"></div>
<div class="panel-body"></div>
</div>
I am currently selecting the parent node of the panel-heading element like so:
e.target.parentNode
This leaves me with the panel class. All is well.
But now I would like to grab the panel-body at that point. Doing something like this unfortunately does not work:
e.target.parentNode.querySelector('.panel-body')
Is there a clean way to do this in vanilla javascript?

If you know the node's class, you can always use document object:
var tgt = document.querySelector('.panel-body');
If you need to get nodes in the context of an event such as click, you can delegate.
Find node that is an ancestor of all of the nodes you wish to access.
ex. .panel
Register the event on that node.
ex. panel.addEventListener('click', callback)
During the bubbling phase, find the event.target by comparing it to the event.currentTarget (the node that is registered to the event)
ex. if(e.target !== e.currentTarget) {...
Click nodes and it's tag and class will be displayed.
Details are commented in snippet
Snippet
// Reference top element
var panel = document.querySelector('.panel');
// Register .panel on click event
panel.addEventListener('click', highlight);
function highlight(e) {
// if the clicked node is not .panel
if (e.target !== e.currentTarget) {
// Get the clicked node's class
var tgtClass = e.target.className;
// Get the clicked node's tag
var tgtTag = e.target.tagName;
}
/* Set the clicked node's tag and class
|| as it's content.
*/
e.target.textContent += ' ' + tgtTag + '.' + tgtClass;
}
[class*=panel] {
border: 1px dashed blue;
color: red;
}
<section class="panel">
<hgroup class='panel-heading-group'>
<h1 class="panel-heading">HEADING</h1>
<h2 class='panel-sub-heading'>SUB-HEADING</h2>
</hgroup>
<main class="panel-body">
<p>CONTENT A</p>
<p>CONTENT B</p>
</main>
<footer class='panel-footer'>FOOTER</footer>
</section>

Related

how to add event listener to li thats inside a div

I have been trying to add an event listener to three li elements. The issue is that doing document.getElementbyid(about) doesn't work. But when I do get element document.getElementbyid(last), it works but on all three elements simultaneously.
I want to target them individually so that it opens up models respective to each li element.
Here is the HTML:
<section>
<h1 id="intro">Hi, My name is</h1>
<div id="last">
<ul>
<li id="about">About</li>
<li id="project">Projects</li>
<li id="resume">Resume</li>
</ul>
</div>
</section>
well like this :
let lis = document.querySelectorAll('section div ul li')
lis.forEach((li) => {
li.addEventListener('click', () => {
})
})
if you want you can handle each li individually like so :
let resume = document.querySelector('#resume')
resume.addEventListener('click', () => {
// do whatever you need
})
the click event here is an exemple, use the event you need, like perhaps mouseover
It is possible to add a click event to each element. You would use querySelectorAll to reference them all. You would then loop over it.
document.querySelectorAll("#last li").forEach(function (li) {
li.addEventListener("click", function () {
console.log("Clicked", li.id);
});
});
A better option is event delegation where you just bind one event.
Here is the basic idea using event delegation. Add one click event listener on a parent. You select the target which is what was clicked and find the link. You can use the link's hash to get the element you want to show.
// Bind one click event to the parent, we will use event delegation
document.getElementById("last").addEventListener("click", function(event) {
// get what was clicked and look for the anchor tag
const anchor = event.target.closest("a");
// did we find an anchor?
if (anchor) {
// hash will have the id of the element to show
showElement(anchor.hash);
}
});
function showElement(selector) {
// do we have an active modal? Yes? Close it
const modalActive = document.querySelector(".modal.active");
if (modalActive) {
modalActive.classList.remove('active');
}
// Enable the modal we just clicked on
const elem = document.querySelector(selector);
if (elem) {
elem.classList.add('active');
}
}
// Page load, if has hash then show that element
const hash = window.location.hash;
if (hash) {
showElement(hash);
}
.modal {
display: none;
}
.active {
display: block;
}
<section>
<h1 id="intro">Hi, My name is</h1>
<div id="last">
<ul>
<li>About</li>
<li>Projects</li>
<li>Resume</li>
</ul>
</div>
</section>
<div id="about" class="modal">ABOUT CONTENT</div>
<div id="project" class="modal">PROJECT CONTENT</div>
<div id="resume" class="modal">RESUME CONTENT</div>

Set parent style from child element [duplicate]

This question already has an answer here:
Preventing event bubbling
(1 answer)
Closed 1 year ago.
I'm trying to create a custom dropdown field. Following is my code:
const list = document.querySelector('.list');
const listItems = list.getElementsByTagName('p');
document.querySelector('#category').onclick = function() {
// Able to show the .list DOM element
list.style.display = 'block';
}
Array.from(listItems)
.forEach(function(listItem) {
listItem.onclick = function() {
// Unable to hide the .list DOM element
list.style.display = 'none';
// .listItem's value is getting logged
console.log(listItem.getAttribute('value'));
}
}
);
<div class="dropdown" id="category" name="category">
<div class="trigger">
<p class="selected-category">Category</p>
</div>
<div class="list">
<p value="">None</p>
<p value="1">harum inventore</p>
<p value="2">dolorem voluptatem</p>
<p value="3">dolores consectetur</p>
<p value="4">velit culpa</p>
<p value="5">beatae nulla</p>
</div>
</div>
I'm not exactly sure where it is going wrong as I'm able to set the list's style attribute to to display: block but I'm unable to set it back to display: none even though I'm trying to do the same thing from two different places.
I'm pretty new to Javascript. This might be a duplicate but I honestly tried looking up and trying as many solutions as possible. But none seemed to work. So apologies in advance.
Your code was working right, but because your parent and child had click event listeners on them, you were hiding and showing the list back.
Here you can find more about event bubbling: https://javascript.info/bubbling-and-capturing
const list = document.querySelector('.list');
const listItems = list.getElementsByTagName('p');
// Overlapping event listener 1
document.querySelector('#category').onclick = function() {
list.style.display = 'block';
}
Array.from(listItems)
.forEach(function(listItem) {
// Overlapping event listener 2
listItem.onclick = function(event) {
// to fix double event issue, you have to prevent its bubbling up
event.stopImmediatePropagation();
list.style.display = 'none';
// Uncomment the debugger here and you will see that this code works right
// debugger;
console.log(listItem.getAttribute('data-value'));
}
}
);
<div class="dropdown" id="category" name="category">
<div class="trigger">
<p class="selected-category">Category</p>
</div>
<div class="list">
<p data-value="">None</p>
<p data-value="1">harum inventore</p>
<p data-value="2">dolorem voluptatem</p>
<p data-value="3">dolores consectetur</p>
<p data-value="4">velit culpa</p>
<p data-value="5">beatae nulla</p>
</div>
</div>
The event listener of your parent #category element is executed every time you click one of it's child elements. You can prevent the click event from bubbling up to the parent element by using the stopPropagation() method on the child elements' listener.
Also, as #connexo mentioned in the comments, <p> elements do not have a value attribute. You are probably looking for a <ul> element with <li> children.
Check and run the following Code Snippet for a practical example of the above approach:
const list = document.querySelector('.list');
const listItems = document.querySelectorAll('.list li');
document.querySelector('#category').onclick = function() {
list.style.display = 'block'; // Able to show the .list DOM element
}
Array.from(listItems)
.forEach(function(listItem) {
listItem.onclick = function(e) {
e.stopPropagation(); // prevent parent event listener from being executed
list.style.display = 'none'; // Unable to hide the .list DOM element
}
}
);
.list {display: none; list-style: none;}
<div class="dropdown" id="category" name="category">
<div class="trigger">
<p class="selected-category">Category</p>
</div>
<ul class="list">
<li>None</li>
<li>harum inventore</li>
<li>dolorem voluptatem</li>
<li>dolores consectetur</li>
<li>velit culpa</li>
<li>beatae nulla</li>
</ul>
</div>

Detect an Element in event bubbles

I have this html code:
<div class="container">
<div class="parent">
<!-- P Element Completely On Parent -->
<p style="width: 100%; height: 100%;">Hello</p>
</div>
........
</div>
This is my Javascript:
document.querySelector(".container").addEventListener("click", (e) => {
if ( e.target == document.querySelector(".parent") ) {
alert(" It Worked!!! ")
}
},
true // <--- bubbling
)
I don't want to add library like jQuery and etc...
I don't want to set p elements style="pointer-events: none"
I want to use dynamic code (don't using e.target.parentElement)
Is it possible to detect parent elements clicking without giving event to it ( with event bubbling ) ?
The result code should work after client clicking on p element
If the nesting of .parent is dynamic, you might want to determine the path from the currentTarget to the target. Then search through the path for the specific element you are looking for.
// Determines the node path from baseNode to targetNode, by travelling up from
// the targetNode. The retuned array will include both the baseNode and the
// targetNode. If the targetNode is the baseNode an array with one elment is
// returned. Throws an error if baseNode is not an ancestor of targetNode.
function nodePath(baseNode, targetNode, currentPath = []) {
currentPath.unshift(targetNode);
if (targetNode == baseNode) return currentPath;
return nodePath(baseNode, targetNode.parentNode, currentPath);
}
document
.querySelector(".container")
.addEventListener("click", ({currentTarget, target}) => {
const path = nodePath(currentTarget, target);
const parent = path.find(node => node.matches(".parent"));
if (parent) {
console.log("It Worked!");
}
});
<div class="container">
<div class="parent">
<!-- P Element Completely On Parent -->
<p style="width: 100%; height: 100%;">Hello</p>
</div>
........
</div>
Yes you could try this
document.querySelector(".container").addEventListener("click", (e) => {
var parentel = e.target.parentElement
if (parentel.className == "parent") {
alert(" It Worked!!! ")
}
})
<div class="container">
<div class="parent">
<!-- P Element Completely On Parent -->
<p class- "child" style="width: 100%; height: 100%;">Hello</p>
</div>
</div>

Track all elements on page

I'm attempting to track events for all UI elements on a page. The page contains dynamically generated content and various frameworks / libraries. Initially I tracked elements through creating a css class "track" , then adding style "track" to tracked elements. elements are then tracked using :
$('.track').on('click', function() {
console.log('Div clicked' + this.id);
console.log(window.location.href);
console.log(new Date().getTime());
});
As content can be dynamically generated I wanted a method to track these elements also. So tried this using wildcard jQuery operator.
In this fiddle : https://jsfiddle.net/xx68trhg/37/ I'm attempting to track all elements using the jquery '*' selector.
Using jQuery '*' selector appears to fire the event for all elements of given type.
So for this case if is clicked all the click event is fired for all divs. But id is just available for div being clicked.
For the th element the click event is fired twice , what is reason for this ?
Can the source be modified that event is fired for just currently selected event ?
fiddle src :
$(document).ready(function() {
$('*').each(function(i, ele) {
$(this).addClass("tracked");
});
$('.tracked').on('click', function() {
console.log('Div clicked' + this.id);
console.log(window.location.href);
console.log(new Date().getTime());
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<!-- <div id="1" data-track="thisdiv">
Any clicks in here should be tracked
</div>
-->
<div id="1">
Any clicks in here should be tracked 1
</div>
<div id="2">
Any clicks in here should be tracked 2
</div>
<div id="3">
Any clicks in here should be tracked 3
</div>
<th id="th">tester</th>
You can try with:
$(document).ready(function() {
$("body > *").click(function(event) {
console.log(event.target.id);
});
});
$(document).ready(function() {
$("body > *").click(function(event) {
console.log(event.target.id);
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="1">
Any clicks in here should be tracked 1
</div>
<div id="2">
Any clicks in here should be tracked 2
</div>
<div id="3">
Any clicks in here should be tracked 3
</div>
<table>
<tr>
<td>Cols 1</td>
<td id="td">Cols 2</td>
</tr>
</table>
<p id="th">tester</p>
You may want to use event delegation to target the elements you need. Advantage is that this also works for dynamically generated elements. See code for an example of this.
// method to add/set data-attribute and value
const nClicksInit = (element, n = "0") => element.setAttribute("data-nclicked", n);
// add data-attribute to all current divs (see css for usage)
// btw: we can't use the method directly (forEach(nClicksInit))
// because that would send the forEach iterator as the value of parameter n
document.querySelectorAll("div").forEach(elem => nClicksInit(elem));
// add a click handler to the document body. You only need one handler method
// (clickHandling) to handle all click events
document.querySelector('body').addEventListener('click', clickHandling);
function clickHandling(evt) {
// evt.target is the element the event is generated
// from. Now, let's detect what was clicked. If none of the
// conditions hereafter are met, this method does nothing.
const from = evt.target;
if (/^div$/i.test(from.nodeName)) {
// aha, it's a div, let's increment the number of detected
// clicks in data-attribute
nClicksInit(from, +from.getAttribute("data-nclicked") + 1);
}
if (from.id === "addDiv") {
// allright, it's button#addDiv, so add a div element
let newElement = document.createElement("div");
newElement.innerHTML = "My clicks are also tracked ;)";
const otherDivs = document.querySelectorAll("div");
otherDivs[otherDivs.length-1].after(newElement);
nClicksInit(newElement);
}
}
body {
font: 12px/15px normal verdana, arial;
margin: 2em;
}
div {
cursor:pointer;
}
div:hover {
color: red;
}
div:hover:before {
content: '['attr(data-nclicked)' click(s) detected] ';
color: green;
}
#addDiv:hover:after {
content: " and see what happens";
}
<div id="1">
Click me and see if clicks are tracked
</div>
<div id="2">
Click me and see if clicks are tracked
</div>
<div id="3">
Click me and see if clicks are tracked
</div>
<p>
<button id="addDiv">Add a div</button>
</p>
<h3 id="th">No events are tracked here, so clicking doesn't do anything</h3>
You can invoke the stopPropagation and the condition this === e.currentTarget to ensure invoke the handler function of the event source DOM.
And you must know the <th> tag must wrapped by <table>, otherwise it will not be rendered.
$(document).ready(function() {
$('*').each(function(i, ele) {
$(this).addClass("tracked");
});
$('.tracked').on('click', function(e) {
if (this === e.currentTarget) {
e.stopPropagation();
console.log('Div clicked' + this.id);
console.log(window.location.href);
console.log(new Date().getTime());
}
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<!-- <div id="1" data-track="thisdiv">
Any clicks in here should be tracked
</div>
-->
<div id="1">
Any clicks in here should be tracked 1
</div>
<div id="2">
Any clicks in here should be tracked 2
</div>
<div id="3">
Any clicks in here should be tracked 3
</div>
<table>
<th id="th">tester</th>
</table>

Select next <div /> once you know the closest selector with javascript

I am developing a website and I can't use jQuery (no discussion about this), so pure javascript and a custom javascript framework is used.
Actually I have found a situation that I don't know how to handle:
I've a group of selectors, that for each one I add a "onclick" event to display / hide a div.
For example:
<div id="menu">
<div class="menu-item">
<div class="arrow">
<a class="down">Open / Close</a>
</div>
Menu Item
<div class="extramenu hidden">
Extra menu items
</div>
</div>
<div class="menu-item">
<div class="arrow">
<a class="up">Open / Close</a>
</div>
Menu Item 2
<div class="extramenu">
Extra menu items
</div>
</div>
<div class="menu-item">
<div class="arrow">
<a class="down">Open / Close</a>
</div>
Menu Item 3
<div class="extramenu hidden">
Extra menu items
</div>
</div>
</div>
I select all "div.menu-item .arrow a" items, so I've 3 items. For each item I add a onclick event (that actually works fine).
What I need to archive is how to select the "closest" class .extramenu inside the div.menu-item. Then detect if the <a /> have a class .up or .down and if class == .up, add the class hidden; and if class == .down, remove the class hidden.
This a concept of what have to do, it's not javascript code:
var elements; // my list of elements
each(elements, function(element) {
// here element is pointing to the ANCHOR
add_event(element, "onclick", function(e) {
var submenu; // here I need to detect the submenu closest to my anchor
var state; // here I need to know if the anchor has class up or down
if (state == "up")
{
add_class(submenu, "hidden"); // hide the submenu div
remove_class(element, "up"); // remove the class up
add_class(element, "down"); // and add the class down
}
else if (state == "down")
{
remove_class(submenu, "hidden"); // remove the class to show the menu
remove_class(element, "down"); // remove the class down
add_class(element, "up"); // and add the class up
}
});
});
Thank you guys and sorry if it's not well explained, I did my best!
element.querySelectorAll allows you to select elements by CSS selector.
element.classList allows you to access the classes of an element
add_event(element, "onclick", function(e) {
var el = e.target, state;
var parent = el.parentNode;
while (!parent.classList.contains('menu-item')) {
parent = parent.parentNode;
}
var submenu = parent.querySelector('extramenu');
if (el.classList.contains('up')) {
state = 'up';
} else {
state = 'down'
}
/* ... */
});
You can write the rest of the pseudo code yourself.
I'm assuming your already using Modernizr for supporting legacy browsers like IE8. If your not, then do so.
Maybe not exactly the way you want to do this but if class up or down would be added to parent of the a ie div.arrow you could do all of the hiding/showing with css combinator +. like this:
.arrow.down + .extramenu {
/* the same styles as with hidden class */
}
This is the code you are looking for. Tested in FF5, should be cross-browser.
// use in your add_event function
var submenu = element.parentNode.nextSibling;
while(submenu && (submenu.nodeName!="DIV" || submenu.className.indexOf("extramenu")==-1)) submenu = submenu.nextSibling;
var state = element.className;

Categories