diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d940b07..cefb8c26 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +### 0.20.0 + +* Added bindings for `HTMLFormControlsCollection` +* Added binding for `HTMLOptionsCollection` +* Added bindings for `RadioNodeList` +* Added binding for `Document.forms` +* Added binding for `HTMLFormElement.elements` + ### 0.19.2 * Removed peer dependency on `bsdoc` diff --git a/lib/js/tests/Webapi/Dom/Webapi__Dom__HtmlFormElement__test.js b/lib/js/tests/Webapi/Dom/Webapi__Dom__HtmlFormElement__test.js index 28900af1..e1dc35c3 100644 --- a/lib/js/tests/Webapi/Dom/Webapi__Dom__HtmlFormElement__test.js +++ b/lib/js/tests/Webapi/Dom/Webapi__Dom__HtmlFormElement__test.js @@ -1,9 +1,311 @@ 'use strict'; +var Js_exn = require("bs-platform/lib/js/js_exn.js"); +var Belt_Option = require("bs-platform/lib/js/belt_Option.js"); +var Caml_option = require("bs-platform/lib/js/caml_option.js"); +var TestHelpers = require("../../testHelpers.js"); +var Webapi__Dom__Document = require("../../../src/Webapi/Dom/Webapi__Dom__Document.js"); +var Webapi__Dom__HtmlFormElement = require("../../../src/Webapi/Dom/Webapi__Dom__HtmlFormElement.js"); +var Webapi__Dom__HtmlSelectElement = require("../../../src/Webapi/Dom/Webapi__Dom__HtmlSelectElement.js"); +var Webapi__Dom__HtmlFormControlsCollection = require("../../../src/Webapi/Dom/Webapi__Dom__HtmlFormControlsCollection.js"); + +function createElement(__x) { + return document.createElement(__x); +} + +function createTextNode(__x) { + return document.createTextNode(__x); +} + +function createInput(param) { + return document.createElement("input"); +} + +function createLabelWithText(text) { + var el = document.createElement("label"); + var textNode = document.createTextNode(text); + el.appendChild(textNode); + return el; +} + +var form = TestHelpers.unsafelyUnwrapOption(Webapi__Dom__HtmlFormElement.asFormElement(document.createElement("form"))); + +var usernameInput = document.createElement("input"); + +usernameInput.setAttribute("type", "text"); + +usernameInput.setAttribute("name", "username"); + +var usernameLabel = createLabelWithText("Username:"); + +usernameLabel.appendChild(usernameInput); + +var passwordInput = document.createElement("input"); + +passwordInput.setAttribute("type", "password"); + +passwordInput.setAttribute("name", "password"); + +var passwordLabel = createLabelWithText("Password:"); + +passwordLabel.appendChild(passwordInput); + +var radioInput1 = document.createElement("input"); + +radioInput1.setAttribute("type", "radio"); + +radioInput1.setAttribute("name", "radiogroup"); + +radioInput1.setAttribute("value", "one"); + +radioInput1.setAttribute("checked", "true"); + +var radioLabel1 = createLabelWithText("Choice 1:"); + +radioLabel1.appendChild(radioInput1); + +var radioInput2 = document.createElement("input"); + +radioInput2.setAttribute("type", "radio"); + +radioInput2.setAttribute("name", "radiogroup"); + +radioInput2.setAttribute("value", "two"); + +var radioLabel2 = createLabelWithText("Choice 2:"); + +radioLabel2.appendChild(radioInput2); + +var select = document.createElement("select"); + +select.setAttribute("name", "select"); + +var selectLabel = createLabelWithText("Select:"); + +selectLabel.appendChild(select); + +var usernameContainer = document.createElement("div"); + +var passwordContainer = document.createElement("div"); + +var radioContainer = document.createElement("div"); + +var selectContainer = document.createElement("div"); + +usernameContainer.appendChild(usernameLabel); + +passwordContainer.appendChild(passwordLabel); + +radioContainer.appendChild(radioLabel1); + +radioContainer.appendChild(radioLabel2); + +selectContainer.appendChild(selectLabel); + +form.appendChild(usernameContainer); + +form.appendChild(passwordContainer); + +form.appendChild(radioContainer); + +form.appendChild(selectContainer); + +var body = TestHelpers.unsafelyUnwrapOption(Belt_Option.flatMap(Webapi__Dom__Document.asHtmlDocument(document), (function (prim) { + return Caml_option.nullable_to_opt(prim.body); + }))); + +body.appendChild(form); + +var collection = form.elements; + +console.log("HtmlFormElement.elements:", collection); + +var len = collection.length; + +console.log("HtmlFormControlsCollection.length:", len); + +var el0 = collection.item(0); + +console.log("HtmlFormControlsCollection.item:", (el0 == null) ? undefined : Caml_option.some(el0)); + +var el0$1 = Webapi__Dom__HtmlFormControlsCollection.namedItem("username", collection); + +console.log("HtmlFormControlsCollection.namedItem:", el0$1); + +var el1 = collection.item(1); + +console.log("HtmlFormControlsCollection.length:", (el1 == null) ? undefined : Caml_option.some(el1)); + +var el1$1 = Webapi__Dom__HtmlFormControlsCollection.namedItem("password", collection); + +console.log("HtmlFormControlsCollection.namedItem:", el1$1); + +var radioNodeList = collection.item(2); + +console.log("HtmlFormControlsCollection.namedItem:", (radioNodeList == null) ? undefined : Caml_option.some(radioNodeList)); + +var radioNodeList$1 = Webapi__Dom__HtmlFormControlsCollection.namedItem("radiogroup", collection); + +console.log("HtmlFormControlsCollection.namedItem:", radioNodeList$1); + +var match = TestHelpers.unsafelyUnwrapOption(radioNodeList$1); + +if (typeof match !== "number") { + var variant = match[0]; + if (variant >= 96709417) { + if (variant >= 488741627) { + if (variant !== 516394780) { + if (variant !== 942443387) { + + } else { + console.log("RadioNodeList.value", match[1].value); + } + } else { + Js_exn.raiseError("incorrect namedItem return value"); + } + } else if (variant !== 242538002 && variant < 488741626) { + + } else { + Js_exn.raiseError("incorrect namedItem return value"); + } + } else if (variant >= -908856608) { + if (variant !== -783600662 && variant < 96709416) { + + } else { + Js_exn.raiseError("incorrect namedItem return value"); + } + } else if (variant !== -1055554783 && variant < -908856609) { + + } else { + Js_exn.raiseError("incorrect namedItem return value"); + } +} + +var select$1 = TestHelpers.unsafelyUnwrapOption(Webapi__Dom__HtmlSelectElement.ofElement(select)); + +var opts = select$1.options; + +console.log("HtmlSelectElement.options:", opts); + +opts.length = 3; + +console.log("collection length:", opts.length); + +opts[0] = null; + +console.log("collection length:", opts.length); + +opts[2] = document.createElement("option"); + +console.log("collection length:", opts.length); + +opts.length = 0; + +var opt1 = document.createElement("option"); + +opt1.setAttribute("value", "1"); + +opt1.appendChild(document.createTextNode("opt1")); + +opts.add(opt1); + +var selectedIndex = opts.selectedIndex = 0; + +console.log("collection length:", opts.length); + +console.log("HtmlOptionsCollection.setSelectedIndex", selectedIndex); + +var opt2 = document.createElement("option"); + +opt2.setAttribute("value", "2"); + +opt2.appendChild(document.createTextNode("opt2")); + +var item = opts.item(0); + +console.log("HtmlOptionsCollection.item:", (item == null) ? undefined : Caml_option.some(item)); + +console.log("collection length:", opts.length); + +opts.add(opt2, 0); + +opts.selectedIndex = opt2; + +var item$1 = opts.item(0); + +console.log("HtmlOptionsCollection.addBefore:", (item$1 == null) ? undefined : Caml_option.some(item$1)); + +console.log("collection length:", opts.length); + +console.log("selected index", opts.selectedIndex); + +var opt3 = document.createElement("option"); + +opt3.setAttribute("value", "3"); + +opt3.appendChild(document.createTextNode("opt3")); + +opts.add(opt3, opt2); + +var item$2 = opts.item(0); + +console.log("HtmlOptionsCollection.addBeforeElement:", (item$2 == null) ? undefined : Caml_option.some(item$2)); + +console.log("collection length:", opts.length); + +var item$3 = opts.selectedIndex; + +console.log("HtmlOptionsCollection.selectedIndex:", item$3); + +var item$4 = opts.selectedIndex = opt3; + +console.log("HtmlOptionsCollection.setSelectedElement:", item$4); + +var item$5 = opts.selectedIndex; + +console.log("HtmlOptionsCollection.selectedIndex:", item$5); + +opts.remove(0); + +console.log("collection length:", opts.length); function test_data(formElement) { return new FormData(formElement).get("foo"); } +var formEl = form; + +exports.createElement = createElement; +exports.createTextNode = createTextNode; +exports.createInput = createInput; +exports.createLabelWithText = createLabelWithText; +exports.form = form; +exports.usernameInput = usernameInput; +exports.usernameLabel = usernameLabel; +exports.passwordInput = passwordInput; +exports.passwordLabel = passwordLabel; +exports.radioInput1 = radioInput1; +exports.radioLabel1 = radioLabel1; +exports.radioInput2 = radioInput2; +exports.radioLabel2 = radioLabel2; +exports.selectLabel = selectLabel; +exports.usernameContainer = usernameContainer; +exports.passwordContainer = passwordContainer; +exports.radioContainer = radioContainer; +exports.selectContainer = selectContainer; +exports.formEl = formEl; +exports.body = body; +exports.collection = collection; +exports.len = len; +exports.el0 = el0$1; +exports.el1 = el1$1; +exports.radioNodeList = radioNodeList$1; +exports.select = select$1; +exports.opts = opts; +exports.opt1 = opt1; +exports.selectedIndex = selectedIndex; +exports.opt2 = opt2; +exports.opt3 = opt3; +exports.item = item$5; exports.test_data = test_data; -/* No side effect */ +/* form Not a pure module */ diff --git a/package.json b/package.json index 48b01682..0c995415 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "bs-webapi", - "version": "0.19.2", + "version": "0.20.0", "description": "Reason + BuckleScript bindings to DOM", "repository": { "type": "git", diff --git a/src/Webapi/Dom/Webapi__Dom__Document.re b/src/Webapi/Dom/Webapi__Dom__Document.re index b6a2c9b4..1b8dd2e9 100644 --- a/src/Webapi/Dom/Webapi__Dom__Document.re +++ b/src/Webapi/Dom/Webapi__Dom__Document.re @@ -34,6 +34,8 @@ module Impl = (T: {type t;}) => { [@bs.get] external implementation : T.t => Dom.domImplementation = ""; [@bs.get] external lastStyleSheetSet : T.t => string = ""; [@bs.get] [@bs.return nullable] external pointerLockElement : T.t => option(Dom.element) = ""; /* experimental */ + /** @since 0.20.0 */ + [@bs.get] external forms : T.t => Dom.htmlCollection = ""; [@bs.get] external preferredStyleSheetSet : T.t => string = ""; [@bs.get] [@bs.return nullable] external scrollingElement : T.t => option(Dom.element) = ""; diff --git a/src/Webapi/Dom/Webapi__Dom__HtmlButtonElement.re b/src/Webapi/Dom/Webapi__Dom__HtmlButtonElement.re new file mode 100644 index 00000000..b8ddb71e --- /dev/null +++ b/src/Webapi/Dom/Webapi__Dom__HtmlButtonElement.re @@ -0,0 +1,16 @@ +/** + * Spec: https://html.spec.whatwg.org/multipage/form-elements.html#the-button-element + */ +module Impl = (T: {type t;}) => { + type t_htmlButtonElement = T.t; + + // TODO +}; + +type t; // TODO: Dom.htmlButtonElement + +include Webapi__Dom__EventTarget.Impl({ type nonrec t = t; }); +include Webapi__Dom__Node.Impl({ type nonrec t = t; }); +include Webapi__Dom__Element.Impl({ type nonrec t = t; }); +include Webapi__Dom__HtmlElement.Impl({ type nonrec t = t; }); +include Impl({ type nonrec t = t; }); diff --git a/src/Webapi/Dom/Webapi__Dom__HtmlCollection.re b/src/Webapi/Dom/Webapi__Dom__HtmlCollection.re index b57d911d..196f140e 100644 --- a/src/Webapi/Dom/Webapi__Dom__HtmlCollection.re +++ b/src/Webapi/Dom/Webapi__Dom__HtmlCollection.re @@ -1,7 +1,12 @@ -type t = Dom.htmlCollection; +module Impl = (T: { type t;}) => { + type t_htmlCollection = T.t; + [@bs.val] [@bs.scope ("Array", "prototype", "slice")] external toArray : t_htmlCollection => array(Dom.element) = "call"; + + [@bs.get] external length : t_htmlCollection => int = ""; + [@bs.send.pipe : t_htmlCollection] [@bs.return nullable] external item : int => option(Dom.element) = ""; + [@bs.send.pipe : t_htmlCollection] [@bs.return nullable] external namedItem : string => option(Dom.element) = ""; +}; -[@bs.val] [@bs.scope ("Array", "prototype", "slice")] external toArray : t => array(Dom.element) = "call"; +type t = Dom.htmlCollection; -[@bs.get] external length : t => int = ""; -[@bs.send.pipe : t] [@bs.return nullable] external item : int => option(Dom.element) = ""; -[@bs.send.pipe : t] [@bs.return nullable] external namedItem : string => option(Dom.element) = ""; +include Impl({ type nonrec t = t; }); diff --git a/src/Webapi/Dom/Webapi__Dom__HtmlFieldSetElement.re b/src/Webapi/Dom/Webapi__Dom__HtmlFieldSetElement.re new file mode 100644 index 00000000..ceb44e6b --- /dev/null +++ b/src/Webapi/Dom/Webapi__Dom__HtmlFieldSetElement.re @@ -0,0 +1,16 @@ +/** + * Spec: https://html.spec.whatwg.org/multipage/form-elements.html#the-fieldset-element + */ +module Impl = (T: {type t;}) => { + type t_htmlFieldSetElement = T.t; + + // TODO +}; + +type t; // TODO: Dom.htmlFieldSetElement + +include Webapi__Dom__EventTarget.Impl({ type nonrec t = t; }); +include Webapi__Dom__Node.Impl({ type nonrec t = t; }); +include Webapi__Dom__Element.Impl({ type nonrec t = t; }); +include Webapi__Dom__HtmlElement.Impl({ type nonrec t = t; }); +include Impl({ type nonrec t = t; }); diff --git a/src/Webapi/Dom/Webapi__Dom__HtmlFormControlsCollection.re b/src/Webapi/Dom/Webapi__Dom__HtmlFormControlsCollection.re new file mode 100644 index 00000000..57684c03 --- /dev/null +++ b/src/Webapi/Dom/Webapi__Dom__HtmlFormControlsCollection.re @@ -0,0 +1,43 @@ +/** + * Spec: https://html.spec.whatwg.org/multipage/common-dom-interfaces.html#htmlformcontrolscollection + */ +type t; // TODO: Dom.htmlFormControlsCollection + +type t_namedItem = [ + | `RadioNodeList(Webapi__Dom__RadioNodeList.t) + | `Button(Webapi__Dom__HtmlButtonElement.t) + | `FieldSet(Webapi__Dom__HtmlFieldSetElement.t) + | `Input(Webapi__Dom__HtmlInputElement.t) + | `Object(Webapi__Dom__HtmlObjectElement.t) + | `Output(Webapi__Dom__HtmlOutputElement.t) + | `Select(Webapi__Dom__HtmlSelectElement.t) + | `TextArea(Webapi__Dom__HtmlTextAreaElement.t) +]; + +include Webapi__Dom__HtmlCollection.Impl({ type nonrec t = t; }); + +let isRadioNodeList: 'a => bool = [%raw {| + function(x) { return x instanceof RadioNodeList; } +|}]; + +[@bs.send.pipe : t] [@bs.return nullable] external _namedItem: string => option('a) = "namedItem"; +let namedItem = (name, t) => + switch (_namedItem(name, t)) { + | Some(el) => + if (Webapi__Dom__RadioNodeList.unsafeAsRadioNodeList(el)->isRadioNodeList) { + el->Obj.magic->`RadioNodeList->Some; + } else { + switch (Webapi__Dom__Element.tagName(el)) { + // fixme: this should be a classify function in Webapi__Dom__HtmlElement + | "BUTTON" => el->Obj.magic->`Button->Some + | "FIELDSET" => el->Obj.magic->`FieldSet->Some + | "INPUT" => el->Obj.magic->`Input->Some + | "OBJECT" => el->Obj.magic->`Object->Some + | "OUTPUT" => el->Obj.magic->`Output->Some + | "SELECT" => el->Obj.magic->`Select->Some + | "TEXTAREA" => el->Obj.magic->`TextArea->Some + | _ => None + }; + } + | None => None + }; diff --git a/src/Webapi/Dom/Webapi__Dom__HtmlFormElement.re b/src/Webapi/Dom/Webapi__Dom__HtmlFormElement.re index fabae957..e0c9accf 100644 --- a/src/Webapi/Dom/Webapi__Dom__HtmlFormElement.re +++ b/src/Webapi/Dom/Webapi__Dom__HtmlFormElement.re @@ -6,7 +6,16 @@ module Impl = (T: {type t;}) => { type t_htmlFormElement = T.t; - /* TODO: elements: HTMLFormControlsCollection */ + external unsafeAsFormElement: Dom.element => t_htmlFormElement = "%identity"; + external asElement: t_htmlFormElement => Dom.element = "%identity"; + + let asFormElement = (el): option(t_htmlFormElement) => switch(Webapi__Dom__Element.tagName(el)) { + | "FORM" => el->unsafeAsFormElement->Some + | _ => None + }; + + /** @since 0.20.0 */ + [@bs.get] external elements : t_htmlFormElement => Webapi__Dom__HtmlFormControlsCollection.t = "elements"; [@bs.get] external length : t_htmlFormElement => int = ""; [@bs.get] external name : t_htmlFormElement => string = ""; [@bs.set] external setName : (t_htmlFormElement, string) => unit = "name"; diff --git a/src/Webapi/Dom/Webapi__Dom__HtmlObjectElement.re b/src/Webapi/Dom/Webapi__Dom__HtmlObjectElement.re new file mode 100644 index 00000000..629d0594 --- /dev/null +++ b/src/Webapi/Dom/Webapi__Dom__HtmlObjectElement.re @@ -0,0 +1,16 @@ +/** + * https://html.spec.whatwg.org/multipage/iframe-embed-object.html#the-object-element + */ +module Impl = (T: {type t;}) => { + type t_htmlObjectElement = T.t; + + // TODO +}; + +type t; // TODO: Dom.htmlObjectElement + +include Webapi__Dom__EventTarget.Impl({ type nonrec t = t; }); +include Webapi__Dom__Node.Impl({ type nonrec t = t; }); +include Webapi__Dom__Element.Impl({ type nonrec t = t; }); +include Webapi__Dom__HtmlElement.Impl({ type nonrec t = t; }); +include Impl({ type nonrec t = t; }); diff --git a/src/Webapi/Dom/Webapi__Dom__HtmlOptionsCollection.re b/src/Webapi/Dom/Webapi__Dom__HtmlOptionsCollection.re new file mode 100644 index 00000000..ec9e8d1b --- /dev/null +++ b/src/Webapi/Dom/Webapi__Dom__HtmlOptionsCollection.re @@ -0,0 +1,25 @@ +/** + * Spec: https://html.spec.whatwg.org/multipage/common-dom-interfaces.html#htmloptionscollection + */ +type t; // TODO: Dom.htmlOptionsCollection; +include Webapi__Dom__HtmlCollection.Impl({ type nonrec t = t; }); + +/** Properties */ + +[@bs.set] external setLength: (t, int) => unit = "length"; +[@bs.set_index] external setItem: (t, int, Dom.element) => unit = ""; +[@bs.set_index] external clearItem: (t, int, Js.null('a)) => unit = ""; + +[@bs.get] external selectedIndex: t => int = "selectedIndex"; +[@bs.set] external setSelectedIndex: (t, int) => int = "selectedIndex"; +[@bs.set] external setSelectedElement: (t, Dom.element) => Dom.element = "selectedIndex"; + +/** Methods */ + +/** + * This method will throw a "HierarchyRequestError" DOMException if element is an ancestor of the element into which it is to be inserted. + */ +[@bs.send.pipe: t] external add: (Dom.element) => unit = "add"; +[@bs.send.pipe: t] external addBefore: (Dom.element, int) => unit = "add"; +[@bs.send.pipe: t] external addBeforeElement: (Dom.element, Dom.element) => unit = "add"; +[@bs.send.pipe: t] external remove: int => unit = "remove"; diff --git a/src/Webapi/Dom/Webapi__Dom__HtmlOutputElement.re b/src/Webapi/Dom/Webapi__Dom__HtmlOutputElement.re new file mode 100644 index 00000000..1130fcf0 --- /dev/null +++ b/src/Webapi/Dom/Webapi__Dom__HtmlOutputElement.re @@ -0,0 +1,16 @@ +/** + * Spec: https://html.spec.whatwg.org/multipage/form-elements.html#the-output-element + */ +module Impl = (T: {type t;}) => { + type t_htmlOutputElement = T.t; + + // TODO +}; + +type t; // TODO: Dom.htmlOutputElement + +include Webapi__Dom__EventTarget.Impl({ type nonrec t = t; }); +include Webapi__Dom__Node.Impl({ type nonrec t = t; }); +include Webapi__Dom__Element.Impl({ type nonrec t = t; }); +include Webapi__Dom__HtmlElement.Impl({ type nonrec t = t; }); +include Impl({ type nonrec t = t; }); diff --git a/src/Webapi/Dom/Webapi__Dom__HtmlSelectElement.re b/src/Webapi/Dom/Webapi__Dom__HtmlSelectElement.re new file mode 100644 index 00000000..647bd5c8 --- /dev/null +++ b/src/Webapi/Dom/Webapi__Dom__HtmlSelectElement.re @@ -0,0 +1,28 @@ +/** + * Spec: https://html.spec.whatwg.org/multipage/form-elements.html#the-select-element + */ +module Impl = (T: {type t;}) => { + type t_htmlSelectElement = T.t; + + external unsafeOfElement: Dom.element => t_htmlSelectElement = "%identity"; + external asElement: t_htmlSelectElement => Dom.element = "%identity"; + + let ofElement = (el): option(t_htmlSelectElement) => switch(Webapi__Dom__Element.tagName(el)) { + | "SELECT" => el->unsafeOfElement->Some + | _ => None + }; + + // TODO + + /** Properties */ + + [@bs.get] external options: t_htmlSelectElement => Webapi__Dom__HtmlOptionsCollection.t = "options"; +}; + +type t; // TODO: Dom.htmlSelectElement + +include Webapi__Dom__EventTarget.Impl({ type nonrec t = t; }); +include Webapi__Dom__Node.Impl({ type nonrec t = t; }); +include Webapi__Dom__Element.Impl({ type nonrec t = t; }); +include Webapi__Dom__HtmlElement.Impl({ type nonrec t = t; }); +include Impl({ type nonrec t = t; }); diff --git a/src/Webapi/Dom/Webapi__Dom__HtmlTextAreaElement.re b/src/Webapi/Dom/Webapi__Dom__HtmlTextAreaElement.re new file mode 100644 index 00000000..949ec69b --- /dev/null +++ b/src/Webapi/Dom/Webapi__Dom__HtmlTextAreaElement.re @@ -0,0 +1,16 @@ +/** + * Spec: https://html.spec.whatwg.org/multipage/form-elements.html#the-textarea-element + */ +module Impl = (T: {type t;}) => { + type t_htmlTextAreaElement = T.t; + + // TODO +}; + +type t; // TODO: Dom.htmlTextAreaElement + +include Webapi__Dom__EventTarget.Impl({ type nonrec t = t; }); +include Webapi__Dom__Node.Impl({ type nonrec t = t; }); +include Webapi__Dom__Element.Impl({ type nonrec t = t; }); +include Webapi__Dom__HtmlElement.Impl({ type nonrec t = t; }); +include Impl({ type nonrec t = t; }); diff --git a/src/Webapi/Dom/Webapi__Dom__NodeList.re b/src/Webapi/Dom/Webapi__Dom__NodeList.re index 90cf9e54..5d466042 100644 --- a/src/Webapi/Dom/Webapi__Dom__NodeList.re +++ b/src/Webapi/Dom/Webapi__Dom__NodeList.re @@ -1,9 +1,15 @@ -type t = Dom.nodeList; +module Impl = (T: {type t;}) => { + type t_nodeList = T.t; + + [@bs.val] external toArray : t_nodeList => array(Dom.node) = "Array.prototype.slice.call"; -[@bs.val] external toArray : t => array(Dom.node) = "Array.prototype.slice.call"; + [@bs.send.pipe : t_nodeList] external forEach : ((Dom.node, int) => unit) => unit = ""; -[@bs.send.pipe : t] external forEach : ((Dom.node, int) => unit) => unit = ""; + [@bs.get] external length : t_nodeList => int = ""; -[@bs.get] external length : t => int = ""; + [@bs.send.pipe : t_nodeList] [@bs.return nullable] external item : int => option(Dom.node) = ""; +}; + +type t = Dom.nodeList; -[@bs.send.pipe : t] [@bs.return nullable] external item : int => option(Dom.node) = ""; +include Impl({ type nonrec t = t; }); diff --git a/src/Webapi/Dom/Webapi__Dom__RadioNodeList.re b/src/Webapi/Dom/Webapi__Dom__RadioNodeList.re new file mode 100644 index 00000000..a28e4fd5 --- /dev/null +++ b/src/Webapi/Dom/Webapi__Dom__RadioNodeList.re @@ -0,0 +1,14 @@ +/** + * Spec: https://html.spec.whatwg.org/multipage/common-dom-interfaces.html#radionodelist + */ +module Impl = (T: {type t;}) => { + type t_radioNodeList = T.t; + [@bs.get] external value: t_radioNodeList => string = "value"; + + external unsafeAsRadioNodeList: 'a => t_radioNodeList = "%identity"; +}; + +type t; // TODO: Dom.radioNodeList + +include Webapi__Dom__NodeList.Impl({type nonrec t = t;}); +include Impl({type nonrec t = t;}); diff --git a/src/Webapi/ResizeObserver/Webapi__ResizeObserver__ResizeObserverEntry.re b/src/Webapi/ResizeObserver/Webapi__ResizeObserver__ResizeObserverEntry.re index ca235441..4b1acceb 100644 --- a/src/Webapi/ResizeObserver/Webapi__ResizeObserver__ResizeObserverEntry.re +++ b/src/Webapi/ResizeObserver/Webapi__ResizeObserver__ResizeObserverEntry.re @@ -1,4 +1,4 @@ -type t; +type t; // TODO: Dom.resizeObserverEntry; [@bs.get] external contentRect: t => Dom.domRect = ""; [@bs.get] external target: t => Dom.element = ""; diff --git a/src/Webapi/Webapi__Dom.re b/src/Webapi/Webapi__Dom.re index 8d658660..3e2e13d5 100644 --- a/src/Webapi/Webapi__Dom.re +++ b/src/Webapi/Webapi__Dom.re @@ -24,11 +24,19 @@ module EventTarget = Webapi__Dom__EventTarget; module FocusEvent = Webapi__Dom__FocusEvent; module History = Webapi__Dom__History; module HtmlCollection = Webapi__Dom__HtmlCollection; +module HtmlFormControlsCollection = Webapi__Dom__HtmlFormControlsCollection; +module HtmlOptionsCollection = Webapi__Dom__HtmlOptionsCollection; module HtmlDocument = Webapi__Dom__HtmlDocument; module HtmlElement = Webapi__Dom__HtmlElement; +module HtmlButtonElement = Webapi__Dom__HtmlButtonElement; +module HtmlFieldSetElement = Webapi__Dom__HtmlFieldSetElement; module HtmlFormElement = Webapi__Dom__HtmlFormElement; module HtmlImageElement = Webapi__Dom__HtmlImageElement; module HtmlInputElement = Webapi__Dom__HtmlInputElement; +module HtmlObjectElement = Webapi__Dom__HtmlObjectElement; +module HtmlOutputElement = Webapi__Dom__HtmlOutputElement; +module HtmlSelectElement = Webapi__Dom__HtmlSelectElement; +module HtmlTextAreaElement = Webapi__Dom__HtmlTextAreaElement; module IdbVersionChangeEvent = Webapi__Dom__IdbVersionChangeEvent; module Image = Webapi__Dom__Image; module InputEvent = Webapi__Dom__InputEvent; @@ -42,6 +50,7 @@ module Node = Webapi__Dom__Node; module NodeFilter = Webapi__Dom__NodeFilter; module NodeIterator = Webapi__Dom__NodeIterator; module NodeList = Webapi__Dom__NodeList; +module RadioNodeList = Webapi__Dom__RadioNodeList; module PageTransitionEvent = Webapi__Dom__PageTransitionEvent; module PointerEvent = Webapi__Dom__PointerEvent; module PopStateEvent = Webapi__Dom__PopStateEvent; @@ -175,11 +184,7 @@ include Webapi__Dom__Types; CanvasPixelArray NotifyAudioAvailableEvent HTMLAllCollection - HTMLFormControlsCollection - HTMLOptionsCollection HTMLPropertiesCollection - DOMStringMap - RadioNodeList MediaError /* SVG Element interfaces */ diff --git a/src/Webapi/Webapi__ResizeObserver.re b/src/Webapi/Webapi__ResizeObserver.re index 7ea6f104..d44c59a3 100644 --- a/src/Webapi/Webapi__ResizeObserver.re +++ b/src/Webapi/Webapi__ResizeObserver.re @@ -1,6 +1,6 @@ module ResizeObserverEntry = Webapi__ResizeObserver__ResizeObserverEntry; -type t; +type t; // TODO: Dom.resizeObserver; [@bs.new] external make: (array(ResizeObserverEntry.t) => unit) => t = "ResizeObserver"; diff --git a/tests/Webapi/Dom/Webapi__Dom__HtmlFormElement__test.re b/tests/Webapi/Dom/Webapi__Dom__HtmlFormElement__test.re index fe0ab98e..aa1b4038 100644 --- a/tests/Webapi/Dom/Webapi__Dom__HtmlFormElement__test.re +++ b/tests/Webapi/Dom/Webapi__Dom__HtmlFormElement__test.re @@ -1,4 +1,172 @@ +open Webapi.Dom; open Webapi.FormData; open Webapi.Dom.HtmlFormElement; +let createElement = Document.createElement(_, document); +let createTextNode = Document.createTextNode(_, document); +let createInput = () => createElement("input"); +let createLabelWithText = (text) => { + let el = createElement("label"); + let textNode = createTextNode(text); + Element.appendChild(textNode, el); + el; +}; + +let form = createElement("form") |> asFormElement |> TestHelpers.unsafelyUnwrapOption; + +let usernameInput = createInput(); +Element.setAttribute("type", "text", usernameInput); +Element.setAttribute("name", "username", usernameInput); + +let usernameLabel = createLabelWithText("Username:"); +Element.appendChild(usernameInput, usernameLabel); + +let passwordInput = createInput(); +Element.setAttribute("type", "password", passwordInput); +Element.setAttribute("name", "password", passwordInput); + +let passwordLabel = createLabelWithText("Password:"); +Element.appendChild(passwordInput, passwordLabel); + +let radioInput1 = createInput(); +Element.setAttribute("type", "radio", radioInput1); +Element.setAttribute("name", "radiogroup", radioInput1); +Element.setAttribute("value", "one", radioInput1); +Element.setAttribute("checked", "true", radioInput1); + +let radioLabel1 = createLabelWithText("Choice 1:"); +Element.appendChild(radioInput1, radioLabel1); + +let radioInput2 = createInput(); +Element.setAttribute("type", "radio", radioInput2); +Element.setAttribute("name", "radiogroup", radioInput2); +Element.setAttribute("value", "two", radioInput2); +// Element.setAttribute("checked", "true", radioInput2); + +let radioLabel2 = createLabelWithText("Choice 2:"); +Element.appendChild(radioInput2, radioLabel2); + +let select = createElement("select"); +Element.setAttribute("name", "select", select); +let selectLabel = createLabelWithText("Select:"); +Element.appendChild(select, selectLabel); + +let usernameContainer = createElement("div"); +let passwordContainer = createElement("div"); +let radioContainer = createElement("div"); +let selectContainer = createElement("div"); + +let formEl = form->asElement; + +Element.appendChild(usernameLabel, usernameContainer); +Element.appendChild(passwordLabel, passwordContainer); +Element.appendChild(radioLabel1, radioContainer); +Element.appendChild(radioLabel2, radioContainer); +Element.appendChild(selectLabel, selectContainer); +Element.appendChild(usernameContainer, formEl); +Element.appendChild(passwordContainer, formEl); +Element.appendChild(radioContainer, formEl); +Element.appendChild(selectContainer, formEl); + +let body = + Document.asHtmlDocument(document)->Belt.Option.flatMap(HtmlDocument.body)->TestHelpers.unsafelyUnwrapOption; + +Element.appendChild(formEl, body); + +let collection = elements(form); + +Js.log2("HtmlFormElement.elements:", collection); + +let len = HtmlFormControlsCollection.length(collection); +Js.log2("HtmlFormControlsCollection.length:", len); + +let el0 = HtmlFormControlsCollection.item(0, collection); +Js.log2("HtmlFormControlsCollection.item:", el0); + +let el0 = HtmlFormControlsCollection.namedItem("username", collection); +Js.log2("HtmlFormControlsCollection.namedItem:", el0); + +let el1 = HtmlFormControlsCollection.item(1, collection); +Js.log2("HtmlFormControlsCollection.length:", el1); + +let el1 = HtmlFormControlsCollection.namedItem("password", collection); +Js.log2("HtmlFormControlsCollection.namedItem:", el1); + +let radioNodeList = HtmlFormControlsCollection.item(2, collection); +Js.log2("HtmlFormControlsCollection.namedItem:", radioNodeList); + +let radioNodeList = HtmlFormControlsCollection.namedItem("radiogroup", collection); +Js.log2("HtmlFormControlsCollection.namedItem:", radioNodeList); + +switch (TestHelpers.unsafelyUnwrapOption(radioNodeList)) { +| `Button(_) +| `FieldSet(_) +| `Input(_) +| `Object(_) +| `Output(_) +| `Select(_) +| `TextArea(_) => Js.Exn.raiseError("incorrect namedItem return value") +| `RadioNodeList(radioNodeList) => Js.log2("RadioNodeList.value", RadioNodeList.value(radioNodeList)) +| _ => () +}; + +let select = HtmlSelectElement.ofElement(select) + ->TestHelpers.unsafelyUnwrapOption; + +let opts = HtmlSelectElement.options(select); +Js.log2("HtmlSelectElement.options:", opts); + +HtmlOptionsCollection.setLength(opts, 3); +Js.log2("collection length:", HtmlOptionsCollection.length(opts)); +HtmlOptionsCollection.clearItem(opts, 0, Js.Null.empty); +Js.log2("collection length:", HtmlOptionsCollection.length(opts)); +HtmlOptionsCollection.setItem(opts, 2, createElement("option")); +Js.log2("collection length:", HtmlOptionsCollection.length(opts)); + +HtmlOptionsCollection.setLength(opts, 0); + +let opt1 = createElement("option"); +Element.setAttribute("value", "1", opt1); +Element.appendChild(createTextNode("opt1"), opt1); + +HtmlOptionsCollection.add(opt1, opts); +let selectedIndex = HtmlOptionsCollection.setSelectedIndex(opts, 0); +Js.log2("collection length:", HtmlOptionsCollection.length(opts)); +Js.log2("HtmlOptionsCollection.setSelectedIndex", selectedIndex); + +let opt2 = createElement("option"); +Element.setAttribute("value", "2", opt2); +Element.appendChild(createTextNode("opt2"), opt2); + +let item = HtmlOptionsCollection.item(0, opts); +Js.log2("HtmlOptionsCollection.item:", item); +Js.log2("collection length:", HtmlOptionsCollection.length(opts)); + +HtmlOptionsCollection.addBefore(opt2, 0, opts); +HtmlOptionsCollection.setSelectedElement(opts, opt2); +let item = HtmlOptionsCollection.item(0, opts); +Js.log2("HtmlOptionsCollection.addBefore:", item); +Js.log2("collection length:", HtmlOptionsCollection.length(opts)); +Js.log2("selected index", HtmlOptionsCollection.selectedIndex(opts)); + +let opt3 = createElement("option"); +Element.setAttribute("value", "3", opt3); +Element.appendChild(createTextNode("opt3"), opt3); + +HtmlOptionsCollection.addBeforeElement(opt3, opt2, opts); +let item = HtmlOptionsCollection.item(0, opts); +Js.log2("HtmlOptionsCollection.addBeforeElement:", item); +Js.log2("collection length:", HtmlOptionsCollection.length(opts)); + +let item = HtmlOptionsCollection.selectedIndex(opts); +Js.log2("HtmlOptionsCollection.selectedIndex:", item); + +let item = HtmlOptionsCollection.setSelectedElement(opts, opt3); +Js.log2("HtmlOptionsCollection.setSelectedElement:", item); +let item = HtmlOptionsCollection.selectedIndex(opts); +Js.log2("HtmlOptionsCollection.selectedIndex:", item); + +HtmlOptionsCollection.remove(0, opts); +Js.log2("collection length:", HtmlOptionsCollection.length(opts)); + let test_data = formElement => formElement |> data |> get("foo");