JavaScript Styling

Thus far we have been doing a lot with text, mostly because that is the least complicated thing JavaScript does. But now I'm going to teach you how to change how the page looks. Yep, we're getting into JavaScript styling.

JavaScript styling is what allows you to highlight words, change backgrounds, or even cause menus to appear or disappear, expand or collapse. The style property of an element is signified by the property name style, and all specific stylings are properties of that property.

JavaScript vs. CSS

The styling keywords are likely to be familiar, mostly because they come from CSS keywords. There are a few differences, however; properties that have hyphens in CSS are capitalized in JavaScript and the hyphens are removed. For example, list-style-type in CSS becomes listStyleType in JavaScript, and background-color in CSS becomes backgroundColor in JavaScript, and instead of a colon, as we have in CSS, we assign values with an equal sign in JavaScript. The values are all the same, however, and should be typed as strings in JavaScript.

There are other important differences between JavaScript and CSS:

For this reason, JavaScript is seldom used to style an entire webpage (though it can be done). Instead, the basic presentation is done through CSS and is overridden in specific instances with JavaScript.

Demonstrations

I think the best way to do this chapter is to be long on demonstration and short on explanation. I think you know enough about JavaScript that I don't need to reëxplain things. Please understand that I'm not trying to leave you in the dark by giving all this such short shrift. I've spent the last several chapters explaining various parts of JavaScript, so you've seen this all before.

Collapsing Menu

This one is actually quite easy. The style you're going to control is display. The keywords, as mentioned in Arranging The Page, are:

We're going to use block and none.

We want submenus to be hidden from the start. List items that have a hidden submenu will have a solid disc, those that have a visible submenu will have a circle.

This kind of styling we will have to set through JavaScript, since CSS 2.1 can't style an element according to its children.

First of all, the CSS and the HTML:

Starting Code For A Collapsing List
CSS
ul{list-style-type:circle;}
HTML
<ul id="JS-Style-Menu">
<li>Item 1</li>
<li>Item 2<ul>
<li>Item 2.1</li>
<li>Item 2.2</li>
<li>Item 2.3</li>
</ul></li>
<li>Item 3</li>
<li>Item 4</li>
<li>Item 5<ul>
<li>Item 5.1</li>
<li>Item 5.2</li>
<li>Item 5.3<ul>
<li>Item 5.3.1</li>
<li>Item 5.3.2</li>
<li>Item 5.3.3</li>
</ul></li>
<li>Item 5.4</li>
</ul></li>
</ul>

The list looks like this:

Before we go any further, there's one thing you have to be aware of. I could have included the following rule in my CSS:

Hiding All Sublists Through CSS
ul ul{display:none;}

However, if you do the CSS this way and the viewer has JavaScript turned off (and many of them do), there will be no way for the viewer to view the submenus. If I hide them through JavaScript, then if someone has JavaScript turned off, the submenus are never hidden and thus still useable (this is good practice).

The way I'm going to do hide the submenus through JavaScript is assign a list of li element nodes to a variable. Then I will use some JavaScript to single out li elements that are parent elements of ul elements, give them discs instead of circles for list styles and hide their respective submenus.

Hiding All Sublists Through JavaScript
var menu_items = document.getElementById("Menu").getElementsByTagName("li");
for(var l = 0; l < menu_items.length; l++){
if((menu_items[l].getElementsByTagName("ul").length) > 0){
menu_items[l].style.listStyleType = "disc";
menu_items[l].getElementsByTagName("ul")[0].style.display = "none";
}
}

What the if statement does is check to see if any li elements have descendant ul elements. If they don't, then the returned node list of ul elements has a length of 0. It is unwise to use if((menu_items[l].getElementsByTagName("ul").length) == 1){}, however; the item with a sub-submenu has two descendant ul elements.

So, now we have this:

Comparing the two pictures of the lists, you can tell that those with sublists are now the ones with discs. Now, to create a command that will make the submenus appear and change their list styles to a circle:

The Commands That Change The Style
menu_items[l].getElementsByTagName("ul")[0].style.display = "block";
menu_items[l].style.listStyleType = "circle";

Stating that only the first ul element will be displayed keeps the sub-submenus from being displayed as well.

Now to assign click events. We've got the loop already; let's use it.

Adding Click Events Through The For Loop
for(var l = 0; l < menu_items.length; l++){
if((menu_items[l].getElementsByTagName("ul").length) > 0){
menu_items[l].style.listStyleType = "disc";
menu_items[l].getElementsByTagName("ul")[0].style.display = "none";
menu_items[l].onclick = function(l){return function(){
menu_items[l].getElementsByTagName("ul")[0].style.display = "block";
menu_items[l].style.listStyleType = "circle";
}}(l);
}
}

Now only li elements with descendant ul elements will get the click events. Below, I've expanded the submenu and sub-submenu of Item 5, but not the submenu of Item 2.

But what if I wanted to hide them again? I could, of course, expand the click event with an if/else statement:

Using An If-Else Statement To Expand And Collapse Menu Items
menu_items[l].onclick = function(l){return function(){
if(menu_items[l].style.listStyleType == "disc"){
menu_items[l].getElementsByTagName("ul")[0].style.display = "block";
menu_items[l].style.listStyleType = "circle";
} else {
menu_items[l].getElementsByTagName("ul")[0].style.display = "none";
menu_items[l].style.listStyleType = "disc";
}
)}(l);

This would work perfectly well—except for one problem: Item 5.3 has a submenu of its own, and expanding that will cause submenu 5 to collapse. And what if the items contained links? Suddenly JavaScript gets tricky. You see, when you click on a child element of a list item, you're clicking on the list item as well.

Adding Another Mouse Event

You could add a special element that would allow a precise element to be clicked on. But possibly the easiest way to keep this from happening is to assign a different mouse event to close it—let's use ondblclick:

Adding A Different Mouse Event
menu_items[l].ondblclick = function(l){return function(){
menu_items[l].getElementsByTagName("ul")[0].style.display = "none";
menu_items[l].style.listStyleType = "disc";
})(l);

The final script looks like this:

Expand Through Click, Collapse Through Double-Click
var menu_items = document.getElementById("Menu").getElementsByTagName("li");
for(var l = 0; l < menu_items.length; l++){
if((menu_items[l].getElementsByTagName("ul").length) > 0){
menu_items[l].style.listStyleType = "disc";
menu_items[l].getElementsByTagName("ul")[0].style.display = "none";
menu_items[l].onclick = function(l){return function(){
menu_items[l].getElementsByTagName("ul")[0].style.display = "block";
menu_items[l].style.listStyleType = "circle";
}}(l);
menu_items[l].ondblclick = function(l){return function(){
menu_items[l].getElementsByTagName("ul")[0].style.display = "none";
menu_items[l].style.listStyleType = "disc";
})(l);
}
}

This does have the disadvantage of hiding the entire submenu of Item 5 when the submenu of Item 5.3 is double-clicked. Otherwise, this works pretty well.

The menu, after clicking

Adding A Special Element

If you wanted to add a special element to the list (say, button), you could assign the click event, which means you click on that element, rather than on the entire li element itself:

Adding Buttons
<ul id="JS-Style-Menu">
<li>Item 1</li>
<li><button>+</button> Item 2<ul>
<li>Item 2.1</li>
<li>Item 2.2</li>
<li>Item 2.3</li>
</ul></li>
<li>Item 3</li>
<li>Item 4</li>
<li><button>+</button> Item 5<ul>
<li>Item 5.1</li>
<li>Item 5.2</li>
<li><button>+</button> Item 5.3<ul>
<li>Item 5.3.1</li>
<li>Item 5.3.2</li>
<li>Item 5.3.3</li>
</ul></li>
<li>Item 5.4</li>
</ul></li>
</ul>

Because the button will let us know there's a submenu, I'm going to get rid of the list styling trick altogether. To save some typing, I'm also going to assign a few properties to the list items containing unordered lists that normally aren't there.

Using Said Buttons
var menu_items = document.getElementById("Menu").getElementsByTagName("li");
for(var l = 0; l < menu_items.length; l++){
if((menu_items[l].getElementsByTagName("button").length) > 0){
menu_items[l].Clicker = menu_items[l].getElementsByTagName("button")[0];
menu_items[l].SubMenu = menu_items[l].getElementsByTagName("ul")[0];
menu_items[l].SubMenu.style.display = menu_items[l].getElementsByTagName("none")[0];
menu_items[l].Clicker.onclick = function(l){return function(){
if(menu_items[l].Clicker.firstChild.data == "+"){
menu_items[l].SubMenu.style.display = "block";
menu_items[l].Clicker.firstChild.data = "-";
} else {
menu_items[l].SubMenu.style.display = "none";
menu_items[l].Clicker.firstChild.data = "+";
}
}}(l;
}
}
The menu with buttons

Dropdown Menus

The problem of clicks doesn't often occur in dropdown menus, because they normally use completely different mouse events: onmouseover and onmouseout. So here's a menu:

A menu

The HTML for this list is identical to the our previous menu, but the style sheet is quite different:

Style Sheet For Dropdown Menu
body, ul, li{
margin:0;
padding:0;
}
ul{list-style-type:none;}
#Menu{
position:absolute;
top:14px;
left:40px;
}
#Menu li{
position:absolute;
top:0;
border:1px solid black;
height:30px;
width:100px;
text-align:center;
line-height:30px;
background:white;
}
#li_1{left:0;}
#li_2{left:106px;}
#li_3{left:212px;}
#li_4{left:318px;}
#li_5{left:424px;}
#Menu ul{
display:none;
position:relative;
left:-1px;
top:1px;
}
#Menu ul li{
margin-top:-1px;
position:static;
}
#Menu ul ul{
left:100px;
top:-30px;
}

Yes, I know I automatically hide the submenus here. If this were a list of links, I'd have the submenus on the main items' respective pages.

The JavaScript code is nearly identical to the collapsing menu without the buttons as well, except for a few, minor adjustments:

JavaScript For Dropdown Menu
var menu_items = document.getElementById("Menu").getElementsByTagName("li");
for(var l = 0; l < menu_items.length; l++){
if((menu_items[l].getElementsByTagName("ul").length) > 0){
menu_items[l].onmouseover = function(l){return function(){menu_items[l].getElementsByTagName("ul")[0].style.display = "block";}}(l);
menu_items[l].onmouseout = function(l){return function(){menu_items[l].getElementsByTagName("ul")[0].style.display = "none";}}(l);
}
}

And it's, well, as simple as that:

Dropdown Menus.

The only difference between a dropdown menu and a flyout menu is the orientation of the main menu, really.

Tabbed Menus

Tabbed menus are a little more complex because they have to both hide one element and display another at the same time. So either you have to let your script know which element is showing, or hide all relevant elements before showing the one you want to display (I'll use the first method). Beyond that, it's the same old story all over again.

Below is the menu in question and the content it controls:

The Stylesheet Of The Tabbed Menu
<ul id="JS-Style-Menu">
<li id="JS-Style-li_1">Item 1</li>
<li id="JS-Style-li_2">Item 2</li>
<li id="JS-Style-li_3">Item 3</li>
<li id="JS-Style-li_4">Item 4</li>
<li id="JS-Style-li_5">Item 5</li>
</ul>
<div id="JS-Style-Headers">
<h2>Item 1</h2>
<h2>Item 2</h2>
<h2>Item 3</h2>
<h2>Item 4</h2>
<h2>Item 5</h2>
</div>

And below is a script that:

Setting Up The Loop
var menu_items = document.getElementById("Menu").getElementsByTagName("li");
var h2s = document.getElementsByTagName("h2");
var cur = 0;
for(var l = 0; l < menu_items.length; l++){
h2s[l].style.display = "none";
menu_items[l].onclick = function(l){return function(){
// Event Coding Goes here
}}(l);
}
h2s[cur].style.display = "block";

We want this event to do three things:

The Script Of The Tabbed Menu
var menu_items = document.getElementById("Menu").getElementsByTagName("li");
var h2s = document.getElementsByTagName("h2");
var cur = 0;
for(var l = 0; l < menu_items.length; l++){
h2s[l].style.display = "none";
menu_items[l].onclick = function(l){return function(){
h2s[cur].style.display = "none";
h2s[l].style.display = "block";
var cur = l;
}}(l);
}
h2s[cur].style.display = "block";

Below, I have clicked on each tab in turn:

The Illusion of Tabs

I know a lot of tabbed menus look like real tabs, the current one being on top. This is quite simply an optical illusion created by border manipulation. Let me show you what I mean. First, we have a style sheet that defines that all elements here are absolutely positioned. The property z-index is a one that says This layer is on top of that one. The menu has a z-index of 2, which places it on top of the h2 elements, which have a z-index of 1. No, I haven't mentioned that property before. The z-index properties are in bold.

Furthermore, the li elements have a background of opaque white and no bottom border. This will become very important very soon.

Because of the way this is styled, I set the id attributes of the li elements (the IDs are shown in red in the style sheet).

Styling For A Tabbed Bordered Menu
body, ul, li{
margin:0;
padding:0;
}
#Menu{
position:absolute;
list-style-type:none;
top:0;
left:20px;
margin:0;
padding:0;
}
#Menu li{
position:absolute;
vertical-align:top;
border:1px solid black;
border-bottom-width:0;
height:30px;
width:100px;
margin:0;
padding:0;
text-align:center;
line-height:30px;
top:20px;
background-color:white;
}
#li_1{left:20px;}
#li_2{left:121px;}
#li_3{left:222px;}
#li_4{left:323px;}
#li_5{left:424px;}
h2{
border:1px solid black;
width:600px;
padding:0 0 0 10px;
background-color:white;
}
#Headers{margin-top:80px;}

You might be noticing right now that a lot of successful JavaScript requires planning ahead when you're creating the (X)HTML and the CSS, or at least tinkering with those. Planning ahead is a good thing, and tinkering is often necessary.

The script is nearly identical, but take a look at what's been added.

Scripting For A Tabbed Bordered Menu
var menu_items = document.getElementById("Menu").getElementsByTagName("li");
var h2s = document.getElementsByTagName("h2");
var cur = 0;
for(var l = 0; l < menu_items.length; l++){
menu_items[l].onclick = function(l){return function(){
menu_items[cur].style.height = "30px";
h2s[cur].style.display = "none";
menu_items[l].style.height = "31px";
h2s[l].style.display = "block";
var cur = l;
}}(l);
h2s[l].style.position = "absolute";
h2s[l].style.top = "34px";
h2s[l].style.left = "10px";
h2s[l].style.display = "none";
}
h2s[cur].style.display = "block";
menu_items[cur].style.height = "31px";

Six lines in total have been added:

  • three lines, adjusting the height of two specific li items by a single pixel, either extending (and thus overlapping and hiding) or retracting (and thus revealing) the top border of the h2 element that is showing;
  • three lines that set the positioning of the h2 elements.
  • Tabbed Menu
  • Tabbed Menu
  • Tabbed Menu
  • Tabbed Menu
  • Tabbed Menu

Abracadabra.

You may wonder why I used JavaScript to absolutely position the h2 elements instead of CSS and why I bothered with giving the body element padding on top. The answer is simple: absolutely positioned elements can overlap other elements. So if someone had JavaScript turned off and I'd used CSS for the positioning, they'd see a mess of overlapping text. But with what I did, a viewer looking at my webpage would see this:

Certainly not as slick-looking as when JavaScript is turned on, but it's readable, and that is the point.