I believe I have found an issue within cleanData.
I have an app with a nav bar and when a button in the nav bar is clicked, it loads an associated control into the main content area of the app. When another button is clicked, the content element is emptied using .empty() function and that button's associated control (app) is loaded into mainContent. I'd like to hear more thoughts on this concept, but that is not the issue.
Let's say the nav bar has a "Dashboard", "Players, and "Teams" button with associated apps (controls).
I have another separate control that can be used to make a div collapsible. The control is attached to a DOM element and it makes the .next() element collapsible. When attached, it add some styling, event listeners and a small div element to contain and up/down arrow to show if the content is expanded/collapsed. In the destroy function of this control, I clean up the classes and remove the div that contained the up/down arrow.
This control is used within the Dashboard app to make certain sections of content collapsible. If the Dashboard is loaded and a new nav button is clicked, the $('#mainContent').empty() function is executed removing the DOM elements within. When a DOM element with a control attached is removed that control's destroy method is invoked. This is as expected.
But here's the issue:
Inside .empty(), the following is called:
- jQuery.cleanData( elem.getElementsByTagName("*") );
This returns all the DOM elements within mainContent to cleanData which then iterates over them and triggers the destroyed method. When this happens, this includes the div elements that were created to contain the up/down arrows. But when the element containing the collapsible control is processed it invokes the destroy method which deletes the added div elements before they are processed by cleanData.
However the $.each loop still attempts to process the removed elements resulting in cleanData trying to trigger destroyed on undefined elements and an error. Uncaught TypeError: Cannot read property 'trigger' of undefined
I think this can be fixed two ways:
1. Make sure elem exists before triggering:
if(elem)
can.trigger(elem,"destroyed",[],false)
2. Instead of $.each we could use a for loop like this where we recalculate the length:
for(var i=0; i<elems.length; ++i) {
can.trigger(elems[i],"destroyed",[],false);
}
Either way, I talked to daffl about this today and he suggested I post the issue here. I created a
jsfiddle that emulates the issue. It's not a very pretty one, but it demonstrates the issue. If you want to see a full version of the issue, you can check out my git repo: