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.
Related
I have written some code that changes an input quantity on a magento 1.9 ecommerce website.
jQuery("input.qty").val("10");
The problem is the javascript that triggers the total to update doesn't fire. I have found the code responsible and it looks like this:
(function() {
var qtyFields = $('super-product-list').select('input.qty');
qtyFields.each(function(el) {
el.observe("change", updateGroupedPrice);
});
Event.observe(window, "load", updateGroupedPrice);
function updateGroupedPrice() {
//do stuff
}
})();
I think this is using prototype.js but I tried to isolate it in a codepen but couldn't get it working.
I have tried to trigger the change event like so:
jQuery("input.qty").trigger("change")
But it does not work. I also ran through a load of other events but in the dev tools it shows the code listening on "change".
Does anyone know why I can't trigger the change?
Since the page is using Prototype.js, you ought to keep using that to trigger your change. If you introduce jQuery into this, you're a) loading another complete duplicate of what Prototype already does, and b) asking for a lot of trouble isolating the fact that $() is a method in both libraries.
Your jQuery is a little fishy to me, too. You're setting the value of one picker (I imagine) and yet you are addressing it with a classname, so potentially there is more than one select.qty in the page, and all of them will change to value 10, firing off (potentially) multiple callback functions.
The Prototype code you see here is setting up a "listener" for changes on what you would address in jQuery as$(#super-product-list input.qty) inputs.
jQuery always treats $() as returning an array of objects, and thus all of its methods act on the array, even if it only contains one member. Prototype has two different methods for accessing elements in the DOM: $('some_id'), which always returns one element (or none, if no match), and $$('some css selector'), which always returns an array (of zero or more matching elements). You would write (or use native) callback methods differently, depending on which accessor you used to gather the element(s).
If you want to change one of these inputs, you will need to isolate it before you set its value.
Let's say there are three select pickers with the classname qty in your #super-product-list element. You want to change the third one to 10:
$('super-product-list').select('input.qty').last().setValue('10');
Or, much smarter than this, you add an ID to the third one, and then your code is much shorter:
$('quantity_3').setValue('10');
In either case, this will send the "change" event from your select, and the updateGroupedPrice method will observe that and do whatever you have coded it to do.
You won't need to (and should not ever) trigger the change event -- that's a "native" event, and the browser owns it. jQuery's trigger() (which is fire() in Prototype, is used exclusively for "synthetic events", like you see in Bootstrap: show.bs.modal, hide.bs.modal, etc. You can spot these by the punctuation in their names; usually dots or colons to namespace the events and avoid collisions with other code.
Finally, if you really, really, really wanted to change every single #super-product-list select.qty element on the whole page to '10', you would do this in Prototype.js:
$$('#super-product-list select.qty').invoke('setValue', 10);
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.
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.
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.
I'm trying to implement a simple horizontal navigation menu that just shows a single div for each link. It is kinda like a dropdown menu but instead of a mouseover triggering a dropdown, an onclick event will trigger the showing of a div. I want to make sure I am taking the right approach before going too much further, any help is appreciated. This is what I have so far:
<ul id="settings_nav">
<li>
<a>Theme</a>
<div id="settings_block"><%= render :partial => 'email_password' %></div>
</li>
<li>
Lists
<div id="settings_block"><%= render :partial => 'lists' %></div>
</li>
</ul>
window.onload = function(){
settingsMenuInit('settings_nav')
}
function settingsMenuInit(settings_nav){
$(settings_nav).childElements().each(
function(node){
node.onclick= function(){ this.next.show() };
})
}
Something like that, but I am unsure how to get the div that is currently shown and hide it. I could iterate through all the childElements and hide each div and then show the one that is being clicked, but maybe there's a better way?
Some notes FW(T)W:
With Prototype and similar libraries, you don't want to hook up event handlers by assigning functions to the element's onclick and similar properties; that style has several disadvantages (not least that there can only be one handler for the event on the element). Instead, use Prototype's observe function:
someElement.observe('click', functionRefHere);
// or
Element.observe(someElementOrID, 'click', functionRefHere);
This also lets Prototype work around some IE memory loss bugs for you.
You might look at is Prototype's dom:loaded event, which happens sooner than window.onload (which won't happen until all of your images and other external resources have loaded, which can be a second or two after the page is displayed):
document.observe('dom:loaded', initFunctionRefHere);
You can use event delegation and just watch your settings_nav element, rather than each child node individually.
$(settings_nav).observe('click', handleNavClick);
function handleNavClick(event) {
var elm = event.findElement("some CSS selector here");
if (elm) {
event.stop();
// Handle it
}
}
As you can see, Event#findElement accepts a CSS selector. It starts with the actual element that was clicked and tries to match that with the selector; if it matches, it returns the element, otherwise it goes to the parent to see if it matches; etc. So with your HTML you might look for a li (event.findElement('li')) or the link (event.findElement('a')).
But if you want to watch each one individually, they can share a function (as they do in your example):
$(settings_nav).childElements().invoke('observe', 'click', handleNavClick);
function handleNavClick(event) {
// Prototype makes `this` reference the element being observed, so
// `this` will be the `li` element in this example.
}
Whether you watch each element individually or use event delegation depends on what you're doing (and personal preference). Whenever anything is likely to change (adding and removing navigation li elements, for instance) or when there are lots of things to watch, look to event delegation -- it's much easier simpler to deal with changing sets of elements using event delegation and just watching the parent. When dealing with a stable structure of just a few things (as in your example), it may be simpler to just watch the elements individually.
Once inside your handler, you can use Element#down to find child elements (so from the li, you might use li.down('div') to find the div), or Element#next to get to the next sibling element (e.g., going from the link to the div). Either way, once you have a reference to the div, you can use Element#show and Element#hide (or Element#toggle).
I recommend using named functions instead of anonymous ones (see my example above). Named functions help your tools (debuggers, browsers showing errors, etc.) help you. Just be sure not to declare a named function and use it as an expression (e.g., don't immediately assign it to something):
// Don't do this because of browser implementation bugs:
someElement.observe('click', function elementClickHandler(event) {
// ...
});
// Do this instead:
someElement.observe('click', elementClickHandler);
function elementClickHandler(event) {
// ...
}
...because although you should be able to do that according to the spec, in reality various bugs in various browsers make it not work reliably (article).