PDA

View Full Version : Alter 'hover' behavior for a tablet



zimbo
11-13-2012, 04:41 PM
1) Script Title: jQuery Multi Level CSS Menu #2

2) Script URL (on DD): http://www.dynamicdrive.com/style/csslibrary/item/jquery_multi_level_css_menu_2/

3) Describe problem: This script is used in a Wordpress theme I am using to develop a new web site, and it controls the primary navigation menu on each page. The script & menu works fine when viewed on a desktop computer. However, when viewed on a 7 inch tablet there is no mouse with which to 'hover' over a top level heading and activate the drop-down sub-menu. You have to 'press and hold' the heading for about 2 seconds before the sub-menu appears. When it does, the sub-menu stays there until you press something again. If you just press, then no sub-menu appears and you get taken to the linked page of the heading.

The problem is that it is not intuitive to a tablet user that you have to 'press and hold' to get a sub-menu to display, especially for 2 seconds. I have seen some web sites with similar menu functionality in desktop mode to the default operation of this script, but when in mobile mode a sub-menu appears instantly/automatically after pressing the heading (and remains visible until the heading redirects if there's a linked page on the top level). In other words, in mobile mode, a finger press of a menu heading is like hover.

Here's the site: http://dev.henleyherald.com/

Is this behaviour and/or 2 second 'delay' when on a tablet what you'd expect from the script? If not, any suggestions how to fix it, e.g. get the sub-menu to display when pressed?

If there is something 'mobile specific' required, I can target code/CSS within the theme.

bernie1227
11-14-2012, 08:07 AM
Here's a novel idea, you could modify something like [url="http://www.abeautifulsite.net/blog/2011/11/detecting-mobile-devices-with-javascript/"[/url], to detect a mobile device and then change the setup of the dropdown, and then cause this to be the script reference when it's mobile, something like:


if( isMobile.any() ){
var s = document.createElement("script");
s.type = "text/javascript";
s.src = "mobile/jqueryslidemenu.js";
document.getElementsByTagName("head")[0].appendChild(s);
}
else {
var s = document.createElement("script");
s.type = "text/javascript";
s.src = "desktop/jqueryslidemenu.js";
document.getElementsByTagName("head")[0].appendChild(s);
}

Note the paths to the javascript, my idea is to have it respond onclick rather than on hover for the mobile js:


/*********************
//* jQuery Multi Level CSS Menu #2- By Dynamic Drive: http://www.dynamicdrive.com/
//* Last update: Nov 7th, 08': Limit # of queued animations to minmize animation stuttering
//* Menu avaiable at DD CSS Library: http://www.dynamicdrive.com/style/
*********************/

//Update: April 12th, 10: Fixed compat issue with jquery 1.4x

//Specify full URL to down and right arrow images (23 is padding-right to add to top level LIs with drop downs):
var arrowimages={down:['downarrowclass', 'down.gif', 23], right:['rightarrowclass', 'right.gif']}

var jqueryslidemenu={

animateduration: {over: 200, out: 100}, //duration of slide in/ out animation, in milliseconds

buildmenu:function(menuid, arrowsvar){
jQuery(document).ready(function($){
var $mainmenu=$("#"+menuid+">ul")
var $headers=$mainmenu.find("ul").parent()
$headers.each(function(i){
var $curobj=$(this)
var $subul=$(this).find('ul:eq(0)')
this._dimensions={w:this.offsetWidth, h:this.offsetHeight, subulw:$subul.outerWidth(), subulh:$subul.outerHeight()}
this.istopheader=$curobj.parents("ul").length==1? true : false
$subul.css({top:this.istopheader? this._dimensions.h+"px" : 0})
$curobj.children("a:eq(0)").css(this.istopheader? {paddingRight: arrowsvar.down[2]} : {}).append(
'<img src="'+ (this.istopheader? arrowsvar.down[1] : arrowsvar.right[1])
+'" class="' + (this.istopheader? arrowsvar.down[0] : arrowsvar.right[0])
+ '" style="border:0;" />'
)
$curobj.click(
function(e){
var $targetul=$(this).children("ul:eq(0)")
this._offsets={left:$(this).offset().left, top:$(this).offset().top}
var menuleft=this.istopheader? 0 : this._dimensions.w
menuleft=(this._offsets.left+menuleft+this._dimensions.subulw>$(window).width())? (this.istopheader? -this._dimensions.subulw+this._dimensions.w : -this._dimensions.w) : menuleft
if ($targetul.queue().length<=1) //if 1 or less queued animations
$targetul.css({left:menuleft+"px", width:this._dimensions.subulw+'px'}).slideDown(jqueryslidemenu.animateduration.over)
},
function(e){
var $targetul=$(this).children("ul:eq(0)")
$targetul.slideUp(jqueryslidemenu.animateduration.out)
}
) //end hover
$curobj.click(function(){
$(this).children("ul:eq(0)").hide()
})
}) //end $headers.each()
$mainmenu.find("ul").css({display:'none', visibility:'visible'})
}) //end document.ready
}
}

//build menu with ID="myslidemenu" on page:
jqueryslidemenu.buildmenu("myslidemenu", arrowimages)


Get the idea? It's not perfect, but it could work. All this will do is stop you from having to hold it down.

However, there is no proper solution in terms of opening menus, it's either click, or not. An idea, is that rather than actually changing it, put the drop down links as actual a tags, with styling as such to make the user aware. An even more drastic measure, is to redesign the site completely for uses with touch devices, with more natural movements for touch screens.

zimbo
11-14-2012, 11:31 AM
Thanks, useful code snippet to detect a mobile device that I can maybe use elsewhere. I've tried the click change you suggested and it doesn't do what I want on mobile. This issue only arises on tablets in landscape mode because the WP theme is fully responsive and changes the jqueryslidermenu into a different mobile-friendly menu on screen sizes less than ~900px. So as you suggest I can either look at a way of re-styling the menu to imply drop-downs exist (although that doesn't address the press-and-hold issue), or the other option is to force the responsive menu into action on larger tablets.

traq
11-14-2012, 02:50 PM
Another option would be to add :focus alongside :hover in your CSS (or in the script, in this case, which could be quite a bit harder). If there is a link in that top menu item it may need to be removed.

bernie1227
11-14-2012, 09:37 PM
On that idea, you could just try replacing .click/.hover in the red part of the script I posted with .focus (http://api.jquery.com/focus/) to try traq's idea.

traq
11-14-2012, 10:57 PM
I've been messing around with it, and it's not so straightforward, unfortunately. I'll let you know if I figure anything out.

traq
11-14-2012, 11:26 PM
well, a start (http://jsfiddle.net/traq/W4Txx/3/).
requires jQuery 1.7+.
.hover() is replaced with .on() (http://api.jquery.com/on/) to attach the touch events
lots of code repetition (needs to be optimized once it works)



to open the menu, do a touch and a small swipe (the menu will close immediately if you touch only)

to close the menu, touch it again.

Not optimal, obviously, but it's a start.

bernie1227
11-15-2012, 12:08 AM
touch events (http://touchpunch.furf.com/)

traq
11-15-2012, 01:09 AM
not sure that's even intended to work outside of jQuery UI. Doesn't appear to work in this case (http://jsfiddle.net/traq/W4Txx/4).



...but it gave me an idea (http://jsfiddle.net/traq/W4Txx/8/show). very hacky, but it works (at least on Android 4; no iPhone, here) :)

bernie1227
11-15-2012, 02:38 AM
It's quite buggy, but yes it does work on IOS.

traq
11-15-2012, 03:41 AM
what's it do wrong?

bernie1227
11-15-2012, 05:38 AM
There are numerous issues, one being that the submenus don't work at all as afar as I can tell, and opening one does not close another.

traq
11-15-2012, 06:18 AM
hmm... to bad I don't have an iPhone.

zimbo: I can fix that if you want. just send me an iPhone. I'll need a data plan too. And an extra iPhone, in case, uh, I want to test two things at once. My wife says the second iPhone should be pink.

:thumbsup:

...crud, this'll attract some iPhone spam.

bernie1227
11-15-2012, 06:44 AM
You don't need an iPhone, just any idevice will do :p. Anyway, I can test for you if you make any changes.

traq
11-15-2012, 07:35 AM
I don't know what to change... how are you at iTroubleshooting?

bernie1227
11-15-2012, 08:35 AM
What did you change in the js? I'll take a crack at fixing it.

traq
11-15-2012, 02:51 PM
Changed the .hover() event to .on()

Changed the hover event into separate mouseenter and mousseleave events

Added a flag (var touch start) to track if the menu is opened/closed via touch

Added a touchstart event that triggers the normal mouseenter / mouseleave events (determined by the flag).

bernie1227
11-16-2012, 06:24 AM
I've found the issue with the submenus not coming out:


/*********************
//* jQuery Multi Level CSS Menu #2- By Dynamic Drive: http://www.dynamicdrive.com/
//* Last update: Nov 7th, 08': Limit # of queued animations to minmize animation stuttering
//* Menu avaiable at DD CSS Library: http://www.dynamicdrive.com/style/
*********************/

//Update: April 12th, 10: Fixed compat issue with jquery 1.4x

//Specify full URL to down and right arrow images (23 is padding-right to add to top level LIs with drop downs):
var arrowimages={down:['downarrowclass', 'down.gif', 23], right:['rightarrowclass', 'right.gif']}

var jqueryslidemenu={

animateduration: {over: 200, out: 100}, //duration of slide in/ out animation, in milliseconds

buildmenu:function(menuid, arrowsvar){
jQuery(document).ready(function($){
var $mainmenu=$("#"+menuid+">ul")
var $headers=$mainmenu.find("ul").parent()
$headers.each(function(i){
var $curobj=$(this)
var $subul=$(this).find('ul:eq(0)')
this._dimensions={w:this.offsetWidth, h:this.offsetHeight, subulw:$subul.outerWidth(), subulh:$subul.outerHeight()}
this.istopheader=$curobj.parents("ul").length==1? true : false
$subul.css({top:this.istopheader? this._dimensions.h+"px" : 0})
$curobj.children("a:eq(0)").css(this.istopheader? {paddingRight: arrowsvar.down[2]} : {}).append(
'<img src="'+ (this.istopheader? arrowsvar.down[1] : arrowsvar.right[1])
+'" class="' + (this.istopheader? arrowsvar.down[0] : arrowsvar.right[0])
+ '" style="border:0;" />'
)
var touchstart = 1;
$curobj.on( {'mouseenter':
function(e){
var $targetul=$(this).children("ul:eq(0)")
this._offsets={left:$(this).offset().left, top:$(this).offset().top}
var menuleft=this.istopheader? 0 : this._dimensions.w
menuleft=(this._offsets.left+menuleft+this._dimensions.subulw>$(window).width())? (this.istopheader? -this._dimensions.subulw+this._dimensions.w : -this._dimensions.w) : menuleft
if ($targetul.queue().length<=1) //if 1 or less queued animations
$targetul.css({left:menuleft+"px", width:this._dimensions.subulw+'px'}).slideDown(jqueryslidemenu.animateduration.over)
},'mouseleave':
function(e){
var $targetul=$(this).children("ul:eq(0)")
$targetul.slideUp(jqueryslidemenu.animateduration.out)
},'touchstart':function(e){
e.preventDefault();
if( touchstart == 1 ){ $(this).trigger( 'mouseenter' ); touchstart = 0; }
else{ $(this).trigger( 'mouseleave' ); touchstart = 1; }
}
}
)
$curobj.click(function(){
$(this).children("ul:eq(0)").hide()
})
}) //end $headers.each()
$mainmenu.find("ul").css({display:'none', visibility:'visible'})
}) //end document.ready
}
}

//build menu with ID="myslidemenu" on page:
jqueryslidemenu.buildmenu("myslidemenu", arrowimages)​

the problem was that when you clicked on the submenu, it was identifying that as outside of the menu, and thus closing the dropdown.
Commenting out the highlighted line will make the submenus work, but it will also stop them from closing.
The only thing I can think of to make it work, is causing the code to identify the children of the menu as part of the menu.

traq
11-16-2012, 07:05 AM
the problem was that when you clicked on the submenu, it was identifying that as outside of the menu, and thus closing the dropdown.
Commenting out the highlighted line will make the submenus work, but it will also stop them from closing.
The only thing I can think of to make it work, is causing the code to identify the children of the menu as part of the menu.

...?? silly iOS.

I've got an idea. I'll work on it maņana.

bernie1227
11-17-2012, 01:02 AM
alright, I can get it to "work" (I use the term work very loosely) in IOS as such:


/*********************
//* jQuery Multi Level CSS Menu #2- By Dynamic Drive: http://www.dynamicdrive.com/
//* Last update: Nov 7th, 08': Limit # of queued animations to minmize animation stuttering
//* Menu avaiable at DD CSS Library: http://www.dynamicdrive.com/style/
*********************/

//Update: April 12th, 10: Fixed compat issue with jquery 1.4x

//Specify full URL to down and right arrow images (23 is padding-right to add to top level LIs with drop downs):
var arrowimages={down:['downarrowclass', 'down.gif', 23], right:['rightarrowclass', 'right.gif']}

var jqueryslidemenu={

animateduration: {over: 200, out: 100}, //duration of slide in/ out animation, in milliseconds

buildmenu:function(menuid, arrowsvar){
jQuery(document).ready(function($){
var $mainmenu=$("#"+menuid+">ul")
var $headers=$mainmenu.find("ul").parent()
$headers.each(function(i){
var $curobj=$(this)
var $subul=$(this).find('ul:eq(0)')
this._dimensions={w:this.offsetWidth, h:this.offsetHeight, subulw:$subul.outerWidth(), subulh:$subul.outerHeight()}
this.istopheader=$curobj.parents("ul").length==1? true : false
$subul.css({top:this.istopheader? this._dimensions.h+"px" : 0})
$curobj.children("a:eq(0)").css(this.istopheader? {paddingRight: arrowsvar.down[2]} : {}).append(
'<img src="'+ (this.istopheader? arrowsvar.down[1] : arrowsvar.right[1])
+'" class="' + (this.istopheader? arrowsvar.down[0] : arrowsvar.right[0])
+ '" style="border:0;" />'
)
var touchstart = 1;
$curobj.on( {'mouseenter':
function(e){
var $targetul=$(this).children("ul:eq(0)")
this._offsets={left:$(this).offset().left, top:$(this).offset().top}
var menuleft=this.istopheader? 0 : this._dimensions.w
menuleft=(this._offsets.left+menuleft+this._dimensions.subulw>$(window).width())? (this.istopheader? -this._dimensions.subulw+this._dimensions.w : -this._dimensions.w) : menuleft
if ($targetul.queue().length<=1) //if 1 or less queued animations
$targetul.css({left:menuleft+"px", width:this._dimensions.subulw+'px'}).slideDown(jqueryslidemenu.animateduration.over)
},'touchcancel':
function(e){
var $targetul=$(this).children("ul:eq(0)")
$targetul.slideUp(jqueryslidemenu.animateduration.out)
},'touchstart':function(e){
e.preventDefault();
if( touchstart == 1 ){ $(this).trigger( 'mouseenter' ); touchstart = 0; }
else{ $(this).trigger( 'mouseleave' ); touchstart = 1; }
}
}
)
$curobj.click(function(){
$(this).children("ul:eq(0)").hide()
})
}) //end $headers.each()
$mainmenu.find("ul").css({display:'none', visibility:'visible'})
}) //end document.ready
}
}

//build menu with ID="myslidemenu" on page:
jqueryslidemenu.buildmenu("myslidemenu", arrowimages)​

When I say work, I mean that it drops down and such fine and slides back up when you 'click' elsewhere (the touch is canceled).

traq
11-17-2012, 02:26 AM
that breaks the desktop experience (menus don't close on mouseleave; you have to click the menu to close it).

Give this a try (http://jsfiddle.net/traq/W4Txx/9/)?

bernie1227
11-17-2012, 02:31 AM
that's why we have:


var mobile = (/iphone|ipad|ipod|android|blackberry|mini|windows\sce|palm/i.test(navigator.userAgent.toLowerCase()));
if (mobile) {


no dice on the jsfiddle unfortunately

traq
11-17-2012, 03:18 AM
did you use that (I didn't see it), or are you suggesting it now?
I'd be more comfortable using feature detection instead of testing the UA string: checking for touch event support would make a lot of sense in this case.

Is your solution "good" for the iPhone (meaning, are you satisfied with the behavior)?

bernie1227
11-17-2012, 03:41 AM
I'm suggesting that we use it now.

It's far from a perfect solution, but It's also by far the result I've had from anything Ive tried. If you can't find anything else, I'd recommend this.

traq
11-17-2012, 03:51 AM
well, like I said, it's kinda hard for me to find any solution, since I don't really know what it is I'm trying to solve : )

zimbo, does this work well enough for you?