dom-classlist
Version:
Cross-browser element (including SVG) class list manipulation
166 lines (146 loc) • 3.71 kB
JavaScript
/**
* Module export
*
* @param {Element} el
* @return {ClassList}
*/
module.exports = function (el) {
return new ClassList(el);
};
/**
* Initialize a new ClassList for the given element
*
* @param {Element} el DOM Element
*/
function ClassList(el) {
if (!el || el.nodeType !== 1) {
throw new Error('A DOM Element reference is required');
}
this.el = el;
this.classList = el.classList;
}
/**
* Check token validity
*
* @param token
* @param [method]
*/
function checkToken(token, method) {
method = method || 'a method';
if (typeof token != 'string') {
throw new TypeError(
'Failed to execute \'' + method + '\' on \'ClassList\': ' +
'the token provided (\'' + token + '\') is not a string.'
);
}
if (token === "") {
throw new SyntaxError(
'Failed to execute \'' + method + '\' on \'ClassList\': ' +
'the token provided must not be empty.'
);
}
if (/\s/.test(token)) {
throw new Error(
'Failed to execute \'' + method + '\' on \'ClassList\': ' +
'the token provided (\'' + token + '\') contains HTML space ' +
'characters, which are not valid in tokens.'
);
}
}
/**
* Return an array of the class names on the element.
*
* @return {Array}
*/
ClassList.prototype.toArray = function () {
var str = (this.el.getAttribute('class') || '').replace(/^\s+|\s+$/g, '');
var classes = str.split(/\s+/);
if ('' === classes[0]) { classes.shift(); }
return classes;
};
/**
* Add the given `token` to the class list if it's not already present.
*
* @param {String} token
*/
ClassList.prototype.add = function (token) {
var classes, index, updated;
checkToken(token, 'add');
if (this.classList) {
this.classList.add(token);
}
else {
// fallback
classes = this.toArray();
index = classes.indexOf(token);
if (index === -1) {
classes.push(token);
this.el.setAttribute('class', classes.join(' '));
}
}
return;
};
/**
* Check if the given `token` is in the class list.
*
* @param {String} token
* @return {Boolean}
*/
ClassList.prototype.contains = function (token) {
checkToken(token, 'contains');
return this.classList ?
this.classList.contains(token) :
this.toArray().indexOf(token) > -1;
};
/**
* Remove any class names that match the given `token`, when present.
*
* @param {String|RegExp} token
*/
ClassList.prototype.remove = function (token) {
var arr, classes, i, index, len;
if ('[object RegExp]' == Object.prototype.toString.call(token)) {
arr = this.toArray();
for (i = 0, len = arr.length; i < len; i++) {
if (token.test(arr[i])) {
this.remove(arr[i]);
}
}
}
else {
checkToken(token, 'remove');
if (this.classList) {
this.classList.remove(token);
}
else {
// fallback
classes = this.toArray();
index = classes.indexOf(token);
if (index > -1) {
classes.splice(index, 1);
this.el.setAttribute('class', classes.join(' '));
}
}
}
return;
};
/**
* Toggle the `token` in the class list. Optionally force state via `force`.
*
* Native `classList` is not used as some browsers that support `classList` do
* not support `force`. Avoiding `classList` altogether keeps this function
* simple.
*
* @param {String} token
* @param {Boolean} [force]
* @return {Boolean}
*/
ClassList.prototype.toggle = function (token, force) {
checkToken(token, 'toggle');
var hasToken = this.contains(token);
var method = hasToken ? (force !== true && 'remove') : (force !== false && 'add');
if (method) {
this[method](token);
}
return (typeof force == 'boolean' ? force : !hasToken);
};