Render Object using lit.html - polymer

Scenario :
Inside my webComponent - health-check.js file I have:
static get properties() {
return {
plan:Object,
};
}
<div class="body"><p class="title">${this.plan.title}</p></div>
and
Inside my index.html file I pass json like below:
<health-check plan='{ "title" : "Solution Architecure",
"status" : "Approved" }'></health-check>
but it doesn't render title inside my health-check component
Problem :
Render Object Value with using lit.html?
Render title inside my health-check component.
Any ideas?

I think you cannot pass the object from html file because it's just html not lit-element or lit-html and lit-html seems not try to parse value in this case (Update I just found another way to define property see below). But of course you can still pass string and parse it inside element.
In index.html
<health-check planString='{
"title": "Solution Architecure",
"status": "Approved"
}'>
</health-check>
In health-check.js
class HealthCheck extends LitElement {
static get properties () {
return {
planString: String
}
}
render () {
return html`
<div class='body'>
<p class='title'>${JSON.parse(this.planString).title}</p>
</div>
`
}
}
But I would recommend to wrap you code in single entrypoint like my-app element
In index.html
<my-app></my-app>
In app.js
class App extends LitElement {
render () {
return html`
<health-check .plan='${{
title: 'Solution Architecure',
status: 'Approved'
}}'>
</health-check>
`
}
}
In health-check.js
class HealthCheck extends LitElement {
static get properties () {
return {
plan: Object
}
}
constructor () {
super()
this.plan = {}
}
render () {
return html`
<div class='body'>
<p class='title'>${this.plan.title}</p>
</div>
`
}
}
Update
You can define property type to tell lit-element how to serialize and deserialize.
class HealthCheck extends LitElement {
static get properties () {
return {
plan: {
type: {
fromAttribute (value) {
return JSON.parse(value)
}
}
}
}
}
render () {
return html`
<div class='body'>
<p class='title'>${this.plan.title}</p>
</div>
`
}
}
Note: This code written in lit-element 0.6.x and lit-html 0.11.x

Related

Passing props through a "template" component

I'm building a template component using React with TypeScript but i'm facing an issue i'm unable to solve. I'm posting this in case anyone knows how to approach it.
My project has a MyComp component that invokes TemplateComp using a subcomponent GraphComp and the data that Graph requires.
TemplateComp invokes and stylises the Graph subcomponent plus adds some props that are needed (such as customPropertyA) next to graphData.
GraphComp is requiring certain parameters that graphData needs to render properly.
The issue i'm facing is related to the types definition from GraphComp to MyComp while passing through TemplateComp. It may seem that (because TemplateComp defines graphData as any, as it is unknown to it) MyComp understands that graphData can also be any, but in reality it should be equal to the properties that Graph is requiring as Props (but not all of them).
Is there any way to let MyComp and TemplateComp infer the types that GraphComp is asking for?
Here is my code:
import { Component, ElementType } from 'react'
export default class MyComp extends Component<{}, {}> {
render() {
return (
<div>
<TemplateComp
Graph={GraphComp}
graphData={{
value: 0
}}
/>
</div>
)
}
}
class TemplateComp extends Component<
{
Graph: ElementType
graphData: any
},
{}
> {
customPropertyA = 'hello'
render() {
const { graphData, Graph } = this.props
return (
<div>
<Graph {...graphData} customPropertyA={this.customPropertyA} />
</div>
)
}
}
class GraphComp extends Component<
{
value: number
customPropertyA: string
},
{}
> {
render() {
return <div>my value: {this.props.value}</div>
}
}
I'm perfectly fine with modifying how these components work. However, i still need the 3 layer approach and to be able to define GraphComp's props from within MyComp and TemplateComp separately.
For those wondering how to approach this, i managed to fix it by calling the mounted JSX instead of mounting it in TemplateComp and removed properties from it as they can also be described in MyComp.
import { Component, ElementType } from 'react'
export default class MyComp extends Component<{}, {}> {
render() {
return (
<div>
<TemplateComp
Graph={<GraphComp value={0} customPropertyA={"hello"} />}
/>
</div>
)
}
}
class TemplateComp extends Component<
{
Graph: JSX.Element
},
{}
> {
render() {
const { Graph } = this.props
return (
<div>
{Graph}
</div>
)
}
}
class GraphComp extends Component<
{
value: number
customPropertyA: string
},
{}
> {
render() {
return <div>my value: {this.props.value}</div>
}
}

Why does Webcomponent object value is shared between the different instances of the same webcomponent?

I created a web component called TestElement with Polymer as below. It simply has an object value and a function which adds values to this component.
<dom-module id="test-element">
<template>Test Element</template>
<script>
class TestElement extends Polymer.Element {
static get is() { return "test-element"; }
static get properties() {
return {
_myVar: {
type: Object,
value: {},
},
};
}
addNew(key, val) {
this._myVar[key] = val;
}
}
customElements.define(TestElement.is, TestElement);
</script>
</dom-module>
I created two instances of this in a parent element as follows:
class ParentElement extends Polymer.Element {
static get is() { return "parent-element"; }
ready() {
super.ready();
this.$.myElement.addNew('key1', 'val-1');
this.$.myElement2.addNew('key2', 'val2');
console.log(this.$.myElement._myVar); // Expected to have {'key1': 'val-1'} but got {'key1': 'val-1', 'key2': 'val2'}. Why?
}
}
In the console log above, I expect to have {'key1': 'val-1'} but got {'key1': 'val-1', 'key2': 'val2'}. Why?
If you want to have a working example please refer to this plunkr:
http://plnkr.co/edit/7jSwTTBbBxD4qCbUoCGI?p=preview
JavaScript objects are by reference and when you define the default value it's a single object.
static get properties() {
return {
_myVar: {
type: Object,
value: {},
},
};
}
To avoid this you can define a function that will return a new object for each component instance.
static get properties() {
return {
_myVar: {
type: Object,
value: function() { return {}; },
},
};
}
While thinking through it I found a solution.
Test Element
class TestElement extends Polymer.Element {
static get is() { return "test-element"; }
static get properties() {
return {
_myVar: {
type: Object,
value: {},
},
};
}
ready() {
super();
_myVar = {}; // if initialized like this, the objects are not shared anymore.
}
addNew(key, val) {
this._myVar[key] = val;
}
}
customElements.define(TestElement.is, TestElement);
So below, logs what I expected:
class ParentElement extends Polymer.Element {
static get is() { return "parent-element"; }
ready() {
super.ready();
this.$.myElement.addNew('key1', 'val-1');
this.$.myElement2.addNew('key2', 'val2');
// Expected to have {'key1': 'val-1'} and got {'key1': 'val-1'} check! :)
console.log(this.$.myElement._myVar);
}
}

how to bind the values using object in polymer 3.x?

I'm trying to bind the values using an object reference like this:
this.set('state', tab1Data);
but values are not binding with respective input fields.
We are using the polymer 3.x version
It depends very much on where you bind your data and how you bind it.
As you provide not so much information on the concrete context, I would guess, that you bind your data, outside the element ths calls this.set('state', ...) .
Then your property needs to have the notify-flag been set to true, otherwise changes won't be propagated outside.
The following code shows this behavior:
<html>
<head>
<script src="https://unpkg.com/#webcomponents/webcomponentsjs#latest/webcomponents-loader.js"></script>
</head>
<body>
<script type="module">
import {PolymerElement, html} from 'https://unpkg.com/#polymer/polymer/polymer-element.js?module';
import 'https://unpkg.com/#polymer/paper-button/paper-button.js?module';
class MyElement extends PolymerElement {
static get properties() {
return {
someData: {
type: Object,
value: () => {return {}},
notify: true
},
otherData: {
type: Object,
value: () => {return {}},
notify: false
},
counter: Number
}
}
constructor() {
super();
this.counter = 0;
}
static get template() {
return html`
<style> :host { display: block; } .mood { color: green; } </style>
Mood: <span class="mood">[[someData.mood]] ([[otherData.mood]])</span>
<paper-button raised on-click="setNewObject">set object</paper-button>
<paper-button raised on-click="setNewValue">set value</paper-button>
`;
}
setNewObject() {
this.counter++
const mood = `good [${this.counter}]`
this.set('someData', {mood: mood});
this.set('otherData', {mood: mood});
}
setNewValue() {
this.counter++
const mood = `better [${this.counter}]`
this.set('someData.mood', mood);
this.set('otherData.mood', mood);
}
}
customElements.define('my-element', MyElement);
class MySecondElement extends PolymerElement {
static get properties() {
return {
mood: String
}
}
static get template() {
return html`Web Components are <span>[[mood]]</span>!`;
}
}
customElements.define('my-second-element', MySecondElement);
class MyApp extends PolymerElement {
static get template() {
return html`
<my-element some-data="{{someData}}" other-data="{{otherData}}" ></my-element>
<h2>notify true</h2>
<my-second-element mood="[[someData.mood]]"></my-second-element>
<h2>notify false</h2>
<my-second-element mood="[[otherData.mood]]"></my-second-element>
`;
}
}
customElements.define('my-app', MyApp);
</script>
<my-app></my-app>
</body>
</html>
You should put the code on the value which set on the localstorage. I show it here.
import '#polymer/iron-localstorage/iron-localstorage.js';
....
<iron-localstorage name="user-info" value="{{userInfo}}" on-iron-localstorage-load-empty="initializeUserInfo"></iron-localstorage>
<vaadin-text-field id="test">
........
initializeUserInfo(){
this.set('userInfo', []);
}
ready(){
super.ready();
this.set('userInfo', this.$.test.value);
}
This is able to set data in the localstorage.

How to implement lit-html instead of juicy-html in a polymer 3 component?

I am converting an app from polymer 1 to polymer 3. I used juicy-html, but they have not updated to Polymer 3 and I see that there is lit-html. I am wondering how I can change this snippet to using lit-html. It is used for expanding a string like: 'Hello <span class="highlight">world</span>!'
Here is the snippet of code from my polymer 1 component.
<template is="dom-repeat" items="[[item.snippets]]">
<template is="dom-repeat" items="[[item.matches]]">
<div><template is="juicy-html" content$="[[item.text]]"></template></div>
</template>
</template>
Do I need to implement a new component for the inner div? Is there an example that I can look at?
Here is the resulting Polymer 3 element to display a highlighted text within a string:
import {html, LitElement} from '#polymer/lit-element/lit-element.js';
/**
* `search-snippet-highlight`
*
*
* #customElement
* #polymer
* #demo demo/index.html
*/
class SearchSnippetHighlight extends LitElement {
static get properties() {
return {
snippet: { type: String }
};
}
render() {
return html`
<style>.highlight { background-color: yellow; }</style>
<div .innerHTML="${this.sanitizeHtml(this.snippet)}"></div>`;
}
sanitizeHtml(input) {
return input; // TODO: actually sanitize input with sanitize-html library
}
}
window.customElements.define('search-snippet-highlight', SearchSnippetHighlight);
The equivalent of that <template> with juicy-html in Polymer's LitElement (the recommended base element that uses lit-html) is:
render() {
let content = '';
for (const s of this.item.snippets) {
for (const m of s.matches) {
content += `<div>${m.text}</div>`;
}
}
return html`<div .innerHTML="${this.sanitizeHtml(content)}"></div>`;
}
The render function above does the following:
builds an HTML string from inputs
sanitizes the HTML
puts the result into the container div's innerHTML, using LitElement syntax (.PROPERTY="VALUE")
<html>
<head>
<!-- Polyfills only needed for Firefox and Edge. -->
<script src="https://unpkg.com/#webcomponents/webcomponentsjs#latest/webcomponents-loader.js"></script>
</head>
<body>
<!-- Works only on browsers that support Javascript modules like
Chrome, Safari, Firefox 60, Edge 17 -->
<script type="module">
import {LitElement, html} from 'https://unpkg.com/#polymer/lit-element/lit-element.js?module';
class MyElement extends LitElement {
static get properties() {
return {
item: { type: Object }
}
}
constructor() {
super();
this.item = {
snippets: [
{
matches: [
{text: 'hello <span class="highlight">world</span>'},
{text: 'we are the <span class="highlight">world</span>'},
{text: 'war of the <span class="highlight">world</span>s'},
]
},
{
matches: [
{text: 'the <span class="highlight">cat</span> in the hat'},
{text: '<span class="highlight">cat</span>fish are in the water'},
{text: '<span class="highlight">cat</span>erpillars become butterflies'},
]
},
]
};
}
render() {
let content = '';
for (const s of this.item.snippets) {
for (const m of s.matches) {
content += `<div>${m.text}</div>`;
}
}
return html`
<style>.highlight { background: rgb(255, 251, 222); }</style>
<div .innerHTML="${this.sanitizeHtml(content)}"></div>`;
}
sanitizeHtml(input) {
return input; // TODO: actually sanitize input with sanitize-html library
}
}
customElements.define('my-element', MyElement);
</script>
<my-element mood="great"></my-element>
</body>
</html>
I put this in an answer as #tony19 was suggesting it can be formatted properly. I wasn't really asking a separate question, really just asking a rhetorical one rather than offering a separate solution.
You can simplify the approach quite a bit as lit allows you to build up your html in layers through the map function.
render() {
return html`
<div>${this.item.snippets.map(s => s.matches.map(m =>
html`<div>${this.sanitizeHtml(m.text)}</div>`
))}
</div>
`;
}
Now one issue with this (and the accepted answer) is that every render you recalculate the whole thing, including sanitizing the Html. lit-html has a guard directive that can help with this. Adding this will provide for it only re-rendering on a change.
render() {
return html`
<div>${guard(this.item.snippets, () => this.item.snippets.map(s =>
guard(s.matches, () => s.matches.map(m =>
html`<div>${this.sanitizeHtml(m.text)}</div>`
))))}
</div>
`;
}
This does require you impose some discipline on how both this.item.snippets and the underlying matches get updated. You have to ensure that the "Array" references being used change when there is an update. Something like this (assuming the matches are being updated with a new match) and sindex is the index of the snippet you want to update and mindex is the index of the match within that snippet you are updating with newMatch;
this.items.snippets = this.items.snippets.map((snippet, index) => index === sindex ?
{...snippet, matches: snippet.matches.map((match, index) => index === mindex ?
newMatch : match)} : snippet);

polymer 2.0 two way binding

I am experimenting with Polymer 2.0 and have a simple class. It has an input text box that does not respond when changing its content. What is needed to reflect changes to text box to reflect as two way binding?
The code for the class follows.
<!-- Styles MUST be inside template -->
<style>
</style>
<div>
<input type="text" id="greeting" name="greeting" value="{{greeting}}"/>
<slot></slot>
{{greeting}}
</div>
// Extend Polymer.Element base class
class Polymer2Class extends Polymer.Element {
static get is() { return 'polymer-2-class' }
static get config() {
return {
properties: {
greeting: {
type: String,
value: "Hello",
notify: true
//observer: 'greetingChanged'
}
},
observers: [
'greetingChanged(greeting)'
]
}
}
constructor() {
super();
console.log('created');
}
connectedCallback() {
super.connectedCallback();
console.log('attached');
}
ready() {
this.addEventListener('click', (e)=>this.handleClick(e));
this._ensureAttribute('tabIndex', 0);
super.ready();
console.log('ready');
}
greetingChanged(greeting) {
if(greeting === undefined) {
console.log("greetingChanged: undefined");
} else {
console.log("greetingChanged: " + greeting);
}
}
handleClick(e) {
console.log("hamdleClick: " + e.type);
}
// Register custom element definition using standard platform API
customElements.define(Polymer2Class.is, Polymer2Class);
I assume you're expecting greeting to appear below the <input> exactly as you type it. You'll need to use two-way native binding on the <input>'s input event:
<input type="text" value="{{greeting::input}}">
codepen