Make your own jQuery from scratch
Tags: javascript, beginners, tutorial, oop
Published: 2023-03-18 05:41:25


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
```javascript
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
```javascript
 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

```javascript
$('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()
```javascript
ready(callback){
    this[0].addEventListener('readystatechange', e => {
        if(this[0].readyState === "complete"){
            callback();
            return true;
        }
      });
}
```
```javascript
$(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()
```javascript
    each(callback) {
        if (callback && typeof(callback) == 'function') {
            for (let i = 0; i < this.length; i++) {
                callback(this[i], i);
            }
            return this;
        } 
    }
```
```javascript
$('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()
```javascript
siblings(){
    return  [...this[0].parentNode.children].filter(c=>c!=this[0])
}
```
```javascript
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()
```javascript
addClass(className) {
    this.each(function(el) {
        el.classList.add(className);
    })
    return this;
}
```
```javascript
$('div').addClass('pink')
```

we use classList.add() to add the className to each item with the help of this.each method.
### removeClass()
```javascript
removeClass(className) {
    this.each(function(el) {
        el.classList.remove(className);
    })
    return this;
}
```
```javascript
$('div').removeClass('pink')
```
similar to addClass we use classList.remove() to remove the className .
### hasClass()
```javascript

hasClass(className) {
    return this[0].classList.contains(className);
}
```
```javascript
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()
```javascript
 css(propertyObject) {
       this.each(function(el) {
           Object.assign(el.style,propertyObject);
        })
        return this;
    }
```
```javascript
$('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()
```javascript
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;
}
```
```javascript
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()
```javascript
html(data) {
    if (data) {
        this.each(function(el) {
            el.innerHTML = data;
        })
    } else {
        return this[0].innerHTML;
    }
    return this;
}
```
```javascript
console.log($('div').html()) //gets the innerHTML 
$('div').html('x') //add the innerHTML
```
Similar to attr() method just uses innerHTML instead of attribute 
### prepend()
```javascript
prepend(el){
    this[0].prepend(el);
    return this;
}
```
```javascript
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()
```javascript
append(el){
    this[0].append(el);
    return this;
}
```
```javascript
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()
```javascript
hide() {
    this.each(function(el) {
        el.style.display = "none";
    });
    return this;
}
```
```javascript
$('div').hide()
```

change the style.display property to none
### show()
```javascript
show() {
    this.each(function(el) {
        el.style.display = "block";
    });
    return this;
}
```
```javascript
$('div').show()
```
change the style.display property to block
## on()
```javascript
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;
}
```
```javascript
$('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
```javascript
$(document).on('click','div',function(){
    alert('x')
})
```
and for the second case
```javascript
$('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()
```javascript
$.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();
        }
    }
```
```javascript
$.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
```javascript
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](https://codegleam.in/)<br>
Read next
> [Virtual Dom Diffing Algorithm Implementation In Vanilla JavaScript](https://codegleam.in/articles/Virtual-Dom-diffing-algorithm-implementation-in-Vanilla-JS-1)<br>