I'm submitting a ... feature request
I'm thinking about creating a small library with yew. But I want some reusable components and I want the parent components to pass the actual child-components instead of the components specifying their children themselves. My point is, at the moment I can't just say
<MyButton: color=Color::Red, text="Click the octocat">
<MyIcon icon="octocat",/>
</MyButton>
and expect MyButton to render MyIcon at the right position, or render MyIcon at all. That's a limitation if you want to create a good library.
I was thinking to bypass this by adding a children-attribute to the components properties and use this in view. But this idea has three drawbacks, first I might get into trouble with the rust typesystem (haven't tested it yet), second I have to reuse this code over all the components I want to use this on, and third this is quite ugly and would look like this:
<MyButton color=Color::Red, children=html! { <MyIcon icon="octocat",/> }, text="Click the octocat",/>
(Assuming nesting the html!-macro would even work, for which I strongly believe it won't)
Because of this I suggest to either pass the children via properties (just like react does with this.props.children) or pass the children to view(...). Or one could try to implement the slot-system used by vue and polymer, but that's way more complex. (Personally I like the slot-system more because it allows better customization of components where react only gives you to possibility to pass multiple render-functions via properties)
I think the code demo you shown should work, since I did the similar thing the other day:
impl Renderable<Context, Self> for Model {
fn view(&self) -> Html<Context, Self> {
html! {
<div class="model-container",>
<Copyrights: since=2018, name="huangjj27", rights=true, />
</div>
}
}
}
and it look like:

these codes work with yew v0.4 which is a patching dependency pointing to github master branch.
I might be wrong but did you check in your web browser that:
style.css have set to right class to your MyIcon?I'm not sure if this help, but I come across similar error in #258
<MyButton: color=Color::Red, text="Click the octocat">
<MyIcon: icon="octocat",/> <!-- add the colon after "MyIcon and have another try? -->
</MyButton>
To further expound on what is suggested, I think there are three implementation options:
pub trait Component {
...
fn set_child(props: &mut Self::Properties, child: Html<Self>) {
// do nothing
}
}
This function would be called when the html macro finds nodes nested within a component, allowing the passage of those nodes to the component's Properties. This could eventually be implemented as a proc macro where macro identifies the field in the Self::Properties to assign the child to.
pub trait Component {
...
fn set_child(&mut self, child: Html<Self>) -> ShouldRender {
// do nothing
false
}
}
Allowing setting the component directly. I don't like this, because it introduces another vector through which state in another component can be changed at any time.
view() like so:pub trait Renderable<T:...> {
fn view(&mut self, child: &Html<T>) -> Html<T>;
}
or once Optional rendering lands:
pub trait Renderable<T:...> {
fn view(&self, child: Option<&Html<T>>) -> Html<T>;
}
There are a few implementation problems with this though, the child would be difficult to work with, as I don't think that &VNode<T> can easily be turned into VNode<T> as called for in the return type (for those unfamiliar Html<T> is an alias to VNode<T>).
Additionally, I think some confusion would be encountered regarding to which message type would be sent if something like this was to be implemented.
All my examples assume this would work:
html! {
<Outer: >
<Inner:>
<button: onclick=|_| Inner::Message::Variant ,>{"button"}</button>
</Inner>
</Outer>
}
Although this may also be a viable approach:
html! {
<Outer: >
<Inner:>
<button: onclick=|_| Outer::Message::Variant ,>{"button"}</button>
</Inner>
</Outer>
}
What would happen if someone wanted to do the following?
html! {
<Outer: >
<Inner:>
<button: onclick=|_| Outer::Message::Variant ,>{"button"}</button>
<button: onclick=|_| Inner::Message::Variant ,>{"button"}</button>
</Inner>
</Outer>
}
Would this mean that you would effectively have to deal with something like this for Renderable? I think this would be difficult to reason about in a typesafe manner:
fn view(&self, child: Option<&Html<Any>>) -> Html<T>;
Without a doubt, this is a _very_ hard to implement feature.
This feature is essential is you want to build a container component.
I think
rust
pub trait Renderable<T:...> {
fn view(&self, child: Option<&Html<T>>) -> Html<T>;
}
````
is a good interface,
but what about
rust
pub trait Renderable
}
````
this interface can give the container more control over elements.
It may be helpful to mention how things are done in React, Angular and Vue.
In React children are passed via props.children. This property contains the virtual nodes which you're supposed to put at some point in your render:
render() {
return (<div class="wrapper">
{ this.props.children }
</div>)
}
This approach works fine for 95% of all problems, but for components that allow custom html at more than one place you normally pass a function that can have some parameters and returns a vnode, e.g. <ComplexTable header={(param) => (<div>{param}</div>)}><Child/></ComplexTable>. Of course this approach has its valid use cases, but it will get ugly when a component has more than one place for custom html.
I think the html!-macro can easily be modified to parse a components children and set them on some attribute on the component. Instead of approaches 1. and 2. pointed out by @hgzimmerman I'd suggest keeping the children as an attribute, calling a lifecycle-method when children change and re-render the component if the children change. This should avoid duplicate logic.
Angular does something else entirely, and in my personal opinion everything I can legally say about this framework is too nice. Angular uses templates, which are kind of global (once we duplicated a component and it broke our app because we copied the template-id too), or <ng-children></ng-children> in the component-markup to render the children. Templates have their own scope and such, which add to the overengineered mess of angular, so we'll ignore them. Using <ng-children/> will automatically render what was passed to the component as children, that's basically what react does except you can't access the vnodes.
That leaves Vue and its concept of slots. It's just like Angulars <ng-children/>, but you can have multiple versions of it in one component:
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
By passing the slot name when passing children to the component you can control where your html goes. The scoping behavior also is the most logical. @hgzimmerman already asked the question what would happen with something like this:
html! {
<Outer: >
<Inner:>
<button: onclick=|_| Outer::Message::Variant ,>{"button"}</button>
</Inner>
</Outer>
}
In Vue the <button/> has the scope of the component that defined it, that is the component that calls html! here. That implies the message should go to this component and neither to Inner or Outer. IMO this is the most logical, since you don't want to know the inner workings of another component and you don't need the ability to send events to it nor do you want to lose the ability of using event-listeners just because the component is nested inside another component.
To sum all of it up, I think we should go with the Vue slot-idea, it allows us to define different slots where custom html can be put and provides a satisfying answer to scoping-problems. And we don't need to change already existing method-signatures or add properties. All we need to do is using a <slot/>-tag or something the like in the html!-macro.
Of course implementing is the hard part. After some time looking at the source-code I realized I am not familiar enough with this codebase. E.g. I couldn't figure out how event-listeners make sure the right component is being messaged.
I think the following things need to be done, feel free to corrent me if I'm wrong:
html!-macro to allow children for components<slot/>-tagFor now only one slot should be enough, after implementing this one could see if multiple slots are needed. The only thing this approach cannot provide is reusing the children, but I believe every usecase of this can be solved differently. E.g., if you want something template-like you can either just pass a Fn(&SomeObj) -> Html<COMP> or a component whose sole property is the data-structure you want to render type MyTable = TableComp<MyButton>; html! { <MyTable: data={...}/> }.
Fixed in 0.9
Most helpful comment
To further expound on what is suggested, I think there are three implementation options:
This function would be called when the html macro finds nodes nested within a component, allowing the passage of those nodes to the component's
Properties. This could eventually be implemented as a proc macro where macro identifies the field in theSelf::Propertiesto assign the child to.Allowing setting the component directly. I don't like this, because it introduces another vector through which state in another component can be changed at any time.
view()like so:or once Optional rendering lands:
There are a few implementation problems with this though, the
childwould be difficult to work with, as I don't think that&VNode<T>can easily be turned intoVNode<T>as called for in the return type (for those unfamiliarHtml<T>is an alias toVNode<T>).Additionally, I think some confusion would be encountered regarding to which message type would be sent if something like this was to be implemented.
All my examples assume this would work:
Although this may also be a viable approach:
What would happen if someone wanted to do the following?
Would this mean that you would effectively have to deal with something like this for Renderable? I think this would be difficult to reason about in a typesafe manner:
Without a doubt, this is a _very_ hard to implement feature.