Cycling Event Listeners vs many Listeners - javascript

I'm doing some work with a program that has quite a lot to click, however somethings only need to be clicked when say their parent has been clicked on and is active. Now there are three ways as I see it to approach this.
Many event handlers
Constantly Create and Destroy them
Have one top-level handler and read the Event for what child even occurred on (event.target)
The reason three is not the most obvious choice for me is that children of what I clicked would be event.target and object.parentNode would need to repeatedly run to see what element was in-fact available for clicking; I hear DOM api's are expensive.
I'm interested to know how expensive each method is relative to each other and what is accepted practice in something like that. Number of listeners is in the hundreds range, it's a dynamic application with modest DOM mutation.
EDIT: For clarification this question is about DIV's and attaching event handlers to them.

If you are using buttons or similar you can enable and disable them. They usually have a different appearance too so the user can know not to click on a disabled button.
If you aren't using buttons, you can add or remove a class called disabled. When the element is clicked, the listener can check its class and if it has a class of "disabled", have it do nothing. Otherwise, have it do what it does.
The addition and removal of the class can also be used to modify the appearance of the element, making it look disabled (similar to how buttons and inputs change appearance when disabled).
Simple has/add/removeClass functions:
var util = {dom:{}};
util.trim = function(s) {
return s.replace(/(^\s+)|(\s+$)/g,'').replace(/\s+/g,' ');
}
util.dom.hasClassName = function(el, cName) {
var re = new RegExp('(^|\\s+)' + cName + '(\\s+|$)');
return el && re.test(el.className);
}
util.dom.addClassName = function(el, cName) {
if (!util.dom.hasClassName(el, cName)) {
el.className = util.trim(el.className + ' ' + cName);
}
}
util.dom.removeClassName = function(el, cName) {
if (util.dom.hasClassName(el, cName)) {
var re = new RegExp('(^|\\s+)' + cName + '(\\s+|$)','g');
el.className = util.trim(el.className.replace(re, ''));
}
}
A simple example:
<script>
function toggle(id) {
var el = document.getElementById(id);
if (!el) return;
if (util.dom.hasClassName(el, 'disabled')) {
util.dom.removeClassName(el, 'disabled')
} else {
util.dom.addClassName(el, 'disabled')
}
}
function doStuff(el) {
if (util.dom.hasClassName(el, 'disabled')) return;
alert('I\'m alive!');
}
</script>
<button id="b0" onclick="doStuff(this);">button 0</button>
<button onclick="toggle('b0')">change b0</button>
Alternatively the disabled property could be set to true or false.
You can also maintain state independantly of the elements, so when an element is clicked on it knows what to do based on the state if its child or parent elements (or whatever).

Related

Is there a difference between "click" function vs e.target in javascript? Performance speed?

Is there any major difference in using
document.getElementById("button").addEventListener("click", function() {
//kk
})
document.getElementById("button2").addEventListener("click", function() {
//kk
})
VS
document.addEventListener("click",function(e){
if(e.target.closest(#button){
//kk
}else if(e.target.closest(#button2){
//kk
}
})
Is there a performance benefit for looping though if statements or just attaching individual listener for each element that is clickable?
Dynamic elements
There's often times when a specific element is not yet present in the page - but we want to do something if a specific event happens in the future.
In such circumstances a common way to tackle the problem is to use an ancestor delegator (like document or a closest known Element).
// We don't have buttons yet, but might appear in the future
document.addEventListener("click", function(evt) {
const EL_btn = evt.target.closest("button");
if (!EL_btn) return; // No button was clicked. Play dead!
if (EL_btn.id === "foo") {
console.log("Button #foo was clicked!")
}
});
If those elements could be anywhere in such case we use document and call it a day. But if we know exactly the parent container that will hold those child elements always use that element as delegator. I.e: EL_asideMenu.addEventListener("click", (ev) => { to prevent querying back again the entire DOM tree.
Dynamic elements pt2:
When creating in-memory elements, assign at creation a click handler. Append your elements (when time comes) where needed - and that's it. No need to do DOM events querying or other stuff.
const NewEL = (tag, attr) => Object.assign(document.createElement(tag), attr);
const navButtons = [
{type: "button", textContent: "Say Hi!", onclick() { console.log("Hello World!"); }},
{type: "button", textContent: "Say Foo", onclick() { console.log("Foo!Bar!Baz!"); }},
].map(attr => NewEL("button", attr));
document.querySelector("#navee").append(...navButtons);
<nav id="navee"></nav>
Pro tip: in the above example, that's the only, proper and sole time you want to use the on* attribute handlers on an Element. Since the element is just being created. Every other time you should use Element.addEventListener() to attach additional handlers. But never again the on* to not override any prior handler.
Static elements
That's when direct Events assignment is preferrable
const myButtonHandler = (ev) => {
const EL_btn = ev.currentTarget; // Use currentTarget in that case!
if (EL_btn.id === "foo") {
console.log("Button #foo was clicked!")
}
};
// Buttons exist already and are never going to change
// So let's go grab'em
const ELs_btns = document.querySelectorAll("button");
// Assign a "click" Event handler
ELs_btns.forEach(EL => EL.addEventListener("click", myButtonHandler));
Now, regarding both the above examples and their if and possible lots of else statements, you could create a "map" with functions for every button - by storing the desired function name inside a data-* attribute:
const clickFn = (ev) => ({
sayHi() { console.log("Hello, World!") },
myOtherFn() { console.log("Something else!") },
}[ev.currentTarget.dataset.click](ev));
const ELs_btns = document.querySelectorAll("[data-click]");
ELs_btns.forEach(EL => EL.addEventListener("click", clickFn));
<button data-click="sayHi" type="button">Say hello!</button>
<button data-click="myOtherFn" type="button">Do something else</button>
or many other ways... like a switch .. case, if .. else etc...

Click events from two scripts on same element?

Edit: I think I got the solution! I want to try and fix this myself before I ask for further help = )
First script inhibits the second one from functioning as the click event from the first one overides the second one. Because the second one does not function it is impossible to open the drop down menu to select a list item to trigger the first scripts click.
What I tried was replacing all return false statements with event.stopPropagation(). Didnt work however. Tried re-ordering my scripts but that failed as well. I was thinking of making my second script target another parent div but that didnt work either.I also tried event.stopImmediatePropagation() and .bind methods.
Any idea?
First script that makes the drop down function. Contains click event.
function DropDown(el) {
this.f = el;
this.placeholder = this.f.children('span');
this.opts = this.f.find('ul.dropdown > li');
this.val = '';
this.index = -1;
this.initEvents();
}
DropDown.prototype = {
initEvents : function() {
var obj = this;
obj.f.on('click', function(event){
$(this).toggleClass('active');
return false;
});
obj.opts.on('click',function(){
var opt = $(this);
obj.val = opt.text();
obj.index = opt.index();
obj.placeholder.text(obj.val);
});
},
getValue : function() {
return this.val;
},
getIndex : function() {
return this.index;
}
}
$(function() {
var f = new DropDown( $('#f') );
$(document).click(function() {
// all dropdowns
$('.filter-buttons').removeClass('active');
});
});
Second script that does the filtering, also contains click event:
jQuery(document).ready(function(e) {
var t = $(".filter-container");
t.imagesLoaded(function() {
t.isotope({
itemSelector: "figure",
filter: "*",
resizable: false,
animationEngine: "jquery"
})
});
$(".filter-buttons a").click(function(evt) {
var n = $(this).parents(".filter-buttons");
n.find(".selected").removeClass("selected");
$(this).addClass("selected");
var r = $(this).attr("data-filter");
t.isotope({
filter: r
});
evt.preventDefault();
});
$(window).resize(function() {
var n = $(window).width();
t.isotope("reLayout")
}).trigger("resize")
});
html structure
<div id="f" class="filter-buttons" tabindex="1">
<span>Choose Genre</span>
<ul class="dropdown">
<li>All</li>
<li>Electronic</li>
<li>Popular</a></li>
</ul>
</div>
This doesn't really solve your problem but I was bored while drinking my coffee and felt like helping you write your dropdown plugin a little nicer
My comments below are inline with code. For uninterrupted code, see DropDown complete paste.
We start with your standard jQuery wrapper (function($){ ... })(jQuery)
(function($) {
// dropdown constructor
function DropDown($elem) {
First we'll make some private vars to store information. By using this.foo = ... we expose things (probably) unnecessarily. If you need access to these vars, you can always create functions to read them. This is much better encapsulation imo.
// private vars
var $placeholder = $elem.children("span");
var $opts = $elem.find("ul.dropdown > li")
var value = "";
var index = -1;
Now we'll define our event listeners and functions those event listeners might depend on. What's nice here is that these functions don't have to access everything via this.* or as you were writing obj.f.* etc.
// private functions
function onParentClick(event) {
$elem.toggleClass("active");
event.preventDefault();
}
function onChildClick(event) {
setValue($(this));
event.preventDefault();
}
function setValue($opt) {
value = $opt.text();
index = $opt.index();
$placeholder.text(value);
}
Here's some property descriptors to read the index and value
// properties for reading .index and .value
Object.defineProperty(this, "value", {
get: function() { return value; }
});
Object.defineProperty(this, "index", {
get: function() { return index; }
});
Lastly, let's track each instance of DropDown in an array so that the user doesn't have to define a special listener to deactivate each
// track each instance of
DropDown._instances.push(this);
}
This is the array we'll use to track instances
// store all instances in array
DropDown._instances = [];
This event listener deactivate each "registered" instance of DropDown
// deactivate all
DropDown.deactiveAll = function deactiveAll(event) {
$.each(DropDown._instances, function(idx, $elem) {
$elem.removeClass("active");
});
}
Here's the document listener defined right in the plugin! The user no longer has to set this up
// listener to deactiveAll dropdowns
$(document).click(DropDown.deactiveAll);
Might as well make it a jQuery plugin since everything in our DropDown constructor relies upon jQuery. This let's the user do var x = $("foo").dropdown();
// jQuery plugin
$.fn.dropdown = function dropdown() {
return new DropDown($(this));
};
Close the wrapper
})(jQuery);
Now here's how you use it
$(function() {
var x = $('#f').dropdown();
// get the value
f.value;
// get the index
f.index;
});
Anyway, yeah I know this doesn't really help you with your click listeners, but I hope this is still useful information to you. Off to the Post Office now!
I think you're going to need to simplify this to figure out what's going on. There's actually not enough information to see what elements the events are being attached to here.
For argument's sake, open the console and try the following:
$(document).on('click', function() { console.log('first'); return false; });
$(document).on('click', function() { console.log('second'); return false; });
Then click in the page. You'll see that both events are triggered. It might well be that your code is actually attaching the events to different elements (you don't say anywhere). If that's the case then you need to understand how event bubbling works in the DOM.
When you trigger an event, say a click on an element, that event will fire on that element, and then on it's parent, then grandparent etc all the way to the root node at the top.
You can change this behaviour by calling functions in the event itself. evt.stopPropagation tells the event to not bubble up to the ancestor nodes. evt.preventDefault tells the browser not to carry out the default behaviour for a node (eg, moving to the page specified in the href for an A tag).
In jQuery, return false from an event handler is a shortcut for, evt.preventDefault and evt.stopPropagation. So that will stop the event dead in its tracks.
I imagine you have something like:
<div event_two_on_here>
<a event_one_on_here>
</div>
If the thing that handles event_one_on_here calls stopPropagation then event_two_on_here will never even know it has happened. Calling stopPropagation explicitly, or implicitly (return false) will kill the event before it travels to the parent node/event handler.
UPDATE: In your case the issue is that the handler on .filter-buttons a is stopping the propagation (so #f doesn't get to run its handler).
$(".filter-buttons a").click(function(evt) {
// your code here...
// Don't do this - it stops the event from bubbling up to the #f div
// return false;
// instead, you'll probably just want to prevent the browser default
// behaviour so it doesn't jump to the top of the page ('url/#')
evt.preventDefault();
});

Call back from styled radio buttons

I am using the jQuery plugin radiosToSlider (http://rubentd.com/radios-to-slider/) and need to make sure that all radio button groups are checked and to give an alert when they are
I can do this if they are just radio buttons by checking the length but because the plugin changes the input buttons I am having difficulties
My fiddle is
http://jsfiddle.net/yFaAj/270/
$(document).ready(function () {
$(".radios").radiosToSlider();
});
$(":radio").change(function () {
var names = {};
$(':radio').each(function () {
names[$(this).attr('name')] = true;
});
var count = 0;
$.each(names, function () {
count++;
});
if ($(':radio:checked').length === count) {
alert("all answered");
}
}).change();
thanks
The problem isn't that the plugin changes your structure (although it does add some ins elements, which I don't agree with), it's that the plugin doesn't fire a change event for the converted radio controls, and setting the checked property interactively doesn't appear to do so either.
Since the plugin author doesn't publish an API for this use case, it's hard to know whether this is by design or oversight, but the source code definitely doesn't fire the event when the slider is clicked:
this.bearer.find('.slider-level').click( function(){
var radioId = $(this).attr('data-radio');
slider.bearer.find('#' + radioId).prop('checked', true);
slider.setSlider();
});
Your options, as I see them:
Contact the API author and ask for a bug fix, or the intended way to support this case
Downside: Time: dependent on the author to respond
Attach your "check" function to the click event of the .slider-level class, as the API does.
Downside: Brittle: future versions of the plugin may attach the behavior to different selectors
Attach your function to the click event of your radio group, and catch click events on the bubble
Downside: Inefficient: It will check for every click in the radio control
Here's a sample implementation of option 3. DEMO.
$(document).ready(function () {
$(".radios").radiosToSlider();
});
var makeIsRadioGroupChecked = function(selector) {
var $radioGroup = $(selector);
return function isRadioGroupChecked() {
return $radioGroup.find(':checked').length > 0;
};
};
var isOptionsChecked = makeIsRadioGroupChecked('#optionsRadioGroup');
var isSizeChecked = makeIsRadioGroupChecked('#sizeRadioGroup');
var areAllGroupsChecked = function() {
return isOptionsChecked() && isSizeChecked();
};
var alertIfAllGroupsChecked = function() {
if (areAllGroupsChecked()) {
alert("all answered");
}
};
$('.radios').on('click', alertIfAllGroupsChecked);

Javascript: get element ID from event

How to get the ID of an element passed as (e)?
window.addEventListener('load', function(){
var tags = document.getElementsByClassName("tag");
for (i=0; i<tags.length; i++){
tags[i].addEventListener('mousedown', function(e){ tagClick(e) }, false);
}
}, false);
function tagClick(e){
/* here I'm gonna need the event to cancel the bubble and the ID to work with it*/
alert('The id of the element you clicked: ' + [?object].id);
[?object].className='newClass';
e.stopPropagation();
e.cancelBubble = true;
}
I need to get the element/object inside tagClick so I can change its properties
html:
<div class="tag">
<img src="/images/tags/sample.jpg"/>
<label class="tagLabel">Sample</label>
</div>
See, the element with the event attached is the div, but ig gives me the image object instead when using e.srcElement.
When you bind an event listener with addEventListener, it's called with this referring to the element you bound the event on. So this.id will be the id of the element (if it has one).
alert('The id of the element you clicked: ' + this.id);
But you're breaking that with this line:
tags[i].addEventListener('mousedown', function(e){ tagClick(e) }, false);
...because you're putting an extra function in the middle, then calling tagClick without setting this. There's no need for that extra function, change that to:
tags[i].addEventListener('mousedown', tagClick, false);
...so this doesn't get messed up. Or alternately if you prefer to have the extra function, ensure this is maintained using Function#call:
tags[i].addEventListener('mousedown', function(e){ tagClick.call(this, e) }, false);
...but there's no reason to do that with the tagClick function shown.
The (standard) event object also has the properties target (which may not be the element you bound the event on, it may well be a descendant) and currentTarget (which will be the element you bound the event on). But this is convenient and reliable if you use addEventListener (or even attachEvent, on IE).
You can get the target of the event with e.target.
However keep in mind that some browsers consider text nodes to be a target, so try something like this:
var t = e.target;
while(t && !t.id) t = t.parentNode;
if( t) {
alert("You clicked element #"+t.id);
}
This will find the first element that actually has an ID.
Happy New Year!
EDIT: On second thought, if it's the "tag" element itself you want to refer to, just use this. In an event handler, this refers to the element that actually has the handler. Although in this case you'll need to change your handler to ('mousedown', tagClick, false)
Or better still:
document.body.addEventListener("mousedown",function(e) {
var t = e.target;
while(t && t.nodeName != "TAG") { // note, must be uppercase
t = t.parentNode;
}
if( t) {
alert("You clicked on #"+t.id);
}
},false);
Fewer event handlers is always better.
document.getElementById("body").addEventListener("mousedown", function(e){
console.log(e.target.id);
});
enjoy.

A smart toggle class in jQuery

I am trying to implement a script to set different class name on a specific element…
Let's suppose the dom looks like this:
<body class='pre-existing-class-name'>
If I make
smartToogle('body', 'new-class');
// the dom should look like this
// <body class='pre-existing-class-name new-class'>
smartToogle('body', 'new-class-2');
// the dom should look like this
// <body class='pre-existing-class-name new-class-2'>
I did the following code but it does not work:
var smartToogle = function (element, newClassName) {
var oldClassName;
var $element = $(element);
$element.addClass(newClassName);
if (oldClassName !== newClassName) {
$element.removeClass(oldClassName);
}
oldClassName = newClassName;
};
Requirements:
1) I am using query
2) I would like to pass just one class name, the new one.
Solution:
The following code works but I do not like it because it uses global variable.
Any hint to fix it?
function myToggle(newClassName) {
if (window.oldClassName) {
$('body').toggleClass(window.oldClassName);
}
window.oldClassName = newClassName;
$('body').toggleClass(newClassName);
}
You can use data attribute for the element, that is accessible using
$(element).data(attrib_name)
Just a small change is required in your method
function myToggle(newClassName) {
if (window.oldClassName) {
$('body').toggleClass(window.oldClassName);
}
window.oldClassName = newClassName;
$('body').toggleClass(newClassName);
}
can be replaced with
function myToggle(element, newClassName) {
if ($(element).data('oldClassName')) {
$(element).toggleClass($(element).data('oldClassName'));
}
$(element).data('oldClassName', newClassName)
$(element).toggleClass(newClassName);
}
Hope this solves it for you.
Update:
There is one thing you need to understand.
If you want two different behaviors you don't need 2 different classes for the change in behavior.
One is enough, because you can change the behavior based on weither the class is on or off.
Let's say I want my element to have a red hover event in one way.
And want it to have a blue hover event the other way with CSS.
Then this is the way to go:
$('#toggle').click(function(){
$('.normal').each(function(){
$(this).toggleClass('active');
});
});
JSFiddle Demo
Here we use a button to toggle all the divs and change their CSS behavior, looks easy now right?
However if you need to toggle Javascript/jQuery events as well this won't do. In that case you will need to use 3 other methods to manage this; .on(), .off(), and .hasClass().
$('#toggle').click(function(){
$('.normal').each(function(){
if($(this).hasClass('active')){
$(this).off('click');
} else {
$(this).on('click', function(){
alert('You are clicking on an active div.');
});
}
$(this).toggleClass('active');
});
});
JSFiddle Demo 2
As you can see we have added an if statement. If the element has the .active class we turn .off() the .click(). And if there isn't an active class we turn the .click() .on(). Under the if statement we always toggle the .active class. So this doesn't have to be placed inside the if statement.
I hope this clears everything up for you, good luck!
Old Answer:
It is better to use .toggleClass() here.
Use a first class on the element for the default properties and a second like .active for example for the interaction.
Also, using a .on('click', function(){}) bind will make you able to add interaction that will be bound instantly once the element is toggled.
Here's a fiddle: http://jsfiddle.net/NCwmF/2/
I little jQuery plugin for that. Removes the current smart class (if any) and adds the new smart class. If called without parameter className the current smart class gets only removed.
$.fn.smartToggle = function (className) {
var dataId = 'smartToggle';
return this.each(function () {
var $el = $(this);
$el
.removeClass($el.data(dataId) || '')
.addClass(className)
.data(dataId, className);
});
};
​use it like every other jQuery method:
$('body').smartToggle('myClass');
NEW, SIMPLER ANSWER
Works similar to before, with 2 additions: 1.) works if there is no class initially and 2.) works if other functions change the elements class in between calls. I also changed the function name so it doesn't interfere with jQuerys native toggleClass.
$.fn.fancyToggleClass = function(new_class) {
return this.each(function() {
// get the last class this function added (if exists) or false (if not)
var $this = $(this),
toggled_class = $this.data('toggled-class') || false;
// if we dont have an original class, then set it based on current class
if (toggled_class) {
$this.removeClass(toggled_class);
}
// add new class and store as data,
// which we check for next time function is called
$this.addClass(new_class).data('toggled-class', new_class);
// alert the class, just as a check to make sure everything worked!
// remove this for production, or switch to console.log
alert('element class: ' + $this.attr('class'));
});
}
updated fiddle: http://jsfiddle.net/facultymatt/xSvFC/3/
OLD ANSWER
I would suggest storing the original class in the elements data attribute. Then, your function can check if this data is set, and if so clear the elements class adding the original class from the elements data and also the new class you passed in the function.
If data is not set, the function will store the current class as data the first time it runs.
Check out this fiddle for a working example with comments: http://jsfiddle.net/facultymatt/xSvFC/
here is the code. It's a jquery function so it can be called on any element (and is chainable too!)
$.fn.toggleClass = function(new_class) {
return this.each(function() {
// cache selector for this
$this = $(this);
// get original class (if exists) or false (if not)
var original_class = $this.data('original-class') || false;
// if we dont have an original class, then set it based on current class
if (!original_class) {
original_class = $this.attr('class');
$this.data('original-class', original_class);
// we do have an original class, so we know user is now trying to add class
// here we clear the class, add the original class, and add the new class
} else {
// assign the original class, and new class,
// and a space to keep the classes from becoming one
$this.attr('class', original_class + ' ' + new_class);
}
// alert the class, just as a check to make sure everything worked!
// remove this for production, or switch to console.log
alert('element class: ' + $this.attr('class'));
});
}
Hope this helps!
To avoid a global variable you can use data attribute as #ankur writes. Here is a working solution for your problem:
function myToggle(element, newClassName) {
if (!$(element).data('baseclassname')) {
$(element).data('baseclassname', $(element).attr('class'));
}
$(element)
.attr('class', $(element).data('baseclassname'))
.addClass(newClassName);
}
Does this do your job?
var smartToogle = function (element, preExistingClassName, newClassName) {
$(element)[0].className = preExistingClassName + ' ' + newClassName;
};
Just use hasClass. But you'll have to tell the function what both classes are:
function smartToggle(element, class1, class2) {
var $element = $(element);
if ($element.hasClass(class1)) {
$element.removeClass(class1);
$element.addClass(class2);
}
else {
$element.removeClass(class2);
$element.addClass(class1);
}
}
$(function(){
var smartToggle = function (element, newClassName) {
var elementClasses = element.attr('class');
element.addClass(newClassName);
// check if there is more than one class on the element
if(elementClasses .indexOf(' ') >= 0){
var oldClassNames = elementClasses.split(" ");
if (oldClassNames[oldClassNames.length - 1] !== newClassName) {
element.removeClass(oldClassNames[oldClassNames.length - 1]);
}
}
};
smartToggle($('.test'), 'newclass');
smartToggle($('.test'), 'newclass2');
});
Demo - http://jsfiddle.net/Q9A8N/ (look at the console to see what it is doing on each pass)
That should do what you want but as #T.J. Crowder said it is rather fragile and assumes that the class you want to remove is the last one on the element.
As an answer to your question, I would go with ankur's answer
As a follow-up to Sem's answer, regarding the handling of jQuery events :
you can use the on function to handle any jquery event from a parent node, based on a live filter :
function myToggle(element, newClassName) {
if ($(element).data('oldClassName')) {
$(element).toggleClass($(element).data('oldClassName'));
}
$(element).data('oldClassName', newClassName);
$(element).toggleClass(newClassName);
}
//event delegation : 'on' is called on the $('.divContainer') node, but we handle
//clicks on '.divItm' items, depending on their current class
$('.divContainer')
.on('click', '.divItm.plain', function(){ myToggle( this, 'red' ); })
.on('click', '.divItm.red', function(){ myToggle( this, 'blue' ); })
.on('click', '.divItm.blue', function(){ myToggle( this, 'plain' ); });
//initialize each item with the 'plain' class
myToggle( $('.divItm'), 'plain' );
Here is the jsFiddle.
You will note that the function called each time you click on an item depends on its "live" class, and that you don't need to manually enable/disable click handlers each time an item changes class.
You can learn more details from the documentation page.
var smartToogle = function (element, newClass) {
var $element = $(element),
currentClass = $element.data('toggle-class');
if (currentClass != newClass) $element.data('toggle-class',newClass).removeClass(currentClass || '');
$element.toggleClass(newClass);
};
or the other variant:
$.fn.smartToogle = function (newClass) {
currentClass = this.data('toggle-class');
if (currentClass != newClass) this.data('toggle-class',newClass).removeClass(currentClass || '');
this.toggleClass(newClass);
};
In this implementation you'll have to keep the a reference to this instance of fancytoggle.
var fancytoggle = function(el, oldClass){
// create a function scope so we'll have a reference to oldClass
return function(newClass) {
// toggle the old class and the new class
$(el).toggleClass(oldClass+ ' ' + newClass);
// update the new class to be the old class
oldClass = newClass;
};
};
for your example the code would look something like.
var bodytoggle = fancytoggle('body', 'pre-existing-class-name');
bodytoggle('new-class');
// 'new-class' replaces 'pre-existing-class-name'
bodytoggle('new-class-2');
// 'new-class-2' replaces 'new-class'
to see it in action refer to http://jsfiddle.net/aaf2L/6/

Categories

Resources