Toggle icon on button via JavaScript - javascript

I have a Bootstrap webpage set up which displays a collapsible table. Each table row has a button which has a glyphicon-chevron-down icon. When a user clicks on this button, that icon needs to change to glyphicon chevron-up. I have tried doing this a few different ways to no avail.
Current set up is:
<script >
function toggleChevron(button) {
if (button.find('span').hasClass('glyphicon-chevron-down')) {
button.find('span').className = "glyphicon glyphicon-chevron-up";
}
if (button.find('span').hasClass('glyphicon-chevron-up')) {
button.find('span').className = "glyphicon glyphicon-chevron-down";
}
}
</script>
<button type="button" onclick="toggleChevron(this)" class="btn btn-default">
<span class="glyphicon glyphicon-chevron-down"></span>
</button>
JSFiddle

This works:
toggleChevron = function(button) {
$(button).find('span').toggleClass('glyphicon-chevron-down glyphicon-chevron-up');
}
The main issue is that you are passing this, which isn't a jQuery object, so you need to wrap button in the jQuery function $(button)
http://jsfiddle.net/V9LSS/4/

There was some errors with your jsfiddle. Check the console.
Here's how I did it though:
http://jsfiddle.net/V9LSS/5/
var span = $('.glyphicon');
$('.btn').click(function(e){
if(span.hasClass('glyphicon-chevron-down'))
span.removeClass('glyphicon-chevron-down').addClass('glyphicon-chevron-up');
else
span.removeClass('glyphicon-chevron-up').addClass('glyphicon-chevron-down');
});
Simplified a little:
http://jsfiddle.net/V9LSS/7/
var span = $('.glyphicon');
$('.btn').click(function(e){
span.toggleClass('glyphicon-chevron-down glyphicon-chevron-up');
});

function toggleChevron(button) {
if ($(button).find('span').hasClass('glyphicon-chevron-down')) {
$(button).find('span').removeClass("glyphicon-chevron-down").addClass("glyphicon-chevron-up");
}else{
$(button).find('span').removeClass("glyphicon-chevron-up").addClass("glyphicon-chevron-down");
}
}

You are passing the dom node as a parameter and trying to use jquery functions on it,you need to pass a jquery element in the onclick($(this) instead of just this).

Try this:
$('button').click(function(e) {
var $elm = $(this).find('span'),
cDown = 'glyphicon-chevron-down',
cUp = 'glyphicon-chevron-up';
if ($elm.hasClass(cDown)) {
$elm.removeClass(cDown).addClass(cUp);
}
else {
$elm.removeClass(cUp).addClass(cDown);
}
});
http://jsfiddle.net/V9LSS/3/
Or if you're feeling gutsy enough to inline everything (idea courtesy of dave):
<button type="button" onclick="$(this).find('span').toggleClass('glyphicon-chevron-down glyphicon-chevron-up')" class="btn btn-default">
<span class="glyphicon glyphicon-chevron-down"></span>
</button>
http://jsfiddle.net/V9LSS/9/

Related

Changing multiple button color separately when clicked

I'm having troubles with my javascript code, I have a snippet that creates a number of button depending on the loop range, the buttons share the same class but the ids are different
<div class="panel-footer" id="loop">
<ul class="post-action">
{% for i in range %}
<button class="btn btn-success guess" id="{{ i }}" value="{{ i }}" onclick="transferField(this.value)">{{ i }} </button>
{% endfor %}
</ul>
</div>
I am trying to change the color of each button when it is clicked and when clicked again changes back to default color but its not working fine, when i click on button 5 it changes color but once i click on button 6, it wont change color until i click button 6 or click another button again. Here is the js code:
<script>
clicked = true;
$(".guess").click(function(){
xyz = this.id
console.log(xyz)
if(clicked){
$('#' + this.id).css('background-color', '#FF8E2B');
clicked = false;
} else {
$('#' + this.id).css('background-color', '#27AE60');
clicked = true;
}
});
</script>
What have i done wrong?
Your problem is you are using clicked as a global variable.
Just store clicked in this.
$(".guess").click(function(){
xyz = this.id
console.log(xyz)
if(this.clicked){
$(this).css('background-color', '#FF8E2B');
this.clicked = false;
} else {
$(this).css('background-color', '#27AE60');
this.clicked = true;
}
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="panel-footer" id="loop">
<ul class="post-action">
<button class="btn btn-success guess" id="one" value="one" > one</button>
<button class="btn btn-success guess" id="two" value="two" > two</button>
<button class="btn btn-success guess" id="three" value="three" > three</button>
</ul>
</div>
You are trying to "track" the state of multiple buttons using a single var, of course this wont work
try the following instead
$(".guess").click(function(){
var $this = $(this);
var clicked = $this.data('clicked');
if(clicked) {
$this.css('background-color', '#FF8E2B');
} else {
$this.css('background-color', '#27AE60');
}
$this.data('clicked', !clicked);
});
You can store an array of color for each element at data-* of element, use .data(), Array.prototype.reverse() to toggle to array, set background to element at index 0 of array
$("button").on("click", function() {
$(this).css("background", $(this).data().colors.reverse()[0])
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<button data-colors='["#FF8E2B", "#27AE60"]' style="background:#FF8E2B">click</button>
What have i done wrong?
The main issue you have with your code is that you have one variable keeping track of the clicked state of multiple button elements.
The way to fix this is to add state to the element itself essentially giving multiple variables for multiple elements.
I'm doing this by adding an event object to the callback of the click handler and getting event.currentTarget. The value of event.currentTarget is the element object being clicked. You can add state to this object just like you'd add state to another other javascript object
event.currentTarget.clicked = true;
Now you can keep track of the state for each element!
// just some simple code to get your template in pure JS, don't worry about the code here
const range = [0,1,2,3,4,5];
const template = `<div class="panel-footer" id="loop">
<ul class="post-action">
${range.map(i => `
<button class="btn btn-success guess" id="${i}" value="${i}">${i}</button>
`)}
</ul>
</div>`;
const div = document.createElement('div');
div.innerHTML = template;
document.body.appendChild(div);
// here are where the changes start
// var clicked = true; instead of having a global variable to keep track of the state of all your buttons
$(".guess").click(function(event) {
// you need local state attached to the button
// here we're getting the button element being clicked
const currentTarget = event.currentTarget;
xyz = this.id
console.log(xyz);
// if clicked is truthy
if (currentTarget.clicked) {
$('#' + this.id).css('background-color', '#FF8E2B');
currentTarget.clicked = false;
} else {
$('#' + this.id).css('background-color', '#27AE60');
// set on the element some state
currentTarget.clicked = true;
}
});
.guess {
/* start out in the orange state */
background-color: #FF8E2B;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

How to assign a javascript function by classname

I have multiple buttons (generated by php) for a shopping cart application:
<button class="addtocart" id="<?php echo $code; ?>">
<span id="addtocartbutton">Add to cart</span>
</button>
I want to update my cart using a function:
function AddtoCart() {
alert("Added!");
}
Later, I want to find the id ($code) created by the button which called it (not sure how to do that also, but maybe that's another question). And so I tried this:
document.getElementsByClassName("addtocart").addEventListener("click", AddtoCart());
But it doesn't work. It was working using an onclick, but I understand that the right way to do it by creating an EventListener. Also, I cannot use the on() function in jQuery, because I am forced to use jQuery Version 1.6 which does not have it.
I have looked at https://stackoverflow.com/a/25387857/989468 and I can't really assign it to the parent which is a p tag, because I obviously don't want the other elements in the p tag to be assigned this function.
While the answers given are correct, there is another way: Event Delegation
Attach the listener to a SINGLE thing, in this case the document body and then check to see what element was actually clicked on:
Warning: Typed on the fly: Untested
// Only needed *once* and items may be added or removed on the fly without
// having to add/remove event listeners.
document.body.addEventListener("click", addtoCart);
function addtoCart(event) {
var target = event.target;
while(target) {
if (target.classList.contains('addtocart')) {
break;
}
// Note: May want parentElement here instead.
target = target.parentNode;
}
if (!target) {
return;
}
var id = target.dataset.id;
alert(id + " added!");
}
You should attach click event to every element with class addtocart, since getElementsByClassName() return an array of all objects with given class name so you could use for to loop through everyone of them and associate it with function you want to trigger on click (in my example this function called my_function), check example bellow :
var class_names= document.getElementsByClassName("addtocart");
for (var i = 0; i < class_names.length; i++) {
class_names[i].addEventListener('click', my_function, false);
}
Hope this helps.
function my_function() {
alert(this.id);
};
var class_names= document.getElementsByClassName("addtocart");
for (var i = 0; i < class_names.length; i++) {
class_names[i].addEventListener('click', my_function, false);
}
<button class="addtocart" id="id_1">button 1</button>
<button class="addtocart" id="id_2">button 2</button>
<button class="addtocart" id="id_3">button 3</button>
<button class="addtocart" id="id_3">button 4</button>
I'll show some of the errors you had in your code, then I'll show you how can you improve it so that you can achieve what you want, and I also show that it works with buttons dynamically added later:
First and foremost, you need to pass the function reference (it's name) to the addEventListener! You have called the function, and passed whatever it returned. Instead of:
document.getElementsByClassName("addtocart").addEventListener("click", AddtoCart());
It should've been:
document.getElementsByClassName("addtocart").addEventListener("click", AddtoCart);
Second: document.getElementsByClassName("addtocart") returns a NodeList, you can't operate on it, you need to operate on it's elements: document.getElementsByClassName("addtocart")[0], [1],....
Third, I would suggest you to use the data-... html attribute:
<button class="addtocart" id="addtocart" data-foo="<? echo $code; ?>">
This way you can pass even more data. Now you can get the $code as:
document.getElementById('addtocart').dataset.foo
// el: the button element
function AddtoCart(el) {
// this is the id:
var id = el.id;
// and this is an example data attribute. You can have as many as you wish:
var foo = el.dataset.foo;
alert(id + " (" + foo + ") added!");
}
// Try add a div or something around the area where all the buttons
// will be placed. Even those that will be added dynamically.
// This optimizes it a lib, as every click inside that div will trigger
// onButtonClick()
document.getElementById("buttons").addEventListener("click", onButtonClick);
// this shows that even works when you dynamically add a button later
document.getElementById('add').onclick = addButton;
function addButton() {
var s = document.createElement("span");
s.text = "Add to cart";
var b = document.createElement("button");
b.innerHTML = 'Third <span class="addtocartbutton">Add to cart</span>';
b.className = "addtocart";
b.id="third";
b.dataset.foo="trio";
// note the new button has the same html structure, class
// and it's added under #buttons div!
document.getElementById("buttons").appendChild(b);
}
// this will gett triggered on every click on #buttons
function onButtonClick(event) {
var el = event.target;
if (el && el.parentNode && el.parentNode.classList.contains('addtocart')) {
// call your original handler and pass the button that has the
// id and the other datasets
AddtoCart(el.parentNode);
}
}
<div id="buttons">
<button class="addtocart" id="first" data-foo="uno">First <span class="addtocartbutton">Add to cart</span></button>
<button class="addtocart" id="second" data-foo="duo">Second <span class="addtocartbutton">Add to cart</span></button>
</div>
<button id="add">Add new button</button>
<html>
<head>
<script>
window.onload=function{
var btn = document.getElementsByName("addtocartbtn")[0];
btn.addEventListener("click", AddtoCart());
}
function AddtoCart() {
alert("Added!");
}
</script>
</head>
<body >
<button class="addtocart" name ="addtocartbtn" id="<?php echo $code; ?>" > <span id="addtocartbutton">Add to cart</span></button>
</body>
</html>
Actually class in Javascript is for multiple selection you should provide index like an array.
<button class="addtocart"> <span id="addtocartbutton">Add to cart</span></button>
<script type="text/javascript">
document.getElementsByClassName("addtocart")[0].addEventListener("click", AddtoCart);
function AddtoCart() {
alert("Added!");
}
</script>
Also your second parameter was wrong don't use parentheses.
Applying parentheses means it will call the function automatically when loaded, and will not call the function after that.

AngularJS - how to add a conditional to bootstrap modal target?

So I have a modal that appears when a button is clicked - however I want the modal ONLY to appear when certain conditions are met (which are defined in my controller).
HTML CODE:
<button class="btn btn-primary-outline" type="button" data-uk-modal="{target:'#previewModal'
}" ng-click="previewOfferBefore()">Preview</button>
The above works (modal with the id 'previewModal' pops out on click). So my approach is to add the conditional in the controller and define the value of the "target" in there using Angular data binding.
ie:
<button class="btn btn-primary-outline" type="button" data-uk-modal="{target: {{ previewLink
}}}" ng-click="previewOfferBefore()">Preview </button>
Then the controller would have:
$scope.previewOfferBefore = function() {
if (/*some conditions here*/) {
$scope.previewLink = '#'; /*don't let a modal pop up */
}
else {
$scope.previewLink = '#previewModal' /*let the modal pop up */
}
}
One approach I tried as well is using ng-href instead of bootstrap's data-uk-modal and that also didn't work. I know my controller function is fine since when I place {{ previewLink }} inside a p html tag it prints out the right id I want. So the issue is how I'm binding the data inside the button class.
If you're okay with the button being disabled or greyed out, then one solution would be to use ng-disabled. Your controller would look something like this;
$scope.previewOfferBefore = function() {
if (/*some conditions here*/) {
$scope.canClick= true;
}
else {
$scope.canClick= false;
}
}
and your html would then become;
<button ng-disabled="canClick" class="btn btn-primary-outline" type="button" data-uk-modal="{target:'#previewModal'
}" ng-click="previewOfferBefore()">Preview</button>
Then your button will become unclickable if it hits the false case on the if statement.
You could simply have two buttons and use ng-if or ng-show to just show one of them depending on your expression. You can simply use angular inside.
<button ng-show="previewLink !== '#'" type="button" data-uk-modal="{target: '#previewLink'}" ng-click="previewOfferBefore()">
Preview
</button>
<button ng-show=""previewLink === '#'"" type="button" data-uk-modal="{target: '#'" ng-click="previewOfferBefore()">
Preview
</button>
Alternative:
I would try ng-attr to get your angular expression into the attribute like
ng-attr-uk-modal="{{'target:' + value}}"
and bind value to the target you need. I haven't tried this, might need some adjustments.

Javascript: Multiple buttons, 1 class, cannot use ids. Pressing 1 button changes THEM ALL

I am hoping someone can please help me out here. I've been struggling for 3 days. :)
I am new to wordpress-woocommerce & php, but the theme I am using does not show any "signs" when an item is added to cart with Ajax cart. I am not good with programming, but created some Javascript to try and conquer this issue. Unfortunately, right now if you click one button, ALL buttons change.
I would like the code to only apply to the button actively being clicked.
The main criteria:
The buttons cannot have separate ids. Must use class alone
Buttons are dynamically generated for each product, and the number of products-per-page is unpredictable
Something like, "onclick="this.innerHTML='hi Stack!'" cannot be used I think (it breaks the category page)
This is the pen of what I have so far including the html, css, and js: http://codepen.io/xkurohatox/pen/eNbMKb
HTML
Add to Cart
Add to Cart
Add to Cart
Add to Cart
In actuality, the number of buttons can range from maybe 1-2000 at a time.
css
.button.add_to_cart_button.product_type_simple.added {color:yellow;}
js
function birds(){
var a = document.querySelectorAll('.button.add_to_cart_button.product_type_simple.added' ), i;
for (i = 0; i < a.length; ++i) {
a[i].style.color = "pink";
a[i].innerHTML="Success";
}
setTimeout ( 'cats()', 2000 );
}
function cats(){
var a = document.querySelectorAll('.button.add_to_cart_button.product_type_simple.added' ), i;
for (i = 0; i < a.length; ++i) {
a[i].style.color = "red";
a[i].innerHTML="Add to Cart";
}
}
Here is a page for element inspector: http://s.codepen.io/xkurohatox/debug/eNbMKb?
I have visited this site many times, but never joined until now. Thank you in advance for ANY help! =)
You can use jQuery for this.
HTML
<a href="#" class="button add_to_cart_button product_type_simple added" >Add to Cart</a>
<a href="#" class="button add_to_cart_button product_type_simple added" >Add to Cart</a>
<a href="#" class="button add_to_cart_button product_type_simple added" >Add to Cart</a>
<a href="#" class="button add_to_cart_button product_type_simple added" >Add to Cart</a>
jQuery
$(document).ready(function() {
$('.add_to_cart_button').on('click', function() {
$(this).text('success').css('color', 'red');
})
})
DEMO
In pure javascript, you can do it with
1) Change onclick="birds()" to onclick="birds(this)"
2) In Birds function, receive the anchor as parameter and change the styles of the anchor, instead of iterate over all anchors.
function birds(btn){
btn.style.color = "pink";
btn.innerHTML="Success";
setTimeout ( function() {
cats(btn);
}, 2000 );
}
function cats(btn){
btn.style.color = "red";
btn.innerHTML="Add to Cart";
}
.button.add_to_cart_button.product_type_simple.added {
color:yellow;
}
Add to Cart
Add to Cart
Add to Cart
Add to Cart
You can try it using jQuery.
$(".add_to_cart_button").on("click",function(){
$(this).css("color","red");
$(this).html("Success");
});
.button.add_to_cart_button.product_type_simple.added {
color:yellow;
}
<script src="https://code.jquery.com/jquery-2.1.4.min.js"></script>
Add to Cart
Add to Cart
Add to Cart
Add to Cart
Since Woocommerce support jQuery, you can do something like this, don't struggle with plain Javascript if you don't have to:
$(function() {
$('.add_to_cart_button').click(function(){
$(this).text('success').css('color', 'red');
});
});
I'd like to provide a native way:
var all_buttons = document.querySelectorAll(".add_to_cart_button");
Array.prototype.slice.call(all_buttons)
.forEach(function(button) {
button.onclick = function() {
this.innerHTML = 'success';
this.style.color = 'red';
};
});
.button.add_to_cart_button.product_type_simple.added {
color:yellow;
}
Add to Cart
Add to Cart
Add to Cart
Add to Cart

Can only click jQuery element once before it stops working

I've got the following code:
$("#another").click(function() {
$('#another').replaceWith('<a id="another" class="btn btn-primary btn-mini disabled"><i class="icon-refresh icon-white"></i> Loading...</a>');
$.get('another.php', { 'cycle' : i }, function(data) {
$('tbody').append(data);
$("#another").replaceWith('<a id="another" class="btn btn-primary btn-mini"><i class="icon-plus icon-white"></i> Load another cycle</a>');
});
i++;
});
When I click the element with the id of another, it loads once. After one click, it won't work again.
You're replacing the node with a node that doesn't have the event listener.
Basically before the click you have
[#another]
^
|
[clickListener]
You then build another button (<a id="another" class="btn btn-primary btn-mini disabled"><i class="icon-refresh icon-white"></i> Loading...</a>)
[#another] [#another](2)
^
|
[clickListener]
then we replace the first another with a second one in the layout:
[#another] [#another](2)
^
|
[clickListener]
oh wait, nothing changed in my model. That's because the click listener was linked to that first object ( that is no longer visible), whereas the visible one is still there.
So codewise, what does this mean? It simply means you'll need to attach the event listener back on there. Here's how I'd have done it
var onClick=function(){
$('#another').replaceWith('<a id="another" class="btn btn-primary btn-mini disabled"><i class="icon-refresh icon-white"></i> Loading...</a>')
.click(onClick); // <--- this is the important line!
$.get('another.php', { 'cycle' : i }, function(data) {
$('tbody').append(data);
$("#another").replaceWith('<a id="another" class="btn btn-primary btn-mini"><i class="icon-plus icon-white"></i> Load another cycle</a>');
});
i++;
}
$("#another").click(onClick);
If you replace the element with another, all listeners will be removed. To avoid this you either add the listener again to the new element
$('#another').bind('click', function() {
//do something
});
or move the code to a function and add a onclick attribute to your element.
onclick="my_function();"
in your current javascript it would be
$('#another').replaceWith('<a id="another" class="btn btn-primary btn-mini disabled" onclick="my_function();"><i class="icon-refresh icon-white"></i> Loading...</a>');
It's best to just keep the same button, with the same event handler. Just dynamically change the text and increment i. Try this:
// Execute in a closure to isolate all the local variables
// Optional, but I like doing this when counter variables are involved
(function() {
var button = $("#another");
var a = button.find("a");
var i = 1;
button.click(function() {
// Replace the inner html, not the entire element
a.html("<i class='icon-refresh icon-white'</i> Loading...");
$.get("another.php", {
cycle: i
}, function(data) {
$("tbody").append(data);
a.html("<i class='icon-plus icon-white'></i> Load another cycle");
i++;
});
});
})();
The benefit of this method is that there is less DOM manipulation, no inline JavaScript, and no global functions or variables. There really is no reason to destroy the button each time and recreate it if the outer markup is the same.

Categories

Resources