schell / mogwai

The minimalist, obvious, graphical, web application interface
429 stars 26 forks source link

Fragment Syntax or ViewBuilder<Vec<HtmlElement>> #82

Closed cryptoquick closed 2 years ago

cryptoquick commented 3 years ago

I'm aware not all JSX idioms don't always map to TEA projects, but I was wondering if it might be nice to be able to do either of these:

JSX Fragment Syntax

fn component() -> ViewBuilder<HtmlElement> {
    builder! {
        <>
            <div></div>
            <div></div>
        </>
    }
}

fn parent_component() -> ViewBuilder<HtmlElement> {
    builder! {
        <div id="wrap">
            {component()}
        </div>
    }
}

ViewBuilder<Vec<HtmlElement>>

fn component() -> ViewBuilder<Vec<HtmlElement>> {
    builder! {
        <div></div>
        <div></div>
    }
}

fn parent_component() -> ViewBuilder<HtmlElement> {
    builder! {
        <div id="wrap">
            {component()}
        </div>
    }
}

Right now I have to do it this way, which is perfectly fine, I suppose:

fn component() -> ViewBuilder<HtmlElement> {
    builder! {
        <div id="mydiv">
            <div></div>
            <div></div>
        </div>
    }
}

fn parent_component() -> ViewBuilder<HtmlElement> {
    builder! {
        <div id="wrap">{component()}</div>
    }
}

Just a nice-to-have.

schell commented 3 years ago

Thanks for the issue @cryptoquick. There is a way to get this done now, without changes, though it is a bit of an HTML hack. The solution involves using the slot element, which is a special type of HTML element used for presenting separate DOM trees at the same time together.

fn component() -> ViewBuilder<Vec<HtmlElement>> {
    builder! {
        <slot>
            <div></div>
            <div></div>
        </slot>
    }
}

fn parent_component() -> ViewBuilder<HtmlElement> {
    builder! {
        <div id="wrap">
            {component()}
        </div>
    }
}

The slot element has no style of its own and is effectively not rendered, but the content inside it is rendered. The only bummer is that it doesn't work on IE.

Let me know your thoughts.

cryptoquick commented 3 years ago

I'd prefer not having any extraneous markup rendering in the document. That's the point of using fragments, to omit, say, a wrapper div in the HTML from the returned component.

schell commented 3 years ago

I'm investigating this now. The main issue is SsrNode needs to be expanded to support fragments.

schell commented 3 years ago

:rubber_ducking:

Another complication is that after a DocumentFragment is added to the DOM, its child nodes are drained and given to the parent the fragment was added to. This has the ramification that any streams with side-effects affecting the fragment are either on the wrong target (they should be on the new parent) or are mutating the out-of-band fragment.

schell commented 2 years ago

Turns out I was thinking too hard about this. Instead of ViewBuilder<Vec<Dom>> we can simply allow appending iterators of ViewBuilder or Into<ViewBuilder>:

        let vs: Vec<ViewBuilder<Dom>> = builder! {
            <div>"hello"</div>
            <div>"hola"</div>
            <div>"kia ora"</div>
        };

        let s: ViewBuilder<Dom> = builder! {
            <section>{vs}</section>
        };
        let view: View<Dom> = s.try_into().unwrap();
        assert_eq!(
            String::from(view).as_str(),
            "<section><div>hello</div> <div>hola</div> <div>kia ora</div></section>"
        );

This will be available in the 0.5 release.

cryptoquick commented 2 years ago

Love it! Looking forward to it.

cryptoquick commented 2 years ago

also, Rust rly do be like that sometimes uwu