Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
187 views
in Technique[技术] by (71.8m points)

javascript - Constructing a DOMTokenList/DOMSettableTokenList instance

The DOMTokenList and DOMSettableTokenList interfaces (MDN, WHATWG) provide methods for manipulating ordered sets of string tokens represented by space-delimited strings. They are most commonly used in the form of the Element.prototype.classList property, a DOMTokenList which reflects the class attribute of an associated element.

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');
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

You cannot create an DOMTokenList or an DOMSettableTokenList directly. Instead you should use the class attribute to store and retrieve your data and perhaps map an ids attribute of your DOM element to the classList property.

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

You can use relList according to the documentation but classList is more supported, the only drawback is that you might run into issues if one of your ids matches a class name so set an inline style to hide the element just in case.

For a custom component compatibility should be a concern (classList is present in IE>=10, Firefox 3.6, Chrome 8, Opera 11.5 and Safari 5.1, see http://caniuse.com/#feat=classlist) so if compatibility is in your requirements use the another solution posted below.

If you cannot use clases or classList and/or must use the ids attribute you should implement a custom function according to the spec with the following properties as functions.

  • item()
  • contains()
  • add()
  • remove()
  • toggle()

This is an example implementation of such functionality.

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;
    };
};

Set an 'ids' property in your element with a new instance of this function and finally you must bound the targeted attribute to the property listening to changes to the element and updating the property o viceversa. You can do that with a mutation observer.

See firing event on DOM attribute change and https://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);
};

Testing to see if it works

<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>

Inside a script tag

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');
}

Clicking the buttons in sequence set the ids attribute to '3456 9876'


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...