addEventListener() to non-existent elements? - javascript

I have attached a click event listener on an element like:
document.querySelector('.class-name').addEventListener('click', function () {
});
The element may or may not get generated from the server-side.
So, if the server generates the element then all works fine but if not then I get an error like:
Cannot read property 'addEventListener' of null
I know why this happens, but I want to know whether there is a better way of attaching event listeners to elements that won't generate such errors?

There's no way of doing this without some sort of conditional test, but you can save a few characters compared to an if block thus:
var el = document.querySelector('.class-name');
el && el.addEventListener(...);
I don't believe there's any simple way of avoiding the temporary variable (but see below).
NB: the below is included just to show that it's possible and should not be construed as a recommendation ;-)
If this is a very common pattern in your HTML and JS, a way to avoid the temporary assignment to el is this:
var missing = {
addEventListener: function() {};
}; // a "null" element
(document.querySelector('.className') || missing).addEventListener(...);
The idea being that the || missing ensures that there's something present to absorb the addEventListener reference and invocation.

Just check before if your element is here or not (like in comment ) :
var el = document.querySelector('.class-name');
if (el) { el.addEventListener(...); }
Edit : You can also wrap your element .class-name into a div and do something like that :
document.getElementById("myDiv").addEventListener("click",function(e) {
var classes = e.target.className;
if(classes = ".class-name")
//DO SOMETHING
});

Since ES2020
ES2020 introduced optional chaining and that feature is exactly what you need here:
document.querySelector('.class-name')?.addEventListener('click', ()=> console.log("Clicked"));
Supported in all modern up-to-date browsers.

You have to be sure that element exist. So
var element = document.querySelector('.class-name');
if (element)
element.addEventListener('click', function () {});

Related

Passing object HTMLInputElement as a parameter to function

I am trying to pass a DOM element to a function without any luck. FireBug reports the following issue:
SyntaxError: missing ] after element list
LlenarDetalleReserva([object HTMLInputElement]); return false;
The selector needs to add a few attributes dynamically as it's being created.
I've tried the following:
$('#mySelector').attr({'onkeydown' : 'intOrFloat(event,this.value);', 'onchange' : 'LlenarDetalleReserva('+ $.trim($('#cant_r_'+ vectorid[2])[0]) +'); return false;'});
What am I missing here? What is producing this error?
Thanks in advance.
Please, don't do that. Use on() to bind an event to the element. Never set it as an attribute directly.
$('#mySelector')
.on('keydown', function(event)
{
intOrFloat(event, this.value);
})
.on('change', function(event)
{
if (vectorid && vectorid[2] != undefined)
{
var element = $('#cant_r_'+ vectorid[2]);
if (element.length > 0)
{
LlenarDetalleReserva(element);
}
}
return false;
});
The code above(full of ifs) is made to avoid exceptions at run-time. That is what I would do in your case. Further, I don't really know what you're trying to get in that $.trim() function, but it seems that you want to get the element and pass it as a string. In the code above its being passed by reference, instead.
UPDATE:
In order to answer OP's question of why I shouldn't set an attribute directly I didn't tell you you can't add an attribute directly but an event handler as attribute.I can't tell what happens in the backgrounds of the engine, so in fact, it works adding as attribute. But isn't a best practice and it's part of Unobtrusive approach.

JQuery to Javascript Document.Write

Fiddle (Uses JQuery) - http://jsbin.com/ponikasa/1/edit
I know JQuery is Javascript, but for the sake of an argument how do you write the following in pure Javascript without the need for a js library like JQuery?
$(document).ready(function() {
$('.preview-site').on('click', function(){
window.open('javascript:document.write("'+ $('.workflow').val() +'")', 'Opened Page', 'width=660, height=440');
return false;
});
});
I tried this, but doesn't work.
Any help is greatly appreciated.
window.onload = function() {
var preview = document.getElementsByClassName("preview-site"),
code = document.getElementsByClassName("workflow")[0].value;
preview.onClick = function() {
window.open('javascript:document.write("'+ code = +'")', 'Opened Page', 'width=660, height=440');
return false;
}
}
Well to write in javascript you would do the following
document.addEventListener('DOMContentLoaded', function() {
var previewSite = this.querySelectorAll('.preview-site');
var handler = function() {
var workflow = document.querySelector('.workflow')
window.open('javascript: document.write(' + workflow.value + ')', 'Opened Page', 'width=660, height=440')
return false;
};
for( var i = 0; i < previewSite.length; i++) {
previewSite[i].addEventListener('click', handler);
}
});
The problem you had is getElementsByClassName returns a collection, so you cannot use value or onclick on the collection.
I use querySelectorAll because it's easier and has almost better support that getElementsByClassName
I don't usually answer questions like this, but I am highly supportive of anyone that uses jQuery that want's to actually learn javascript it's self
also, in your question, you have onClick, for the event handler you want onclick
For one minor performance improvement you could move workflow out of handler, that way it won't fetch it on every click, only do this if you don't intend to add dynamic .workflow
Yeah, and also. (as pointed out in comments) window.onload is not the same as document ready, window.onload will wait for images & media to be fully loaded, so use DOMContentLoaded
One of the things jQuery selectors do is try to abstract the "array" when calling functions and assigning handlers. Consider something like this:
$('.preview-site').on('click', function(){
// code
});
This code doesn't just assign the click handler. On a lower level than that presented by the jQuery interface, this iterates the array of .preview-site elements and assigns the click handlers to each element. Sometimes it's one element, sometimes it's many. (Sometimes it's none.) jQuery makes the interface the same regardless of the count.
Without it, you need to handle that difference explicitly. These values are arrays:
var preview = document.getElementsByClassName("preview-site"),
code = document.getElementsByClassName("workflow");
Even if each one only finds a single element by that class name, the result from document.getElementsByClassName() is an array. So even if the array has only one element, it's still an array. And you can't assign a handler to an array, you need to assign it to each element in the array. Potentially something like this:
for (var i = 0; i < preview.length; i++) {
preview[i].addEventListener('click', function() {
window.open('javascript:document.write("'+ code[i].value[0] +'")', 'Opened Page', 'width=660, height=440');
return false;
}
}
Naturally, you'd probably want to put in some checks to ensure that the two arrays are the same length before assuming that for each preview element there exists a code element. But the principle is the same. You just need to account for the enumeration of the array manually.

Javascript Compare Objects to Elements

http://jsfiddle.net/PhilFromHeck/KzSxT/
In this fiddle, you can see at line 38 in the Javascript that I've attempted to make a comparison that isn't working. I believe it because one of the variables is an Object, where the other is an Element; does anyone have any advice as to how I can can find a match between these two?
menuID[0] = document.getElementById('menuOne');
menuID[1] = document.getElementById('menuTwo');
menuID[2] = document.getElementById('menuThree');
menuID[3] = document.getElementById('menuFour');
$('.menu').mouseenter(function () {
for (var i = 0; i < 3; i++) {
if(menuID[i] == $(this)){
//this condition is not met, there's an alert which will add more detail in the fiddle
}
}
}
Method document.getElementById returns a DOM element an not a jQuery object. In the mouseenter event handler this refers to a DOM element as well.
So in order to compare them you shouldn't convert this to a jQuery object:
if (menuID[i] === this) { ... }
You want to use jQuery's .is() for this.
if($(this).is(menuID[i])){
A few issues I see here:
One is simply that, in your jsfiddle, the first 4 lines of code that you list aren't running before the bottom block runs. I'm not sure why you have both an init function that you attach to window.onload and a document.ready() function; but you'll want to make sure that init runs.
Secondly; as VisioN said, I think the main issue is that you're trying to compare a jQuery wrapper around a DOM element $(this) with a DOM element (the result of getElementById). As he says, this == menuID[i] will work.
At a design level, why not simply use the id to identify the element? this.id will give you the the id; why not simply use that to determine which menu div you're looking at?

HTML DOM Extension = bad, but is this OK?

So I realize that in no way do I want to do:
Element.protoype.myfunc = function () {}
But, is this the same or not and is this a good practice?
var e = document.querySelector(q);
e.html = function (html) {
this.innerHTML = html;
}
e.html("Am I in trouble?");
Extending Element will not work in all browsers (notably IE<8). See also this SO question
Extending single elements may result in memory leaks: if such elements are deleted, the method can still exist, containing a link to the non existent element. See this link (it's about handler methods, but it can also apply to extension methods afaik).

$(this) OR event.target OR var input = $(this)

jQuery is currently providing me with a fun introduction to Javascript after 12 years of surviving happily without. I'm at the stage where I'm trying to learn as much as I can about optimising the code I write and, whilst I have found plenty of good reference material, there is something quite basic which is puzzling me and I have been unable to find anything about it anywhere.
When I'm attaching something to an element how should I be referring to that element within the function. For example, when attaching a function to an element's click event :
$('#a_button',$('#a_list_of_buttons')).click(function() {
// NOW WHAT'S THE BEST WAY TO REFER TO '#a_button' ?
});
I know not to keep re-selecting it like so as the browser has to search the whole DOM again from scratch to find what it's already found once :
$('#a_button').click(function() {
// I KNOW THAT THIS IS NAUGHTY
var buttonValue = $('#a_button').val();
$('#a_button').addClass('button_has_been_clicked');
});
Currently I'm using either of the following but am not entirely sure what each is actually doing :
$('#a_button').click(function() {
// USING this
var buttonValue = $(this).val();
$(this).addClass('button_has_been_clicked');
});
But is this just re-selecting like in the first "naughty" example?
$('#a_button').click(function(event) {
// USING event.target
var buttonValue = $(event.target).val();
$(event.target).addClass('button_has_been_clicked');
});
This seems like it might be better but is it efficient to refer to 'event.target' multiple times?
$('#a_button').click(function(event) {
// USING A LOCAL VARIABLE
var thisButton = $(this);
// OR SHOULD THAT BE
var thisButton = $(event.target);
var buttonValue = thisButton.val();
thisButton.addClass('button_has_been_clicked');
});
I understand the performance efficiencies of passing things to variables but I'm unsure whether or not in these situations using $(this) or $(event.target) provides me with the same efficiencies already and so by setting a new variable I'm actually doing more work that I need to.
Thank you.
this and event.target are not always the same.
this refers to the element you assigned the listener to ( in this case the '#a_button' ). event.target however is the element that actualy triggered the event, which can be a childnode of #a_button.
So $(this) is the thing you are looking for.
See reference: http://api.jquery.com/event.target/
I may be wrong, but this and event.target are both just different references to the same element.
this and event.target are not always references to the same element. But in answer to your question, var thisButton = $(this); is definitely the winner. If you were writing C# code, you would never do the following:
this.Controls[0].Controls[0].Text = "Foo";
this.Controls[0].Controls[0].Controls.Clear();
You would do this:
var control = this.Controls[0].Controls[0];
So you probably should never re-use $(this) more than once either. Althought it's trivial to convert this from a DOM element to a jQuery object, it's still an unnecessary overhead.
However, sometimes you need to gear back from optimisation to make sure your code maintains it's readability.
Another option of course is just to change what this is. This is javascript afteral:
this = $(this); // Now `this` is your jQuery object
Disclaimer: I only just tried the above and it seemed to work. Might have some issues though.
I built a little example to demonstrate how this and e.target actually work: http://jsfiddle.net/xZAVa/
In my experience i would go with the following:
$('#a_button').click(function() {
// USING this
var buttonValue = $(this).val();
$(this).addClass('button_has_been_clicked');
});
The this in the context of your click callback method is a reference to the DOM event. Since you already have a reference to the DOM object it is trival to convert it into a jQuery object since a lookup is not required.
But on a side note, if you don't need to use jQuery in your callback, then don't. You can simply get the value of the button using standard JS this.currentTarget.value.
The other examples you mentioned require a DOM lookup, and depending on the complexity of your selector can take longer. Using a id based lookup like '#a_button' will perform better than a class based looked like .myClass.

Categories