Integrate JavaScript libraries with ScalablyTyped
In this third tutorial, we learn how to integrate JavaScript libraries with ScalablyTyped.
We start here with the project developed in the previous tutorial about UI development with Laminar. To follow along this tutorial, either use the result of the previous tutorial, or checkout the laminar-end-state branch of the accompanying repo.
If you prefer to look at the end result for this tutorial directly, checkout the scalablytyped-end-state branch instead.
There are no playgrounds in Scribble for this tutorial because it does not support ScalablyTyped. You will need a local project to follow along.
Prerequisites
Make sure to install the prerequisites before continuing further.
Set up ScalablyTyped with Chart.js
We will use Chart.js, a JavaScript library, to draw a bar chart out of the shopping list data. In order to get static types and bindings for Chart.js, we use ScalablyTyped. ScalablyTyped can read TypeScript type definition files and produce corresponding Scala.js facade types.
We set up our new dependencies as follows.
First, we install some npm packages: Chart.js as a regular dependency (with -S
), and its TypeScript type definitions along with the TypeScript compiler—required by ScalablyTyped—as development dependencies (with -D
):
In project/plugins.sbt
, we add a dependency on ScalablyTyped:
Finally, in build.sbt
, we configure ScalablyTyped on our project:
For these changes to take effect, we have to perform the following steps:
- Restart sbt and the
~fastLinkJS
task (this will take a while the first time, as ScalablyTyped performs its magic) - Restart
npm run dev
if it was running - Possibly re-import the project in your IDE of choice
Chart configuration
We can now enjoy Chart.js with static types in our Scala.js code.
First, we define the Chart.js configuration that we will use:
At the top, we import the facade types for Chart.js generated by ScalablyTyped:
This gives us access to types like ChartConfiguration
and ChartType
.
Inside the ChartConfiguration
, we provide a number of Chart.js-related options to make our chart look the way we want:
- the type of chart as a bar chart:
type = ChartType.bar
, - two datasets with the labels
"Price"
and"Full price"
, respectively, and - the
y
axis’ start value as0
.
All of these configuration options are type-checked, using the static types provided by ScalablyTyped. If written in JavaScript, the above configuration would read as:
In a sense, that is all there is to know about ScalablyTyped. What follows is more about the integration of a third-party “component” into Laminar than anything else.
Rendering the chart
We now amend our appElement()
method to also call a new renderDataChart()
function:
The implementation of renderDataChart()
is rather large.
We show it in its entirety first, then we will pick it apart.
We create a Laminar canvasTag()
element.
We give it a width
and height
using Laminar’s :=
, as we did before.
For its actual content, we want Chart.js to take over.
For that, we have to create an instance of Chart
referencing the DOM HTMLCanvasElement
.
In order to bridge the world of Laminar Element
s and Chart.js, we use onMountUnmountCallback
.
That function takes one callback executed when the element is attached to a DOM tree, and one when it is removed.
When the element is mounted, we want to create the instance of Chart
.
When it is unmounted, we want to call the destroy()
method of Chart.js to release its resources.
The mount
callback receives a nodeCtx
, which, among other things, gives us a handle to the underlying HTMLCanvasElement
.
We name it domCanvas
, and use it together with the chartConfig
defined above to create an instance of Chart.js’ Chart
class:
We store the resulting chart
instance in a local var optChart: Option[Chart]
.
We will use it later to update the chart
’s imperative data model when our FRP dataSignal
changes.
In order to achieve that, we use a dataSignal -->
binder.
We give it as an argument to the Laminar canvasTag
element to tie the binder to the canvas lifetime, as you may recall from the Laminar tutorial.
Once the canvas gets mounted, every time the value of dataSignal
changes, the callback is executed.
In the callback, we get access to the chart: Chart
instance and update its data model.
This -->
binder allows to bridge the FRP world of dataSignal
with the imperative world of Chart.js.
Our application now properly renders the data model as a chart.
When we add or remove data items, the chart is automatically updated, thanks to the connection established by the dataSignal -->
binder.
Conclusion
That concludes our tutorial on ScalablyTyped.
We saw how to configure ScalablyTyped to get static types for external JavaScript libraries, and how to integrate a third-party component into a Laminar model.