I have 2 components using same array binding for example:
{
title: "food",
data : ["data1", "data2", "data3"]
}
title is on parent component and data is binded from parent to child component to child works with the array.
how can i do to when i remove a array data element notify to parent component?
Here the example.
In the example i have a children with binded array and one method to remove array elements and notify. And parent compontent has a observer named arrayChanges.
if code works parent component has to know about child array length, but it doesnt works.
<script type='module'>
import {PolymerElement, html} from 'https://unpkg.com/#polymer/polymer/polymer-element.js?module';
import {} from 'https://unpkg.com/#polymer/polymer#3.1.0/lib/elements/dom-repeat.js?module';
class ParentComp extends PolymerElement {
static get properties() {
return {
myArr: {
type: Array,
observer: "arrayChanges"
},
changes: {
type: String
}
};
}
static get template() {
return html`
<div>[[myArr.title]]</div>
<children-comp data='[[myArr.data]]'></children-comp>
<div>[[changes]]</div>
`;
}
ready(){
super.ready();
this.myArr = {
title : "My component",
data : [
{titulo: "titulo1", comment : "im comment number 1"},
{titulo: "titulo2", comment : "im comment number 2"}
]
}
}
arrayChanges(){
this.changes = "Array length : "+this.myArr.data.length;
console.log("the Array has been changed");
}
}
class ChildrenComp extends PolymerElement {
static get properties() {
return {
data: {
type: Array,
notify: true
}
};
}
static get template() {
return html`
<ul>
<dom-repeat items='[[data]]' >
<template>
<li>
[[index]] )
[[item.titulo]]
[[item.comment]]
<button data-index$='[[index]]' on-click='handle_button'>Borrar</button>
<hr>
</li>
</template>
</dom-repeat>
</ul>
`;
}
handle_button(e){
var index = e.currentTarget.dataset.index;
this.notifyPath("data");
this.splice("data", index, 1);
}
}
customElements.define('children-comp', ChildrenComp);
customElements.define('parent-comp', ParentComp);
</script>
<parent-comp></parent-comp>
The parent component would only handle change-notifications in two-way bindings (using curly brackets). Your data binding incorrectly uses one-way binding (square brackets).
<children-comp data='[[myArr.data]]'></children-comp>
^^ ^^ square brackets: one-way binding
<children-comp data='{{myArr.data}}'></children-comp>
^^ ^^ curly brackets: two-way binding
Also note the simple observer (specified in myArr-property declaration) does not detect array mutations. You should use a complex observer instead. You could observe data/length changes and/or array mutations:
static get properties() {
return {
myArr: {
// type: Array, // DON'T DO THIS (myArr is actually an object)
type: Object,
// observer: 'arrayChanges' // DON'T DO THIS (doesn't detect array splices)
}
}
}
static get observers() {
return [
'arrayChanges(myArr.data, myArr.data.length)', // observe data/length changes
'arraySplices(myArr.data.splices)', // observe array mutations
]
}
arrayChanges(myArrData, myArrDataLength) {
console.log({
myArrData,
myArrDataLength
})
}
arraySplices(change) {
if (change) {
for (const s of change.indexSplices) {
console.log({
sliceIndex: s.index,
removedItems: s.removed,
addedItems: s.addedCount && s.object.slice(s.index, s.index + s.addedCount)
})
}
}
}
<html>
<head>
<script src="https://unpkg.com/#webcomponents/webcomponentsjs/webcomponents-loader.js"></script>
</head>
<body>
<script type='module'>
import {PolymerElement, html} from 'https://unpkg.com/#polymer/polymer/polymer-element.js?module';
import {} from 'https://unpkg.com/#polymer/polymer#3.1.0/lib/elements/dom-repeat.js?module';
class ParentComp extends PolymerElement {
static get properties() {
return {
myArr: {
type: Array,
},
changes: {
type: String
}
};
}
static get observers() {
return [
'arrayChanges(myArr.data, myArr.data.length)',
'arraySplices(myArr.data.splices)',
]
}
static get template() {
return html`
<div>[[myArr.title]]</div>
<children-comp data='{{myArr.data}}'></children-comp>
<div>{{changes}}</div>
`;
}
ready(){
super.ready();
this.myArr = {
title : "My component",
data : [
{titulo: "titulo1", comment : "im comment number 1"},
{titulo: "titulo2", comment : "im comment number 2"}
]
}
}
arrayChanges(myArr, myArrLength){
this.changes = "Array length : " + myArrLength;
console.log("the Array has been changed", myArr);
}
arraySplices(change) {
if (change) {
for (const s of change.indexSplices) {
console.log({
sliceIndex: s.index,
removedItems: s.removed,
addedItems: s.addedCount && s.object.slice(s.index, s.index + s.addedCount)
})
}
}
}
}
class ChildrenComp extends PolymerElement {
static get properties() {
return {
data: {
type: Array,
notify: true
}
};
}
static get template() {
return html`
<ul>
<dom-repeat items='[[data]]' >
<template>
<li>
[[index]] )
[[item.titulo]]
[[item.comment]]
<button data-index$='[[index]]' on-click='handle_button'>Borrar</button>
<hr>
</li>
</template>
</dom-repeat>
</ul>
`;
}
handle_button(e){
var index = e.currentTarget.dataset.index;
this.notifyPath("data");
this.splice("data", index, 1);
}
}
customElements.define('children-comp', ChildrenComp);
customElements.define('parent-comp', ParentComp);
</script>
<parent-comp></parent-comp>
</body>
</html>
Related
The source code:
import { LitElement, html, css } from '../vendor/lit-2.4.0/lit-all.min.js';
export class SearchInput extends LitElement {
static get properties() {
return {
src: { type: String },
items: { type: Array }
}
};
static styles = css`
`;
constructor() {
super();
this.items = [
{ text: 'Hola' },
{ text: 'mundo!' }
];
this.selectedItem = null;
this.text = 'foo';
}
selectItem(item) {
this.selectedItem = item;
this.text = this.selectedItem.text;
}
render() {
return html`
<div class="control">
<input class="input" type="text" value="${this.text}">
<ul class="result-list">
${this.items.map((item) => html`<li #click="${this.selectItem(item)}">${item.text}</li>`)}
</ul>
</div>
`;
}
}
customElements.define('search-input', SearchInput);
The text input (input type="text") value is not updating after changing property (this.text) using an event (this.selectItem) with LitElement library.
I tried it in browser but there is no error in browser console.
I expect that input value update after changing property with the event.
Thanks for the question! There are a few minor issues resulting in the value not updating.
One issue is that this.text is not a reactive property, so changing it isn't scheduling a re-render. Fix is to add text to the static properties.
The second issue is that your event listener click handler is the result of calling this.selectItems(item) and not a function, fixed with: #click=${() => this.selectItems(item)}.
Bonus: You may want to change the value attribute expression to a property expression using the live directive, .value="${live(this.text)}". I suggested this because the native input browser element always updates its contents if you update the value property, but only updates before a user has interacted with it when updating the value attribute. And the live directive is useful to tell Lit to dirty check the live DOM value in the input element.
Your code with the minor fixes: https://lit.dev/playground/#gist=a23dfbcdfbfcfb7de28b1f7255aaa8ee
or running in StackOverflow:
<script type="module">
import { LitElement, html, live } from 'https://cdn.jsdelivr.net/gh/lit/dist#2/all/lit-all.min.js';
class SearchInput extends LitElement {
static get properties() {
return {
src: { type: String },
items: { type: Array },
text: { type: String }, // <- Added this to make `this.text` a reactive property.
}
};
constructor() {
super();
this.items = [
{ text: 'Hola' },
{ text: 'mundo!' },
{ text: 'click these' },
];
this.selectedItem = null;
this.text = 'foo';
}
selectItem(item) {
this.selectedItem = item;
this.text = this.selectedItem.text;
}
render() {
return html`
<div class="control">
<!-- live directive is needed because user can edit the value of the input.
This tells Lit to dirty check against the live DOM value. -->
<input class="input" type="text" .value="${live(this.text)}">
<ul class="result-list">
<!-- Click event is a function -->
${this.items.map((item) =>
html`<li #click="${() => this.selectItem(item)}">${item.text}</li>`)}
</ul>
</div>
`;
}
}
customElements.define('search-input', SearchInput);
</script>
<search-input></search-input>
I have a problem with two web components created using lit-element v3.2.0 and I'm using custom events to emit the input value from the child component up to the parent component.
The form-input component is a reusable input that extracts the input value and emits it to the parent component with a custom event named "new-value" which is dispatched when the user writes in the field.
The form-container component contains the form-input, in here I'm binding the custom event "new-value" to a method called "updateInputValue" which should reassign the inputValue property with the emitted value from the child component, but instead is stuck with whatever value initialized in the parent constructor.
form-container.js
static get properties() {
return {
inputValue: { type: String },
items: { type: Array },
}
}
constructor() {
super()
this.inputValue = ''
this.items = []
}
render() {
return html`<form>
<h1>My form container</h1>
<form-input
#new-value=${this.updateInputValue}
fieldName="name"
id="name"
label="Name"
placeholder="Enter anything"
value="${this.inputValue}"
></form-input>
<button #click=${this.addNewItem} type="submit">Add</button>
<form-list .items="${this.items}"></form-list>
</form>`
}
updateInputValue(e) {
// Update input value with the value emitted from the form-input
this.inputValue = e.detail
}
addNewItem(e) {
// Add new item to the list
e.preventDefault()
console.log('add new item with the following value:', this.inputValue)
form-input.js
static get properties() {
return {
value: { type: String },
fieldName: { type: String },
label: { type: String },
placeholder: { type: String },
type: { type: String },
}
}
constructor() {
super()
}
render() {
return html`
<div>
<label for="name">${this.label}</label>
<input
#input=${this.dispatchEvent}
id="${this.fieldName}"
name="${this.fieldName}"
placeholder="${this.placeholder}"
type="${this.type || 'text'}"
value="${this.value}"
/>
</div>
`
}
dispatchEvent(e) {
// Emit the new value from the input to the parent component
const target = e.target
if (target) {
this.dispatchEvent(
new CustomEvent('new-value', {
detail: target.value,
})
)
}
}
Any help will be very much appreciated.
You are overwriting the dispatchEvent method and calling yourself. Rename the dispatchEvent Method and give it a meaningful name. It works perfectly.
<script type="module">
import {
LitElement,
html, css
} from "https://unpkg.com/lit-element/lit-element.js?module";
class FormInput extends LitElement {
static get properties() {
return {
value: { type: String },
fieldName: { type: String },
label: { type: String },
placeholder: { type: String },
type: { type: String },
};
}
render() {
return html`
<div>
<label for="name">${this.label}</label>
<input
#input=${this.changedInput}
id="${this.fieldName}"
name="${this.fieldName}"
placeholder="${this.placeholder}"
type="${this.type || 'text'}"
value="${this.value}"
/>
</div>
`
}
changedInput(e) {
// Emit the new value from the input to the parent component
console.log(e.target.value);
const myevent = new CustomEvent('my-event', {
bubbles: true,
composed: true,
detail: {
value: e.target.value
}
})
this.dispatchEvent(
myevent
);
}
}
class FormContainer extends LitElement {
static get properties() {
return {
name: {
inputValue: { type: String },
}
};
}
updateInputValue(e) {
console.log('received ' + e.detail.value);
this.inputValue = e.detail.value;
}
addNewItem(e) {
// Add new item to the list
e.preventDefault()
console.log('add new item with the following value:', this.inputValue);
}
render() {
return html`<form>
<h1>My form container</h1>
<form-input
#my-event="${this.updateInputValue}"
fieldName="name"
id="name"
label="Name"
placeholder="Enter anything"
value="${this.inputValue}"
></form-input>
<button #click=${this.addNewItem} type="submit">Add</button>
<form-list .items="${this.items}"></form-list>
</form>
`;
}
}
customElements.define("form-container", FormContainer);
customElements.define("form-input", FormInput);
</script>
<form-container></form-container>
When the page first loads, the delete buttons generated by the code below work as expected. However, if you alter the text in one of the <textarea> elements, the delete button no longer works correctly. How can I fix this?
import { LitElement, html } from 'lit-element';
class MyElement extends LitElement {
static get properties() {
return {
list: { type: Array },
};
}
constructor() {
super();
this.list = [
{ id: "1", text: "hello" },
{ id: "2", text: "hi" },
{ id: "3", text: "cool" },
];
}
render() {
return html`${this.list.map(item =>
html`<textarea>${item.text}</textarea><button id="${item.id}" #click="${this.delete}">X</button>`
)}`;
}
delete(event) {
const id = event.target.id;
this.list = this.list.filter(item => item.id !== id);
}
}
customElements.define("my-element", MyElement);
I'm not sure of the exact cause, but I think it has to do with the way lit-html decides which DOM elements to remove when rendering a list with fewer items than the previous render. The solution is to use the repeat directive. It takes as its second argument a function that helps lit-html identify which DOM elements correspond to which items in the array:
import { repeat } from 'lit-html/directives/repeat.js'
// ...
render(){
return html`
${repeat(this.list, item => item.id,
item => html`<textarea>${item.text}</textarea><button id="${item.id}" #click="${this.delete}">X</button><br>`
)}
`;
}
So I'm trying to build a chart as a webcomponent and I'm having some trouble with mapping an array. The error says: this.values.map is not a function
Here is the code:
import {LitElement, html, css} from 'lit-element';
class ApexChart extends LitElement {
static get properties(){
return{
values: {Array},
labels: {Array}
};
}
constructor(){
super();
this.values = [];
this.labels =[];
}
static get styles(){
return css `
`;
}
render(){
return html`
<p>${this.values.map(value => {value + 1})}</p>
`;
}
}
customElements.define('apex-chart', ApexChart);
I'm passing the values from html
<apex-chart values="[1,2,3,4]" labels="['Hi', 'Hello', 'Oi', 'Hola']"></apex-chart>
I can't see what I'm doing wrong
You have two issues:
1) the properties type converter is not defined correctly. It should be:
static get properties(){
return{
values: { type: Array },
labels: { type: Array }
};
}
2) The map method isn't working correctly. Currently it returns undefined values because if you use {} you must use the return keyword.
> [0, 1].map(value => { value + 1 })
<- (2) [undefined, undefined]
Instead use:
render(){
return html`
<p>${this.values.map(value => value + 1)}</p>
`;
}
Or:
render(){
return html`
<p>${this.values.map(value => { return value + 1; })}</p>
`;
}
I'm using a dom-repeat in Polymer. The corresponding list includes an initial value that should be set whenever the list of the dom-repeat is reset. However, when the first element of the list keeps the same initial value, the value is not reset even though I completely empty the list before resetting it to the new value. Here's my minimum example:
<dom-module id="console-app">
<template>
<div id="command-selection">
<paper-dropdown-menu id="command" label="Function">
<paper-listbox slot="dropdown-content" selected="{{_commandIndex}}">
<paper-item>A</paper-item>
<paper-item>B</paper-item>
</paper-listbox>
</paper-dropdown-menu>
</div>
<div id="parameters">
<template is="dom-repeat" items="[[_parameterData]]">
<parameter-block name="[[item.name]]" initial-value="[[item.initialValue]]" ></parameter-block>
</template>
</div>
</template>
<script>
class ConsoleApp extends Polymer.Element {
static get is() {
return 'console-app';
}
static get properties() {
return {
_commandIndex: {
type: Number,
value: -1,
observer: '_onIndexChange'
},
_parameterData: {
type: Array,
value: () => { return []; }
}
};
}
_onIndexChange() {
this.set('_parameterData', []);
switch (this._commandIndex) {
case 0:
this.set('_parameterData', [
{ name: 'AAA', initialValue: '111'},
{ name: 'BBB', initialValue: '123'}
]);
break;
case 1:
this.set('_parameterData', [
{ name: 'CCC', initialValue: '112'}
]);
break;
}
}
}
customElements.define(ConsoleApp.is, ConsoleApp);
</script>
</dom-module>
parameter-block:
<dom-module id="parameter-block">
<template>
<paper-input id="non-bool-value" label="[[name]]"
value="{{_value}}"></paper-input>
</template>
<script>
class ParameterBlock extends Polymer.Element {
static get is() {
return 'parameter-block';
}
static get properties() {
return {
_value: {
type: String,
value: () => { return ''; }
},
initialValue: {
type: String,
value: () => { return ''; },
observer: '_onInitialValueChange'
},
name: {
type: String,
value: () => { return ''; }
}
};
}
_onInitialValueChange() {
this.set('_value', this.initialValue);
}
}
customElements.define(ParameterBlock.is, ParameterBlock);
</script>
</dom-module>
When the index of the dropdown menu changes I reset _parameterData to [] and would assume that after that all future changes to _parameterData are evaluated as new elements. However, it seems like the list remembers the previous initial value after all as the corresponding listener is not called and previous changes to the first text element don't reset to 111 even though I'm changing the selection. If I use different initial values everything works fine, so I assume that I need to tell Polymer somehow to properly reset the elements, but how?
Since, you were only observing the initialValue the application will not know that the initialValue you were changing is for different name. That is why it is not resetting to default values you assigned.
You will need to observe both properties name and initialValue. So, change your observer code to :
static get observers() {
return [
'_onInitialValueChange(name, initialValue)'
]
}
and your method to:
_onInitialValueChange(name, initialValue) {
this.set('_value', this.initialValue);
}
I have updated the plnkr link provided.