How can I set focus for the initial render? I cannot get access to the element this way. I use the same appcoach as in documentation but it doesnt work.
Maybe i have some mistake in my code
import {LitElement, html} from '#polymer/lit-element'
import {classMap} from 'lit-html/directives/class-map.js';
import '#vaadin/vaadin-text-field'
class ModalConfirm extends LitElement {
constructor() {
super()
this.opened = false;
this.modalInputValue = '';
this.textAreaId = 'myText';
}
static get properties() {
return {
opened: {type: Boolean},
modalInputValue: {type: String},
textAreaId: { type: String },
}
}
firstUpdated() {
const textArea = this.shadowRoot.getElementById(this.textAreaId);
textArea.focus();
}
render() {
return html`
<div class="dialog ${this.opened ? 'opened' : 'closed'}" >
<p class="content">Enter new script name</p>
<input
id="${this.textAreaId}"
value="${this.modalInputValue}"
#change="${this.onChange}">
</input>
<div class="buttons">
<button class="accept" #click="${this.submitNewScript}">Ok</button>
<button class="cancel" #click="${this.cancelChanges}">Cancel</button>
</div>
</div>`
}
}
customElements.define('modal-confirm', ModalConfirm)
consider also using delegatesFocus when creating the shadowRoot
https://developer.mozilla.org/docs/Web/API/Element/attachShadow
with LitElement override of createRenderRoot to set shadowRoot options as needed:
class ModalConfirm extends LitElement {
createRenderRoot(){
return this.attachShadow({
mode: 'open',
delegatesFocus: true
});
}
}
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>
I have a web component and I want to adjust the value of its slot.
Unfortunately, I am unable to get the resolved value from it.
How can I do that?
const template = document.createElement('template');
template.innerHTML = `
<p><slot></slot></p>
`;
class MyComp extends HTMLElement {
constructor() {
super();
this.root = this.attachShadow({mode: 'open'});
this.root.appendChild(template.content.cloneNode(true));
}
connectedCallback() {
const slot = this.shadowRoot.querySelector('slot');
console.log('VALUE:', slot.innerText); // always empty
}
}
customElements.define('my-comp', MyComp);
<my-comp>abc</my-comp>
You can assign an EventListener to watch for SLOT changes
MDN Documentation
slotchange event
assignedNodes
::slotted
How do I style the last slotted element in a web component
customElements.define('my-element', class extends HTMLElement {
constructor() {
super();
this.attachShadow({
mode: 'open'
}).appendChild(document.getElementById(this.nodeName).content.cloneNode(true));
}
connectedCallback() {
this.listeners = [...this.shadowRoot.querySelectorAll("SLOT")].map(slot => {
let name = "slotchange";
let func = (evt) => {
let nodes = slot.assignedNodes();
console.log(`Slot ${slot.name} changed to ${nodes[0].outerHTML}`)
}
slot.addEventListener(name, func);
return () => slot.removeEventListener(name, func); // return cleanup function!!!
})
}
disconnectedCallback() {
this.listeners.forEach(removeFunc => removeFunc());
}
});
<template id="MY-ELEMENT">
<style>
::slotted(*) {
background: yellow;
margin: 0;
}
::slotted(span) {
color: red;
}
</style>
<b>
<slot name=title></slot>
</b>
<slot name=content></slot>
</template>
<my-element>
<span slot="title">Hello World</span>
<p slot="content">What a wonderful day!</p>
</my-element>
<my-element>
<span slot="title">Hello Tomorrow</span>
<p slot="content">What will you bring?</p>
</my-element>
Less code, easier to understand. For simple templates I'd rather create the elements programmatically, then add a slotchange listener:
class MyComp extends HTMLElement {
p = document.createElement('p');
slot = document.createElement('slot');
constructor() {
super();
this.p.append(this.slot);
this.attachShadow({mode: 'open'}).append(this.p);
this.slot.addEventListener('slotchange', () => console.log('VALUE:', this.slottedValue))
}
get slottedValue() { return this.slot.assignedNodes()[0].wholeText }
}
customElements.define('my-comp', MyComp);
console.log(document.querySelector('my-comp').slottedValue);
<my-comp>abc</my-comp>
I want to make a drop-down menu and that when clicking on the input, the menu is displayed with a toggle that removes or places the 'hidden' class
I have this method
toggleMenu() {
this.classList.toggle("hidden");
}
And here the template.
render(){
return html`
<input #click="${this.toggleMenu}" type="button">
<ul class="hidden">
<slot></slot>
</ul>
`;
}
One straightforward solution is to add a property to your custom element, e.g. open, that is toggled in your toggleMenu method:
static get properties() {
return {
open: { type: Boolean },
};
}
constructor() {
super();
this.open = false;
}
toggleMenu() {
this.open = !this.open;
}
Then in your render method set the <ul>'s class attribute based on the value of this.open:
render(){
return html`
<button #click=${this.toggleMenu} type="button">Toggle</button>
<ul class=${this.open ? '' : 'hidden'}>
<slot></slot>
</ul>
`;
}
You can see this working in the below snippet:
// import { LitElement, css, html } from 'lit-element';
const { LitElement, css, html } = litElement;
class DropDownMenu extends LitElement {
static get properties() {
return {
open: { type: Boolean },
};
}
static get styles() {
return css`
ul.hidden {
display: none;
}
`;
}
constructor() {
super();
this.open = false;
}
toggleMenu() {
this.open = !this.open;
}
render(){
return html`
<button #click=${this.toggleMenu} type="button">Toggle</button>
<ul class=${this.open ? '' : 'hidden'}>
<slot></slot>
</ul>
`;
}
}
customElements.define('drop-down-menu', DropDownMenu);
<script src="https://bundle.run/lit-element#2.2.1"></script>
<drop-down-menu>
<li>Item 1</li>
<li>Item 2</li>
</drop-down-menu>
If you want to apply additional classes to the <ul> you'll want to look into the classMap function as described in the LitElement docs.
Alternatively, you can add reflect: true to the open property declaration, which lets you show or hide the <ul> using CSS alone, rather than setting its class in render:
static get properties() {
return {
open: {
type: Boolean,
reflect: true,
},
};
}
static get styles() {
return css`
ul {
display: none;
}
:host([open]) ul {
display: block;
}
`;
}
Here it is in a working snippet:
// import { LitElement, css, html } from 'lit-element';
const { LitElement, css, html } = litElement;
class DropDownMenu extends LitElement {
static get properties() {
return {
open: {
type: Boolean,
reflect: true,
},
};
}
static get styles() {
return css`
ul {
display: none;
}
:host([open]) ul {
display: block;
}
`;
}
constructor() {
super();
this.open = false;
}
toggleMenu() {
this.open = !this.open;
}
render(){
return html`
<button #click=${this.toggleMenu} type="button">Toggle</button>
<ul>
<slot></slot>
</ul>
`;
}
}
customElements.define('drop-down-menu', DropDownMenu);
<script src="https://bundle.run/lit-element#2.2.1"></script>
<drop-down-menu>
<li>Item 1</li>
<li>Item 2</li>
</drop-down-menu>
Both of these are common approaches and the best one for your application will depend on your use case and personal preferences.
I like to keep it simple, if you need a reference to the DOM node then pass the event to the function like the following:
toggleMenu(ev) {
ev.target.classList.toggle("hidden");
}
And for the render method
render(){
return html`
<input #click="${(ev)=>{this.toggleMenu(ev)}}" type="button">
<ul class="hidden">
<slot></slot>
</ul>
`;
}
And you're done
I'm trying to work with localstorage in angular 2. I'm using angular cli.
app.component.ts
export class AppComponent implements OnInit {
currentItem: string;
newTodo: string;
todos: any;
constructor(){
this.currentItem = (localStorage.getItem('currentItem')!==null) ? JSON.parse(localStorage.getItem('currentItem')) : [ ];
localStorage.setItem('currentItem', JSON.stringify(this.currentItem));
this.newTodo = '';
this.todos = [];
}
addTodo() {
this.todos.push({
newTodo: this.newTodo,
done: false
});
this.newTodo = '';
localStorage.setItem('currentItem', JSON.stringify(this.todos));
}
ngOnInit(): void {}
}
app.component.html
<div>
<form (submit)="addTodo()">
<label>Name:</label>
<input [(ngModel)]="newTodo" class="textfield" name="newTodo">
<button type="submit">Add Todo</button>
</form>
</div>
<ul class="heroes">
<li *ngFor="let todo of todos; let i=index ">
<input type="checkbox" class="checkbox" [(ngModel)]="todo.done" />
<span [ngClass]="{'checked': todo.done}">{{ todo.newTodo }}</span>
<span (click)="deleteTodo(i)" class="delete-icon">x</span>
</li>
</ul>
<div>
<button (click)="deleteSelectedTodos()">Delete Selected</button>
</div>
It's a simple ToDo list, but it doesn't persist the data when I reload page.
In chrome inspect > Application > Local Storage I see the data. when I reload page, the data still appears, but it doesn't appears on view and when I add a new todo item, the Local Storage delete old items and update with a new todo.
Does anyone know how to fix it?
use your code like this
constructor(){
this.currentItem = (localStorage.getItem('currentItem')!==null) ? JSON.parse(localStorage.getItem('currentItem')) : [ ];
this.todos = this.currentItem;
}
addTodo() {
let local_items = localStorage.getItem('currentItem')
local_items.push({
newTodo: this.newTodo,
done: false
});
localStorage.setItem('currentItem', JSON.stringify(local_items));
this.newTodo = '';
}
Reason:
at the time of adding you set array in localStorage which has only latest object not old objects.
on refreshing page you are not assigning localStorage objects to todo variable
I modified a little the code provided for Pardeep Jain, and woked!
export class AppComponent implements OnInit {
currentItem: string;
newTodo: string;
todos: any;
constructor(){
this.currentItem = (localStorage.getItem('currentItem')!==null) ? JSON.parse(localStorage.getItem('currentItem')) : [ ];
this.todos = this.currentItem;
}
addTodo() {
this.todos.push({
newTodo: this.newTodo,
done: false
});
this.newTodo = '';
localStorage.setItem('currentItem', JSON.stringify(this.todos));
}
ngOnInit(): void {}
}