jQuery not finding elements (or updating class) after AJAX response - javascript

I have a lot of working functions using the jQuery.on() event, and they work fine - but only if it is binded to an element (like "a.my-link" or something).
This function hovever, is bind to the document or body, and then traverses multiple elements with the same class attribute. I have commented the lines, which do not fire.
selectable["holder"]: elements with the class "a.fr-drive-selectable"
if selected it adds the class "fr-drive-selected"
if more than one were selected (multiple), then it adds the class "fr-drive-selected-b"
I also have a function for selecting multiple elements using only the CTRL key + mouse - it works, because the on() function is binded to the element ...
/**
* Selecting or deselecting all available items (CTRL + ALT + A)
*
* - Selecting: CTRL + ALT + A
* - Deselecting: CTRL + ALT + D
*/
page.$body.on("keydown", function (shortcut) {
if (shortcut.ctrlKey && shortcut.altKey && shortcut.key === "a") {
selectable["$holder"].each(function () {
if (!$(this).children(fdrive["content"]["class"]).hasClass(selectable["multiple"]["class"])) {
if (!(items.indexOf(parseInt($(this).attr('data-id'))) > -1)) {
addItem(items, $(this)); // WORKS FINE!
}
addContextMenuOption($(this)); // WORKS FINE!
console.log(items); // WORKS FINE!
$(this).children(fdrive["content"]["class"]).removeClass(selectable["one"] ["class"]); //NOT WORKING AFTER AJAX
$(this).children(fdrive["content"]["class"]).addClass(selectable["multiple"]["class"]); //NOT WORKING AFTER AJAX
}
});
} else if (shortcut.ctrlKey && shortcut.altKey && shortcut.key === "d") {
selectable["$holder"].each(function () {
if ($(this).children(fdrive["content"]["class"]).hasClass(selectable["multiple"]["class"]) || $(this).children(fdrive["content"]["class"]).hasClass(selectable["one"]["class"])) {
$(this).children(fdrive["content"]["class"]).removeClass(selectable["multiple"]["class"]); //NOT WORKING AFTER AJAX
$(this).children(fdrive["content"]["class"]).removeClass(selectable["one"]["class"]); //NOT WORKING AFTER AJAX
resetContextMenuOptions(); // WORKS FINE!
items = []; // WORKS FINE!
console.log("Removed ALL"); // WORKS FINE!
}
});
}
updateItemCounter(items); // WORKS FINE!
});
The function works if no dynamic data is appended - after that, it only removes items, but not the classes (add() or remove()). I have used the console to print out elements - objects in each() after AJAX do not have "up to date" class attributes.
I know the problem is that the event is not bind to the elements in question, but I need a shortcut for handling the key event on the page, not on the element.
Similar function (works great, because it uses the element):
main_content["$holder"].on('contextmenu', 'a.fr-drive-file-selectable', function (ev) {
if (!(findItemById(items, $(this).attr("data-id")))) {
$(this).siblings(selectable["selector"]).children(fdrive["content"]["class"]).removeClass(selectable["multiple"]["class"]);
$(this).siblings(selectable["selector"]).children(fdrive["content"]["class"]).removeClass(selectable["one"]["class"]);
//resetContextMenuOptions();
items = [];
console.log("Adding one item (RESET)");
addItem(items, $(this));
console.log(items);
$(this).children(fdrive["content"]["class"]).removeClass(selectable["multiple"]["class"]);
$(this).children(fdrive["content"]["class"]).addClass(selectable["one"]["class"]);
}
});
For clarity, check out the console output. The blue line indicates the elements found on "keydown" event before AJAX updated the data. After that, jQuery does not find any element matching: "a.fr-drive-file-selectable - they are on the page!
Console output
** Additional info (after an answer was provided) **
<div id="main-content" class="fr-content">
<!-- AJAX CHANGES CONTENTS with the same elements, same class etc. (only the inner HTML of the elements is changed (ex. name, picture etc.) -->
<div class="fr-drive-file-selectable"><!-- name, picture --></div>
<div class="fr-drive-file-selectable"><!-- name, picture --></div>
<div class="fr-drive-file-selectable"><!-- name, picture --></div>
<div class="fr-drive-file-selectable"><!-- name, picture --></div>
<div class="fr-drive-file-selectable"><!-- name, picture --></div>
</div>
<script>
var selectable = {
$holder: $("a.fr-drive-file-selectable"),
"class": ".fr-drive-file-selectable"
}
</script>
<script>
...
request.done(function (response, textStatus, jqXHR) {
if (response["status"]) {
$(id).html(response["fragment"]); //Loads only div.fr-drive-file-selectable (using foreach in PHP)
stopPageLoader();
adjustSidenavHeight();
} else {
///
}
});
...
</script>

I do not have enough rep to add a comment.
Maybe you are trying to reference the elements before they are added to the DOM.
Also for dynamic elements try mentioning the parent element followed by the element you want to access in the selector query as follows.
$(".parent .dynamic-child").removeClass();

So here it goes ... found the answer. I DON'T KNOW WHY, if someone could later clarify.
So I simply changed the line selectable["$holder"] and declared a local variable within the function (check //DIFF).
Altered code
page.$body.on("keydown", .... {
let selector = "a.fr-drive-file-selectable";
$(this).find(selectable["$holder"]) // DIDNT WORK, works before AJAX updates DOM!
$(this).find(selector) // works ... why?
//selectable["$holder"].each(function () { CHANGED TO
$(this).find(selector).each(function () { // NEW CODE
...
}
Full code
/**
* Selecting or deselecting all available items (CTRL + ALT + A)
*
* - Selecting: CTRL + ALT + A
* - Deselecting: CTRL + ALT + D
*/
page.$body.on("keydown", function (shortcut) {
let selector = "a.fr-drive-file-selectable";
if (shortcut.ctrlKey && shortcut.altKey && shortcut.key === "a") {
$(this).find(selector).each(function () {
if (!$(this).children(fdrive["content"]["class"]).hasClass(selectable["multiple"]["class"])) {
if (!(items.indexOf(parseInt($(this).attr('data-id'))) > -1)) {
addItem(items, $(this));
}
addContextMenuOption($(this));
console.log(items);
$(this).children(fdrive["content"]["class"]).removeClass(selectable["one"]["class"]);
$(this).children(fdrive["content"]["class"]).addClass(selectable["multiple"]["class"]);
}
});
} else if (shortcut.ctrlKey && shortcut.altKey && shortcut.key === "d") {
$(this).find(selector).each(function () {
if ($(this).children(fdrive["content"]["class"]).hasClass(selectable["multiple"]["class"]) || $(this).children(fdrive["content"]["class"]).hasClass(selectable["one"]["class"])) {
$(this).children(fdrive["content"]["class"]).removeClass(selectable["multiple"]["class"]);
$(this).children(fdrive["content"]["class"]).removeClass(selectable["one"]["class"]);
resetContextMenuOptions();
items = [];
console.log("Removed ALL");
}
});
}
updateItemCounter(items);
});

Related

Jquery Select Dynamically added element

Several similar question exist, but after fighting with this for a day or so I feel the need to ask because the vast majority of the answers refer to adding event handlers to elements.
I am not interested in adding an event handler to the elements in question, rather I am interested in adding additional dynamic content to dynamically generated content.
The app works thusly:
load a modal form dynamically upon the click of a static element (working properly)
function loadModal(target,modalId) {
console.log("==================> loadModal() Entry");
$.ajax({
type: "GET",
url: 'http://localhost/retrieve-modal/'+modalId,
success : function (text) {
$("#"+modalId)[0].innerHTML = text;
modalSaveIntercept($("#"+modalId)[0])
},
failure : function (e) {
console.log("something is wrong");
}
})
}
Then I have a save interceptor that overrides the default save behavior of my form here this is also working properly, (I suspect because I am loading this event handler at the time of loading the modal)
function modalSaveIntercept(eventTarget) {
if(eventTarget.hasChildNodes()) {
eventTarget.childNodes.forEach(function(e) {
if(e.tagName == "FORM") {
console.log("found the form: " + e.id + " applying save override listener");
$("#"+e.id).submit(function(event){
event.preventDefault();
submitForm(e);
});
modalSaveIntercept(e)
}
});
}
}
the above attaches a listener to the form loaded into my modal and rather than firing the default behavior of a Save button click, it fires my submitForm() function which is here:
function submitForm(form) {
let payload = constructPayloadFromFormData(form);
validate(payload).then(function(v) {
console.log("response Data:");
for(let p in v) {
if(v.hasOwnProperty(p)) {
constructInvalidFeedbackForProperty(p,v[p])
}
}
});
}
this function constructs a payload from the form data (working fine) then executes another ajax call inside of validate() - I wait for the return call from ajax and then iterate through an array of validation data to confirm the form's validity. However, here is where the problem is:
function constructInvalidFeedbackForProperty(prop,e) {
let el = $("#" + "ic-role-" + prop);
console.log(el);
el.append("<div class=\"invalid-feedback\">problem</div>");
}
the problem is the append - I cannot seem to fire that method. I can select the element as the console.log(el) writes to the log the correctly identified element in my dom.
What am I doing wrong?
I have created a contrived jsfiddle for a sample of the problem. I actually believe it may be that an input field is not something you can append to... perhaps? https://jsfiddle.net/jtango/xpvt214o/987051/
Okay, I messed around with your fiddle a bit. If you inspect the input element that is created you can see that your append does work. It's just not displaying. If you are trying to edit what is in the input box then you must use val()
Here is a copy of your fiddle that will display inside the input:
$("#top").on("click", function(){
$("#form").append("<label>some label: </label><input type=\"text\" id=\"myinput\">");
});
$("#btm").on("click",function(){
$("#myinput").val("<div>I will not appear</div>");
});
As your shared https://jsfiddle.net/jtango/xpvt214o/987051/ It will not appear, this is wrong way to append any HTML element inside "input box" or any of form elements. it should allow to set only new attribute or value.
check screenshot: https://i.stack.imgur.com/4FBgn.png
So verify your below code if it's similar then it will not work:
let el = $("#" + "ic-role-" + prop);
console.log(el);
el.append("<div class=\"invalid-feedback\">problem</div>");

Breaking jQuery/JS code off into functions from .click()

I am using a JQuery cookie library to hide/show elements and then remember the status when the page is reloaded. The working code looks for elements with id #num0 and toggles the next element, and it looks like this:
if(Cookies('divShown0') == 'true') { //defaults to hidden
$('#num0').next().show(); // Show if cookie there and 'true'
}
$('#num0').click(function() {
$(this).next().toggle();
if(Cookies('divShown0') == 'true') {
Cookies.set('divShown0', 'false'); // Remember it was hidden
}
else {
Cookies.set('divShown0', 'true'); // Remember it was shown
}
});
I have multiple of these, each identified by a different #num and stored as a different divShown number. So far I have just added a new code block with new numbers, but obviously this takes up a lot of space. I put the first if() statement into a for() loop, no problem. The second part I broke into a function, toggleShown(num), so I can call $('#num0').click(toggleShown(0));, but this is not working. I suspect that something about the $(this) element isn't properly identifying the next element, but I really don't know.
function toggleShown(num)
{
$(this).next().toggle();
if(Cookies('divShown' + num) == 'true') {
Cookies.set('divShown' + num, 'false'); // Remember it was hidden
}
else {
Cookies.set('divShown' + num, 'true');
}
}
I don't really do Javascript or JQuery, mostly RoR but I am trying to hack this together. Any thoughts on what I am screwing up?
You hunch is correct! $(this) inside of your function does not hold the reference to the original element, because you are in a new function with a new scope.
All you should have to do is change:
$(this).next().toggle();
to
$("#num" + num).next().toggle();
You need to add a handler for each DOM element anyway, so you can use something like this:
function addHandler(num) {
$('#num' + num).click(function() {
$(this).next().toggle();
var cookieName = 'divShown' + num;
if (Cookies(cookieName) == 'true') {
Cookies.set(cookieName, 'false'); // Remember it was hidden
}
else {
Cookies.set(cookieName, 'true'); // Remember it was shown
}
});
}
for (var num = 0; num < numDivs; ++num) {
addHandler(num);
}
You can set the this argument of a function by calling the function through Function.prototype.call(). The first parameter passed will be assigned as this inside the function.
If you are going to have multiple similar divs, instead of just IDs, you could assign a class to all of them and let jQuery add click listeners to all of them at once.
You can then either read the div ID from the ID itself, or you could give each a data attribute.
HTML:
<button class="toggle" data-id="1">First</button>
<button class="toggle" data-id="2">Second</button>
<button class="toggle" data-id="3">Third</button>
<div id="div1">First</div>
<div id="div2">Second</div>
<div id="div3">Third</div>
Javascript:
$('.toggle').click(function(){
var id = $(this).data('id');
hideOrShow(id);
})
function hideOrShow(id){
$('#div'+id).toggle();
}
The second part I broke into a function, toggleShown(num), so I can call $('#num0').click(toggleShown(0));, but this is not working.
You can do
$("#num0").click(toggleShown);
...for just one of them, or
$("[id^=num]").click(toggleShown);
...for all of them at once. (^= means "starts with", so that would be "all elements with an id attribute starting with num"). If you need to weed out ones with other ids (like number):
$("[id^=num]").filter(function() {
return this.id.match(/^num\d+$/);
}).click(toggleShown);
Either way, you then keep using this within toggleShown, and get the index number from the end of this.id:
function toggleShown() {
var divName = "divShown" + /\d+$/.exec(this.id)[0];
$(this).next().toggle();
if (Cookies(divName) == 'true') {
Cookies.set(divName, 'false'); // Remember it was hidden
} else {
Cookies.set(divName, 'true');
}
}
Live Example:
$("[id^=num]").click(toggleShown);
function toggleShown() {
var divName = "divShown" + /\d+$/.exec(this.id)[0];
$("#" + divName).toggleClass("on");
}
.on {
color: blue;
}
<div>Click any of the "num"s, and its corresponding "divShown" toggles between blue and normal text</div>
<div id="num0">num0</div>
<div id="num1">num1</div>
<div id="num2">num2</div>
<div id="num3">num3</div>
<hr>
<div id="divShown0">divShown0</div>
<div id="divShown1">divShown1</div>
<div id="divShown2">divShown2</div>
<div id="divShown3">divShown3</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
Alternately, you can use Function#bind or jQuery.proxy to bind the index to toggleShown, and then use e.currentTarget for this:
$("#num0").click(toggleShown.bind(null, "0"));
then:
function toggleShown(num, e) {
$(e.currentTarget).next().toggle();
if (Cookies('divShown' + num) == 'true') {
Cookies.set('divShown' + num, 'false'); // Remember it was hidden
} else {
Cookies.set('divShown' + num, 'true');
}
}

Find and replace text without destroying click events

A client has asked for all of the trademark symbols (™ and ®) on their website to be styled in a specific way; given the quantity in which they appear—everywhere from titles to body text and navigation—we've decided to do this with JavaScript.
What we want to do is find every instance of ™ and ® in the page text (but not inside element attributes) and wrap them in <sup> tags so we can style them in CSS.
This is the code we currently have:
Trademark = {
init: function () {
$('body').contents().each(function () {
var element = $(this);
if (element.html()) {
element.html(element.html().replace(/(?![^<]+>)™/gi, '<sup class="trademark">™</sup>'));
element.html(element.html().replace(/(?![^<]+>)®/gi, '<sup class="trademark">®</sup>'));
}
});
}
}
$(function () {
Trademark.init();
})
It works well, but we're now suffering the problem that JavaScript click events aren't being registered on elements that have had their contents replaced—I'm assuming because they're being removed from the DOM when they're being manipulated.
Is there a modification to this (to the JS or regex) that will stop this from happening? Thanks!
Filter for textNodes only and replace the innerHTML of the parentNode, that way the elements themselves are never replaced and the event handlers should stay intact.
Trademark = {
init: function () {
$('*').contents().each(function() {
if (this.nodeType == 3 && this.nodeValue) {
if ( this.nodeValue.indexOf('™') != -1 || this.nodeValue.indexOf('®') != -1 ) {
this.parentNode.innerHTML = this.parentNode.innerHTML.replace(/(?![^<]+>)(™|®)/gi, '<sup class="trademark">$1</sup>');
}
}
});
}
}
FIDDLE

Why is my JQuery function not triggered on a cloned element when the event becomes "true"?

I have been making this form that must enable the back-end user to create new questions for users to answer. The form is cloned and appended to a div (selector #content) successfully after the first .on(click) event, but it won't duplicate the form again if the cloned button is pressed. The .on(change) event applied to my drop-down selection does change the content of respective divs like it is supposed to, but only on the original form.
Here's the JQuery code:
$(document).ready(function () {
$('.addAnswer').on("click", function () {
var idx = $('#mp div[name^="antwoord"]:last').index() + 1;
$clone = $('#mp div[name^="antwoord"]:first').clone(true, true).attr('class', 'answer content_' + idx);
$('.removeAnswer').show;
$('#mp').append($clone);
$('.answer:last').each(function () {
$('b:last').empty();
$('b:last').prepend(String.fromCharCode(64 + idx) + ". ")
$('.addAnswer').on("click", function () {
idx++;
});
});
if (idx == 2) {
$('.removeAnswer').show();
}
});
$('.nextq').click(function () {
var newqid = $('#content form:last').index() + 1;
$('.done, .nextq, .remove').hide();
$('#content').append('<hr>');
$('#content').append($('form').html()).attr('class', 'q_' + newqid);
$('.nextq').on("click", function () {
newqid++;
});
$('.done:last, .nextq:last, .remove:last').show();
return false;
});
$('.group').hide();
$('#text:last').show();
$('.select:last').on("change", function () {
$('.group').hide();
$('#' + $(this).val() + ':last').fadeIn();
$('button.' + $(this).val() + ':last').fadeIn();
});
});
Because I thought posting the whole HTML template would be a tad bit too much, I provided a JSFiddle for you people.
One extra question for the ones that are feeling kind: In the JQuery code it is seen that the contents of the HTML are being parsed using .html() and appended with .append.(Line 33 on the JSFiddle) As the .on(change) function switches the contents of the divisions it should change, .html() sees those changes and takes those along with it. I'd like the .on(click) function to append the div's content in its original state, unchanged by the changes made beforehand by the back-end user. Any help with this would be much obliged.
In order to have jQuery trigger on new elements you would do something like
$( document ).on( "click", "<your id or class>", function() {
//Do stuff
});

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