Login | Register   
LinkedIn
Google+
Twitter
RSS Feed
Download our iPhone app
TODAY'S HEADLINES  |   ARTICLE ARCHIVE  |   FORUMS  |   TIP BANK
Browse DevX
Sign up for e-mail newsletters from DevX


advertisement
 

Customizing Style Sheets on the Fly

This solution explains why stylesheets were originally conceived and gives a basic introduction to using stylesheets to achieve certain key interface and accessibility enhancements.


advertisement
'm afraid that age is catching up with me.  Once, in my distant youth (well, maybe not all that distant), my eyesight was about perfect—I 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 768—those 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 sense—designers 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 Lynx—or in a Braille reader, for that matter?).



Style sheets were meant to alleviate this problem somewhat by offering both programmers and designers what they wanted—for 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 that—designers 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 English—which I assure you is quite illegible as a computer font—there 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:

&lt;STYLE> P {FONT-SIZE:12pt;} .Default{} .Warning{ COLOR: red; MARGIN-LEFT: 0.5in; MARGIN-RIGHT: 0.5in; TEXT-ALIGN: justify; } XMP{ FONT-FAMILY: Courier; COLOR: blue; } .SubHead{ } &lt;/STYLE>
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 sheets—that 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 LINK—from 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:

&lt;SCRIPT> function GetRuleNames(){ var styleSheet=null; var Rules=new Array(); var styleCount=0; for (var ssIndex=0; ssIndex&lt;document.styleSheets.length; ssIndex++){ styleSheet=document.styleSheets[ssIndex]; for (var rIndex=0; rIndex&lt;styleSheet.rules.length; rIndex++){ rule=styleSheet.rules[rIndex] Rules[styleCount]=rule.selectorText styleCount++; } } return Rules; } &lt;/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:

&lt;SCRIPT> function GetRule(ruleName){ var styleSheet=null; var Rules=new Array(); var styleCount=0; for (var ssIndex=0; ssIndex&lt;document.styleSheets.length; ssIndex++){ styleSheet=document.styleSheets[ssIndex]; for (var rIndex=0; rIndex&lt;styleSheet.rules.length; rIndex++){ rule=styleSheet.rules[rIndex] if ((rule.selectorText=="."+ruleName)||(rule.selectorText==ruleName)){ Rules[styleCount]=rule styleCount++; } } } return Rules; } &lt;/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:

&lt;SCRIPT> var WarningRules=GetRule("Warning") if (WarningRules.length>0){ var WarningRule=WarningRules[0]; } else { var WarningRule=null; } ... &lt;/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:

&lt;SCRIPT> var rule=GetRule("Warning")[0] rule.style.color="green"; &lt;/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:

&lt;SCRIPT> var rule=GetRule("Warning")[0] rule.style.fontSize=rule.style.fontSize+4; &lt;/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:

.Warning{ COLOR: red; FONT-SIZE:12pt; MARGIN-LEFT: 0.5in; MARGIN-RIGHT: 0.5in; TEXT-ALIGN: justify; }

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&lt;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 definition—unfortunately, 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 exist—otherwise 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&lt;document.styleSheets.length;sIndex++){ var styleSheet=document.styleSheets[index] for (var rIndex=0;rIndex&lt;styleSheet.rules.length;rindex++){ var rule=styleSheet.rules[rIndex] if (""+rule.style.fontSize!="null"){ var fontSize=parseInt(rule.style.fontSize,10)+amt; if (fontSize&lt;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 page—or 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 managing editor for XMLToday.org and a contributing editor for O'Reilly Media. He is currently working on a book about XBRL. Follow him on Twitter at twitter.com/kurt_cagle.
Comment and Contribute

 

 

 

 

 


(Maximum characters: 1200). You have 1200 characters left.

 

 

Sitemap
Thanks for your registration, follow us on our social networks to keep up-to-date