JavaScript setTimeout scope issue - javascript

var toogleDelay = 500;
var closeTimeout = null;
$(">ul>li", $nav).hover(function () {
var $this = $(this);
if(closeTimeout) {
clearTimeout(closeTimeout);
}
var openMenuCallback = function() {
$this.addClass("hover");
};
window.setTimeout(openMenuCallback, toogleDelay);
}, function () {
var $this = $(this);
var closeMenuCallback = function() {
$this.removeClass("hover");
};
closeTimeout = window.setTimeout(closeMenuCallback, toogleDelay);
});
I use this snippet to open and close a multidropdown-menu and I want the menu to fade in and out with a 0.5s delay. I also added a cleartimeout to the mouseover part of the jquery hover function, so that the menu does not close if somebody (accidently) leaves the menuarea and enters it again within the 0.5s. This all works fine, but I now have the problem, because there is more than just one dropdown, that the closeTimeout of lets say the first dropdown gets cleared, if I move the mouse from the first directly to the second dropdown and I now have both dropdown-elements open. How must I rewrite the code, so that every dropdown has its own closeTimeout and at the same time I am still able to clear the timeout in the mouseover part of the hover function.
thx
sub

You could store the timer's id in the element's data. Something like
var $this = $(this);
var closeMenuCallback = function() {
$this.removeClass("hover");
$this.removeData("timerid");
};
var closeTimeout = window.setTimeout(closeMenuCallback, toogleDelay);
$this.data("timerid", closeTimeout);
and then check it with
var $this = $(this);
var closeTimeout = $this.data("timerid");
if(closeTimeout) {
clearTimeout(closeTimeout);
$this.removeData("timerid");
}

I know this isn't part of the question but I like the CSS approach to this.
http://webdesignerwall.com/tutorials/css3-dropdown-menu
there are a number of examples for this. some multilevel some not.

Related

Appear dropdown menu when mouseover two seoconds

I used DROPDOWNHOVER script and i need to make if mouseover two seconds then appear the menu this is the code before my edit:
var Dropdownhover = function (element, options) {
this.options = options
this.$element = $(element)
var that = this
// Defining if navigation tree or single dropdown
this.dropdowns = this.$element.hasClass('dropdown-toggle') ? this.$element.parent().find('.dropdown-menu').parent('.dropdown') : this.$element.find('.dropdown')
this.dropdowns.each(function(){
$(this).on('mouseenter.bs.dropdownhover', function(e) {
that.show($(this).children('a, div'))
})
})
this.dropdowns.each(function(){
$(this).on('mouseleave.bs.dropdownhover', function(e) {
that.hide($(this).children('a, div'))
})
})
}
after my edit:
var Dropdownhover = function (element, options) {
this.options = options
this.$element = $(element)
var that = this
// Defining if navigation tree or single dropdown
this.dropdowns = this.$element.hasClass('dropdown-toggle') ? this.$element.parent().find('.dropdown-menu').parent('.dropdown') : this.$element.find('.dropdown')
var timeout;
this.dropdowns.each(function(){
$(this).on('mouseenter.bs.dropdownhover', function(e) {
timeout = setTimeout(function () {
that.show($(this).children('a, div'))
}, 2000)
})
})
this.dropdowns.each(function(){
$(this).on('mouseleave.bs.dropdownhover', function(e) {
that.hide($(this).children('a, div'))
})
})
}
after my edit it's not working when hover and i must click to open it i try many solutions but failed, What i need when mouseover two seonds then open the dropdown menu.
Any advice ?
I did not read your code and i can only suggest a way to do that yourself. What you want is a setTimeout function and cancel it if needed:
var timeout;
$('.your-Mouse-enter-Menu').mouseenter(function(){
timeout = setTimeout(function(){
//Showing The Sub Menu Code
},2000);
});
$('.your-Mouse-enter-Menu').mouseleave(function(){
clearTimeout(timeout); //cancel opening submenu if mouse leave
});
This should help: https://jsfiddle.net/4eww3pf3/
The JavaScript:
var hovered = false;
$('.my-select').on('mouseenter',function(){
hovered = true;
setTimeout(function(){
if(hovered){
// do your stuff here
$('body').append('2 seconds passed!');
}
},2000);
}).on('mouseleave',function(){
hovered = false;
});
What is it doing?
A hovered variable is defined at the start and set to false. When the <select> is hovered, the variable gets set to true, and back to false when unhovered. When the <select> is hovered, we set a timeout for 2 seconds; once the 2 seconds has passed we check the state of the hovered variable, to find out if we're still hovering on the element or not.

Detect when two different elements are clicked at the same time

I want to run some code when two different elements are clicked at the same time. Obviously we only have one mouse, but it seems like this may be possible on mobile where the user can use two fingers at once.
I found this answer in which a multiple selector is used to achieve this type of functionality:
$('#someElement').mouseleave() || $('#someOtherElement').mouseleave()) {
// do something
});
However, what I want to achieve is more accurately represented by using the && operator:
$('#someElement').click() && $('#someOtherElement').click()) {
// do something
});
Keep track of the click with a timeout and a tolerance of the timespan between clicks you will accept. In the example below if they are clicked within 1 second of each other the logic will trigger:
(function(){
var $el1 = $();
var $el2 = $();
var el1Clicked = false;
var el2Clicked = false;
var tolerance = 1000;
var onEl1Click = function(){
el1Clicked = true;
setTimeout(function(){
el1Clicked = false;
}, tolerance);
if(el2Clicked){
//both are clicked within a tolerance
}
};
var onEl2Click = function(){
el2Clicked = true;
setTimeout(function(){
el2Clicked = false;
}, tolerance);
if(el1Clicked){
//both are clicked within a tolerance
}
};
$el1.on('click', onEl1Click);
$el2.on('click', onEl2Click);
})();
You can use available Mobile JavaScript Frameworks or write down your custom code like this.
Hope this is what you are looking for.

How can I use variables only once with 'on mouseenter' and 'on mouseleave'?

I have multiple objects with different sizes that I want each to display additional box on mouseenter and hide on mouseleave. I have a jquery script that does it perfectly, my only concern is that I am repeating variables twice and something tells me that this can be done without repeating myself.
Problem is they both are strongly based on $(this) core element, so I can't make variables global. My guess was that I should put them withing the element container function, right before on mouseenter and on mouseleave are called, but syntax wise I have no idea how to do it. But again, I might be terribly wrong on that.
Here's the code:
$(document).ready(function() {
$('.box-options').hide();
var $boxModule = $('div.box');
$boxModule.on({
mouseenter: function() {
var $this = $(this), // repeat
$options = $this.find('div.options'), // repeat
$optionsBox = $this.find('div.options-box'); // repeat
var boxHeight = $this.height(), // repeat
boxWidth = $this.width(), // repeat
optionsBoxHeight = $optionsBox.outerHeight(); // repeat
if ( // statement referring to variables above }
else { // statement referring to variables above };
$options.fadeIn(200).addClass('shadow').css({"height" : boxHeight + optionsBoxHeight});
$optionsBox.delay(100).fadeIn(200).css({"top" : boxHeight}, 200);
},
mouseleave: function() {
var $this = $(this), // repeat
$options = $this.find('div.options'), // repeat
$optionsBox = $this.find('div.options-box'); // repeat
var boxHeight = $this.height(), // repeat
boxWidth = $this.width(), // repeat
optionsBoxHeight = $optionsBox.outerHeight(); // repeat
$optionsBox.hide().css({"top" : boxHeight});
$options.hide().removeClass('shadow').css({"height" : boxHeight}, 200);
}
});
});
Obviously the code contains more lines, but the important part is variables marked as // repeat. Does anyone know how I can re-structure the code to make variables be written only once?
UPDATE: I updated the code to describe logic better. Just to make it clear, on each page there are also multiple objects with identical classes, structure and size, only difference is content (text) within and id number.
Use hover function and for variables declare them before hovering event like you have done for $boxModule.
Calling
$( selector ).hover( handlerIn, handlerOut )
is shorthand for:
$( selector ).mouseenter( handlerIn ).mouseleave( handlerOut );
I think the solution for repetition in that code would be to create an external function to get some of the information from the element passed as an argument.
For example:
function options($element) {
return $element.find('div.options');
}
The same goes for every other property based on $(this).
Then you can just use your options inside your event handler as such: options($this).fadeIn(200)
Hope this helps to clean your code.
You can declare var outside on event like :
$('div.box').each(function(){
var $this = $(this),
$options = $this.find('div.options'),
$optionsBox = $this.find('div.options-box'),
boxHeight = $this.height();
$this.on({
mouseenter: function() {...}
mouseleave: function() {...}
});
});
How about making a function that return an object that you can use in every handler?
var calcVars = function(){
var $this = $(this),
$options = $this.find('div.options'),
$optionsBox = $this.find('div.options-box');
var boxHeight = $this.height(),
boxWidth = $this.width(),
optionsBoxHeight = $optionsBox.outerHeight();
return {
boxHeight: boxHeight,
//every other variable you need outside
}
$boxModule.on({
firstEvent: function() {
var object = calcVars.call(this);
object.calculatedProperty.doSomething();
//other code
},
secondEvent: function() {
var object = calcVars.call(this);
object.anotherCalculatedProperty.doSomething();
//other code
}
})
or you can do:
$boxModule.on("anEvent anotherEvent", function(event) {
/*
var declarations
*/
var $this = $(this),
//etc..
if(event.type == "anEvent"){
doStuff();
else if(event.type == "anotherEvent"){
doOtherStuff();
}
})

Click event object tracking woes

So I am working on this but of jQuery that gets the element id through a click event. This then triggers a function that acts like the deprecated .toggle()- it slides an element down on the fist click and slides that element up on the second click. However, there is a bug that causes the element to slide up and down the amount of times that it has been clicked on. For instance, if this is the second time I use the .clickToggle function, the element (table) slides up and down twice before settling, and so on. I suspect it has something to do with the event object, e, tracking the number of clicks-- i.e. I probably shouldn't set id = e.target.id-- but I'm not sure how to fix while still getting the relevant element id that I need.
Here is the relevant clickToggle plug in (courtesy of an answer here on stackoverflow).
(function($) {
$.fn.clickToggle = function(func1, func2) {
var funcs = [func1, func2];
this.data('toggleclicked', 0);
this.click(function() {
var data = $(this).data();
var tc = data.toggleclicked;
$.proxy(funcs[tc], this)();
data.toggleclicked = (tc + 1) % 2;
});
return this;
};
}(jQuery));
Here is the buggy code that fits the above description.
$(document).click(function(e) {
//get the mouse info, and parse out the relevant generated div num
var id = e.target.id;
var strId = id.match(/\d$/);
//clickToggle the individual table
$('#showTable' + strId).clickToggle(function () {
$('#table' + strId).slideDown();
$('#table' + strId).load('files.php');
},
function () {
$('#table' + strId).slideUp();
});
});//close mousemove function
Any help would be much appreciated. Thanks.
The problem is that you're registering a new click handler for the element each time you invoke clickToggle:
this.click(function() {...
On each subsequent click, you add another handler, as well as invoking all previous handlers. Bleagh.
Better to be straightforward: (DEMO)
var showTable = function($table) {
$table.slideDown();
$table.load('files.php');
$table.removeClass('hidden');
};
var hideTable = function($table) {
$table.slideUp();
$table.addClass('hidden');
};
$(document).click(function (e) {
//get the mouse info, and parse out the relevant generated div num
var id = e.target.id;
var strId = id.match(/\d$/)[0];
var $table = $('#table' + strId);
if ($table.hasClass('hidden')) {
showTable($table);
} else {
hideTable($table);
}
});

Stop the touchstart performing too quick when scrolling

I'm trying to figure out how to solve the tapped class being assigned to the elements when scrolling, but it's taking effect too quick which I need to delay it a bit when it's actually touched instead of touched while scrolling, this is my code of how it works:
$('div, a, span').filter('[tappable][data-tappable-role]').bind('touchstart', function()
{
var self = $(this);
self.addClass(self.data('tappable-role'));
}).bind('touchend', function()
{
var self = $(this);
self.removeClass(self.data('tappable-role'));
}).bind('click', function()
{
var self = $(this),
goTo = self.data('goto');
if(typeof goTo !== 'undefined')
{
window.location = goTo;
}
});
When scrolling, it will assign the class to the element when I've barely touched it, I want to prevent this from happening unless it's properly touched (not clicked). Although I tried experimenting with the setTimeout, but that doesn't work well as it delays but it will still assign the class later on.
This is how I did it with the setTimeout:
var currentTapped;
$('div, a, span').filter('[tappable][data-tappable-role]').bind('touchstart', function()
{
clearTimeout(currentTapped);
var self = $(this);
var currentTapped = setTimeout(function()
{
self.addClass(self.data('tappable-role'));
}, 60);
}).bind('touchend', function()
{
clearTimeout(currentTapped);
var self = $(this);
self.removeClass(self.data('tappable-role'));
}).bind('click', function()
{
clearTimeout(currentTapped);
var self = $(this),
goTo = self.data('goto');
if(typeof goTo !== 'undefined')
{
window.location = goTo;
}
});
How can I do this the effective way?
Demo #1 (with setTimeout).
Demo #2 (with no setTimeout)
You need to view it on your iPhone/iPod/iPad or an emulator to test the fiddle.
UPDATE:
function nextEvent()
{
$(this).on('touchend', function(e)
{
var self = $(this);
self.addClass(self.data('tappable-role')).off('touchend');
})
.on('touchmove', function(e)
{
var self = $(this);
self.removeClass(self.data('tappable-role')).off('touchend');
})
.click(function()
{
var self = $(this),
goTo = self.data('goto');
if(typeof goTo !== 'undefined')
{
window.location = goTo;
}
});
}
$('div, a, span').filter('[tappable][data-tappable-role]').on('touchstart', this, nextEvent);
Here's how I did it:
Essentially, when you navigate a page you're going to tap or scroll. (Well there are other things like pinch and slide put you can figure them out later)...
So on a tap your 'touchstart' will be followed by a 'touchend'
On a scroll your 'touchstart' will be followed by a 'touchmove'
Using Jq 1.7... on other versions you can use .bind()
function nextEvent() {
//behaviour for end
$(this).on('touchend', function(e){
/* DO STUFF */
$(this).off('touchend');
});
//behaviour for move
$(this).on('touchmove', function(e){
$(this).off('touchend');
});
}
$('div, a, span').filter('[tappable][data-tappable-role]').on('touchstart', this, nextEvent);
Basically, when a 'touchstart' happens, I bind actions to 'touchend' and 'touchmove'.
'Touchend' does whatever I would want a tap to do and then unbinds itself
'Touchmove' basically does nothing except unbind 'touchend'
This way if you tap you get action, if you scroll nothing happens but scrolling..
RESPONSE TO COMMENT: If I understand your comment properly, try this:
function nextEvent() {
var self = $(this);
self.addClass(self.data('tappable-role'))
//behaviour for move
$(this).on('touchmove', function(e){
self.removeClass(self.data('tappable-role'));
});
}
$('div, a, span').filter('[tappable][data-tappable-role]').on('touchstart', this, nextEvent);
Despite this is a relatively old question with a best answer already selected, I want to share my solution.
I achieved this by triggering the events just on click.
$("div, a, span").on("click", function() {
// Your code here
}
Maybe is not the best way to do it, but this worked for me.

Categories

Resources