InternotesSharing Web Development Techniques

Blossoms

Toggling Attributes or Classes

[toc]


An important technique is simply to be able to turn property on or off. Once you have done this, you can perform a lot of additional magic using CSS.

Here are some examples of what you could do:

  • Change the appearance of an element
  • show or hide a related element
  • implement drop-down menus

The Basic Technique

There are two main techniques you can use:

  • change a class
  • change an arbitrary attribute

We will look at changing classes later. The technique will be the same, but the implementation will be slightly different.

Attributes

Attributes make their presence felt in HTML, CSS and JavaScript.

HTML attributes represent properties assigned to HTML elements. Some, but not all, are also directly exposed in JavaScript as element properties. Those which are not can be manipulated using special attribute methods; these methods can also manipulate attributes which are directly exposed.

CSS can use attributes as part of the selector. Modern browsers have more flexibility with the type of selector, but the basic ability has been present in all browsers for a very long time.

You can also make up your own attribute names. You can do so using HTML or JavaScript.

If you make up an attribute in HTML, remeber:

  • Your HTML may not pass validation
  • Don’t expect the browser to have any idea what to do with it.

If you make up an attribute in JavaScript, you won’t have to worry about validation problems, and you probably have your own plans for what to do with it.

CSS will select on any nominated attribute, whether it’s made up or not.

HTML5 does allow a special group of made up attributes. Attributes beginning with data- will:

  • pass HTML validation
  • appear to JavaScript in a special collection.

HTML

In HTML, and attribute is a property assigned in the opening tag:

<element attribute> … </element>
<element attribute="value"> … </element>

An attribute may have a value or it may not, depending on its role. An attribute without a value is sometimes referred to as a boolean attribute — just having it there switches a property on.

In XHTML, all attrubutes must have a value, even if it’s a boolean attribute. In this case, you would need to give it a dummy value. Typically, this value is the name of the attribute itself:

<element attribute="attribute"> … </element>

CSS

In CSS, you can select for an attribute using an attribute selector:

element[attribute] {            /*  attribute exists */

}
element[attribute="value"] {    /* attribute has a value */

}

Modern CSS has more variations on attribute selectors.

Now, there are two important things to note about attributes and CSS:

  • Attributes don’t have to be real attributes to work: you can make up any arbitrary attribute name you like; however, don’t expect the browser to know what else to do with it.
  • As with any CSS, you can use the attribute selector in combination with other selectors. In particular:
element[attribute]+something {  /*  something is after this element */

}
element[attribute]>something {  /* something is inside this element */

}

JavaScript

In JavaScript, there are four functions which work with attributes:

function Meaning
element.hasAttribute(attribute) Test whether the element has the attribute
element.getAttribute(attribute) Read the value of the attribute
element.setAttribute(attribute,value) Set the attribute to this value
element.removeAttribute(attribute) Remove the attribute from the element

Unfortunately there should have been a fifth. JavaScript does not have a toggleAttribute function, so we will have to write our own if that’s what we want:

function toggleAttribute(element,attribute,value) {
    if(!element || !attribute) return;
    if(value===undefined) value=true;
    if(!element.hasAttribute(attribute)) element.setAttribute(attribute,value);
    else element.removeAttribute(attribute);
}

Note:

  • It is considered bad form to tamper with the Element prototype, so this function is called differently.
  • setAttribute requires a value, even if it is a boolean attribute; for this, we will simply set it to true.

Different Behaviours

We will write some code to toggle attributes for one or more elements. However, when it comes to mutiple elements, there are two main possibilities.

  • Any number of elements may be on or off, from none to all. This is like multiple checkboxes in a form, and we will refer to this as checkbox behaviour.
  • Only one element at a time may be on; selecting another will deselect the current element. This is similar to a group of radio buttons in a form, and we will refer to this as radio button behaviour.

Unlike radio buttons in a form, we will also allow the possibility deselecting all elements. This is not normally possible without JavaScript, but that’s why we’re here.

A Basic Checkbox Attribute Toggle

We will write a function which will toggle Attributes on one or more elements. The function will take this form:

function doCheckbox(selector,attribute,selected) {

}

The parameters are:

  • selector: a CSS-style selector to find elements using document.querySelector
  • attribute: the name of the attribute to toggle
  • selected: an optional index to set the attribute of one of the elemements.

The first step is to attach an onclick event listener to the element(s) to be enabled:

function doCheckbox(selector,attribute,selected) {
    var elements=document.querySelectorAll(selector);
    for(let i=0;i<elements.length;i++) elements[i].onclick=toggle;
    function toggle(event) {

    }
}

Toggling an attribute is similar to the function described previously:

  • if the element has the attribute, remove it
  • else, set the attribute
    function toggle(event) {
        if(this.hasAttribute(attribute)) this.removeAttribute(attribute);
        else this.setAttribute(attribute,true);
    }

In an event handler, this is the element which has triggered the function.

The final code is:

function doCheckbox(selector,attribute,selected) {
    var elements=document.querySelectorAll(selector);
    for(let i=0;i<elements.length;i++) elements[i].onclick=toggle;
    function toggle(event) {
        if(this.hasAttribute(attribute)) this.removeAttribute(attribute);
        else this.setAttribute(attribute,true);
    }
}

Testing the Toggle

Turning an attribute on and off is, of itself, pointless, unless the attribute has its own special meaning.

In this example, we will use CSS to show and hide elements which come after a toggled element.

The basic HTML will look like this:

<div class="checkbox">
    <h2>One</h2>
    <div>Item One</div>
    <h2>Two</h2>
    <div>Item Two</div>
    <h2>Three</h2>
    <div>Item Three</div>
    <h2>Four</h2>
    <div>Item Four</div>
</div>
  • The container will have a class, for want of a better name, of checkbox to indicate the behaviour of its contents.
  • Each h2 element will be used to show or hide the following div element.

A simple CSS ruleset might be as follows:

div.checkbox>div {  /* initially hide all inner div elements */
    display: none;
}
h2[selected] {
    color: red;
}
h2[selected]+div {  /*  show div after selected h2 */
    display: block;
}
  • By default, the inner div elements will be hidden (display: none, which also removes the space).
  • When the h2 element, the next div will show or hide by toggling its 'display' property between block and none.
  • An attribute name of selected will be used. It could have been anything.
  • The h2 element will also change colour, only to show that it is working.

The JavaScript

All that is needed is to run the function when the document is loaded:

document.addEventListener('DOMContentLoaded',function() {
    doCheckbox('div.checkbox>h2','selected');
},false);

CSS3 Transitions

Using CSS3, you can transition the display of the inner div element. However, you cannot do this with the display property, since you can only transition properties with numeric values.

The following CSS will forego the display property, and, instead, show or hide the element using the max-height and opacity properties. Both of these have numeric values, so they can be transitioned.

The max-height will expand to show the content, or shrink to hide it. It doesn’t matter if it’s more than the content, as it won’t grow any further. The opacity is just for effect.

div.checkbox>div {
    overflow: hidden;
    max-height: 0em;
    opacity: 0;
    transition: max-height .5s, opacity .5s;
}
h2[selected]+div {
    max-height: 4em;
    opacity: 1;
}

The only thing to note here is that using max-height is a workaround. It must be set to a fixed value (4em in this case), so you will need to adjust this according your requirements:

  • The value must be at least large enough for the largest of the inner div elements.
  • If it is larger than an individual inner div, as it will be in some cases, then there will appear to be a small delay before the max-height begins to close in on the content.

Pre-Selecting a Single Item

Often, you want to start with one of the elements selected. For this we will use the selected parameter.

The value of selected must be a number, from 0 to the last index. If it is not, we will ignore it. In particular, if it is omitted, it will be undefined; you can also set it to null to deliberately ignore it.

if(typeof selected=='number' && selected>-1 && selected <elements.length)
    elements[selected].setAttribute(attribute,true);

The new version of the function is:

function doCheckbox(selector,attribute,selected) {
    var elements=document.querySelectorAll(selector);
    for(let i=0;i<elements.length;i++) elements[i].onclick=toggle;
    if(typeof selected=='number' && selected>-1 && selected<elements.length)
        elements[selected].setAttribute(attribute,true);
    function toggle(event) {
        if(this.hasAttribute(attribute)) this.removeAttribute(attribute);
        else this.setAttribute(attribute,true);
    }
}

We can test this with the following variation:

document.addEventListener('DOMContentLoaded',function() {
    doCheckbox('div.checkbox>h2','selected',1); //  pre-select the second item
},false);

A Basic Radio Button Attribute Toggle

The checkbox technique above toggles individual elements with no regard for others. If we want to apply the Radio Button approach of only selecting a single element:

  • We will need to define a group of elements
  • We will need to track the currently selected element

Real Radio Buttons in forms tend to lock you in once you have selected one — you can no longer de-select them all. We can, however allow the user to de-select all all of the selected elements:

  • Optionally, if an element is already selected we can choose to de-select it.

The process will involve tracking the currently selected element.

The JavaScript

The outline begins in a similar fashion to the checkbox version:

function doRadio(selector,attribute,selected,total) {
    var elements=document.querySelectorAll(selector);
    for(let i=0;i<elements.length;i++) elements[i].onclick=select;
    if(typeof selected=='number' && selected>-1 && selected<elements.length)
        elements[selected].setAttribute(attribute,true);
    function select() {

    }
}

The additional parameter, total will determine whether it’s OK to deselect all of the elements.

To track the currently selected element, we will include another variable current. It will be set to the selected element. If the selected parameter is true, we will also need to set current to the initially selected element:

function doRadio(selector,attribute,selected,total) {
    //  …
    var current=undefined;
    //  …
    if(typeof selected=='number' && selected>-1 && selected<elements.length) {
        elements[selected].setAttribute(attribute,true);
        current=elements[selected];
    }
    //  …
}

The rest comes in two parts.

First, if the selected element is different to the new element, we will need to deselect the currently selected element, if any. Then, we will set the current variable to the new element, and select it:

function doRadio(selector,attribute,selected,total) {
    //  …
    function select() {
        if(this==current) {                             //  same?

        }
        if(current) current.removeAttribute(attribute); //  deselect, if any
        current=this;                                   //  new selected
        this.setAttribute(attribute,true);              //  select
    }
}

The Second part will determine what happens if you select the element already selected. This will depend on whether you want to be able to deselect all elements.

If total is true, we will deselect the element. Otherwise we will ignore it. In both cases, we will exit the function.

function doRadio(selector,attribute,selected,total) {
    //  …
    function select() {
        if(this==current) {
            if(total) current=current.removeAttribute(attribute);           return;
        }
        //  …
    }
}

The finished function is below:

function doRadio(selector,attribute,selected,total) {
    var elements=document.querySelectorAll(selector);
    var current=undefined;
    for(let i=0;i<elements.length;i++) elements[i].onclick=select;
    if(typeof selected=='number' && selected>-1 && selected<elements.length) {
        elements[selected].setAttribute(attribute,true);
        current=elements[selected];
    }
    function select() {
        if(this==current) {
            if(total) current=current.removeAttribute(attribute);
            return;
        }
        if(current) current.removeAttribute(attribute)
        current=this;
        this.setAttribute(attribute,true);
    }
}

Toggling with Classes

In principle, you could to the same thing with classes. Traditionally, you could set a class as follows:

element.className='something';  //  set the class
element.className='';           //  remove the class

The property is renamed to className to avoid a conflict with the class reserved word in JavaScript.

The problem with this approach is that elements may have multiple classes, separated by a space. If you set or remove the class this way, you will lose all other classes in the process.

classList

Modern JavaScript implements classList, which is an object representing all of the classes. This object has the following methods:

  • .classList.add(class[,class])
  • .classList.remove(class[,class])
  • .classList.item(number)
  • .classList.toggle(class[,condition])
  • .classList.contains(class)

Note that this includes a toggle method which will greatly simplify the checkbox version.

Modified Checkbox Function

This is a matter of replacing the attribute methods with classList methods:

  • to set a class use .classList.add()
  • to toggle a class use .classList.toggle()
  • we will also use className as the parameter to avoid conflict with the class reserved word

The addition of a toggle method simplifies the toggle function in our code.

function doCheckbox(selector,className,selected) {
    var elements=document.querySelectorAll(selector);
    for(let i=0;i<elements.length;i++) elements[i].onclick=toggle;
    if(typeof selected=='number' && selected>-1 && selected<elements.length)
        elements[selected].classList.add(className,true);   //  modified to use .classList.add()
    function toggle(event) {
        this.classList.toggle(className);   //  modified to use .classList.toggle()
    }
}

Modified Radio Button Function

This will be more similar to the original function. We cannot take advantage of the toggle method, as we are using a more complex test.

In addition to using the add() method:

  • to test for a class use .classList.contains()
  • to remove a class use .classList.remove()
function doRadio(selector,className,selected,total) {
    var elements=document.querySelectorAll(selector);
    var current=undefined;
    for(let i=0;i<elements.length;i++) elements[i].onclick=select;
    if(typeof selected=='number' && selected>-1 && selected<elements.length) {
        elements[selected].classList.add(className,true);
        current=elements[selected];
    }
    function select() {
        if(this==current) {
            if(total) current=current.classList.remove(className);
            return;
        }
        if(current) current.classList.remove(className)
        current=this;
        this.classList.add(className);
    }
}

Classes vs Attributes

We can achieve the toggling behaviour using either attributes or classes. Today, the choice is entire yours. Here are some reasons why you might choose to use custom attributes over classes:

  • Attribute selectors are avaiable on legacy browsers; IE<10 doesn’t support classList.
  • Conceptually, you might want to distinguish between a class to control appearance and an attribute to control behaviour.

As regards legacy browsers, remember that we can probably ignore IE<10 today.