Monday, October 19, 2009

Section 11.2. Stylesheet Switching







11.2. Stylesheet Switching

Stylesheet switching is a concept that has been around for almost as long as stylesheets. It is another way in which developers make their applications feel more Windows-like by giving users control over the style of the page.

11.2.1. Creating the Stylesheets

When you're creating the ability to switch styles directly in an Ajax application, first you must craft the stylesheet in a way that makes it easy to switch. Unless you plan to do a lot of rework, you will have to break the CSS rules into multiple files. This method of style switching will also be easier if you set up the structure to support the different stylesheets that will be created. Figure 11-6 shows a simple structure for CSS files.

Figure 11-6. A possible structure for CSS files


Obviously, this isn't the only way to set up the structure. However, I've found that this structure makes it easy to quickly find the rules I'm looking for. This screen directory contains a separate file for the structure, font, and color (or theme) that will be used for the page. Inside this directory you can have two more directories: font-sizes and themes. The font-sizes directory will hold all the alternative CSS files for controlling the sizes the font can have, and the themes directory will hold any alternative color schemes the page might have.

You can find a slightly simpler setup in the print directory. This is because the developer will normally not want the end user to have as much control over how the page will be printed as she does how the pages are viewed on-screen. Therefore, there are no choices for font sizes or themes within the current directory. There are merely the three files for structure, font, and color, as there are in the screen directory.

First we'll look at the three main files in the screen and print directories so that you can have a better understanding of what each file will contain. Once these files are set up, we can turn our attention to the alternate stylesheet files:


screen.css

The screen.css file contains all the screen CSS style rules for the following property types: boxes and layout, lists, and text. The screen CSS file holds the rules for the structure of the page and is not interested in anything that has to do with fonts or colors.


fonts.css

The fonts.css file contains all the screen CSS style rules for the font properties. This file is used to control the default font family, sizes, weights, and so forth.


colors.css

The colors.css file contains all the screen CSS style rules for the color and background property types. In this file, you set all of the page's color attributes and background settings.

The following shows an example of what the structure.css file would contain:


body {
line-height: 1.5em;
margin: 0;
padding: 0;
}

a:hover {
text-decoration: none;
}

tr td {
border-style: solid;
border-width: 1px;
padding: 2px 4px;
}

a > img {
border-style: none;
}

p.required {
text-align: center;
}

.f_right {
float: right;
}

#bodyFooter {
clear: both;
padding: 3px;
text-transform: uppercase;
}




Here is an example of what the fonts.css file would contain:


body {
font-family: Arial, Helvetica, sans-serif;
font-size: 1em;
}

a:hover {
font-style: italic;
}

tr td {
font-size: .9em;
}

p.required {
font-weight: bold;
}

#bodyFooter {
font-size: .75em;
}


Finally, here is an example of what the colors.css file would contain:


body {
background: #fff url('../..//images/bodyBackground.png') no-repeat fixed 0 0;
color: #000;
}

a:hover {
background-color: transparent;
color: #0c0;
}

tr td {
background-color: transparent;
border-color: #000;
color: #000;
}

a > img {
vertical-align: middle;
}

p.required {
background-color: transparent;
color: #f00;
}

#bodyFooter {
background-color: #009;
color: #fff;
}




There is no difference between the content of the files in the print and screen directories. What is different is the unit of measure that is used. Screen files are more likely to use px and em units, for example, whereas print files are more likely to use pt, cm, or in. The difference is in the two types of units: relative and absolute.

The relative units that CSS supports are pixels (px), x-height (ex), relative size (em), and percentage (%). The absolute units that CSS supports are centimeters (cm), inches (in), millimeters (mm), points (pt), and picas (pc). The difference is that absolute units are assumed to be the same distance across all browsers, screen resolutions, and printer faces (one inch should be one inch wherever it is used). Relative units, on the other hand, may vary because of differences in browser rendering, screen resolutions, and printer faces.


11.2.2. Alternate Stylesheets

Now that the default files are defined, it is time to consider what alternatives the user may need. Of course, alternate stylesheets can be used for theme switching that has nothing to do with giving the user greater accessibility. In this case, the developer merely wants to give the user different options regarding how the page looks or flows. No matter what the intention, or what the alternate stylesheets are going to do to the page, the basics on how to switch the CSS files with JavaScript will be the same.

I touched on the basic structure of an alternate stylesheet link at the beginning of the chapter. Now we will take a closer look at what we need to make main and alternate stylesheet links. Consider the following:


<link type="text/css" rel="stylesheet" media="screen" title="medium"
href="screen/font-sizes/medium.css" />
<link type="text/css" rel="alternate stylesheet" media="screen" title="smaller"
href="screen/font-sizes/smaller.css" />
<link type="text/css" rel="alternate stylesheet" media="screen" title="larger"
href="screen/font-sizes/larger.css" />
<link type="text/css" rel="alternate stylesheet" media="screen" title="monochrome"
href="screen/themes/mono




In this example, there is one main stylesheet link and three alternative ones. You must place the alternate keyword in the row attribute of the <link> element. This not only tells the browser that the link is supposed to be the alternative so as not to break browser functionality, but it also allows for easier parsing to determine the alternative ones in JavaScript. Each link also contains a title attribute which, strictly speaking, is not necessary for our JavaScript code, but is another way to make sure we are grabbing the appropriate stylesheets. More important, the title attribute is necessary for the browser to recognize that the link is an alternative link.

You can provide three different types of stylesheets for the browser: persistent, preferred, and alternate. Persistent stylesheets use the keyword stylesheet in the rel attribute but have no title attribute set; preferred stylesheets use the keyword stylesheet in the rel attribute and have a title attribute set; and alternate stylesheets, as we just discussed, have the alternate keyword in the rel attribute and do have a title attribute set. Paul Sowden wrote a great article, "Alternative Style: Working with Alternate Style Sheets," for A List Apart in 2001 that explains this better (http://alistapart.com/stories/alternate/).


11.2.3. The Switching Object

We built some alternate stylesheets, and now we need to enable the user to switch between the different choices without having to rely on the functionality provided by the browser. So, if the user is going to rely on our application, we must provide the means to do the necessary switching.

It does not matter whether we provide the choices in a drop down or in a list, as long as there is an intuitive means to apply the switching functionality. The following example provides the user with a list of choices of alternate stylesheets:


<div id="styleChoicesContainer">
<ul id="styleChoicesList">
<li>style choices: </li>
<li>
<a href="setStyle.php?s=default"
onclick="return StyleSwitcher.setActive('default');">
default
</a>
</li><li>
<a href="setStyle.php?s=alternate1"
onclick="return StyleSwitcher.setActive('alternate1');">
alternate 1
</a>
</li><li>
<a href="setStyle.php?s=alternate2"
onclick="return StyleSwitcher.setActive('alternate2');">
alternate 2
</a>
</li><li>
<a href="setStyle.php?s=alternate3"
onclick="return StyleSwitcher.setActive('alternate3');">
alternate 3
</a>
</li><li>
<a href="setStyle.php?s=alternate4"
onclick="return StyleSwitcher.setActive('alternate4');">
alternate 4
</a>
</li>
</ul>
</div>




It would be easy to style this list for horizontal display with a little CSS. But this does nothing until we write functionality behind the list. Example 11-2 shows the JavaScript required for us to make the list functional.

Example 11-2. A simple style-switching object


/* Example 11-2. A simple style-switching object. */

/**
* This object, StyleSwitcher, contains all of the functionality to get and set an
* alternative style chosen by the user from a list provided by the application. It
* contains the following methods:
* - setActive(p_title)
* - getActive( )
*/
var StyleSwitcher = {
/**
* This method, setActive, takes the passed /p_title/ variable and sets the
* appropriate alternate stylesheet as the current enabled one.
*
* @param {String} p_title The title that is to be set as active.
* @member StyleSwitcher
* @return Returns false so that the click event will be ignored.
* @type Boolean
*/
setActive: function(p_title) {
/* Get a list of the <link> elements in the document */
var links = document.getElementsByTagName('link');

/*
* Loop through the list, setting the appropriate <link> elements to
* disabled, and set the <link> element with the title attribute equal to
* /p_title/ to active.
*/
for (var i = links.length; i > 0;) {
/* Get the current <link> element */
var iLink = links[i--];

/* Is this element an appropriate stylesheet to mark? */
if (iLink.getAttribute('rel').indexOf('style') != -1 &&
iLink.getAttribute('title')) {
iLink.disabled = true;
/* Is this element the one we are looking for? */
if (iLink.getAttribute('title') == p_title)
iLink.disable = false;
}
}
/* Set the cookie to the passed /p_title/ variable, and do not let it expire
* for one year.
*/
Cookie.set('appStyle', p_title, 365);
return (false);
},
/**
* This method, getActive, returns the current active stylesheet node (<link>
* element) in the document, provided that there is one to return.
*
* @member StyleSwitcher
* @return Returns the active stylesheet node, if one exists.
* @type Node
*/
getActive: function( ) {
/* Get a list of the <link> elements in the document */
var links = document.getElementsByTagName('link');

/*
* Loop through the list until the active stylesheet is located, then
* return it.
*/
for (var i = links.length; i > 0;) {
/* Get the current link element */
var iLink = links[i--];

/* Is this the currently active <link> element? */
if (iLink.getAttribute('rel').indexOf('style') != -1 &&
iLink.getAttribute('title') && !iLink.disabled)
return (iLink.getAttribute('title'));
}
return (null);
}
};




Now when the user clicks on one of the choices on our list, the active stylesheet will change to the corresponding <link> element located in our document's <head> element. Figure 11-7 and Figure 11-8 demonstrate what dynamic changing of stylesheets might look like.

Figure 11-7. The original page before switching styles


Figure 11-8. The same page once the styles have switched


11.2.4. Remembering the User's Selection

Our style switcher is fine, but it only changes the style for the user for the currently active session. The next time the user visits the page, the page will default to the stylesheet the developer chose, and not what the user had selected. We need a way to save the user's choice between browser sessions. And of course, the use of cookies will do the trick. Example 11-3 shows an easy cookie object that you can implement in a page.

Example 11-3. cookie.js: A simple cookie object


/**
* @fileoverview Example 11-3. cookie.js: A simple cookie object.
*
* This file, cookie.js, contains a simple cookie object that can be used to get and
* set a cookie as well as erase cookies and check to see if a given browser even
* supports cookies.
*/

/**
* This object, Cookie, is simply a mechanism to allow for easier access of the
* document.cookie object for the page. It contains the following methods:
* - set(p_name, p_value, p_expires)
* - get(p_name)
* - erase(p_name)
* - accept( )
*/
var Cookie = {
/**
* This method, set, creates a cookie with the name equal to /p_name/ with a
* value of /p_value/ that expires at the specified /p_expires/ should it
* exist, returning whether the cookie was created or not.
*
* @member Cookie
* @param {String} p_name The name for the cookie to be set.
* @param {String} p_value The value for the cookie to be set.
* @param {Float} p_expires The time before the cookie to be set expires.
* @return Returns whether the cookie was set or not.
* @type Boolean
*/
set: function(p_name, p_value, p_expires) {
/* The expires string for the cookie */
var expires = '';

/* Was an expires time sent to the method? */
if (p_expires != undefined) {
/* Get a base time for the expiration date */
var expirationDate = new Date( );

/* Set the expiration to one day times the passed /p_expires/ value */
expirationDate.setTime(expirationDate.getTime( ) + (86400000 *
parseFloat(p_expires)));
/* Create the expires string for the cookie */
expires = '; expires=' + expirationDate.toGMTString( );
}
return (document.cookie = escape(name) + '=' + escape(p_value || '') +
expires);
},
/**
* This method, get, returns the cookie with a name equal to the passed /p_name/
* variable if one exists.
*
* @member Cookie
* @param {String} p_name The name for the cookie to return.
* @return Returns the cookie data if it exists or /null/ otherwise.
* @type String
*/
get: function(p_name) {
/* Get the matching cookie */
var cookie = document.cookie.match(new RegExp('(^|;)\\s*' + escape(p_name)
+ '=([^;\\s]*)'));

return (cookie ? unescape(cookie[2]) : null);
},
/**
* This method, erase, removes the cookie with the passed /p_name/ variable from
* the document. It returns the erased cookie (i.e., null if erase succeeded,
* the cookie otherwise).
*
* @member Cookie
* @param {String} p_name The name for the cookie to erase.
* @return Returns the cookie after it is erased.
* @type Boolean | String
*/
erase: function(p_name) {
/* Get the cookie with the passed /p_name/ variable */
var cookie = Cookie.get(p_name) || true;

/*
* Set the cookie with the passed /p_name/ variable to an empty string, and
* make it expire
*/
Cookie.set(p_name, '', -1);
return (cookie);
},
/**
* This method, accept, tests to see if the browser accepts cookies and returns
* the results of this test.
*
* @member Cookie
* @return Returns whether the browser accepts cookies or not.
* @type Boolean
*/
accept: function( ) {
/* Can the test be accomplished using the browser's built-in members? */
if (typeof navigator.cookieEnabled == 'boolean')
return (navigator.cookieEnabled);
/* Attempt to set and erase a cookie and return the results */
Cookie.set('_test', '1');
return (Cookie.erase('_test') === '1');
}
};




We must incorporate this cookie object into the style-switching object from Example 11-2. It is not enough to just store the user's choice in a cookie. We must also provide a way to choose the user's choice from the cookie when the page first loads. Example 11-4 shows how we do this.

Example 11-4. Using cookies to store user choices incorporated in our original style switcher


/*
* Example 11-4. Using cookies to store user choices incorporated in our original
* style switcher.
*/

/**
* This object, StyleSwitcher, contains all of the functionality to get and set an
* alternate style chosen by the user from a list provided by the application. It
* contains the following methods:
* - setActive(p_title)
* - getActive( )
* - getPreferred( )
* - loadStyle( )

*/
var StyleSwitcher = {
/**
* This method, setActive, takes the passed /p_title/ variable and sets
* the appropriate alternate style sheet as the current enabled one. It then
* stores this choice into a cookie for future use.
*
* @member StyleSwitcher
* @param {String} p_title The title that is to be set as active.
* @return Returns false so that the click event will be ignored.
* @type Boolean
* @requires Cookie This method uses the Cookie object to store the
* user's selection.
* @see Cookie#set

*/
setActive: function(p_title) {
/* Get a list of the <link> elements in the document */
var links = document.getElementsByTagName('link');

/*
* Loop through the list, setting the appropriate <link> elements to
* disabled, and set the <link> element with the title attribute equal to
* /p_title/ to active.
*/
for (var i = links.length; i > 0;) {
/* Get the current <link> element */
var iLink = links[i--];

/* Is this element an appropriate stylesheet to mark? */
if (iLink.getAttribute('rel').indexOf('style') != -1 &&
iLink.getAttribute('title')) {
iLink.disabled = true;
/* Is this element the one we are looking for? */
if (iLink.getAttribute('title') == p_title)
iLink.disable = false;
}
}

/*
* Set the cookie to the passed /p_title/ variable, and do not let it expire
* for one year
*/
Cookie.set('appStyle', p_title, 365);

return (false);
},
/**
* This method, getActive, returns the current active stylesheet node (<link>
* element) for the page, provided that there is one to return.
*
* @member StyleSwitcher
* @return Returns the active stylesheet node, if one exists.
* @type Node
*/
getActive: function( ) {
/* Get a list of the <link> elements in the document */
var links = document.getElementsByTagName('link');

/*
* Loop through the list until the active stylesheet is located, then
* return it
*/
for (var i = links.length; i > 0;) {
/* Get the current <link> element */
var iLink = links[i--];

/* Is this the currently active <link> element? */
if (iLink.getAttribute('rel').indexOf('style') != -1 &&
iLink.getAttribute('title') && !iLink.disabled)
return (iLink.getAttribute('title'));
}
return (null);
},
/**
* This method, getPreferred, returns the preferred stylesheet title for the
* page, provided that there is one to return.
*
* @member StyleSwitcher
* @return Returns the preferred stylesheet title, if one exists.
*/
* @type String
getPreferred: function( ) {
/* Get a list of the <link> elements in the document */
var links = document.getElementsByTagName('link');

/*
* Loop through the list until the preferred stylesheet is located, then
* return it.
*/
for (var i = links.length; i > 0;) {
/* Get the current <link> element */
var iLink = links[i--];

/* Is this the preferred <link> element? */
if (iLink.getAttribute('rel').indexOf('style') != -1 &&
iLink.getAttribute('rel').indexOf('alt') == -1 &&
iLink.getAttribute('title'))
return (iLink.getAttribute('title'));
}
return (null);
},
/**
* This method, loadStyle, loads the stylesheet for the application,
* attempting to first get it from the cookie, and if not from there, then the
* preferred stylesheet for the page is selected instead.
*
* @member StyleSwitcher
* @requires Cookie This method uses the Cookie object to get the
* user's selection.
* @see Cookie#get
*/
loadStyle: function( ) {
/* Get the cookie, and extract the appropriate title to set active */
var cookie = Cookie.get('appStyle');
var title = ((cookie) ? cookie : this.getPreferred( ));

/* Set the active stylesheet for the page */
this.setActive(title);
}
};

try {
/* Load the style sheet for the page */
Event.observe(window, 'load', StyleSwitcher.loadStyle, false);
} catch (ex) {}












No comments:

Post a Comment