How to append only node's child nodes? - javascript

Assuming the following node(s) (which is/are actually not generated like this):
let outer = document.createElement('div');
outer.innerHTML = `
<div>foo</div>
<div>bar</div>
.
.
.
<div>whatever</div>
`;
Now I can append outer to another node:
let body = document.querySelector('body');
body.append(outer);
But how to append only the inner of outer without loosing event listeners etc.?

Call append() with each child of outer as a separate argument by using the ... syntax to spread an iterable.
body.append(...outer.children);

To answer your "without losing event handlers" - it turns out your are not when you append
Without delegation
let outer = document.createElement('div');
outer.classList.add("outer")
outer.innerHTML = `
<div class="foo" onclick="console.log('Foo clicked')">foo</div>
<div class="bar">bar</div>
<div>whatever</div>
`;
let body = document.querySelector('body');
body.append(outer);
body.append(...outer.children);
.outer { height:100px; width:100px; border: 1px solid red;}
With delegation from body
let outer = document.createElement('div');
outer.classList.add("outer")
outer.innerHTML = `
<div class="foo">foo</div>
<div class="bar">bar</div>
<div>whatever</div>
`;
let body = document.querySelector('body');
body.append(outer);
body.append(...outer.children);
body.addEventListener("click", e => {
const tgt = e.target;
if (tgt.matches(".foo")) console.log("Foo clicked")
else if (tgt.matches(".bar")) console.log("Bar clicked")
})
.outer { height:100px; width:100px; border: 1px solid red;}

Related

How to replace nested span to div element in JQuery?

I'm trying to check whether the given htmlData has nested(parent,child not siblings) span elements with attribute name data-fact or not.
if it does then replace it with span to div with class='inline-span' pass all the attributes with it.
else just return the htmlData
var htmlData = `<p style="font: 10pt Times New Roman, Times, Serif; margin: 0pt 0;" xvid="f5ea22ec52553bc61525766b631e126f">
<span xvid="2b80c95cd4b851345ba4c3fe6937d30b" conceptid="619959bc062c677faebd7a6f" xbrlid="rr:ProspectusDate" class="manual-map" data-fact="619959c0062c677faebd7b55">
<span xvid="ca5635a4e4de332d7dc3036a68e57009" class="wrapped manual-map" data-fact="619959c0062c677faebd7b57">November 1, 2021</span>
</span>
</p>
`
replaceTags(htmlData)
function replaceTags (htmlData) {
var $elm = $(htmlData).find("span[data-fact]");
var $nestedElm = $elm.children().length > 1;
if($nestedElm){
htmlData = htmlData.replace(/<span/g, '<div class="inline-span" ');
htmlData = htmlData.replace(/<\/span>/g, '<\/div>');
}else{
return htmlData;
}
},
The output htmlData i want is something like this
<p style="font: 10pt Times New Roman, Times, Serif; margin: 0pt 0;" xvid="f5ea22ec52553bc61525766b631e126f">
<div class='inline-span' xvid="2b80c95cd4b851345ba4c3fe6937d30b" conceptid="619959bc062c677faebd7a6f" xbrlid="rr:ProspectusDate" class="manual-map" data-fact="619959c0062c677faebd7b55">
<div class='inline-span' xvid="ca5635a4e4de332d7dc3036a68e57009" class="wrapped manual-map" data-fact="619959c0062c677faebd7b57">November 1, 2021</div>
</div>
</p>
Here i'm not able to find is the span element is nested or not and then the conversion of how can i pass the class='inline-span' with all the previous attributes to the div.
PS: answer i want is in JQuery
It is typically a bad idea to do string replacement to change HTML. You should instead use the tools of jquery to manipulate the DOM. Which is safer and less error prone.
const replaceTags = ($tagToReplace) => {
// create a copy of the htmlData
const $cloned = $tagToReplace.clone();
// While there are still more span's in the p
while ($cloned.find('span[data-fact]').length > 0) {
// get the next span to replace with a div
const $span = $($cloned.find('span[data-fact]')[0]);
// create the new div
const $newDiv = $('<div>');
// copy the span's html into the div
$newDiv.html($span.html());
// For each attribute in the span ...
$.each($span[0].attributes, (_ , attr) => {
// ... set the new div to have the span's attribute.
$newDiv.attr(attr.name, attr.value);
});
// new div needs 'inline-span' property.
$newDiv.addClass('inline-span');
// finally replace the span with the new div
$span.replaceWith($newDiv);
}
return $cloned;
}
// select tag to replace
const $tagToReplace = $('p');
// get the new cloned tag
const $newHtmlData = replaceTags($tagToReplace);
// add the cloned to the body
$('body').append($newHtmlData);
// print that new elements html
console.log($newHtmlData[0].outerHTML);
p {
padding: 8px;
border: 1px dashed green;
}
span[data-fact] {
border: 1px solid red;
padding: 3px;
}
div[data-fact] {
border: 1px solid blue;
padding: 3px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<p style="font: 10pt Times New Roman, Times, Serif; margin: 0pt 0;" xvid="f5ea22ec52553bc61525766b631e126f">
<span xvid="2b80c95cd4b851345ba4c3fe6937d30b" conceptid="619959bc062c677faebd7a6f" xbrlid="rr:ProspectusDate" class="manual-map" data-fact="619959c0062c677faebd7b55">
<span xvid="ca5635a4e4de332d7dc3036a68e57009" class="wrapped manual-map" data-fact="619959c0062c677faebd7b57">November 1, 2021</span>
</span>
</p>
NOTE: it is invalid HTML to have div tag inside p so one should probably replace the p tag too.
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script> var htmlData = `<p style="font: 10pt Times New Roman, Times, Serif; margin: 0pt 0;" xvid="f5ea22ec52553bc61525766b631e126f">
<span xvid="2b80c95cd4b851345ba4c3fe6937d30b" conceptid="619959bc062c677faebd7a6f" xbrlid="rr:ProspectusDate" class="manual-map" data-fact="619959c0062c677faebd7b55">
<span xvid="ca5635a4e4de332d7dc3036a68e57009" class="wrapped manual-map" data-fact="619959c0062c677faebd7b57">November 1, 2021</span>
</span>
</p>
`
console.log(replaceTags(htmlData, "span span[data-fact]","div"));
//a very handy function from Matt Basta to rplace tag names cannot be done on the fly without such functions
function replaceElement(source, newType) {
// Create the document fragment
const frag = document.createDocumentFragment();
// Fill it with what's in the source element
while (source.firstChild) {
frag.appendChild(source.firstChild);
}
// Create the new element
const newElem = document.createElement(newType);
// Empty the document fragment into it
newElem.appendChild(frag);
// Replace the source element with the new element on the page
source.parentNode.replaceChild(newElem, source);
}
//we now use our function as warper on above function.
function replaceTags (htmlData,whatToChange,withWhat) {
var fragment = document.createElement('just');
fragment.innerHTML=htmlData;
var found = fragment.querySelector(whatToChange);
if(found){
replaceElement(fragment.querySelector(whatToChange), withWhat);}
return fragment.innerHTML;
}
</script>
Getting as to what you want here is more logical solution that mixes bunch of search logics to do the job. Not perfect but its close
I did some changes related to Find in HTML element and in replace jquery code here is a working demo hope it will be helpful for you.
you can direcatly replace all html with like
htmlData = htmlData.replace($factElem[0].outerHTML, 'div html');
using $factElem[0].outerHTML you can find element containing [data-fact] html.
yes you can check only using data-fact and replace it with div there is no span needed
I updated Code Please check now.
<!DOCTYPE html>
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script>
$(document).ready(function () {
$("button").click(function () {
var htmlData = '<p style="font: 10pt Times New Roman, Times, Serif; margin: 0pt 0;" xvid="f5ea22ec52553bc61525766b631e126f"><span xvid="2b80c95cd4b851345ba4c3fe6937d30b" conceptid="619959bc062c677faebd7a6f" xbrlid="rr:ProspectusDate" class="manual-map" data-fact="619959c0062c677faebd7b55"><span xvid="ca5635a4e4de332d7dc3036a68e57009" class="wrapped manual-map" data-fact="619959c0062c677faebd7b57">November 1, 2021</span></span></p>'
replaceTags(htmlData);
});
});
function replaceTags(htmlData) {
var $factElem = $(htmlData).find('[data-fact]');
if ($factElem) {
htmlData = htmlData.replace($factElem[0].outerHTML, '<div class="inline-span" xvid="2b80c95cd4b851345ba4c3fe6937d30b" conceptid="619959bc062c677faebd7a6f" xbrlid="rr:ProspectusDate" class="manual-map" data-fact="619959c0062c677faebd7b55"><div class="inline-span" xvid="ca5635a4e4de332d7dc3036a68e57009" class="wrapped manual-map" data-fact="619959c0062c677faebd7b57">November 1, 2021</div></div>');
$("#append").empty().append(htmlData);
alert(htmlData);
} else {
$("#append").empty().append(htmlData);
}
}
</script>
</head>
<body>
<div id="append"></div>
<button>Click me to Replace!!</button>
</body>
</html>

How can i simplify this? (noob here)

i got this very long block and i think that it can be simplify but i dont know how to do it
const one = document.getElementById("one");
const two = document.getElementById("two");
const three = document.getElementById("three");
const four = document.getElementById("four");
one.onclick = () => {
one.innerHTML = "";
};
two.onclick = () => {
two.innerHTML = "";
};
three.onclick = () => {
three.innerHTML = "";
};
four.onclick = () => {
four.innerHTML = "";
};
I agree with Alex, using a class on the elements you wish to run the logic on makes more sense. For example:
const elements = document.querySelectorAll('.js-clear-on-click');
elements.forEach(el => el.addEventListener('click', event => {
el.textContent = '';
}));
Now you can reuse this functionality by just adding the class 'js-clear-on-click' on the DOM-element, so there will be no need to go back to your js-code and update it with 'five' for example.
Edit: If you wish to clear the element of all inner HTML, replace .textContent = ''; with .innerHTML = '';
You can create an array of id and use forEach on that and add event listeners dynamically
['one', 'two', 'three', 'four'].forEach(x => {
const element = document.getElementById(x);
element.onclick = function(){
element.innerHTML = '';
}
})
Or you can put 1 class for all elements, and use document.getElementsByClassName().
let eles = document.getElementsByClassName('yourClass');
for (let i = 0; i < eles.length; i++) {
eles[i].onclick = function () {
eles[i].innerHTML = '';
}
}
May be this way ?
['one','two','three','four']
.forEach(el=>document.getElementById(el)
.onclick=e=>e.target.textContent='');
proof:
['one','two','three','four']
.forEach(el=>document.getElementById(el)
.onclick=e=>e.target.textContent='');
div {
display: block;
width: 3em;
height:1.3em;
border: 1px solid grey;
margin: .7em 1em;
padding: .3em;
}
<div id="one" contenteditable >div 1</div>
<div id="two" contenteditable >div 2</div>
<div id="three" contenteditable >div 3</div>
<div id="four" contenteditable >div 4</div>
or if All elements have the same class (named "clearOnClick" here on snippet)
document.querySelectorAll('.clearOnClick')
.forEach(el=>el.onclick=e=>e.target.textContent='');
proof:
document.querySelectorAll('.clearOnClick')
.forEach(el=>el.onclick=e=>e.target.textContent='');
.clearOnClick {
display: block;
width: 3em;
height:1.3em;
border: 1px solid grey;
margin: .7em 1em;
padding: .3em;
}
<div id="one" contenteditable class="clearOnClick">div 1</div>
<div id="two" contenteditable class="clearOnClick">div 2</div>
<div id="three" contenteditable class="clearOnClick">div 3</div>
<div id="four" contenteditable class="clearOnClick">div 4</div>

Making multilayer accordions with pure JS and using nextElementSibling

New to Javascript. I recently posted a question about creating multiple multilayer accordions. I got some great feedback, but someone mentioned that if my HTML was set up correctly, I could achieve the same goal by using nextElementSibling and thus have much cleaner JS.
I figured out how to do this using only queryselect. See the below example:
HTML:
<div class="mainAccordion">
<h2>dropdown one</h2>
<h3>dropdown two</h3>
<p>content content content content</p>
</div>
CSS:
.mainAccordion {
background-color:lightblue;
width:200px;
margin:auto;
padding:3%;
}
.mainAccordion :nth-child(1){
background-color: blue;
padding:3%;
cursor:pointer;
color:white;
}
.mainAccordion :nth-child(2){
background-color:yellow;
cursor:pointer;
max-height:0;
overflow:hidden;
}
.mainAccordion :nth-child(3){
font-weight:bold;
max-height:0;
overflow:hidden;
}
And the JS:
var mainAccordion = document.querySelector(".mainAccordion").addEventListener("click", function(e) {
if (e.target.nextElementSibling.style.maxHeight) {
e.target.nextElementSibling.style.maxHeight = null;
} else {
e.target.nextElementSibling.style.maxHeight = e.target.nextElementSibling.scrollHeight + "px";
}
});
This works as intended. However, when I introduce multiple multilayer accordions and switch to "querySelectorAll", it stops working. Also depending on the browser, I sometimes get an error message saying my "addEventListener" is not a function.
See below:
HTML:
<div class="mainAccordion">
<h2>dropdown one</h2>
<h3>dropdown two</h3>
<p>content content content content</p>
</div>
<div class="mainAccordion">
<h2>dropdown one</h2>
<h3>dropdown two</h3>
<p>content content content content</p>
</div>
CSS:
body {
display:flex;
width: 900px;
margin:auto;
}
.mainAccordion {
background-color:lightblue;
width:200px;
margin:auto;
padding:3%;
}
.mainAccordion :nth-child(1){
background-color: blue;
padding:3%;
cursor:pointer;
color:white;
}
.mainAccordion :nth-child(2){
background-color:yellow;
cursor:pointer;
max-height:0;
overflow:hidden;
}
.mainAccordion :nth-child(3){
font-weight:bold;
max-height:0;
overflow:hidden;
}
and JS:
var mainAccordion = document.querySelectorAll(".mainAccordion").addEventListener("click", function(e) {
if (e.target.nextElementSibling.style.maxHeight) {
e.target.nextElementSibling.style.maxHeight = null;
} else {
e.target.nextElementSibling.style.maxHeight = e.target.nextElementSibling.scrollHeight + "px";
}
});
I've tried changing "querySelectorAll(".mainAccordion") to getElementsByClassName("mainAccordion") but also doesn't work.
Is forEach somehow involved?
Note: I know you can also achieve the same goal by toggling a class that has the "max-height:0;overflow:hidden". However, this was how I was initially taught to do accordions.
This is for my own practice.
I appreciate the help.
Try this:
let accordionElements = document.querySelectorAll(".mainAccordion");
accordionElements.forEach(element => {
element.addEventListener("click", function(e) {
if (e.target.nextElementSibling.style.maxHeight) {
e.target.nextElementSibling.style.maxHeight = null;
} else {
e.target.nextElementSibling.style.maxHeight = e.target.nextElementSibling.scrollHeight + "px";
}
})
});
It's because with querySelector() an HTML Element is return. With querySelectorAll() it's a NodeList. In your sample code you try to attach an event to a node list which is not possible.
You need to loop inside and then attaching the event to each HTML Element inside.
i think that the problem is that the querySelector() returns the first Element within the document that matches the specified selector. then the event listener will be applied to the first element found.
the querySelectorAll() returns a list. you could use it with forEach like this
var mainAccordion = document.querySelectorAll(".mainAccordion");
console.log(mainAccordion);
mainAccordion.forEach(accordion => {
accordion.addEventListener("click", function(e) {
if (e.target.nextElementSibling.style.maxHeight) {
e.target.nextElementSibling.style.maxHeight = null;
} else {
e.target.nextElementSibling.style.maxHeight =
e.target.nextElementSibling.scrollHeight + "px";
}
});
});

How can I use method on object which name I have as a string?

I'm coming back with another problem.
I have such code:
HTML
<button>first</button>
<button>second</button>
<div class="first"></div>
<div class="second"></div>
CSS
div{
margin-top: 10px;
width: 100px;
height: 100px;
border:1px solid black;
background-color: #ddd;
}
JS
const btns = document.querySelectorAll("button");
const first = document.querySelector(".first");
const second = document.querySelector(".second");
btns.forEach(btn => btn.addEventListener("click", function(){
this.classList.toggle("pressed");
let selection = this.textContent;
// selection.style.transform = "translate(100px)";
}))
https://codepen.io/ptr11dev/pen/oREymM
I'd like to create one function that'll be responsible for moving respective div to the right side by 100px - I stuck with such problem. Under "selection" I have respective name of div (stored under the same name), but simple code like
selection.style.transform = "translate(100px);"
doesn't work. I know that workaround like creating two functions and using
first.style.transform = "translate(100px);"
and
second.style.transform = "translate(100px);"
would work, but in my main code it's a bit more complicated.
I'll really appreciate any input from your side. Thanks
P.S. I'd like to use Vanilla JS.
You can find them by the class name, assuming that the button text and their class are the same.
const btns = document.querySelectorAll("button");
const first = document.querySelector(".first");
const second = document.querySelector(".second");
btns.forEach(btn => btn.addEventListener("click", function(){
this.classList.toggle("pressed");
let selection = this.textContent;
document.querySelector(`.${selection}`).style.transform = "translate(100px)";
}))
div{
margin-top: 10px;
width: 100px;
height: 100px;
border:1px solid black;
background-color: #ddd;
}
<button>first</button>
<button>second</button>
<div class="first"></div>
<div class="second"></div>
Your problem is textContext is just that TEXT not an object. This sets selection as the first element that matches the class name pulled as this.textContent;
let selection = document.getElementsByClassName(this.textContent)[0];
selection.style.transform = "translate(100px)";

One click multiple functions with arrays

I'm trying to create this piece of code in which an element is pushed into an array, displayed and get a style added which gives them a random hex color. I got the hex color and the pushing into the array partly done, but I can't seem to be able to add the style nor display the div… Here is my code so far:
JS
var colorBg = '#' + (Math.random() * 0xFFFFFF << 0).toString(16)
var elements = []
var el = '<div class="element bg"></div>'
document.getElementById("addEl").onclick = () => {
elements.push(el)
//console.log(elements)
for (i = 0; i < elements.length; i++) {
console.log(elements[i])
document.write(elements[i])
//elements[i].style.backgroundColor = colorBg
}
}
HTML
<div class="container">
<div class="element bg"></div>
</div>
<input type="button" value="Add Block" id="addEl"/>
CSS
html, body{
height:80%;
}
.container{
width:100%;
height:100%;
}
.element{
width:100px !important;
height:100px;
}
Do not use document.write(). Instead, create HTML elements with document.createElement() and then append them to other elements.
You can also just set their color and append them once when you create the element. No need to do all that for ALL the elements every time you click the button.
If you want to randomize the color of every element on every button press, you could instead select all of the elements, iterate over them, and randomize their color that way.
document.getElementById("addEl").onclick = () => {
var el = document.createElement("div");
el.className = ["element bg"];
var colorBg = '#' + (Math.random() * 0xFFFFFF << 0).toString(16)
el.style.backgroundColor = colorBg
document.getElementById("container").append(el)
}
html, body{
height:80%;
}
#container{
}
.element{
width:100px !important;
height:100px;
margin:10px;
border:1px solid black;
}
<div id="container">
</div>
<input type="button" value="Add Block" id="addEl"/>
To give structure to the code it is nice to have each operation as a separate function. Random color string generation, new DOM element construction (without displaying it), main actions. This way it is easy to read the code: can start reading at any point and understand what the lines of code are doing, not necessary having to learn the whole code logic.
What's happening here. It starts with a button click, which fires a "click" event, that has function addEl() bound to it. addEl() would acquire a new DOM element from createEl(), place it inside container element and push the element to the elements array if its required for some other functionality not covered in the original question.
function getColor() {
return '#' + (Math.random() * 0xFFFFFF << 0).toString(16);
}
function createEl(){
var el = document.createElement("div");
el.className = "element bg";
el.style.backgroundColor = getColor();
return el;
}
function addEl() {
var el = createEl();
container.appendChild(el);
elements.push(el);
}
var container = document.getElementById("container");
var elements = [];
document
.getElementById("addEl")
.addEventListener('click', addEl)
;
html, body{
height:80%;
}
#container{
width:100%;
height:100%;
}
.element{
width:100px !important;
height:100px;
float:left;
}
<div id="container"></div>
<input type="button" value="Add Block" id="addEl"/>
You would create elements using DOM methods instead of using document.write(). It's usage is discouraged. The following will do what you are after:
document.getElementById("addEl").addEventListener('click', () => {
let container = document.querySelector('.container');
let el = document.createElement("div");
el.className = 'element bg';
el.innerText = 'foo';
el.style.backgroundColor = getRandomColor();
container.appendChild(el);
});
function getRandomColor() {
return '#' + (Math.random() * 0xFFFFFF << 0).toString(16);
}
html, body{
height:80%;
}
.container{
width:100%;
height:100%;
}
.element{
width:100px !important;
height:100px;
}
<div class="container">
<div class="element bg"></div>
</div>
<input type="button" value="Add Block" id="addEl"/>
This can be simply done using jquery
$(function () {
var elements = []
var el = '<div class="element bg"></div>'
$("#addEl").click(function () {
var colorBg = '#' + (Math.random() * 0xFFFFFF << 0).toString(16)
var el = $("<div />",
{
class: "element bg",
css: {
width: "20px",
height: "10px",
backgroundColor: colorBg
}
});
elements.push(el)
$("#mycontainer").append(el);
});
})
html, body{
height:80%;
}
.container{
width:100%;
height:100%;
}
.element{
width:100px !important;
height:100px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="container" id="mycontainer">
<div class="element bg"></div>
</div>
<input type="button" value="Add Block" id="addEl"/>

Categories