I'm creating a responsive template and I want to remove the listeners on an element when screen is being resized or is smaller than the specified width.
Imagine an menu which when you hover on it's items, it shows you the sub-menus in normal displays but the same menu in mobile devices will show the sub-menus only by tapping or clicking on the items.
I can't make the undelegate work. In resized screen I still have the mouseover and mouseout event-listeners. I'm not getting any errors in console and I've tried both:
.off('mouseover', 'li')
.off('mouseover')
.undelegate('li', 'mouseover')
.undelegate('li')
and none of them works.
var $window = $(window);
function handleSidenav() {
$(".nav-list").delegate('li', 'mouseover', function(e) {
$(this).find("a").addClass('active');
$(this).find("div.sub-items").toggle();
}).delegate('li', 'mouseout', function(e) {
$(this).find('a').removeClass('active');
$(this).find("div.sub-items").toggle();
});
}
function checkWidth() {
var windowsize = $window.width();
if (windowsize < 767) {
smallScreenDelegation();
} else {
SmallScreenUndelegation();
}
}
checkWidth();
handleSidenav();
$window.resize(checkWidth());
function smallScreenDelegation() {
$(".nav-list").undelegate('li'); //It's not working
$(".nav-list").undelegate('li'); //It's not working
$(".nav-list").delegate('li a:first', 'click', function(event) {
if ($(this).next().is(':hidden')) {
$(this).addClass('active');
$(this).next().slideDown('slow');
} else {
$(this).removeClass('active').next().slideUp('slow');
}
event.preventDefault();
});
}
You need to wrap window in the jQuery object. I'm not sure if you set $window = $(window), but it seems here that $window.width() and $window.resize(checkWidth) are missing parenthesis. I was able to get it working fine once I changed those to $(window). You have to define which event you want to undelegate. I used:
$('.nav-list').undelegate('li', 'mouseover');
Open up console and you can see that it works: http://jsbin.com/efonut/6/edit
Also, it's really best to use .on() and off() vs .delegate() and .undelegate(), but at least this works...
I still don't know what was wrong with undelegate which I couldn't make it work, but I managed to fix my code by using on and off.
As adeneo said I was delegating and undelegating on each window resize which was quiet a bug and I think I fixed that but holding the last state on device variable.
var $window = $(window);
var device;
function desktopSidenav() {
$(".nav-list > li").off('click');
$(".nav-list > li").on('mouseover', function(e) {
$(this).find("a").addClass('active');
$(this).find("div.sub-items").toggle();
}).on('mouseout', function(e) {
$(this).find('a').removeClass('active');
$(this).find("div.sub-items").toggle();
});
}
function handheldSidenav() {
$(".nav-list > li").off('mouseover').off('mouseout');
$(".nav-list > li").on('click', function(e) {
if ($(this).find("div.sub-items").is(':hidden')) {
$(this).find("a:first").addClass('active').next().slideDown('slow');
} else {
$(this).find("a:first").removeClass('active').next().slideUp('slow');
}
e.preventDefault();
});
}
Now I check the window size before doing anything else an I'll hold the device type in device variable. If window is resized, I'm gonna check the device state and do the things based on device type.
if ($window.width() > 767) {
device = 'desktop';
desktopSidenav();
} else {
device = 'handheld';
handheldSidenav();
}
$window.resize(function() {
if ($window.width() > 767) {
if (device == 'handheld') {
device = 'desktop';
desktopSidenav();
}
} else {
if (device == 'desktop') {
device = 'handheld';
handheldSidenav();
}
}
});
If I use delegate and undelegate instead of on and off, the code won't work and I still don't know why, so this cannot be count as a real answer, but I wanted to tell everyone who has a similar problem to use jQuery's on and off instead on delegate.
Related
what I'm trying to do
I have dropdown-menus that open on hover and the parent menus have their own landing page link. we're not willing to sacrifice this behavior, but if obviously creates problem for large touch enabled devices. So, I'm detecting touch devices with jquery, and disabling the parent menu click on devices larger than 990px wide. devices below 990px is considered as mobile view and it switches to toggle. This switch between the toggle and the desktop view is expected to continue on screen rotation too.
what is happening
the menu link is disabled on first load and works as expected. Then I rotate the screen (from landscape to portrait) and see the mobile menu as expected and navigate to another page. once the page loads, I rotate it again (from portrait to landscape) and the desktop view is visible, but the parent links are clickable now!
I want to prevent this click event on second rotation too. HTML is standard bootstrap 3 navigation code and my js is like this:
function isTouchDevice() {
return true == ("ontouchstart" in window || window.DocumentTouch && document instanceof DocumentTouch);
}
$(document).ready(function () {
$(window).resize(function () {
var o = $(window).innerWidth();
function isTouchDevice() {
return true == ("ontouchstart" in window || window.DocumentTouch && document instanceof DocumentTouch);
}
if ((isTouchDevice() === true) && (o >= 990)) {
$('.navbar .dropdown > a ').each(function () {
$(this).on("click", function(){
return false
})
})
alert('oi!!')
}
else {
$('.navbar .dropdown > a ').each(function () {
$(this).on("click", function(){
location.href = this.href;
})
});
alert ("bad!") //for debugging purpose, not really needed
}
}).resize();
//the mobile menu clicks events
$('#menu .dropdown > a ').click(function () {
location.href = this.href;
});
});
PS this is a website, not an android app. I have found answers that answer this type of questions for android apps.
Update the jsfiddle for my code
I solved it myself. Turns out, the condition for width checking was creating the problem and in my case, unnecessary, because bootstrap is already hiding the menu in smaller screens and I was targeting touch enabled desktop devices anyway. so I took off && (o >= 990) from the if condition and it is working as expected.
full js is below (in case anyone needs it). I used the timer to prevent the event from firing before the resize, but it will probably work without the timer too. :
$(document).ready(function () {
var resizeTimer;
$(window).on('resize', function(e){
clearTimeout(resizeTimer);
resizeTimer = setTimeout(function () {
function isTouchDevice() {
return true == ("ontouchstart" in window || window.DocumentTouch && document instanceof DocumentTouch);
}
if (isTouchDevice() === true) {
$('.navbar .dropdown > a ').click(function () {
return false
});
console.log("landscape")
}
else {
$('.navbar .dropdown > a ').each(function () {
$(this).on("click", function(){
location.href = this.href;
})
});
console.log("portrait")
}
}, 250)
}).trigger('resize');
});
I think this is a problem with the way you are recognizing the mobile device. For checking device sizes I would not suggest using $(window).innerWidth(). What you are doing now does not check the screen size, rather it checks the window size, which fluctuates when switching orientation.
I would like to suggest that instead of checking for only >900px, that you check for the entire area of the device (width x height) so landscape and portrait would act the same way. And I would like to suggest using screen.availHeight * screen.availWidth to determine this.
I really hope this helps you with your problem. Please let me know if not and I'll help you debug.
$(window).scroll(function() {
var windscroll = $(window).scrollTop();
if (windscroll >= 5) {
$('#page-header').addClass('fixed');
} else {
$('#page-header').removeClass('fixed');
}
}).scroll();
Why my fix menu in not working smoothly on scrolling. i am using in my moodle theme frontpage.php or i have to add some thing for smoothness.
You're modifying the DOM too frequently, as $(window).scroll fires multiple times in a single scroll. Consider checking the existence of the class before add or remove it.
$(window).scroll(function() {
var windscroll = $(window).scrollTop();
if (windscroll >= 5) {
if(!$('#page-header').hasClass('fixed')) {
$('#page-header').addClass('fixed');
}
} else {
if(!$('#page-header').hasClass('fixed')) {
$('#page-header').removeClass('fixed');
}
}
});
also, I removed an extra .scroll() call at the end of the script.
Alternatively, you can make use of a jQuery On Screen plugin which will add a pseudo class :onscreen with the div visible in the browser. Codes are as follow:
$(document).scroll(function() {
if($("#page-header").is(':onScreen')) {
console.log("Element appeared on Screen");
if(!$('#page-header').hasClass('fixed')) {
$('#page-header').addClass('fixed');
}
} else {
console.log("Element not on Screen");
if(!$('#page-header').hasClass('fixed')) {
$('#page-header').removeClass('fixed');
}
}
});
See if the plugin fits your needs.
I've got a fairly simple navigation menu that opens and closes on click. The menu behaviour only comes into play when the browser viewport is below a certain size.
It all works great 90% of the time. The remaining 10% of the time (when I'm demonstrating it to the client, natch) the click event doesn't fire at all. As far as I can tell, the problem only occurs after the browser has been resized a few times, but as it usually works normally when the window has been resized, it's difficult to track down why it's happening.
Code:
var smallViewport = false;
$(document).ready(function(){
if($(window).width() < 520) {
smallViewport = true;
}
if(smallViewport == true) {
$('nav.main').click(function(){
console.log(' + clicky clicky');
if($(this).find('.level-1').hasClass('open') == true) {
$(this).find('.level-1').slideUp('fast').removeClass('open');
} else {
$(this).find('.level-1').slideDown('fast', function(){ $(this).addClass('open'); });
}
})
}
});
$(window).resize(function() {
if($(window).width() < 520) {
smallViewport = true;
} else {
smallViewport = false;
}
console.log(smallViewport);
if(smallViewport == true) {
$('.level-1').removeClass('open').css('display','none');
} else {
$('.level-1').css('display','block');
}
});
When the problem chooses to manifest itself, console.log(smallViewport) in the resize function outputs 'true' when it should be true, the click event just refuses to fire along with it.
Has anybody encountered a similar problem before? Any obvious solutions I'm missing?
You're only binding the click when the page loads, not when it's resized
if $(window).width() < 520 evaluates as false on the page load, the click event will not be bound - which is why your console log is correct but the event is not firing
Put the viewport check inside the click event handler. As it is now, the event handler isn't bound if the check evaluates to false on page load. Try changing it to this:
$('nav.main').click(function(){
if(smallViewport == true) {
console.log(' + clicky clicky');
if($(this).find('.level-1').hasClass('open') == true) {
$(this).find('.level-1').slideUp('fast').removeClass('open');
} else {
$(this).find('.level-1').slideDown('fast', function() {
$(this).addClass('open');
});
}
}
});
I'm curious is there an event listener or perhaps a way to construct a method that will trigger when a CSS change happens?
My stylesheet uses media queries and I want to know if there's a way to attach a listener to see when those media queries kick in and out. For example I have a media query that hides a button at certain screen widths
#media screen and (max-width: 480px) {
#search-button {
display: none;
}
}
What event listener would I use to detect when that display changes? I'm currently doing this:
$(window).resize(function() {
if($('#search-button').css("display") == "none") {
//do something
} else {
//do something else
}
});
Which works fine, but it calls the listener every time the user changes the screen and I'd rather just have it fire only when the css of the button changes. I hope that makes sense.
for example this is what I'd like
$('#search-button').cssEventListenerOfSomeKind(function() {
alert('the display changed');
});
Binding to the window.resize is your best option (I believe). There isn't any event fired when you change an element's CSS. You can however optimize a bit by caching the selector used:
var $searcButton = $('#search-button');
$(window).resize(function() {
if($searcButton.css("display") == "none") {
//do something
} else {
//do something else
}
});
Or you can use $(window).width() to check the width of the viewport:
var $window = $(window);
$window.resize(function() {
if($window.width() <= 480) {
//do something
} else {
//do something else
}
});
UPDATE
You can always throttle your own event handler:
var $window = $(window),
resize_ok = true,
timer;
timer = setInterval(function () {
resize_ok = true;
}, 250);
$window.resize(function() {
if (resize_ok === true) {
resize_ok = false;
if($window.width() <= 480) {
//do something
} else {
//do something else
}
}
});
This will prevent the code in your resize event handler from running more than once every quarter second.
If it is only a one time event you could try to unbind the event.
http://api.jquery.com/unbind/
I know this is old but I managed to solve it with this logic
// set width and height of element that is controlled by the media query
var page_width = $page.width();
var page_height = $page.height();
$window = $(window).resize(function(){
if( $page.width() != page_width ) {
// update page_width and height so it only executes your
// function when a change occurs
page_width = $page.width();
page_height = $page.height();
// do something
// ...
}
});
Any insights on how to catch a scrolling event on a element that has overflow:hidden? I would like to scroll in a column without showing a scrollbar to the user.
This is actually a somewhat indepth process. What I do is set global flags when users mouse enters and leaves the element that you want to scroll. Then, on the mousewheel event for the body I check to see if the MOUSE_OVER flag is true, then stop propagation of the event. This is so the main body doesnt scroll in case your entire page has overflow.
Note that with overflow hidden, the default scrolling ability is lost so you must create it yourself. To do this you can set a mousewheel listener on your div in question and use the event.wheelDelta property to check whether the user is scrolling up or down. This value is different according to browser, but it is generally negative if scrolling down and positive if scrolling up. You can then change position of your div accordingly.
This code is hacked up quickly but it would essentially look like this...
var MOUSE_OVER = false;
$('body').bind('mousewheel', function(e){
if(MOUSE_OVER){
if(e.preventDefault) { e.preventDefault(); }
e.returnValue = false;
return false;
}
});
$('#myDiv').mouseenter(function(){ MOUSE_OVER=true; });
$('#myDiv').mouseleave(function(){ MOUSE_OVER=false; });
$('#myDiv').bind('mousewheel', function(e){
var delta = e.wheelDelta;
if(delta > 0){
//go up
}
else{
//go down
}
});
I use overflow:scroll, but also Absolutely position a div over the scroll bar in order to hide it.
$("body").css("overflow", "hidden")
$(document).bind('mousewheel', function(evt) {
var delta = evt.originalEvent.wheelDelta
console.log(delta)
})
works for me. adapted from How do I get the wheelDelta property?
I edited #anson s answer to Vanilla Javascript since it may be useful for others. Also note that "mousewheel" event is deprecated. So my code uses "wheel" instead. Next to that I added arrow functions for practical access the to "this".
fixScrollBehavior(elem) {
elem.addEventListener('scroll', (e) => {
console.log('scrolling');
});
let MOUSE_OVER = false;
elem.addEventListener('wheel', (e) => {
if (MOUSE_OVER) {
if (e.preventDefault) {
e.preventDefault();
}
e.returnValue = false;
return false;
}
});
elem.addEventListener('mouseenter', () => {
MOUSE_OVER = true;
});
elem.addEventListener('mouseleave', () => {
MOUSE_OVER = false;
});
elem.addEventListener('wheel', (e) => {
let delta = e.wheelDelta;
if (delta > 0) {
//go up
} else {
//go down
}
});
}
Note that this does not fix the mobile touch-"scroll"s.
$("div").on('wheel', function (e) {
if (e.originalEvent.deltaY < 0) {
console.log("Scroll up");
} else {
console.log("Scroll down");
}
});
This did the trick for me.
JSFiddle
StackFiddle:
$("div").on('wheel', function(e) {
if (e.originalEvent.deltaY < 0) {
console.log("Scroll up");
} else {
console.log("Scroll down");
}
});
div {
height: 50px;
width: 300px;
background-color: black;
overflow: hidden;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div></div>
I am late, but I think I have a better answer.
Style your container as overflow: overlay, this will free up space of scrollbar, then style scrollbar or hide it or make its handle height/width 0,
Then you should get scroll events also.
Note : styling the scrollbar is not supported in all web browsers.