web-sys
The web-sys
crate provides bindings for Web APIs. This is
procedurally generated from browser WebIDL which is why some names are so long and why some types are vague.
Features in web-sys
The web-sys
crate with all of its features enabled can add lots of bloat to a Wasm application.
To get around this issue most types are feature gated so that you only include the types
you require for your application. Yew enables several features from web-sys
and
exposes some types in its public API. You will often need to add web-sys
as a dependency yourself.
Inheritance in web-sys
In the Simulating inheritance section you can read how in
general Rust provides an approach to simulate inheritance in JavaScript. This is very important in
web-sys
as understanding what methods are available on a type means understanding its inheritance.
This section is going to look at a specific element and list out its inheritance using Rust by
calling Deref::deref
until
the value is JsValue
:
use std::ops::Deref;
use web_sys::{
Element,
EventTarget,
HtmlElement,
HtmlTextAreaElement,
Node,
};
fn inheritance_of_text_area(text_area: HtmlTextAreaElement) {
// HtmlTextAreaElement is <textarea> in html.
let html_element: &HtmlElement = text_area.deref();
let element: &Element = html_element.deref();
let node: &Node = element.deref();
let event_target: &EventTarget = node.deref();
// Notice we have moved from web-sys types now into built-in
// JavaScript types which are in the js-sys crate.
let object: &js_sys::Object = event_target.deref();
// Notice we have moved from js-sys type to the root JsValue from
// the wasm-bindgen crate.
let js_value: &wasm_bindgen::JsValue = object.deref();
// Using deref like this means we have to manually traverse
// the inheritance tree, however, you can call JsValue methods
// on the HtmlTextAreaElement type.
// The `is_string` method comes from JsValue.
assert!(!text_area.is_string());
// empty function just to prove we can pass HtmlTextAreaElement as a
// &EventTarget.
fn this_function_only_takes_event_targets(targets: &EventTarget) {};
// The compiler will walk down the deref chain in order to match the types here.
this_function_only_takes_event_targets(&text_area);
// The AsRef implementations allow you to treat the HtmlTextAreaElement
// as an &EventTarget.
let event_target: &EventTarget = text_area.as_ref();
}
Inheritance in web-sys
in The wasm-bindgen
Guide.
The Node
in NodeRef
Yew uses a NodeRef
to provide a way for keeping a reference to
a Node
made by the html!
macro. The Node
part of NodeRef
is referring to
web_sys::Node
. The
NodeRef::get
method will return a Option<Node>
value, however, most of the time in Yew you want
to cast this value to a specific element so you can use its specific methods. This casting
can be done using JsCast
on the Node
value, if present, but Yew
provides the NodeRef::cast
method to perform this casting for convenience and so that you do not
necessarily have to include the wasm-bindgen
dependency for the JsCast
trait.
The two code blocks below do essentially the same thing, the first is using NodeRef::cast
and
the second is using JsCast::dyn_into
on the web_sys::Node
returned from NodeRef::get
.
- Using NodeRef::cast
- Using NodeRef::get
use web_sys::HtmlInputElement;
use yew::NodeRef;
fn with_node_ref_cast(node_ref: NodeRef) {
if let Some(input) = node_ref.cast::<HtmlInputElement>() {
// do something with HtmlInputElement
}
}
use wasm_bindgen::JsCast;
use web_sys::HtmlInputElement;
use yew::NodeRef;
fn with_jscast(node_ref: NodeRef) {
if let Some(input) = node_ref
.get()
.and_then(|node| node.dyn_into::<HtmlInputElement>().ok()) {
// do something with HtmlInputElement
}
}
JavaScript example to Rust
This section demonstrates examples of how JavaScript code which interact with the
Web APIs can be rewritten with web-sys
in Rust.
JavaScript example
document.getElementById('mousemoveme').onmousemove = (e) => {
// e = Mouse event.
var rect = e.target.getBoundingClientRect()
var x = e.clientX - rect.left //x position within the element.
var y = e.clientY - rect.top //y position within the element.
console.log('Left? : ' + x + ' ; Top? : ' + y + '.')
}
web-sys
example
Using web-sys
alone the above JavaScript example could be implemented like this:
[dependencies]
wasm-bindgen = "0.2"
[dependencies.web-sys]
version = "0.3"
# We need to enable all the web-sys features we want to use!
features = [
"console",
"Document",
"HtmlElement",
"MouseEvent",
"DomRect",
]
use wasm_bindgen::{prelude::Closure, JsCast};
use web_sys::{console, Document, HtmlElement, MouseEvent};
let mousemove = Closure::<dyn Fn(MouseEvent)>::wrap(Box::new(|e| {
let rect = e
.target()
.expect("mouse event doesn't have a target")
.dyn_into::<HtmlElement>()
.expect("event target should be of type HtmlElement")
.get_bounding_client_rect();
let x = (e.client_x() as f64) - rect.left();
let y = (e.client_y() as f64) - rect.top();
console::log_1(&format!("Left? : {} ; Top? : {}", x, y).into());
}));
Document::new()
.expect("global document not set")
.get_element_by_id("mousemoveme")
.expect("element with id `mousemoveme` not present")
.unchecked_into::<HtmlElement>()
.set_onmousemove(mousemove.as_ref().dyn_ref());
// we now need to save the `mousemove` Closure so that when
// this event fires the closure is still in memory.
This version is much more verbose, but you will probably notice part of that is because of failure
types reminding us that some of these function calls have invariants that must be held, or otherwise will
cause a panic in Rust. Another part of the verbosity is the calls to JsCast
to cast into
different types so that you can call its specific methods.
Yew example
In Yew you will mostly be creating Callback
s to use in the
html!
macro so the example is going to use this approach instead of completely copying
the approach above:
[dependencies.web-sys]
version = "0.3"
# We need to enable the `DomRect` feature to use the
# `get_bounding_client_rect` method.
features = [
"console",
"HtmlElement",
"MouseEvent",
"DomRect",
]
use web_sys::{console, HtmlElement, MouseEvent};
use yew::{
html,
Callback, TargetCast,
};
let onmousemove = Callback::from(|e: MouseEvent| {
if let Some(target) = e.target_dyn_into::<HtmlElement>() {
let rect = target.get_bounding_client_rect();
let x = (e.client_x() as f64) - rect.left();
let y = (e.client_y() as f64) - rect.top();
console::log_1(&format!("Left? : {} ; Top? : {}", x, y).into());
}
});
html! {
<div id="mousemoveme" {onmousemove}></div>
};
External libraries
web-sys
is a raw binding to the Web API so it comes with some pain in Rust because it was not
designed with Rust or even a strong type system in mind, this is where community crates
provide abstractions over web-sys
to provide more idiomatic Rust APIs.