I have been reading about some good practices in JavaScript and one of them was Unobtrusive JavaScript. The first point caught my attention
Separation of functionality (the "behavior layer") from a Web page's structure/content and presentation
On the wiki page, one of the examples is that you should bind actions to events in JS files instead of in the HTML. This example
<input type="text" name="date" id="date" />
...
window.onload = function() {
document.getElementById('date').onchange = validateDate;
};
is favored over
<input type="text" name="date" onchange="validateDate()" />
However, I would say I'd prefer the second code with onchange
attribute over the first one. My reasons for this are
- It is easily readable and immediately clear what will happen on change (or any other event) on that element.
- I don't have to got through JavaScript files and look where (and whether) the
onchange
event is bind and whether there are some other events such asclick
defined for#date
. - Frameworks such as AngularJS have
ng-click
and is mixing up HTML structure with JS. Why shouldn't I?
The disadvantages of not using unobtrusive javascript that I have read about are
- Polluting global namespace.
- Creating long and unreadable inline code.
- If the code for an event changes, you have to change it only in one place - that is in a JS file.
But I think that the disadvantages can be solved.
- Instead of polluting namespace, create the app in one variable so the code would look like
onchange="app.validateDate()"
and no polluting would happen. - Inline code would not be written and would be separated in a function in JS file, then it'd be called like
onclick="app.action();"
. - Isn't it the same as using a function in
onclick
attribute? Because in the end you have to make a change just in one function in both approaches whether it is$('input').change(function () {/* ... change ... */});
orapp.action = function () {/* ... change ... */}
.
So is it actually still considered a good practice?
This is a very broad topic and heavily opinion based. There is no one answer to everything. However, here are some observations:
You are polluting the namespace whatever you do.
app.validateDate
pollutes the namespace just asvalidateDate
does, just by virtue of needing to have a globally accessible function name. In complex modern sites, there are tons of scripts competing for global names. Ideally you're never exposing any name globally, not even a namespace name..onclick = handler
is not great either. You'd want:This is even less obtrusive and allows several scripts to bind event listeners to the same element. Again, in complex modern sites one of the highest priorities you can have is to ensure nobody is stepping on anyone else's feet. You never know who else might be interested in the
change
event of that element in the future.It is still more code to write it inline than elsewhere. Longer HTML code that is. HTML can already be very verbose. Anything you can move elsewhere you should. Reducing the amount of code in any one particular file is an art in itself and important for readability. Yeah, it's "just one more attribute"... on top of all the other attributes and elements and inline declarations you're also not avoiding. It's just piling up, and that's how code gets messy and unreadable and unmaintainable.
Reusability:
document.getElementById(..).addEventListener
can be written once in an external file and automagically reused across many different pages.<.. onclick="..">
needs to be repeatedly written every single time. DRY your code.For tiny projects it often hardly matters. But again, sites are getting more and more complex. Business needs demand constant changes. Just one more analytics script, just one more Javascript based social widget, now change it all back, now keep all the versions of dependencies in sync, now rip it all out again and redesign for our 2.0 launch next week. Do it all with 10 other people in parallel without nuking each others code on every build or needing long sessions of resolving git-merge conflicts. In such an environment, every little bit of decoupling and indirection and flexibility helps.
Since you mention Angular:
Angular avoids some of these issues by employing a completely different template parsing model. When you write
onclick=foo
, then you need to bind to a global function name. However, when Angular doesng-click=foo
,foo
is a locally scoped entity in an ng-scope. It's not a global name. Angular separates between a controller and a view, where the controller essentially exposes a specific API on the$scope
object which the view can use; both the controller and the view are still interchangeable as long as the specified API contract is kept (meaning as long as the$scope
object keeps the same attributes).All
ng
directives are evaluated against a custom scoping and evaluation engine which does not have much to do with Javascript's default modus operandi.