ExtJs exceptions immediately break all ExtJs components on a page - javascript

If you've ever worked with ExtJs, you've probably run into the nefarious "Cannot read property 'dom' of null" exception. For me, it usually happens inside doInsert (the last of 14 stacks in the outer trace) (ExtJs's code):
doInsert: function(el, o, returnElement, pos, sibling, append) {
//<My own comment>: hint: el === null (curiously not undefined?)
el = el.dom || Ext.getDom(el);
var newNode;
...
}
This happens when you attempt to attach an ExtJs component (Tree, Grid, Button, whatever) to a DOM element which is not yet in the DOM. Usually the element is in memory, constructed as part of an AJAX request, ready to be inserted on success.
The problem is that when ExtJS throws, it fundamentally breaks every ExtJs component in the page. All grids, trees, menus, buttons are immediately mangled. Event bindings like click and hover either do nothing or generate exceptions.
I currently mitigate against this by first doing a document.getElementById check, and if the result is undefined, then I defer 100 ms and try again. This is ugly, but it works. It's also expensive to do. As certain pages get more complex, that's more and more document.getElementById defer loops; and it gets harder and harder to track down which method inside which module hasn't tested getElementById.
It's a mess.
Question: is there any way to force ExtJS to throw gracefully? To override its exception handling mechanism? Flip the "dontBreakTheEntirePage" configuration variable?
UPDATE:
After trying to go direct to Sencha and after a few rebuffs, I demonstrated the problem more clearly (I hope):
EDIT: Sencha is moderating my replies to the original thread. If you have any interest in this class of fault, please add to the discussion.
Open the docs (http://docs.sencha.com/extjs/4.1.0/)
Open a few tabs (I have Ext, Ext.tree.Panel, Ext.menu.Menu, Ext.AbstractComponent, and Ext.draw.Color)
Open the Chrome Dev Tools and look for errors.
Click back and forth between tabs a few times as a sanity check. Tabs render as expected, without error.
Now, let's break the page.
Back to the Dev Tools.
Switch to the console.
Define an adhoc method:
var tryCatch = function (func) {
try {
func.apply(this, Array.prototype.slice.call(arguments, 1));
} catch (ex) {
var e = {
type: 'js',
message: ex.message,
detail: 'JS Error type: ' + ex.type + '<br/>\n' + 'Stack: ' + ex.stack
};
console.error(e);
}
};
Define a method which will throw an exception:
var myExceptionFunction = function () {
//Demo taken straight from ExtJs docs on Ext.menu.Menu
Ext.create('Ext.menu.Menu', {
width: 100,
margin: '0 0 10 0',
floating: false,
renderTo: 'thisIdDoesNotExist', //and will break the already rendered page
items: [{
text: 'regular item 1'
}, {
text: 'regular item 2'
}, {
text: 'regular item 3'
}]
});
};
Execute the myExceptionFunction method in the tryCatch wrapper:
tryCatch(myExceptionFunction);
Observe the reported error: "Cannot read property 'dom' of null"
Now start clicking back and forth between the opened tabs.
Suddenly, the page is exhibiting some odd behavior.
You begin to see new exceptions in the console: "Uncaught TypeError: Cannot read property 'dom' of undefined" Tabs render, but in unexpected places. The currently selected tab is not always properly updated.
Now, ExtJs's documentation site actually handles this fault quite well. Most of the page still works in ways that you can cope with; but in my scenario, my page is crippled.
We're talking about content that has already rendered--is already in the DOM.
I cannot imagine a reason for exceptions thrown past that point to break what has already executed.

Christopher, I'm sorry, but Evan's responses to your questions in your linked thread are all appropriate and make a lot of sense. He completely understands what you're asking for and you are not understanding him. Further research into the inner mechanisms of how Extjs is built would make you come to the realization that things 'already rendered' as you describe them are tightly linked to everything else that has to do with the layout engine. This is done for micro-optimizations that greatly improve render speed. Because it's so tightly linked together, when you try to render something to another thing that doesn't exist, you are going to bring the whole engine down.
The misunderstanding here is your assumption that things 'already rendered' have nothing to do with the things 'to be rendered.' In extjs these things are tightly coupled, and breaking one will break the other. Since this tight coupling is necessary to make the library render faster (believe me, they can't make it render any slower and still be usable on IE8), the 'issue' you are having is a necessary evil.
Evan's example of surrounding your code with a simple try-catch block is the best route if you are not able to or do not want to take the dev time to implement another solution within the logic of your own code.

Related

Extjs Intermittent error

Using Extjs 4.1, an intermittent error is occurring.
Sometimes it occurs, but most of the time it does not.
It always occurs when I start the application, so I press F5 and everything returns to normal.
Using this reference to debug the error, I noticed that it occurs at line 29590.
The error message is very generic:
Uncaught TypeError: Cannot read property 'dom' of null
at constructor.finishRender (ext-all-debug-w-comments.js:29590)
at constructor.finishRenderItems (ext-all-debug-w-comments.js:39796)
at constructor.finishRender (ext-all-debug-w-comments.js:40889)
at constructor.finishRenderChildren (ext-all-debug-w-comments.js:44526)
at constructor.afterRender (ext-all-debug-w-comments.js:29331)
at constructor.callParent (ext-all-debug-w-comments.js:6194)
at constructor.afterRender (ext-all-debug-w-comments.js:36521)
at constructor.finishRender (ext-all-debug-w-comments.js:29625)
at constructor.finishRenderItems (ext-all-debug-w-comments.js:39796)
at constructor.finishRender (ext-all-debug-w-comments.js:40889)
Here is an error print
What must be causing this?
I would have made this a comment, but it doesn't fit.
There is a wide variety of reasons I could think of. Most of them are based on asynchronous calls. You definitely have to provide more information for the question to be answerable.
Obviously, me.el is null, which it shouldn't be. So first you have to find which component is causing the problem. For this, you should exchange line 29590 against something like this:
try {
me.container = Ext.get(me.el.dom.parentNode);
} catch(e) {
console.log(me.id);
console.log(me.itemId);
console.log(me.xtype);
...
console.log(me);
throw e;
}
This should give you an idea which component(s) would be affected.
Then show us the code of that component. Also check whether you modify that component's config from outside the component, e.g. from a store load operation or other asynchronous tasks. Plus you should look at whether overrides for the component and its ancestors are loaded from separate JS files - maybe they are sometimes loaded before, sometimes after the finishRender has been called, and fix exactly this error.
These are only a few of the possible reasons.

Unable to get property 'style' of undefined or null reference but works in another server

I have a classic asp project. In one of my pages i have to call a javascript function. That call does not have any problem and works fine on my test server (not localhost, just a server to test he project). But when i deploy it to the actual server, that function does not work. I call this function in onload event.
That function has this type of lines (i cannot write the whole code, because of the company that i work for, does not allow it)
document.getElementById("R6C2_1").style.display = 'block'
document.getElementById("R6C2_2").style.display = 'none'
....
When I try to debug it on IE10, i got "Unable to get property 'style' of undefined or null reference" error. After that, the elements in javascript function are not load. They are not seen on the page.
My main problem is, as i mentioned before differences between servers. I do not understand why it works on one server, but not on another server.
While it's not possible to determine the issue from this information alone, you should look into:
Whether the elements you're looking for actually exist when the code is invoked (use browser debug / breakpoints to look at the page the moment the code is invoked).
If they exist, check if they have the ID you expect (e.g R6C2_1) - if not, why? who creates these IDs? could be a server configuration issue.
Do a debug using the app from each server, and look at the page / DOM, see if there are differences or check if the code is invoked at different times.
These could lead you to pinpoint the issue. Good luck!
In case the elements just take time to be created, you can just wait until they are present:
function ExecuteWhenExists() {
var R6C2_1 = document.getElementById("R6C2_1");
var R6C2_2 = document.getElementById("R6C2_2");
if (R6C2_1 && R6C2_2) {
R6C2_1.style.display = 'block';
R6C2_2.style.display = 'none';
} else {
window.setTimeout(ExecuteWhenExists, 100);
}
}
ExecuteWhenExists();
This will not crash when the elements do not exist, and will just keep trying to execute in a non-blocking way (polling every 0.1 seconds) until they exist.

CreateJS - Type not recognized error thrown during sound registration

I am trying to load sounds through the SoundJS sound registration, and getting the following error:
createjs.js:15 Uncaught Error: Type not recognized.
I figure that the soundjs library is having issues either locating my files or having trouble with the file extensions, but I am using .ogg, which is inline with all the examples I've seen.
Here is my code:
createjs.Sound.alternateExtensions = ["mp3", "ogg"];
createjs.Sound.on("fileload", function(event) {
console.log(event);
}, this);
for (var i = 0; i < soundManifest.length; i++) {
soundManifest[i].loaded = false;
console.log("loading " + soundManifest[i].src);
createjs.Sound.registerSound(soundManifest[i].src, soundManifest[i].id)
}
soundManifest is an array of objects with a source item giving the path to the .ogg files, and an id. I've double and triple checked the path names, so pretty sure that's not it. Any ideas? I am developing on Chrome.
Thanks for posting a github link. It was helpful. Fortunately, I have a super simple answer for you.
Rename the "Object" class you made in Main.js, and you should be good to go.
-- The long answer --
I tossed a breakpoint the error that is thrown, and it showed that when SoundJS tries to create a LoadItem, it fails. This is because it should be treating the LoadItem it receives as an Object, but the line below is failing:
} else if (value instanceof Object && value.src) {
// This code should be executed
}
At first I thought there was a bug in SoundJS that we had somehow missed in the last 2 years, but closer inspection showed that object prototypes in your application are messed up. If you open any browser window, and hit the console, this will return true:
({}) instanceof Object
// true
However while running your app, it returns false.
The issue became clear when I removed all your other classes other than CreateJS and main, and then tried this:
new Object();
// Throws an error that includes info about "Victor"
In main.js, you are defining an "Object" class, which extends a CreateJS Shape. It is global because there is no method closure around the code, so it overwrites the global Object class/prototype.
The reason I included this explanation, is because I couldn't figure out what was going on until I had my steps to show that prototypes were broken in the app mostly written out before the reason dawned on me. I thought it might be of some interest :)

TypeError: this._url is undefined at Dojo FilteringSelect onChange callback

I keep getting following error: TypeError: this._url is undefined at the callback of a Dojo FilteringSelect.
I simplified my code to this:
var testFeatures = [
{ "id": 1, "Adresa": "Pyšvejcova 6, Kotěhůlky"},
...
];
var filteringSelect = new FilteringSelect({
id: "testSelect",
autoComplete: false,
searchAttr: "Adresa",
store: new Memory({ data: testFeatures }),
onChange: this.onSelected
}, "testSelect");
filteringSelect.startup();
//end of the function, still in the same define callback
onSelected: function () {
//custom processing
var queryTask = new esri.tasks.QueryTask(this.serviceUrl);
var query = new esri.tasks.Query();
query.outFields = ["OBJECTID"];
query.where = "Adresa=" + dojo.byId("testSelect").value;
query.returnGeometry = true;
queryTask.execute(query, this.zoomToSelected);
}
zoomToSelected: function (featureSet) {
//custom map processing
}
and HTML:
<input id="testSelect" data-dojo-attach-point="testSelect" />
I have no idea where's the problem, Google found no case similar to mine. FireBug says the error occurs in init.js. Console says line 199, but the code in question (...nction(){},onExecuteForCountComplete:function(){},onExecuteForExtentComplete:fun...) is on line 256.
One possible cause of problems might be ArcGIS JavaScript API or Web AppBuilder - this seems not to be one of the "ArcGIS-heavy" parts of the code, but I don't know for sure. It's ArcGIS JS API 3.15 (Dojo 1.10) and Web AppBuilder for developers version 1.4.
EDIT: with help of #KenFranqueiro and this post I made some progress. I can't say that I fully understand the onChange callbacks, but I learnt to omit the parentheses while calling a named function. The onSelected still wasn't called, but modifying the input data to include id solved this. But it didn't solve the main problem...
Now the old good TypeError: this._url is undefined occurs at queryTask.execute, or between it and start of zoomToSelected method. The exact place where the error occurs changed to line 256, column 351, with following referenced code:
...e:function(a,b,c,d,f){var m=f.assembly;a=this._encode(n.mixin({},this._url.query...
There is a pointer at the = after "a", so the error seems to be to the right of it, trying to mixin something non-existent to something else. I have no idea what the this in the init.js should mean. Unfortunately, dojo core code is almost undebuggable without some de-obfuscation. Is this my only option, or does anybody know how to solve it?
So debugging the init.js wasn't so hard, it takes QueryTask as this. As far as I know, this shouldn't have a _url attribute. So I must have some mistake in the queryTask as well.
Obsolete part of the question, archived:
I wonder whether the problem might be caused by the onChange callback's argument, which I don't really understand. I've read several examples of Dojo FilteringSelect with onChange event set, like the official one. Usually it has some argument, but I don't know what does it mean and where it gets from. In the official example it corresponds to the div id and nothing else. However, how is the id translated to a variable and even whether my assumption that it's the id is correct. The detailed documentation is not much helpful in this case.
Older EDIT: testSelect is an object. Seems it was created implicitly with the dom - it's a childnode of this.domNode. So now I understand where it comes from, but not how to prepare another function to be called on change and why the correct syntax is what it is.
Also, I don't know where exactly is the problematic undefined this._url supposed to exist. In my example, this refers to the dijit, but I have no idea what is the scope of the part of init.js where the problem occurs.
I'm not sure how much related are the two subquestions (confusion in the onChange callback and identification of this in init.js), I only suppose they are symptoms of the same issue since the error is thrown at the onChange event in my code.
The problem at the query was caused by this.serviceUrl either missing as a part of wrong coontext, or being an object instead of a URL string. So the final step was to make sure the correct context is used (by lang/hitch) and then setting this.serviceUrl to be what I originally thought, one of the attributes of the object referenced as serviceUrl before.

Is saving elements as object properties good practice?

I'm writing a small JavaScript framework for fun and possible implementation similar to backbone(hence the tag). I've started saving elements as object properties, as shown below. I'm not sure if I've seen this done, so I was curious if this causes any issues.
Similarly, If the module depends on other modules I list those at the top of the object in the form of....another object.
I wanted a way to list dependencies ( page elements or JavaScript modules ) and detect any issues up front. This has similar ( not same ) benefits as dependency injection.
This is a specific question on this code review post which explains a bit further on how the framework works.
/*MUserTry
**
**
**
*/
$A.modelAjax({
Name: 'MUserTry',
S: {
DynSma: SDynSma,
DynTwe: SDynTwe,
DynArc: SDynArc,
AniFlipPage: SAniFlipPage,
ClientStorage: SClientStorage
},
E: {
but: $A('#ut_but')[0]
},
J: {
box: $('#ut_box')
},
init: function () {
var pipe = {},
this_hold = this;
this.J.box.draggable();
this.E.but.addEventListener("click", function () {
pipe = $A.definePipe(this_hold.Name);
$A.ajaxMachine(pipe);
}, false);
},
pre: function (pipe) {
pipe.page.email = this.e_button.getAttribute('data-email');
pipe.proceed = true;
},
post: function (pipe) {
this.S.ClientStorage.setAll(pipe.server.smalls);
this.S.DynSma.run(pipe.server.smalls);
this.S.DynArc.run(pipe.server.arcmarks);
this.S.DynTwe.run(pipe.server.tweets);
this.S.AniFlipPage.run('ma');
},
finish: function (pipe) {
$A.log(pipe);
}
});
Ok first off let me offer the obligatory "you'll never get a better wheel by re-inventing the wheel" warning. Whatever you're trying to accomplish, you're almost certainly going to be more successful with it if you use an existing library. And even if there is good cause for you to make your own, it would still benefit you immensely to at least look at existing libraries.
But ... maybe you're just having fun with this project, and looking at other projects isn't fun so you're not doing it. Fair enough.
In any case, if you do look at Backbone, you'll find that this practice is core to the Backbone View class. Every View in Backbone has an "el" and "$el" property, which refer to the raw DOM element for the view and the jQuery-wrapped version of that element.
Backbone has no real performance issues with this because variables/properties in JS are just pointers; in other words, when you set the property of an object to an element, you aren't duplicating that element, you're just adding a reference to it (to put it another way, it's more like you're an A tag rather than a whole new document).
The one time Backbone does have a problem though (and your framework will too) is with stale references. In other words, if you just remove element X from the page, the browser will stop using memory for it (eventually, via garbage collection). But if there is an object out there which points to that element, it won't get garbage-collected, because anything with a reference isn't "garbage".
So, the main thing you have to watch out for is making sure that these objects either:
A) get deleted whenever their elements do, or
B) get rid of their references (eg. delete obj.reference) when their elements get deleted
If you don't do that, things will still probably work just fine ... until you use it on a page with lots of elements being created/deleted, at which point Firefox will start popping up "this script took way too long to run, are you really sure you want to be doing this?" messages.

Categories