On my Vue instance I have this:
async mounted () {
document.addEventListener('paste', this.onPasteEvent)
},
beforeDestroy () {
document.removeEventListener('paste', this.onPasteEvent)
},
methods: {
onPasteEvent () {
return async (event) => {
try {
const items = event.clipboardData.items
const files = await this.getBase64Files(items)
this.transferedItems = files
this.modal = true
} catch (error) {
this.$toast.error('Não foi possível detectar um arquivo na área de transferência.')
}
}
},
I'm trying to destroy the "paste" event when the component is destroyed, but this just doesnt work, I know I need to pass the same reference to removeEventListener, but is this not the same reference?
The only way I found to make this work is placing the onPasteEvent method outside the Vue instance as a constant, but that way I don't have access to this instance, which is important to me, also, I can't pass anything as arguments, if I try to pass something, looks like my function create a new reference on memory, making unable to destroy it using removeEventListener.
Please, I just don't understand how to remove a event in JavaScript, can someone help me with that example? I already saw a lot of similar questions but no one explains:
How to keep the method reference even if it has parameters?
How to remove the event working with Vue instances?
Your code is already removing the event listener correctly, but there's a couple other problems:
onPasteEvent returns a function, so when the paste event occurs, the handler only returns a new function (which does not get executed), so it's basically doing nothing useful.
To fix the paste event handler, convert the returned function into the onPasteEvent function itself:
export default {
methods: {
async onPasteEvent (event) {
try {
const items = event.clipboardData.items
const files = await this.getBase64Files(items)
this.transferedItems = files
this.modal = true
} catch (error) {
this.$toast.error('Não foi possível detectar um arquivo na área de transferência.')
}
}
}
}
And if you're using Vue 3, the beforeDestroy hook from Vue 2 is renamed to beforeUnmount:
export default {
// beforeDestroy() { ❌ renamed in Vue 3
beforeUnmount() { ✅
document.removeEventListener('paste', this.onPasteEvent)
},
}
demo
Related
I am trying to create a custom button for arrows in the drawing tool of leaflet-geoman.
The idea was to work with the copyDrawControl function, and to use Line as a model to make Polylines with arrow tips.
I wrote a code mostly inspired from this demonstration https://codesandbox.io/s/394eq?file=/src/index.js and modified it for my goals. Here is the code :
import { useEffect } from "react";
import { useLeafletContext } from "#react-leaflet/core";
import "#geoman-io/leaflet-geoman-free";
import "#geoman-io/leaflet-geoman-free/dist/leaflet-geoman.css";
const Geoman = () => {
const context = useLeafletContext();
useEffect(() => {
const leafletContainer = context.layerContainer || context.map;
leafletContainer.pm.setGlobalOptions({ pmIgnore: false });
//draw control options
leafletContainer.pm.addControls({
positions: {
draw: 'topleft',
edit: 'topright',
},
drawMarker: false,
rotateMode: false,
cutPolygon: false,
position: "bottomright"
});
//new button
leafletContainer.pm.Toolbar.copyDrawControl('Line', {
name: 'SoonToBeArrow',
block: 'draw',
title: 'Display text on hover button',
actions: [
// uses the default 'cancel' action
'cancel',
],
});
return () => {
leafletContainer.pm.removeControls();
leafletContainer.pm.setGlobalOptions({ pmIgnore: true });
};
}, [context]);
return null;
};
export default Geoman;
When trying to add the copyDrawControl, I faced a bug that would announce that "Button with this name already exists"
I suspect its because I add the button inside a useEffect that gets called several times, but it's also the only way to access leafletContainer, since it must be updated everytime the context changes.
I tried creating another useEffect that contains the same context and my new button, but it did not work.
Does anyone have any suggestion on how to solve this ?
Thnak you in advance
You only want to run this effect once, just after context becomes available. In order to do this, we can make a state variable to track whether or not you've already added the control:
const Geoman = () => {
const context = useLeafletContext();
const [added, setAdded] = useState(false);
useEffect(() => {
const leafletContainer = context.layerContainer || context.map;
// if the context is ready, and we've not yet added the control
if (leafletContainer && !added){
leafletContainer.pm.setGlobalOptions({ pmIgnore: false });
//draw control options
leafletContainer.pm.addControls({
// ...
});
//new button
leafletContainer.pm.Toolbar.copyDrawControl('Line', {
// ...
});
// register that we've already added the control
setAdded(true);
}
return () => {
leafletContainer.pm.removeControls();
leafletContainer.pm.setGlobalOptions({ pmIgnore: true });
};
}, [context]);
return null;
};
In this way, you effect will run whenever context changes - once context is ready, you add the control. You register that you've added the control, but then your if statement will make sure that further changes in context will not try to keep adding controls again and again.
BTW, a second option to using leaflet geoman with react leaflet is to use the official createControlComponent hook to create custom controls. This is not at all straightforward with leaflet-geoman, as createControlComponent requires you to feed it an instance of an L.Control that has all the required hooks and initializer methods. geoman does not have these - it is quite different in the way it initializes and adds to a map. However, you can create an L.Control from geoman methods, and then feed it to createControlComponent.
Create the L.Control:
/**
* Class abstraction wrapper around geoman, so that we can create an instance
* that is an extension of L.Control, so that react-leaflet can call all
* L.PM methods using the expected L.Control lifecycle event handlers
*/
const GeomanControl = L.Control.extend({
initialize(options: Props) {
L.PM.setOptIn(options.optIn ?? false);
L.setOptions(this, options);
},
addTo(map: L.Map) {
const { globalOptions, events } = this.options;
// This should never happen, but its better than crashing the page
if (!map.pm) return;
map.pm.addControls(toolbarOptions);
map.pm.setGlobalOptions({
pmIgnore: false,
...globalOptions,
});
// draw control options
map.pm.addControls({
// ...
});
// new button
map.pm.Toolbar.copyDrawControl('Line', {
// ...
});
Object.entries(events ?? {}).forEach(([eventName, handler]) => {
map.on(eventName, handler);
});
},
});
Then simply use createControlComponent
const createControl = (props) => {
return new GeomanControl(props);
};
export const Geoman = createControlComponent(createControl);
You can add quite a lot of logic into the addTo method, and base a lot of its behaviors off the props you feed to <Geoman />. This is another flexible way of adapting geoman for react-leaflet v4.
So I have managed to inject hls.js to work with nuxtjs $root element and this
I did so doing it like this (hls.client.js):
import Hls from 'hls.js';
export default (context, inject) => {
inject('myhls', Hls)
}
and nuxt.config.js
plugins: [
'~plugins/hls.client.js',
],
This works great, literally :-) but I can't reference 'this' in the hls events.
playHls() {
this.playing = true
if(this.$myhls.isSupported()) {
this.hls = new this.$myhls();
this.audio = new Audio('');
this.hls.attachMedia(this.audio);
this.hls.loadSource(this.scr_arr[2]);
this.audio.play()
// 'THIS' DOES NOT WORK INSIDE this.hls.on
this.hls.on(this.$myhls.Events.MEDIA_ATTACHED, function () {
console.log(this.scr_arr[2]); // DOES NOT LOG
console.log("ok") // works great
});
// 'THIS' DOES NOT WORK INSIDE this.hls.on
this.hls.on(this.$myhls.Events.MANIFEST_PARSED, function (event, data) {
console.log('manifest loaded, found ' + data.levels.length + ' quality level') // WORKS
console.log("ok") // WORKS
this.audio.volume = 1 // DOES not work
});
}
},
So I in these Events I can't use nuxtjs 'this', cause there seems to be a different scope?
Can I somehow get 'this' nuxt scope inside these Events?
Replace your functions like
this.hls.on(this.$myhls.Events.MANIFEST_PARSED, function (event, data) {
into
this.hls.on(this.$myhls.Events.MANIFEST_PARSED, (event, data) => {
to keep the this context tied to the Vue app, otherwise it will be scoped to the context of the block scope.
I am trying to write a game using lance-gg library.
I tried to implement a simple aframe component, that print entity's object3D position and rotation in world space.
The problem is that I cannot access this from within the component event listener.
I have tried to search around I've found this [thread] (Aframe unregister component), so I guess the problem is the initialization order. I have tried to include a component directly from the index but it does't worked either.
// aSeparateFile.js
AFRAME.registerComponent(
'custom-component',
{
schema: {
controllerID: {
type: 'string',
default: 'none'
}
},
init: () => {
console.log('componet has been created');
console.log(this);
},
tick: () => {
console.log(this.el.object3D.rotation);
console.log(this.el.object3D.position);
}
}
);
this component was created in a separate file called aSeparateFile.js, I include this file from my AFrameRenderer extension. Like this:
import {AFRAMERenderer} from 'lance-gg';
import './aSeparateFile.js';
I would like to know the best way to register a custom component with lance-gg.
Don't use arrow functions that will bind the methods to the wrong this. Use regular functions instead:
AFRAME.registerComponent(
'custom-component',
{
schema: {
controllerID: {
type: 'string',
default: 'none'
}
},
init: function () {
console.log('componet has been created');
console.log(this);
},
tick: function () {
console.log(this.el.object3D.rotation);
console.log(this.el.object3D.position);
}
});
Suppose I have pageA where I listen for a firebase document changes
export default {
mounted() {
this.$f7ready(() => {
this.userChanges();
})
},
methods: {
userChanges() {
Firebase.database().ref('users/1').on('value', (resp) => {
console.log('use data has changed');
});
}
}
}
Then I go to pageB using this..$f7.views.current.router.navigate('/pageB/')
If on pageB I make changes to the /users/1 firebase route I see this ,message in the console: use data has changed, even though I'm on a different page.
Any way to avoid this behavior, maybe unload the page somehow?
I tried to stop the listener before navigating away from pageA using Firebase.off() but that seems to break a few other things.
Are you properly removing the listener for that specific database reference? You'll have to save the referenced path on a dedicated variable to do so:
let userRef
export default {
mounted() {
this.$f7ready(() => {
this.userChanges();
})
},
methods: {
userChanges() {
userRef = Firebase.database().ref('users/1')
userRef.on('value', (resp) => {
console.log('use data has changed');
});
},
detachListener() {
userRef.off('value')
}
}
}
That way you only detach the listener for that specific reference. Calling it on the parent, would remove all listeners as the documentation specifies:
You can remove a single listener by passing it as a parameter to
off(). Calling off() on the location with no arguments removes all
listeners at that location.
More on that topic here: https://firebase.google.com/docs/database/web/read-and-write#detach_listeners
I'm getting posts and comments from an API, using Vuex. Right now, I have:
mounted () {
this.$store.dispatch('getComments', this.post);
},
computed: {
comments () {
return this.$store.getters.orderedComments;
},
The comments body is a string that contains HTML tags. I need to strip out the href attribute from a tags of a given class.
cleanUnwantedLinks () {
const link = document.getElementsByClassName('link-class')[0];
link.removeAttribute('href');
}
I'm not sure how to call cleanUnwantedLinks. It should be called just after the component is mounted (when I have the comments already). Is there a way to do it?
If you will return a promise from your getComments action, you could do:
this.$store
.dispatch('getComments', this.post)
.then(() => {
cleanUnwantedLinks()
/*
Can also try wrapping your call in $nextTick
this.$nextTick(() => { cleanUnwantedLinks() })
*/
});