'How to change child components own public property - Lit Elements?

I am trying to implement active button using lit-elements. On click of a button css class should get attached to that particular element. On next button click css has to be removed from previous button and get added to new button. I have implemented this using single custom element and it is working fine. But I tried to implement this using parent-child custom elements.

my-app - parent component my-button - child component

Issue: active class is not getting removed from 1st button on next button click. Please refer output image attached.

I am trying to change my-button's public property on its own click.

this.clicked = ! this.clicked;

Is there any way to make this below code work by changing child component's public property inside its own class?

<!doctype html>

<head>

    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    
    <style>
        body { 
            margin: 0; 
            height: 100vh; 
            text-align: center; 
            margin-top: 50px; 
            font-family: Arial;
        } 
    </style>  
    
</head>

<body> 
    <my-app></my-app>
</body> 

<script type="module">

    import {LitElement, html, css} from 'https://unpkg.com/lit-element/lit-element.js?module';
    
    class MyButton extends LitElement {
        static properties = {
            clicked: {},
            label: {},
        };

        static styles = css`
            /* Style the buttons */
            button {
              border: none;
              outline: none;
              padding: 10px 16px;
              background-color: #f1f1f1;
              cursor: pointer;
              font-size: 18px;
            }

            /* Style the active class, and buttons on mouse-over */
            .active, button:hover {
              background-color: #666;
              color: white;
            }            
        `;

        constructor() {
            super();
            this.label = 0;                 
        }

        render() {
            console.log("C Render " + this.clicked);
            return html`
                <button 
                    class=${ this.clicked ? 'active' : '' } 
                    @click=${ this.setClicked } 
                >
                ${this.label}
                </button>
            `;
        }
    
        setClicked(e) {     
            const event = new Event('ask-parent-reset', {bubbles: true, composed: true});
            this.dispatchEvent(event);
            this.clicked = ! this.clicked;
        }
    
    }
    
    customElements.define('my-button', MyButton);       

    const fBoolRrand = () => Math.random() < 0.5;
    
    class MyApp extends LitElement {

        static properties = {
            pclicked: { 
                hasChanged(newVal, oldVal) {
                    return true;
                }
            },
        };


        constructor() {
            super();
            this.pclicked = [ false, false, false, false ];                 
        }

        render() {
            console.log("P Render " + this.pclicked);
            return html`
                <div>
                    ${ 
                    this.pclicked.map( (_,i) => {
                        return html `                                
                            <my-button 
                                .label=${i+1} 
                                .clicked=${this.pclicked[i]}
                                @ask-parent-reset=${this.resetClicked}
                            >
                            </my-button>
                        `
                    })
                    }
                </div>
            `;
        }
    
        resetClicked(e) {
            this.pclicked = [ false, false, false, false ]; 
            //this.requestUpdate();
            //this.pclicked = [ true, true, true, true ];     
            //this.pclicked = [ fBoolRrand(), fBoolRrand(), fBoolRrand(), fBoolRrand() ]; 
            //this.pclicked = this.pclicked.map( x => x ? false : false );
            //console.log(e.target.clicked);
            //const recvElem = e.srcElement;
            //recvElem.clicked = !recvElem.clicked;                
        }
    
    }
    
    customElements.define('my-app', MyApp);                 

</script>    

output: output image



Solution 1:[1]

Is there any way to make this below code work by changing child component's public property inside its own class?

I'm not 100% sure I understood your question correctly but I think it is possible. I think your attempt came very close to working so I suggest a small change which will produce a working solution.

  1. You can include a 'detail' property in events so I changed the 'ask-parent-reset' event to include data on which button has been pressed:

Before:

const event = new Event('ask-parent-reset', {bubbles: true, composed: true});

After:

const event = new CustomEvent('ask-parent-reset', {bubbles: true, composed: true, detail: {button: this.label}});
  1. Use the new event to set the pclicked parent property so that it reflects the requested change.

Before:

resetClicked(e) {
    this.pclicked = [ false, false, false, false ];               
}

After:

resetClicked(e) {
    const buttonIndex = e.detail.button - 1
    const oldState = this.pclicked[buttonIndex]
    this.pclicked = [ false, false, false, false ]; 
    this.pclicked[buttonIndex] = !oldState
}

Hopefully that helped!

Stackblitz link to my suggested code: https://stackblitz.com/edit/web-platform-6pfvzh?file=index.html

Full HTML source:

<!doctype html>
<head>

    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    
    <style>
        body { 
            margin: 0; 
            height: 100vh; 
            text-align: center; 
            margin-top: 50px; 
            font-family: Arial;
        } 
    </style>  
    
</head>

<body> 
    <my-app></my-app>
</body> 

<script type="module">

    import {LitElement, html, css} from 'https://unpkg.com/lit-element/lit-element.js?module';
    
    class MyButton extends LitElement {
        static properties = {
            clicked: {},
            label: {},
        };

        static styles = css`
            /* Style the buttons */
            button {
              border: none;
              outline: none;
              padding: 10px 16px;
              background-color: #f1f1f1;
              cursor: pointer;
              font-size: 18px;
            }

            /* Style the active class, and buttons on mouse-over */
            .active, button:hover {
              background-color: #666;
              color: white;
            }            
        `;

        constructor() {
            super();
            this.label = 0;                 
        }

        render() {
            console.log("C Render " + this.clicked);
            return html`
                <button 
                    class=${ this.clicked ? 'active' : '' } 
                    @click=${ this.setClicked } 
                >
                ${this.label}
                </button>
            `;
        }
    
        setClicked(e) {     
            const event = new CustomEvent('ask-parent-reset', {bubbles: true, composed: true, detail: {button: this.label}});
            this.dispatchEvent(event);
            this.clicked = ! this.clicked;
        }
    
    }
    
    customElements.define('my-button', MyButton);       

    const fBoolRrand = () => Math.random() < 0.5;
    
    class MyApp extends LitElement {

        static properties = {
            pclicked: { 
                hasChanged(newVal, oldVal) {
                    return true;
                }
            },
        };


        constructor() {
            super();
            this.pclicked = [ false, false, false, false ];                 
        }

        render() {
            console.log("P Render " + this.pclicked);
            return html`
                <div>
                    ${ 
                    this.pclicked.map( (_,i) => {
                        return html `                                
                            <my-button 
                                .label=${i+1} 
                                .clicked=${this.pclicked[i]}
                                @ask-parent-reset=${this.resetClicked}
                            >
                            </my-button>
                        `
                    })
                    }
                </div>
            `;
        }
    
        resetClicked(e) {
            this.pclicked = [ false, false, false, false ]; 
            this.pclicked[e.detail.button - 1] = true
            //this.requestUpdate();
            //this.pclicked = [ true, true, true, true ];     
            //this.pclicked = [ fBoolRrand(), fBoolRrand(), fBoolRrand(), fBoolRrand() ]; 
            //this.pclicked = this.pclicked.map( x => x ? false : false );
            //console.log(e.target.clicked);
            //const recvElem = e.srcElement;
            //recvElem.clicked = !recvElem.clicked;                
        }
    
    }
    
    customElements.define('my-app', MyApp);                 

</script>

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 adamjhawley