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

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');
}
}

Related

Checking Id of clicked element

I have several images with the same class and would like to set up a click function that changes the text in some elements based on which image is clicked.
My if statement is not working, I'm not entirely sure why because I've used this method before, or so I thought.
$('.gallery_image').click(function(e) {
var t = $(this);
if(t.id == 'csf') {
console.log('It works!');
}
});
JSFIDDLE
Use t.attr('id') instead of t.id
More about .attr()
An alternate solution is using classes instead of id's. Call the click function on the class they share then use a second class to distinguish them with the hasClass function:
<div id="csf" class="gallery_image csf">csf</div>
<div id="csi" class="gallery_image csi">csi</div>
$('.gallery_image').click(function(e) {
var t = $(this);
if(t.hasClass('csf')) {
alert("It works!");
}
else if(t.hasClass('csi')) {
alert("So does this!");
}
});

Checking value of an html select with jQuery

I have the following select box:
<select id="choose">
<option value=1>A</option>
<option value=2>B</option>
<option value=3>C</option>
</select>
<div id="hide-me">hide me!</div>
How can I hide an element when I select option "B"? I tried this:
<script type="text/javascript">
if ("#chose option:selected").val(2) {
$("#hide-me").hide();
};
</script>
I think I'm close, but nothing happens when I select option "B". Anyone have any suggestions?
You need to attach change event to select element to detect the option change. then use .toggle() with argument value not equal to 2 for making show/hide decision :
$('#choose').change(function(){
$("#hide-me").toggle($(this).val() != "2");
})
Working Demo
Listen to the change event of the #choose element, and add your conditional logic there.
Example Here
$('#choose').on('change', function () {
if (this.value === "2") {
$("#hide-me").hide();
} else {
$("#hide-me").show();
}
});
..equivalent, but perhaps less readable version:
Example Here
$('#choose').on('change', function () {
$("#hide-me").toggle(this.value !== "2");
});
As a side note, the value of the selected option is a string.
If you wanted to, you could also convert it to a number:
if (parseInt(this.value, 10) === 2) { ... }
or you could use == rather than ===: (not suggested, though. This could lead to logical errors.)
if (this.value == 2) { ... }
Without jQuery:
Example Here
document.querySelector('#choose').addEventListener('change', function () {
var element = document.getElementById('hide-me');
if (this.value === "2") {
element.style.display = 'none';
} else {
element.style.display = 'block';
}
});
The problem with your code is that:
It is not syntactilally correct — ("#chose option:selected").val(2) is an expression, which in turn should be wrapped in parentheses to be a proper condition for if, like this:
if (("#chose option:selected").val(2)) {
However, even after that your code is not correct because you're not actually using jQuery. You're calling method .val() just on a string "#chose option:selected", whereas that string should instead be a jQuery selector passed to jQuery itself:
if ($("#chose option:selected").val(2)) { // $ is the jQuery object that does all the work
However2, even that is incorrect, because you'd just be checking the value of an element right away. What you need is to wait for an element to be changed. Other answers explain very well how to do that.

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/

need the script to be called for "onload" too not just on "blur"

the code is:
$(document).ready(function () {
$('#url0, #url1, #url2, #url3, #url4, #url5, #url6, #url7, #url8, #url9, #url10').each(function(index, element) {
$(element).blur(function() {
var vals = this.value.split(/\s+/);
var $container = $(this).hide().prev().show().empty();
$.each(vals, function(i, val) {
if (i > 0) {
$("<span> </span>").appendTo($container);
}
$("<a />")
.html(val)
.attr('href',/^https?:\/\//.test(val) ? val : 'http://' + val)
.appendTo($container)
.click(handleClickEvent);
});
});
}).trigger('blur');
// ms to wait for a doubleclick
var doubleClickThreshold = 300;
// timeout container
var clickTimeout;
$('.aCustomDiv a').click(handleClickEvent);
$('.aCustomDiv').click(handleDivClick);
function handleClickEvent(e) {
var that = this;
var event;
if (clickTimeout) {
try {
clearTimeout(clickTimeout);
} catch(x) {};
clickTimeout = null;
handleDoubleClick.call(that, e);
return false;
}
clickTimeout = setTimeout(function() {
clickTimeout = null;
handleClick.call(that, event);
}, doubleClickThreshold);
return false;
}
function handleDivClick(e) {
var $this = $(this);
$this.parent()
.find('input,textarea')
.show()
.focus();
$this.hide();
}
function handleDoubleClick(e) {
var $this = $(this).parent();
$this.parent()
.find('input,textarea')
//.val($a.text())
.show()
.focus();
$this.hide();
}
function handleClick(e) {
window.open(this.href, '_blank')
}
});
HTML CODE:
<div style="padding:0 !important;margin-top:8px !important;">
<div class="aCustomDiv" style="padding: 0px ! important; display: block;">
www.google.com<span></span>www.facebook.com<span></span>www.wikipedia.org
</div>
<input type="text" value="www.google.com www.facebook.com www.wikipedia.org" onchange="immediateEditItemInCart(this.value,'url',0,'pr','35')" class="mandatory0" id="url0" style="display: none;" readonly="readonly">
this script does the following:
converts the text to url for those ids (url0 ...)
double click on the link makes it editable
one click on the div area, next to link makes it editable
one click on the link => goes to the page
my problem : for some reason i don't know, the one click on the link doesn't go to the page but edits it, only the FIRST time , after that works great, so i want the first function to be called also onload not only when blur. how can i do this ?
As far as loading on startup, javascript is single threaded so just firing a method will work if you keep things in order (... just add a couple of parens). But because you are trying to access the DOM you do want to wait for the elements to be available (otherwise you will get nothing back from a selector).
But you do already have:
$(document).ready( function(){} );
Which does exactly what you are asking for, it also has a shorthand of:
$( function(){} );
So I would have to agree with rodneyrehm that is it probably some collision that you have with other js on your page. You might want to encapsulate it a bit in some namespace to make sure that is the not the problem.
I wrote up a quick version that should get your on your way as a starting point if you are still having problems: http://jsfiddle.net/scispear/dUWwB/. I pulled the 'updateURL ' method out in case your ajax call (you mentioned) was just pre-populating the field (that way you could pass in the value also).
It also works with multiple inputs/displays which is what I think you were going for was not 100% sure.

multiple functions in the document.ready function

here's my code:
$(document).ready(function () {
$('.flip1').click(function () {
$('.panel1').slideToggle("slow");
});
$('.flip2').click(function () {
$('.panel2').slideToggle("slow");
});
$('.flip3').click(function () {
$('.panel3').slideToggle("slow");
});
$('.flip4').click(function () {
$('.panel4').slideToggle("slow");
});
});
I want to make a loop with .flip as the variable (flipVar) and .panel as (panelVar)
Well if it were my page I'd make sure that those elements all shared a class so that I wouldn't need to loop. However, you could do this:
for (var i = 1; i <= 4; ++i) $('.flip' + i).click((function(i) {
return function() { $('.panel' + i).slideToggle('slow'); };
})(i));
The loop variable has to be trapped in a closure so that each "click" handler references the proper value. Again, I really wouldn't do it this way. I'd make the "flip" elements share a class, and then keep that index (the implicit reference to a corresponding "panel") in a separate class element or in a "data-" attribute. Then the handler could find the panel using that value.
edit — as a hack, you could leverage the fact that the class names of the related elements are both of the form "somethingNN", where "NN" is the numeric part. You could strip off the number and then append it to "panel":
for (var i = 1; i <= 4; ++i) $('.flip' + i).click(function() {
var panelClass = this.className.replace(/.*\bflip(\d+).*/, "panel$1");
$(panelClass).slideToggle('slow');
});
Even though you want to run the selector in a loop, I wouldn't do it like that because you're doing multiple DOM selections. You can get it done with one DOM selection:
$(document).ready(function () {
$('div[class^=flip]').each(function ( idx ) {
$(this).click(function() {
$('.panel' + (idx + 1)).slideToggle("slow");
});
});
});
This will work assuming that the flip elements occur on the page in their numerical order.
This uses the attribute starts with selector to get all <div> elements that have a class name starting with "flip". Change the tag name if it is different, or remove it if they don't all have the same tag.
It uses the index that .each() makes available to you in order to toggle the correct .panel.
$(document).ready(function () {
for (var i=1; i<=4; i++) {
(function(i) {
$('.flip'+i).click(function () {
$('.panel'+i).slideToggle("slow");
});
})(i);
}
});
try this:
$(function(){
for(var i=1;i<5;i++){
$('.flip'+i).click(function () {
$('.panel'+i).slideToggle("slow");
});
}
});
PS:- don't use this it will not work as pointed out by #patrick

Categories

Resources