In the below code, i am looping through a list of my data model (this.panel), and generating an input box for each. I would like to propagate any changes made by the user back to the model. to do this i am storing the index of the item im rendering in the html element, and adding a change event handler which will update the model using that index. The problem im having is the @change event at run time is throwing an error:
Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'updateModel')'
What's odd is i have another event handler, updateToggleState in a check box lower down that works fine. Any thoughts on what is going on here?
Here is the code: class Panel extends LitElement { static properties = { is_fetch_on_change: {}, };
constructor() {
super();
this.panel = [
{
"id": "ticker",
"type": "str",
"place_holder": "ticker (i.e. YHOO:^NDX)",
"value": "YHOO:^NDX",
"fn_param_name": "ticker"
},
{
"id": "st_dt",
"type": "datetime",
"place_holder": "st_dt (i.e. 2012-01-01)",
"value": "2012-01-01",
"fn_param_name": "st_dt"
},
{
"id": "end_dt",
"type": "datetime",
"place_holder": "end_dt (i.e. 2022-01-01)",
"value": "",
"fn_param_name": "end_dt"
}
]
this.is_fetch_on_change = false;
}
render() {
let ret_html = []
this.panel.forEach(function(c, i){
ret_html.push(html`
<div class="m-2">
<label>${c.fn_param_name}</label>
<input type="text" class="form-control" id = "${c.id}" placeholder="${c.place_holder}" value="${c.value}"
idx="${i}" @change=${this.updateModel} />
</div>
</div>
`)
})
ret_html.push(html`
<div class="m-2">
<label class="form-check-label" for="flexCheckDefault">
<input class="form-check-input" type="checkbox" @change=${this.updateToggleState}>
Refresh on change
</label>
</div>
<button type="submit" class="btn btn-primary m-2" ?hidden=${this.is_fetch_on_change}>Submit</button>
`)
return ret_html;
}
updateModel(e){
let idx = e.target.getAttribute('idx');
//add code here to update model
}
updateToggleState(e){
this.is_fetch_on_change = e.target.checked;
}
}
customElements.define('lit-panel', Panel);
Update:
I was able to solve the problem of not being able to reference 'this' from within the foreach loop. i needed to basically 'pass it in'. so with that solved, i can update the specific item of the dictionary now.
However, that does not update the element or re-render it.
Update 2: this is what i have now. i've tested this and it does add an element to this._panel_data when text input is changed. that change however is still not reflected in the UI.
class Panel extends LitElement {
static properties() {
_panel_data:{state: true}
};
constructor() {
super();
this.id = 100;
this._panel_data = [{"id":"ticker","type":"str","place_holder":"ticker (i.e. YHOO:^NDX)","value":"YHOO:^NDX","fn_param_name": "ticker"},
{"id":"st_dt","type": "datetime","place_holder": "st_dt (i.e. 2012-01-01)","value": "2012-01-01","fn_param_name": "st_dt"}]
}
render() {
return html`<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-gH2yIJqKdNHPEq0n4Mqa/HGKIhSkIHeL5AyhkYV8i59U5AR6csBvApHHNl/vI1Bx" crossorigin="anonymous">
${this._panel_data.map((c, i) =>
html`
<div class="m-2">
<label>${c.fn_param_name}</label>
<input type="text" class="form-control" id = "${c.id}" placeholder="${c.place_holder}" value="${c.value}"
idx="${i}" .ref_obj=${c} @change=${e => this.updateModel(e)} />
<!--test if changes made to above, are reflected here...-->
<input type="text" class="form-control" value="${c.value}" />
</div>
</div>
`
)}
`
}
updateModel(e){
let clone = {...e.target.ref_obj};
clone.value = e.target.value;
this._panel_data = [...this._panel_data, clone];
}
}
customElements.define('lit-panel', Panel);
Using an arrow function would solve the this
problem
this.panel.forEach((c, i) => {
// `this` will be bound within here
});
Or you can use a regular for loop too.
Since panel
is not specified as a reactive property, changing that will not automatically re-render the component. Perhaps you want to make that into an internal reactive state.
Keep in mind that since it is an array, just mutating it with this.panel[idx] = newValue
won't trigger a re-render since the array reference will be the same. You need to create a new array reference or call this.requestUpdate()
. More explained here https://lit.dev/docs/components/properties/#mutating-properties