1

I wanted to implement an interactive slider using ReScript, so I wanted to translate this JavaScript to ReScript (courtesy: https://www.w3schools.com/howto/howto_js_rangeslider.asp):

var slider = document.getElementById("myRange");
var output = document.getElementById("demo");
output.innerHTML = slider.value; // Display the default slider value

// Update the current slider value (each time you drag the slider handle)
slider.oninput = function() {
  output.innerHTML = this.value;
}

where the target HTML (slider.html) is:

<!DOCTYPE html>
<html>
    <head>
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <style>
.slidecontainer {
    width: 100%;

}

.slider {
    -webkit-appearance: none;
    width: 100%;
    height: 25px;
    background: #d3d3d3;
    outline: none;
    opacity: 0.7;
    -webkit-transition: .2s;
    transition: opacity .2s;

}

.slider:hover {
    opacity: 1;
}

.slider::-webkit-slider-thumb {
    -webkit-appearance: none;
    appearance: none;
    width: 25px;
    height: 25px;
    background: #04AA6D;
    cursor: pointer;

}

.slider::-moz-range-thumb {
    width: 25px;
    height: 25px;
    background: #04AA6D;
    cursor: pointer;

}
        </style>
    </head>
    <body>
        <div class="slidecontainer">
            <input type="range" min="1" max="100" value="50" class="slider" id="myRange">
            <p>Value: <span id="demo"></span></p>
        </div>

        <script src="./bundled.bs.js"></script>
    </body>
</html>

where the bundled.bs.js is a result of bundling the compiled .bs.js code with Browerify.

So far, I have the following piece of code written using the rescript-webapi:

open Webapi.Dom
open Belt.Option

@val external document: Document.t = "document"

let slider = getExn(Document.getElementById("myRange", document))
let output = getExn(Document.getElementById("demo", document))

Element.setInnerText(output, getExn(Element.getAttribute("value", slider)))

Element.addEventListener("value", _ => 
    Element.setInnerHTML(output, getExn(Element.getAttribute("value", slider))), slider)

However, this does not work as intended; I can see the slider and slide it around, but the input tag's value does not get updated:

the slider is there, but it does not update the value.

What is missing from my ReScript translation?

+Update:

@glennsl has provided me with this code below:

open Webapi.Dom
open Belt

let slider = document->Document.getElementById("myRange")->Option.getExn
let output = document->Document.getElementById("demo")->Option.getExn

output->Element.setInnerText(slider->Element.getAttribute("value")->Option.getExn)

slider->Element.addEventListener("input", _ => {
  let value = slider->Element.getAttribute("value")->Option.getExn
  output->Element.setInnerHTML(value)
})

I can see the Value: with the correct initial value, 50. the initial screen with Value: 50. However, even if I repositioned the slider, the value does not get updated:the Value is not updated with the repositiioned slider.

As mentioned above, I compiled this code (dubbed slider.res) into slider.bs.js and bundled it with browserify slider.bs.js -o bundled.bs.js.

Namudon'tdie
  • 306
  • 1
  • 10

2 Answers2

1

There's no event called value. The JavaScript code you're translating from is using input (oninput).

You're also mixing "data first" and "data last". rescript-webapi uses "data first", while bs-webapi, which rescript-webapi is forked from, uses "data last". You might be basing this on example code from bs-webapi.

In any case, this should work with rescript-webapi:

open Webapi.Dom
open Belt

let slider = document->Document.getElementById("myRange")->Option.getExn
let output = document->Document.getElementById("demo")->Option.getExn

output->Element.setInnerText(slider->Element.getAttribute("value")->Option.getExn)

slider->Element.addEventListener("input", _ => {
  let value = slider->Element.getAttribute("value")->Option.getExn
  output->Element.setInnerHTML(value)
})
glennsl
  • 28,186
  • 12
  • 57
  • 75
  • Follow-up question here: how is "data first" and "data last" defined, respectively? – Namudon'tdie Dec 26 '21 at 09:50
  • Also, the above code seems to not update the `demo`'s value on sliding the slider. :( I'm on the latest Safari, and wrapped the `.bs.js` code with `browserify`. – Namudon'tdie Dec 26 '21 at 09:55
  • Without a full [mcve] it's hard to provide any further help with this. – glennsl Dec 26 '21 at 10:07
  • 1
    "data first", or "t-first", means a function takes the main value or "object" it operates on as the first argument. "data last", or "t-last", means it's the last argument. "data last" used to be much more common because ReScript is based on a curried language where this is more natural and convenient. But for various reasons, perhaps most importantly better interop with JavsScript, the `->` operator and "data first" concept was introduced (in an amazingly bad way, which causes these kinds of problems even several years later, but that's a different story). – glennsl Dec 26 '21 at 10:13
  • I have updated the question with more details. Thanks for pointing it out. – Namudon'tdie Dec 26 '21 at 12:14
  • https://www.javierchavarri.com/data-first-and-data-last-a-comparison/ – Mulan Apr 14 '22 at 04:11
  • @glennsl i'm interested to hear your thoughts on `->` and `|>` – Mulan Apr 14 '22 at 05:40
  • 1
    @Mulan I haven't given this much thought in a long while, but I think the biggest problem is how it was forced on and effectively split the community in two. Javier does a good job explaining the differences inherent in the two implementations, but doesn't address possible ways to evolve `|>` to get many of the same benefits. It could easily be implemented as syntax sugar as well, for example, and have all the same performance benefits. And the type inference algorithm could alter the order of inference when `|>` is used, giving it almost all of the benefits of `|.` without the downsides. – glennsl Apr 14 '22 at 07:09
1

Ugh, the main problem was twofold:

  1. As @glennsl pointed out, the event name was wrong. It should be "input", not "value".

  2. I mistook the this.value to refer to the value attribute of this, which is slider. Actually, this is considering slider to be an HTML input element, and referring to its input value.

As we cannot directly cast an Dom.Element.t to an Dom.HtmlInputElement.t, we first convert this to Dom.Node.t and then to Dom.HtmlInputElement.t. So, building upon @glennsl's code, the code is:

open Webapi.Dom
open Belt

let slider = document->Document.getElementById("myRange")->Option.getExn
let output = document->Document.getElementById("demo")->Option.getExn

output->Element.setInnerText(slider->Element.getAttribute("value")->Option.getExn)

slider->Element.addEventListener("input", _ => {
  let value = slider->Element.asNode->HtmlInputElement.ofNode->Option.getExn->HtmlInputElement.value
  output->Element.setInnerHTML(value)
})
glennsl
  • 28,186
  • 12
  • 57
  • 75
Namudon'tdie
  • 306
  • 1
  • 10