StudioTime
StudioTime

Reputation: 24019

Changing class with javascript

I have the following menu and I want to add a class 'active' to the last clicked link. Using native javascript not with JQuery.

Firstly, I'm removing active from all links then trying to add 'active' to clicked link

Here's what I'm trying but simply does nothing:

<a href="#" class="menu active" onclick="changeClass()">test 1</a>
<a href="#" class="menu" onclick="changeClass()">test 2</a>
<a href="#" class="menu" onclick="changeClass()">test 3</a>

function changeClass() {
  document.getElementsByClassName('menu').classList.remove('active');
  this.className('menu active');
}

Here's a fiddle to have a look at but not if jsfiddle is working correctly

Upvotes: 1

Views: 124

Answers (5)

canon
canon

Reputation: 41715

You've got a few problems:

1. document.getElementsByClassName() returns an HTMLCollection which has no classList property. You have to access the items within the collection in order to access their properties, i.e.:

document.getElementsByClassName('menu')[0].classList = "foo";

2. Element.className is a property, not a function. You must assign a value like so:

this.className = 'menu active';

3. When you attach a handler using inline markup, i.e.: <a onclick="foo()">, this (in the context of foo) is not the element that raised the event; it's the global object, window. You might consider attaching those handlers programmatically instead (fiddle):

function menuClickHandler(e) {
    // on click...
    // if the event target is already active, return
    if (this.classList.contains("active")) return;
    // if the event target is not already active...
    // get an element that has both menu and active classes
    // apparently only one item is active at any time
    var active = document.querySelector(".menu.active");
    // did we find an item? remove the active class
    active && active.classList.remove("active");
    // add the active class to the event target, "this"
    // which is actually the appropriate element now
    this.classList.add("active");
}
// iterate over all menu items
[...document.querySelectorAll(".menu")].forEach(item =>
    // add a click handler to each item
    item.addEventListener("click", menuClickHandler)
);

See also: document.querySelector(), document.querySelectorAll(), Array.prototype.forEach(), and EventTarget.addEventListener()

Upvotes: 1

Butt4cak3
Butt4cak3

Reputation: 2429

There are a couple of things wrong with your code.

document.getElementsByClassName does not return a single element, but an HTMLCollection, which is basically an array with additional properties. This means that you have to iterate over it in order to remove "active" from the classlists of all included elements.

var collection = document.getElementsByClassName("menu"),
    i;

for (i = 0; i < collection.length; i++) {
    collection[i].classList.remove("active");
}

this does not point to the element you clicked on, but to the window object. In order to use the event target in your function, you have to pass it as a parameter inside the HTML code and use this parameter inside your function.

<a href="#" class="menu active" onclick="changeClass(this)">test 1</a>
...

function changeClass(target) {
    ...
    target.classList.add("active");
}

As you can see in the code above, you can also use Element.classList.add to add a class to your element. If you use classList in one case, you should probably use it in all cases. Older browsers do not support this feature, though. If you want to write backwards compatible code, you can use the className attribute.

collection[i].className = "menu"; // Removing the action class

target.className = "menu active"; // Adding the action class

Upvotes: 1

T.J. Crowder
T.J. Crowder

Reputation: 1075735

className is not a function, so this line:

this.className('menu active');

causes an error you can readily see in the web console.

As you're using classList elsewhere, you could do:

this.classList.add('active');

Alternately, completely overwrite the classes:

this.className = 'menu active';

(Note that older browsers don't have classList, so for them you'll need a shim if you rely on it.)

Separately, this within your function will not be the element that was clicked, because of the way you have the event handler hooked up. You can ensure that it is this with a minimal change using call:

<a href="#" class="menu active" onclick="changeClass.call(this)">test 1</a>
<a href="#" class="menu" onclick="changeClass.call(this)">test 2</a>
<a href="#" class="menu" onclick="changeClass.call(this)">test 3</a>

You might even want to pass the event in:

<a href="#" class="menu active" onclick="changeClass.call(this, event)">test 1</a>
<a href="#" class="menu" onclick="changeClass.call(this, event)">test 2</a>
<a href="#" class="menu" onclick="changeClass.call(this, event)">test 3</a>

Or of course use modern techniques to hook up the handlers instead of attributes.

Upvotes: 3

V31
V31

Reputation: 7666

You need to pass the element on the onclick event and then change the class using the .className

HTML Code

<a href="#" class="menu active" onclick="changeClass(this)">test 1</a>
<a href="#" class="menu" onclick="changeClass(this)">test 2</a>
<a href="#" class="menu" onclick="changeClass(this)">test 3</a>

JS Code

function changeClass(elm) {
  var elements = document.getElementsByClassName('active');
  elements[0].className = "menu";
  elm.className = 'menu active';
}

Upvotes: 0

Tiborg
Tiborg

Reputation: 2305

className is a property, not a method, so you should use it like this:

this.className = 'menu active'

And be aware that classList works in IE10+, and not below.

Upvotes: 1

Related Questions