Skip to content

Instantly share code, notes, and snippets.

@amcgregor
Last active August 20, 2019 02:11
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save amcgregor/fe585da8105da866c6885712488ba177 to your computer and use it in GitHub Desktop.
Save amcgregor/fe585da8105da866c6885712488ba177 to your computer and use it in GitHub Desktop.
If your first instinct is to give a jQuery answer to a CSS question, stop. Please stop. Especially stop before you start arguing in jQuery's favour in this day and age. We have, and can do better.
// Okay, this is a thing. A StackOverflow Answer thing.
// Answering the question: "how do I use CSS to make a span not clickable"
// https://stackoverflow.com/a/3144071/211827
$('a span:nth-child(2)').click(function(event) { event.preventDefault(); });
// Because that's *totally* using CSS.
// (Spoiler, it's pointer-events: none.)
// Comment on that answer points this out, gets this response:
// Those of us who work in jQuery tend to give examples in jQuery because it's
// a lot easier than the absurdities non-frameworked JS requires you to go through.
for ( let elem of [...document.querySelectorAll('a span:nth-child(2)')] )
elem.addEventListener('click', e => { e.preventDefault(); });
// Like that was difficult?
// You must be joking, right?
// Loop through the elements of a CSS query selector matching multiple elements,
// associate the simplest possible callback handler that cancels the event. A tiny
// bit of ES6 goes a long way, people. https://caniuse.com/#feat=es6
@tomhodgins
Copy link

tomhodgins commented Jul 3, 2019

The best solution would be pure CSS using the pointer-events property:

a span:nth-child(2) {
  pointer-events: none;
}

But, even if you were to use JavaScript for this, you can use the forEach() method on the NodeList that querySelectorAll() returns to express this much simpler than either JS examples above:

document.querySelectorAll('a span:nth-child(2)').forEach(tag =>
  tag.addEventListener('click', e => e.preventDefault())
)

If you wanted it to run in IE11 where there is no let, ..., or =>, you could express it in ES5 like this:

[].prototype.slice.call(document.querySelectorAll('a span:nth-child(2)')).forEach(function(tag) {
  tag.addEventListener('click', function(e){
    e.preventDefault()
  })
})

@amcgregor
Copy link
Author

amcgregor commented Jul 3, 2019

All of this is essentially covered in the original StackOverflow question and series of answers, @tomhodgins. The use of forEach is still unsavoury from a repeated stack allocation overhead perspective, I try to avoid tight iterative callbacks as much as possible; sure, a JIT will "do the right thing"… after ~1009 iterations or so; enough to "prove" the code in question is a hot path worthy of optimization and not just a fluke; depends on the JIT, of course. (Short, infrequent iterations will be penalized by never triggering JIT.)

Additionally, that hack is uglier then it needs to be: [].prototype.slice.call() is pretty yikes! [].forEach.call(iterable, callback) is more than good enough: it's also 'semantic' (saying what you actually want), since we're not trying to shallow copy of a portion of an array into a new array object, we're trying to iterate its contents, and that slice.call's only purpose is to expensively typecast, making an unnecessary duplicate copy of the array of nodes and entirely a side-effect / implementation detail for the overall goal. (Internally the shorter version might whole-cast, too, but it doesn't need to be that dumb. ;)

Edited to add: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from — polyfill available, and semantic (i.e. not a hack).

@amcgregor
Copy link
Author

amcgregor commented Jul 3, 2019

It's not like I was the one asking the question, I'm just pointing out the silliness of using jQuery for this, and the extra silliness of attempting to defend jQuery against the ES6 version of this use case. No, jQuery is not your friend.

@MassDebates
Copy link

I like the Pure CSS solution.

Great work at bringing some sanity to the mix, @tomhodgins !
Good effort at trying to clear up the confusion, @amcgregor !

It is good to be proud and passionate in your work. This is what the web is all about: finding the best way to do things.

@AndresInSpace
Copy link

AndresInSpace commented Jul 3, 2019

I second the pure CSS solution by @tomhodgins
You need something that can work in IE11 considering that private corporations like Mercedes-Benz still uses IE11 internally and you are leaving potential income on the table by not supporting IE11..
And it is fairly easy enough to support it and still have a modern site.

@amcgregor
Copy link
Author

amcgregor commented Jul 3, 2019

Huh. GitHub offer a "lock thread" option elsewhere, but not on gists, it seems. :/ Most of this chatter ought to be directed at various answers on the SO; this wasn't an attempt at an "answer" for it, or I'd have filed an answer there, this was just to point out the atrocious nature of jQuery dependence. See various comments of mine on answers to that question pointing out the pure CSS solution. It's not like I'm advocating JS use. At all. JS also needs to die, but it can't until something better comes along to replace it. ES6, and modern HTML5 DOM & APIs, do that for jQuery.

@AndresInSpace "You need" — I explicitly and emphatically do not need. I target browsers and platforms based on actual user base usage, i.e. analytics, and there is no situation that can arise that will force me to damage my code to support 0.4% of users. (My threshold is 1% to at least attempt to not break under it, 3% to "support" it, 5%+ to permit damaging changes.)

Even easier to write ES6, properly, then Babel for IE and just IE. Let automated tools worry about the brain damage, and let that damage be unidirectional, not impacting the sources. And you still really don't need jQuery.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment