Bootstrap 4 Lit-Element
A hands-on guide to setting up Bootstrap for a Lit-Element app in native plain vanilla JavaScript (read: no jQuery) and web components. Oh, and no pun intended!
A picture is worth a thousand words, so without further ado, check out what we’ll make today with just a couple of lines of code using web components:
One of the stumbling blocks I encountered with Polymer fairly early on was with CSS. The issue for me was that web components render in Shadow DOM meaning the CSS inside of a web components is “scoped”.
Per Google:
Hands down the most useful feature of shadow DOM is scoped CSS:
- CSS selectors from the outer page don’t apply inside your component.
- Styles defined inside don’t bleed out. They’re scoped to the host element.
This is a really good thing when building components designed to run universally, under a wide set of environments. But if the web component you are writing is meant to only be run inside your app and nowhere else, the “scope-ness” can work against you by preventing your website’s stylesheet from being applied.
So, if you wanted to build your application using a bootstrap template, good luck getting your components to “bootstrap”. Google had work-arounds for this in the previous versions of Polymer. But in Lit-Element, I stumbled on hands down the most natural way of “de-scoping” CSS: createRenderRoot()
Lit-Element’s createRenderRoot() method allows you to render your web component in the LightDOM. It takes your web component’s DOM out of the shadow and into the light.
With the shadow DOM / scoped CSS issue out of the way, we can leverage a bootstrap template along with Lit-Element.
Lit-Element + Bootstrap Native + Bootstrap Template = Winning Combo
At least for people like me who are design challenged and tend to avoid dealing with CSS!
Bootstrap Native is basically:
The jQuery plugins for Bootstrap 4 redeveloped with native JavaScript, providing same basic functionality, but lighter in size and delivering higher performance for your application.
Now we’re all set to git it done…
First, let’s get a free bootstrap template to work with: Light Bootstrap Dashboard by Creative Tim. After exploding the file, I’ll copy ./examples/dashboard.html to ./index.html. Running the basic polymer development web server:
$ polymer serve -p 8083
Step one is to get rid of jQuery. At the bottom of index.html, *delete all* of the “<script>…” tags underneath the Core JS Files comment.
<!-- Core JS Files -->
<script src="../assets/js/core/jquery.3.2.1.min.js" type="text/javascript"></script>
...
<script src="../assets/js/demo.js"></script>
<script type="text/javascript">
$(document).ready(function() {
// Javascript method's body can be found in assets/js/demos.js
demo.initDashboardPageCharts();
demo.showNotification();
});</script>
We’ll replace the scripts with native javascript libraries. The boostrap-native-v4 library should be placed in the <head> section. We’ll also put back the chartist library (it actually has no jQuery dependency) back just after the body tag:
<head>
...
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/bootstrap.native@2.0.15/dist/bootstrap-native-v4.min.js"></script>
</head>...
</body>
<script src="../assets/js/plugins/chartist.min.js"></script>
Then we’ll re-create the initDashboardPageCharts() function found in assets/js/demo.js by rewriting it as plain vanilla JavaScript. The only change here is the declaration of the function!
, initDashboardPageCharts: function() { ...
to
function initDashboardPageCharts() { ...
Here is the result…
<script type="text/javascript">
function initDashboardPageCharts() {
var dataPreferences = {
series: [
[25, 30, 20, 25]
]};
var optionsPreferences = {
donut: true,
donutWidth: 40,
startAngle: 0,
total: 100,
showLabel: false,
axisX: {
showGrid: false
}};
Chartist.Pie('#chartPreferences', dataPreferences, optionsPreferences);
Chartist.Pie('#chartPreferences', {
labels: ['53%', '36%', '11%'],
series: [53, 36, 11]
});
var dataSales = {
labels: ['9:00AM', '12:00AM', '3:00PM', '6:00PM', '9:00PM', '12:00PM', '3:00AM', '6:00AM'],
series: [
[287, 385, 490, 492, 554, 586, 698, 695, 752, 788, 846, 944],
[67, 152, 143, 240, 287, 335, 435, 437, 539, 542, 544, 647],
[23, 113, 67, 108, 190, 239, 307, 308, 439, 410, 410, 509]
]
};
var optionsSales = {
lineSmooth: false,
low: 0,
high: 800,
showArea: true,
height: "245px",
axisX: {
showGrid: false,
},
lineSmooth: Chartist.Interpolation.simple({
divisor: 3
}),
showLine: false,
showPoint: false,
fullWidth: false
};
var responsiveSales = [
['screen and (max-width: 640px)', {
axisX: {
labelInterpolationFnc: function(value) {
return value[0];
}}
}]
];
var chartHours = Chartist.Line('#chartHours', dataSales, optionsSales, responsiveSales);var data = {
labels: ['Jan', 'Feb', 'Mar', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
series: [
[542, 443, 320, 780, 553, 453, 326, 434, 568, 610, 756, 895],
[412, 243, 280, 580, 453, 353, 300, 364, 368, 410, 636, 695]
]
};
var options = {
seriesBarDistance: 10,
axisX: {
showGrid: false
},
height: "245px"
};
var responsiveOptions = [
['screen and (max-width: 640px)', {
seriesBarDistance: 5,
axisX: {
labelInterpolationFnc: function(value) {
return value[0];
}
}
}]
];var chartActivity = Chartist.Bar('#chartActivity', data, options, responsiveOptions);
}initDashboardPageCharts();</script>
At this point, you will find that the dashboard page still functions and charts are still displaying.
Now install Lit-Element using npm:
$ npm install lit-element
As an example, let’s create a dynamic one-way data-binding table of some of my favorite movies as a web component and put it in the “Users Behavior” box on the dashboard.
First create a new web component named: movies-table.js.
import { LitElement,html } from 'lit-element';class MoviesTable extends LitElement {
render() {
return html`
<div class="container-fluid">
<div class="row">
<div class="col-md-12"><div class="card strpied-tabled-with-hover">
<div class="card-header ">
<h4 class="card-title">Movies Table</h4>
<p class="card-category">Striped Table with Hover</p>
</div>
<div class="card-body table-full-width table-responsive">
<table class="table table-hover table-striped">
<thead>
<th>Title</th>
<th>Director</th>
<th>Released</th>
</thead>
<tbody>
${this.movies.map(
(movie) => {
var disp=html`
<tr>
<td>${movie.title}</td>
<td>${movie.director}</td>
<td>${movie.released}</td>
</tr>
`
return disp;
})
}</tbody></table>
</div>
</div>
</div>
</div> <!-- row -->
</div> <!-- container fluid -->
`
} //render()constructor() {
super();
this.movies=[];
}firstUpdated() {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = () => {
if (xhr.readyState === 4){
this.movies = JSON.parse(xhr.responseText);
this.requestUpdate();
}
};
xhr.open('GET', 'http://cloudsqlnet.lightbase.io:3100/db_test_test/movies?select=title,%20director,%20released,%20gross&limit=6');
xhr.send();
} //firstUpdated()} customElements.define('movies-table', MoviesTable);
What we’ve done here is create a lit-element web-component that dynamically gets a JSON payload using standard native JavaScript. The html is borrowed from the file: “/examples/table.html” specifically the “Striped Table with Hover” sample html. The static html data rows were removed and replaced with Lit-Element’s dynamic one-way data binding.
Now we’ll place the web-component inside index.html by invoking the standard ES Module specification.
<script type="module" src="movies-table.js"></script>
We’ll remove the contents of card-body for the “Users Behavior” card and replace it with our movies-table web-component.
...
<div class="card-header ">
<h4 class="card-title">Users Behavior</h4>
<p class="card-category">24 Hours performance</p>
</div>
<div class="card-body ">
<movies-table></movies-table>
</div>
Now our dashboard looks like this:
But wait, what happened to the styling for the table? The answer is there is none. We didn’t write any CSS inside our movies-table web-component and the shadow dom prevents any CSS selectors from the outer page from applying inside the component.
Let’s solve this problem by adding the createRenderRoot() method to our movies-table component directly after the firstUpdated() method.
...
} //firstUpdated()createRenderRoot() {
return this;
}} customElements.define('movies-table', MoviesTable);
Reloading our page:
Ta-da! We have a bootstrap styled “striped table with hover table” and the best part is that we didn’t have to write a lick of CSS.
If you are writing a web-component designed to be run universally, rendering to the Light DOM would be a terrible idea. But if the component is meant to run only a single application, then this technique will save you a lot of time and frustration.
If you are wondering what the AJAX call was, it’s to a Postgrest server aimed at a PostgreSQL backend.
PostgREST is a standalone web server that turns your PostgreSQL database directly into a RESTful API
PostgREST allows you to send SQL statements via JavaScript and get a JSON payload back with the query results.
http://cloudsqlnet.lightbase.io:3100/db_test_test/movies?select=title,%20director,%20released,%20gross&limit=6
We’ve found it really useful here at Lightbase. And guess what? plug alert… You can get a free 500MB database combo including PostgreSQL + PostgREST and CouchDB from Lightbase at www.lightbase.io/freeforlife.
A code sample with the modified index.html and movies-table.js is available on our GitHub repo: lightbaseio/bootstrap4lit.edu.
That’s all folks. I hope you found this article useful in your never-ending quest to Git It Done. If you liked it, please clap the article and follow me so that I know to write more.