jQuery calling click event after taphold event
I'm developing a PhoneGap app for Android using Jquery and Jquery Mobile.
I've got a list of items that need two events bound to each item in the list. I need a "taphold" event and a "click" event. The problem I'm having is when I do a "taphold", the correct "taphold" event is fired. However, as soon as I release, the click event is also fired. How can I prevent the click event from firing after a taphold?
Code:
function LoadMyItems(items) {
for(var idx in items)
{
var itemLine = '<div class="my_item" id="my_item_'+items[idx].user_item_id+'">' +
'<img class="item_icon_32" src=./images/graphicFiles/Icon48/'+items[idx].item.graphic.graphicFiles.Icon48.filename+' />' +
items[idx].item.name+
'</div>';
$('#my_list').append('<li>'+itemLine+'</li>');
$('#my_item_'+items[idx].user_item_id).bind('taphold', {userItem:items[idx]},ShowMyItemInfo);
$('#my_item_'+items[idx].user_item_id).bind('click tap', {userItem:items[idx]},FitMyUpgradeItem);
console.log('UserItem '+items[idx].user_item_id+' loaded and events bound');
}
$('#my_items_loader').hide();
myScroll.refresh();
}
After the advice below, Here is what I ended up with. This works inside the iScroll object.
function LoadMyItems(items) {
for(var idx in items)
{
var itemLine = '<div class="my_item" id="my_item_'+items[idx].user_item_id+'">' +
'<img class="item_icon_32" src=./images/graphicFiles/Icon48/'+items[idx].item.graphic.graphicFiles.Icon48.filename+' />' +
items[idx].item.name+
'</div>';
$('#my_list').append('<li>'+itemLine+'</li>');
(function(index) {
var tapTime = 0;
var xPos = 0;
var yPos = 0;
$('#my_item_'+items[index].user_item_id).bind('vmousedown vmouseup', function (event) {
if (event.type == 'vmousedown') {
tapTime = new Date().getTime();
xPos = event.pageX;
yPos = event.pageY;
var timer = setTimeout(function() {
var duration = (new Date().getTime() - tapTime);
var xDiff = Math.abs(mouseXPos - xPos);
var yDiff = Math.abs(mouseYPos - yPos);
if(duration >= 700 && (yDiff <= 40 || mouseXPos == 0))
ShowItemInfo(items[index].item);
},750);
} else {
//event.type == 'vmouseup'
var duration = (new Date().getTime() - tapTime);
var xDiff = Math.abs(event.pageX - xPos);
var yDiff = Math.abs(event.pageY - yPos);
tapTime = new Date().getTime();
if (duration < 699 && yDiff <= 40) {
//this is a tap
FitMyUpgradeItem(items[index]);
}
}
});
$('#my_item_'+items[index].user_item_id).bind('touchmove',function(event) {
event.preventDefault();
});
})(idx);
console.log('UserItem '+items[idx].user_item_id+' loaded and events bound');
}
$('#my_items_loader').hide();
myScroll.refresh();
}
Rather than use tap
and taphold
(which I've tried to use but ran into the same problems, it seems to be an inherent issue with the taphold
event) you can use vmousedown
and set a flag, then bind to vmouseup
to determine if it was a tap
or a taphold
:
var tapTime = 0;
$('#my_item_'+items[idx].user_item_id).bind('vmousedown vmouseup', function (event) {
if (event.type == 'vmousedown') {
tapTime = new Date().getTime();
} else {
//event.type == 'vmouseup'
//here you can check how long the `tap` was to determine what do do
var duration = (new Date().getTime() - tapTime);
if (duration > 3000) {
//this is a tap-hold
ShowMyItemInfo(items[idx]);
} else {
//this is a tap
FitMyUpgradeItem(items[idx]);
}
}
});
For this to work properly you'll have to add an IIFE around the loop-code or change ShowMyItemInfo(items[idx]);
to work without referencing the variable that changes each iteration of the loop. An easy to create an IIFE is to just use $.each()
. Otherwise your loop would look something like this:
for(var idx in items)
{
(function (idx) {
...
})(idx);
}
IIFE = Immediately-Invoked-Function-Expression. It allows us to take a "snapshot" of the current state of variables we pass into the IIFE. So as we pass in idx
(technically the second instance is the variable that's being passed in, and the first instance is the variable available inside the IIFE, which could be changed to something like ids_new
for simplicity sake), the value passed in is saved for when the tap
event handler fires.
Update
You can also set a timeout to determine taphold
rather than using the vmouseup
event:
//setup a timer and a flag variable
var tapTimer,
isTapHold = false;
$('#my_item_'+items[idx].user_item_id).bind('vmousedown vmouseup', function (event) {
if (event.type == 'vmousedown') {
//set the timer to run the `taphold` function in three seconds
//
tapTimer = setTimeout(function () {
isTapHold = true;
ShowMyItemInfo(items[idx]);
}, 3000);
} else {
//event.type == 'vmouseup'
//clear the timeout if it hasn't yet occured
clearTimeout(tapTimer);
//if the flag is set to false then this is a `tap` event
if (!isTapHold) {
//this is a tap, not a tap-hold
FitMyUpgradeItem(items[idx]);
}
//reset flag
isTapHold = false;
}
});
This way the event will fire after the user holds down their finger for three seconds. Then the tap
event handler will only fire if that three seconds did not occur.
Simply set this at the top of your document or anywhere before you define your even:
$.event.special.tap.emitTapOnTaphold = false;
Then you can use it like this:
$('#button').on('tap',function(){
console.log('tap!');
}).on('taphold',function(){
console.log('taphold!');
});
Personally, I thought the answers here vastly over-complicated the issue. If you just want a simple way to be able to continue using the taphold event and ignore the click event that fires when you release a taphold, here's how I solved this same problem in my own project:
// We will use this flag to ignore click events we don't want
var skipNextClick = false;
//Set up your event handler. You could do these using two handlers or one. I chose one.
$('div.element').on('click taphold', function (e) {
//set up a quick bool flag that is true if click
var isClick = (e.type == 'click');
if (isClick && !skipNextClick) {
//run your code for normal click events here...
}
else if (isClick && skipNextClick) {
//this is where skipped click events will end up...
//we need to reset our skipNextClick flag here,
//this way, our next click will not be ignored
skipNextClick = false;
}
else {
//taphold event
//to ignore the click event that fires when you release your taphold,
//we set the skipNextClick flag to true here.
skipNextClick = true;
//run your code for taphold events here...
}
});