Vanilla JavaScript - Appending Elements and Txt to an Element without using .innerHTML - javascript

element.append() not working in MS Edge
I was wondering how is the best way to create a new element and then append a string into it made up of other elements ang text variables? The .append method doesn’t seem to work in MS Edge
The error I’m getting is: SCRIPT438: SCRIPT438: Object doesn't support property or method 'append'
Isn’t this the correct way to do this without creating a string and then append to the parent.inner HTML??
parent = document.createElement("h4");
txtNode = document.createTextNode("");
txtNode.append(“WHATEVER”);
parent.appendChild(txtNode):
Thanks in advance
// Const
const numCopyrightTxtYear = 2018;
// Copyright Var
var elmCopyright = document.createElement("h4");
var elmCopyrightTxt = document.createTextNode("");
// Elements to append to elmCopyrightTxt
var elmTime = document.createElement("time");
var elmTimeTxt = document.createTextNode(numCopyrightTxtYear);
var elmCopyrightHolder = document.createElement("em");
var elmCopyrightHolderTxt = document.createTextNode("")
// Copyright
elmTime.dateTime = numCopyrightTxtYear;
elmTime.setAttribute("itemprop", "copyrightYear");
elmCopyrightHolder.setAttribute("itemprop", "creator copyrightHolder");
elmCopyrightHolder.appendChild(elmCopyrightHolderTxt);
elmCopyright.append("© ");
elmTime.appendChild(elmTimeTxt);
elmCopyright.append(elmTime);
elmCopyright.append(elmCopyrightHolder);
// This is the compleat code if I left somthing out??
function createHeaderFragment() {
// Main Fragment
var elmHeaderFragment = document.createDocumentFragment();
// Other Containers
var elmHeader = document.createElement("header");
// Values to Set
const strTitle = "Title";
const strCaption = "Caption";
const strSubjectOf = "SubjectOf";
const strLocation = "Location";
const strHashtags = "Hashtags";
const strKeywords = "Keywords";
const numCopyrightTxtYear = 2018;
// New Elements
// Title
var elmTitle = document.createElement("h2");
var elmTitleTxt = document.createTextNode(strTitle);
// Caption
var elmCaption = document.createElement("h3");
var elmCaptionTxt = document.createTextNode(strCaption);
// SubjectOf
var elmSubjectOf = document.createElement("h3");
var elmSubjectOfTxt = document.createTextNode(strSubjectOf);
// Location
var elmLocation = document.createElement("h3");
var elmLocationTxt = document.createTextNode(strLocation);
// Hashtags
var elmHashtags = document.createElement("h3");
var elmHashtagsTxt = document.createTextNode(strHashtags);
// Keywords
var elmKeywords = document.createElement("h3");
var elmKeywordsTxt = document.createTextNode(strKeywords);
// Copyright
var elmCopyright = document.createElement("h4");
var elmCopyrightTxt = document.createTextNode("");
var elmTime = document.createElement("time");
var elmTimeTxt = document.createTextNode(numCopyrightTxtYear);
var elmCopyrightHolder = document.createElement("em");
var elmCopyrightHolderTxt = document.createTextNode(" Evan Santé Photography")
// <img src= "/images/nav/grid1.jpg" alt= "Thumbnail Image" itemprop= "hasPart image thumbnail" />
//var objHeaderImage = document.createElement("img");
// Set Element Nodes
elmTitle.setAttribute("itemprop", "headline");
elmTitle.appendChild(elmTitleTxt);
elmCaption.setAttribute("itemprop", "caption");
elmCaption.appendChild(elmCaptionTxt);
elmSubjectOf.setAttribute("itemprop", "subjectOf");
elmSubjectOf.appendChild(elmSubjectOfTxt);
elmLocation.setAttribute("itemprop", "contentLocation");
elmLocation.appendChild(elmLocationTxt);
elmHashtags.setAttribute("itemprop", "keywords");
elmHashtags.appendChild(elmHashtagsTxt);
elmKeywords.setAttribute("itemprop", "keywords");
elmKeywords.appendChild(elmKeywordsTxt);
// Copyright
elmTime.dateTime = numCopyrightTxtYear;
elmTime.setAttribute("itemprop", "copyrightYear");
elmCopyrightHolder.setAttribute("itemprop", "creator copyrightHolder");
elmCopyrightHolder.appendChild(elmCopyrightHolderTxt);
elmCopyright.append("© ");
elmTime.appendChild(elmTimeTxt);
elmCopyright.append(elmTime);
elmCopyright.append(elmCopyrightHolder);
// Append To Header
elmHeader.appendChild(elmTitle);
elmHeader.appendChild(elmCaption);
elmHeader.appendChild(elmSubjectOf);
elmHeader.appendChild(elmLocation);
elmHeader.appendChild(elmHashtags);
elmHeader.appendChild(elmKeywords);
elmHeader.appendChild(elmCopyright);
return elmHeaderFragment.appendChild(elmHeader);
}

append is fairly new and not supported in all browsers. However, the MDN page has a polyfill for it you can use on IE9-IE11 and, presumably, Edge.
If you don't use a polyfill, what you're looking for are appendChild (spec | MDN) or insertBefore (spec | MDN), which you call on the parent, passing in the node to append.
var parent = document.createElement("h4");
parent.appendChild(
document.createTextNode("WHATEVER")
);
// ...presumably you `.appendChild(parent)` at some point...
Live Example:
var parent = document.createElement("h4");
parent.appendChild(
document.createTextNode("WHATEVER")
);
document.body.appendChild(parent);
appendChild always adds at the end of the parent. insertBefore adds before another node you specify (or at the end if you give null for the other node).
Having said that, innerHTML is universally supported and browsers are very fast at reading markup and turning it into DOM nodes. There's no reason not to use innerHTML when you have something complex you want to use as the content of an element. Naturally, though, you need appendChild, insertBefore, and other DOM methods in other situations.
In a comment you've said you want to append text to an existing text node: If so, just add to nodeValue:
setTimeout(function() {
var d = document.getElementById("target");
var txt = d.firstChild;
txt.nodeValue += " more text";
}, 800);
<div id="target">existing text</div>

Related

js: how to simplify html string

is there any way to simplify the HTML string? Like removing all redundant tags from the string.
For instance:
Source HTML:
<div><span><span>1</span></span><span>2</span></div>
Expected output:
<div><span>12</span></div>
(or even less)
<div>12</div>
I've known some libs like quilljs can do this, but it's a huge library, kind of overkill for my case.
also, https://github.com/htacg/tidy-html5 is kind of what I want, but it does not have a js release
You can try using the DOMParser:
let s = `<div><span><span>1</span></span><span>2</span></div>`
let d = new DOMParser()
let doc = d.parseFromString(s, 'application/xml')
let tag = doc.children[0].tagName
let text = doc.children[0].textContent
let result = `<${tag}>${text}</${tag}>`
console.log(result)
Please refer to the below code, It may help you to go further.
var childs = document.querySelectorAll("div#parent")
var tmpTexts = []
for (const c of childs) {
if (tmpTexts.includes(c.innerText)) continue
tmpTexts.push((c.innerText).trim())
c.parentNode.removeChild(c)
}
tmpTextArr = tmpTexts[0].split('\n');
console.log(tmpTextArr);
const para = document.createElement("div");
tmpTextArr.forEach(function(text) {
var node = document.createElement("div");
var nodeTxt = document.createTextNode(text);
node.appendChild(nodeTxt);
para.appendChild(node)
});
document.body.appendChild(para);
https://jsfiddle.net/Frangly/pnLgr8ym/66/
In tmpTexts, for every new line - you should add a div tag.
Create a new Element and iterate the tmpTexts array and a div tag by using innerHTML

vanilla js - get ID of element inside another element, which isn't part of the document

var div = document.createElement('div');
div.innerHTML = htmlString
var obj = {};
obj.doctors = div.getElementById('doctors');
Is there a sensible way to find an ID within an element which is not yet attached to the DOM? Don't want to use JQuery here.
Two ways:
Create a DocumentFragment, attach your node to it and use getElementById:
var domFragment = new DocumentFragment();
domFragment.appendChild(div);
var elem = domFragment.getElementById("yourid");
Use query selector:
var elem = div.querySelector("#yourid");

How to append an HTML string to a DocumentFragment?

I'm adding textnodes to a documentFragment like this:
var foo = document.createDocumentFragment();
var someText = "Hello World";
foo.appendChild(document.createTextNode(someText));
This works fine, but sometimes I'm being passed "text" which includes inline links like this:
var someOtherText = "Hello <a href='www.world.com'>World</a>";
Which in my handler is converted to hardcoded text instead of a link.
Question:
How do I append an HTML string like the above into a documentFragment? If I'm not using textNodes can this be done using appendChild?
Create a template-element, add the text with .innerHTML and get a doumentFragment with the content-property:
function stringToFragment(string) {
const temp = document.createElement('template');
temp.innerHTML = string;
return temp.content;
}
Now you can create a documentFragment from a string, and you can even append a documentFragment to a documentFragment:
const frag = stringToFragment('<div>Hello</div>');
frag.append(stringToFragment('<div>Stackoverflow</div>'));
document.createRange().createContextualFragment("<span>Hello World!</span>");
It returns a DocumentFragment.
Support IE >= 9
EDIT:
recent versions Safari seems to fail with the short way, here is a little bit longer but working way:
var range = document.createRange();
range.selectNode(document.body); // Select the body cause there is always one (documentElement fail...)
var fragment = range.createContextualFragment("<span>Hello World!</span>");
This may works:
var foo = document.createDocumentFragment();
var someText = 'Hello World';
var item = document.createElement('span');
item.innerHTML = someText
foo.appendChild(item);
document.body.appendChild(foo);

Getting Attributes from User submitted text! RegExp?

I am trying to pull the attributes out of piece of submitted text in Javascript and change it to an array.
So the user submits this:
<iframe src="http://www.stackoverflow.com/" width="123" height="123" frameborder="1"></iframe>
and I would get:
arr['src'] = http://www.stackoverflow.com/
arr['width'] = 123
arr['height'] = 123
arr['frameborder'] = 1
Just need a regexp I think but any help would be great!
I recommend to use a RegExp to parse user-inputed HTML, instead of creating a DOM object, because it's not desired to load external content (iframe, script, link, style, object, ...) when performing a "simple" task such as getting attribute values of a HTML string.
Using similar (although similarcontradiction?) methods as in my previous answer, I've created a function to match quoted attribute values. Both quoted, as non-quoted attributes are matched.
The code currently returns an object with attributes from the first tag, but it's easily extensible to retrieve all HTML elements (see bottom of answer).
Fiddle: http://jsfiddle.net/BP4nF/1/
// Example:
var htmlString = '<iframe src="http://www.stackoverflow.com/" width="123" height="123" frameborder="1" non-quoted=test></iframe>';
var arr = parseHTMLTag(htmlString);
//arr is the desired object. An easy method to verify:
alert(JSON.stringify(arr));
function parseHTMLTag(htmlString){
var tagPattern = /<[a-z]\S*(?:[^<>"']*(?:"[^"]*"|'[^']*'))*?[^<>]*(?:>|(?=<))/i;
var attPattern = /([-a-z0-9:._]+)\s*=(?:\s*(["'])((?:[^"']+|(?!\2).)*)\2|([^><\s]+))/ig;
// 1 = attribute, 2 = quote, 3 = value, 4=non-quoted value (either 3 or 4)
var tag = htmlString.match(tagPattern);
var attributes = {};
if(tag){ //If there's a tag match
tag = tag[0]; //Match the whole tag
var match;
while((match = attPattern.exec(tag)) !== null){
//match[1] = attribute, match[3] = value, match[4] = non-quoted value
attributes[match[1]] = match[3] || match[4];
}
}
return attributes;
}
The output of the example is equivalent to:
var arr = {
"src": "http://www.stackoverflow.com/",
"width": "123",
"height": "123",
"frameborder": "1",
"non-quoted": "test"
};
Extra: Modifying the function to get multiple matches (only showing code to update)
function parseHTMLTags(htmlString){
var tagPattern = /<([a-z]\S*)(?:[^<>"']*(?:"[^"]*"|'[^']*'))*?[^<>]*(?:>|(?=<))/ig;
// 1 = tag name
var attPattern = /([-a-z0-9:._]+)\s*=(?:\s*(["'])((?:[^"']+|(?!\2).)*)\2|([^><\s]+))/ig;
// 1 = attribute, 2 = quote, 3 = value, 4=non-quoted value (either 3 or 4)
var htmlObject = [];
var tag, match, attributes;
while(tag = tagPattern.exec(htmlString)){
attributes = {};
while(match = attPattern.exec(tag)){
attributes[match[1]] = match[3] || match[4];
}
htmlObject.push({
tagName: tag[1],
attributes: attributes
});
}
return htmlObject; //Array of all HTML elements
}
Assuming you're doing this client side, you're better off not using RegExp, but using the DOM:
var tmp = document.createElement("div");
tmp.innerHTML = userStr;
tmp = tmp.firstChild;
console.log(tmp.src);
console.log(tmp.width);
console.log(tmp.height);
console.log(tmp.frameBorder);
Just make sure you don't add the created element to the document without sanitizing it first. You might also need to loop over the created nodes until you get to an element node.
Assuming they will always enter an HTML element you could parse it and read the elements from the DOM, like so (untested):
var getAttributes = function(str) {
var a={}, div=document.createElement("div");
div.innerHTML = str;
var attrs=div.firstChild.attributes, len=attrs.length, i;
for (i=0; i<len; i++) {
a[attrs[i].nodeName] = attrs[i].nodeValue];
}
return a;
};
var x = getAttributes(inputStr);
x; // => {width:'123', height:123, src:'http://...', ...}
Instead of regexp, use pure JavaScript:
Grab iframe element:
var iframe = document.getElementsByTagName('iframe')[0];
and then access its properties using:
var arr = {
src : iframe.src,
width : iframe.width,
height : iframe.height,
frameborder : iframe.frameborder
};
I would personally do this with jQuery, if possible. With it, you can create a DOM element without actually injecting it into your page and creating a potential security hazard.
var userTxt = '<iframe src="http://www.stackoverflow.com/" width="123" height="123" frameborder="1"></iframe>';
var userInput = $(userTxt);
console.log(userInput.attr('src'));
console.log(userInput.attr('width'));
console.log(userInput.attr('height'));
console.log(userInput.attr('frameborder'));

JavaScript Node.replaceChild() doesn't count new child's innerHtml

While creating a Firefox addon, I've run into a weird problem.
I have an array of nodes, returned by some iterator. Iterator returns only nodes, containing Node.TEXT_NODE as one or more of it's children. The script runs on page load.
I have to find some text in that nodes by regexp and surround it with a SPAN tag.
//beginning skipped
var node = nodeList[i];
var node_html = node.innerHTML;
var node_content = node.textContent;
if(node_content.length > 1){
var new_str = "<SPAN class='bar'>" + foo + "</SPAN>";
var regexp = new RegExp( foo , 'g' );
node_html = node_html.replace(regexp, new_str);
node.innerHTML = node_html;
}
Basic version looked like this, and it worked except one issue - node.innerHTML could contain attributes, event handlers, that could also contain foo, that should not be surrounded with <span> tags.
So I decided to make replacements in text nodes only. But text nodes can't contain a HTML tag, so I had to wrap them with <div>. Like this:
var node = nodeList[i];
for(var j=0; j<node.childNodes.length; j++){
var child = node.childNodes[j];
var child_content = child.textContent;
if(child.nodeType == Node.TEXT_NODE && child_content.length >1){
var newChild = document.createElement('div');
// var newTextNode = document.createTextNode(child_content);
// newChild.appendChild(newTextNode);
var new_html = child_content;
var new_str = "<SPAN class='bar'>" + foo + "</SPAN>";
var regexp = new RegExp( foo , 'g' );
new_html = new_html.replace(regexp, new_str);
newChild.innerHTML = new_html;
alert(newChild.innerHTML);
node.replaceChild(newChild, child);
}
}
In this case, alert(newChild.innerHTML); shows right html. But after the page is rendered, all <div>s created are empty! I'm puzzled.
If I uncomment this code:
// var newTextNode = document.createTextNode(child_content);
// newChild.appendChild(newTextNode);
alert also shows things right, and <div>s are filled with text (textNode adding works ok) , but again without <span>s. And another funny thing is that I can't highlight that new <div>s' content with a mouse in browser.
Looks like it doesn't take new innerHTML into account, and I can't understand why.
Do I do something wrong? (I certainly do, but what? Or, is that a FF bug/feature?)
Since you are in Firefox you can use fun stuff like TreeWalker and Range. You may even be able to get rid of the code that gives you the initial array of nodes.
var walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, null, false);
var range = document.createRange();
var wrapper = document.createElement('span');
wrapper.className = "wrapper";
var node;
var re = /^wrap me$/;
while (node = walker.nextNode()) {
if (re.test(node.textContent)) {
range.selectNode(node);
range.surroundContents(wrapper.cloneNode(true));
}
}
JSBin
You could tweak this so only part of the text node is wrapped by setting the range differently and TreeWalker can be filtered more.
Range / TreeWalker
That code is really odd; why are those three lines outside of the if statement?
I think it should look something like this:
var node = nodeList[i];
for(var j=0; j<node.childNodes.length; j++){
var child = node.childNodes[j];
var child_content = child.textContent;
if(child.nodeType == Node.TEXT_NODE && child_content.length >1){
var newChild = document.createElement('div');
newChild.innerHTML = '<span class="bar">' + child_content + '</span>';
node.replaceChild(newChild, child);
}
Now I can't figure out what was going on with that regex and the replacement stuff; it makes no sense to me in the code you've posted.

Categories

Resources