Hire the author: Obua E
Image source https://www.tutorialrepublic.com/lib/images/jquery-illustration.png
Introduction
jQuery is a lightweight, “write less, do more” JavaScript library. The purpose of jQuery is to make it much easier to use JavaScript on your website.
jQuery takes a lot of common tasks that require many lines of JavaScript code to accomplish and wraps them into methods that you can call with a single line of code.
Throughout this article, I will be demonstrating how to build a jQuery clone using VanillaJS that can be used to manipulate DOM elements.
Glossary
- DOM: DOM stands for Document Object Model. It is the W3C ( World Wide Web Consortium ) Standard.
- It defines a standard for accessing and manipulating HTML and XML documents and The elements of DOM are head, title, body tag, etc.
- UI/UX: This is User Interface/ User Experience
- API: An API in full is an Application Programming Interface.
- It is a set of operations a software component (i.e., a system, a sub-system, class, or a function) provides to its clients
- HTML: HTML in full is Hypertext Markup Language, a code that is used to structure a web page and its content
- XML: XML in full is an extensible markup language, a document formatting language used for some World Wide Web pages
Step by step procedure
Prerequisite:
A web browser preferable Chrome, Firefox, Safari, or Edge
Setup Environment
We shall be building a simple image gallery slider to use the custom jQuery clone, We shall create custom functions to perform operations to selected elements on the DOM.
The gist code block contains a well-documented self-explanatory source code as seen below.
Step 1: Create a project folder with a relevant name and add all necessary files as listed below.
- css. Inside this folder, create style.css which will have our basic stylings
- images. Inside this folder, we shall have images for the project
- js. Inside this folder, create a main.js file that demonstrates the usage of the custom jQuery clone.
- Index.html file in the root folder
- jQueryClone.js file in the root folder. This file will have our jQuery functions to manipulate the DOM.
Step 2: Open the style.css file and add the following code.
This is the basic CSS that we shall be using in this project, the different operations performed by the CSS are explained in the documentation
/* Set the background and color of text on the body */ | |
body { | |
font-family: "Arial",sans-serif; | |
font-size: 14px; | |
color: #fff; | |
background: #333; | |
} | |
/* Color all the anchor tags white and remove default text-decoration of underlining */ | |
a { | |
color: #fff; | |
text-decoration: none; | |
} | |
/* Center text of all the h1 tag */ | |
h1 { | |
text-align: center; | |
} | |
/* Define width, margin of the container, and make overflow to be auto to avoid unnecessary scrolling */ | |
.container { | |
width: 540px; | |
margin: 40px auto; | |
overflow: auto; | |
} | |
/* Define the styles of the div holding all the images */ | |
.slider-inner { | |
width: 500px; | |
height: 300px; | |
position: relative; | |
overflow: hidden; | |
float: left; | |
padding: 3px; | |
border: #666 solid 1px; | |
} | |
/* Get individual image in the div and change display to none, then define the width and height */ | |
.slider-inner img { | |
display: none; | |
width: 500px; | |
height: 300px; | |
} | |
/* Change display of the image having the active class to inline-block */ | |
.slider-inner img.active { | |
display: inline-block; | |
} | |
/* Define general behavior of the next and prev class */ | |
.next, | |
.prev { | |
float: left; | |
margin-top: 130px; | |
cursor: pointer; | |
} | |
/* Define indivial behavior of prev and next relative to the above general defination */ | |
.prev { | |
position: relative; | |
margin-right: -45px; | |
z-index: 100; | |
} | |
.next { | |
position: relative; | |
margin-left: -45px; | |
z-index: 100; | |
} |
Step 3: Add some images to the images folders.
Add relevant custom images of your choice here and link them in the index.html file.
Step 4: Open the main.js file and add the following code below.
The code block below demonstrates how the custom $ function is used to select and manipulate the DOM elements.
The operations performed is well documented in the code as seen below.
/** | |
* Select the document then call the ready function which then checks if DOM content is loaded, | |
* then executes the callback. | |
*/ | |
$(document) | |
.ready(function () { | |
$(document) | |
.on("click", ".next", function () { | |
const currentImg = $(".active") | |
const nextImg = currentImg.next() | |
/** | |
* Check if there is a next image then perform an action | |
*/ | |
if (nextImg.length) { | |
/** | |
* Remove the active class from the current image and hide it behind the next image | |
*/ | |
currentImg | |
.removeClass("active") | |
.css("z-index", -10) | |
/** | |
* Add the active class to the next image and show it infront of the current image | |
*/ | |
nextImg | |
.addClass("active") | |
.css("z-index", 10) | |
} | |
}) | |
$(".prev").on("click", function () { | |
const currentImg = $(".active") | |
const prevImg = currentImg.prev() | |
/** | |
* Check is there is a previous image then perform an action | |
*/ | |
if (prevImg.length) { | |
currentImg | |
.removeClass("active") | |
.css("z-index", -10) | |
prevImg | |
.addClass("active") | |
.css("z-index", 10) | |
} | |
}) | |
// Demonstrating use of custom jQuery functions | |
/** | |
* Select all elements having this class and bold the text | |
*/ | |
$('.bold-text').bold() | |
/** | |
* Select all elements having this class then, change text color to green and change the font size to 15px | |
*/ | |
$('.color-text') | |
.greenify() | |
.size('15px') | |
/** | |
* Select all elements having this class then, bold and change the text color to green | |
*/ | |
$('.bold-and-color-text') | |
.bold() | |
.greenify() | |
/** | |
* Select all elements having this class and underline text | |
*/ | |
$('.underline-text').underline() | |
}) | |
$.get({ | |
url: "https://jsonplaceholder.typicode.com/todos/1", | |
success: data => { | |
console.log("First success", data) | |
} | |
}) | |
.done(data => console.log("Second success", data)) | |
.fail(e => console.error("Fail", e)) | |
.always(() => console.log("Always")) |
Step 5: Open index.html and add in this boilerplate code below in the gist code block.
Below is all the basic HTML that we need, It’s basically having the slider HTML code block and another code block for demonstrating custom jQuery functions as commented in the code block below.
<!DOCTYPE html> | |
<html> | |
<head> | |
<title>JQSlider</title> | |
<link rel="stylesheet" href="css/style.css"> | |
<!-- | |
Import jQueryClone.js first as it has the core functions to be used by main.js | |
Or, you can pass the defer keyword to the main.js like | |
<script src="js/main.js" defer></script> incase loaded before the jQueryClone.js | |
so that it loads after the rest of the contents are loaded. | |
--> | |
<script src="js/main.js" defer></script> | |
<script src="jQueryClone.js"></script> | |
</head> | |
<body> | |
<div class="container"> | |
<h1>JQSlider</h1> | |
<div class="slider-outer"> | |
<!-- The previous button --> | |
<img src="images/arrow-left.png" class="prev" alt="Prev"> | |
<!-- Elements for the slideshow --> | |
<div class="slider-inner"> | |
<img src="images/image1.jpg" class="active"> | |
<img src="images/image2.jpg"> | |
<img src="images/image3.jpg"> | |
<img src="images/image4.jpg"> | |
</div> | |
<!-- The next button --> | |
<img src="images/arrow-right.png" class="next" alt="Next"> | |
</div> | |
</div> | |
<hr> | |
<!-- HTML elements to test the custom built functions by using class selector --> | |
<h4 style="text-align: center;">Demonstrating use of the custom functions</h4> | |
<div style="display:flex; justify-content:space-evenly"> | |
<div class="bold-text">Bold Text</div> | |
<div class="color-text">Make me green</div> | |
<div class="bold-and-color-text">Bold and make me green</div> | |
<div class="underline-text">Underline me</div> | |
</div> | |
</body> | |
</html> |
Step 6: Open the jQueryClone.js file and add the attached code below in the gist code block
This is where we shall have the core definition and implementation of the custom jQuery, it shall explain how all the jQuery and custom functions/ methods are created.
It’s well explained as it can be seen from the documented code below.
/** | |
* The $ function | |
* It takes in a param which can either be a string like class selector .active or | |
* an element selector like div | |
* @param param | |
* @returns ElementCollection | |
* | |
*/ | |
function $(param) { | |
/** | |
* Return an ElementCollection instance with a querySelectorAll of the param like $('.active') | |
* else | |
* Return an ElementCollection instance with the param which is an element like $('div') | |
*/ | |
if (typeof param === "string" || param instanceof String) { | |
/** | |
* querySelectorAll returns HTMLElement collection | |
* we need to use the spread syntax ... to convert it to an array. | |
*/ | |
return new ElementCollection(...document.querySelectorAll(param)) | |
} else { | |
/** | |
* The param here in this case is converted to an array through literal constructor | |
* Essentially this is similar to doing this [param] sinces ElementCollection extends Array class | |
*/ | |
return new ElementCollection(param) | |
} | |
} | |
/** | |
* ElementCollection class extends a generic Array class which provides a | |
* number of array methods which allows us to encapsulate the array functionalities. | |
*/ | |
class ElementCollection extends Array { | |
/** | |
* The ready function is to check if the DOM Elements are loaded before selecting them. | |
* It receives a callback function cb which is called when the DOM is already fully loaded. | |
* @param {*} cb | |
* @returns ElementCollection | |
*/ | |
ready(cb) { | |
/** | |
* The ready function is called on an element like document e.g. $(document).ready(), | |
* so we need to check if some of the element in the collection is ready. | |
* We are using the some array method which runs through all the elements and | |
* ruturns true if some of elements from the DOM gets loaded | |
*/ | |
const isReady = this.some(e => { | |
return e.readyState != null && e.readyState != "loading" | |
}) | |
if (isReady) { | |
/** | |
* Call the callback (cb) immediately if DOM content is ready | |
*/ | |
cb() | |
} else { | |
/** | |
* Add an event listener of DOMContentLoaded and then call the callback (cb) | |
* So let's define the on function? | |
*/ | |
this.on("DOMContentLoaded", cb) | |
} | |
return this | |
} | |
/** | |
* The on function takes in the following parameters | |
* @param {*} event this is the event the element or selector should listen to | |
* @param {*} cbOrSelector the second parameter can either be a callback or a selector | |
* @param {*} cb the third element is always an optional callback which is used when the second parameter is a selector | |
* @returns ElementCollection | |
*/ | |
on(event, cbOrSelector, cb) { | |
/** | |
* Check if the second parameter is a function. | |
* If yes, then it's a callback | |
* else | |
* It's a selector | |
*/ | |
if (typeof cbOrSelector === "function") { | |
/** | |
* Run through each individual element and add the event listener with the callback | |
*/ | |
this.forEach(e => e.addEventListener(event, cbOrSelector)) | |
} else { | |
/** | |
* Again run through each individual element and add the event listener but for this time, | |
* for every event listener you add to an element, check if the target which is what's clicked for example matches the selector #cbOrSelector, | |
* then call the callback (cb) and pass in the event (e). | |
* Example code | |
* <code> | |
* $('.table').on('click','.tr-btn', function(e) {}) | |
* </code> | |
* Essentially meaning, for every tr-btn inside the table, | |
* check which tr-btn is clicked and call the callback (cb) with the event | |
*/ | |
this.forEach(element => { | |
element.addEventListener(event, e => { | |
if (e.target.matches(cbOrSelector)) | |
cb(e) | |
}) | |
}) | |
} | |
return this | |
} | |
/** | |
* The next function returns the next sibling in the collection. | |
* In this case it will return the next sibling in $('.active') selector | |
* | |
* @returns Array | |
*/ | |
next() { | |
/** | |
* Create a new collection with next element siblings and only return the elements which are not null | |
*/ | |
return this | |
.map(e => e.nextElementSibling) | |
.filter(e => e != null) | |
} | |
/** | |
* The prev function returns the previous sibling in the collection. | |
* In this case it will return the previous sibling in $('.active') selector | |
* | |
* @returns Array | |
*/ | |
prev() { | |
/** | |
* Create a new collection with previous element siblings and only return the elements which are not null | |
*/ | |
return this | |
.map(e => e.previousElementSibling) | |
.filter(e => e != null) | |
} | |
/** | |
* This function takes in the class name and removes it from the element(s) | |
* @param {*} className | |
* @returns ElementCollection | |
*/ | |
removeClass(className) { | |
/** | |
* Run through the elements collection and for each element, | |
* locate the classList object then remove the class name | |
*/ | |
this.forEach(e => e.classList.remove(className)) | |
return this | |
} | |
/** | |
* This function takes in the class name and adds it to the element(s) | |
* @param {*} className | |
* @returns ElementCollection | |
*/ | |
addClass(className) { | |
/** | |
* Run through the elements collection and for each element, | |
* locate the classList object then add the class name | |
*/ | |
this.forEach(e => e.classList.add(className)) | |
return this | |
} | |
/** | |
* The function takes in the css property to modify and then the value | |
* NOTE: jQuery accepts the normal css property like font-size, but then | |
* the style property of an element accepts camel case style like fontSize, | |
* so we need to convert the normal style to camel case before applying the value to it. | |
* | |
* @param {*} property | |
* @param {*} value | |
* @returns ElementCollection | |
*/ | |
css(property, value) { | |
/** | |
* Check for anytime there is a an hythen(-) followed by any letter between a to z, | |
* then select the hythen(-) and the first letter after the hythen(-) and return to the group. | |
* After this, get the group, replace the hythen(-) with nothing/empty string and | |
* then convert the first letter to uppercase | |
* | |
*/ | |
const camelProp = property.replace(/(-[a-z])/, group => { | |
return group | |
.replace("-", "") | |
.toUpperCase() | |
}) | |
/** | |
* Run through the elements and apply the value to the selected property. | |
*/ | |
this.forEach(e => (e.style[camelProp] = value)) | |
return this | |
} | |
/** | |
* A list of all the custom functions with its signatures and implementation | |
*/ | |
/** | |
* This function gets the color property of the selected element | |
* and applies the value of green to it. | |
* @returns ElementCollection | |
*/ | |
greenify() { | |
this.css("color", "green") | |
return this | |
} | |
/** | |
* This function gets the font-weight property of the selected element | |
* and applies the value of bold to it. | |
* @returns ElementCollection | |
*/ | |
bold() { | |
this.css("font-weight", "bold") | |
return this | |
} | |
/** | |
*This function gets the font-size property of the selected element | |
* and applies the value to it. | |
* @param {*} value | |
* @returns ElementCollection | |
*/ | |
size(value) { | |
this.css("font-size", value) | |
return this | |
} | |
/** | |
* This function gets the text-decoration property of the selected element | |
* and applies the value of underline to it. | |
* @returns ElementCollection | |
*/ | |
underline() { | |
this.css("text-decoration", "underline") | |
return this | |
} | |
} | |
//End ElementCollection class | |
/** | |
* The get function takes in a number of object parameters as object properties, | |
* In this case, we shall use the following parameters. | |
* | |
* url: this is the endpoint we query | |
* data: this is the data we send to the endpoint and since this is a get method, we shall use query string structure of url rewriting | |
* success: this is the callback function called when operation is successful and response sent to it as a parameter. | |
* dataType: this is the type of data we want returned to the client, e.g. json | |
* | |
* @param {url,data,success,dataType} | |
* @returns AjaxPromise | |
*/ | |
$.get = function ({ | |
url, | |
data = {}, | |
success = () => {}, | |
dataType | |
}) { | |
/** | |
* Since data is passed in as a JavaScript Object, | |
* We need to convert it to query string. So | |
* We loop through object entries and return key value pairs of the format key=value&key=value etc. | |
* | |
*/ | |
const queryString = Object | |
.entries(data) | |
.map(([key, value]) => { | |
return `${key}=${value}` | |
}) | |
.join("&") | |
/** | |
* Instantiate the AjaxPromise class and pass in the fetch API promise to it | |
*/ | |
return new AjaxPromise(fetchGetPromise(url, queryString, dataType, success)) | |
} | |
/** | |
* This function calls the fetch API with the given parameters. | |
* @param {*} url | |
* @param {*} queryString | |
* @param {*} dataType | |
* @param {*} success | |
* @returns Promise | |
*/ | |
function fetchGetPromise(url, queryString, dataType, success) { | |
return fetch(`${url}?${queryString}`, { | |
method: "GET", | |
headers: { | |
"Content-Type": dataType | |
} | |
}).then(res => { | |
if (res.ok) { | |
return res.json() | |
} else { | |
throw new Error(res.status) | |
} | |
}).then(data => { | |
success(data) | |
return data | |
}) | |
} | |
/** | |
* AjaxPromise class accepts a promise in it's constructor then performs all the specified | |
* defined function operations to the promise | |
*/ | |
class AjaxPromise { | |
constructor(promise) { | |
this.promise = promise | |
} | |
/** | |
* The done function gets the promise and calls the then function to get the data, it then sends it to a callback | |
* then returns a new promise | |
* @param {*} cb | |
* @returns AjaxPromise | |
*/ | |
done(cb) { | |
this.promise = this | |
.promise | |
.then(data => { | |
cb(data) | |
return data | |
}) | |
return this | |
} | |
/** | |
* The fail function gets the promise and calls the catch function to get the error incase of an error, | |
* it then sends it to a callback then returns a new promise | |
* @param {*} cb | |
* @returns AjaxPromise | |
*/ | |
fail(cb) { | |
this.promise = this.promise.catch(cb); | |
return this | |
} | |
/** | |
* The always function gets the promise and calls the finally function and calls a callback then returns a new promise | |
* Once this function is called on the promise, it will always be called regardless there is an error or not. | |
* @param {*} cb | |
* @returns AjaxPromise | |
*/ | |
always(cb) { | |
this.promise = this | |
.promise | |
. finally(cb) | |
return this | |
} | |
} |
Step 7: Open index.html with a live server or any of the mentioned browsers in the prerequisites above.
There you go, you have successfully built a custom jQuery from scratch using VanillaJS.
Steps to publish the library to NPM
- First, create a package.json file if it does not exist within your root directory.
Run command npm init -y on the terminal in the root directory. This will generate a boilerplate package.json file and fill it with basic information about the project, the values however can be edited based on your needs.
The field’s name and version are required. But it’s very important to fill in other fields as well for the clarity of the library.
The output would look like this after modifying the fields to suit the needs.
{ | |
"name": "customised-jquery-obuae", | |
"version": "1.0.0", | |
"description": "This is a customised jquery library with extra features for manipulating the DOM elements", | |
"main": "jQueryClone.js", | |
"scripts": { | |
"test": "echo \"Error: no test specified\" && exit 1" | |
}, | |
"repository": { | |
"type": "git", | |
"url": "git+https://github.com/learningdollars/emmanuelo-vanilla-build-jquery.git" | |
}, | |
"keywords": ["jQuery","JavaScript"], | |
"author": "EmmanuelObua", | |
"license": "ISC", | |
"bugs": { | |
"url": "https://github.com/learningdollars/emmanuelo-vanilla-build-jquery/issues" | |
}, | |
"homepage": "https://github.com/learningdollars/emmanuelo-vanilla-build-jquery#readme" | |
} |
The main field contains the starting js file where we export our module. In this case, the main field value shall be jQueryClone.js
2. Let’s now update our jQueryClone.js file and export our variable(s) to ready it for publishing by adding the content below to the bottom of the file.
/** | |
* Add this at the end of jQueryClone.js file to export the $ function | |
* Check if we are using modules and export the $ function else attach it to the window object | |
*/ | |
if (typeof exports != "undefined") { | |
exports.$ = $; | |
} else { | |
window.$ = $ | |
} |
3. Create a .npmignore file in the root directory to help ignore unwanted files during publishing, add in the following content as below.
css | |
images | |
js | |
index.html |
4. If you don’t have an NPM user you need to sign up here and make sure you verify your email then follow the instructions below.
// Login in your terminal in the root directory. | |
npm login | |
//Now we are ready to publish our library. | |
npm publish | |
//Now install in any project for use. | |
npm i customised-jquery-obuae --save | |
//USAGE | |
import { $ } from 'customised-jquery-obuae'; | |
$('.color-text').greenify().size('15px') |
5. We also want to access the module from a script tag, we’ll use the simple solution UNPKG which gets the file from npmjs for us. This is what we put in the src attribute. by using the format below, you can access the library from npm in your script tag
unpkg.com/:package@:version/:file, our library would be fetched like this
https://unpkg.com/customised-jquery-obuae@1.2.0/jQueryClone.js
Check out to publish-to-npm branch on the Github repo to get the full source code or follow this link to access it on the repo.
Learning Tool
To best learn about jQuery, I recommend reading documentation and relevant blog posts
- https://api.jquery.com. This provides jQuery API documentation including all the function signatures and relevant usage
- .https://developer.mozilla.org/en-US/docs/Web/JavaScript. This has all the JavaScript API with all the functions of JavaScript and its usages.
Learning Strategy
I had to first learn about jQuery basic and advanced API and function usage before thinking of building the clone using VanillaJS.
Talking about VanillaJS, I had to first get familiar with basic concepts and basic API including useful functions and classes built in VanillaJS before writing the jQuery clone.
This made it easier to build this feature to work like the very known jQuery/$ global variable
By searching the keywords jquery official documentation and vanilla js official documentation, I was able to get the API docs for jQuery and VanillaJS respectively
Am now able to build my own JavaScript library with the help of the skill sets from this beautiful article.
Reflective Analysis
As a result of using jQuery, the benefits can be best seen when manipulating dynamic DOM elements on the web page without having to force refresh pages already rendered. This plays a very big role in boosting the UI/UX experience.
Conclusion
I believe this article will help boost your creativity for building JavaScript Libraries both for your own and for public consumption by developers around the globe.
jQuery can also be used by also famous JavaScript Libraries like ReactJS, VueJS, and many other libraries.
Here is the link to the Github repository to get started.
Nice Learning and Happy Coding.
Good one Obua, I am thinking maybe in another article you can discuss how to make this clone reusable and distributable like publishing on NPM or CDN since this article is targeting how to create a reusable library like Jquery. Good job on this
Yes peter, this has be demonstrated as suggested, thanks for your contribution.
Obua, nice update