CSSValue replacement proposal from Ian Hickson

Below is a proposal Ian Hickson made to replace CSSValue and co somewhere  
at the end of 2002. This email makes the proposal public. For reference  
the original Member-only proposal can be found here:

   http://lists.w3.org/Archives/Member/w3c-css-wg/2002OctDec/0264.html

===

I discussed possiblities for a replacement for CSSValues with some of
the web authors who have been the most vocal to me about the need for
a non-string-based API for complex manipulation of the CSSOM.

First, here are some examples showing what the API currently requires
to do simple tasks. [1]

1. Increasing the 'top' property of a style declaration by one pixel.

    Using the CSSValues API:

       var top = style.getPropertyCSSValue('top');
       top.setFloatValue(top.CSS_PX, top.getFloatValue(top.CSS_PX) + 1);


    Using the string-based API (assumes that the property is already
    set in pixels):

       style.top = parseInt(style.top) + 1 + 'px';


    Using the API proposed below:

       style.top.px++;


2. Getting the URL of the background image of an element.

    Using the CSSValues API:

       var style = document.defaultView.getComputedStyle(element, '');
       var url =  
style.getPropertyCSSValue('background-image').getStringValue();


    Using the string-based API:

       var style = document.defaultView.getComputedStyle(element, '');
       var url = style.backgroundImage;
       url.replace(/^url\(['"]?/, '');
       url.replace(/['"]?\)$/, '');
       url.replace(/\\(.)/, '$1');


    Using the API proposed below:

       var url = element.computedStyle.backgroundImage.url;


3. Increasing the red component of color by 1 part in 255 (typically
    this would really be changing two or three values, or the hue, this
    is just a simple example).

    Using the CSSValues API:

       var red = style.getPropertyCSSValue('color').getRGBColorValue.red;
       red.setFloatValue(red.CSS_NUMBER, red.getFloatValue(red.CSS_NUMBER)  
+ 1);


    Using the string-based API:

       var color = style.color;
       var red, green, blue;
       if (color.match(/^rgb\(([0-9]+), ?([0-9]+), ?([0-9]+)\)$/)) {
         red = RegExp.$1;
         green = RegExp.$2;
         blue = RegExp.$3;
       } else if (color.match(/^rgb\(([0-9.]+)%, ?([0-9.]+)%,  
?([0-9.]+)%\)$/)) {
         red = 255 * RegExp.$1 / 100;
         green = 255 * RegExp.$2 / 100;
         blue = 255 * RegExp.$3 / 100;
       } else {
        // XXX incomplete
        throw "#rrggbb, #rgb, and named constants not supported (received  
'" + color + "')";
      }
      red++;
      style.color = 'rgb(' + red + ',' + green + ',' + blue + ')';


    Using the API proposed below:

       style.color.red++;


I maintain that there is definitely a need for DOM-based manipulation
of a CSSOM, because SMIL, while great for simple or even moderately
complex animations, is not able to do things such as a Pacman game.
The existence of sites such as http://www.javascript-games.org/ is
pretty clear evidence that there is a demand for programmatic access
to the DOM and style-related issues.

The complexity of the examples given above speaks for itself. The
existing interface is just not suitable.

What follows is a straw-man proposal based on feedback I have received
 from Mozilla's CSSOM implementors and dynamic content authors.


Requirements Summary:

    Being able to manipulate style data through a scriptable object
    model without an intermediate string representation.

    Being able to do so without going through pointless theoretical
    methods and attributes.


Examples using the proposal below:

    // increase font-size by 1px
    element.style.fontSize.px++;

    // set width to x%, where x is a variable
    rule.width.percent = x;

    // desaturate the colour of an element
    for (property in ['color', 'background-color', 'border-top-color' /*  
... */]) {
      document.getOverrideStyle(element,  
null).getProperty(property).saturation = 0;
    }

    // set content to 'url(example) counter(headers) "."'
    style.content.clear();
    style.content.append(CSSItemURL.create('example'));
    style.content.append(CSSItemCounter.create('headers', 1));
    style.content.append(CSSItemString.create('.'));

    // get the background-image URL (possibly relative)
    var url = style.backgroundImage.url;

    // get the background-image URL (resolved to an absolute URL)
    var url = style.backgroundImage.absoluteUrl;


Interfaces for this Straw Man Proposal:

    interface CSSStyleDeclaration {
      CSSPropertyValue getProperty(in DOMString propertyName);
      // (plus everything in DOM2 except getPropertyCSSValue)
    }

    // Available using binding-specific casting mechanisms on  
CSSStyleDeclaration
    interface CSSStyleDeclarationProperties {
      attribute CSSBackgroundAttachmentPropertyValue backgroundAttachment;
      attribute CSSColorPropertyValue backgroundColor;
      attribute CSSURLPropertyValue backgroundImage;
      attribute CSSBackgroundPositionPropertyValue backgroundPosition;
      // ...
      attribute CSSContentPropertyValue content;
      // etc...
      // Each longhand property has an attribute on this interface.
      // Initially we might want to skip properties which only take
      // keywords, as string manipulation is not a problem with those.
    }

    interface CSSPropertyValue {
      // Note. On bindings with string coercion (such as
      // ECMAScript) the string representation of this value must
      // be the same as its cssText attribute. This allows the
      // CSSStyleDeclarationProperties and CSS2Properties
      // interfaces to be used interchangeably without casting in
      // some environments.
      attribute DOMString cssText;

      const unsigned short CSS_UNSET   = 0;
      const unsigned short CSS_INHERIT = 1; // CSS2
      const unsigned short CSS_INITIAL = 2; // CSS3
      const unsigned short CSS_DEFAULT = 3; // CSS3
      const unsigned short CSS_ATTR    = 4; // CSS3
      const unsigned short CSS_VALUE   = 16;
      attribute unsigned short value; // takes one of the CSS_* constants

      // This will be null unless value == CSS_ATTR
      // setting it sets value to CSS_ATTR
      attribute AttrExpression attr;
    }

    // initially we might want to avoid mentioning attr altogether
    interface AttrExpression {
      const unsigned short CSS_STRING = 0;
      attribute DOMString attribute;
      attribute unsigned short type;
      attribute CSSPropertyValue default;
      AttrExpression create(in DOMString attribute,
                            in unsigned short type,
                            in CSSPropertyValue default);
    }

    // initially we might want to skip out on the keyword-only properties
    // since string manipulation for those is no big deal
    interface CSSBackgroundAttachmentPropertyValue {
      const unsigned short CSS_SCROLL = CSS_VALUE + 0;
      const unsigned short CSS_FIXED  = CSS_VALUE + 1;
    }

    interface CSSColorPropertyValue : CSSPropertyValue {
      const unsigned short CSS_COLOR         = CSS_VALUE + 0;
      const unsigned short CSS_ACTIVEBORDER  = CSS_VALUE + 1;
      const unsigned short CSS_ACTIVECAPTION = CSS_VALUE + 2; // etc..

      // These will be 0 unless value >= CSS_COLOR
      attribute short          red;
      attribute float          redPercent; // 100% = 255
      attribute short          green;
      attribute float          greenPercent; // 100% = 255
      attribute short          blue;
      attribute float          bluePercent; // 100% = 255
      attribute float          alpha; // float in range 0.0..1.0
      attribute short          hue; // integer in range 0..360
      attribute float          saturation; // percentage
      attribute float          lightness; // percentage
      attribute unsigned long  hex3;
      attribute unsigned long  hex6;

      // If the given values match a named colour, then namedColor will
      // have the appropriate value from the following list. If not, it
      // will have the value UNNAMED.
      const unsigned short UNNAMED = 0;
      const unsigned short BLACK   = 1;
      const unsigned short SILVER  = 2; // etc... (all CSS3 named colours)
      attribute unsigned short namedColor;
    }

    // useful, because the string representation would have to be CSS  
escaped
    interface CSSURLPropertyValue : CSSPropertyValue {
      // The following is only defined if value == CSS_VALUE
      attribute DOMString url;
      attribute DOMString absoluteUrl;
    }

    // this shows how multi-values properties would be done
    interface CSSBackgroundPositionPropertyValue : CSSPropertyValue {
      // the value of x and y are the same as this interface's
      // if this interface's value is less than CSS_VALUE, else
      // they are greater than or equal to CSS_VALUE.
      attribute DOMLengthAndPercentagePropertyValue x;
      attribute DOMLengthAndPercentagePropertyValue y;
    }

    interface CSSLengthPropertyValue : CSSPropertyValue {
      const unsigned short LENGTH_EM = CSS_VALUE + 100;
      const unsigned short LENGTH_EX = CSS_VALUE + 101;
      const unsigned short LENGTH_PX = CSS_VALUE + 102;
      const unsigned short LENGTH_IN = CSS_VALUE + 103;
      const unsigned short LENGTH_CM = CSS_VALUE + 104;
      const unsigned short LENGTH_MM = CSS_VALUE + 105;
      const unsigned short LENGTH_PT = CSS_VALUE + 106;
      const unsigned short LENGTH_PC = CSS_VALUE + 107;
      attribute float length; // length in units specified by value
      // the following set the units on setting, and
      // perform conversions on getting. If conversion is not
      // possible (e.g. relative to absolute without a view)
      // then an exception is thrown.
      attribute float em;
      attribute float ex;
      attribute float px;
      attribute float cm;
      attribute float mm;
      attribute float pt;
      attribute float pc;
    }

    interface CSSLengthAndPercentagePropertyValue : CSSLengthPropertyValue {
      const unsigned short LENGTH_PERCENT = CSS_VALUE + 200;
      attribute float percent;
    }

    interface CSSPercentagePropertyValue : CSSLengthPropertyValue {
      const unsigned short LENGTH_PERCENT = CSS_VALUE + 200;
      attribute float length;
      attribute float percent;
    }


    // The following is for 'content', probably the most complicated
    // CSS2 property.

    interface CSSContentPropertyValue : CSSPropertyValue {
      const unsigned short CSS_NONE = CSS_VALUE + 1;
      // value == CSS_VALUE iff length > 0
      readonly attribute unsigned long length;
      CSSItem item(in unsigned long index);
      // if index == length, acts like appendItem()
      // if index > length, throws an exception
      void setItem(in unsigned long index, in CSSItem value);
      // if index == length, acts like appendItem()
      // if index > length, throws an exception
      void insertItem(in unsigned long index, in CSSItem value);
      // if value != CSS_VALUE, appendItem sets it to CSS_VALUE
      void appendItem(in CSSItem value);
      // if deletion would result in a zero length, set value to CSS_NONE
      void removeItem(in CSSItem value);
      // if deletion would result in a zero length, set value to CSS_NONE
      void deleteItem(in unsigned long index);
      // raises an exception if item is not there
      unsigned long indexOf(in CSSItem value);
      // sets value to CSS_NONE
      void clear();
    }

    interface CSSItem {
      attribute DOMString cssText;
    }

    interface CSSItemKeyword : CSSItem {
      const unsigned short CSS_NONE = 0;
      const unsigned short CSS_NORMAL = 1;
      const unsigned short CSS_OPEN_QUOTE = 100;
      const unsigned short CSS_CLOSE_QUOTE = 101;
      const unsigned short CSS_NO_OPEN_QUOTE = 102;
      const unsigned short CSS_NO_CLOSE_QUOTE = 103;
      attribute unsigned short value;
      CSSItemKeyword create(in unsigned short value);
    }

    interface CSSItemAttrExpression : CSSItem {
      // this could do with just inheriting from AttrExpression instead
      attribute attrExpression attr;
      CSSItemAttrExpression create(in unsigned short value);
    }

    interface CSSItemString : CSSItem {
      attribute DOMString value;
      CSSItemString create(in DOMString value);
    }

    interface CSSItemURL : CSSItem {
      attribute DOMString url;
      attribute DOMString absoluteUrl;
      CSSItemURL create(in DOMString url);
    }

    interface CSSItemCounter : CSSItem {
      attribute DOMString name;
      attribute long increment;
      CSSItemCounter create(in DOMString name, in long increment);
    }

    interface CSSItemCounters : CSSItem {
      attribute DOMString name;
      attribute long increment;
      CSSItemCounters create(in DOMString name, in long increment);
    }


-- Footnotes --
[1] See http://www.damowmow.com/playground/demos/animation/

===


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

Received on Friday, 6 November 2009 00:31:16 UTC