I know I'm conceptually wrong somewhere here, but how would you arrange this type of situation? I think my confusion would be clear from the code below:
class Base {
constructor() {
this._defineProperties( this.properties );
}
_defineProperties(properties) {
properties.forEach((value, key) => {
Object.defineProperties(this, key, {
value: value,
enumerable: true
})
});
}
}
class Child extends Base {
properties: {
'title': '',
'text': ''
}
}
const obj = new Child();
child.title
child.text
Related
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>`
)}
`;
}
I have a parent component (B) that is getting data from it's parent input (A)
(C) have is (B) child component.
Inside (B) I'm having a selector that gets data from the store.
export class BComponent implements OnChanges {
#Input() branchId;
ngOnChanges() {
this.selectedDataByBranch$ = this.store.pipe(
select(selectBranchDirections, { branchId: this.branchId, dir: this.selectedDirection })
);
this.selectedDataByBranch$.subscribe(selectedDataByBranch => {
this.trainsDatasets = this.getDatasets(selectedDataByBranch);
this.lineChart.data.datasets = this.trainsDatasets ? this.trainsDatasets : [];
this.lineChart.update();
});
directionChanged(event) {
this.selectedDirection = event;
this.selectedDataByBranch$ = this.store.pipe(
select(selectBranchDirections, { branchId: this.branchId, dir: this.selectedDirection })
);
}
}
directionChanged is the Output event that I get from (C)
The issue this that selectedDataByBranch subscription is not getting the new data update triggered inside selectedDataByBranch$
I have also tried this way
directionChanged(event) {
this.selectedDirection = event;
select(selectBranchDirections, { branchId: this.branchId, dir: this.selectedDirection });
}
What i could suggest is. Turn your parameters into a Subject then merge with the store selection, in your directionChanged(event) method provide value to subject.
So your final code will be something like this:
export class BComponent implements OnChanges {
#Input() branchId;
criterias$= new Subject<{branchId:number,dir:number}>;
ngOnChanges() {
this.selectedDataByBranch$ = this.criterias$.pipe(mergeMap(criteria=> this.store.pipe(
select(selectBranchDirections, { branchId: criteria.branchId, dir: this.searchDirection})
)));
this.selectedDataByBranch$.subscribe(selectedDataByBranch => {
this.trainsDatasets = this.getDatasets(selectedDataByBranch);
this.lineChart.data.datasets = this.trainsDatasets ? this.trainsDatasets : [];
this.lineChart.update();
});
this.criterias$.next({branchId:this.branchId,dir:this.sortDirection}); // init first call
}
directionChanged(event) {
this.selectedDirection = event;
this.criterias$.next({ branchId: criteria.branchId, dir: this.searchDirection}});
);
}
}
This stackblitz tries to materialize what i say.
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>
I've cloned a repository which focuses on creating a To-Do application using ES6 and Polymer 3. I'm trying to implement a button which turns the background color containing a string green upon click. I've tried doing this, but I keep failing to get the desired result.
Example code:
static get properties() {
return {
list: {type: Array},
todo: {type: String},
};
}
constructor() {
super();
this.list = [
this.todoItem('buy cereal'),
this.todoItem('buy milk')
];
this.todo = '';
this.createNewToDoItem = this.createNewToDoItem.bind(this);
this.handleKeyPress = this.handleKeyPress.bind(this);
this.handleInput = this.handleInput.bind(this);
}
todoItem(todo) {
return {todo}
}
createNewToDoItem() {
this.list = [
...this.list,
this.todoItem(this.todo)
];
this.todo = '';
}
//Right here is where I tried to implement the background color change.
checkItem() {
checkItem = document.getElementById('checkItem'),
checkItem.addEventListener('click', () => {
this.list = this.list.filter(this.todo)
document.body.style.backgroundColor = 'green';
});
}
deleteItem(indexToDelete) {
this.list = this.list.filter((toDo, index) => index !== indexToDelete);
}
render() {
return html`
${style}
<div class="ToDo">
<h1>Grocery List</h1>
<h1 class="ToDo-Header">What do I need to buy today?</h1>
<div class="ToDo-Container">
<div class="ToDo-Content">
${repeat(
this.list,
(item, key) => {
return html`
<to-do-item
item=${item.todo}
.deleteItem=${this.deleteItem.bind(this, key)}
></to-do-item>
`;
}
)}
</div>
I'd be eternally thankful if someone helped me out. I've created two JSFiddle links which show the code I've worked on thus far:
Link 1: https://jsfiddle.net/r2mxzp1c/ (Check line 42-49)
Link 2: https://jsfiddle.net/zt0x5u94/ (Check line 13 & 22-24)
I'm not sure about the approach. But this link might help you
https://stackblitz.com/edit/web-components-zero-to-hero-part-one?file=to-do-app.js
from this guy: https://stackblitz.com/#thepassle
You should try to make the reactive templating work for you by defining presentation details in terms of your element's properties.
For example, this is a stripped-down approach to the same problem:
class TestElement extends LitElement{
static get properties() {
return {
'items': { 'type': Array }
};
}
constructor() {
super();
// set up a data structure I can use to selectively color items
this.items = [ 'a', 'b', 'c' ].map((name) =>
({ name, 'highlight': false }));
}
render() {
return html`<ol>${
this.items.map((item, idx) =>
html`<li
#click="${ () => this.toggle(idx) }"
style="background: ${ item.highlight ? '#0f0' : '#fff' }">
${ item.name }
</li>`)
}</ol>`;
}
toggle(idx) {
// rendering won't trigger unless you replace the whole array or object
// when using properties of those types. alternatively, mutate with the
// usual .push(), .splice(), etc methods and then call `this.requestUpdate()`
this.items = this.items.map((item, jdx) =>
jdx === idx ? { ...item, 'highlight': !item.highlight } : item
);
}
}
https://jsfiddle.net/rzhofu81/305/
I define the template such that the elements are colored the way I want depending on an aspect of their state (the "highlight" attribute of each entry in the list), and then I focus the interaction on updating the state to reflect what the user is doing.
I want to update/re-render the component after a new update comes up. All I am doing is:
I have a list of dealers for a casino game, what I want is to add a new dealer, and once the new dealer is added then display it in the view. It is actually happening, but in order for me to see the new dealer, I have to reload the page.
I am not updating the state, I am working with this.props. Look at my code
#connectToStores
export default class Dealers extends Component {
constructor (props) {
super(props);
this.state = {}
}
componentWillMount () {
GetDealersActions.getDealers();
}
static getStores () {
return [ GetDealersStore, CreateDealersStore ];
}
static getPropsFromStores () {
return {
...GetDealersStore.getState(),
...CreateDealersStore.getState(),
}
}
render () {
return (
<div>
{!!this.props.dealerData ?
this.props.dealerData.dealersData.map((dealer) => {
return (here I am rendering what I need);
: <p>Loading . . .</p>
</div>
}
_addDealer = () => {
CreateDealersActions.createDealer({
DealerName : this.refs.DealerName.getValue(),
CardId : this.refs.CardId.getValue(),
NickName : this.refs.NickName.getValue(),
});
}
}
as you see the component above in the code is doing the initial rendering properly, the problem comes up when you hit _addDealer(), which is not updating the component, you should reload the page in order to see the new item in the view.
If you do a console.log(this.props); within _addDealer(), you will get something like this
{params: Object, query: Object, dealerData: Object, newDealerData: null}
where dealerData holds the full data of the dealers in the view but you can't see there the new dealer created. And newDealerData remains null
so, what do you think I should do in order to update the component everytime a new prop/dealer comes up ? or how do I update the props? which is the proper method in this situation ?
here is the full code for stores and actions just in case
action
#createActions(flux)
class CreateDealersActions {
constructor () {
this.generateActions('createDealerSuccess', 'createDealerFail');
}
createDealer (data) {
const that = this;
that.dispatch();
axios.post(`${API_ENDPOINT}/create-dealer/create-dealer`, data)
.then(function success (data) {
that.actions.createDealerSuccess({data});
})
}
};
store
#createStore(flux)
class CreateDealersStore {
constructor () {
this.state = {
newDealerData : null,
};
}
#bind(CreateDealersActions.createDealerSuccess)
createDealerSuccess (data) {
this.setState({
newDealerData : data.response.config.data,
});
}
}
the Dealers component is within a tab named management, which is this one:
const menuItems = [
{ route : 'dealers', text : 'Dealers' },
{ route : 'game-info', text : 'Game Info' },
{ route : 'player-info', text : 'Players Info' },
{ route : 'money', text : 'Money' }
];
export default class Management extends React.Component {
static propTypes = {
getActivePage : React.PropTypes.func,
menuItems : React.PropTypes.arrayOf(React.PropTypes.object),
}
static contextTypes = {
router : React.PropTypes.func,
}
render () {
return (
<div>
<TabsMainMenu menuItems={menuItems} getActivePage={this._getActivePage} />
<RouteHandler />
</div>
);
}
_getActivePage = () => {
for (const i in menuItems) {
if (this.context.router.isActive(menuItems[i].route)) return parseInt(i, 10);
}
}
}