Can a lit-element track a variable outside its scope - javascript

In my lit-element I'm rendering something based on an outside variable. How can I know to update when that variable changes?
import { LitElement, html } from 'lit-element';
import './element01.js';
class Layout extends LitElement {
createRenderRoot(){ return this; }
static get properties() {
return {
settings: { Object }
};
}
constructor() {
super();
}
render() {
return html`
${(settings.foo === 'bar')? html`<my-element01 />` : null}
`;
}
}
customElements.define('my-layout', Layout);
The settings object is being modified outside, how can this element know to update? I'm not using any other framework.

The index.html file should always have a
<my-app></my-app>
Within the my-app element, you will be able to use all the features of litElement such as
class MyApp extends LitElement {
static get properties() {
return {
prop: {type: Object},
};
}
render() {
return html`
<my-element .prop="${this.prop}"</my-element>
<my-server .prop="${this.prop}"</my-server>
<button #onClick='${(e) => this.prop += 1}' >change the settings value</button>
`;
}
}

Here one example I tried to illustrate. settings property changed outside of litElement and effected in litElement.
demo
index.html
...
<my-layout></my-layout>
<br>
<button onClick="_buttonClicked()" >change the settings value</button>
<script>
document.querySelector('my-layout').settings={foo:"bar"};
function _buttonClicked (e) {
document.querySelector('my-layout').settings = {foo:"baz"};
}
</script>
my-layout :
import { LitElement, html } from 'lit-element';
//import './element01.js';
class Layout extends LitElement {
createRenderRoot(){ return this; }
static get properties() {
return {
settings: { Object }
};
}
constructor() {
super();
}
render() {
return html`
${this.settings.foo === 'bar'? html`<span> element01 will be rendered</span>` : null}
`;
}
}
customElements.define('my-layout', Layout)
I made some syntax corrections

Related

Twind Configuration not working with LitElement

I can't seem to get theme working with LitElement. I can set the other props of setup no problem, but the theme doesn't get recognized by Twind. It's also worth mentioning that I get no error when compiling. Anyone have a quick solution?
import {LitElement, html} from 'lit';
import {setup, warn, create, cssomSheet} from 'twind';
setup({
mode: warn,
theme: {
colors: {
purple: '#2013',
},
},
});
const sheet = cssomSheet({target: new CSSStyleSheet()});
const {tw} = create({sheet});
export class MyApp extends LitElement {
static styles = [sheet.target];
static get properties() {
return {
name: {type: String},
};
}
constructor() {
super();
this.name = 'World';
}
render() {
return html` <h1 class="${tw`text(3xl purple)`}">${this.name}!</h1> `;
}
}
window.customElements.define('my-app', MyApp);
Probably, the problem is the shadow-dom.
If you can use Twind, you can try to render to light-dom, instead shadow-dom.
To use light-dom add in your web-component class this method:
createRenderRoot() {
return this;
}
In other hand, I don't sure it works without Lit...

Lit reactive controllers fail to requestUpdate in nested components

I want to create a basic state management using Lit reactive controllers.
The purpose is to share property values accross the application.
The issue occurs when a controller is attached to a view and to a component nested in the view. When value inside controller changes, the value in the view gets updated, but not in the nested component.
Example:
state.js contains the store logic. A view access the store to create a value and show state value. Nested components also show state value.
state.js
export class StateController {
static get properties() {
return {
state: { type: Object },
host: { type: Object }
}
}
constructor(host) {
// Store a reference to the host
this.host = host;
this.state = {};
// Register for lifecycle updates
host.addController(this);
}
_setStoreValue(property, val) {
this.state[property] = val;
this.host.requestUpdate();
}
}
component.js
import { LitElement, html } from 'lit';
import { StateController } from '../state.js';
export class TestComponent extends LitElement {
static get properties() {
return {
stateCtrl: { type: Object },
state: { type: Object },
};
}
constructor() {
super();
this.stateCtrl = new StateController(this);
this.state = this.stateCtrl.state
}
render() {
return html` Value in component: ${this.state?.test} `;
}
}
customElements.define('test-component', TestComponent);
view.js
import { LitElement, html } from 'lit';
import { StateController } from '../state.js';
import './test-component.js';
export class MonTodo extends LitElement {
static get properties() {
return {
stateCtrl: { type: Object },
state: { type: Object },
};
}
constructor() {
super();
this.stateCtrl = new StateController(this);
this.state=this.stateCtrl.state
}
render() {
return html`
<button #click=${() => this.setValueTest()}>Set value to 3</button>
Value in view: ${this.state?.test}
<h3> Component 1</h3>
<test-component></test-component>
<h3> Component 2</h3>
<test-component></test-component>
`;
}
setValueTest() {
this.stateCtrl._setStoreValue("test", 3)
}
}
customElements.define('mon-todo', MonTodo);
A button click in view.js updates this.state.test in view.js but not in component.js
Since you create a new StateController in both MonTodo and TestComponent, they are two different instances of the StateController which only have their specific component as host.
So the StateController in MonTodo only has MonTodo as a host and only updates that and not TestComponent.
You would need to share one controller with both components and call requestUpdate on both.

How to send props from parent to child with LitElement

Can someone show me some simple example about sending props from parent class to child? What is the problem:
parent component:
import { LitElement, html, css } from 'lit-element';
import './child.js';
class Parent extends LitElement {
constructor() {
super()
this.message = "hello world"
}
render() {
return html `<child-component message=${this.message}></child-component>` //how to get this props in child component?
}
}
customElements.define('parent-component', Parent);
and child component:
import { LitElement, html, css } from 'lit-element';
class Child extends LitElement {
...
render() {
return html `<p>${message from parent, but how}</p>` //message props should go to constructor? to render method as argument? how?
}
}
}
customElements.define('child-component', Child);
ok, I found solution. If I want to define properties in Parent class I have to add dot.
Reference: https://lit-element.polymer-project.org/guide/properties
render() {
return html `<child-component .message=${this.message}></child-component>`
}
So now everything is working.
And full example:
parent component:
import { LitElement, html, css } from 'lit-element';
import './child.js';
class Parent extends LitElement {
constructor() {
super()
this.message = "hello world"
}
render() {
return html `<child-component .message=${this.message}></child-component>`
}
}
customElements.define('parent-component', Parent);
child component:
import { LitElement, html, css } from 'lit-element';
class Child extends LitElement {
...
render() {
return html `<p>${this.message}</p>` //this.message have "hello world" value now
}
}
customElements.define('child-component', Child);
Make it much standard.
In your child component., set properties to listen to message.
import { LitElement, html, css } from 'lit-element';
class Child extends LitElement {
static get properties(){
return{
message: {
type: String,
}
}
}
constructor(){
super();
console.log(message);
}
render() {
return html `<p>${this.message}</p>` //this.message have "hello world" value now
}
}
customElements.define('child-component', Child);
In children to load property from parent component:
#property({type: Number}) propertyChild: number;
constructor() {
super();
this.propertyChild = (this.parentElement as TypeParent).propertyParent;
}
In parent to update properties on children:
override updated(): void {
this.childNodes.forEach(node=>{
const nodeType = node as TypeChildren;
if(nodeType.property) {
nodeType.propertyChild = this.propertyParent;
}
})
}

Lit-elements, the idiomatic way to write a controlled component

I'm working with lit-elements via #open-wc and is currently trying to write a nested set of components where the inner component is an input field and some ancestor component has to support some arbitrary rewrite rules like 'numbers are not allowed input'.
What I'm trying to figure out is what the right way to built this is using lit-elements. In React I would use a 'controlled component' see here easily forcing all components to submit to the root component property.
The example below is what I've come up with using Lit-Elements. Is there a better way to do it?
Please note; that the challenge becomes slightly harder since I want to ignore some characters. Without the e.target.value = this.value; at level-5, the input elmement would diverge from the component state on ignored chars. I want the entire chain of components to be correctly in sync, hence the header tags to exemplify.
export class Level1 extends LitElement {
static get properties() {
return {
value: { type: String }
};
}
render() {
return html`
<div>
<h1>${this.value}</h1>
<level-2 value=${this.value} #input-changed=${this.onInput}></level-2>
</div>`;
}
onInput(e) {
this.value = e.detail.value.replace(/\d/g, '');
}
}
...
export class Level4 extends LitElement {
static get properties() {
return {
value: { type: String }
};
}
render() {
return html`
<div>
<h4>${this.value}</h4>
<level-5 value=${this.value}></level-5>
</div>`;
}
}
export class Level5 extends LitElement {
static get properties() {
return {
value: { type: String }
};
}
render() {
return html`
<div>
<h5>${this.value}</h5>
<input .value=${this.value} #input=${this.onInput}></input>
</div>`;
}
onInput(e) {
let event = new CustomEvent('input-changed', {
detail: { value: e.target.value },
bubbles: true,
composed: true
});
e.target.value = this.value;
this.dispatchEvent(event);
}
}
export class AppShell extends LitElement {
constructor() {
super();
this.value = 'initial value';
}
render() {
return html`
<level-1 value=${this.value}></level-1>
`;
}
}
Added later
An alternative approach was using the path array in the event to access the input element directly from the root component.
I think it's a worse solution because it results in a stronger coupling accross the components, i.e. by assuming the child component is an input element with a value property.
onInput(e) {
const target = e.path[0]; // origin input element
this.value = e.path[0].value.replace(/\d/g, '');
// controlling the child elements value to adhere to the colletive state
target.value = this.value;
}
Don't compose your events, handle them in the big parent with your logic there. Have the children send all needed info in the event, try not to rely on target in the parent's event handler.
To receive updates, have your components subscribe in a shared mixin, a la #mishu's suggestion, which uses some state container (here, I present some imaginary state solution)
import { subscribe } from 'some-state-solution';
export const FormMixin = superclass => class extends superclass {
static get properties() { return { value: { type: String }; } }
connectedCallback() {
super.connectedCallback();
subscribe(this);
}
}
Then any component-specific side effects you can handle in updated or the event handler (UI only - do logic in the parent or in the state container)
import { publish } from 'some-state-solution';
class Level1 extends LitElement {
// ...
onInput({ detail: { value } }) {
publish('value', value.replace(/\d/g, ''));
}
}

calling a class from a method

I am learning react and I am trying to call a class from a method. Here is what I have came up so far
class Pop extends React.Component{
render(){
return (
<div>
<button onClick={() => { this.desit("data")} }>Componant Caller</button>
</div>
)
}
desit(data){
<Test info={data}/>
}
}
class Test extends React.Component{
render(){
return
<div>{alert(this.props.info)}</div>
}
}
ReactDOM.render(<Pop />,document.getElementById("targ"));
Help please?
You want to have a method in a separate class and trigger it from the Component?
Don't use another component, but you can create a regular class with some logic:
class Test
{
test() {
alert('success!');
}
}
In the desit method, you can now invoke Test.test(), like this:
desit() {
var amazingTest = new Test();
amazingTest.test();
}
You could use a static method as well:
class Test
{
static test() {
alert('success!');
}
}
// and the desit method:
desit() {
Test.test();
}

Categories