UNPKG

dom-classlist

Version:

Cross-browser element (including SVG) class list manipulation

166 lines (146 loc) 3.71 kB
/** * 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); };