Edit: Point of clarificatioon, IDs and classes as separate hooks is just one form of the applied idea in question which is to never use the same hooks for CSS as you do in JS. I’ve also seen people want things like js-combo_box and css-combo_box as classes on the same element.
This is one I’ve started running into more and more in recent years. People wanting to only use IDs for JS and classes for CSS. Or they don’t want JS to touch classes and IDs at all and rely on things like HTML5 data-attributes hooks (which is and will probably remain the slowest way to actually find an element even in modern browsers).
In 5+ years of occasionally heavy-duty/insane-environment client-side web development including one very large totally messed up e-commerce retail operation where HTML nobody was even working with would mutate because we had 200 offshore devs of varying levels code illiteracy (completely on the low end, not kidding) jumping up and down on the back end codebase with no source control, I’ve never run into an issue where JS and CSS targeting shared attributes in the HTML caused any kind of maintainability or ease of modification problem for me. I guess I’m wondering what is informing this perceived need and whether I’m missing something that comes up for a lot of people?
Isn’t HTML via selectors and the DOM API effectively the point of abstraction that ties everything together? Why would JS and CSS concerns sharing the same attributes there ever cause a problem?
5
Maybe you notice that because, as you said it yourself, you have to work with inexperienced programmers.
In practice, the distinction you observe doesn’t exist. Frameworks, by the way, discourage such distinction.
For example, jQuery encourages pretty well to use selectors in a general way, which means that you use IDs to target a single element and classes to target multiple elements.
In the same way, a not totally retarded developer would quickly notice (learn) that classes are bad candidates for unique elements on a page: of course, you can replace all IDs by classes in both HTML and correspond CSS, but it would make things slightly… let’s say unreadable, if nobody cares about performance.
As for the inexperienced programmers, they may tend to do strange things. It took me a few weeks to explain to one fellow colleague that:
<div id="entry">Some text here</div>
<div id="entry">Some other text</div>
is wrong, because IDs are unique. Take one beginner who have just read an article explaining how to use classes in CSS and another article about data-
attributes in jQuery, and you easily understand why this beginner starts to use classes for CSS and data-
for jQuery selectors.
6
Using classes for JS makes perfect sense.
Classes, like their name suggests classify DOM elements.
For example, if I had a list of items
<div class='item'>Item1</div>
<div class='item'>Item2</div>
<div class='item'>Item3</div>
<div class='item'>Item4</div>
It would make perfect sense to select them with the following selector .item
instead of assigning an ID to each of them.
More-over, in many commonly used JavaScript libraries and code bases class selectors are used for selecting elements. I’ve seen it used in production code many times and it seems like acceptable practice.
The only beef people have with class selectors over ID selectors is that ID selectors are faster (perf here). Most of the time I have found that doesn’t really matter.
1
Okay, a complete flip-flop of what I was going to write, based on two specific parts of the question:
we had 200 offshore devs of varying levels code illiteracy (completely on the low end, not kidding)
and
Why would sharing attributes there ever cause a problem?
I have run across something similar to this in the project I’m on at work right now. What I was dealing with is what happens when the unskilled programmers tie functionality entirely to classes, without thinking about consequences or maintainability. (That is, they were reusing classes just because they’re there and happen to be on all the right elements)
What you’re looking at is a simple, sledgehammer-like way to enforce separation of concerns on the unskilled programmers.
If functionality is strictly tied to IDs, and design to Classes, then they can change both without having to worry about the other. This is especially helpful from a design standpoint – as long as they include all the requisite IDs for the Javascript functionality, they’re fine.
That said, I strongly disagree with this approach (see other answers for why). It’s extremely limiting to even the halfway-decent programmers and almost always requires extra bookkeeping javascript to keep track of #foo_1
, #foo_2
… when .foo
would have sufficed.
EDIT @jmoreno has pointed out that I missed the clarification paragraph at the top, so now parts of my original answer seems much more appropriate. Personal anecdote time!
See, at work, we have a library written in Django+JS+CSS, shared among a large chunk of our products. It was written in a very specific way, meant to be highly configurable, and included into various projects by way of a Django templatetag with arguments used for configuration of the JS.
Unfortunately, because the JS and CSS was tied together by sharing classes, it meant that certain features that looked like entirely separate options could only work if both were specified, or both were left out. And they were mutually exclusive with a third. Any other combination caused the JS to error out, because it would make assumptions about which classes existed in the generated HTML.
We needed options 2 and 3, but not 1. This was unfortunate, to say the least: For example, Option 1 was including CSS-styled classes for Option 2’s JS (which it was only using because, as described above, the classes were already there so why not?). So if you used Option 2 without Option 1, there would be so much breakage styling-wise that it became unusable.
I have been beating that JS into submission on and off for nearly three months at this point, just making all 3 options independent of each other and ironing out the bugs.
This extra work shouldn’t have been necessary, and wouldn’t have been if the whole thing was well-written initially. Forcibly separating which classes the JS and CSS act upon would have helped to prevent Option 1 and Option 2 from getting tied together back when it was first being written.
That said, it’s generally not possible to be 100% on this rule. One of those is a simple .loading
class that gets added/removed as necessary.
7
As someone who’s adopted this pattern, one of the biggest reasons I’ve done it is that I feel it makes the markup a little more semantic. If I’m working on changes that touch an element with a .js-
class on it I know that there should be some JS somewhere that is either listening to this element or operating on it. Knowing that the JS is using this element means that I’m extra careful about how I change it.
The same could be said about using a data attribute except that you could argue that it goes one step further by separating the JS from the CSS by not using id’s or classes(I don’t typically use data attributes as JS selectors but I’ve seen it).
First, I don’t think that separation is absolute. id’s for example are unique to a document and so it makes sense to use them for both CSS and JS. For classes though, there are good reasons for these to be separate.
The basic issue is that CSS and JS implement what you might think of as cross concerns. CSS implements layout, display, and typography, while JS implements behavior. Or at least this is how they are most often (and I think, best) used. You can think of these as orthogonal concerns or dimensions, and so individual DOM objects may need to be placed, not in a line of functionality, but rather in a two-dimensional area where the dimensions are display and functionality.
Again if you want to look up a single element, CSS and JS may both do this by ID, but if you want to look up a group you probably don’t want to tie display and behavior together too tightly.
This is a general issue and I don’t think it depends on how experienced your developers are or what tools you are using. As long as the concerns are different you may want to be able to classify elements according to multiple types of concerns and this is one way this happens.
In 2020, I will tend to do something like the following.
Imagine an accordion menu:
<nav class="mainMenu">
<ul class="mainMenuList">
<li class="mainMenuSection">
<strong class="mainMenuSectionTitle">Site Section A</strong>
<ul class="mainMenuPageList">
<li class="mainMenuPage">Page A1</li>
<li class="mainMenuPage">Page A2</li>
</ul>
</li>
<li class="mainMenuSection open" data-main-menu-section-state="open">
<strong class="mainMenuSectionTitle">Site Section B</strong>
<ul class="mainMenuPageList">
<li class="mainMenuPage">Page B1</li>
<li class="mainMenuPage">Page B2</li>
<li class="mainMenuPage">Page B3</li>
<li class="mainMenuPage">Page B4</li>
</ul>
</li>
<li class="mainMenuSection">
<strong class="mainMenuSectionTitle">Site Section C</strong>
<ul class="mainMenuPageList">
<li class="mainMenuPage">Page C1</li>
<li class="mainMenuPage">Page C2</li>
<li class="mainMenuPage">Page C3</li>
</ul>
</li>
</ul>
</nav>
(To clarify, data-main-menu-section-state="open"
is the open section of the accordion, which, when opened by the visitor, unfurls downwards until all the links from Page B1 to Page B4 are fully revealed.)
The argument against this would be that, since both:
.open
data-main-menu-section-state="open"
indicate the accordion section which is open, only the first is necessary.
But, this kind of setup where:
classes
represent CSS hooks; andHTML5 Custom data-* attributes
represent JS hooks
means I can have some confidence (not least if I am coming back to a project that I last worked on months before) that I can remove or change the name of any particular class when refactoring the document’s Presentation and it will never disable any JS Behaviour currently applied to the same element.
Furthermore, I may deploy the generically-named class .open
elsewhere in the document, leading to two similar but not identical style-blocks in my stylesheet, such as:
.mainMenuSection.open
.headerMenuSection.open
in full confidence that the new elements that I append the class .open
to, won’t then also immediately adopt javascript behaviour which I don’t need or want them to have.
In Summary:
When I’m refactoring CSS, I don’t want to also have to be constantly thinking about whether this is going to result in any unwanted javascript side-effects.
And when I’m refactoring JS, I don’t want to also have to be constantly thinking about whether this is going to result in any unwanted CSS side-effects.
See also: Uncle Bob Martin’s Single Responsibility Principle