egil / Htmxor

Supercharges Blazor static server side rendering (SSR) by seamlessly integrating the Htmx.org frontend library.
MIT License
124 stars 13 forks source link

Feature Proposal: Introduce HtmxSwapService for Out-of-Band Swapping #13

Closed tanczosm closed 4 months ago

tanczosm commented 8 months ago

Description:

Service Name: HtmxSwapService (Alternative names: OOBSwapService, ComponentSwapService)

Currently, there's a need for a mechanism to perform Out-of-Band (OOB) swaps in response to htmx requests. This feature request proposes the addition of a scoped service named HtmxSwapService to fulfill this requirement. While it is possible to add OOB swaps anywhere you want, they still need to be largely done on HTMX requests rather than full page renders.

A service would then allow you to easily inject the components / html you want to render on an out-of-band basis. It could also be utilized by third-party components as well to add interactivity to the page.

Htmx supports the following swaps:

Functionality:

Example Usage:

egil commented 8 months ago

Did you consider using Blazors "sections concept" to do this? There are even custom section types like <HeadContent> built into Blazor.

In an app, relevant certain parts of a page like a navbar component placed in a layout would provide a section outlet that pages and components can write into. On normal requests, that just works as is. On HX-requests, any <SectionContent> components would be included in the response. It will require some tinkering the rendering engine which I plan to do anyway, but this should work.

Leaning on the section-concept is the idiomatic Blazor approach. Getting it to work is the challenge.

Let me know what you think.

tanczosm commented 8 months ago

Sections would be fine to determine the rendering point, but it should definitely be available as a service to push the components / content that will be rendered onto the page. The idea would be to expose it to other libraries and classes that might also need to inject content into a page without having actual access to the page itself.

Content could even be potentially rendered out-of-band on a regular page request. Alpine.js uses a css attribute called x-cloak that hides elements until they are fully on the page. A similar approach could work with htmxor to hide injected elements until htmx fully loads the page, since htmx will still process OOB swaps even if it's a normal page load not from htmx. That would take some client-side javascript as part of the htmxor framework to remove the cloaking attribute once the page is loaded but it's pretty simple.

Side note.. something like this made me think you could kind of reproduce streaming using OOB swaps. You could basically pre-render components and then awaiting the quiescence task. When that's complete you push the result into the service as an OOB swap and let htmx handle it. You'd still need to do some response buffer tricks though to pull that off, but an immediately htmx friendly-way would be to push swaps via websocket connections, SSE, or the htmx-signalr extension.

A more expansive approach would utilize something of a pub/sub model. Any approach you start with, there is definitely a lot of opportunity for swapped in content.

egil commented 8 months ago

To understand your proposed solution, can you create some example code that shows a few of the different scenarios (don't implement it, just show how the API would work)?

tanczosm commented 8 months ago

I think this would probably require some discussion but on a base level users would need to be able to add components/content for publishing as OOB swaps.

If it's kept generic where all this content ultimately gets injected into a section outlet then there is no need to get complicated by specifically handling OOB swaps. Instead create a base set of razor components that mirror htmx principles. The name HtmxSwapService may not be appropriate then but I'm not sure what else I'd go with.


public interface IHtmxSwapService
{
    // Method to add a Razor component with a parameter dictionary/object
    void AddComponent<TComponent>(string target, SwapStyle swapStyle, string selector, Dictionary<string, object> parameters = null ) where TComponent : IComponent;

    // Method to add a Razor component with RenderFragment as a parameter
    void AddFragment(string target, SwapStyle swapStyle, string selector, RenderFragment renderFragment) where TComponent : IComponent;

   // Add Raw html content
   void AddContent (string target, SwapStyle swapStyle, string selector, string content)

   // Render the list of components to a ??? type so that it is included in the output of the page
   public RenderFragment RenderToFragment ();
}

Then maybe we create a Razor Component for swaps. It could either just render the OOB swap html or better, add the component content wrapped in a swappable div to the SwapService implementation for inclusion in the page at the end.

Something like:

<HxSwappable Id="alerts" SwapStyle="SwapStyle.OuterHTML" CssSelector="whatever">
    ... Content here
</HxSwappable>
tanczosm commented 5 months ago

A few thoughts on HxSwappable after putting it into Rizzy. It does work well as a defined OOB swap component but the swap service only works adding content outside of blazor page lifecycle methods. My hope was to be able to render swaps at the end of the response after the initial page render on htmx requests. Unfortunately blazor response rendering was completing before the SwapService contains any content if any of the add methods are called inside of a page.

Section outlets aren't really a solution because ideally you could piggyback and render swaps on any htmx request to get page content updates rather than what might be in a full page request.

Htmxor might work as you have better control over the renderer.

egil commented 5 months ago

Interesting. Can you share an example that I can test against and perhaps your ideal solution?

tanczosm commented 5 months ago

I'll think about this a bit. For Rizzy it wasn't much of an issue to render swaps outside of the Razor components. I tested it primarily with page navigation that may need to be updated as you move from page to page without a full refresh. This could include elements like updating whether a user is logged in, their current notification count, and even full navigation bars if you were to visit a subsection of a larger site (say going from the main page to a Technology subsection that has a different navbar).

HxSwappable, as described above, can cover a lot of use cases btw. It can act as a container for content that can be swapped into (wrap a data table in HxSwappable, for example) or if rendered out of bounds on an htmx request as a vehicle to swap in content (rendering the next page of a data table as an OOB request).

egil commented 4 months ago

Is this still relevant @tanczosm?

tanczosm commented 4 months ago

Let's close it for now.