Skip to content

Instantly share code, notes, and snippets.

@alessioalex
Forked from ShirtlessKirk/domtokenlist.js
Created October 22, 2021 19:17
Show Gist options
  • Select an option

  • Save alessioalex/6d8e378dc5cfe1a964581a08660fd621 to your computer and use it in GitHub Desktop.

Select an option

Save alessioalex/6d8e378dc5cfe1a964581a08660fd621 to your computer and use it in GitHub Desktop.

Revisions

  1. @ShirtlessKirk ShirtlessKirk revised this gist Oct 30, 2015. No changes.
  2. @ShirtlessKirk ShirtlessKirk created this gist Oct 30, 2015.
    304 changes: 304 additions & 0 deletions domtokenlist.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,304 @@
    /**
    * 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;
    }
    }));