How to overwrite html element from javascript? - javascript

I have HTML page with some HTML element with ID="logo". I need to create JS script (with no external libs calls) that will overwrite that html element with other HTML element like "<div id=logo> stuff inside </div>".

Most of the time, it's just the content you want to replace, not the element itself. If you actually replace the element, you'll find that event handlers attached to it are no longer attached (because they were attached to the old one).
Replacing its content
Replacing the element's content is easy:
var element;
element = document.getElementById("logo");
if (element) {
element.innerHTML = "-new content-";
}
The innerHTML property has only recently been standardized, but is supported by all major browsers (probably most minor ones, too). (See notes below about innerHTML and alternatives.)
Replacing the element iself
Actually replacing the element itself is a little harder, but not much:
var element, newElement, parent;
// Get the original element
element = document.getElementById("logo");
// Assuming it exists...
if (element) {
// Get its parent
parent = element.parentNode;
// Create the new element
newElement = document.createElement('div');
// Set its ID and content
newElement.id = "logo";
newElement.innerHTML = "-new content here-";
// Insert the new one in front of the old one (this temporarily
// creates an invalid DOM tree [two elements with the same ID],
// but that's harmless because we're about to fix that).
parent.insertBefore(newElement, element);
// Remove the original
parent.removeChild(element);
}
Notes on innerHTML and other DOM manipulation techiques
There are a number of wrinkles around using innerHTML in certain browsers, mostly around tables and forms. If you can possibly use a library like jQuery, Prototype, etc., I'd do so, as they've got workarounds for those issues built-in.
Alternatively, you can use the various other DOM methods rather than innerHTML (the same ones I used for creating the div and adding/removing, above). Note that in most browsers, doing any significant amount of markup by doing a bunch of createElement, appendChild, etc., calls rather than using innerHTML will be dramatically slower. Parsing HTML into their internal structures and displaying it is fundamentally what browsers do, and so they're highly optimized to do that. When you go through the DOM interface, you're going through a layer built on top of their internal structures and not getting the advantage of their optimizations. Sometimes you have to do it that way, but mostly, innerHTML is your friend.

Do you really need to 'replace' the element or can you just toggle its visibility? This is a technique that's much simpler and will be more efficient. Most importantly it keeps the content (html) separated from the behavior (javascript).
function toggle() {
document.getElementById("logo").style.display="none";
document.getElementById("element_to_show").style.display="block";
}
see T.J.'s answer if you actually want to replace the element.

Do you really need to 'replace' the element or can you just toggle its visibility? This is a technique that's much simpler and will be more efficient. Most importantly it keeps the content (html) separated from the behavior (javascript).

Related

Adding an html element around text that does not affect the page (for tooltip)

I have built a tooltip plugin here:
http://shawntabrizi.com/cryptip/
Source here: https://github.com/shawntabrizi/cryptip/blob/master/webextension/content.js
The basics here is that I use Tippy.js to add a tooltip around certain matching text to display price information.
To do this, I look through the HTML body, find any text nodes, and then search that text node for the matching text. If I find matching text, I modify the text to include a <span> with a certain class in order to trigger Tippy.js.
Here is the relevant code:
if (node.nodeType === 3) {
var text = node.nodeValue;
if (reCoins.test(text)) {
text = text.replace(reCoins, function (a, b) {
console.info('Adding cryptip to:' + b, element.tagName)
let priceString = createPriceString(coindict, b, currency, time);
return `<span class="cryptip" title="${priceString}">${b}</span>`;
});
var replacementNode = document.createElement('span');
replacementNode.innerHTML = text;
node.parentNode.insertBefore(replacementNode, node);
node.parentNode.removeChild(node)
}
}
However, I have received reports that on some pages, the span element is picking up styling which affects the rendering of the page. One example of a page is https://yout.com/ which has a default styling on span elements nested in certain areas creating a problem like this:
I tried a solution where I set the CSS on the element to be:
.cryptip {
all: unset;
}
However, the page seems to be loading other styles on top of it:
So I guess my questions are:
Is there some CSS trick I can do to avoid my element being styled? Maybe create a CSS styling with every possible style element for a span, and set it to default with the !important attribute.
Is there another 'psudo' element in HTML that I can use which does not affect the page rendering? Something like <foo class="cryptip"></foo> should work for getting tippy on the page, but does there exist such an element?
If you use an HTML Tag declaration that isn't recognized the node is created from an HTMLUnknownElement prototype, which doesn't have its own properties and methods, but acts as a pass-through to inherit everything from HTMLElement. HTMLElement is the prototype that offers your most base element methods and properties such as offsetHeight and style
You can imagine then that there is no designated pseudo element because any unassigned tag can be used, though if you can style the specific tag you're looking to replace instead that would obviously be preferred for posterity.
If you have to, in your current use case, use an unassigned tag, because of it's prototype it is setup like other elements and you will be able to search for it in the DOM. A section tag is directly inherited from the HTMLElement prototype, and you can think of the unknown tag similarly since all it has between it and the HTMLElement prototype is an empty object. It's also worth noting that there is a customElements spec being looked at by the w3c but its support is sparse.
To summarize there aren't any real drawbacks currently that I'm aware of except maintainability of the codebase(you're going to want to be sure to document well that you are adding a tag) and you may have to be prepared to change it if, for instance, in the future Custom Elements require a declaration - and always keep in mind that there's no guarantee it will always work as well as it does now since there is no spec on the issue and Browsers are fickle.

Express item.parentElement.remove() in a way that is supported by IE

From what I understand, the
remove()
function is not supported in IE. I have a JS function that creates a div and appends it to an existing list.The div contains another div styled as a button (this is the 'item' in the title, that's what I called it when I got it from HTML), which, on click, removes its parentNode (and consequently itself) from the DOM (by means of remove()), though it still 'exists' in that JavaScript can read it's data and stuff. I need a way to remove it from the DOM, as well as all of it's child elements. Setting it's innerHTML equal to nothing will not work, nor will setting it's display to none. Any idea how to do this in a way compatible with IE?
Any help appreciated, please no jQuery or other frameworks.
Anytime you would use .remove(), you can always just use .removeChild() instead with slightly different code around it.
So, if you wanted to do to remove the parent of a given node:
item.parentElement.remove();
Then, you can instead do this:
var p = item.parentNode;
p.parentNode.removeChild(p);
If you want to put this in a utility function, you can do this:
function removeNode(node) {
node.parentNode.removechild(node);
}
And, in your case, instead of item.parentElement.remove() you would call it like this:
removeNode(item.parentNode);
Note, I'm using parentNode instead of parentElement since you appear to want IE compatibility with older versions of IE. parentNode and parentElement are not exactly the same, but it's very rare where parentNode would be different than parentElement (in fact, I can't think of a case in a normal DOM where it wouldn't be appropriate to use parentNode). See Difference between DOM parentNode and parentElement for a discussion of the differences.

Why is element empty in IE after being removed from the DOM?

The following HTML and JavaScript is taken from parts of this jsFiddle:
http://jsfiddle.net/stephenjwatkins/2j3ZB/3/
HTML:
<p class="source">
Source
</p>
<div id="target">
<p class="dummy">
Target
</p>
</div>
<button id="transfer-button">Transfer</button>
JavaScript:
var sourceEl = $('.source');
var targetEl = $('#target');
$('#transfer-button').click(function() {
targetEl.html('<p class="dummy">Transferring...</p>');
setTimeout(function() {
// Source element will be empty on second attempt to append
targetEl.html('').append(sourceEl);
}, 750);
return false;
});​
Note that the setTimeout and dummy text is there simply for a visual indicator.
As one can see, after the source element is appended and removed once from the DOM, IE (all versions) will add an empty element to the DOM upon any further appends; whereas, all other browsers will add the correct, non-empty element.
Another aspect that adds to the confusion is that the sourceEl still has element information (e.g. sourceEl.attr('class') will return "source").
I know of methods to mitigate the issue (e.g. sourceEl.clone()), but it would be nice to have a better understanding as to why IE is behaving differently to avoid any related problems in the future.
What is causing the source element to be uniquely empty in IE after once replacing the element?
First let's highlight the important parts:
(first click) Takes the source element and put it inside the target element;
(second click) Empties the target element and appends a new child (p.dummy) to it, effectively removing source from the DOM;
Empties the target element and tries to re-append source, which is no longer present in the DOM.
At first look, this wouldn't work in any browser as the source element has already been removed from the DOM. The "magic" here is JavaScript's Garbage Collector. Browsers see that sourceEl is still scoped (inside the setTimeout closure) and do not trash the referenced DOM element inside of the sourceEl jQuery object.
The issue here is not JScript (Microsft's Javascript implementation)'s Garbage Collector, but rather how JScript handles the DOM parsing when setting an element's innerHTML.
Other browsers will simply detach all childNodes (whose will be collected by GC when there are no more active references) and parse the passed html string into DOM elements appending them to the DOM. Jscript, on the other hard, will also erase the detached childNodes' innerHTML/childNodes. Check this fiddle for an illustration.
The element, in fact, does still exist in IE and is appended to the DOM:
It just has no childNodes anymore.
To prevent this kind of behavior, .clone() the element (as mentioned in the question) or .detach() it before calling .html() on its parent, if you intend to re-use the element instead of "overwriting" it.
Here's a fiddle using .detach() before the element is overwritten, works fine in all browsers.
In my mind, IE is behaving correctly, and the other browsers are magical for working.
It all stems from when you call the line:
targetEl.html('<p class="dummy">Transferring...</p>');
This removes the sourceEl element from the page. So it no longer exists. I guess other browsers are remembering the DOM object as there is still a variable referencing it. But IE recognizes this as no longer existing on the page, so it loses the reference.
As you mentioned, I would recommend cloning the object when you click. This creates a new object in JavaScript. Luckily, overwriting same variable works.
sourceEl = sourceEl.clone();
http://jsfiddle.net/AbJQE/3/
edit You can also remove any possibly existing original source objects before you insert this new one. This fixes the problem for trigger happy clickers:
setTimeout(function() {
$('.source').remove();
targetEl.html('').append(sourceEl);
}, 750);

How to insert created object into a div

I created an iframe using jQuery that I want to insert into an existing div element. However, when I use innerHTML to insert it, it shows up as: "[object HTMLIFrameElement]"
What could be the reason for this?
Here is the example: http://jsfiddle.net/MarkKramer/PYX5s/2/
You want to use the appendChild method rather than innerHTML. Change the last line in the JSFiddle from
iframediv.innerHTML = iframe;
to
iframediv.appendChild(iframe);
Edit to actually answer your question:
Your variable iframe is a reference to a DOM element. It's object representation is an <iframe> element while its textual representation is simply [object HTMLIFrameElement].
By using innerHTML you are attempting to insert its textual representation into the DOM. This is just how the method works. You may come across JS code where elements are added to the DOM via innerHTML, but it's always with text, e.g.
element.innerHTML = '<div>some text</div>';
In this case the browser will correctly add a <div> node as a child of element.
For your <iframe> element to be inserted into the DOM using the variable iframe, you must use the appendChild method which will add the IFrame object as a child node to iframediv.
$('#iframecontainer').append(iframe);
instead of
var iframediv = document.getElementById('iframecontainer');
iframediv.innerHTML = iframe;
should fix the problem
var new_iframe = $("<iframe></iframe>");
new_iframe.appendTo($("#div_to_insert_into"));
The idea behind (most) of the posted solutions is that you can work with your iframe and it's container as jQuery objects instead of regular dom elements. A jQuery object is a reference to a div or an iframe that has access to all of jQuery's awesome methods... like .append() and .click().
Generally speaking, jQuery's real purpose is to turn lines of code like
var iframediv = document.getElementById('iframecontainer');
...into ...
var iframediv = $("#iframecontainer");
...which you can then use to do with whatever you please, like
iframediv.appendTo("#anotherDiv");
Good luck.

jQuery: getting next node (also text nodes)

I've a jQuery object and I shall retrieve the next sibling node, which may be a text node.
For example:
<div id="test">some text<br/>other text</div>
var test = $("#test");
var br = $("br", test);
alert(br.next().length); // No next ELEMENTS
alert(br.get(0).nextSibling.nodeValue); // "other text"
alert(br.get(0).nextSibling.nextSibling); // null
The DOM level 2 allows to get the next NODE sibling, but jQuery next() works on elements (nodeType 1, I guess).
I am asking this because I'm already using jQuery and I prefer to don't touch the DOM nodes directly (also because jQuery itself may provide a layer of abstraction from DOM and may run where DOM level 2 is not supported, but this is only a thought).
If jQuery doesn't provide this, shall I use DOM like above, or there are better options?
EDIT: I forgot something: I don't want to get ONLY text elements, but any kind of node, just as nextSibling does.
I'm using .contents() to iterate over the content, but this is pretty annoying (and slow, and many other bad things) when you just need the next node and DOM provides a solution.
EDIT2: Looking jQuery source code, it seems it relies on nextSibling.
Use the DOM. Don't be scared of it; it's easy and you already seem to know what to use. jQuery is built on top of the DOM and for this kind of thing, using the DOM will in fact work in more browsers than the jQuery version.
To my knowledge, there is no method in jquery like nextSibling in javaScript which also returns text elements.But you can go to the parent element and use contents() as it will also consider text elements.

Categories

Resources