Manipulating the DOM with ReScript (Part 1)
Tags: rescript
Published: 2020-08-25 19:15:50

Sometimes you will want to write a single-page web application, but you’d like to manipulate the DOM directly rather than use a framework like React.

The project we’re going to build will ask for parameters to two equations based on trigonometric functions and then draw a polar or Lissajous figure from those equations.

![Blank canvas on left, input fields and Draw button on right](http://langintro.com/reasonml/domgraph_article/domgraph_screenshot.png)

## Setting up

Start by creating a ReScript project, renaming it, and going to its directory:

```bash
git clone https://github.com/rescript-lang/new-project
mv new-project domgraphs
cd domgraphs
```

The `src` directory contains two files: `Demo.res`, the ReScript source file, and `Demo.bs.js` the generated JavaScript. (The `bs` stands for *BuckleScript*. BuckleScript has been rebranded as ReScript.) Remove the `.bs.js` file, and rename the source file:

```bash
rm src/Demo.bs.js
mv src/Demo.res src/DomGraphs.res
```

Next, put the HTML into file `src/index.html`.

```html
<!DOCTYPE html>
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>DOM Graphs</title>
    <style type="text/css">
      #canvas {
        width: 450px;
        float: left;
        border: 1px solid black;
        margin: 5px;
      }
    </style>
  </head>
  <body>
    <h1>DOM Graphs</h1>
    <canvas id="canvas" width="400" height="400">
      Your browser does not support the &lt;canvas&gt; element :(
    </canvas>
    <div>
      <p><input type="text" size="3" id="factor1"/>
      <select id="fcn1">
        <option value="sin">sin</option>
        <option value="cos">cos</option>
      </select>
      (
        <input type="text" size="3" id="theta1"/>
        &theta; +
        <input type="text" size="3" id="offset1"/>&deg;
      )</p>

      <p><input type="text" size="3" id="factor2"/>
      <select id="fcn2">
        <option value="sin">sin</option>
        <option value="cos">cos</option>
      </select>
      (
        <input type="text" size="3" id="theta2"/>
        &theta; +
        <input type="text" size="3" id="offset2"/>&deg;
      )</p>

      <p>
        Type of graph:
        <input type="radio" value="polar" name="graphType" id="polar"/>
          <label for="polar">Polar</label>
        <input type="radio" value="polar" name="graphType" id="lissajous"/>
          <label for="lissajous">Lissajous</label>
      </p>

      <p><button id="draw" value="draw">Draw</button></p>
    </div>
    <script type="text/javascript" src="DomGraphs.bs.js"></script>
  </body>
</html>
```

The `<script>` element at the end gives the name of the JavaScript file containing the application code, which ReScript will generate from the `DomGraphs.res` file.

ReScript includes a `Dom` library for basic manipulation of the DOM, but for more power and convenience, you’ll want to use the `bs-webapi` library. Put the dependency for `bs-webapi` into the project’s `bs-config.json` file:


```json
  "bs-dependencies": [
    "bs-webapi"
  ],
```

Install `bs-platform` and `bs-webapi`:

```bash
npm install # installs the ReScript platform
npm install --save bs-webapi
```

Finally, we need to have a *bundler* that will take the HTML file and JavaScript generated by the compiler and put it in one nice web application. In this article, I’m using `parcel`, because it works right out of the box and doesn’t require a lot of configuration. Install it globally so that you don’t need to duplicate it for every project.

```bash
npm install --global parcel-bundler
```

## Accessing the DOM

Let’s take a look at some of the modules in `bs-webapi` that you’ll use frequently, and those that we’ll use in this program.

```
Webapi
├── Canvas
│   ├── Webapi.Canvas.Canvas2d
├── Dom
│   ├── Webapi.Dom.Attr
│   ├── Webapi.Dom.ChildNode
│   ├── Webapi.Dom.Document
│   ├── Webapi.Dom.Element
│   ├── Webapi.Dom.Event
│   ├── Webapi.Dom.EventTarget
│   ├── Webapi.Dom.HtmlElement
│   ├── Webapi.Dom.HtmlInputElement
│   ├── Webapi.Dom.MouseEvent
│   ├── Webapi.Dom.Node
│   ├── Webapi.Dom.Text
│   └── Webapi.Dom.Window
```

Let’s do a quick test of the JavaScript `Window.alert()` function. Replace the code in your `DomGraphs.res` file with this code:

```
let testAlert = Webapi.Dom.Window.alert("It works!", Webapi.Dom.window)
```

The `bs-webapi` library is set up so that, most of the time, the element you’re working with (in this case, the window we’re using for the alert) is the *last* argument in the call.

Compile and bundle:

```bash
npm run build # or: bsb -make-world
parcel build src/index.html --public-url ./
```

The `parcel` command specifies the entry point for the application (`src/index.html`) and the URL to use when serving the JavaScript&mdash;in this case, the same directory as the HTML (`--public-url ./`). This will locally install the `cssnano` package. You can specify the `--no-minify` flag to prevent this local install.

When these steps are complete, you’ll see a file named `DomGraphs.bs.js`, which is the JavaScript generated by ReScript, and a directory named `dist` that contains the bundled web app.

```bash
$ ls dist
DomGraphs.bs.5acda38d.js  DomGraphs.bs.5acda38d.js.map  index.html
```

Every time you run `parcel`, it generates a new number to append to the bundled files based on the content of the files being processed. To avoid getting multiple different `.js` and `.js.map` files accumulating in your `dist` directory, you may want to remove the directory before rebuilding. Here is a `bash` script to do exactly that:

```bash
#!/bin/bash

npm run build # or bsb -make-world
status=$?
if test $status -eq 0
then
  rm -rf dist
  parcel build src/index.html src/*csv --public-url ./
fi
```

If all has gone well, when you open the `dist/index.html` file in your browser, you’ll see an alert telling you that `It works!`

Now that everything is set up, it’s time to start writing the project code.