Why create a web component? They’re reusable across the web on different platforms, and testable using open source frameworks such as Jest. Here is the original JavaScipt component and the revised component.
Revisit the component’s design
Migrating a component is not a line-by-line conversion, and it’s a good opportunity to revisit your component’s design.
We make the following changes in the Lightning web component:
- Since the component uses plenty of date manipulation, use momentjs library to easily format dates.
- Allow the user to select a date by clicking on a date cell. The selected date should be highlighted in the UI.
- Add a Today button: on user click, go to Today and update the month and year. Highlight today’s date cell in the UI.
- The original component used HTML injection (line 136) but that is highly discouraged for security reasons. Instead, let’s create date nodes and append them to the DOM.
- The original component uses a table element to append date nodes to. This is costly because the table element needs many DOM nodes, including table header, table rows, and table cells. Additionally, table elements are difficult to display in a responsive way. Instead, let’s use an unordered list, with list elements for date nodes. Additionally, since table headers are static, we extract them to the markup as list elements in a separate unordered list.
With the requirements defined, let’s start the migration by creating a Lightning Web Components app.
First, create a new Lightning Web Components app
Start by creating an LWC app. Then create a
datepicker folder at the same level as the
app folder. Inside the
datepicker folder, create the files datePicker.css, datePicker.html, and datePicker.js. As a best practice let’s add a
__tests__ folder too, to add tests later. The resulting
src folder structure looks like this.
Lastly, we change the app.html to use the new date picker component. Notice how the child component becomes my-date-picker due to the folder structure and the camel case folder name.
Save app.html, start the server via npm run watch, and watch the browser reload automatically! Live reloading makes development fast and fun.
The original markup uses the
id attribute. This is discouraged in LWC, since ID values may be transformed into globally unique values on template render. Instead, use the element’s
class attribute. As a side note, you can still use IDs for ARIA attributes. For our use case, let’s convert all IDs to classes.
Instead of the
div element, we use an unordered list to append the date nodes to. Hence we replace
<div id="divCal"></div> with
"manual"></ul>. Notice we marked our new container element with the attribute
"manual", to later append DOM elements to it.
The final Lightning web component HTML file has a container with four child nodes:
<div>for the Today button, selected date, and year
<div>to navigate to Previous and Next month
<ul>for the seven days of the week
<ul lwc:dom="manual"></ul>to attach the date nodes to.
Lightning web components use standard CSS syntax. All we have to do is replace the CSS rules that use ID selectors and use class selectors. Here is the finished CSS file based on the new markup.
- Declare the properties.
- Port over window.onload code.
- Move event handler methods.
- Define getters to format the properties.
- Refactor rendering code.
Before we write any code, what does the default datepicker.js look like? The first line is an import statement. The core module in Lightning Web Components is
import statement imports
LightningElement from the
LightningElement is a custom wrapper of the standard HTML element.
Next, notice datepicker.js uses the ES6 class syntax.
In the original component, these properties are defined with today’s date:
In the LWC counterpart, today becomes a constant since its value does not change throughout the component lifecycle. We use momentjs as a helper for date-handling so
new Date() becomes
Here are all of the properties of the new datepicker LWC component, commented with their use case.
Notice dateContext, selectedDate, and error are decorated with @track. The @track decorator in Lightning Web Component makes the property reactive: when the value of the property changes, the component rerenders.
Port over window.onLoad code
window.onload. This is where we call a method (ie.
c.showcurr) to create and attach date nodes.
In the Lightning web component we also need to initialize the datepicker after the DOM is loaded. Let’s use
renderedCallback, one of LWC’s powerful lifecycle hooks, because the container node should be queryable then. Since
renderedCallback is called after every component render, we use the property
isDatePickerInitialized to gate the initial render of the component.
Move event handler methods
this.methodName. Hence the original component’s event handler method
c.previousMonth on line 42 easily maps into LWC’s
this.previousMonth. Let’s move over this.nextMonth too.
Upon clicking the Today button, the datepicker should update to show the current month, with today selected. We implement a this.goToday method to to fulfill the requirement.
Notice the event handler method
this.setSelected is decorated with the @api decorator. The
@api exposes the method as public. This enables a parent component to call the
setSelected method to set a date.
Define getters to format properties
To format the date properties in the UI, use getter methods to return formatted year, month, and
selectedDate. A getter method looks like this. In this case the getter returns a formatted date string.
To include the string in the DOM, reference the getter name, for example on line 9:
Refactor rendering code
In the original component, we call
c.showcurr() to change the calendar to a certain year and month. The DOM is updated via setting
<div id="divCal"></div>‘s innerHTML to a
<table> element. The table element is built row by row in the
The LWC version is simpler. Properties
this.currMonth are replaced with the
dateContext property. Instead of
this.showMonth, we use
- Migration is not just copy and paste, it is an opportunity to improve the performance of our component. The LWC version renders fewer DOM nodes dynamically, since we moved nodes out from table header and changed the container element from table to unordered list.
- Most of the conversion effort happens on the JS. Where can you use reactive properties instead of manual DOM manipulations? Which methods can you expose as public, for a parent component to call? Which methods make more sense as a component lifecycle method?
- During the conversion, the new component might not work as expected. Here’s how to debug Lightning web components. The short version: you can set breakpoints in the browser, using Chrome Developer Tools → Sources
It is fun to create web components with the Lightning Web Components programming model! With live reload, straightforward debugging, and automated tests, the sky is the limit on what you can create.
- Lightning Web Components OSS: Create a Starter App
- Momentjs cheatsheet
- The original datepicker
- Lightning web component datepicker
About the author
Anny He works as a Developer Evangelist at Salesforce. She focuses on UI components and integrations with the Salesforce Platform. You can follow her on Twitter @annyhehe.