This solution explains why stylesheets were originally conceived and gives a basic introduction to using stylesheets to achieve certain key interface and accessibility enhancements.
by Kurt Cagle
July 1, 1998
'm afraid that age is catching up with
me. Once, in my distant youth (well, maybe not all that distant), my
eyesight was about perfectI could make out inch high letters from fifty
yards away, and had trouble understanding how frustrating not having good vision
could be. However, years of staring at computer screens and burying my face in
technical manuals have begun insidiously to take their toll on my once vaunted
powers of perception, and with it has come that glimmer of realization as to why
my father prefers not to have his computer set to 1024 x 768those tiny letters
are no longer so easy to read.
The original HTML specification was
targeted in part as a way for a wide range of devices to display the same
information. This is one of the reasons why there isn't a
specification anywhere that claims H1 tags will always be 36 pt Helvetica Bold.
The idea was that the receiving device (conceivably set by the user of that
device) should be able to interpret the size of an H1 tag to best fit its
intended audience. If you are an eagle-eyed twenty something who wants to get as
much information as possible on one screen without needing to scroll, you can
override the defaults so that your H1 tags are set in 18 pt Garamond Light. On
the other hand, if your eyes have been as overworked as mine, then maybe bumping
that font up to 42 pt Helvetica Extended Bold might be a good idea, not to
mention fine-tuning the paragraph tag up a font size or three.
However, the moment that graphic designers
realized the potential inherent in Web pages, most rules were immediately tossed
out the window. Designers started demanding finer control over their pages,
which resulted first in the appearance of the <FONT> tag, then (as the
specter of utter contextual chaos loomed on the horizon) the innovation of style
sheets. The FONT tag was at best a compromise solution in the purist sensedesigners disliked it because it still didn't render the absolute control that
they would have preferred and programmers didn't like it because it contained no
useful information about the contents of the tag. The same could be said for the
<B> and <I> tags, although most developers implicitly understood that
these were synonyms for the <STRONG>
and
<EM> (for Emphasis) tags respectively.
The FONT tag had no analogs, making it difficult to customize
information contained therein (what meaning does a <FONT
SIZE="+2"> tag have in Lynxor in a Braille reader, for that
matter?).
Style sheets were meant to alleviate this problem
somewhat by offering both programmers and designers what they wantedfor
programmers, stricter controls on the context of an element container, for
designers more precise placement and sizing of text and graphical elements on
the screen. If closely adhered to, style sheets do precisely thatdesigners can set up the styles they need based upon contextual information
rather than form, placing the specifications (what's known in programming
circles as the presentation layer) either in a separate portion of the document,
or even better in a separate document altogether. This solution makes the HTML code
itself easier to read, simplifying the life of programmer and designer
alike.
Still, style sheets don't necessarily help the
person for whom HTML was originally targeted: the end viewer. If the
designer has a yen for 7 pt Olde Englishwhich I assure you is quite
illegible as a computer fontthere is absolutely nothing that your viewer can do to render the type more readable. However, with a proper
understanding of how style sheets themselves are implemented, you can offer
your viewers the ability to modify the style
sheets programmatically, and as an added bonus can actually add or delete style
sheet entries at run time (granted, this may offer only limited utility for a
static page, but is fairly exciting when dealing with a Web page generator or
similar program).
Note: As is typically the case, Netscape and
Internet Explorer offer conflicting mechanisms for controlling sheets.
What works for IE4 and 5 will not work in Communicator, and vice versa. The
example given here explains style sheet creation specifically for Internet
Explorer. The style sheets for Netscape 4 are both simpler and much less
powerful, and will be covered in a subsequent article.
The object model for Internet Explorer is
far from intuitive, which is part of the reason I suspect that so few people
actually mess with manipulating style sheets in Internet Explorer. Far
from having a collection of styles, IE4 and IE5 actually have a collection of
style sheets. A Sheet is defined in one of two ways. The most obvious
sheet is one defined within a <STYLE> element. For example, this document
contains only one style sheet, which looks more or less as follows:
If you have more than one section of
<STYLE></STYLE> in your document, you have one style sheet for each
set. Furthermore, if you link to one or more external style sheets, each of
these is added into the collection. All of these together constitute an
internal document collection of style sheetsthat is, they can be accessed by
using the syntax:
document.StyleSheets[n]
in JavaScript, where n ranges from 0 to one less than
the total number of sheets. VBScript of course uses the slightly different
notation:
document.StyleSheets(n).
As with all collections, you can determine
the number of style sheets through the length
property:
var
numStyleSheets=document.StyleSheets.length;
Note that this includes external style
sheets added to your document with the LINKfrom the standpoint of the
StyleSheets collection, external and internal style sheets are treated the same.
However, here's where things get murky.
The documentation about StyleSheets in the Internet Client SDK is remarkably
ambiguous about what exactly a StyleSheet contains, and many of the other
references that I personally count on (including the otherwise excellent
Wrox Instant IE4 Dynamic HTML Programmer's Reference)
similarly contain no hint about what exactly is to be found within a style
sheet.
It turns out that from the standpoint of
the IE4 document object model, an individual style sheet is itself a collection
of Rules. A rule can be thought of as the
container for an individual style. For example, for this page there are
four rules, indexed from 0 to 3. Each rule in turn has three basic
properties: selectorText, style, and a boolean flag
readOnly.
The selectorText is the handle of the style: either its class name (for
example, .Warning) or the tagname and class name
as a dotted pair (for example, P.Warning) if the style is
explicitly associated with a given class. While you can't explicitly
reference the style via the selectorText, you can iterate through all of the
rules and compare them with the desired one to find out the associated rule's
index. This mechanism works wonderfully to get a list of all of
the styles defined in your document. For example, the following code populates
an array with the names of all of the user-defined rules in the current Web
page:
<SCRIPT>
function GetRuleNames(){
var styleSheet=null;
var Rules=new Array();
var styleCount=0;
for (var ssIndex=0;
ssIndex<document.styleSheets.length;
ssIndex++){
styleSheet=document.styleSheets[ssIndex];
for (var rIndex=0;
rIndex<styleSheet.rules.length;
rIndex++){
rule=styleSheet.rules[rIndex]
Rules[styleCount]=rule.selectorText
styleCount++;
}
}
return Rules;
}
</SCRIPT>
Some caveats to note: style names do not
necessarily have to be unique, especially where the same style
sheets contain both Netscape and Internet Explorer type tags, which may differ
in attribute names and references. This is one of the reasons there is no
direct function that maps a style class name to a given rule. However, writing
one is fairly straightforward, and runs in much the same vein as the last
function:
<SCRIPT>
function GetRule(ruleName){
var styleSheet=null;
var Rules=new Array();
var styleCount=0;
for (var ssIndex=0;
ssIndex<document.styleSheets.length;
ssIndex++){
styleSheet=document.styleSheets[ssIndex];
for (var rIndex=0;
rIndex<styleSheet.rules.length;
rIndex++){
rule=styleSheet.rules[rIndex]
if ((rule.selectorText=="."+ruleName)||(rule.selectorText==ruleName)){
Rules[styleCount]=rule
styleCount++;
}
}
}
return Rules;
}
</SCRIPT>
In this case, the function returns an
array of rule objects, since it is perfectly
possible for more than one rule to have the same name. In order to actually grab
a reference to a rule, you need to pass an index for that rule. Because an array
supports the length property, you can test to see
how many rules were found with that property). For example, the following code
snippet retrieves the "Warning" rule, or the first one encountered if
more than one is in the document:
<SCRIPT>
var WarningRules=GetRule("Warning")
if (WarningRules.length>0){
var WarningRule=WarningRules[0];
}
else {
var WarningRule=null;
}
...
</SCRIPT>
Additionally, if a blank string is passed
to the GetRule function (for example, GetRule("")) then every user-defined
rule is returned. This can be handy if you want to increase or decrease font
sizes throughout a document, as is shown below.
The style
attribute is essentially identical to the style attribute found throughout
Internet Explorer, and can be manipulated in the same way. However, unlike
a container style, changing an attribute in a rule will force a change globally
throughout the document. Any element that has that class definition will
be modified, provided that the element doesn't
explicitly override the attribute being changed. For example, if I
had defined a <P> tag that used the Warning class, but set the color
explicitly in a style statement within the tag to green, then that paragraph
would not be modified if the rule's color was changed to orange.
<P class=Warning>This paragraph
defaults to red.</P>
<P class=Warning
style="color:orange;">This paragraph's color attribute has been
changed to orange.</P>
Now, change the color attribute of the
rule:
<SCRIPT>
var rule=GetRule("Warning")[0]
rule.style.color="green";
</SCRIPT>
The overridden paragraph stays yellow,
while the unmodified paragraph turns green.
<P
class=Warning>This paragraph defaults to red, but when the rule is changed,
this tag turns green.</P>
<P class=Warning
style="color:orange;">This paragraph's color attribute remains
orange.</P>
As with normal styles, if the particular
css property has not been explicitly defined elsewhere (with a few exceptions),
it will return an undefined value. You can still set the property to a new
value, but you should be careful when testing properties. For example, the following code will have fairly disastrous consequences:
<SCRIPT>
var rule=GetRule("Warning")[0]
rule.style.fontSize=rule.style.fontSize+4;
</SCRIPT>
The rule.style.fontSize will return a null value (since it's currently
undefined), and JavaScript will treat the expression rule.style.fontSize+1 the
same as 0+1. Your font's size will be set to one point, rendering it
effectively invisible except for some font droppings here and there. However, if
in your page's description you had explicitly set the size in the style sheet then the same code would bump it up to 13
points:
Also, there's no reason why you can't override the <P> tag (or any
other for that matter) to have an explicit value. Since classes inherit the
attributes of their base tags (in most cases), by modifying the base tag
you can also modify any classes that inherit the base tag, as long as the class
doesn't explictly override it.
This point brings me back to the lament that I
started with. If you explicitly define a paragraph tag, for example, you
can increase the size of the paragraph tag, as well as any class that doesn't
explicitly override the font-size attribute. You can see this with the
following buttons:
The code to accomplish this
is exceedingly straightforward:
function ChangeFontSize(amt){
var rule=GetRule("P")[0]
var fontSize=parseInt(rule.style.fontSize,10)+amt;
if (fontSize<1){fontSize=1;}
rule.style.fontSize=fontSize+"pt";
return rule.style.fontSize;
}
You can retrieve the rule by using the GetRule
function that was previously derived. Note in the style sheet definition above that the
font-size property was explicitly set to 12pt. The fontSize property
returns a string, "12pt", which needs to be converted to an integer.
You can use the parseInt routine to convert the string,
although you have to indicate what base to convert to (in this case,
you're parsing from a decimal source, so that you'd just pass the value 10 to
the routine). From there, increase or decrease by a specific amount, trap to
make sure that you don't set your font smaller than 1 pt, convert the value back
to a string, and set the rule's attribute (returning the size is simply a
convenience, not a necessity).
You may have noticed that the code
fragments in this document didn't change size. This is to be expected, since
they aren't "descended" from <P>, but rather from the tag
<XMP>. Similarly, the <H1>, <H2>, etc. tags are exempt from
the changes to the <P> tag. Since the whole exercise here involves
scaling all of the document's fonts up or down, it's worth examining how tags
could be added on the fly if they weren't explicitly supported in a style
sheet. At this stage, the only attribute that matters is the font-size
style, so the question becomes how to add these attributes at run time.
You can find the solution in the addRule method. By passing the selectorText (the name of the style
in question) and the specific style text (in exactly the same manner that you
would pass style text within an HTML document) you can actually define a new
class, a subclassed entry (for example, P.Warning) or redefine standard tags (such as
the H1 tag). Note that if you already have a style sheet definition for a
given element, then the addRule will add a second
definitionunfortunately, the first rule always supercedes subsequent rules,
so the change will have no effect. Consequently, you should only use addRule to define previously undefined entries.
function AssignDefaultSizes(){
if (document.styleSheets.length==0){
var sheet=document.createStyleSheet();
}
else {
var sheet=document.styleSheets[0];
}
if (GetRule("H1").length==0){
sheet.addRule("H1","font-size:24pt;");
}
if (GetRule("H2").length==0){
sheet.addRule("H2","font-size:18pt;");
}
if (GetRule("H3").length==0){
sheet.addRule("H3","font-size:14pt;");
}
if (GetRule("H4").length==0){
sheet.addRule("H4","font-size:12pt;");
}
if (GetRule("H5").length==0){
sheet.addRule("H5","font-size:10pt;");
}
if (GetRule("XMP").length==0){
sheet.addRule("XMP","font-size:11pt;");
}
// Continue for each tag you wish to define
}
All right, I admit that I snuck a second
method in here. It's entirely possible that a document may have no style sheets
defined at all. You can only create rules on an existing style sheet, so the
CreateStyleSheet method is called to create a new
sheet if one doesn't already existotherwise the new rules are appended to
the first style sheet. The expression GetRule("H1").length will
return a value of zero if no rules are found for the given tag, so it provides a
convenient way of checking to see if a tag definition exists. The sizes given
are arbitrary, of course. There's absolutely no reason (other than aesthetics)
why <H1> couldn't be smaller than <H3>.
Once you have elements defined, you can
modify the ChangeFontSize function so that it automatically increments every
rule that has its font-size explicitly defined:
function ChangeFontSize(amt){
for (var sIndex=0;sIndex<document.styleSheets.length;sIndex++){
var styleSheet=document.styleSheets[index]
for (var rIndex=0;rIndex<styleSheet.rules.length;rindex++){
var rule=styleSheet.rules[rIndex]
if (""+rule.style.fontSize!="null"){
var fontSize=parseInt(rule.style.fontSize,10)+amt;
if (fontSize<1){fontSize=1;}
rule.style.fontSize=fontSize+"pt";
}
}
}
}
}
This example is one of many such applications for
rules manipulation within your IE4 documents. If you were ambitious, you could
actually use the same techniques to create a customization page that lets your
users define every aspect of your pageor in conjunction with a textarea box to create an online HTML page editor with precise granularity. With cookies, or the persistance
offered by Internet Explorer 5.0, you can also create a customizable state that
will be retained whenever your viewers return to your site, a topic for a later
article.
Until then, you can help get things into focus (or
at least ease the eyestrain on your poor viewers) with rules and stylesheets.
Oh, and if you know a good optometrist....
Kurt Cagle is the author or co-author of twelve books and several dozen articles on web technologies, XML and web services. He is the president of Cagle Communications (Olympia, WA), which specializes in the production of training materials for XML and Web Services education.