Cannot access class variable - angularjs

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.

Related

Referring to Class functions inside a function when function is intended to be abstract. (Cannot read properties of undefined) [duplicate]

What is the difference between class method, class property which is a function, and class property which is an arrow function? Does the this keyword behave differently in the different variants of the method?
class Greeter {
constructor() {
this.greet();
this.greet2();
this.greet3();
}
greet() {
console.log('greet1', this);
}
greet2 = () => {
console.log('greet2', this);
}
greet3 = function() {
console.log('greet3', this);
}
}
let bla = new Greeter();
This is the resulting JavaScript when transpiled from TypeScript.
var Greeter = /** #class */ (function () {
function Greeter() {
var _this = this;
this.greet2 = function () {
console.log('greet2', _this);
};
this.greet3 = function () {
console.log('greet3', this);
};
this.greet();
this.greet2();
this.greet3();
}
Greeter.prototype.greet = function () {
console.log('greet1', this);
};
return Greeter;
}());
var bla = new Greeter();
My TypeScript version is 3.4.5.
There are differences between all 3 versions. This differences are in 3 areas:
Who is this at runtime
Where the function is assigned
What is the type of this in typescript.
Lets start with where they work just the same. Consider this class, with a class field:
class Greeter {
constructor(private x: string) {
}
greet() {
console.log('greet1', this.x);
}
greet2 = () => {
console.log('greet2', this.x);
}
greet3 = function () {
// this is typed as any
console.log('greet3', this.x);
}
}
let bla = new Greeter(" me");
With this class all 3 function calls will print as expected: 'greet* me' when invoked on bla
bla.greet()
bla.greet2()
bla.greet3()
Who is this at runtime
Arrow functions capture this from the declaration context, so this in greet2 is always guaranteed to be the class instance that created this function. The other versions (the method and function) make no such guarantees.
So in this code not all 3 print the same text:
function call(fn: () => void) {
fn();
}
call(bla.greet) // greet1 undefined
call(bla.greet2) //greet2 me
call(bla.greet3) // greet3 undefined
This is particularly important when passing the function as an event handler to another component.
Where the function is assigned
Class methods (such as greet) are assigned on the prototype, field initializations (such as greet2 and greet3) are assigned in the constructor. This means that greet2 and greet3 will have a larger memory footprint as they require an allocation of a fresh closure each time Greeter is instantiated.
What is the type of this in typescript.
Typescript will type this as an instance of Greeter in both the method (greet) and the arrow function (greet2) but will type this as any in greet3. This will make it an error if you try to use this in greet3 under noImplictAny
When to use them
Use the method syntax if this function will not be passed as an event handler to another component (unless you use bind or something else to ensure this remains the instance of the class)
Use arrow function syntax when your function will be passed around to other components and you need access to this inside the function.
Can't really think of a good use case for this, generally avoid.
this keyword difference:
In the above all three have same this but you will see the difference when you will pass the method to another functions.
class Greeter {
constructor() {
}
greet() {
console.log(this);
}
greet2 = () => {
console.log(this);
}
greet3 = function() {
console.log(this);
}
}
let bla = new Greeter();
function wrapper(f){
f();
}
wrapper(bla.greet) //undefined
wrapper(bla.greet2) //Greeter
wrapper(bla.greet3) //undefined
But there is another difference that the first method is on the prototype of class while other two are not. They are the method of instance of object.
class Greeter {
constructor() {
}
greet() {
console.log('greet1', this);
}
greet2 = () => {
console.log('greet2', this);
}
greet3 = function() {
console.log('greet3', this);
}
}
let bla = new Greeter();
console.log(Object.getOwnPropertyNames(Greeter.prototype))
If I have in the class -> str = "my string"; and in all the 3 methods I can say console.log(this.str) and it outputs the "my string". But I wonder - is this really actually the same thing
No they are not same things. As I mentioned that greet2 and greet3 will not be on Greeter.prototype instead they will be on the instance itself. It mean that if you create 1000 instances of Greeter their will be 1000 different method(greet2 and greet3) stored in memory for 1000 different instances. But there will a single greet method for all the instances.
See the below snippet with two instances of Greeter()

ExtJS-4: override on method 'onMouseDown' ain't working

I m not able to override the function 'onmousedown' from the class
Ext.util.Floating.
the sample is given below.
Is there some pattern to follow if i want to override functions with 'on' prefix?
constructor is getting invoked form the overridden code block but 'onMouseDown' still reverts back to the ext-all-debug file.
Ext.define('Ext.util.Floating.override', {
override: 'Ext.util.Floating',
constructor: function(){console.log('onmousedown: This gets invoked.')},
onMouseDown: function (e) {
console.log('OverrideOnMouseDown');
var focusTask = this.focusTask;
if (this.floating && (!focusTask || !focusTask.id)) {
this.toFront(!!e.getTarget(':focusable'));
}
}
});
The constructor of the class 'Ext.util.Floating' was hardcoded to pick up the method 'onMouseDown' with out checking for any override.
I overrode the constructor as well and implemented the check of override on 'onMouseDown' existence.
Ext.define('Ext.util.Floating.override', {
override: 'Ext.util.Floating',
constructor: function(){
.
.
.
me.mon(me.el, {
//redirects to the overridden method we provided.
mousedown: me.mixins.floating.onMouseDown ? me.mixins.floating.onMouseDown: me.onMouseDown,
scope: me
});
.
.
.
},
onMouseDown: function (e) {
console.log('OverrideOnMouseDown');
var focusTask = this.focusTask;
if (this.floating && (!focusTask || !focusTask.id)) {
this.toFront(!!e.getTarget(':focusable'));
}
}
});

Es6 class with event handlers and issues with context/scope

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>

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

Sencha Touch: Clicking a button rapidly will push a view twice

Say I have a button that triggers a push of a new view.
I noticed that if I click it more than once, fast enough, it will push the same view twice.
You can mimic this behavior using their official docs on this page, where they have a live sample:
http://docs.sencha.com/touch/2-0/#!/guide/navigation_view
the clear question is, simply how to prevent it?
Another method is to check what the active view is, and only push if it is not the same as the view you are about to push. I've tested this and it works.
E.g.
if (this.getNavigationView().getActiveItem().xtype != "someView") {
this.getNavigationView().push({ xtype: "someView" });
}
Extending jayteejee's answer, I've overridden the push method in a custom navigation view, like this:
Ext.define('BT.navigation.View', {
extend: 'Ext.navigation.View',
xtype: 'btnavigationview',
push: function (view) {
if(this.getActiveItem().xtype != view.xtype)
this.callParent(arguments);
else
console.warn("Prevented pushing a potentially duplicate view of xtype: " + view.xtype);
}
});
I'm not totally sure if the xtype assumption is safe enough, but I can't think of any situation in my current app that would require one view pushing another view of the same type onto the navigation stack. So, the solution works for me, and it's pretty neat. The warning is there to save me headache later on and possibly pulling my hair out trying to work out why push wouldn't work!
Masking successfully prevents double tapping problem.
In my code I'm using two functions for mask/unmask navigation container:
/**
* Mask container with rolling wheel. Usually need if Ajax-request is sent to the server and app waiting for response
* Best practice is masking the current navigator container, to prevent blocking whole app. Method warns if no container
* is defined. In some cases warning could be suppress with parameter
*
* #param container
* #param {boolean} [suppressWarning]
*/
startLoading: function(container, suppressWarning) {
var loadingComponent = container;
if (!loadingComponent) {
// <debug>
if (!suppressWarning) {
console.warn('Please define navigator container for non-blocking operation, or define suppressWarning parameter');
}
// </debug>
loadingComponent = Ext.Viewport;
}
// var lastMaskedContainer = container;
this.lastMaskedContainer = container;
loadingComponent.setMasked({
xtype: 'loadmask',
message: 'Loading...'
});
/*
Ext.defer(function() {
lastMaskedContainer.setMasked(false);
}, Pipedrive.app.maskingTimeout * 1000)
*/
},
/**
*
* #param {Ext.Container} container
* #param {boolean} [suppressWarning]
*/
stopLoading: function(container, suppressWarning) {
var loadingComponent = container;
if (!loadingComponent) {
// <debug>
if (!suppressWarning) {
console.warn('Please define either navigator container for non-blocking operation, or define suppressWarning parameter');
}
// </debug>
loadingComponent = Ext.Viewport;
}
var alreadyMasked = loadingComponent.getMasked();
var lastMaskedContainer = this.lastMaskedContainer;
if (!alreadyMasked && !suppressWarning) {
// <debug>
if (lastMaskedContainer != container) {
console.warn('Found Start/Stop Loading inconsistency. Please revise code'
+ (container ? '. Container: ' + container.getId() : 'Ext.Viewport')
+ (lastMaskedContainer ? ', last masked container: ' + lastMaskedContainer.getId() : '')
);
}
// </debug>
loadingComponent = Ext.Viewport;
}
loadingComponent.setMasked(false);
}
than in the tap handler:
onDealDetailsTap: function(ct) {
console.log('onDealDetailsTap', ct);
var form = ct.getReferenceForm(),
navigatorContainer = this.getNavigatorContainer(form),
model = form.getRecord();
UiHelper.startLoading(navigatorContainer);
Ext.Viewport.fireEvent('detailfields', {
title: model.get('title'),
id: model.get('id'),
store: 'DealFields',
navigatorContainer: navigatorContainer
})
},
to cleanup the loading mask:
control : {
activitiesContainer: {
push: 'onPushActivitiesContainer'
},
onPushActivitiesContainer: function(ct) {
//console.log('onPushActivitiesContainer', ct);
UiHelper.stopLoading(ct);
},
especially it is cool for waiting for long-timed ajax requests....
Cheers, Oleg
Just suspend the events on the button when it's tapped and resume them when the view is pushed
button.suspendEvents();
...
button.resumeEvents();
I don't think there is another way. As a developer or a user, when you tap a button twice, you expect the event handler to be called twice.
Hope this helps
simply mask the entire container and then unmask it; create a ref for the container or panel in which the button exists in your controller and on tap set:
ref.setMasked(true)
After the new view is pushed simply unmask by
ref.setMasked(false)
Another way is to flip a parameter once the list item has been tapped once, like this:
{
onListItemTap: function () {
if (!this.tapped) {
this.tapped = true;
...
}
}
}
Of course, that only works if you are destroying the list view as soon as the user goes to a different screen.
I created a method for checking this:
ENSURE_NO_DOUBLE_TAP : function(classNameToPush) {
if (Ext.getClassName(Ext.getCmp('MyViewport').getActiveItem()) == classNameToPush) {
return false;
}
return true;
}
Then from your app before anything that could be double tapped is processed:
if (!ENSURE_NO_DOUBLE_TAP('MyApp.view.View')) {
return;
}
If you are listening to the tap event of a button using listeners,then here is
my solution:
listeners : {
release : function(){
if(this.getDisabled())return false;
this.setDisabled(true);
this.fireEvent('tap');
},
tap : function() {
//do what you want
}
}
Extending on jayteejee's and Merott's answers, I've added some code to intercept on multiple fast pushes to not only prevent duplicates but to prevent pushing of different views as well before the page transition completes. Think of a user tapping different list items.
Also notice the view.destroy(); method in the else block to prevent view instances from heaping up in memory.
Ext.define('Overrides.navigation.View', {
extend: 'Ext.navigation.View',
xtype: 'ovrnavigationview',
interceptPush: false,
push: function (view) {
var activeItem = this.getActiveItem();
// Prevent multiple pushes & duplicates
if (!this.interceptPush && activeItem.xtype !== view.xtype) {
// Set interceptPush
this.interceptPush = true;
// Reset interceptPush after 500 ms
Ext.defer(function() {
this.interceptPush = false;
}, 500, this);
// Handle push
this.callParent(arguments);
} else {
// Warn developer
console.warn("Prevented pushing view of xtype: " + view.xtype);
// Destroy view
view.destroy();
return false;
}
}
});
You can just use the "itemsingletap" event.
If you want to support double taps as well, make a second listener for "itemdoubletap" and invoke the same function, both listeners work fine together.

Resources