This post is Part 2 of 'Building your own DOM utility'.
Download code/resources for this post from GitHub
You can read Part 1 first to
see my implementation of the $DOM
utility. This post follows on
from that by showing how to add further useful methods.
In order to make our custom $DOM
utility more useful, we should add further
methods to help perform common tasks with the DOM. Below are the details
of how to implement various methods, many of which will be familiar to those
who have used jQuery before.
First up, we'll create some methods for dealing with adding, removing and
checking for the presence of CSS classes on elements. Each DOM
element has a className
property which stores all classes
currently set on that element as a string, with a single space between each
class name. Knowing this fact, it is fairly easy to implement class-oriented
methods.
hasClass()
This method checks to see whether the element has a given class.
$DOM_proto.hasClass = function hasClass(className) {
// Error if class name not specified
if (!className)
throw new Error('No class name specified');
var currentClasses = this._element.className.split(' ');
return currentClasses.indexOf(className) > -1;
};
addClass()
This method adds a given class name to the current element if it is not already present. This method is chainable.
$DOM_proto.addClass = function addClass(className) {
// Error if no class name specified
if (!className)
throw new Error('No class name specified')
var currentClasses = this._element.className.split(' ');
if (!this.hasClass(className))
currentClasses.push(className);
this._element.className = currentClasses.join(' ').trim();
return this;
};
removeClass()
This method removes a given class name from the current element if it is present on the element. This method is chainable.
$DOM_proto.removeClass = function removeClass(className) {
// Error if no class name specified
if (!className)
throw new Error('No class name specified')
var classList = this._element.className
if (this.hasClass(className))
classList = classList.replace(className, '');
// Update className property, removing excess white space
this._element.className = classList.replace(/\s+/g, ' ').trim();
return this;
};
toggleClass()
This method toggles a given class name on the element. If it is currently set, it is removed, otherwise it is added to the element. This method is chainable.
$DOM_proto.toggleClass = function toggleClass(className) {
// Error if class name not specified
if (!className)
throw new Error('No class name specified')
if (this.hasClass(className)) {
this.removeClass(className);
} else {
this.addClass(className);
}
return this;
};
Next we'll implement methods for getting the parent element, getting direct children elements, and selecting elements using the current element as the root element.
parent()
This method gets the parent element of the current element
if it exists, otherwise it returns null
. It
simply accesses the parentNode
property on the
standard DOM element. Returns the parent element
as a $DOM
object.
$DOM_proto.parent = function parent() {
return (this._element.parentNode ?
$DOM(this._element.parentNode) :
null);
};
children()
This method gets the direct children elements of the
current element, by using the children
property present on all standard DOM
elements. Returns an array of $DOM
corresponding
to the children elements.
$DOM_proto.children = function children() {
var c = this._element.children,
$doms = [],
i, n = c.length;
// Convert to $DOM objects
for (i = 0; i < n; i++)
$doms[i] = $DOM(c[i]);
return $doms;;
};
find()
This method returns all descendant elements (at any level)
that match the provided selector. It does this by calling
querySelectorAll
on the current element
instead of on document
. Returns an array of
matching $DOM
objects.
$DOM_proto.find = function find(selector) {
var elems;
try {
elems = this._element.querySelectorAll(selector);
} catch (exception) {
throw new Error('Invalid selector provided');
}
var i, n = elems.length,
$doms = [];
// Convert to $DOM objects
for (i = 0; i < n; i++)
$doms.push($DOM(elems[i]));
return $doms;
};
append()
This method appends an element as a last child of the
current element. We will accept three types of argument:
a $DOM
object, a raw DOM element,
or a string specifying that we want to create a new element
on the fly (e.g. <div>
). Returns the
appended element as a $DOM
object.
$DOM_proto.append = function append(child) {
// Error for no argument
if (!child)
throw new Error('No argument provided');
// Raw DOM element
if (child instanceof Element) {
this._element.appendChild(child);
return $DOM(child);
}
// Custom $DOM object
if (child._element instanceof Element) {
this._element.appendChild(child._element);
return child;
}
var newTagPattern = /^<([^\/]+)\/?>$/,
elem;
// Create a new element on the fly
if (newTagPattern.test(child)) {
elem = document.createElement(child.match(newTagPattern)[1]);
this._element.appendChild(elem);
return $DOM(elem);
}
// Otherwise throw error
throw new Error('Invalid argument provided');
};
prepend()
This method prepends an element as a first child of the
current element. We will accept three types of argument:
a $DOM
object, a raw DOM element,
or a string specifying that we want to create a new element
on the fly (e.g. <div>
). Returns the
prepended element as a $DOM
object.
$DOM_proto.prepend = function prepend(child) {
// Helper function to avoid code repetition
var self = this;
var _insertBefore = function(elem) {
self._element.insertBefore(elem, self._element.childNodes[0]);
};
// Error for no argument
if (!child)
throw new Error('No argument provided');
// Raw DOM element
if (child instanceof Element) {
_insertBefore(child);
return $DOM(child);
}
// Custom $DOM object
if (child._element instanceof Element) {
_insertBefore(child._element);
return child;
}
var newTagPattern = /^<([^\/]+)\/?>$/,
elem;
// Create a new element on the fly
if (newTagPattern.test(child)) {
elem = document.createElement(child.match(newTagPattern)[1]);
_insertBefore(elem)
return $DOM(elem);
}
// Otherwise throw error
throw new Error('Invalid argument provided');
};
remove()
Removes the current element from the document. This method is chainable, but note that any further operations will not be visible unless the element is later added back into the document.
$DOM_proto.remove = function remove() {
var parent = this.parent();
// Throw error if element is document root
if (!parent)
throw new Error('Cannot remove root element!');
parent._element.removeChild(this._element);
return this;
}
All modern browsers support addEventListener
, so this is what
we we will use in our method for adding event listeners to the current element.
on()
Add an event listener to the current element. The third argument
context
allows you to use a custom value for
this
inside the event handler function. This
method is chainable.
$DOM_proto.on = function on(eventName, handler, context) {
if (typeof context === 'undefined')
context = this;
this._element.addEventListener(eventName, handler.bind(context), false);
return this;
};
I hope you enjoyed this post. Most of the convenience methods have
now been implemented. We have still not implemented a css()
method for adding inline styles, so watch for Part 3 of this post
coming soon. See Part 3 next,
where we will also look at a data()
method for binding data to
elements.