Last active
October 22, 2021 19:17
-
-
Save ShirtlessKirk/b9c0adcfd57430a201c9 to your computer and use it in GitHub Desktop.
DOMTokenList polyfill for IE < 9; implements element.classList
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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