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/