I want to build a resizable component which acts as a container for other components. Take for example any popular editor, it has various panes / widgets (File explorer, terminal, etc.) which can be resized. The purpose of each widget is different but the resizable behavior is common to all. There are 2 ways I can think of, to achieve this:
// Component x
html! {
<Resizable>
<div>child component x</div>
</Resizable>
}
// Component y
html! {
<Resizable>
<div>child component y</div>
</Resizable>
}
Hey @krankur, great question. I think you could try giving your Resizable component a children property which is of type Html<Self> and then do this:
// Component x
let children = html! {
<div>{ /*child component x*/ </div>
};
html! {
<Resizable children=children />
}
I'm also in favour of supporting wrapping html elements with component tags but it's not implemented yet. I'll see about adding it to the next release
New issue here: https://github.com/DenisKolodin/yew/issues/537
Thanks for logging the issue.
I think you could try giving your
Resizablecomponent achildrenproperty which is of typeHtml<Self>and then do this:
So, after I have the passed the children (of type Html<Self>) to the children property of the Resizable component, how can I append the children to the component's content (innerHTML)? Is it possible currently, or will it be supported only after #537 is resolved?
Then, in the Resizable component you would have something like:
html! {
<div>
{ self.props.children }
</div>
}
@jstarry So, I tried this:
pub struct Props {
children: Option<Html<Self>>,
}
But the compiler complains, saying:
the trait bound `components::navigation::resizable::Props: yew::Component` is not satisfied
the trait `yew::Component` is not implemented for `components::navigation::resizable::Props`
note: required by `yew::virtual_dom::VNode`rustc(E0277)
resizable.rs(37, 5): the trait `yew::Component` is not implemented for `components::navigation::resizable::Props`
the trait bound `yew::virtual_dom::VNode<components::navigation::resizable::Props>: std::clone::Clone` is not satisfied
the trait `std::clone::Clone` is not implemented for `yew::virtual_dom::VNode<components::navigation::resizable::Props>`
note: required because of the requirements on the impl of `std::clone::Clone` for `std::option::Option<yew::virtual_dom::VNode<components::navigation::resizable::Props>>`
note: required by `std::clone::Clone::clone`rustc(E0277)
resizable.rs(37, 5): the trait `std::clone::Clone` is not implemented for `yew::virtual_dom::VNode<components::navigation::resizable::Props>`
the trait bound `components::navigation::resizable::Props: yew::Component` is not satisfied
the trait `yew::Component` is not implemented for `components::navigation::resizable::Props`
note: required by `yew::virtual_dom::VNode`rustc(E0277)
resizable.rs(37, 5): the trait `yew::Component` is not implemented for `components::navigation::resizable::Props`
binary operation `==` cannot be applied to type `std::option::Option<yew::virtual_dom::VNode<components::navigation::resizable::Props>>`
note: an implementation of `std::cmp::PartialEq` might be missing for `std::option::Option<yew::virtual_dom::VNode<components::navigation::resizable::Props>>`rustc(E0369)
binary operation `!=` cannot be applied to type `std::option::Option<yew::virtual_dom::VNode<components::navigation::resizable::Props>>`
note: an implementation of `std::cmp::PartialEq` might be missing for `std::option::Option<yew::virtual_dom::VNode<components::navigation::resizable::Props>>`rustc(E0369)
Am I missing something here?
@krankur Looks like you're trying to use Props as a Component somewhere. I would need to see more of your code to know where the issue is happening. Also, I would recommend that you try using latest master which no longer requires that Component::Properties implement Clone and PartialEq. You can change your Cargo.toml from yew = "0.7" to:
yew = { git = "https://github.com/DenisKolodin/yew" }
This will require that you add this attribute to your Props:
use yew::Properties;
#[derive(Properties)]
pub struct Props {
#[props(required)]
children: Html<Self>,
}
@jstarry Here is a link to my code https://github.com/krankur/atelier-editor/blob/resizable-layout/editor/src/components/navigation/resizable.rs
I am on yew version 0.6. Do I need to bump the version?
I tried removing the Clone trait for Props in one of the Yew examples as well, which uses the latest yew version, and the compiler still complains that the trait bound barrier::Props: std::clone::Clone is not satisfied.
Yes try using master which is 0.8.
A few suggestions on your code:
Your Resizable struct should have a props field
You should destructure the optional innerTemplate or make it not an Option type. Putting an Option inside the html! block will not work
Hey thanks Justin for all the help and patience. Sorry about the messy code. I was still trying to figure out the the trait yew::Component is not implemented for components::navigation::resizable::Props error, so didn't get to those parts.
So, I decided to upgrade to 0.8, as you suggested. But I am still getting the same compilation error: the trait yew::Component is not implemented for components::navigation::resizable::Props. If I remove the innerTemplate field from here, then the error goes away.
Also, I am getting the following error after bumping the yew version, on https://github.com/krankur/atelier-editor/blob/resizable-layout/editor/src/core/model.rs#L243, which I am not able to figure out. Not sure if I should continue this discussion here or move to another issue, or gitter.
the trait bound `yew::virtual_dom::VComp<_>: yew::virtual_dom::vcomp::Transformer<_, [closure@editor/src/core/model.rs:245:44: 245:94], std::option::Option<yew::Callback<std::string::String>>>` is not satisfied
the trait `yew::virtual_dom::vcomp::Transformer<_, [closure@editor/src/core/model.rs:245:44: 245:94], std::option::Option<yew::Callback<std::string::String>>>` is not implemented for `yew::virtual_dom::VComp<_>`
Let me know if I should move this discussion to somewhere else.
Hey no worries, we will figure this out :)
I think the Component trait not implemented error is happening because the html macro is assuming it is used inside a component method. So your approach of initializing to a default value inside Props is breaking things. I will have to look into making this work in the macro to fix the issue.
To get around this for now, can you try creating the innerTemplate value inside the render method of a component which is using your Resizable component?
In regards to your new issue. This is a breaking change in 0.8 which has an easy fix. Simply change onsignal inside PrefabNewModal to not be an Option type. Then add the #[props(required)] attribute to onsignal.
This is really new stuff so I appreciate you trying it out!
The new issue, one related to onsignal, got fixed by doing the changes that you suggested. Thanks :)
About the workaround for the original issue, I am assuming that by render method you mean the view method of the Renderable implementation for the component which is using the Resizable component. But I am not sure how would I pass the innerTemplate to the Resizable component. I need to append the innerTemplate inside the Resizable component. Are you suggesting something like:
impl Renderable<Model> for Model {
fn view(&self) -> Html<Self> {
let innerTemplate: Html<Self> = html! {
<div>{"I am inner template"}</div>
};
html! {
<Resizable>{innerTemplate}</Resizable>
}
}
}
I tried the above, but this also doesn't compile.
Yes, sorry I meant the view method. Does it work if you try:
impl Renderable<Model> for Model {
fn view(&self) -> Html<Self> {
let innerTemplate: Html<Self> = html! {
<div>{"I am inner template"}</div>
};
html! {
<Resizable innerTemplate=innerTemplate />
}
}
}
If I do the above, the compiler complains that no field innerTemplate on type components::navigation::resizable::Props and when I add the innerTemplate field to components::navigation::resizable::Props like below:
#[derive(Properties)]
pub struct Props {
innerTemplate: Option<Html<Self>>
}
we again go back to where we started from, i.e. the compiler complains saying:
the trait bound `components::navigation::resizable::Props: yew::Component` is not satisfied
the trait `yew::Component` is not implemented for `components::navigation::resizable::Props`
note: required by `yew::virtual_dom::VNode`rustc(E0277)
It shouldn't be Html<Self> you need to do Html<Resizable>
Oops! corrected. And that fixes the original compilation error. Yay! Getting another error now here:
`yew::virtual_dom::VNode<components::navigation::resizable::Resizable>` doesn't implement `std::fmt::Display`
`yew::virtual_dom::VNode<components::navigation::resizable::Resizable>` cannot be formatted with the default formatter
Hmm this is because self.innerTemplate.as_ref().unwrap() is a &VNode<Resizable> which cannot be used in that way. Looks like the only way to get this working without changes in yew is for you to have innerTemplate be a type that implements Renderable. Try that :)
But wouldn't fixing the innerTemplate to a specific type (that implements Renderable) restrict the innerTemplate to always be of that type? I want the content of Resizable to not be fixed.
@krankur how about if you set innerTemplate to Box<impl Renderable<Resizable>> does that fix your problem?
Is that allowed? I tried this:
#[derive(Properties)]
pub struct Props {
// pub innerTemplate: Option<Html<Resizable>>
pub innerTemplate: Box<impl Renderable<Resizable>>
}
and got the following error:
`impl Trait` not allowed outside of function and inherent method return types
So I tried this as well:
#[derive(Properties)]
pub struct Props {
// pub innerTemplate: Option<Html<Resizable>>
pub innerTemplate: Box<Resizable>
}
And I got the components::navigation::resizable::Resizable doesn't implement std::fmt::Display error.
I am new to Rust, which you might already have figured out, hence all these questions.
Sorry, I meant to say Box<dyn Renderable<Resizable>> 馃槃 This will allow you to use a different innerTemplate each time you use Resizable.
So, putting it all together (and a few minor changes) you would have:
// model.rs
struct ModelInner;
impl Renderable<Resizable> for ModelInner {
fn view(&self) -> Html<Resizable> {
html! {<div>{"I am inner template"}</div>}
}
}
impl Renderable<Model> for Model {
fn view(&self) -> Html<Self> {
html! {
<Resizable inner_template=Box::new(ModelInner) />
}
}
}
// resizable.rs
pub struct Resizable {
console: ConsoleService,
state: ResizableState,
props: Props,
}
#[derive(Properties)]
pub struct Props {
#[props(required)]
pub inner_template: Box<dyn Renderable<Resizable>>,
}
impl Renderable<Resizable> for Resizable {
fn view(&self) -> Html<Self> {
html! {
<div class="resizable",
onmousedown=|_|ResizableMsg::StartResize, >
{ self.props.inner_template.deref() }
</div>
}
}
}
It worked! Finally! I would never have been able to do this without your help. I learnt a lot. Thanks you so much for all your time and patience.
Great!! Happy to help. This isn't the most ergonomic code but cool to see that it can work! This solution will be sure to inspire the implementation of wrapper component tags in the future