Older browsers, such as Netscape's Navigator 4 series, had relatively poor support for modifying Cascading Stylesheets from JavaScript -- instead requiring that you use either document.write() calls to rewrite the contents of a particular element or their equally burdensome (and Navigator-only) Javascript Stylesheets mechanism.
With the release of Microsoft Internet Explorer 5 and Netscape 6.0 for the Macintosh, however, we now have the ability to change styles on the fly from JavaScript, using the W3C DOM. Unfortunately, due to a distinction between the way that embedded and remote stylesheet properties are exposed as opposed to the way that inline STYLE properties are exposed, this can be tricky. In effect, developers are required to define all styles inline or in embedded stylesheets (whether internal or external) if they want to be able to read and modify them reliably. Despite the fact that stylesheets are far easier to work with, may be reused across pages, and may be designed by someone with no JavaScript knowledge, browser vendors recommend that dynamic changes be made via the JavaScript .style property directly.
For example, given the following embedded stylesheet declaration:
<style type="text/css">
H1 {
font-family: Arial;
font-size: 12pt;
}
</style>
You can read the properties from JavaScript as follows (note that the CSS properties themselves are camelCased rather than hyphenated):
<script type="text/javascript">
var myStyle = document.styleSheets[0].cssRules[0].style;
var fFamily = myStyle.fontFamily;
var fSize = myStyle.fontSize;
alert("H1 stylesheet:\nfamily: "
+ fFamily + "\nsize: " + fSize);
</script>
However, given the following inline style attribute on a particular H1, you need to use a different interface to read the properties:
<h1 style="font-family: Palatino;
font-size: 14pt;">example</h1>
<script type="text/javascript">
var myH1Array = document.getElementsByTagName("H1");
var myElem = myH1Array.item(0);
var myStyle = myElem.style;
var fFamily = myStyle.fontFamily;
var fSize = myStyle.fontSize;
alert("H1 Inline:\nfamily: "
+ fFamily + "\nsize: " + fSize);
</script>
If you use the former method to read the style properties for H1, but have declared them inline as in the latter example, you get the wrong valuesthose defined in the embedded stylesheet, not those defined in the inline style attribute. Obviously, the problem can be resolved for named elements by reading the style properties for the element in question:
<h1 id="example"
style="font-family: Palatino;
font-size: 18pt;">example</h1>
<script type="text/javascript">
var myElem = document.getElementById("example");
var myStyle = myElem.style;
var fFamily = myStyle.fontFamily;
var fSize = myStyle.fontSize;
alert("H1 with ID `example':\nfamily: "
+ fFamily + "\nsize: " + fSize);
</script>
If the element in question doesn't use inline style attributes, you get empty or null values, rather than the embedded stylesheet properties you're after.
In essence, although the browser knows how to properly override global stylesheet declarations when rendering the element, there is no single built-in cross-browser way to access the style being used for that rendering. Internet Explorer has (in the 5.x generation) introduced the currentStyle and runtimeStyle collections, but they're not standard or supported in Navigator, and runtimeStyle doesn't seem to be supported in Internet Explorer 5.0 for the Macintosh.
If you want to change an element's style, a similar problem exists. If you only modify the global stylesheet for an element type for which inline styles are defined, only those elements without inline styles are changed. This may be viewed as a feature or a weakness, depending on your point of view, your particular application, and the lateness of the hour or proximity of the deadline.
There is a standard way to read the current style properties for any element, the getComputedStyle() method, but it is not yet supported on the Macintosh platform. Internet Explorer does support the currentStyle collection, which is a proprietary implementation of the method, but Navigator 6 does not yet appear to support either method reliably, if at all.
Fortunately, there is a way to read and change an element's styles regardless of where they were originally defined. As you might expect, it entails checking all the available stylesheet properties in both embedded styles and inline STYLE attributes. I've provided a script that will take some of the pain out of doing so.
|
Related Reading
|
As demonstrated above, with the increased power of the newer browsers come some compromises in ease of use for the developer. More importantly, as more and more applications are being jointly produced by developers and designers working in tandem (and sometimes without perfect communication between those implementing behavior and those implementing the look and feel) it's important to preserve flexibility in the approaches used to apply and modify style. This script helps the developer do just that.
Why would you want to modify styles on the fly? As Web applications become more and more powerful, users are demanding more flexibility and control over their personal preferences, just as they are used to having in traditional desktop applications. For instance, some users may like reading body copy in serif fonts, while others may enjoy sans-serif. Or source code listings, like those in this article, might be too small to read, so you'd want the user to be able to modify them at will. Of course, there are literally thousands of different applications for these capabilities -- I'm just pointing out some that are convenient in the context of this article.
|
Coding the script was surprisingly difficult, given the hoopla surrounding the supposed excellent standards support in the latest browsers. Everything I tried at first had to be abandoned or recoded for one reason or another, due to a lack of support for a particular approach or property in one browser or another. Here is a short list of many of the problems were encountered.
selectorText not supported in NS6/Mozilla
Ultimately, one of the methods our script provides (setStyleByTag()) was extremely difficult to implement in Netscape Navigator 6.0 or Mozilla, due to a lack of support for the selectorText property of the cssRules array. This lack of support is documented here.
We hope that the Mozilla project will remedy this quickly, as disabling selectorText has the unfortunate side effect of also excluding its value from cssRules objects altogether. It is possible to read the entire rule and scan for the selector. However Internet Explorer 5.0 gives you the full cssText value (albeit with case-folded properties):
|
Related Reading
|
SPAN { FONT-SIZE: 12pt; }
Navigator only gives the internal rule text, not the selector:
font-size: 12pt
Your only option is to grab the raw data from inside the STYLE tags and parse out your own alternate list of ruleset selectors, which is a pretty egregious kludge. This makes it difficult to process rulesets by selector, and since there is no standard capability for accessing rulesets by name (e.g., styleSheets[0].cssRules["H1"]) this missing feature of the DOM makes Netscape6/Mozilla far less powerful. I've included a workaround, based on code found in the Mozilla source, which creates an alternate list of selectors for comparison's sake, and which is only called in Netscape6/Mozilla.
hasAttributes() not supported by IE5/Mac
An early version of the script used the hasAttributes() method of the Level 2 Core W3C DOM, to check for STYLE attributes in the elements we wished to modify, but it is not supported in Internet Explorer 5.0/Macintosh. An alternative was to use the attributes array.
childNodes.length not supported by IE5/Mac
Another approach I tried involved the use of the childNodes property of the Level 1 Core W3C DOM, but it is not supported properly by Internet Explorer 5.0/Macintosh. Instead of returning a NodeList array, with a length property (useful for looping over all children of a given node) it returns a Boolean value with no length property.
childNodes (can use getElementsByTagName("*") on some platforms)
Due to the lack of a properly implemented childNodes property, it is difficult to loop over all elements in a document using standards-based code. Fortunately, the DOM method getElementsByTagName() accepts the "*" argument, which specifies that all document elements should be returned. This works properly in both Internet Explorer 5.0 and Netscape 6.0 on the Macintosh, but is not supported by Internet Explorer 5.x on Windows, so in order to work around this the script checks for support for document.all and uses that if it is found. If not, the script reverts to using the appropriate standard method.
getAttributeNode() for IE5, getAttribute() for Navigator
According to the Level 1 Core W3C DOM, the getAttribute() method should return the string value of the attribute passed as an argument, and the getAttributeNode() method should return an Attr object. However, in Internet Explorer 5.0/Macintosh, the getAttributeNode() method returns a string, and the getAttribute() method returns a null value. In an early version of the script, we had a workaround for this problem that involved checking for both methods, getAttributeNode() first. In the end, we simply used the attributes array combined with the nodeName and nodeValue properties.
getAttribute() call
It doesn't appear to make any difference to either Macintosh browser whether you use
getAttribute("STYLE")
or
getAttribute("style")
for regular HTML documents, but depending on how an XHTML document is served, it may have an impact, as XHTML is case-sensitive (as are all XML documents).
The script contains several functions, discussed in detail below. The first three functions set styles, allowing you to discriminate between elements based on their ID, their CLASS, or their tag name. The second set of functions simply allows you to get the current style settings for each element.
The simplest of these is setStyleById(), which takes an element ID, a CSS property, and a value, and sets that element's property to that value.
setStyleById(ID, property, value)
The second function takes a tag name (e.g., H1), a class name, a CSS property, and a value, and sets the style for all elements of that type (or for all elements, if passed the "*" specifier).
setStyleByClass(type, class, property, value)
The third function takes a tag name (e.g., H1), a CSS property, a value, and the final (and optional) Boolean argument, which specifies whether to modify the style for that element in the stylesheet or in the .style array of the element. Unfortunately, due to the lack of a functional selectorText property in Netscape6/Mozilla, the stylesheet mode of the function does not work.
setStyleByTag(tagname, property, value, global)
The second set of functions allows you to read the values of any CSS property for any element, based on the element name, ID, or CLASS selector, as above.
getStyleById(ID, property)
getStyleByClass(type, class, property)
getStyleByTag(type, property)
If the property is found, it is returned as a string. If not, the functions return null.
Note that because these scripts use advanced DOM functionality, they will not work in any browser previous to the IE5/Netscape6/Mozilla generation. This includes Navigator 4.x, Internet Explorer 4.x, and other, older browsers. To ensure that your site provides for users of those browsers, consider using a browser sniffer or server-side logic to deliver different content to those browsers.
Simply click on the buttons below to demonstrate the script.
Return to the JavaScript and CSS DevCenter.
Copyright © 2007 O'Reilly Media, Inc.