`this` is lost when I use lit-html expressions - javascript

I'm training with LitElement and lit-html. I'm trying to make complex templates with functions and event listener. I have a module for the template module
and another for one component where I use the templates.
I'm having problem with a template for a buttom which I pass a function as parameter and when I clicked on the buttom call the function.
This works: it makes the call but the reference for this is lost. I thought a possible cause maybe the arrow function so I rewrote the function like this:
let timerElementOperation = function(operationTimer, operation,that){
operationTimer.bind(that);
return html` <button #click=${function(){operationTimer()}}>${operation}</button> `;
}
But the problem is still there. What's happening?
//timer-element.js
class TimerElement extends LitElement{
...
static get properties(){
return {
running: { type: Boolean, reflect: true}
};
}
render(){
let partialTemplate;
if( this.isPausable(this.running, this.finished) && this.time > 0 ){
partialTemplate = Template.timerElementOperation(this.pause, 'pause');
} else if(!this.running && this.time > 0){
partialTemplate = Template.timerElementOperation(this.resume,'resume');
}
pause(){
this.running = false; // this is lost.
}
}
//timer-templates.js
import {html} from '#polymer/lit-element';
let timerElementOperation = (operationTimer, operation) => {
return html` <button #click=${() => operationTimer()}>${operation}</button> `;
}
export timerElementOperation;

How should the template in Template.timerElementOperation know what this should be? It's an isolated function.
There's two ways about this - the first is to explicitly bind this: replace this.pause with this.pause.bind(this). This will work, but honestly makes for fairly idiosyncratic code.
The common practice is to use the fact that any #event binding in Lit will have this bound for you to the class it's in, so:
class TimerElement
extends LitElement{
...
renderTimerElementOperation(operation){
return html`<button #click=${this[operation]}>${operation}</button>`;
}
render(){
let partialTemplate;
if( this.isPausable(this.running, this.finished) && this.time > 0 ){
partialTemplate = this.timerElementOperation('pause');
} else if(!this.running && this.time > 0){
partialTemplate = this.timerElementOperation('resume');
}
return html`... ${partialTemplate} ...`
}
}
This is a lot simpler, and you generally want to keep the templates with or in the associated elements, rather than imported. If you want something re-usuable create another LitElement to reuse that, rather than importing functions that render templates in isolation.

Related

Why am I getting "potentially invalid reference access to a class field via this in a nested function" error

In vanilla JS, my code would work fine. For this case, I'd like to componentize my Wall class which's supposed to display the image in the browser that the user has uploaded. Again, this works normally in vanilla JS but not JSX.
I'm getting a potentially invalid reference access to a class field via this in a nested function on the document.querySelector("#file-input").addEventListener("change", this.previewImages); line which I think is causing the issue.
What am I doing wrong and how can I fix it?
import React, {Component} from 'react';
class Wall extends Component {
constructor(props) {
super(props);
this.previewImages = this.previewImages.bind(this);
}
previewImages() {
let preview = document.createElement("div");
if (this.files) {
[].forEach().call(this.files, readAndPreview());
}
function readAndPreview() {
if (!/\.(jpe?g|png|gif)$/i.test(file.name)) {
return alert(file.name + " is not an image");
}
let reader = new FileReader();
reader.addEventListener("load", () => {
let image = new Image();
image.height = 100;
image.title = file.name;
image.src = this.result;
let date = Date.now();
let d = new Date(parseInt(date, 10));
let ds = d.toString("MM/dd/yy HH:mm:ss");
console.log(ds);
let initialCountOfLikes = 0;
let zeroLikes = document.createElement("h1");
let zeroLikesTextNode = zeroLikes.createTextNode(initialCountOfLikes + " likes");
zeroLikes.appendChild(zeroLikesTextNode);
preview.appendChild(image); // makes image appear
preview.appendChild(zeroLikes); // makes like count appear
image.ondblclick = function() {
if (initialCountOfLikes === 0) {
console.log("Inside if block");
initialCountOfLikes++;
console.log("initialCountOfLikes++ => " + initialCountOfLikes);
} else if (initialCountOfLikes === 1) {
console.log("inside second else if block");
initialCountOfLikes--;
console.log("initialCountOfLikes-- => " + initialCountOfLikes);
}
zeroLikesTextNode.nodeValue = initialCountOfLikes + " likes";
};
});
reader.readAsDataURL(file);
document.querySelector("#file-input").addEventListener("change", this.previewImages);
}
}
render() {
return (
<div id="file-input-wrapper">
<input type="file" />
<label htmlFor="file-input" id={"LblBrowse"} />
</div>
);
}
}
export default Wall;
The warning is telling you that using this in JavaScript frequently has confusing implications, specifically when used inside a function nested inside another function. this stops referring to the class, and instead refers to the scope of your nested function.
In your case, this probably is a legitimate problem (I think) because you have your class, Wall, which has a method previewImages() and a property files. Within that function, you have instantiated a new function, readAndPreview(), inside which you specify this.previewImages as a function callback to the addEventListener function.
They're saying you're potentially using this.previewImages incorrectly, because you're writing functions in traditional JavaScript syntax, function foo() { ... }, where this keeps being redefined in each child function call. In your case, I believe that this is referring to the context of readAndPreview(), and hence cannot access the method this.previewImages() since this doesn't refer to your parent class, Wall.
People used to do things like, make a var that = this; on the parent class, and you'd know that that always meant the parent class.
But now, with ES6 lambda functions using the "fat arrow" syntax () => { } you can access this and know it's referring to the parent scope.
I believe you can refactor your class to change previewImages() { into previewImages = () => { and know that this will refer to the class. You'll have to do the same with function readAndPreview() {. Change it to const readAndPreview = () => {. If you're setting it to a variable, though, I think you'll have to move it above the place you call it, though. e.g. above
if (this.files) {
[].forEach().call(this.files, readAndPreview());
}
I faced this error in Angular 8.
I used the Arrow function instead of regular functions to solve.
In your case.
readAndPreview = () => { ... }
This might solve your problem.
Using of arrow function may help you. Arrow functions don't have their own bindings to this, arguments or super.

Is it a good idea to overcharge a method?

I have 3 classes, all extend the previous one.
Entity -> Body -> Player
Each one has a die() method which do very different things.
Entity.die() will call the db
Body.die() will animate the body
Player.die() will call the UI and play special sound.
I don't want to manually call Entity.die() inside Body.die method, mainly because I have many classes and many common methods and I don't want to forget something.
I wrote this little piece of code which does exactly this, the Error stack is easy to understand and points to the correct lines.
function overLoadMethods (parent, children) {
const methods = {}
for (let [fname, fn] of Object.entries(parent)) {
if (typeof fn === 'function') {
if (children[fname]) {
methods[fname] = function () {
fn()
children[fname]()
}
Object.defineProperty(methods[fname], 'name', { value: fname })
} else {
methods[fname] = fn
}
}
}
return methods
}
function createEntity () {
return {
die: () => {
console.log(new Error().stack)
console.log('entity die')
}
}
}
const bodyMethods = {
die: () => {
console.log(new Error().stack)
console.log('body die')
}
}
function createBody () {
const entity = createEntity()
const overLoadedMethods = overLoadMethods(entity, bodyMethods)
return {
...entity,
...bodyMethods,
...overLoadedMethods
}
}
const playerMethods = {
die: () => {
console.log(new Error().stack)
console.log('player die')
}
}
function createPlayer () {
const body = createBody()
const overLoadedMethods = overLoadMethods(body, playerMethods)
return {
...body,
...playerMethods,
...overLoadedMethods
}
}
const player = createPlayer()
// will call Entity.die() then Body.die() then Player.die()
player.die()
Everything is working fine but I never saw this pattern before and I guess there is a good reason which I'm unaware of.
Could someone point the weakness of this pattern if there is one (pretty sure there is) ?
Common Lisp has something similar. When you define a method in a derived class you can decide whether this method should be executed:
:before (i.e. the base method will be called automatically after specialized one)
:after (i.e. the base method will be called automatically before the specialized one)
:around (i.e. only the specialized method will be called, but inside its body you can call the base method with call-next-method that is a special syntax that allows calling base method with either the parameters specified by the caller or the parameters that you want to pass instead).
For example C++ only has around available for general methods (but without the ability to call the base version with original parameters) and forces instead use of before in constructor and after in destructors.
I understand the desire to not repeat code and create code that makes it hard to make mistakes and forget things. But you still have code the you need to remember to wire up. For example, instead of calling Entity.die() you need to call overLoadMethods(). I'm not sure that's an improvement over regular of classes and calling super.die().
You can get the chained method behavior using ES6 classes (you can also get it using prototypes). This has a lot of advantages:
• The pattern is baked into the language.
• It's very clear to see parent/child relationship
• There's a lot of commentary, theory, and examples of different patterns
class Entity {
die() {
// Entity-specific behavior
console.log('entity die')
}
}
class Body extends Entity {
die() {
super.die()
// Body-specific behavior
console.log('body die')
}
}
class Player extends Body {
die() {
super.die()
// Player-specific behavior
console.log('player die')
}
}
const player = new Player
// will call Entity.die() then Body.die() then Player.die()
player.die()

"this" inside class methods targets window obj instead of class obj for reasons I can't fathom [duplicate]

This question already has answers here:
How to access the correct `this` inside a callback
(13 answers)
Closed 4 years ago.
I'm really confused as to why the "this" inside the class methods refers to window object instead of class object, I'm developing within the node environment and am exporting the class to be used within an React component, i've created the react app using create-react-app
here's the class code: it takes a dom element and applies a shake animation to it.
// make into a class/instance for shake animation on individual cards.
// that individual card will hold reference to obj instance
class ShakeElement{
constructor(div){
this.elementRef = div;
this.deg = 0;
this.loop = null; // setInterval ref
this.shakeAnim = null;
this.clearShakeAnimAfterInterval = null; // setTimeout ref
}
// var deg = rotated ? 0 : 66;
alterAngleOfElement(){
console.log("angle running for " + this.elementRef);
this.deg+=1;
this.elementRef.style.webkitTransform = 'rotate('+this.deg+'deg)';
this.elementRef.style.mozTransform = 'rotate('+this.deg+'deg)';
this.elementRef.style.msTransform = 'rotate('+this.deg+'deg)';
this.elementRef.style.oTransform = 'rotate('+this.deg+'deg)';
this.elementRef.style.transform = 'rotate('+Math.cos(this.deg * 1000)+'deg)';
if(this.deg >= 360){
this.deg = 0;
}
}
shakeEleCycle() {
var self = this;
console.log(this);
console.log(this.alterAngleOfElement);
this.shakeAnim = window.setInterval(this.alterAngleOfElement, 20);
this.clearShakeAnimAfterInterval = window.setTimeout(() => {
window.clearInterval(this.shakeAnim);
}, 500);
console.log("id to clear" + window.shakeAnim);
}
start(){
console.log("STARTING");
this.loop = window.setInterval(this.shakeEleCycle, 1000);
}
stop(){
console.log("STOPPING");
// clean up of timers
window.clearInterval(this.loop);
window.clearInterval(this.shakeAnim);
window.clearTimeout(this.clearShakeAnimAfterInterval);
}
}
module.exports = ShakeElement;
here's the react component where i instantiate the object and call the methods (not sure if that part is relevant)
class Card extends Component {
// isSpinning: false -- state
constructor(props) {
super(props);
this.spinCard = this.spinCard.bind(this);
this.shakeElementHelper = null;
}
spinCard(nodeId){ // wobble card event
this.shakeElementHelper = new ShakeElement(this["card"+ nodeId]);
console.log(this.shakeElementHelper);
this.shakeElementHelper.start();
}
componentDidUpdate(prevProps, prevState){
// nest functions in here with specific purposes to run different logic, break it up
// this.setState({isSpinning: true});
if(this.shakeElementHelper != null){
this.shakeElementHelper.stop();
}
if(this.props.isFan === true)
this.spinCard(this.props.id); // or id of redux item object
}
// componentWillMount
componentWillUnmount(){
// if(this.spinTimerId != null){
// window.clearInterval(this.spinTimerId);
// }
if(this.shakeElementHelper != null)
this.shakeElementHelper.stop();
}
render() {
return (
<div className="card" ref={(ele) => {
this["card" + this.props.id] = ReactDOM.findDOMNode(ele);
}} style={this.props.styles}>
<CardHintInformation gender={this.props.gender} school={this.props.school}/>
<CardHeader name={this.props.name} surname={this.props.surname} profession={this.props.profession}/>
<CardSectionsContainer bio={this.props.bio} tags={this.props.tags} links={this.props.links}/>
</div>
);
}
}
any help greatly appreciated, thanks
In
this.shakeAnim = window.setInterval(this.alterAngleOfElement, 20);
You are passing the function address of alterAngleOfElement, but it is not bound to the this of the instantiated object.
In your constructor of ShakeElement, you should:
this.alterAngleOfElement = this.alterAngleOfElement.bind(this)
so that it is bound the instantiated object. You'll see you do this elsewhere.
Also, consider refactoring your clean up code, as there is no guarantee that just because the interval is longer, the cleanup will fire after the animation has completed (due to browser background coalescing).
What #pointy said is correct,
When the alterAngleOfElement timer fires, the value of this won't be bound, so this is the global object.
Couple of changes are needed here,
shakeEleCycle() {
var self = this;
console.log(this);
console.log(this.alterAngleOfElement);
this.shakeAnim = window.setInterval(this.alterAngleOfElement, 20);
this.clearShakeAnimAfterInterval = window.setTimeout(() => {
window.clearInterval(this.shakeAnim);
}, 500);
console.log("id to clear" + window.shakeAnim);
}
Change it to,
shakeEleCycle() {
var self = this;
console.log(this);
console.log(this.alterAngleOfElement);
this.shakeAnim = window.setInterval(this.alterAngleOfElement.bind(this), 20);
this.clearShakeAnimAfterInterval = window.setTimeout(() => {
window.clearInterval(self.shakeAnim);
}, 500);
console.log("id to clear" + window.shakeAnim);
}
Now using the self variable that you've declared and the bind function to bind the value of this to the function.
Please read the note from #Dave below,
It's not considered efficient to bind on each call, when it can be done once in the constructor. Functionally this is correct, but sub-optimal. I point this out only because its a bad habit to get into, as repetitive use of bind can hurt render performance in React.
So better to do it once in the constructor rather than do it multiple times in the setInterval
You can read more about how this keyword in JavaScript works here and you can read more about the bind function here.
You should already know the bind function because you are using it here,
this.spinCard = this.spinCard.bind(this);
You can read more about why this is needed in React here.

.begin_search not a function

I have typescript function. Here is code
export class Step1{
constructor(){
this.begin_search();
this.results_ready();
}
private begin_search():void {
setTimeout(() => {
Searchfield.show_searchfield()
}, 100);
$('.search_box_overlay').show()
$('.search_box_overlay_top').show()
if (gon.search['search_type'] == 'package')
$('.search_box_overlay_top .package').show()
else if (gon.search['search_type'] == 'hotel')
$('.search_box_overlay_top .hotel').show()
else
$('.search_box_overlay_top .air').show()
window.seach_status_task = setInterval(Itinerary.check_search_status, 2000)
window.search_loading_itineraries = false
}
And then I importing this code into pack
Like this
$(document).ready(() => {
Translation.addDict(gon.translations);
Track.initialize();
new Searchfield();
if (!gon.search['searched']) {
Step1.begin_search();
}
if (gon && gon.search['id'] && $('.offer_hotel_addon').length > 0) {
check_status();
}
});
But when I run project, I have this error.
WEBPACK_IMPORTED_MODULE_3__components_step1.a.begin_search is not a function
Where can be problem and how I need to fix it?
The code defines begin_search as an instance method (a part of an instance), but is trying to use them as if they were static (a part of the class itself). Mark the method as static so that it belongs to the Step1 class itself:
private static begin_search():void {
See the handbook for more info on static methods. I'd also remove the this.begin_search(); call from the constructor as well. When the method is static, it doesn't exist on this anymore.

TypeScript: Overwrite instance function in unrelated class

I have an Item Class built like this
export class Item {
element:JQuery<HTMLElement>
constructor(...) {
this.element = $("<div >...</div>");
...
this._tooltipUpdate();
this.element.tooltip({
items: "div[data-tooltip]",
content: this.element.attr("data-tooltip"),
open: this._tooltipUpdate,
...
});
}
...
public _tooltipUpdate = ():void => {
this.element.attr(
"data-tooltip",
`...`
);
};
}
Basically, the Item Class has an element attribute that holds its DOM element.
I now have a different class, InventoryElement
export class InventoryElement extends MenuElement {
amount:number;
constructor(item:Item) {
super(...)
this.amount = 0;
...
this.item._tooltipUpdate = () => {
this.element.attr(
"data-tooltip",
`${this.amount}`
);
}
}
}
The Item instance of InventoryElement should have a different _tooltipUpdate function, basically.
Currently, it's not overwriting it correctly.
I had _tooltipUpdate on Item implemented like this before,
_tooltipUpdate() {
...
}
but I read that that would implement it as a prototype function instead of an instance function with the arrow operator above.
Am I using the arrow function properly? How can I change the function of the Item instance? Thanks!
When using arrow function, you are binding this to the calling instance of type InventoryElement.
If you want to call this.element in Item, you need to do
this.item._tooltipUpdate = function() {
// this is now bound to Item
}.bind(this.item);
See bind function definition
Generally I do not think the way you would do this, is the best way.
It seems like your _tooltipUpdate is something like a handler, that enables you to react upon a change within the item. It is always better to implement some event logic and then attach listeners to it.
Also, as I see, there is just amount that is changing. So why not just have a method within Item that says setAmount. That would be much cleaner and easier to implement.

Categories

Resources