Scrolling menu on iPhone - javascript

I have problem with my dropdown menu on iPhone. Whenever I have more than 6 links on my menu the list is too long and whenever I want to scroll it little bit down with finger I can't because I press on link and its instantly fire to another url. How I can avoid that and recognize that I want to scroll menu little bit down and how to recognize if I tapped a menu li to go into another url?
$(".content-bar--content").on("click", function() {
window.location.href = link;
});
This is what my code looks like.

Based on https://www.falise.com/blog/prevent-click-event-scrolling-ipad/ without jquery
First we check if the device is iOS or not
var iOS = agent.indexOf('iphone') >= 0 || agent.indexOf('ipad') >= 0;
We also need a variable which will tell us if the screen is being touched and moved.
var touchMoving = false;
Then for iOS only add event listeners that listen for touchmove and touchend that will set touchMoving to the appropriate value.
if (iOS)
{
document.addEventListener("touchmove", function(e)
{
touchMoving = true;
});
document.addEventListener("touchend", function(e)
{
touchMoving = false;
});
}
Now when a link with a class content-bar--content is clicked while scrolling it will prevent the click event because touchMoving is true.
document.querySelectorAll('.content-bar--content').forEach(elem => {
elem.addEventListener('click', function(e) {
if (touchMoving) {
e.preventDefault();
}
}
});

Related

Disable text selection on right click

I'm writing a desktop app using nwjs, and I want to use the right mouse button for some UI functions. This is working pretty ok right now; I am able to disable the context menu when the right click was for a UI function.
However, I am having an awful time figuring out how to not only stop right click events from opening a context menu, but also to stop them from selecting the text under the cursor.
Here is an example of what is happening (that I do not want to happen) - I am left-click dragging a handle to resize a UI view, and then while the left mouse is held down I am right clicking to cancel the resize. When the right click ends over any text, the text is selected. (Normally, a context menu would also appear.)
When handling the right mouse down event and context menu event, I am calling event.preventDefault() and returning false.
What the actual event handler code looks like (appearing in the same order as the events are spawned and handled)...
this.windowMouseDownListener = event => {
if(this.draggingResize &&
event.button === 2 && !event.ctrlKey
){
for(let view of this.area.views){
view.size = view.sizeBeforeDrag;
}
this.area.updateElementSizes();
this.draggingResize = false;
this.recentDraggingResize = true;
event.preventDefault();
event.stopPropagation();
return false;
}
};
this.windowContextMenuListener = event => {
if(this.recentDraggingResize){
event.preventDefault();
return false;
}
};
this.windowMouseUpListener = event => {
this.sizeBeforeDrag = this.size;
if(this.size <= 0.0001){
this.area.removeView(this);
}
if(this.draggingResize || this.recentDraggingResize){
this.recentDraggingResize = false;
this.draggingResize = false;
event.preventDefault();
event.stopPropagation();
return false;
}
};
How can I fix this behavior?

Event listener does not seem to recognise my class?

I have a mobile menu that opens when the open menu button is clicked. I am trying to add a trap focus functionality to the menu when it is open so that users cannot access outside of my menu with a keyboard when tabbing.
I have used an event listener to listen for tab and shift tab keydown events in the menu. The logic is that if the active element does not have a class that only my menu items have then to lock focus back onto the close button of my menu.
Problem is that the focus gets stuck on my close button instantly and I cannot move it (even though it has the class "in-listview"). I realise that my code may not be the best way to implement this feature but for now I would just like to know why my class does not seem to be recognised and I can tidy up after. My app is built using ReactJS.
openNav = () => {
const listView = document.querySelector(".listview");
listView.classList.remove('closeMobMenu');
listView.style.visibility = "visible";
listView.classList.add('openMobMenu');
document.addEventListener('keydown', this.trapTabMobMenu)//Maybe move this to componentDidMount()?
}
trapTabMobMenu = (event) => {
if(event.keyCode === 9) { // if the tab key is pressed in the mobile menu
const closeBtn = this.closeBtn;
let activeElement = document.activeElement;
if(event.shiftKey) { // shift-tab
event.preventDefault();
//if focused element does not have class in-list-view
if(!activeElement.classList.contains("in-listview")) {
closeBtn.focus();
}
} else { // normal tab
event.preventDefault();
if(!activeElement.classList.contains("in-listview")) {
closeBtn.focus();
}
}
}
if(event.keyCode === 27) {
this.closeNav();
}
}
<button ref={(close) => { this.closeBtn = close; }} className="closebtn in-listview" onClick={closeNav} onKeyPress={handleKeyPress} tabIndex="2">×</button>
document.activeElement is not set until after the focus event has been completed, so the above code is not performing your logic on the new element that is firing the onFocus event.
You need to change document.activeElement to be event.target then perform your logic.
https://developer.mozilla.org/en-US/docs/Web/API/DocumentOrShadowRoot/activeElement

jquery angularjs how to know if mouse down event is trigger by scrollbar of browser

I have a small panel which i close if mouse down button is pressed anywhere else than that panel, basically it clears the data to display and just with the help of angularjs ng-show i hide it if there is no data...application is in angularjs and jquery
please find the code below
var closeSearchResultsIfClickedOutside = function (e) {
if ($(e.target).parents('.searchResults').length === 0) {
var scope = angular.element($("#searchContainer")).scope();
scope.$apply(function () {
/*Cancels any existing search*/
if ($scope.defer != undefined) {
$scope.defer.resolve();
}
$scope.showSearchResults = false;
reinitialize();
});
$("html").off("mousedown", closeSearchResultsIfClickedOutside);
reinitializePanelsWidth();
}
};
but i dont want to close this panel if mouse down is on scrollbar of browser window or any scrollbar..please tell me how to do that
to fix the above problem i am not capturing both event, mouse down and click, if the target element on both event matches then only i am closing the panel.
/*
If mouse down and click on the same control, then only close the panel,
Click event closing is added to avoid closing panel on scrollbar click.
*/
var closeSearchResultsIfClickedOutside = function (e) {
if (e.type === 'mousedown') { /* only if mouse down is outside of search panel then only close the panel. */
if($(e.target).parents('.searchResults').length === 0)
{
isMouseDownOnSearchPanel = true;
}
else {
isMouseDownOnSearchPanel = false;
}
}
else if (e.type === 'click' && isMouseDownOnSearchPanel) { /*click event is implemented to avoid closing when mouse down is on scrollbar. you dont get click get event for scrollbar*/
var scope = angular.element($("#searchContainer")).scope();
$("html").off("mousedown", closeSearchResultsIfClickedOutside);
$("html").off("click", closeSearchResultsIfClickedOutside);
isMouseDownOnSearchPanel = false;
reinitializePanelsWidth();
}
};

Temporarily disable touchstart event

I have a mobile based web application. Currently I am encountering an issue when ajax calls are being made. The wait spinner which is enclosed in a div can be clicked through on the ipad device. The javascript event being triggered is touchstart. Is there anyway to prevent this event from going through normal processing?
Tried to call the following, however it did not work.
Disable
document.ontouchstart = function(e){ e.preventDefault(); }
Enable
document.ontouchstart = function(e){ return true; }
How touchstart is handled
$(document).on('touchstart', function (eventObj) {
//toggle for view-icon
if (eventObj.target.id == "view-icon") {
$("#view-dropdown").toggle();
} else if ($(eventObj.target).hasClass("view-dropdown")) {
$("#view-dropdown").show();
} else {
$("#view-dropdown").hide();
}
});
As user3032973 commented, you can use a touchLocked variable, which is working perfectly.
I have used it in combination with the Cordova Keyboard-Plugin. Scrolling will be disabled the time the keyboard is shown up and reenabled the time the keyboard is hiding:
var touchLocked = false;
Keyboard.onshowing = function () {
touchLocked = true;
};
Keyboard.onhiding = function () {
touchLocked = false;
};
document.ontouchstart = function(e){
if(touchLocked){
e.preventDefault();
}
};

Make ul list work like select input

I want to be able to use a ul list as an select form element, for styling reasons.
I'm able to populate an hidden input with my code (not included in this jsfiddle), and so far so good.But now I'm trying to let my ul behave like the select input when the keyboard is pressed, or the mouse is used.
In my previous question i had some problems with keyboard controls. They are now fixed. See: Autoscroll on keyboard arrow up/down
The problem that remains is that the mouse is not ignored when the keyboard buttons are pressed. This is causing the "hover effect" to listen to the keyboard input first, but than immediately going to the mouse and select this li item as being selected.
This can be seen in my jsfiddle example: http://jsfiddle.net/JVDXT/3/
My javascript code:
// scrollTo plugin
$.fn.scrollTo = function( target, options, callback ){
if(typeof options == 'function' && arguments.length == 2){ callback = options; options = target; }
var settings = $.extend({
scrollTarget : target,
offsetTop : 100,
duration : 0,
easing : 'linear'
}, options);
return this.each(function(){
var scrollPane = $(this);
var scrollTarget = (typeof settings.scrollTarget == "number") ? settings.scrollTarget : $(settings.scrollTarget);
var scrollY = (typeof scrollTarget == "number") ? scrollTarget : scrollTarget.offset().top + scrollPane.scrollTop() - parseInt(settings.offsetTop);
scrollPane.animate({scrollTop : scrollY }, parseInt(settings.duration), settings.easing, function(){
if (typeof callback == 'function') { callback.call(this); }
});
});
}
//My code
//The function that is listing the the mouse
jQuery(".btn-group .dropdown-menu li").mouseover(function() {
console.log('mousie')
jQuery(".btn-group .dropdown-menu li").removeClass('selected');
jQuery(this).addClass('selected');
})
//What to do when the keyboard is pressed
jQuery(".btn-group").keydown(function(e) {
if (e.keyCode == 38) { // up
console.log('keyup pressed');
var selected = jQuery('.selected');
jQuery(".btn-group .dropdown-menu li").removeClass('selected');
if (selected.prev().length == 0) {
selected.siblings().last().addClass('selected');
} else {
selected.prev().addClass('selected');
jQuery('.btn-group .dropdown-menu').scrollTo('.selected');
}
}
if (e.keyCode == 40) { // down
console.log('keydown');
var selected = jQuery('.selected');
jQuery(".btn-group .dropdown-menu li").removeClass('selected');
if (selected.next().length == 0) {
selected.siblings().first().addClass('selected');
} else {
selected.next().addClass('selected');
jQuery('.btn-group .dropdown-menu').scrollTo('.selected');
}
}
});
So could anyone teach me how to igonore the mouse when the keyboard buttons are pressed, but listing to the mouse when it's touched again by the user. Like the default select input form field.
Update
Here's a new jsfiddle.
Check this out:
http://jsfiddle.net/coma/9KvhL/25/
(function($, undefined) {
$.fn.dropdown = function() {
var widget = $(this);
var label = widget.find('span.valueOfButton');
var list = widget.children('ul');
var selected;
var highlighted;
var select = function(i) {
selected = $(i);
label.text(selected.text());
};
var highlight = function(i) {
highlighted = $(i);
highlighted
.addClass('selected')
.siblings('.selected')
.removeClass('selected');
};
var scroll = function(event) {
list.scrollTo('.selected');
};
var hover = function(event) {
highlight(this);
};
var rebind = function(event) {
bind();
};
var bind = function() {
list.on('mouseover', 'li', hover);
widget.off('mousemove', rebind);
};
var unbind = function() {
list.off('mouseover', 'li', hover);
widget.on('mousemove', rebind);
};
list.on('click', 'li', function(event) {
select(this);
});
widget.keydown(function(event) {
unbind();
switch(event.keyCode) {
case 38:
highlight((highlighted && highlighted.prev().length > 0) ? highlighted.prev() : list.children().last());
scroll();
break;
case 40:
highlight((highlighted && highlighted.next().length > 0) ? highlighted.next() : list.children().first());
scroll();
break;
case 13:
if(highlighted) {
select(highlighted);
}
break;
}
});
bind();
};
$.fn.scrollTo = function(target, options, callback) {
if(typeof options === 'function' && arguments.length === 2) {
callback = options;
options = target;
}
var settings = $.extend({
scrollTarget : target,
offsetTop : 185,
duration : 0,
easing : 'linear'
}, options);
return this.each(function(i) {
var scrollPane = $(this);
var scrollTarget = (typeof settings.scrollTarget === 'number') ? settings.scrollTarget : $(settings.scrollTarget);
var scrollY = (typeof scrollTarget === 'number') ? scrollTarget : scrollTarget.offset().top + scrollPane.scrollTop() - parseInt(settings.offsetTop, 10);
scrollPane.animate({scrollTop: scrollY}, parseInt(settings.duration, 10), settings.easing, function() {
if (typeof callback === 'function') {
callback.call(this);
}
});
});
};
})(jQuery);
$('div.btn-group').dropdown();
The key is to unbind the mouseover and rebind when mouse moves.
I refactored it a little by using a closure function, adding the logic to a jQuery method called dropdown so you can reuse it, using switch instead of a bunch of if's and more things.
Well, there are bazillions of plugins to transform a select to a list:
http://ivaynberg.github.io/select2/
http://harvesthq.github.io/chosen/
http://meetselva.github.io/combobox/
and I have mine too! (ready for touch devices using the same trick as http://uniformjs.com)
https://github.com/coma/jquery.select
But this question is about taking that HTML and make it behave like a select avoiding the hover issue right?
Here's a solution, I'm using mousemove as this will ensure that the right list item is selected as soon as the mouse starts moving again, with mouseover it only starts to select a list item upon entering a new list item:
Take the anonymous function and give it a name:
function mousemove() {
console.log('mousie')
jQuery(".btn-group .dropdown-menu li").removeClass('selected');
jQuery(this).addClass('selected');
}
Declare a global variable mousemoved indicating if the mouse has moved over the document and set it to false, on mousemove over the document, set it to true and attach the mousemove function to the mousemove event on the list items.
var mousemoved = false;
jQuery(document).mousemove(function() {
if(!mousemoved) {
$('.btn-group .dropdown-menu li').mousemove(mousemove);
mousemoved = true;
}
})
As soon as a key is pressed (at the start of the keydown event), use jQuery's .off() method to remove the mousemove event on the list items if it is present, and set mousemoved to false to ensure the mousemove event doesn't get attached again until the mouse is moved again.
jQuery(".btn-group").keydown(function(e) {
$('.btn-group .dropdown-menu li').off('mousemove');
mousemoved = false;
... // Some more of your code
Here's a jsFiddle.
I tried to solve your issue by prevent autoscroll, adding tabindex on the li, setting the focus on active, and using a flag to suppress mouse.
Fixed fiddle: http://jsfiddle.net/8nKJT/ [fixed an issue in Chrome ]
http://jsfiddle.net/RDSEt/
The issue is because of the automatic scroll which is triggered on keydown that again triggers mouseenter messes the selection of the li.
Note: The differences with the other approaches(answers here) I noticed is it scrolls on every keypress instead of scrolling only after reaching the top or bottom(normal behavior). You will feel the difference when you check the demo side-by-side.
Below is the list of change description and a small demo to explain how it was fixed,
Prevented auto scroll that is triggered on pressing up arrow/down arrow using e.preventDefault() http://jsfiddle.net/TRkAb/ [press up/down on the ul li], Now try the same on http://jsfiddle.net/TRkAb/1/ [No more scroll]
Added a flag on keydown to suppress the mouseevents on keydown, this flag is reset onmousemove
Added tabindex to li which would allow you to set focus using .focus function. [More info: https://stackoverflow.com/a/6809236/297641 ]
Calling .focus would automatically scroll to the desired location. (no need for scrollTo plugin) http://jsfiddle.net/39h3J/ - [Check how it scrolls to li that is on focus]
Check out the demo and code changes too (added few improvements) and let me know.
Also thanks to your question, I noticed this issue and bunch of other issue in one of the plugin I wrote.
I wrote a plugin few months back to filter options and also act exactly like a drop down.
DEMO: http://jsfiddle.net/nxmBQ/ [change filterType to '' to turnoff the filtering ]
The original plugin page is http://meetselva.github.io/combobox/
.. more
You could use a global to ignore the mouseover event if a keydown was pressed recently on the widget. For example:
var last_key_event = 0;
jQuery(".btn-group .dropdown-menu li").mouseover(function() {
if ((new Date).getTime() > last_key_event + 1000) {
console.log('mousie')
jQuery(".btn-group .dropdown-menu li").removeClass('selected');
jQuery(this).addClass('selected');
}
});
Then the keydown handler can set when it was handled to avoid interaction with the mouse:
//What to do when the keyboard is pressed
jQuery(".btn-group").keydown(function(e) {
last_key_event = (new Date).getTime();
...
});
May be it could make sense to have the last_key_event variable separate for each widget instead of being a global.
You could try this solution. It ignores the mousemove event if the coordinates have not changed (since the last mousemove event)
//The function that is listing the the mouse
var lastOffsets = "";
jQuery(".btn-group .dropdown-menu li").mouseover(function(e) {
var curOffsets = e.clientX+":"+e.clientY;
if(curOffsets == lastOffsets) {
// mouse did not really move
return false;
}
lastOffsets = curOffsets;
///// rest of your code
}
Updated fiddle to verify if this is what you were after:
http://jsfiddle.net/pdW75/1/
Approach A reasonable solution should imitate the behavior of other UI elements that serve a similar purpose. On all checked systems (Windows, Linux, major browsers), drop-down boxes behave as follows:
Mousing over an item highlights it. Pressing arrow keys change the selected element, and scroll accoringly. Moving the mouse selects the element underneath. If the selection is empty, pressing down selects the first element. Pressing up selects the last element.
Solution This code illustrates my approach to imitating the described behavior. It's kinda cool, try it...
Additional Considerations There would be a number of other options to suppress unwanted mouse movement to change the selected element. These include:
Keeping a state of last input method. If last selection was using the keyboard, hovering over an element will not select it, only clicking will
ignoring the mouseover event if the coordinates have not changed by a specified distance, e.g. 10 pixels
ignoring mouseover if the user has ever used the keyboard
However, at least for an application accessible to the public, it's always best to stick with established UI patterns.
The problem showing up is that when the mouse is left over a part of the expanded list, then selecting using the keys is nullified because the selection made by the keyboard immediately reverts to the item that happens to be under the mouse.
You can solve this problem and retain all functionality without doing any complicated conditional behavior or any removing of event handlers.
Just change your mouseover event handler to be a mousemove event handler. This way any keyboard navigation and selection is listened to and the mouse position is ignored anytime that the user is using the keyboard to select. And anytime the mouse is being used to select, then the mouse is listened to.
This sounds trivial but it seems to make your JS Fiddle behave perfectly and without any conflicting behavior between mouse and keyboard. Like this:
//The function that is listening to the mouse
jQuery(".btn-group .dropdown-menu li").mousemove...
(your code continues unchanged, only replacing mouseover with mousemove)

Categories