I have an HTML page like this
<div id="div"></div>
I want do add there two elements (the problem appears with a and button) using JavaScript
a1 = document.createElement("a")
a1.innerText = "element1"
div.appendChild(a1)
a2 = document.createElement("a")
a2.innerText = "element2"
div.appendChild(a2)
The result is <div id="div"><a>element1</a><a>element2</a></div>.
It differs from
<div id="div">
<a>element1</a>
<a>element2</a>
</div>
The second variant has some space beetween words.
Why does this happen?
What should I do in order to get some fixed amount of space (I need it because originally worked with buttons)? What is the proper actions in this situation?
The spaced-out version has text nodes between the elements. The other version does not.
Usually, one should make sure that one's code is designed such that text nodes are completely irrelevant - whether they exist or not, the code works regardless. This is quite easy to do. Biggest thing to keep in mind that parentNode.childNodes returns a collection of all children, including text nodes, whereas parentNode.children returns a collection of only element children.
If you wanted to add text nodes like in your second code, you can pass text strings to insertAdjacentText:
const div = document.querySelector('div');
// First text node
div.insertAdjacentText('beforeend', `
`);
a1 = document.createElement("a")
a1.innerText = "element1"
div.appendChild(a1)
// Second text node
div.insertAdjacentText('beforeend', `
`);
a2 = document.createElement("a")
a2.innerText = "element2"
div.appendChild(a2)
// Third text node
div.insertAdjacentText('beforeend', `
`);
console.log('"' + div.innerHTML + '"');
<div></div>
But that's really weird. While you can do it if you really want to, it doesn't accomplish anything useful.
Related
This is quite a challenging problem. I haven't seen it solved anywhere on Stack Overflow. So I decided to post it.
0 ----17----+ +---30---
| | | +----47
| | | |
<div>ABC<b>B Elem<i>Italic</i>ent</b> DEF</div>
| |
+---8--- ---37--+
Action: Let's say Element <i> tag is clicked.
Problem: Create a function that returns coordinates [17,30]
Note: The coordinates are start and end caret position, represented as 0-based index, in original HTML source code, encompassing only the element that was clicked. May assume normalized HTML nodes as in id = "" becomes id="". (But extra credit, if it doesn't.)
Example 2: If <b> tag was clicked. The script should return [8, 37] because it is the start/end caret position encompassing the B tag.
Example 3: If ABC text or DEF text was clicked, return value is [0,47]
Walk the parent chain until you hit whatever tag you consider to be a container (<div> in your case, apparently).
Use the parent's childs to locate the particular child you're coming from, in case you have two or more identical childs, like in from <i>two</i> to <i>two</i> to <i>two</i> <i>two</i>.
That should give you the child offset within the parent. You can then cumulate the offsets until you hit the div tag or whatever other container element.
Ending position is just this offset plus the clicked element length.
And after two days of solving this, I am posting my own solution.
I tried to parse the DOM and count characters manually, at first. But that was more complicated than it had to be.
Credit: Thanks to kuroi neko, who suggested the end caret position is just start position + length of the HTML encompassing the clicked tag.
Note: I am manually removing <tbody> tags, before calculating caret values. This is because, even original HTML does not contain them, during normalization process (which takes place during innerHTML or outerHTML call,) they are auto-inserted. It's a personal preference, if you're building a text editor that needs this functionality -- to leave them alone and update original HTML.
On the other hand, if you prefer the purist approach, and want to consider the original HTML intact, as it was written by the author of said HTML, then you may want to remove <tbody> manually. This also assumes that you take responsibility for taking care of all other cases, similar to these. Whatever they might be. (Not included in the solution below.)
Solution: Considering textarea (HTML source editor) and #preview are two separate elements representing the same HTML.
$(document).ready(function() {
// Normalize source code
var normalized_html = document.getElementById("preview").innerHTML;
// Remove all TBODY tags (they are auto-inserted, even if not present in original HTML)
normalized_html = normalized_html.replace(/<tbody>/g, '');
$("#textarea").html(normalized_html);
$("#preview").on("click", function(event) {
// Get clicked tag HTML
var tag = event.target.outerHTML;
// Get original HTML before split character is inserted
var orig_html = document.getElementById("preview").innerHTML;//.replace(/<preview>/g, '').replace(/<\/preview>/g, '');
// Insert unique separator just before the tag that was clicked, to mark beginning
$(event.target).before("[*-*]");
// Get preview source code
var html = document.getElementById("preview").innerHTML;
// Remove line breaks
html = html.replace(/\r|\n/g, '');
// Remove tags that were auto-inserted by native normalization process that did not exist in original HTML.
html = html.replace(/<tbody>/g, '');
var before_split = html;
// Split HTML at the tag that was clicked
html = html.split("[*-*]")[0];
// Restore preview to original HTML
$("#preview")[0].innerHTML = orig_html;
// Get start and end of caret in source code
var caret_start = html.length;
var caret_end = caret_start + tag.length;
console.log("caret start = " + caret_start + " end = " + caret_end);
});
});
You achieve that by simply using Descop library.
// Get the source html code of target document
var html = yourFunctionToGetHTML();
// Get the target document itself
var dom = yourFunctionToGetDocument();
// Get the element you want to found in source code
var element = document.getElementById("target-element");
// Create an instance of Descop
var descop = new Descop();
// Connect document
descop.connectDocument(dom);
// Connect source code
descop.connectSource(html);
// Get element position in source code
var position = descop.getElementPosition(element);
// eg. position => { start: 320, end: 480 }
My question is that I have an html code <p> Hello World </p>
And want to change the css of every letter using JavaScript. Essentially, I will change the background color to make an animation. Is there a way to do this without making a span or some sort of tag around every letter and going through all that struggle?
I have my string array with colors and a method to call the correct color (data-index attribute).
Thanks!
EDIT: I have the entire word changing color and thought of an idea by making a function that iterates over the indexes of the innerHTML string and assigns a data-index to the letter's span by editing the function provided below by Cymen. Is this a good approach?
No, you will need to use a tag that supports background-color. You can easily wrap a string of characters in spans like so:
function wrapInSpans(string) {
return '<span>' + string.split('').join('</span><span>') + '</span>';
}
You would have to use a JavaScript function to wrap each character in a <span>.
window.onload = function() { // when everything loads, run the function
var elem = document.getElementById( "someId" );
var text = elem.innerHTML; // get the <p>'s text content
elem.innerHTML = ""; // then make the <p> empty
for( var i=0; i<text.length; i++ ) { // for each character in the text
elem.innerHTML += "<span>"+text[i]+"</span>";
}
};
Remember to change "someId" to the id of your <p> element.
You can access each individual character inside the for loop with text[i].
This would take quite a bit of code to spell out completely, but, if it's very important in your case to not actually add some type of wrapping element, then I believe this would be possible via a dynamically generated background image.
Roughly the steps would be:
Create a Range with a start and end around each character in the .textContents of the element you care about.
.getBoundingClientRect() on each range to get its rendered dimensions.
Draw rectangles of the desired color to a <canvas>.
Export the <canvas> as a data URI.
Use the data URI as a background-image.
Repeat for each block displayed element that you care about.
Be advised that there will, no doubt, be various edge cases in this approach and possible browser support limitations. Obviously just wrapping each character is a much simpler.
Imagine that I have a couple of statements as following:
var n1 = oDiv.firstChild;
var n2 = oDiv.lastChild.previousSibling.firstChild; //know this crazy, but for knowledge sake
How can I apply various styles like the following (which usually works only for "element" types and not "node" types):
//does not work
n1.style.borderWidth = "1px";
n1.style.borderColor = "#336699";
n2.style.borderStyle = "solid";
Also, is there any way to typecast "node" to "element" in JavaScript?
update:
The code I am trying to accomplish above is here http://jsfiddle.net/anthachetta/4mXrd/
The DOM works exactly the same way as HTML does. That makes sense since the DOM was designed to model HTML as objects. So, what do you do if you want to make the following bold:
Hello World
From your code, what you're trying to do is something like this:
style=font-weight:bold Hello World
Obviously that wouldn't work because it's not valid HTML. What you'd normally do is this:
<span style='font-weight:bold;'>Hello World</span>
So you need to do the same in the DOM:
// Assume you have a div "div" and the first child
// is the text node "Hello World"
var hello_world = div.firstChild;
// Now, you want to make Hello World bold.
// So you need to create a span:
var span = document.createElement('span');
span.style['font-weight'] = 'bold';
// Now wrap hello_world in the span:
span.appendChild(div.removeChild(hello_world));
The above is actual working DOM code that does what you want. But beware:
Standards compliant browsers also count whitespace as nodes.
IE doesn't count whitespace as nodes.
For example, if your HTML looks like this:
<div>
<span>Hi</span>
</div>
the standard says your DOM must look like this:
div +
|---- text node (whitespace)
'---- span +
'---- text node (Hi)
but IE does what most people probably expect:
div +
'---- span +
'---- text node (Hi)
This means that you can't blindly trust node.firstChild without checking to see if it's what you expect.
There are a few problems.
What is with the lastChild.previousSibling.firstChild mess? All that's doing is confusing you.
That mess of properties is returning you a text node, not an element.
lastChild gives you "a line", previousSibling gets the <i> tag, then firstChild returns "just". You are trying to apply a style to a text node, which you can't do, you need to style an element.
You are trying to apply the style to the nodeValue. That's a string. You can use parentNode to get to the <i> tag from the text node.
oDiv.lastChild.previousSibling.firstChild.parentNode.style.color = "#FF0000";
DEMO: http://jsfiddle.net/NTICompass/4mXrd/2/
I am wondering if it is possible to remove a tag but leave the content in tact? For example, is it possible to remove the SPAN tag but leave SPAN's content there?
<p>The weather is sure <span>sunny</span> today</p> //original
<p>The weather is sure sunny today</p> //turn it into this
I have tried using this method of using replaceWith(), but it it turned the HTML into
<p>
"The weather is sure "
"sunny"
" today"
</p>
EDIT : After testing all of your answers, I realized that my code is at fault. The reason why I keep getting three split text nodes is due to the insertion of the SPAN tag. I'll create another question to try to fix my problem.
<p>The weather is sure <span>sunny</span> today</p>;
var span=document.getElementsByTagName('span')[0]; // get the span
var pa=span.parentNode;
while(span.firstChild) pa.insertBefore(span.firstChild, span);
pa.removeChild(span);
jQuery has easier ways:
var spans = $('span');
spans.contents().unwrap();
With different selector methods, it is possible to remove deeply nested spans or just direct children spans of an element.
There are several ways to do it. Jquery is the most easy way:
//grab and store inner span html
var content = $('p span').html;
//"Re"set inner p html
$('p').html(content);
Javascript can do the same using element.replace. (I don't remember the regex to do the replace in one stroke, but this is the easy way)
paragraphElement.replace("<span>", "");
paragraphElement.replace("</span>", "");
It's just three text nodes instead of one. It doesn't make a visible difference does it?
If it's a problem, use the DOM normalize method to combine them:
$(...)[0].normalize();
$(function(){
var newLbl=$("p").clone().find("span").remove().end().html();
alert(newLbl);
});
Example : http://jsfiddle.net/7gWdM/6/
If you're not looking for a jQuery solution, here something that's a little more lightweight and focused on your scenario.
I created a function called getText() and I used it recursively. In short, you can get the child nodes of your p element and retrieve all the text nodes within that p node.
Just about everything in the DOM is a node of some sort. Looking up at the following links I found that text nodes have a numerical nodeType value of 3, and when you identify where your text nodes are, you get their nodeValueand return it to be concatenated to the entire, non-text-node-free value.
https://developer.mozilla.org/en/nodeType
https://developer.mozilla.org/En/DOM/Node.nodeValue
var para = document.getElementById('p1') // get your paragraphe
var texttext = getText(para); // pass the paragraph to the function
para.innerHTML = texttext // set the paragraph with the new text
function getText(pNode) {
if (pNode.nodeType == 3) return pNode.nodeValue;
var pNodes = pNode.childNodes // get the child nodes of the passed element
var nLen = pNodes.length // count how many there are
var text = "";
for (var idx=0; idx < nLen; idx++) { // loop through the child nodes
if (pNodes[idx].nodeType != 3 ) { // if the child not isn't a text node
text += getText(pNodes[idx]); // pass it to the function again and
// concatenate it's value to your text string
} else {
text += pNodes[idx].nodeValue // otherwise concatenate the value of the text
// to the entire text
}
}
return text
}
I haven't tested this for all scenarios, but it will do for what you're doing at the moment. It's a little more complex than a replace string since you're looking for the text node and not hardcoding to remove specific tags.
Good Luck.
If someone is still looking for that, the complete solution that has worked for me is:
Assuming we have:
<p>hello this is the <span class="highlight">text to unwrap</span></p>
the js is:
// get the parent
var parentElem = $(".highlight").parent();
// replacing with the same contents
$(".highlight").replaceWith(
function() {
return $(this).contents();
}
);
// normalize parent to strip extra text nodes
parentElem.each(function(element,index){
$(this)[0].normalize();
});
If it’s the only child span inside the parent, you could do something like this:
HTML:
<p class="parent">The weather is sure <span>sunny</span> today</p>;
JavaScript:
parent = document.querySelector('.parent');
parent.innerHTML = parent.innerText;
So just replace the HTML of the element with its text.
You can remove the span element and keep the HTML content or internal text intact. With jQuery’s unwrap() method.
<html>
<head>
<script src="https://code.jquery.com/jquery-1.12.4.min.js"></script>
<script type="text/javascript">
$(document).ready(function(){
$("button").click(function(){
$("p").find("span").contents().unwrap();
});
});
</script>
</head>
<body>
<p>The weather is sure <span style="background-color:blue">sunny</span> today</p>
<button type="button">Remove span</button>
</body>
</html>
You can see an example here: How to remove a tag without deleting its content with jQuery
Consider following DOM fragment:
<div id="div-1">foo</div>
<div id="div-2">bar</div>
Is it possible to insert HTML string (EDIT: that contains tags to render) between divs without wrapping it in another div (EDIT: or some other tag) created via document.createElement and setting its innerHTML property?
Most browsers support element#insertAdjacentHTML(), which finally became standard in the HTML5 specification. Unfortunately, Firefox 7 and lower don't support it, but I managed to find a workaround that uses ranges to insert the HTML. I've adapted it below to work for your scenario:
var el = document.getElementById("div-2"),
html = "<span>Some HTML <b>here</b></span>";
// Internet Explorer, Opera, Chrome, Firefox 8+ and Safari
if (el.insertAdjacentHTML)
el.insertAdjacentHTML ("beforebegin", html);
else {
var range = document.createRange();
var frag = range.createContextualFragment(html);
el.parentNode.insertBefore(frag, el);
}
Live example: http://jsfiddle.net/AndyE/jARTf/
This does it for straight text, which is how I read your original question (see below for an update for strings that include tags):
var div = document.getElementById('div-2');
var textNode = document.createTextNode('your text');
div.parentNode.insertBefore(textNode, div);
Live example
If you start with:
<div id="div-1">foo</div>div id="div-2">bar</div>
(note that there's no whitespace between them) then the result of the above is exactly what you would get with this HTML:
<div id="div-1">foo</div>your text<div id="div-2">bar</div>
If you really have that whitespace between the divs, you'll already have a text node there and the above will insert another one next to it. For virtually all intents and purposes, that doesn't matter, but if that bothers you, you can append to the existing text node instead if you like:
var text = 'your text';
var div = document.getElementById('div-2');
var prev = div.previousSibling;
if (prev && prev.nodeType == 3) { // 3 == TEXT_NODE
// Prev is a text node, append to it
prev.nodeValue = prev.nodeValue + text;
}
else {
// Prev isn't a text node, insert one
var textNode = document.createTextNode(text);
div.parentNode.insertBefore(textNode, div);
}
Live example
Links to W3C docs: insertBefore, createTextNode
Including HTML tags
In your revised question you've said you want to include tags to be interpreted in doing all this. It's possible, but it's roundabout. First you put the HTML string into an element, then you move the stuff over, like this:
var text, div, tempdiv, node, next, parent;
// The text
text = 'your text <em>with</em> <strong>tags</strong> in it';
// Get the element to insert in front of, and its parent
div = document.getElementById('div-2');
parent = div.parentNode;
// Create a temporary container and render the HTML to it
tempdiv = document.createElement('div');
tempdiv.innerHTML = text;
// Walk through the children of the container, moving them
// to the desired target. Note that we have to be sure to
// grab the node's next sibling *before* we move it, because
// these things are live and when we moev it, well, the next
// sibling will become div-2!
node = tempdiv.firstChild;
next = node.nextSibling;
while (node) {
parent.insertBefore(node, div);
node = next;
next = node ? node.nextSibling : undefined;
}
Live example
But here there be dragons, you have to select the container element as appropriate to the content you're inserting. For instance, we couldn't use a <tr> in your text with the code above because we're inserting it into a div, not a tbody, and so that's invalid and the results are implementation-specific. These sorts of complexities are why we have libraries to help us out. You've asked for a raw DOM answer and that's what the above is, but I really would check out jQuery, Closure, Prototype, YUI, or any of several others. They'll smooth a lot of stuff over for you.
var neuB = document.createElement("b");
var neuBText = document.createTextNode("mit fettem Text ");
neuB.appendChild(neuBText);
document.getElementById("derText").insertBefore(neuB, document.getElementById("derKursiveText"));
You search for: insertBefore
Using jquery it is very simple:
$("#div-1").after("Other tag here!!!");
See: jquery.after
It is obvious that javascript is not a pure javascript solution.