How to best get the parent of parent element in javascript - javascript

I'm trying to get the parent of some deeply nested element like below
<div class='content id='cart-content'>
<div class='child1'>
<div class='child1-1'>
<div class='child1-1-1'>
<input
type="checkbox"
class='selectAllItem'
name='selectAllItem'
/> Select All
</div>
</div>
</div>
<div class='child2'>
<div class='child2-2'>
<div class='child2-2-2'>
<input
type="checkbox"
class='selectOneItem'
name='selectOneItem'
/> Select One
</div>
</div>
</div>
</div>
when i check Select All box I want to get the node of the root parent which have id='cart-content'
my approach
1.
let rootNode = event.target.closest('#cart-content')
but the problem is clicking on select one checkbox would also return same result because they both have same root parent and are on same level
approach 2.
let rootNode = event.target.parentNode.parentNode.parentNode.parentNode;
the problem with this approach is the same if i click on select one checkbox would also return the root parent because the distance between the element and the root parent is also 4 parents
Now in jquery i would do the below to get desire result
let rootNode = $(this).parent('.child1-1-1').parents('#');
and when select one is clicked it won't return the rootNode because it doesn't have a direct parent with the class name child1-1-1
How can I achieve this same result using pure javascript vanilla js
Thanks for any help

This is my approach to solve the problem
get the immediate parent of the input field then check if it contails a specific class
var rootParentNode = e.target.parentNode.classList.contains('child1-1-1') ? e.target.closest('#cart-content') : null;

I think you only need to add an unique id to your input dynamically (not hard coding as I do below) and then just do what you want with the parentNode only if your input id is number 1, for instance (Select All) and nothing if it is number 2 (Select One).
Note that the event is listening to changes on check box, not to the checked status
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Static Template</title>
</head>
<body>
<h1>
This is a static template, there is no bundler or bundling involved!
</h1>
<div class="content" id="cart-content">
<div class="child1">
<div class="child1-1">
<div class="child1-1-1">
<input
id="input1"
type="checkbox"
class="selectAllItem selectinput"
name="selectAllItem"
/>
Select All
</div>
</div>
</div>
<div class="child2">
<div class="child2-2">
<div class="child2-2-2">
<input
id="input2"
type="checkbox"
class="selectOneItem selectinput"
name="selectOneItem"
/>
Select One
</div>
</div>
</div>
</div>
</body>
<script>
let allInputs = document.getElementsByClassName("selectinput");
allInputs = [...allInputs];
allInputs.forEach((input) => {
input.addEventListener('change', (e) => {
let parentNode;
if (e.target.id == "input1") {
parentNode = e.target.parentNode.parentNode.parentNode.parentNode;
// Do something with parentNode
console.log(parentNode)
}
else{
// Do something else
}
});
});
</script>
</html>

Related

When changing the element/node style.display, the dom is missing some elements

I have a large DOM with a pre element containing thousands of span nodes with X different name attributes. Upon a user action, I'm changing the style(style.display) X-1 name attributes to none. The node count of unchanged name attribute is over 1000. But after performing this operation, the page is displaying only a couple of hundreds of unchanged instead of all.
Note: I don't see any issue when node count is low.
function filters() {
var nameElement = document.getElementsByName('filterType');
let selectedFiltersList = [];
if (nameElement.length > 0) {
for (let i = 0; i < nameElement.length; i++) {
if (nameElement[i].checked) {
selectedFiltersList.push(nameElement[i].defaultValue);
}
}
}
if (selectedFiltersList.length > 0) {
const allFilters = ["Warning", "Error", "State"];
const unSelectedFilters = allFilters.filter(
val => !selectedFiltersList.includes(val),
);
unSelectedFilters.forEach(name => {
let nameArray = document.getElementsByName(name);
if (
nameArray &&
nameArray.length > 0 &&
nameArray[0].style.display !== 'none'
) {
nameArray.forEach(elem => (elem.style.display = 'none'));
}
});
}
}
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8" />
<title>Viewer</title>
<link rel="stylesheet" href="../css/view.css" />
</head>
<body>
<div class="header">
<div class="filters" onclick="filters()">
<input type="checkbox" name="filterType" value="Warning" />
Warning
<input type="checkbox" name="filterType" value="Severe" />
Severe
<input type="checkbox" name="filterType" value="State" /> State
</div>
</div>
<div class="output">
<pre id="data">
<span class="st" name="State">X State ===> hello state
</span>
<span class="wrn" name="Warning">X Warn ===> hello warn
</span>
<span class="err" name="Error">X Error ===> hello error
</span>
....(// thousands of span elements like above)
</pre>
</div>
</body>
</html>
As #Mike Kamermans already commented: you should work with a class (here hide) to make targeted spans invisible. The name-attribute cannot be assigned to spans but you can use data-name instead.
Your markuo structure is still somewhat "unorthodox". I expect you will not be using <span>s in a <pre> in the finished version of the page, but the principle can be demonstrated here nonetheless.
in flt I collect the values of all checked boxes. I then loop (forEach) over all #data spans and toggle() their class hide by checking if their dataset.name can be found in flt: if they cannot be found I "hide" the element, otherwise I remove the class "hide" again.
function filters() {
let flt=[...document.querySelectorAll('[name=filterType]:checked')].map(f=>f.value);
[...document.querySelectorAll('#data span')].forEach(s=>
s.classList.toggle('hide',!flt.includes(s.dataset.name)));
}
.hide {display:none}
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8" />
<title>Viewer</title>
<link rel="stylesheet" href="../css/view.css" />
</head>
<body>
<div class="header">
<div class="filters" onclick="filters()">
<label><input type="checkbox" name="filterType" value="Warning" checked/>
Warning</label>
<label><input type="checkbox" name="filterType" value="Severe" checked/>
Severe</label>
<label><input type="checkbox" name="filterType" value="State" checked/> State</label>
</div>
</div>
<div class="output">
<pre id="data"><span class="st" data-name="State">X State ===> hello state
</span>
<span class="wrn" data-name="Warning">X Warn ===> hello warn
</span>
<span class="err" data-name="Severe">X Error ===> hello error
</span>
....(// thousands of span elements like above)
</pre>
</div>
</body>
</html>
( I adjusted some of your span-(data-)names so they correspond with the three checkbox
values. )

how to connect javascript to elements added after the html code is updated?

I am following the Dom javascript net ninja series, and I am stuck because after I inject an 'li' element the delete function doesn't work anymore. I had a similar problem at first, the querySelector() method didn't work, but it was solved using setTimeout(), can u help, please?
Here is the HTML code:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<link href="style.css" rel="stylesheet" />
<script src="cont.js"></script>
<title>Testing JavaScript</title>
</head>
<body>
<div class="wrapper">
<header>
<div id="page-banner">
<h1 class="title">My Contacts</h1>
<p>Special List</p>
<form id="search">
<input type="text" placeholder="Search Contacts..." />
</form>
</div>
</header>
<div id="Contact-List">
<h2 class="title">Contacts</h2>
<ul>
<li>
<span class="contat">Zineb Zrhari</span>
<span class="delete">delete</span>
</li>
<li>
<span class="contat">Kawter Lebouni</span>
<span class="delete">delete</span>
</li>
<li>
<span class="contat">Oumayma Touji</span>
<span class="delete">delete</span>
</li>
<li>
<span class="contat">Rim Essahel</span>
<span class="delete">delete</span>
</li>
</ul>
</div>
<form id="add">
<input type="text" placeholder="Add a Contact..." />
<button>Add</button>
</form>
</div>
</body>
</html>
And the javascript code:
window.setTimeout(() => {
const list = document.querySelector("#Contact-List ul");
// delete books
list.addEventListener("click", function (e) {
if (e.target.className == "delete") {
const li = e.target.parentElement;
li.parentNode.removeChild(li);
}
});
// add book
const addForm = document.forms["add"];
addForm.addEventListener("submit", function (e) {
e.preventDefault();
const value = addForm.querySelector("input[type='text']").value;
// create elements
const li = document.createElement("li");
const contact = document.createElement("span");
const deleteBtn = document.createElement("span");
// add contant
deleteBtn.textContent = "delete";
deleteBtn.classList.add("delete");
contact.textContent = value;
deleteBtn.classList.add("name");
// append to document
li.appendChild(contact);
li.appendChild(deleteBtn);
list.appendChild(li);
});
});
Thank you!
Thank you for posting your complete code.
The issue is with this line of your JavaScript:
if(e.target.className == 'delete'){
This will work if delete is the only class applied to an element. However, you are applying two classes to the new element, which means e.target.className becomes delete name.
You can fix it by changing the line to:
if(e.target.classList.includes('delete')) {
This will work even if the element has multiple classes.

Getting the child img inside a div using $(this); [on click event]

I'm trying to get the child image of a clicked div.
I want to get it's src value. But it's returning undefined.
Could someone point me in the right direction?
Tried using Jquery .find() https://api.jquery.com/find/
Tried using Jquery .children() https://api.jquery.com/children/
Both return undefined.
for (let i = 0; i < $('#draw-raster > div').length; i++) {
$(document).on('click', '#raster-item'+i, () => {
let image = $(this).children('img').attr('src'); //undefined
let image2 = $(this).find('img').attr('src'); //undefined
if (image) {
console.log(image);
return alert("image child found!");
}
return setTimeout(() => {
$('#raster-item'+i).children('img').hide();
}, 4500);
});
$('#image'+i).hide();
}
load html:
for(let i = 0; i < 16; i++)
{
let image = displayImages();
$('#draw-raster').prepend(
"<div id=raster-item" + i + " class='imageh"+i+"' data-id=" + i + "><img src='"+ displayImages() +"' class='image "+i+"' id='image"+ i +"' alt='Failed to load image' width='173.19' height='107.3'></div>"
);
}
html page:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Memory</title>
<script src="inc/js/jquery.min.js"></script>
<link rel="stylesheet" href="inc/css/boostrap.min.css">
<link rel="stylesheet" href="inc/css/memory.css">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
</head>
<body>
<div class="container justify-content-center">
<div class="wrapper">
<div class="row">
<div class="col-lg-9">
<div class="card">
<div class="card-header bg-dark" style="color:white;">
<h2>Memory</h2>
</div>
<div class="card-body">
<section class="col-12 mx-auto" id="draw-raster">
</section>
</div>
</div>
</div>
<div class="col-lg-3">
<div class="card">
<div class="card-header bg-dark" style="color:white;">
<h2>Turns</h2>
</div>
<div class="card-body">
<div id="turns">Turns: 0</div>
<div id="sets">Sets: 0</div>
</div>
</div>
<div class="form-group">
<button class="btn btn-success col-12" type="button" id="reset">Reset scores</button>
</div>
</div>
</div>
</div>
</div>
<script src="inc/js/memory.js"></script>
</body>
</html>
Both attempts return undefined, i'm uncertain what would work.
Yes, I've been spamming google too. :'^)
A couple of notes on your code:
1) If you want to use this you'll need to switch from an arrow function back to a regular anonymous function. Arrow functions don't have a this of their own and will borrow the context from their outer lexical environment. It's why your code keeps return undefined.
2) You don't need a loop. The benefit of using jQuery is that you can operate on collections of elements all at once. In your case you're attaching a single event listener to a parent element (here: document) and waiting for events to bubble up from the .raster-item imgs and be "captured". This is called event delegation and is useful when you want to process new elements added to the DOM after it has loaded.
2) You will find it easier to use a class instead of many ids.
Here's an example based on your code with these changes:
// Use event delegation to add an event listener to the element with
// the container class that watches out for click events on **all**
// elements with the raster-item class that contain images
$('.container').on('click', '.raster-item img', function () {
// `$(this)` will be the image element, so simply grab its src
// from the attribute
console.log($(this).attr('src'));
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="container">
<div class="raster-item"><img src="https://dummyimage.com/50x50/555/fff.png" /></div>
<div class="raster-item"><img src="https://dummyimage.com/50x50/777/fff.png" /></div>
<div class="raster-item"><img src="https://dummyimage.com/50x50/999/fff.png"/></div>
<div class="raster-item"><img src="https://dummyimage.com/50x50/bbb/fff.png" /></div>
</div>
You don't need jQuery for this. You can harness the power of event bubbling with vanilla JavaScript.
In the web page below, the code inside the script tags, listen for a click event and runs some code if that event happens, i.e. bubbles, through a DIV element:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Clicked div img</title>
</head>
<body>
<div class="catcher">
<p>This is a div with an image inside</p>
<img src="image-to-pick.jpg" alt="image to pick" ()>
</div>
<script>
document.addEventListener('click', function (event) {
if (event.target.tagName == 'DIV') {
var imgToPick = event.target.querySelector('img');
console.log(imgToPick.src); // add your code here
}
}, false);
</script>
</body>
</html>
In other words, you trigger a "click event" whenever you click on that page, that event bubbles up until it reaches the root of the HTML document (which you can imagine as an upside-down tree where the root is the html tag).
If you don't need or don't want to let it bubble to the elements "above" you DIV, you can also stop the propagation of that click event by using event.stopPropagation(), right after you handle the img src.
You can find more info about how this works here on MDN (Mozilla Dev. Network)
I'm not quite sure in what context you need to do this, but with jquery it's pretty straight forward.
If you have multiple images within a parent div, you can set the child images as the selecters for the click event, and return each image src when clicked on directly.
The resulting jquery is only three lines long this way, and you can add as many images as you like to the parent div:
<div class="image-container">
<img id="first" src="first-source-goes-here.jpg" alt="img text" />
<img id="second" src="second-source-goes-here.jpg" alt="img text" />
<img id="third" src="third-source-goes-here.jpg" alt="img text" />
</div>
$(".image-container > img").click(function() {
// replace 'alert' with what ever you need it to be
alert( $(this).attr("src") )
})
EDIT:
In response to Andy's comment on my answer below, if you are loading images once the DOM has been loaded, then you could run a check on the click parent div to see if there are any images within it before returning the source:
$(".image-container").click(function() {
if( $(this).children("img").length > 0 ) {
alert( $(this).find("img").attr("src") )
} else {
alert('there are no images here')
}
})

I cannot seem to push items to my to-do list

The finished product is just supposed to have a checkbox next to each entry, and the option to edit or delete each item. I'm nowhere near that as I can't even get an item to post.
Here's are the files that I have: HTML, CSS, JS.
Also, I'm sorry for the formatting.I didn't paste the CSS as that's not an issue as far as I'm concerned.
HTML:
var list = document.getElementById('list'); //The unordered list.
var entry = document.createElement("li"); //Whatever this is. I'm assuming a command saved into a variable (?).
//var todolist = list.getElementsById('li');
// var btn = document.getElementById('ToDoButton');
//
// btn.addEventListener("click", function(){
// todolist.appendChild('li');
// });
/* Upon submission of the item in the text field, the string is stored in inputValue
and theText becomes a text node of inputValue, which is appended to the end of the variable
entry. This means that there should be a new item added to the unordered list with the information
found in the text field, but it doesn't show.
Also, as far as I know, this is only if the Add button is clicked, and not upon
pressing Enter while in the text box. */
function newElement() {
var inputValue = document.getElementById("textBox").value;
var theText = document.createTextNode(inputValue);
entry.appendChild(theText);
if (inputValue !== '') {
document.getElementById(list).appendChild(entry);
}
}
<!DOCTYPE html>
<html>
<head>
<title>To-Do List</title>
<link rel="stylesheet" href="todo.css">
</head>
<body>
<div class="toDoList">
<h4>To-Do List</h4>
<form target="_self">
<!-- This is so that the submitted information doesn't go anywhere but to the current page. -->
<input type="text" name="toDoList" placeholder="To-Do" id="textBox">
<input type="submit" value="Add" onclick="newElement()" id="ToDoButton">
<!-- newElement() is a JS function that will add the information in the search bar to the unordered list below. -->
</form>
</div>
<section id="main">
Tasks:
<ul id="list">
<!-- These are dummy values for the unordered list. The idea
is that the next item should be placed beneath them. -->
<li>test1</li>
<li>test2</li>
<li>test3</li>
</ul>
</section>
<script type="text/javascript" src="todo.js">
</script>
</body>
</html>
This is a short example of how to dynamically add elements on the page. User types in a to do item, then clicks the button. When they click the button, we get the value from the input box, create a new list item element, and then append it to the dom.
function addItem() {
var el = document.createElement('li')
var val = document.getElementById('item-val').value;
el.innerHTML = val;
document.getElementById('list').append(el);
}
<input id="item-val" type="text" />
<button onclick="addItem()">Add an item</button>
<ul id="list"></ul>
A few problems:
1) When you click the button, it submits the form. This causes your page to refresh, so any and all changes made by the JavaScript are lost, because you re-load the page from the server. Changing it to <button type="button" means it doesn't cause a postback any more. To be honest you probably don't actually need <form> here at all if you aren't going to send the data to the server.
2) Better to put your list and entry variables inside the function - globals are best avoided if you can, to reduce accidental scope issues. Also you need to create a new entry each time, not keep appending the same one.
3) document.getElementById(list).appendChild(entry) doesn't work because list is already an object representing an element - it's not a string containing an ID. so list.appendChild() is correct here - i.e. you can just call the appendChild() function on the existing object directly.
4) Optionally, you don't really need the separate textNode object - just set the innerText property of the list item instead.
5) Optionally again, but considered best practice: I declared an unobtrusive event handler (using addEventListener) rather than putting it inline inside the HTML. This is generally considered to make the code more maintainable and traceable, as all the script is held in one place, separate from the HTML.
Here's a fixed version:
document.querySelector("#ToDoButton").addEventListener("click", newElement);
/* Upon submission of the item in the text field, the string is stored in inputValue
and theText becomes a text node of inputValue, which is appended to the end of the variable
entry.*/
function newElement() {
var list = document.getElementById('list'); //The unordered list.
var entry = document.createElement("li"); //a new list item
var inputValue = document.getElementById("textBox").value;
if (inputValue !== '') {
entry.innerText = inputValue;
list.appendChild(entry);
}
}
<!DOCTYPE html>
<html>
<head>
<title>To-Do List</title>
<link rel="stylesheet" href="todo.css">
</head>
<body>
<div class="toDoList">
<h4>To-Do List</h4>
<form target="_self">
<input type="text" name="toDoList" placeholder="To-Do" id="textBox">
<button type="button" id="ToDoButton">Add</button>
</form>
</div>
<section id="main">
Tasks:
<ul id="list">
<!-- These are dummy values for the unordered list. The idea
is that the next item should be placed beneath them. -->
<li>test1</li>
<li>test2</li>
<li>test3</li>
</ul>
</section>
<script type="text/javascript" src="todo.js">
</script>
</body>
</html>
Your main issue is that you were using and <input type="submit"> when you were expecting the behavior of <input type="button"> with a click event listener:
document.querySelector('#ToDoButton').addEventListener('click', newElement);
function newElement() {
var inputValue = document.getElementById("textBox").value;
var theText = document.createTextNode(inputValue);
var liEl = document.createElement('li');
liEl.appendChild(theText);
if (inputValue !== '') {
document.getElementById('list').appendChild(liEl);
}
}
<head>
<title>To-Do List</title>
<link rel="stylesheet" href="todo.css">
</head>
<body>
<div class="toDoList">
<h4>To-Do List</h4>
<input type="text" name="toDoList" placeholder="To-Do" id="textBox">
<!--The input type needs to be "button, not "submit"-->
<input type="button" value="Add" id="ToDoButton">
</div>
<section id="main">
Tasks:
<ul id="list">
<li>test1</li>
<li>test2</li>
<li>test3</li>
</ul>
</section>
<script type="text/javascript" src="todo.js">
</script>
</body>

Ajax debug Error

i have problem ,when i bind two components (checkbox and label) by adding tag attribute "for" to label , and tag attribute "id" to checkbox, it throws ajax debug error : " Cannot bind a listener for event "click" on element "variantBox4" because the element is not in the DOM".
Here is checkbox code:
AjaxCheckBox checkBox = new AjaxCheckBox("variantBox", variantModel) {
#Override
protected void onUpdate(AjaxRequestTarget target) {
if (variantModel.getObject()) {
target.appendJavaScript(";utils_showElement(" + item.getModelObject().getId() + ");");
} else {
target.appendJavaScript(";utils_hideElement(" + item.getModelObject().getId() + ");");
}
}
};
i add attribute modifier to checkbox in this code:
checkBox.add(new VariantElementAttributeModifier("id",Model.of("checkbox_"+Long.toString(item.getModelObject().getId()))));
here i do the same operation with label:
Label headerLabel = new Label("content", Model.of(item.getModelObject().getContent()));
headerLabel.add(new VariantElementAttributeModifier("for",Model.of("checkbox_"+Long.toString(item.getModelObject().getId()))));
here is html:
<!DOCTYPE html>
<html lang="en" xmlns:wicket="http://maven.apache.org/FML/1.0.1">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<wicket:panel>
<section class="column column_form">
<div class="column__title">Опросный лист</div>
<div wicket:id="container" class="column__content" style="height:
475px;">
<div wicket:id="list" class="form">
<div wicket:id="contentArea"></div>
<div wicket:id="helpLabel"></div>
<wicket:fragment wicket:id="variantFragment"
class="form__item checkbox">
<div class="checkbox__label" wicket:id="content">
</div>
<input class="checkbox__input" type="checkbox"
wicket:id="variantBox" />
<!--<input class="checkbox__input" type="checkbox"
name="input-name" id="checkbox_1"/>-->
<!--<label class="checkbox__label" for="checkbox_1"><b>текст</b><span>текст текст</span><span>текст текст</span></label>-->
</wicket:fragment>
<wicket:fragment wicket:id="Textfragment"
class="form__item form__textfield">
<label wicket:id="textlabel">лейбл</label>
<input type="text" wicket:id="textfield" />
</wicket:fragment>
</div>
</div>
</section>
</wicket:panel>
</body>
</html>
Here is attribute modifier code:
package ru.simplexsoftware.constructorOfDocuments.Utils;
import org.apache.wicket.AttributeModifier;
import org.apache.wicket.model.IModel;
public class VariantElementAttributeModifier extends AttributeModifier {
public VariantElementAttributeModifier(String attribute, IModel<?> replaceModel) {
super(attribute, replaceModel);
}
}
Thanks for help.
If you want to manually change the ID of an element, you have to use Component#setMarkupId(String).
Using the AttributeModifier basicly just adds whatever attribute and value you want to the generated HTML. It doesn't tell Wicket about the new ID you want to use though, so Wicket internally still uses its own ID to generate the JavaScript for the AjaxCheckBox.
Btw: If you have an HTML label tag and a corresponding HTML form component, you can also use the wicket:for attribute as in this example:
<label wicket:for="nameInput">Name</label>
<input wicket:id="nameInput">
This tells Wicket which label and form component belong together, so it can generate the correct attributes and values on its own without any extra code in you Java class.

Categories