You know what the DOM is, and if you don’t it’s easy enough to find out. What isn’t so easily discovered is the answer to this: how should your code communicate with the DOM API? Should it be treated as just another API? Is it no more deserving than, for example, a JSON web service or the web storage API?
What I’ve been seeing a lot of is the DOM becoming incredibly intertwined with the logic, i.e. the program becomes indifferentiable to the DOM. It all ends up being the same:
var counter = document.createElement('div'); counter.innerHTML = '0'; document.body.appendChild(counter); function add() { counter.innerHTML++; }
Where does the logic stop and the DOM representation of that logic begin?
This is a rudimentary example, and probably something that few of us would be caught doing, but I think similar things are done every day, and not just by beginners. This is normal code. It isn’t some beginner’s hack.
It wears a different mask wherever it rears its head. Here, for example, it’s under the guise of jQuery’s data API:
var alphabetList = { giveHigher: function(dom1, dom2) { return $(dom1).data('word') > $(dom2).data('word') ? dom1 : dom2; }, //... };
A shockingly high majority of jQuery.data uses seem to be making the mistake that I’ve demonstrated.
It’s subtle, not easily spotted. Right now, I’m wondering whether or not it’s acceptable.
For me, it goes against what I’ve picked up in programming thus far. I’m talking about best practices like separating your concerns and maintaining an OO approach, writing clean APIs to access your program’s logic (etc.). To me, the DOM is yet another representation of an object’s data. The data shouldn’t be in the DOM. This is how I would do it:
function Counter() { this.dom = document.createElement('div'); this.n = 0; } Counter.prototype.add = function() { this.dom.innerHTML = ++this.n; };
The thing is, the approach here is so ridiculously similar to foo.innerHTML++ that I really can’t tell what I’m making such a fuss over.
The feeling I get when I see foo.innerHTML++ is the same as when I see:
jQuery('div').click(function(){ if ($(this).data('active')) { // do x } else { // do y } $(this).data('active', !$(this).data('active')); });
… instead of:
jQuery('div').each(function(){ var active = false; $(this).click(function(){ if (active) { // do x } else { // do y } active = !active; }); });
Opinions welcome.
This sort of mixing is pretty common especially with a lot of jQuery code I’ve seen.
I think the problem lies partially in how jQuery itself is designed: It makes it really simple to work with the DOM. People naturally use the features that are easy to use, and with jQuery, that’s DOM manipulation.
Additionally, jQuery doesn’t provide a facility for creating more general-purpose code. Especially beginners have a harder time creating “classes” with JavaScript itself.
Comparing for example to Dojo, which provides a straightforward way to declare custom classes in a more familiar way and other tools for creating more structured code, you do see code that separates concerns more when working with Dojo. Since Dojo also provides a simple way to declare UI widgets via dijits, you often see general functionality like that defined as classes as well.
Perhaps if jQuery provided a more standard way of declaring widgets and classes, the situation would improve.
DOM, no matter how academic you want it to be, is mentally imaged as the programmatic representation something eventually visual in the browser; you are absolutely right about it has no business in business logic, you shouldn’t even change a word in that statement. It’s horses for courses. I mean, like a store, for about an Apple store, it’s the “back” that says “okay, put 5 MacBook on the window, 10 iphone on the table; and when a customer comes in to buy something, he doesn’t to bring the 5 MacBook from the window, more like it goes to the counter, asks for one, then counter then marks one sold, fetches the one on the window, hands it to the buyer. Instead of keeping the book through the window, like asks the buyer the fetch one himself, then the counter guy walks up to the window and counts how many are left and marks it in the book.
Okay, that’s almost babbling, not exactly the most convincing argument. Unless you’re selling Ferraris where you can counter in one hand how many’s got left in your shop, most should pay attention to their inventory database.
But I think it absolutely is a good practice. I think you’re just a bit of afraid that so many out there are doing it the other way that you’ll take some heat from them. They are doing it the other way, obviously, because most tutorials are like that, and most tutorials are like that because they are teaching what people want to know, which is a quick, quick, quick fix for some problem so their blog or whatever looks a lot more snazzy.
This is, simply put, pointing towards the direction of MVC.
Not a matter of jQuery, on which you just could rely on in terms of manipulating the view, but probably shouldn’t when it comes to the rest.
It is needed to have a piece of abstract code allowing to decouple logic and the DOM API. An example of which being what I’ve mentioned at the beginning.
If there ever was a reason to look down on jQuery, I think this a good one. Or bad, whatever way you want to look at it :p
Good article, the problems stated here are just too common today. There’s an interesting javascript library called knockout ( http://knockoutjs.com/ ) that I’ve been looking at recently. It could mean a way to decouple data from presentation although it might not be the solution for all cases.
Certainly a lot of us have learned that not a good idea to mix business logic and presentation logic. These examples are saying it’s not a good idea to mix DOM logic and presentation logic, which seems like a fine practice to me since the DOM is the presentation. jQuery’s
.data()API certainly can be used poorly, but take a look at jQuery UI for some examples of how to use it well.In that final example regarding the active flag, the use of
.data()is much better in that when the div is removed the data will disappear with it, automagically. Imagine each div represents a comment in a large and ever-changing comment stream. What is the lifetime of the closure variable?Imagine the div is instead a textarea, and instead of a custom
activevariable we use the DOM’s built-indisabledproperty to manage state and CSS to manage appearance. Is that still bad? Do we really need to create an entire JavaScript object to manage a variable that mirrors the state of the DOM property?If it’s any consolation, I’m wrestling with the same questions – as are many of the developers I work with. A previous commenter said that this was really about moving towards “MVC” – and while I agree with that, it’s possibly more accurate to call it MVVM, in that it seems you are advocating a “DOM proxy object”…or….in MVVM terms, a model who’s sole purpose is to contain data and view behavior for a given view. At the very least, I think devs like us need to be exploring this territory – perhaps one of us will stumble upon something useful. Knockoutjs is becoming popular, and I initially loved it, but that love has cooled significantly the more I’ve thought about how it’s not naturally unobtrusive (and I have some complaints regarding performance and API as well). Other competitors (like Angular and Batman) aren’t unobtrusive either. There *has* to be better ways to do this stuff, and I commend you for asking those questions, and being willing to take the heat from all the jQuery-is-my-spaghetti-code-hammer-and-you-look-like-a-nail fan boys.
When I was doing iPhone stuff with phoneGap I noticed that having a JavaScript object holding my database greatly improved my performance since I didn’t do unnecessary reads/writes. The database just persisted state across sessions, my real state was held in memory.
Now I’m working on a rather large application for an insurance firm with hundreds of form fields. When we started, the DOM held state. “Is the field visible? Ask the field $(“#blah”).isVisible().” With a very complicated DOM structure and complex business logic, this style of app just wouldn’t perform. Once we treated the DOM like a slow, clunky database and used a fast state control layer in memory to filter “reads and writes” we performed much better. If the field is already visible, don’t invoke jQuery to call visible(). Once we made this view-model layer, we realized we didn’t need sizzle anymore and the app got even faster.
Go with MVVM, it’s the right choice but you won’t really see the difference until the app has to scale.