I'm trying to get the inner text of a header, then I would like to split the characters, then wrap each character in a span. I tried previously using insertBefore but I couldn't get it to work.
Here is my current code, which so far just logs each character.
// Title Split
const titleSplit = document.querySelectorAll('.title-split');
// Each El
Array.prototype.forEach.call(titleSplit, function(el, i) {
let innerText = el.innerText;
el.setAttribute('data-word', innerText);
const chars = innerText.split('');
Array.prototype.forEach.call(chars, function(el, i) {
console.log(el);
});
});
I specifically don't want any jQuery solutions. I'm trying to reverse learn Javascript to become more efficient. Any advise would be great. I tried putting in an insertBefore in the chars forEach but it just kept saying undefined.
document.querySelectorAll(...) return a NodeList which contains a method named .forEach(). So Instead of
Array.prototype.forEach.call(titleSplit, function(el, i) {
...
}
You can just do
titleSplit.forEach(...);
Similarly, you can just do
chars.forEach(...);
because chars is an array and you can call .forEach() method on an array.
Now coming to your question, once you have the chars array, you can use .map() method instead of .forEach(), to get a new array that contains strings where each character is wrapped by span tags.
const result = chars.map(c => `<span>${c}</span>`);
Finally, you need to iterate over the result array and insert its elements in the DOM.
result.forEach(el => document.body.append(el));
I have used document.body as the parent element of all the span elements. You can use any element that needs to be the parent element of all the span elements.
You can use map() inside the forEach() loop to construct the result with span. Also I will recommend you not to use innerText as a variable name as it is property name which can lead to some confusion:
// Title Split
const titleSplit = document.querySelectorAll('.title-split');
// Each El
Array.prototype.forEach.call(titleSplit, function(el, i) {
let ElText = el.innerText;
el.setAttribute('data-word', ElText);
const chars = ElText.split('');
var res = chars.map(function(el, i) {
return `<span>${el}</span>`;
}).join('');
el.innerHTML = res;
});
span{
border: 1px solid red;
padding: 2px;
}
<div class="title-split">Hello there</div>
const myHeader = querySelector('query my header please');
const spans = myHeader.innerText
.split('')
.map((char) => `<span>${char}</span>`)
.join('');
document.querySelector('query element to put them').innerHTML = spans;
// OR
document.querySelector('query element to put them').insertAdjacentHTML('beforeend', spans);
// etc.
Related
I have a function returning a template literal:
function generateStuff(nodes) {
const output = `<ul>${nodes}</ul>`;
return document.body.innerHTML = output;
}
nodes is an array of <li> elements made with createElement and then added to nodes via appendChild.
Is there a way of render a list with generateStuff(nodes)? Right now all it returns is <ul>[object NodeList]</ul> and I just want it to return proper HTML with working links etc.
I suppose I need to parse it somehow but as this is JS and NodeList is native to it maybe there's a method in place for it already?
You could use document.createElement and Element#append.
function generateStuff(nodes) {
const el = document.createElement('ul');
el.append(...nodes);
document.body.append(el);
return el.outerHTML;
}
console.log(generateStuff([...Array(3)].map((_, i) => Object.assign(document.createElement('li'), {textContent: i}))));
Is there a way of render a list with generateStuff(nodes)?
Yes, but I'd consider changing how you're doing this instead. But the way you'd do it is with a round-trip through HTML:
function generateStuff(nodes) {
const output = `<ul>${Array.from(nodes, (node) => node.outerHTML)}</ul>`;
return (document.body.innerHTML = output);
}
But that's inefficient and lossy (loses event handlers on the nodes, for instance). Instead of using a template literal, consider appending the nodes directly:
function generateStuff(nodes) {
const ul = document.createElement("ul");
for (const node of nodes) {
ul.appendChild(node);
}
// Or replace the loop with: `ul.append(...nodes)`
document.body.innerHTML = "";
document.body.appendChild(ul);
return ul.outerHTML; // **If** you really need to return HTML, but hopefully not
}
In a comment you've said:
I think I might have overly simplified my case, normally I wouldn't have use template literal here but instead of I have like 20 nested containers there. Your second approach seems really cool but what if ul is inside of a x different containers? How do I append to then? Do I still have to manually create and append every single one of them? That's what I'm trying to avoid here.
You could create the structure by assigning to innerHTML with a means of identifying the ul, then once the structure exists, do the append:
function generateStuff(nodes) {
// Create a new replacement `body` element
const body = document.createElement("body");
// Create the structure
body.innerHTML = "<ul class='target'></ul>";
// Get the `ul` and append to it
const ul = body.querySelector(".target");
ul.append(...nodes);
// Replace the element
document.body.replaceWith(body);
// **If** you really need to return HTML
return document.body.innerHTML;
}
Live Example:
document.querySelector("input[type=button]").addEventListener("click", () => {
const nodes = Array.from({length: 5}, (_, i) => {
const li = document.createElement("li");
li.textContent = "li #" + i;
return li;
});
generateStuff(nodes);
});
function generateStuff(nodes) {
// Create a new replacement `body` element
const body = document.createElement("body");
// Create the structure
body.innerHTML = "<ul class='target'></ul>";
// Get the `ul` and append to it
const ul = body.querySelector(".target");
ul.append(...nodes);
// Replace the element
document.body.replaceWith(body);
// **If** you really need to return HTML
return document.body.innerHTML;
}
<input type="button" value="Go!">
I have a function, getTextNodes, that searches text nodes recursively. Then I use a addHighlight function to highlight the text with <mark> tags:
const buttonEl = `<button>
<span>
Icon
</span>
Text
</button>
`;
document.body.innerHTML = buttonEl;
const foundButtonEl = document.querySelector("button");
const elements = [];
elements.push(foundButtonEl);
addHighlight(elements, "T");
function addHighlight(elements, text) {
elements.forEach((element, index) => {
const textNodes = getTextNodes(document.body);
const matchingNode = textNodes.find(node => node.textContent.includes(text));
const markElement = document.createElement('mark');
markElement.innerHTML = matchingNode.textContent;
matchingNode.replaceWith(markElement);
});
}
function getTextNodes(node) {
let textNodes = [];
if (node.nodeType === Node.TEXT_NODE) {
textNodes.push(node);
}
node.childNodes.forEach(childNode => {
textNodes.push(...getTextNodes(childNode));
});
return textNodes;
}
The problem is that addHighlight is highlighing the whole text (in the example, Text), instead of the matched text (in the example, T).
How to change this code so that only the matched text is highlighted (text)?
matchingNode is the whole node so you're replacing everything. If you want to match just part of it, you need to iterate though the textnode and find the index position of the substring that you're searching for.
Start by splitting the node into an array
matchingNode.wholeText.split("")
Then find the index position, insert markElement at that position, and go from there.
The problem is that the node you match is the element of which the innerContent contains the string you want to highlight.
What you should do instead of :
markElement.innerHTML = matchingNode.textContent;
matchingNode.replaceWith(markElement);
is probably something like
markElement.innerHTML = text;
matchingNode.replaceTextWithHTML(text, markElement);
replaceTextWithHTML is a fictive function :)
I have this code that's to parse a string into html and display the text of each element.
That's working good except when I have nested tags for example <div><p>Element 1</p><p>Element 2</p></div>. In this case, the code displays <p>Element 1</p><p>Element 2</p>.
How can I do to get each tags one after the other ? (Here I want Element 1 and then Element 2)
Here's the code :
let text = new DOMParser().parseFromString(stringHtml, 'text/html');
let textBody = text.body.firstChild;
while (textBody) {
alert(textBody.innerHTML);
// other actions on the textBody element
textBody = textBody.nextSibling;
}
Thanks for helping me out
It sounds like you want a recursive function that prints the textContent of itself, or of its children, if it has children:
const stringHtml = '<div><p>Element 1</p><p>Element 2</p></div><div><p>Element 3</p><p>Element 4</p></div>';
const doc = new DOMParser().parseFromString(stringHtml, 'text/html');
const showElms = parent => {
const { children } = parent;
if (children.length) Array.prototype.forEach.call(children, showElms);
else console.log(parent.textContent);
}
showElms(doc.body);
That's assuming you want to iterate over the actual elements. If you want all text nodes instead, then recursively iterate over the childNodes instead.
This question already has answers here:
Javascript - efficiently insert multiple HTML elements
(3 answers)
Closed 5 years ago.
I have an array of elements:
const itemCurrent = document.createElement('div');
const itemNext = document.createElement('div');
Ive created an array of them:
const itemsAll = [itemCurrent, itemNext];
How can I insert all of them into the body of my page?
You may simply use .append() to append multiple DOM nodes all at once as follows;
var divs = Array.from({length:10}) // lets make an array of 10 divs
.map(function(_,i){
var el = document.createElement('div');
el.textContent = `I am div # ${i}`;
return el;
});
document.body.append(...divs);
In fact the above snippet has some redundany since Array.from() bears built in mapping. So the following rephrased code would be more reasonable.
var divs = Array.from( {length:10} // lets make an array of 10 divs
, function(_,i){ // and map it accordingly
var el = document.createElement('div');
el.textContent = `I am div # ${i}`;
return el;
}
);
document.body.append(...divs);
To add the elements to the body, you need to append them to the body of your document. This is done by using the appendChild(element) function :
// looping through your array :
for (let i=0;i<itemsAll.length;i++) {
// appending your elements to the body :
document.body.appendChild(itemsAll[i]);
}
To recollect what others have said:
in JS, you iterate over an array with array.forEach which takes one argument - a function that will be called on all the elements of the array sequentially
to append (add at the end) an element to the DOM (body of the page) you call document.body.appendChild which takes the element to be added as an argument
Code you need then becomes
itemsAll.forEach(function (element, index, array) {
document.body.appendChild(element);
})
or (using ES6 syntax)
itemsAll.forEach(el => document.body.appendChild(el))
Iterating over array and appending each using appendChild:
itemsAll.forEach(el => {
document.body.appendChild(el)
})
I'm trying to assign class and id to items in an array I created in js and input into my html. I'm doing this so I can style them in my stylesheet. Each item will not be styled the same way.
I'm a beginner so trying to keep it to code I can understand and make it as clean as possible, i.e. not making each of these items an element in the html.
This part works fine:
var pool =['A','B','3','J','R','1','Q','F','5','T','0','K','N','C','R','U']
var letters = pool.join('');
document.getElementById('key').innerHTML = letters;
This part not so much:
var char1 = letters[1];
char1.classList.add('hoverRed');
There is a similar question here that didn't work for me, it just showed [object][object][object] when I ran it.
Your code attempts to apply a style to an array element, but CSS only applies to HTML. If you wish to style one character in a string, that character must be wrapped in an HTML element (a <span> is the best choice for wrapping an inline value).
This code shows how to accomplish this:
var pool =['A','B','3','J','R','1','Q','F','5','T','0','K','N','C','R','U']
var letters = pool.join('');
// Replace a specific character with the same character, but wrapped in a <span>
// so it can be styled
letters = letters.replace(letters[1], "<span>" + letters[1] + "</span>");
// Insert the letters string into the div
var theDiv = document.getElementById('key');
// Inject the string into the div
theDiv.innerHTML = letters;
// Get a reference to the span:
var theSpan = theDiv.querySelector("span");
// Add the style to the <span> that wraps the character, not the character itself
theSpan.classList.add('hoverRed');
.hoverRed {
color:red;
}
<div id="key"></div>
And, this snippet shows how you could apply CSS to any letter:
var pool =['A','B','3','J','R','1','Q','F','5','T','0','K','N','C','R','U'];
// Leave the original array alone so that it can be manipulated any way needed
// in the future, but create a new array that wraps each array element within
// a <span>. This can be accomplished in several ways, but the map() array method
// is the most straight-forward.
var charSpanArray = pool.map(function(char){
return "<span>" + char + "</span>";
});
// Decide which character(s) need CSS applied to them. This data can come from anywhere
// Here, we'll just say that the 2nd and 5th ones should.
// Loop through the new array and on the 2nd and 5th elements, apply the CSS class
charSpanArray.forEach(function(element, index, array){
// Check for the particular array elements in question
if(index === 1 || index === 4){
// Update those strings to include the CSS
array[index] = element.replace("<span>","<span class='hoverRed'>");
}
});
// Now, turn the new array into a string
var letters = charSpanArray.join('');
// For diagnostics, print the string to the console just to see what we've got
console.log(letters);
// Get a reference to the div container
var theDiv = document.getElementById('key');
// Inject the string into the div
theDiv.innerHTML = letters;
.hoverRed {
color:red;
}
<div id="key"></div>
You're on the right track, but missed one key thing.
In your example, pool contains characters. When you combine them using join, you get a string. Setting that string as the innerHTML of an element doesn't give the string super powers, it's still just a string.
In order to get a classList, you need to change your letters into elements and work with them.
I've included an es6 example (and a working plunker) of how to get the functionality you want below.
let pool = ['A','B','3','J','R','1','Q','F','5','T','0','K','N','C','R','U']
const letterToElement = function(char) {
//Create the element
let e = document.createElement("SPAN");
//Create the text node
let t = document.createTextNode(char);
//Put the text node on the element
e.appendChild(t);
//Add the class name you want
e.className += "hoverRed";
return e;
};
//create your elements from your pool and append them to the "key" element
window.onload = function() {
let container = document.getElementById("key");
pool.map(l => letterToElement(l))
.forEach(e => container.appendChild(e));
}
https://plnkr.co/edit/mBhA60aUCEGSs0t0MDGu