Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding items to the page with ServerLoad #123

Closed
hasansezertasan opened this issue Dec 24, 2023 · 6 comments
Closed

Adding items to the page with ServerLoad #123

hasansezertasan opened this issue Dec 24, 2023 · 6 comments

Comments

@hasansezertasan
Copy link
Contributor

hasansezertasan commented Dec 24, 2023

I cloned the repo and ran the demo and tried to modifie it so that I can ServerLoad items one by one, not just swap the ServerLoad

I modified this block:

c.Div(
components=[
c.Heading(text='Server Load', level=2),
c.Paragraph(text='Even simpler example of server load, replacing existing content.'),
c.Button(text='Load Content from Server', on_click=PageEvent(name='server-load')),
c.Div(
components=[
c.ServerLoad(
path='/components/dynamic-content',
load_trigger=PageEvent(name='server-load'),
components=[c.Text(text='before')],
),
],
class_name='py-2',
),
],
class_name='border-top mt-3 pt-1',
),

... like this:

        c.Div(
            components=[
                c.Heading(text="Server Load SSE", level=2),
                c.Markdown(text="`ServerLoad` can also be used to load content from an SSE stream."),
                c.Button(text="Load SSE content", on_click=PageEvent(name="server-load-sse")),
                c.Div(
                    components=[
                        c.ServerLoad(
                            path="/components/sse",
                            sse=True,
                            load_trigger=PageEvent(name="server-load-sse"),
                            components=[c.Text(text="before")],
                        ),
                    ],
                    class_name="my-2 p-2 border rounded",
                ),
            ],
            class_name="border-top mt-3 pt-1",
        ),

And I modified this block:

@router.get('/dynamic-content', response_model=FastUI, response_model_exclude_none=True)
async def modal_view() -> list[AnyComponent]:
await asyncio.sleep(0.5)
return [c.Paragraph(text='This is some dynamic content. Open devtools to see me being fetched from the server.')]

... like this:

@router.get("/dynamic-content", response_model=FastUI, response_model_exclude_none=True)
async def modal_view() -> list[AnyComponent]:
    await asyncio.sleep(0.5)
    return [
        c.Paragraph(
            text="This is some dynamic content. Open devtools to see me being fetched from the server."),
        c.ServerLoad(
            path="/components/dynamic-content",
            load_trigger=PageEvent(name="server-load"),
            components=[c.Text(text="before")],
        ),
    ]

It visually works but there is something wrong with the HTML Source Code

image

It's probably not a "bug" or something like that, it probably works as it should be and I probably using it wrong, just asking how to do it? Do what? This...

image

What I'm implementing? Let's say a simple TODO app, creating tasks but I don't want to get the entire demo_page and re-render everything, just append the new item...

@samuelcolvin
Copy link
Member

I'm not clear what you're asking for?

@hasansezertasan hasansezertasan changed the title Adding todo items to the page with ServerLoad Adding todo items to the page with 'ServerLoad' Dec 24, 2023
@hasansezertasan hasansezertasan changed the title Adding todo items to the page with 'ServerLoad' Adding todo items to the page with ServerLoad Dec 24, 2023
@hasansezertasan
Copy link
Contributor Author

hasansezertasan commented Dec 24, 2023

Perhaps I was off-topic a bit at the beginning. Also, I hope I could explain the problem clearly this time. Sorry about my poor English. 🙏

Forget everything I said before this line: "It visually works but there is something wrong with the HTML Source Code".

I have sketched an example here:

from __future__ import annotations as _annotations

import random
import time

from fastapi import FastAPI
from fastapi.responses import HTMLResponse
from fastui import AnyComponent, FastUI, prebuilt_html
from fastui import components as c
from fastui.events import GoToEvent, PageEvent


def Master(*components: AnyComponent, title: str | None = None) -> list[AnyComponent]:
    return [
        c.PageTitle(text="Lotto Numbers Generator"),
        c.Navbar(
            title="Still Lotto Numbers Generator",
            title_event=GoToEvent(url="/"),
        ),
        c.Page(
            components=[
                *((c.Heading(text=title),) if title else ()),
                *components,
            ],
        ),
    ]


app = FastAPI()


@app.get("/api/", response_model=FastUI, response_model_exclude_none=True)
def components_view() -> list[AnyComponent]:
    return Master(
        c.Div(
            components=[
                c.Heading(text="Lotto Numbers Generator", level=2),
                c.Paragraph(text="Generate lotto numbers for your next game."),
                c.Button(text="Generate a lotto number!", on_click=PageEvent(name="add-number")),
                # When the button is clicked, the client will send a request to `/api/add-number`
                # As expected, The response will be rendered in the `ServerLoad` component below.
                c.Div(
                    components=[
                        c.ServerLoad(
                            path="/add-number",
                            load_trigger=PageEvent(name="add-number"),
                            components=[c.Paragraph(text="What are you waiting for?")],
                        ),
                    ],
                    class_name="py-2",
                ),
            ],
            class_name="border-top mt-3 pt-1",
        ),
        title="Quite simple and Understandable Example",
    )


@app.get("/api/add-number", response_model=FastUI, response_model_exclude_none=True)
async def modal_view() -> list[AnyComponent]:
    time.sleep(0.5)
    return [
        c.Paragraph(
            text=f"Your lotto number is {random.randint(1, 99)}!",
        ),
        # We are sending with another `ServerLoad` component.
        # This will help us to keep the previous button working.
        c.ServerLoad(
            path="/add-number",
            load_trigger=PageEvent(name="add-number"),
            components=[c.Paragraph(text="Ready for another one?")],
        ),
    ]


@app.get("/{path:path}")
async def html_landing() -> HTMLResponse:
    """Simple HTML page which serves the React app, comes last as it matches all paths."""
    return HTMLResponse(prebuilt_html(title="FastUI Demo"))

When we run this code, it'll generate the following HTML:

<div class="py-2">
    <p class="">What are you waiting for?</p>
</div>

When we click the button, re-rendering will happen and the following HTML will be generated:

<div class="py-2">
    <div>
        <div class="transition-overlay"></div>
        <p class="">Your lotto number is 36!</p>
        <p class="">Ready for another one?</p>
    </div>
</div>

innerHTML of div.py-2 is replaced with the incoming component's HTML.

When we click the button again, re-rendering will happen and the following HTML will be generated:

<div class="py-2">
    <div>
        <div class="transition-overlay"></div>
        <p class="">Your lotto number is 36!</p>
        <div>
            <div class="transition-overlay"></div>
            <p class="">Your lotto number is 13!</p>
            <p class="">Ready for another one?</p>
        </div>
    </div>
</div>

<p class="">Ready for another one?</p> element from the previous code block is now replaced with the incoming component's HTML.

When we click the button again and again, let's do 10, re-rendering will happen and the following HTML will be generated:

<div class="py-2">
    <div>
        <div class="transition-overlay"></div>
        <p class="">Your lotto number is 36!</p>
        <div>
            <div class="transition-overlay"></div>
            <p class="">Your lotto number is 13!</p>
            <div>
                <div class="transition-overlay"></div>
                <p class="">Your lotto number is 82!</p>
                <div>
                    <div class="transition-overlay"></div>
                    <p class="">Your lotto number is 87!</p>
                    <div>
                        <div class="transition-overlay"></div>
                        <p class="">Your lotto number is 94!</p>
                        <div>
                            <div class="transition-overlay"></div>
                            <p class="">Your lotto number is 51!</p>
                            <div>
                                <div class="transition-overlay"></div>
                                <p class="">Your lotto number is 48!</p>
                                <div>
                                    <div class="transition-overlay"></div>
                                    <p class="">Your lotto number is 65!</p>
                                    <div>
                                        <div class="transition-overlay"></div>
                                        <p class="">Your lotto number is 2!</p>
                                        <div>
                                            <div class="transition-overlay"></div>
                                            <p class="">Your lotto number is 90!</p>
                                            <div>
                                                <div class="transition-overlay"></div>
                                                <p class="">Your lotto number is 52!</p>
                                                <div>
                                                    <div class="transition-overlay"></div>
                                                    <p class="">Your lotto number is 10!</p>
                                                    <p class="">Ready for another one?</p>
                                                </div>
                                            </div>
                                        </div>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>

This is what I meant to illustrate in the first image.

As you can see, it's nesting the components. This might be the expected behavior, but I'm not sure since I don't know any react ReactJS and there is no documentation about it. So excited to see the documentation. 🤩

The problem I see is nesting the components over and over. I might be implementing it wrong, but I don't know how to achieve the expected behavior.

The expected behavior is something like this:

<div class="border-top mt-3 pt-1">
    <h2 id="lotto-numbers-generator" class="">Lotto Numbers Generator</h2>
    <p class="">Generate lotto numbers for your next game.</p>
    <button class="btn btn-primary">Generate a lotto number!</button>
    <div class="py-2">
        <div>
            <div class="transition-overlay"></div>
            <p class="">Your lotto number is 36!</p>
            <div class="transition-overlay"></div>
            <p class="">Your lotto number is 13!</p>
            <div class="transition-overlay"></div>
            <p class="">Your lotto number is 82!</p>
            <div class="transition-overlay"></div>
            <p class="">Your lotto number is 87!</p>
            <div class="transition-overlay"></div>
            <p class="">Your lotto number is 94!</p>
            <div class="transition-overlay"></div>
            <p class="">Your lotto number is 51!</p>
            <div class="transition-overlay"></div>
            <p class="">Your lotto number is 48!</p>
            <div class="transition-overlay"></div>
            <p class="">Your lotto number is 65!</p>
            <div class="transition-overlay"></div>
            <p class="">Your lotto number is 2!</p>
            <div class="transition-overlay"></div>
            <p class="">Your lotto number is 90!</p>
            <div class="transition-overlay"></div>
            <p class="">Your lotto number is 52!</p>
            <div class="transition-overlay"></div>
            <p class="">Your lotto number is 10!</p>
            <p class="">Ready for another one?</p>
        </div>
    </div>
</div>

This is what I meant to illustrate in the second image.

Instead of nesting the elements, I want to add them to the same level. Can you please help me to achieve this behavior?

I hope I could explain the problem clearly. Thank you for your time. 🙏

@samuelcolvin
Copy link
Member

I think this might be fixed on #61.

@hasansezertasan
Copy link
Contributor Author

hasansezertasan commented Dec 24, 2023

I think this might be fixed on #61.

I can see that it's not merged yet. I checked the code in it and, I couldn't find any ServerLoad related modifications. Are you sure you are referring to the right PR @samuelcolvin?

And what's up with the behavior above? Is it supposed to work that way?

@hasansezertasan
Copy link
Contributor Author

hasansezertasan commented Dec 24, 2023

I just found something. Can you run this example above, please? If you navigate to the / endpoint, you'll see that the ServerLoad components are looping and automatically triggered without pressing the button.

from __future__ import annotations as _annotations

import random
import time

from fastapi import FastAPI
from fastapi.responses import HTMLResponse
from fastui import AnyComponent, FastUI, prebuilt_html
from fastui import components as c
from fastui.events import GoToEvent, PageEvent


def Master(*components: AnyComponent, title: str | None = None) -> list[AnyComponent]:
    return [
        c.PageTitle(text="Lotto Numbers Generator"),
        c.Navbar(
            title="Still Lotto Numbers Generator",
            title_event=GoToEvent(url="/"),
        ),
        c.Page(
            components=[
                *((c.Heading(text=title),) if title else ()),
                *components,
            ],
        ),
    ]


app = FastAPI()


@app.get("/api/", response_model=FastUI, response_model_exclude_none=True)
def components_view() -> list[AnyComponent]:
    return Master(
        c.Div(
            components=[
                c.Heading(text="Lotto Numbers Generator", level=2),
                c.Paragraph(text="Generate lotto numbers for your next game."),
                c.Button(text="Generate a lotto number!", on_click=PageEvent(name="add-number")),
                # When the button is clicked, the client will send a request to `/api/add-number`
                # The response will be rendered in the `ServerLoad` component below, as expected.
                c.Div(
                    components=[
                        c.ServerLoad(
                            path="/add-number",
                            load_trigger=PageEvent(name="add-number"),
                        ),
                    ],
                    class_name="py-2",
                ),
            ],
            class_name="border-top mt-3 pt-1",
        ),
        title="Quite simple and Understandable Example",
    )


@app.get("/api/add-number", response_model=FastUI, response_model_exclude_none=True)
async def modal_view() -> list[AnyComponent]:
    time.sleep(0.5)
    return [
        c.Paragraph(
            text=f"Your lotto number is {random.randint(1, 99)}!",
        ),
        # We are sending with another `ServerLoad` component.
        # This will help us to keep the previous button working.
        c.ServerLoad(
            path="/add-number",
            load_trigger=PageEvent(name="add-number"),
        ),
    ]


@app.get("/{path:path}")
async def html_landing() -> HTMLResponse:
    """Simple HTML page which serves the React app, comes last as it matches all paths."""
    return HTMLResponse(prebuilt_html(title="FastUI Demo"))

@hasansezertasan hasansezertasan changed the title Adding todo items to the page with ServerLoad Adding items to the page with ServerLoad Dec 25, 2023
@hasansezertasan
Copy link
Contributor Author

0.4.0 fixed this one successfully.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants