I want to be able to bind a single and double click event to a span of text. I know I can use
data-bind ="event: { dblclick: doSomething }
for a double click, but I also need the ability to perform a different function on single click. Any suggestions?
First, I wouldn't recommend click
binding at all. Instead you should use "click"
and "dblclick"
handlers from jQuery:
$(someParentElement).on('click', 'your span selector', function (event) {
var myViewModelFragment = ko.dataFor(this);
// your code here
});
$(someParentElement).on('dblclick', 'your span selector', function (event) {
var myViewModelFragment = ko.dataFor(this);
// your code here
});
Edit: see also Niko's suggestion regarding supporting both single and double clicks. Basically, you should count the number of clicks manually and call different functions accordingly. I assumed jQuery handles this for you but, unfortunately, it doesn't.
<div data-bind="singleClick: clicked, event : { dblclick: double }">
Click Me
</div>
This will filter out the single clicks that are also double clicks.
ko.bindingHandlers.singleClick= {
init: function(element, valueAccessor) {
var handler = valueAccessor(),
delay = 200,
clickTimeout = false;
$(element).click(function() {
if(clickTimeout !== false) {
clearTimeout(clickTimeout);
clickTimeout = false;
} else {
clickTimeout = setTimeout(function() {
clickTimeout = false;
handler();
}, delay);
}
});
}
};
Here is a demo.
The above answers were very helpful, but didn't give the exact solution I believe the OP was after: a simple Knockout binding that allows for exclusive single and double click events. I know the post was a year ago, but I found this article when looking to do the same thing today, so I'm posting in case this answer is useful for someone else.
The below example seems to fit the OPs requirement and may save someone some time (disclaimer: limited cross browser testing). JSFiddle: http://jsfiddle.net/UxRNy/
Also, there are the questions around whether you should use this in the first place (mobile browsers, slowing down the page, accessibility etc.) - but that is another post (e.g. https://ux.stackexchange.com/questions/7400/should-double-click-be-avoided-in-web-applications)
Example view usage:
<div data-bind="singleOrDoubleClick: { click: singleClick, dblclick: doubleClick }">
Click or double click me...
</div>
Binding:
ko.bindingHandlers.singleOrDoubleClick= {
init: function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
var singleHandler = valueAccessor().click,
doubleHandler = valueAccessor().dblclick,
delay = valueAccessor().delay || 200,
clicks = 0;
$(element).click(function(event) {
clicks++;
if (clicks === 1) {
setTimeout(function() {
if( clicks === 1 ) {
// Call the single click handler - passing viewModel as this 'this' object
// you may want to pass 'this' explicitly
if (singleHandler !== undefined) {
singleHandler.call(viewModel, bindingContext.$data, event);
}
} else {
// Call the double click handler - passing viewModel as this 'this' object
// you may want to pass 'this' explicitly
if (doubleHandler !== undefined) {
doubleHandler.call(viewModel, bindingContext.$data, event);
}
}
clicks = 0;
}, delay);
}
});
}
};
The above was put together from a combination of the examples above and examples from here: https://gist.github.com/ncr/399624 - I just merged the two solutions.
@madcapnmckay provided a great answer,
below is a modified version using the same idea to provide double click. And by using latest version of knockout, the vm get passed to handler as context. This can works at the same time with single click.
<div data-bind="doubleClick: clicked">
Double click Me
</div>
--
ko.bindingHandlers.doubleClick= {
init: function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
var handler = valueAccessor(),
delay = 200,
clickTimeout = false;
$(element).click(function() {
if(clickTimeout !== false) {
handler.call(viewModel);
clickTimeout = false;
} else {
clickTimeout = setTimeout(function() {
clickTimeout = false;
}, delay);
}
});
}
};
Here is my programming solution to the question:
var ViewModel = function() {
var self = this;
this.onSingleClick = function() {
self.lastTimeClicked = undefined;
console.log('Single click');
};
this.onDoubleClick = function() {
console.log('Double click');
};
this.timeoutID = undefined;
this.lastTimeClicked = undefined;
this.clickDelay = 500;
this.clickedParagraph = function(viewModel, mouseEvent) {
var currentTime = new Date().getTime();
var timeBetweenClicks = currentTime - (viewModel.lastTimeClicked || currentTime);
var timeToReceiveSecondClick = viewModel.clickDelay - timeBetweenClicks;
if (timeBetweenClicks > 0 && timeBetweenClicks < viewModel.clickDelay) {
window.clearTimeout(viewModel.timeoutID); // Interrupt "onSingleClick"
viewModel.lastTimeClicked = undefined;
viewModel.onDoubleClick();
} else {
viewModel.lastTimeClicked = currentTime;
viewModel.timeoutID = window.setTimeout(viewModel.onSingleClick, timeToReceiveSecondClick);
}
};
};
ko.applyBindings(new ViewModel(), document.getElementById("myParagraph"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.0/knockout-debug.js"></script>
<p id="myParagraph" data-bind="click: clickedParagraph">Click me</p>
Every "double click" is created from two single clicks. If there is a "double click", we have to make sure that the event handler for the first single click does not get executed (this is why I use window.setTimeout
& window.clearTimeout
). When setting the timers, we also have to consider that the first click on an element can be already a double click.
In my code I set the clickDelay
to 500ms. So two clicks within 500ms are recognized as a "double click". You can also increase this value to test the behaviour of my clickedParagraph
function.