Re: [cssom] CSS Value API

On Thu, 25 Mar 2010 16:10:58 +0100, Anne van Kesteren <annevk@opera.com>  
wrote:
> [...]
>
> Thoughts on this are very much welcome. Some brainstorming during the  
> F2F might also be a good use of time if we limit the amount of time :-)

This is a fairly long email describing a value API proposal for CSS. None  
of this made it into CSSOM yet. Hopefully people can make the time to  
review this and point out mistakes or ideas for improvement. Feel free to  
ask any questions. I'll do my best to reply timely.

TERMINOLOGY

The terminology is abbreviated here to make the proposal more easily to  
skim through.

value -- the value of a property

   margin: 20px 20px
   the value is 20px 20px

component value -- one of the components of a value

   margin: 20px 20px
   the component values are 20px and 20px
   color: <color>
   the component value is <color>

map value -- component values are independent but grouped in a single value

   border:2px solid red
   the value of this property is a map value

list value -- component values are comma-separated

   cursor:url(big), pointer;
   the value of this property is a list value

space-list value -- component values are space-separated, possibly with a  
maximum length

   counter-increment: foo 1 bar 2
   the value of this property is a space-list value of map values
   (or a single ident)

   margin:20px 20px
   the value of this property is a space-list value of map values
   (but limited in length)


INTERFACES

All values implement a single base interface, CSSValue. This interface has  
all the various value types exposed as string constants and the value it  
currently exposes can be read by getting its type attribute. It uses  
string constants to make extensibility easier and uses constants to make  
optimizations easier. It also has a cssText attribute for parsing from  
string and serializing purposes.

Map values implement an interface (CSSMapValue) with a single attribute,  
named m for now, to access a map object (CSSMap). The map object has a  
named getter and a way to iterate over the items in a map. (I.e. there's  
length, getter key(int), and getter item(string).) The map object is not  
directly implemented on the value object to prevent potential future  
clashes between new members of the value object and existing keys on the  
map object. (An alternative design for CSSMap might be to just expose  
string[] keys and getter item(string) as to not reveal ordering and allow  
more optimizations.)

List values and space-list values behave similarly to map values  
(CSSListValue, CSSList), except their attribute is named l, for now.  
Interface-wise they are identical but to distinguish them the type  
attribute will return a different value. List and space-list values can  
also be mutated. I.e. new items can be added and existing items can be  
removed.

Components have specific interfaces depending on the type of component. A  
value implements all the various component interfaces it supports. E.g.  
'width' takes both <length> and <percentage> so value objects for it will  
support both the length and percentage component interfaces.


VALUE ATTRIBUTE

I also want to give CSSValue a value attribute. This attribute is of type  
any. I.e. it can return anything and be set to anything. What you can set  
it to and what you get out of it depends on the type attribute of  
CSSValue. In some cases this will be the same as mutating a specific  
member, such as px on <length>, but for some types this makes sense as the  
only accessor, e.g. <string> and <ident>.


ADDING LIST ITEMS

Currently everything with appending in the CSSOM is done from a string.  
I'm inclined to follow that model for now. We could construct a way to  
create all these various kind of objects, but that would be tricky and not  
very convenient for authors. We could also have a method that appends an  
object of the right type at a certain position and then let authors modify  
it after it is appended. I'm open to suggestions.


EXAMPLES

'color': Returns an object that implements CSSValue and the interface for  
<color>.

   ele.style.color.red++

'margin': Returns an object that implements CSSValue and CSSListValue. Its  
associated CSSList has up to four items, each of which implements CSSValue  
and the interfaces for <length>, <percentage>, and <ident>.

   ele.style.margin.l[0].px++ // increases the px value of the first item

'border-top': Returns an object that implements CSSValue and CSSMapValue.  
Its associated CSSMap has three items, named "width", "style", and  
"color". Each of the items implement CSSValue and the appropriate  
component value interface.

   ele.style.borderTop.m.style.value = "double"

'border-image': Returns an object that implements CSSValue and  
CSSMapValue. Its associated CSSMap has five items, named "source",  
"slice", "width", "outset", and "repeat". The "source" item implements  
CSSValue, <image>, and <ident>. "slice" implements CSSValue and  
CSSMapValue. Its associated CSSMap consists of two items, one a  
CSSListValue and one an <ident>... 'border-image' is definitely doable  
with generics (just follow the patterns outlined so far), but for complex  
properties like these we might actually want to simplify matters and offer  
a more high-level API. E.g. for "slice" a CSSValue/CSSListValue that also  
has the ability to easily set the 'fill' keyword rather than having to use  
a CSSMapValue/CSSMap around them.


EXTENSIBILITY

Or, what we should do when property values change. Property values, for  
better or worse, change. E.g. 'background-image' used to be a simple  
keyword or a <uri>, now it is a comma-separated list of keywords or <uri>  
(let alone <uri> -> <image>). If we imagine that this value API was  
introduced before this property value change the 'background-image' value  
would be represented by an object that implements CSSValue, <uri>, and  
<ident>. (Likely <ident> is nothing apart from a constant on CSSValue.)  
Now the change is introduced so we need to modify the API. Basically the  
idea is that besides CSSValue, <uri>, and <ident>, it also implements  
CSSListValue. To not break usage of the old API modifying <uri>/<ident>  
would modify the first item in the list. (Alternatively they could empty  
the list on setting and return nothing if more than one item is in the  
list, I suppose.)


CHANGES SINCE INITIAL DISCUSSION

Initially we discussed 'margin' and properties like it to be represented  
by a map. However, given the way one or more component values work for  
'margin' it behaves very much like a list with a maximum length. It seems  
better therefore to use the list interface for it.


-- 
Anne van Kesteren
http://annevankesteren.nl/

Received on Tuesday, 6 April 2010 10:29:19 UTC