Skip to main content

17 Migrating From jQuery to Vanilla Javascript

Episode 190 · May 23, 2017

Without jQuery in Rails 5.1, we explore what it takes to convert your traditional jQuery code into vanilla Javascript methods

Javascript


Selecting elements

// jQuery version
$("#notifications")

// Javascript version
document.querySelector("#notifications") // returns the first matching element
document.querySelectorAll("#notifications a") // returns an array of elements

document.getElementById("notifications") // returns a single element
document.getElementsByTagName("a") // returns an array of elements

document.querySelector("#notifications").querySelectorAll("a") // returns an array of sub-elements from the first query

Adding Event Listeners

// jQuery version
$("a").on("click", function(event) { 
  event.preventDefault()
  console.log("clicked") 
})

// Javascript version
document.querySelectorAll.forEach(function(item) {
  item.addEventListener("click", function(event) {
    event.preventDefault()
    console.log("clicked")
  })
})

Hiding Elements

// jQuery version
$("#notifications").hide()
$("#notifications").show()

// Javascript version
document.querySelector("#notifications").style.display = 'none'
document.querySelector("#notifications").style.display = ''

Appending an element

// jQuery version
$("#notifications").append("<p>New notification</p>")

// Javascript version
node = document.createRange().createContextualFragment("<p>New notification</p>")
document.querySelector("#notifications").appendChild(node)

Retrieving Attributes and Data Attributes

// jQuery version
$("element").attr("attribute")
$("element").data("id")

// Javascript version
document.querySelector("element").getAttribute("attribute")
JSON.parse(document.querySelector("element").getAttribute("data-id"))
// or 
JSON.parse(document.querySelector("element").dataset.id)

AJAX requests

// jQuery version
$.ajax({
  url: "/notifications.json",
  type: "GET",
  success: function(data) {
    console.log(data)
  }
})

// Javascript version (with Rails UJS)
// This automatically includes your CSRF token for non-GET requests as well
Rails.ajax({
  url: "/notifications.json",
  type: "GET",
  success: function(data) {
    console.log(data)
  }
})

Document Event Handlers

// jQuery version
$(document).on("turbolinks:load", function() {
  // initialize code
})

// Javascript version
document.addEventListener("turbolinks:load", function() {
  // initialize code
})

Transcripts

Javascript and CSS Asset Pipeline:

Migrating From jQuery to Vanilla Javascript

Today we are going to talk about using vanilla Javascript as an alternative to jQuery, especially moving forward since Rails 5.1 removes jQuery as a dependency by default. You'll still probably use it in a lot of applications, especially ones that need good backwards compatibility, because jQuery was really introduced as a compatibility layer across your browsers since Javascript operated very differently than it does today in modern browsers.

Some things we'll look at today:

  • How to do queries for elements on the page.
  • How to add events.
  • How to hide/show just like you do with jQuery.
  • and so on...

The first most common thing you want to do is grab items from the DOM and query for them so you can interact with them in some way.

For example, if we have an id = "notifications", which is a wrapper around each of the notifications on the page. We can grab and interact with all of the notifications, using Javascript.

<html>
  <head>...</head>
  <body>
    <div class="container">
      ::before
      <h1>Notifications</h1>
      <div id="notifications">
        <p id="notification_1">...</p>
        <p id="notification_2">...</p>
        <p id="notification_3">...</p>
        <p id="notification_4">...</p>
      </div>
      ::after
    </div>
  </body>
</html>

If you wanted to insert a new notification from WebSocket or an Ajax request you should be able to to grab the id = "notifications" and then either prepend or append your item into that list. Let's go into the console to see how we grab that item.

Selecting Elements

// Using jQuery

> $("#notifications");

jQuery would see this and say okay that's an ID, we need to go and look for an element on the page with the ID of notifications. We can do the same thing with vanilla Javascript:

// Using Javascript

> document.querySelector("#notifications");
// or
> document.querySelectorAll("#notifications");

The difference between the two is that querySelector() will give you the very first item and that item by itself, where querySelectorAll() will give you every single item and return an array. Since we are interacting with an ID, querySelector() is what we want.

If we run that we will get the <div id="notifications>...</div>" element back. If we were to run document.querySelectorAll("#notifications") we would get an array back, which is more similar to the way that jQuery worked because it would probably handle these always as arrays. You might want to use querySelectorAll(), but if you know you that you are looking for a single item, just use document.querySelector(). This also supports the multi-parameter syntax that jQuery does, so if you ever wanted to grab all the paragraph tags out of the notifications div, you can run querySelectorAll("notifications p") and it will return an array and have narrowed down the scope to the appropriate section. As you can see, you can use querySelector() and querySelectorAll() as a replacement for your jQuery selectors.

You can also use:

> document.getElementById()
> document.getElementsByClassName()
> document.getElementsByName()
> document.getElementsByTagName()
// ...etc

You can mix and match these together as well. For example, if you want to select an element and grab all the children out of that element:

> document.getElementById("notifications").querySelectorAll("p")
// (4) [p#notifications_1, p#notifications_2, p#notifications_3, p#notifications_4]

This produces the exact same chained and scoped queries that way as well. So that works the same exact way as if you did a find in jQuery on another element and is how you would convert your jQuery code over to these new selectors. Of course, once you've added your selectors usually you want to interact with those elements in some way and a lot of times that is by adding event listeners to those things.

Adding Event Listeners

For example, Turbolinks looks for all those links on the page and then it will make them navigate with Turbolinks. So it installs an event listener on click, and that will intercept that stuff, where if it doesn't do it on click, it does something very much like that. Let's work on building our own as it intercepts that. The difference here is that we can use our querySelectorAll("a") and we can grab all the links on the page and we can use the new addEventListener() method to create our event listener, but the difference here is that this is going to give us an array back and we actually need to loop over each item in the array and add the event listener to it directly. Previously with jQuery you could say on click, run this function, and it would run that function and do whatever you wanted inside of it. This would internally loop across all of those anchor tags on the page.

// jQuery
> $("a").on("click", function() {})

We have to explicitly do that with vanilla Javascript. The way we would do that is say querySelectorAll(), grab all the anchors and then we can say for each, and that will take a function which grabs us our anchor and passes it in an optionally it also gives you the index if you need it, but we don't in this case, so we'll leave it out. Then with that anchor you can say addEventListener() and it takes a function which also gives you an event on click, so we close these out. Normally, we would say event.preventDefault() and that will cancel out any of the bubbling up so this would prevent Turbolinks from running if we prevent default.

// Javascript

> document.querySelectorAll("a").forEach(function(anchor) {
    anchor.addEventListener("click", function(event) {
      event.preventDefault();
      // Test it out
      console.log("clicked!")
    })
})

Now if you click on any of the links, it should say "clicked" in the console. Now we can use that to fire off an Ajax request or something like that and prevent the normal click action from functioning. This is a lot more code that you previously had to write with jQuery, but it does the same exact thing. If you only had one element, you could shorten this because you wouldn't need the forEach() method anymore. You could use the querySelector() and grab the very first item and add your event listener on it directly. Often another thing we do in our Javascript is use jQuery to select an element and then hide/show it.

Hiding and Showing Elements

When we want to hide/show elements using jQuery we could add hide() or show() which would add a style display: block or display: none to our elements. We don't have access to that in vanilla Javascript, but if we use our querySelector() and we target #notifications and then call style.display = none we can hide that, and we can do the same exact thing by making it a block or an inline-block, or whatever.

// jQuery
> $("#notifications").hide()
> $("#notifications").show()

// Javascript
> document.querySelector("#notifications").style.display = "none"
> document.querySelector("#notifications").style.display = "block"

So, this would be your alternative to jQuery's hide/show methods using vanilla Javascript. Next, we'll look at appending elements using vanilla javascript.

Appending an Element

Another thing you might want to do is insert a new notification from Rails' Ajax requests or from WebSockets when they send you the HTML for it and you just want to take that HTML and insert it into the page in the proper spot.

You can do this with jQuery using the append() method.

// jQuery

> $("#notifications").append("<p>New Notification</p>")

With vanilla Javascript we have to use the createRange() and createContextualFragment() method and this is where we would give it that code and the HTML snippet and then you would create this node as follows:

// Javascript

> node = document.createRange().createContextualFragment("<p>New notification</>")
> document.querySelector("#notifications").appendChild(node)

What this will do is parse the HTML and create a fragment for us to insert into the page as an actual DOM element. We now have that appended to the list, notice we didn't include the anchor tag and all that stuff, but that will do the exact same thing. So you have very much the exact same functionality you would use in jQuery code with just a little restructuring for vanilla javascript. One thing you may want to do if you are inserting a new item is to add the same event listener to that new item. You can accomplish this like before by installing the event listener for all the ones that are pre-existing on the page. Another incredibly common feature that we use often is adding data attributes and other attributes into our HTML, allowing us to easily grab stuff and load it into our Javascript. Let's look at how we can do that with vanilla Javascript in the next section.

Retrieving Attributes and Data Attributes

With jQuery we would normally grab an element on the page and you would say data ID and that would grab the data ID attribute and that would grab that for you:

// jQuery

> $("element").attr("attribute")
> $("element").data("id")

If you were doing this with vanilla Javascript you can say let's grab that item, so let's grab the first anchor tag on the page which has the data attribute. With this you can call dataset() and then the name of your property, so for us we're using id, and so we will grab that and get the string of "1" back.

// Javascript

> document.querySelector("a").dataset.id
// "1"

These will always be strings, whereas jQuery would actually run JSON.parse for you. If you ever need to do that you can do the same thing by passing in that attribute to JSON.parse like so:

> JSON.parse(document.querySelector("a").dataset.id)
// 1

That will parse it out and give you the value as a native Javascript object, in this case an integer. So that is an easy way to do that but these are accessible like most of the things we are doing are accessible and more modern Javascript, so the data set stuff is maybe not the most browser compatible way, but you can also do getAttribute() and you can simply say:

> document.querySelector("a").getAttribute("data-id")

This will grab you the same exact thing, because data attributes are nothing special. They are just prepended with data and a hyphen in front "data-" of the name and so those have their own data set method. If you want to use it you can, but you absolutely do not have to. You can use the getAttribute() which will be a little more agnostic, but a little bit more verbose in accessing those. Either method will work just fine. Last, but not least and probably the most painful thing to write yourself is Ajax requests, and if you're not using a library like Axios, and you're just writing your own XML-HTTP request objects, it can be a real bear to write and setup. Luckily, Rails UJS that comes with Rails 5.1 comes to the rescue for us here. Rather than writing your $.ajax, or $.getJSON or $.post requests from jQuery, we can now use the Rails.ajax method.

> Rails.ajax({
  url: "/notifications.json",
  type: "GET"
})

This will run an Ajax request and we see nothing special here, but if we look in our Rails console we see:

# ...
Started GET "/notifications.json" for 127.0.0.1 at 2017-05-23...
# ...

We can add a success callback here and we will receive the data back:

> Rails.ajax({
  url: "/notifications.json",
  type: "GET",
  success: function(data) {
    console.log(data);
  }

If you add console.log(data) you should see that we can add all of those objects back which is one for each one of the notifications in our database. So, the Rails.ajax method for the most part has the same sort of syntax for the URL, the method, the success, and that sort of thing. It's a little bit different, some of the option names are a bit different, but for the most part this is going to work exactly the same way as the jQuery Ajax method worked previously. This will also take advantage of the CSRF token from Rails, so grab that out of the head and include that with all you Ajax requests automatically, so you don't have to do that manually anymore. If you're interested you can watch Episode 186 on the Rails 5.1 UJS primer. I walk through everything that the Rails UJS library does and talk about the changes and the features and how they work specifically with the Rails Ajax request. We look at the source code for it, so you can see exactly all the options you can pass in and how those get handled in XML HTTP requests that it build internally and then executes for you.

Before we wrap up, the last thing I wanted to point out, is that you have seen me write

> document.addEventListener("turbolinks:load", function() {
    console.log("Hey turbolinks navigated for us!");
  })

in previous episodes where we listen to the turbolinks:load event so anytime that the document fires that event where Turbolinks defines this event and says hey, we're going to fire that and so you can listen to it and set this up so that anytime you click on the links it's going to fire that console.log() message or do whatever code you want. For example, in our Vue.js app we had it setup so that every time it would load up our Vue app, every single time that we load the page and then we would tear down view just before it gets cached when we navigate away so that it can be loaded up again when you come back without any issues.

Conclusion

So, that wraps up this episode. Of course we can't dive into every jQuery method that's available. There are far too many to use, but jQuery is becoming less required these days. It used to be that if you wanted this to work in multiple browsers you pretty much had to use jQuery. The browsers have very good APIs, you just have to learn those new APIs so that you can write your vanilla Javascript and replace jQuery with it. As you notice, most of those things are pretty easy to transfer from jQuery syntax over to the browser's APIs, except for some little details like you need to do your own JSON parsing or you need to do your own looping around these elements in order to add those event listeners. Maybe those things will improve in the future, but really it's kind of trivial little stuff you just have to worry about doing those things yourself here and there and it's not too bad. So that is it for this episode, hopefully that gives you a better understanding of how you would go convert your jQuery code over to vanilla Javascript and drop that dependency moving forward. Until next time. Peace!

Transcript written by Scott Christensen

Discussion


Login or create an account to join the conversation.