diff --git a/README.md b/README.md
index 3bbbe8d..bf3acde 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,5 @@
# Htmxor - supercharging Blazor Static SSR with Htmx
-![Htmxor logo](https://raw.githubusercontent.com/egil/Htmxor/main/docs/htmxor.svg)
+![Htmxor logo](https://github.com/egil/Htmxor/blob/main/docs/htmxor-x.svg)
This packages enables Blazor Static SSR (.NET 8 and later) to be used seamlessly with Htmx.
@@ -21,6 +21,6 @@ The following Blazor Web Apps (Htmxor) are used to test Htmxor and demo the capa
## Documentation
-- **[Getting Started](/docs/getting-started.md)** - how to create a new Htmxor/Blazor project.
-- **[Routing in Htmxor](/docs/routing.md)** - there are two types of routing in Htmxor, standard and direct.
+- **[Getting Started](https://github.com/egil/Htmxor/blob/main/docs/getting-started.md)** - how to create a new Htmxor/Blazor project.
+- **[Routing in Htmxor](https://github.com/egil/Htmxor/blob/main/docs/routing.md)** - there are two types of routing in Htmxor, standard and direct.
diff --git a/docs/htmxor-icon-light-mode.png b/docs/htmxor-icon-light-mode.png
deleted file mode 100644
index 6ac8f0b..0000000
Binary files a/docs/htmxor-icon-light-mode.png and /dev/null differ
diff --git a/docs/htmxor-icon-square-x.png b/docs/htmxor-icon-square-x.png
new file mode 100644
index 0000000..e976f14
Binary files /dev/null and b/docs/htmxor-icon-square-x.png differ
diff --git a/docs/htmxor-icon-square-x.svg b/docs/htmxor-icon-square-x.svg
new file mode 100644
index 0000000..bdf434e
--- /dev/null
+++ b/docs/htmxor-icon-square-x.svg
@@ -0,0 +1,97 @@
+
+
diff --git a/docs/htmxor-icon-square.svg b/docs/htmxor-icon-square.svg
index f9862db..cf7936f 100644
--- a/docs/htmxor-icon-square.svg
+++ b/docs/htmxor-icon-square.svg
@@ -12,7 +12,31 @@
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
+ id="defs2">
+
+
+
+
+
+
+ transform="matrix(0.1,0,0,0.1,23.959639,2.4731442)"
+ d="m 303.935,88.479 c -6.598,41.362 -27.653,79.041 -59.42,106.335 -31.767,27.294 -72.185,42.433 -114.064,42.723 -8.483,0.326 -16.977,-0.19 -25.358,-1.539 -36.118487,-6.13741 -63.046845,-36.664 -64.63,-73.266 0.169345,-19.81144 8.121378,-38.76199 22.14,-52.762 29.166633,-29.127376 76.30977,-29.460408 105.885,-0.748 14.21517,13.80013 24.46568,29.66737 23.66525,59.16175 -0.47395,18.0809 -30.60079,37.39317 -35.94696,30.60308 l -21.4873,0.42065 c 9.42596,15.67372 15.60307,15.87769 26.57421,15.35762 20.02411,-1.45526 49.73787,-28.34334 48.7738,-48.3971 0.0936,-8.27469 -0.83373,-16.52934 -2.761,-24.577 C 202.363,119.057 189.18,98.957 170.298,85.368 131.19182,57.223602 77.148268,63.143381 45.062,99.086 c -15.4934,17.3557 -24.014167,39.83207 -23.92,63.097 0.297293,24.66924 10.144647,48.26303 27.473,65.824 17.326835,17.56034 40.786376,27.72012 65.448,28.344 0,0 6.98,0.635 14.849,0.454 38.25133,-0.24208 75.63888,-11.39739 107.769,-32.155 0.457,-0.318 0.914,0.317 0.61,0.78 -31.66134,34.10808 -76.73636,52.54266 -123.225,50.396 C 83.723945,276.37698 54.475178,264.50168 33.105,242.955 11.732917,221.40739 0.09497613,192.06164 0.89,161.723 0.90465337,125.43048 18.035698,91.270681 47.113,69.553 66.385324,55.360119 89.675605,47.671177 113.61,47.6 h 35.772 c 28.25376,-0.0211 55.19732,-11.91811 74.247,-32.784 0.19911,-0.220697 0.46372,-0.371754 0.755,-0.431 0.59655,-0.129176 1.20883,0.137891 1.52,0.663 0.153,0.257 0.222,0.555 0.197,0.854 -1.51626,16.078697 -6.86879,31.558774 -15.608,45.14 -0.26477,0.48189 -0.21965,1.074731 0.115,1.511 0.33279,0.435523 0.89085,0.634214 1.424,0.507 33.48742,-7.564906 61.3946,-30.578926 75.198,-62.013 0.173,-0.277 0.411,-0.507 0.695,-0.67 0.58004,-0.32720395 1.28896,-0.32720395 1.869,0 0.284,0.162 0.523,0.392 0.694,0.67 14.0198,26.857635 18.74839,57.602774 13.447,87.432 z"
+ fill="#702af7"
+ id="blazor-logo-2"
+ sodipodi:nodetypes="csccccccccccccscccccccccccccccccccccccc"
+ clip-path="url(#clipPath7)"
+ inkscape:path-effect="#path-effect7"
+ inkscape:original-d="m 303.935,88.479 c -6.598,41.362 -27.653,79.041 -59.42,106.335 -31.767,27.294 -72.185,42.433 -114.064,42.723 -8.483,0.326 -16.977,-0.19 -25.358,-1.539 -36.118487,-6.13741 -63.046845,-36.664 -64.63,-73.266 0.169345,-19.81144 8.121378,-38.76199 22.14,-52.762 29.166633,-29.127376 76.30977,-29.460408 105.885,-0.748 14.21517,13.80013 24.46568,29.66737 23.66525,59.16175 -0.47395,18.0809 -30.60079,37.39317 -35.94696,30.60308 l -21.4873,0.42065 c 9.42596,15.67372 15.60307,15.87769 26.57421,15.35762 20.02411,-1.45526 49.73787,-28.34334 48.7738,-48.3971 0.0936,-8.27469 -0.83373,-16.52934 -2.761,-24.577 C 202.363,119.057 189.18,98.957 170.298,85.368 131.19182,57.223602 77.148268,63.143381 45.062,99.086 c -15.4934,17.3557 -24.014167,39.83207 -23.92,63.097 0.297293,24.66924 10.144647,48.26303 27.473,65.824 17.326835,17.56034 40.786376,27.72012 65.448,28.344 0,0 6.98,0.635 14.849,0.454 38.25133,-0.24208 75.63888,-11.39739 107.769,-32.155 0.457,-0.318 0.914,0.317 0.61,0.78 -31.66134,34.10808 -76.73636,52.54266 -123.225,50.396 C 83.723945,276.37698 54.475178,264.50168 33.105,242.955 11.732917,221.40739 0.09497613,192.06164 0.89,161.723 0.90465337,125.43048 18.035698,91.270681 47.113,69.553 66.385324,55.360119 89.675605,47.671177 113.61,47.6 h 35.772 c 28.25376,-0.0211 55.19732,-11.91811 74.247,-32.784 0.19911,-0.220697 0.46372,-0.371754 0.755,-0.431 0.59655,-0.129176 1.20883,0.137891 1.52,0.663 0.153,0.257 0.222,0.555 0.197,0.854 -1.51626,16.078697 -6.86879,31.558774 -15.608,45.14 -0.26477,0.48189 -0.21965,1.074731 0.115,1.511 0.33279,0.435523 0.89085,0.634214 1.424,0.507 33.48742,-7.564906 61.3946,-30.578926 75.198,-62.013 0.173,-0.277 0.411,-0.507 0.695,-0.67 0.58004,-0.32720395 1.28896,-0.32720395 1.869,0 0.284,0.162 0.523,0.392 0.694,0.67 14.0198,26.857635 18.74839,57.602774 13.447,87.432 z"
+ style="fill:#702af7" />
diff --git a/docs/htmxor-x.svg b/docs/htmxor-x.svg
new file mode 100644
index 0000000..99794c8
--- /dev/null
+++ b/docs/htmxor-x.svg
@@ -0,0 +1,47 @@
+
+
diff --git a/samples/MinimalHtmxorApp/Components/Pages/Weather.razor b/samples/MinimalHtmxorApp/Components/Pages/Weather.razor
index c9eddf2..afafe1f 100644
--- a/samples/MinimalHtmxorApp/Components/Pages/Weather.razor
+++ b/samples/MinimalHtmxorApp/Components/Pages/Weather.razor
@@ -1,9 +1,17 @@
@page "/weather"
-Weather - loading data
+@inherits ConditionalComponentBase
+Weather
Weather
-
This component demonstrates showing data.
-
-
Loading data . . .
-
+
This component demonstrates showing lazy loaded data.
+
+
+
+ Weather - Loading data, please wait ...
+
Loading data . . .
+
+
+
+
+
diff --git a/samples/MinimalHtmxorApp/Components/Pages/WeatherData.razor b/samples/MinimalHtmxorApp/Components/Pages/WeatherData.razor
index 03dd913..88d589e 100644
--- a/samples/MinimalHtmxorApp/Components/Pages/WeatherData.razor
+++ b/samples/MinimalHtmxorApp/Components/Pages/WeatherData.razor
@@ -1,34 +1,30 @@
-@page "/weather/data"
-@* This shows how the page title can be updated during an htmx request. *@
Weather - data loaded
-
-
+
+
+
Date
+
Temp. (C)
+
Temp. (F)
+
Summary
+
+
+
+ @foreach (var forecast in forecasts)
+ {
-
Date
-
Temp. (C)
-
Temp. (F)
-
Summary
+
@forecast.Date.ToShortDateString()
+
@forecast.TemperatureC
+
@forecast.TemperatureF
+
@forecast.Summary
-
-
- @foreach (var forecast in forecasts)
- {
-
-
@forecast.Date.ToShortDateString()
-
@forecast.TemperatureC
-
@forecast.TemperatureF
-
@forecast.Summary
-
- }
-
-
+ }
+
@code {
private WeatherForecast[] forecasts = [];
protected override async Task OnInitializedAsync()
{
// Simulate asynchronous loading to demonstrate streaming rendering
- await Task.Delay(500);
+ await Task.Delay(2000);
var startDate = DateOnly.FromDateTime(DateTime.Now);
var summaries = new[] { "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering",
diff --git a/src/Htmxor/Components/HtmxAsyncLoad.cs b/src/Htmxor/Components/HtmxAsyncLoad.cs
new file mode 100644
index 0000000..0e8ecc8
--- /dev/null
+++ b/src/Htmxor/Components/HtmxAsyncLoad.cs
@@ -0,0 +1,102 @@
+using Htmxor.Http;
+using Microsoft.AspNetCore.Components;
+using Microsoft.AspNetCore.Components.Rendering;
+
+namespace Htmxor.Components;
+
+///
+/// A component that will render out an element with the hx-trigger="load" attribute
+/// that causes htmx to issue a second request to the server to load the child content in the background.
+/// This is useful when loading the child content takes a long time and you want to show a loading spinner.
+///
+public sealed class HtmxAsyncLoad : ConditionalComponentBase
+{
+ [Inject]
+ private HtmxContext HtmxContext { get; set; } = default!;
+
+ [SuppressMessage("Usage", "CA2227:Collection properties should be read only", Justification = "False positive. This is a parameter.")]
+ [Parameter(CaptureUnmatchedValues = true)]
+ public IDictionary? AdditionalAttributes { get; set; }
+
+ ///
+ /// The content to render out when the htmx load request arrives.
+ ///
+ [Parameter, EditorRequired]
+ public required RenderFragment ChildContent { get; set; }
+
+ ///
+ /// The content to should be rendered while the htmx load request is in progress.
+ ///
+ [Parameter]
+ public RenderFragment? Loading { get; set; }
+
+ ///
+ /// The ID of the element that should render it's content inside.
+ /// This is also the ID that htmx will use to target the element to replace it's content.
+ ///
+ [Parameter, EditorRequired]
+ public required string Id { get; set; }
+
+ ///
+ /// The element type that should render
+ /// and into.
+ ///
+ /// Default is a div element.
+ [Parameter]
+ public string Element { get; set; } = "div";
+
+ protected override void OnParametersSet()
+ {
+ Element = string.IsNullOrWhiteSpace(Element) ? "div" : Element.Trim();
+ Id = string.IsNullOrWhiteSpace(Id) ? Id : Id.Trim();
+
+ if (AdditionalAttributes is null)
+ {
+ return;
+ }
+
+ RemoveControlledAttributeAndThrow(AdditionalAttributes, HtmxConstants.Attributes.HxGet);
+ RemoveControlledAttributeAndThrow(AdditionalAttributes, HtmxConstants.Attributes.HxTrigger);
+ RemoveControlledAttributeAndThrow(AdditionalAttributes, HtmxConstants.Attributes.HxTarget);
+ RemoveControlledAttributeAndThrow(AdditionalAttributes, HtmxConstants.Attributes.HxSwap);
+ }
+
+ protected override void BuildRenderTree([NotNull] RenderTreeBuilder builder)
+ {
+ var request = HtmxContext.Request;
+ builder.OpenElement(1, Element);
+ builder.AddAttribute(2, HtmxConstants.Attributes.Id, Id);
+ if (request.RoutingMode == RoutingMode.Standard)
+ {
+ builder.AddAttribute(3, HtmxConstants.Attributes.HxGet, HtmxContext.Request.Path);
+ builder.AddAttribute(4, HtmxConstants.Attributes.HxTrigger, HtmxConstants.Triggers.Load);
+ builder.AddAttribute(5, HtmxConstants.Attributes.HxTarget, $"#{Id}");
+ builder.AddAttribute(6, HtmxConstants.Attributes.HxSwap, HtmxConstants.SwapStyles.OuterHTML);
+ }
+
+ if (AdditionalAttributes is not null)
+ {
+ builder.AddMultipleAttributes(7, AdditionalAttributes);
+ }
+
+ if (request.RoutingMode == RoutingMode.Standard && Loading is not null)
+ {
+ builder.AddContent(8, Loading);
+ }
+
+ else if (request.RoutingMode == RoutingMode.Direct || request.Target == Id && request.Trigger == Id)
+ {
+ builder.AddContent(9, ChildContent);
+ }
+
+ builder.CloseElement();
+ }
+
+ private static void RemoveControlledAttributeAndThrow(IDictionary attributes, string attributeName)
+ {
+ if (attributes.Remove(attributeName))
+ {
+ throw new ArgumentException($"The '{attributeName}' attribute is controlled by the {nameof(HtmxAsyncLoad)} components and should not be set explicitly.");
+ }
+ }
+}
diff --git a/src/Htmxor/Htmxor.csproj b/src/Htmxor/Htmxor.csproj
index 17133d2..3a2452c 100644
--- a/src/Htmxor/Htmxor.csproj
+++ b/src/Htmxor/Htmxor.csproj
@@ -24,7 +24,7 @@
README.mdblazor, htmx, blazor-ssr, blazor-web-appEgil Hansen
- htmxor-icon-square.png
+ htmxor-icon-square-x.pnghttps://github.com/egil/Htmxorhttps://github.com/egil/Htmxorgit
@@ -61,7 +61,7 @@
-
+