Booming by a hilly golf course in a new pair of lime green Nike Lunar Elites, I observed some deer running through the deep snow that a blizzard referred to as "Snowmageddon" had dumped on Pittsburgh. Though I love to run in fresh powder, 3 feet could be too much. But the deer did just fine. Besides, in those lime green shoes, I felt invincible. So, off the road and into the snow I went.
Probably ten minutes later, my heart thumping forcefully enough to burst, I glanced down at my run timer. Ugh. Still 70 minutes left. I was going to drop dead before it beeps, I thought. So, after cresting the highest hilltop, raising my fists, and rasping "Drago!" a few times, I turned tail and made for the road to finish my run.
But midway through the downhill, the snow depth suddenly increased, sending me airborne less one lime green shoe. Landing in a snow drift, I flailed about for a while trying to right myself. Eventually I had to more or less swim to shallower snow. Finding my missing shoe, I then continued to the road without further mishap and finished my run. Passersby certainly gave me some odd looks, dusty with snow as I was from head to toe!
Now then, deer with their four spindly legs and hooves are better designed for deep-snow running than we are with our two bulkier legs and feet. In the same way, Firefox, Safari, and Opera are better designed to script CSS with DOM than Internet Explorer is. So in this chapter, we will have to dumb things down for Internet Explorer. That is to say, we'll put some snow shoes on our DOM dummy so that it can keep up!
In Chapter 7, we explored some DOM interfaces for querying markup. So as you might guess, in this chapter, we will explore those for querying CSS. We won't explore all of them, just some essential ones:
CSSStyleDeclaration CSS2Properties StyleSheet CSSStyleSheet CSSRuleList CSSRule CSSStyleRule CSSImportRule ElementCSSInlineStyle ViewCSS DocumentStyle StyleSheetList
Remember from Chapter 7 that an interface is a feature list for an object. That is, an interface tells you what methods and members an object contains for your scripting pleasure. So, those are the names to look under on the Web or in a printed DOM reference when you inevitably forget what I tell you here!
Before we get rolling, let's go over some CSS jargon. Consider the following:
img {display:block;border:0;}
The whole enchilada, img {display:block;border:0;}
, is referred to as a rule. Rules have two parts. First there is a selector indicating what part of the document to which to apply the rule. In our sample rule, img
is the selector. So, the rule is for any img
element in the document. The second part of a rule is the declaration block. They contain one or more declarations. In our sample rule, {display:block;border:0;}
is the declaration block. display:block;
is a declaration. border:0;
is too. A declaration pairs a property with a value. That means in our rule, the display
property is paired with the block
value. And the border
property is paired with the 0
value.
You can't query a CSS rule unless you know what to query. JavaScript represents a CSS rule like img {display:block;border:0;}
with a CSSStyleRule
object. They receive the following two members from CSSStyleRule
:
selectorText style
Then they receive four more from CSSRule
:
cssText parentRule parentStyleSheet type
That is, they do in Firefox, Safari, and Opera. Internet Explorer implements CSSStyleRule
but not CSSRule
. So, we will dumb things down for Internet Explorer and work with just selectorText
and style
.
CSSStyleRule
objects have the members listed in both the CSSStyleRule
and CSSRule
interfaces. So, there are two lists of features for this kind of DOM object.
Moving right along, here's a question for you: for our sample rule, img {display:block;border:0;}
, what do you think CSSStyleRule.selectorText
would contain?
Yup, "img"
. So, selectorText
contains the rule's selector as a string. Who'd have thought?
The other member, CSSStyleRule.style
, is not so simple. JavaScript represents a declaration block like {display:block;border:0;}
with a CSSStyleDeclaration
object, which is what the style member contains.
Now then, a CSSStyleDeclaration
object has the following features listed in the CSSStyleDeclaration
interface:
cssText length parentRule getPropertyCSSValue() getPropertyPriority() getPropertyValue() item() removeProperty() setProperty()
Now for the bad news—the Internet Explorer news. Other than cssText
, Internet Explorer does not implement any methods or members from CSSStyleDeclaration
. We will have to pretend cssText
is the only CSSStyleDeclaration
member. Even though this is not true for Firefox, Safari, and Opera, it is for dummy Internet Explorer. Sigh and move forward. It is what it is.
On the bright side, pretending CSSStyleDeclaration
has one member means I can only give you a one-question test: for our sample declaration block, {display:block;border:0;}
, what would CSSStyleDeclaration.cssText
contain?
Here's a hint: some CSS text.
Hmm. cssText
contains CSS text.
That much was obvious. Sorry.
OK, what do you think?
Close, but not quite. Though cssText
does contain the CSS code of the declaration block, it doesn't contain the curly braces. For a CSS rule like {display:block;border:0;}, cssText
would contain "display:block;border:0;"
, not {display:block;border:0;}
.
Internet Explorer failing to implement any of the CSSStyleDeclaration
methods for querying property values would seem to be a disaster. I mean if Internet Explorer is totally illiterate, which is to say it cannot read or write CSS properties, how do we script CSS cross-browser?
Don't panic.
To simplify querying the property-value pairings within a declaration block, JavaScript extends CSSStyleDeclaration
with an optional interface named CSS2Properties
that Internet Explorer, Firefox, Safari, and Opera all implement. CSS2Properties
adds one member for every CSS property in the CSS2 standard to a CSSStyleDeclaration
object. So for any CSS property, reading a CSS2Properties
member is equivalent to invoking CSSStyleDeclaration.getPropertyValue()
. Conversely, writing a CSS2Properties
member is equivalent to invoking CSSStyleDeclaration.setProperty()
.
In other words, CSSStyleDeclaration.getProperty()
and CSSStyleDeclaration.setProperty()
are redundant, so it's no big deal that Internet Explorer fails to implement them.
Just as a reminder, insofar as CSS2Properties
members are named with an identifier, you may query those with the .
operator and an identifier or with the []
operator and a string, typically in the form of a variable or parameter. If you are foggy on how those operators work, flip back to Chapter 3. I'll wait for you to return. Promise.
One-word CSS properties such as display
or left
have identically named CSS2Properties
members. But JavaScript identifiers cannot have dashes. But you know that from Chapter 1. So, any CSS property containing dashes has a CSS2Properties
member that is named in camel case rather than in dash case. For example, margin-bottom
is named marginBottom
. Finally, float
is a JavaScript reserved word. So, float
is represented by a member named cssFloat
in Firefox, Safari, and Opera. For whatever reason, Internet Explorer deviates from DOM and names its member styleFloat
instead...just roll your eyes.
CSS2Properties
members are named with camel case identifiers. What about their values? Those are always strings. Yup, always. Even if you want to set a property like left
or border
to 0
, you have to assign "0"
.
Whenever we want to script declarations, remember those are property-value pairings, we will query either CSS2Properties
members or the CSSStyleDeclaration
member, cssText
, since those are the only features that dummy Explorer knows about. So if we had a variable named myDeclarations
containing a CSSStyleDeclaration
object, it would contain an object equivalent to the following:
var myDeclarations = { display: "block", border: "0", cssText: "display:block;border:0;" }
I did that one. Now it's your turn. Create an object literal equivalent to the CSSStyleRule
representing our sample rule, img {display:block;border:0;}
. Save it to a variable named myRule
.
Here's a hint: one of the members might just contain my object literal.
One more minute.
Time is up.
Did you create an object literal like the following? Give yourself a pat on the back.
var myRule = { selectorText: "img", style: { display: "block", border: "0", cssText: "display:block;border:0;" } }
CSSStyleDeclaration
and CSS2Properties
provide a way for you to query the declarations in a rule. Additionally, those enable you to work with two other kinds of declaration blobs that we'll explore later in the day. First, the cumulative declarations from the CSS cascade that target an element (however, note that those declarations are read-only), and second, the declarations in an element's style
attribute. These declarations are represented by ElementCSSInlineStyle.style
and, like the declarations in a rule, are read-write. Therefore, you can change CSS property values in a rule or style
attribute, but not the cumulative ones from the cascade.
For any tag in your markup lacking a style
attribute, the element representing the tag will still have a style member containing a CSSStyleDeclaration
object, which may have some default members from the DTD.
Download or code the following markup, eight.html
, from this book's web site:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>Scripting CSS</title> <link rel="stylesheet" type="text/css" href="eight.css" id="spriteStyles" /> </head> <body>
<div id="running"> <h4>Running</h4> <ul class="blue"> <li><a id="adidas" href="http://www.adidas.com">adidas</a></li> <li><a id="asics" href="http://www.asics.com">ASICS</a></li> <li><a id="brooks" href="http://www.brooksrunning.com">Brooks</a></li> <li><a id="newBalance" href="http://www.newbalance.com">New Balance</a></li> <li><a id="nike" href="http://www.nike.com">Nike</a></li> <li><a id="saucony" href="http://www.saucony.com">Saucony</a></li> </ul> </div> </body> </html>
Additionally, download or code the following CSS file, eight.css
. Put it in the same folder with eight.html
.
* { margin:0; padding:0; border:0; } body { background:rgb(255,255,255); color:rgb(0,0,0); font:11px Verdana, Arial, Helvetica, sans-serif; } div#running { position:absolute; left:40px; top:40px; width:120px; height:243px; background:url(images/container.gif) 0 0 no-repeat; } div#running h4 { position:absolute; left:0px; top:0px; width:63px; height:25px; text-indent:−9999px; text-decoration:none; overflow:hidden; } div#running li { display:inline; } div#running li a { position:absolute; left:10px; width:100px; height:28px; color:rgb(0,0,0);
text-indent:−9999px; text-decoration:none; overflow:hidden; } ul.blue a { background-image:url(images/blue.gif); } ul.fuchsia a { background-image:url(images/fuchsia.gif); } ul.green a { background-image:url(images/green.gif); } a#adidas { top:30px; background-position:0 0; } a#asics { top:65px; background-position:0 −27px; } a#brooks { top:100px; background-position:0 −54px; } a#newBalance { top:135px; background-position:0 −81px; } a#nike { top:170px; background-position:0 −108px; } a#saucony { top:205px; background-position:0 −135px; }
Finally, download the sprite images: blue.gif, fuchsia.gif
, and green.gif
. Download the background image, container.gif
, too. Put those four images in a folder aptly named images
within the folder you put eight.html
and eight.css
in. If you are unfamiliar with sprites, they work by sliding an image to different coordinates by way of the CSS property background-position
. As Figure 8-1 illustrates, this enables us to combine all 12 link images into a single image, which improves load time.
Open eight.html
in Firefox, and then press F12 to enable Firebug. If you're just joining us, flip back to the preface for details on how to do this.
Now let's try querying the style
attribute for the Nike link. Insofar as its markup tag does not contain a style
attribute, ElementCSSInlineStyle.style
will contain a bunch of ""
empty strings. That makes this a pretty dull read. Nevertheless, take a peek at some CSS properties:
var myStyle = document.getElementById("nike").style; myStyle.backgroundPosition; // "" myStyle.backgroundImage; // "" myStyle.left; // "" myStyle.top; // ""
As Figure 8-2 displays, JavaScript returns one ""
empty string after another. If you abide by the separation of markup content from CSS presentation credo, this will be the case for every Element
node in the DOM tree—at least initially.
Let's see what I mean by that cryptic comment. Go ahead and write the members from the previous sample like so, and click Run, verifying your work with Figure 8-3:
var myStyle = document.getElementById("nike").style; myStyle.backgroundPosition = "−99px −108px"; myStyle.backgroundImage = "url(images/fuchsia.gif)"; myStyle.left = "200px"; myStyle.top = "30px";
We replaced the blue sprite with the fuchsia one, slid the sprite to the left in order to reveal the down button, and moved the link entirely out of the container. It's pretty draconian, but it illustrates the power of writing an element's style member: scripted declarations in ElementCSSInlineStyle.style
override those from anywhere else in the CSS cascade.
One more thing: take another peek at ElementCSSInlineStyle.style
for the Nike link. There's something there other than one ""
empty string after another now:
var myStyle = document.getElementById("nike").style; myStyle.backgroundPosition; // "−99px −108px" myStyle.left; // "200px"
So, our writing four CSS properties by way of ElementCSSInlineStyle.style
was equivalent to doing so by way of the following markup:
<a style="background-position:−99px −108px;background- image:url(images/fuchsia.gif);left:200px;top:30px" id="nike" href="http://www.nike.com">Nike</a>
In turn, this is why our scripted styles override those from elsewhere in the cascade, in other words, from eight.css
.
We're good with querying CSS2Properties
members. Now let's take a peek at CSSStyleDeclaration.cssText
. Clear both Firebug panels, but do not refresh Firefox. This leaves our scripted styles in place so cssText
has something for us to read other than an ""
empty string. Enter and run the following, verifying your work with Figure 8-4:
myStyle.cssText; // "background-position: −99px −108px; background-image: url("images/fuchsia.gif"); left: 200px; top: 30px;"
cssText
contains the CSS text of the style
attribute. Imagine that. Now take a peek at cssText
for the adidas
link:
document.getElementById("adidas").style.cssText; // ""
It's just a dull ""
empty string. If you fully separate CSS from markup, reading cssText
will be as mind-numbing as reading CSS2Properties
members—unless you have a thing for ""
empty strings. On the other hand, writing cssText
provides a way to change several CSS properties in one fell swoop. Want to try doing that? Me too. Refresh Firefox, clear Firebug, and then enter and run the following sample. Note that the second statement wraps to two lines in this book but should be keyed in as one line in Firebug:
var myStyle = document.getElementById("brooks").style; myStyle.cssText = "background-image:url(images/fuchsia.gif);left:210px;top:0;padding- left:99px;height:55px";
That did in one assignment statement what would have taken five with CSS2Properties
members. Now for the tricky part: writing cssText
wipes away any previous inline style declarations. With this in mind, click Clear in Firebug, but do not refresh Firefox, and then enter the following assignment; however, before clicking Run, take a guess at what will happen. You get a Smiley Cookie if you get it right.
myStyle.cssText = "background-image:url(images/fuchsia.gif)";
Smiley Cookies are a Pittsburgh institution. If you want to try them, visit www.eatnpark.com
, and click the Create your own Smiley Cookies!link. Trust me, you'll love them.
Does your guess correspond to Figure 8-5?
Don't feel bad. I didn't think you'd get that one right. What's going on here? Any declarations you write to cssText
totally overwrite those already in there. So, our declarations for left, top, padding-left
, and height
disappeared. This in turn means that the declarations for those properties in eight.css
now shine through. Therefore, other than swapping the blue sprite for the fuchsia one, our brooks
link is styled the same as when the page initially loaded. This is equivalent to the following markup:
<a style="background-image:url(images/fuchsia.gif);" id="brooks" href="http://www.brooksrunning.com">Brooks</a>
Note that when you write the value of cssText
, property names are dash case, not camel case—just like in the CSS standard.
Writing CSS2Properties
members or cssText
provides a way to restyle one element, but what if you want to do so for several elements? One way is to script className
, typically of an ancestor element. Remember that for any element, the className
member represents the class
attribute.
In our included style sheet, eight.css
, we have a rule for a blue class and a fuchsia class. The latter is dormant. That is to say, no element in our markup is a member of the fuchsia class. So, refresh Firefox, clear Firebug, and let's change the className
member for the <ul>
element from blue
to fuchsia
. Doing so, as Figure 8-6 illustrates, swaps the sprite for all six links in one fell swoop:
document.getElementsByTagName("ul")[0].className = "fuchsia";
Totally overwriting className
like that is fine if the element is a member of just one class that you want to replace. But an element can be a member of two or more classes. So, is there a less draconian way to go about scripting className
?
Of course. Why else would I even bring it up? Scripting className
is something you will do quite often. With this in mind, let's write a function to do the job for us. Maybe name it swapClass
. Then refresh Firefox, clear Firebug, and enter the following:
function swapClass(element, oldClass, newClass) { var re = new RegExp("\b" + oldClass + "\b", "g"); element.className = element.className.replace(re, newClass); }
swapClass()
works with three parameters. element
is an Element
node that you want to change className
for. newClass
is the name of the class you want to replace oldClass
with. Note that both oldClass
and newClass
are strings.
swapClass()
does its work in just two statements. First, we save a regular expression to a local variable named re
. This will match a word boundary, followed by the value of oldClass
, followed by another word boundary. So, re
will match "blue"
but not "blueberry"
. In the next statement, we call String.replace()
on className
, passing re
for the first parameter and newClass
for the second. Doing so swaps oldClass
for newClass
.
String.replace()
and other wrapper methods for strings were covered in Chapter 2.
There it is. Now let's try swapping "blue"
for "fuchsia"
for the <ul>
element with the help of swapClass()
. Verify your work with Figure 8-7:
function swapClass(element, oldClass, newClass) { var re = new RegExp("\b" + oldClass + "\b", "g"); element.className = element.className.replace(re, newClass); } swapClass(document.getElementsByTagName("ul")[0], "blue", "fuchsia");
In addition to scripting className
, another way to change the appearance of several elements is to script a rule that targets them. Rather than swapping className
from "blue"
for "fuchsia"
for the <ul>
, we could just as well change the rule for the blue
class in eight.css
.
Now that we have our marching orders, refresh Firefox, and clear Firebug. JavaScript represents a style sheet like eight.css
with a CSSStyleSheet
object. Those implement the members listed in two DOM interfaces, StyleSheet
and CSSStyleSheet
. The following seven members come from StyleSheet
:
disabled href media ownerNode parentStyleSheet title type
Additionally, four CSS-only members come from the CSSStyleSheet
interface:
cssRules ownerRule deleteRule() insertRule()
Internet Explorer, of course, deviates from the DOM standard but not by much for StyleSheet
. Internet Explorer renames ownerNode
, which refers to the <link>
or <style>
element for the style sheet, as owningElement
. For the other StyleSheet
members, Internet Explorer abides by the DOM names.
On the other hand, Internet Explorer does not implement any members from CSSStyleSheet
. But don't worry. There are Internet Explorer-only members that will enable us to muddle through. The ones we will explore are as follows:
rules imports addRule() addImport() removeRule() removeImport() cssText
Things could be worse, you know. Internet Explorer deviating from DOM makes scripting style sheets tough. But the proprietary workarounds make it doable. So, rather than feel sorry for ourselves, let's start moving forward in small steps.
Before we can query a rule, we need to get at the style sheet that contains the rule. One way is to query the sheet
member of a <link>
or <style>
element in Firefox, Safari, and Opera. Internet Explorer deviates from DOM, no surprise, and implements a proprietary styleSheet
member instead. With this in mind, our first steps will be the following:
var myStyleSheet = document.getElementsByTagName("link")[0]; myStyleSheet = myStyleSheet.sheet || myStyleSheet.styleSheet;
Another, less reliable way to query a style sheet is by way of document.styleSheets
, which contains an array-like object with one member for every <style>
or <link>
element having a rel
attribute value set to "stylesheet"
. Internet Explorer and Opera also add one member for every <style>
or <link>
element having a rel
attribute value set to "alternate stylesheet"
. No browser adds imported style sheets to document.styleSheets
—just those included with a <link>
or embedded in a <style>
. Anyway, we could rewrite the previous sample like so:
var myStyleSheet = document.styleSheets[0];
document.styleSheets
is provided by the DocumentStyle
interface. In turn, styleSheets
contains an array-like object provided by the StyleSheetList
interface. Members contained by a StyleSheetList
are objects that have the features listed in the StyleSheet
and CSSStyleSheet
interfaces. So, there are four interfaces for you to consult in a DOM reference.
Now myStyleSheet
contains an object representing eight.css
. Let's find the rule for the blue
class. Where would that be? It's in a cssRules
member for Firefox, Safari, and Opera, but in a rules
member for Explorer. Those are both CSSRuleList
objects. Those are array-like objects, which is to say their members are elements. Moreover, cssRules.lengt
h or rules.length
contains the number of members.
cssRules
contains both styling rules and @
directives like @import
. On the other hand, rules
contains only styling rules. In other words, it contains the ones comprised of a selector and declaration block. So if a style sheet contains @
directives, cssRules.length
will be greater than rules.length
. In Internet Explorer, @import
directives are nowhere to be found, but any @page
directives are in an array-like object named pages
. Note that although @import
directives are missing in Internet Explorer, scripting imported style sheets remains doable by way of imports, addImport()
, and removeImport()
. Later in the day, we will explore those features. Note that Internet Explorer splits grouped selectors into more than one rule. Therefore, for the following rule, Internet Explorer would add two members to rules
, while Firefox, Safari, and Opera would add one member to cssRules
:
div#mast form, div#mast h1 { display:inline; }
In other words, Internet Explorer separates the previous rule into two like so:
div#mast form { display:inline; } div#mast h1 { display:inline; }
With all those variations between cssRules
and rules
in mind, obviously do not hard-code the index of a rule, because it is apt to be wrong inInternet Explorer or in Firefox, Safari, and Opera.
Now, with length
and either cssRules
or rules
, we can loop through the styling rules in eight.css
. Note that in any browser, rules are numerically indexed in source code order. Say we want to find the rule with the selector "ul.blue a"
in order to change the sprite to fuchsia; a for
loop like the following would work cross-browser:
var myStyleSheet = document.getElementsByTagName("link")[0]; myStyleSheet = myStyleSheet.sheet || myStyleSheet.styleSheet; var myRules = myStyleSheet.cssRules || myStyleSheet.rules; for (var i = myRules.length - 1; i > −1; i --) { if (myRules[i].selectorText && myRules[i].selectorText.toLowerCase() === "ul.blue a") { myRules[i].style.backgroundImage = "url(images/fuchsia.gif)"; break; } }
Three things in the for
loop bear explaining. Thought I'd leave you in the lurch, didn't you?
First, we loop through the rules in reverse order. Now why would we do that? CSS precedence: in the event that more than one rule in the style sheet has the selector we are looking for, we want to change the last one.
Second, @import
and other @
rules do not define a selectorText
or style
member. Calling String.toLowerCase()
on undefined
returns an error. So, we use the &&
operator to skip any rules that do not define selectorText
.
Third, the string in selectorText
may or may not match the CSS selector. For this reason, we lowercase selectorText
prior to comparing it with ===
.
If you have forgotten how the &&
and ===
operators work, flip back to Chapter 3.
Now let's see whether everything goes according to plan. Refresh Firefox to revert the sprite to blue. Then click Run in Firebug, and verify your work with Figure 8-8.
Finding a rule certainly takes some doing. And it's something we're likely to do often. So, maybe we ought to write a helper function to simplify things. Yup, that's what we'll do. Refresh Firefox, clear Firebug, and then declare a function named findRule()
that takes two parameters. element
will contain the <link>
or <style>element
, and selector
will contain the selector for the rule we want to find.
function findRule(element, selector) { }
So, an Element
node and selector string will go in one end of findRule()
, and then a CSSStyleRule
object will pop out of the other. Great. Now let's make it happen.
First, compensate for Internet Explorer deviating from DOM by renaming sheet
and cssRules
. To do so, create a couple local variables like so. Then lowercase the string in the selector
argument. This will save your bacon in the event you forget to pass in the selector in lowercase to begin with.
function findRule(element, selector) { var sheet = element.sheet || element.styleSheet; var rules = sheet.cssRules || sheet.rules; selector = selector.toLowerCase() }
Now findRules()
has the values necessary to find the desired rule. Let's tweak the for
loop from the previous sample; the body of the loop just needs to return the matching CSSStyleRule
object. In case there are none, return null
following the for
loop. Recall from earlier in the book that a function that typically returns an object ought to return null
(which conveys no object) on failure rather than undefined
(which conveys no string, number, or boolean).
With those brave words, our work is done:
function findRule(element, selector) { var sheet = element.sheet || element.styleSheet; var rules = sheet.cssRules || sheet.rules; selector = selector.toLowerCase() for (var i = rules.length - 1; i > −1; i --) { if (rules[i].selectorText && rules[i].selectorText.toLowerCase() === selector) { return rules[i]; } } return null; }
Now let's see whether findRule()
works as planned. Refresh Firefox to revert the sprite to blue. Then in Firebug, swap the blue sprite for the fuchsia sprite with the help of findRule()
, verifying your work with Figure 8-9.
function findRule(element, selector) { var sheet = element.sheet || element.styleSheet; var rules = sheet.cssRules || sheet.rules; selector = selector.toLowerCase() for (var i = rules.length - 1; i > −1; i --) { if (rules[i].selectorText && rules[i].selectorText.toLowerCase() === selector) { return rules[i]; } } return null; } var myRule = findRule(document.getElementsByTagName("link")[0], "ul.blue a"); if (myRule !== null) { myRule.style.backgroundImage = "url(images/fuchsia.gif)"; }
Remember from earlier in the chapter that CSSStyleRule.style
contains an object with the members listed in the CSSStyleDeclaration
and CSS2Properties
interfaces. We just wrote CSS2Properties.backgroundImage
. Now by way of CSSStyleDeclaration.cssText
, let's change the sprite from blue to green for the Saucony link and slide it to the down position. To do so, refresh Firefox, and rework the previous sample like so. Then click Run.
function findRule(element, selector) { var sheet = element.sheet || element.styleSheet; var rules = sheet.cssRules || sheet.rules; selector = selector.toLowerCase() for (var i = rules.length - 1; i > −1; i --) { if (rules[i].selectorText && rules[i].selectorText.toLowerCase() === selector) { return rules[i]; } } return null; } var myRule = findRule(document.getElementsByTagName("link")[0], "a#saucony"); if (myRule !== null) { myRule.style.cssText = "background:url(images/green.gif) −99px −135px; top:205px"; }
One thing to note or reiterate is that changing cssText
is draconian, obliterating any declarations in there. This is why we had to reset top
to 205px
in the previous sample. Another, more general thing to note is that a style sheet differs from a <link>
or <style>
. The latter is markup, while the former is CSS. In other words, a style sheet is the CSS code in the file included by a <link>
element or contained by a <style>
element. So, you cannot, for example, retrieve a style sheet by its id
member, since it does not have one. However, you can and probably should retrieve the corresponding <link>
or <style>
element by its id
. Doing so ensures that your script continues to work in the event that CSS designers add more <link>
or <style>
tags. It's also the case if you add more tags dynamically by script, which we will cover not far down the road.
For example, rather than hoping that the first <link>
element continues to be the one we want to script in the future, we ought to modify the previous sample like so:
function findRule(element, selector) { var sheet = element.sheet || element.styleSheet; var rules = sheet.cssRules || sheet.rules; selector = selector.toLowerCase() for (var i = rules.length - 1; i > −1; i --) { if (rules[i].selectorText && rules[i].selectorText.toLowerCase() === selector) { return rules[i]; } } return null; } var myRule = findRule(document.getElementById("spriteStyles"), "a#saucony"); if (myRule !== null) { myRule.style.cssText = "background:url(images/green.gif) −99px −135px; top:205px"; }
Now then, what if you want to script an imported style sheet? In other words, say you want to script one included with an @import
directive. First, save eight.html
as running_copy.html
; then, replace the <link>
element with a <style>
element that imports eight.css
like so:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>Scripting CSS</title> <style type="text/css"> @import url(eight.css); </style> </head> <body> <div id="running"> <h4>Running</h4> <ul class="blue"> <li><a id="adidas" href="http://www.adidas.com">adidas</a></li> <li><a id="asics" href="http://www.asics.com">ASICS</a></li> <li><a id="brooks" href="http://www.brooksrunning.com">Brooks</a></li> <li><a id="newBalance" href="http://www.newbalance.com">New Balance</a></li> <li><a id="nike" href="http://www.nike.com">Nike</a></li>
<li><a id="saucony" href="http://www.saucony.com">Saucony</a></li> </ul> </div> </body> </html>
As noted earlier in the day, an @import
rule does not have a selectorText
or style
member. The reason for this is that an @import
rule does not implement the CSSStyleRule
interface but instead implements CSSImportRule
. This interface provides three members:
href media styleSheet
StyleSheet
refers the imported style sheet. So if we want to change left
to 500px
for the running <div>
, we can do so by entering and running the following in Firebug:
document.getElementsByTagName("style")[0].sheet.cssRules[0].styleSheet.cssRules[2].style.left = "500px";
This works for Firefox, Safari, and Opera. But it doesn't work for Internet Explorer, which does not implement CSSImportRule
. Rather, for a <style>
or <link>
element, styleSheet.imports
contains a collection of imported style sheets. So, we would rework the previous sample like so:
document.getElementsByTagName("style")[0].styleSheet.imports[0].rules[2].style.left = "500px";
Now then, what if you want to dynamically determine the numeric index of a rule cross-browser? Can it be done? Yup. But why would you want to know a rule's index? A couple reasons: to add a rule or delete one. So, before we try explore those operations, let's figure out how to determine the numeric index of a rule regardless of the visitor's browser.
Insofar as this is something we will want to frequently do, you probably know what I am going to say. Right, let's code a helper function. So, delete running_copy.html
and reload eight.html
in Firebug. Then key in the following function, which differs from findRule()
in just two ways:
First, it returns the loop variable i
rather than a CSSStyleRule
object. i
will be the index of the desired rule. That was painless.
Second, to convey failure in the event no rule in the style sheet has the selector we gave findIndex()
to work with, the return value will be undefined
rather than null
. Why? findIndex()
ought to return a number. Those are stored on the stack, and undefined
conveys no value on the stack. On the other hand, findRule()
ought to return a object. Those are stored on the heap, and null
conveys no value on the heap. You forgot about all that, didn't you?
function findIndex(element, selector) { var sheet = element.sheet || element.styleSheet; var rules = sheet.cssRules || sheet.rules; selector = selector.toLowerCase() for (var i = rules.length - 1; i > −1; i --) { if (rules[i].selectorText && rules[i].selectorText.toLowerCase() === selector) { return i; } }
}
Let's see whether findIndex()
works as planned. Refresh Firefox to revert the sprite to blue. Then pass in "a#adidas"
for selector
, and click Run. Since that is the tenth rule in eight.css
, the return value ought to be 9
because JavaScript numbers them beginning with 0
. Verify your work with Figure 8-10.
function findIndex(element, selector) { var sheet = element.sheet || element.styleSheet; var rules = sheet.cssRules || sheet.rules; selector = selector.toLowerCase() for (var i = rules.length - 1; i > −1; i --) { if (rules[i].selectorText && rules[i].selectorText.toLowerCase() === selector) { return i; } } } findIndex(document.getElementById("spriteStyles"), "a#adidas"); // 9
Now that we have a helper function to provide us with the numeric index of a rule, we can explore how to insert a rule into a style sheet. Of course, there is a DOM way and an Internet Explorer way. Let's write another helper function to compensate for Internet Explorer's skullduggery.
Clear Firebug, but do not refresh Firefox because we want findIndex()
to remain in memory. Name the helper function insertRule
. This one will work with four parameters:
element
will be a <link>
or <style>Element
node.
selector
will be the text of the selector for the new rule, in other words, a string like "div#running li"
.
declarations
will be the text of the declaration block, minus the curly braces, such as a string like "top:135px; background-position:0 −81px"
. Note that property names are dash case. It's just like in your CSS code or in CSSStyleDeclaration.cssText
.
index
contains the text of the selector for the rule you want to insert the new rule before. So, that's the selector string we will pass to findIndex()
, which will then return a numeric index, or undefined
if we are out of luck.
function insertRule(element, selector, declarations, index) { }
Now let's move on to the body of our helper function. Assign either the DOM or Internet Explorer member that contains the CSSStyleSheet
object to a local variable named sheet
. Similarly, assign the DOM or Internet Explorer member that contains the CSSRuleList
array-like object to one named rules
. It's just like we did in findIndex()
.
Now let's make the index
parameter optional by way of a couple if
statements. JavaScript will run the block of the first if
statement in the event that we did pass a selector string for the value of index. Otherwise, JavaScript will run the block of the second if
statement, like if we invoked insertRule()
with just three parameters rather than four. In this case, index
defaults to undefined
. Let's overwrite that value with rules.length
, which contains a number one greater than the total number of rules in the style sheet. Later, this numeric index will enable us to append the new rule to the very end of the style sheet. Thus far we have this:
function insertRule(element, selector, declarations, index) { var sheet = element.sheet || element.styleSheet; var rules = sheet.cssRules || sheet.rules; if (typeof index === "string") { index = findIndex(element, index); } if (typeof index !== "number") { index = rules.length; } }
Now sheet
will contain either a DOM method named insertRule()
or an Internet Explorer method named addRule()
. Let's figure out which one is available by way of the else if
idiom. In the event that Firefox, Safari, or Opera is running our function, insertRule()
will be defined. This method takes two parameters:
The full text of the rule, curly braces and all. So, we will cobble that together with the +
operator.
The numeric index of the new rule. We have that number on hand in the index, so just pass that in, and we're done.
Now let's code a path for Internet Explorer to run. Its method, addRule()
, takes three parameters: the text for the selector, the text for the declarations, and the numeric index for where to insert the rule. Simply pass in the values of selector, declarations
, and index
. It's simple as can be:
function insertRule(element, selector, declarations, index) { var sheet = element.sheet || element.styleSheet; var rules = sheet.cssRules || sheet.rules; if (typeof index === "string") { index = findIndex(element, index); } if (typeof index !== "number") { index = rules.length; } if (sheet.insertRule) { sheet.insertRule(selector + "{" + declarations + "}", index); } else if (sheet.addRule) { sheet.addRule(selector, declarations, index); } }
Between the rules with the selectors "ul.blue a"
and "ul.fuchsia a"
, let's insert a new one to swap the sprite from blue to fuchsia by calling insertRule()
like so. Verify your work with Figure 8-11.
function insertRule(element, selector, declarations, index) { var sheet = element.sheet || element.styleSheet; var rules = sheet.cssRules || sheet.rules; if (typeof index === "string") { index = findIndex(element, index); } if (typeof index !== "number") { index = rules.length; } if (sheet.insertRule) { sheet.insertRule(selector + "{" + declarations + "}", index); } else if (sheet.addRule) { sheet.addRule(selector, declarations, index); } } insertRule(document.getElementById("spriteStyles"), "ul.blue a", "background-image:url(images/fuchsia.gif)", "ul.fuchsia a");
Of course, since the final rule in a style sheet clobbers any similar ones appearing earlier, we could just as well omitted the fourth parameter. Our function will then append our new rule to the very end of the rule collection. Take advantage of that feature to swap the sprite from fuchsia to green like so:
function insertRule(element, selector, declarations, index) { var sheet = element.sheet || element.styleSheet; var rules = sheet.cssRules || sheet.rules; if (typeof index === "string") { index = findIndex(element, index); } if (typeof index !== "number") { index = rules.length; } if (sheet.insertRule) { sheet.insertRule(selector + "{" + declarations + "}", index); } else if (sheet.addRule) { sheet.addRule(selector, declarations, index); } }
insertRule(document.getElementById("spriteStyles"), "ul.blue a", "background-image:url(images/green.gif)");
Now then, let's draft a helper function to delete a rule from a style sheet. Just as with adding a rule, Internet Explorer implements a proprietary method instead of the one from the DOM standard. Our game plan for this helper function will be similar to the one for adding a rule. In other words, we will have Firefox, Safari, and Opera invoke the DOM method, deleteRule()
, and Internet Explorer will invoke its proprietary one, removeRule()
. There will be one path for standard savvy browsers and one path for dummy Internet Explorer.
Clear Firebug, but do not refresh Firefox because we want to keep findIndex()
in memory. Name the helper function deleteRule
. It's the same identifier as for the DOM method. Now why do those identifiers not collide?
Take your time.
This is one you ought to know.
By the way, we named the helper function for adding a rule with the identifier for the DOM method, insertRule
. Those didn't collide either.
What do you think?
Yup, different namespaces. The helper functions insertRule()
and deleteRule()
are methods of window
. On the other hand, the DOM functions insertRule()
and deleteRule()
are methods of a CSSStyleSheet
object. They're in different folders, so to speak.
Where were we? Hmm. OK, now define two named arguments:
The first one, element
, will be a <link>
or <style>Element
node.
The second one, selector
, will be the text of the selector for the rule to delete.
Insofar as the DOM and Internet Explorer functions for deleting a rule are methods of a CSSStyleSheet
object, we need to save a reference to the one containing the rule to delete. Put that in a local variable named sheet
. Then save the return value of passing element
and selector to findIndex()
to another local variable named index. If findIndex()
has no luck with those parameters and has to return undefined
instead of a number, we cannot delete a rule—not unless we want to do so randomly! So, write an if
statement to abort deleteRule()
in the event we have no numeric index to work with.
Thus far we have this:
function deleteRule(element, selector) { var sheet = element.sheet || element.styleSheet; var index = findIndex(element, selector); if (typeof index !== "number") { return; } }
Now for the mojo. Both the DOM and Internet Explorer methods work with one parameter, the numeric index of the rule to delete. Just as a reminder, indexes are integers beginning with 0. So, code two paths with the if else
idiom. Firefox, Safari, and Opera go down the first path, and Internet Explorer goes down the second. We're done:
function deleteRule(element, selector) { var sheet = element.sheet || element.styleSheet; var index = findIndex(element, selector); if (typeof index !== "number") { return;
} if (sheet.deleteRule) { sheet.deleteRule(index); } else if (sheet.removeRule) { sheet.removeRule(index); } }
Now let's try to delete a couple rules, such as the two we just added with insertRule()
. Those both have a selector of "ul.blue a"
. The first time we pass "ul.blue a"
to deleteRule()
, the sprite turns from green to fuchsia, and the second time, it turns from fuchsia to blue. So, run the following twice in Firebug, verifying your work with Figure 8-12:
function deleteRule(element, selector) { var sheet = element.sheet || element.styleSheet; var index = findIndex(element, selector); if (typeof index !== "number") { return; } if (sheet.deleteRule) { sheet.deleteRule(index); } else if (sheet.removeRule) { sheet.removeRule(index); } } deleteRule(document.getElementById("spriteStyles"), "ul.blue a");
Now then, what if you want to query the overall styles from the CSS cascade for an element? In other words, you want to know the declarations that have the highest precedence. Those form one humongous declaration block, which, like the declaration block for a rule, JavaScript represents with a CSSStyleDeclaration
object. You already know how to query those cumulative declarations—the .
operator and a CSS2Properties
member. Remember they are camel case, not dash case. On the other hand, CSSStyleDeclaration.cssText
contains the declarations block, minus the curly braces, as a string. However, this string is oftentimes too lengthy to bother with.
The cumulative declaration block from the cascade differs from that of a rule in a style sheet in a few ways:
It is read-only. Try to assign a new value to a property from the cascade, and JavaScript calls you a dummy by way of an error.
Any relative values are converted to absolute values. Typically this means converting the value to pixels.
Any property that sets several properties in one fell swoop, such as a margin or border, may contain undefined
, while their corresponding fully expanded properties, such as marginLeft
or borderRightStyle
, will always contain a value. As a rule of thumb, do not query rollup values from the cascade. Query their fully expanded equivalents instead.
Refresh Firefox, clear Firebug, and let's cobble together a helper function named queryCascade()
that works with two parameters:
element
will contain an Element
node for the DOM tree.
property
will contain the name of the CSS property as a camel case string. That is to say, don't pass a dash case string or a camel case identifier.
Now let's send Firefox, Safari, and Opera down one path and Internet Explorer down another by way of an if
statement and the else if
idiom. First let's test for the DOM function getComputedStyle()
by way of the typeof
operator. If typeof
returns "function"
rather than "undefined"
, invoke with two parameters, element
and null. getComputedStyle()
will then return a CSSStyleDeclaration
object containing the cumulative declarations from the cascade, as well as those implicitly calculated by the browser, for element
. Then query property
by way of the []
operator.
function queryCascade(element, property) { if (typeof getComputedStyle === "function") { return getComputedStyle(element, null)[property]; } }
Do you remember why doing so with the .
operator will not work?
Come on, you know this one.
I'm not telling you the answer.
Maybe I'll go reorganize the Penske file while you think it through.
George reorganized the Penske file in the Seinfeld episode "The Barber."
What do you think? Right, the .
operator works with a hard-coded identifier. On the other hand, []
works with a string or any expression that evaluates to a string, most notably a variable or parameter.
The if
statement and else if
idiom were covered in Chapter 4, while the typeof
and refinement
operators were covered in Chapter 3. So if you have forgotten how those work, take a moment to flip back and refresh your memory. I'll wait for you to return before continuing. Promise!
Why do we use null
for the second parameter? This can be a pseudo-element as a string, for example, ":before"
or ":after"
. However, Internet Explorer's proprietary alternative to getComputedStyle()
does not support pseudo-elements. Therefore, pass null
to make things work cross-browser.
Now for our DOM dummy, Internet Explorer, which does not implement the ViewCSS
interface that provides getComputedStyle()
. Internet Explorer's way is simple: an Element
node has not only a style
member for inline styles but also a currentStyle
member for cascade styles. Both style
and currentStyle
contain a CSSStyleDeclaration
object. TheInternet Explorer path is straightforward, and we're done coding queryCascade()
.
function queryCascade(element, property) { if (typeof getComputedStyle === "function") { return getComputedStyle(element, null)[property]; } else if (element.currentStyle) { return element.currentStyle[property]; } }
Now let's query a CSS property from the cascade. Then we have a couple that were calculated behind the scenes by Firefox:
function queryCascade(element, property) { if (typeof getComputedStyle === "function") { return getComputedStyle(element, null)[property]; } else if (element.currentStyle) { return element.currentStyle[property]; } } queryCascade(document.getElementById("adidas"), "width"); // "100px" queryCascade(document.getElementsByTagName("ul")[0], "width"); // "120px" queryCascade(document.getElementsByTagName("ul")[0], "height"); // "0px"
For the Adidas <a>
element, queryCascade()
returned the value from an explicit declaration in eight.css
. On the other hand, the values for the <ul>
element were calculated behind the scenes for us by Firefox. Note that insofar as the <ul>
is a block element, its width is set to that of the containing <div>
, which we set to "120px"
. On the other hand, since the <a>
elements within the <ul>
are absolutely positioned relative to the <div>
and the <li>
has a display value of inline, the <ul>
collapses to a height of "0px"
.
Here is the situation. Nowadays, CSS presentation and markup content are in separate files. Initially, the CSSStyleDeclaration
object in the style
member for an Element
node will be irrelevant, with just scores of ""
empty strings and other default values. On the other hand, the CSSStyleDeclaration
object representing cascade declarations is totally relevant but read-only. So if you do not know what rule or style sheet contains the declaration with the highest precedence, then what do you do? Typically, scripters read the cascade and write the inline style
attribute. That is to say, they query two separate CSSStyleDeclaration
objects. Obviously, if you know where to find the rule, then you simply read and write the CSSStyleDeclaration
object representing the rule's declaration block, like we did earlier in the day. But more often than not, you won't have that option.
Making the computed styles from the cascade read-only is a DOM failing. But to be fair, the standard predates the notion of putting markup, CSS, and JavaScript in separate layers by five years. Maybe someday the flaw will be fixed. But that day is far off and may never come at all. So, let's give the "read the cascade, write the attribute" thing a try.
Clear Firebug, but do not refresh Firefox because we want queryCascade()
to remain available in memory. Then save the <div>
to a variable named myDIV
. Pass myDIV
and "left"
to queryCascade()
, which will then return "40px"
. Convert that string to the number 40
by way of parseInt()
, and then add 10. Next, convert the sum of 50
to the string "50"
, and append "px"
. Finally, write that string, "50px"
, to the style
attribute. Click Run repeatedly until left
is set to "700px"
. JavaScript will move our running shoe menu to the right by 10 pixels each time you click Run.
If you have forgotten how parseInt()
works, flip back to Chapter 2. Datatype conversion is covered there, too.
function queryCascade(element, property) { if (typeof getComputedStyle === "function") { return getComputedStyle(element, null)[property]; } else if (element.currentStyle) { return element.currentStyle[property]; } } var myDIV = document.getElementById("running"); myDIV.style.left = parseInt(queryCascade(myDIV, "left")) + 10 + "px";
Verify your work with Figure 8-13.
Figure 8-13. Moving the interface to the right in 10-pixel increments with the help of queryCascade()
JavaScript animations typically work in this way – that is to say, by writing the style
attribute at regular intervals. Later in the book, we will do just that.
Now then, sometimes you will want to turn a style sheet on or off. Maybe you'll want to change the skin for an interface, for example. Doing so is straightforward and even works in our DOM dummy, Internet Explorer. Every style sheet embedded in a <style>
element or included by a <link>
element has a disabled
member (whichcomes from the CSSStyleSheet
interface, by the way).
Now do you remember the JavaScript datatype to convey on or off?
It's the same as the one that conveys yes or no.
Come on, you know this one.
Right, the booleans, true
and false
. The only tricky thing with the disabled
member is that it isn't named enabled
. In other words, true
means the style sheet is off, and false
means that the style sheet is on. Trust me, you will get this mixed up from time to time.
Refresh Firefox, and clear Firebug. Then disable eight.css
, entering and running the following helper function and verifying your work with Figure 8-14:
function toggleSheet(element) { var sheet = element.sheet || element.styleSheet; sheet.disabled = ! sheet.disabled; } toggleSheet(document.getElementById("spriteStyles"));
Our markup is totally unstyled now. No, I don't like it either. Click Run again to have Firefox reapply eight.css
.
At this point, there's no disabled
attribute in the markup for a <link>
or <style>
tag. disabled
is a member of an Element
node but not an attribute. Note that we covered difference between a member and an attribute in Chapter 7; flip back to that chapter if you have forgotten.
Inasmuch as disabled
is not a markup attribute, we can set its value to true
only with JavaScript. If JavaScript is not available, we cannot turn off a style sheet by calling toggleSheet()
. But what if we want a style sheet to be applied to a document only when JavaScript is available? Simple. We create and insert a new <link>
or <style>Element
node. However, like everything else with scripting CSS, there are Internet Explorer pitfalls. So, let's code a helper function to do the job.
Refresh Firefox, and clear Firebug. Then name the helper function addSheet
, defining two arguments:
tag
will contain the string "link"
or "style"
.
url
will contain the URL of the style sheet.
Next, pass tag
to document.createElement()
, saving the return value to a local variable named element
. Regardless of whether element
now contains a <link>
or <style>
, the type
attributes will be "text/css"
. Thus far, we have this:
function addSheet(tag, url) { var element = document.createElement(tag); element.type = "text/css"; }
Now append element
to childNodes
for <head>
by way of appendChild()
, which we covered in Chapter 7. We have to do this now for the remainder of the function to work cross-browser.
function addSheet(tag, url) { var element = document.createElement(tag); element.type = "text/css"; document.getElementsByTagName("head")[0].appendChild(element); }
In the event we passed "link"
for tag, set rel
to "stylesheet"
and href
to the string in url
. The browser will then apply the style sheet to the document. So for a <link>
, we're done.
function addSheet(tag, url) { var element = document.createElement(tag); element.type = "text/css"; document.getElementsByTagName("head")[0].appendChild(element); if (tag === "link") { element.rel = "stylesheet"; element.href = url; } }
Things are trickier if tag
contains "style"
. For Firefox, Safari, and Opera, we just cobble together an @import
directive and pass that to insertRule()
. For Internet Explorer, you might think to insert the directive with addRule()
. But you would be wrong. Internet Explorer has a separate method named addImport()
for inserting @import
directives. addImport()
works with two parameters:
The URL of the style sheet to import. Just pass in the URL for the first parameter; there's no need to cobble together an @import
directive.
The second parameter, which is optional, is the numeric index for where to insert the directive in imports
. Remember from earlier in the chapter that imports
is where Explorer puts @import
directives. That is to say, those are kept separate from the rule sets in rules
.
Let's code these two paths by way of a try catch
statement. The try
block will work in Firefox, Safari, and Opera but will generate an error in Internet Explorer, which will then go down the catch
path, merrily as can be. Our final code for addSheet()
is as follows:
function addSheet(tag, url) { var element = document.createElement(tag); element.type = "text/css";
document.getElementsByTagName("head")[0].appendChild(element); if (tag === "link") { element.rel = "stylesheet"; element.href = url; } else if (tag === "style") { try { element.sheet.insertRule("@import url(" + url + ")", 0); } catch (whyNot) { element.styleSheet.addImport(url); } } }
So that we can tangibly test addSheet()
, let's first delete the <link>
that includes eight.css
. Remember how to do so from the previous chapter? Yup, with removeChild()
. Just invoke removeChild()
on the <head>element
and pass in the <link>
, and Firefox will redisplay our document as unstyled markup, as Figure 8-15 illustrates.
function addSheet(tag, url) { var element = document.createElement(tag); element.type = "text/css"; document.getElementsByTagName("head")[0].appendChild(element); if (tag === "link") { element.rel = "stylesheet"; element.href = url; } else if (tag === "style") { try { element.sheet.insertRule("@import url(" + url + ")", 0); } catch (whyNot) { element.styleSheet.addImport(url); } } } document.getElementsByTagName("head")[0]. removeChild(document.getElementsByTagName("link")[0]);
Figure 8-15. Delete the <link>
element with removeChild()
, and Firefox redisplays the document as unstyled markup.
By the way, even though it may be obvious, note that passing a <link>
or <style>
to removeChild()
does the inverse of what addSheet()
does. So, removing a style sheet is simpler than adding one.
Now that our document is totally unstyled, we can tangibly test addSheet()
. Let's first try to include eight.css
with a new <link>
. Go ahead and delete the removeChild()
invocation. Then call addSheet()
like so, verifying your work with Figure 8-16.
function addSheet(tag, url) { var element = document.createElement(tag); element.type = "text/css"; document.getElementsByTagName("head")[0].appendChild(element); if (tag === "link") { element.rel = "stylesheet"; element.href = url; } else if (tag === "style") { try { element.sheet.insertRule("@import url(" + url + ")", 0); } catch (whyNot) { element.styleSheet.addImport(url); }
} } addSheet("link", "eight.css");
Did Firefox restyle your document with the blue sprite? Great. Now let's again remove the <link>
with removeChild()
. Then insert a <style>
with an @import
directive with the help of addSheet()
. Doing so restyles the document, too.
function addSheet(tag, url) { var element = document.createElement(tag); element.type = "text/css"; document.getElementsByTagName("head")[0].appendChild(element); if (tag === "link") { element.rel = "stylesheet"; element.href = url; } else if (tag === "style") { try { element.sheet.insertRule("@import url(" + url + ")", 0); } catch (whyNot) { element.styleSheet.addImport(url);
} } } document.getElementsByTagName("head")[0]. removeChild(document.getElementsByTagName("link")[0]);addSheet("style", "eight.css");
Sometimes you may want to embed a style sheet in a new <style>
element rather than including or importing one. Insofar as the newly minted <style>
is empty, the simplest way to embed a style sheet is to create a Text
node containing all the rules and then insert it into the <style>
with appendChild()
. It works fine for Firefox, Safari, and Opera, but not for Internet Explorer. There's a workaround, though. In Internet Explorer, styleSheet
has a cssText
member. Rather than create a Text
node from our string of CSS rules, we will simply assign the string to cssText
. With those brave words, let's code a helper function named embedSheet()
. This one works with one parameter, a string of CSS rules. The fork in the road where Firefox, Safari, and Opera and Internet Explorer part company will be formed from a try catch
statement:
function embedSheet(text) { var element = document.createElement("style"); element.type = "text/css"; document.getElementsByTagName("head")[0].appendChild(element); text = document.createTextNode(text); try { element.appendChild(text); } catch (whyNot) { element.styleSheet.cssText = text.data; } }
Now let's test embedSheet()
by passing in a couple of rules that will change the sprite from blue to fuchsia and move the interface to the right. Enter and run the following, and then verify your work with Figure 8-17:
function embedSheet(text) { var element = document.createElement("style"); element.type = "text/css"; document.getElementsByTagName("head")[0].appendChild(element); text = document.createTextNode(text); try { element.appendChild(text); } catch (whyNot) { element.styleSheet.cssText = text.data; } } embedSheet("ul.blue a {background-image:url(images/fuchsia.gif);} div#running {left:500px;}");
In this chapter, we explored a number of DOM interfaces for scripting CSS, while in Chapter 7 we explored the interfaces for scripting markup. Now you know how to do what JavaScript behaviors do. You can change the presentation or content of a document. Controlling when those changes occur is the topic of the next chapter. There we will explore event interfaces provided by DOM and Internet Explorer. See you there!
3.137.167.195