WeakMap with event.target - javascript

Edit: turns out nothing is actually wrong with the second snippet (my real code). On one page it works, and on another it doesn't. Yea for underlying errors.
I'm creating a DOM element and giving that DOM element to a WeakMap as a key. Then, with JQuery event delegation/event listener, I'm trying to retrieve the saved key but it's returning undefined:
const item = document.createElement("div"), __data = new WeakMap();
__data.set(item, {hello: "123"})
document.body.appendChild(item)
// later on in event delegation
$("div").on("click", function(event) {
const target = event.target, data = __data.get(target);
console.log(data)
// prints undefined
Anyone know what's wrong or an alternative method to save data for a DOM element that doesn't have an ID?
Edit: I'm kinda annoyed that the example I made works but my own code doesn't... (some bits look redundant. This is modeled after my actual code, so not all the missing pieces are here, just pragmatically) but here's the apparently working code:
const __data = new WeakMap();
function buildingItem() {
const item = document.createElement("div");
item.setAttribute("data-action", "delete");
__data.set(item, {hi: 123});
return item;
}
function build() {
const main = document.getElementById("main")
for (let i = 0; i < 3; i++) {
const container = document.createElement("div"), attached = document.createElement("div");
const build = buildingItem(),
data = __data.get(build);
build.classList.add("classified");
data["hello"] = `Item ${i}`
__data.set(build, data);
build.innerText = `Item ${i}`
attached.append(build);
container.append(attached);
main.append(container);
}
}
build()
$(document).on("click", "div.classified[data-action]", function(event) {
const target = event.currentTarget, data = __data.get(target);
console.log(`CTarget Data: ${data["hello"]}`)
})
<div id="main"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

Two possible issues:
target is the innermost element that was clicked. You probably want this or event.currentTarget instead, which is the element on which you hooked the event handler (which may be an ancestor element to target).
Your jQuery code hooks up the click event on all div elements, not just that one, but you only have that one div in the WeakMap. If you click a different div, you'll naturally get undefined because that other div isn't a key in the map.
Here's an example (I've added a span within the div we have in the map to demonstrate #1, and also added a second div to demonstrate #2):
const item = document.createElement("div"), __data = new WeakMap();
__data.set(item, {hello: "123"});
document.body.appendChild(item);
item.insertAdjacentHTML("beforeend", "<span>Click me, I'll work</span>");
document.body.insertAdjacentHTML("beforeend", "<div>Click me, I won't work (I'm not a key in the map)</div>");
$("div").on("click", function(event) {
const target = event.currentTarget, data = __data.get(target);
console.log("with currentTarget:", data);
// Note that using `event.target` wouldn't hav eworked
console.log("with target:", __data.get(event.target));
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
You've mentioned that in your real code you're using event delegation. currentTarget and this are both fine in that case as well:
// Event delegation
$(document.body).on("click", "div.example", function(event) {
const data1 = __data.get(event.currentTarget);
console.log("using currentTarget:", data1);
const data2 = __data.get(this);
console.log("using this:", data2);
});
// Adding the relevant `div`
const item = document.createElement("div"), __data = new WeakMap();
__data.set(item, {hello: "123"});
item.className = "example";
item.textContent = "Some text to make div clickable";
document.body.appendChild(item);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

Related

Adding a class to a created element

The parameter of my function is a function. It should create an element but I should still be able to add attributes by using the parameter details.
E.g.:
const addElement = (details) => {
const element = document.createElement('div');
}
addElement(function() {
element.id = 'my-div'; // Not working since element is not defined
});
Well, I have tried to store the element in an object to be able to use it outside of that function.
let element = {};
const displayVideo = (type, details) => {
element = document.createElement(type);
element.width = 200;
element.height = 200;
element.classList.add('my-class'); // <--- THE PROBLEM!
if (details) {
details();
}
document.querySelector('#layer').insertBefore(element, document.querySelector('#el'));
};
displayVideo('VIDEO', function () {
element.controls = true;
});
My element can not be created because of element.classList.add('my-class'); and I don't even get an error message. If I remove that line, it works but I would still like to be able to add a class to that object. How can I do this?
Just pass element into the function. Since you're just editing properties on the object, this won't cause reference vs value errors.
const addElement = (details) => {
const element = document.createElement('div');
if (details) details(element);
return element;
}
const ele = addElement(function(element) {
element.id = 'my-div';
});
console.log(ele);
In this case details could be something like classname.
function element(type, classname) {
var element = document.createElement(type);
if (classname !== undefined) {
element.classList.add(classname);
}
return element;
};
element("div","my-class"); //<div class="my-class"></div>
Of course instead of classname you could use an array or an object and loop through in order to set multiple attributes.
Or you could store the return value of your function in a variable and then add all the attributes:
var myelement = element("div");
myelement.classList.add("my-new-class");
myelement //<div class=​"my-new-class">​</div>​

Is it bad practice to add event listeners inline while creating elements in JS?

I'm dynamically creating and rendering div tag using a JS class, like this:
class CreateNote {
constructor(title, body) {
this.title = title;
this.body = body;
this.render = () => {
// Create div 1 & 2 and give them the same class name
const div1 = document.createElement('div');
div1.className = 'notes-inner-container';
const div2 = document.createElement('div');
div2.className = 'notes-prev';
const hr = document.createElement('hr');
hr.className = 'notes__line';
// Nest 'div2' inside 'div1'
div1.appendChild(div2);
div1.appendChild(hr);
/*
Create Paragraph 1 & 2 and give them the same
class name and some text
*/
const p1 = document.createElement('p');
p1.innerText = this.title;
const p2 = document.createElement('p');
p2.className = 'notes-prev__body';
p2.innerText = this.body;
// Nest p 1 & 2 inside 'div2'
div2.appendChild(p1);
div2.appendChild(p2);
// Get the body tag and append everything
const notesDiv = document.querySelector('.notes');
notesDiv.appendChild(div1);
}
}
}
I need to add an event listener to this div. But as it takes some time for this div to actually get rendered, when I try to attach the listener to it, it keeps returning a null value. The solution I found for this issue is if I add the event listener inside my render method, like this:
// Previous chunk of code
const div1 = document.createElement('div');
div1.className = 'notes-inner-container';
div1.addEventListener('click', () => {
// Do something;
}
// Next chunk of code
And then I was wondering, is this a bad practice? If so, which way would you do it? If you need more information please let me know and thanks in advance.
No, this is pretty standard. I would define your event listener separately though and just pass it in:
// at class level
const handleClick = event => { /* handle onClick event */ }
// inside render method
const div = document.createElement('div')
div.className = 'notes-inner-container'
div.addEventListener('click', handleClick)

How to dynamically add function with changing parameters to onclick event?

I'm adding an onlick event dynamically to a series of anchor elements. For each anchor element I need to use different parameter values, in the form of variables. when using the following event listener in a loop:
anchor.addEventListener("click", () => { task(title, author) });
title and author for every anchor element's onclick end up the same (they are all the value of title and author at their last iteration). I'd like them to represent the values of the variables at the particular iteration in the loop. Here's a minimal working code example:
for (obj of objTasks) {
title = obj.title;
author = obj.author;
var anchor = document.createElement("a");
anchor.addEventListener("click", () => { task(title, author) });
}
You're declaring your variables globally - use let because it's block-scoped (var is function scoped):
for (let obj of objTasks) {
let title = obj.title;
let author = obj.author;
let anchor = document.createElement("a");
anchor.addEventListener("click", () => { task(title, author) });
}

Is there any unified way to set both `innerHTML` and `onclick` to one element?

I'm trying to create element using its JSON description.
JavaScript now provides this:
elem.innerHTML = "Text"; // works fine
elem.onclick = "alert(true)"; // doesn't work
elem.setAttribute("innerHTML", "Text"); // doesn't work
elem.setAttribute("onclick", "alert(true)"); // works fine
Unfortunately, I'm not allowed to print
elem.onclick = function() {alert(true)};
Is there any unified way to set both innerHTML and onclick to one element?
Like this:
var props = {"innerHTML":"Text", "onclick":"alert(true)"};
var elem = document.createElement("BUTTON");
for (property in props) elem[property] = props[property];
/* or */
for (property in props) elem.setAttribute(property, props[property]);
/* or maybe something else */
You cold use the Function constructor:
elem.onclick = new Function('', 'return alert(true);');
See working Fiddle
EDIT: I couldn't found a unified way to do this to both events and attributes on an element, but since all events starts with on keyword, you could check if it's an event or attribute on your for loop:
for (property in props) {
if(property.substr(0,2) === 'on') {
//an event
elem.setAttribute(property, props[property]);
}
else {
//an attribute
elem[property] = props[property];
}
}
See working Fiddle

parentNode becomes null after removing previous child element

So, I'm trying to have a button add another dropdown (DD in my code) to the page, and it works just dandy, except when I try to remove (which "works") the remaining node no longer has the parentNode set properly.
Here is my code:
function insertAfter(newNode, elem) {
elem.parentNode.insertBefore(newNode, elem.nextSibling);
}
function addSwitchMenu() {
var lastDD = dropdowns.switches.last();
var newDD = lastDD.cloneNode();
newDD.innerHTML = lastDD.innerHTML;
var oldButton = document.getElementById("add-switch");
var newButton = oldButton.cloneNode();
var newBR = document.createElement("br");
oldButton.value = '-';
oldButton.id = 'remove-switch';
oldButton.onclick = function() {
var index = dropdowns.switches.indexOf(newDD);
dropdowns.switches.splice(index,1);
lastDD.parentNode.removeChild(lastDD);
oldButton.parentNode.removeChild(oldButton);
newBR.parentNode.removeChild(newBR);
updateResults();
}
dropdowns.switches.push(newDD);
console.log(newDD);
console.log(lastDD.parentNode);
insertAfter(newDD, lastDD);
insertAfter(newButton, lastDD);
insertAfter(newBR, lastDD);
}
And basically I call this function, then I call the remove function of the first one, then I call this function once more using the node that was created with the first one. I'm guessing it's something to do with the referencing node being removed, but the new node has a parentNode up until the other node is removed. Why? And how can I fix this?

Categories