🇬🇧 LitElement - First steps - Part II

updated on 2019-01-13 with LitElement 2.0.0-rc.2

Yesterday I explained how to set up a LitElement project with a first main component. Now, it's time to add more components (or elements).

A title component

We are going to create a title component <my-title></my-title>. So in the /src directory add a new JavaScript file my-title.js with this source code:

import { LitElement, html } from 'lit-element'

export class MyTitle extends LitElement {
  constructor() {
    super()
  }
  render(){
    return html`
      <h1>
        Kitchen Sink 🍔 [LitElement]
      </h1> 
    `
  }
}
customElements.define('my-title', MyTitle)

We need to load this new component, so in the index.html file, update this part of source code with adding import('./src/my-title.js'):

<script type="module">
  window.WebComponents = window.WebComponents || { 
    waitFor(cb){ addEventListener('WebComponentsReady', cb) }
  } 
  WebComponents.waitFor(async () => { 
    import('./src/main-application.js')
    import('./src/my-title.js')
  });
</script>

And one last thing, we have to "insert" this title element inside the main element, so edit and update main-application.js like that:

import { LitElement, html } from 'lit-element'

export class MainApplication extends LitElement {

  constructor() {
    super()
  }

  render() {
    return html`
      <section>
        <div>
          <my-title></my-title>
        </div>
      <section>
    `
  }
}

customElements.define('main-application', MainApplication);

Now, you can run this command: polymer serve at the root of the project, and reach this url http://127.0.0.1:8081, you'll get a great ğŸŽ‰ title:

html

Play with properties

Now we'll add a "sub-title", but I would like to set the content of the element with an html attribute, like that:

<my-sub-title my-text="I ❤️ LitElement"></my-sub-title>

For that, we're going to use a static property getter, something like that:

static get properties() {
  return {
    counter: { type: Number }
  }
}

You can read more in the official documentation here: https://lit-element.polymer-project.org/guide/properties

In the /src directory, add a new JavaScript file my-sub-title.js with this source code:

import { LitElement, html } from 'lit-element'

export class MySubTitle extends LitElement {

  static get properties() {
    return {
      myText: { attribute: 'my-text' }
    }
  }

  constructor() {
    super()
  }

  render(){
    return html`
      <h2>
        ${this.myText}
      </h2>    
    `
  }

}
customElements.define('my-sub-title', MySubTitle)

So, this: myText: { attribute: 'my-text' } allows us to make the "link" between this.myText in the JavaScript class andt the html attribute my-text from <my-sub-title my-text="I ❤️ LitElement"> on the html side.

In the index.html file, update the source code like that:

<script type="module">
  window.WebComponents = window.WebComponents || { 
    waitFor(cb){ addEventListener('WebComponentsReady', cb) }
  } 
  WebComponents.waitFor(async () => { 
    import('./src/main-application.js')
    import('./src/my-title.js')
    import('./src/my-sub-title.js')
  });
</script>

And finally, add the new element inside the main element, so edit and update main-application.js like that:

import { LitElement, html } from 'lit-element'

export class MainApplication extends LitElement {

  constructor() {
    super()
  }

  render() {
    return html`
      <section>
        <div>
          <my-title></my-title>
          <my-sub-title my-text="I ❤️ LitElement"></my-sub-title>
        </div>
      <section>
    `
  }

}

customElements.define('main-application', MainApplication);

Go to http://127.0.0.1:8081, and you can see that our web application is becoming better and better 😉:

html

On Click!

There is no proper tutorial without a button and a click handler. Then, create a new JavaScript file src/my-amazing-button.js with this content:

import { LitElement, html } from 'lit-element'

export class MyAmazingButton extends LitElement {

  static get properties() {
    return {
      counter: { type: Number }
    }
  }

  constructor() {
    super()
    this.counter = 0
  }

  render(){
    return html`
      <div>
        <button @click="${this.clickHandler}">👋 Click me! ${this.counter}</button>
      </div> 
    `
  }

  clickHandler() {
    this.counter+=1
  }
}
customElements.define('my-amazing-button', MyAmazingButton)

So, it's simple, you just have to add this to a tag: <button @click="${this.clickHandler}"> and create the clickHandler() method (you can name it as you want of course). You can see that I added a counter property, so guess what? ... counter will be incremented at each click.

Remark: 👋 when you declare a property, you need to initialize it in the class constructor. There are some TypeScript helpers (decorators) to do that, but righ now, I prefer to stay with a good old JavaScript.

You have to reference it in the index.html file:

<script type="module">
  window.WebComponents = window.WebComponents || { 
    waitFor(cb){ addEventListener('WebComponentsReady', cb) }
  } 
  WebComponents.waitFor(async () => { 
    import('./src/main-application.js')
    import('./src/my-title.js')
    import('./src/my-sub-title.js')
    import('./src/my-amazing-button.js')
  });
</script>

and add it to the main component render() method:

render() {
  return html`
    <section>
      <div>
        <my-title></my-title>
        <my-sub-title my-text="I ❤️ LitElement"></my-sub-title>
        <my-amazing-button></my-amazing-button>
      </div>
    <section>
  `
}

Go to http://127.0.0.1:8081, and click on your awesome button:

html

Display a list

Now, a little bit more difficult (I'm joking), I want to display a list of item in my web application.

Our new component (in src/buddies-list.js) looks like that:

import { LitElement, html } from 'lit-element'

export class BuddiesList extends LitElement {

  static get properties() {
    return {
      buddies: { type: Array }
    }
  }

  constructor() {
    super()
    this.buddies = [
      "🦊 Foxy",
      "🦁 Leo",
      "🐯 Tigrou"
    ]
  }

  render(){
    return html`
      <div>
        ${this.buddies.map(buddy => 
          html`<h2>${buddy}</h2>`
        )}
      </div>
    `
  }
}
customElements.define('buddies-list', BuddiesList)

😃 The simplicity of the LitElement template is clever ✨, you just need to use the native JavaScript features, like the map of array in our case.

Of course, you need to register in the index.html file and add it to our main component render method (<main-application>):

<script type="module">
  window.WebComponents = window.WebComponents || { 
    waitFor(cb){ addEventListener('WebComponentsReady', cb) }
  } 
  WebComponents.waitFor(async () => { 
    import('./src/main-application.js')
    import('./src/my-title.js')
    import('./src/my-sub-title.js')
    import('./src/my-amazing-button.js')
    import('./src/buddies-list.js')
  });
</script>
render() {
  return html`
    ${style}
    <section>
      <div>
        <my-title></my-title>
        <my-sub-title my-text="I ❤️ LitElement"></my-sub-title>
        <my-amazing-button></my-amazing-button>
        <hr>
        <buddies-list></buddies-list>
      </div>
    <section>
  `
}

And finally, you can admire the result on http://127.0.0.1:8081

html

My web application is ugly 😢

Never mind, there are several ways to style LitElements. I will choose the easiest way (to my mind). We are going to define our css styles in a JavaScript expression inside a LitElement template (that means that you can use JavaScript expressions inside your styles 😍). We'll write that in a new file: /main-styles.js.

⚠️ Pay attention: I'm not confident with my css skills, nor I don't speak css fluently, so be indulgent.

import { html } from 'lit-element'

export const style = html`
<style>
    .container
    {
      min-height: 100vh;
      display: flex;
      justify-content: center;
      align-items: center;
      text-align: center;
    }
    .title
    {
      font-family: "Source Sans Pro", "Helvetica Neue", Arial, sans-serif;
      display: block;
      font-weight: 300;
      font-size: 100px;
      color: #35495e;
      letter-spacing: 1px;
    }
    .subtitle
    {
      font-family: "Source Sans Pro", "Helvetica Neue", Arial, sans-serif;
      font-weight: 300;
      font-size: 42px;
      color: #526488;
      word-spacing: 5px;
      padding-bottom: 15px;
    }

    .button {
      font-family: "Source Sans Pro", "Helvetica Neue", Arial, sans-serif;
      background-color: white;
      color: #35495e;
      border: 2px solid #35495e;
      border-radius: 4px;
      padding: 10px 32px;
      text-align: center;
      text-decoration: none;
      display: inline-block;
      font-weight: 600;
      font-size: 30px;
    }
 
    .button:hover {
      background-color: #35495e; 
      color: white;
    }

</style>
`

And now, you need to update all your components, importing import {style} from './main-styles.js' and adding css class information to the tags in the render() method and an expression ${style} to include the css styles, like that:

main-application.js

  • add import {style} from './main-styles.js'
  • add ${style} to the template string
  • add class="container" to the section tag
import { LitElement, html } from 'lit-element'

import {style} from './main-styles.js'

export class MainApplication extends LitElement {

  constructor() {
    super()
  }

  render() {
    return html`
      ${style}
      <section class="container">
        <div>
          <my-title></my-title>
          <my-sub-title my-text="I ❤️ LitElement"></my-sub-title>
          <my-amazing-button></my-amazing-button>
          <hr>
          <buddies-list></buddies-list>
        </div>
      <section>
    `
  }
}

customElements.define('main-application', MainApplication);

my-title.js

  • add import {style} from './main-styles.js'
  • add ${style} to the template string
  • add class="title" to the h1 tag
import { LitElement, html } from 'lit-element';

import {style} from './main-styles.js';

export class MyTitle extends LitElement {
  constructor() {
    super()
  }
  render(){
    return html`
      ${style}
      <h1 class="title">
        Kitchen Sink 🍔 [LitElement]
      </h1> 
    `
  }
}
customElements.define('my-title', MyTitle)

my-sub-title.js

  • add import {style} from './main-styles.js'
  • add ${style} to the template string
  • add class="subtitle" to the h2 tag
import { LitElement, html } from 'lit-element';

import {style} from './main-styles.js';

export class MySubTitle extends LitElement {

  static get properties() {
    return {
      myText: { attribute: 'my-text' }
    }
  }

  constructor() {
    super()
  }

  render(){
    return html`
      ${style}
      <h2 class="subtitle">
        ${this.myText}
      </h2>    
    `
  }

}
customElements.define('my-sub-title', MySubTitle)

my-amazing-button.js

  • add import {style} from './main-styles.js'
  • add ${style} to the template string
  • add class="button" to the button tag
import { LitElement, html } from 'lit-element';

import {style} from './main-styles.js';

export class MyAmazingButton extends LitElement {

  static get properties() {
    return {
      counter: { type: Number }
    }
  }

  constructor() {
    super()
    this.counter = 0
  }
  render(){
    return html`
      ${style}
      <div>
        <button @click="${this.clickHandler}" class="button">👋 Click me! ${this.counter}</button>
      </div> 
    `
  }

  clickHandler() {
    this.counter+=1
  }
}
customElements.define('my-amazing-button', MyAmazingButton)

buddies-list.js

  • add import {style} from './main-styles.js'
  • add ${style} to the template string
  • add class="subtitle" to the h2 tag
import { LitElement, html } from 'lit-element'

import {style} from './main-styles.js';

export class BuddiesList extends LitElement {

  static get properties() {
    return {
      buddies: { type: Array }
    }
  }

  constructor() {
    super()
    this.buddies = [
      "🦊 Foxy",
      "🦁 Leo",
      "🐯 Tigrou"
    ]
  }

  render(){
    return html`
      ${style}
      <div>
        ${this.buddies.map(buddy => 
          html`<h2 class="subtitle">${buddy}</h2>`
        )}
      </div>
    `
  }
}
customElements.define('buddies-list', BuddiesList)

And we obtain an incredible fancy web application on http://127.0.0.1:8081

html

Building 🙀

It's time to build for production (I will write something later (days or weeks) about the real production deployment of a LitElement project). So type these commands

polymer build
polymer serve build/default

And go to http://127.0.0.1:8081, and ...

html

... Nothing. It's normal, I forgot to update the polymer.json file with the new files of my project. This is the update we need:

{
  "shell": "src/main-application.js",
  "entrypoint": "index.html",
  "fragments": [
    "src/my-title.js", 
    "src/my-sub-title.js",
    "src/my-amazing-button.js",
    "src/buddies-list.js"
  ],
  "npm": true,
  "moduleResolution": "node",
  "sources": [
    "src/main-application.js", 
    "src/my-title.js", 
    "src/my-sub-title.js",
    "src/my-amazing-button.js",
    "src/buddies-list.js",
    "src/main-styles.js",
    "package.json"
  ],
  "extraDependencies": [
    "node_modules/@webcomponents/webcomponentsjs/**"
  ],
  "builds": [{
    "bundle": true,
    "js": { 
      "minify": false,
      "compile": "es5",
      "transformModulesToAmd": true
    },
    "addServiceWorker": true,
    "addPushManifest": true
  }]
}

Build ans serve again:

polymer build
polymer serve build/default

And now it's better 😍

html

That's all for today. If you need all the files of the project, it's here https://gitlab.com/bots-garden/training-materials/bootstrapping-a-litelement-project/tree/01-more-components.

See you tomorrow 👋 (or later, sometimes I need to sleep). We'll talk about how we can made the components communicate.

Have a nice day 😃

Last Articles

Last Updated: 1/13/2019, 6:46:18 AM