Move the document trait into a separate crate (#3035)

* add a default head method through eval
* remove the old document trait
* implement document for each platform
* pull out document into a dedicated crate to cut down on shared dependencies
---------

Co-authored-by: Jonathan Kelley <jkelleyrtp@gmail.com>
This commit is contained in:
Evan AlmloffandJonathan Kelley authored and GitHub committed 2024-10-14 10:33:37 -07:00
1 parent 2b219c826c
commit 519ec9d294
91 files changed
+1113 -1044

No files matched your search

+125
View File
@@ -0,0 +1,125 @@
# Communicating with JavaScript
You can use the `eval` function to execute JavaScript code in your application with the desktop, mobile, web or liveview renderers. Eval takes a block of JavaScript code (that may be asynchronous) and returns a `Eval` object that you can use to send data to the JavaScript code and receive data from it.
<div class="warning">
## Safety
Please be careful when executing JavaScript code with `eval`. You should only execute code that you trust. **This applies especially to web targets, where the JavaScript context has access to most, if not all of your application data.** Running untrusted code can lead to a [cross-site scripting](https://developer.mozilla.org/en-US/docs/Glossary/Cross-site_scripting) (XSS) vulnerability.
</div>
```rust
use dioxus::prelude::*;
fn App() -> Element {
rsx! {
button {
onclick: move |_| async move {
// Eval is a global function you can use anywhere inside Dioxus. It will execute the given JavaScript code.
let result = document::eval(r#"console.log("Hello World");
return "Hello World";"#);
// You can use the `await` keyword to wait for the result of the JavaScript code.
println!("{:?}", result.await);
},
"Log Hello World"
}
}
}
```
## Sending data to JavaScript
When you execute JavaScript code with `eval`, you can pass data to it by formatting the value into the JavaScript code or sending values to the `Eval` channel.
```rust
use dioxus::prelude::*;
fn app() -> Element {
rsx! {
button {
onclick: move |_| {
// You can pass initial data to the eval function by formatting it into the JavaScript code.
const LOOP_COUNT: usize = 10;
let eval = document::eval(&format!(r#"for(let i = 0; i < {LOOP_COUNT}; i++) {{
// You can receive values asynchronously with the the `await dioxus.recv()` method.
let value = await dioxus.recv();
console.log("Received", value);
}}"#));
// You can send values from rust to the JavaScript code with the `send` method on the object returned by `eval`.
for i in 0..LOOP_COUNT {
eval.send(i).unwrap();
}
},
"Log Count"
}
}
}
```
## Sending data from JavaScript
The `Eval` struct also contains methods for receiving values you send from JavaScript. You can use the `dioxus.send()` method to send values to the JavaScript code and the `Eval::recv()` method to receive values from the JavaScript code.
```rust
use dioxus::prelude::*;
fn app() -> Element {
rsx! {
button {
onclick: move |_| async move {
// You can send values from rust to the JavaScript code by using the `send` method on the object returned by `eval`.
let mut eval = document::eval(r#"for(let i = 0; i < 10; i++) {
// You can send values asynchronously with the `dioxus.send()` method.
dioxus.send(i);
}"#);
// You can receive values from the JavaScript code with the `recv` method on the object returned by `eval`.
for _ in 0..10 {
let value: i32 = eval.recv().await.unwrap();
println!("Received {}", value);
}
},
"Log Count"
}
}
}
```
## Interacting with the DOM with Eval
You can also use the `eval` function to execute JavaScript code that reads or modifies the DOM. If you want to interact with the mounted DOM, you need to use `eval` inside the [`dioxus_hooks::use_effect`] hook which runs after the component has been mounted.
```rust
use dioxus::prelude::*;
const SCRIPT: &str = r#"
let element = document.getElementById("my-element");
element.innerHTML = "Hello World";
return element.getAttribute("data-count");
"#;
fn app() -> Element {
// ❌ You shouldn't run eval in the body of a component. This will run before the component has been mounted
// document::eval(SCRIPT);
// ✅ You should run eval inside an effect or event. This will run after the component has been mounted
use_effect(move || {
spawn(async {
let count = document::eval(SCRIPT).await;
println!("Count is {:?}", count);
});
});
rsx! {
div {
id: "my-element",
"data-count": "123",
}
}
}
```
+59
View File
@@ -0,0 +1,59 @@
# Modifying the Head
Dioxus includes a series of components that render into the head of the page:
- [Title](crate::Title)
- [Meta](crate::Meta)
- [document::Link](crate::document::Link)
- [Script](crate::Script)
- [Style](crate::Style)
Each of these components can be used to add extra information to the head of the page. For example, you can use the `Title` component to set the title of the page, or the `Meta` component to add extra metadata to the page.
## Limitations
Components that render into the head of the page do have a few key limitations:
- With the exception of the `Title` component, all components that render into the head cannot be modified after the first time they are rendered.
- Components that render into the head will not be removed even after the component is removed from the tree.
## Example
```rust, no_run
# use dioxus::prelude::*;
fn RedirectToDioxusHomepageWithoutJS() -> Element {
rsx! {
// You can use the meta component to render a meta tag into the head of the page
// This meta tag will redirect the user to the dioxuslabs homepage in 10 seconds
document::Meta {
http_equiv: "refresh",
content: "10;url=https://dioxuslabs.com",
}
}
}
```
## Fullstack Rendering
Head components are compatible with fullstack rendering, but only head components that are rendered in the initial render (before suspense boundaries resolve) will be rendered into the head.
If you have any important metadata that you want to render into the head, make sure to render it outside of any pending suspense boundaries.
```rust, no_run
# use dioxus::prelude::*;
# #[component]
# fn LoadData(children: Element) -> Element { unimplemented!() }
fn App() -> Element {
rsx! {
// This will render in SSR
document::Title { "My Page" }
SuspenseBoundary {
fallback: |_| rsx! { "Loading..." },
LoadData {
// This will only be rendered on the client after hydration so it may not be visible to search engines
document::Meta { name: "description", content: "My Page" }
}
}
}
}
```