Skip to content

Commit

Permalink
Implicit optional attributes (#1637)
Browse files Browse the repository at this point in the history
  • Loading branch information
siku2 authored Apr 30, 2021
1 parent e5eda4e commit 99d5088
Show file tree
Hide file tree
Showing 80 changed files with 1,054 additions and 1,159 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/pull-request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ jobs:
strategy:
matrix:
toolchain:
- 1.45.0 # MSRV
- 1.51.0 # min version with const generics
- stable
- nightly

Expand Down
5 changes: 3 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,10 @@ Alternatively, you can set the `ECHO_SERVER_URL` environment variable to the URL

When adding or updating tests, please make sure to update the appropriate `stderr` file, which you can find [here](https://github.com/yewstack/yew/tree/master/packages/yew-macro/tests/macro) for the `html!` macro.
These files ensure that macro compilation errors are correct and easy to understand.
These errors can change with each release of the compiler so they should be generated with the MSRV (currently 1.45).
These errors can change with each release of the compiler so they should be generated with the Rust version 1.51
(because some tests make use of const generics which were stabilized in that version).

To update or generate a new `stderr` file you can run `TRYBUILD=overwrite cargo +1.45.2 test` in the `yew-macro` directory.
To update or generate a new `stderr` file you can run `cargo make test-overwrite` in the `yew-macro` directory.

## Linting

Expand Down
4 changes: 2 additions & 2 deletions docs/concepts/function-components/attribute.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ pub fn rendered_at(props: &RenderedAtProps) -> Html {
html! {
<p>
<b>{ "Rendered at: " }</b>
{ &props.time }
{ props.time.clone() }
</p>
}
}
Expand All @@ -46,7 +46,7 @@ fn app() -> Html {
let counter = Rc::clone(&counter);
Callback::from(move |_| set_counter(*counter + 1))
};

html! {
<div>
<button onclick=onclick>{ "Increment value" }</button>
Expand Down
41 changes: 1 addition & 40 deletions docs/concepts/html/components.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ impl Component for Container {

fn view(&self) -> Html {
html! {
<div id=&self.0.id>
<div id=self.0.id.clone()>
{ self.0.children.clone() }
</div>
}
Expand Down Expand Up @@ -112,45 +112,6 @@ impl Component for List {
}
```

## Transformers

Whenever you set a prop its value goes through a transformation step first.
If the value already has the correct type, this step doesn't do anything.
However, transformers can be useful to reduce code repetition.

The following is a list of transformers you should know about:

- `&T` -> `T`

Clones the reference to get an owned value.

- `&str` -> `String`

Allows you to use string literals without adding `.to_owned()` at the end.

- `T` -> `Option<T>`

Wraps the value in `Some`.

```rust
struct Props {
unique_id: Option<usize>,
text: String,
}

struct Model;
impl Component for Model {
type Properties = Props;

// ...
}

// transformers allow you to write this:
html! { <Model unique_id=5 text="literals are fun" /> };
// instead of:
html! { <Model unique_id=Some(5) text="literals are fun".to_owned() /> };
```

## Relevant examples
- [Boids](https://github.com/yewstack/yew/tree/master/examples/boids)
- [Router](https://github.com/yewstack/yew/tree/master/examples/router)
Expand Down
20 changes: 15 additions & 5 deletions docs/concepts/html/elements.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,26 +15,36 @@ let level = 5;
let text = "Hello World!".to_owned()

html! {
<@{format!("h{}", level)} class="title">{ content }</@>
<@{format!("h{}", level)} class="title">{ text }</@>
}
```

## Optional attributes for HTML elements

Most HTML attributes can be marked as optional by placing a `?` in front of
the `=` sign. This makes them accept the same type of value as otherwise, but
wrapped in an `Option<T>`:
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
let maybe_id = Some("foobar");

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.

Please note that it is also valid to give only the value as properties behave
like `Into<Option<T>>`:

```rust
let id = "foobar";

html! {
<div id=id></div>
}
```

## Listeners

Listener attributes need to be passed a `Callback` which is a wrapper around a closure. How you create your callback depends on how you wish your app to react to a listener event:
Expand Down
6 changes: 3 additions & 3 deletions examples/boids/src/slider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,13 +80,13 @@ impl Component for Slider {

html! {
<div class="slider">
<label for=id class="slider__label">{ label }</label>
<label for=id.clone() class="slider__label">{ label }</label>
<input type="range"
id=id
class="slider__input"
min=min max=max step=step
min=min.to_string() max=max.to_string() step=step.to_string()
oninput=onchange.reform(|data: InputData| data.value.parse().unwrap())
value=value
value=value.to_string()
/>
<span class="slider__value">{ display_value }</span>
</div>
Expand Down
6 changes: 3 additions & 3 deletions examples/crm/src/add_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,19 +78,19 @@ impl Component for AddClientForm {
<input
class=classes!("new-client", "firstname")
placeholder="First name"
value=&client.first_name
value=client.first_name.clone()
oninput=link.callback(|e: InputData| Msg::UpdateFirstName(e.value))
/>
<input
class=classes!("new-client", "lastname")
placeholder="Last name"
value=&client.last_name
value=client.last_name.clone()
oninput=link.callback(|e: InputData| Msg::UpdateLastName(e.value))
/>
<textarea
class=classes!("new-client", "description")
placeholder="Description"
value=&client.description
value=client.description.clone()
oninput=link.callback(|e: InputData| Msg::UpdateDescription(e.value))
/>
</div>
Expand Down
2 changes: 1 addition & 1 deletion examples/js_callback/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ impl Component for Model {
<textarea
class="code-block"
oninput=self.link.callback(|input: InputData| Msg::Payload(input.value))
value=&self.payload
value=self.payload.clone()
/>
<button onclick=self.link.callback(|_| Msg::Payload(bindings::get_payload()))>
{ "Get the payload!" }
Expand Down
2 changes: 1 addition & 1 deletion examples/keyed_list/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ impl Model {
{ self.build_component_ratio }
</p>
<input name="ratio" type="range" class="form-control-range" min="0.0" max="1.0" step="any"
value=self.build_component_ratio
value=self.build_component_ratio.to_string()
oninput=self.link.callback(|e: InputData| Msg::ChangeRatio(e.value))
/>
</div>
Expand Down
4 changes: 2 additions & 2 deletions examples/keyed_list/src/person.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,9 +116,9 @@ impl PersonType {
}
Self::Component(info) => {
if keyed {
html! { <PersonComponent key=info.id.to_string() info=info /> }
html! { <PersonComponent key=info.id.to_string() info=info.clone() /> }
} else {
html! { <PersonComponent info=info /> }
html! { <PersonComponent info=info.clone() /> }
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion examples/mount_point/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ impl Component for Model {
html! {
<div>
<input
value=&self.name
value=self.name.clone()
oninput=self.link.callback(|e: InputData| Msg::UpdateName(e.value))
/>
<p>{ self.name.chars().rev().collect::<String>() }</p>
Expand Down
6 changes: 3 additions & 3 deletions examples/nested_list/src/list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,9 @@ where
}
}

impl Into<Html> for ListVariant {
fn into(self) -> Html {
match self.props {
impl From<ListVariant> for Html {
fn from(variant: ListVariant) -> Html {
match variant.props {
Variants::Header(props) => {
VComp::new::<ListHeader>(props, NodeRef::default(), None).into()
}
Expand Down
3 changes: 2 additions & 1 deletion examples/nested_list/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use std::cell::RefCell;
use std::fmt;
use std::ops::Deref;
use std::rc::Rc;
use yew::html::{Component, ComponentLink};
use yew::html::{Component, ComponentLink, ImplicitClone};

pub struct WeakComponentLink<COMP: Component>(Rc<RefCell<Option<ComponentLink<COMP>>>>);

Expand All @@ -16,6 +16,7 @@ impl<COMP: Component> Clone for WeakComponentLink<COMP> {
Self(Rc::clone(&self.0))
}
}
impl<COMP: Component> ImplicitClone for WeakComponentLink<COMP> {}

impl<COMP: Component> Default for WeakComponentLink<COMP> {
fn default() -> Self {
Expand Down
2 changes: 1 addition & 1 deletion examples/router/src/components/author_card.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ impl Component for AuthorCard {
<div class="media">
<div class="media-left">
<figure class="image is-128x128">
<img src=author.image_url />
<img src=author.image_url.clone() />
</figure>
</div>
<div class="media-content">
Expand Down
2 changes: 1 addition & 1 deletion examples/router/src/components/post_card.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ impl Component for PostCard {
<div class="card">
<div class="card-image">
<figure class="image is-2by1">
<img src={ &post.image_url } loading="lazy" />
<img src=post.image_url.clone() loading="lazy" />
</figure>
</div>
<div class="card-content">
Expand Down
2 changes: 1 addition & 1 deletion examples/router/src/components/progress_delay.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ impl Component for ProgressDelay {
fn view(&self) -> Html {
let value = self.value;
html! {
<progress class="progress is-primary" value=value max=1.0>
<progress class="progress is-primary" value=value.to_string() max=1.0>
{ format!("{:.0}%", 100.0 * value) }
</progress>
}
Expand Down
2 changes: 1 addition & 1 deletion examples/router/src/pages/author.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ impl Component for Author {
</div>
<div class="tile is-parent">
<figure class="tile is-child image is-square">
<img src=author.image_url />
<img src=author.image_url.clone() />
</figure>
</div>
<div class="tile is-parent">
Expand Down
2 changes: 1 addition & 1 deletion examples/router/src/pages/author_list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ impl Component for AuthorList {
}

fn view(&self) -> Html {
let authors = self.seeds.iter().map(|seed| {
let authors = self.seeds.iter().map(|&seed| {
html! {
<div class="tile is-parent">
<div class="tile is-child">
Expand Down
6 changes: 3 additions & 3 deletions examples/router/src/pages/post.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ impl Component for Post {
html! {
<>
<section class="hero is-medium is-light has-background">
<img class="hero-background is-transparent" src=post.image_url />
<img class="hero-background is-transparent" src=post.image_url.clone() />
<div class="hero-body">
<div class="container">
<h1 class="title">
Expand Down Expand Up @@ -79,7 +79,7 @@ impl Post {
<article class="media block box my-6">
<figure class="media-left">
<p class="image is-64x64">
<img src=quote.author.image_url loading="lazy" />
<img src=quote.author.image_url.clone() loading="lazy" />
</p>
</figure>
<div class="media-content">
Expand All @@ -99,7 +99,7 @@ impl Post {
fn render_section_hero(&self, section: &content::Section) -> Html {
html! {
<section class="hero is-dark has-background mt-6 mb-3">
<img class="hero-background is-transparent" src=section.image_url loading="lazy" />
<img class="hero-background is-transparent" src=section.image_url.clone() loading="lazy" />
<div class="hero-body">
<div class="container">
<h2 class="subtitle">{ &section.title }</h2>
Expand Down
11 changes: 4 additions & 7 deletions examples/router/src/switch.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
use yew::{
virtual_dom::{Transformer, VComp},
web_sys::Url,
};
use yew::{html::IntoPropValue, web_sys::Url};
use yew_router::{components::RouterAnchor, prelude::*, switch::Permissive};

#[derive(Clone, Debug, Switch)]
Expand Down Expand Up @@ -79,9 +76,9 @@ impl Switch for PublicUrlSwitch {

// this allows us to pass `AppRoute` to components which take `PublicUrlSwitch`.

impl Transformer<AppRoute, PublicUrlSwitch> for VComp {
fn transform(from: AppRoute) -> PublicUrlSwitch {
from.into_public()
impl IntoPropValue<PublicUrlSwitch> for AppRoute {
fn into_prop_value(self: AppRoute) -> PublicUrlSwitch {
self.into_public()
}
}

Expand Down
2 changes: 1 addition & 1 deletion examples/store/src/post.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ impl Component for Post {
<h2>{ format!("Post #{}", self.id) }</h2>
<p>{text}</p>

<TextInput value=text onsubmit=self.link.callback(Msg::UpdateText) />
<TextInput value=text.to_owned() onsubmit=self.link.callback(Msg::UpdateText) />
<button onclick=self.link.callback(|_| Msg::Delete)>
{ "Delete" }
</button>
Expand Down
2 changes: 1 addition & 1 deletion examples/store/src/text_input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ impl Component for TextInput {
html! {
<input
type="text"
value=&self.text
value=self.text.clone()
oninput=self.link.callback(|e: InputData| Msg::SetText(e.value))
onkeydown=self.link.batch_callback(move |e: KeyboardEvent| {
e.stop_propagation();
Expand Down
4 changes: 2 additions & 2 deletions examples/todomvc/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ impl Model {
<input
class="new-todo"
placeholder="What needs to be done?"
value=&self.state.value
value=self.state.value.clone()
oninput=self.link.callback(|e: InputData| Msg::Update(e.value))
onkeypress=self.link.batch_callback(|e: KeyboardEvent| {
if e.key() == "Enter" { Some(Msg::Add) } else { None }
Expand Down Expand Up @@ -242,7 +242,7 @@ impl Model {
class="edit"
type="text"
ref=self.focus_ref.clone()
value=&self.state.edit_value
value=self.state.edit_value.clone()
onmouseover=self.link.callback(|_| Msg::Focus)
oninput=self.link.callback(|e: InputData| Msg::UpdateEdit(e.value))
onblur=self.link.callback(move |_| Msg::Edit(idx))
Expand Down
2 changes: 1 addition & 1 deletion packages/yew-components/src/select.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ where
html! {
<select
ref=self.select_ref.clone()
id=self.props.id
id=self.props.id.clone()
class=self.props.class.clone()
disabled=self.props.disabled
onchange=self.on_change()
Expand Down
6 changes: 3 additions & 3 deletions packages/yew-dsl/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@ impl<COMP: Component> BoxedVNodeProducer<COMP> {
}
}

impl<COMP: Component> Into<VNode> for BoxedVNodeProducer<COMP> {
fn into(self) -> VNode {
self.build()
impl<COMP: Component> From<BoxedVNodeProducer<COMP>> for VNode {
fn from(value: BoxedVNodeProducer<COMP>) -> VNode {
value.build()
}
}

Expand Down
Loading

0 comments on commit 99d5088

Please sign in to comment.