mirror of https://github.com/yewstack/yew
242 lines
8.1 KiB
Plaintext
242 lines
8.1 KiB
Plaintext
import Tabs from '@theme/Tabs';
|
|
import TabItem from '@theme/TabItem';
|
|
|
|
The [`web-sys` crate](https://crates.io/crates/web-sys) provides bindings for Web APIs. This is
|
|
procedurally generated from browser WebIDL which is why some of the names are so long and why
|
|
some of the types are vague.
|
|
|
|
## Features in `web-sys`
|
|
|
|
The `web-sys` crate with all of it's features enabled can add lots of bloat to a Wasm application,
|
|
in order to get around this issue most types are feature gated so that you only include the types
|
|
you require for your application. Yew includes a number of features from `web-sys` and
|
|
exposes some types in it's public API, you will often need to add `web-sys` as a dependency yourself.
|
|
|
|
## Inheritance in `web-sys`
|
|
|
|
In the [Simulating inheritance section](../wasm-bindgen#simulating-inheritance) 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 it's inheritance.
|
|
|
|
This section is going to look at a specific element and list out it's inheritance using Rust by
|
|
calling [`Deref::deref`](https://doc.rust-lang.org/std/ops/trait.Deref.html#tymethod.deref) until
|
|
the value is [`JsValue`](../wasm-bindgen#jsvalue):
|
|
|
|
```rust
|
|
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've 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've 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](https://rustwasm.github.io/wasm-bindgen/web-sys/inheritance.html)._
|
|
|
|
## The `Node` in `NodeRef`
|
|
|
|
Yew uses a [`NodeRef`](../components/refs) in order to provide a way for keeping a reference to
|
|
a `Node` made by the [`html!`](../html/introduction.mdx) macro. The `Node` part of `NodeRef` is referring to
|
|
[`web_sys::Node`](https://rustwasm.github.io/wasm-bindgen/api/web_sys/struct.Node.html). 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 it's specific methods. This casting
|
|
can be done using [`JsCast`](../wasm-bindgen#JsCast) on the `Node` value, if present, but Yew
|
|
provides the `NodeRef::cast` method to perform this casting for convenience and so that you don't
|
|
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`](https://rustwasm.github.io/wasm-bindgen/api/wasm_bindgen/trait.JsCast.html#method.dyn_into)
|
|
on the `web_sys::Node` returned from `NodeRef::get`.
|
|
|
|
<Tabs>
|
|
<TabItem value="Using NodeRef::cast" label="Using NodeRef::cast">
|
|
|
|
```rust
|
|
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
|
|
}
|
|
}
|
|
```
|
|
|
|
</TabItem>
|
|
<TabItem value="Using NodeRef::get" label="Using NodeRef::get">
|
|
|
|
```rust
|
|
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
|
|
}
|
|
}
|
|
```
|
|
|
|
</TabItem>
|
|
</Tabs>
|
|
|
|
|
|
## JavaScript example to Rust
|
|
|
|
This section is to help show that any examples that use JavaScript to interact with the Web APIs
|
|
can be adapted and written using Rust with `web-sys`.
|
|
|
|
### JavaScript example
|
|
|
|
```js
|
|
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 JavaSciprt example could be implemented like this:
|
|
|
|
```toml title=Cargo.toml
|
|
[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",
|
|
]
|
|
```
|
|
|
|
```rust ,no_run
|
|
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 otherwise will
|
|
cause a panic in Rust. Another part of the verbosity is the calls to `JsCast` in order to cast into
|
|
different types so that you can call it's specific methods.
|
|
|
|
### Yew example
|
|
|
|
In Yew you will mostly be creating [`Callback`](../components/callbacks)s to use in the
|
|
[`html!`](../html/introduction) macro so the example is going to use this approach instead of completely copying
|
|
the approach above:
|
|
|
|
```toml title=Cargo.toml
|
|
[dependencies.web-sys]
|
|
version = "0.3"
|
|
# We need to enable the `DomRect` feature in order to use the
|
|
# `get_bounding_client_rect` method.
|
|
features = [
|
|
"console",
|
|
"HtmlElement",
|
|
"MouseEvent",
|
|
"DomRect",
|
|
]
|
|
|
|
```
|
|
|
|
```rust
|
|
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 come in to
|
|
provide abstractions over `web-sys` to provide more idiomatic Rust APIs.
|
|
|
|
_[External libraries page](../../more/external-libs)_
|