作るもの
ReScriptでDOMを扱います。
サンプルとして、以下を作成します。
See the Pen 漱石先生クイズ by sgigagaeru (@sgigagaeru) on CodePen.
EventListenerの登録
まずはEventListerを登録して、以下のような動きをするところまで作ります。
See the Pen 漱石先生クイズ#2 by sgigagaeru (@sgigagaeru) on CodePen.
Global JS Valueであるdocument
とwindow
をバインドします。1
Global Valueなので@val
が付きます。
まずは型にこだわらず多層型の`a
でバインドします。
@val external doc: 'a = "document"
@val external win: 'a = "window"
doc["getElementById"]("btn")["addEventListener"]("click", () => {
win["alert"]("吾輩は猫である。名前はまだ無い。")
}, false);
これでは型付き言語の良さが全く活きないではないかということで、
@val external document: Dom.document = "document"
@val external windows: Dom.window = "window"
としたいところです。
しかしすでにdocument
はDom.document
型に、window
はDom.window
型にバインドされているので23、そのまま使います。
docment["getElementById"]("btn")["addEventListener"]("click", () => {
window["alert"]("吾輩は猫である。名前はまだ無い。")
}, false);
Object Methodの登録
getElementById
、addEventLister
、alert
をバインドします。4
Object Methodなので@send
が付きます。
@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を用いて書くと、こうなります。
@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);
@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
の情報をとっていきます。
@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
document->getElementById("btn")->addEventListener("click", () => {
window->alert(document->getElementsByName("Q1")->item(0)->getValue)
}, false)
alert
で表示させる表示を変えました。これも動きます。
getElementsByName
はDom.nodeList
を返します。Dom.nodeList
は配列でもリストでもないので、これをリスト化するgetlistedNodeList
を定義しました。
その際にgetLength
をバインドしています。
@get external getLength: Dom.nodeList => int = "length"
正解の選択肢にはdata-correct-choice="true"
を設定しています。
この属性が設定されたinput
タグのvalue
を取得するgetCorrectAnswer
を定義します。
data-*
属性を取得するのはdataset
でdomStringMap
を返します。そのdomStringMap
のcorrectChoice
属性にdata-correct-choice
の値が入っています。7
バインドします。
@get external getDataset: Dom.element => Dom.domStringMap = "dataset"
@get external getCorrectChoice: Dom.domStringMap => string = "correctChoice"
選択肢が選ばれているかは、checked
を調べれば分かります。
@get external getChecked: Dom.element => bool = "checked"
これを用いてgetSelectedAnswer
を定義しました。
だいたい動くバージョンができました。
DOM情報の書き換え
あとはinnerHTML
を書き換えて、ページを書き換えます。
innerHTMLへのアクセスにはsetter4を用います。
@get external getBody: Dom.document => Dom.element = "body"
@set external setInnerHTML: (Dom.element, string) => unit = "innerHTML"
module
を使った方が良いのかも
いまバインドのところは
@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"
となっています。さすがにこれは、見通しが良くないので、
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サーバ経由でアクセスする必要があります。
-
https://rescript-lang.org/docs/manual/latest/bind-to-global-js-values ↩
-
https://rescript-lang.org/docs/manual/latest/api/core#value-document ↩
-
https://rescript-lang.org/docs/manual/latest/api/core#value-window ↩
-
https://rescript-lang.org/docs/manual/latest/bind-to-js-function#object-method ↩ ↩2
-
https://rescript-lang.org/docs/manual/latest/bind-to-js-object#bind-using-special-getter-and-setter-attributes ↩
-
https://developer.mozilla.org/ja/docs/Web/API/HTMLElement/dataset ↩