Bug 13999 - [Awaiting ES6] DOMTokenList: allow to add/remove multiple tokens at once
Summary: [Awaiting ES6] DOMTokenList: allow to add/remove multiple tokens at once
Status: RESOLVED FIXED
Alias: None
Product: WebAppsWG
Classification: Unclassified
Component: DOM (show other bugs)
Version: unspecified
Hardware: All All
: P2 normal
Target Milestone: ---
Assignee: Anne
QA Contact: public-webapps-bugzilla
URL:
Whiteboard:
Keywords:
Depends on:
Blocks:
 
Reported: 2011-09-01 17:50 UTC by Marat Tanalin | tanalin.com
Modified: 2013-01-05 10:14 UTC (History)
16 users (show)

See Also:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Marat Tanalin | tanalin.com 2011-09-01 17:50:43 UTC
http://www.w3.org/TR/html5/common-dom-interfaces.html#domtokenlist-0

DOMTokenList should be able to add/remove multiple tokens at once. In particular, element.classList should be able to add or remove more than one class at once.

For example, following should be possible:

element.classList.add("lorem ipsum dolor");
element.classList.remove("lorem ipsum dolor");

Additionally, it may make sense to allow to use Array instead of string as argument:

element.classList.add(["lorem", "ipsum", "dolor"]);
element.classList.remove(["lorem", "ipsum", "dolor"]);

Currently, DOMTokenList is almost useless since element.classList is _slower_ than pure-script implementation when adding/removing multiple classes at once.

For example, jQuery _intentionally drops_ using element.classList exactly for this reason:
http://bugs.jquery.com/ticket/5087

Adding multiple classes is widely used, for example, in jQuery UI.

Thanks.
Comment 1 Tab Atkins Jr. 2011-09-01 20:45:54 UTC
What do other libraries do with this?  jQuery only allows a single class to be added/removed at a time.  Of course, it allows you to chain addClass() calls, while you can't chain classList.add() calls.  Would it make more sense to change things so that DOMTokenList returns itself from add/remove/toggle?

The performance of classList is an implementation detail.  There's no particular reason for it to be slower, except that jQuery doesn't actually implement the spec (there are a bunch of requirements that you need to be careful about).
Comment 2 Marat Tanalin | tanalin.com 2011-09-01 21:06:15 UTC
(In reply to comment #1)
> jQuery only allows a single class to be added/removed at a time.

You are wrong. See
http://api.jquery.com/addClass/#example-1
http://api.jquery.com/removeClass/#example-1
Comment 3 Marat Tanalin | tanalin.com 2011-09-01 21:11:37 UTC
(In reply to comment #1)

> The performance of classList is an implementation detail.  There's no
> particular reason for it to be slower.

One of major reasons is that consequent adding classes one by one causes multiple rendering-reflows. (Each reflow is quite time-expensive operation.)

Opposite to this, adding multiple classes at once causes only one reflow.
Comment 4 Tab Atkins Jr. 2011-09-01 21:26:40 UTC
Whoops, you're right.  jQuery *does* allow multiple classes.  I was misled by a quick skim.

(In reply to comment #3)
> (In reply to comment #1)
> 
> > The performance of classList is an implementation detail.  There's no
> > particular reason for it to be slower.
> 
> One of major reasons is that consequent adding classes one by one causes
> multiple rendering-reflows. (Each reflow is quite time-expensive operation.)
> 
> Opposite to this, adding multiple classes at once causes only one reflow.

I agree with the reasoning; I was just looking for precedent for good ways to do it.
Comment 5 Simon Pieters 2011-09-02 06:54:55 UTC
(In reply to comment #3)
> One of major reasons is that consequent adding classes one by one causes
> multiple rendering-reflows. (Each reflow is quite time-expensive operation.)

I don't think this is true. Reflows are generally batched up and happen only once when the script is finished doing what it's doing (except in Opera if the script is taking a long time), unless you force a reflow by asking for layout with .offsetLeft or so between adding your classes.
Comment 6 Marat Tanalin | tanalin.com 2011-09-02 19:40:52 UTC
(In reply to comment #5)

Multiple consequent calls to a function are always slower than one call to the same function. Even if a specific browser does not do reflows after each function-call.
Comment 7 Ian 'Hixie' Hickson 2011-09-27 19:48:14 UTC
The question is whether it is slower by any sort of relevant measure. One CPU cycle slower, for example, really seems irrelevant for a function such as this. But if each call takes 10 seconds, that's a big deal.

The reflow thing is definitely not an issue. Nothing happens of that nature until the scripts have ended.

Anyway, DOMTokenList is no longer my problem. ->DOM Core.
Comment 8 Anne 2012-03-03 10:05:14 UTC
FYI: My tentative plan is to resolve this by doing the simplest thing possible. Extend add() and remove() to take a space-separated string.
Comment 9 Marat Tanalin | tanalin.com 2012-03-03 12:44:26 UTC
(In reply to comment #8)
> FYI: My tentative plan is to resolve this by doing the simplest thing possible.
> Extend add() and remove() to take a space-separated string.

Thanks, that's nice. And how about also supporting Array as argument? Ability to pass multiple items as Array (native data-type to store multiple items) looks very natural and intuitive.
Comment 10 miket 2012-03-05 03:50:29 UTC
(In reply to comment #9)

> Thanks, that's nice. And how about also supporting Array as argument? Ability
> to pass multiple items as Array (native data-type to store multiple items)
> looks very natural and intuitive.

el.classList(arr.join(' ')) would give you that ability as well. But maybe since the difference is so minimal it's worth adding it? I'm not sure to what degree an array is as or more intuitive than a space separated string, but something like Array.isArray(classes) ? classes.join(" ") : classes would allow both.
Comment 11 Rick Waldron 2012-03-05 04:02:48 UTC
Another possible approach would be to follow the precedent set by Array.prototype.push: make classList.add() a variable arity function - after all, it's essentially "push"ing onto the token list.

el.classList.add( token[, token... ] );

This also allows a user to do fun stuff like...

var classes = [ "foo", "bar", "baz" ];

// ... some program stuff that adds or substracts tokens from `classes`...

el.classList.add.apply( null, classes );
Comment 12 Erik Arvidsson 2012-03-05 17:52:23 UTC
(In reply to comment #3)
> (In reply to comment #1)
> 
> > The performance of classList is an implementation detail.  There's no
> > particular reason for it to be slower.

There are reasons that makes classList slower. None of the JS frameworks validate the input. Validation of the input is required by the spec for DOMTokenList.

> One of major reasons is that consequent adding classes one by one causes
> multiple rendering-reflows. (Each reflow is quite time-expensive operation.)

This is not true. However, it does serialize the DOMTokenList to a string. That string is then used in setAttribute('class', string) since there might be mutation listeners etc.
Comment 13 Erik Arvidsson 2012-03-05 18:11:23 UTC
(In reply to comment #11)
> Another possible approach would be to follow the precedent set by
> Array.prototype.push: make classList.add() a variable arity function - after
> all, it's essentially "push"ing onto the token list.
> 
> el.classList.add( token[, token... ] );
> 
> This also allows a user to do fun stuff like...
> 
> var classes = [ "foo", "bar", "baz" ];
> 
> // ... some program stuff that adds or substracts tokens from `classes`...
> 
> el.classList.add.apply( null, classes );

And with ES6 this will be even easier.

el.classList.add(...classes)

I'm opposed to space separated string since it is not well structured. Having structure hidden in the string is less clear than taking multiple arguments.

Also, I can easily flip the join argument to use split

el.classList.add(...someString.split(/\s+/))

another reason why add should not take a space separated string is that className already supports this:

el.className += ' ' + spaceSeparatedString;
Comment 14 Erik Arvidsson 2012-08-10 16:55:53 UTC
Rest params and spread are in the ES6 draft and are available in Firefox.

I want to start implementing this as

interface DOMTokenList {

  void add(in DOMString... tokens);
  void remove(in DOMString... tokens);

}

Any objections?
Comment 15 Rick Waldron 2012-08-10 21:15:36 UTC
+1
Comment 16 Anne 2012-08-27 06:28:14 UTC
There are no objections. We are just waiting for ES6 to be stable enough. It is now?