jQuery is one of the most popular JavaScript library . It greatly simplifies JavaScript programming. You might be curious about how jQuery is made. If you look through the source code of any well-known library, it's likely that you won't understand anything or you may feel that you need to be a genius to build one. However, it's not as difficult as it seems in reality. In this article I'm going show you how you can make your own library like jQuery from scratch.
Lets get started!
Creating the selector function
const $ = function(el) {
return new jQuery(el);
}
As you can see the selector function returns a class named jQuery. Every time you use the selector a new instance of jQuery will be created .
Now lets create our jQuery Class
jQuery object
class jQuery extends Array{
constructor(el) {
super();
if (el == document || el instanceof HTMLElement) {
this.push(el);
} else {
let elements=document.querySelectorAll(el);
for(let i=0;i<elements.length;i++){
this.push(elements[i]);
}
}
}
}
Our jQuery class extends the build-in Array class so every instance behave like an array and an object at the same time.Every time we create a new jQuery instance it checks if the element is html element or if it is the document itself. If not then it performs a querySelectorAll to get the all matched elements. Then it pushes each element to itself. Now we can add our methods to it.
Chaining
The best thing I like about jQuery is that you can chain methods, like
$('div').addClass('red').html('hello');
to apply Chaining to our jQuery class we just need to return the current instance, i.e adding
return this
after every method.
ready()
ready(callback){
this[0].addEventListener('readystatechange', e => {
if(this[0].readyState === "complete"){
callback();
return true;
}
});
}
$(document).ready(function(){
//codes
})
Checks the readyStates of an element if the readyState is complete than we know the element is completely loaded and ready for use;
each()
each(callback) {
if (callback && typeof(callback) == 'function') {
for (let i = 0; i < this.length; i++) {
callback(this[i], i);
}
return this;
}
}
$('div').each(function(el){
$(el).html('text')
})
this method iterate over all elements and give a callback for each element. We will use this for creating other methods.
siblings()
siblings(){
return [...this[0].parentNode.children].filter(c=>c!=this[0])
}
console.log($('script').siblings())
It's a pretty simple one liner. We first select all the child elements of parent element and then just remove the current element from the list , resulting an array of sibling elements.
addClass()
addClass(className) {
this.each(function(el) {
el.classList.add(className);
})
return this;
}
$('div').addClass('pink')
we use classList.add() to add the className to each item with the help of this.each method.
removeClass()
removeClass(className) {
this.each(function(el) {
el.classList.remove(className);
})
return this;
}
$('div').removeClass('pink')
similar to addClass we use classList.remove() to remove the className .
hasClass()
hasClass(className) {
return this[0].classList.contains(className);
}
console.log($('div').hasClass('pink')) //returns true or false
hasClass check if the 1st selected element has any specific class with the help of classList.contains() and return true or false accordingly.
css()
css(propertyObject) {
this.each(function(el) {
Object.assign(el.style,propertyObject);
})
return this;
}
$('div').css({
color:'red'
})
in this method the parameter is a type of object. each property of this object is added to the element.style object using in-build Object.assign function()
attr()
attr(attr, value = null) {
let getattr = this;
if (value){
this.each(function(el) {
el.setAttribute(attr, value);
});
} else {
getattr = this[0].getAttribute(attr);
}
return getattr;
}
console.log($('div').attr('class')) //gets the attribute value
$('div').attr('name','div') //sets the name attribute with value
If the value argument is not present then we just simple return the attribute value else we set the attribute for each element.
html()
html(data) {
if (data) {
this.each(function(el) {
el.innerHTML = data;
})
} else {
return this[0].innerHTML;
}
return this;
}
console.log($('div').html()) //gets the innerHTML
$('div').html('x') //add the innerHTML
Similar to attr() method just uses innerHTML instead of attribute
prepend()
prepend(el){
this[0].prepend(el);
return this;
}
let h1=document.createElement('h1');
h1.innerHTML='this is a heading';
$('div').prepend(h1) //prepend the node
$('div').prepend('hello') //prepend the string
prepend a node or string to the selected elements using in-build node.prepend().
append()
append(el){
this[0].append(el);
return this;
}
let h1=document.createElement('h1');
h1.innerHTML='this is a heading';
$('div').append(h1) //append the node
$('div').append('hello') //append the string
append a node or string to the selected elements using in-build node.append().
hide()
hide() {
this.each(function(el) {
el.style.display = "none";
});
return this;
}
$('div').hide()
change the style.display property to none
show()
show() {
this.each(function(el) {
el.style.display = "block";
});
return this;
}
$('div').show()
change the style.display property to block
on()
on(event, child, callback = null) {
if (callback != null) {
let selector = child;
this.each(function(element) {
element.addEventListener(event, function(event) {
if (event.target.matches(selector + ', ' + selector + ' *')) {
callback.apply(event.target.closest(selector), arguments);
}
})
})
} else {
//if the callback argument is not present then assume the child argument is being use as callback
callback = child;
this.each(function(element) {
element.addEventListener(event, callback);
})
}
return this;
}
$('div').on('click',function(){
alert('x')
})
$(document).on('click','div',function(){
alert('x')
})
this one is little bit complex.
If we pass all three arguments to the method it will add an EventListener to the parent element and only if the target matches child element then will give a callback.
but if we just pass two argument then it will simply add an EventListener to the element with a callback.
for the first case we can take this as an example
$(document).on('click','div',function(){
alert('x')
})
and for the second case
$('div').on('click',function(){
alert('x')
})
both serves their different purpose . First one is very useful for binding event to dynamic element.
Second one is useful for general event binding .
$.ajax()
$.ajax=function(args) {
let url = args["url"];
let type = "get";
let success =function(){};
let fail = function(){};
if(args['success']){
success=args['success'];
}
if(args['fail']){
fail=args['fail'];
}
let xhttp = new XMLHttpRequest();
xhttp.onerror = function(error){
//return fial callback with error
return fail(error);
}
xhttp.onload = function() {
let response;
if (this.readyState == 4 && this.status == 200) {
let response="";
try {
//check if the response in json
//if json the parse it
response=JSON.parse(this.responseText)
} catch (e) {
//if not json the simple reurn the response
response = this.responseText;
}
// give a success callback
return success(response);
} else {
//give a fail callback with the error status
return fail(this.status);
}
};
let parameters="";
if (args) {
type = args["type"];
if ('data' in args) {
//converting object to url URLSearchParams
parameters = new URLSearchParams(args['data']).toString();
}
}
if (type && type.toUpperCase()=='POST') {
xhttp.open("POST", url, true);
xhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
xhttp.send(parameters);
} else if (!type || type.toUpperCase()=='GET'){
xhttp.open("GET", url + "?" + parameters, true);
xhttp.setRequestHeader("X-Requested-With", "XMLHttpRequest");
xhttp.send();
}
}
$.ajax({
url:'post.php',
type:'post',
data:{
name:'Joydeep'
},
success: function(res){
console.log(res)
}
})
this ajax function is not a method but works as a property of $ object.
this function takes url,type,data,success and fail arguments just like the original jQuery.
What we do is that we create a new new XMLHttpRequest() each time the function is called and send post or get request. Now if we receive the response then we check if it is JSON response.For JSON response we parse it otherwise just simply return a success callback with the response.But if request gives a error, fail callback with the status code will be returned.
Putting it all together
class jQuery extends Array{
constructor(el) {
super();
if (el == document || el instanceof HTMLElement) {
this.push(el);
} else {
let elements=document.querySelectorAll(el);
for(let i=0;i<elements.length;i++){
this.push(elements[i]);
}
}
}
ready(callback){
this[0].addEventListener('readystatechange', e => {
if(this[0].readyState === "complete"){
callback();
return true;
}
});
}
each(callback) {
if (callback && typeof(callback) == 'function') {
for (let i = 0; i < this.length; i++) {
callback(this[i], i);
}
return this;
}
}
siblings(){
return [...this[0].parentNode.children].filter(c=>c!=this[0])
}
addClass(className) {
this.each(function(el) {
el.classList.add(className);
})
return this;
}
removeClass(className) {
this.each(function(el) {
el.classList.remove(className);
})
return this;
}
hasClass(className) {
return this[0].classList.contains(className);
}
css(propertyObject) {
this.each(function(el) {
Object.assign(el.style,propertyObject);
})
return this;
}
attr(attr, value = null) {
let getattr = this;
if (value) {
this.each(function(el) {
el.setAttribute(attr, value);
});
} else {
getattr = this[0].getAttribute(attr);
}
return getattr;
}
html(data) {
if (data) {
this.each(function(el) {
el.innerHTML = data;
})
} else {
return this[0].innerHTML;
}
return this;
}
append(el){
this[0].append(el);
return this;
}
prepend(el){
this[0].prepend(el);
return this;
}
hide() {
this.each(function(el) {
el.style.display = "none";
});
return this;
}
show() {
this.each(function(el) {
el.style.display = "block";
});
return this;
}
on(event, child, callback = null, state = null) {
if (callback != null) {
let selector = child;
this.each(function(element) {
element.addEventListener(event, function(event) {
if (event.target.matches(selector + ', ' + selector + ' *')) {
callback.apply(event.target.closest(selector), arguments);
}
}, false)
})
} else {
callback = child;
this.each(function(element) {
if (state) {
element.addEventListener(event, callback, state);
} else {
element.addEventListener(event, callback, false);
}
})
}
return this;
}
}
const $ = function(el) {
return new jQuery(el);
}
$.ajax=function(args) {
let url = args["url"];
let type = "get";
let success =function(){};
let fail = function(){};
if(args['success']){
success=args['success'];
}
if(args['fail']){
fail=args['fail'];
}
let xhttp = new XMLHttpRequest();
xhttp.onerror = function(error){
return fail(error);
}
xhttp.onload = function() {
let response;
if (this.readyState == 4 && this.status == 200) {
let response="";
try {
response=JSON.parse(this.responseText)
} catch (e) {
response = this.responseText;
}
return success(response);
} else {
return fail(this.status);
}
};
let parameters="";
if (args) {
type = args["type"];
if ('data' in args) {
parameters = new URLSearchParams(args['data']).toString();
}
}
if (type && type.toUpperCase()=='POST') {
xhttp.open("POST", url, true);
xhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
xhttp.send(parameters);
} else if (!type || type.toUpperCase()=='GET'){
xhttp.open("GET", url + "?" + parameters, true);
xhttp.setRequestHeader("X-Requested-With", "XMLHttpRequest");
xhttp.send();
}
}
Now you have your own jQuery library. Of course the Original jQuery have more methods and good optimization but this is enough for regular tasks.
IF you are interested in this type of posts please check out my
blog
Read next
Virtual Dom Diffing Algorithm Implementation In Vanilla JavaScript
Top comments (2)
Сongratulations 🥳! Your article hit the top posts for the week - dev.to/fruntend/top-10-posts-for-f...
Keep it up 👍
Thank you ☺️☺️☺️☺️