diff --git a/README.md b/README.md index 57fce2f32..897525085 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ There are a significant number of controls in ASP.NET Web Forms, and we will foc - RadioButtonList - Substitution - Table - - TextBox + - [TextBox](docs/EditorControls/TextBox.md) - View - Xml - Data Controls diff --git a/docs/EditorControls/TextBox.md b/docs/EditorControls/TextBox.md new file mode 100644 index 000000000..6e878d1f3 --- /dev/null +++ b/docs/EditorControls/TextBox.md @@ -0,0 +1,202 @@ +# TextBox + +The **TextBox** component displays a single-line text box, multi-line text area, or specialized HTML5 input control (password, email, number, date, etc.) for user input. + +Original Microsoft documentation: https://docs.microsoft.com/en-us/dotnet/api/system.web.ui.webcontrols.textbox?view=netframework-4.8 + +## Features Supported in Blazor + +- Single-line text input (``) +- Multi-line text area (` +``` + +## Migration Notes + +When migrating from Web Forms to Blazor: + +1. **Remove `runat="server"`** - Not needed in Blazor +2. **Remove `AutoPostBack`** - Use `@bind-Text` or event handlers +3. **Replace `OnTextChanged` with `@bind-Text`** - For real-time updates +4. **Update `TextMode` references** - Use `TextBoxMode.` enum prefix (e.g., `TextBoxMode.Password`) +5. **Use HTML5 input types** - Take advantage of new types like Email, Number, Date, etc. + +## See Also + +- [Label](Label.md) - Display static text +- [Button](Button.md) - Trigger actions +- [RequiredFieldValidator](../ValidationControls/RequiredFieldValidator.md) - Validate required fields diff --git a/mkdocs.yml b/mkdocs.yml index d061a0c28..466164c09 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -74,6 +74,7 @@ nav: - Label: EditorControls/Label.md - LinkButton: EditorControls/LinkButton.md - Literal: EditorControls/Literal.md + - TextBox: EditorControls/TextBox.md - RadioButton: EditorControls/RadioButton.md - Data Controls: - DataList: DataControls/DataList.md diff --git a/samples/AfterBlazorServerSide/Pages/ControlSamples/TextBox/Default.razor b/samples/AfterBlazorServerSide/Pages/ControlSamples/TextBox/Default.razor new file mode 100644 index 000000000..ffb61e71e --- /dev/null +++ b/samples/AfterBlazorServerSide/Pages/ControlSamples/TextBox/Default.razor @@ -0,0 +1,50 @@ +@page "/ControlSamples/TextBox" +@using BlazorWebFormsComponents.Enums + +

TextBox Samples

+ +

Single Line TextBox

+ +

Value: @singleText

+ +

Password TextBox

+ + +

Email TextBox

+ + +

Number TextBox

+ + +

MultiLine TextBox

+ +

Value: @multiLineText

+ +

ReadOnly TextBox

+ + +

Disabled TextBox

+ + +

With MaxLength (10 characters max)

+ + +

With Styles

+ + +

Date Picker

+ + +

URL Input

+ + +@code { + private string singleText = "Default Text"; + private string multiLineText = "Line 1\nLine 2\nLine 3"; +} diff --git a/samples/BeforeWebForms/ControlSamples/TextBox/Default.aspx b/samples/BeforeWebForms/ControlSamples/TextBox/Default.aspx new file mode 100644 index 000000000..6bfbf461e --- /dev/null +++ b/samples/BeforeWebForms/ControlSamples/TextBox/Default.aspx @@ -0,0 +1,30 @@ +<%@ Page Language="C#" AutoEventWireup="true" %> + + +TextBox Sample + +
+

Single Line TextBox

+ + +

Password TextBox

+ + +

MultiLine TextBox

+ + +

ReadOnly TextBox

+ + +

Disabled TextBox

+ + +

With MaxLength

+ + +

With Styles

+ + + + diff --git a/src/BlazorWebFormsComponents.Test/TextBox/Attributes.razor b/src/BlazorWebFormsComponents.Test/TextBox/Attributes.razor new file mode 100644 index 000000000..b3efce02c --- /dev/null +++ b/src/BlazorWebFormsComponents.Test/TextBox/Attributes.razor @@ -0,0 +1,70 @@ +@using TBM = BlazorWebFormsComponents.Enums.TextBoxMode + +@code { + + [Fact] + public void TextBox_WithMaxLength_RendersMaxLengthAttribute() + { + // Arrange & Act + var cut = Render(@); + + // Assert + var input = cut.Find("input"); + input.GetAttribute("maxlength").ShouldBe("10"); + } + + [Fact] + public void TextBox_WithColumns_RendersSizeAttribute() + { + // Arrange & Act + var cut = Render(@); + + // Assert + var input = cut.Find("input"); + input.GetAttribute("size").ShouldBe("20"); + } + + [Fact] + public void TextBox_ReadOnly_RendersReadonlyAttribute() + { + // Arrange & Act + var cut = Render(@); + + // Assert + var input = cut.Find("input"); + input.HasAttribute("readonly").ShouldBeTrue(); + } + + [Fact] + public void TextBox_Disabled_RendersDisabledAttribute() + { + // Arrange & Act + var cut = Render(@); + + // Assert + var input = cut.Find("input"); + input.HasAttribute("disabled").ShouldBeTrue(); + } + + [Fact] + public void TextBox_WithTabIndex_RendersTabIndexAttribute() + { + // Arrange & Act + var cut = Render(@); + + // Assert + var input = cut.Find("input"); + input.GetAttribute("tabindex").ShouldBe("5"); + } + + [Fact] + public void TextBox_MultiLineWithMaxLength_DoesNotRenderMaxLength() + { + // Arrange & Act + var cut = Render(@); + + // Assert + var textarea = cut.Find("textarea"); + textarea.HasAttribute("maxlength").ShouldBeFalse(); + } +} diff --git a/src/BlazorWebFormsComponents.Test/TextBox/MultiLine.razor b/src/BlazorWebFormsComponents.Test/TextBox/MultiLine.razor new file mode 100644 index 000000000..3aa478c50 --- /dev/null +++ b/src/BlazorWebFormsComponents.Test/TextBox/MultiLine.razor @@ -0,0 +1,62 @@ +@using TBM = BlazorWebFormsComponents.Enums.TextBoxMode + +@code { + + [Fact] + public void TextBox_MultiLine_RendersTextarea() + { + // Arrange & Act + var cut = Render(@); + + // Assert + var textarea = cut.Find("textarea"); + textarea.TextContent.ShouldBe("Line 1\nLine 2\nLine 3"); + } + + [Fact] + public void TextBox_MultiLineWithRows_RendersRowsAttribute() + { + // Arrange & Act + var cut = Render(@); + + // Assert + var textarea = cut.Find("textarea"); + textarea.GetAttribute("rows").ShouldBe("5"); + } + + [Fact] + public void TextBox_MultiLineWithColumns_RendersColsAttribute() + { + // Arrange & Act + var cut = Render(@); + + // Assert + var textarea = cut.Find("textarea"); + textarea.GetAttribute("cols").ShouldBe("40"); + } + + [Fact] + public void TextBox_MultiLineWithRowsAndCols_RendersBothAttributes() + { + // Arrange & Act + var cut = Render(@); + + // Assert + var textarea = cut.Find("textarea"); + textarea.GetAttribute("rows").ShouldBe("5"); + textarea.GetAttribute("cols").ShouldBe("40"); + } + + [Fact] + public void TextBox_MultiLineWithCssClass_RendersClassAttribute() + { + // Arrange & Act + var cut = Render(@); + + // Assert + var textarea = cut.Find("textarea"); + textarea.GetAttribute("class").ShouldBe("my-textarea"); + } +} diff --git a/src/BlazorWebFormsComponents.Test/TextBox/Style.razor b/src/BlazorWebFormsComponents.Test/TextBox/Style.razor new file mode 100644 index 000000000..b1b137b44 --- /dev/null +++ b/src/BlazorWebFormsComponents.Test/TextBox/Style.razor @@ -0,0 +1,68 @@ +@using static BlazorWebFormsComponents.WebColor +@using static BlazorWebFormsComponents.Enums.BorderStyle +@using TBM = BlazorWebFormsComponents.Enums.TextBoxMode + +@code { + + [Fact] + public void TextBox_WithBackColor_RendersStyleAttribute() + { + // Arrange & Act + var cut = Render(@); + + // Assert + var input = cut.Find("input"); + var style = input.GetAttribute("style"); + style.ShouldContain("background-color"); + } + + [Fact] + public void TextBox_WithForeColor_RendersStyleAttribute() + { + // Arrange & Act + var cut = Render(@); + + // Assert + var input = cut.Find("input"); + var style = input.GetAttribute("style"); + style.ShouldContain("color"); + } + + [Fact] + public void TextBox_WithBorderColor_RendersStyleAttribute() + { + // Arrange & Act + var cut = Render(@); + + // Assert + var input = cut.Find("input"); + var style = input.GetAttribute("style"); + style.ShouldNotBeNull(); + style.ShouldContain("border"); + } + + [Fact] + public void TextBox_WithWidth_RendersStyleAttribute() + { + // Arrange & Act + var cut = Render(@); + + // Assert + var input = cut.Find("input"); + var style = input.GetAttribute("style"); + style.ShouldNotBeNull(); + style.ShouldContain("width"); + } + + [Fact] + public void TextBox_MultiLineWithStyle_RendersStyleOnTextarea() + { + // Arrange & Act + var cut = Render(@); + + // Assert + var textarea = cut.Find("textarea"); + var style = textarea.GetAttribute("style"); + style.ShouldContain("background-color"); + } +} diff --git a/src/BlazorWebFormsComponents.Test/TextBox/Text.razor b/src/BlazorWebFormsComponents.Test/TextBox/Text.razor new file mode 100644 index 000000000..1ee200049 --- /dev/null +++ b/src/BlazorWebFormsComponents.Test/TextBox/Text.razor @@ -0,0 +1,47 @@ +@code { + + [Fact] + public void TextBox_SingleLine_RendersInputText() + { + // Arrange & Act + var cut = Render(@); + + // Assert + var input = cut.Find("input"); + input.GetAttribute("type").ShouldBe("text"); + input.GetAttribute("value").ShouldBe("Hello World"); + } + + [Fact] + public void TextBox_SingleLineWithCssClass_RendersClassAttribute() + { + // Arrange & Act + var cut = Render(@); + + // Assert + var input = cut.Find("input"); + input.GetAttribute("class").ShouldBe("form-control"); + } + + [Fact] + public void TextBox_SingleLineWithPlaceholder_RendersPlaceholder() + { + // Arrange & Act + var cut = Render(@); + + // Assert + var input = cut.Find("input"); + input.GetAttribute("placeholder").ShouldBe("Enter text here"); + } + + [Fact] + public void TextBox_EmptyText_RendersEmptyValue() + { + // Arrange & Act + var cut = Render(@); + + // Assert + var input = cut.Find("input"); + input.GetAttribute("value").ShouldBe(""); + } +} diff --git a/src/BlazorWebFormsComponents.Test/TextBox/TextMode.razor b/src/BlazorWebFormsComponents.Test/TextBox/TextMode.razor new file mode 100644 index 000000000..5204fc37b --- /dev/null +++ b/src/BlazorWebFormsComponents.Test/TextBox/TextMode.razor @@ -0,0 +1,71 @@ +@using TBM = BlazorWebFormsComponents.Enums.TextBoxMode + +@code { + + [Fact] + public void TextBox_PasswordMode_RendersPasswordInput() + { + // Arrange & Act + var cut = Render(@); + + // Assert + var input = cut.Find("input"); + input.GetAttribute("type").ShouldBe("password"); + input.GetAttribute("value").ShouldBe("secret"); + } + + [Fact] + public void TextBox_EmailMode_RendersEmailInput() + { + // Arrange & Act + var cut = Render(@); + + // Assert + var input = cut.Find("input"); + input.GetAttribute("type").ShouldBe("email"); + } + + [Fact] + public void TextBox_NumberMode_RendersNumberInput() + { + // Arrange & Act + var cut = Render(@); + + // Assert + var input = cut.Find("input"); + input.GetAttribute("type").ShouldBe("number"); + } + + [Fact] + public void TextBox_DateMode_RendersDateInput() + { + // Arrange & Act + var cut = Render(@); + + // Assert + var input = cut.Find("input"); + input.GetAttribute("type").ShouldBe("date"); + } + + [Fact] + public void TextBox_UrlMode_RendersUrlInput() + { + // Arrange & Act + var cut = Render(@); + + // Assert + var input = cut.Find("input"); + input.GetAttribute("type").ShouldBe("url"); + } + + [Fact] + public void TextBox_PhoneMode_RendersTelInput() + { + // Arrange & Act + var cut = Render(@); + + // Assert + var input = cut.Find("input"); + input.GetAttribute("type").ShouldBe("tel"); + } +} diff --git a/src/BlazorWebFormsComponents.Test/TextBox/Visible.razor b/src/BlazorWebFormsComponents.Test/TextBox/Visible.razor new file mode 100644 index 000000000..d10b93497 --- /dev/null +++ b/src/BlazorWebFormsComponents.Test/TextBox/Visible.razor @@ -0,0 +1,22 @@ +@code { + + [Fact] + public void TextBox_VisibleFalse_RendersNothing() + { + // Arrange & Act + var cut = Render(@); + + // Assert + cut.Markup.ShouldBeEmpty(); + } + + [Fact] + public void TextBox_VisibleTrue_RendersInput() + { + // Arrange & Act + var cut = Render(@); + + // Assert + cut.Find("input").ShouldNotBeNull(); + } +} diff --git a/src/BlazorWebFormsComponents/Enums/TextBoxMode.cs b/src/BlazorWebFormsComponents/Enums/TextBoxMode.cs new file mode 100644 index 000000000..1c4840c7d --- /dev/null +++ b/src/BlazorWebFormsComponents/Enums/TextBoxMode.cs @@ -0,0 +1,22 @@ +namespace BlazorWebFormsComponents.Enums +{ + public enum TextBoxMode + { + SingleLine = 0, + MultiLine = 1, + Password = 2, + Color = 3, + Date = 4, + DateTime = 5, + DateTimeLocal = 6, + Email = 7, + Month = 8, + Number = 9, + Range = 10, + Search = 11, + Phone = 12, + Time = 13, + Url = 14, + Week = 15 + } +} diff --git a/src/BlazorWebFormsComponents/Extensions/HasStyleExtensions.cs b/src/BlazorWebFormsComponents/Extensions/HasStyleExtensions.cs index 72b1a120b..dc7075738 100644 --- a/src/BlazorWebFormsComponents/Extensions/HasStyleExtensions.cs +++ b/src/BlazorWebFormsComponents/Extensions/HasStyleExtensions.cs @@ -44,6 +44,9 @@ public static StyleBuilder ToStyle(this IStyle hasStyle) => .AddValue(() => ColorTranslator.ToHtml(hasStyle.BorderColor.ToColor()), HasBorders(hasStyle)), when: HasBorders(hasStyle)) + .AddStyle("width", hasStyle.Width.ToString(), hasStyle.Width != Unit.Empty) + .AddStyle("height", hasStyle.Height.ToString(), hasStyle.Height != Unit.Empty) + .AddStyle("font-weight", "bold", hasStyle.Font.Bold) .AddStyle("font-style", "italic", hasStyle.Font.Italic) .AddStyle("font-family", hasStyle.Font.Names, !string.IsNullOrEmpty(hasStyle.Font.Names)) diff --git a/src/BlazorWebFormsComponents/TextBox.razor b/src/BlazorWebFormsComponents/TextBox.razor new file mode 100644 index 000000000..e31fc8e03 --- /dev/null +++ b/src/BlazorWebFormsComponents/TextBox.razor @@ -0,0 +1,13 @@ +@inherits BaseStyledComponent + +@if (Visible) +{ + @if (TextMode == TextBoxMode.MultiLine) + { + + } + else + { + + } +} diff --git a/src/BlazorWebFormsComponents/TextBox.razor.cs b/src/BlazorWebFormsComponents/TextBox.razor.cs new file mode 100644 index 000000000..d03881147 --- /dev/null +++ b/src/BlazorWebFormsComponents/TextBox.razor.cs @@ -0,0 +1,107 @@ +using BlazorWebFormsComponents.Enums; +using BlazorWebFormsComponents.Interfaces; +using Microsoft.AspNetCore.Components; +using System; +using System.Collections.Generic; + +namespace BlazorWebFormsComponents +{ + public partial class TextBox : BaseStyledComponent, ITextComponent + { + [Parameter] + public string Text { get; set; } = string.Empty; + + [Parameter] + public TextBoxMode TextMode { get; set; } = TextBoxMode.SingleLine; + + [Parameter] + public int MaxLength { get; set; } + + [Parameter] + public int Columns { get; set; } + + [Parameter] + public int Rows { get; set; } + + [Parameter] + public bool ReadOnly { get; set; } + + [Parameter] + public string Placeholder { get; set; } + + [Parameter] + public EventCallback TextChanged { get; set; } + + [Parameter] + public EventCallback OnTextChanged { get; set; } + + [Parameter, Obsolete("AutoComplete is handled by browser settings")] + public string AutoComplete { get; set; } + + [Parameter, Obsolete("AutoPostBack is not supported in Blazor")] + public bool AutoPostBack { get; set; } + + internal string CalculatedType => TextMode switch + { + TextBoxMode.SingleLine => "text", + TextBoxMode.Password => "password", + TextBoxMode.Color => "color", + TextBoxMode.Date => "date", + TextBoxMode.DateTime => "datetime", + TextBoxMode.DateTimeLocal => "datetime-local", + TextBoxMode.Email => "email", + TextBoxMode.Month => "month", + TextBoxMode.Number => "number", + TextBoxMode.Range => "range", + TextBoxMode.Search => "search", + TextBoxMode.Phone => "tel", + TextBoxMode.Time => "time", + TextBoxMode.Url => "url", + TextBoxMode.Week => "week", + _ => "text" + }; + + internal Dictionary CalculatedAttributes + { + get + { + var attributes = new Dictionary(); + + if (!string.IsNullOrEmpty(CssClass)) + attributes["class"] = CssClass; + + if (!string.IsNullOrEmpty(Style)) + attributes["style"] = Style; + + if (!Enabled) + attributes["disabled"] = true; + + if (ReadOnly) + attributes["readonly"] = true; + + if (!string.IsNullOrEmpty(Placeholder)) + attributes["placeholder"] = Placeholder; + + if (TabIndex != 0) + attributes["tabindex"] = TabIndex; + + if (TextMode == TextBoxMode.MultiLine) + { + if (Rows > 0) + attributes["rows"] = Rows; + if (Columns > 0) + attributes["cols"] = Columns; + } + else + { + if (MaxLength > 0) + attributes["maxlength"] = MaxLength; + if (Columns > 0) + attributes["size"] = Columns; + } + + return attributes; + } + } + } +} diff --git a/src/BlazorWebFormsComponents/_Imports.razor b/src/BlazorWebFormsComponents/_Imports.razor index 36d9817c3..e740ebf3c 100644 --- a/src/BlazorWebFormsComponents/_Imports.razor +++ b/src/BlazorWebFormsComponents/_Imports.razor @@ -1,3 +1,4 @@ @inherits BaseWebFormsComponent @using Microsoft.AspNetCore.Components.Web +@using BlazorWebFormsComponents.Enums