mirror of
https://github.com/yewstack/yew.git
synced 2025-12-08 21:26:25 +00:00
Add testing for website code blocks (#2014)
* Add doc-test to test website code snippets Heavily inspired by tokio-rs/website repo. * Fix code snippets to pass doc tests Some code snippets are explicitly ignored and some are not run to avoid having to include dependencies for one liners. * Add website code snippet tests to CI * Fix CI * Remove doc-test from workspace * Exclude doc-test from workspace * Refactor code snippets and tests Code snippets can import types from doc_test crate i.e.: ```rust use doc_test::agents::EventBus; ``` This allows for moving some boilerplate away from the example and still checks that the code compiles correctly. Also some slight changes to some of the examples and the information about `ComponentLink` which is deprecated. * Move doc-test to packages * Rename doc-test crate to website-test The new name makes it more clear the purpose of this crate. * fix ci
This commit is contained in:
parent
bd8ecf5d5e
commit
ed2e1ea00e
5
.github/workflows/pull-request.yml
vendored
5
.github/workflows/pull-request.yml
vendored
@ -105,6 +105,11 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
cd packages/yew
|
cd packages/yew
|
||||||
cargo test --doc --features "doc_test wasm_test"
|
cargo test --doc --features "doc_test wasm_test"
|
||||||
|
|
||||||
|
- name: Run website code snippet tests
|
||||||
|
run: |
|
||||||
|
cd packages/website-test
|
||||||
|
cargo test
|
||||||
|
|
||||||
integration_tests:
|
integration_tests:
|
||||||
name: Integration Tests on ${{ matrix.toolchain }}
|
name: Integration Tests on ${{ matrix.toolchain }}
|
||||||
|
|||||||
@ -35,3 +35,6 @@ members = [
|
|||||||
# Release tools
|
# Release tools
|
||||||
"packages/changelog",
|
"packages/changelog",
|
||||||
]
|
]
|
||||||
|
exclude = [
|
||||||
|
"packages/website-test",
|
||||||
|
]
|
||||||
|
|||||||
@ -37,7 +37,7 @@ category = "Testing"
|
|||||||
description = "Run all tests"
|
description = "Run all tests"
|
||||||
dependencies = ["tests-setup"]
|
dependencies = ["tests-setup"]
|
||||||
env = { CARGO_MAKE_WORKSPACE_SKIP_MEMBERS = ["**/examples/*", "**/packages/changelog"] }
|
env = { CARGO_MAKE_WORKSPACE_SKIP_MEMBERS = ["**/examples/*", "**/packages/changelog"] }
|
||||||
run_task = { name = ["test-flow", "doc-test-flow"], fork = true }
|
run_task = { name = ["test-flow", "doc-test-flow", "website-test"], fork = true }
|
||||||
|
|
||||||
[tasks.benchmarks]
|
[tasks.benchmarks]
|
||||||
category = "Testing"
|
category = "Testing"
|
||||||
@ -86,6 +86,14 @@ private = true
|
|||||||
command = "cargo"
|
command = "cargo"
|
||||||
args = ["test", "--doc"]
|
args = ["test", "--doc"]
|
||||||
|
|
||||||
|
[tasks.website-test]
|
||||||
|
script = [
|
||||||
|
"""
|
||||||
|
cd packages/website-test
|
||||||
|
cargo test
|
||||||
|
"""
|
||||||
|
]
|
||||||
|
|
||||||
[tasks.bench-flow]
|
[tasks.bench-flow]
|
||||||
private = true
|
private = true
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|||||||
20
packages/website-test/Cargo.toml
Normal file
20
packages/website-test/Cargo.toml
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
[package]
|
||||||
|
name = "website-test"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2018"
|
||||||
|
build = "build.rs"
|
||||||
|
publish = false
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
yew-agent = { path = "../../packages/yew-agent/" }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
yew = { path = "../../packages/yew/" }
|
||||||
|
yew-router = { path = "../../packages/yew-router/" }
|
||||||
|
derive_more = "0.99"
|
||||||
|
boolinator = "2.4"
|
||||||
|
weblog = "0.3.0"
|
||||||
|
wasm-bindgen = "0.2"
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
glob = "0.3"
|
||||||
5
packages/website-test/Makefile.toml
Normal file
5
packages/website-test/Makefile.toml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
[tasks.doc-test]
|
||||||
|
command = "cargo"
|
||||||
|
args = [
|
||||||
|
"test"
|
||||||
|
]
|
||||||
100
packages/website-test/build.rs
Normal file
100
packages/website-test/build.rs
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
use glob::glob;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::env;
|
||||||
|
use std::fmt::{self, Write};
|
||||||
|
use std::fs;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
struct Level {
|
||||||
|
nested: HashMap<String, Level>,
|
||||||
|
files: Vec<PathBuf>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let home = env::var("CARGO_MANIFEST_DIR").unwrap();
|
||||||
|
let pattern = format!("{}/../../website/docs/**/*.md", home);
|
||||||
|
let base = format!("{}/../../website", home);
|
||||||
|
let base = Path::new(&base).canonicalize().unwrap();
|
||||||
|
|
||||||
|
let mut level = Level::default();
|
||||||
|
|
||||||
|
for entry in glob(&pattern).unwrap() {
|
||||||
|
let path = entry.unwrap();
|
||||||
|
let path = Path::new(&path).canonicalize().unwrap();
|
||||||
|
let rel = path.strip_prefix(&base).unwrap();
|
||||||
|
|
||||||
|
let mut parts = vec![];
|
||||||
|
|
||||||
|
for part in rel {
|
||||||
|
parts.push(part.to_str().unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
level.insert(path.clone(), &parts[..]);
|
||||||
|
}
|
||||||
|
|
||||||
|
let out = format!("{}/website_tests.rs", env::var("OUT_DIR").unwrap());
|
||||||
|
|
||||||
|
fs::write(&out, level.to_contents()).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Level {
|
||||||
|
fn insert(&mut self, path: PathBuf, rel: &[&str]) {
|
||||||
|
if rel.len() == 1 {
|
||||||
|
self.files.push(path);
|
||||||
|
} else {
|
||||||
|
let nested = self.nested.entry(rel[0].to_string()).or_default();
|
||||||
|
nested.insert(path, &rel[1..]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_contents(&self) -> String {
|
||||||
|
let mut dst = String::new();
|
||||||
|
|
||||||
|
self.write_inner(&mut dst, 0).unwrap();
|
||||||
|
dst
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_into(&self, dst: &mut String, name: &str, level: usize) -> fmt::Result {
|
||||||
|
self.write_space(dst, level);
|
||||||
|
let name = name.replace(|c| c == '-' || c == '.', "_");
|
||||||
|
writeln!(dst, "pub mod {} {{", name)?;
|
||||||
|
|
||||||
|
self.write_inner(dst, level + 1)?;
|
||||||
|
|
||||||
|
self.write_space(dst, level);
|
||||||
|
writeln!(dst, "}}")?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_inner(&self, dst: &mut String, level: usize) -> fmt::Result {
|
||||||
|
for (name, nested) in &self.nested {
|
||||||
|
nested.write_into(dst, name, level)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.write_space(dst, level);
|
||||||
|
|
||||||
|
for file in &self.files {
|
||||||
|
let stem = Path::new(file)
|
||||||
|
.file_stem()
|
||||||
|
.unwrap()
|
||||||
|
.to_str()
|
||||||
|
.unwrap()
|
||||||
|
.replace("-", "_");
|
||||||
|
|
||||||
|
self.write_space(dst, level);
|
||||||
|
writeln!(dst, "#[doc = include_str!(\"{}\")]", file.display())?;
|
||||||
|
self.write_space(dst, level);
|
||||||
|
writeln!(dst, "pub fn {}_md() {{}}", stem)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_space(&self, dst: &mut String, level: usize) {
|
||||||
|
for _ in 0..level {
|
||||||
|
dst.push_str(" ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
50
packages/website-test/src/agents.rs
Normal file
50
packages/website-test/src/agents.rs
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
//! Agent types that compile to be used by website code snippets
|
||||||
|
|
||||||
|
use yew_agent::{Agent, AgentLink, Context, HandlerId};
|
||||||
|
|
||||||
|
pub struct EventBus;
|
||||||
|
|
||||||
|
impl Agent for EventBus {
|
||||||
|
type Reach = Context<Self>;
|
||||||
|
type Message = ();
|
||||||
|
type Input = ();
|
||||||
|
type Output = String;
|
||||||
|
|
||||||
|
fn create(_link: AgentLink<Self>) -> Self {
|
||||||
|
Self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, _msg: Self::Message) {
|
||||||
|
// impl
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_input(&mut self, _msg: Self::Input, _id: HandlerId) {
|
||||||
|
// impl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum WorkerMsg {
|
||||||
|
Process,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct MyWorker;
|
||||||
|
|
||||||
|
impl Agent for MyWorker {
|
||||||
|
type Reach = Context<Self>;
|
||||||
|
|
||||||
|
type Message = ();
|
||||||
|
type Input = WorkerMsg;
|
||||||
|
type Output = ();
|
||||||
|
|
||||||
|
fn create(_link: AgentLink<Self>) -> Self {
|
||||||
|
Self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, _msg: Self::Message) {
|
||||||
|
// impl
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_input(&mut self, _msg: Self::Input, _id: HandlerId) {
|
||||||
|
// impl
|
||||||
|
}
|
||||||
|
}
|
||||||
3
packages/website-test/src/lib.rs
Normal file
3
packages/website-test/src/lib.rs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
pub mod agents;
|
||||||
|
|
||||||
|
include!(concat!(env!("OUT_DIR"), "/website_tests.rs"));
|
||||||
@ -95,7 +95,7 @@ implementation of the main page and render the component you are working on on t
|
|||||||
|
|
||||||
The slower speed and memory overhead are minor in comparison to the size gains made by not including the default allocator. This smaller file size means that your page will load faster, and so it is generally recommended that you use this allocator over the default, unless your app is doing some allocation-heavy work.
|
The slower speed and memory overhead are minor in comparison to the size gains made by not including the default allocator. This smaller file size means that your page will load faster, and so it is generally recommended that you use this allocator over the default, unless your app is doing some allocation-heavy work.
|
||||||
|
|
||||||
```rust
|
```rust ,ignore
|
||||||
// Use `wee_alloc` as the global allocator.
|
// Use `wee_alloc` as the global allocator.
|
||||||
#[global_allocator]
|
#[global_allocator]
|
||||||
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
|
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
|
||||||
|
|||||||
@ -19,23 +19,33 @@ in the lifecycle of a component.
|
|||||||
|
|
||||||
### Create
|
### Create
|
||||||
|
|
||||||
When a component is created, it receives properties from its parent component as well as a `ComponentLink`. The properties can be used to initialize the component's state and the "link" can be used to register callbacks or send messages to the component.
|
When a component is created, it receives properties from its parent component and is stored within
|
||||||
|
the `Context<Self>` thats passed down to the `create` method. The properties can be used to
|
||||||
It is common to store the props (data which can be passed from parent to child components) and the
|
initialize the component's state and the "link" can be used to register callbacks or send messages to the component.
|
||||||
`ComponentLink` in your component struct, like so:
|
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
|
use yew::{Component, Context, html, Html, Properties};
|
||||||
|
|
||||||
|
#[derive(PartialEq, Properties)]
|
||||||
|
pub struct Props;
|
||||||
|
|
||||||
pub struct MyComponent;
|
pub struct MyComponent;
|
||||||
|
|
||||||
impl Component for MyComponent {
|
impl Component for MyComponent {
|
||||||
|
type Message = ();
|
||||||
type Properties = Props;
|
type Properties = Props;
|
||||||
// ...
|
|
||||||
|
|
||||||
|
// highlight-start
|
||||||
fn create(ctx: &Context<Self>) -> Self {
|
fn create(ctx: &Context<Self>) -> Self {
|
||||||
MyComponent
|
MyComponent
|
||||||
}
|
}
|
||||||
|
// highlight-end
|
||||||
|
|
||||||
// ...
|
fn view(&self, _ctx: &Context<Self>) -> Html {
|
||||||
|
html! {
|
||||||
|
// impl
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -49,15 +59,35 @@ differences in programming language aside).
|
|||||||
One difference is that Yew provides a shorthand syntax for properties, similar to Svelte, where instead of writing `onclick={onclick}`, you can just write `{onclick}`.
|
One difference is that Yew provides a shorthand syntax for properties, similar to Svelte, where instead of writing `onclick={onclick}`, you can just write `{onclick}`.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
impl Component for MyComponent {
|
use yew::{Component, Context, html, Html, Properties};
|
||||||
// ...
|
|
||||||
|
|
||||||
|
enum Msg {
|
||||||
|
Click,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Properties)]
|
||||||
|
struct Props {
|
||||||
|
button_text: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct MyComponent;
|
||||||
|
|
||||||
|
impl Component for MyComponent {
|
||||||
|
type Message = Msg;
|
||||||
|
type Properties = Props;
|
||||||
|
|
||||||
|
fn create(_ctx: &Context<Self>) -> Self {
|
||||||
|
Self
|
||||||
|
}
|
||||||
|
|
||||||
|
// highlight-start
|
||||||
fn view(&self, ctx: &Context<Self>) -> Html {
|
fn view(&self, ctx: &Context<Self>) -> Html {
|
||||||
let onclick = ctx.link().callback(|_| Msg::Click);
|
let onclick = ctx.link().callback(|_| Msg::Click);
|
||||||
html! {
|
html! {
|
||||||
<button {onclick}>{ ctx.props().button_text }</button>
|
<button {onclick}>{ &ctx.props().button_text }</button>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// highlight-end
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -72,15 +102,24 @@ is also a parameter called `first_render` which can be used to determine whether
|
|||||||
being called on the first render, or instead a subsequent one.
|
being called on the first render, or instead a subsequent one.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
use web_sys::HtmlInputElement;
|
use yew::{
|
||||||
use yew::prelude::*;
|
Component, Context, html, Html, NodeRef,
|
||||||
|
web_sys::HtmlInputElement
|
||||||
|
};
|
||||||
|
|
||||||
pub struct MyComponent {
|
pub struct MyComponent {
|
||||||
node_ref: NodeRef,
|
node_ref: NodeRef,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Component for MyComponent {
|
impl Component for MyComponent {
|
||||||
// ...
|
type Message = ();
|
||||||
|
type Properties = ();
|
||||||
|
|
||||||
|
fn create(_ctx: &Context<Self>) -> Self {
|
||||||
|
Self {
|
||||||
|
node_ref: NodeRef::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn view(&self, ctx: &Context<Self>) -> Html {
|
fn view(&self, ctx: &Context<Self>) -> Html {
|
||||||
html! {
|
html! {
|
||||||
@ -88,6 +127,7 @@ impl Component for MyComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// highlight-start
|
||||||
fn rendered(&mut self, _ctx: &Context<Self>, first_render: bool) {
|
fn rendered(&mut self, _ctx: &Context<Self>, first_render: bool) {
|
||||||
if first_render {
|
if first_render {
|
||||||
if let Some(input) = self.node_ref.cast::<HtmlInputElement>() {
|
if let Some(input) = self.node_ref.cast::<HtmlInputElement>() {
|
||||||
@ -95,6 +135,7 @@ impl Component for MyComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// highlight-end
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -112,27 +153,50 @@ by event listeners, child components, Agents, Services, or Futures.
|
|||||||
Here's an example of what an implementation of `update` could look like:
|
Here's an example of what an implementation of `update` could look like:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
|
use yew::{Component, Context, html, Html};
|
||||||
|
|
||||||
|
// highlight-start
|
||||||
pub enum Msg {
|
pub enum Msg {
|
||||||
SetInputEnabled(bool)
|
SetInputEnabled(bool)
|
||||||
}
|
}
|
||||||
|
// highlight-end
|
||||||
|
|
||||||
|
struct MyComponent {
|
||||||
|
input_enabled: bool,
|
||||||
|
}
|
||||||
|
|
||||||
impl Component for MyComponent {
|
impl Component for MyComponent {
|
||||||
|
// highlight-next-line
|
||||||
type Message = Msg;
|
type Message = Msg;
|
||||||
|
type Properties = ();
|
||||||
|
|
||||||
// ...
|
fn create(_ctx: &Context<Self>) -> Self {
|
||||||
|
Self {
|
||||||
fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
|
input_enabled: false,
|
||||||
match msg {
|
}
|
||||||
Msg::SetInputEnabled(enabled) => {
|
|
||||||
if self.input_enabled != enabled {
|
|
||||||
self.input_enabled = enabled;
|
|
||||||
true // Re-render
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// highlight-start
|
||||||
|
fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
|
||||||
|
match msg {
|
||||||
|
Msg::SetInputEnabled(enabled) => {
|
||||||
|
if self.input_enabled != enabled {
|
||||||
|
self.input_enabled = enabled;
|
||||||
|
true // Re-render
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// highlight-end
|
||||||
|
|
||||||
|
fn view(&self, _ctx: &Context<Self>) -> Html {
|
||||||
|
html! {
|
||||||
|
// impl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -153,7 +217,7 @@ before it is destroyed. This method is optional and does nothing by default.
|
|||||||
|
|
||||||
The `Component` trait has two associated types: `Message` and `Properties`.
|
The `Component` trait has two associated types: `Message` and `Properties`.
|
||||||
|
|
||||||
```rust
|
```rust ,ignore
|
||||||
impl Component for MyComponent {
|
impl Component for MyComponent {
|
||||||
type Message = Msg;
|
type Message = Msg;
|
||||||
type Properties = Props;
|
type Properties = Props;
|
||||||
|
|||||||
@ -30,15 +30,43 @@ There is a different method called `callback_once` which accepts a `FnOnce` inst
|
|||||||
You should use this with care though, as the resulting callback will panic if executed more than once.
|
You should use this with care though, as the resulting callback will panic if executed more than once.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
// Create a callback that accepts some text and sends it to the component as the `Msg::Text` message variant.
|
use yew::{html, Component, Context, Html};
|
||||||
let cb = link.callback(|text: String| Msg::Text(text));
|
|
||||||
|
|
||||||
// The previous line is needlessly verbose to make it clearer.
|
enum Msg {
|
||||||
// It can be simplified it to this:
|
Text(String),
|
||||||
let cb = link.callback(Msg::Text);
|
}
|
||||||
|
|
||||||
// Will send `Msg::Text("Hello World!")` to the component.
|
struct Comp;
|
||||||
cb.emit("Hello World!".to_owned());
|
|
||||||
|
impl Component for Comp {
|
||||||
|
|
||||||
|
type Message = Msg;
|
||||||
|
type Properties = ();
|
||||||
|
|
||||||
|
fn create(_ctx: &Context<Self>) -> Self {
|
||||||
|
Self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view(&self, ctx: &Context<Self>) -> Html {
|
||||||
|
// Create a callback that accepts some text and sends it
|
||||||
|
// to the component as the `Msg::Text` message variant.
|
||||||
|
// highlight-next-line
|
||||||
|
let cb = ctx.link().callback(|text: String| Msg::Text(text));
|
||||||
|
|
||||||
|
// The previous line is needlessly verbose to make it clearer.
|
||||||
|
// It can be simplified it to this:
|
||||||
|
// highlight-next-line
|
||||||
|
let cb = ctx.link().callback(Msg::Text);
|
||||||
|
|
||||||
|
// Will send `Msg::Text("Hello World!")` to the component.
|
||||||
|
// highlight-next-line
|
||||||
|
cb.emit("Hello World!".to_owned());
|
||||||
|
|
||||||
|
html! {
|
||||||
|
// html here
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### `batch_callback`
|
### `batch_callback`
|
||||||
@ -70,9 +98,31 @@ They have an `emit` function that takes their `<IN>` type as an argument and con
|
|||||||
A simple use of a callback might look something like this:
|
A simple use of a callback might look something like this:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
let onclick = link.callback(|_| Msg::Clicked);
|
use yew::{html, Component, Context, Html};
|
||||||
html! {
|
|
||||||
<button {onclick}>{ "Click" }</button>
|
enum Msg {
|
||||||
|
Clicked,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Comp;
|
||||||
|
|
||||||
|
impl Component for Comp {
|
||||||
|
|
||||||
|
type Message = Msg;
|
||||||
|
type Properties = ();
|
||||||
|
|
||||||
|
fn create(_ctx: &Context<Self>) -> Self {
|
||||||
|
Self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view(&self, ctx: &Context<Self>) -> Html {
|
||||||
|
// highlight-next-line
|
||||||
|
let onclick = ctx.link().callback(|_| Msg::Clicked);
|
||||||
|
html! {
|
||||||
|
// highlight-next-line
|
||||||
|
<button {onclick}>{ "Click" }</button>
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -81,16 +131,38 @@ The function passed to `callback` must always take a parameter. For example, the
|
|||||||
If you need a callback that might not need to cause an update, use `batch_callback`.
|
If you need a callback that might not need to cause an update, use `batch_callback`.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
let onkeypress = link.batch_callback(|event| {
|
use yew::{html, Component, Context, Html, KeyboardEvent};
|
||||||
if event.key() == "Enter" {
|
|
||||||
Some(Msg::Submit)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
html! {
|
enum Msg {
|
||||||
<input type="text" {onkeypress} />
|
Submit,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Comp;
|
||||||
|
|
||||||
|
impl Component for Comp {
|
||||||
|
|
||||||
|
type Message = Msg;
|
||||||
|
type Properties = ();
|
||||||
|
|
||||||
|
fn create(_ctx: &Context<Self>) -> Self {
|
||||||
|
Self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view(&self, ctx: &Context<Self>) -> Html {
|
||||||
|
// highlight-start
|
||||||
|
let onkeypress = ctx.link().batch_callback(|event: KeyboardEvent| {
|
||||||
|
if event.key() == "Enter" {
|
||||||
|
Some(Msg::Submit)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
html! {
|
||||||
|
<input type="text" {onkeypress} />
|
||||||
|
}
|
||||||
|
// highlight-end
|
||||||
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@ -9,7 +9,7 @@ what type of children the component has. In such cases, the below example will
|
|||||||
suffice.
|
suffice.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
use yew::prelude::*;
|
use yew::{html, Children, Component, Context, Html, Properties};
|
||||||
|
|
||||||
#[derive(Properties, PartialEq)]
|
#[derive(Properties, PartialEq)]
|
||||||
pub struct ListProps {
|
pub struct ListProps {
|
||||||
@ -20,8 +20,12 @@ pub struct ListProps {
|
|||||||
pub struct List;
|
pub struct List;
|
||||||
|
|
||||||
impl Component for List {
|
impl Component for List {
|
||||||
|
type Message = ();
|
||||||
type Properties = ListProps;
|
type Properties = ListProps;
|
||||||
// ...
|
|
||||||
|
fn create(_ctx: &Context<Self>) -> Self {
|
||||||
|
Self
|
||||||
|
}
|
||||||
|
|
||||||
fn view(&self, ctx: &Context<Self>) -> Html {
|
fn view(&self, ctx: &Context<Self>) -> Html {
|
||||||
html! {
|
html! {
|
||||||
@ -40,10 +44,24 @@ In cases where you want one type of component to be passed as children to your c
|
|||||||
you can use `yew::html::ChildrenWithProps<T>`.
|
you can use `yew::html::ChildrenWithProps<T>`.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
use yew::html::ChildrenWithProps;
|
use yew::{html, ChildrenWithProps, Component, Context, Html, Properties};
|
||||||
use yew::prelude::*;
|
|
||||||
|
|
||||||
// ...
|
pub struct Item;
|
||||||
|
|
||||||
|
impl Component for Item {
|
||||||
|
type Message = ();
|
||||||
|
type Properties = ();
|
||||||
|
|
||||||
|
fn create(_ctx: &Context<Self>) -> Self {
|
||||||
|
Self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view(&self, _ctx: &Context<Self>) -> Html {
|
||||||
|
html! {
|
||||||
|
{ "item" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Properties, PartialEq)]
|
#[derive(Properties, PartialEq)]
|
||||||
pub struct ListProps {
|
pub struct ListProps {
|
||||||
@ -53,9 +71,13 @@ pub struct ListProps {
|
|||||||
|
|
||||||
pub struct List;
|
pub struct List;
|
||||||
|
|
||||||
impl Component for ListProps {
|
impl Component for List {
|
||||||
|
type Message = ();
|
||||||
type Properties = ListProps;
|
type Properties = ListProps;
|
||||||
// ...
|
|
||||||
|
fn create(_ctx: &Context<Self>) -> Self {
|
||||||
|
Self
|
||||||
|
}
|
||||||
|
|
||||||
fn view(&self, ctx: &Context<Self>) -> Html {
|
fn view(&self, ctx: &Context<Self>) -> Html {
|
||||||
html! {
|
html! {
|
||||||
@ -76,19 +98,53 @@ for better ergonomics. If you don't want to use it, you can manually implement
|
|||||||
`From` for each variant.
|
`From` for each variant.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
use yew::prelude::*;
|
use yew::{
|
||||||
use yew::html::ChildrenRenderer;
|
html, html::ChildrenRenderer, virtual_dom::VChild, Component,
|
||||||
use yew::virtual_dom::{ VChild, VComp };
|
Context, Html, Properties,
|
||||||
|
};
|
||||||
|
|
||||||
// `derive_more::From` implements `From<VChild<Primary>>` and
|
pub struct Primary;
|
||||||
// `From<VChild<Secondary>>` for `Item` automatically!
|
|
||||||
#[derive(Clone, derive_more::From)]
|
impl Component for Primary {
|
||||||
|
type Message = ();
|
||||||
|
type Properties = ();
|
||||||
|
|
||||||
|
fn create(_ctx: &Context<Self>) -> Self {
|
||||||
|
Self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view(&self, _ctx: &Context<Self>) -> Html {
|
||||||
|
html! {
|
||||||
|
{ "Primary" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Secondary;
|
||||||
|
|
||||||
|
impl Component for Secondary {
|
||||||
|
type Message = ();
|
||||||
|
type Properties = ();
|
||||||
|
|
||||||
|
fn create(_ctx: &Context<Self>) -> Self {
|
||||||
|
Self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view(&self, _ctx: &Context<Self>) -> Html {
|
||||||
|
html! {
|
||||||
|
{ "Secondary" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, derive_more::From, PartialEq)]
|
||||||
pub enum Item {
|
pub enum Item {
|
||||||
Primary(VChild<Primary>),
|
Primary(VChild<Primary>),
|
||||||
Secondary(VChild<Secondary>),
|
Secondary(VChild<Secondary>),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now, we implement `Into<Html>` so that yew knows how to render `Item`.
|
// Now, we implement `Into<Html>` so that yew knows how to render `Item`.
|
||||||
|
#[allow(clippy::from_over_into)]
|
||||||
impl Into<Html> for Item {
|
impl Into<Html> for Item {
|
||||||
fn into(self) -> Html {
|
fn into(self) -> Html {
|
||||||
match self {
|
match self {
|
||||||
@ -107,8 +163,12 @@ pub struct ListProps {
|
|||||||
pub struct List;
|
pub struct List;
|
||||||
|
|
||||||
impl Component for List {
|
impl Component for List {
|
||||||
|
type Message = ();
|
||||||
type Properties = ListProps;
|
type Properties = ListProps;
|
||||||
// ...
|
|
||||||
|
fn create(_ctx: &Context<Self>) -> Self {
|
||||||
|
Self
|
||||||
|
}
|
||||||
|
|
||||||
fn view(&self, ctx: &Context<Self>) -> Html {
|
fn view(&self, ctx: &Context<Self>) -> Html {
|
||||||
html! {
|
html! {
|
||||||
@ -124,9 +184,27 @@ impl Component for List {
|
|||||||
You can also have a single optional child component of a specific type too:
|
You can also have a single optional child component of a specific type too:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
use yew::prelude::*;
|
use yew::{
|
||||||
use yew::virtual_dom::VChild;
|
html, html_nested, virtual_dom::VChild, Component,
|
||||||
|
Context, Html, Properties
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct PageSideBar;
|
||||||
|
|
||||||
|
impl Component for PageSideBar {
|
||||||
|
type Message = ();
|
||||||
|
type Properties = ();
|
||||||
|
|
||||||
|
fn create(_ctx: &Context<Self>) -> Self {
|
||||||
|
Self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view(&self, _ctx: &Context<Self>) -> Html {
|
||||||
|
html! {
|
||||||
|
{ "sidebar" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Properties, PartialEq)]
|
#[derive(Properties, PartialEq)]
|
||||||
pub struct PageProps {
|
pub struct PageProps {
|
||||||
@ -137,8 +215,12 @@ pub struct PageProps {
|
|||||||
struct Page;
|
struct Page;
|
||||||
|
|
||||||
impl Component for Page {
|
impl Component for Page {
|
||||||
|
type Message = ();
|
||||||
type Properties = PageProps;
|
type Properties = PageProps;
|
||||||
// ...
|
|
||||||
|
fn create(_ctx: &Context<Self>) -> Self {
|
||||||
|
Self
|
||||||
|
}
|
||||||
|
|
||||||
fn view(&self, ctx: &Context<Self>) -> Html {
|
fn view(&self, ctx: &Context<Self>) -> Html {
|
||||||
html! {
|
html! {
|
||||||
@ -149,20 +231,22 @@ impl Component for Page {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
|
||||||
|
|
||||||
The page component can be called either with the sidebar or without:
|
// The page component can be called either with the sidebar or without:
|
||||||
|
|
||||||
```rust
|
pub fn render_page(with_sidebar: bool) -> Html {
|
||||||
// Page without sidebar
|
if with_sidebar {
|
||||||
html! {
|
// Page with sidebar
|
||||||
<Page />
|
html! {
|
||||||
}
|
<Page sidebar={{html_nested! {
|
||||||
|
<PageSideBar />
|
||||||
// Page with sidebar
|
}}} />
|
||||||
html! {
|
}
|
||||||
<Page sidebar={html_nested! {
|
} else {
|
||||||
<PageSideBar />
|
// Page without sidebar
|
||||||
}} />
|
html! {
|
||||||
|
<Page />
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|||||||
@ -91,15 +91,47 @@ The macro uses the same syntax as a struct expression except that you can't use
|
|||||||
The type path can either point to the props directly (`path::to::Props`) or the associated properties of a component (`MyComp::Properties`).
|
The type path can either point to the props directly (`path::to::Props`) or the associated properties of a component (`MyComp::Properties`).
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
let props = yew::props!(LinkProps {
|
use std::rc::Rc;
|
||||||
href: "/",
|
use yew::{props, Properties};
|
||||||
text: Rc::from("imagine this text being really long"),
|
|
||||||
size: 64,
|
|
||||||
});
|
|
||||||
|
|
||||||
// build the associated properties of a component
|
#[derive(Clone, PartialEq)]
|
||||||
let props = yew::props!(Model::Properties {
|
pub enum LinkColor {
|
||||||
href: "/book",
|
Blue,
|
||||||
text: Rc::from("my bestselling novel"),
|
Red,
|
||||||
});
|
Green,
|
||||||
|
Black,
|
||||||
|
Purple,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_default_link_color() -> LinkColor {
|
||||||
|
LinkColor::Blue
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Properties, PartialEq)]
|
||||||
|
pub struct LinkProps {
|
||||||
|
/// The link must have a target.
|
||||||
|
href: String,
|
||||||
|
text: Rc<String>,
|
||||||
|
/// Color of the link. Defaults to `Blue`.
|
||||||
|
#[prop_or_else(create_default_link_color)]
|
||||||
|
color: LinkColor,
|
||||||
|
/// The view function will not specify a size if this is None.
|
||||||
|
#[prop_or_default]
|
||||||
|
size: Option<u32>,
|
||||||
|
/// When the view function doesn't specify active, it defaults to true.
|
||||||
|
#[prop_or(true)]
|
||||||
|
active: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LinkProps {
|
||||||
|
pub fn new_link_with_size(href: String, text: String, size: u32) -> Self {
|
||||||
|
// highlight-start
|
||||||
|
props! {LinkProps {
|
||||||
|
href,
|
||||||
|
text: Rc::from(text),
|
||||||
|
size,
|
||||||
|
}}
|
||||||
|
// highlight-end
|
||||||
|
}
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|||||||
@ -14,16 +14,39 @@ a canvas element after it has been rendered from `view`.
|
|||||||
The syntax is:
|
The syntax is:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
// In create
|
use yew::{html, web_sys::Element, Component, Context, Html, NodeRef};
|
||||||
self.node_ref = NodeRef::default();
|
|
||||||
|
|
||||||
// In view
|
struct Comp {
|
||||||
html! {
|
node_ref: NodeRef,
|
||||||
<div ref={self.node_ref.clone()}></div>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// In rendered
|
impl Component for Comp {
|
||||||
let has_attributes = self.node_ref.cast::<Element>().unwrap().has_attributes();
|
type Message = ();
|
||||||
|
type Properties = ();
|
||||||
|
|
||||||
|
fn create(_ctx: &Context<Self>) -> Self {
|
||||||
|
Self {
|
||||||
|
// highlight-next-line
|
||||||
|
node_ref: NodeRef::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view(&self, _ctx: &Context<Self>) -> Html {
|
||||||
|
html! {
|
||||||
|
// highlight-next-line
|
||||||
|
<div ref={self.node_ref.clone()}></div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rendered(&mut self, _ctx: &Context<Self>, _first_render: bool) {
|
||||||
|
// highlight-start
|
||||||
|
let has_attributes = self.node_ref
|
||||||
|
.cast::<Element>()
|
||||||
|
.unwrap()
|
||||||
|
.has_attributes();
|
||||||
|
// highlight-end
|
||||||
|
}
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Relevant examples
|
## Relevant examples
|
||||||
|
|||||||
@ -8,19 +8,71 @@ Generally data is passed down the component tree using props but that becomes te
|
|||||||
user preferences, authentication information etc. Consider the following example which passes down the
|
user preferences, authentication information etc. Consider the following example which passes down the
|
||||||
theme using props:
|
theme using props:
|
||||||
```rust
|
```rust
|
||||||
// root
|
use yew::{html, Children, Component, Context, Html, Properties};
|
||||||
let theme = // ...
|
|
||||||
html! {
|
#[derive(Clone, PartialEq)]
|
||||||
<Navbar {theme} />
|
pub struct Theme {
|
||||||
|
foreground: String,
|
||||||
|
background: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Navbar component
|
#[derive(PartialEq, Properties)]
|
||||||
html! {
|
pub struct NavbarProps {
|
||||||
<div>
|
theme: Theme,
|
||||||
<Title {theme}>{ "App title" }<Title>
|
|
||||||
<NavButton {theme}>{ "Somewhere" }</NavButton>
|
|
||||||
</div>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct Navbar;
|
||||||
|
|
||||||
|
impl Component for Navbar {
|
||||||
|
type Message = ();
|
||||||
|
type Properties = NavbarProps;
|
||||||
|
|
||||||
|
fn create(_ctx: &Context<Self>) -> Self {
|
||||||
|
Self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view(&self, ctx: &Context<Self>) -> Html {
|
||||||
|
html! {
|
||||||
|
<div>
|
||||||
|
<Title theme={ctx.props().theme.clone()}>
|
||||||
|
{ "App title" }
|
||||||
|
</Title>
|
||||||
|
<NavButton theme={ctx.props().theme.clone()}>
|
||||||
|
{ "Somewhere" }
|
||||||
|
</NavButton>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Properties)]
|
||||||
|
pub struct ThemeProps {
|
||||||
|
theme: Theme,
|
||||||
|
children: Children,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[yew::function_component(Title)]
|
||||||
|
fn title(_props: &ThemeProps) -> Html {
|
||||||
|
html! {
|
||||||
|
// impl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[yew::function_component(NavButton)]
|
||||||
|
fn nav_button(_props: &ThemeProps) -> Html {
|
||||||
|
html! {
|
||||||
|
// impl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// root
|
||||||
|
let theme = Theme {
|
||||||
|
foreground: "yellow".to_owned(),
|
||||||
|
background: "pink".to_owned(),
|
||||||
|
};
|
||||||
|
|
||||||
|
html! {
|
||||||
|
<Navbar {theme} />
|
||||||
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
Passing down data like this isn't ideal for something like a theme which needs to be available everywhere.
|
Passing down data like this isn't ideal for something like a theme which needs to be available everywhere.
|
||||||
@ -54,14 +106,36 @@ The `Scope::context` method is used to consume contexts in struct components.
|
|||||||
##### Example
|
##### Example
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
|
use yew::{Callback, html, Component, Context, Html};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
struct Theme {
|
||||||
|
foreground: String,
|
||||||
|
background: String,
|
||||||
|
}
|
||||||
|
|
||||||
struct ContextDemo;
|
struct ContextDemo;
|
||||||
|
|
||||||
impl Component for ContextDemo {
|
impl Component for ContextDemo {
|
||||||
/// ...
|
type Message = ();
|
||||||
|
type Properties = ();
|
||||||
|
|
||||||
|
fn create(_ctx: &Context<Self>) -> Self {
|
||||||
|
Self
|
||||||
|
}
|
||||||
|
|
||||||
fn view(&self, ctx: &Context<Self>) -> Html {
|
fn view(&self, ctx: &Context<Self>) -> Html {
|
||||||
let theme = ctx.link().context::<Theme>();
|
let (theme, _) = ctx
|
||||||
|
.link()
|
||||||
|
.context::<Theme>(Callback::noop())
|
||||||
|
.expect("context to be set");
|
||||||
html! {
|
html! {
|
||||||
<button style={format!("background: {}; color: {};", theme.background, theme.foreground)}>
|
<button style={format!(
|
||||||
|
"background: {}; color: {};",
|
||||||
|
theme.background,
|
||||||
|
theme.foreground
|
||||||
|
)}
|
||||||
|
>
|
||||||
{ "Click me!" }
|
{ "Click me!" }
|
||||||
</button>
|
</button>
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,6 +15,8 @@ Hooks allow function components to use state and other Yew features without impl
|
|||||||
The easiest way to create a function component is to add the [`#[function_component]`](function-components/attribute.md) attribute to a function.
|
The easiest way to create a function component is to add the [`#[function_component]`](function-components/attribute.md) attribute to a function.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
|
use yew::{function_component, html};
|
||||||
|
|
||||||
#[function_component(HelloWorld)]
|
#[function_component(HelloWorld)]
|
||||||
fn hello_world() -> Html {
|
fn hello_world() -> Html {
|
||||||
html! { "Hello world" }
|
html! { "Hello world" }
|
||||||
|
|||||||
@ -12,7 +12,18 @@ The attribute doesn't replace your original function with a component. You need
|
|||||||
Assuming you have a function called `chat_container` and you add the attribute `#[function_component(ChatContainer)]` you can use the component like this:
|
Assuming you have a function called `chat_container` and you add the attribute `#[function_component(ChatContainer)]` you can use the component like this:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
html! { <ChatContainer /> }
|
use yew::{function_component, html, Html};
|
||||||
|
|
||||||
|
#[function_component(ChatContainer)]
|
||||||
|
pub fn chat_container() -> Html {
|
||||||
|
html! {
|
||||||
|
// chat container impl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
html! {
|
||||||
|
<ChatContainer />
|
||||||
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
## Example
|
## Example
|
||||||
@ -21,6 +32,8 @@ html! { <ChatContainer /> }
|
|||||||
<!--With props-->
|
<!--With props-->
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
|
use yew::{function_component, html, Properties};
|
||||||
|
|
||||||
#[derive(Properties, PartialEq)]
|
#[derive(Properties, PartialEq)]
|
||||||
pub struct RenderedAtProps {
|
pub struct RenderedAtProps {
|
||||||
pub time: String,
|
pub time: String,
|
||||||
@ -40,13 +53,15 @@ pub fn rendered_at(props: &RenderedAtProps) -> Html {
|
|||||||
<!--Without props-->
|
<!--Without props-->
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
|
use yew::{function_component, html, use_state, Callback};
|
||||||
|
|
||||||
#[function_component(App)]
|
#[function_component(App)]
|
||||||
fn app() -> Html {
|
fn app() -> Html {
|
||||||
let (counter, set_counter) = use_state(|| 0);
|
let counter = use_state(|| 0);
|
||||||
|
|
||||||
let onclick = {
|
let onclick = {
|
||||||
let counter = Rc::clone(&counter);
|
let counter = counter.clone();
|
||||||
Callback::from(move |_| set_counter(*counter + 1))
|
Callback::from(move |_| counter.set(*counter + 1))
|
||||||
};
|
};
|
||||||
|
|
||||||
html! {
|
html! {
|
||||||
@ -54,7 +69,7 @@ fn app() -> Html {
|
|||||||
<button {onclick}>{ "Increment value" }</button>
|
<button {onclick}>{ "Increment value" }</button>
|
||||||
<p>
|
<p>
|
||||||
<b>{ "Current value: " }</b>
|
<b>{ "Current value: " }</b>
|
||||||
{ counter }
|
{ *counter }
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@ -68,30 +83,47 @@ fn app() -> Html {
|
|||||||
The `#[function_component(_)]` attribute also works with generic functions for creating generic components.
|
The `#[function_component(_)]` attribute also works with generic functions for creating generic components.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
|
use std::fmt::Display;
|
||||||
|
use yew::{function_component, html, Properties};
|
||||||
|
|
||||||
#[derive(Properties, PartialEq)]
|
#[derive(Properties, PartialEq)]
|
||||||
pub struct Props<T>
|
pub struct Props<T>
|
||||||
where T: PartialEq
|
where
|
||||||
|
T: PartialEq,
|
||||||
{
|
{
|
||||||
data: T,
|
data: T,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[function_component(MyGenericComponent)]
|
#[function_component(MyGenericComponent)]
|
||||||
pub fn my_generic_component<T>(props: &Props<T>) -> Html
|
pub fn my_generic_component<T>(props: &Props<T>) -> Html
|
||||||
where T: PartialEq + Display
|
where
|
||||||
|
T: PartialEq + Display,
|
||||||
{
|
{
|
||||||
html! {
|
html! {
|
||||||
<p>
|
<p>
|
||||||
{ props.data }
|
{ &props.data }
|
||||||
</p>
|
</p>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq)]
|
||||||
|
struct Foo;
|
||||||
|
|
||||||
|
impl Display for Foo {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.write_str("foo")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// used like this
|
// used like this
|
||||||
html! {
|
html! {
|
||||||
<MyGenericComponent<i32> data=123 />
|
<MyGenericComponent<i32> data=123 />
|
||||||
}
|
};
|
||||||
|
|
||||||
// or
|
// or
|
||||||
|
let foo = Foo;
|
||||||
|
|
||||||
html! {
|
html! {
|
||||||
<MyGenericComponent<Foo> data={foo} />
|
<MyGenericComponent<Foo> data={foo} />
|
||||||
}
|
};
|
||||||
```
|
```
|
||||||
|
|||||||
@ -9,17 +9,23 @@ Component's stateful logic can be extracted into usable function by creating cus
|
|||||||
|
|
||||||
Consider that we have a component which subscribes to an agent and displays the messages sent to it.
|
Consider that we have a component which subscribes to an agent and displays the messages sent to it.
|
||||||
```rust
|
```rust
|
||||||
|
use yew::{function_component, html, use_effect, use_state, Callback};
|
||||||
|
use yew_agent::Bridged;
|
||||||
|
// EventBus is an implementation yew_agent::Agent
|
||||||
|
use website_test::agents::EventBus;
|
||||||
|
|
||||||
|
|
||||||
#[function_component(ShowMessages)]
|
#[function_component(ShowMessages)]
|
||||||
pub fn show_messages() -> Html {
|
pub fn show_messages() -> Html {
|
||||||
let (state, set_state) = use_state(|| vec![]);
|
let state = use_state(Vec::new);
|
||||||
|
|
||||||
{
|
{
|
||||||
let mut state = Rc::clone(&state);
|
let state = state.clone();
|
||||||
use_effect(move || {
|
use_effect(move || {
|
||||||
let producer = EventBus::bridge(Callback::from(move |msg| {
|
let producer = EventBus::bridge(Callback::from(move |msg| {
|
||||||
let mut messages = (*state).clone();
|
let mut messages = (*state).clone();
|
||||||
messages.push(msg);
|
messages.push(msg);
|
||||||
set_state(messages)
|
state.set(messages)
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|| drop(producer)
|
|| drop(producer)
|
||||||
@ -38,6 +44,8 @@ We'll start by creating a new function called `use_subscribe`.
|
|||||||
The `use_` prefix conventionally denotes that a function is a hook.
|
The `use_` prefix conventionally denotes that a function is a hook.
|
||||||
This function will take no arguments and return `Rc<RefCell<Vec<String>>>`.
|
This function will take no arguments and return `Rc<RefCell<Vec<String>>>`.
|
||||||
```rust
|
```rust
|
||||||
|
use std::{cell::RefCell, rc::Rc};
|
||||||
|
|
||||||
fn use_subscribe() -> Rc<RefCell<Vec<String>>> {
|
fn use_subscribe() -> Rc<RefCell<Vec<String>>> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
@ -48,19 +56,27 @@ We'll use `use_state` hook to store the `Vec` for messages, so they persist betw
|
|||||||
We'll also use `use_effect` to subscribe to the `EventBus` `Agent` so the subscription can be tied to component's lifecycle.
|
We'll also use `use_effect` to subscribe to the `EventBus` `Agent` so the subscription can be tied to component's lifecycle.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
fn use_subscribe() -> Rc<Vec<String>> {
|
use std::collections::HashSet;
|
||||||
let (state, set_state) = use_state(Vec::new);
|
use yew::{use_effect, use_state, Callback};
|
||||||
|
use yew_agent::Bridged;
|
||||||
|
// EventBus is an implementation yew_agent::Agent
|
||||||
|
use website_test::agents::EventBus;
|
||||||
|
|
||||||
|
fn use_subscribe() -> Vec<String> {
|
||||||
|
let state = use_state(Vec::new);
|
||||||
|
|
||||||
|
let effect_state = state.clone();
|
||||||
|
|
||||||
use_effect(move || {
|
use_effect(move || {
|
||||||
let producer = EventBus::bridge(Callback::from(move |msg| {
|
let producer = EventBus::bridge(Callback::from(move |msg| {
|
||||||
let mut messages = (*state).clone();
|
let mut messages = (*effect_state).clone();
|
||||||
messages.push(msg);
|
messages.push(msg);
|
||||||
set_state(messages)
|
effect_state.set(messages)
|
||||||
}));
|
}));
|
||||||
|| drop(producer)
|
|| drop(producer)
|
||||||
});
|
});
|
||||||
|
|
||||||
state
|
(*state).clone()
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@ -15,6 +15,8 @@ This value remains up-to-date on subsequent renders.
|
|||||||
### Example
|
### Example
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
|
use yew::{Callback, function_component, html, use_state};
|
||||||
|
|
||||||
#[function_component(UseState)]
|
#[function_component(UseState)]
|
||||||
fn state() -> Html {
|
fn state() -> Html {
|
||||||
let counter = use_state(|| 0);
|
let counter = use_state(|| 0);
|
||||||
@ -46,19 +48,25 @@ If you need the component to be re-rendered on state change, consider using [`us
|
|||||||
### Example
|
### Example
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
|
use yew::{
|
||||||
|
function_component, html, use_ref, use_state,
|
||||||
|
web_sys::{Event, HtmlInputElement},
|
||||||
|
Callback, TargetCast,
|
||||||
|
};
|
||||||
|
|
||||||
#[function_component(UseRef)]
|
#[function_component(UseRef)]
|
||||||
fn ref_hook() -> Html {
|
fn ref_hook() -> Html {
|
||||||
let message = use_state(|| "".to_string());
|
let message = use_state(|| "".to_string());
|
||||||
let message_count = use_ref(|| 0);
|
let message_count = use_ref(|| 0);
|
||||||
|
|
||||||
let onclick = Callback::from(move |e| {
|
let onclick = Callback::from(move |_| {
|
||||||
let window = yew::utils::window();
|
let window = yew::utils::window();
|
||||||
|
|
||||||
if *message_count.borrow_mut() > 3 {
|
if *message_count.borrow_mut() > 3 {
|
||||||
window.alert_with_message("Message limit reached");
|
window.alert_with_message("Message limit reached").unwrap();
|
||||||
} else {
|
} else {
|
||||||
*message_count.borrow_mut() += 1;
|
*message_count.borrow_mut() += 1;
|
||||||
window.alert_with_message("Message sent");
|
window.alert_with_message("Message sent").unwrap();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -72,14 +80,13 @@ fn ref_hook() -> Html {
|
|||||||
|
|
||||||
html! {
|
html! {
|
||||||
<div>
|
<div>
|
||||||
<input {onchange} value={message} />
|
<input {onchange} value={(*message).clone()} />
|
||||||
<button {onclick}>{ "Send" }</button>
|
<button {onclick}>{ "Send" }</button>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
## `use_reducer`
|
## `use_reducer`
|
||||||
|
|
||||||
`use_reducer` is an alternative to [`use_state`](#use_state). It is used to handle component's state and is used
|
`use_reducer` is an alternative to [`use_state`](#use_state). It is used to handle component's state and is used
|
||||||
@ -95,6 +102,9 @@ For lazy initialization, consider using [`use_reducer_with_init`](#use_reducer_w
|
|||||||
### Example
|
### Example
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
|
use std::rc::Rc;
|
||||||
|
use yew::{function_component, html, use_reducer, Callback};
|
||||||
|
|
||||||
#[function_component(UseReducer)]
|
#[function_component(UseReducer)]
|
||||||
fn reducer() -> Html {
|
fn reducer() -> Html {
|
||||||
/// reducer's Action
|
/// reducer's Action
|
||||||
@ -114,13 +124,13 @@ fn reducer() -> Html {
|
|||||||
counter: match action {
|
counter: match action {
|
||||||
Action::Double => prev.counter * 2,
|
Action::Double => prev.counter * 2,
|
||||||
Action::Square => prev.counter * prev.counter,
|
Action::Square => prev.counter * prev.counter,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
// initial state
|
// initial state
|
||||||
CounterState { counter: 1 },
|
CounterState { counter: 1 },
|
||||||
);
|
);
|
||||||
|
|
||||||
let double_onclick = {
|
let double_onclick = {
|
||||||
let counter = counter.clone();
|
let counter = counter.clone();
|
||||||
Callback::from(move |_| counter.dispatch(Action::Double))
|
Callback::from(move |_| counter.dispatch(Action::Double))
|
||||||
};
|
};
|
||||||
@ -149,16 +159,34 @@ This is useful for lazy initialization where it is beneficial not to perform exp
|
|||||||
computation up-front.
|
computation up-front.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
let counter = use_reducer_with_init(
|
use std::rc::Rc;
|
||||||
// reducer function
|
use yew::{function_component, use_reducer_with_init, html};
|
||||||
|prev: Rc<CounterState>, action: i32| CounterState {
|
|
||||||
counter: prev.counter + action,
|
#[function_component(ReducerWithInit)]
|
||||||
},
|
fn reducer_with_init() -> Html {
|
||||||
0, // initial value
|
|
||||||
|initial: i32| CounterState { // init method
|
/// reducer's State
|
||||||
counter: initial + 10,
|
struct CounterState {
|
||||||
},
|
counter: i32,
|
||||||
);
|
}
|
||||||
|
|
||||||
|
let counter = use_reducer_with_init(
|
||||||
|
// reducer function
|
||||||
|
|prev: Rc<CounterState>, action: i32| CounterState {
|
||||||
|
counter: prev.counter + action,
|
||||||
|
},
|
||||||
|
0, // initial value
|
||||||
|
|initial: i32| CounterState { // init method
|
||||||
|
counter: initial + 10,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
html! {
|
||||||
|
<>
|
||||||
|
<div id="result">{ counter.counter }</div>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## `use_effect`
|
## `use_effect`
|
||||||
@ -173,27 +201,29 @@ The destructor can be used to clean up the effects introduced and it can take ow
|
|||||||
### Example
|
### Example
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
|
use yew::{Callback, function_component, html, use_effect, use_state};
|
||||||
|
|
||||||
#[function_component(UseEffect)]
|
#[function_component(UseEffect)]
|
||||||
fn effect() -> Html {
|
fn effect() -> Html {
|
||||||
let (counter, set_counter) = use_state(|| 0);
|
let counter = use_state(|| 0);
|
||||||
|
|
||||||
{
|
{
|
||||||
let counter = counter.clone();
|
let counter = counter.clone();
|
||||||
use_effect(move || {
|
use_effect(move || {
|
||||||
// Make a call to DOM API after component is rendered
|
// Make a call to DOM API after component is rendered
|
||||||
yew::utils::document().set_title(&format!("You clicked {} times", counter));
|
yew::utils::document().set_title(&format!("You clicked {} times", *counter));
|
||||||
|
|
||||||
// Perform the cleanup
|
// Perform the cleanup
|
||||||
|| yew::utils::document().set_title("You clicked 0 times")
|
|| yew::utils::document().set_title("You clicked 0 times")
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
let onclick = {
|
let onclick = {
|
||||||
let counter = Rc::clone(&counter);
|
let counter = counter.clone();
|
||||||
Callback::from(move |_| set_counter(*counter + 1))
|
Callback::from(move |_| counter.set(*counter + 1))
|
||||||
};
|
};
|
||||||
|
|
||||||
html! {
|
html! {
|
||||||
<button {onclick}>{ format!("Increment to {}", counter) }</button>
|
<button {onclick}>{ format!("Increment to {}", *counter) }</button>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@ -201,7 +231,9 @@ fn effect() -> Html {
|
|||||||
### `use_effect_with_deps`
|
### `use_effect_with_deps`
|
||||||
|
|
||||||
Sometimes, it's needed to manually define dependencies for [`use_effect`](#use_effect). In such cases, we use `use_effect_with_deps`.
|
Sometimes, it's needed to manually define dependencies for [`use_effect`](#use_effect). In such cases, we use `use_effect_with_deps`.
|
||||||
```rust
|
```rust ,no_run
|
||||||
|
use yew::use_effect_with_deps;
|
||||||
|
|
||||||
use_effect_with_deps(
|
use_effect_with_deps(
|
||||||
move |_| {
|
move |_| {
|
||||||
// ...
|
// ...
|
||||||
@ -221,6 +253,9 @@ use_effect_with_deps(
|
|||||||
### Example
|
### Example
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
|
use yew::{ContextProvider, function_component, html, use_context, use_state};
|
||||||
|
|
||||||
|
|
||||||
/// App theme
|
/// App theme
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
struct Theme {
|
struct Theme {
|
||||||
@ -237,7 +272,8 @@ pub fn app() -> Html {
|
|||||||
});
|
});
|
||||||
|
|
||||||
html! {
|
html! {
|
||||||
// `ctx` is type `Rc<UseStateHandle<Theme>>` while we need `Theme` so we deref it
|
// `ctx` is type `Rc<UseStateHandle<Theme>>` while we need `Theme`
|
||||||
|
// so we deref it.
|
||||||
// It derefs to `&Theme`, hence the clone
|
// It derefs to `&Theme`, hence the clone
|
||||||
<ContextProvider<Theme> context={(*ctx).clone()}>
|
<ContextProvider<Theme> context={(*ctx).clone()}>
|
||||||
// Every child here and their children will have access to this context.
|
// Every child here and their children will have access to this context.
|
||||||
|
|||||||
@ -28,33 +28,41 @@ Tags must either self-close `<... />` or have a corresponding end tag for each s
|
|||||||
<!--Open - Close-->
|
<!--Open - Close-->
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
|
use yew::html;
|
||||||
|
|
||||||
html! {
|
html! {
|
||||||
<div id="my_div"></div>
|
<div id="my_div"></div>
|
||||||
}
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
<!--Invalid-->
|
<!--Invalid-->
|
||||||
|
|
||||||
```rust
|
```rust ,compile_fail
|
||||||
|
use yew::html;
|
||||||
|
|
||||||
html! {
|
html! {
|
||||||
<div id="my_div"> // <- MISSING CLOSE TAG
|
<div id="my_div"> // <- MISSING CLOSE TAG
|
||||||
}
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
<!--Self-closing-->
|
<!--Self-closing-->
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
|
use yew::html;
|
||||||
|
|
||||||
html! {
|
html! {
|
||||||
<input id="my_input" />
|
<input id="my_input" />
|
||||||
}
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
<!--Invalid-->
|
<!--Invalid-->
|
||||||
|
|
||||||
```rust
|
```rust ,compile_fail
|
||||||
|
use yew::html;
|
||||||
|
|
||||||
html! {
|
html! {
|
||||||
<input id="my_input"> // <- MISSING SELF-CLOSE
|
<input id="my_input"> // <- MISSING SELF-CLOSE
|
||||||
}
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
<!--END_DOCUSAURUS_CODE_TABS-->
|
<!--END_DOCUSAURUS_CODE_TABS-->
|
||||||
@ -71,6 +79,8 @@ Create complex nested HTML and SVG layouts with ease:
|
|||||||
<!--HTML-->
|
<!--HTML-->
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
|
use yew::html;
|
||||||
|
|
||||||
html! {
|
html! {
|
||||||
<div>
|
<div>
|
||||||
<div data-key="abc"></div>
|
<div data-key="abc"></div>
|
||||||
@ -86,12 +96,14 @@ html! {
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
<!--SVG-->
|
<!--SVG-->
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
|
use yew::html;
|
||||||
|
|
||||||
html! {
|
html! {
|
||||||
<svg width="149" height="147" viewBox="0 0 149 147" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="149" height="147" viewBox="0 0 149 147" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path d="M60.5776 13.8268L51.8673 42.6431L77.7475 37.331L60.5776 13.8268Z" fill="#DEB819"/>
|
<path d="M60.5776 13.8268L51.8673 42.6431L77.7475 37.331L60.5776 13.8268Z" fill="#DEB819"/>
|
||||||
@ -108,7 +120,7 @@ html! {
|
|||||||
</filter>
|
</filter>
|
||||||
</defs>
|
</defs>
|
||||||
</svg>
|
</svg>
|
||||||
}
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
<!--END_DOCUSAURUS_CODE_TABS-->
|
<!--END_DOCUSAURUS_CODE_TABS-->
|
||||||
|
|||||||
@ -23,53 +23,65 @@ is that every expression implements `Into<Classes>`.
|
|||||||
<!--Literal-->
|
<!--Literal-->
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
|
use yew::{classes, html};
|
||||||
|
|
||||||
html! {
|
html! {
|
||||||
<div class={classes!("container")}></div>
|
<div class={classes!("container")}></div>
|
||||||
}
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
<!--Multiple-->
|
<!--Multiple-->
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
|
use yew::{classes, html};
|
||||||
|
|
||||||
html! {
|
html! {
|
||||||
<div class={classes!("class-1", "class-2")}></div>
|
<div class={classes!("class-1", "class-2")}></div>
|
||||||
}
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
<!--String-->
|
<!--String-->
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
|
use yew::{classes, html};
|
||||||
|
|
||||||
let my_classes = String::from("class-1 class-2");
|
let my_classes = String::from("class-1 class-2");
|
||||||
|
|
||||||
html! {
|
html! {
|
||||||
<div class={classes!(my_classes)}></div>
|
<div class={classes!(my_classes)}></div>
|
||||||
}
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
<!--Optional-->
|
<!--Optional-->
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
|
use yew::{classes, html};
|
||||||
|
|
||||||
html! {
|
html! {
|
||||||
<div class={classes!(Some("class"))} />
|
<div class={classes!(Some("class"))} />
|
||||||
}
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
<!--Vector-->
|
<!--Vector-->
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
|
use yew::{classes, html};
|
||||||
|
|
||||||
html! {
|
html! {
|
||||||
<div class={classes!(vec!["class-1", "class-2"])}></div>
|
<div class={classes!(vec!["class-1", "class-2"])}></div>
|
||||||
}
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
<!--Array-->
|
<!--Array-->
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
|
use yew::{classes, html};
|
||||||
|
|
||||||
let my_classes = ["class-1", "class-2"];
|
let my_classes = ["class-1", "class-2"];
|
||||||
|
|
||||||
html! {
|
html! {
|
||||||
<div class={classes!(&my_classes)}></div>
|
<div class={classes!(my_classes.as_ref())}></div>
|
||||||
}
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
<!--END_DOCUSAURUS_CODE_TABS-->
|
<!--END_DOCUSAURUS_CODE_TABS-->
|
||||||
@ -77,9 +89,13 @@ html! {
|
|||||||
## Components that accept classes
|
## Components that accept classes
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
|
use yew::{
|
||||||
|
classes, html, Children, Classes, Component,
|
||||||
|
Context, Html, Properties
|
||||||
|
};
|
||||||
use boolinator::Boolinator;
|
use boolinator::Boolinator;
|
||||||
|
|
||||||
#[derive(Properties)]
|
#[derive(PartialEq, Properties)]
|
||||||
struct Props {
|
struct Props {
|
||||||
#[prop_or_default]
|
#[prop_or_default]
|
||||||
class: Classes,
|
class: Classes,
|
||||||
@ -90,9 +106,12 @@ struct Props {
|
|||||||
struct MyComponent;
|
struct MyComponent;
|
||||||
|
|
||||||
impl Component for MyComponent {
|
impl Component for MyComponent {
|
||||||
|
type Message = ();
|
||||||
type Properties = Props;
|
type Properties = Props;
|
||||||
|
|
||||||
// ...
|
fn create(_ctx: &Context<Self>) -> Self {
|
||||||
|
Self
|
||||||
|
}
|
||||||
|
|
||||||
fn view(&self, ctx: &Context<Self>) -> Html {
|
fn view(&self, ctx: &Context<Self>) -> Html {
|
||||||
let Props {
|
let Props {
|
||||||
|
|||||||
@ -8,18 +8,72 @@ description: "Create complex layouts with component hierarchies"
|
|||||||
Any type that implements `Component` can be used in the `html!` macro:
|
Any type that implements `Component` can be used in the `html!` macro:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
|
use yew::{Component, Html, html, Context, Properties};
|
||||||
|
|
||||||
|
struct MyComponent;
|
||||||
|
|
||||||
|
impl Component for MyComponent {
|
||||||
|
type Message = ();
|
||||||
|
type Properties = ();
|
||||||
|
|
||||||
|
fn create(_ctx: &Context<Self>) -> Self {
|
||||||
|
Self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view(&self, _ctx: &Context<Self>) -> Html {
|
||||||
|
html! {
|
||||||
|
{ "This component has no properties!" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Properties)]
|
||||||
|
struct Props {
|
||||||
|
prop1: String,
|
||||||
|
prop2: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct MyComponentWithProps;
|
||||||
|
|
||||||
|
impl Component for MyComponentWithProps {
|
||||||
|
type Message = ();
|
||||||
|
type Properties = Props;
|
||||||
|
|
||||||
|
fn create(_ctx: &Context<Self>) -> Self {
|
||||||
|
Self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view(&self, ctx: &Context<Self>) -> Html {
|
||||||
|
html! {
|
||||||
|
{
|
||||||
|
format!(
|
||||||
|
"prop1: {} and prop2: {}",
|
||||||
|
ctx.props().prop1,
|
||||||
|
ctx.props().prop2
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let props = Props {
|
||||||
|
prop1: "Hello".to_owned(),
|
||||||
|
prop2: "World".to_owned(),
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
html!{
|
html!{
|
||||||
<>
|
<>
|
||||||
// No properties
|
// No properties
|
||||||
<MyComponent />
|
<MyComponent />
|
||||||
|
|
||||||
// With Properties
|
// With Properties
|
||||||
<MyComponent prop1="lorem" prop2="ipsum" />
|
<MyComponentWithProps prop1="lorem" prop2="ipsum" />
|
||||||
|
|
||||||
// With the whole set of props provided at once
|
// With the whole set of props provided at once
|
||||||
<MyComponent with props />
|
<MyComponentWithProps with props />
|
||||||
</>
|
</>
|
||||||
}
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
## Nested
|
## Nested
|
||||||
@ -27,52 +81,82 @@ html!{
|
|||||||
Components can be passed children if they have a `children` field in their `Properties`.
|
Components can be passed children if they have a `children` field in their `Properties`.
|
||||||
|
|
||||||
```rust title="parent.rs"
|
```rust title="parent.rs"
|
||||||
|
use yew::{Children, Component, Context, html, Html, Properties};
|
||||||
|
|
||||||
|
#[derive(PartialEq, Properties)]
|
||||||
|
struct Props {
|
||||||
|
id: String,
|
||||||
|
children: Children,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Container;
|
||||||
|
|
||||||
|
impl Component for Container {
|
||||||
|
type Message = ();
|
||||||
|
type Properties = Props;
|
||||||
|
|
||||||
|
fn create(_ctx: &Context<Self>) -> Self {
|
||||||
|
Self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view(&self, ctx: &Context<Self>) -> Html {
|
||||||
|
html! {
|
||||||
|
<div id={ctx.props().id.clone()}>
|
||||||
|
{ ctx.props().children.clone() }
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
html! {
|
html! {
|
||||||
<Container id="container">
|
<Container id="container">
|
||||||
<h4>{ "Hi" }</h4>
|
<h4>{ "Hi" }</h4>
|
||||||
<div>{ "Hello" }</div>
|
<div>{ "Hello" }</div>
|
||||||
</Container>
|
</Container>
|
||||||
}
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
When using the `with props` syntax, the children passed in the `html!` macro overwrite the ones already present in the props.
|
When using the `with props` syntax, the children passed in the `html!` macro overwrite the ones already present in the props.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
|
use yew::{Children, Component, Context, html, Html, props, Properties};
|
||||||
|
|
||||||
|
#[derive(PartialEq, Properties)]
|
||||||
|
struct Props {
|
||||||
|
id: String,
|
||||||
|
children: Children,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Container;
|
||||||
|
|
||||||
|
impl Component for Container {
|
||||||
|
type Message = ();
|
||||||
|
type Properties = Props;
|
||||||
|
|
||||||
|
fn create(_ctx: &Context<Self>) -> Self {
|
||||||
|
Self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view(&self, ctx: &Context<Self>) -> Html {
|
||||||
|
html! {
|
||||||
|
<div id={ctx.props().id.clone()}>
|
||||||
|
{ ctx.props().children.clone() }
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let props = yew::props!(Container::Properties {
|
let props = yew::props!(Container::Properties {
|
||||||
id: "container-2",
|
id: "container-2",
|
||||||
children: Children::default(),
|
children: Children::default(),
|
||||||
});
|
});
|
||||||
|
|
||||||
html! {
|
html! {
|
||||||
<Container with props>
|
<Container with props>
|
||||||
// props.children will be overwritten with this
|
// props.children will be overwritten with this
|
||||||
<span>{ "I am a child, as you can see" }</span>
|
<span>{ "I am a child, as you can see" }</span>
|
||||||
</Container>
|
</Container>
|
||||||
}
|
};
|
||||||
```
|
|
||||||
|
|
||||||
Here's the implementation of `Container`:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
#[derive(Properties, Clone)]
|
|
||||||
pub struct Props {
|
|
||||||
pub id: String,
|
|
||||||
pub children: Children,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Container;
|
|
||||||
impl Component for Container {
|
|
||||||
type Properties = Props;
|
|
||||||
|
|
||||||
// ...
|
|
||||||
|
|
||||||
fn view(&self, ctx: &Context<Self>) -> Html {
|
|
||||||
html! {
|
|
||||||
<div id={ctx.props().id.clone()}>
|
|
||||||
{ ctx.props().children.clone() }
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Nested Children with Props
|
## Nested Children with Props
|
||||||
@ -80,29 +164,49 @@ impl Component for Container {
|
|||||||
Nested component properties can be accessed and mutated if the containing component types its children. In the following example, the `List` component can wrap `ListItem` components. For a real world example of this pattern, check out the `yew-router` source code. For a more advanced example, check out the `nested-list` example in the main yew repository.
|
Nested component properties can be accessed and mutated if the containing component types its children. In the following example, the `List` component can wrap `ListItem` components. For a real world example of this pattern, check out the `yew-router` source code. For a more advanced example, check out the `nested-list` example in the main yew repository.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
html! {
|
use std::rc::Rc;
|
||||||
<List>
|
use yew::{html, ChildrenWithProps, Component, Context, Html, Properties};
|
||||||
<ListItem value="a" />
|
|
||||||
<ListItem value="b" />
|
|
||||||
<ListItem value="c" />
|
|
||||||
</List>
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
```rust
|
#[derive(Clone, PartialEq, Properties)]
|
||||||
#[derive(Properties, Clone)]
|
pub struct ListItemProps {
|
||||||
|
value: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ListItem;
|
||||||
|
|
||||||
|
impl Component for ListItem {
|
||||||
|
type Message = ();
|
||||||
|
type Properties = ListItemProps;
|
||||||
|
|
||||||
|
fn create(_ctx: &Context<Self>) -> Self {
|
||||||
|
Self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view(&self, ctx: &Context<Self>) -> Html {
|
||||||
|
html! {
|
||||||
|
<span>
|
||||||
|
{ ctx.props().value.clone() }
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Properties)]
|
||||||
pub struct Props {
|
pub struct Props {
|
||||||
pub children: ChildrenWithProps<ListItem>,
|
pub children: ChildrenWithProps<ListItem>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct List;
|
pub struct List;
|
||||||
impl Component for List {
|
impl Component for List {
|
||||||
|
type Message = ();
|
||||||
type Properties = Props;
|
type Properties = Props;
|
||||||
|
|
||||||
// ...
|
fn create(_ctx: &Context<Self>) -> Self {
|
||||||
|
Self
|
||||||
|
}
|
||||||
|
|
||||||
fn view(&self, ctx: &Context<Self>) -> Html {
|
fn view(&self, ctx: &Context<Self>) -> Html {
|
||||||
html!{{
|
html! {{
|
||||||
for ctx.props().children.iter().map(|mut item| {
|
for ctx.props().children.iter().map(|mut item| {
|
||||||
let mut props = Rc::make_mut(&mut item.props);
|
let mut props = Rc::make_mut(&mut item.props);
|
||||||
props.value = format!("item-{}", props.value);
|
props.value = format!("item-{}", props.value);
|
||||||
@ -111,6 +215,13 @@ impl Component for List {
|
|||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
html! {
|
||||||
|
<List>
|
||||||
|
<ListItem value="a" />
|
||||||
|
<ListItem value="b" />
|
||||||
|
<ListItem value="c" />
|
||||||
|
</List>
|
||||||
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
## Relevant examples
|
## Relevant examples
|
||||||
|
|||||||
@ -12,10 +12,22 @@ Using `web-sys`, you can create DOM elements and convert them into a `Node` - wh
|
|||||||
used as a `Html` value using `VRef`:
|
used as a `Html` value using `VRef`:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
// ...
|
use yew::{
|
||||||
fn view(&self, _ctx: &Context<Self>) -> Html {
|
Component, Context, html, Html, utils::document,
|
||||||
use yew::{utils::document, web_sys::{Element, Node}};
|
web_sys::{Element, Node}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Comp;
|
||||||
|
|
||||||
|
impl Component for Comp {
|
||||||
|
type Message = ();
|
||||||
|
type Properties = ();
|
||||||
|
|
||||||
|
fn create(_ctx: &Context<Self>) -> Self {
|
||||||
|
Self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view(&self, _ctx: &Context<Self>) -> Html {
|
||||||
// Create a div element from the document
|
// Create a div element from the document
|
||||||
let div: Element = document().create_element("div").unwrap();
|
let div: Element = document().create_element("div").unwrap();
|
||||||
// Add content, classes etc.
|
// Add content, classes etc.
|
||||||
@ -25,6 +37,7 @@ used as a `Html` value using `VRef`:
|
|||||||
// Return that Node as a Html value
|
// Return that Node as a Html value
|
||||||
Html::VRef(node)
|
Html::VRef(node)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Dynamic tag names
|
## Dynamic tag names
|
||||||
@ -35,12 +48,14 @@ Instead of having to use a big match expression, Yew allows you to set the tag n
|
|||||||
using `@{name}` where `name` can be any expression that returns a string.
|
using `@{name}` where `name` can be any expression that returns a string.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
|
use yew::html;
|
||||||
|
|
||||||
let level = 5;
|
let level = 5;
|
||||||
let text = "Hello World!".to_owned()
|
let text = "Hello World!".to_owned();
|
||||||
|
|
||||||
html! {
|
html! {
|
||||||
<@{format!("h{}", level)} class="title">{ content }</@>
|
<@{format!("h{}", level)} class="title">{ text }</@>
|
||||||
}
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
## Boolean Attributes
|
## Boolean Attributes
|
||||||
@ -49,35 +64,39 @@ Some content attributes (e.g checked, hidden, required) are called boolean attri
|
|||||||
boolean attributes need to be set to a bool value:
|
boolean attributes need to be set to a bool value:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
html! {
|
use yew::html;
|
||||||
<div hidden=true>
|
|
||||||
{ "This div is hidden." }
|
html! {
|
||||||
</div>
|
<div hidden=true>
|
||||||
}
|
{ "This div is hidden." }
|
||||||
|
</div>
|
||||||
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
This will result in **HTML** that's functionally equivalent to this:
|
This will result in **HTML** that's functionally equivalent to this:
|
||||||
```html
|
```html
|
||||||
<div hidden>This div is hidden.</div>
|
<div hidden>This div is hidden.</div>
|
||||||
```
|
```
|
||||||
|
|
||||||
Setting a boolean attribute to false is equivalent to not using the attribute at all; values from
|
Setting a boolean attribute to false is equivalent to not using the attribute at all; values from
|
||||||
boolean expressions can be used:
|
boolean expressions can be used:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
let no = 1 + 1 != 2;
|
use yew::html;
|
||||||
|
|
||||||
html! {
|
let no = 1 + 1 != 2;
|
||||||
<div hidden={no}>
|
|
||||||
{ "This div is NOT hidden." }
|
html! {
|
||||||
</div>
|
<div hidden={no}>
|
||||||
}
|
{ "This div is NOT hidden." }
|
||||||
|
</div>
|
||||||
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
This will result in the following **HTML**:
|
This will result in the following **HTML**:
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<div>This div is NOT hidden.</div>
|
<div>This div is NOT hidden.</div>
|
||||||
```
|
```
|
||||||
|
|
||||||
## Optional attributes for HTML elements
|
## Optional attributes for HTML elements
|
||||||
@ -85,11 +104,13 @@ This will result in the following **HTML**:
|
|||||||
Most HTML attributes can use optional values (Some(x) or None). This allows us to omit the attribute if the attribute is marked as optional.
|
Most HTML attributes can use optional values (Some(x) or None). This allows us to omit the attribute if the attribute is marked as optional.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
|
use yew::html;
|
||||||
|
|
||||||
let maybe_id = Some("foobar");
|
let maybe_id = Some("foobar");
|
||||||
|
|
||||||
html! {
|
html! {
|
||||||
<div id={maybe_id}></div>
|
<div id={maybe_id}></div>
|
||||||
}
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
If the attribute is set to `None`, the attribute won't be set in the DOM.
|
If the attribute is set to `None`, the attribute won't be set in the DOM.
|
||||||
@ -102,6 +123,8 @@ Listener attributes need to be passed a `Callback` which is a wrapper around a c
|
|||||||
<!--Component handler-->
|
<!--Component handler-->
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
|
use yew::{Component, Context, html, Html};
|
||||||
|
|
||||||
struct MyComponent;
|
struct MyComponent;
|
||||||
|
|
||||||
enum Msg {
|
enum Msg {
|
||||||
@ -113,20 +136,21 @@ impl Component for MyComponent {
|
|||||||
type Properties = ();
|
type Properties = ();
|
||||||
|
|
||||||
fn create(_ctx: &Context<Self>) -> Self {
|
fn create(_ctx: &Context<Self>) -> Self {
|
||||||
MyComponent;
|
Self
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(&mut self, msg: Self::Message) -> bool {
|
fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
|
||||||
match msg {
|
match msg {
|
||||||
Msg::Click => {
|
Msg::Click => {
|
||||||
// Handle Click
|
// Handle Click
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
fn view(&self, ctx: Context<Self>) -> Html {
|
fn view(&self, ctx: &Context<Self>) -> Html {
|
||||||
// Create a callback from a component link to handle it in a component
|
// Create a callback from a component link to handle it in a component
|
||||||
let click_callback = ctx.link().callback(|_: ClickEvent| Msg::Click);
|
let click_callback = ctx.link().callback(|_| Msg::Click);
|
||||||
html! {
|
html! {
|
||||||
<button onclick={click_callback}>
|
<button onclick={click_callback}>
|
||||||
{ "Click me!" }
|
{ "Click me!" }
|
||||||
@ -139,23 +163,32 @@ impl Component for MyComponent {
|
|||||||
<!--Agent Handler-->
|
<!--Agent Handler-->
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
|
use yew::{html, Component, Context, Html};
|
||||||
|
use yew_agent::{Dispatcher, Dispatched};
|
||||||
|
use website_test::agents::{MyWorker, WorkerMsg};
|
||||||
|
|
||||||
struct MyComponent {
|
struct MyComponent {
|
||||||
worker: Dispatcher<MyWorker>,
|
worker: Dispatcher<MyWorker>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Component for MyComponent {
|
impl Component for MyComponent {
|
||||||
type Message = ();
|
type Message = WorkerMsg;
|
||||||
type Properties = ();
|
type Properties = ();
|
||||||
|
|
||||||
fn create(_ctx: &Context<Self>) -> Self {
|
fn create(_ctx: &Context<Self>) -> Self {
|
||||||
MyComponent {
|
MyComponent {
|
||||||
worker: MyWorker::dispatcher()
|
worker: MyWorker::dispatcher(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn view(&self, _ctx: &Context<Self>) -> Html {
|
fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
|
||||||
|
self.worker.send(msg);
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view(&self, ctx: &Context<Self>) -> Html {
|
||||||
// Create a callback from a worker to handle it in another context
|
// Create a callback from a worker to handle it in another context
|
||||||
let click_callback = self.worker.callback(|_: ClickEvent| WorkerMsg::Process);
|
let click_callback = ctx.link().callback(|_| WorkerMsg::Process);
|
||||||
html! {
|
html! {
|
||||||
<button onclick={click_callback}>
|
<button onclick={click_callback}>
|
||||||
{ "Click me!" }
|
{ "Click me!" }
|
||||||
@ -168,6 +201,9 @@ impl Component for MyComponent {
|
|||||||
<!--Other Cases-->
|
<!--Other Cases-->
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
|
use yew::{Callback, Context, Component, html, Html};
|
||||||
|
use weblog::console_log;
|
||||||
|
|
||||||
struct MyComponent;
|
struct MyComponent;
|
||||||
|
|
||||||
impl Component for MyComponent {
|
impl Component for MyComponent {
|
||||||
@ -180,7 +216,7 @@ impl Component for MyComponent {
|
|||||||
|
|
||||||
fn view(&self, _ctx: &Context<Self>) -> Html {
|
fn view(&self, _ctx: &Context<Self>) -> Html {
|
||||||
// Create an ephemeral callback
|
// Create an ephemeral callback
|
||||||
let click_callback = Callback::from(|| {
|
let click_callback = Callback::from(|_| {
|
||||||
console_log!("clicked!");
|
console_log!("clicked!");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -9,22 +9,26 @@ The `html!` macro always requires a single root node. In order to get around thi
|
|||||||
<!--DOCUSAURUS_CODE_TABS-->
|
<!--DOCUSAURUS_CODE_TABS-->
|
||||||
<!--Valid-->
|
<!--Valid-->
|
||||||
```rust
|
```rust
|
||||||
|
use yew::html;
|
||||||
|
|
||||||
html! {
|
html! {
|
||||||
<>
|
<>
|
||||||
<div></div>
|
<div></div>
|
||||||
<p></p>
|
<p></p>
|
||||||
</>
|
</>
|
||||||
}
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
<!--Invalid-->
|
<!--Invalid-->
|
||||||
```rust
|
```rust ,compile_fail
|
||||||
|
use yew::html;
|
||||||
|
|
||||||
/* error: only one root html element allowed */
|
/* error: only one root html element allowed */
|
||||||
|
|
||||||
html! {
|
html! {
|
||||||
<div></div>
|
<div></div>
|
||||||
<p></p>
|
<p></p>
|
||||||
}
|
};
|
||||||
```
|
```
|
||||||
<!--END_DOCUSAURUS_CODE_TABS-->
|
<!--END_DOCUSAURUS_CODE_TABS-->
|
||||||
|
|
||||||
@ -36,20 +40,28 @@ Yew supports two different syntaxes for building html from an iterator:
|
|||||||
<!--DOCUSAURUS_CODE_TABS-->
|
<!--DOCUSAURUS_CODE_TABS-->
|
||||||
<!--Syntax Type 1-->
|
<!--Syntax Type 1-->
|
||||||
```rust
|
```rust
|
||||||
|
use yew::{html, Html};
|
||||||
|
|
||||||
|
let items = (1..=10).collect::<Vec<_>>();
|
||||||
|
|
||||||
html! {
|
html! {
|
||||||
<ul class="item-list">
|
<ul class="item-list">
|
||||||
{ props.items.iter().map(renderItem).collect::<Html>() }
|
{ items.iter().collect::<Html>() }
|
||||||
</ul>
|
</ul>
|
||||||
}
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
<!--Syntax Type 2-->
|
<!--Syntax Type 2-->
|
||||||
```rust
|
```rust
|
||||||
|
use yew::{html};
|
||||||
|
|
||||||
|
let items = (1..=10).collect::<Vec<_>>();
|
||||||
|
|
||||||
html! {
|
html! {
|
||||||
<ul class="item-list">
|
<ul class="item-list">
|
||||||
{ for props.items.iter().map(renderItem) }
|
{ for items.iter() }
|
||||||
</ul>
|
</ul>
|
||||||
}
|
};
|
||||||
```
|
```
|
||||||
<!--END_DOCUSAURUS_CODE_TABS-->
|
<!--END_DOCUSAURUS_CODE_TABS-->
|
||||||
|
|
||||||
|
|||||||
@ -9,6 +9,8 @@ All display text must be enclosed by `{}` blocks because text is handled as an e
|
|||||||
the largest deviation from normal HTML syntax that Yew makes.
|
the largest deviation from normal HTML syntax that Yew makes.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
|
use yew::html;
|
||||||
|
|
||||||
let text = "lorem ipsum";
|
let text = "lorem ipsum";
|
||||||
html!{
|
html!{
|
||||||
<>
|
<>
|
||||||
@ -16,7 +18,7 @@ html!{
|
|||||||
<div>{"dolor sit"}</div>
|
<div>{"dolor sit"}</div>
|
||||||
<span>{42}</span>
|
<span>{42}</span>
|
||||||
</>
|
</>
|
||||||
}
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
## Expressions
|
## Expressions
|
||||||
@ -24,6 +26,10 @@ html!{
|
|||||||
You can insert expressions in your HTML using `{}` blocks, as long as they resolve to `Html`
|
You can insert expressions in your HTML using `{}` blocks, as long as they resolve to `Html`
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
|
use yew::html;
|
||||||
|
|
||||||
|
let show_link = true;
|
||||||
|
|
||||||
html! {
|
html! {
|
||||||
<div>
|
<div>
|
||||||
{
|
{
|
||||||
@ -36,12 +42,14 @@ html! {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
}
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
It often makes sense to extract these expressions into functions or closures to optimize for readability:
|
It often makes sense to extract these expressions into functions or closures to optimize for readability:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
|
use yew::{html, Html};
|
||||||
|
|
||||||
let show_link = true;
|
let show_link = true;
|
||||||
let maybe_display_link = move || -> Html {
|
let maybe_display_link = move || -> Html {
|
||||||
if show_link {
|
if show_link {
|
||||||
@ -55,5 +63,5 @@ let maybe_display_link = move || -> Html {
|
|||||||
|
|
||||||
html! {
|
html! {
|
||||||
<div>{maybe_display_link()}</div>
|
<div>{maybe_display_link()}</div>
|
||||||
}
|
};
|
||||||
```
|
```
|
||||||
|
|||||||
@ -17,7 +17,9 @@ at the top of the application.
|
|||||||
|
|
||||||
Routes are defined by an `enum` which derives `Routable`. This enum must be `Clone + Sized.
|
Routes are defined by an `enum` which derives `Routable`. This enum must be `Clone + Sized.
|
||||||
```rust
|
```rust
|
||||||
#[derive(Routable)]
|
use yew_router::Routable;
|
||||||
|
|
||||||
|
#[derive(Clone, Routable)]
|
||||||
enum Route {
|
enum Route {
|
||||||
#[at("/")]
|
#[at("/")]
|
||||||
Home,
|
Home,
|
||||||
@ -38,6 +40,20 @@ nothing is rendered, and a message is logged to console stating that no route wa
|
|||||||
`yew_router::attach_route_listener` is used to attach a listener which is called every time route is changed.
|
`yew_router::attach_route_listener` is used to attach a listener which is called every time route is changed.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
|
use yew_router::{Router, Routable};
|
||||||
|
use yew::{Callback, function_component, html, Html};
|
||||||
|
|
||||||
|
#[derive(Clone, Routable)]
|
||||||
|
enum Route {
|
||||||
|
#[at("/")]
|
||||||
|
Home,
|
||||||
|
#[at("/secure")]
|
||||||
|
Secure,
|
||||||
|
#[not_found]
|
||||||
|
#[at("/404")]
|
||||||
|
NotFound,
|
||||||
|
}
|
||||||
|
|
||||||
#[function_component(Main)]
|
#[function_component(Main)]
|
||||||
fn app() -> Html {
|
fn app() -> Html {
|
||||||
html! {
|
html! {
|
||||||
@ -49,7 +65,7 @@ fn switch(route: &Route) -> Html {
|
|||||||
match route {
|
match route {
|
||||||
Route::Home => html! { <h1>{ "Home" }</h1> },
|
Route::Home => html! { <h1>{ "Home" }</h1> },
|
||||||
Route::Secure => {
|
Route::Secure => {
|
||||||
let callback = Callback::from(|_| yew_router::push_route(Routes::Home));
|
let callback = Callback::from(|_| yew_router::push_route(Route::Home));
|
||||||
html! {
|
html! {
|
||||||
<div>
|
<div>
|
||||||
<h1>{ "Secure" }</h1>
|
<h1>{ "Secure" }</h1>
|
||||||
|
|||||||
@ -55,7 +55,7 @@ If you would like to start your application with any dynamic properties, you can
|
|||||||
:::
|
:::
|
||||||
|
|
||||||
|
|
||||||
```rust
|
```rust ,no_run
|
||||||
use yew::prelude::*;
|
use yew::prelude::*;
|
||||||
|
|
||||||
enum Msg {
|
enum Msg {
|
||||||
|
|||||||
@ -17,7 +17,7 @@ exception. There are a few options available:
|
|||||||
|
|
||||||
This crate integrates with the familiar Rust `log` crate:
|
This crate integrates with the familiar Rust `log` crate:
|
||||||
|
|
||||||
```rust
|
```rust ,ignore
|
||||||
// setup
|
// setup
|
||||||
fn main() {
|
fn main() {
|
||||||
wasm_logger::init(wasm_logger::Config::default());
|
wasm_logger::init(wasm_logger::Config::default());
|
||||||
|
|||||||
@ -16,7 +16,7 @@ Feel free to contribute to add instructions for your editor of choice.
|
|||||||
3. Give it a name and description of your preference.
|
3. Give it a name and description of your preference.
|
||||||
4. Paste the following snippet in Template Text section:
|
4. Paste the following snippet in Template Text section:
|
||||||
|
|
||||||
```rust
|
```rust ,ignore
|
||||||
use yew::prelude::*;
|
use yew::prelude::*;
|
||||||
|
|
||||||
struct $NAME$;
|
struct $NAME$;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user