Is this right? A jQuery bug that erases the data store? - javascript

Fire up your firebug console and try this out.
Compare this:
$('body').data('x',1);
$(thisx).remove();
console.log($('body').data('x'));
to this:
$('body').data('x',1);
$(this.x).remove();
console.log($('body').data('x'));
Notice the difference? If thisx is undefined, it will immediatly throw a reference error. If x is an undefined property of this, jQuery will return the document as it's result set instead. Next jQuery will attempt to remove your document (which it can't), but before doing that it will remove all data attached to any child element of the document. Thus, wiping out your data store.
Note: this can be any element reference or object. You only need to have jQuery attempt to access an undefined property.
(Talk about a pain. It fails silently, and I'm trying to figure out why my data is suddenly missing. I track it down to a special case where an element reference was undefined in a specific situation.)
So on to my questions:
1) Before I submit a bug report, am I analyzing this correctly? Also if someone happens to know that this is a known issue, let me know. I couldn't find it in the bug tracker, but the interface isn't that great (or maybe I have this wrong).
2) Why is there ultimately any difference? I'm guessing thisx is evaluated immediately which causes the exception while this.x is a reference that is passed and evaluated in the called function, right? (where I think the line selector = selector || document; is the culprit.
3) Suggestions for how to handle this? I guess I should be checking that any/every element reference or property of an object (e.g. stored selector strings) is defined before I pass it to jQuery when removing something.

Why is there ultimately any difference?
Both thisx and this.x are evaluated when the function is called. The first one refers to an undefined variable name and this throws a reference error. The second one accesses an undefined property of an object, which results in the value undefined. This is just how javascript behaves in these cases.
Now when JQuery is called in the second case, the call $(this.x) evaluates to $(undefined) which is the same as if you just would have called $(). Since it looks to JQuery as if no argument was provided, it uses a default instead, and in this case the default is document. Then it proceeds trying to delete document, since it was effectively called as $().remove(), in which case this would be expected.
Suggestions for how to handle this?
The difference with the ReferenceError is a fundamental Javascript difference, not much that can be done about that. JQuerys behavior is unfortunate and a consequence of setting defaults by arg = arg||default. One could user arguments.length instead to get the real number of call parameters, but a change like this would surely result in lot's of broken code that relied on the default being used when undefined or 0 is passed, so it's unlikely to happen.

Try typing these into the console too (with no extra variables defined beforehand):
> a
ReferenceError: Can't find variable: a
> b = {}
Object
> b.a
undefined
one is a javascript error, one silently returns undefined (which jQuery will interpret as $() because javascript and jQuery can't tell the functions $() and $(undefined) apart)
this is the way javascript works, bug or feature I leave open to debate, but I don't think that this is jQuery's fault or problem.
edit: why does jQuery have $() defined?
From the docs:
By default, if no context is specified,
$() looks for DOM elements within the
context of the current HTML document.
If you do specify a context, such as a
DOM element or jQuery object, the
expression will be matched against the
contents of that context.
edit: the docs were referring to the context argument passed to $(), not to calling $() without arguments, so it isn't relevant here.
also note that
$().get(0) == $("").get(0)

Related

Undefined object method call inside the same defined object

I have this following scenario:
I have a JS (parent) class that constructs/creates two other classes. The parent class is attached to window object on the client side (browser). My issue is a call in the following manner:
window.parent.create({data: window.parent.utility.getId()})
in rare occasions, like one in 200 or one in 1000 I get an error that says can't call utility of undefined!
this means that the parent class is undefined! but if it was undefined how did it even make a call to window.parent.create() ?
what could possibly allow parent to be defined on the first call then make it undefined in the inner call?
but if it was undefined how did it even make a call to window.parent.create() ?
The arguments are processed before the function is called (after all, the program needs to know the function's arguments). This is why it's failing on window.parent.utility.getId() instead of window.parent.create(), the .getId() is running first.
I'd bet that if you replaced window.parent.utility.getId() with a string literal you'd see the failure happen on the .create() call instead.
Edit as pointed out by Bergi below technically the program is accessing window.parent.create first, which will return undefined. This value doesn't matter until after the arguments are processed and it tries to call the value as a function. The getId() function fails before this happens, which is why you are seeing this specific error.

How to silently fail querySelector() like jQuery() if element does not exists

I can't get this thing right.
Why jQuery() outputs an object by default? And is there some way to achieve same result with JavaScript?
jQuery('#configurator-material')
init [section#configurator-material.section, context: document, selector: "#configurator-material"]
document.querySelector('#configurator-material');
<section id=​"configurator-material" class=​"section" ng-controller=​"materialCtrl as material">​…​</section>​
document.querySelectorAll('#configurator-material');
NodeList [section#configurator-material.section]
In general my problem is that at some point I have this line:
jQuery('#myid').addClass('myclass');
im trying to replace it with JS like this:
document.querySelector('#myid').classList.add('myclass');
I thought that it should be fine and without errors,
but i'm getting "Cannot read property 'classList' of null" on the JS variant if a specific element does not exists on the page.
Handling non existent Elements
Internally the jQuery library conditionally handles non existent elements - failing silently without error.
Take for example a $("#nonExistent"), even if the Element is not retrieved in the DOM, the returned value will still be a jQuery Object instance - with all its constructor methods available, like .addClass() etc.
In contrast, JavaScript's .querySelector() returns an Element Object, but if the element is not found — the primitive null is returned.
JavaScript will break your code if you try to access properties of non-Object. Just like manually writing null.classList will throw an error:
Uncaught TypeError: Cannot read properties of null
Use JavaScript Optional Chaining .?
The simplest way to emulate the similar non-erratic behavior is to use the Optional Chaining operator .?
// jQuery
$("#notExistent").addClass("red"); // No errors
// JavaScript
document.querySelector("#notExistent")?.classList.add("red"); // No errors
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
Use Babel if you need support for IE browser - or use an if statement:
// JavaScript
const EL_notExistent = document.querySelector("#notExistent");
if (EL_notExistent) EL_notExistent.classList.add("red"); // No errors

VBA IE call a javascript containing 'this' keyword

I am attempting to call a javascript function on a webpage that contains the 'this' keyword which is referring to the <input> textbox on the webpage. The function looks like this:
functiondostuff('hdnAttribute',this,'Key')
Using
js = "functiondostuff('hdnAttribute',this,'Key')"
Call IE.Document.parentWindow.execScript(js)
doesn't throw an error but does not produce the results of the function since this cannot be identified.
Stepping through the website this = [object DispHTMLInputElement] instead of the element name while the function is running. Anyone have any ideas?
Good Morning,
Adding more to this issue. There seems to be two problems, 1st is setting the window.event, functiondostuff begins with: if (window.event && window.event.keyCode == 13), when the function is called it exits out immediately due to the event being null. Is there a way to pass the event as 13 to the website? The second issue is submitting the "this" HTMLInputObject.
Does anyone know a method to fire the 'onkeypress' event? I am at the point of trying sendkeys to avoid calling the function but have not been able to get them to work with IE. Thanks for any suggestions!
Key point is context. If you have this HTML
<input onclick="functiondostuff('hdnAttribute',this,'Key')">
then the browser can infer context from the user interaction and set this for you correctly.
From within VBA that's a slightly different matter and you have to define context manually.
How about this:
Dim js As Variant
js = Array( _
"var input = document.getElementById('yourElementsId');", _
"functiondostuff('hdnAttribute',input,'Key');" _
)
Call IE.Document.parentWindow.execScript(Join(js, vbNewLine))
This way you get to define context yourself.
document.getElementById was just for the sake of the example. If your element has no ID, use any other method (like DOM traversal, document.querySelectorAll, document.getElementsByTagName + a loop, ...) to get a reference to the desired element.

How do I check an element is exists in JQuery?

I have to set a specific value to an element if the element is exists.
var a = jQuery("#abc");
if(a) {
a.val("something");
}
For this, I've to check a.length to check the element is exits.
What happen if I directly set the value without checking the element is present or not?
Because, If I do the following
jQuery("#abc").val("dfd");
I don't get any error in chrome when the element is not present. So, can I continue to use like this?
or
any workaround?
Help appreciated!
What happen if I directly set the value without checking the element is present or not?
Nothing. Calling jQuery methods on an empty jQuery object (set) doesn't cause a problem, it just does nothing. This is one of the great things about the set-based concept used in jQuery. The equivalent DOM code (document.getElementById("abc").value = "something";) would throw an error, but the jQuery version doesn't.
Specifically, if the jQuery set is empty:
Calling setter methods (like your val call) becomes a no-op.
Calling getter methods — for instance, var x = $("#foo").val(); — returns the value undefined.
Calling traversal methods — for instance, var divs = $("#foo).find("div"); — gives you a new empty set.
You only need to check (using if (a.length) as you said, or if (a[0])) if you actually care.
jQuery("#abc").val("dfd");
I don't get any error in chrome when the element is not present. So, can I continue to use like this?
Yup.
jQuery's val() method simply sets (or gets) the value of each matching element. If there are no matching element, there will be no value to set (or get). You don't need to check if the element exists first.
From jQuery's val() documentation:
Description: Set the value of each element in the set of matched elements.
If there are no matched elements, nothing will happen.
Try with -
jQuery("#abc").length > 0
Yes, you can safely continue. JQuery just executes a function on all elements found by the selector - if there are none, it does nothing. There's no error.

appendChild returning Cannot read property 'containerDiv' of undefined

I am trying to add a div element to my top level div container, but for some reason I get an error.
Here's my code. It's a button, that once you click, is supposed to add a box on the screen.
To avoid the jQuery bug with this., I defined a variable on top of my class, called self
var self = this;
So this fixes the jQuery bug.
$(this.button).click(function() {
self.newContainer = new divGenerator();
self.containerDiv.parentNode.appendChild(self.newContainer.divContainer);
});
What's really weird is that when I print out
console.log("self.newContainer: "+self.newContainer.containerDiv);
I get "self.newContainer: [object HTMLDivElement]" as the result. And an HTMLDivElement is exactly what I need, right? It's a node, and appendChild() needs a node element. So everythign seems right. But it's not. Why?
It seems to me (and I may be reading this wrong, you'd have to post the internals of divGenerator for me to be certain) that your problem is simply a naming error... a typo if you will. In the following line:
self.containerDiv.parentNode.appendChild(self.newContainer.divContainer);
I notice that you are calling divContainer rather than containerDiv on self.newContainer. Unless you also have a property defined as divContainer, I'm guessing you just need to change the name of your property call to be containerDiv, as is the case in the rest of your code.
Sidebar: I noticed that you mention
To avoid the jQuery bug with this, I defined a variable on top of my class, called self
To the best of my knowledge, there is no such "error" in jQuery. Rather, you are experiencing confusion regarding the usage of this, which is as a keyword referencing the current function context (i.e. the object within which you are currently operating).
Your solution is, in fact, a common idiom within JavaScript, allowing you to refer to an enclosing context by means of a variable assigned to this in the outer scope. I recommend reading this article to clarify your understanding of the this construct and how it can and should be used.

Categories