Proposal for asTokenList(attr)


#1

I posted a piece with a proposal/rationale here: https://briankardell.wordpress.com/2016/03/29/tokenlists-missing-web-dna/

TL;DR: There are several properties that hold space delimited values - the class="" attribute used by CSS is the most well understood/widely encountered… Working with them as simple attributes is painful, so we created .classList which is a DOMTokenList that is reflective on the class attribute. This is great, but the more I run into similar cases because of increased focus on A11Y (several aria attributes are similar) the more I wish that we had access to DOMTokenLists for those… I’m proposing (and providing a prollyfil linked in the article) that we add an accessor method in the DOM API

asTokenList(‘attributeName’)

Which returns you a DOMTokenList that you can work with… Full details in the linked piece.


#2

So, the main use case here is for data-/aria- attributes?

If so, could we define something that’s applicable to these attributes, and doesn’t concern other attributes where a DOMTokenList may not make sense? (e.g. id, src, srcset)


#3

Or maybe I’m missing your point and the intent here is to have a unified API for all DOMTokenLists wherever they’re applicable: classList, relList, sandbox, etc


#4

The more one works with the accessibility tree, the more one realizes that it stands apart from other object models. If I’m understanding correctly, this strives takes the pain out of cross-OM communication. Brilliant. One of the biggest pain points I see today. (See also forthcoming work on CSS Generated Content Module)


#5

Yes, to have a unified API that you can use for any DOMTokenList-able attributes (including all of the above, ones in the future and custom elements, etc).


#6

Would love something like that as a standard (+polyfill). Using jQuery we end up abusing the class attribute because .removeClass, .addClass and .hasClass and .toggleClass are just easier to work with. Element.classList reproduces this same problem.

This borders on bikeshedding, but doesn’t element.asTokenList suggests that it would return the element itself as a token list (which doesn’t make much sense). Perhaps this would be better:

  • element.tokenList(attrName)
  • element.tokenListFor(attrName)

On a different topic, is there an issue tracker for your prollyfill? It has a few issues.

var el = document.createElement('p');
el.setAttribute('class', ' a   b      c ');
var no_classList = el._asTokenList('class');

console.log( el.classList.length ); // 3
console.log( no_classList.length ); // 12

console.log( '' + el.classList ); // " a   b      c "
console.log( '' + no_classList ); // "[object Object]"

Array.prototype.join.call(el.classList) // "a,b,c"
Array.prototype.join.call(no_classList) // ",,,,,,,,,,,"

el.classList[ el.classList.length - 1 ] // "c"
no_classList[ no_classList.length - 1 ] // undefined

Perhaps you could create a _DummyDOMTokenListPrototype object and use Object.create(_DummyDOMTokenListPrototype) and perhaps protect the length property too if possible.


#7

re: an issue tracker: I created https://github.com/bkardell/tokenListFor - I used _tokenListFor(attr) as suggested, that’s better, good feedback.

I’ve resolved all but the last one of your examples - not sure that is resolvable?


#8

By the way, a standalone function (method of Document?) like parseTokenList() or createTokenList() would probably be more useful since we then could parse any token string, not just a value of an attribute of an existing element.


#9

It may also be useful, but I would argue that not more so because the token list isn’t generated and disconnected, it remains connected…

// gets the token list
var tokenList = document.body.classList;

// uses setAttribute after the token list is created...
document.body.setAttribute("class", document.body.className + " bar");

// the token list does contain bar
console.log("contains bar? %s", tokenList.contains("bar"))

So, yeah, it might be interesting to have that, but that’s pretty trivial to implement with just one or two lines if you have this because all you would have to do is create an element and ask for an attribute as a token list.


#10

Good point. By haven’t live collections proven they are actually almost useless in practice?

To deal with items (e. g. looping), live collections are almost always converted to static Array, while there is nothing preventing us from having a static token list that would still be able to add()/remove()/etc. its tokens.


#11

In this case, I’m not so sure - they seem to work pretty well and prevent a lot of footguns for dealing with a single serialization of N values, like the one I illustrated.

There is nothing preventing us from creating a new fundamental primitive like this, but that isn’t what I’m currently advocating here and I think Array.from(…) is already basically this?


#12

Live collections of elements are problematic for a number of reasons. This is not that.

This is just elevating the X/XList pattern to something generic. The XList object is just a useful interface over the string, letting you query/manipulate it in a more convenient way.


#13

Arrays don’t have methods like add() / remove() to manipulate tokens. The token list is not just about splitting a string to parts, isn’t it? :wink:

Array.from() was exactly a consequence of the need to convert DOM collections to Array everyday.

FWIW, I’m not sure we would necessarily have to introduce a new object class for static token list. DOMTokenList could just be either live or static depending on how it has been created.


#14

Live collections of elements are problematic for a number of reasons. This is not that.

Could you provide some usecases that would really benefit from token list being live?

The XList object is just a useful interface over the string, letting you query/manipulate it in a more convenient way.

Yes, I agree with that.


#15

Are you trying to argue that, for example, el.classList.add('foo') would have been better as el.class = el.classList.add('foo'); (or el.class = TokenList(el.class).add('foo'))?


#16

Absolutely no. It would just be nice to have a generic way (as long as we are going to make token lists more generic anyway) to convert a string to token list regardless whether the string is an element-attribute value or just a standalone literal (without creating a dummy element). Both ways could coexist.

For example:

var a = element.classList; // Predefined token list based on class attribute.
var b = element.tokenListFor('data-foo'); // Token list based on arbitrary attribute.
var c = document.createTokenList('foo bar'); // Also token list, but based on string.
var d = 'foo bar'.toTokenList(); // Possible alternate syntax.
var e = DOMTokenList.from('foo bar'); // Possible Array.from()-like syntax. Eureka!

#17

We already have a way to create unconnected token lists from any string:

var tl = new Set(str.split(/\s+/));

#18

Good point. But we are then forced to deal explicitly with regular expressions as well as we would also need to use trim() before splitting:

var f = new Set(' foo bar '.trim().split(/\s+/));

And this looks far less clean and clear than a straightforward function/method with clear purpose like DOMTokenList.from(). Similar to how Array.from() is better than [].slice.call() used before.

// Thoughts aloud: it’s a shame we have so similar things with incompatible syntax (e. g. remove() and contains() for DOMTokenList versus delete() and has() for Set).


#19

Can I ask though: why would you want to create a disconnected token list? at that point, wouldn’t you simply use an array/set? Like - what is the use case for a DOMTokenList that isn’t part of DOM?


#20

I suspect disconnected token lists could be useful e. g. for some virtual-DOM implementations like in React.js. Didn’t work with this library myself though.