0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ReScriptでDOMを扱う

Last updated at Posted at 2024-05-27

作るもの

ReScriptでDOMを扱います。
サンプルとして、以下を作成します。

See the Pen 漱石先生クイズ by sgigagaeru (@sgigagaeru) on CodePen.

EventListenerの登録

まずはEventListerを登録して、以下のような動きをするところまで作ります。

See the Pen 漱石先生クイズ#2 by sgigagaeru (@sgigagaeru) on CodePen.


Global JS Valueであるdocumentwindowをバインドします。1
Global Valueなので@valが付きます。
まずは型にこだわらず多層型の`aでバインドします。

souseki_form.res
@val external doc: 'a = "document"
@val external win: 'a = "window"
doc["getElementById"]("btn")["addEventListener"]("click", () => {
     win["alert"]("吾輩は猫である。名前はまだ無い。")
   }, false);

これで動きます。


これでは型付き言語の良さが全く活きないではないかということで、

souseki_form.res
@val external document: Dom.document = "document"
@val external windows: Dom.window = "window"

としたいところです。
しかしすでにdocumentDom.document型に、windowDom.window型にバインドされているので23、そのまま使います。

souseki_form.res
docment["getElementById"]("btn")["addEventListener"]("click", () => {
     window["alert"]("吾輩は猫である。名前はまだ無い。")
   }, false);

これはエラーになります。

Object Methodの登録

getElementByIdaddEventListeralertをバインドします。4
Object Methodなので@sendが付きます。

souseki_form.res
@send external getElementById: (Dom.document, string) => Dom.element = "getElementById"
@send external addEventListener: (Dom.element, string, () => unit, bool) => unit = "addEventListener"
@send external alert: (Dom.window, string) => unit = "alert"

登録したObject Methodを用いて書くと、こうなります。

souseki_form.res
@send external getElementById: (Dom.document, string) => Dom.element = "getElementById"
@send external addEventListener: (Dom.element, string, () => unit, bool) => unit = "addEventListener"
@send external alert: (Dom.window, string) => unit = "alert"

addEventListener(getElementById(document, "btn"), "click", () => {
     alert(window, "吾輩は猫である。名前はまだ無い。")
   }, false);

これで動くので問題ないですが、Pipe5を使って

souseki_form.res
@send external getElementById: (Dom.document, string) => Dom.element = "getElementById"
@send external addEventListener: (Dom.element, string, () => unit, bool) => unit = "addEventListener"
@send external alert: (Dom.window, string) => unit = "alert"

document->getElementById("btn")->addEventListener("click", () => {
  window->alert("吾輩は猫である。名前はまだ無い。")
  }, false);

としておきます。これも動きます。

Form情報の取得

getElementsByNameを用いてinputの情報をとっていきます。

souseki_form.res
@send external getElementsByName: (Dom.document, string) => Dom.nodeList = "getElementsByName"
@send external item: (Dom.nodeList, int) => Dom.element = "item"
@get external getValue: Dom.element => string = "value"

getValueはオブジェクトのプロパティvalueの値を取得しているので@getを付けます。6

souseki_form.res
document->getElementById("btn")->addEventListener("click", () => {
  window->alert(document->getElementsByName("Q1")->item(0)->getValue)
  }, false)

alertで表示させる表示を変えました。これも動きます。


getElementsByNameDom.nodeListを返します。Dom.nodeListは配列でもリストでもないので、これをリスト化するgetlistedNodeListを定義しました。
その際にgetLengthをバインドしています。

souseki_form.res
@get external getLength: Dom.nodeList => int = "length"

正解の選択肢にはdata-correct-choice="true"を設定しています。
この属性が設定されたinputタグのvalueを取得するgetCorrectAnswerを定義します。
data-*属性を取得するのはdatasetdomStringMapを返します。そのdomStringMapcorrectChoice属性にdata-correct-choiceの値が入っています。7
バインドします。

souseki_form.res
@get external getDataset: Dom.element => Dom.domStringMap = "dataset"
@get external getCorrectChoice: Dom.domStringMap => string = "correctChoice"

選択肢が選ばれているかは、checkedを調べれば分かります。

souseki_form.res
@get external getChecked: Dom.element => bool = "checked"

これを用いてgetSelectedAnswerを定義しました。


だいたい動くバージョンができました。

DOM情報の書き換え

あとはinnerHTMLを書き換えて、ページを書き換えます。
innerHTMLへのアクセスにはsetter4を用います。

souseki_form.res
@get external getBody: Dom.document => Dom.element = "body"
@set external setInnerHTML: (Dom.element, string) => unit = "innerHTML"

一旦の完成版です。

moduleを使った方が良いのかも

いまバインドのところは

souseki_form.res
@send external getElementById: (Dom.document, string) => Dom.element = "getElementById"
@send external addEventListener: (Dom.element, string, () => unit, bool) => unit = "addEventListener"
@send external alert: (Dom.window, string) => unit = "alert"
@send external getElementsByName: (Dom.document, string) => Dom.nodeList = "getElementsByName"
@send external item: (Dom.nodeList, int) => Dom.element = "item"
@get external getValue: Dom.element => string = "value"
@get external getLength: Dom.nodeList => int = "length"
@get external getDataset: Dom.element => Dom.domStringMap = "dataset"
@get external getCorrectChoice: Dom.domStringMap => string = "correctChoice"
@get external getChecked: Dom.element => bool = "checked"
@get external getBody: Dom.document => Dom.element = "body"
@set external setInnerHTML: (Dom.element, string) => unit = "innerHTML"

となっています。さすがにこれは、見通しが良くないので、

souseki_form.res
module Document = {
  @send external getElementById: (Dom.document, string) => Dom.element = "getElementById"
  @send external getElementsByName: (Dom.document, string) => Dom.nodeList = "getElementsByName"
  @get external getBody: Dom.document => Dom.element = "body"
}
module Element = {
  @send external addEventListener: (Dom.element, string, () => unit, bool) => unit = "addEventListener"
  @get external getDataset: Dom.element => Dom.domStringMap = "dataset"
  @get external getValue: Dom.element => string = "value"
  @get external getChecked: Dom.element => bool = "checked"
  @set external setInnerHTML: (Dom.element, string) => unit = "innerHTML"
}
module NodeList = {
  @get external getLength: Dom.nodeList => int = "length"
  @send external item: (Dom.nodeList, int) => Dom.element = "item"
}
module DomStringMap = {
  @get external getCorrectChoice: Dom.domStringMap => string = "correctChoice"
}

と、moduleで分けるのかなと思いました。
その他、関数をまとめたりする変更を加えて、完成版としました。

ReScriptファイルはモジュールです

ReScriptでは一つのファイルは一つのモジュールとして扱われます。
そのため生成されるJSファイルにはexport(esmoduleの場合)かexports(commonjsの場合)が書かれます。
ブラウザに読み込ませる場合はesmoduleにする必要があり、モジュールとして扱われるのでscriptタグでtype="module"の設定が必要です。
そしてモジュールをローカルで読み込むとCORSエラーになるので、試験時はローカルのWebサーバ経由でアクセスする必要があります。

  1. https://rescript-lang.org/docs/manual/latest/bind-to-global-js-values

  2. https://rescript-lang.org/docs/manual/latest/api/core#value-document

  3. https://rescript-lang.org/docs/manual/latest/api/core#value-window

  4. https://rescript-lang.org/docs/manual/latest/bind-to-js-function#object-method 2

  5. https://rescript-lang.org/docs/manual/latest/pipe

  6. https://rescript-lang.org/docs/manual/latest/bind-to-js-object#bind-using-special-getter-and-setter-attributes

  7. https://developer.mozilla.org/ja/docs/Web/API/HTMLElement/dataset

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?