Skip to content

Commit eba69b7

Browse files
csharpfritzCopilot
andcommitted
feat: Milestone 6 Phase 4 P2 nice-to-have features (WI-47 to WI-54)
WI-47: Add DataTextFormatString to BaseListControl applies string.Format to item display text for all 5 list controls WI-48: Add AppendDataBoundItems to BaseListControl when true, data-bound items append to static items instead of replacing them WI-51: Add AssociatedControlID to Label renders <label for=...> instead of <span> for accessibility WI-52: Change Login, ChangePassword, CreateUserWizard base class to BaseStyledComponent adds BackColor, CssClass, Font, etc. on outer container (~30 gaps closed) WI-53: Add 32 bUnit tests covering all P2 features WI-54: Add sample pages for DataTextFormatString, Menu Orientation, and Label AssociatedControlID Also fixes 5 existing Menu test files and 3 sample pages that broke when the Orientation parameter was added (removed redundant orientation=Vertical attribute since Vertical is the default). Fixed 3 existing combined static + data-bound item tests to use AppendDataBoundItems=true. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 62c2a01 commit eba69b7

41 files changed

Lines changed: 1137 additions & 455 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

samples/AfterBlazorServerSide/ComponentCatalog.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ public static class ComponentCatalog
3333
new("Image", "Editor", "/ControlSamples/Image", "Displays an image with alt text support",
3434
Keywords: new[] { "img", "picture" }),
3535
new("ImageMap", "Editor", "/ControlSamples/ImageMap", "Image with clickable hotspot regions"),
36+
new("Label", "Editor", "/ControlSamples/Label", "Renders text as span or accessible label element",
37+
Keywords: new[] { "text", "label", "accessibility" }),
3638
new("LinkButton", "Editor", "/ControlSamples/LinkButton", "Button rendered as a hyperlink",
3739
new[] { "JavaScript" },
3840
new[] { "link", "postback" }),

samples/AfterBlazorServerSide/Components/Pages/ComponentList.razor

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
<li><a href="/ControlSamples/HiddenField">HiddenField</a></li>
1313
<li><a href="/ControlSamples/HyperLink">HyperLink</a></li>
1414
<li><a href="/ControlSamples/Image">Image</a></li>
15+
<li><a href="/ControlSamples/Label">Label</a></li>
1516
<li><a href="/ControlSamples/LinkButton">LinkButton</a></li>
1617
<li><a href="/ControlSamples/Literal">Literal</a></li>
1718
<li><a href="/ControlSamples/Localize">Localize</a></li>

samples/AfterBlazorServerSide/Components/Pages/ControlSamples/DropDownList/Index.razor

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,26 @@
1818
<p>Selected Product ID: <strong>@selectedProduct</strong></p>
1919
<p>Selected Product: <strong>@(products.FirstOrDefault(p => p.Id == selectedProduct)?.Name ?? "None")</strong></p>
2020

21+
<h2>Data-bound with DataTextFormatString (Currency)</h2>
22+
<DropDownList TItem="PricedProduct"
23+
Items="pricedProducts"
24+
DataTextField="Price"
25+
DataValueField="Id"
26+
DataTextFormatString="{0:C}"
27+
@bind-SelectedValue="selectedPricedProduct" />
28+
<p>Selected: <strong>@selectedPricedProduct</strong></p>
29+
<small>The <code>DataTextFormatString="{0:C}"</code> formats each item's text as currency.</small>
30+
31+
<h2>Data-bound with DataTextFormatString (Prefix)</h2>
32+
<DropDownList TItem="Product"
33+
Items="products"
34+
DataTextField="Name"
35+
DataValueField="Id"
36+
DataTextFormatString="Item: {0}"
37+
@bind-SelectedValue="selectedFormattedProduct" />
38+
<p>Selected: <strong>@selectedFormattedProduct</strong></p>
39+
<small>The <code>DataTextFormatString="Item: {0}"</code> adds a prefix to each item's display text.</small>
40+
2141
<h2>Disabled DropDownList</h2>
2242
<DropDownList TItem="object" StaticItems="staticItems" Enabled="false" SelectedValue="2" />
2343

@@ -43,6 +63,8 @@
4363
@code {
4464
private string selectedStatic = "";
4565
private string selectedProduct = "";
66+
private string selectedPricedProduct = "";
67+
private string selectedFormattedProduct = "";
4668
private string eventSelection = "";
4769
private string lastSelection = "None";
4870
private DateTime lastChangeTime = DateTime.Now;
@@ -90,6 +112,14 @@
90112
new Product { Id = "4", Name = "Doohickey" }
91113
};
92114

115+
private List<PricedProduct> pricedProducts = new()
116+
{
117+
new PricedProduct { Id = "1", Price = 9.99m },
118+
new PricedProduct { Id = "2", Price = 24.50m },
119+
new PricedProduct { Id = "3", Price = 149.95m },
120+
new PricedProduct { Id = "4", Price = 3.00m }
121+
};
122+
93123
private void HandleSelectionChanged(ChangeEventArgs e)
94124
{
95125
lastSelection = e.Value?.ToString() ?? "None";
@@ -102,6 +132,12 @@
102132
public string Id { get; set; }
103133
public string Name { get; set; }
104134
}
135+
136+
public class PricedProduct
137+
{
138+
public string Id { get; set; }
139+
public decimal Price { get; set; }
140+
}
105141
}
106142

107143
<hr />
@@ -110,5 +146,7 @@
110146
<code>
111147
&lt;DropDownList TItem="object" StaticItems="staticItems" @@bind-SelectedValue="selectedValue" /&gt;<br />
112148
&lt;DropDownList TItem="Product" Items="products" DataTextField="Name" DataValueField="Id" @@bind-SelectedValue="selectedProduct" /&gt;<br />
149+
&lt;DropDownList TItem="PricedProduct" Items="pricedProducts" DataTextField="Price" DataValueField="Id" DataTextFormatString="{0:C}" /&gt;<br />
150+
&lt;DropDownList TItem="Product" Items="products" DataTextField="Name" DataValueField="Id" DataTextFormatString="Item: {0}" /&gt;<br />
113151
&lt;DropDownList TItem="object" StaticItems="staticItems" Enabled="false" SelectedValue="2" /&gt;
114152
</code>
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
@page "/ControlSamples/Label"
2+
@using BlazorWebFormsComponents.Enums
3+
4+
<PageTitle>Label Component Samples</PageTitle>
5+
6+
<h2>Label Component</h2>
7+
8+
<p>The <code>Label</code> component renders text on the page. By default it renders as a <code>&lt;span&gt;</code>,
9+
matching the behavior of the ASP.NET Web Forms Label control.</p>
10+
11+
<h3>Basic Label</h3>
12+
13+
<Label Text="Hello, World!" />
14+
15+
<pre><code>&lt;Label Text="Hello, World!" /&gt;</code></pre>
16+
17+
<hr />
18+
19+
<h3>Styled Label</h3>
20+
21+
<Label Text="Important notice"
22+
CssClass="text-danger fw-bold"
23+
ForeColor="WebColor.Red" />
24+
25+
<pre><code>&lt;Label Text="Important notice"
26+
CssClass="text-danger fw-bold"
27+
ForeColor="WebColor.Red" /&gt;</code></pre>
28+
29+
<hr />
30+
31+
<h3>AssociatedControlID — Accessible Form Label</h3>
32+
33+
<p>When you set <code>AssociatedControlID</code>, the Label renders as a <code>&lt;label for="..."&gt;</code>
34+
element instead of a <code>&lt;span&gt;</code>. This associates the label with a form input for accessibility —
35+
clicking the label focuses the input, and screen readers announce the relationship.</p>
36+
37+
<div class="mb-3">
38+
<Label Text="Email Address:" AssociatedControlID="emailInput" CssClass="form-label" />
39+
<TextBox id="emailInput"
40+
TextMode="TextBoxMode.Email"
41+
Placeholder="you@example.com"
42+
CssClass="form-control"
43+
Width="Unit.Pixel(300)" />
44+
</div>
45+
46+
<div class="mb-3">
47+
<Label Text="Full Name:" AssociatedControlID="nameInput" CssClass="form-label" />
48+
<TextBox id="nameInput"
49+
Placeholder="Enter your full name"
50+
CssClass="form-control"
51+
Width="Unit.Pixel(300)" />
52+
</div>
53+
54+
<pre><code>&lt;Label Text="Email Address:" AssociatedControlID="emailInput" CssClass="form-label" /&gt;
55+
&lt;TextBox id="emailInput" TextMode="TextBoxMode.Email"
56+
Placeholder="you@@example.com" CssClass="form-control" /&gt;
57+
58+
&lt;Label Text="Full Name:" AssociatedControlID="nameInput" CssClass="form-label" /&gt;
59+
&lt;TextBox id="nameInput" Placeholder="Enter your full name" CssClass="form-control" /&gt;</code></pre>
60+
61+
<hr />
62+
63+
<h4>Without AssociatedControlID (default)</h4>
64+
<p>Without <code>AssociatedControlID</code>, the Label renders as a <code>&lt;span&gt;</code>:</p>
65+
66+
<Label Text="This renders as a span" />
67+
68+
<pre><code>&lt;!-- Renders: &lt;span&gt;This renders as a span&lt;/span&gt; --&gt;
69+
&lt;Label Text="This renders as a span" /&gt;
70+
71+
&lt;!-- Renders: &lt;label for="emailInput"&gt;Email Address:&lt;/label&gt; --&gt;
72+
&lt;Label Text="Email Address:" AssociatedControlID="emailInput" /&gt;</code></pre>
73+
74+
@code {
75+
}

samples/AfterBlazorServerSide/Pages/ControlSamples/Menu/DatabindingSitemap.razor

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
DisappearAfter="2000"
1212
StaticDisplayLevels="2"
1313
StaticSubmenuIndent="10"
14-
orientation="Vertical"
1514
font-names="Arial"
1615
target="_blank"
1716
DataSource="MenuSource"

samples/AfterBlazorServerSide/Pages/ControlSamples/Menu/DynamicMenuStyleSample.razor

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
<Menu ID="NavigationMenu"
1010
StaticDisplayLevels="2"
1111
StaticSubmenuIndent="10"
12-
orientation="Vertical"
1312
target="_blank"
1413
runat="server">
1514

samples/AfterBlazorServerSide/Pages/ControlSamples/Menu/Index.razor

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
@page "/ControlSamples/Menu"
2+
@using BlazorWebFormsComponents.Enums
23

34

45
<h2>Menu Component homepage</h2>
@@ -11,7 +12,6 @@
1112
DisappearAfter="2000"
1213
StaticDisplayLevels="2"
1314
StaticSubmenuIndent="10"
14-
orientation="Vertical"
1515
font-names="Arial"
1616
target="_blank"
1717
runat="server">
@@ -59,6 +59,46 @@
5959

6060
<hr />
6161

62+
<h2>Horizontal Menu</h2>
63+
64+
<p>Set <code>Orientation</code> to display menu items horizontally. This is useful for top navigation bars.</p>
65+
66+
<Menu id="HorizontalMenu"
67+
Orientation="@horizontal"
68+
StaticDisplayLevels="2"
69+
StaticSubmenuIndent="10"
70+
target="_blank"
71+
runat="server">
72+
73+
<StaticMenuItemStyle BackColor="@("LightSteelBlue")"
74+
ForeColor="@("Black")" />
75+
<StaticHoverStyle BackColor="WebColor.LightSkyBlue" />
76+
77+
<Items>
78+
<MenuItem navigateurl="Home.aspx"
79+
text="Home"
80+
tooltip="Home" />
81+
<MenuItem text="Products"
82+
tooltip="Products">
83+
<MenuItem navigateurl="Software.aspx"
84+
text="Software"
85+
tooltip="Software" />
86+
<MenuItem navigateurl="Hardware.aspx"
87+
text="Hardware"
88+
tooltip="Hardware" />
89+
</MenuItem>
90+
<MenuItem navigateurl="About.aspx"
91+
text="About"
92+
tooltip="About" />
93+
<MenuItem navigateurl="Contact.aspx"
94+
text="Contact"
95+
tooltip="Contact" />
96+
</Items>
97+
98+
</Menu>
99+
100+
<hr />
101+
62102
<p>Code:</p>
63103
<code>
64104
&lt;Menu id="NavigationMenu" orientation="Vertical"&gt;<br />
@@ -68,5 +108,17 @@
68108
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;MenuItem text="Music" tooltip="Music"&gt;...&lt;/MenuItem&gt;<br />
69109
&nbsp;&nbsp;&nbsp;&nbsp;&lt;/MenuItem&gt;<br />
70110
&nbsp;&nbsp;&lt;/Items&gt;<br />
111+
&lt;/Menu&gt;<br /><br />
112+
&lt;!-- Horizontal orientation --&gt;<br />
113+
&lt;Menu id="HorizontalMenu" Orientation="horizontal"&gt;<br />
114+
&nbsp;&nbsp;&lt;Items&gt;<br />
115+
&nbsp;&nbsp;&nbsp;&nbsp;&lt;MenuItem text="Home" /&gt;<br />
116+
&nbsp;&nbsp;&nbsp;&nbsp;&lt;MenuItem text="Products"&gt;...&lt;/MenuItem&gt;<br />
117+
&nbsp;&nbsp;&nbsp;&nbsp;&lt;MenuItem text="About" /&gt;<br />
118+
&nbsp;&nbsp;&lt;/Items&gt;<br />
71119
&lt;/Menu&gt;
72120
</code>
121+
122+
@code {
123+
BlazorWebFormsComponents.Enums.Orientation horizontal = BlazorWebFormsComponents.Enums.Orientation.Horizontal;
124+
}

src/BlazorWebFormsComponents.Test/BulletedList/DataBinding.razor

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@
7272
};
7373

7474
// Act
75-
var cut = Render(@<BulletedList TItem="Product" StaticItems="staticItems" Items="products" DataTextField="Name" DataValueField="Url" />);
75+
var cut = Render(@<BulletedList TItem="Product" StaticItems="staticItems" Items="products" AppendDataBoundItems="true" DataTextField="Name" DataValueField="Url" />);
7676

7777
// Assert
7878
var listItems = cut.FindAll("li");
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
@using BlazorWebFormsComponents.Enums
2+
3+
@code {
4+
5+
public class Product
6+
{
7+
public string Name { get; set; }
8+
public string Url { get; set; }
9+
}
10+
11+
[Fact]
12+
public void BulletedList_DataTextFormatString_AppliesFormatToDataBoundItems()
13+
{
14+
// Arrange
15+
var products = new List<Product>
16+
{
17+
new Product { Name = "Alpha", Url = "/a" },
18+
new Product { Name = "Beta", Url = "/b" }
19+
};
20+
21+
// Act
22+
var cut = Render(
23+
@<BulletedList TItem="Product"
24+
Items="products"
25+
DataTextField="Name"
26+
DataValueField="Url"
27+
DataTextFormatString=">> {0}" />
28+
);
29+
30+
// Assert
31+
var spans = cut.FindAll("li span");
32+
spans[0].TextContent.ShouldBe(">> Alpha");
33+
spans[1].TextContent.ShouldBe(">> Beta");
34+
}
35+
36+
[Fact]
37+
public void BulletedList_DataTextFormatString_AppliesFormatToStaticItems()
38+
{
39+
// Arrange
40+
var items = new ListItemCollection
41+
{
42+
new ListItem("One", "1"),
43+
new ListItem("Two", "2")
44+
};
45+
46+
// Act
47+
var cut = Render(
48+
@<BulletedList TItem="object"
49+
StaticItems="items"
50+
DataTextFormatString="#{0}" />
51+
);
52+
53+
// Assert
54+
var spans = cut.FindAll("li span");
55+
spans[0].TextContent.ShouldBe("#One");
56+
spans[1].TextContent.ShouldBe("#Two");
57+
}
58+
59+
[Fact]
60+
public void BulletedList_DataTextFormatString_Empty_NoFormatting()
61+
{
62+
// Arrange
63+
var items = new ListItemCollection
64+
{
65+
new ListItem("Raw Text", "1")
66+
};
67+
68+
// Act
69+
var cut = Render(
70+
@<BulletedList TItem="object"
71+
StaticItems="items"
72+
DataTextFormatString="" />
73+
);
74+
75+
// Assert
76+
var span = cut.Find("li span");
77+
span.TextContent.ShouldBe("Raw Text");
78+
}
79+
80+
}

0 commit comments

Comments
 (0)