# Toggling Attributes or Classes

[toc]

<hr class="page">

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:

```html
<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:

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

<hr class="page">

### CSS

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

```css
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:

```css
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:

```javascript
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:

```javascript
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.

<hr class="page">

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

```javascript
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

```javascript
	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:

```javascript
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);
	}
}
```

<hr class="page">

##	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:

```html
<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:

```css
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.

<hr class="page">

### The JavaScript

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

```javascript
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.  

```css
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.

<hr class="page">

## 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.

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

The new version of the function is:

```javascript
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:

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

<hr class="page">

## 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:

```javascript
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:

```javascript
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:

```javascript
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.

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

<hr class="page">

The finished function is below:

```javascript
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:

```javascript
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.

```javascript
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()`

```javascript
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.