I am writing code in plain JavaScript, there are lot of scenarios where I will use querySelector() method, I ran into issue multiple times like
"Uncaught TypeError: Cannot read property 'classList' of null" for the following code,
document.querySelector('.tab.active').classList.remove('active');
/** Tab not available at the time**/
In Jquery $('.tab.active').removeClass('active'); will run only if the element is available without throwing error.
I want to achieve similar behavior in JavaScript. Please provide your ideas.
I am not willing to write three lines of code for every DOM operation I am doing, looking for one line code like Jquery.
var activeTab = document.querySelector('.tab.active');
if(activeTab !== 'null' ){
activeTab.classList.remove('active');
}
Explicitly checking for the existence of the element in your code as you're doing originally is surely the clearest way you could do it, but if you really don't want to, you could create your own function that, if no element is found, returns an object with methods that don't do anything. For example:
const customQS = selector => (
document.querySelector(selector)
|| {
classList: {
remove: () => void 0
}
}
);
customQS('.tab.active').classList.remove('active');
console.log('done, no error');
Of course, with that method, you'd have to create properties for each DOM method you'd want to use. A more robust option would be to actually create an element and return it, which would be more expensive, but the element will be garbage collected right afterward:
const customQS = selector => (
document.querySelector(selector)
|| document.createElement('div')
);
customQS('.tab.active').classList.remove('active');
console.log('done, no error');
Related
I'm experiencing a very strange bug using testcafe. On one of my websites I'm not able to select div-elements but I'm still able to select other elements. So
await t.click(Selector("span").withAttribute('id', 'foo'));
await t.click(Selector("p").withAttribute('id', 'foo'));
await t.click(Selector("button").withAttribute('id', 'foo'));
await t.click(Selector("input").withAttribute('id', 'foo'));
works fine while
await t.click(Selector("div").withAttribute('id', 'foo'));
will throw the following error:
Function that specifies a selector can only return a DOM node, an
array of nodes, NodeList, HTMLCollection, null or undefined. Use
ClientFunction to return other values.
This only happens on one specific website of mine. What could this be? What could I have possibly done in my script to prohibit the testcafe selector to get "div" elements? Really out of ideas right now.
This issue can occur if you pass a function with an incorrect return value as a Selector argument:
test('Return non-DOM node', async () => {
await Selector(() => 'hey')();
});
However, the error should not occur if you use string as a parameter to specify your selector. Please check that you do not pass a function to your Selector object.
If your Selector is defined correctly and the issue still appears, please share the example (html page or public url to your site) and your full test code to demonstrate the issue.
I want to grab a string that has a particular class name, lets say 'CL1'.
This is what is used to do and it worked:
(we are inside an asycn function)
var counter = await page.evaluate(() => {
return document.querySelector('.CL1').innerText;
});
Now, after some months, when i try to run the code i get this error:
Error: Evaluation failed: TypeError: Cannot read property 'innerText' of null
I did some debugging with some console.log() before and after the previous snippet of code and found out that this is the culprit.
I looked the code of the webpage and the particular class is inside.
But i found out two more classes with the same name.
All three of them are nested deep inside many classes.
So what is the proper way to selected the one i want, given i know the class hierarchy for the one i am interested in?
EDIT:
Since there are three class names with the same name, and i want to extract info from the first, can i use an array notation on the querySelector() to access the information from the first one?
EDIT2:
I run this:
return document.querySelector('.CL1').length;
and i got
Error: Evaluation failed: TypeError: Cannot read property 'length' of null
This gets even more confusing...
EDIT 3:
I trie the suggestion of Md Abu Taher and i saw that the snippet of code he provided did not return undefined. This means that the selector is visible to my code.
Then i run this snippet of code:
var counter = await page.evaluate(() => {
return document.querySelector('#react-root > section > main > div > header > section > ul > li:nth-child(1) > a > span').innerText;
});
And i got back the same error:
Error: Evaluation failed: TypeError: Cannot read property 'innerText' of null
The answer is divided in to parts. Getting right selector, and getting data.
1. Getting right Selector
Use inspect element
Right click on your desired element and click inspect element.
Then right click and click Copy > Copy selector
This will give you a unique selector for that specific element.
Use a selector tool
There are bunch of chrome extension that helps you find the right selector.
Selectorgadget
Get Unique CSS Selector
Copy Css Selector
2. Getting the data
Given your selector is .CL1, you need to do few things.
Wait for all Network events to finish
Basically on a navigation you can wait until network is idle.
await page.goto(url, {waitUntil: 'networkidle2'});
Wait for the element to appear in DOM.
Even if the network is idle, there might be redirect etc. Best choice is to wait until the element appears. The following will wait until the element is found and will throw an error otherwise.
await page.waitFor('.CL1');
Or, Check if element exists and return data only if it exists
If you do not want to throw an error or if the element appears randomly, you need to check it's existence and return data.
await page.evaluate(() => {
const element = document.querySelector('.CL1');
return element && element.innerText; // will return undefined if the element is not found
});
try to verify the element before
var x = document.getElementsByClassName("example");
OR
var x = document.getElementsById("example");
and then
var counter = await page.evaluate(() => {
return x.innerText;
});
I'm having a strange issue that's being thrown in Firefox when using my Dojo (v.1.10.0) application.
Here is the following error that I'm seeing in Firefox:
Exception
{ message: "",
result: 2147549183,
name: "NS_ERROR_UNEXPECTED",
filename: "http://localhost:8888/dojo/on.js",
lineNumber: 354,
columnNumber: 0,
inner: null,
data: null
}
""
Unfortunately, I'm not sure where to go with this in my application. Can anyone point me in the right direction?
On line 354 of dojo/on, this is happening:
if(has("dom-addeventlistener")){
// emitter that works with native event handling
on.emit = function(target, type, event){
if(target.dispatchEvent && document.createEvent){
// use the native event emitting mechanism if it is available on the target object
// create a generic event
// we could create branch into the different types of event constructors, but
// that would be a lot of extra code, with little benefit that I can see, seems
// best to use the generic constructor and copy properties over, making it
// easy to have events look like the ones created with specific initializers
var ownerDocument = target.ownerDocument || document;
var nativeEvent = ownerDocument.createEvent("HTMLEvents");
nativeEvent.initEvent(type, !!event.bubbles, !!event.cancelable);
// and copy all our properties over
for(var i in event){
if(!(i in nativeEvent)){
nativeEvent[i] = event[i];
}
}
return target.dispatchEvent(nativeEvent) && nativeEvent; // Line 354
}
return syntheticDispatch.apply(on, arguments); // emit for a non-node
};
}
This is a generic FF error message... it's usually triggered by a timing or race condition, which may explain why it's showing up via dojo/on. Maybe the target or event handler that you're trying to work with is acting on something that has been removed, etc. It's unclear without knowing what event is triggering it or without seeing your full code example.
For example, maybe you're trying to add event listeners before the DOM is available, but that's just a guess. Or maybe the target node doesn't exist.
You can use the debugger to see the values of the event parameters, or you can look at your various event registration mechanisms, etc.
We have a similar issue using intern 2.0 and unit tests creating native select boxes.
Some library code (verified that its not our own) triggers a dojo.emit() which causes the internal error.
We're trying to identify the problem in more detail. If you find something please let us know as well!
we were also getting same exception at exactly same point,
for us, we replaced our code elementReference.destroy() // destroy is a dojo function with elementReference.domNode.remove() and it solved our problem.
Why following piece of code doesn't work since jQuery 1.9.1? With previous versions works fine.
$(function () {
$(document).append(test);
document.write('done');
});
var test = {
version: "1.0",
};
JSFiddle: http://jsfiddle.net/Chessjan/NsjqM/
In JS console it issues error like this:
TypeError: document is null
safeFrag = document.createDocumentFragment(); jquery-1.9.1.js (line 5823)
Edit:
Thanks everybody for quick and extensive aswers. Observed issue was found by accident, and of course, $(document.body).append() is proper approach.
jQuery 1.9.x calls
this[ 0 ].ownerDocument
within its buildFragment() method. Since you pass in the document, the call
document.ownerDocument
will reference to null and cause the error. Any other node will reference the document, which of course, works.
Conclusion: Don't call $(document).append() but use $(document.body) for instance.
Your code will of never worked. It has to document.body not document.
Here's a few examples in different versions of it not working:
jQuery 1.6.4: http://jsfiddle.net/us9Kz/
jQuery 1.7.2: http://jsfiddle.net/us9Kz/1/
jQuery 1.8.3: http://jsfiddle.net/us9Kz/3/
jQuery 1.9.1: http://jsfiddle.net/us9Kz/4/
jQuery 2.0.0b1: http://jsfiddle.net/us9Kz/5/
Code working with document.body (on jQuery 1.9.1): http://jsfiddle.net/us9Kz/6/
Inside the jQuery code it has this line:
jQuery.buildFragment( args, this[ 0 ].ownerDocument, false, this );
this is the jQuery object you selected. In your case, the document. The ownerDocument value of document is null and this is what is passed through as document to the call to document.createDocumentFragment();. Hence you get the error that document is null (Slightly bad naming of variables there as it makes you think the document object itself is somehow null)
As other people have said. Append to the body instead and it will work fine.
To answer your question i tried in JSfiddle all the available jQuery versions. It happened to give the same error.
Why it doesnt work: document becomes something like [object HTMLDocument] when cast to string, and there is of course no such id, it will return null.
The following works:
var test = "1.0"
$('body').append(test);
or doing it trough object notation like you did:
var test = {
version: '1.0'
}
$('body').append(test.version)
I'm writing quite a bit of code in Prototype.js which returns null if a DOM-id wasn't found.
$("someId").show();
If someId doesn't exist, a method will be called on null, which halts the entire program, in effect disabling all JS effects after the error. I could just check for null before executing such a statement, but this is getting tiring.
I would like to catch an exception but I'm not sure which one its is. MDC lists the following ECMA Script error types, but on first glance none of them seem to be what I want:
* Error
* EvalError
* RangeError
* ReferenceError
* SyntaxError
* TypeError
* URIError
* DOMException
* EventException
* RangeException
Also, do browsers have a unified way of dealing with a method call on null?
I don't believe there's unity to be found. Chrome throws a TypeError, but IE throws an Error, so you would probably have to catch everything and make severe assumptions. Better to check for null first.
var element = $('someId');
if (element) {
element.show();
// whatever else...
}
If element.show() is the only thing you need it for, then it can obviously be written a lot shorter, but in most cases that would be appropriate.
The correct way to handle this is to check for null before doing something with an object. There are several shorthand ways to do this, the shortest is (as Alex K) wrote
$("someId") && $("someId").show();
but this seems to me to be harder to read.
To answer your question directly you can do
try { $('someId').show(); } catch (e) {}
but this seems amateurish. You should program explicitly because later on someone else won't know why you wrote that odd code. The first example is slightly opaque but at least contains the null test first, and doesn't hide errors in the show() method.
Incidentally, if you were using JQuery instead of Prototype, this code would work without error even if there is no object with id 'someId':
$('#someId').show()
That's because the $() function in JQuery returns a collection which may be empty but is never null.
If your going to chain .show() on $("someId") then check its result first.
if ($("someId"))
$("someId").show();
or
$("someId") && $("someId").show();
or
if (someVar = $("someId"))
someVar.show();
If for some reason you really need to identify them you could wrap $() and throw a custom exception:
function NullReferenceException(id) {this.id = id}
function $my(id) {
var el = $(id);
if (!el)
throw new NullReferenceException(id);
return el
}
try {
$my("iDontExistId").show();
} catch (e) {
if (e instanceof NullReferenceException)
alert(e.id + " doesn't exist");
}
Just ignore which exception it is...
try
{
null.hey()
}
catch(e)
{
//handle it here
}