From f1b945e6f40e19e90391a0c6c983a7c234e43bd5 Mon Sep 17 00:00:00 2001 From: 10x developer Date: Tue, 5 Mar 2024 15:42:29 +0400 Subject: [PATCH] changed how the templating works for inheriting QuickForm added validation docs. --- docs/customization.md | 141 +++++++++++++++++- docs/getting-started.md | 8 +- docs/images/customized-form.png | Bin 0 -> 35609 bytes docs/index.md | 2 + mkdocs.yml | 5 +- ...Attribute.cs => ValidFeedbackAttribute.cs} | 6 +- .../Common/PropertyInfoExtensions.cs | 2 +- src/QuickForm/Components/BsQuickForm.razor | 37 +++-- src/QuickForm/Components/BsQuickForm.razor.cs | 61 -------- src/QuickForm/Components/QuickForm.razor | 3 +- src/QuickForm/Components/QuickForm.razor.cs | 17 +-- src/QuickForm/Components/TwQuickForm.razor | 31 ++-- src/QuickForm/Components/TwQuickForm.razor.cs | 61 -------- test/QuickForm.Example/Pages/Custom.razor | 34 +---- test/QuickForm.Example/Pages/CustomForm.razor | 35 +++++ test/QuickForm.Example/RegisterCommand.cs | 2 +- .../Components/TestCustomTemplate.razor | 2 +- .../Components/TestValidationFeedback.razor | 2 +- 18 files changed, 234 insertions(+), 215 deletions(-) create mode 100644 docs/images/customized-form.png rename src/QuickForm/Attributes/{ValidMessageAttribute.cs => ValidFeedbackAttribute.cs} (63%) delete mode 100644 src/QuickForm/Components/BsQuickForm.razor.cs delete mode 100644 src/QuickForm/Components/TwQuickForm.razor.cs create mode 100644 test/QuickForm.Example/Pages/CustomForm.razor diff --git a/docs/customization.md b/docs/customization.md index 0703933..4db09f2 100644 --- a/docs/customization.md +++ b/docs/customization.md @@ -1,11 +1,136 @@ +--- +title: Customization +--- + # Customization -While being effortless to use, %product% provides support for a wide variety of customizations. -such as: -- Css Class customization -- Validation -- Custom data types -- Custom Input / Label / Description / Valid-Invalid feedback / Submit Button customization +While being effortless to use, QuickForm provides support for a wide variety of customization. + +There are a few ways to customize QuickForm: + +- **[Using attributes](../attributes)**: You can use the `attributes` to decorate the individual fields and their looks. +- **[Customizing the whole layout](#custom-layout)**: You can create your own custom proxy component and use the `QuickForm` component + inside it. + +--- + +## Custom layout + +This is a step-by-step guide to creating a custom layout for your app. +Please note, that this process may seem a little complex for users who are not familiar with Blazor. + +--- + + +## Define the directives + +``` jsx title="AppForm.razor - declaring directives" linenums="1" +@using QuickForm.Components // (1)! +@inherits QuickForm // (2)! +@typeparam TEntity where TEntity : class, new() // (3)! +``` + +1. The `@using` directive is used to import the namespace of the `QuickForm` component. + This is necessary to use the `QuickForm` component in the current file. +2. The `@inherits` directive is used to specify the base class for the component. This is the class that contains the logic for the + component. + In this case, we are inheriting from the `QuickForm` component, which is the base class for the QuickForm component. +3. The `@typeparam` directive is used to specify that the component will be generic and work with any type, + as long as it satisfies the constraints specified in the `where` clause. + +--- + + +## Create the layouts + +``` jsx title="AppForm.razor - setting parameters and field layouts" linenums="5" hl_lines="3-4 8-24 28-30" +@{ + // Additional attributes, in this case, it is the class to be applied to the form. + AdditionalAttributes ??= new Dictionary(); + AdditionalAttributes.Add("class", "flex flex-col"); + + // Field layout + ChildContent = context => // (1)! + @
+ + + @context.InputComponent("peer") // (4)! + + + @context.Description // (5)! + + + + + @context.ValidationMessages("hidden peer-[.invalid]:block text-red-700") // (7)! +
; + + // Submit button layout + SubmitButtonTemplate = + @; +} +``` + +1. The markup inside the `ChildContent` fragment, will be applied to individual fields. + The `@context` object is used to access the field's metadata and the input component. It is an object which + implements [IQuickFormField](../api/QuickForm.Components.IQuickFormField). +2. `@context.EditorId` is a string, which holds the id of the input field. This can be used to associate the label with the input field. + It is automatically generated +3. `@context.DisplayName` is a string, which holds the display name for the field. This can be set using the `[DisplayName]` attribute. +4. `@context.InputComponent` is a [RenderFragment<string>](https://blazor-university.com/templating-components-with-renderfragements/) + that renders the input field. The string parameter for this RenderFragment is the CSS class to be applied to the input field. +5. `@context.Description` is a string, which holds the description for the field. This can be set using the `[Description]` attribute. +6. `@context.ValidFeedback` is a string, which holds the valid feedback for the field. This can be set using the `[ValidFeedback]` attribute. +7. Just like the `@context.InputComponent`, `@context.ValidationMessages` is also + a [RenderFragment<string>](https://blazor-university.com/templating-components-with-renderfragements/) + that renders the validation messages. The string parameter for this RenderFragment is the CSS class to be applied to the container + element containing validation messages. + +!!! info "@context" + + See [IQuickFormField]() interface for more information on the `@context` object and what fields are abstracted to you. + +--- + +## **Important!** add the base component's markup + +``` jsx title="AppForm.razor - parent markup rendering" linenums="37" hl_lines="2" +@{ + base.BuildRenderTree(__builder); // (1)! +} +``` + +1. This line is used to call your custom component's base class's - QuickForm's `BuildRenderTree` method. + +!!! warning "Important" + + This is necessary to render the form and its fields. **Without this line your custom form will not be rendered.** + +--- + + +## Congratulations! + +You can now use your component all around your application! + +Here is what a minimally styled TailwindCSS form looks like: + +![your custom form](images/customized-form.png) + +--- + +# Next steps + +
+ + +- :octicons-check-24: [Check out validation](../validation) +- :material-format-font: [Check out the attributes](../attributes) -> full example -{title="Most types of customization on one form" style="note"} \ No newline at end of file +
\ No newline at end of file diff --git a/docs/getting-started.md b/docs/getting-started.md index ca77a81..a4dc06f 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -96,7 +96,7 @@ public class Person --- -# Create the form +## Create the form === "Tailwind" @@ -135,7 +135,7 @@ public class Person --- -# Congratulations! +## Congratulations! You now have a fully functional form with validation and submit handling. @@ -147,10 +147,10 @@ You now have a fully functional form with validation and submit handling. ![The generated form](images/bs-user.png){ loading=lazy } - --- -# Next steps + +## Next steps
diff --git a/docs/images/customized-form.png b/docs/images/customized-form.png new file mode 100644 index 0000000000000000000000000000000000000000..9e2294ffc29f6f7c3bdf64f7e0ee9a3f252f814e GIT binary patch literal 35609 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 Hu0mjf&7tn@ literal 0 HcmV?d00001 diff --git a/docs/index.md b/docs/index.md index efdf8b1..1c9969e 100644 --- a/docs/index.md +++ b/docs/index.md @@ -79,4 +79,6 @@ But with time, it quickly grew into a passionate project which I believe will sa Big thanks to [Anton Toshik](https://www.youtube.com/@RawCoding "The man, the legend behind Raw Coding"), for inspiration about Expressions and Reflection. +[blazor-university](https://blazor-university.com/templating-components-with-renderfragements/) best place to learn blazor! + And thank **you**, for using QuickForm! 💗 diff --git a/mkdocs.yml b/mkdocs.yml index 998dc55..26c9744 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -24,8 +24,9 @@ theme: - search.suggest - search.highlight - content.tabs.link - - content.code.annotation - content.code.copy + - content.code.select + - content.code.annotate - content.tooltips palette: @@ -55,6 +56,8 @@ markdown_extensions: generic: true - pymdownx.highlight: anchor_linenums: true + line_spans: __span + pygments_lang_class: true - pymdownx.emoji: emoji_index: !!python/name:material.extensions.emoji.twemoji emoji_generator: !!python/name:materialx.emoji.to_svg diff --git a/src/QuickForm/Attributes/ValidMessageAttribute.cs b/src/QuickForm/Attributes/ValidFeedbackAttribute.cs similarity index 63% rename from src/QuickForm/Attributes/ValidMessageAttribute.cs rename to src/QuickForm/Attributes/ValidFeedbackAttribute.cs index 7b69eac..2eb9f21 100644 --- a/src/QuickForm/Attributes/ValidMessageAttribute.cs +++ b/src/QuickForm/Attributes/ValidFeedbackAttribute.cs @@ -4,13 +4,13 @@ /// Supplies the message for when the input is valid. /// [AttributeUsage(AttributeTargets.Property)] -public sealed class ValidMessageAttribute : Attribute +public sealed class ValidFeedbackAttribute : Attribute { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The message for valid feedback. - public ValidMessageAttribute(string message) => Message = message; + public ValidFeedbackAttribute(string message) => Message = message; /// /// Gets the css class. diff --git a/src/QuickForm/Common/PropertyInfoExtensions.cs b/src/QuickForm/Common/PropertyInfoExtensions.cs index e396dcc..b51d2b4 100644 --- a/src/QuickForm/Common/PropertyInfoExtensions.cs +++ b/src/QuickForm/Common/PropertyInfoExtensions.cs @@ -68,7 +68,7 @@ internal static string DisplayName(this PropertyInfo prop) internal static string? ValidFeedbackText(this PropertyInfo prop) { - if (prop.GetCustomAttribute() is not { Message: var message }) + if (prop.GetCustomAttribute() is not { Message: var message }) return null; return message; diff --git a/src/QuickForm/Components/BsQuickForm.razor b/src/QuickForm/Components/BsQuickForm.razor index 9b3ff52..9197a25 100644 --- a/src/QuickForm/Components/BsQuickForm.razor +++ b/src/QuickForm/Components/BsQuickForm.razor @@ -1,10 +1,17 @@ @typeparam TEntity where TEntity : class, new() +@inherits QuickForm - - -
+@{ + AdditionalAttributes ??= new Dictionary + { + { + "class", + "d-flex flex-column text-white text-start gap-3 rounded border p-3 bg-dark" + } + }; + + ChildContent = context => + @
@@ -26,12 +33,14 @@ } @context.ValidationMessages("invalid-feedback text-start text-danger fw-bold") -
- - - - - - \ No newline at end of file +
; + + SubmitButtonTemplate = + @; +} + +@{ + base.BuildRenderTree(__builder); +} \ No newline at end of file diff --git a/src/QuickForm/Components/BsQuickForm.razor.cs b/src/QuickForm/Components/BsQuickForm.razor.cs deleted file mode 100644 index dbea88c..0000000 --- a/src/QuickForm/Components/BsQuickForm.razor.cs +++ /dev/null @@ -1,61 +0,0 @@ -using Microsoft.AspNetCore.Components; -using Microsoft.AspNetCore.Components.Forms; - -namespace QuickForm.Components; - -/// -/// A BootstrapCSS styled generic component that displays a form for editing / creating a model. -/// -/// -public partial class BsQuickForm : ComponentBase, IDisposable - where TEntity : class, new() -{ - private QuickForm QuickFormRef { get; set; } = default!; - - /// - [Parameter, EditorRequired] - public TEntity? Model { get; set; } - - /// - /// Gets or sets the callback to be invoked when the model changes. - /// This could be used to update the model in the parent component. - /// - [Parameter] - public EventCallback OnModelChanged { get; set; } - - /// - /// A callback that will be invoked when the form is submitted. - /// - /// If using this parameter, you are responsible for triggering any validation - /// manually, e.g., by calling . - /// - [Parameter] - public EventCallback OnSubmit { get; set; } - - /// - /// A callback that will be invoked when the form is submitted and the - /// is determined to be valid. - /// - [Parameter] - public EventCallback OnValidSubmit { get; set; } - - /// - /// A callback that will be invoked when the form is submitted and the - /// is determined to be invalid. - /// - [Parameter] - public EventCallback OnInvalidSubmit { get; set; } - - /// - /// Gets or sets a collection of additional attributes that will be applied to the created form element. - /// - [Parameter(CaptureUnmatchedValues = true)] - public IReadOnlyDictionary? AdditionalAttributes { get; set; } - - /// - void IDisposable.Dispose() - { - GC.SuppressFinalize(this); - QuickFormRef.Dispose(); - } -} \ No newline at end of file diff --git a/src/QuickForm/Components/QuickForm.razor b/src/QuickForm/Components/QuickForm.razor index f8b6c85..b6c58d4 100644 --- a/src/QuickForm/Components/QuickForm.razor +++ b/src/QuickForm/Components/QuickForm.razor @@ -1,12 +1,11 @@ @using Microsoft.AspNetCore.Components.Forms @using Blazored.FluentValidation -@using QuickForm.Common @typeparam TEntity where TEntity : class, new() + AdditionalAttributes="AdditionalAttributes?.AsReadOnly()"> diff --git a/src/QuickForm/Components/QuickForm.razor.cs b/src/QuickForm/Components/QuickForm.razor.cs index a71bbaf..d895f3a 100644 --- a/src/QuickForm/Components/QuickForm.razor.cs +++ b/src/QuickForm/Components/QuickForm.razor.cs @@ -1,6 +1,5 @@ using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Forms; -using Microsoft.AspNetCore.Components.Web; using QuickForm.Common; namespace QuickForm.Components; @@ -33,8 +32,8 @@ public partial class QuickForm : ComponentBase, IDisposable /// /// Gets or sets the template for the fields in this form. /// - [Parameter, EditorRequired] - public RenderFragment ChildContent { get; set; } = default!; + [Parameter] + public virtual RenderFragment ChildContent { get; set; } = default!; /// /// Gets or sets the submit button template of the form. @@ -43,7 +42,7 @@ public partial class QuickForm : ComponentBase, IDisposable /// Make sure to give this button `type="submit"` to make it work. /// [Parameter] - public RenderFragment? SubmitButtonTemplate { get; set; } + public virtual RenderFragment? SubmitButtonTemplate { get; set; } /// /// Gets or sets the callback to be invoked when the model changes. @@ -79,14 +78,12 @@ public partial class QuickForm : ComponentBase, IDisposable /// Gets or sets a collection of additional attributes that will be applied to the created form element. /// [Parameter(CaptureUnmatchedValues = true)] - public IReadOnlyDictionary? AdditionalAttributes { get; set; } + public IDictionary? AdditionalAttributes { get; set; } - /// - /// Gets or sets the that is used to determine the CSS class, - /// for valid and invalid fields. - /// private CustomValidationCssClassProvider ValidationCssClassProvider { get; set; } = default!; + private List> Fields { get; set; } = []; + /// /// Gets or sets the <see cref="EditContext"/> instance explicitly associated with this model /// @@ -94,8 +91,6 @@ public partial class QuickForm : ComponentBase, IDisposable internal string BaseEditorId { get; } = Guid.NewGuid().ToString()[..8]; - internal List> Fields { get; set; } = new(); - /// protected override void OnParametersSet() { diff --git a/src/QuickForm/Components/TwQuickForm.razor b/src/QuickForm/Components/TwQuickForm.razor index 3cdb6c7..c8687ad 100644 --- a/src/QuickForm/Components/TwQuickForm.razor +++ b/src/QuickForm/Components/TwQuickForm.razor @@ -1,9 +1,12 @@ @typeparam TEntity where TEntity : class, new() +@inherits QuickForm - - -
+@{ + AdditionalAttributes ??= new Dictionary(); + AdditionalAttributes.Add("class", "flex flex-col text-white text-start gap-3 rounded-md border border-gray-600 p-3 bg-gray-900"); + + ChildContent = context => + @
@@ -25,12 +28,14 @@ } @context.ValidationMessages("hidden peer-[.invalid]:block text-start text-sm text-red-700 font-bold") -
- - - - - - +
; + + SubmitButtonTemplate = + @; +} + +@{ + base.BuildRenderTree(__builder); +} \ No newline at end of file diff --git a/src/QuickForm/Components/TwQuickForm.razor.cs b/src/QuickForm/Components/TwQuickForm.razor.cs deleted file mode 100644 index ed1e5fa..0000000 --- a/src/QuickForm/Components/TwQuickForm.razor.cs +++ /dev/null @@ -1,61 +0,0 @@ -using Microsoft.AspNetCore.Components; -using Microsoft.AspNetCore.Components.Forms; - -namespace QuickForm.Components; - -/// -/// A TailwindCSS styled generic component that displays a form for editing / creating a model. -/// -/// -public partial class TwQuickForm : ComponentBase, IDisposable - where TEntity : class, new() -{ - private QuickForm QuickFormRef { get; set; } = default!; - - /// - [Parameter, EditorRequired] - public TEntity? Model { get; set; } - - /// - /// Gets or sets the callback to be invoked when the model changes. - /// This could be used to update the model in the parent component. - /// - [Parameter] - public EventCallback OnModelChanged { get; set; } - - /// - /// A callback that will be invoked when the form is submitted. - /// - /// If using this parameter, you are responsible for triggering any validation - /// manually, e.g., by calling . - /// - [Parameter] - public EventCallback OnSubmit { get; set; } - - /// - /// A callback that will be invoked when the form is submitted and the - /// is determined to be valid. - /// - [Parameter] - public EventCallback OnValidSubmit { get; set; } - - /// - /// A callback that will be invoked when the form is submitted and the - /// is determined to be invalid. - /// - [Parameter] - public EventCallback OnInvalidSubmit { get; set; } - - /// - /// Gets or sets a collection of additional attributes that will be applied to the created form element. - /// - [Parameter(CaptureUnmatchedValues = true)] - public IReadOnlyDictionary? AdditionalAttributes { get; set; } - - /// - void IDisposable.Dispose() - { - GC.SuppressFinalize(this); - QuickFormRef.Dispose(); - } -} \ No newline at end of file diff --git a/test/QuickForm.Example/Pages/Custom.razor b/test/QuickForm.Example/Pages/Custom.razor index bf2a1ff..6582902 100644 --- a/test/QuickForm.Example/Pages/Custom.razor +++ b/test/QuickForm.Example/Pages/Custom.razor @@ -1,38 +1,6 @@ @page "/custom" - - -
- - - @context.InputComponent("peer rounded-md border-2 bg-gray-100 text-black border-gray-300 dark:border-white p-1") - - @if (!string.IsNullOrEmpty(context.Description)) - { - - @context.Description - - } - - @if (!string.IsNullOrEmpty(context.ValidFeedback)) - { - - } - - @context.ValidationMessages("hidden peer-[.invalid]:block text-start text-sm text-red-700 font-bold") -
-
- - - - -
+ @code { diff --git a/test/QuickForm.Example/Pages/CustomForm.razor b/test/QuickForm.Example/Pages/CustomForm.razor new file mode 100644 index 0000000..7efed76 --- /dev/null +++ b/test/QuickForm.Example/Pages/CustomForm.razor @@ -0,0 +1,35 @@ +@typeparam TEntity where TEntity : class, new() +@inherits QuickForm + +@{ + AdditionalAttributes ??= new Dictionary(); + AdditionalAttributes.Add("class", "flex flex-col"); + + ChildContent = context => + @
+ + + @context.InputComponent("peer") + + + @context.Description + + + + + @context.ValidationMessages("hidden peer-[.invalid]:block text-red-700") +
; + + SubmitButtonTemplate = + @; +} + +@{ + base.BuildRenderTree(__builder); +} diff --git a/test/QuickForm.Example/RegisterCommand.cs b/test/QuickForm.Example/RegisterCommand.cs index d9383d8..05f7a1f 100644 --- a/test/QuickForm.Example/RegisterCommand.cs +++ b/test/QuickForm.Example/RegisterCommand.cs @@ -26,7 +26,7 @@ public class RegisterCommand [Placeholder] [DisplayName("Email Address")] [EmailAddress] - [ValidMessage("Looks good!")] + [ValidFeedback("Looks good!")] [Description("We will never share your email with anyone")] public string EmailAddress { get; set; } = string.Empty; diff --git a/test/QuickForm.Test/Components/TestCustomTemplate.razor b/test/QuickForm.Test/Components/TestCustomTemplate.razor index f76dbb9..bbb1307 100644 --- a/test/QuickForm.Test/Components/TestCustomTemplate.razor +++ b/test/QuickForm.Test/Components/TestCustomTemplate.razor @@ -7,7 +7,7 @@ [SuppressMessage("ReSharper", "UnusedMember.Local")] private class Model { - [ValidMessage("valid-feedback-text")] + [ValidFeedback("valid-feedback-text")] [System.ComponentModel.Description("description-text")] [Required(ErrorMessage = "required-error-message")] public string A { get; set; } = default!; diff --git a/test/QuickForm.Test/Components/TestValidationFeedback.razor b/test/QuickForm.Test/Components/TestValidationFeedback.razor index 887004a..a602ed7 100644 --- a/test/QuickForm.Test/Components/TestValidationFeedback.razor +++ b/test/QuickForm.Test/Components/TestValidationFeedback.razor @@ -7,7 +7,7 @@ [SuppressMessage("ReSharper", "UnusedMember.Local")] private class Model { - [ValidMessage("valid message")] + [ValidFeedback("valid message")] public string A { get; set; } = default!; [Required(ErrorMessage = "error message")]