Dom manipulation with for loop - javascript

I am trying to do append() function. i have a table of data. i run a loop to first remove text in cell then i will append a new tag.this usecase is to create a progress bar. for an example
data sample inside cell is e.g 39% 39% 82% etc etc
let cf_percent;
let cf_regex;
for(let i = 0 ;i < tbl[0].length;i++){
cf_percent = tbl[0][i].innerHTML
cf_regex = cf_percent.replace(/[`~%]/gi, '');
console.log(cf_regex)
//Clear fields
tbl[0][i].innerHTML = ''
tbl[0][i].append('<p>Textfield</p>');
}
It should return texfield but instead, it is returning '<p> textfield </p>' in table cell.it should return textField. i have tried .html() but this does not work for this usecase.

in d3.js append function appends a new element with the specified name as the last child of each element in the current selection, returning a new selection containing the appended elements.
So to append p element use: tbl[0][i].append("p") and to set text use .text() further: e.g.
tbl[0][i].append("p").text("Textfield")

I will suggest to use:
let cf_percent;
let cf_regex;
for(let i = 0 ;i < tbl[0].length;i++){
cf_percent = tbl[0][i].innerHTML
cf_regex = cf_percent.replace(/[`~%]/gi, '');
console.log(cf_regex)
//Clear fields
tbl[0][i].innerHTML = ''
tbl[0][i].innerHTML += `<p>Textfield</p>`;
}

You have 2 options. The first is along what you are trying to do where you set the innerHTML to a string. The second is actually generating an element and then appending it. Your current scenario seems to be mixing the two.
const div1 = document.getElementById('sample1'),
div2 = document.getElementById('sample2'),
p = document.createElement('p');
div1.innerHTML = '<p>This set the innerHTML</p>';
p.innerText = 'This is a p appended to a div';
div2.appendChild(p);
#sample1 {
background-color: red;
}
#sample2 {
background-color: yellow;
}
<div id="sample1"></div>
<div id="sample2"></div>

Related

Javascript: How to check/compare if a String contains some tag?

I'd like to use pure JS to check if some String, the textareas .innerHTML = newContent below, contains some tag (h1in my case) at the beginning (=as first child). What would be the best way to do this?
Thanks!
function submitNewSectionContent(e) {
for (var i = 0; i < sections.length; i++)
let newHeading = document.getElementById('edit-title').value;
/* edit-title is text-input*/
let newContent = document.getElementById('edit-sectionText').innerHTML;
/* edit-sectionText is textarea */
if (newContent.indexOf('<h1>') > -1 && newContent.indexOf('<h1>') < 10) { /* <h1> is at beginning so replace with newHeading */
let toberemoved = newContent.match('<h1>.*<\/h1>');
newContent = newContent.replace(toberemoved[0], '').trim();
sections[i].innerHTML = '<h1>'+newHeading+'</h1>' + sections[i].innerHTML;
} elseĀ { /* newContent has no h1 as first child, so add h1 from newHeading */
sections[i].innerHTML = '<h1>'+newHeading+'</h1>' + newContent;
}
}
}
Problem with Regular expressions is they do not really work well with HTML. So Your best bet is to convert it to a DOM fragment and do the manipulations and convert it back. Only issue with this method really is you can lose formatting. There are libraries out there that can pretty print HTML.
function updateHeadline(txt) {
const ta = document.querySelector("textarea");
const data = ta.value; // read value, not innerHTML
const temp = document.createElement('div'); // temp div to hold html
temp.innerHTML = data; // set the html to the temp element
let firstChild = temp.firstElementChild // look at the dom
if (!firstChild || firstChild.tagName!=="H1") { // see if we have an h1
firstChild = document.createElement("h1") // if not create one
temp.prepend(firstChild) // add it to the front
}
firstChild.innerHTML = txt // set the new text of the h1
ta.value = temp.innerHTML // put the content back into the textarea
}
const btn = document.querySelector("button");
btn.addEventListener("click", function (e) {
e.preventDefault()
updateHeadline(document.querySelector("#text").value)
})
textarea {
width: 300px;
height: 200px;
}
<textarea>
<p>Some other text</p>
<p>Some more text</p>
</textarea>
<input value="foo" id="text"/>
<button>Set</button>
You could use regex like so, (updated based on comment)
if( /^\s*<h1>/gi.test(stringToTest) ) {
//logic here
}
It checks if the stringToTest begins with ^ tag
See here : https://regex101.com/r/vSo4sL/1
convert to dom, and parse dom
this portion of code makes it possible to treat a chain to retrieve titles placed in the H1 tag and (on the fly) treat the string of characters.
It's easily expandable for future processing : or tag processing or other ...!
commented code
<html>
<script>
var s="<H1>Hey Title</H1>\n Hello,\n other title <H1>Green!</H1>\n Ipsum dolore sit...";
console.log(s);
console.log("-------------------------");
var partialDoc = document.createElement( 'html' );
partialDoc.innerHTML = s;
var parsed='';
var titles=[];
treatment(partialDoc);
console.log("\n-------------------------\n");
console.log("parsed",parsed);
console.log("\n-------------------------\n");
console.log("s var contains "+titles.length+ " H1 tag");
console.log("titles "+titles);
function treatment(root) {
var child = root.firstChild;
while (child) {
// child.nodeName = H1 | H2 | P etc...
// child.nodeType = 1
// catch H1
if (child.nodeName=='H1') {
// append your title,
parsed+=" [H1 FOUND content= {"+child.innerText+"} H1])";
// or
// parsed+="<H1>"+child.innerText+"<H1>";
// add your own process here
// add this title in array
// or what you want...
titles.push(child.innerText);
// next part of document
child = child.nextSibling;
continue;
}
// capture other text than H1
if (child.nodeType==3) { // Node Type Text
parsed+=child.nodeValue;
}
if (child.nodeType==1) { // Node Type ELEMENT, : sub nodes...
treatment(child);
}
// continue the rest of doc
child = child.nextSibling;
}
}
</script>
</html>
One way you could do it is: Node.firstElementChild which will avoid giving child node as #text for white-spaces and Node.nodeName
let firstChild = document.getElementById('edit-sectionText').firstElementChild;
if(firstChild.nodeName === "H1"){
firstChild.innerHTML = "Replacement Value"
}
Note & Update: The earlier api that I had suggested Node.firstChild will not prevent white-spaces which gives #text node and comments as #comment node.
2nd Way: Node.children and picking the first child out of it should have a similar result to Node.firstElementChild.
let elem = document.getElementById('edit-sectionText');
if(elem){
let firstChild = elem.children[0];
}
Update based on comments: Using Dom Parser Interface
The interface allows to parse XML or HTML source from a string based on the mime type provided for its method parseFromString(string, mimeType)
It will give the top level #document node with parsed HTML from the string where if exists <h1> or <H1> at the beginning would be the first child of body and subsequently can be tested via tagName property.
Note: Takes care of preceding HTML comments and spaces at the beginning but a caveat is doesn't check fully closed tags ex: var s = \t <h1>I am a heading <h1> here the <h1> was never closed and in the result will two fully formed headings at the body with content : I am a heading and ""
let textAreaString = document.getElementById("edit-sectionText").value;
const domParser = new DOMParser();
const parsedDoc = domParser.parseFromString(textAreaString, "text/html");
if (parsedDoc.body.firstElementChild.tagName === "H1") {
//yes it starts with <h1> or <H1>
}

Loop through string and style individual letters

I have the title "INTRO TO GRID", and I want to individually style the letters in the string "GRID". I know I could do this with CSS, but I want to try with JS. I am having difficulty figuring out how to attach .css() to the individual letters. I have attached my code for reference. Thanks in advance!
var header = $("#header").text();
var grid = header.substring(9, header.length);
var chars = grid.split("");
for(var i = 0; i < chars.length; i++){
console.log(chars[i]);
}
console.log(chars);
With the help previous commenters, here is the solution to my problem.
var header = $("#header");
var chars = header.text().split("");
var headerSpans = chars.map(function(char){
return $('<span>' + char + '</span>');
});
$(header).html(headerSpans);
for(var i=9; i <= headerSpans.length; i++){
headerSpans[i].css({
"margin" : "20px",
"background-color" : "red"
});
}
#Jon Uleis is right, you cannot style individual characters. But you can style DOM elements, like <span>:
var header = $('#header'),
headerSpans = header.text()
.split("")
.map(function(char){
return $('<span>' + char + '</span>');
});
header.html(headerSpans);
headerSpans[9].css('color', 'red');
headerSpans[10].css('color', 'blue');
headerSpans[11].css('color', 'green');
headerSpans[12].css('color', 'orange');
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="header">INTRO TO GRID</div>
You can change the style of elements with JavaScript using style object which can be accessed from specific HTML element or from the head section of the document. Find more about style document here.
There are plenty of solutions for your problem but I tried to provide you one which is simple to understand.
My solution:
You need to get HTML element where you will store your text. (In my example it is div element which id is root)
Store every character of your text in one array. (In my example, name of array is text).
Loop through array of characters with forEach method.( More about forEach here).
Create new span element for every letter inside forEach method.
Wrap every character in text node and append it to created span.
Style every span however you want to. (If statement is used to limit styling only to the word "GRID").
Append created and styled span to the div element where text will be displayed.
Solution code:
const div = document.getElementById(`root`); // div from HTML
const text = `INTRO TO GRID`.split(``); // every character of your text stored in array
text.forEach((char, index) => {
const node = document.createElement(`span`); // create new span element
const textnode = document.createTextNode(char); // create character as text node for span element
node.appendChild(textnode); // add text to span
// if you want only to style characters only in the word `GRID`
if(index >= text.length - `GRID`.length){
// style the characters however you want to
node.style.color = index % 2 === 0 ? `red` : `black`; // change color
node.style.fontWeight = index % 2 !== 0 ? 900 : 1; // change font weight
}
div.appendChild(node); // add span (character) to the div element in HTML
});
<div id="root">
</div>

Need help changing appendChild to replaceChild

Is there an easy way to change this from appendChild to replaceChild?
This of course continuously adds more ele. Also for some reason it doesn't put the value inside the DIV or SPAN, seems to put below it.
var para = document.createElement("P");
var total = document.createTextNode(parseFloat((subT + tax).toFixed(2))) ;
para.appendChild(total);
document.getElementById("total_id").appendChild(para);
Its updating this:
<div class="prod_totals">Order Total: $<span id="total_id"></span></div>
you can simply use innerHTML instead of appendChild
document.getElementById("total_id").innerHTML = parseFloat((subT + tax).toFixed(2));
Because you're not inserting any user input values inside the total_id element and also as far as the question mentions, its data is not later passed to the server I think you'll be safe using the innerHTML here. But if for any reasons you'd still like to use replaceChild you could do it like this:
var para = document.createElement("P");
var total = document.createTextNode(parseFloat((subT + tax).toFixed(2))) ;
para.appendChild(total);
var existingText=document.getElementById("total_id").querySelector('p');
if(existingText){
document.getElementById("total_id").replaceChild(existingText, para);
}
else{
document.getElementById("total_id").appendChild(para);
}
There's no need to use .replaceChild here, you can simply check if the element was already created before trying to update it.
Note that you were trying to insert a p element inside a span which is wrong and is not valid HTML markup, you can see in the span documentation that its possible content is only Phrasing content, so you better use another span.
This is how should be your code:
var para = document.querySelector("#total_id span");
if (!para || para.length == 0) {
para = document.createElement("span");
}
var total = parseFloat((subT + tax).toFixed(2));
para.innerText = total;
document.getElementById("total_id").appendChild(para);
And here's a Demo:
var para = document.querySelector("#total_id span");
if (!para || para.length == 0) {
para = document.createElement("span");
}
var total = new Date();
para.innerText = total;
document.getElementById("total_id").appendChild(para);
<div class="prod_totals">Order Total: $<span id="total_id"></span></div>

JavaScript get textContent excluding children

First, I'm creating a library for JavaScript and I can not use jQuery. I'm trying to get the text content of an HTML element without the text contents of its children.
Both attributes innerText and textContent don't give me what needed, please help.
You can solve using DOM API as childNodes and nodeType.
var elChildNode = document.querySelector("#hello").childNodes;
var sChildTextSum = "";
elChildNode.forEach(function(value){
if(value.nodeType === Node.TEXT_NODE) {
console.log("Current textNode value is : ", value.nodeValue.trim());
sChildTextSum += value.nodeValue;
}
});
console.log("All text value of firstChild : ", sChildTextSum);
I created a sample code as above.
https://jsfiddle.net/nigayo/p7t9bdc3/
To get Author's Name from the following element, excluding <span>...:
<div class="details__instructor">
Author's Name<span ng-show="job_title">, Entrepreneur</span>
</div>
use childNodes[0]. For example:
document.querySelector('div.details__instructor').childNodes[0].textContent
Using only JavaScript (you specified you cannot use jQuery), and given that you have provided and know the id for the parent element:
document.getElementById('parent_element_id').childNodes[0].nodeValue;
You can also use .trim() to remove any trailing space characters left behind from the removal of any child element text:
document.getElementById('parent_element_id').childNodes[0].nodeValue.trim();
var mydiv = getElementByID("id");
function Get_text(element) {
var selected = element.cloneNode(true);
var text;
while (selected.firstChild) {
if (selected.firstChild.nodeType == 3) text = selected.firstChild.nodeValue;
selected.removeChild(selected.firstChild);
}
return text;
}
Get_text(mydiv);
I know many good solutions here exist, but none of them actually achieved what I needed (get the textContent of a single node, none of its children), so sharing this for future searchers.
var html = document.getElementsByTagName("*");
for (var i = 0; i < html.length; i++) {
var el = html[i];
for (var j = 0; j < el.children.length; j++) {
var child = el.children[j],
childTextContent = child.innerHTML;
// Remove all children tags, leaving only the actual text of the node.
childTextContent = childTextContent.replace(/\<.*\>.*\<\/.*\>/gmi, "");
// Also remove <img /> type tags.
childTextContent = childTextContent.replace(/\<.*\ \/\>/gmi, "");
console.log(childTextContent);
// Now you can do any type of text matching (regex) on the result.
}
});

Remove tag if id matches but keep text

I have a highlighter function that formats the matched words to an anchor with yellow bg-color and I need a function to remove the anchor elements for the next search.
The markup of a matched word, for the first one looks like this:
<a id="searchword1" class="searchword" style="background-color: yellow; text-decoration: none; color: black;">my text</a>
I need to remove the anchor but leave my text there. There are other anchors in my document that I dont want to interfere with. I need to do this in pure Javascript (no jQuery).
An addational requirement: Don't create new lines after tag removal, leave it as it was.
Thanks to enhzflep, the code until now:
for (z=0;z<szam;z++){
var removal = parent.frames['pagina'].document.getElementById("searchword"+z);
var highlightedText = removal.innerHTML.toLowerCase;
removeh(removal,highlightedText,doc);
}
function removeh(node,high,doc) {
doc = typeof(doc) != 'undefined' ? doc : document;
if (node.hasChildNodes) {
var hi_cn;
for (hi_cn=0;hi_cn<node.childNodes.length;hi_cn++) {
removeh(node.childNodes[hi_cn],high,doc);
}
}
//1. Get element containing text
if (node.nodeType == 3) {
tempnode = node.nodeValue.toLowerCase();
if (tempnode.indexOf(high) != -1) {
nv = node.nodeValue;
nv = node.nodeValue;
ni = tempnode.indexOf(high);
//2. Get the text it contains
before = doc.createTextNode(nv.substr(0,ni));
dochighVal = nv.substr(ni,high.length);
after = doc.createTextNode(nv.substr(ni+high.length));
//3. Get the highlighted element's parent
var daddy = node.parentNode;
//4. Create a text node:
var newNode = document.createTextNode(dochighVal);
//5. Insert it into the document before the link
daddy.insertBefore(before, node);
daddy.insertBefore(newNode, node);
daddy.insertBefore(after, node);
//6. Remove the link element
daddy.removeChild(node);
}
}
}
Where num is the number of matched words.
For some reason this wont work, please help, I will accept the answer that solves this minor problem too.
Two answers had the method right but it is still buggy as it separates the resulting text with new lines. This makes the highlighter function to get the "my word" as separate temporary node values and won't be able to highlight a match for /my.word/.
If I understand you correctly, you wish to turn this:
<a id="searchword1" class="searchword" style="background-color: yellow; text-decoration: none; color: black;">my text</a>
into this:
my text
If that's the case, then it's very easy.
As it stands, it looks like you're asking for an child of the element you showed (the element doesn't have any children, other than the text-node. I expect your script is hosed by line 2 - when it tries to get a non-existent child)
//1. Get element containing text
var element = document.getElementById('searchWord1');
//2. Get the text it contains
var highlightedText = element.innerHTML;
//3. Get the highlighted element's parent
var parent = element.parentNode;
//4. Create a text node:
var newNode = document.createTextNode(highlightedText);
//5. Insert it into the document before the link
parent.insertBefore(newNode, element);
//6. Remove the link element
parent.removeChild(element);
If you are using jQuery it will be simple DEMO
$('#searchword1').contents().unwrap();
But if you only want to use js for this there is solution by user113716 on the Link
DEMO
var b= document.getElementsByClassName('searchword');
while(b.length) {
var parent = b[ 0 ].parentNode;
while( b[ 0 ].firstChild ) {
parent.insertBefore( b[ 0 ].firstChild, b[ 0 ] );
}
parent.removeChild( b[ 0 ] );
}

Categories