Svelte Long Press - javascript

I need a long press event to bind to buttons in svelte 3. I want to do this in the least "boilerplaty" way possible.
I've tried with a long press function but this seems a little convoluted and hacky, also seems a little slow.
function longPress(node, callback) {
console.log(node)
function onmousedown(event) {
const timeout = setTimeout(() => callback(node.innerHTML), 1000);
function cancel() {
clearTimeout(timeout);
node.removeEventListener("mouseup", cancel, false);
}
node.addEventListener("mouseup", cancel, false);
}
node.addEventListener("mousedown", onmousedown, false);
return {
destroy() {
node.removeEventListener("mousedown", onmousedown, false);
}
};
}
</script>
<div>
<Video />
{#each Object.entries(bindings) as [id, value]}
<button on:click = {()=>longPress(this,addImage)}> {id} </button>
{/each}
</div>
This works but I'm sure there is a better way.

For this sort of thing I would use an action, which is a function that runs when an element is created (and can return functions that are run when parameters are changed, or the element is destroyed): https://svelte.dev/tutorial/actions
In this case you could create a reusable longpress action, much like your function above, which dispatches a custom longpress event on the target element that you can listen for like a native DOM event:
<script>
import { longpress } from './actions.js';
let pressed;
</script>
<button use:longpress on:longpress="{e => pressed = true}">
longpress me
</button>
export function longpress(node, threshold = 500) {
// note — a complete answer would also consider touch events
const handle_mousedown = () => {
let start = Date.now();
const timeout = setTimeout(() => {
node.dispatchEvent(new CustomEvent('longpress'));
}, threshold);
const cancel = () => {
clearTimeout(timeout);
node.removeEventListener('mousemove', cancel);
node.removeEventListener('mouseup', cancel);
};
node.addEventListener('mousemove', cancel);
node.addEventListener('mouseup', cancel);
}
node.addEventListener('mousedown', handle_mousedown);
return {
destroy() {
node.removeEventListener('mousedown', handle_mousedown);
}
};
}
The advantage of this approach is that you've separated the definition of 'longpress' from the thing that handles it, so the addImage/node.innerHTML logic can be cleanly separated, and you can re-use the action elsewhere in your app.
Full demo, including passing a parameter to the action: https://svelte.dev/repl/f34b6159667247e6b6abb5142b276483?version=3.6.3

I would recommend using 'press' action in svelte-gestures library if you want consistent support on desktop and mobile devices. It allows setting up duration time.

Related

In videojs, I can add a Text Track event handler, but I can't remove it

I added a handler for the 'cuechange' event to a Text Track" This works fine. But I can not find a way to remove this handler. I tried each of instructions below to remove the handler, but it still gets called.
onHiliteSpeech() {
const textTrack = this.videojsComponent.getTextTrack();
const handleCueChange = () => {
...
console.log(in event handler);
}
};
if (this.bevents) {
textTrack.addEventListener('cuechange', handleCueChange);
} else {
// none of the below instructions remove the handler.
textTrack.removeEventListener('cuechange', handleCueChange);
// textTrack.removeAllListeners();
// textTrack.removeAllListeners('cuechange');
// textTrack.eventListeners = null;
}
}
In my videojsComponent:
getTextTrack(): TextTrack {
return this.player.textTracks()[0];
}
After some trial and error, I found the problem. The function "handleCueChange" should not be a nested function within onHiliteSpeech.
I moved handleCueChange outside of onHiliteSpeech. (This also involved some work to allow handleCueChange to access some OnHiliteSpeech properties.) The new working code became:
const handleCueChange = () => {
...
console.log(in event handler);
}
};
onHiliteSpeech() {
textTrack.addEventListener('cuechange', this.handleCueChange);
...
textTrack.removeEventListener('cuechange', this.handleCueChange);
}

Debounce for 500ms if True else execute

Spoiler; I'm completely new to jQuery/Javascript.
I have a boolean field CheckMe and an input field textField.
If textField is empty, CheckMe should not be shown else it should (this means if goes from not-empty to empty CheckMe should be hidden right away again). I want to parse a delay, say 500 ms, i.e CheckMe is shown if text is not empty and after 500 ms of the last keypress
I have tried using the debounce function from this SO answer (see my implementation below), but the problem is, CheckMe is also first hidden after 500 ms of textField being empty
<script type="text/javascript">
function debounce(fn, duration) {
var timer;
return function(){
clearTimeout(timer);
timer = setTimeout(fn, duration);
}
}
$(document).ready(function () {
const textField= $("#textField");
const CheckMe= $("#CheckMe");
CheckMe.hide();
textField.on("input", debounce(()=> {
if (textField.val()) {
CheckMe.show();
} else {
CheckMe.hide();
}
},500));
});
</script>
but that removes checkMe after 500 ms when I clear textField.
I have tried moving the debounce into the True statement i.e
...
textField.on("input", function() {
if (textField.val()) {
debounce(function(){CheckMe.show();},500)
} else {
CheckMe.hide();
}
}
but that does not show CheckMe at any point
The reason the attempt with if () { debouce(()={}); } else { immediate(); } doesn't work is due to how event handlers store the function and how debounce stores its timer.
When you run .on("input", function() { }) that function definition is stored within the event handler, ready to run. However with debounce, that's not what is being done, instead it's:
.on("input", function())
there's no function definition - it calls the function itself, which happens to return another function to be called when the event runs.
It's why there there are so many questions on SO saying something like "my code runs immediately when I do .on("input", myfunction()) when it should be .on("input", myfunction)
So that one function (the debounce) runs once per event setup - not once per input event fire, but just once when setting up the event. So there's only one instance of var timer and it's all contained within the debounce function. The event fire then calls the code inside the return function() which already has var timer defined previously in its outer scope (the previous debounce call).
If you call debounce again with a 2nd input $("#secondInp").on("input", debounce(() => ... you get a second instance of the function with its own variable (so they don't conflict between inp1 and inp2).
So you can then see that if you put this inside the event handler (in the if), you're calling a new debounce each time (not the same one).
Your attempt did nothing because your code debounce(function(){CheckMe.show();},500) simply returns the function - so you would need to do
debounce(function(){CheckMe.show();},500)();`
but even that won't work as each time it's called (each event) you get a new instance of the debounce function and a new instance of var timer.
You can use two events. Inside each event check if the "check me" should be shown or not.
The debounced one will run after 500ms and the not-debounced one will run immediately.
function debounce(fn, duration) {
var timer;
return function() {
clearTimeout(timer);
timer = setTimeout(fn, duration);
}
}
$(document).ready(function() {
const textField = $("#textField");
const checkMe = $("#checkMe");
checkMe.hide();
textField.on("input", debounce(() => {
if (textField.val()) {
checkMe.show();
}
}, 500));
textField.on("input", () => {
if (!textField.val()) {
checkMe.hide();
}
});
});
#checkMe { color: red; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<input id="textField" type="text">
<div id='checkMe'>^-- check this</div>
is it possible to keep it in a single if-else
Given the explanation above, it is not possible using your debounce function as it is; because of the return function() { } and the single instance of timer. The key being the single instance of timer.
Without the debounce, this can be implemented in a single function, using an outer variable for the timer, but will need the debounce code repeated each time (eg for a 2nd input) - just the clearTimeout and setTimeout code - so not much - it's the "global"/outer-scope variable that becomes an issue.
$(document).ready(function() {
var textFieldTimer = null;
const textField = $("#textField");
const checkMe = $("#checkMe");
checkMe.hide();
textField.on("input", () => {
if (textField.val()) {
clearTimeout(textFieldTimer);
textFieldTimer = setTimeout(() => checkMe.show(), 500);
}
else {
checkMe.hide();
}
});
});
#checkMe { color: red; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<input id="textField" type="text">
<div id='checkMe'>^-- check this</div>
So how can we use both: a reusable function and inside an if inside the event handler?
By storing the single instance of timer on the element itself - using .data() in the code below, but any method to store a single instance per element will also work.
Here's an example, using a single event with an if and repeated for a second input to show how it might work.
function debounce2(fn, duration)
{
var timer = $(this).data("timer");
clearTimeout(timer);
$(this).data("timer", setTimeout(fn, duration));
}
$(document).ready(function() {
$("#checkMe, #checkMe2").hide();
$("#textField, #textField2").on("input", function() {
if ($(this).val()) {
debounce2.call(textField, () => $("#checkMe").show(), 500);
}
else {
$("#checkMe").hide();
}
});
// second input confirming `timer` is per element.
$("#textField2").on("input", function() {
if ($(this).val()) {
debounce2.call(textField, () => $("#checkMe2").show(), 500);
}
else {
$("#checkMe2").hide();
}
});
});
#checkMe, #checkMe2 { color: red; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<input id="textField" type="text">
<div id='checkMe'>^-- check this</div>
<hr/>
<input id="textField2" type="text">
<div id='checkMe2'>^-- check this</div>

Unable to remove event listener from document

I have a button. When this button is clicked I do two things
Open up a search menu
Attach an event listener to the document body to listen for close events.
However, I cannot seem to be able to remove the eventlistener from the document on the close function. That is, the second time I try to open the menu, it immediately calls the close function
My question is...
How do I remove the document event listener?
And how do I make it so that if the user clicks the search menu, it does not trigger the document click event
openDesktopSearchMenu() {
this.$desktopSearchMenu.style.height = '330px';
document.addEventListener('click', this.closeDesktopSearchMenu.bind(this), true);
}
closeDesktopSearchMenu() {
this.$desktopSearchMenu.style.height = '0px';
document.removeEventListener('click', this.closeDesktopSearchMenu.bind(this), true);
}
Update July 24
Nick's answer definitely put me in the right direction. However, the document was always being called first due to the capture parameter. So if the user clicks inside the search menu, it's automatically closed.
Removing the capture parameter causes the close function to be invoked immediately after it opens.
The way around this that worked for me is to wrap the listener inside a timeout when I add it. And then naturally I had to call stopPropagation() on search menu click
searchMenuClick = (e) => {
e.stopPropagation();
}
/** open the desktop search menu */
openDesktopSearchMenu = () => {
this.$desktopSearchMenu.style.height = '330px';
this.$navBar.classList.add('search');
setTimeout(() => {
document.addEventListener('click', this.closeDesktopSearchMenu, { capture: false });
});
}
closeDesktopSearchMenu = () => {
this.$desktopSearchMenu.style.height = '0px';
setTimeout(() => {
this.$navBar.classList.remove('search');
}, 300);
document.removeEventListener('click', this.closeDesktopSearchMenu, { capture: false });
}
The .bind() method returns a new function, so the function which you're adding as the callback to addEventListener is a different reference to the one you're trying to remove. As a result, the event listener doesn't get removed.
You could consider binding in your constructor like so:
constructor() {
...
this.closeDesktopSearchMenu = this.closeDesktopSearchMenu.bind(this);
...
}
And then use your method like so (without the bind, as that's now done in the constructor):
openDesktopSearchMenu() {
this.$desktopSearchMenu.style.height = '330px';
document.addEventListener('click', this.closeDesktopSearchMenu, true);
}
closeDesktopSearchMenu() {
this.$desktopSearchMenu.style.height = '0px';
document.removeEventListener('click', this.closeDesktopSearchMen, true);
}
See example snippet below:
class Test {
constructor() {
this.prop = "bar";
this.foo = this.foo.bind(this);
}
foo() {
console.log('Foo', this.prop);
}
a() {
document.addEventListener('click', this.foo, true);
}
b() {
document.removeEventListener('click', this.foo, true);
}
}
const test = new Test();
console.log("Event listener added");
test.a();
setTimeout(() => {
console.log("Event listener removed");
test.b();
}, 3000);

removeEventListener doesn't seems to work?

for some reason ,I need to delay click and preventDefault for some time when scroll page end.So I write something like this :
// const disableClickDuringScrollHandler=(e)=> {
// e.preventDefault();
// };
this.scrollHandler = e => {
console.log('scrolling');
const disableClickDuringScrollHandler = (e) => {
e.preventDefault();
};
document.addEventListener('click', disableClickDuringScrollHandler);
window.clearTimeout(this.scrollTimer);
this.scrollTimer = window.setTimeout(() => {
console.log('scroll end');
document.removeEventListener('click', disableClickDuringScrollHandler);
}, 300);
}
window.addEventListener('scroll', this.scrollHandler);
I also has been write a codepen: https://codepen.io/zhangolve/pen/gRNMoX
my question is when I put disableClickDuringScrollHandler outside of the scrollHandler ,removeEventListener can work well,but when I put disableClickDuringScrollHandler inside of the scrollHandler ,removeEventListener doesn't seem to work .
I have tried so many times to found why but failed.So I get here to ask your help.
The problem is that each time the user scrolls, you create a new disableClicksDuringScroll closure and add it as a click listener. When this timer runs, it removes the latest click listener, but not the previous ones (because they're different closures, so they aren't equal to the function you're removing this time).
You should define disableClicksDuringScroll function just once, outside the scroll handler, since it doesn't refer to any local variables here. Then when you call removeEventListener it will find this handler.
You can also use a variable so you only add the click listener once, when scrolling starts, not every time you reset the timer.
this.disableClickDuringScrollHandler = (e) => {
e.preventDefault();
};
this.inScroll = false;
this.scrollHandler = e => {
console.log('scrolling');
if (!this.inScroll) {
document.addEventListener('click', this.disableClickDuringScrollHandler);
this.inScroll = true;
}
window.clearTimeout(this.scrollTimer);
this.scrollTimer = window.setTimeout(() => {
this.inScroll = false;
console.log('scroll end');
document.removeEventListener('click', disableClickDuringScrollHandler);
}, 300);
}
window.addEventListener('scroll', this.scrollHandler);

Delaying default events in Javascript

I would like to be able to delay the default action of an event until some other action has been taken.
What it's for: I'm trying to build a reusable, unobtrusive way to confirm actions with a modal-type dialogue. The key wishlist item is that any Javascript handlers are attached by a script, and not written directly inline.
To make this truly reusable, I want to use it on different types of items: html links, checkboxes, and even other Javascript-driven actions. And for purely HTML elements like links or checkboxes, I want them to degrade gracefully so they're usable without Javascript turned on.
Here's how I would envision the implementation:
Some Link
_________
<script>
attachEvent('a.confirm','click', confirmAction.fire)
var confirmAction = (function(){
var public = {
fire: function(e){
e.default.suspend();
this.modal();
},
modal: function(){
showmodal();
yesbutton.onclick = this.confirmed;
nobutton.onclick = this.canceled;
},
confirmed: function(){
hidemodal();
e.default.resume();
},
canceled: function(){
hidemodal();
e.default.prevent();
}
}
return public;
})()
</script>
I know about the e.preventDefault function, but that will kill the default action without giving me the ability to resume it. Obviously, the default object with the suspend, resume and prevent methods is made up to illustrate my desired end.
By the way, I'm building this using the Ext.Core library, if that helps. The library provides a good deal of normalization for handling events. But I'm really very interested in learning the general principles of this in Javascript.
To resume, you could try saving the event and re-fire it, setting a flag that can be used to skip the handlers that call suspend() ('confirmAction.fire', in your example).
Some Link
_________
<script>
function bindMethod(self, method) {
return function() {
method.apply(self, arguments);
}
}
var confirmAction = (function(){
var public = {
delayEvent: function(e) {
if (e.suspend()) {
this.rememberEvent(e);
return true;
} else {
return false;
}
},
fire: function(e){
if (this.delayEvent(e)) {
this.modal();
}
},
modal: function(){
showmodal();
yesbutton.onclick = bindMethod(this, this.confirmed);
nobutton.onclick = bindMethod(this, this.canceled);
},
confirmed: function(){
hidemodal();
this.rememberEvent().resume();
},
canceled: function(){
hidemodal();
this.forgetEvent();
},
rememberEvent: function (e) {
if (e) {
this.currEvent=e;
} else {
return this.currEvent;
}
},
forgetEvent: function () {
delete this.currEvent;
}
}
return public;
})()
// delayableEvent should be mixed in to the event class.
var delayableEvent = (function (){
return {
suspend: function() {
if (this.suspended) {
return false;
} else {
this.suspended = true;
this.preventDefault();
return true;
}
},
resume: function () {
/* rest of 'resume' is highly dependent on event implementation,
but might look like */
this.target.fireEvent(this);
}
};
})();
/* 'this' in event handlers will generally be the element the listener is registered
* on, so you need to make sure 'this' has the confirmAction methods.
*/
mixin('a.confirm', confirmAction);
attachEvent('a.confirm','click', confirmAction.fire);
</script>
This still has potential bugs, such as how it interacts with other event listeners.

Categories