Why is ParentNode is null on dynamically create element added to DOM - javascript

I have created a custom datepicker that opens when a date input is focused on. The content inside that datepicker is created dynamically based on the current date. I want to hide it if someone clicks outside the div, but when I try to determine if the click is inside or outside by using .closest(".date-picker") I get null. Upon further inspection my date picker doesn't return any ancestors at all, not even parentNode. Any idea why this would be?
Here is my click event listener to hide the date picker and where I am getting the null for parentNode.
window.addEventListener("DOMContentLoaded", function () {
_initDatePicker(); // <--- dynamically adding elements here
document.addEventListener("click",
function (event) {
var classes = event.target.classList; // <---- this works just fine
var parent = event.target.parentNode; // <---- returns null
// If user clicks outside the datepicker window, then close
if (!event.target.closest(".date-picker")) {
hideDatePicker();
}
},
false
);
}, false);
Here is my code where I dynamically create the content inside the div and add it to the dom
title_element.textContent = "Select Year";
cal_elements.innerHTML = ''; // Clear previous content
if (!cal_elements.classList.contains('year_list'))
cal_elements.classList.add('year_list');
for (let i = 0; i < amount_years; i++) {
let create_year = year - i;
const element = document.createElement('div'); // Create the new div
element.classList.add('cal_element');
if (create_year >= min_year) {
element.classList.add('text-light');
element.classList.add('pointer');
element.textContent = create_year;
element.addEventListener('click', function (e) {
selectedYear = this.innerText;
selected_date_element.textContent = selectedYear;
if (date_picker_element.classList.contains('years')) {
date_picker_element.classList.remove('years');
date_picker_element.classList.add('months');
}
populateDates();
});
}
cal_elements.appendChild(element); // Add div to the DOM
}
When clicking on the datepicker, I can get the ClassList of the element that I click on, but I can't get the ParentNode even though it has been added to the DOM

Related

Create multiple elements and delete a single one JavaScript

I'm working on a JavaScript project where a user can click a button to create a text element. However, I also want a feature where I can click a different button and the element that was created most recently will be removed, so In other words, I want to be able to click a button to create an element and click a different button to undo that action.
The problem I was having was that I created the element, then I would remove the element using:
element.parentNode.removeChild(element); , but it would clear all of the elements that were created under the same variable.
var elem = document.createElement("div");
elem.innerText = "Text";
document.body.appendChild(elem);
This code allows an element to be created with a button click. All elemente that would be created are under the "elem" variable. so when I remove the element "elem", all element are cleared.
Is there a simple way to remove on element at a time that were all created procedurally?
Thanks for any help
When you create the elements, give the a class. When you want to remove an element, just get the last element by the className and remove it.
The below snippet demonstrates it -
for(let i = 0; i<5; i++){
var elem = document.createElement("div");
elem.innerText = "Text " + i;
elem.className = "added";
document.body.appendChild(elem);
}
setTimeout(function(){
var allDivs = document.getElementsByClassName("added");
var lastDiv = allDivs.length-1;
document.body.removeChild(allDivs[lastDiv]);
}, 3000);
I would probably use querySelectors to grab the last element:
// optional
// this is not needed it's just a random string added as
// content so we can see that the last one is removed
function uid() {
return Math.random().toString(36).slice(2);
}
document.querySelector('#add')
.addEventListener('click', (e) => {
const elem = document.createElement('div');
elem.textContent = `Text #${uid()}`;
document.querySelector('#container').appendChild(elem);
// optional - if there are elements to remove,
// enable the undo button
document.querySelector('#undo').removeAttribute('disabled');
});
document.querySelector('#undo')
.addEventListener('click', (e) => {
// grab the last child and remove
document.querySelector('#container > div:last-child').remove();
// optional - if there are no more divs we disable the undo button
if (document.querySelectorAll('#container > div').length === 0) {
document.querySelector('#undo').setAttribute('disabled', '');
}
});
<button id="add">Add</button>
<button id="undo" disabled>Undo</button>
<div id="container"></div>

Loop array until matches the value of the item clicked

I'm trying to loop through an array until it matches the value of the object that is clicked.
When the object is created the text input box shares it's value with the object and the array. I would like to be able to loop through the array until there is a match, then find the index, after that pass the index value to a variable to be used. From there remove the object that is clicked from the webpage and the array.
Additional details are that there is an input box with a button. The user enters a line of information into the input box and selects a button to appendChild it to the list. The object created is a div with the input value as the paragraph with a span element with an X which is supposed to remove the object when clicked.
Here is the HTML Code being used
<div id="outerDiv">
<div id="taskList">
</div>
</div>
Here is the code to create the object.
var magicArray = [];
function makeOutline() {
var textValue = document.getElementById("inputBox").value;
if (textValue == "" || textValue == null){
alert("Please enter a item you want to add to the to-do list");
} else {
var inputField = document.getElementById("taskList");
var inputText = document.createTextNode(textValue);
var mainHeading = document.createElement("p");
mainHeading.setAttribute("class", "outlineBorder");
var spanText = document.createTextNode("x");
var spanBox = document.createElement("span");
spanBox.setAttribute("class", "close");
spanBox.setAttribute("onclick", "removeMe()");
var outlineList = document.createElement("div");
outlineList.setAttribute("value", textValue);
spanBox.appendChild(spanText);
mainHeading.appendChild(inputText);
mainHeading.appendChild(spanBox);
outlineList.appendChild(mainHeading);
inputField.appendChild(outlineList);
magicArray[magicArray.length] = textValue;
document.getElementById("inputBox").value = "";
}
}
Here is the code to remove the item.
I am able to have it set to a static number and work every time; however,
struggling to find a dynamic solution since there can be multiple objects.
function removeMe() {
var removeList = document.getElementById("taskList");
removeList.removeChild(removeList.childNodes[1];
}
Here is a screenshot of the family tree structure
First, you can use this to get the element. docs:
When the event handler is invoked, the this keyword inside the handler is set to the DOM element on which the handler is registered.
function removeMe()
{
// this refers to the item that invoked removeMe()
var removeList = document.getElementById("taskList");
removeList.removeChild(this.parentNode.parentNode);
}
Also, this is how you properly add event listeners
spanBox.addEventListener("click", removeMe);
Here is a working jsfiddle for you

Dropping image to a div removes it from the previous div

I have made this
https://jsfiddle.net/a4376mr8/
When I drag and drop the image div to a new div, why is it not there in the previous div?
const boxes = document.querySelectorAll('.box');
const imageBox = document.querySelector('#draggableItem');
for (const box of boxes) {
box.addEventListener('dragover', function (e) {
e.preventDefault();
this.className += ' onhover';
})
box.addEventListener('dragleave', function () {
this.className = 'box';
})
box.addEventListener('drop', function (e) {
this.className = 'box';
this.append(imageBox);
})
}
If I understood your needs, that the image be repeated on drag and drop, you need to clone your div tag dom object. Since js just sees the reference to it, when you simply append it, this causes it to just move from place to place instead of duplicating.
So instead of just appending, clone the node as follows (line 15 of your fiddle's js).
this.append(imageBox.cloneNode(true));
See here: https://developer.mozilla.org/en-US/docs/Web/API/Node/cloneNode
Updated fiddle: https://jsfiddle.net/a4376mr8/1/

How to add a function on an element that was made after DOM creation?

I have a function that creates an html element with an unique ID.
And after that I want that when I click this element I could call a new function.
Quick example:
1) I click a button "Create element";
2) An element is created with id of "New_Element";
3) I click the "New_Element";
4) I get a function that was already preset to this element.
My current code for creating an element.
var pageRows = document.getElementsByClassName('pageRows');
var pageRowID = "section";
var el = document.createElement('section');
el.setAttribute('id', pageRowID + pageRows.length);
var row = document.getElementById('allNewRows');
row.parentNode.appendChild(el);
el.innerText = "New " + pageRows.length + " ROW!";
Now that the Element of id "pageRowId0" is created I want to have a function that works when I click this element.
Best wishes.
Thanks for helping.
You can do element.onclick= function(){}
var pageRows = document.getElementsByClassName('pageRows');
var pageRowID = "section";
var el = document.createElement('section');
el.setAttribute('id', pageRowID + pageRows.length);
el.onclick = function(){
/*write your fn here*/
};
var row = document.getElementById('allNewRows');
row.parentNode.appendChild(el);
el.innerText = "New " + pageRows.length + " ROW!";
You can use event delegation:
var row = document.getElementById('allNewRows');
row.parentNode.onclick = function(e) {
if (e.target.nodeName.toLowerCase() == 'select') {
//click on target select element
}
};
The snippet below has two parts. The first piece of code allows you to add a bunch of elements with different texts to the document.
The second parts shows the text of the element you clicked.
You will notice that the click event handler is just assigned to the parent element in which the new elements are added. No explicit click event handlers are bound to the new element.
I like to use addEventListener, because I think it's better to add a listener for a specific goal than to override any other event listeners by bluntly setting 'onclick', but that's a matter of opinion.
// Only run this code when the DOM is loaded, so we can be sure the proper elements exist.
window.addEventListener('DOMContentLoaded', function(){
// The code to add an element when the add button was clicked.
document.getElementById('add').addEventListener('click', function() {
var element = document.createElement('div');
element.innerText = document.getElementById('text').value;
element.className = 'clickableElement';
document.getElementById('elements').appendChild(element);
});
// Click event handler for the 'elements' div and everything in it.
document.getElementById('elements').addEventListener('click', function(event) {
var target = event.target; // The element that was clicked
// Check if the clicked element is indeed the right one.
if (target.classList.contains('clickableElement')) {
alert(target.innerText);
}
});
})
<input id="text" value="test"><button id="add">add</button>
<div id="elements"></div>

Javascript element handling

I've made a table with many griditems which come from a database. I'm using pure Javscript and AJAX for editing and deleting those griditems.
Code:
var main_table=document.getElementById('main-table'),//get the element handler for the table
griditems=main_table.querySelectorAll('.griditem'),//get an array with all the griditems in the main-table
griditems_count=griditems.length;
var griditem_array = [];
for(var i=0;i<griditems_count;i++){
griditem_array[i] = new create_griditem(griditems, i); //here i create a array for every single element and pass the handler for the griditems and the position on the table to the object
function create_griditem(griditems, griditems_count){
var pos=griditems_count;
var id=griditems[pos].getAttribute('data-id'); //the griditem div's also have the id from the Database
var griditems_buttons=griditems[pos].querySelectorAll('.griditem-buttons');
var del_button = griditems_buttons[0].querySelectorAll('.delete')[0];
del_button.addEventListener('click', function() {
alert('Delete '+id);
}, false);
//there are more things called for creating a delete and edit button for every griditem
}
I'm getting every griditem from the table and create an object array for every single griditem for creating the appropriate buttons for the corresponding item.
my problem is, as you can see on line 13(griditems[pos]), I'm getting the element handle(or DOM handle) through the position in the query selector from the main table.
BUT this grid is a sortable grid, so I can drag and drop the items and the position for the griditem with the corresponding id is somewhere completely different.
So how do I get DOM handler through my id, which I'm passing to the object?
Or can someone give my advice how to code the whole situation in a better way?
Edit:
I've created a fiddle the see the whole thing running: http://jsfiddle.net/MCRte/
As you can see,in the last line main_table.removeChild(griditems[pos]);, I'm getting the DOM handle through the position in the query selector(.griditem) for the main-table. But what if the sorting of those changed before i deleted one... any other would be deleted.
How to i get the DOM handle through the id?
No need to use an auto executing anonymous function at all. Just use the browser event API.
The event handler function receives a single argument called event which is the click event object. The event.target property is the element that was clicked by the user, which in your case would be the button that has the class name ".delete". You can climb the document tree from that element until you reach an element that has the ".griditem" class name, and grab the data-id attribute in order to know which element to delete.
var gridItems = document.querySelectorAll('#main-table .griditem'),
i = 0,
length = gridItems.length;
for(i; i < length; i++) {
gridItems[i]
.querySelector(".griditem-buttons .delete")
.addEventListener("click", deleteGridItem);
}
function deleteGridItem(event) {
event.preventDefault();
var gridItem = event.target;
// Climb the document tree from the .delete button to the .griditem
while (gridItem) {
if (gridItem.classList.contains("griditem")) {
break;
}
else {
gridItem = gridItem.parentNode;
}
}
if (gridItem) {
alert("Delete item: " + gridItem.getAttribute("data-id"));
}
else {
throw new Error("Failed to find griditem");
}
}
I'm assuming at some point the table row will be removed from the document. You must detach event handlers to avoid memory leaks. I've found the best way to do this is by utilizing Event Delegation.
JsFiddle: http://jsfiddle.net/Yj2eg/
var table = document.getElementById("main-table");
table.addEventListener("click", function(event) {
if (event.target.nodeName === "BUTTON") {
event.preventDefault();
console.log("BUTTON");
if (event.target.classList.contains("delete")) {
deleteItem(event.target);
}
else if (event.target.classList.contains("edit")) {
editItem(event.target);
}
}
});
function deleteItem(button) {
var gridItem = getGridItem(button);
if (confirm("Delete item: " + gridItem.getAttribute("data-id") + "?")) {
gridItem.parentNode.removeChild(gridItem);
}
}
function editItem(button) {
var gridItem = getGridItem(button);
alert("Edit item: " + gridItem.getAttribute("data-id"));
}
function getGridItem(button) {
var gridItem = button;
while (gridItem) {
if (gridItem.classList.contains("griditem")) {
break;
}
else {
gridItem = gridItem.parentNode;
}
}
if (!gridItem) {
throw new Error("Could not find griditem");
}
return gridItem;
}
I'm not completely certain what your actual problem is, but if you're trying to set up a click listener for a button within one of your griditems that performs an action based on the griditem's data-id attribute, something like this would do the job:
var main_table = document.getElementById('main-table'); //get the element handler for the table
var griditems = main_table.querySelectorAll('.griditem'); //get an array with all the griditems in the main-table
for (var i = 0; i < griditems.length; i++) {
var griditem = griditems[i];
var id = griditem.getAttribute('data-id');
var del_button = griditem.querySelector('.delete');
del_button.addEventListener('click', function(id) {
alert('Delete ' + id);
}(id), false);
}

Categories