I have heard many times that using JavaScript events, such as onClick(), in HTML is a bad practice, because it's not good for semantics. I would like to know what the downsides are and how to fix the following code?
link
You're probably talking about unobtrusive Javascript, which would look like this:
link
with the logic in a central javascript file looking something like this:
$('#someLink').click(function(){
popup('/map/', 300, 300, 'map');
return false;
});
The advantages are
behaviour (Javascript) is separated from presentation (HTML)
no mixing of languages
you're using a javascript framework like jQuery that can handle most cross-browser issues for you
You can add behaviour to a lot of HTML elements at once without code duplication
If you are using jQuery then:
HTML:
<a id="openMap" href="/map/">link</a>
JS:
$(document).ready(function() {
$("#openMap").click(function(){
popup('/map/', 300, 300, 'map');
return false;
});
});
This has the benefit of still working without JS, or if the user middle clicks the link.
It also means that I could handle generic popups by rewriting again to:
HTML:
<a class="popup" href="/map/">link</a>
JS:
$(document).ready(function() {
$(".popup").click(function(){
popup($(this).attr("href"), 300, 300, 'map');
return false;
});
});
This would let you add a popup to any link by just giving it the popup class.
This idea could be extended even further like so:
HTML:
<a class="popup" data-width="300" data-height="300" href="/map/">link</a>
JS:
$(document).ready(function() {
$(".popup").click(function(){
popup($(this).attr("href"), $(this).data('width'), $(this).data('height'), 'map');
return false;
});
});
I can now use the same bit of code for lots of popups on my whole site without having to write loads of onclick stuff! Yay for reusability!
It also means that if later on I decide that popups are bad practice, (which they are!) and that I want to replace them with a lightbox style modal window, I can change:
popup($(this).attr("href"), $(this).data('width'), $(this).data('height'), 'map');
to
myAmazingModalWindow($(this).attr("href"), $(this).data('width'), $(this).data('height'), 'map');
and all my popups on my whole site are now working totally differently. I could even do feature detection to decide what to do on a popup, or store a users preference to allow them or not. With the inline onclick, this requires a huge copy and pasting effort.
It's not good for several reasons:
it mixes code and markup
code written this way goes through eval
and runs in the global scope
The simplest thing would be to add a name attribute to your <a> element, then you could do:
document.myelement.onclick = function() {
window.popup('/map/', 300, 300, 'map');
return false;
};
although modern best practise would be to use an id instead of a name, and use addEventListener() instead of using onclick since that allows you to bind multiple functions to a single event.
With very large JavaScript applications, programmers are using more encapsulation of code to avoid polluting the global scope. And to make a function available to the onClick action in an HTML element, it has to be in the global scope.
You may have seen JS files that look like this...
(function(){
...[some code]
}());
These are Immediately Invoked Function Expressions (IIFEs) and any function declared within them will only exist within their internal scope.
If you declare function doSomething(){} within an IIFE, then make doSomething() an element's onClick action in your HTML page, you'll get an error.
If, on the other hand, you create an eventListener for that element within that IIFE and call doSomething() when the listener detects a click event, you're good because the listener and doSomething() share the IIFE's scope.
For little web apps with a minimal amount of code, it doesn't matter. But if you aspire to write large, maintainable codebases, onclick="" is a habit that you should work to avoid.
Revision
Unobtrusive JavaScript approach was good in the PAST - especially events handler bind in HTML was considered as bad practice (mainly because onclick events run in the global scope and may cause unexpected error what was mention by YiddishNinja)
However...
Currently it seems that this approach is a little outdated and needs some update. If someone want to be professional frontend developper and write large and complicated apps then he need to use frameworks like Angular, Vue.js, etc... However that frameworks usually use (or allow to use) HTML-templates where event handlers are bind in html-template code directly and this is very handy, clear and effective - e.g. in angular template usually people write:
<button (click)="someAction()">Click Me</button>
In raw js/html the equivalent of this will be
<button onclick="someAction()">Click Me</button>
The difference is that in raw js onclick event is run in the global scope - but the frameworks provide encapsulation.
So where is the problem?
The problem is when novice programmer who always heard that html-onclick is bad and who always use btn.addEventListener("onclick", ... ) wants to use some framework with templates (addEventListener also have drawbacks - if we update DOM in dynamic way using innerHTML= (which is pretty fast) - then we loose events handlers bind in that way). Then he will face something like bad-habits or wrong-approach to framework usage - and he will use framework in very bad way - because he will focus mainly on js-part and no on template-part (and produce unclear and hard to maintain code). To change this habits he will loose a lot of time (and probably he will need some luck and teacher).
So in my opinion, based on experience with my students, better would be for them if they use html-handlers-bind at the beginning. As I say it is true that handlers are call in global scope but a this stage students usually create small applications which are easy to control. To write bigger applications they choose some frameworks.
So what to do?
We can UPDATE the Unobtrusive JavaScript approach and allow bind event handlers (eventually with simple parameters) in html (but only bind handler - not put logic into onclick like in OP quesiton). So in my opinion in raw js/html this should be allowed
<button onclick="someAction(3)">Click Me</button>
or
function popup(num,str,event) {
let re=new RegExp(str);
// ...
event.preventDefault();
console.log("link was clicked");
}
link
But below examples should NOT be allowed
<button onclick="console.log('xx'); someAction(); return true">Click Me</button>
link
The reality changes, our point of view should too
There are a few reasons:
I find it aids maintenence to separate markup, i.e. the HTML and client-side scripts. For example, jQuery makes it easy to add event handlers programatically.
The example you give would be broken in any user agent that doesn't support javascript, or has javascript turned off. The concept of progressive enhancement would encourage a simple hyperlink to /map/ for user agents without javascript, then adding a click handler prgramatically for user agents that support javascript.
For example:
Markup:
<a id="example" href="/map/">link</a>
Javascript:
$(document).ready(function(){
$("#example").click(function(){
popup('/map/', 300, 300, 'map');
return false;
});
})
It's a new paradigm called "Unobtrusive JavaScript". The current "web standard" says to separate functionality and presentation.
It's not really a "bad practice", it's just that most new standards want you to use event listeners instead of in-lining JavaScript.
Also, this may just be a personal thing, but I think it's much easier to read when you use event listeners, especially if you have more than 1 JavaScript statement you want to run.
Your question will trigger discussion I suppose. The general idea is that it's good to separate behavior and structure. Furthermore, afaik, an inline click handler has to be evalled to 'become' a real javascript function. And it's pretty old fashioned, allbeit that that's a pretty shaky argument. Ah, well, read some about it #quirksmode.org
onclick events run in the global scope and may cause unexpected
error.
Adding onclick events to many DOM elements will slow down the
performance and efficiency.
Two more reasons not to use inline handlers:
They can require tedious quote escaping issues
Given an arbitrary string, if you want to be able to construct an inline handler that calls a function with that string, for the general solution, you'll have to escape the attribute delimiters (with the associated HTML entity), and you'll have to escape the delimiter used for the string inside the attribute, like the following:
const str = prompt('What string to display on click?', 'foo\'"bar');
const escapedStr = str
// since the attribute value is going to be using " delimiters,
// replace "s with their corresponding HTML entity:
.replace(/"/g, '"')
// since the string literal inside the attribute is going to delimited with 's,
// escape 's:
.replace(/'/g, "\\'");
document.body.insertAdjacentHTML(
'beforeend',
'<button onclick="alert(\'' + escapedStr + '\')">click</button>'
);
That's incredibly ugly. From the above example, if you didn't replace the 's, a SyntaxError would result, because alert('foo'"bar') is not valid syntax. If you didn't replace the "s, then the browser would interpret it as an end to the onclick attribute (delimited with "s above), which would also be incorrect.
If one habitually uses inline handlers, one would have to make sure to remember do something similar to the above (and do it right) every time, which is tedious and hard to understand at a glance. Better to avoid inline handlers entirely so that the arbitrary string can be used in a simple closure:
const str = prompt('What string to display on click?', 'foo\'"bar');
const button = document.body.appendChild(document.createElement('button'));
button.textContent = 'click';
button.onclick = () => alert(str);
Isn't that so much nicer?
The scope chain of an inline handler is extremely peculiar
What do you think the following code will log?
let disabled = true;
<form>
<button onclick="console.log(disabled);">click</button>
</form>
Try it, run the snippet. It's probably not what you were expecting. Why does it produce what it does? Because inline handlers run inside with blocks. The above code is inside three with blocks: one for the document, one for the <form>, and one for the <button>:
let disabled = true;
<form>
<button onclick="console.log(disabled);">click</button>
</form>
Since disabled is a property of the button, referencing disabled inside the inline handler refers to the button's property, not the outer disabled variable. This is quite counter-intuitive. with has many problems: it can be the source of confusing bugs and significantly slows down code. It isn't even permitted at all in strict mode. But with inline handlers, you're forced to run the code through withs - and not just through one with, but through multiple nested withs. It's crazy.
with should never be used in code. Because inline handlers implicitly require with along with all its confusing behavior, inline handlers should be avoided as well.
Related
I'm relatively new to JS but I'm very aware that performance is a very important issue in web development, hence this question:
I have searched and found some relevant threads (such as: Javascript pattern: Conditional event handler), but I have found this confusing as my scenario is far simpler and doesn't involve any advanced concepts such as classes.
For example context, imagine my website contains pages of various 'types' as defined by differing body class="type" tags. For clarity, we could have body class="member-details", body class="club-details" and body class="random-page-type". Imagine there were quite a few, say 20 possible body class types.
Consider the situation wherein I may wish to bind a particular click event handler to an element if the body is of some certain types and another if it's others.
The question is:
Is it better performance-wise to place handler in condition or condition in handler? For clarity, pseudocode:
1) condition inside event handler
ON DOCUMENT READY {
if page type is 'club-details' || page type is 'x' || page type is 'y' {
bind function1 ();
}
}
2) event handler inside condition
if page type is 'club-details' || page type is 'x' || page type is 'y' {
ON DOCUMENT READY {
bind fuction1();
}
}
This is a very simplified example, but I guess I'm asking for info regarding performance of always binding an event as in example 1 or decided whether to bind through an iterative condition as in example 2?
Since Javascript Events are executed asynchronously, your example is not the term of performance but memory. The second example will take less memory than the first one just because less events are bound. Client side performance is not that important in comparison with the server side one. That being said, you should consider writing nice and readable code over trivial performance benefits. Javascript is really fast nowaday.
I was just wondering which is the correct or most efficient way of navigating through the Dom using variables.
For example, can I concatenate selectors
var $container = '.my-container';
$($container).addClass('hidden');
$($container + ' .button').on('click', function(){
//something here
});
or should I use the jQuery traversal functions
var $container = $('.my-container');
$container.addClass('hidden');
$container.children('.button').on('click', function(){
//something here
});
Is there a different approach, is one best, or can you use them at different times?
The $ is usually used only when working with an actual jquery object. You generally shouldn't prefix anything with that unless it's really something from jquery.
Beyond that little bit though, performance-wise, your second bit of code is going to be faster. I made an example jsperf here: http://jsperf.com/test-jquery-select
The reason the second bit of code is faster is because (if I remember correctly) jquery caches the selection, and then any actions performed on that selection are scoped. When you use .find (which is really what you meant in your code, not .children), instead of trying to find elements through the entire document, it only tries to find them within the scope of whatever my-container is.
The time when you wouldn't want to use the second pattern is when you expect the dom to change frequently. Using a previous selection of items, while efficient, is potentially a problem if more buttons are added or removed. Granted, this isn't a problem if you're simply chaining up a few actions on an item, then discarding the selection anyway.
Besides all of that, who really wants to continuously type $(...). It's awkward.
I've seen some JavaScript code to access HTML elements like this: elementID.innerHTML, and it works, though practically every tutorial I searched for uses document.getElementById(). I don't even know if there's a term for the short addressing.
At first I thought simplistically that each id'ed HTML element was directly under window but using getParent() shows the tree structure is there, so it didn't matter that elements I wanted were nested. I wrote a short test case:
http://jsfiddle.net/hYzLu/
<div id="fruit">Mango<div id="color">red</div></div>
<div id="car">Chevy</div>
<div id="result" style="color: #A33"></div>
result.innerHTML = "I like my " + color.innerHTML + " " + car.innerHTML;
The "short" method looks like a nice shortcut, but I feel there is something wrong with it for it practically not appearing in tutorials.
Why is document.getElementById() preferred, or may be even required in some cases?
Why shouldn't I access elements more “directly” (elemId.innerHTML)
Because, according to the others in this thread, referencing arbitrarily by id name is not fully supported.
So, what I think you should be doing instead is store their selections into a var, and then reference the var.
Try instead
var color = document.getElementById('color');
color.innerHTML = 'something';
The reason why this would be a good thing to do is that performing a lookup in the DOM is an expensive process, memory wise. And so if you store the element's reference into a variable, it becomes static. Thus you're not performing a lookup each time you want to .doSomething() to it.
Please note that javascript libraries tend to add shim functions to increase general function support across browsers. which would be a benefit to using, for example, jquery's selectors over pure javascript. Though, if you are in fact worried about memory / performance, native JS usually wins speed tests. (jsperf.com is a good tool for measuring speed and doing comparisons.)
It's safer I guess. If you had a variable named result in the same context that you are doing result.HTML I'm pretty sure the browser will throw a wobbler. Doing it in the way of document.getElementById() in this instance would obviously provide you with the associated DOM element.
Also, if you are dynamically adding HTML to the page I may be wrong, but you could also encounter unexpected behaviour in terms of what result is :)
Also I will add that not all ID's can have values that will not work as variable names. For instance if your ID is "nav-menu".
Although I suppose you could write window["nav-menu"].innerHTML
Which makes me think, what happens if you create a window level variable with the same name as an ID?
Checkout this jsfiddle (tested in chrome): http://jsfiddle.net/8yH5y/
This really seems like a bad idea altogether. Just use document.getElementById("id") and store the result to a variable if you will be using the reference more than once.
using javascript, I generate HTML code, for example adding an function which starts by clicking a link, like:
$('#myDiv').append('click');
So start() should be called if somebody hits the link (click).
TERM could contain a single word, like world or moody's, the generated HTML code would look like:
click
OR
click
As you can see, the 2nd example will not work. So i decided to "escape" the TERM, like so:
$('#myDiv').append('click');
Looking at the HTML source-code using firebug, is see, that the following code was generated:
click
Thats works fine, until I really click the link - so the browser (here firefox) seams to interpret the %27 and tries to fire start('moody's');
Is there a way to escape the term persistent without interpreting the %27 until the term is handled in JS? Is there an other solution instead of using regular expressions to change ' to \'?
Don't try to generate inline JavaScript. That way lies too much pain and maintenance hell. (If you were to go down that route, then you would escape characters in JavaScript strings with \).
Use standard event binding routines instead.
Assuming that $ is jQuery, and not one of the many other libraries that use that unhelpful variable name:
$('#myDiv').append(
$('<a>').append("click").attr('href', 'A sensible fallback').click(function (e) {
alert(TERM); // Because I don't have the function you were calling
e.preventDefault();
})
);
See also http://jsfiddle.net/TudEw/
escape() is used for url-encoding stuff, not for making it possible to put in a string literal. Your code is seriously flawed for several reasons.
If you want an onclick event, use an onclick event. Do not try to "inject" javascript code with your markup. If you have the "string" in a variable, you should never need to substitute anything in it unless you are generating urls or other restricted terms.
var element = $('<span>click</span>');
element.bind('click', function () { start(TERM); });
$('#myDiv').append(element);
If you don't know what this does, then go back to basic and learn what events and function references in javascript means.
That escape() function is for escaping url's for passing over a network, not strings. I don't know that there's a built-in function to escape strings for JavaScript, but you can try this one I found online: http://www.willstrohl.com/Blog/EntryId/67/HOW-TO-Escape-Single-Quotes-for-JavaScript-Strings.
Usage: EscapeSingleQuotes(strString)
Edit: Just noticed your note about regular expressions. This solution does use regular expressions, but I think there's nothing wrong with that :-)
I'm populating a list by cloning elements into it. Then I change attrs to make each item unique. They need to call a function on click, so I'm wondering if it's more efficient to use new_element.click(func); or new_element.attr('onlick','func();');
new_element.attr('onclick','func();');
Is:
inefficient (needlessly creating a new inline function from a string, that does nothing except call func and lose the this reference);
aggravating to put any complex code in, since it all has to be JS string escaped;
broken in IE, due to bugs in setAttribute.
Avoid. click()/bind('click') is there for a reason.
onclick has a number of limitations, including cluttering the DOM and only allowing one function at a time. So you should use click. See Quirks Mode for more information.
Directly referencing the function will be more efficient than having to interpret a string.
The lowest touch way of doing this, however, is this way:
$(links_selector).live('click', func);
links_selector will presumably be something like ul.listClass a.actionClass. This will not require anything to be done when new list elements get added.
Since you are using jQuery then make it this way
new_element.click(function(){
// your code
});
or you can bind the click event handler like
new_element.bind("click", function(){
// your code
});
Any difference in performance between the two is most likely going to be negligible. You should use the one that reads better, and that's element.click. (Plus, onclick has many disadvantages, as #Matthew Flaschen mentioned.)