I'm trying to get my head around to understand how to use closures in connection with jQuery event functions.
My current problem is to round shapes on the screen and make them stop and fade on mouseover and fade and restart on mouse out. I've to use imagemaps to create a round mouseover sensitive area. While the animation works fine I'm having trouble to make use of closures on the mouseover function as i would like it to be.
Given this setup:
(function($){
$.fn.xyz = function( option ) {
// override defaults with specified option
option = $.extend( {}, $.fn.xyz.option, option );
return this.each(function(index, element) {
// run works fine.
function run(index) {
$(".ball:eq(" + index + ")").css({top: 500).startAnimation({ top: -500}, 1000, "linear", (function (i) {
return function() {
run(i);
}})(index));
}
//1 this version works great but I don't like the .parent().parent() especially as the animation requires
// just the ball I hover over gets the opacity assigned
$("area").mouseover(
function () {$(this).parent().parent().css('opacity', 0.5);}
);
//2 this version makes all balls transparent on page load
$("area").mouseover(
(function (activeElement) {
$(activeElement).css('opacity', 0.5);
})(this)
);
//3 this version makes all balls transparent on the first mouse over event
$("area").mouseover(
(function (activeElement) {
return function() {
$(activeElement).css('opacity', 0.5);
}
})(this)
);
//4 also this version affecs all balls and not just the one that is mouse overed
var activeBall = $(this);
$("area").mouseover(function () {
$(activeBall).css('opacity', 0.5);
}).mouseout(function () {
$(activeBall).css('opacity', 1);
});
run(index);
});
},
$.fn.xyz.option = {};
})(jQuery);
Why does version 2, 3, and 4 target all elements and not just the one which is actively hovered over. How would I make use of closures to have to avoid to make use of indexes or similar workarounds?
Many thanks!
You made it a Self Invoking Anonymous Function. Basically, called it automagically with the jQuery object. You also wrapped functions in functions...which I don't get. This should work:
(function($){
$.fn.xyz = function( option ) {
// override defaults with specified option
option = $.extend( {}, $.fn.xyz.option, option );
return this.each(function(index, element) {
// run works fine.
function run(index) {
$(".ball:eq(" + index + ")").css({top: 500).startAnimation({ top: -500}, 1000, "linear", (function (i) {
return function() {
run(i);
}})(index));
}
//1 this version works great but I don't like the .parent().parent() especially as the animation requires
// just the ball I hover over gets the opacity assigned
$("area").mouseover(
function () {$(this).parent().parent().css('opacity', 0.5);}
);
//2 this version makes all balls transparent on page load
$("area").mouseover(
(function (activeElement) {
$(activeElement).css('opacity', 0.5);
})
);
//3 this version makes all balls transparent on the first mouse over event
$("area").mouseover(
(function (activeElement) {
return function() {
$(activeElement).css('opacity', 0.5);
}
})
);
//4 also this version affecs all balls and not just the one that is mouse overed
var activeBall = $(this);
$("area").mouseover(function () {
$(activeBall).css('opacity', 0.5);
}).mouseout(function () {
$(activeBall).css('opacity', 1);
});
run(index);
});
},
$.fn.xyz.option = {};
})(jQuery);
Basically, SIAF is doing things like this:
(function(txt) { alert(txt); })('Hello world!');
You declare an anonymous function (it has no name), which accepts a parameter, and then with the parentheses at the end, you call it, and what's in the parens are the function's parameters.
So, when you said
(function (activeElement) {
return function() {
$(activeElement).css('opacity', 0.5);
}
})(this)
The compiler saw "activate the function with the this object as parameter". Seeing as how this would refer outside your declared function to the jQuery object, jQuery saw it as "change all elements I have with the .css function".
Related
The problem is that when i use the toggle function without any options i.e default options the 'is(':visible')' on the item returns me the correct state.
However when i use toggle("slow"), it reveals incorrect state and always shows the item operated upon by the toggle as visible false. Of course i am checking that inside the callback function so as to be sure that the animation is complete.
please look at the below code
jQuery(document).ready(function () {
var h3 = jQuery("#myAccordion").find('h3');
jQuery("#myAccordion").find('h3').find('span').addClass("ui-state-active ui-icon");
jQuery.each(h3, function () {
jQuery(this).bind('click', function () {
jQuery(this).next('div').toggle("slow", "swing", callback);
});
});
});
function callback () {
if (jQuery(this).next('div').is(':visible')) {
alert('visible--' + jQuery(this).next('div').is(':visible'));
jQuery(this).find('span').removeClass("ui-state-default ui-icon").addClass("ui-state-active ui-icon");
}
else {
alert('visible--' + jQuery(this).next('div').is(':visible')); // always goes into this 'else' even though the item is visible.
jQuery(this).find('span').removeClass("ui-state-active ui-icon").addClass("ui-state-default ui-icon");
}
}
However the same works perfectly fine when not using the "slow" option with toggle.
Update 2:
Check this out here http://jsfiddle.net/tariquasar/7xt7D/2/
Any pointers...
Update 1: This is the fiddle http://jsfiddle.net/tariquasar/7xt7D/
The context this is not extended to the callback function too. You could try doing this. I have updated the jsfiddle (click here). Ill paste the same here.
jQuery(document).ready(function () {
var h3 = jQuery("#myAccordion").find('h3');
jQuery("#myAccordion").find('h3').find('span').addClass("ui-state-active ui-icon"); // first the item is visible
jQuery.each(h3, function () {
jQuery(this).bind('click', function () {
console.log(this);
jQuery(this).next('div').toggle("slow","swing",callback(this));
});
});
});
function callback (that) {
setTimeout( function () {
console.log(jQuery(that).next('div').is(':visible'));
if (jQuery(that).next('div').is(':visible')) {
alert('visible--' + jQuery(that).next('div').is(':visible'));
jQuery(that).find('span').removeClass("ui-state-default ui-icon").addClass("ui-state-active ui-icon");
}
else {
alert('visible--' + jQuery(that).next('div').is(':visible'));
jQuery(that).find('span').removeClass("ui-state-active ui-icon").addClass("ui-state-default ui-icon");
}
}, 1000);
}
I have added a SetTimeout to get the result you wanted. The callback function is called after the animation completes. Yes. But not after the CSS changes to display:none. CSS change happens a few millisecs later.
However the same works perfectly fine when not using the "slow" option with toggle.
I'm not really sure about how you got it working with options other than slow
I even looked at this and this solution still didn't help me : Execute a function after X seconds in jquery
Here is my code:
// featured bounce
$('#featured .animated').hover(function() {
$(this).addClass('bounce');
setTimeout( function(){
$(this).removeClass('bounce');},
1300
);
});
The adding of the class works, but the setTimeout ordeal will not work. It won't even execute and not javascript error is thrown in the Chrome console. I feel like I have everything typed out correctly.. the class on the .animated object after the addClass() looks like this:
"animated bounce"
And the animation plays, but then it NEVER removes the "bounce" from the class attribute.
Any help?
Using Function.prototype.bind correctly, you can avoid cheap context hacks like var that = this.
// featured bounce
$('#featured .animated').hover(function() {
var elem = $(this);
elem.addClass('bounce');
setTimeout(elem.removeClass.bind(elem, 'bounce'), 1300);
});
Side Note: Function.prototype.bind is an ES5 addition and browser support needs to be considered. See the compatibility table at the bottom of the MDN article on the function.
The scope of this is pointing at window, not the element you expect.
$('#featured .animated').hover(function() {
var elem = $(this);
elem.addClass('bounce');
setTimeout( function(){
elem.removeClass('bounce');},
1300
);
});
$('#featured .animated').hover(function() {
$(this).addClass('bounce');
(function(that) {
setTimeout( function(){
// use `that` instead of `this`
$(that).removeClass('bounce');
}, 1300);
})(this); //pass `this` into this function
});
I'm new to javascript and jquery and I am trying to make a video's opacity change when I mouseover a li item. I know 'onmouseover' works because I have tested using the same jquery I use to scroll to the top of the page onclick.
The problem seems to be the syntax to check and update the style of the video div is not working. I adapted the code from a lesson on codeacademy and don't see why it work:
window.onload = function () {
// Get the array of the li elements
var vidlink = document.getElementsByClassName('video');
// Get the iframe
var framecss = document.getElementsByClassName('videoplayer1');
// Loop through LI ELEMENTS
for (var i = 0; i < vidlink.length; i++) {
// mouseover function:
vidlink[i].onmouseover = function () {
//this doesn't work:
if (framecss.style.opacity === "0.1") {
framecss.style.opacity = "0.5";
}
};
//onclick function to scroll to the top when clicked:
vidlink[i].onclick = function () {
$("html, body").animate({
scrollTop: 0
}, 600);
};
}
};
Here is a jsfiddle you can see the html and css:
http://jsfiddle.net/m00sh00/CsyJY/11/
It seems like such a simple problem so I'm sorry if I'm missing something obvious.
Any help is much appreciated
Try this:
vidlink[i].onmouseover = function () {
if (framecss[0].style.opacity === "0.1") {
framecss[0].style.opacity = "0.5";
}
};
Or alternatively:
var framecss = document.getElementsByClassName('videoplayer1')[0];
Or, better, give the iframe an id and use document.getElementById().
The getElementsByClassName() function returns a list, not a single element. The list doesn't have a style property. In your case you know the list should have one item in it, which you access via the [0] index.
Or, given that you are using jQuery, you could rewrite it something like this:
$(document).ready(function(){
// Get the iframe
var $framecss = $('.videoplayer1');
$('.video').on({
mouseover: function () {
if ($framecss.css('opacity') < 0.15) {
$framecss.css('opacity', 0.5);
}
},
click: function () {
$("html, body").animate({
scrollTop: 0
}, 600);
}
});
});
Note that I'm testing if the opacity is less than 0.15 because when I tried it out in your fiddle it was returned as 0.10000000149011612 rather than 0.1.
Also, note that the code in your fiddle didn't run, because by default jsfiddle puts your JS in an onload handler (this can be changed from the drop-down on the left) and you then wrapped your code in window.onload = as well. And you hadn't selected jQuery from the other drop-down so .animate() wouldn't work.
Here's an updated fiddle: http://jsfiddle.net/CsyJY/23/
I've seen other similar questions to this on SO but the answers weren't quite what I'm looking for. My problem with the code below is about the variable hide.
In it's current form hide won't be visible to the first hover function, but I don't want to declare it at a higher scope because it has no use there. Plus declaring it at a higher scope would require making a different variable for every li.
What's the solution for keeping this variable containing a timeout between these two functions?
$('li').hover(function() {
clearTimeout(hide);
$('.menu', this).show();
}, function() {
var menu = $('.menu', this);
var hide = setTimeout(function() {
menu.hide();
}, 500);
});
You can store the value with .data()
$('li').hover(function() {
clearTimeout($(this).data('hide'));
$('.menu', this).show();
}, function() {
var menu = $('.menu', this);
$(this).data('hide', setTimeout(function() {
menu.hide();
}, 500));
});
could someone please help me out with the (simplified) code below. I'm trying to call the doTheSlide() function from within the slide event function. I'm still a tad shady on my understanding of scope in JS.
What is a correct way to achieve this? I'm getting this error:
Uncaught TypeError: object is not a function
(function($) {
bindEvent = function(slider) {
slider.bind('slide', function(event, ui) {
doTheSlide(ui.value);
});
}
doTheSlide = function(value) {
//Animate the slide
}
var methods
})(jQuery);
You need to declare 'doTheSlide' somewhere. You seem to be using it as a global function, which is wrong most of the time. Make sure that everything is declared properly (with var or using a named function).
Here is a fixed version of your code (the way I prefer it):
(function($) {
function doTheSlide(value) {
//Animate the slide
}
function bindEvent(slider) {
slider.bind('slide', function(event, ui) {
doTheSlide(ui.value);
});
}
})(jQuery);
Remember to declare functions before they are used. It will work to call a function that is declared later, but it is not good style.
Alternative syntax:
(function($) {
var doTheSlide = function(value) {
//Animate the slide
};
var bindEvent = function(slider) {
slider.bind('slide', function(event, ui) {
doTheSlide(ui.value);
});
};
})(jQuery);