`.replaceChild()` on `this` Throwing NotFoundError: DOM Exception 8 - javascript

I figured I would get fancy and use vanilla JavaScript during a jQuery event. The idea is that on click of a heading, I want to slide up a div (which works) and replace the tag clicked on to a larger heading.
From what I've read around, this can be caused by the parentNode referencing an element that's not the actual parent, but after checking it appears to be selecting the element that's directly above it.
So... here's the code!
HTML (in Jade)
.policy-container
h6.policy-heading Policies
.policy-list
.content-we-are-hiding
.not-actually-important
jQuery
$('.policy-heading').click(function() {
var self = this;
if (this.classList.contains('closed')) {
$(this).next().slideDown(300);
this.parentNode.replaceChild(self, '<h6 class="policy-heading">Policies</h6>');
} else {
$(this).next().slideUp(300);
this.parentNode.replaceChild(self, '<h2 class="policy-heading closed">Policies</h2>');
}
});
Everything seems pretty standard. Luckily I can just take care of this with jQuery, however I'd rather be using vanilla JS here. Any ideas why this isn't working?

As has been pointed out, replaceChild takes two nodes.
The following will work with native JS wrapped inside jQuery, as you've specified:
$('.policy-heading').click(function () {
var self = this,
h2 = document.createElement('h2'),
h6 = document.createElement('h6');
h2.class = "policy-heading closed";
h2.innerHTML = "Policies";
h6.class = "policy-heading";
h6.innerHTML = "Policies";
if (this.classList.contains('closed')) {
$(this).next().slideDown(300);
this.parentNode.replaceChild(h6, self);
} else {
$(this).next().slideUp(300);
this.parentNode.replaceChild(h2, self);
}
});

replaceChild takes two nodes, you are giving it a node and a string.
It looks like you'd be much better off just sticking with jQuery and using toggle functions for the sliding and class change.

try this :
.click(function(this)
you also need some debugging to understand what is going on I would advice you to use :
console.log(this)
use this :
el = document.createElement('h6');
el.class = "policy-heading";
el.innerHTML = "Policies";
this.parentNode.replaceChild(self, el);

As everyone pointed out, .replaceChild accepts two DOM elements, rather than the string like I was using. I also had its arguments backwards, the first is for the new element, the second is the replaced element.
Example code that works
$('.policy-container').on('click', '.policy-heading', function() {
var self = this,
newElement;
if (this.classList.contains('closed')) {
newElement = document.createElement( 'h6' );
newElement.classList.add('policy-heading');
newElement.innerHTML = 'Policies';
} else {
newElement = document.createElement( 'h2' );
newElement.classList.add('policy-heading');
newElement.classList.add('closed');
newElement.innerHTML = 'Policies';
}
$(this).next().slideDown(300, function() {
self.parentNode.replaceChild( newElement, self );
});
});

Related

Copy text from div innerHtml to textarea

A connected question to this problem with the iframe issue:
Copy div from parent website to a textarea in iframe
I'm trying to copy InnerHtml from a div to a TextArea.
I've made two instances of google translator on the same web page, and I'm trying to apply auto-correction of the first instance to the second instance, without changing the first textarea.
I tried different code:
setInterval(function() {
childAnchors1 = document.querySelectorAll("#spelling-correction > a")[0];
$("#source")[1].val(childAnchors1.text());
}, 100);
setInterval(function copyText() {
$(".goog-textarea short_text")[1].val($("#spelling-correction > a")[0].innerText());
}
, 100);
setInterval(function copyText() {
$("#source")[1].val($("#spelling-correction > a")[0].innertext());
}
, 100);
setInterval(function() {
var finalarea = document.getElementsByClassName("goog-textarea short_text")[1];
var correction = document.querySelectorAll("#spelling-correction > a")[0].innerHTML
document.getElementsByClassName("goog-textarea short_text")[1].value = correction.innerText;
}, 100);
onclick='document.getElementsByClassName("goog-textarea short_text")[1].innerHTML=document.getElementById("spelling-correction > a")[0].innerHTML;'
But nothing of that seems to work, unfortunately...
I would be very grateful for any help.
I should have mentioned this. I used iframe to create the second instance, so simple solutions don't work...
This is the code I used for creating iframe instance:
var makediv = document.createElement("secondinstance");
makediv.innerHTML = '<iframe id="iframenaturalID" width="1500" height="300" src="https://translate.google.com"></iframe>';
makediv.setAttribute("id", "iframeID");
var NewTranslator = document.getElementById("secondinstance");
var getRef = document.getElementById("gt-c");
var parentDiv = getRef.parentNode;
parentDiv.insertBefore(makediv, getRef);
I tried to use this to communicate between the iframe and the parent website:
setInterval(function() {
var childAnchors1 = window.parent.document.querySelectorAll("#spelling-correction > a");
var TheiFrameInstance = document.getElementById("iframeID");
TheiFrameInstance.contentWindow.document.querySelectorAll("#source").value = childAnchors1.textContent;
}, 100);
But it doesn't work...
Ok, I made it work with:
var a = document.createElement('iframe');
a.src = "https://translate.google.com";
a.id = "iframenaturalID";
a.width = "1000";
a.height = "500";
document.querySelector('body').appendChild(a)
And
let iframe = document.getElementById("iframenaturalID");
setInterval(function() {
let source = iframe.contentWindow.document.getElementById("source");
let destination = window.parent.document.querySelector("#spelling-correction > a");
source.value = destination.textContent;
}, 100);
Now it does what I tried to do, however I still get mistake message: Uncaught TypeError: Cannot set property 'value' of null
at eval, which points at this line: source.value = destination.textContent;. It's not a big problem though, but still it's strange that it returns this mistake...
Ok, I was able to solve it by adding setTimeout.
Since a textarea is a form element, neither .innerText or .innerHTML will work. You need to extract its content with the value property (or .val() with JQuery).
And FYI:
It's innerText, not .innertext.
.innerText is a property, not a function, so you don't use () after it.
It's .innerHTML, not .innerHtml.
innerHTML is used when there is HTML in the string that should be
parsed as HTML and .textContent is used for strings that should not
be parsed as HTML. Usually, you don't map the contents of one to the
other.
document.querySelectorAll() scans the entire DOM to find all
matching nodes. If you know you only have one matching node or you
only want the first matching node, that's a waste of resources.
Instead, use .querySelector(), which stops searching after the
first match is found.
Since you are using JQuery, you should be consistent in its use. There's no need for .querySelector() or .querySelectorAll() with JQuery, just use JQuery selector syntax.
Here's an example that shows both the vanilla JavaScript and JQuery approaches using the HTML types that you show in your question with the same id values and nesting structure that you show. You can see that I'm using different selectors to correctly locate the input/output elements.
// Standare DOM queries to get standard DOM objects
let source = document.getElementById("source");
let destination = document.querySelector("#spelling-correction > a");
// JQuery syntax to get JQuery objects:
let jSource = $("#source");
let jDestination = $("#spelling-correction > a");
// Vanilla JavaScript way to set up the event handler and do the work
source.addEventListener("keyup", function(){
destination.textContent = source.value;
});
// JQuery way to set up the event handler and do the work
jSource.on("keyup", function(){
jDestination.text(jSource.val());
});
textarea, div {
border:3px solid grey;
width:500px;
height:75px;
font-size:1.5em;
font-family:Arial, Helvetica, sans-serif;
}
.destination { pointer-events:none; background:#e0e0e0; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<p>Type in the first textarea</p>
<textarea id="source"></textarea>
<div id="spelling-correction">
Did you mean:
</div>
The problem in all the codes you've tried is how to get the text correctly.
Starting with your First example it should be using innerText instead of text() since it's a jquery object and you're returning a DOM object not a jQuery object:
setInterval(function() {
childAnchors1 = document.querySelectorAll("#spelling-correction > a")[0];
$("#source")[1].val(childAnchors1.innerText);
}, 100);
In your Second example and Third one, you need to remove the parentheses from the innerText like:
setInterval(function copyText() {
$(".goog-textarea short_text")[1].val($("#spelling-correction > a")[0].innerText);
}, 100);
I suggest the use of pure js and textContent attribute like:
setInterval(function() {
var childAnchors1 = document.querySelectorAll("#spelling-correction > a")[0];
document.querySelectorAll("#source")[1].value = childAnchors1.textContent;
}, 100);
NOTE: I should point that your HTML code in invalid since you're using duplicate identifier when the id attribute should be unique in the same document.

Appending an element inside the child of another element

I am trying to append a link ("a" tag) to a child of the "topBar" element.
Here is what i've got so far:
document.getElementById('topBar').innerHTML += 'Cookie Clicker Classic';
This puts the link inside the "topBar" element as a new child, but I want it inside the existing child of the "topBar" element. How do I do this? The child is just within a div tag, it has no id... I have done some reasearch on .appendChild but I haven't found any related help, thus why I am asking here...
I would be very appreciative for any ideas or even a solution to be posted.
Thanks,
Daniel
EDIT: topBar has only one child, it is nameless
also, am I doing something wrong with this?
setTimeout(doSomething, 1000);
function doSomething() {
var element = document.getElementById('particles');
if (typeof(element) != 'undefined' && element != null)
{
var newLink = document.createElement('a');
newLink.setAttribute('href', 'http://orteil.dashnet.org/experiments/cookie/');
newLink.target = 'blank';
document.getElementById('topBar').appendChild(newLink);
var del = document.getElementById('links')
del.parentNode.removeChild(del);
return;
} else {
setTimeout(doSomething, 1000);
}
}
EDIT: I have finished! Thanks to everyone for their help, especially Elias Van Ootegem. This is what I used:
var link=document.createElement('a');
link.setAttribute('href', 'http://orteil.dashnet.org/experiments/cookie/');
link.target = 'blank';
link.appendChild(
document.createTextNode('Cookie Clicker Classic')
);
var add = document.getElementsByTagName('div')[1]; //this picked the second div tag in the whole document
if(add.lastChild) add.insertBefore(link,add.lastChild); //appending it to the end of the child
else add.prependChild(link);
First, create the node:
var newLink = document.createElement('a');
//set attributes
newLink.setAttribute('href', 'http://orteil.dashnet.org/experiments/cookie/');
newLink.target = 'blank';//preferred way is using setAttribute, though
//add inner text to link:
newLink.appendChild(
document.createTextNode('Cookie Clicker Classic')//standard way, not innerHTML
);
Then, append the child, using appendChild:
document.getElementById('topBar').appendChild(newLink);
Or, given your update (your deleting some other element), use replaceChild:
document.getElementById('topBar').replaceChild(
newLink,//new
document.getElementById('links')//old, will be removed
);
And you're there!

JavaScript Node assembling

I'm trying to create a function for rendering dom elements and I seem to be getting stuck with assembling more than one dom element before appending.
I've tried this: http://jsfiddle.net/RruyA/1/
And I can't seem to wrap my image with a link.
And using appendChild() where innerHTMl is now (marked with comment in the fiddle) produces an invalid pointer error.
I have a bunch of theories on what might be going wrong, but no solution yet. Help would rock!
Here's the full code:
(function () {
"use strict";
function tag (name, attributes, contents) {
var tag = {};
tag.name = name;
tag.attributes = attributes;
tag.contents = contents
tag.create = function () {
tag.element = document.createElement(tag.name);
for (var prop in tag.attributes) {
tag.element.setAttribute(prop, tag.attributes[prop]);
}
// This is the problem:
tag.element.innerHTML = contents;
}
tag.render = function () {
document.body.appendChild(tag.element);
}
return tag;
}
var p = tag('p', {'id':'details', 'class':'red nice lovely'}, 'Once upon a time in a golden castle on a silver cloud...');
var img = tag('img', {'src':'http://miyazakihayao.blog.com/files/2010/05/castle-in-the-sky-x1.jpg', 'width': '200px', 'alt':'Golden Castle'});
img.create();
img.render();
p.create();
p.render();
var a = tag('a', {'href':'http://google.com', 'target':'_blank'}, img.element);
a.create();
a.render();
}());
Your problem is that you are attempting to add text and HTML elements in the same way. Text will work fine with innerHTML although elements will be coerced to strings, and appendChild will add HTML elements, but you would need to wrap Strings in TextNodes.
So you can choose between those types and it works fine.
// This is a solution
if (contents) {
if (contents instanceof HTMLElement) {
tag.element.appendChild(contents);
}
else {
tag.element.innerHTML = contents;
}
}

id of a link that a function is called from

I hope it's not a problem to post much specific code here, but I figure it will be better explained if everyone can just see it, so I will give you my code and then I will explain my problem.
My code:
function addBeGoneLinks () {
var beGoneClassElems;
var beGoneSpan;
var beGoneLink;
var beGonePrintSafe;
var spacesSpan;
//var middotSpan = document.createElement ('span');
var interactionContainer = document.getElementsByClassName('feedItemInteractionContainer');
for (var i=0; i<children.length; i++)
{
beGonePrintSafe = false;
beGoneClassElems = children[i].getElementsByClassName('beGone')
beGonePrintSafe = true;
if (beGoneClassElems.length == 0)
{
beGoneLink = document.createElement('a');
beGoneLink.href = 'javascript:void(0);';
beGoneLink.appendChild(document.createTextNode('Be Gone'));
beGoneLink.className = 'beGone';
beGoneLink.id = 'beGoneLink' + i.toString();
beGoneLink.addEventListener ("click", function() {beGone();}, false);//This line!
beGoneLink.align = 'right';
spacesSpan = document.createElement('span');
spacesSpan.innerHTML = ' - ';
if (interactionContainer[i] != undefined)
{
interactionContainer[i].appendChild(spacesSpan);
interactionContainer[i].appendChild(beGoneLink);
}
}
}
}
Here I have a function from a Greasemonkey script that I am working on. When one of the links is clicked, my aim is to have it call the function beGone() which will, among other things, remove the whole element a few parents up, thereby removing their sibling's, their parents and their parents' siblings, and one or two levels after that.
My idea was just to get the id of the link that was pressed and pass it to beGone() so that I could then get the parents using its id, but I do not know how to do that. Am I able to have the id of a link passed by the function that it calls? If not, is there any other way to do this?
I am not sure whether I am missing some really simple solution, but I haven't been able to find one rooting around the web, especially because I was unsure how I would search for this specific problem.
Try this:
beGoneLink.addEventListener("click", beGone, false);
beGone = function (evt) {
evt.target; // evt.target refers to the clicked element.
...
}
You can then use evt.target.id, evt.target.parentNode, etc.

dynamically adding/removing a div to html

I want to dynamically create a div element with id="xyz". Now before creating this, I want to remove any other div with id ="xyz" if it exists. How can i do it?
var msgContainer = document.createElement('div');
msgContainer.setAttribute('id', 'xyz'); //set id
msgContainer.setAttribute('class', 'content done'); // i want to add a class to it. it this correct?
var msg2 = document.createTextNode(msg);
msgContainer.appendChild(msg2);
document.body.appendChild(msgContainer);
}
How can i remove all divs with id =xyz if they exist before executing above code?
Removing:
var div = document.getElementById('xyz');
if (div) {
div.parentNode.removeChild(div);
}
Or if you don't control the document and think it may be malformed:
var div = document.getElementById('xyz');
while (div) {
div.parentNode.removeChild(div);
div = document.getElementById('xyz');
}
(Alternatives below.)
But you only need the loop with invalid HTML documents; if you control the document, there's no need, simply ensure the document is valid. id values must be unique. And yet, one sees plenty of documents where they aren't.
Adding:
var msgContainer = document.createElement('div');
msgContainer.id = 'xyz'; // No setAttribute required
msgContainer.className = 'someClass' // No setAttribute required, note it's "className" to avoid conflict with JavaScript reserved word
msgContainer.appendChild(document.createTextNode(msg));
document.body.appendChild(msgContainer);
If you don't like the code duplication in my loop above and you think you need the loop, you could do:
var div;
while (!!(div = document.getElementById('xyz'))) {
div.parentNode.removeChild(div);
}
or
var div;
while (div = document.getElementById('xyz')) {
div.parentNode.removeChild(div);
}
...although that last may well generate lint warnings from various tools, since it looks like you have = where you mean == or === (but in this case, we really do mean =).

Categories