Aframe component can't reference el from event handler - javascript

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);
}
});

Related

Leaflet geoman "Button with this name already exists" error when creating a new button in react

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.

ExtJS call funtion of another component

I created a Ext.Mixin component and would like to call a function of it from another component. How do I have to do that? Must be very obvious, but I can't see right now.
EDIT:
Ext.define('ABC.mixin.MyMixin', {
extend: 'Ext.Mixin',
mixinConfig: {
after: {
},
before: {
initComponent: 'init'
}
},
init: function () {
let me = this;
myfunction();
},
myfunction: function () {
//do stuff
}
}
How do I call myfunction() ?
When you include a mixin to a component all of the functions the mixin provides are included to the component itself.
So when you have a reference to your created component you cann call the function on the component itself.
Ext.define('ABC.mixin.MyMixin', {
extend: 'Ext.Mixin',
myfunction: function () {
//do stuff
}
});
Ext.define('ABC.view.MyView', {
mixins: ['ABC.mixin.MyMixin'],
// ...other config stuff
});
let myView = Ext.create('ABC.view.MyView'); // concreate Object of the class ABC.view.MyView
myView.myfunction(); // we can call the function of the mixin on the Object directly.
For more information see the ExtJs documentation
The API Docs seem to provide the information you need. You just include your mixin in the component you need, like so:
Ext.define('ABC.view.MyComponent', {
mixins: ['ABC.mixin.MyMixin'],
initComponent() {
this.myfunction();
this.callParent();
}
});
And from the component's scope, call the mixin's functions you need

ReactJS window.addEventListener("hashchange") not working

I have a React component called Home. Within Home, I have a function "urlListener" in which I have added an event listener to alert whenever the URL is being changed.
var Home = React.createClass({
getInitialState: function () {
return {
openedFile: this.props.location.query.file || '',
apps: [],
showNav: this.props.location.query.file ? false : true,
layout: 'row',
cloneAppName: 'New Application',
appName: 'Application Name',
showSave: false
}
},
urlListener: function(){
window.addEventListener("hashchange", function(){
saveUnsaved();
});
},
saveUnsaved: function(){
}
The listener works fine and is being called whenever there is a change in the URL. However, the console says that the function I'm trying to call is not a function. I am new to React and any help on this would be appreciated.
Someone had commented the answer which seemed to work for me, but the comment was removed before I could accept the answer. The correct way of doing it is
{() => this.saveUnsaved();}
The arrow function apparently switches the context from "window" to "this"(the current component) internally. Without the arrow function, "this" would be referring to the "window" component.
Works perfectly for me.
// ES6:
componentDidMount() {
window.addEventListener('popstate', this.handleOnUrlChange, false)
}
componentWillUnmount() {
window.removeEventListener('popstate', this.handleOnUrlChange, false)
}
handleOnUrlChange = () => {
// your code
}

vue is not defined on the instance but referenced during render

I'm trying to build a simple app in vue and I'm getting an error. My onScroll function behaves as expected, but my sayHello function returns an error when I click my button component
Property or method "sayHello" is not defined on the instance but
referenced during render. Make sure to declare reactive data
properties in the data option. (found in component )
Vue.component('test-item', {
template: '<div><button v-on:click="sayHello()">Hello</button></div>'
});
var app = new Vue({
el: '#app',
data: {
header: {
brightness: 100
}
},
methods: {
sayHello: function() {
console.log('Hello');
},
onScroll: function () {
this.header.brightness = (100 - this.$el.scrollTop / 8);
}
}
});
I feel like the answer is really obvious but I've tried searching and haven't come up with anything. Any help would be appreciated.
Thanks.
But for a few specific circumstances (mainly props) each component is completely isolated from each other. Separate data, variables, functions, etc. This includes their methods.
Thus, test-item has no sayHello method.
You can get rid of the warning by using .mount('#app') after the Vue instance rather than the el attribute.
Check the snippet below;
var app = new Vue({
data: {
header: {
brightness: 100
}
},
methods: {
sayHello: function() {
console.log('Hello');
},
onScroll: function () {
this.header.brightness = (100 - this.$el.scrollTop / 8);
}
}
}).mount('#app');
Please note; the following might not be necessary but did it along the way trying to solve the same issue: Laravel Elixir Vue 2 project.

Meteor: onRendered doesn't get fired again after second render iron-route

I have an iron-router route:
Router.route('/profiel/bewerken', {
subscriptions: function () {
return Meteor.subscribe('currentUser');
},
action: function () {
if (this.ready())
this.render('profielBewerken', {
to: 'container',
data: function () { return Meteor.user(); }
});
else
this.render('profielBewerken', {
to: 'container',
data: { loading: true }
});
}
});
It waits until the subscription is available, and then renders the template again once the data is available. Even though it does render the template again with the data, my Template.profielBewerken.onRendered(function () { ...}) callback does not get fired a second time! Does anyone know why not and if there is a solution to this?
I could copy the template and rename it profielBewerken2 and render that, but then I would have to mirror two chucks of code and manually copy it again every time I modify the template... If there is a better option available then please let me know.
For those interested, I am adding the 'loading' class to a form in the initial template load (see http://semantic-ui.com/collections/form.html for the effect).
Thanks!
onRendered only fires when an instance of the template is added to the DOM, it will thereby not fire again on data changes.
If you want to execute code once the data is ready you should use the template.autorun function like so:
Template.profielBewerken.onRendered(function () {
this.autorun(function (comp) {
if (Meteor.user()) {
// do some stuff
comp.stop();
}
});
});

Categories

Resources