loading...

Snippets for Vanilla JS Coding

terabaud profile image Lea Rosema ・1 min read

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

Posted on by:

terabaud profile

Lea Rosema

@terabaud

Frontend Developer by day, funstuff developer princess by night. Working at SinnerSchrader. she/her

Discussion

pic
Editor guide
 

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)
 

Good point :)

 

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?

 

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

 

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?

 

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.

 

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

 

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 =).

 

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

if (options == null) options = {passive: true};
 

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')
 

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)
}
 
 

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! 👌

 

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

 

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

 

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

 

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.