76

I am looking for a way to have two separate operations / functions / "blocks of code" run when something is clicked and then a totally different block when the same thing is clicked again. I put this together. I was wondering if there was a more efficient / elegant way. I know about jQuery .toggle() but it doesn't work as desired.

Working here: http://jsfiddle.net/reggi/FcvaD/1/

var count = 0;
$("#time").click(function() {
    count++;
    //even odd click detect 
    var isEven = function(someNumber) {
        return (someNumber % 2 === 0) ? true : false;
    };
    // on odd clicks do this
    if (isEven(count) === false) {
        $(this).animate({
            width: "260px"
        }, 1500);
    }
    // on even clicks do this
    else if (isEven(count) === true) {
        $(this).animate({
            width: "30px"
        }, 1500);
    }
});
ThomasReggi
  • 55,053
  • 85
  • 237
  • 424

10 Answers10

73

jQuery has two methods called .toggle(). The other one [docs] does exactly what you want for click events.

Note: It seems that at least since jQuery 1.7, this version of .toggle is deprecated, probably for exactly that reason, namely that two versions exist. Using .toggle to change the visibility of elements is just a more common usage. The method was removed in jQuery 1.9.

Below is an example of how one could implement the same functionality as a plugin (but probably exposes the same problems as the built-in version (see the last paragraph in the documentation)).


(function($) {
    $.fn.clickToggle = function(func1, func2) {
        var funcs = [func1, func2];
        this.data('toggleclicked', 0);
        this.click(function() {
            var data = $(this).data();
            var tc = data.toggleclicked;
            $.proxy(funcs[tc], this)();
            data.toggleclicked = (tc + 1) % 2;
        });
        return this;
    };
}(jQuery));

DEMO

(Disclaimer: I don't say this is the best implementation! I bet it can be improved in terms of performance)

And then call it with:

$('#test').clickToggle(function() {   
    $(this).animate({
        width: "260px"
    }, 1500);
},
function() {
    $(this).animate({
        width: "30px"
    }, 1500);
});

Update 2:

In the meantime, I created a proper plugin for this. It accepts an arbitrary number of functions and can be used for any event. It can be found on GitHub.

Felix Kling
  • 795,719
  • 175
  • 1,089
  • 1,143
  • @ThomasReggi: Thanks, for the update. It was very late when I wrote this ;) However, it is not necessary to use `this.each`, especially as this will create a `funcs` array and new event handler function for every element. Fixed it and added demo. – Felix Kling Feb 06 '11 at 10:07
  • I don't get it... why not just use http://api.jquery.com/toggle-event/ ? You even mentioned it in your answer... maybe you should highlight that part, as it is a native jQuery method. – dmmd Aug 16 '12 at 00:03
  • @jlcd: That's why I put it at the beginning of the answer. I edited my answer to stress it more, do you think it's better now? TBH, I wrote the code and then discovered the other `.toggle` method later. I did not want to throw away that code though... – Felix Kling Aug 16 '12 at 00:10
  • It's a bit better. And sorry about that, just after I posted here I saw that the http://api.jquery.com/toggle-event/ is deprecated. I couldn't figure out **why** it is deprecated though. Seems like a good method, and I also didn't find a replacement. – dmmd Aug 16 '12 at 01:01
  • @jlcd: Oh thank you, I did not know it was deprecated... maybe I should update my answer again then ;) I don't know why it is either. Maybe because of the the other toggle method and the reasons mentioned in the last paragraph in the documentation... I don't know. – Felix Kling Aug 16 '12 at 01:23
  • @FelixKling I _think_ your plugin here will misbehave if it's passed multiple elements... – Alnitak Feb 14 '13 at 20:21
  • @Alnitak: It works fine in my demo :) (I really had to check though). What made you think it wouldn't? – Felix Kling Feb 14 '13 at 20:24
  • @FelixKling ah, no, it's OK. I got confused between the `.data` call outside the click handler that affects _all_ elements in the set, and the one _inside_ the handler that only affects the current element. I had thought that the elements were going to get their state all tied together. Apologies for the false alarm! – Alnitak Feb 14 '13 at 20:27
  • 1
    check my answer http://stackoverflow.com/questions/4911577/jquery-click-toggle-between-two-functions/18287434#18287434 – Tushar Gupta - curioustushar Aug 17 '13 at 10:16
  • Can I reset the toggle of one button by clicking other button? – Syed Sep 14 '15 at 06:35
  • 1
    @Syed: Sure, just set `data.toggleclicked` of the element to the index you want to reset to. – Felix Kling Sep 14 '15 at 07:50
  • @FelixKling just did this `el.data('toggleclicked', 0)` and it's working fine. thanks. – Syed Sep 14 '15 at 08:37
  • the jQuery object that your function gets need to be outside the parenthesis i.e. (function ($) {})(jQuery) you're current answer will not run. and if that code is ever copied to a working project it can accidentally invoke any function defined before it (if not ended with a };) and cause chaos! – Dennis Oct 05 '16 at 15:34
  • @Dennis: That is incorrect. `(function() { ... }(foo))` is equivalent to `(function() { ... })(foo)`. See http://stackoverflow.com/q/3384504/218196 . *"you're current answer will not run"* The [demo](https://jsfiddle.net/npwAz/1/) shows that it does ;) *"it can accidentally invoke any function defined before"* That's true, but not relevant for the answer. – Felix Kling Oct 05 '16 at 15:37
  • @FelixKing sorry you right it really runs and its a nice function, but still people need to be careful when they copy past it to their code because as I said it can invoke other functions. sorry if I were too tough :) – Dennis Oct 06 '16 at 17:41
68

DEMO

.one() documentation.

I am very late to answer but i think it's shortest code and might help.

function handler1() {
    alert('First handler: ' + $(this).text());
    $(this).one("click", handler2);
}
function handler2() {
    alert('Second handler: ' + $(this).text());
    $(this).one("click", handler1);
}
$("div").one("click", handler1);

DEMO With Op's Code

function handler1() {
    $(this).animate({
        width: "260px"
    }, 1500);
    $(this).one("click", handler2);
}

function handler2() {
    $(this).animate({
        width: "30px"
    }, 1500);
    $(this).one("click", handler1);
}
$("#time").one("click", handler1);
Tushar Gupta - curioustushar
  • 58,085
  • 24
  • 103
  • 107
52

Micro jQuery Plugin

If you want your own chainable clickToggle jQuery Method you can do it like:

jQuery.fn.clickToggle = function(a, b) {
  return this.on("click", function(ev) { [b, a][this.$_io ^= 1].call(this, ev) })
};

// TEST:
$('button').clickToggle(function(ev) {
  $(this).text("B"); 
}, function(ev) {
  $(this).text("A");
});
<button>A</button>
<button>A</button>
<button>A</button>

<script src="//code.jquery.com/jquery-3.3.1.min.js"></script>

Simple Functions Toggler

LIVE DEMO

function a(){ console.log('a'); }
function b(){ console.log('b'); }

$("selector").click(function() { 
  return (this.tog = !this.tog) ? a() : b();
});

If you want it even shorter (why would one, right?!) you can use the Bitwise XOR *Docs operator like:
DEMO

  return (this.tog^=1) ? a() : b();

That's all.
The trick is to set to the this Object a boolean property tog, and toggle it using negation (tog = !tog)
and put the needed function calls in a Conditional Operator ?:




In OP's example (even with multiple elements) could look like:

function a(el){ $(el).animate({width: 260}, 1500); }
function b(el){ $(el).animate({width: 30}, 1500);  }

$("selector").click(function() {
  var el = this;
  return (el.t = !el.t) ? a(el) : b(el);
}); 

ALSO: You can also store-toggle like:
DEMO:

$("selector").click(function() {
  $(this).animate({width: (this.tog ^= 1) ? 260 : 30 });
}); 

but it was not the OP's exact request for he's looking for a way to have two separate operations / functions


Using Array.prototype.reverse:

Note: this will not store the current Toggle state but just inverse our functions positions in Array (It has it's uses...)

You simply store your a,b functions inside an array, onclick you simply reverse the array order and execute the array[1] function:

LIVE DEMO

function a(){ console.log("a"); }
function b(){ console.log("b"); }
var ab = [a,b];

$("selector").click(function(){
  ab.reverse()[1](); // Reverse and Execute! // >> "a","b","a","b"...
});

SOME MASHUP!

jQuery DEMO
JavaScript DEMO

Create a nice function toggleAB() that will contain your two functions, put them in Array, and at the end of the array you simply execute the function [0 // 1] respectively depending on the tog property that's passed to the function from the this reference:

function toggleAB(){
  var el = this; // `this` is the "button" Element Obj reference`
  return [
    function() { console.log("b"); },
    function() { console.log("a"); }
  ][el.tog^=1]();
}

$("selector").click( toggleAB );
Roko C. Buljan
  • 196,159
  • 39
  • 305
  • 313
  • 2
    Fantastic answer! Thank you – Alexander Holsgrove Aug 16 '16 at 10:43
  • I understand most of it. But I don't understand where "tog" is coming from? and how does el.tog^=1 go between 0 and 1 alternatively? thnx much appreciated – koz Feb 01 '17 at 05:25
  • @koz `.tog` is just a property we add to the `this` object. Just like you do in say `Person.name = "koz"` Person is an Object, you assign a `name` property and than a value. More on the XOR operator `^` find here: http://stackoverflow.com/a/22061240/383904 (you can find the same link in my answer ;) ) – Roko C. Buljan Feb 01 '17 at 05:29
  • @RokoC.Buljan thnx for the response, much appreciated. I looked at the link but it doesn't explain the mathematics of it which I like to understand. I understand it returns 0, 1, 0, 1...but how is it doing that??. What does "^=1" actually mean in mathematics to equal 0,1,0,1 alternatively. thnx – koz Feb 01 '17 at 23:43
  • @koz Think binary, zeros and ones. The XOR operator **Returns a *`one`* in each bit position for which the corresponding bits of either but not both operands are *`ones`*.** Now, as you noticed we always go against *ones(`1`)* operators: `operand^1` so here's a **results table**: `0^1 =1` and `1^1 =0`. So far so good - the results are switching! But in order to actually modify our operator `el.tog` you need to use the *Assignment operator (`=`)* - And there you go! `el.tog^=1`. Now refer back to the small table and you'll understand what values are now assigned to the `el.tog` property. – Roko C. Buljan Feb 02 '17 at 00:03
  • @koz So if `el.tog` holds `0` than `el.tog^=1` will shift it's value to `1`. Now that `el.tog` holds the value of `1`, `el.tog^=1` will shift it's value back to `0` ...etc etc 1010101.... :) Hope it's much clearer now from a logical-mathematical/point of view. See also https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators to expand on my simplifications. – Roko C. Buljan Feb 02 '17 at 00:14
  • @RokoC.Buljan I understand almost all of it, except the equals sign and its importance?? For example, the difference between el.tog^1 and el.tog^=1. I know you said to "allow modification" but I don't understand what you mean by that?? appreciate the time your taking to explain this – koz Feb 02 '17 at 03:28
  • @koz As I said we're using the assignment operator. So basically `el.tog^=1` is a shorthand for `el.tog = el.tog ^ 1` See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Assignment_Operators I hope you'll have the "Aha!" moment :) – Roko C. Buljan Feb 02 '17 at 13:17
  • @RokoC.Buljan Oh ok I get it now :) thank you so much for the long discussion I did have the aha moment haha – koz Feb 03 '17 at 04:00
24

I would do something like this for the code you showed, if all you need to do is toggle a value :

var oddClick = true;
$("#time").click(function() {
    $(this).animate({
        width: oddClick ? 260 : 30
    },1500);
    oddClick = !oddClick;
});
Roko C. Buljan
  • 196,159
  • 39
  • 305
  • 313
dom
  • 6,702
  • 3
  • 30
  • 35
  • This works only for unique elements. I suggest to remove the oddClick-part and replace the `width:`-line with `width: $(this).width() == 30 ? 260 : 30`. – mgutt Feb 28 '15 at 12:41
15

I used this to create a toggle effect between two functions.

var x = false;
$(element).on('click', function(){
 if (!x){
  //function
  x = true;
 }
 else {
  //function
  x = false;
 }
});
metamlkshk
  • 261
  • 4
  • 12
6

I don't think you should implement the toggle method since there is a reason why it was removed from jQuery 1.9.

Consider using toggleClass instead that is fully supported by jQuery:

function a(){...}
function b(){...}   

Let's say for example that your event trigger is onclick, so:

First option:

$('#test').on('click', function (event) {

    $(this).toggleClass('toggled');

    if ($(this).hasClass('toggled')) {
        a();
    } else{
        b();
    }
}

You can also send the handler functions as parameters:

Second option:

$('#test').on('click',{handler1: a, handler2: b}, function (event) {

    $(this).toggleClass('toggled');

    if ($(this).hasClass('toggled')) {
        event.data.handler1();
    } else{
        event.data.handler2();
    }
}
marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Dudi
  • 3,069
  • 1
  • 27
  • 23
2

If all you're doing is keeping a boolean isEven then you can consider checking if a class isEven is on the element then toggling that class.

Using a shared variable like count is kind of bad practice. Ask yourself what is the scope of that variable, think of if you had 10 items that you'd want to toggle on your page, would you create 10 variables, or an array or variables to store their state? Probably not.

Edit:
jQuery has a switchClass method that, when combined with hasClass can be used to animate between the two width you have defined. This is favourable because you can change these sizes later in your stylesheet or add other parameters, like background-color or margin, to transition.

nedk
  • 673
  • 3
  • 8
1

Use a couple of functions and a boolean. Here's a pattern, not full code:

 var state = false,
     oddONes = function () {...},
     evenOnes = function() {...};

 $("#time").click(function(){
     if(!state){
        evenOnes();
     } else {
        oddOnes();
     }
     state = !state;
  });

Or

  var cases[] = {
      function evenOnes(){...},  // these could even be anonymous functions
      function oddOnes(){...}    // function(){...}
  };

  var idx = 0; // should always be 0 or 1

  $("#time").click(function(idx){cases[idx = ((idx+1)%2)]()}); // corrected

(Note the second is off the top of my head and I mix languages a lot, so the exact syntax isn't guaranteed. Should be close to real Javascript through.)

Charlie Martin
  • 110,348
  • 25
  • 193
  • 263
  • Besides syntax errors, the second one would not work. You call `$("#time").click(..)` only *once* which will set the function that will be called. – Felix Kling Feb 06 '11 at 04:51
  • just needs another anonymous function to call the correct case. – zzzzBov Feb 06 '11 at 05:00
0

modifying the first answer you are able to switch between n functions :

<!doctype html>
<html lang="en">
 <head>
  <meta charset="UTF-8">
  <meta name="Generator" content="EditPlus.com®">
<!-- <script src="../js/jquery.js"></script> -->
<script src="https://code.jquery.com/jquery-2.2.4.min.js"></script>
  <title>my stupid example</title>

 </head>
 <body>
 <nav>
 <div>b<sub>1</sub></div>
<div>b<sub>2</sub></div>
<div>b<sub>3</sub></div>
<!-- .......... -->
<div>b<sub>n</sub></div>
</nav>
<script type="text/javascript">
<!--
$(document).ready(function() {
 (function($) {
        $.fn.clickToggle = function() {
          var ta=arguments;
         this.data('toggleclicked', 0);
          this.click(function() {
        id= $(this).index();console.log( id );
            var data = $(this).data();
            var tc = data.toggleclicked;
            $.proxy(ta[id], this)();
            data.toggleclicked = id
          });
          return this;
        };
   }(jQuery));

    
 $('nav div').clickToggle(
     function() {alert('First handler');}, 
        function() {alert('Second handler');},
        function() {alert('Third handler');}
  //...........how manny parameters you want.....
  ,function() {alert('the `n handler');}
 );

});
//-->
</script>
 </body>
</html>
0

one line solution: basic idea:

$('sth').click(function () {
    let COND = $(this).propery == 'cond1' ? 'cond2' : 'cond1';
    doSomeThing(COND);
})

examples on jsfiddle

example 1, changing innerHTML of an element in a toggle-mode:

$('#clickTest1').click(function () {
    $(this).html($(this).html() == 'click Me' ? 'clicked' : 'click Me');
});

example 2, toggling displays between "none" and "inline-block":

$('#clickTest2, #clickTest2 > span').click(function () {
    $(this).children().css('display', $(this).children().css('display') == 'inline-block' ? 'none' : 'inline-block');
});