Reputation: 375
I'm writing a simple text-base game in my free time. It's called TARDIS flight (Doctor Who!) and I'm working on the main menu.
So I'm using a function, addMainMenuListeners
, to add all the event listeners with addEventListener
, after I set the innerHTML
of the main menu.
Everything works fine, until the point where I go back to the main menu from one of the submenus. Then, I found that the buttons don't work anymore.
I'm calling addMainMenuListeners
after I set the innerHTML
, but even though I do it, and I do it in the console, and I check, there is no event.
Code:
In my main javascript file:
function addMainMenuListeners()
{
if($("start")) $("start").addEventListener("click", startGame);
if($("instructions")) $("instructions").addEventListener("click", instructions);
if($("settings")) $("settings").addEventListener("click", settings);
if($("back")) $("back").addEventListener("click", resetMainMenu);
if($("volume")) $("volume").addEventListener("change", function(){saveAndChangeVolume($("volume").value);});
}
function instructions()
{
$("mainmenu").innerHTML = "<h1>Instructions</h1><p>Fly your TARDIS through the time vortex using the console. Make sure that you use the correct materialization codes. Try to keep the time-space coordinates close to the values of the ones given. AND DON'T CREATE PARADOXES, WHATEVER YOU DO!</p><button id='back'>Back</button>";
addMainMenuListeners();
return true;
}
function settings()
{
$("mainmenu").innerHTML = "<h1>Volume</h1><span>1</span><input type='range' id='volume' min='1' max='100' /><span>100</span><br><button id='back'>Back</button>";
addMainMenuListeners();
loadVolume();
return true;
}
function resetMainMenu()
{
$("mainmenu").innerHTML = "<h1>TARDIS Flight</h1><button id='start'>Start!</button><button id='instructions'>Instructions</button><button id='settings'>Settings</button>";
addMainMenuListeners();
return true;
}
And in my HTML file:
<div id="mainmenu">
<h1>TARDIS Flight</h1>
<button id="start">Start!</button>
<button id="instructions">Instructions</button>
<button id="settings">Settings</button>
</div>
If you need any clarification, just ask.
EDIT: Evidentally, nobody got what I meant. I was readding the event listeners after doing the innerHTML
, as you can see from the code. I simply cannot see the event being added, the function is firing but not adding the event.
Also, I am using a custom $
function, just a return document.getElementById(id)
sort of function.
Upvotes: 2
Views: 6080
Reputation: 14645
Sukima psychic ability's catched the main problem: your custom $ function (to replace document.getElementById) used a caching mechanism.
After some testing (out of personal curiosity) it turned out that as long as you have some reference to an element, the element is still accessible, even after elm.parentNode.removeChild
or a full elm.parentNode.innerHTML
rewrite (at least, in FF).
In other words: the events WERE added, every time, just to the wrong/old/previous elements instead of the new ones. Otherwise there would also have been errors that the elements didn't exist and thus didn't have an addEventListener
method..
See this crude test-fiddle for proof: http://jsfiddle.net/G28Lu/
I toyed around with a $ function (as you haven't posted yours yet) and gave it an 'refresh'-flag to go with the optional cache mechanism:
var $=function(/*cache_enabled*/c){ // getElementById with optional caching & refresh
return c ?( c={}, function(s,r){return (!r && c[s]) || (c[s]=document.getElementById(s));}
):( function(s){return document.getElementById(s);} );
}(true); // or nothing or 0 etc to disable the cache
Note: dynamically created functions are slow (for some reason) so it has 2 functions so the browser can run them optimized (and the other should be cleared by the garbage collector).
To request a fresh just add a flag that evaluates to true as second argument, like: $('elm_id', 1)
Then I modified your addMainMenuListeners
function a little to first test for the existence of a fresh getElementById and then add the eventListener via the freshly updated cached reference (so, essentially I changed nothing in the flow of your routine).
formatted to emphasize what changed and how it works
function addMainMenuListeners(){
$( 'start' , 1) && $( 'start' ).addEventListener('click', startGame);
$('instructions', 1) && $('instructions').addEventListener('click', instructions);
$( 'settings' , 1) && $( 'settings' ).addEventListener('click', settings);
$( 'back' , 1) && $( 'back' ).addEventListener('click', resetMainMenu);
$( 'volume' , 1) && $( 'volume' ).addEventListener('change', function(){
saveAndChangeVolume($('volume').value);
});
}
Finally, putting above 2 functions and the rest of your functions/html into this fiddle rendered a working result: http://jsfiddle.net/xHUGu/ !
Note: I had to substitute a dummy function startGame
otherwise there would be a fatal error. The missing volume-functions were not critical.
I would like to point out that this is not really the way to go with your interface, there would be a lot of work you could save both yourself and the browser. You might want to look into div's (containing your subsections of html) and toggle them so there is only one visible. Like tabs. (Hint for google-query).
Credit still goes to Sukima ('the force is strong in this one'), but I felt it was a good idea to share the correct explanation to your problem (with proof) and not to waste the work that was done anyway (out of curiosity).
Hope this helps!
Upvotes: 1
Reputation: 10074
Check to see if your $()
uses any caching. If it caches old references to elements then when innerHTML
is set the $("id")
will return a reference to an invalid reference.
[edit] The references are more likely valid even though they are no longer visible in the HTML DOM. So modifying the detached elements works but it doesn't do any good since they are detached from the DOM.
Upvotes: 5
Reputation: 375
Disabling caching on the $
function worked. It was referencing to a destroyed HTML element, and that's why it didn't work. Also, setTimeout
s helped for reliability in the case that innerHTML
didn't execute in time.
Upvotes: 0