DOMTokenList和DOMSettableTokenList接口(interface)(MDNWHATWG)提供了用于处理由空格分隔的字符串表示的字符串 token 的有序集合的方法。它们最常以Element.prototype.classList属性的形式使用,该属性是DOMTokenList,它反射(reflect)关联元素的class属性。

var div = document.createElement('div');
div.setAttribute('class', 'hello world goodnight moon');

var list = div.classList;

console.assert(list.length           === 4);
console.assert(list[0]               === 'hello');
console.assert(list.item(1)          === 'world');
console.assert(list.contains('moon') === true);
console.assert(list.contains('mars') === false);

list.remove('world', 'earth', 'dirt', 'sand');
list.add('hello', 'mars');
list.toggle('goodnight');

console.assert(div.getAttribute('class') === 'hello moon mars');


我正在处理一个自定义元素(HTML5RocksW3C Draft),该元素显示指定的Stack Overflow用户的 Activity 的实时提要。此用户列表是在ids属性中指定的,可以随时更新。

<so-users ids="1114 22656 106224"></so-users>

document.querySelector('so-users').setAttribute('ids', '23354 115866');

我不希望用户直接操作此属性,而希望拥有一个.ids属性,该属性提供了可供他们使用的DOMTokenList。理想情况下,这将与属性直接关联,但是我必须手动绑定(bind)的未绑定(bind)DOMSettableTokenList实例也可以。

document.querySelector('so-users').ids.add('17174');

不幸的是,我一直无法找到创建DOMTokenList实例的任何方法。该定义不是构造函数,当我调用任何关联方法时,使用其原型(prototype)直接创建对象会导致错误:

new DOMTokenList;         // TypeError: Illegal constructor
new DOMSettableTokenList; // TypeError: Illegal constructor

var list = Object.create(DOMSettableTokenList.prototype, {
  value: { value: 'hello world' }
});
console.assert(list instanceof DOMTokenList);
console.assert(list instanceof DOMSettableTokenList);
list.item(0); // TypeError: Illegal invocation

function TokenListConstructor() {
  this.value = 'hello world';
}
TokenListConstructor.prototype = DOMSettableTokenList.prototype;
var list = new TokenListConstructor;
console.assert(list instanceof DOMTokenList);
console.assert(list instanceof DOMSettableTokenList);
list.add('moon'); // TypeError: Illegal invocation

如何构造新的DOMTokenListDOMSettableTokenList实例?

最佳答案

您不能直接创建DOMTokenList或DOMSettableTokenList。相反,您应该使用class属性来存储和检索数据,并且可能将DOM元素的ids属性映射到classList属性。

    var element = document.querySelector('so-users');
    element.ids = element.classList;

您可以根据文档使用relList,但更支持classList,唯一的缺点是,如果您的ID之一与类名匹配,则可能会遇到问题,因此请设置内联样式以隐藏元素以防万一。

对于自定义组件的兼容性应予以关注(IE> = 10,Firefox 3.6,Chrome 8,Opera 11.5和Safari 5.1中存在classList,请参阅http://caniuse.com/#feat=classlist),因此,如果兼容性是您的要求,请使用下面发布的另一种解决方案。

如果您不能使用clases或classList和/或必须使用ids属性,则应根据规范实现自定义函数,并具有以下属性作为函数。
  • item()
  • contains()
  • add()
  • remove()
  • toggle()

  • 这是这种功能的示例实现。
    var TokenList = function (ids) {
        'use strict';
        var idsArray = [],
            self = this,
            parse = function (id, functionName, cb) {
                var search = id.toString();
                if (search.split(' ').length > 1) {
                    throw new Error("Failed to execute '" + functionName + "' on 'TokenList': The token provided ('" + search + "') contains HTML space characters, which are not valid in tokens.');");
                } else {
                    cb(search);
                }
            };
    
        function triggerAttributeChange() {
            if (self.tokenChanged && typeof self.tokenChanged === 'function') {
                self.tokenChanged(idsArray.toString());
            }
        }
    
        if (ids && typeof ids === 'string') {
            idsArray = ids.split(' ');
        }
        self.item = function (index) {
            return idsArray[index];
        };
    
        self.contains = function (id) {
            parse(id, 'contains', function (search) {
                return idsArray.indexOf(search) !== -1;
            });
        };
    
        self.add = function (id) {
            parse(id, 'add', function (search) {
                if (idsArray.indexOf(search) === -1) {
                    idsArray.push(search);
                }
                triggerAttributeChange();
            });
        };
    
        self.remove = function (id) {
            parse(id, 'remove', function (search) {
                idsArray = idsArray.filter(function (item) {
                    return item !== id;
                });
                triggerAttributeChange();
            });
        };
    
        self.toggle = function (id) {
            parse(id, 'toggle', function (search) {
                if (!self.contains(search)) {
                    self.add(search);
                } else {
                    self.remove(search);
                }
            });
        };
    
        self.tokenChanged = null;
    
        self.toString = function () {
            var tokens = '',
                i;
            if (idsArray.length > 0) {
                for (i = 0; i < idsArray.length; i = i + 1) {
                    tokens = tokens + idsArray[i] + ' ';
                }
                tokens = tokens.slice(0, tokens.length - 1);
            }
            return tokens;
        };
    };
    

    使用此函数的新实例在元素中设置“ids”属性,最后必须将目标属性绑定(bind)到该属性,以侦听对该元素的更改并更新该属性,反之亦然。您可以使用变异观察者来做到这一点。

    参见firing event on DOM attribute changehttps://developer.mozilla.org/en-US/docs/Web/API/MutationObserver
    var attachTokenList = function (element, prop, initialValues) {
        'use strict';
        var initValues = initialValues || element.getAttribute(prop),
            MutationObserver = window.MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver,
            observer,
            config,
            cancelMutation = false;
    
        function createTokenList(values) {
            var tList = new TokenList(values);
            tList.tokenChanged = function () {
                element.setAttribute(prop, element[prop].toString());
                cancelMutation = true;
            };
            element[prop] = tList;
        }
    
        createTokenList(initValues);
    
        observer = new MutationObserver(function (mutation) {
            var i,
                mutationrec,
                newAttr;
            if (mutation.length > 0 && !cancelMutation) {
                for (i = 0; i < mutation.length; i = i + 1) {
                    mutationrec = mutation[i];
                    if (mutationrec.attributeName === prop && element[prop]) {
                        newAttr = element.getAttribute(prop);
                        createTokenList(newAttr);
                    }
                }
            }
            cancelMutation = false;
        });
    
        config = {
            attributes: true
        };
        observer.observe(element, config);
    };
    

    测试看看是否有效
    <so-users ids="1234 5678"></so-users>
    <button onclick="clickButton1()">Add 7890</button>
    <button onclick="clickButton2()">Set to 3456</button>
    <button onclick="clickButton3()">Add 9876</button>
    

    在脚本标签内
    var elem = document.querySelector('so-users');
    attachTokenList(elem, 'ids')
    
    function clickButton1 () {
        elem.ids.add('7890');
    }
    
    function clickButton2 () {
        elem.setAttribute('ids', '3456');
    }
    
    function clickButton3 () {
        elem.ids.add('9876');
    }
    

    依次单击按钮,将ids属性设置为“3456 9876”

    10-06 04:22