document.body.appendChild to add, but parentNode.removeChild to remove? - javascript

I've decided it's time for me to learn javascript in a more formal and less haphazard way than I've used it in the past.
So I went to developer.mozilla.org to take their tutorial.
One of the first teaching exercises they provide is in the article entitled,"A First splash into JavaScript" at https://developer.mozilla.org/en-US/docs/Learn/JavaScript/First_steps/A_first_splash, in which they create a simple number guessing game.
In that game's code, the exercise creates a function called setGameOver which creates a button labeled, "Start New Game".
The code for that button is:
document.body.appendChild(resetButton);
This is followed by another function called resetGame, in which this button is removed from the page:
resetButton.parentNode.removeChild(resetButton);
My question is: if the code to add the button is document.body.appendChild, why isn't the code to remove the button something like document.body.removeChild?
And why do we have to use parentNode.removeChild instead?
This is the kind of thing with coding tutorials that drives me nuts. They introduce a new command and they don't explain why. They just say in effect, "This is how you remove the button" and they leave it at that. Ugh!

There's a lot of ways to remove and add an element to the page, this just happens to be the way that the tutorial author chose to demonstrate that. Perhaps their intention was to demonstrate that you can in fact access document.body through resetButton.parentNode, we will never know.
The main point here is that you can remove the button by accessing it's parent node. In this case, we know for a fact that document.body is its parent node (we added the button to that element, so it must be the parent), so in this case resetButton.parentNode and document.body refer to the same object, and are functionally equivalent.
var node1 = document.body;
node1.appendChild(resetButton);
// resetButton is now a child of node1 (and document.body, its the same object)
var node2 = resetButton.parentNode;
// we now know that node2 == node1 so we could do either of the following:
node1.removeChild(resetButton);
// -- or --
node2.removeChild(resetButton);
If you have the reference to a specific html node (in this case, resetButton) and don't know what the parent node is, then using .parentNode to find the parent would be the right way to do it. If you do know the parent (because you just added it) then it doesn't really matter which way you do it.

Related

How do I find the React DOM node that represents its actual html element?

Here's my problem, in case you're interested: I want to find the text selected by the user and insert a tag at the start and end of that selection (to highlight the text).
The best approach to find the selection, as quoted by the top minds of StackOverflow, is to use window.getSelection().
This, in combination with getRangeAt(0), turns up a list of things, where startContainer and endContainer look especially promising. startContainer.parentNode points directly to the p tag I started the selection in. Great!
However, how do I actually know which element this represents in the React DOM? How do I manipulate the right thing?
window.addEventListener("mousedown", function(e){console.log(e)});
using this event listener (or better use mouseup), you can find all the data about the target, example: if you copy/paste this piece of code into the console and see the log, you'll find all info about this event including the target, timestamp, shift key, control key, x,y, which mouse button, and all relevant information. if you combine this with what you described earlier you will get the results you need
EDIT:
As Mox had indirectly mentioned... When React re-renders, all the of the new nodes would be over-written and the highlighting would be lost.
This option as it is won't work well with React.
I'm going to leave this answer here since it is an option if the highlighted text is not being re-rendered often.
I believe the [surroundContents][1] method will be of great use to you.
According to the documentation, you can create a new node. Setup the class attribute on that new node. This class should enable the highlighting you want.
Then you can pass the new node into the surroundContents method to place the content selected by the range into your new node.
Here is an example from the documentation:
var range = document.createRange();
var newNode = document.createElement("p");
range.selectNode(document.getElementsByTagName("div").item(0));
range.surroundContents(newNode);
I believe you already have a range object, so you would just need to create a new node to wrap the highlighted text and use the surroundContents method.
I do see 1 major concern with this method.
If a user highlights text from inside a container and outside the same container, a situation like following may occur:
<p>
some text
<span class="highlighting">
some more text
</p>
some other text
</span>
I do not know how surroundContents will handle this, so this may be become a problem for you.

Destroying Selectize.js instances

I am loading an ajax form with inputs that I apply .selectize() to. I run into issues when I close and reopen the form because there are instances of the Selectize constructor function that are still around.
Is there a way to remove these instances when I close the form? I can see these objects build up as I open and close the form by looking through firebug in the DOM under Selectize.count. How do I access these instances and destroy them?
I have tried this:
instance1[0].selectize.destroy();
instance2[0].selectize.destroy();
Assigned the variables like this:
instance1 = $('#preferences_sport').selectize({
//custom code
});
instance2 = $('#preferences_sport').selectize({
//custom code
});
The Selectize.count continues to build up and I am not sure where to go from here.
Here is a JSFiddle where I show the objects building up
So I see what you are saying now that the fiddle was added. I began by searching the documentation for that count property. I couldn't find it. So next I searched the source code since it seems this is some undocumented thing. The only count I could find in source code was this line:
eventNS : '.selectize' + (++Selectize.count),
So basically this explains it. That count while it does increase for every element this is called on is not a current count of running widgets. Its an internal property the guy who wrote this uses as a GUID for event namespaces. So for instance when you call destroy he can only remove the events specific to that instance of the widget.
I would not use this property to tell you anything. I think its safe to assume that your destroy is working fine. If you are unfamiliar with event namespacing you can read more about it here:
https://api.jquery.com/event.namespace/
You can see he uses that eventNS throughout the code to attach events if you search for it. jQuery does this in their code too for lots of stuff like in their event and data code. They have a GUID variable they use so anyone who loads more than one instance of jQuery on a page, the instances won't step on each others toes.
So I guess the only thing I would now ask you is where did you learn about this count property? If you just found it and assumed that it meant this was a count of running instances try to remember to always check with the documentation. If you found it in the docs then please point me towards now so I can take a look and see if it verifies what I found or requires more looking into.
Also as a bonus heads up, I saw this in your fiddle, input is a self closing tag or also known as void elements.
<input type="text" value="Calgary, Edmonton" class="selectize_this"></input>
Should just be:
<input type="text" value="Calgary, Edmonton" class="selectize_this" />
From the spec:
Void elements can't have any contents (since there's no end tag, no
content can be put between the start tag and the end tag).
Void elements: area, base, br, col, embed, hr, img, input, keygen,
link, meta, param, source, track, wbr
The Selectize API does expose the following method:
destroy()
Destroys the control and unbinds event listeners so that it can be garbage collected.

Multiple appendChild calls don't work

I read through several threads without find a clear answer.
I'm using a JavaScript library (Drinks.js) to put several widgets on my webpage.
The following code will add one single item to my div element pnlThermo:
function create(item) {
var thermo = Drinks.createElement('display');
thermo.setAttribute('id', item);
thermo.setAttribute('label', item);
Drinks.appendChild('pnlThermo', thermo);
}
Well, now I want to add several items to the same div element. No matter if I use a for cycle or call the function explicitly only the first item will be rendered. For example:
create('T1');
create('T2');
create('T3');
leads to show T1 only.
Perhaps I missed something, I'm quite new to JavaScript programming.
Thanks in advanced.
The reference manual ( http://goincompany.com/DTManual01.pdf ) says :
After you have created the HTML element, you have to append it to an
HTML container. In order to do this you have to use the appendChild
function, provided by the Drinks class. Drinks.appendChild('body',
gauge); 'body' is the id of the HTML container. If the parent is an
instrument, this function doesn't work. We have to use the appendChild
method of the instrument, but we'll see this later.
Seems to imply that the first parameter needs to be an HTML tagname, which makes little sense
but then the library is a little wierd IMHO.

'document' vs. 'content.document'

I'm trying to write a Firefox extension that adds elements to the loaded page. So far, I get the root element of the document via
var domBody = content.document.getElementsByTagName("BODY").item(0);
and create the new elements via
var newDiv = content.document.createElement("div");
and everything worked quite well, actually. But the problems came when I added a button with on onclick attribute. While the button is correctly displayed, I get an error. I already asked asked here, and the answer with document.createElement() (without content) works.
But if I remove the 'content.' everywhere, the real trouble starts. Firstly, domBody is null/undefined, no matter how I try to access it, e.g. document.body (And actually I add all elements _after_the document is fully loaded. At least I think so). And secondly, all other elements look differently. It's seem the style information, e.g., element.style.width="300px" are no longer considered.
In short, with 'content.document' everything looks good, but the button.onclick throws an error. with only 'document' the button works, but the elements are no longer correctly displayed. Does anybody see a solution for that.
It should work fine if you use addEventListener [MDN] (at least this is what I used). I read somewhere (I will search for it) that you cannot attach event listener via properties when creating elements in chrome code.
You still should use content.document.createElement though:
Page = function(...) {
...
};
Page.prototype = {
...
addButton : function() {
var b = content.document.createElement('button');
b.addEventListener('click', function() {
alert('OnClick');
}, false);
},
...
};
I would store a reference to content.document somewhere btw.
The existing answer doesn't have a real explanation and there are too many comments already, so I'll add another answer. When you access the content document then you are not accessing it directly - for security reasons you access it through a wrapper that exposes only actual DOM methods/properties and hides anything that the page's JavaScript might have added. This has the side-effect that properties like onclick won't work (this is actually the first point in the list of limitations of XPCNativeWrapper). You should use addEventListener instead. This has the additional advantage that more than one event listener can coexist, e.g. the web page won't remove your event listener by setting onclick itself.
Side-note: your script executes in the browser window, so document is the XUL document containing the browser's user interface. There is no <body> element because XUL documents don't have one. And adding a button won't affect the page in the selected tab, only mess up the browser's user interface. The global variable content refers to the window object of the currently selected tab so that's your entry point if you want to work with it.

Understanding classes with Mootools

Sorry for my english, I'm french :)
I created a Mootools class named "Slider".
This class has a "slider_element" attribute, which is a DIV element.
The class also has a "destroyer" method. This method destroys the DIV element.
The slider_element is supposed to be a div that contains another DIV with a "remove" CSS classname. When I click on the "remove DIV", I want the "destroyer" method to be called, so that the DIV disappears.
Here is my code below, and it works graphically like I want.
My question is : when I destroy the DIV element, I don't need no more my Slider instance (here "mySlider"). But my code destroys the DIV elements, not the slider instance. Do this instance still exist ? I suppose yes. So I searched how to destroy an instance of a class with Mootools, but didn't find ... so I supposed I'm doing something the wrong way, even if my code does what I want graphically. Please help :)
var Slider = new Class({
initialize: function(slider_element){
this.slider_element = slider_element;
this.slider_element.getElements('*[class="remove"]').addEvent('click', this.destroyer.bind(this));
},
destroyer: function(){
this.slider_element.destroy();
}
});
var myElement = $('my_slider');
var mySlider = new Slider(myElement);
(in reality, this is a simplified code, so that I don't disturb you with my whole code)
There is no way to explicitly destroy an object in JavaScript. The best you can do is to remove all references to it and hope that your JavaScript implementation reuses the memory.
So in principle you can just overwrite any references (like mySlider in your example) with null. But there can easily be 'implicit' references that you can't control, in closures for instance (as used for events) -- you might be able to help the garbage collector by 'cleaning out' any properties that reference other objects, before throwing it away, but then you have to make sure nothing bad happens if a reference survives somewhere and something tries to use those properties.
For elements, Mootools has the destroy method that goes through a whole DOM subtree and clears out all properties as well as the associated storage of the elements, such as event listeners, before removing it from the DOM.
In your case, as #Dimitar Christoff wrote, if you don't have any external code that calls methods on the Slider object, you don't need to save a reference to it in var mySlider.
And if you don't do that, the only thing that keeps the Slider object alive is the reference from the stack frame of the closure that is built by .bind(this) in the addEvent call. When the event fires and destroy is called, the event listener is removed, and the JavaScript engine is free to deallocate the Slider object as well.

Categories

Resources