Getting Started with PolymerJS and LitElement
Tags: polymer, litelement, javascript, frontend
Published: 2021-09-02 03:05:28

## Introduction to Web Components.
We all have built web pages using HTML, CSS, and JavaScript. Generally, we write the HTML tags and render them on the page. Sometimes, we have to repeat tags to render the same type of UI elements. It makes the page messy. And also, adding styles to the elements make an impact on multiple tags and elements. We have to override the style for each different element. Developers always try to work more in less time. 

**We try to follow “Don’t Repeat Yourself (DRY)” but just using HTML, CSS, and JavaScript, is not possible. Web components make it possible.**

Web components are a set of web platform APIs that allow us to make new custom HTML tags or elements with encapsulated functionality that can be reused multiple times and utilized on our web pages. It helps us share data between components and saves our time and energy.

```javascript
<user-avatar
   class="mr-2x"
   name="${name}"
   shape="${this.shape}"
   .imageURL="${imageURL}"
   .withBorder="${this.withBorder}"
 >
</user-avatar>
```

This is the simple example of custom component. Properties such as name, shape, imageURL, withBorder are pass into the component in the form of component attributes.

If this looks confusing, don’t worry, you will be able to build one web application where we can Add, Edit, Delete and List the posts, by the end of this article.

## Things you need to know before diving into the tutorial.

- [**Custom Elements**](https://polymer-library.polymer-project.org/2.0/docs/devguide/custom-elements)
Custom Elements help developers build their customizable element or HTML tags with encapsulated functionality which can be helpful for them in their web applications. Let’s say we have to create a component that displays user details with images. You can create a <user-profile> element where you can structure it as you want.

- [**Shadow DOM**](https://glazkov.com/2011/01/14/what-the-heck-is-shadow-dom/)
Shadow DOM is a way to encapsulate the styling and markup of your components. It prevents overriding of styles. It is the concept of scoped style. It does not replace the styling of parent or child components. It behaves separately which allows us to write the styling of the same class or id in a separate component.

- [**ES Modules**] (https://hacks.mozilla.org/2018/03/es-modules-a-cartoon-deep-dive/)
ES Modules defines the inclusion and reuse of JS documents in a standard-based, modular, performant way. Web Components follow the ES Modules pattern.

- [**HTML Templates**](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/template)
HTML Templates are ways to insert HTML structures that only get rendered when the main template is rendered. Whatever we write inside <template></template> tag will get rendered.

## What is Polymer?
It is an [open-source](https://github.com/polymer/polymer) JavaScript library based upon Web Components. It is developed by Google. Polymer helps us to create custom elements for building web applications. It's much easier and faster to create custom elements that work like DOM elements.

## What is [LitElement](https://lit-element.polymer-project.org/guide)?
It is a simple base class that helps us to create a web component. It uses lit-html to create the web components using Shadow DOM and manage the properties and attributes. The element is updated whenever the properties of the element are changed.
 
This is the basic structure of LitElement to create a new component.

```javascript
import { LitElement, html, css } from 'lit-element';
 
// Creating MyElement component extending the LitElement Class.
class MyElement extends LitElement {
 // Add Styles for the component
  static get styles() {
	return [
 	css `
    	:host {
       	  display:block;
    	}
    	`];
  }
 
// Add Properties which will be used into the components.
 static get properties() {
	return {
  	myString: { type: String },
	};
  }
 
// Initialize all the properties and bind the function into the constructor.
  constructor() {
	// Always call super first in constructor
	super();
	
	this.myString = 'Hello World';
  }
 
// Add the html structure for the component you want to build.
  render() {
	return html`
  	<p>${this.myString}</p>
	`;
  } 
}
 
// register custom element on the CustomElementRegistry using the define() method
customElements.define('my-element', MyElement);
```

Now, Let’s Dive into CRUD operations using Polymer and LitElement. We are going to develop an application to Add, Edit, Delete, and listing the Post.

The GitHub Repo for this tutorial is available [here](https://github.com/mukeshkarna/litelement-crud). I recommend checking it out, because its got this whole tutorial.

Okay, so lets get started!

Download the starter file from [here](https://github.com/PolymerLabs/lit-element-starter-js)

##### Setup

Clone the repo and open it with the Text Editor. Delete the __*docs*__, __*docs-src*__, and __*test*__. Go to the __*dev*__ folder and move the __*index.html*__ into the root folder. Afterward, you can delete the __*dev*__ folder too.

Install dependencies:

```bash
npm i
```

After that install [*@vaadin/router*](https://vaadin.com/router). It is a client-side router library developed in JavaScript. It is mostly used in Web Components based web applications. It is a lightweight router library. It has different features such as child routes, async routes resolution, and many more.

```bash
npm install --save @vaadin/route
```

Create an __*src*__ folder. After that create __*components*__ folder inside it. Then create a file named __*post-app.js*__ inside it. Add the below given code into the __*post-app.js*__ file.

```javascript
import {LitElement, html} from 'lit';
 
class PostApp extends LitElement {
   firstUpdated() {
	const el = this.shadowRoot.querySelector('main'); 
  }
 
   render() {
	return html` <main></main> `;
  }
}
customElements.define('post-app', PostApp);
```

Here main is the DOM where every other component gets rendered.
Create a folder named __*router*__ inside the __*src*__ folder and also __*router.js*__ into the newly created folder.

```javascript
import { Router } from '@vaadin/router';
 
/**
* Initializes the router.
*
* @param {Object} outlet
*/
function initRouter(outlet) {
const router = new Router(outlet);
 
 router.setRoutes([
  {
   path: '/',
   component: 'landing-page',
   action: () => {
	import('../components/landing-page/landing-page');
   },
  },
 ]);
}
 
export default initRouter;
```

Now import the *initRouter* into the __*post-app.js*__

`import initRouter from '../router/router';`

Call the *initRouter* function inside the *firstUpdated*.

```javascript
firstUpdated() {
 const el = this.shadowRoot.querySelector('main');
 initRouter(el);
}
```
Open __*index.html*__ of the root folder.

Add the script tag inside the head tag.
```javascript
<script type="module" src="./src/components/post-app.js"></script>
```
Add the post-app component tag into the body tag.

```javascript
<body>
  <post-app></post-app>
</body>
```

We will be using the paper elements which are a collection of custom UI components. We can simply install it and import it in the file we want to use and add the tag of that element in the form of an HTML Tag. We are going to use a paper-card element to set the background container for the page. So, let’s install the paper-card package.

```bash
npm install @polymer/paper-card --save
```

Create the __*landing-page*__ folder Inside the components folder and also create __*landing-page.js*__ into the newly created folder.

```javascript
import { css, html, LitElement } from 'lit';
 
import '@polymer/paper-card/paper-card';
 
class LandingPage extends LitElement {
static get properties() {
 return {};
}
 
static get styles() {
return [
css`
 .main-wrapper,
  paper-card {
	height: 100vh;
	display: flex;
	flex-direction: column;
   }
`,
];
}
 
constructor() {
super();
}

render() {
 return html` <div class="main-wrapper">
  	<paper-card>
    	 <div class="menu-wrapper">
      	  <a href="/home">Home</a>
      	  <a href="/post">Posts</a>
    	 </div>
    	 <div>
      	  <slot></slot>
    	 </div>
  	</paper-card>
    </div>`;
 }
}
 
customElements.define('landing-page', LandingPage);
```

We have added the URL for the Home and Posts page which is rendered into all the pages because we have added /home and /post as children of a home directory inside the router. Now the remaining page DOM is rendered inside the slot. A slot is a place where we can pass anything we want to render into the component.

Let’s say we have a fruit component with the title fruit and we want to pass the image into the component as a children's DOM.

*fruit_component.js*
```javascript
<div>
  ${this.title}
  <slot></slot>
</div>
```
Now we can pass the image as children in this way
```javascript
<fruit_component>
  <img src=”/images/img.jpeg” />
</fruit_component>
```

Whatever we pass between the components displays into the slot.


Let’s open the terminal and run
```bash
npm run serve
```

Copy the local URL and paste it into the browser and open it.
It shows the menu list which we have added into the landing page component.

It will not work now. As we have not set up to display its content.

```javascript
  router.setRoutes([
    {
      path: '/',
      component: 'landing-page',
      action: () => {
        import('../components/landing-page/landing-page');
      },
    },
    {
      path: '/',
      component: 'landing-page',
      children: [
        {
          path: '/',
          redirect: '/post',
        },
        {
          path: '/post',
          component: 'post-list',
          action: async () => {
            await import('../components/posts/post-list.js');
          },
        },
        {
          path: '/home',
          component: 'home-page',
          action: () => {
            import('../components/home-page/home-page');
          },
        },
        {
          path: '(.*)+',
          component: 'page-not-found',
          action: () => {
            import('../components/page-not-found');
          },
        },
      ],
    },
  ]);
```

Now create a __*home-page*__ folder inside the __*components*__ folder and create the __*home-page.js*__ file inside it.

```javascript
import { LitElement, css, html } from 'lit';

class HomePage extends LitElement {
  static get styles() {
    return [css``];
  }

  render() {
    return html`
      <div>
        Home Page
      </div>
    `;
  }
}
customElements.define('home-page', HomePage);
```

Create a __*posts folder*__ inside the __*components*__ folder and create the __*post-list.js*__ file inside it.

```javascript
import { css, html, LitElement } from 'lit';

class PostList extends LitElement {
  static get properties() {
    return {};
  }

  static get styles() {
    return [css``];
  }

  constructor() {
    super();
  }

  render() {
    return html`
      <div>
          Post List
      </div>
    `;
  }
}
customElements.define('post-list', PostList);
```

Now refreshing the page, we can see the text ‘Home page’ while clicking on Home and ‘Post List’ while clicking on Posts.

## Retrieve Operation
Now let’s create a new component named ‘table-view’ to display the table. Let’s create a folder named __*common*__ inside the __*src*__ folder. And create a file named __*index.js*__ and __*table-view.js*__
Inside __*index.js*__ let’s  import the __*table-view.js*__

`import ‘./table-view.js’;`

Before opening the __*table-view.js*__, let’s install these packages which we will be using later in our new component.

```bash
npm install --save @polymer/paper-input
npm install --save @polymer/paper-dialog
npm install --save @polymer/paper-button
```

Open __*table-view.js*__ and add the following code.
```javascript
import { LitElement, html, css } from 'lit';

import '@polymer/paper-input/paper-input';
import '@polymer/paper-dialog/paper-dialog';
import '@polymer/paper-button/paper-button';

export class TableView extends LitElement {
    static get properties() {
        return {
            posts: { type: Array },
        };
    }

    static get styles() {
        return [
            css`
    	:host {
      	display: block;
    	}
 
    	table {
      	border: 1px solid black;
    	}
 
    	thead td {
      	font-weight: 600;
    	}
 
    	tbody tr td:last-child {
      	display: flex;
      	flex-direction: row;
      	margin: 0px 12px;
    	}
 
    	.mr {
      	margin-right: 12px;
    	}
 
    	.dflex {
      	display: flex;
      	flex-direction: column;
    	}
    	.input-container {
      	margin: 4px 4px;
    	}
 
    	paper-dialog {
      	width: 500px;
    	}
 
    	.edit-button {
      	background-color: green;
      	color: white;
    	}
 
    	.delete-button {
      	background-color: red;
      	color: white;
    	}
 
    	.add-button {
      	background-color: blue;
      	color: white;
    	}
 
    	.ml-auto {
      	margin-left: auto;
    	}
  	`,
        ];
    }

    constructor() {
        super();
    }

    renderAddButton() {
        return html`<div class="ml-auto">
  	<paper-button raised class="add-button">Add</paper-button>
	</div>`;
    }


    render() {
        return html`
  	<div class="dflex">
   	${this.renderAddButton()}
    	<div>
      	<table>
        	<thead>
          	<tr>
            	<td>S.No.</td>
            	<td>Title</td>
            	<td>Description</td>
            	<td>Action</td>
          	</tr>
        	</thead>
        	<tbody>
          	${this.posts.map((item, index) => {
            	return html`
              	<tr>
                	<td>${index + 1}</td>
                	<td>${item.title}</td>
                	<td>${item.description}</td>
                	<td>
                  	<div class="mr">
                    	<paper-button raised class="edit-button">
                      	Edit
                    	</paper-button>
                  	</div>
                  	<div>
              	      <paper-button raised class="delete-button">
                      	Delete
                    	</paper-button>
                  	</div>
                	</td>
              	</tr>
            	`;
          	})}
        	</tbody>
      	</table>
    	</div>
  	</div>
	`;
    }
}
customElements.define('table-view', TableView);
```
We have to add a table-view component into __*post-list.js*__ so that when we click on a post, we can see the table on that page. We have to pass the posts data into the table-view component. For that, we have to create a property to store the data of posts. Open __*post-list.js*__ and add new property into the property section.

```javascript
static get properties() {
    return {
        posts: { type: Array },
    };
}
```

After creating the property let’s initialize it into a constructor. Since we have not used any API, we can simply add dummy data into it.

```javascript
constructor() {
    super();

    this.posts = [
        {
            id: 1,
            title: 'Title 1',
            description: 'This is description of post',
        },
        {
            id: 2,
            title: 'Title 2',
            description: 'This is description of post',
        },
        {
            id: 3,
            title: 'Title 3',
            description: 'This is description of post',
        },
    ];
}
```

Inside the render function, let’s call the table-view component and pass the posts as a property of the table-view component.

```javascript
render() {
    return html`
  	<div>
    	<h2>Post Lists</h2>
    	<div>
      	<table-view .posts="${this.posts}"></table-view>
    	</div>
  	</div>
	`;
}
```

Now we can see our page as displayed below.

![Screen Shot 2021-08-15 at 19.22.14](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/eue86bmw9vnfvwzx74gd.png)
 
## Add Operation
 
Now let’s work on adding an item. We have already added an Add button into our component.
 
Now let’s update the *renderAddButton* function by adding the click action into it.
```javascript
renderAddButton() {
	return html`<div class="ml-auto" @click="${() => this.toggleDialog()}">
  	<paper-button raised class="add-button">Add</paper-button>
	</div>`;
  }
```

To make the button actionable let’s create a *toggleDialog* function below this function. Before creating the function let’s add *operation* and *selectedItem* properties into the properties section.

```javascript
static get properties() {
	return {
  	posts: { type: Array },
  	operation: { type: String },
  	selectedItem: { type: Object },
	};
  }
```

We will have these lists of properties after adding those properties. Also, we have to initialize the newly added properties into the constructor.

```javascript
this.operation = 'Add';

this.selectedItem = {};
 
this.toggleDialog = this.toggleDialog.bind(this);
```

Now we can use these properties in the *toggleDialog* function.

```javascript
toggleDialog(item) {
    if (item) {
        this.operation = 'Edit';
        this.selectedItem = item;
    } else {
        this.operation = 'Add';
    }
}
```

Toggle dialog will try to open the dialog, so let’s add a dialog component. We will use paper-dialog.

```javascript
openAddEditDialog() {
    return html`<paper-dialog>
  	<h2>${this.operation} Post</h2>
  	<div class="input-container">
    	<paper-input
      	label="Title"
      	@input="${(event) => this.setItemValue('title', event.target.value)}"
      	value="${this.selectedItem.title || ''}"
    	></paper-input>
    	<paper-input
      	label="Description"
      	value="${this.selectedItem.description || ''}"
      	@input="${(event) =>
        	this.setItemValue('description', event.target.value)}"
    	></paper-input>
  	</div>
  	<div class="buttons">
    	<paper-button dialog-confirm autofocus @click="${this.onAcceptBtnClick}"
      	>${this.operation === 'Add' ? 'Save' : 'Update'}</paper-button
    	>
    	<paper-button dialog-dismiss @click="${this.closeDialog}"
      	>Cancel</paper-button
    	>
  	</div>
	</paper-dialog>`;
}
```

Paper card components need to open when clicked on the Add button. To open the dialog box lets add 
```javascript
this.shadowRoot.querySelector('paper-dialog').open();
``` at the end of the `toggleDialog` Function and add `${this.openAddEditDialog()}` before the last div inside render function. This function will open the dialog box. And after opening the dialog we have to close the dialog box. For this let’s add the `closeDialog` function.

```javascript
  closeDialog() {
	this.shadowRoot.querySelector('paper-dialog').close();
	this.selectedItem = {};
  }
```

Here, if we have selected any item to edit, then we have to clear it because it will store the data of the currently selected post item.
 
Here we have a Title and Description field to add posts. We have made a common dialog box for Add and Edit of posts. This will help us not to create the same component repeatedly.
 

When opening the dialog, we have to set the button name as __Save__ while adding and __Update__ while editing the post. That’s why we have added the condition on the accept button and also the title to display when the dialog box is opened. __Add Post__ is shown when the __Add__ button is clicked and __Edit Post__ is shown when the __Edit__ button is clicked.

Now we have to get the value of Title and Description when typed into the input field. To do so, we have to add the new property named item in the properties section.

```
item: { type: Object },
```

Also initialize it into the `constructor`.

```
this.item = {};
```

Now create a function named `setItemValue` below the `openAddEditDialog` function.

```javascript
setItemValue(key, value) {
    this.item = {
        ...this.item,
        [key]: value,
    };
}
```

Paper input has the `@input` property which calls a function to add the item into the variable.

```javascript
@input="${(event) => this.setItemValue('title', event.target.value)}"
```

This will pass the key and the value to the `setItemValue` function and will create the object.
We have added the `@click` action in one of the paper buttons inside the paper-dialog component.

`@click="${this.onAcceptBtnClick}"`

Whenever clicked the `onAcceptBtnClick` function is called. So, we have to create that function and also bind it inside the `constructor`.

```javascript
onAcceptBtnClick() {
    if (this.operation === 'Add') {
        this.item = {
            id: this.posts.length + 1,
            ...this.item
        };
        this.posts = [...this.posts, this.item];
    }
}
```

`this.onAcceptBtnClick = this.onAcceptBtnClick.bind(this);`

When the operation value is ‘Add’, the new item will be added to posts.
 
Now the Add function is completed. We can add new data to the post.

## Edit Operation
 
It’s time to Edit the Post.
 
To edit the post, we have to add the `@click` action into the edit button. So, let’s update the edit button inside the table.

```javascript
<div class="mr" @click="${() => this.toggleDialog(item)}">
 <paper-button raised class="edit-button">
   Edit
 </paper-button>
</div>
```

we have to update the `setItemValue` function. We have set selected items that we have chosen to edit on the `selectedItem` property on the `toggleDialog` function. We can now update the `setItemValue` function. When the `operation` is set to Edit, it will update on `this.selectedItem` property when we update the value.

```javascript
setItemValue(key, value) {
    if (this.operation === 'Edit') {
        this.selectedItem = {
            ...this.selectedItem,
            [key]: value,
        };
    } else {
        this.item = {
            ...this.item,
            [key]: value,
        };
    }
}
```
Now we have to update the `onAcceptBtnClick` function where the updated post is replaced with the new one.

```javascript
onAcceptBtnClick() {
    if (this.operation === 'Add') {
        this.item = {
            id: this.posts.length + 1,
            ...this.item
        };
        this.posts = [...this.posts, this.item];
    } else {
        this.posts = this.posts.map((post) => {
            if (post.id === this.selectedItem.id) {
                return this.selectedItem;
            }
            return post;
        });
    }
}
```

This will end up in the Edit function of the post.


## Delete Operation
 
Now let’s move on to the Delete function of the post.
 
Firstly, we have to add `@click` action into the delete button.

```javascript
<div @click="${() => this.handleOnDelete(item)}">
 <paper-button raised class="delete-button">
   Delete
 </paper-button>
</div>
```
Now we have to create the `handleOnDelete` function and bind it into the constructor.

```javascript
 handleOnDelete(item) {
	this.posts = this.posts.filter((post) => {
  	return post.id !== item.id;
	});
  }
```

```
this.handleOnDelete = this.handleOnDelete.bind(this);
```

Here, the post item which we want to delete is passed into the function and we compare its ID with the post inside the Posts array. After that, the post is deleted from the posts array.


In this way, we can do a simple CRUD operation using PolymerJS and LitElement.