How to automatically map a function to a newly added DOM element - javascript

I'm trying to automate calling a function (initBox) on all elements with class box on the page in vanilla javascript. I have made a functional solution except that it only applies to elements that are on the page when it is loaded. If I add an element to the DOM using javascript, the function (initBox) is not called on it.
Of course I can call the function (initBox) manually after adding the element, but I would like to automate it.
I'm looking for something similar to what jQuery does for events.
For example:
$('table').on('click', 'td', function (event) {
doSomething();
});
This event is called even if I add the TD element to the table later via javascript.
Here is my current solution:
function addBox() {
var btn = document.getElementsByTagName('button')[0];
var el = document.createElement('div');
el.classList.add('box');
el.innerText = (document.getElementsByTagName('div').length + 1);
btn.before(el);
}
function initBox(el) {
el.innerText += ' Initialized';
}
document.querySelectorAll('.box').forEach(initBox);
.box {
display: inline-block;
border: 1px solid #1f2227;
padding: 20px;
}
button {
padding: 20px;
}
<div class="box">1</div>
<div class="box">2</div>
<button onclick="addBox()">Add box</button>

The OP's problem can be solved just by the usage of a MutationObserver instance where the OP needs the callback from the list off added nodes just to initialize the very nodes which do match the OP's definition of a box.
function addBox() {
var btn = document.getElementsByTagName('button')[0];
var el = document.createElement('div');
el.classList.add('box');
el.innerText = (document.getElementsByTagName('div').length + 1);
btn.before(el);
}
function initBox(el) {
el.innerText += ' Initialized';
}
document.querySelectorAll('.box').forEach(initBox);
function initializeBoxClassDivOnly(node) {
if (node.nodeType === 1 && node.matches('div.box')) {
initBox(node);
}
}
function handleNodeInsertion(mutationList/*, observer*/) {
for (const mutation of mutationList) {
mutation
.addedNodes
.forEach(initializeBoxClassDivOnly);
}
};
const observer = new MutationObserver(handleNodeInsertion);
observer
.observe(document.body, { childList: true, subtree: true });
.box {
display: inline-block;
border: 1px solid #1f2227;
padding: 20px;
}
button {
padding: 20px;
}
<div class="box">1</div>
<div class="box">2</div>
<button onclick="addBox()">Add box</button>

Look Into Mutation Observers, they are the new way of observing dom objects, it allows you to run a function when an item is added or removed or modified in a particular DOM element, or if you want you can go old school ( meaning you add event listeners).
Mutation Observers ( Recommended Method ) https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver
Old School Evens https://developer.mozilla.org/en-US/docs/Web/API/MutationEvent
if you want me to write the code, let me know in the comments.

Related

wait until insertAdjacentHTML is done then continue code

I dynamically add content to my web page using insertAdjacentHTML like the following example code:
...
for(let i = 0; i < someArray.length; i++)
{
someDiv.insertAdjacentHTML('beforeend', `<div id='post-${i}'>...</div>`);
let insertedObject = document.getElementById(`post-${i}`);
// at this point `insertedObject` is null!
}
...
but content does not get added instantly and insertedObject is null, now I have tried finding fixes and the most common is to put a setTimeout to wait until the new element is added but thats too slow! could there be a better way to get the newly added element?
Following on from mine and Peter's comments here's how I might approach this.
Create a containing element. Let's assume that you want it to catch click events from the HTML you're about to insert (event delegation). Add a listener to it that, for example, calls a function that logs the id of the clicked post.
Then map over the array to produces an array of HTML strings that you then join up, and then use insertAdjacentHTML to add that HTML to the container.
// Create the container and add a listener to it
const container = document.querySelector('.container');
container.addEventListener('click', handleClick);
const arr = [1, 2, 3, 4];
// `map` over the array to create some HTML strings, joining
// them up when the iteration is complete
const html = arr.map(n => {
return `<div class="post" id="post-${n}">${n}</div>`;
}).join('');
// Add the HTML to the container
container.insertAdjacentHTML('beforeend', html);
// When the container catches an event fired from
// a child element, check that it's an element with
// a post class, and then log its id
function handleClick(e) {
if (e.target.matches('.post')) {
console.log(e.target.id);
}
}
.container { width: 50%; }
.post { background-color: lightgray; margin: 0.25em; padding: 0.25em; }
.post:hover { background-color: yellow; cursor: pointer; }
<div class="container">
</div>

After first append javascript is not adding a cloned item for the second time

In my application I am adding cloned elements:
document.getElementById("add").addEventListener("click", function(){
let theblock = document.getElementById("theblock").cloneNode(true);
let newer = theblock;
newer.removeAttribute("id");
document.getElementById("restblocks").append(newer);
Bit if I do cloning outside scope it adds the element to html only once:
let theblock = document.getElementById("theblock").cloneNode(true);
document.getElementById("add").addEventListener("click", function(){
let newer = theblock;
newer.removeAttribute("id");
document.getElementById("restblocks").append(newer);
What could be the reason?
Because when you clone outside you only ever make a single clone, and a Node can only exist in one place in a document. If you want to avoid the query inside the click handler you can query outside, but clone inside.
let theblock = document.getElementById("theblock");
document.getElementById("add").addEventListener("click", function(){
let newer = theblock.cloneNode(true);
...
});
const theblock = document.getElementById('theblock');
const restblocks = document.getElementById('restblocks');
document.getElementById('add').addEventListener('click', function () {
let newer = theblock.cloneNode(true);
newer.removeAttribute('id');
newer.classList.remove('hidden');
restblocks.append(newer);
});
.block { width: 40px; height: 40px; margin: 4px; background-color: pink; }
.hidden { display: none; }
<button type="button" id="add">Add a block</button>
<div id="restblocks"></div>
<div id="theblock" class="block hidden"></div>

Vanilla Javascript: Remove toggle class from X element if I click on Y element with the same toggle class

I'm currently working on a sidebar menu where I toggle the "selected" class on a category, which has the classname "sidebar-category".
With jQuery I can easily achieve my desired goal: after toggling the "selected" class (if I click on another category) the previous category gets the class removed and is then applied to the currently clicked category:
$('.sidebar-category').click(function() {
$(".sidebar-category").not(this).removeClass("selected");
$(this).toggleClass('selected');
});
My problem is that for this project I cannot use jQuery and must use vanilla Javascript.
So far I can achieve the toggling easily, but I'm not sure how I can remove the class when clicking on another category using vanilla Javascript. This is my current code:
var selectCategory = document.getElementsByClassName('sidebar-category');
for (var i = 0, l = selectCategory.length; i < l; i++) {
selectCategory[i].onclick = function() {
this.classList.toggle('selected');
}
}
The jQuery code that removes the selected class is equivalent to a loop. So just write that loop in your event listener.
var selectCategory = document.getElementsByClassName('sidebar-category');
for (var i = 0, l = selectCategory.length; i < l; i++) {
selectCategory[i].onclick = function() {
for (var j = 0; j < l; j++) {
if (selectCategory[j] != this) {
selectCategory[j].classList.remove("selected");
}
}
this.classList.toggle('selected');
}
}
Assuming your target environment supports ES2015 (or you transpile your code to support such an environment), a declarative approach using Array.from, filter and forEach can be achieved with the following code:
function toggleSelectedClass(event) {
Array.from(document.getElementsByClassName('sidebar-category'))
.filter(element => element !== event.target)
.forEach(element => {
element.classList.remove('selected')
element.setAttribute('aria-pressed', false);
});
event.target.classList.toggle('selected');
const pressed = event.target.getAttribute('aria-pressed') === 'true';
event.target.setAttribute('aria-pressed', String(!pressed));
}
.sidebar-category {
padding: 5px;
}
.selected {
background: blue;
color: white;
}
<div onclick="toggleSelectedClass(event)">
<button type="button" class="sidebar-category selected" aria-pressed="true">Click</button>
<button type="button" class="sidebar-category" aria-pressed="false">Click</button>
<button type="button" class="sidebar-category" aria-pressed="false">Click</button>
<button type="button" class="sidebar-category" aria-pressed="false">Click</button>
</div>
Note: getElementsByClassName returns an HTMLCollection, not an array, so Array.from is required to use the array methods filter and forEach.
Note 2: Keep accessibility in mind when designing such a menu. A good reference for this is https://inclusive-components.design/toggle-button/.
This can be achieved with the Events API in JavaScript.
Using the onClick="" property of an HTML element we can construct a toggling system.
Create a function to handle the click action of the user and pass in the element that has been clicked as the parameter. function toggle(element){...}
Inside that element first fire off an event to clear the selected element(s) using the event named clearselected that will iterate through the elements and set the selected property to false. Thus, semantically deselecting the elements.
Change the selected property of the element passed in the onclick handler to true.
Update the user interface (UI) using an event called updateui that changed the selected element to its desired appearance, and all non-selected elements to their desired appearance using a for loop that iterates through all elements and looks at the selected property.
Down below I have a code snippet that uses vanilla JavaScript to create a toggle system on the UI. It has a very basic HTML that uses the same class names and adds very little CSS to make the demo easier to understand. I hope this is the thing you were looking for!
// Set up the HTML elements in JavaScript
var sidebar = document.getElementsByClassName("sidebar")[0];
var sidebarCategories = document.getElementsByClassName("sidebar-category");
// Add an event listener for clearing the selected elements
sidebar.addEventListener("clearselected", function(e) {
for(var i = 0; i < sidebarCategories.length; i++){
sidebarCategories[i].selected = false;
}
}, false);
// Add an event listener updating the UI to reflect changes
sidebar.addEventListener("updateui", function(e) {
for(var i = 0; i < sidebarCategories.length; i++){
var current = sidebarCategories[i];
if(current.selected){
current.textContent = "selected";
}else{
current.textContent = "";
}
}
}, false);
// Write a on click handler to handle the toggle
function toggle(element){
var event = document.createEvent("Event");
event.initEvent("clearselected", true, true);
element.dispatchEvent(event);
element.selected = true;
var event = document.createEvent("Event");
event.initEvent("updateui", true, true);
element.dispatchEvent(event);
}
.sidebar-category {
width: 100px;
height: 100px;
border: 3px solid black;
margin-bottom: 10px;
line-height: 100px;
text-align: center;
}
<div class="sidebar">
<p>Click the boxes to see the toggle in action</p>
<div class="sidebar-category" onclick="toggle(this)"></div>
<div class="sidebar-category" onclick="toggle(this)"></div>
<div class="sidebar-category" onclick="toggle(this)"></div>
</div>

How to select a new added element and edit it?

I have an <a> element:
<a id='addNewElementk' onclick='//Some Js Code' class='continueButton'>Click To Add</a>
When this anchor is clicked , A new element added:
New Added Element
And the first anchor which was clicked , Is removed.
I want to select that new element.
I tried:
window.onload = function(){
var newElem = document.getElementsByClassName('continueButton')[1];
alert(newElem.innerHTML);
}
I'm using ('continueButton')[1] , As there is another input with the same class before that anchor.
But for sure I get Click To Add from the first one , As that's was found when the page is loaded.
So how can I select that new element?
You're attempting to select the element before it exists in the DOM.
You instead need to run that code within the click event handler of the first <a>, like this:
window.onload = function() {
document.querySelector('#addNewElementk').addEventListener('click', function(e) {
e.preventDefault();
var a = document.createElement('a');
a.textContent = 'New Added Element';
a.href = '#';
a.classList.add('continueButton');
a.addEventListener('click', function(e) {
console.log(a.innerHTML);
});
this.parentNode.insertBefore(a, this);
this.remove();
});
}
<a id='addNewElementk' href="#" class='continueButton'>Click To Add</a>
Note the use of addEventListener() over the outdated on* event attributes which should be avoided.
You are attempting to select on an element that doesn't exist in the DOM. Dynamically added elements can be accessed in a couple of ways, above someone has an answer that adds an event listener to the created element which is a solid solution. The other most common way would be to use event delegation (if you are familiar with jQuery that would be $(parentElement).on('action', 'elementWeWantToWatch', function)) in Vanilla js the pattern is effectively the same, find or make a container element for your dynamic html, then add a listener to that container. Inside the listener you will want to ensure the target matches whatever your dynamic selection would be and execute when you find a match.
In this Example
The event listener is initiated on page load to watch the container element. The listener watches for clicks on elements with the continueButton class and when it finds one it removes the clicked element and adds a new element (the counter is to demonstrate that new content is being displayed :D)
(function() {
let i = 1;
const makeButton = () => {
const a = document.createElement('a');
a.classList.add('continueButton');
a.href = '#';
a.textContent = `Button ${i}`
i++;
return a;
}
const init = () => {
const container = document.querySelector('.test');
container.addEventListener('click', e => {
if (e.target.classList.contains('continueButton')) {
let button = makeButton();
container.appendChild(button);
container.removeChild(e.target);
return;
}
});
};
if (document.readyState == 'loading') {
window.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})()
.test {
width: 100%;
display: block;
text-align: center;
}
.continueButton {
display: block;
color: white;
background-color: green;
border-radius 2px;
padding: 15px 30px;
line-height: 2;
margin: 50px auto;
width: 200px;
text-decoration: none
}
<section class="test">
<a id='addNewElementk' class='continueButton'>Click To Add</a>
</section>

Append new Element only once on click and save it to local storage html javascript

Here is JS Fiddle!
I am trying to append new Elements to the div, this is working.
My problem is that I want to append the new element only once on button click, and save it to localStorage so that I would not loose the state on refresh or any other action.
div {
text-align: center;
}
#Neighborhood {
color: brown;
}
#NewElement {
color: green;
}
<div id="Neighborhood">
<div id="Neighbor1">Neighbor 1</div>
<div id="Neighbor2">Neighbor 2</div>
<div id="Neighbor3">Neighbor 3</div>
</div>
<input type="button"onclick="add_prev();" value="ACTION">​
/* Adds Element BEFORE NeighborElement */
Element.prototype.appendBefore = function (element) {
element.parentNode.insertBefore(this, element);
}, false;
/* Adds Element AFTER NeighborElement */
Element.prototype.appendAfter = function (element) {
element.parentNode.insertBefore(this, element.nextSibling);
}, false;
/* Typical Creation and Setup A New Orphaned Element Object */
add_prev = function () {
var NewElement = document.createElement('div');
NewElement.innerHTML = 'New Element';
NewElement.id = 'NewElement';
NewElement.appendBefore(document.getElementById('Neighbor2'));
}
I am thankful for every tip or solution! Cheers!
Pass this to the onclick function. That way, after you do your things, just remove the listener to click or disable the button.
Also, when the page loads, load the information from the storage, if it's true, directly call the function and then disable the button.
Maybe it isn't exaclty what you need, but it can help a lot, you can follow this logic to get there. The code below is just an example.
OBS: it won't work well here because localStorage is not allowed in StackOverflow.
In this fiddle you can try it better: https://jsfiddle.net/so5u1c4z/
On the fiddle above, create the element, then save and reload the page. the element will be there once the page loads.
$(document).ready(function(){
add_prev = function (elem) {
var NewElement = document.createElement('div');
NewElement.innerHTML = 'New Element';
NewElement.id = 'NewElement';
document.getElementById('Neighbor2').append(NewElement);
localStorage.setItem('elementCreated', true);
if (elem){
$(elem).attr('disabled', true);
}
}
var isCreated = localStorage.getItem('elementCreated');
if (isCreated){
add_prev();
$("#btnAdd").attr('disabled', true);
}
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input type="button" onclick="add_prev(this);" value="ACTION" id="btnAdd">
<div id="Neighbor2"></div>

Categories

Resources