Es6 class with event handlers and issues with context/scope - reactjs

I have a container with two panes. I am making one of them re-sizable and absolute, and the other adjusts in real time based on the size of the first.
Unfortunately I am having problems with the es6 "resize-controller" that I just wrote. Because I made the wrong decision to use arrow functions, I am unable to remove the event listeners. Other than that and a bug with my math, it is functioning as expected. However when I tried to fix my mistakes with several possible solutions, I either get no functionality or errors about context and functions not being functions.
Hoping someone here could take a look at my exact setup and show me how it should be. Replacing the Arrow functions with normal functions, with or without event params, does not work. Would I be better off ditching Es6 classes for this one? Perhaps I have unlocked a few layers of complexity.
class ResizeController {
constructor(){
this.chatContainer = document.getElementById("chatContainer");
this.messageWindowContainer = document.getElementById("messageWindowContainer");
this.messageWindowContentContainer = document.getElementById("messageWindowContentContainer");
this.messageWindowContent = document.getElementById("messageWindowContent");
this.startX = "";
this.startWidth = this.getMessageWindowWidth();
}
init(){
this.messageWindowContainer.addEventListener('mousedown', (e)=>{e.stopPropagation()});
this.messageWindowContentContainer.addEventListener('mousedown', (e)=>{this.onMouseDown(e)});
this.messageWindowContent.addEventListener('mousedown', (e)=>{e.stopPropagation()})
}
onMouseDown(e) {
this.onDown(e);
e.preventDefault();
}
onDown(e){
this.startX = e.clientX;
this.messageWindowContentContainer.addEventListener('mousemove', (e)=>{this.onMouseMove(e)});
this.messageWindowContentContainer.addEventListener('mouseup', (e)=>{this.onMouseUp(e)});
}
onMouseMove(e){
this.mouseMove(e);
e.preventDefault();
}
onMouseUp(e){
this.mouseUp(e);
e.preventDefault();
}
mouseMove(e) {
this.messageWindowContainer.setAttribute("style","width:"+( this.startWidth - e.clientX + this.startX)+"px");
}
mouseUp(e){
console.log("stopdrag")
this.messageWindowContentContainer.removeEventListener('mousemove', (e)=>{this.onMouseMove(e)});
this.messageWindowContentContainer.removeEventListener('mouseup', (e)=>{this.onMouseUp(e)});
}
getMessageWindowWidth(){
let chatContainerWidth = document.getElementById("chatContainer").offsetWidth;
let messageWindowContainerWidth = document.getElementById("messageWindowContainer").offsetWidth;
return (chatContainerWidth - messageWindowContainerWidth);
}
}
export default ResizeController

Answer found here: https://gist.github.com/Restuta/e400a555ba24daa396cc
I simply defined the following code once in my constructor and then used them as my event handlers.
this.bound_onMouseDown = this.onMouseDown.bind(this);
this.bound_onMouseMove = this.onMouseMove.bind(this);
this.bound_onMouseUp = this.onMouseUp.bind(this);
Declaring them outside the constructor is not a solution.

The value passed as second parameter to .addEventListener() and .removeEventListener() should be a function reference instead of an anonymous function. You can use .bind() to preserve this.
class ResizeController {
constructor() {
this.chatContainer = document.getElementById("chatContainer");
this.button = document.querySelector("button");
this.init();
}
init() {
// pass function reference
this._onMouseMove = this.onMouseMove.bind(this);
this.chatContainer.addEventListener("mousemove", this._onMouseMove);
this.button.onclick = () => this.removeListener();
}
onMouseMove(e) {
this.chatContainer.innerHTML = "moved at " + new Date();
}
removeListener() {
// pass function reference
this.chatContainer.removeEventListener("mousemove", this._onMouseMove);
this.chatContainer.textContent = "removed mousemove event listener";
}
}
onload = () => new ResizeController();
#chatContainer {
display: block;
position: relative;
width: 300px;
height: 200px;
border: 2px solid blue;
}
<div id="chatContainer"></div>
<button>remove mousemove event listener</button>

Related

"_mock.default.unsubscribe is not a function" React using mock.js can't unsubscribe

Will attach full example of this task mock.js unsubscribe. I have a store reducer (tbodyCommand.reducer.js) that needs to take 10 rows from data by subscribing it and than after that it have to be unsubscribed. I am using standard .unsubscribe() method, but it goes to TypeError _mock.default.unsubscribe is not a function. How can i turn off my subscribing?
I found similar question over here unsubscribe is not a function on an observable where main problem appears in RxJS version but mine seems to work good for me. The example of syntax construction over here with subscribe method doesn't contain any way to deal with mock.js; How can I use it in my example so it will work? Is there any other ways to come to this problem simpler?
method
Got changed properties$.subscribe to variable and took out return from subscribe function.
var row_temp = [];
const makeTbodyArray = () => {
properties$.subscribe((data) => { // this stuff need to go in variable
if (row_temp.length < 11) {
row_temp.push(data);
} else {
properties$.unsubscribe();
return row_temp; // taking out return
}
});
};
to
var row_temp = [];
const makeTbodyArray = () => {
let subscription = properties$.subscribe((data) => {
if (row_temp.length < 11) {
row_temp.push(data);
} else {
subscription.unsubscribe();
}
});
return row_temp; // like that
};

Super keyword doesn't work with variables when trying to extend p5.js library

I want to extend the p5.js library in order to have error text on various locations on the screen. I will be using it in different places throughout my app and I believe it's better to do this than duplicating the code.
For now, almost everything is working fine, except some properties. For example, if I access super.height I'll get 0, while if I access this.height I'll get the actual window height. But, when accessing this.height I get an error saying that height isn't defined in CustomP5, which is right, but at the same time confusing.
import * as p5 from 'p5';
export class CustomP5 extends p5 {
... // private fields not related to this issue
constructor(sketch, htmlElement) {
super(sketch, htmlElement);
// Set tooltip error variables
this.resetTooltipError();
}
setSetup(setupFunction) {
super.setup = () => {
setupFunction();
this.setupAdditional();
}
}
setDraw(drawFunction) {
super.draw = () => {
drawFunction();
this.drawAdditional();
};
}
showTooltipError() {
...
}
Is there a reason why super.height, super.mouseX, and super.mouseY don't work, while super.draw or super.mousePressed are working correctly?
PS: I'm quite new to js and ts, so be patient if I'm wrong, please.
I'm not an expert, but it sounds like super only works with functions, not variables.
You say it works with super.draw and super.mousePressed. These are both functions. You say it does not work with super.height, super.mouseX, or super.mouseY. All of these are variables.
This matches the MDN docs for super:
The super keyword is used to access and call functions on an object's parent.
class Rectangle {
constructor(height, width) {
this.name = 'Rectangle';
this.height = height;
this.width = width;
}
sayName() {
console.log('Hi, I am a ', this.name + '.');
}
get area() {
return this.height * this.width;
}
set area(value) {
this.height = this.width = Math.sqrt(value);
}
}
class Square extends Rectangle {
constructor(length) {
this.height; // ReferenceError, super needs to be called first!
// Here, it calls the parent class' constructor with lengths
// provided for the Rectangle's width and height
super(length, length);
// Note: In derived classes, super() must be called before you
// can use 'this'. Leaving this out will cause a reference error.
this.name = 'Square';
}
}
So it sounds like this is working as intended. You might want to take some time to read up on how inheritance and the super keyword work in JavaScript.

Binding Angular2 components inside of a Jquery plugin template

I'm working on using a kendo inside of an angular 2 project.
Getting the widget set up correctly is no problem:
ngOnInit() {
let options = inputsToOptionObject(KendoUIScheduler, this);
options.dataBound = this.bound;
this.scheduler = $(this.element.nativeElement)
.kendoScheduler(options)
.data('kendoScheduler');
}
When that runs, the plugin modifies the DOM (and, to my knowleged, without modifiying the shadow DOM maintained by angular2). My issue is that if I want to use a component anywhere inside of the plugin, like in a template, Angular is unaware of it's existence and won't bind it.
Example:
public views:kendo.ui.SchedulerView[] = [{
type: 'month',
title: 'test',
dayTemplate: (x:any) => {
let date = x.date.getDate();
let count = this.data[date];
return `<monthly-scheduler-day [date]="test" [count]=${count}"></monthly-scheduler-day>`
}
}];
The monthly-scheduler-day class:
#Component({
selector: 'monthly-scheduler-day',
template: `
<div>{{date}}</div>
<div class="badge" (click)=dayClick($event)>Available</div>
`
})
export class MonthlySchedulerDayComponent implements OnInit{
#Input() date: number;
#Input() count: number;
constructor() {
console.log('constructed');
}
ngOnInit(){
console.log('created');
}
dayClick(event){
console.log('clicked a day');
}
}
Is there a "right" way to bind these components inside of the markup created by the widget? I've managed to do it by listening for the bind event from the widget and then looping over the elements it created and using the DynamicComponentLoader, but it feels wrong.
I found some of the details I needed in this thread: https://github.com/angular/angular/issues/6223
I whipped this service up to handle binding my components:
import { Injectable, ComponentMetadata, ViewContainerRef, ComponentResolver, ComponentRef, Injector } from '#angular/core';
declare var $:JQueryStatic;
#Injectable()
export class JQueryBinder {
constructor(
private resolver: ComponentResolver,
private injector: Injector
){}
public bindAll(
componentType: any,
contextParser:(html:string)=>{},
componentInitializer:(c: ComponentRef<any>, context: {})=>void):
void
{
let selector = Reflect.getMetadata('annotations', componentType).find((a:any) => {
return a instanceof ComponentMetadata
}).selector;
this.resolver.resolveComponent(componentType).then((factory)=> {
$(selector).each((i,e) => {
let context = contextParser($(e).html());
let c = factory.create(this.injector, null, e);
componentInitializer(c, context);
c.changeDetectorRef.detectChanges();
c.onDestroy(()=>{
c.changeDetectorRef.detach();
})
});
});
}
}
Params:
componentType: The component class you want to bind. It uses reflection to pull the selector it needs
contextParser: callback that takes the existing child html and constructs a context object (anything you need to initialize the component state)
componentInitializer - callback that initializes the created component with the context you parsed
Example usage:
let parser = (html: string) => {
return {
date: parseInt(html)
};
};
let initer = (c: ComponentRef<GridCellComponent>, context: { date: number })=>{
let d = context.date;
c.instance.count = this.data[d];
c.instance.date = d;
}
this.binder.bindAll(GridCellComponent, parser, initer );
Well your solution works fine until the component needs to change its state and rerender some stuff.
Because I haven't found yet any ability to get ViewContainerRef for an element generated outside of Angular (jquery, vanilla js or even server-side)
the first idea was to call detectChanges() by setting up an interval. And after several iterations finally I came to a solution which works for me.
So far in 2017 you have to replace ComponentResolver with ComponentResolverFactory and do almost the same things:
let componentFactory = this.factoryResolver.resolveComponentFactory(componentType),
componentRef = componentFactory.create(this.injector, null, selectorOrNode);
componentRef.changeDetectorRef.detectChanges();
After that you can emulate attaching component instance to the change detection cycle by subscribing to EventEmitters of its NgZone:
let enumerateProperties = obj => Object.keys(obj).map(key => obj[key]),
properties = enumerateProperties(injector.get(NgZone))
.filter(p => p instanceof EventEmitter);
let subscriptions = Observable.merge(...properties)
.subscribe(_ => changeDetectorRef.detectChanges());
Of course don't forget to unsubscribe on destroy:
componentRef.onDestroy(_ => {
subscriptions.forEach(x => x.unsubscribe());
componentRef.changeDetectorRef.detach();
});
UPD after stackoverflowing once more
Forget all the words above. It works but just follow this answer

React JS - cloneElement example?

When I change the inline style of a component depending on the state, I get this error.
Warning: `div` was passed a style object that has previously been mutated. Mutating `style` is deprecated.
In my render function, I'm calling this function before the return, to check a property.
this._isGameOver();
_isGameOver:
_isGameOver()
{
if (this.props.gameOver === false)
{
style.well.backgound = '#f5f5f5';
style.h1.color = '#32936F';
}
else
{
style.well.background = 'red';
style.h1.color = 'white';
}
}
So where and how do I use this clone? The documentation doesn't give any solid examples.
Sean
No need for cloneElement in your example, simply return a new style object instead of mutating the existing one.
_isGameOver() {
if (this.props.gameOver === false) {
return {
well: { background: '#f5f5f5' },
h1: { color: '#32936F' }
}
}
else return {
well: { background: 'red' },
h1: { color: 'white' }
}
}
Then you can merge the new styles with the old styles wherever you need using something like Object.assign
var newStyle = this._isGameOver()
return <h1 style={Object.assign({}, style.h1, newStyle.h1)} />
I'm guessing that you're defining that style object outside of your component (perhaps importing it?). So wherever you're going to modify that style you need to make a clone of it.
I always import as baseStyles (to indicate it shouldn't be mutated) and use lodash.cloneDeep() (because cloning deep objects is fiddly to do yourself).
So your function becomes
import cloneDeep from 'lodash/cloneDeep';
import baseStyle from '../blah/yourStyle.js';
_isGameOver()
{
const style = cloneDeep(baseStyle);
if (this.props.gameOver === false)
{
style.well.backgound = '#f5f5f5';
style.h1.color = '#32936F';
}
else
{
style.well.background = 'red';
style.h1.color = 'white';
}
}
You could also do something like:
const wellStyle = {
...style.well,
background: '$f5f5f5',
}
and then pass wellStyle to your component rather than style.well.
It might not suit your situation, but I only change styles inside the actual render method. It keeps everything in the one place (cloning your import in more than one place won't work).

Cannot access class variable

I started using typescript together with angularjs. I have written a simple class that is responsible for drag and drop behavior (or will be in future). But right now in my handleDragEnd function when I access canvas element I get error
Cannot read property of undefined
Below is my code - if someone could tell me what I am doing wrong I would be grateful.
class ToolBox {
private canvas;
constructor(canvas)
{
this.canvas = canvas;
$(".beacon").draggable({
helper: 'clone',
cursor: 'pointer',
start: this.handleDragStart,
stop: this.handleDragEnd
});
}
handleDragStart()
{
console.log('Drag Started!');
}
handleDragEnd()
{
var pointer = this.canvas.getPointer(event.e);
console.log('Drag End!');
return;
}
}
Since class methods are defined on ToolBox.prototype, the value of this is getting lost when you pass in the methods directly.
Change:
start: this.handleDragStart,
stop: this.handleDragEnd
To:
start: () => this.handleDragStart(),
stop: () => this.handleDragEnd()
That will preserve the value of this by calling the methods on the instance.

Resources