Experimental bindings to the DOM and other Web APIs.
The bindings are currently undocumented, but as the code mostly just consists of external declarations with type signatures, the code itself is fairly self-documenting. The bindings generally also correspond very well to the Web APIs they bind to, so using MDN along with GitHub should go a long way.
npm install bs-webapi
Then add bs-webapi
to bs-dependencies
in your bsconfig.json
. A minimal example:
{
"name": "my-thing",
"sources": "src",
"bs-dependencies": ["bs-webapi"]
}
See the examples folder
Please only use the modules exposed through the toplevel module Webapi
, for example Webapi.Dom.Element
. In particular, don't use the 'flat' modules like Webapi__Dom__Element
as these are considered private and are not guaranteed to be backwards-compatible.
The DOM API is mostly organized into interfaces and relies heavily on inheritance. The ergonomics of the API is also heavily dependent on dynamic typing, which makes it somewhat challenging to implement a thin binding layer that is both safe and ergonomic. To achieve this we employ subtyping and implementation inheritance, concepts which aren't very idiomatic to OCaml (or Reason), but all the more beneficial to understand in order to be able to use these bindings effectively.
The Dom types, and the relationships between them, are actually defined in the Dom
module that ships with bs-platform
(Source code), where you'll find a bunch of types that look like this:
type _element('a);
type element_like('a) = node_like(_element('a));
type element = element_like(_baseClass);
This is subtyping implemented with abstract types and phantom arguments. The details of how this works isn't very important (but see #23 for a detailed explanation of how exactly this trickery works) in order to just use them, but there are a few things you should know:
- If you expand the alias of a concrete DOM type, you'll discover it's actually a list of abstract types. e.g.
element
expands to_baseClass _element _node _eventTarget_like
This meanselement
is a subtype of_element
,_node
and_eventTarget_like
. - The
_like
type are "open" (because they have a type variable). This means that a function accepting an'a element_like
will accept any "supertype" ofelement_like
. A function accepting just anelement
will only accept anelement
(Technicallyelement
is actually a "supertype" ofelement_like
too).
This system works exceptionally well, but has one significant flaw: It makes type errors even more complicated than they normally are. If you know what to look for it's not that bad, but unfortunately the formatting of these errors don't make looking for it any easier. We hope to improve that in other ways (see BetterErrors)
If you've looked through the source code a bit, you've likely come across code like this:
include Webapi__Dom__EventTarget.Impl({ type nonrec t = t });
include Webapi__Dom__Node.Impl({ type nonrec t = t });
include Webapi__Dom__ParentNode.Impl({ type nonrec t = t });
include Webapi__Dom__NonDocumentTypeChildNode.Impl({ type nonrec t = t });
include Webapi__Dom__ChildNode.Impl({ type nonrec t = t });
include Webapi__Dom__Slotable.Impl({ type nonrec t = t });
include Impl({ type nonrec t = t });
This is the implementation inheritance. Each "inheritable" module defines an "Impl" module where all its exported functions are defined. include Webapi__Dom__Node.Impl { type nonrec t = t };
means that all the functions in Webapi__Dom__Node.Impl
should be included in this module, but with the t
type of that module replaced by the t
type of this one. And that's it, it now has all the functions.
Implementation inheritance is used instead of subtyping to make it easier to understand which functions operate on any given "subject". If you have an element
and you need to use a function defined in Node
, let's say removeChild
you cannot just use Node.removeChild
. Instead you need to use Element.removeChild
, which you can since Element
inherits from Node
. As a general rule, always use the function in the module corresponding to the type you have. You'll find this makes it very easy to see what types you're dealing with just by reading the code.
- Added
File.size
- Added
URLSearchParams.forEach
- Added
Element.scrollBy
andElement.srollByWithOptions
- (Breaking) Changed
DomRect
coordinates to usefloat
instead ofint
- Added
File.name
- Added
width
,height
,setWidth
andsetHeight
toCanvasElement
- Removed deprecated
Webapi.Dom.onload
function - Removed deprecated
Webapi.File.Url
module alias - Restructured internal module layout (non-breaking for modules accessed through the
Webapi
top-level module, but breaking if internal*Re
modules have been accessed directly) - Enforce private modules (effectively a formality and non-breaking)
- Added
HtmlElement.focusPreventScroll
- Refined return type of
Node.cloneNode
andNode.cloneDeepNode
so it now returns the specific type of the cloned node.
- Added
Element.scrollTo
,Element.scrollToWithOptions
- Added
URLSearchParams.makeWithDict
andURLSearchParams.makeWithArray
- Added
bs.return nullable
toURLSearchParams.get
since it returnsnull
, notundefined
and therefore does not autmatically conform to the runtime representation ofoption
as previosuly assumed.
- Fixed signature of
NamedNodeMap.toArray
, which returnedelement
but should returnattr
(considere non-breaking since it was just plain wrong) - Added
add...
andremovePopStateEventListener
toWindow
- Added
add...
andremove...
functions for touch and animation event listeners toEventTarget
- Added
add...
andremove...
functions for drag event listeners toEventTarget
- (Breaking) Requires bs-platform > 4.0.0
- (Breaking) Changed
FocusEvent.relatedTarget
to returnoption
- Added
HtmlFormElement
andHtmlInputElement
- (Breaking) Fixed return type if
StorageEvent.oldValue
andStorageEvent.newValue
. They should benullable
, but were not. - Added
Url
andUrlSearchParams
- Deprecated
Webapi.File.Url
in favor ofWebapi.Url
EventTarget.dispatchEvent
now take aDom.event_like(_)
instead of justDom.event
, so it will accept any event subtype.Window.pageXOffset
,pageYOffset
,scrollX
,scrollY
,scrollLeft
andscrollTop
now returnfloat
s instead ofint
s, andWindow.scroll
,scrollBy
,scrollTo
,setScrollLeft
andsetScrollTop
takefloat
s instead ofint
sHtmlElement.offsetParent
now returns anoption
Selection.anchorNode
andSelection.focusNode
now returnoption
sElement.closest
now returns anoption
- Added inheritance of
HtmlElement
and its ancestors toHtmlImageElement
- Deprecated
HtmlImageElement.onload
- Fixed inconsistencies with
HtmlImageElement.src
andHtmlImageElement.getSrc
, breaking the API - Fleshed out
HtmlImageElement
- Renamed
Document.docType
toDocument.doctype
to fix #95
- Support
[email protected]
. If your app isn't using that version, then don't upgrade to0.9.0
; otherwise, please do!
- Added
EventTarget.unsafeAsDocument
,EventTarget.unsafeAsElement
andEventTarget.unsafeAsWindow
functions - Removed deprecated
Bs_webapi
module` - Added event-specific listener APIs to
EventTarget
, e.g.EventTarget.addMouseMoveListener(mouseEvent => ...)
- Added
requestCancellableAnimationFrame
andcancelAnimationFrame
- Fixed msising
@bs.return
annotations causing type unsoundness - Fixed typo in encoding of
insertPosition
type - Added
Dom.HtmlImageElement
,File
andFile.Url
- Fixed
HtmlElement.offsetParent
returningint
instead ofDom.Element
- Added
Webapi
module, DeprecatedBs_webapi
- Removed deprecated Storage API
- Add
Document.unsafeAshtmlDocument
,Element.unsafeAsHtmlElement
. DeprecatedDocument.asHtmlDocument
,Element.asHtmlElement
,HtmlEleement.ofElement
. - Changed
Dom.history
andDom.location
to usewindow
instead ofdocument
- Fix incorrect heuristic in
HtmlElement.ofElement
- Renamed createText to CreateTextNode, according to spec
- Deprecated Storage API, it's been upstreamed to
bs-platform
asDom.Storage
- Removed
ReasonJs
namespace. UseBs_webapi
instead