I'm a python/data guy mucking around in a little bit of web, fundamentally clueless about JS and the dom and all that. So something really weird happened, and even though I found a solution, I'm trying to understand the mechanics of why.
The scene: a common problem
There's like a million prior SO questions that go along the lines of "I had some working jQuery/JS that manipulated some HTML. Then instead of hard-coding the relevant HTML, I created it programatically somewhere else and dragged it in via AJAX. Suddenly everything broke."
Invariably, the answer goes like this: "you can't do that. Hook up your code to something higher up the parent-child chain, event delegation is a magic thing and it will save you."
So that happened to me, I spent an hour or so reading prior SOs and learning about event delegation, and, indeed, it saved me, and my code worked.
But I don't understand why it was broken in the first place. So I'm hoping someone can explain the underlying theory to me, and that way I'll come to a deeper understanding of the whole dom bit.
Some Broken Code
$(document).ready(function(){
$("#autopubs").load("pubslist.html");
// Obviously, the stuff in pubslist.html is what the next line was
// supposed to work on
$('.collapse').on('show.bs.collapse', function () {
$('.collapse.in').collapse('hide');
});
});
probably no surprises about the solution. wrap autopubs in an outer div and hook the collapse thing to that. Done, worked, deeply dissatisfied.
But I still don't understand why this is necessary. Here's my mental model of what the broken code should have been doing.
Ok, the document's finished loading! Let's execute our code!
First line: let's go get this file and add it to the DOM. This is out there in the world as state, and now we're going to mutate it, as one does with state.
Ok, now it's part of the DOM. We're done with that line. Let's go to the next line.
Ok, now we want to hook up a bunch of event listeners to everything with the class collapse. Cool, let's look up the present state of the DOM. Hey look, there's all this stuff in there with the collapse class. Bam. Hooked up.
Except 4 never happened, because the second line of code apparently couldn't see all the stuff that the first line added.
Intuitively, there are two plausible reasons for this:
The second line executed before the first one finished getting the file in. If that's true, then I've learned something important about javascript (or jquery, or ajax, or something): lines don't always execute in order, or at least don't always finish before the next one starts.
The first line didn't actually mutate any state at all. The DOM isn't state. It's something else, something... maybe even immutable? The first line tinkered around with something else entirely, something the second line couldn't touch because it was trying to tinker with the DOM.
Honestly, both of those possibilities seem kind of bizarre to me. But there's obviously something I fundamentally don't understand going on under the hood here. Can someone help me?
JavaScript makes heavy use of asynchronous behaviour. Lines execute in order within the same function, but that doesn't mean they "complete" their action.
If you look at the documentation for load (http://api.jquery.com/load/) you can see that it takes an optional complete parameter. That is a callback. It is a function that will be ran when the operation completes. Calling load itself only makes the loading start, sort of "in the background".
So you could do this:
$(document).ready(function(){
$("#autopubs").load("pubslist.html", function() {
$('.collapse').on('show.bs.collapse', function () {
$('.collapse.in').collapse('hide');
});
});
});
The callback to load is ran after it completes.
Why your original hack-solution actually works? It attaches the event handler to an existing outer div, and the loaded HTML is put inside later, when it loads.
Your first assumption is correct. When calling ajax its asynchronous which means it will run a callback when complete. In this case jquery already calls a callback to append the content to the div but that happens after a delay of maybe 100 to 200ms or so.
The other code however can be run instantly but doesn't find anything because the async call isnt finished yet.
In jQuery if you check the documentation for load you will see something along the lines of (arg1, [arg2], [complete]) where complete will be a closure or callback function.
If you wrap the code within that callback function instead of below it than that code will be executed after the content is loaded and added.
EDIT
The load load is also one of the convenience / shorthand method in jquery. For more documentation check Ajax
Furthermore the DOM is very mutable and your first line that does the request does perform an operation on the dom. Due to the fact that it is async it will tell the script to continue running while it waits for its data which is why it fails.
Hope this helps abit! You've got the right picture :)
If I correctly understand, you should refer the new loaded DOM only on a 'complete' callback. Take a look here:
http://api.jquery.com/load/
$( "#result" ).load( "ajax/test.html", function() {
alert( "Load was performed." );
});
The first line did get executed, it changed the state. After that, you hooked an event to the change and that will get called everytime there is a change, but not for the first one since it has already happened before the event was hooked up.
jQuery.load is asynchronous, meaning you need to provide any code to execute after the content is added via callback. You have a working example at the bottom of the documentation page.
As others stated JavaScript wont wait with finishing the load function before executing the following lines. Therefore, you can use the callback parameter of the load function (or any other function that takes time, like AJAX or animations). So your first assumption is correct. AJAX (asynchronous JS) is a huge thing, you should dig into it :)
Additionally, you still can use delegation in your case:
$(document).on('show.bs.collapse', '.collapse', function () {
var collapse = $(this); // the individual $('.collapse') element triggering the event
});
This will bind the show.bs.collapse event to all .collapse elements, regardless if they exist now or are added later. Here the original selector is the $(document). In simple words:
"Hey document, whenever the event show.bs.collapse gets fired on an element with the selector '.collapse', run the following code."
You distinguish between direct and indirect event delegation or binding. In your example, you use direct binding, since you are ultimately selecting all now-existing .collapse elements and then do stuff with them:
$('.collapse').on()
You have to understand that this works only for currently existing elements that get selected when running that line. This wont apply for dynamically added elements of the same class.
Related
I am working on the project below initially stages animation works fine but after 2nd level the Frame rate drops drastically, below is the link of the game can anyone look into and help me, please!
http://fbapps.ae/mfc/game-demo/
thanks
mustafa
Currently can not get passed level 1. The _animalClicked method shows that the _questions variable is undefined, so you get wrong answers indefinitely.
I would say if you can in fact get past this, and are seeing a frame drop, then it is likely your not properly cleaning up your stage or events.
A few other notes:
Get rid of all the stage.update() calls you have everywhere. Once you call handleComplete, you no longer need to update the stage, because there is a Ticker event which updates the stage constantly. You might want to consider just adding a ticker event initially, rather than waiting for the content to load.
Note that removeEventListener can not be called with no handler argument. Unlike jQuery, this does not mean "remove all events of this type". You are using anonymous handlers, so you will have to hang on to a reference to that function, and pass it to removeEventListener instead. Alternatively, you could use removeAllEventListeners(). Looks like you do that elsewhere.
Alright lets see if I can describe this.
I have a handful of functionality that was created sometime ago, and works swimmingly. However there is a new desired spec, so without having to rewrite the code base in a matter of speaking, and without having to double up on code to pull the same effect off I am trying to figure out how I can go about making something jump back higher in the code within the same function to repeat the run of the function rather then doing the same code again below.
What I have is a click based triggers ie:
$('.selector').click(function(){});
In this function is about 30 lines of functionality to create a new element and populate it accordingly. However unfortunately in that same bit of functionality there is conditions to wether it should or not.*The previous requirement was when the element it creates is open and populated just throw an alert() saying essentially wrap up what your doing, and then go on to the next. *Now the new requirement is just close that and open a new element. Which I've gotten to close out the existing, and do everything I want it to do, except the population of the new element which is above where the condition is currently. Knowing there is no "go to" type of logic in javascript (or last I knew). the only thing I can think of is taking the same code from above and putting it in the condition as well, doubling up on the code and having litterally 2 copies of the same bit. I want to avoid that, but cant think of a way to do it. So here I am looking for ideas
Knowing there is no "go to" type of logic in javascript (or last I
knew). the only thing I can think of is taking the same code from
above and putting it in the condition as well, doubling up on the code
and having litterally 2 copies of the same bit. I want to avoid that,
but cant think of a way to do it. So here I am looking for ideas
Why don't you just pull this piece of code out into a function? You can run the function if the conditional is true in the original instance, and run it all the time in your callback? This is fairly minimal refactoring, just move the code out of the logic into a separate function, keeping it as is and maybe making some of the referenced variables into parameters.
So something like this if you want to run all the actions regardless of the conditional statements:
...
if(condition){
actionA();
}
if(condition2){
actionB();
}
...
$('.selector').click(function(){
actionA();
actionB();
});
You're familiar with that pattern, right?
var aCallback = function(){........};
$('.selector').click(aCallback);
I am building a single page webapp. This means over a period of time I get new DOM elements, remove unneeded ones. For example when I fetch a new form I just replace the contents of a specific div with that form HTML and also set up listeners unique to this form's elements. After some period I replace the contents of this form with a new instance of form (having different ID's).
I set up the event listeners again for this new form. Now the previous form is no longer part of the DOM so I the DOM elements should be automatically garbage collected. I am also expecting the listener functions pointing to the elements removed from the DOM to disappear.
However the following profile gathered from Chrome suggests that my listener count is increasing over time. Can you tell me why this is so? I tried clicking on the "Collect Garbage" button. But this is the profile I get. Is there something wrong with the way I am building my application? Is there a problem and if so how should I fix it?
In case it matters I am using JSP templating language with jquery, jquery-ui and some other plugins.
This is how the dynamic fragments that I add/remove on my page look like.
<script>
$(document).ready(function() {
$("#unique_id").find(".myFormButton").button().click(
function() {
$.ajax({url: "myurl.html",
success: function(response) {
console.log(response);
}
});
});
});
</script>
<div id="unique_id">
<form>
<input name="myvar" />
<button class="myFormButton">Submit</button>
</form>
</div>
Update
If you want to have a look at the actual code here is the relevant portion.
This link shows that when clear button is pressed the function clearFindForm is called which effectively refetches content (HTML fragment) using an ajax request and replaces the entire div in this jsp with the content fetched.
The refetchContent function works as below: Here is the link to the code in case that helps in giving a better answer.
function refetchContent(url, replaceTarget) {
$.ajax({
url: url,
data: {},
type: "GET",
success: function (response) {
replaceTarget.replaceWith(response);
},
error: function (response) {
showErrorMessage("Something went wrong. Please try again.");
}
});
}
While jQuery is very good at removing event listeners to DOM elements that are removed via it's methods (including .html() - just read the API: http://api.jquery.com/html/) - it won't remove event listeners to DOM elements that may still have a reference to them in a detached DOM tree.
For example, if you do something like this:
$.ajax({
....
})
.done(function(response,status,jqXHR) {
//create a detached DOM tree
form = $(response)
//add an event listener to the detached tree
form.find('#someIDInTheResponse').on('submit',function() {
});
//add the form to the html
$('#someID').html(form);
});
//at some other point in the code
$('#someIDInTheResponse').remove();
Note that in the above example, despite the fact that you removed the element from the DOM, the listener will not be removed from memory. This is because the element still exists in memory in a detached DOM tree accessible via the global variable "form" (this is because I didn't create use "var" to create the initial detached DOM tree in the scope of the done function....there are some nuances and jQuery can't fix bad code, it can only do it's best.
2 other things:
Doing everything inside callbacks or event listeners (like do this on a button click) turns into real bad spaghetti code really fast and becomes unmanageable rather quickly. Try and separate application logic from UI interaction. For example, don't use callbacks to click events to perform a bunch of logic, use callbacks to click events to call functions that perform a bunch of logic.
Second, and somewhat less important, (I welcome feedback on this perspective via comments) I would deem 30MB of memory to be a fairly high baseline for a web app. I've got a pretty intensive google maps web app that hits 30MB after an hour or so of intensive use and you can really notice start to notice it's sluggishness when you do. Lord knows what it would act like if it ever hit 60MB. I'm thinking IE<9 would become virtually unusable at this point, although, like I said, I welcome other people's feedback on this idea.
I wonder if you are simply not unbinding/removing the previously bound event listeners when you replace fragments?
I briefly looked at the specific sections of code you linked to in your updated question, but didn't see any event listener binding other than what you are doing in document ready, so I'm guessing you are doing some additional binding when you replace the document fragments. I'm not a jQuery expert, but in general binding or assigning additional event listeners does not replace previously bound/assigned event listeners automatically.
My point is that you should look to see if you are doing binding via "click()" (or via some other approach) to existing elements without unbinding the existing event listener first.
You might take a look at moff's answer to this question, which provides an example for click, specifically.
I can't add a comment because of reputation but to respond to what Adam is saying...
To summarise the case Adam presents, it's potentially nothing to do with jQuery, the problem may be within normal Javascript. However you don't present enough code for anyone to really get to the bottom of the problem. Your usage of scoped encapsulation may be perfectly fine and the problem may be else where.
I would recommend that you search for tools for finding the cause of memory leaks (for example, visualising/traversing the entire object/scope/reference/function tree, etc).
One thing to watch out for with jQuery are plugins and global insertions into the DOM! I've seen many JS libs, not just jQuery plugins, fail to provide destroyers and cleanup methods. The worst offenders are often things with popups and popouts such as date pickers, dialogs, etc that have a nasty habit of appending layer divs and the like into body without removing them after.
Something to keep in mind if that a lot of people just get as far as to make things construct but don't handle destruct especially in JS because they expect still even in this day and age that you will be serving normal webpages. You should also check plugins for destroy methods because not all will hook onto a remove event. Events are also used in a messy fashion in jQuery by others so a rogue handler might be halting the execution of following cleanup events.
In summary, jQuery is a nice robust library but be warned, just because someone depends on it does not mean it inherits jQuery's quality.
Out of curiosity... have you checked the listeners on document.ready? Maybe you need to manually GC those.
I am trying to do all dom manipulations off screen and then make it visible. Which works, except now I have the situation where I am trying to do it with a form which I want to focus on the first input text upon rendering it on the browser.
Something like: myForm.prependTo(myDiv).show().find('input:first').focus();
Problem is that the focus is being called before the form has finished rendering which is causing the lovely error 'Can't move focus to the control because it is invisible, not enabled, or of a type that does not accept the focus'
How do other web developers handle the similiar situation of manipulating elements off screen and then making it visible? I wish jQuery had something like myForm.prependTo(myDiv, function() { /* on render code here */ })
I know one way of doing it is setting a timeout and when it fires I put focus on the input, but I feel like that's not really the cleanest way to do things. I know the iframe has an onload event, so I'm curious if people usually draw their elements in some hidden iframe and listen for its load event to know when the element has finished rendering? If so could you point me to an example of doing this?
myForm.prependTo(myDiv).show(function(e){
$(this).find('input:first').focus();
});
I know I'm 7 years late, but I had a similar problem, which I solved by putting the stuff I needed to happen after the render in a ready handler.
I had a restore function that worked, but there was zero or near zero visual feedback that the element had been restored.
I tried emptying the element first. It still worked, but still had zero visual feedback.
$("#someSelector").empty();
restore();
Then I discovered ready() happens after the rendering; so I changed it to something like....
$("#someSelector").empty().ready(function() {
restore();
});
Now the restore() doesn't happen until after the empty() action RENDERS. This means my element APPEARS to empty out and then refill (it always did, but now the user can see it happen).
I found this solution somehow a few days ago for a different problem with some vague search that I can't remember. Then I needed it again but couldn't exactly remember what I did. Now my searches included the word "jquery" and "render" and lead me here.
I ended up going thru my code to find out what I did, and I thought it might be a good idea to post it here in case other people stumble on this post and actually need to execute something AFTER rendering happens.
Cheers.
I am working on a Greasemonkey script that will actually upgrade the version of jQuery used on the page. To do this, I need to add a "ready" event handler that will fire after all the other ones that might be on the page.
I know that jQuery waits for the DOM to be manipulable before invoking the ready event handlers, so is there a way to influence the order in which it executes them? Thank you,
They are called in the order they are registered. So from the top of the page to the bottom. If you need this to be the last registered ready callback register it at the very end of the body tag. Also use the $(window).load as opposed to $(document).ready.
The ready handlers are added to a readyList Array, which I'm pretty sure is private, so I don't think you'll be able to influence it directly.
One thing you could perhaps do is add your code to the readyList, but place it in a setTimeout() so it waits a bit to execute. Hopefully all the others will be done first.
Still, you may have troubles when upgrading jQuery like this. For example, there may be differences in the implementation of jQuery.cache which stores event handlers, and other data.
So if jQuery.cache was populated with one version, it may not be compatible with another.
How to control the order of functions being called in jQuery $(document).ready
According to answers given to the question above, they should fire in the order they are added (the ajax-calls in that specific question add more mud to the water than in your question).