Skip to content

Instantly share code, notes, and snippets.

@alessioalex
Forked from ShirtlessKirk/domtokenlist.js
Created October 22, 2021 19:17
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save alessioalex/6d8e378dc5cfe1a964581a08660fd621 to your computer and use it in GitHub Desktop.
Save alessioalex/6d8e378dc5cfe1a964581a08660fd621 to your computer and use it in GitHub Desktop.
DOMTokenList polyfill for IE < 9; implements element.classList
/**
* Polyfill of DOMTokenList for IE < 9
* Monkey patch of .add, .remove for IE 10 / 11, Firefox < 26 to support multiple arguments
* Monkey patch of .toggle for IE 10 / 11, Firefox < 24 to support second argument
*/
/*global define: false, module: false */
/*jslint nomen: true */
(function domTokenListModule(global, definition) { // non-exporting module magic dance
'use strict';
var
amd = 'amd',
exports = 'exports'; // keeps the method names for CommonJS / AMD from being compiled to single character variable
if (typeof define === 'function' && define[amd]) {
define(function definer() {
return definition(global);
});
} else if (typeof module === 'function' && module[exports]) {
module[exports] = definition(global);
} else {
definition(global);
}
}(this, function domTokenListPolyfill(global) {
'use strict';
if (!global.Element) {
return;
}
var
document = global.document,
id = 0,
lists = {},
test = document.createElement('_');
/**
* @private
*/
function createMethod(method) {
var
original = this.prototype[method];
this.prototype[method] = function override() {
var
counter,
length,
token;
for (counter = 0, length = arguments.length; counter < length; counter += 1) {
token = arguments[counter];
original.call(this, token);
}
};
}
/**
* @private
* @param {string} token Token to search for
*/
function indexOf(token) {
var
arrayPrototype = Array.prototype,
counter;
if (!!arrayPrototype.indexOf) {
return arrayPrototype.indexOf.call(this, token);
}
counter = this.length - 1;
while (counter > -1) {
if (this[counter] === token) {
return counter;
}
counter -= 1;
}
return -1;
}
/**
* @private
*/
function newId() {
id += 1;
return id;
}
/**
* @private
*/
function onchange() {
var
classes = this.classes,
classesString = classes.join(' ');
if (this.isSVG) {
this.setAttribute('class', classesString);
} else {
this.element.className = classesString;
}
this.list.length = classes.length;
}
/**
* @param {string} token Token to find
*/
function contains(token) {
return indexOf.call(lists[this.id].classes, token) !== -1;
}
function add() {
var
counter = 0,
item = lists[this.id],
classes = item.classes,
length = arguments.length,
token,
updated = false;
while (counter < length) {
token = arguments[counter];
if (indexOf.call(classes, token) === -1) {
classes.push(token);
updated = true;
}
counter += 1;
}
if (updated) {
onchange.call(item);
}
}
/**
* @param {number} index Index of list to return value
*/
function item(index) {
return lists[this.id].classes[index] || null;
}
function remove() {
var
counter = arguments.length - 1,
entry = lists[this.id],
classes = entry.classes,
index,
token,
updated = false;
while (counter > -1) {
token = arguments[counter];
index = indexOf.call(classes, token);
if (index !== -1) {
classes.splice(index, 1);
updated = true;
}
counter -= 1;
}
if (updated) {
onchange.call(entry);
}
}
function toString() {
var
entry = lists[this.id];
onchange.call(entry);
return entry.element.className;
}
/**
* @param {string} token Token to toggle
* @param {force=} force Flag to force toggle
*/
function toggle(token, force) {
var
hasToken = indexOf.call(lists[this.id].classes, token) !== -1,
method;
if (hasToken) {
if (force !== true) {
method = 'remove';
}
} else {
if (force !== false) {
method = 'add';
}
}
if (method) {
this[method](token);
}
return (force === true || force === false) ? force : !hasToken;
}
/**
* @constructor
*/
function DOMTokenList(element) {
var
className,
listId,
isSVG;
listId = element.domTokenListId;
if (listId && (listId in lists)) {
return lists[listId].list;
}
isSVG = typeof element.className === 'object';
className = element.className;
className = String(isSVG ? className.baseVal : className).replace(/^\s+|\s+$/, '');
listId = newId();
lists[listId] = {
classes: className.length !== 0 ? className.split(/\s+/) : [],
element: element,
list: this,
isSVG: isSVG
};
this.id = listId;
this.length = lists[listId].classes.length;
element.domTokenListId = listId; // apply id lookup reference to element
}
if (!('classList' in test)) {
(function () {
var
counter,
elementPrototype = global.Element.prototype,
methodList = [add, contains, item, remove, toString, toggle],
methods = ['add', 'contains', 'item', 'remove', 'toString', 'toggle'],
propertyDescriptor,
prototype = DOMTokenList.prototype;
function get() {
var
thisId = this.domTokenListId;
return thisId && (thisId in lists) ? lists[thisId].list : new DOMTokenList(this);
}
counter = methods.length - 1;
while (counter > -1) {
prototype[methods[counter]] = methodList[counter];
counter -= 1;
}
propertyDescriptor = {
get: get,
configurable: true,
enumerable: true
};
if (Object.defineProperty) {
try {
Object.defineProperty(elementPrototype, 'classList', propertyDescriptor);
} catch (ex) {
if (ex.number === -0x7FF5EC54) { // IE 8 doesn't like it to be enumerable
propertyDescriptor.enumerable = false;
Object.defineProperty(elementPrototype, 'classList', propertyDescriptor);
}
}
} else if (Object.prototype.__defineGetter__) {
elementPrototype.__defineGetter__('classList', propertyDescriptor);
}
global.DOMTokenList = DOMTokenList;
}());
} else {
test.classList.add('c1', 'c2');
if (!test.classList.contains('c2')) { // IE 10 / 11, Firefox < 26
createMethod.call(global.DOMTokenList, 'add');
createMethod.call(global.DOMTokenList, 'remove');
}
test.classList.toggle('c3', false);
if (test.classList.contains('c3')) { // IE 10 / 11, Firefox < 24
global.DOMTokenList.prototype.toggle = (function () {
var
original = global.DOMTokenList.prototype.toggle;
return function toggle(token, force) {
var
notForce = !force;
return (arguments.length > 1 && !(this.contains(token) === notForce)) ? force : original.call(this, token);
};
}());
}
test = null;
}
}));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment