javascriptinnerhtmllit-elementlit-html

What is the equivalent of document.getElementById('aa').innerHTML = this.value in lit-element?


I am implementing a double slider for price range.There is a need to show the value when sliding by the user to select specific range.For that,I have used innerHTML which worked fine. But there is an error saying

"Uncaught TypeError: Cannot read property 'nodeType' of null
at NodePart._setText (lit-html.ts:657)
at NodePart.setValue (lit-html.ts:616)
at TemplateInstance.update (lit-html.ts:810)
at render (shady-render.ts:159)
at HTMLElement._applyRender (lit-element.ts:286)
at HTMLElement._propertiesChanged (lit-element.ts:218)
at HTMLElement._flushProperties (properties-changed.js:387)
at HTMLElement._flushProperties (lit-element.ts:232)
at properties-changed.js:334
at MutationObserver.microtaskFlush (async.js:41)".

I have searched a lot and the answer I got was, one is If the function called before loading the DOM and the other is innerHTML is not used in lit-html. Please help me to get rid of that error.

    import {LitElement, html} from '@polymer/lit-element';
import {connect} from 'pwa-helpers/connect-mixin.js';
import {store} from '../../redux/store.js';
import {style} from './genie-flex-css.js';
import {repeat} from 'lit-html/lib/repeat';
export class GenieSlider extends connect(store)(LitElement) {
    static get is() {
        return 'genie-slider';
    }
    static get properties() {
        // TODO: declare the properties here.
        return {
            minimum: Number,
            maximum: Number
        };
    }
    constructor() {
        super();
        // TODO: Initialize the properties here
        this.minimum = 0;
        this.maximum = 10000;
    }
    requestRender() {
        this._invalidateProperties();
      }
      _invalidateProperties() {
        this.__isInvalid = true;
        super._invalidateProperties();
      }
    minPrice(e) {
        this.dispatchEvent(new CustomEvent('GG:minPriceChanged', {
            bubbles: false,
            detail: {
                event: e,
                value: e.currentTarget.value
            }
        }));
        e.stopPropagation();
        this.selectedOption = e.currentTarget.value;
        this.minimum = this.selectedOption; 
        this.requestRender();
    }
    maxPrice(e) {
        this.dispatchEvent(new CustomEvent('GG:maxPriceChanged', {
            bubbles: false,
            detail: {
                event: e,
                value: e.currentTarget.value
            }
        }));
        e.stopPropagation();
        this.selectedOption = e.currentTarget.value;
        this.maximum = this.selectedOption;
        this.requestRender(); 
    }

    _firstRendered() {

    }

    _render(properties) {

        return html `
    <style>
    ${style}
        [slider] {
            width: 270px;
            position: relative;
        }

        [slider] > div {
            position: absolute;
            left: 13px;
            right: 15px;
            height: 5px;
        }
        [slider] > div > [inverse-left],[slider] > div > [inverse-right] {
            position: absolute;
            height: 3px;
            border-radius: 10px;
            background-color: #CCC;
            margin: 0 7px;
        }

        [slider] > div > [inverse-left] {
            left: 0;
        }

        [slider] > div > [inverse-right] {
            right: 0;
        }


        [slider] > div > [range] {
            position: absolute;
            left: 0;
            height: 3px;
            border-radius: 14px;
            background-color: #d02128;
        }

        [slider] > div > [thumb] {
            position: absolute;
            top: -8px;
            z-index: 2;
            height: 20px;
            width: 20px;
            text-align: left;
            margin-left: -11px;
            cursor: pointer;
            box-shadow: 0 3px 8px rgba(0, 0, 0, 0.4);
            background-color: #FFF;
            border-radius: 50%;
            outline: none;
        }

        [slider] > input[type=range] {
            position: absolute;
            pointer-events: none;
            -webkit-appearance: none;
            z-index: 3;
            height: 14px;
            top: -2px;
            width: 100%;
            opacity: 0;
        }

        div[slider] > input[type=range]:focus::-webkit-slider-runnable-track {
            background: transparent;
            border: transparent;
        }

        div[slider] > input[type=range]:focus {
            outline: none;
        }

        div[slider] > input[type=range]::-ms-thumb {
            pointer-events: all;
            width: 28px;
            height: 28px;
            border-radius: 0px;
            border: 0 none;
            background: red;
        }

        div[slider] > input[type=range]::-moz-range-thumb {
            pointer-events: all;
            width: 28px;
            height: 28px;
            border-radius: 0px;
            border: 0 none;
            background: red;
        }

        div[slider] > input[type=range]::-webkit-slider-thumb {
            pointer-events: all;
            width: 28px;
            height: 28px;
            border-radius: 0px;
            border: 0 none;
            background: red;
            -webkit-appearance: none;
        }

        div[slider] > input[type=range]::-ms-fill-lower {
            background: transparent;
            border: 0 none;
        }

        div[slider] > input[type=range]::-ms-fill-upper {
            background: transparent;
            border: 0 none;
        }

        div[slider] > input[type=range]::-ms-tooltip {
            display: none;
        }

        [slider] > div > [sign] {
            opacity: 0;
            position: absolute;
            margin-left: -11px;
            top: -39px;
            z-index:3;
            background-color: #d02128;
            color: #fff;
            width: 28px;
            height: 28px;
            border-radius: 28px;
            -webkit-border-radius: 28px;
            align-items: center;
            -webkit-justify-content: center;
            justify-content: center;
            text-align: center;
        }

        [slider] > div > [sign]:after {
            position: absolute;
            content: '';
            left: 0;
            border-radius: 16px;
            top: 19px;
            border-left: 14px solid transparent;
            border-right: 14px solid transparent;
            border-top-width: 16px;
            border-top-style: solid;
            border-top-color: #d02128;
        }

        [slider] > div > [sign] > span {
            font-size: 10px;
            font-weight: 700;
            line-height: 28px;
        }

        [slider]:hover > div > [sign] {
            opacity: 1;
        }
    }
    </style>    
        <div slider id="slider-distance">
            <div>
                <div inverse-left style="width:70%;"></div>
                <div inverse-right style="width:70%;"></div>
                <div range style="left:${this.minimum? (this.minimum/100)+'%': 0+'%' };right:${this.maximum? 100 - (this.maximum/100)+'%': 0+'%' }"></div>
                <span thumb style="left:${this.minimum? (this.minimum/100)+'%': 0+'%' }"></span>
                <span thumb style="left:${this.maximum? (this.maximum/100)+'%': 100+'%' }"></span>
                <div sign style="left:${this.minimum? (this.minimum/100)+'%': 0+'%' }">
                    <span id="minResult">${this.minimum}</span>
                </div>
                <div sign style="left:${this.maximum? (this.maximum/100)+'%': 100+'%' }">
                    <span id="maxResult">${this.maximum}</span>
                </div>
            </div>
            <input type="range" value="0" max="${this.maximum}" min="0" step="100" oninput=" 
                this.value=Math.min(this.value,this.parentNode.childNodes[5].value-500);
                let value = (this.value/parseInt(this.max))*100
                var children = this.parentNode.childNodes[1].childNodes;
                children[1].style.width=value+'%';
                children[5].style.left=value+'%';
                children[7].style.left=value+'%';children[11].style.left=value+'%';
                children[11].childNodes[1].innerHTML=this.value;" onchange="${e => {this.minPrice(e)}}"/>

            <input type="range" value="10000" max="10000" min="${this.minimum}" step="100" oninput="  
                this.value=Math.max(this.value,this.parentNode.childNodes[3].value-(-500));
                let value = (this.value/parseInt(this.max))*100
                var children = this.parentNode.childNodes[1].childNodes;
                children[3].style.width=(100-value)+'%';
                children[5].style.right=(100-value)+'%';
                children[9].style.left=value+'%';children[13].style.left=value+'%';
                children[13].childNodes[1].innerHTML=this.value;" onchange="${e => {this.maxPrice(e)}}"/>
        </div>
    `;
    }
    _stateChanged(state) {
        // TODO Set the state dependent properties here
    }
}
customElements.define(GenieSlider.is, GenieSlider);

Solution

  • You should be using LitElement's template binding syntax for both handling the input event (@input=${...}) and for rendering the selected value in your template. In short:

    render() {
      return html`
        <input type="range"
          value=${this.selectedValue}
          @input=${this._handleRangeChange}>
        Selected value: ${this.selectedValue}
      `;
    }
    
    _handleRangeChange(event) {
      this.selectedValue = event.target.valueAsNumber;
    }
    

    You can see this working in the full snippet below:

    const { LitElement, html } = litElement;
    
    class MyTestElement extends LitElement {
      static get properties() {
        return {
          selectedValue: { type: Number },
        };
      }
    
      constructor() {
        super();
        this.selectedValue = 0;
      }
    
      render() {
        return html`
          <input type="range"
            min=0
            max=10000
            step=100
            value=${this.selectedValue}
            @input=${this._handleRangeChange}>
          Selected value: ${this.selectedValue}
        `;
      }
    
      _handleRangeChange(event) {
        this.selectedValue = event.target.valueAsNumber;
      }
    }
    
    customElements.define('my-test', MyTestElement);
    <script src="https://bundle.run/lit-element@2.2.1"></script>
    
    <my-test></my-test>