Skip to main content Accessibility Feedback

Create your own personal jQuery!?

Yesterday, I shared a video of me converting some jQuery code into vanilla JS. Today, I thought it would be fun to show how you can create your own tiny jQuery-like library using the JavaScript class pattern and some modern methods.

Let’s dig in!

Psst! If you want to learn how to do stuff like this, you’ll love my Structure & Scale workshop (registration open now!) and my Writing Libraries pocket guide.

The starting code

The original jQuery code looked like this…

jQuery(document).ready( function() {

	let menu = jQuery('#menu');

	menu.find('li.item')
		.addClass('foo')
		.removeClass('bar')
		.css('background-color', 'rebeccapurple')
		.css('color', '#fff');

	menu.find('.hidden').css('display', 'none');

});

And I converted it into the following vanilla JS…

// Get the #menu element from the DOM
let menu = document.querySelector('#menu');

// Loop through each li.item in the menu
let items = menu.querySelectorAll('li.item');
for (let item of items) {
	item.classList.add('foo');
	item.classList.remove('bar');
	item.style.backgroundColor = 'rebeccapurple';
	item.style.color = '#fff';
}

// loop through each .hidden class and actually hide it
let hidden = menu.querySelectorAll('.hidden');
for (let item of hidden) {
	item.setAttribute('hidden', '');
}

Let’s create a small library that let’s us author this code more like the jQuery version… without jQuery!

Prefer to watch?

This is also available as a video on YouTube.

Creating a $ class

I very much like jQuery’s $() shorthand selector method, so let’s create a class named $.

class $ {
	// ...
}

Inside the constructor() for the class, we’ll accept a selector string.

We’ll pass it into the document.querySelectorAll() method, and convert the returned NodeList into an array with the Array.from() method. Then, we’ll save the result to the elems property on the instance.

class $ {

	/**
	 * The constructor object
	 * @param  {String} selector The selector to use
	 */
	constructor (selector) {
		this.elems = Array.from(document.querySelectorAll(selector));
	}

}

Now, we can use the new $() constructor to create a new instance of our class, like this…

let menu = new $('#menu');

Searching inside an element

jQuery has the find() method for searching inside a jQuery object. Let’s recreate that in our class.

The querySelectorAll() method can be run on any element, not just the document.

Let’s first update the constructor to accept a second argument, context. We’ll give it a default value of document, and use it as the Element for the querySelectorAll() method.

class $ {

	/**
	 * The constructor object
	 * @param  {String} selector The selector to use
	 * @param  {Element} context The element to search for the selector in
	 */
	constructor (selector, context = document) {
		this.elems = Array.from(context.querySelectorAll(selector));
	}

}

Next, let’s create a find() method.

We’ll pass in the selector as an argument. Then, we’ll create a new $() instance, and pass in the selector and the first item in our current instance’s elems property (this.elems) as arguments.

Then, we’ll return the new instance.

class $ {

	/**
	 * The constructor object
	 * @param  {String} selector The selector to use
	 * @param  {Element} context The element to search for the selector in
	 */
	constructor (selector, context = document) {
		this.elems = Array.from(context.querySelectorAll(selector));
	}

	/**
	 * Find matching elements inside the parent element
	 * @param  {String} selector The selector string
	 * @return {$}               A new $ instance
	 */
	find (selector) {
		return new $(selector, this.elems[0]);
	}

}

Now, we can use the find() method to search inside the first matching element, and get back a new instance of our library (just like jQuery).

let menu = new $('#menu');
menu.find('li.item');

Adding classes

Next, let’s create the addClass() method.

We’ll accept one or more classes as arguments, and use a rest parameter to capture them all as an array.

Inside the addClass() method, we’ll use a for...of loop to loop through each item in the this.elems array. Then, we’ll use the Element.classList.add() method to add our classes.

We can use the spread operator (it has the same syntax as rest parameters, which is confusing) to pass in each item of our array as its own argument.

class $ {

	// ...

	/**
	 * Add classes to all elements
	 * @param {...String} classes The classes to add
	 */
	addClass(...classes) {
		for (let item of this.elems) {
			item.classList.add(...classes);
		}
	}

}

Now, we can use the find() method to get our child list items, and then run the addClass() method to add the foo class, like this.

let menu = new $('#menu');
menu.find('li.item').addClass('foo');

Chaining methods

jQuery supports chaining multiple methods. We can, too, by running return this at the end of each of our methods that’s not already returning something.

This will return the instance itself, and give access to all of its other properties and methods.

/**
 * Add classes to all elements
 * @param {...String} classes The classes to add
 */
addClass(...classes) {
	for (let item of this.elems) {
		item.classList.add(...classes);
	}
	return this;
}

Removing classes

Let’s create another method, removeClass(), to remove classes from our elements.

We can copy/paste our addClass() method and rename it. We’ll also update it to use the Element.classList.remove() method instead.

/**
 * Remove classes from all elements
 * @param {...String} classes The classes to remove
 */
removeClass(...classes) {
	for (let item of this.elems) {
		item.classList.remove(...classes);
	}
	return this;
}

Now, we can do something like this…

let menu = new $('#menu');

menu.find('li.item')
	.addClass('foo')
	.removeClass('bar');

Updating CSS

Next, let’s recreate jQuery’s css() method.

For this one, we’ll accept a prop and val to add to the element. Then, we’ll loop through each elems, and use the Element.style property to set it.

/**
 * Set CSS on elements
 * @param  {String} prop The property to set
 * @param  {String} val  The value to use
 */
css (prop, val) {
	for (let item of this.elems) {
		item.style[prop] = val;
	}
	return this;
}

CSS uses kebab-case (like background-color), but JavaScript uses camelCase (like backgroundColor). To make things easier for users, let’s accept kebab-case and convert it to camelCase automatically.

We’ll use the String.splice() method to create an array of property “parts”, splitting on the dash (-). Then, we’ll use the Array.map() method to loop through each one and update it.

We’ll use the Array.join() method to convert it back to a string, and reassign the prop variable.

/**
 * Set CSS on elements
 * @param  {String} prop The property to set
 * @param  {String} val  The value to use
 */
css (prop, val) {

	// Convert CSS naming to JavaScript naming
	prop = prop.split('-').map(function (part, index) {
		// ...
	}).join('');

	for (let item of this.elems) {
		item.style[prop] = val;
	}

	return this;
}

Inside the Array.map() callback function, we’ll first check if the index is 0.

If so, it’s the first part of the CSS property, and we can return it as-is.

// Convert CSS naming to JavaScript naming
prop = prop.split('-').map(function (part, index) {
	if (index === 0) return part;
}).join('');

Otherwise, we’ll use the String.split() method again to get an array of each character in the string.

We’ll use the String.toUpperCase() method to capitalize the first character, and the Array.splice() method replace the current first character with the capitalized version.

Then, we’ll use the Array.join() method to convert it back into a string and return it.

// Convert CSS naming to JavaScript naming
prop = prop.split('-').map(function (part, index) {
	if (index === 0) return part;
	let arr = part.split('');
	arr.splice(0, 1, arr[0].toUpperCase());
	return arr.join('');
}).join('');

Now, we can update CSS properties like this.

let menu = new $('#menu');

menu.find('li.item')
	.addClass('foo')
	.removeClass('bar')
	.css('background-color', 'rebeccapurple')
	.css('color', '#fff');

Setting attributes

Finally, let’s add a method to modify attributes. jQuery uses the attr() method for this, so let’s copy their naming convention.

We’ll accept an attr and val to use. The Element.setAttribute() method must include a value, so we’ll set a default value of an empty string ('') for the val parameter.

Then, we’ll loop through each item in the this.elems array and set our attribute.

/**
 * Set an attribute on elements
 * @param  {String} attr The attribute to set
 * @param  {String} val  The attribute value
 */
attr (attr, val = '') {
	for (let item of this.elems) {
		item.setAttribute(attr, val);
	}
	return this;
}

Now, we have full parity with the original jQuery code, with our own tiny jQuery clone.

let menu = new $('#menu');

menu.find('li.item')
	.addClass('foo')
	.removeClass('bar')
	.css('background-color', 'rebeccapurple')
	.css('color', '#fff');

menu.find('.hidden').attr('hidden');

You can download the source code on GitHub.