DEV Community

Cover image for Refactoring JQuery to ES6
Justin Ho
Justin Ho

Posted on • Updated on • Originally published at jcsh.dev

Refactoring JQuery to ES6

Cover Photo by Markus Spiske on Unsplash

A Little Look At The History Of JQuery

The big bang of web development

Between 2006 and 2015, JQuery was revolutionary. It was a library which did everything that developers had a hard time maintaining code for: standardized API to select elements in HTML, and cross browser support.

Now in 2020, you as a reader might scoff at these features. But this was the reality for front end web developers before the appearance of ECMAscript 6 (ES6) and other important features in CSS3 you may take for granted.

Why You Might Want To Remove JQuery As A Dependency

Over-reliance can be an addiction

Dependencies are not a bad thing, we as developers do not need to code everything from scratch and "reinvent the wheel" every single time. However, I would argue that most usages of JQuery are trivial to accomplish in vanilla Javascript and do not require importing the entire JQuery library into your client's browser. In other words, we might want to remove this dependency to increase load speed and make better use of native browser capabilities if we can.

The Demo Code

Lead by example

So today I will be showing how I refactored several JQuery functions from my HypeTracker project into vanilla Javascript.

// private/js/ajax.js
// run functions after document has loaded
$('document').ready(function() {
    // ajax update shown products after select value changes
    $('#brand').change(function() {
        var brand_id = $(this).val();
        $.ajax({
            url: "./load_data.php",
            method: "POST",
            data: {brand_id : brand_id},
            success: function (data) {
                $('#show_product').html(data);
            }
        });
    });

    // update view if user enters characters into search box
    $('#sneaker_search').unbind().keyup(function(e) {
        var value = $(this).val();
        if (value.length >= 1) {
            search_sneaker(value);
        } else {
            $('#search_result').hide();
            $('#show_product').show();
        }
    });

    // ajax post call to return search results from search box
    function search_sneaker(val) {
        $('#show_product').hide();
        $('#search_result').show();
        $.post('./load_data.php', {
            'search_data' : val
        }, function(data) {
            if (data != "") {
                $('#search_result').html(data);
            } else {
                // default view if no results
                $('#search_result').html("<div class=\"row pl-3\"><div class=\"col-3\">No Result Found...</div></div>");
            }
        });
    }
});
Enter fullscreen mode Exit fullscreen mode

I apologize in advance because the code is not great but it was also written several years ago and I've learned to write better code. The reason I'm using this code instead of a contrived example though is because it is code from an actual project with constraints, and there are already resources which tell you exact Javascript functions to use in place of JQuery (which I will link at the end).

Breaking The Code Down

Making it bite-sized

As with any project, even an old one I wrote myself, the first thing is having an understanding of what it does or is trying to accomplish. Reading through the code, I listed out the gist of what each section does:

  1. An event is called when the value of a select element with the id brand changes
    • event: sends an AJAX POST request to load_data.php which returns some data to be shown in an element with the id show_product
  2. An event is called when the input field with id sneaker_search is focused and a key from a detected keyboard is released
    • it calls a search_sneaker function when there are characters detected in the input field
    • otherwise it shows the element show_product and hides the element search_result
  3. The function search_sneaker sends an AJAX POST request to load_data.php and renders the return values in an element with the id search_result
  4. Everything above is wrapped inside a helper function to make sure all code will not run until the document has fully loaded

Tackling Changes One By One

Baby steps, one at a time

To begin, I will start by extracting the AJAX requests. If you have not noticed, there are two instances in my breakdown above where I noted "sends an AJAX POST request to load_data.php". This means that I'm violating the DRY (don't repeat yourself) principle and can refactor it into one reusable function.

Asynchronous Javascript And XML

A powerful proposition of JQuery was its streamlined AJAX request and response functions over the native XMLHttpRequest. However, the introduction of the fetch API changes this because it too was fairly easy to use, and supported the promise-based syntax. The last point makes it even better when you don't have to support IE with the introduction of the async / await keywords in the browser.

Since I'm not supporting anyone, really, I'll be making use of both the fetch API and the async / await to create my AJAX function.

First, I will use the fetch equivalent for JQuery's $post(url, data), which is fetch(url, { method: 'POST'}).

Next, we can encode our data in two ways,

  1. Manually encode it ourselves (ex. field1=data1&field2=data2)
  2. Use the FormData interface to do it for us

Using the FormData interface, we will end up with a new function like this:

async function searchSneaker(type, value) {

  let searchData = new FormData();
  searchData.append(type, value);

  const response = await fetch('./load_data.php', {
    method: 'POST',
    body: searchData
  });

  if (!response.ok) {
    throw new Error(response.status);
  }

  const text = await response.text();
  return text;
}
Enter fullscreen mode Exit fullscreen mode

As a quick side note, the fetch promise object does not throw errors on bad HTTP status by default. Instead, checking if the promise object's ok property is true (for HTTP status codes 200-299) or manually checking the return code of the status property.

Element Selectors

The second part is relatively short but it'll help us consolidate our selectors in Javascript variables.

In JQuery, elements can be selected with the $(element) function. On the other hand, vanilla Javascript has querySelector and getElementById. Switching syntax and identifying the elements I needed from above, I will end up with these global variables:

const products = document.getElementById('show_product');
const searchResult = document.getElementById('search_result');
const searchBox = document.getElementById('sneaker_search');
const brandSelector = document.getElementById('brand');
Enter fullscreen mode Exit fullscreen mode

Event Listeners

The final step to finish the refactoring is to use native event listeners. In JQuery, we chained together element selectors along with event listeners such as $(element).on(...) or $(element).change(...). The native event listener interface is not too different other than having to specify the type of event as an argument.

document.onreadystatechange = function () {
  if (document.readyState === 'complete') {
    searchBox.addEventListener('input', filterBySearch);
    brandSelector.addEventListener('change', filterByBrand);
  }
}
Enter fullscreen mode Exit fullscreen mode

The filterBySearch and filterByBrand variables are actually functions passed as an argument. In this case, the corresponding functions will receive an Event object as a parameter. This chunk of code takes care of making sure no AJAX functions are dispatched until the document has fully loaded by attaching the event listeners at that time.

The Completed Code

Glad you made it this far

After making changes in these 3 categories and using JSDoc style comments, I ended up with code like this:

const products = document.getElementById('show_product');
const searchResult = document.getElementById('search_result');
const searchBox = document.getElementById('sneaker_search');
const brandSelector = document.getElementById('brand');

document.onreadystatechange = function () {
  if (document.readyState === 'complete') {
    searchBox.addEventListener('input', filterBySearch);
    brandSelector.addEventListener('change', filterByBrand);
  }
}

/*
 * Limit Products by Brand Select
 *
 * @param {Event} brand - value from select event
 * @return {void} updates product html
 *
 */
async function filterByBrand(brand) {
  brandId = brand.target.value;
  const sneakers = await searchSneaker('brand_id', brandId);
  if (sneakers === null) return;
  products.innerHTML = sneakers;
}

/*
 * Limit Products by Search Phrase
 *
 * @param {Event} search - value from input event
 * @return {void} updates search result html
 *
 */
async function filterBySearch(search) {

  let searchValue = search.target.value;

  if (searchValue.length <= 0) {
    searchResult.style.display = 'none';
    products.style.display = 'block';
    return;
  }

  const sneakers = await searchSneaker('search_data', searchValue);

  if (sneakers !== "") {
    searchResult.innerHTML = sneakers;
    return;
  }

  searchResult.innerHTML = "<div class=\"row pl-3\"><div class=\"col-3\">No Result Found...</div></div>";
}

/*
 * Send an async request with search term
 *
 * @param {string} type - search type
 * @param {string} value - search value
 * @return {text} the formatted HTML with data
 *
 */
async function searchSneaker(type, value) {

  let searchData = new FormData();
  searchData.append(type, value);

  const response = await fetch('./load_data.php', {
    method: 'POST',
    body: searchData
  });

  if (!response.ok) {
    throw new Error(response.status);
  }

  const text = await response.text();
  return text;
}
Enter fullscreen mode Exit fullscreen mode

I omitted explaing the filterByBrand and filterBySearch functions since they're basic Javascript. One last note is that the innerHTML property is the same as the html property in JQuery.

Wrapping Up

I hoped you learned something from reading about my process in refactoring this code, whether it be that you can write better code or that it's pretty easy to convert from JQuery to native Javascript.

I'll leave some links for reference and further readings in case you were still confused.

Feel free to connect with me on Twitter @justinhodev and happy coding!

Resources

History of JQuery
You Might Not Need JQuery
Essential Cheatsheet: Convert JQuery to Javascript
How to use fetch with async / await

Discussion (9)

Collapse
shane profile image
shane

Very interesting to see the side by side of the old and the new. Not supporting IE greatly improves things also

It is tough to know at what point to remove old code that is very legacy but still does the desired job

Collapse
jcsh profile image
Justin Ho Author • Edited

Thanks for the reply Shane!

Yes, while I've never had the pleasure to support old IE, the thought of polyfills sounds extremely tedious without tools like babel so I do understand where jquery came in there.

I would also agree not to rewrite things if it does not provide business value.

The reason I had the idea for this post though was because I helped rewrite an old app to React and removing the jQuery dependency was something I did fairly quickly.

This made me realize I really didn't need it for most things so I wrote about it using my own project as example.

Collapse
carlyraejepsenstan profile image
CarlyRaeJepsenStan

Great article! I really liked how you broke down the code before refactoring it so it was easy to understand. I thought you were going to replace $('document').ready(... with window.onload()..., though - how is it different from document.readyState === 'complete'?

Collapse
jcsh profile image
Justin Ho Author

Thanks for the comment @carlyraejepsenstan !

I don't think there was any real reason I used one over the other, might just have been the first link I chanced on while searching.

According to MDN and based on my understanding, they should be the same event.

Collapse
codr profile image
Ilya Nevolin

Jquery still wins when it comes to DOM manipulation 😊

Collapse
jcsh profile image
Justin Ho Author • Edited

Thanks for the comment Ilya!

Do you have an example of when jQuery Dom manipulation is better than native or even virtual doms like vue or react?

jcsh profile image
Justin Ho Author

Thanks for the tip Kieran!