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>");
}
});
}
});
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:
- An event is called when the value of a select element with the id
brand
changes- event: sends an AJAX
POST
request toload_data.php
which returns some data to be shown in an element with the idshow_product
- event: sends an AJAX
- 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 elementsearch_result
- it calls a
- The function
search_sneaker
sends an AJAXPOST
request toload_data.php
and renders the return values in an element with the idsearch_result
- 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,
- Manually encode it ourselves (ex.
field1=data1&field2=data2
) - 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;
}
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');
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);
}
}
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;
}
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
Top comments (7)
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
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.
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(...
withwindow.onload()...
, though - how is it different fromdocument.readyState === 'complete'
?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.
Jquery still wins when it comes to DOM manipulation 😊
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?
Thanks for the tip Kieran!