Using jQuery to alternate between classes upon click event
Asked Answered
M

3

8

I'm feeling awfully silly here - I can't get a simple class switching statement to work in jQuery! I can only sit in frustration as for the last 45 minutes, I've searched Stack Overflow questions and answers, to no avail.

My goal is, upon clicking an item with the colorClick id (already containing a default class of "white"), to rotate that item between being assigned the class green, yellow, orange, red, and back to white again (ad infinitum).

The CSS is simple - each class simply corresponds to a different background color.

The HTML is simple - a div tag with two CSS classes (one static, one to be changed by jQuery).

The jQuery is simple - read the class on the clicked item, and change it.

And now, you understand what vexes me. Here's what I'm working with so far:

$("#colorClick").click(function () {
    if ($(this).hasClass('white')) {
        $(this).removeClass("white").addClass("green");
    } else if ($(this).hasClass('green')) {
        $(this).removeClass('green').addClass('yellow');
    } else if ($(this).hasClass('yellow')) {
        $(this).removeClass('yellow').addClass('orange');
    } else if ($(this).hasClass('orange')) {
        $(this).removeClass('orange').addClass('red');
    } else if ($(this).hasClass('red')) {
        $(this).removeClass('red').addClass('white');
});
.toDoItem {
    text-align: left;
    padding: 3px;
    margin-bottom: 5px;
    border: 1px solid;
    border-color: #e8e7e7;
}
.white {
    background-color: #ffffff;
}
.green {
    background-color: #b2d8b2;
}
.yellow {
    background-color: #ffffb2;
}
.orange {
    background-color: #ffe4b2;
}
.red {
    background-color: #ffb2b2;
}
<div class="toDoItem white" id="colorClick">To-do list item</div>
<div class="toDoItem white" id="colorClick">To-do list item</div>
<div class="toDoItem white" id="colorClick">To-do list item</div>

Link to the fiddle: http://jsfiddle.net/andrewcbailey89/4Lbm99v0/2/

Michell answered 14/10, 2015 at 18:9 Comment(5)
It's a little disturbing to see bounties given to answers that are just personal and very specific tutorials on trivial works. What can be the point ?Epilimnion
There's a very similar question jQuery switching between more than two classes, the answers aren't that brilliant, as here, but still, a bounty?Annmaria
@Jarrod start voting to delete with a vengeance! What?!Flavour
For the fun, a CSS only solution.Flavour
The point is I think @TinyGiant is a great community member and role model for what good Answers look like. He works really hard to help keep the site clean, when he hits 10K he can contribute to the site even more. It is my rep I am spending your opinion is just that, why do you care.Hygienist
C
30

First things first, when making a list, you should use the correct list elements. Your "To Do" list fits the definition of a description list (<dl>) so you should use that instead of <div> elements.

You can save a lot of lines of code by getting rid of the classes and creating an array of colors. Make sure that the colors are in the same order that you want them to be shown. We will use this array to set the background color based on an incremented counter.

var colors = ['#b2d8b2', '#ffffb2', '#ffe4b2', '#ffb2b2', '#fff'];

You can also greatly simplify your script by using a "factory" function which defines a scope and builds an event listener function, which it returns. This creates a "safe" scope for each listener function to reside in that we can define variables which will store information between events.

In the following snippet, we define a count variable that we increment on each click. We use the incremented variables remainder when dividing by the length of the color array using the modulo operator %. If the number is smaller than the length of the array, it will return the number, otherwise it will return the remainder when dividing by the length of the array, allowing us to loop through continuously.

function todoItemListener() {
    var count = 0;
    return function () {
        $(this).css({ 'background-color': colors[count++ % colors.length] });
    }
}

Then instead of assigning the function declaration as normal (without the parenthesis), we assign the result of the factory function, simply append the parenthesis and the function will execute and return the resulting listener function. This allows us to add as many listener functions as we want, so if you're adding new todo list items, we can simply build another listener function.

$('.todo-list dd').each(function () {
    $(this).on('click', todoItemListener());
});

$('.add-item').on('click', function () {
    var list = this.parentNode.parentNode;
    $('<dd>To-do list item</dd>').appendTo(list).on('click', todoItemListener());
});

This method also allows you to easily change the array of colors at will. So say if an option is selected somewhere on the page, another color could become available, or not available.

Also, for some extra UX goodness, I added CSS to stop selection of the text on click (that can get annoying) and to change the cursor to a pointer to give it a more actionable feel.


Here is the full demo, I've included multiple to-do lists to show that it can be done.

var colors = ['#b2d8b2', '#ffffb2', '#ffe4b2', '#ffb2b2', '#fff'];

function todoItemListener() {
    var count = 0;
    return function () {
        $(this).css({ 'background-color': colors[count++ % colors.length] });
    }
}

$('.todo-list dd').each(function () {
    $(this).on('click', todoItemListener());
});

$('.add-item').on('click', function () {
    var list = this.parentNode.parentNode;
    $('<dd>To-do list item</dd>').appendTo(list).on('click', todoItemListener());
});
.glyphicon-plus-sign {
    font-size: 15px;
}
.todo-list {
    background: #efefef;
    padding: 3px;
}
.todo-list dd {
    margin: 0;
    text-align: left;
    padding: 3px;
    margin-bottom: 7px;
    border: 1px solid;
    border-color: #e8e7e7;
    background-color: #fff;
}
.add-item, .todo-list dd {
    -webkit-user-select: none;
    -moz-user-select: none;
    -ms-user-select: none;
    -o-user-select: none;
    user-select: none;
    cursor: pointer;
}
.add-item {
    float: right;
    margin: 4px;
}
.todo-list dh::after {
    content: "";
    display: block;
    clear: both;
}
.todo-list dh h3 {
    float: left;
    margin: 0px;
    max-width: 100%;
    overflow: hidden;
}

/* This rule is for the demo only */
.wrp {
    float: left;
    width: 33.33333333%;
    padding: 1px;
}
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>

<div class="wrp">

    <dl class="todo-list" id="todo-list-1">
        <dh>
            <h3 class="center" contenteditable>To Do List 1</h3>
            <span class="add-item glyphicon glyphicon-plus-sign"></span>
        </dh>
        <dd>To-do list item</dd>
        <dd>To-do list item</dd>
        <dd>To-do list item</dd>
    </dl>
    
</div>
<div class="wrp">

    <dl class="todo-list" id="todo-list-2">
        <dh>
            <h3 class="center">To Do List 2</h3>
            <span class="add-item glyphicon glyphicon-plus-sign"></span>
        </dh>
        <dd>To-do list item</dd>
        <dd>To-do list item</dd>
        <dd>To-do list item</dd>
    </dl>
    
</div>
<div class="wrp">

    <dl class="todo-list" id="todo-list-3">
        <dh>
            <h3 class="center">To Do List 3</h3>
            <span class="add-item glyphicon glyphicon-plus-sign"></span>
        </dh>
        <dd>To-do list item</dd>
        <dd>To-do list item</dd>
        <dd>To-do list item</dd>
    </dl>
    
</div>
Cathrine answered 14/10, 2015 at 18:51 Comment(0)
P
8

You are missing some quotes in a few places, and you didn't close the last if statement.

ex: $(this).hasClass(green) should be $(this).hasClass('green')

Additionally, you should change colorClick to a class rather than an ID, as there are multiple of these elements.

I also changed all of your quotes to single quotes for consistency's sake.

Here is a working snippet:

$(".colorClick").click(function () {
    if ($(this).hasClass('white')) {
        $(this).removeClass('white').addClass('green');
    } else if ($(this).hasClass('green')) {
        $(this).removeClass('green').addClass('yellow');
    } else if ($(this).hasClass('yellow')) {
        $(this).removeClass('yellow').addClass('orange');
    } else if ($(this).hasClass('orange')) {
        $(this).removeClass('orange').addClass('red');
    } else if ($(this).hasClass('red')) {
        $(this).removeClass('red').addClass('white');    
    }
});
.toDoItem {
    text-align: left;
    padding: 3px;
    margin-bottom: 5px;
    border: 1px solid;
    border-color: #e8e7e7;
}
.white {
    background-color: #ffffff;
}
.green {
    background-color: #b2d8b2;
}
.yellow {
    background-color: #ffffb2;
}
.orange {
    background-color: #ffe4b2;
}
.red {
    background-color: #ffb2b2;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="colorClick toDoItem white">To-do list item</div>
<div class="colorClick toDoItem white">To-do list item</div>
<div class="colorClick toDoItem white">To-do list item</div>
Purchasable answered 14/10, 2015 at 18:12 Comment(0)
W
1

First you are using same id for multiple elements. id should be unique for each element. You can use toDoItem class instead of colorClick id to bind click event. To get rid of complex if else statement you can put all class in an array in your required sequence. Then on click of toDoItem change class according to the sequence of array. If you reached at the last item of array then go back to first.

var colors = ['white', 'green', 'yellow', 'orange', 'red'];
var total = colors.length-1;

$(".toDoItem").click(function() {
    var color = $(this).attr('class').split(' ')[1];
    var index = colors.indexOf(color);
    index = index==total? 0 : index+1;
    
    $(this).removeClass(color).addClass(colors[index]);
});
.toDoItem {
  text-align: left;
  padding: 3px;
  margin-bottom: 5px;
  border: 1px solid;
  border-color: #e8e7e7;
}

.white {
  background-color: #ffffff;
}

.green {
  background-color: #b2d8b2;
}

.yellow {
  background-color: #ffffb2;
}

.orange {
  background-color: #ffe4b2;
}

.red {
  background-color: #ffb2b2;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="toDoItem white">To-do list item</div>
<div class="toDoItem white">To-do list item</div>
<div class="toDoItem white">To-do list item</div>

JS FIDDLE DEMO

Woolery answered 27/1, 2016 at 7:53 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.