The image below illustrates a problem that I'm experiencing with jQuery's Draggable method. I have a list of products. When users swipe left or right, the products should scroll accordingly. When users swipe down, they should be able to drag the items downward. Here's the issue: although I can successfully differentiate between vertical and horizontal swipes, I can't enable and disable jQuery drag functionality at the right times.
Here is my code:
var touchPos = {
x: null,
y: null,
threshold: {
x: 20,
y: 20
}
};
// Shortcut for getting finger position from touch event
// This is working correctly
var getClient = function (ev, axis) {
var touches = ev.touches || ev.originalEvent.touches;
return touches[0]['client' + axis];
};
// All of the products are draggable by default
$el.draggable({
containment: '.pageWrapper',
cursor: 'move',
revert: 'invalid',
helper: 'clone',
disabled: false
});
function disableDragging () {
$el.draggable('option', 'cancel', '.planSquare');
$el.draggable("option", "disabled", true );
}
function enableDragging () {
$el.draggable('option', 'cancel', '.pageWrapper');
$el.draggable("option", "disabled", false);
}
// When the user starts moving a product
element.bind('touchstart', function (event) {
// Capture finger position
touchPos.x = getClient(event, 'X');
touchPos.y = getClient(event, 'Y');
if ($el.hasClass('unselectable')) {
return false;
}
$el.css('border-color', 'transparent');
});
element.bind('touchmove', function(event) {
// Update the position of the user's finger
var newY = getClient(event, 'Y');
var newX = getClient(event, 'X');
var draggingDown = newY - touchPos.y >= touchPos.threshold.y;
var draggingSideways = Math.abs(newX - touchPos.x) >= touchPos.threshold.x;
if (!draggingDown && draggingSideways) {
disableDragging();
}
});
// When the user stops moving a square
element.bind('touchend', function () {
touchPos.x = null;
touchPos.y = null;
enableDragging();
$el.css('border-color', '#ccc')
});
The enableDragging and disableDragging have the desired effect when I call them outside the context of the touch event. My hypothesis is that, using jQuery UI, I can't disable dragging once a drag event has already begun. To recap, my goal is to disable dragging and switch to native horizontal scrolling if the user is not dragging down.
Can someone recommend a good approach for toggling between dragging and native scrolling behavior?
Related
I use jQuery UI to drag div elements. There is a div, it is draggable, when I drag with the left mouse button. I'd like to drag it with the right mouse button, too. So I want the item to be dragged with the left and the right mouse button. But only this div element, other elements are draggable only with left mouse button.
I modify the jQuery UI js file, like this:
$(document).ready(function () {
//override jQuery UI draggable
$.extend($.ui.draggable.prototype, {
_mouseDown: function (event) {
// we may have missed mouseup (out of window)
(this._mouseStarted && this._mouseUp(event));
this._mouseDownEvent = event;
var that = this,
btnIsLeft = (event.type === 'mousedown'),
// event.target.nodeName works around a bug in IE 8 with
// disabled inputs (#7620)
elIsCancel = (typeof this.options.cancel === "string" && event.target.nodeName ? $(event.target).closest(this.options.cancel).length : false);
if (!btnIsLeft || elIsCancel || !this._mouseCapture(event)) {
return true;
}
this.mouseDelayMet = !this.options.delay;
if (!this.mouseDelayMet) {
this._mouseDelayTimer = setTimeout(function () {
that.mouseDelayMet = true;
}, this.options.delay);
}
if (this._mouseDistanceMet(event) && this._mouseDelayMet(event)) {
this._mouseStarted = (this._mouseStart(event) !== false);
if (!this._mouseStarted) {
event.preventDefault();
return true;
}
}
// Click event may never have fired (Gecko & Opera)
if (true === $.data(event.target, this.widgetName + ".preventClickEvent")) {
$.removeData(event.target, this.widgetName + ".preventClickEvent");
}
// these delegates are required to keep context
this._mouseMoveDelegate = function (event) {
return that._mouseMove(event);
};
this._mouseUpDelegate = function (event) {
return that._mouseUp(event);
};
$(document)
.bind("mousemove." + this.widgetName, this._mouseMoveDelegate)
.bind("mouseup." + this.widgetName, this._mouseUpDelegate);
event.preventDefault();
mouseHandled = true;
return true;
}
});
});
The change is here:
btnIsLeft = (event.type === 'mousedown'),
With this code my specific div element works fine, it works with the left and right mouse button, too, but other elements are draggable with both mouse button what I don't want.
My div element is this (I'd like to apply only this with the right and left draggable function):
$('.mydiv_with_right_and_left_drag').draggable({
scroll: false,
delay: 0,
distance: 0,
cursor: 'hand',
snap: false,
cursor: 'pointer',
stack: '.mydiv_with_right_and_left_drag',
containment: 'parent',
opacity: 0.35,
});
JSFIDDLE SOURCE
It can be workable somehow? Thanks.
I have a scrollable list. It has the overflow-y property set to scroll and I can scroll with the scroll bar but I would like to be able to use my mouse to drag up and down.
Here is what I have so far
//Allows the user to scroll by dragging the mouse
element.on({
'mousemove': function(e)
{
if (self.get('clicked'))
{
const difference = self.get('clickY') - element.scrollTop();
const scrollY = difference + (e.pageY - element.offset().top);
element.scrollTop(scrollY);
}
},
'mousedown': function (e)
{
const clickY = (e.pageY - element.offset().top) + element.scrollTop();
self.set('clicked', true);
self.set('clickY', clickY);
},
'mouseup': function (e)
{
self.set('clicked', false);
},
'mouseleave': function(e)
{
self.set('clicked', false);
}
});
I have been playing around with it but just cant get it right, because I have to take into consideration the y position of the element, how much its already scrolled, the mouseY etc. Would someone be able to help me out with this please?
I am trying to catch when user press left button on mouse while hovering over cells in a html table using vanilla javascript. The purpose is to paint a cell in black when user is clicking with mouse while dragging (drawing like in MsPaint, when you draw a line for example)
I added an "over" event listener on each td of my table and used buttons property to check if left button is pressed or not:
celle = document.getElementsByTagName("td");
for (i=0;i<celle.length;i++)
celle[i].addEventListener("mouseover", function(e){
if(e.buttons == 1 ){
e.target.style.backgroundColor="black";
}
})
This code works but not always and not perfectly. First it starts setting the background color of the next element, not the one on which I pressed the mouse. Moreover, sometimes it doesn't set any color at all (there is a small icon like "accessed denied" in Chrome's window). It appears to work quite randomly and unpredicatably.
I tried also with jQuery, but I found similar problems. Anyone can help me?
Thanks a lot
Split the problem into several parts. I would add a mousedown and mouseup eventlistener to the whole window and set a global state if you're currently drawing:
var drawState=false
window.addEventListener("mousedown",function(e){
if(e.button===1){
drawState = true;
}});
window.addEventListener("mouseup",function(e){
if(e.button===1){
drawState = false;
}});
You can improve the window listeners with some checks, if the mouse is over a cell.
After this you can add a mouseenter listener to all your cells. Mouseenter is only fired once you enter a cell and not on every move inside the element:
celle[i].addEventListener("mouseenter", function(e){
if(drawState){
e.target.style.backgroundColor="black";
}
})
Instead of tracking mouseover, track three events:
mousemove - to constantly get the mouse position
mousedown - to set the mouse state as currently clicked down
mouseup - to set the mouse state as currently released
It works this way:
handleMousemove constantly updates the mouse position and check mouse state
When the mouse is clicked down, handleMousedown is fired
handleMousedown set the state as 'down'
When handleMousemove sees that mouse state is 'down', it fires click event at the current mouse position
When the mouse is released, handleMouseup is fired
handleMouseup set the state as 'released' and everything returns to normal
Repeat
var mouseIsDown = false;
var mousePosition = { x:-1, y:-1 };
let handleMousemove = (event) => {
// get the mouse position
mousePosition.x = event.x;
mousePosition.y = event.y;
if(mouseIsDown) // if mouse state is currently down, fire click at mouse position
{
let elem = document.elementFromPoint(mousePosition.x, mousePosition.y);
// you can add some conditions before clicking
if(something)
{
elem.click();
}
}
};
let handleMousedown = (event) => {
mouseIsDown = true;
// set the mouse state as 'down'
};
let handleMouseup = (event) => {
mouseIsDown = false;
// set the mouse state as 'release'
};
document.addEventListener('mousemove', handleMousemove);
document.addEventListener('mousedown', handleMousedown);
document.addEventListener('mouseup', handleMouseup);
Working fiddle: https://jsfiddle.net/Black3800/9wvh8bzg/5/
Thanks to everybody for your kind answers. Proposed codes work almost ok. The only problem is that sometimes browser shows the NO SYMBOL cursor. Unfortunately I can't post an image but you can find it here:
NO Symbol
and the only way to keep on drawing is clicking outside the table and then clicking again inside.
This is my code:
var mouseIsDown = false;
var mousePosition = { x:-1, y:-1 };
let handleMousemove = (event) => {
// get the mouse position
mousePosition.x = event.x;
mousePosition.y = event.y;
if(mouseIsDown) // if mouse state is currently down, fire click at mouse position
{
let elem = document.elementFromPoint(mousePosition.x, mousePosition.y);
// you can add some conditions before clicking
if (event.buttons==1)
{
elem.click();
}
}
};
let handleMousedown = (event) => {
mouseIsDown = true;
// set the mouse state as 'down'
};
let handleMouseup = (event) => {
mouseIsDown = false;
// set the mouse state as 'release'
};
document.addEventListener('mousemove', handleMousemove);
document.addEventListener('mousedown', handleMousedown);
document.addEventListener('mouseup', handleMouseup);
celle = document.getElementsByTagName("td");
for (i=0;i<celle.length;i++)
celle[i].addEventListener("click", function(e){
e.target.style.backgroundColor="black";
}
)
Isn't it easier to just add a listener for "click" ? If the element is clicked it also over the cell.
celle[i].addEventListener("click", function(e){
e.target.style.backgroundColor="black";
}
I want to be able to click on a canvas, move it to the map, drop it on the map. After dropping it on the map, they can select the canvas on the map and move it again. The process is repeated. I have trouble with the ignoreMouseMove variable, it does not reset to false and is always true.
Here is link to the demo: https://pokemon-map-electro2k.c9users.io/index.html
var moveCanvas = function ($canvas, e) {
$(".map ul li." + $canvas).offset({
left: e.pageX - 30,
top: e.pageY - 30
});
};
// When user first click on canvas
var onmousemove = function ($canvas) {
var ignoreMouseMove = false;
// Make canvas follow cursor in the map area
$(".map").mousemove(function (e) {
if (ignoreMouseMove) return; // event handling mousemove is "disabled"
moveCanvas($canvas, e);
}).click(function () {
// "re-enable" mousemove
ignoreMouseMove = true;
// When canvas is click on again within the map area, make canvas follow cursor
$(".map ul li").click(function () {
$(".map").mousemove(function (e) {
if (!ignoreMouseMove) return;
moveCanvas($canvas, e);
}).click(function () {
// Click function does not work anymore. ignoreMouseMove can't be reset. It is always true
ignoreMouseMove = false;
})
});
});
};
You may need to change the below line
// "re-enable" mousemove
ignoreMouseMove = true;
in to
// "re-enable" mousemove
ignoreMouseMove = !ignoreMouseMove;
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)