DEV Community

Lea Rosema (she/her)
Lea Rosema (she/her)

Posted on • Updated on

Snippets for Vanilla JS Coding

When coding in VanillaJS, I usually create shortcuts for document.querySelector and document.querySelectorAll. I also like to declare D as a shortcut to document:

const D = document
const $ = D.querySelector.bind(D)
const $$ = (selector, startNode = D) => [...startNode.querySelectorAll(selector)]

Enter fullscreen mode Exit fullscreen mode

It's also good to know that the $ and $$ functions are already built-in aka Command Line API when you open the JS console in Devtools.

With that dollar functions, you can already use a syntax that is similar to jQuery:

<button id="button">click me!</button>
Enter fullscreen mode Exit fullscreen mode
$('#button').onclick = () => {
  alert('You clicked me!')
}
Enter fullscreen mode Exit fullscreen mode

If you want to play with multiple elements, the $$ shortcut to document.querySelectorAll comes into play.

<button> button 1 </button>
<button> button 2 </button>
<button> button 3 </button>
Enter fullscreen mode Exit fullscreen mode
$$('button').map(btn => {
  btn.style.backgroundColor = 'red'
})
Enter fullscreen mode Exit fullscreen mode

When it comes to event handling, having an on method can be useful:

Array.prototype.on = function(type, listener, options) {
  this.map(el => {
    if (el instanceof Element) {
      el.addEventListener(type, listener, options)
    }
  })
  return this // for chaining
}
Enter fullscreen mode Exit fullscreen mode

This way, you can register multiple event handlers on multiple elements at once:

$$('button').on('click', e => {
  const btn = e.target
  alert('You clicked ' + btn.textContent)
}).on('mouseover', e => {
  const btn = e.target
  btn.style.backgroundColor = 'red'
}).on('mouseout', e => {
  const btn = e.target
  btn.style.backgroundColor = 'blue'
})

Enter fullscreen mode Exit fullscreen mode

Discussion (17)

Collapse
1hko profile image
1hko • Edited on

Instead of startNode||D I would recommend default arguments – it's exactly the reason they were added to the language

const $$ = (selector, elem = D) =>
  elem.querySelectorAll(selector)
Collapse
learosema profile image
Lea Rosema (she/her) Author

Good point :)

Collapse
the_fln profile image
Francois Lanthier Nadeau

Short but sweet post, Lea! 🙌

Found it while researching our story on vanilla JS. Also been working on an open source list of paid & free quality resources for devs to learn vanilla JS--open for PRs. Maybe I should add an "article" section with content such as yours?

Collapse
learosema profile image
Lea Rosema (she/her) Author

Hi Franck, thank you! I love your list of resources for Vanilla JS. Feel free to add it as you like :)

Collapse
hjfitz profile image
Harry

I like that you monkey-patch Array#on - but is there ever an instance that a NodeList returned from querySelector wouldn't return an Element?

What I'm trying to ask is:

Is if (el instanceof Element) a necessary check?

Collapse
learosema profile image
Lea Rosema (she/her) Author

The reason I did the check is that NodeList gets converted into an Array via the $$ function. NodeLists are read-only and always contain Element elements, so it is safe to call addEventListener on them without the check. Arrays are not read-only and the array elements are not safe from reassignment.

Collapse
genejams profile image
{Gene}

Perhaps a bit excessive to attach the on method to the Array prototype but it does the trick =)

Collapse
learosema profile image
Lea Rosema (she/her) Author • Edited on

Another option is to attach the on method plus Array.prototype.map to the NodeList prototype instead:

const $$ = (selector, startNode = D) => startNode.querySelectorAll(selector)
NodeList.prototype.map = Array.prototype.map
NodeList.prototype.on = function(type, listener, options) {
  this.map(el => {
    if (el instanceof Element) {
      el.addEventListener(type, listener, options)
    }
  })
  return this // for chaining
}

This keeps the Array prototype clean =).

Collapse
genejams profile image
{Gene}

Would it be good to have passive listener by default if no options are passed?

if (options == null) options = {passive: true};
Collapse
kapouer profile image
Jérémy Lal

Hi, ignoring with which document you're working is a quick way to bang your head to the wall. Always keep a reference to which document you're in:

HTMLDocument.prototype.$ = HTMLDocument.prototype.querySelector
HTMLDocument.prototype.$$ = HTMLDocument.prototype.querySelectorAll

A shorthand for document, why not:

window.doc = window.document
doc.$("body")
doc.$$("script")

Everything's nice now, you need to work off-line ?

var offdoc = doc.cloneNode()
// mind that some browsers don't create offdoc.documentElement so
if (!offdoc.documentElement) offdoc.appendChild(offdoc.createElement('html'))
offdoc.documentElement.innerHTML = '<head></head><body></body>'
// this won't load the file
offdoc.head.insertAdjacentHTML('beforeEnd', '<script src="toto.js"></script>')
// and this still works
offdoc.$('script')
Collapse
learosema profile image
Lea Rosema (she/her) Author • Edited on

In this case, another option is to define another shortcut for documents other than document. For example, you can move the $ function definition inside a function that takes a document as a parameter:

function insertScriptIntoDocument(doc, jsfile) { 
  const $o = doc.querySelector.bind(doc)
  const script = document.createElement('script')
  script.setAttribute('src', jsFile)
  $o`head`.insertAdjacentHTML('beforeEnd', script)
}
Collapse
ben profile image
Ben Halpern

Great tips

Collapse
flykasual profile image
Johannes Nielsen

Great article!
I am not sure how I feel about altering built-in prototypes, I generally like to keep those clean. But the jquery-like syntax and the ability to chain event listeners without having to actually use jQuery is super sweet! 👌

Collapse
itaditya profile image
Aditya Agarwal

We should avoid mutating the Array prototype though. That's why MooTools SmooshGate happened. Good post though, short and simple

Collapse
brianemilius profile image
Brian Emilius

Love it!
Definitely going to incorporate this in my snippet library.

Collapse
efeichen profile image
Kenneth Chen • Edited on

Another Lea happens to have made something similar: blissfuljs.com/

Collapse
learosema profile image
Lea Rosema (she/her) Author • Edited on

Yes, Bliss.js by Lea Verou also provide the $ and $$ shortcuts for querySelector and querySelectorAll, plus more functions that make it a fully-fledged, powerful and lightweight framework.