Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Beginning JavaScript With DOM Scripting And Ajax - From Novice To Professional (2006)

.pdf
Скачиваний:
80
Добавлен:
17.08.2013
Размер:
17.27 Mб
Скачать

136

C H A P T E R 5 P R E S E N T A T I O N A N D B E H A V I O R ( C S S A N D E V E N T H A N D L I N G )

peekaboo:function(e){

if(DOMhelp.cssjs('check',sc.ad,sc.hidingClass)){

DOMhelp.cssjs('remove',sc.ad,sc.hidingClass)

}else { DOMhelp.cssjs('add',sc.ad,sc.hidingClass)

}

DOMhelp.cancelClick(e);

}

}

DOMhelp.addEvent(window,'load',sc.init,false);.

The head of the HTML example is the following after the script has executed (you can test this in Firefox by selecting the whole document via Ctrl+A or Cmd+A and then right-clicking anywhere and choosing View Selected Source).

exampleStyleSheetChange.html after script execution (excerpt)

<head>

<meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <title>Example: Using dynamic classes</title>

<style type="text/css"> @import "lowlevel.css";

</style>

<script type="text/javascript" src="DOMhelp.js"></script>

<script type="text/javascript" src="styleSheetChange.js"></script> <link href="highlevel.css" rel="StyleSheet" type="text/css">

</head>

You might have encountered the dynamic changing of styles earlier. As early as 2001, socalled style switchers became fashionable. These are small page widgets that allow the user to choose a page’s look and feel by selecting a style from a given list. Modern browsers have this option built in—in Firefox, for example, you can choose View Page Style and get all the available styles to select from. For MSIE, JavaScript developers came up with a clever trick—if you set the disabled attribute of a link element, the browser would not apply it. So all you need to do is to loop through all the link elements of the document and disable all but the chosen one.

The demo exampleStyleSwitcher.html shows how this is done. In the HTML, you define a main style sheet and alternate style sheets for large print and high contrast.

exampleStyleSwitcher.html (excerpt)

<link href="demoStyles.css" title="Normal" rel="stylesheet" type="text/css">

<link href="largePrint.css" title="Large Print" rel="alternate stylesheet" type="text/css">

<link href="highContrast.css" title="High Contrast" rel="alternate stylesheet" type="text/css">

C H A P T E R 5 P R E S E N T A T I O N A N D B E H A V I O R ( C S S A N D E V E N T H A N D L I N G )

137

The script is not complex. You loop through all LINK elements in the document and for each one determine whether its attribute is either stylesheet or alternate stylesheet. You create a new list with links pointing to a function that disables all but the currently chosen style sheet and add this list to the document.

You start with two properties: one to store the ID of the “style menu” to allow for CSS styling, and one to store a label to show as the first list item preceding all the available styles.

styleSwitcher.js

switcher={

menuID:'styleswitcher', chooseLabel:'Choose Style:',

An initialization method called init() creates a new HTML list and adds a list item with the label as text content. You set the ID of the list to the one defined in the property.

styleSwitcher.js (continued)

init:function(){

var tempLI,tempA,styleTitle;

var stylemenu=document.createElement('ul'); tempLI=document.createElement('li'); tempLI.appendChild(document.createTextNode(switcher.chooseLabel)); stylemenu.appendChild(tempLI);

stylemenu.id=switcher.menuID;

You loop through all the LINK elements in the document. For each element, test for the value of its rel attribute. If the value is neither stylesheet nor alternate stylesheet, skip this LINK element. This is necessary to avoid other alternative content offered via the LINK tags—like an RSS feed—from being disabled.

styleSwitcher.js (continued)

var links=document.getElementsByTagName('link'); for(var i=0;i<links.length;i++){

if(links[i].getAttribute('rel')!='stylesheet' && links[i].getAttribute('rel')!='alternate stylesheet'){

continue;

}

Create a new list item with a link for each style and set the text value of the link to the value of the LINK element’s title attribute. Set a dummy href attribute to make the link appear as a link; otherwise the user may not recognize the new link as an interactive element.

styleSwitcher.js (continued)

tempLI=document.createElement('li');

tempA=document.createElement('a');

styleTitle=links[i].getAttribute('title');

138

C H A P T E R 5 P R E S E N T A T I O N A N D B E H A V I O R ( C S S A N D E V E N T H A N D L I N G )

tempA.appendChild(document.createTextNode(styleTitle));

tempA.setAttribute('href','#');

Apply an event handler to the link that triggers the setSwitch() method and sends the link itself as a parameter via the this keyword. You can then continue to add the new list items to the menu list and append the list to the document body when the loop is complete.

styleSwitcher.js (continued)

tempA.onclick=function(){

switcher.setSwitch(this);

}

tempLI.appendChild(tempA);

stylemenu.appendChild(tempLI);

}

document.body.appendChild(stylemenu);

},

In the setSwitch() method, you’ll retrieve the link that was activated as the parameter o. Loop through all LINK elements, and test each to see whether the title attribute is the same as the text content of the link (you can safely read the text via firstChild.nodeValue without testing for the node type, as you generated the links). If the title is different, set the disabled property of the LINK to true; and if it is the same, set disable to false and the rel attribute to stylesheet instead of alternate stylesheet. Then stop the link from being followed by returning false.

styleSwitcher.js (continued)

setSwitch:function(o){

var links=document.getElementsByTagName('link'); for(var i=0;i<links.length;i++){

if(links[i].getAttribute('rel')!='stylesheet' && links[i].getAttribute('rel')!='alternate stylesheet'){

continue;

}

var title=o.firstChild.nodeValue; if(links[i].getAttribute('title')!=title){

links[i].disabled=true;

}else { links[i].setAttribute('rel','stylesheet'); links[i].disabled=false;

}

}

return false;

}

}

You can test the functionality by opening exampleStyleSwitcher.html in a browser that has JavaScript and CSS enabled.

C H A P T E R 5 P R E S E N T A T I O N A N D B E H A V I O R ( C S S A N D E V E N T H A N D L I N G )

139

Paul Sowden pioneered this trick in 2001 at Alistapart.com with his article “Alternative Style: Working With Alternate Style Sheets” (http://www.alistapart.com/articles/alternate/). As Netscape 4 (remember this was 2001) did not support this method properly, Daniel Ludwin came up with “A Backward Compatible Style Switcher” in 2002 (http://www.alistapart.com/ articles/n4switch/), which used document.write instead (a bit of a step backwards in terms of coding style).

Following comments about the inaccessibility of JavaScript style switchers, Chris Clark moved the trick server side with his “Build a PHP Switch” article (http://www.alistapart.com/ articles/phpswitch/) in 2002. This method is more stable, but it means you have to reload the page to apply a new style.

A lot of variants of the same idea came up in between, and in 2005, Dustin Diaz took up the idea and mixed the stability of the PHP switcher with the slickness of a JavaScript enhanced interface in his “Unobtrusive Degradable Ajax Style Sheet Switcher” (http://24ways.org/ advent/introducing-udasss), which uses Ajax to bridge the gap.

Style switchers can be a useful feature, especially when you offer styles that might help users overcome problems like poor eyesight such as larger fonts or a higher contrast between foreground and background. On the other hand, they can be quite pointless eye candy if you use them exclusively for the sake of offering different styles.

This evolution of the style switcher idea shows that JavaScript solutions are never set in stone, but need testing in “the real world” and feedback from users and other developers to be really applicable in a production environment or a live site. If you surf the web these days, you’ll see many “experimental” scripts promising a lot but on closer inspection turning out to be slow, unstable, or just a neat trick that could be done a lot better with another technology. Just because we can do anything in JavaScript doesn’t mean we should.

Easing the Maintenance

Keeping the whole look and feel out of your scripts and inside the style sheet (and thereby keeping it the responsibility of the CSS designer) is just half the battle. During maintenance of a project, the CSS class names might have to change—for example, to support a certain back end or Content Management System (CMS). One example would be Adobe’s Contribute (a lightweight CMS that allows for WYSIWYG editing of web sites), which—in a standard configu- ration—needs classes that should not show up in the editor’s style picker to start with mmhide_. Therefore, it is important to make it easy for the designer to change the names of your dynamically applied classes. The most basic trick is to keep the class names in their own variables or parameters. You’ve done that in the earlier examples already. You could have applied the class names directly:

sc={

init:function(){

// Check for DOM and apply a class to the body if it is supported if(!document.getElementById || !document.createElement){return;} DOMhelp.cssjs('add',document.body, 'dynamic'); sc.head=document.getElementsByTagName('h3')[0]; if(!sc.head){return;}

sc.ad=DOMhelp.closestSibling(sc.head,1); DOMhelp.cssjs('add',sc.ad, 'hide');

var t=DOMhelp.getText(sc.head);

140

C H A P T E R 5 P R E S E N T A T I O N A N D B E H A V I O R ( C S S A N D E V E N T H A N D L I N G )

var collapseLink=DOMhelp.createLink('#',t); sc.head.replaceChild(collapseLink,sc.head.firstChild); DOMhelp.addEvent(collapseLink,'click',sc.peekaboo,false) collapseLink.onclick=function(){return;} // Safari fix

},

peekaboo:function(e){

if(DOMhelp.cssjs('check',sc.ad,sc.hidingClass)){

DOMhelp.cssjs('remove',sc.ad,sc.hidingClass) } else {

DOMhelp.cssjs('add',sc.ad,sc.hidingClass)

}

DOMhelp.cancelClick(e);

}

}

DOMhelp.addEvent(window,'load',sc.init,false);

Instead, you moved them out of the methods as properties of the main object and commented them to allow those who don’t know JavaScript to change the class names without endangering the quality or functionality of your methods:

sc={

// CSS classes

 

hidingClass:'hide',

// Hide elements

DOMClass:'dynamic',

// Indicate DOM support

init:function(){

if(!document.getElementById || !document.createElement){return;} DOMhelp.cssjs('add',document.body,sc.DOMClass); sc.head=document.getElementsByTagName('h3')[0]; if(!sc.head){return;}

sc.ad=DOMhelp.closestSibling(sc.head,1);

DOMhelp.cssjs('add',sc.ad,sc.hidingClass); var t=DOMhelp.getText(sc.head);

var collapseLink=DOMhelp.createLink('#',t); sc.head.replaceChild(collapseLink,sc.head.firstChild); DOMhelp.addEvent(collapseLink,'click',sc.peekaboo,false); collapseLink.onclick=function(){return;} // Safari fix

},

peekaboo:function(e){ // More code snipped

}

}

DOMhelp.addEvent(window,'load',sc.init,false);

For smaller scripts and projects that don’t have many different JavaScript includes, this is enough—if accompanied by some documentation on the matter. If you have a lot of dynamic classes scattered over several documents, or you are rather paranoid about

C H A P T E R 5 P R E S E N T A T I O N A N D B E H A V I O R ( C S S A N D E V E N T H A N D L I N G )

141

noncoders changing your code, you could use a separate JavaScript include file containing an object called CSS with all the classes as parameters. Give it an obvious file name like cssClassNames.js, and document its existence in the project documentation.

cssClassNames.js

css={

//Hide elements hide:'hide',

//Indicator for support of dynamic scripting

//will be added to the body element supported:'dynamic'

}

You can apply it to the document just like any other of the scripts in use:

exampleDynamicStylingCSSObject.html

<head>

<meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <title>Example: Importing class names from a CSS names object</title> <style type="text/css">

@import "dynamicStyling.css"; </style>

<script type="text/javascript" src="DOMhelp.js"></script>

<script type="text/javascript" src="cssClassNames.js"></script>

<script type="text/javascript" src="dynamicStylingCSSObject.js"></script> </head>

The practical upshot of this method is that you don’t have to come up with parameter names for the different CSS class names (which normally contain “class” and are therefore confusing for programmers). Instead, use the following:

dynamicStylingCSSObject.js

sc={

init:function(){

if(!document.getElementById || !document.createElement){return;}

DOMhelp.cssjs('add',document.body,css.supported);

sc.head=document.getElementsByTagName('h3')[0];

if(!sc.head){return;}

sc.ad=DOMhelp.closestSibling(sc.head,1);

DOMhelp.cssjs('add',sc.ad,css.hide);

142

C H A P T E R 5 P R E S E N T A T I O N A N D B E H A V I O R ( C S S A N D E V E N T H A N D L I N G )

var t=DOMhelp.getText(sc.head);

var collapseLink=DOMhelp.createLink('#',t); sc.head.replaceChild(collapseLink,sc.head.firstChild); DOMhelp.addEvent(collapseLink,'click',sc.peekaboo,false); collapseLink.onclick=function(){return;} // Safari fix

},

peekaboo:function(e){ // More code snipped

}

}

DOMhelp.addEvent(window,'load',sc.init,false);

In this example, the cssClassNames.js file uses object literal notation. You could go even further and get rid of the comments if you use JSON (http://www.json.org/), which is a format for transferring data from one program or system to another. You will hear more about JSON and its merits in Chapter 7. For now, it is enough to notice that JSON allows you to make the file with the class names a lot more readable for humans:

cssClassNameJSON.js

css={

'hide elements' : 'hide',

'dynamic scripting enabled' : 'dynamic'

}

Instead of the attribute notation used earlier, you’ll now have to read the data as if it were an associative array:

dynamicStylingJSON.js

sc={

init:function(){

if(!document.getElementById || !document.createElement){return;}

DOMhelp.cssjs('add',document.body,

css['dynamic scripting enabled']);

sc.head=document.getElementsByTagName('h3')[0];

if(!sc.head){return;}

sc.ad=DOMhelp.closestSibling(sc.head,1);

DOMhelp.cssjs('add',sc.ad,css['hide elements']);

C H A P T E R 5 P R E S E N T A T I O N A N D B E H A V I O R ( C S S A N D E V E N T H A N D L I N G )

143

var t=DOMhelp.getText(sc.head);

var collapseLink=DOMhelp.createLink('#',t); sc.head.replaceChild(collapseLink,sc.head.firstChild); DOMhelp.addEvent(collapseLink,'click',sc.peekaboo,false); collapseLink.onclick=function(){return;} // Safari fix

},

peekaboo:function(e){ // More code snipped

}

}

DOMhelp.addEvent(window,'load',sc.init,false);

It is really up to you whether you want to go that far to separate presentation from behavior, but depending on the complexity of the project and the knowledge of the maintenance staff, it might just prevent a lot of avoidable errors.

Overcoming CSS Support Problems

In the last years, CSS has become more and more important for web development. Complex nested table layouts, on the decline, are being replaced by lightweight CSS layouts. Furthermore, CSS solutions for effects that were traditionally handled by JavaScript, like image rollovers and foldout menus, became fashionable and gave the design community a new hope for being able to create slick, interactive interfaces without having to understand JavaScript.

The problem that many CSS developers had to face sooner or later was lack of support by legacy browsers or hard-to-understand issues with newer browsers. As CSS is not a programming language that allows for looping, conditions, or testing of objects before you try to apply them, you need to rely both on CSS and JavaScript to support some effects. A lot is possible in CSS2 and CSS3 using pseudo-classes, generated content, and CSS hacks to exclude certain browsers, but you just cannot rely on them being supported—especially with CSS-problematic browsers like MSIE 6 still going strong, if not being the market lead. CSS hacks also tend to have the same problem browser sniffing has: when a new browser comes out, you’ll have to check whether it still obeys the rules the hack demands. Right now, the beta versions of MSIE 7 require a lot of CSS developers to unhack their previous code.

A lot of very interesting CSS-only concepts counteract these issues by relying on a JavaScript fallback to support browsers like MSIE 6.

Multiple Columns with the Same Height

One of the most annoying things about CSS layouts for designers who only dealt with table layouts before is that, if you use CSS float techniques for columns, they don’t have the same height, as shown in Figure 5-3.

144

C H A P T E R 5 P R E S E N T A T I O N A N D B E H A V I O R ( C S S A N D E V E N T H A N D L I N G )

Figure 5-3. The multiple-column height problem

Let’s start with a list of news items, each containing a heading, a “teaser” paragraph, and a “more” link.

exampleColumnHeightIssue.html (with dummy content)

<ul id="news"> <li>

<h3><a href="news.php?item=1">News Title 1</a></h3> <p>Description 1</p>

<p class="more"><a href="news.php?item=1">more link 1</a></p> </li>

<li>

<h3><a href="news.php?item=2">News Title 2</a></h3> <p>Description 2</p>

<p class="more"><a href="news.php?item=2">more link 2</a></p> </li>

<li>

<h3><a href="news.php?item=3">News Title 3</a></h3> <p>Description 3</p>

<p class="more"><a href="news.php?item=1">more link 3</a></p> </li>

<li>

<h3><a href="news.php?item=1">News Title 1</a></h3> <p>Description 4</p>

<p class="more"><a href="news.php?item=4">more link 4</a></p> </li>

</ul>

C H A P T E R 5 P R E S E N T A T I O N A N D B E H A V I O R ( C S S A N D E V E N T H A N D L I N G )

145

If you now apply a style sheet that floats the list items and the main list to the left and set some more text and layout styles, you get a multicolumn layout. The CSS to achieve this is pretty basic:

columnHeightIssue.css

#news{

width:800px;

float:left;

}

#news li{ width:190px; margin:0 4px; float:left; background:#eee;

}

#news h3{ background:#fff; padding-bottom:5px;

border-bottom:2px solid #369;

}

#news li p{ padding:5px;

}

As you can see in the example, each column has a different height, and neither the paragraphs nor the “more” links are in the same position. This makes the design look uneven and can confuse the reader. There might be a CSS way to fix this issue (I am always impressed what kind of hacks and workarounds people find), but let’s use JavaScript to work around this problem.

The following script—called in the HEAD of the document, will fix the problem:

fixColumnHeight.js—used in exampleFixedColumnHeightIssue.html

fixcolumns={

highest:0,

moreClass:'more',

init:function(){

if(!document.getElementById || !document.createTextNode){return;} fixcolumns.n=document.getElementById('news'); if(!fixcolumns.n){return;}

fixcolumns.fix('h3');

fixcolumns.fix('p');

fixcolumns.fix('li');

},