ThreeJs EventHandling Hierarchies - javascript

I have many instantiated objects which all require their own handling of a specific event.
I have a class foo:
export default class Foo(){
constructor(eventManager){//reference to an event manager class
eventManager.eventPool.push(this.eventHandler)
this.someProperty = 'hello world'
}
eventHandler(e){
// logic to handle passed in event args
console.log(this.someProperty) //any property I access is undefined, no matter what I try
}
}
I have a static event handling class
export default class EventManager(){
constructor(){
this.eventPool = []
window.addEventListener('mousemove', this.onMouseMove.bind(this), false)
}
onMouseMove(e){
if(this.eventPool.length > 0){
for(let i=0;i<this.eventPool.length; ++i){
this.eventPool[i](e)
}
}
}
}
However when I call the eventHandler of a class and access its properties, they are undefined, I tried to bind the eventHandler to the class but that didn't work either. I'm not sure how the references are being handled since java-scripts not statically typed (natively)
this is being used in the context of threejs to abstract event handling away to be able to handle user input in various different ways on mesh's/other intractable items in the scene. I am aware of three.js's EventDispatcher but it doesn't give me enough control of the event hierarchy, I plan to make complicated event chains that I would like to be all neatly handled in a class that would not require editing source code.
How do I allow own objects eventHandlers to be called from a class managing all the objects function references? on a certain event?

I think the main problem is
for(let evenHandler in this.eventPool)
this.eventPool is an array, for let eventHandler in will only get the key or index of the array, rather than the element value. Try for let eventHandler of or let eventHandlerValue = this.eventPool[eventHandler].

I stumbled on the answer whilst reading some of three's source code and found a similar pattern...
While the solution was to bind the function to the instance as I tried before, it had to actually be done WHEN YOU PASS IT INTO THE ARRAY, not before, or after. With the example provided it would look like this....
export default class Foo(){
constructor(eventManager){//reference to an event manager class
eventManager.eventPool.push(this.eventHandler.bind(this))//bind the method while we pass it in
this.someProperty = 'hello world'
}
eventHandler(e){
console.log(this.someProperty) //now the function refrence will correctly access the instances properties
}

Related

Accessing this inside super() method - javascript

The code is as follows
class ComposerForm extends BaseForm {
constructor(formsObject, options) {
super({
...options,
setup: {},
});
this.formsObject = { ...formsObject };
}
..
}
Now i have a new form
class PreferencesForm extends ComposerForm {
constructor(company, options = {}) {
super(
{
upids: new UpidsForm(company).initialize(),
featureSettings: new FeatureSettingsForm(company)
},
options
);
}
}
When initialising the FeatureSettingsForm, i need to pass the Preference form along with the company object
Something like
{
featureSettings: new FeatureSettingsForm(company, {prefForm: this})
},
so that i can access the preference form inside featureSettings form.
But this cannot be done since this cannot be accessed inside the super method.
Any idea on how to achieve this?
If I understand you right,
You need to pass a FeatureSettingsForm instance in the object you're passing to super (ComposerForm) in the PreferencesForm constructor, and
You need this in order to create the FeatureSettingsForm instance
So you have a circular situation there, to do X you need Y but to do Y you need X.
If that summary is correct, you'll have to¹ change the ComposerForm constructor so that it allows calling it without the FeatureSettingsForm instance, and add a way to provide the FeatureSettingsForm instance later, (by assigning to a property or calling a method) once the constructor has finished, so you can access this.
¹ "...you'll have to..." Okay, technically there's a way around it where you could get this before calling the ComposerForm constructor, by falling back to ES5-level ways of creating "classes" rather than using class syntax. But it general, it's not best practice (FeatureSettingsForm may expect the instance to be fully ready) and there are downsides to have semi-initialized instances (that's why class syntax disallows this), so if you can do the refactoring above instead, that would be better. (If you want to do the ES5 thing anyway, my answer here shows an example of class compared to the near-equivalent ES5 syntax.)

Creating new instances of custom HTML elements

I am trying to understand the newish HTML custom elements.
My goal is, given some array of data, create n instances of the custom element. For example, given a list of 10 users, create 10 user html objects.
Ok - so I define a custom element in the html
HTML
<template-user>
<div class="user-name"></div>
</template-user>
Then I create my controller
JS
class UserTemplate extends HTMLElement {
constructor(){
super();
this.username = this.querySelectorAll('[class="user-name"]')[0];
}
setName(name){
this.username.innerHtml = name;
}
}
customElements.define('template-user', UserTemplate);
The page loads fine, but now I am confused on how to reuse that element. If I was doing normal old school stuff, I'd have a for-loop pumping out some HTML strings and setting the innerHTML of something. But now I'd rather do something like
for(let i = 0; i < users.length; i++){
let userTemplate = new UserTemplate();
userTemplate.setName(user.name);
// append to user list, etc..
}
When I try to do this, it seems to almost work. But it cannot find username, ie this.querySelectorAll will return null. That's only when I try to construct a new instance of this element. How then, am I supposed to create new custom element DOM objects?
Make sure you understand the requirements and limitations of constructors for Web Components:
https://html.spec.whatwg.org/multipage/custom-elements.html#custom-element-conformance
4.13.2 Requirements for custom element constructors
When authoring custom element constructors, authors are bound by the following conformance requirements:
A parameter-less call to super() must be the first statement in the constructor body, to establish the correct prototype chain and this value before any further code is run.
A return statement must not appear anywhere inside the constructor body, unless it is a simple early-return (return or return this).
The constructor must not use the document.write() or document.open() methods.
The element's attributes and children must not be inspected, as in the non-upgrade case none will be present, and relying on upgrades makes the element less usable.
The element must not gain any attributes or children, as this violates the expectations of consumers who use the createElement or createElementNS methods.
In general, work should be deferred to connectedCallback as much as possible—especially work involving fetching resources or rendering. However, note that connectedCallback can be called more than once, so any initialization work that is truly one-time will need a guard to prevent it from running twice.
In general, the constructor should be used to set up initial state and default values, and to set up event listeners and possibly a shadow root.
Several of these requirements are checked during element creation, either directly or indirectly, and failing to follow them will result in a custom element that cannot be instantiated by the parser or DOM APIs. This is true even if the work is done inside a constructor-initiated microtask, as a microtask checkpoint can occur immediately after construction.
You could make changes similar to this:
class TemplateUser extends HTMLElement {
static get observedAttributes() {
return ['user-name'];
}
constructor(){
super();
this.attachShadow({mode:'open'});
this.shadowRoot.innerHTML = '<div></div>';
}
attributeChangedCallback(attrName, oldVal, newVal) {
if (oldVal !== newVal) {
this.shadowRoot.firstChild.innerHTML = newVal;
}
}
get userName() {
return this.getAttribute('user-name');
}
set userName(name) {
this.setAttribute('user-name', name);
}
}
customElements.define('template-user', TemplateUser);
setTimeout( function () {
var el = document.querySelector('[user-name="Mummy"]');
el.userName = "Creature from the Black Lagoon";
}, 2000);
<template-user user-name="Frank N Stein"></template-user>
<template-user user-name="Dracula"></template-user>
<template-user user-name="Mummy"></template-user>
This uses shadowDOM to store a <div>, then you set the value through the user-name attribute or through the userName property.
But it cannot find username, ie this.querySelectorAll will return null.
When you make a new instance, the new element has no children so querySelectorAll will return an empty NodeList. If you query the DOM and select the template-user which has been defined in your markup then the username property will refer to your div element.
If you want a dynamically generated template-user element to have a <div class="user-name"></div> child by default, you should create and append an element in your constructor.
Also for selecting the first matching element you can use .querySelector(...) instead of .querySelectorAll(...)[0].
When you create a custom element via Javascript with new, you can set some variables that will be passed as parameters in the constrcutor() method:
for (let user of users) {
document.body.appendChild( new UserTemplate(user.name) )
}
You can get it and save it in an object variable, or use in as a variable in a template literal string in a Shadow DOM.
class UserTemplate extends HTMLElement {
constructor(username){
super()
//this.username = username
this.attachShadow({ mode:'open' })
.innerHTML = `<div class="user-name"> ${username} </div>`
}
}
customElements.define('template-user', UserTemplate);
you can create your components and assign properties right in JS
let templateUser = document.createElement('template-user');
templateUser.userName= 'Your name here';
document.body.appendChild(templateUser);
or something similar to that based on your needs.
Google has some docs here, about 2/3 the way down they describe how to "create an instance in JavaScript". Which can be pretty powerful, especially in a for-loop like your example

Class properties for react lifecycle methods

Can I write React lifecycle methods as class properties?
I've been using class properties for a while as I like the fact that I no longer have to manually bind my methods, but I'd like to keep some consistency across my components and I'm wondering if there is any drawback on writing the React lifecycle methods as class properties
import React, { Component } from 'react';
class MyComponent extends Component {
render = () => {
return (
<div>Foo Bar</div>
);
}
}
export default MyComponent;
For example, is the context of this class property affected compared to the context in an equivalent method. Given that the render method in the above code is written as an arrow function, this concern seems relevant.
In a way, the true answer depends on your build pipeline and what the resulting Javascript output looks like. There are two primary possibilities:
Input Code
Let's start by saying you are writing the following before going through any sort of pipeline transformations (babel, typescript, etc):
class Test {
test = () => { console.log('test'); };
}
Output as class member variable.
In one possible world, your pipeline will also be outputting the test function as a member variable for the output class. In this case the output might look something like:
function Test() {
this.test = function() { console.log('test'); };
}
This means that whenever you write new Test() the test function is going to be recreated every single time.
Output as class prototype function
In the other major possibility, your pipeline could be recognizing this as a function property and escape it from the class instance to the prototype. In this case the output might look something like:
function Test() {
}
Test.prototype = {
test: function() { console.log('test'); }
}
This means that no matter how many times you call new Test() there will still be only one creation of the test function around in memory.
Desired behavior
Hopefully it's clear that you want your end result to have the function end up on the prototype object rather than being recreated on each class instance.
However, while you would want the function to not end up as a property, that doesn't necessarily mean you couldn't write it that way in your own code. As long as your build chain is making the correct transformations, you can write it any way you prefer.
Although, looking at the default babel settings (which your babeljs tag leads me to believe you are using) it does not make this transformation for you. You can see this in action here. On the left I've created one class with the function as a property and one class with the function as a class method. On the right hand side, where babel shows it's output, you can see that the class with the function as a property still has it being an instance-level property, meaning it will be recreated each time that class's constructor is called.
I did find this babel plugin, which seems like it might add this transformation in, but I've not used it for myself so I'm not positive.
In my experience, the most reason for writing a method as a class property is when the method will be passed as a callback, and you need it to always be bound to the instance. React lifecycle methods will always be called as a method, so there's no reason to bind them (and you incur a tiny memory penalty when you do). Where this makes a difference is when you're passing a function to a component as a callback (e.g. onClick or onChange).
Take this example:
class BrokenFoo extends React.Component {
handleClick() {
alert(this.props.message);
}
render() {
return (
<button onClick={this.handleClick}>
Click me
</button>
)
}
}
The function represented by this.handleClick is not automatically bound to the component instance, so when the method tries to read the value of this.props it will throw a TypeError because this is not defined. Read this article if you're not familiar with this; the problem described in section 4.2 "Pitfall: extracting methods improperly" is essentially what's happening when you pass around a method without making sure it's bound correctly.
Here's the class, rewritten with the handler as a class property:
class HappyFoo extends React.Component {
handleClick = () => {
alert(this.props.message);
}
render() {
return (
<button onClick={this.handleClick}>
Click me
</button>
)
}
}
Effectively, you can think of the handleClick definition in the second example as placing this code into the component's constructor (which is just about exactly the way Babel does it):
this.handleClick = () => {
alert(this.props.message);
}
This achieves the same thing as calling bind on the function (as described in the linked article) but does it a little differently. Because this function is defined in the constructor, the value of this in this.props.message is bound to the containing instance. What this means is that the function is now independent of the calling context; you can pass it around and it won't break.
The rule of thumb that I follow: by default, write methods as methods. This attaches the method to the prototype and will usually behave the way you'd expect. However, if the method is ever written without parentheses (i.e. you're passing the value and not calling it), then you likely want to make it a class property.

undefined variables in javascript class

I'm having trouble running and understanding classes in javascript. I am experimenting this with a simple app that I made. The end goal of it is just to grab names from a textarea, store that in an array, and then perform actions on it such as displaying them to the screen or shuffling them. To do this, I made 2 classes. One for storing the names to an array and then another that extends from this class to manipulate the array - just for the sake of organization. Here's my code:
Store names to array
import $ from 'jquery';
class SaveInput {
// dom selection usually and firing events when a page loads.
constructor(){
this.mainText = $('.main-text');
this.names = [];
this.events();
}
//events to watch for such as click
events() {
// save names to array, no submit button
this.mainText.blur(this.saveNameIndex.bind(this));
}
// methods to be called from events
// save names without submitting
saveNameIndex() {
let namesResult = this.names = this.mainText.val().split('\n');
return namesResult;
}
}
export default SaveInput;
Maniuplate Array
import $ from 'jquery';
import SaveInput from './SaveInput';
class Display extends SaveInput {
// dom selection usually and firing events when a page loads.
constructor(names){
super(names);
this.h1= $('h1');
this.events();
}
//events to watch for such as click
events(){
this.h1.click(this.log.bind(this));
}
// methods to be called from events
//display images with names
log () {
console.log('test');
}
}
export default Display;
It doesn't matter what I do, as you can see from my 2nd class that I was just trying to test by click an h1 tag to console log some text. I always get the same error when the app loads:
App.js:10522 Uncaught TypeError: Cannot read property 'click' of undefined
at Display.events (App.js:10522)
at Display.SaveInput (App.js:96)
at new Display (App.js:10509)
at Object.defineProperty.value (App.js:10412)
at __webpack_require__ (App.js:20)
at Object.defineProperty.value (App.js:63)
at App.js:66
The only thing I want from the saveInput class is the names array so I can pass it on and there seems to be some conflict when I extend that class. What am I missing?
When you create a Display object, it first calls the base object's constructor. That SaveInput constructor calls this.events() which you have overridden in the derived Display class. That overridden implementation is expecting this.h1 to be set, but it isn't set yet, thus the error you see.
I don't really understand what you're trying to do here to know what fix to suggest, but this is a timing issue based on what happens in the constructor and what the derived class is ready for. Probably you should take some stuff out of the constructor so the object can get fully formed before you call this.events().
Also, if you expect the SaveInput version of events() to get called, you need to add super.events() to the Display implementation of events() so the base object's version gets called too.

Preventing Circular Dependencies with Exported Singleton Class

I have a question regarding a scenario I keep running into building HTML5 games resulting in difficult to manage circular dependencies.
I understand completely why the circular dependency is occuring and where it is occurring. However, I can't seem to figure out a convenient way to get around it, so I assume my logic / approach is fundamentally flawed.
Here's a little bit of context.
I have a game that has a single point of entry (compiled with Webpack) called Game.js. I have a basic event manager that allows for two functions on(key, callback) and fire(key, parameters).
The event manager simply creates an object, sets the supplied key of on as a property with an array value populated with any callback functions registered to that key. When the fire method is called that property is retrieved and all of the fuctions defined in it's array value are invoked.
What I'm trying to do
I want to be able to instance the event manager on Game.js and export an instance of Game that other classes can import and subsequently register callbacks to the Game instances event manager.
class Game {
constructor() {
this.events = new EventManager();
window.addEventListener('resize', this.resize.bind(this));
}
resize(event) {
if(window.innerWidth < window.innerHeight) {
this.events.fire('orientation-change', 'vertical');
} else {
this.events.fire('orientation-change', 'horizontal');
}
}
}
export default new Game();
Then for example a Button class may need to respond to an orientation change event fired by the Game. Please note the above is simply an example of a circumstance in which the event manager may fire an event, but this condition could be anything.
import Game from '../core/Game';
class Button {
constructor() {
Game.events.on('orientation-change', this.reorient.bind(this));
}
reorient() {
// ...
}
}
export default Button;
The above class is a UI component called Button that needs to know when the orientation-change event is fired, again please note this event could be anything.
What's the problem?
Nothing looks particularly wrong with the above, however, because Game.js is the entry point, at some point an instance of Button is created whether it be directly in Game.js or through another class which is subsequently instanced via Game.js which of course causes a circular dependency because even if not directly, Game imports Button and Button imports Game.
What I've tried
There are two main solutions that I have found that work (to some degree). The first being simply waiting for the export to be available using an interval check of the value of Game in the constructor of Button, like this:
import Game from '../core/Game';
class Button {
constructor() {
let check = setInterval(() => {
if(Game !== undefined) {
Game.events.on('orientation-change', this.reorient.bind(this));
clearInterval(check);
}
}, 100);
}
reorient() {
// ...
}
}
export default Button;
This will typically resolve in a single iteration.
The second solution being to use dependency injection and pass reference of Game to Button when it's instanced, which again works great, but the prospect of having to repeatedly do this per class seems unintuitive. The interval check works fine too, but seems hacky.
I'm feel like I'm completely missing something and that the solution isn't a difficult as I'm making it.
Thanks for any help regarding this.

Categories

Resources