egil / Htmxor

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

feat: Add ability to utilize multiple htmx configurations #10

Closed tanczosm closed 3 months ago

tanczosm commented 6 months ago

Based on conversation regarding configuration on startup I realized that it isn't unreasonable to have different configurations for different pages. While a single config per-site might work I think a better approach would be to also allow for named configurations along with a default configuration. Htmx could potentially have different configurations across different pages, since the configs are page-level.

Adding a specific configuration for a page could just involve configuring the HtmxConfigHeadOutlet as follows:

<HtmxConfigHeadOutlet Configuration="articles"/>

or leave Configuration off to use the default configuration

Then implement a config we can create a config builder that works like the following:

builder.AddHtmxor()
    .WithDefaultConfiguration(config =>
    {
        config.SelfRequestsOnly = true;
    })
    .WithNamedConfiguration("articles", config =>
    {
        config.SelfRequestsOnly = true;
        config.GlobalViewTransitions = true;
    });

Side Effects HtmxAntiforgeryOptions will need to be configured globally for the site on startup instead since there is no reasonable way to know which config should be used per-request

egil commented 6 months ago

I am not sure of the implications of enabling configuration based on individual pages or groups of pages. It's certainly not all configuration options that make sense to change on a per-page basis, most seem to be controllable on individual elements and are just defaults.

A few questions:

  1. Can you describe one or more scenarios/options that make sense to change on a per-page basis?
  2. Does htmx pick up on configuration changes in the head element on hx requests?
  3. Will it cause hard to detect bugs if one page has a different behavior than others? Is there edge cases where htmxor code changes its behavior depending on the config, and would need to know the page that triggered a requests config and page being targeted in a requests config?

Design of the feature:

In configuration, I am not opposed to having "named" configurations. I guess individual pages can set this via the <HeadContent> tag, e.g.:

<HeadContent>
    <HtmxConfigHeadOutlet Configuration="articles"/>
</HeadContent>

An alternative is to register HtmxConfig as a scoped service instead of a singleton. That would allow individual pages to override the config by having it injected into them and changing settings as needed. And since the config is scoped to the request, the change would only apply to that specific page, e.g.;

@inject HtmxConfig config
@code {
  protected override void OnInitialized()
  {
    config.Timeout = Timespan.FromSeconds(2);
  }
}

A 3rd option is to allow setting up configurations that match path templates, such that the path of a request determines the config:

builder.AddHtmxor(config =>
    {
        config.SelfRequestsOnly = true; // is the default btw.
    })
    .WithPathConfiguration("/articles/**", config =>
    {
        config.GlobalViewTransitions = true;
    });
tanczosm commented 6 months ago

I would see configuration changes more localized to certain sections of your site. By design htmx configs are set at a page-level, and you can't make the assumption that the whole site will function as an SPA. Consider also a site that has a front-facing side and an administrative side. Or perhaps an ecommerce side and media presentation side. They might have different configuration requirements.

I used named configurations in my code writeup because they are pre-built into the options pattern. I think it's simple to reason about if you are just applying that code to a layout page that is used by a group of pages. The route approach, while nice, is probably not needed if you consider a section like articles will likely share a similar layout with the same head anyway.

I can see a strong argument for the injection approach though, as that would allow you to localize the configuration in the same place as the layout. If the spirit of htmx is to localize most page state in the dom, then injection of the config and manipulation of it on the same layout page could be a winner (even though it's technically in a code section). I personally see layouts as being the strongest contender for manipulating a config.

So I could see this as a working solution then. Keep the default configuration format that is set up in Program.cs, but register as a scoped service instead as you mentioned. Any changes that are made to the config would be done as a mutation of the default state.

egil commented 6 months ago

Really good points. I'll set up a test that verifies config can be scoped.

egil commented 6 months ago

Getting scoped working wasn't a five minute thing so I'll park this for now and get back to it. Also being able to extend HtmxConfig with more options for people using HX extensions.

tanczosm commented 6 months ago

Are extension configurations assumed to just be part of the htmx config? Do you know any extensions that utilize custom htmx configuration variables? I'll take another stab at this after I finish the response handling.

egil commented 6 months ago

For extension configs I am thinking users should be able to create a derived type of HtmxConfig. That way they can add anything they want to HtmxConfig without breaking the internal bits that depend on certain aspects of HtmxConfig in Htmxor.