Alternative to jQuery's .toggle() method that supports eventData?
Asked Answered
M

2

12

The jQuery documentation for the .toggle() method states:

The .toggle() method is provided for convenience. It is relatively straightforward to implement the same behavior by hand, and this can be necessary if the assumptions built into .toggle() prove limiting.

The assumptions built into .toggle have proven limiting for my current task, but the documentation doesn't elaborate on how to implement the same behavior. I need to pass eventData to the handler functions provided to toggle(), but it appears that only .bind() will support this, not .toggle().

My first inclination is to use a flag that's global to a single handler function to store the click state. In other words, rather than:

$('a').toggle(function() {
  alert('odd number of clicks');
}, function() {
  alert('even number of clicks');
});

do this:

var clicks = true;
$('a').click(function() {
  if (clicks) {
    alert('odd number of clicks');
    clicks = false;
  } else {
    alert('even number of clicks');
    clicks = true;
  }
});

I haven't tested the latter, but I suspect it would work. Is this the best way to do something like this, or is there a better way that I'm missing?

Murmurous answered 17/3, 2010 at 0:29 Comment(2)
I don't understand your question. What are you trying to do?Morril
@cletus: I can't use .toggle() because it doesn't seem to support passing eventData to the handler function. I'm looking for the best alternative solution that does support eventData, which (to the best of my knowledge) is going to be based on the .bind() method. In other words, where the jQuery documention says "it is relatively straightforward to implement the same behavior by hand," I'm trying to do just that.Murmurous
C
35

Seems like a reasonable way to do it... I'd just suggest that you make use of jQuery's data storage utilities rather than introducing an extra variable (which could become a headache if you wanted to keep track of a whole bunch of links). So based of your example:

$('a').click(function() {
  var clicks = $(this).data('clicks');
  if (clicks) {
    alert('odd number of clicks');
  } else {
    alert('even number of clicks');
  }
  $(this).data("clicks", !clicks);
});
Citizen answered 17/3, 2010 at 0:47 Comment(5)
Thanks, Alconja - good answer! I appreciate the concise code example.Murmurous
@Gnuey - Works for me on first click (just copy/pasted this exact source into my Firebug console and now every link on this page is firing alerts first go). Are you seeing an error, or just no alert?Citizen
Well, I tried to implement a slideDown upon the first click. Also changed the 'a' selection to a class selection. Not sure if that is relevant though. I replicated the problem here: jsfiddle.net/3gpfcLatterll
@Gnuey - Switch your slideUp and slideDowns. On the first click, clicks will be undefined which will be interpreted as false, so it will execute the else branch.Citizen
Ahh, I see. I finally understand your code, and it's beautiful. Thanks, I would upvote again if I hadn't already!Latterll
D
2

Here is a plugin that implements an alternative to .toggle(), especially since it has been removed in jQuery 1.9+.

How to use:

The signature for this method is:

.cycle( functions [, callback] [, eventType])
  • functions [Array]: An array of functions to cycle between
  • callback [Function]: A function that will be executed on completion of each iteration. It will be passed the current iteration and the output of the current function. Can be used to do something with the return value of each function in the functions array.
  • eventType [String]: A string specifying the event types to cycle on, eg. "click mouseover"

An example of usage is:

$('a').cycle([
    function() {
      alert('odd number of clicks');
    }, function() {
      alert('even number of clicks');
    }
]);

I've included a demonstration here.

Plugin code:

(function ($) {
    if (!Array.prototype.reduce) {
        Array.prototype.reduce = function reduce(accumulator) {
            if (this === null || this === undefined) throw new TypeError("Object is null or undefined");
            var i = 0,
                l = this.length >> 0,
                curr;

            if (typeof accumulator !== "function") // ES5 : "If IsCallable(callbackfn) is false, throw a TypeError exception."
            throw new TypeError("First argument is not callable");

            if (arguments.length < 2) {
                if (l === 0) throw new TypeError("Array length is 0 and no second argument");
                curr = this[0];
                i = 1; // start accumulating at the second element
            } else curr = arguments[1];

            while (i < l) {
                if (i in this) curr = accumulator.call(undefined, curr, this[i], i, this);
                ++i;
            }

            return curr;
        };
    }
    $.fn.cycle = function () {
        var args = Array.prototype.slice.call(arguments).reduce(function (p, c, i, a) {
            if (i == 0) {
                p.functions = c;
            } else if (typeof c == "function") {
                p.callback = c;
            } else if (typeof c == "string") {
                p.events = c;
            }
            return p;
        }, {});
        args.events = args.events || "click";
        console.log(args);
        if (args.functions) {
            var currIndex = 0;

            function toggler(e) {
                e.preventDefault();
                var evaluation = args.functions[(currIndex++) % args.functions.length].apply(this);
                if (args.callback) {
                    callback(currIndex, evaluation);
                }
                return evaluation;
            }
            return this.on(args.events, toggler);
        } else {
            //throw "Improper arguments to method \"alternate\"; no array provided";
        }
    };
})(jQuery);
Dichotomize answered 27/2, 2013 at 4:4 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.