Skip to content

Commit c6d5181

Browse files
Anu6isgoogle-labs-jules[bot]qodo-code-review[bot]
authored
Rework architecture for Static SSR best practices (#59)
* Rework architecture for Static SSR best practices - Multi-target .NET 8.0, 9.0, and 10.0. - Centralize JavaScript behavior in a Blazor JS initializer (lib.module.js). - Implement 'enhancedload' event listener for Blazor enhanced navigation support. - Rework components (TextField, CheckBox, Switch, Radio, NavMenu) to use data attributes (data-mud-static-) instead of scoped inline scripts. - Remove legacy NavigationObserver.js and IJSRuntime dependencies in static components. - Ensure all 48 unit tests (Bunit and Playwright) pass. Co-authored-by: Anu6is <4596077+Anu6is@users.noreply.github.com> * Rework architecture for Static SSR best practices - Multi-target .NET 8.0, 9.0, and 10.0. - Centralize JavaScript behavior in a Blazor JS initializer (Extensions.MudBlazor.StaticInput.lib.module.js). - Implement 'enhancedload' event listener for Blazor enhanced navigation support. - Rework components (TextField, CheckBox, Switch, Radio, NavMenu) to use declarative data attributes (data-mud-static-) instead of scoped inline scripts. - Remove legacy NavigationObserver.js and IJSRuntime dependencies in static components. - Update unit tests and verified 48 pass. Co-authored-by: Anu6is <4596077+Anu6is@users.noreply.github.com> * Rework architecture for Static SSR and update sample project - Multi-target .NET 8.0, 9.0, and 10.0. - Centralize JavaScript behavior in a Blazor JS initializer. - Implement 'enhancedload' event listener for Blazor enhanced navigation support. - Rework components to use declarative data attributes instead of scoped inline scripts. - Remove legacy NavigationObserver.js and IJSRuntime dependencies in static components. - Update sample project (StaticSample) to align with architectural changes. - Ensure all 48 unit tests pass. Co-authored-by: Anu6is <4596077+Anu6is@users.noreply.github.com> * minor updates (#60) * safety checks * update password script * Address drawer toggle bug and improve WASM compatibility - Implement MutationObserver in JS module for robust initialization in WASM/dynamic scenarios. - Add IsStatic() checks to all components to avoid conflicts between static and interactive states. - Ensure data-mud-static-initialized="true" is correctly managed. - Fix drawer toggle to handle responsive classes and layout parent more accurately. - Update Bunit tests to support RendererInfo checks. Co-authored-by: Anu6is <4596077+Anu6is@users.noreply.github.com> * cleanup (#61) * cleanup * Update demo/StaticSample/StaticSample/Components/App.razor Co-authored-by: qodo-code-review[bot] <151058649+qodo-code-review[bot]@users.noreply.github.com> --------- Co-authored-by: qodo-code-review[bot] <151058649+qodo-code-review[bot]@users.noreply.github.com> --------- Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> Co-authored-by: Anu6is <4596077+Anu6is@users.noreply.github.com> Co-authored-by: qodo-code-review[bot] <151058649+qodo-code-review[bot]@users.noreply.github.com>
1 parent 4214ec2 commit c6d5181

29 files changed

+622
-505
lines changed

README.md

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -162,9 +162,5 @@ To start using MudBlazor.StaticInput in your projects, simply install the packag
162162
```bash
163163
dotnet add package Extensions.MudBlazor.StaticInput
164164
```
165-
Then add the following to the `body` of your `App.razor` file
166-
```
167-
<script src="_content/Extensions.MudBlazor.StaticInput/NavigationObserver.js"></script>
168-
```
169165
> [!NOTE]
170166
> Note: MudBlazor should already be setup for your application

demo/StaticSample/StaticSample/Components/Account/Pages/Login.razor

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
@page "/Account/Login"
1+
@page "/Account/Login"
22

33
@using System.ComponentModel.DataAnnotations
44
@using Microsoft.AspNetCore.Authentication
@@ -136,20 +136,3 @@
136136
public bool RememberMe { get; set; }
137137
}
138138
}
139-
140-
<script>
141-
let timeoutId;
142-
143-
function showPassword(inputElement, button) {
144-
if (inputElement.type === 'password') {
145-
inputElement.type = 'text';
146-
clearTimeout(timeoutId);
147-
timeoutId = setTimeout(function () {
148-
inputElement.type = 'password';
149-
}, 5000);
150-
} else {
151-
inputElement.type = 'password';
152-
clearTimeout(timeoutId);
153-
}
154-
}
155-
</script>

demo/StaticSample/StaticSample/Components/App.razor

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,25 @@
1515
<Routes @rendermode="RenderModeForPage" />
1616
<script src="_framework/blazor.web.js"></script>
1717
<script src="_content/MudBlazor/MudBlazor.min.js"></script>
18-
<script src="_content/Extensions.MudBlazor.StaticInput/NavigationObserver.js"></script>
18+
<script>
19+
const passwordTimeouts = new WeakMap();
20+
function showPassword(inputElement) {
21+
if (!inputElement) return;
22+
const existingTimeout = passwordTimeouts.get(inputElement);
23+
if (existingTimeout) clearTimeout(existingTimeout);
24+
if (inputElement.type === 'password') {
25+
inputElement.type = 'text';
26+
const timeoutId = setTimeout(function () {
27+
inputElement.type = 'password';
28+
passwordTimeouts.delete(inputElement);
29+
}, 5000);
30+
passwordTimeouts.set(inputElement, timeoutId);
31+
} else {
32+
inputElement.type = 'password';
33+
passwordTimeouts.delete(inputElement);
34+
}
35+
}
36+
</script>
1937
</body>
2038

2139
</html>

demo/StaticSample/StaticSample/Program.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
var builder = WebApplication.CreateBuilder(args);
1313

1414
// Add services to the container.
15-
builder.Services.AddRazorComponents()
15+
builder.Services.AddRazorComponents()
1616
.AddInteractiveServerComponents()
1717
.AddInteractiveWebAssemblyComponents();
1818

demo/StaticSample/StaticSample/UserRegistrationCleanupService.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ public async Task StopAsync(CancellationToken cancellationToken)
7575
{
7676
_cts.Cancel();
7777
}
78-
finally
78+
finally
7979
{
8080
await Task.WhenAny(_executingTask, Task.Delay(Timeout.Infinite, cancellationToken));
8181
}

src/Components/MudStaticButton.razor

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
@namespace MudBlazor.StaticInput
1+
@namespace MudBlazor.StaticInput
22

33
@inherits MudButton
44

@@ -36,7 +36,15 @@ else
3636

3737
protected override void OnParametersSet()
3838
{
39-
UserAttributes["data-static-component"] = true;
39+
if (IsStatic())
40+
{
41+
UserAttributes["data-mud-static-type"] = "button";
42+
}
43+
else
44+
{
45+
UserAttributes.Remove("data-mud-static-type");
46+
UserAttributes.Remove("data-mud-static-initialized");
47+
}
4048

4149
base.OnParametersSet();
4250
}
@@ -78,4 +86,4 @@ else
7886

7987
return null;
8088
}
81-
}
89+
}

src/Components/MudStaticButton.razor.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,23 @@
11
using Microsoft.AspNetCore.Components;
22
using Microsoft.AspNetCore.Components.Web;
3+
using Microsoft.AspNetCore.Http;
34

45
namespace MudBlazor.StaticInput;
56

67
public partial class MudStaticButton : MudButton
78
{
9+
[CascadingParameter]
10+
private HttpContext HttpContext { get; set; } = default!;
11+
12+
private bool IsStatic()
13+
{
14+
#if NET9_0_OR_GREATER
15+
return !RendererInfo.IsInteractive;
16+
#else
17+
return HttpContext != null;
18+
#endif
19+
}
20+
821
/**********************************************
922
* Hide these inherited properties to prevent *
1023
* consumers from modifying them directly. *

src/Components/MudStaticCheckBox.razor

Lines changed: 15 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
1-
@namespace MudBlazor.StaticInput
1+
@namespace MudBlazor.StaticInput
22

33
@inherits MudCheckBox<bool>
44

55
<MudInputControl Class="@Classname" Style="@Style" Error="@HasErrors" ErrorText="@GetErrorText()" Required="@Required">
66
<InputContent>
7-
<label class="@LabelClassname" id="@($"checkbox-container-{_elementId}")" @onclick:stopPropagation="@StopClickPropagation">
7+
<label class="@LabelClassname" @onclick:stopPropagation="@StopClickPropagation">
88
<span tabindex="0" class="@IconClassname">
9-
<input type="hidden" id="@($"empty-checkbox-{_elementId}")" name="@(_checkboxValue ? "" : _name)" value="False" />
9+
<input type="hidden" name="@(_checkboxValue ? "" : _name)" value="False" />
1010
<input tabindex="-1" @attributes="@UserAttributes" type="checkbox" class="mud-checkbox-input" name="@(_checkboxValue ? _name : "")"
1111
value="True" aria-checked="@_checkboxValue.ToString().ToLower()" aria-readonly="@(GetDisabledState().ToString().ToLower())"
12-
id="@($"static-checkbox-{_elementId}")" disabled="@GetDisabledState()" @onclick:preventDefault="@GetReadOnlyState()"/>
12+
disabled="@GetDisabledState()" @onclick:preventDefault="@GetReadOnlyState()"/>
1313
<MudIcon Icon="@CheckedIcon" Color="HasErrors ? Color.Error : this.Color" Size="@Size"
1414
id="@($"check-icon-{_elementId}")" style="@($"display: {_checkedStyle}")"/>
1515
<MudIcon Icon="@UncheckedIcon" Color="HasErrors ? Color.Error : this.UncheckedColor ?? Color.Inherit" Size="@Size"
@@ -40,7 +40,17 @@
4040

4141
protected override void OnParametersSet()
4242
{
43-
UserAttributes["data-static-component"] = true;
43+
if (IsStatic())
44+
{
45+
UserAttributes["data-mud-static-type"] = "checkbox";
46+
UserAttributes["data-mud-static-name"] = _name;
47+
}
48+
else
49+
{
50+
UserAttributes.Remove("data-mud-static-type");
51+
UserAttributes.Remove("data-mud-static-name");
52+
UserAttributes.Remove("data-mud-static-initialized");
53+
}
4454

4555
base.OnParametersSet();
4656
}
@@ -73,29 +83,3 @@
7383
base.OnInitialized();
7484
}
7585
}
76-
77-
<script>
78-
(function () {
79-
const checkbox = document.getElementById('static-checkbox-@_elementId');
80-
const emptyCheckbox = document.getElementById('empty-checkbox-@_elementId');
81-
const checkedIcon = document.getElementById('check-icon-@_elementId');
82-
const uncheckedIcon = document.getElementById('unchecked-icon-@_elementId');
83-
84-
checkbox.addEventListener('change', () => {
85-
checkbox.ariaChecked = checkbox.checked ? "true" : "false";
86-
checkedIcon.style.display = checkbox.checked ? 'block' : 'none';
87-
uncheckedIcon.style.display = checkbox.checked ? 'none' : 'block';
88-
89-
if (checkbox.checked) {
90-
emptyCheckbox.removeAttribute("name");
91-
checkbox.setAttribute("name", "@_name");
92-
checkbox.setAttribute("checked", true);
93-
}
94-
else {
95-
checkbox.removeAttribute("name");
96-
checkbox.removeAttribute("checked");
97-
emptyCheckbox.setAttribute("name", "@_name");
98-
}
99-
});
100-
})();
101-
</script>

src/Components/MudStaticCheckBox.razor.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,22 @@
11
using Microsoft.AspNetCore.Components;
2+
using Microsoft.AspNetCore.Http;
23

34
namespace MudBlazor.StaticInput;
45

56
public partial class MudStaticCheckBox : MudCheckBox<bool>
67
{
8+
[CascadingParameter]
9+
private HttpContext HttpContext { get; set; } = default!;
10+
11+
private bool IsStatic()
12+
{
13+
#if NET9_0_OR_GREATER
14+
return !RendererInfo.IsInteractive;
15+
#else
16+
return HttpContext != null;
17+
#endif
18+
}
19+
720
/**********************************************
821
* Hide these inherited properties to prevent *
922
* consumers from modifying them directly. *
Lines changed: 16 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,15 @@
1-
@namespace MudBlazor.StaticInput
1+
@namespace MudBlazor.StaticInput
22

33
@typeparam T
44
@inherits MudRadio<T>
55

66
<MudInputControl Class="@Classname" Style="@Style" Error="@HasErrors" ErrorText="@GetErrorText()" Required="@Required">
77
<InputContent>
8-
<label class="@LabelClassname" style="@Style" id="@($"static-radio-container-{_elementId}")" @onclick:stopPropagation="@StopClickPropagation">
8+
<label class="@LabelClassname" style="@Style" @onclick:stopPropagation="@StopClickPropagation">
99
<span tabindex="0" class="@IconClassname">
1010
<input id="@($"static-radio-{_elementId}")" tabindex="-1" @attributes="UserAttributes" type="radio" role="radio"
1111
class="mud-radio-input static-radio-input" checked="@_isChecked" disabled="@GetDisabledState()" name="@(_isChecked ? ParentGroup?.GroupName : "")" value="@Value"
12-
aria-checked="@(_isChecked.ToString().ToLower())" aria-disabled="@(GetDisabledState().ToString().ToLower())" @onclick:preventDefault="@GetReadOnlyState()"
13-
data-value="@Value" data-group-name="@ParentGroup?.GroupName" />
12+
aria-checked="@(_isChecked.ToString().ToLower())" aria-disabled="@(GetDisabledState().ToString().ToLower())" @onclick:preventDefault="@GetReadOnlyState()"/>
1413
<MudIcon Icon="@(ParentGroup?.CheckedIcon ?? CheckedIcon)" Color="HasErrors? Color.Error: ParentGroup?.Color ?? this.Color" Size="@Size" Disabled="@Disabled"
1514
id="@($"radio-checked-icon-{_elementId}")" style="@($"display: {_checkedStyle}")" />
1615
<MudIcon Icon="@(ParentGroup?.UncheckedIcon ?? UncheckedIcon)" Color="HasErrors ? Color.Error: ParentGroup?.UncheckedColor ?? this.UncheckedColor ?? Color.Inherit"
@@ -55,52 +54,20 @@
5554

5655
protected override void OnParametersSet()
5756
{
58-
UserAttributes["data-static-component"] = true;
57+
if (ParentGroup is not null && ParentGroup.IsStatic())
58+
{
59+
UserAttributes["data-mud-static-type"] = "radio";
60+
UserAttributes["data-value"] = Value?.ToString();
61+
UserAttributes["data-group-name"] = ParentGroup?.GroupName;
62+
}
63+
else
64+
{
65+
UserAttributes.Remove("data-mud-static-type");
66+
UserAttributes.Remove("data-value");
67+
UserAttributes.Remove("data-group-name");
68+
UserAttributes.Remove("data-mud-static-initialized");
69+
}
5970

6071
base.OnParametersSet();
6172
}
6273
}
63-
64-
<script>
65-
document.addEventListener("DOMContentLoaded", function () {
66-
document.querySelectorAll('.static-radio-input').forEach(function (radio) {
67-
radio.addEventListener('change', function () {
68-
const parentContainer = radio.closest("[role='radiogroup']");
69-
if (!parentContainer) return;
70-
71-
const hiddenInput = parentContainer.querySelector("input[type='hidden']");
72-
const selectedValue = radio.getAttribute('data-value');
73-
const groupName = radio.getAttribute('data-group-name');
74-
75-
parentContainer.querySelectorAll('.static-radio-input').forEach(function (r) {
76-
if (r !== radio) {
77-
r.checked = false;
78-
r.removeAttribute("name");
79-
r.setAttribute("checked", false);
80-
r.setAttribute("aria-checked", false);
81-
}
82-
83-
const radioId = r.id.split('-')[2];
84-
const checkedIcon = document.getElementById(`radio-checked-icon-${radioId}`);
85-
const uncheckedIcon = document.getElementById(`radio-unchecked-icon-${radioId}`);
86-
87-
if (r.checked) {
88-
checkedIcon.style.display = 'block';
89-
uncheckedIcon.style.display = 'none';
90-
r.setAttribute("checked", true);
91-
r.setAttribute("aria-checked", true);
92-
r.setAttribute("name", groupName);
93-
// Update the hidden input value
94-
if (hiddenInput) {
95-
hiddenInput.value = selectedValue;
96-
hiddenInput.setAttribute("value", selectedValue);
97-
}
98-
} else {
99-
checkedIcon.style.display = 'none';
100-
uncheckedIcon.style.display = 'block';
101-
}
102-
});
103-
});
104-
});
105-
});
106-
</script>

0 commit comments

Comments
 (0)