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 totrue
.
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 usingdocument.querySelector
attribute
: the name of the attribute to toggleselected
: 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 followingdiv
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 betweenblock
andnone
. - 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 themax-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 theclass
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.