How to get child ID of parent contenteditable div on keydown? - javascript

I'm trying to get back children id on keydown event but so far i had only luck with click event.
When i'm doing with keydown event my solution got me parent ID back but with click event i got children ID back.
Do you have any idea how can i achieve this but with keydown event?
// Refernce the parent of all of the target nodes
var parent = document.getElementById('parent');
// Register the click event to #parent
parent.addEventListener('keydown', idNode);
// This is the callback that is invoked on each click
function idNode(e) {
/* If the node clicked (e.target) is not the
|| the registered event listener
|| (e.currentTarget = #parent)
*/
if (e.target !== e.currentTarget) {
// Get the #id of clicked node
var ID = e.target.id;
// Reference e.target by its #id
var child = document.getElementById(ID);
}
// Log the #id of each e.target at every click
console.log('The caret is located at ' + ID);
// Return the e.target as a DOM node when needed
return child;
}
<div id="parent" contenteditable="true">
<div id="child-1" tabindex="-1">
One
</div>
<div id="child-2" tabindex="-1">
Two
</div>
<div id="child-3" tabindex="-1">
Three
</div>
</div>

Check out this snippet:
<!DOCTYPE html>
<html>
<head>
<title></title>
</head>
<body>
<div id="parent">
<div id="child-1" contenteditable="true">
One
</div>
<div id="child-2" contenteditable="true">
Two
</div>
<div id="child-3" contenteditable="true">
Three
</div>
</div>
<script type="text/javascript">
// Refernce the parent of all of the target nodes
var parent = document.getElementById('parent');
// Register the click event to #parent
parent.addEventListener('keydown', idNode, false);
// This is the callback that is invoked on each click
function idNode(e) {
/* If the node clicked (e.target) is not the
|| the registered event listener
|| (e.currentTarget = #parent)
*/
var ID, child;
if (e.target !== e.currentTarget) {
// Get the #id of clicked node
ID = e.target.id;
// Reference e.target by its #id
child = document.getElementById(ID);
}
// Log the #id of each e.target at every click
console.log('The caret is located at ' + ID);
// Return the e.target as a DOM node when needed
return child;
}
</script>
</body>
</html>
Basically you making parent editable, so KeyboardEvent receiving id of parent only. Instead of parent I have set contenteditable="true" on child s which make sure KeyboardEvent find out right target.

Ids in dynamic content are hard to manage, as the user can remove elements with an id, and create new elements without an id. Refer the elements in a content editable with references only.
Only focusable elements (like form control elements and an editable, also document) can fire a keydown event. An unfocusable element doesn't participate on KeyboardEvent firing in any way, the event really fires on the element it is attached, it's not bubbling up from the seemingly targeted element (the element the caret is positioned to). The seemingly targeted element isn't also included in any of the properties of KeyboardEvent object.
You've to use Selection Object to get the element where the caret lies when KeyboardEvent fires. Something like this:
const parent = document.getElementById('parent');
parent.addEventListener('keydown', idNode);
function idNode(e) {
const selection = window.getSelection(),
target = selection.anchorNode.parentElement;
console.log(target);
}
<div id="parent" contenteditable="true">
<div id="child-1">
One <span>Plus</span>
</div>
<div id="child-2">
Two
</div>
<div id="child-3">
Three
</div>
</div>
There are multiple ways to get the selected element from Selection object, the above code is just an example. As a sidenote, returning from an event handler function is not useful, as there's no place in the code where you could receive the returned value (the value is returned to the event queue).

Related

How to get the highest parent element of an element that was clicked on that is still a child of the element that the event listener was added to?

How do I get the highest parent element of an element that was clicked on that is still a child of the element that the event listener was added to? Does this exist as a property for the event object?
I think you've to go with the "old-fashion" way with this, i.e. iterate through the event.target parents until the correct child is found.
const target = document.getElementById('currentTarget');
target.addEventListener('click', function(e) {
let target = e.target,
parent = target,
outmost = e.currentTarget;
console.log('The clicked element is', target.id);
while (parent) {
if (parent.parentElement === outmost) {
break;
}
parent = parent.parentElement;
}
console.log(parent ? 'Hit ' + parent.id : 'didn\'t hit');
});
div {
margin: 20px;
border: 3px solid #ccc;
}
<div id="currentTarget">
<div id="wantedElement">
<div id="intermediate1">
<div id="intermediate2">
<div id="mostInner"></div>
</div>
</div>
</div>
</div>
This algorithm is generic, it can be used with any HTML structure, and the result doesn't depend on any identifiers of the elements. See a jsFiddle with two children in the outer wrapper.

EventListener doesn't trigger by clicking to div's

I create multiple div's dynamically with Javascript
var cart = document.createElement("div");
cart.classList.add("buy-item");
cart.setAttribute("name", "buy-food");
div.appendChild(cart);
and when i collect all the elements with the "buy-item" class i get the elements as an HTMLCollection but when i want to know when it was clicked nothing happens
var elements = document.getElementsByClassName("buy-item");
console.log(elements)
function addFoodToCart() {
console.log("One of buy-item class itemes was clicked")
};
for (var i = 0; i < elements.length; i++) {
elements[i].addEventListener('click', addFoodToCart);
}
The HTML element looks like this
<div class="food-wrap dynamic-food">
<img class="food-item-img" src="/img/foodItem.png">
<p class="food-title">Csípős</p><p class="food-price">190</p>
<div class="buy-item" name="buy-food"></div>
<div class="tooltip">
<span class="tooltiptext">Csípős szósz</span>
</div>
</div>
tl;dr: Add the event listener to the parent div (the one holding all the other elements that you have/will be creating) and use event.target to know which one was clicked.
Not sure if this helps and if this is your case, but you could be benefited by the understanding of Event Bubbling and Event Delegation in Javascript.
In my example below, run the code and click in each paragraph. The console log will show which element you cliked.
function findTheOneClicked(event) {
console.log(event.target)
}
const mainDiv = document.getElementById("parent");
mainDiv.addEventListener('click', findTheOneClicked);
<div id="parent">
<p>"Test 1"</p>
<p>"Test 2"</p>
<p>"Test 3"</p>
</div>
This is really good when you have an element with many other elements on it and you want to do something with them, or when you want to add an event handler to an element that is not available (not created yet) on your page.
Good reading on this topic:
https://javascript.info/bubbling-and-capturing

For Loop is stopping at 1, not counting elements added to DOM

I am trying to make it so each child input added have the same event listener as the parent input.
Snippet (also on Codepen):
var main = document.getElementById("main").getElementsByTagName("a");
var button = document.createElement('input');
// Loop over A tags in #main
for (var i = 0; i < main.length; i++) {
// # of A tags
console.log(main.length);
// Event listener per # of A tags
main[i].addEventListener("click",function(e){
// Clone parentElement #main
var node = e.target.parentElement;
var clone = node.cloneNode(true);
// Append to DOM
document.getElementById('main').appendChild(clone);
});
}
<div id="main">
<div class="input__container">
<label>Input</label>
<input placeholder="Placeholder..." class="input" id="" name="" type="text"/>
<a class="btn">+</a>
</div>
</div>
Instead of trying to duplicate the event handler, use a single delegated event handler attached to #main:
var main = document.getElementById("main");
main.addEventListener("click", function(e) {
// Delegated event handler returning early if wrong target
if (!e.target.matches(".btn")) return;
// Clone parentElement .input__container
var node = e.target.parentElement;
var clone = node.cloneNode(true);
// Append to main
this.appendChild(clone);
});
<div id="main">
<div class="input__container">
<label>Input</label>
<input placeholder="Placeholder..." class="input" id="" name="" type="text" />
<a class="btn">+</a>
</div>
</div>
cloneNode method does not copy event listeners.
Cloning a node copies all of its attributes and their values, including intrinsic (in–line) listeners. It does not copy event listeners added using addEventListener() or those assigned to element properties (e.g. node.onclick = fn).
cloneNode description
You're not adding any a elements to the DOM within your for loop, so it stops when it's done with the ones within #main that are there when it runs (there's only one).
You probably want to use event delegation on main instead: You handle the click on main, but then only do something with it if the click passed through an a.btn; see comments:
// Get the element, not the `a` elements within it
var main = document.getElementById("main")
// Listen for clicks on it
main.addEventListener("click", function(e) {
// If this click passed through an `a.btn` within #main (this``)...
var btn = e.target.closest("a.btn");
if (btn && this.contains(btn)) {
// Clone the btn's parentElement, append to #main
var node = btn.parentElement;
var clone = node.cloneNode(true);
main.appendChild(clone);
}
});
<div id="main">
<div class="input__container">
<label>Input</label>
<!-- Note that `id` and `name` are not allowed to be blank; just leave them off -->
<input placeholder="Placeholder..." class="input" type="text"/>
<a class="btn">+</a>
</div>
</div>
I wouldn't say this is necessarily the best way to achieve this, but trying to stick with your general strategy here:
let cloneSelf = function(e) {
var parent = e.target.parentElement;
var clone = parent.cloneNode(true);
// Event listeners are not cloned with "cloneNode" so we have to do it manually.
clone.getElementsByTagName("a")[0].addEventListener("click", cloneSelf);
document.getElementById('main').appendChild(clone);
}
// Get the first link, and add the event listener
var firstLink = document.getElementById("main").getElementsByTagName("a")[0];
firstLink.addEventListener("click", cloneSelf);
<div id="main">
<div class="input__container">
<label>Input</label>
<input placeholder="Placeholder..." class="input" id="" name="" type="text" />
<a class="btn">+</a>
</div>
</div>

"If not div" onclick event not including children divs

Using vanilla JS, I'm trying to create an off click - as in, if the body is clicked and it is not a certain element that is clicked, close that element.
However, it works when you click the specified element (it doesn't close), but it fires the event when you click any of that element's child nodes. I'd like the if statement to include any child nodes of that parent element, as well as the parent node itself.
HTML:
<ul id="NavSocial-target" class="Nav_social">
<li class="Nav_social_item">Facebook</li>
<li class="Nav_social_item">Twitter</li>
<li class="Nav_social_item">Google</li>
</ul>
<ul class="Nav_options FlexList">
<li class="Nav_options_item FlexList_item" id="NavSocial-trigger">Social</li>
<li class="Nav_options_item FlexList_item">Curr/Lang</li>
</ul>
Javascript:
this.triggerDOM = document.getElementById('NavSocial-trigger');
this.targetDOM = document.getElementById('NavSocial-target');
// If not list item that triggers model && if not model itself
document.getElementsByTagName('body')[0].onclick = (e) => {
if(e.target !== this.triggerDOM && e.target !== this.targetDOM){
this.removeClass();
}
};
I assume this.triggerDOM is the element you want to ignore. You need to see if the click passed through the element, with a loop:
this.triggerDOM = document.getElementById('NavSocial-trigger');
// ...
document.body.addEventListener("click", e => {
var element = e.target;
while (element && element !== document.body) {
if (element === this.triggerDOM) {
// It was a click in `this.triggerDOM` or one of its
// children; ignore it
return;
}
element = element.parentNode;
}
// It wasn't a click anywhere in `this.triggerDOM`
this.removeClass();
}, false);
Side notes on the above:
document.getElementsByTagName("body")[0] is just a long way to write document.body :-)
Using onclick doesn't play nicely with others; use addEventListener (or attachEvent on IE8 and earlier, if you still need to support seriously obsolete browsers).
Arrow functions accepting a single argument don't need () around the argument list (though of course it's fine to have them there if you prefer the style).
Working example:
this.triggerDOM = document.getElementById('NavSocial-trigger');
// ...
document.body.addEventListener("click", e => {
var element = e.target;
while (element && element !== document.body) {
if (element === this.triggerDOM) {
// It was a click in `this.triggerDOM` or one of its
// children; ignore it
return;
}
element = element.parentNode;
}
// It wasn't a click anywhere in `this.triggerDOM`
this.triggerDOM.parentNode.removeChild(this.triggerDOM);
}, false);
#NavSocial-trigger {
border: 1px solid #ddd;
background-color: #dd0;
}
<!-- Note the really deep nesting -->
<div><div><div><div><div><div><div><div><div><div>
Click
<div>
anywhere
<div>
on
<div>
this
<div>
page
<div>
except
<div id="NavSocial-trigger">
here
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div></div></div></div></div></div></div></div></div>

jQuery click event handle both class element and element inside

I have the following HTML:
<div class="eventContainer" id="dennaVecka" style="background-color: #2B4699;">
<p>Denna vecka</p>
</div><!--eventContainer ends-->
<div class="eventList" id="dennaVecka_table">
...
</div>
And the following jQuery:
eventID = null;
$('.eventContainer p, .eventContainer').click(function (e) {
//eventID = $(this).attr('id');
eventID = e.target.id;
$('.eventList#' + eventID + '_table').fadeIn(300);
//alert(e.target.id);
});
I want: When div class eventContainer or the paragraph inside it is clicked, I want to use the ID of eventContainer (id="dennaVecka") to display the div class eventList beneath it. My problem with the current setup is that I don't know how to get the ID of eventContainer if the paragraph is clicked. If I click the paragraph I will get the ID of the paragraph ("undefined").
Is there a way to get the ID of eventContainer in jQuery no matter if I click the div or the paragraph?
Maybe I should setup the HTML in another way?
Use the fact that click event bubbles up to the parent container. In this case you can bind only one event handler to .eventContainer and read this.id to get container id:
$('.eventContainer').click(function (e) {
eventID = this.id;
$('.eventList#' + eventID + '_table').fadeIn(300);
});
So if you click p element inside .eventContainer event will still be handled by above listener on .eventContainer, with this pointing to it.

Categories