diff --git a/docs/en/docs-params.json b/docs/en/docs-params.json index a5665a1215..c15e60c8cb 100644 --- a/docs/en/docs-params.json +++ b/docs/en/docs-params.json @@ -36,6 +36,17 @@ "Tiered": "Tiered", "Microservice": "Microservice" } + }, + { + "name": "BlazorUI", + "displayName": "Blazor UI Library", + "values": { + "Blazorise": "Blazorise", + "MudBlazor": "MudBlazor" + }, + "dependsOn": { + "UI": ["Blazor", "BlazorServer", "BlazorWebApp"] + } } ] } \ No newline at end of file diff --git a/docs/en/framework/ui/blazor/basic-theme.md b/docs/en/framework/ui/blazor/basic-theme.md index 33c0cb7175..f8cd0c7023 100644 --- a/docs/en/framework/ui/blazor/basic-theme.md +++ b/docs/en/framework/ui/blazor/basic-theme.md @@ -10,7 +10,8 @@ ````json //[doc-params] { - "UI": ["Blazor", "BlazorServer"] + "UI": ["Blazor", "BlazorServer"], + "BlazorUI": ["Blazorise", "MudBlazor"] } ```` @@ -20,6 +21,18 @@ The Basic Theme is a theme implementation for the Blazor UI. It is a minimalist > See the [Theming document](theming.md) to learn about themes. +{{if BlazorUI == "MudBlazor"}} + +> **MudBlazor Variant** — When the `--blazor-ui-library mudblazor` option is used, the Basic Theme ships as a MudBlazor variant. Replace `BasicTheme` with `MudBlazorBasicTheme` everywhere in this document (package names, module type names and namespaces). The MudBlazor variant is **not** based on Bootstrap — it uses MudBlazor's Material Design layout components. +> +> Concrete package names you will see when using the MudBlazor variant: +> +> * `Volo.Abp.AspNetCore.Components.{Server,WebAssembly}.MudBlazorBasicTheme` +> * `Volo.Abp.AspNetCore.Components.{Server,WebAssembly}.MudBlazorBasicTheme.Bundling` +> * Module types: `Abp{...}MudBlazorBasicThemeModule`, `Abp{...}MudBlazorBasicThemeBundlingModule` + +{{end}} + ## Installation If you need to manually this theme, follow the steps below: diff --git a/docs/en/framework/ui/blazor/components/submit-button.md b/docs/en/framework/ui/blazor/components/submit-button.md index 832554c9b1..82dbeeeee9 100644 --- a/docs/en/framework/ui/blazor/components/submit-button.md +++ b/docs/en/framework/ui/blazor/components/submit-button.md @@ -1,12 +1,21 @@ +```json +//[doc-params] +{ + "BlazorUI": ["Blazorise", "MudBlazor"] +} +``` + ```json //[doc-seo] { - "Description": "Explore the `SubmitButton` component in Blazor UI, designed for easy form submissions with localization support and loading indicators." + "Description": "Explore the submit button component in Blazor UI, designed for easy form submissions with localization support and loading indicators." } ``` # Blazor UI: SubmitButton Component +{{if BlazorUI == "Blazorise"}} + `SubmitButton` is a simple wrapper around `Button` component. It is used to be placed inside of page Form or Modal dialogs where it can response to user actions and to be activated as a default button by pressing an ENTER key. Once clicked it will go into the `disabled` state and also it will show a small loading indicator until clicked event is finished. ## Quick Example @@ -29,4 +38,54 @@ Notice that we didn't specify any text, like `Save Changes`. This is because `Su @L["Save"] -``` \ No newline at end of file +``` + +{{end}} + +{{if BlazorUI == "MudBlazor"}} + +The MudBlazor variant of ABP UI does not ship a dedicated `SubmitButton` wrapper. Use the standard `MudButton` together with the typical `Processing`/`Disabled` pattern to disable the button and show a progress indicator while the click handler is running. + +## Quick Example + +```razor + + @if (_processing) + { + + } + @L["Save"] + + +@code { + private bool _processing; + + private async Task SaveAsync() + { + _processing = true; + try + { + // ... your save operation + } + finally + { + _processing = false; + } + } +} +``` + +## Submit on Enter + +When the button is placed inside a `` or a ``, pressing ENTER inside an input control submits the form. To run validation before saving, call `_form.Validate()` first. See the [Forms & Validation](../forms-validation.md) page for details. + +## Use Inside `AbpMudCrudPageBase` + +The MudBlazor CRUD page base (`AbpMudCrudPageBase`) already wires up the standard create/update buttons inside its dialogs and shows a progress indicator while the application service call is running. In most cases you don't need to author a save button by hand; override `OnCreatingEntityAsync` / `OnUpdatingEntityAsync` instead. + +> Check the [MudBlazor button documentation](https://mudblazor.com/components/button) for all available options. + +{{end}} \ No newline at end of file diff --git a/docs/en/framework/ui/blazor/customization-overriding-components.md b/docs/en/framework/ui/blazor/customization-overriding-components.md index 8855ff89bd..c919ed084e 100644 --- a/docs/en/framework/ui/blazor/customization-overriding-components.md +++ b/docs/en/framework/ui/blazor/customization-overriding-components.md @@ -10,7 +10,8 @@ ````json //[doc-params] { - "UI": ["Blazor", "BlazorServer"] + "UI": ["Blazor", "BlazorServer"], + "BlazorUI": ["Blazorise", "MudBlazor"] } ```` @@ -41,6 +42,8 @@ The next step is to create a razor component, like `MyBranding.razor`, in your a The content of the `MyBranding.razor` is shown below: +{{if BlazorUI == "Blazorise"}} + ````html @using Volo.Abp.DependencyInjection {{if UI == "BlazorServer"}} @@ -58,6 +61,33 @@ The content of the `MyBranding.razor` is shown below: ```` +{{end}} + +{{if BlazorUI == "MudBlazor"}} + +The MudBlazor variant uses the LeptonX-based MudBlazor theme by default. The component to override is the `Branding` component shipped by the active MudBlazor theme: + +````html +@using Volo.Abp.DependencyInjection +{{if UI == "BlazorServer"}} +@using Volo.Abp.AspNetCore.Components.Server.MudBlazorLeptonXTheme.Themes.MudBlazorLeptonX +{{end}} +{{if UI == "Blazor"}} +@using Volo.Abp.AspNetCore.Components.WebAssembly.MudBlazorLeptonXTheme.Themes.MudBlazorLeptonX +{{end}} + +@inherits Branding +@attribute [ExposeServices(typeof(Branding))] +@attribute [Dependency(ReplaceServices = true)] + + + +```` + +> If you are using the MudBlazor BasicTheme or a different MudBlazor theme, replace the namespace with the namespace of that theme's `Themes/` folder. + +{{end}} + Let's explain the code: * `@inherits Branding` line inherits the Branding component defined by the [Basic Theme](basic-theme.md) (in the {{if UI == "BlazorServer"}}`Volo.Abp.AspNetCore.Components.Server.BasicTheme.Themes.Basic`{{end}} {{if UI == "Blazor"}}`Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme.Themes.Basic`{{end}} namespace). @@ -75,6 +105,8 @@ Now, you can run the application to see the result: If you prefer to use code-behind file for the C# code of your component, you can use the attributes in the C# side. +{{if BlazorUI == "Blazorise"}} + **MyBlazor.razor** ````html @@ -113,6 +145,50 @@ namespace MyProject.Blazor.Components } ```` +{{end}} + +{{if BlazorUI == "MudBlazor"}} + +**MyBlazor.razor** + +````html +{{if UI == "BlazorServer"}} +@using Volo.Abp.AspNetCore.Components.Server.MudBlazorLeptonXTheme.Themes.MudBlazorLeptonX +{{end}} +{{if UI == "Blazor"}} +@using Volo.Abp.AspNetCore.Components.WebAssembly.MudBlazorLeptonXTheme.Themes.MudBlazorLeptonX +{{end}} +@inherits Branding + + + +```` + +**MyBlazor.razor.cs** + +````csharp +{{if UI == "BlazorServer"}} +using Volo.Abp.AspNetCore.Components.Server.MudBlazorLeptonXTheme.Themes.MudBlazorLeptonX; +{{end}} +{{if UI == "Blazor"}} +using Volo.Abp.AspNetCore.Components.WebAssembly.MudBlazorLeptonXTheme.Themes.MudBlazorLeptonX; +{{end}} + +using Volo.Abp.DependencyInjection; + +namespace MyProject.Blazor.Components +{ + [ExposeServices(typeof(Branding))] + [Dependency(ReplaceServices = true)] + public partial class MyBranding + { + + } +} +```` + +{{end}} + ## Theming The [Theming](theming.md) system allows you to build your own theme. You can create your theme from scratch or get the [Basic Theme](basic-theme.md) and change however you like. diff --git a/docs/en/framework/ui/blazor/data-table-column-extensions.md b/docs/en/framework/ui/blazor/data-table-column-extensions.md index b89b825201..bb6bc6eeb0 100644 --- a/docs/en/framework/ui/blazor/data-table-column-extensions.md +++ b/docs/en/framework/ui/blazor/data-table-column-extensions.md @@ -1,3 +1,10 @@ +```json +//[doc-params] +{ + "BlazorUI": ["Blazorise", "MudBlazor"] +} +``` + ```json //[doc-seo] { @@ -102,6 +109,8 @@ public class CustomTableColumn Navigate to the razor file and paste the following code. +{{if BlazorUI == "Blazorise"}} + ```csharp @using System @using Volo.Abp.Identity @@ -116,6 +125,27 @@ else } ``` +{{end}} + +{{if BlazorUI == "MudBlazor"}} + +```razor +@using Volo.Abp.Identity + +@if (Data.As().EmailConfirmed) +{ + +} +else +{ + +} +``` + +> When using MudBlazor, the standard data grid in module pages is `AbpMudExtensibleDataGrid`. You can replace `Component = typeof(CustomTableColumn)` exactly the same way as in Blazorise; the column system is shared across both UI libraries. + +{{end}} + Navigate back to the `CustomizedUserManagement` class, and use `Component` property to specify the custom blazor component. ```csharp diff --git a/docs/en/framework/ui/blazor/entity-action-extensions.md b/docs/en/framework/ui/blazor/entity-action-extensions.md index ab3d4b3cba..78d16f7327 100644 --- a/docs/en/framework/ui/blazor/entity-action-extensions.md +++ b/docs/en/framework/ui/blazor/entity-action-extensions.md @@ -1,3 +1,10 @@ +```json +//[doc-params] +{ + "BlazorUI": ["Blazorise", "MudBlazor"] +} +``` + ```json //[doc-seo] { @@ -95,6 +102,8 @@ Here, the list of the properties that you use in the `EntityAction`. #### Example +{{if BlazorUI == "Blazorise"}} + ```csharp var clickMeAction = new EntityAction() { @@ -115,3 +124,33 @@ var clickMeAction = new EntityAction() } }; ``` + +{{end}} + +{{if BlazorUI == "MudBlazor"}} + +```csharp +var clickMeAction = new EntityAction() +{ + Text = "Click Me!", + Clicked = (data) => + { + //TODO: Write your custom code + + return Task.CompletedTask; + }, + Color = MudBlazor.Color.Error, + Icon = MudBlazor.Icons.Material.Filled.PanTool, + ConfirmationMessage = (data) => "Are you sure you want to click to the action?", + Visible = (data) => + { + //TODO: Write your custom visibility action + //var selectedUser = data.As(); + return true; + } +}; +``` + +> The MudBlazor variant uses `MudBlazor.Color` enum values (e.g. `Color.Primary`, `Color.Error`, `Color.Success`) for `Color`, and Material Icon constants (e.g. `Icons.Material.Filled.Edit`) for `Icon`. The `EntityAction` model itself is shared with Blazorise; only the values you put inside it change. + +{{end}} diff --git a/docs/en/framework/ui/blazor/error-handling.md b/docs/en/framework/ui/blazor/error-handling.md index 2fae4ff38d..211c56f25e 100644 --- a/docs/en/framework/ui/blazor/error-handling.md +++ b/docs/en/framework/ui/blazor/error-handling.md @@ -10,7 +10,8 @@ ````json //[doc-params] { - "UI": ["Blazor", "BlazorServer"] + "UI": ["Blazor", "BlazorServer"], + "BlazorUI": ["Blazorise", "MudBlazor"] } ```` @@ -36,7 +37,9 @@ There are different type of `Exception` classes handled differently by the ABP. **Example** -````csharp +{{if BlazorUI == "Blazorise"}} + +````razor @page "/" @using Volo.Abp @@ -60,11 +63,41 @@ There are different type of `Exception` classes handled differently by the ABP. {{end}} +{{if BlazorUI == "MudBlazor"}} + +````razor +@page "/" +@using Volo.Abp + +Throw test exception + +@code +{ + private async Task TestException() + { + try + { + throw new UserFriendlyException("A user friendly error message!"); + } + catch(UserFriendlyException ex) + { + await HandleErrorAsync(ex); + } + } +} +```` + +{{end}} + +{{end}} + {{if UI == "Blazor"}} **Example** -````csharp +{{if BlazorUI == "Blazorise"}} + +````razor @page "/" @using Volo.Abp @@ -78,6 +111,28 @@ There are different type of `Exception` classes handled differently by the ABP. } } ```` + +{{end}} + +{{if BlazorUI == "MudBlazor"}} + +````razor +@page "/" +@using Volo.Abp + +Throw test exception + +@code +{ + private void TestException() + { + throw new UserFriendlyException("A user friendly error message!"); + } +} +```` + +{{end}} + {{end}} ABP automatically handle the exception and show an error message to the user: diff --git a/docs/en/framework/ui/blazor/forms-validation.md b/docs/en/framework/ui/blazor/forms-validation.md index 7fb1423e77..7426ab6cab 100644 --- a/docs/en/framework/ui/blazor/forms-validation.md +++ b/docs/en/framework/ui/blazor/forms-validation.md @@ -1,12 +1,21 @@ +```json +//[doc-params] +{ + "BlazorUI": ["Blazorise", "MudBlazor"] +} +``` + ```json //[doc-seo] { - "Description": "Learn how to implement form validation in ABP Blazor UI using Blazorise's validation infrastructure with practical examples." + "Description": "Learn how to implement form validation in ABP Blazor UI using Blazorise or MudBlazor with practical examples." } ``` # Blazor UI: Forms & Validation +{{if BlazorUI == "Blazorise"}} + ABP Blazor UI is based on the [Blazorise](https://blazorise.com/docs) and does not have a built-in form validation infrastructure. However, you can use the [Blazorise validation infrastructure](https://blazorise.com/docs/components/validation) to validate your forms. ## Sample @@ -44,4 +53,91 @@ _The example is provided by official Blazorise documentation._ } ``` -> Check the [Blazorise documentation](https://blazorise.com/docs/components/validation) for more information and examples. \ No newline at end of file +> Check the [Blazorise documentation](https://blazorise.com/docs/components/validation) for more information and examples. + +{{end}} + +{{if BlazorUI == "MudBlazor"}} + +ABP Blazor UI built on top of [MudBlazor](https://mudblazor.com) uses MudBlazor's built-in form components and validation infrastructure. MudBlazor integrates with ASP.NET Core's `DataAnnotations` and supports custom validation through `IValidationRule`, `Func>`, or fluent validators. + +## Sample + +The most common pattern is wrapping inputs in a `` and binding the form's validation state through `IsValid`: + +> Standard MudBlazor and ABP usings (`@using MudBlazor`, `@using Volo.Abp.MudBlazorUI`, etc.) come from the project's `_Imports.razor`. The example below only adds the additional usings needed for validation. + +```razor +@using System.ComponentModel.DataAnnotations + + + + + + + + Submit + + + +@code { + private MudForm _form; + private bool _isValid; + private SampleModel _model = new(); + + private async Task SubmitAsync() + { + await _form.Validate(); + if (_isValid) + { + // ... + } + } + + public class SampleModel + { + [Required] + public string Name { get; set; } + + [Required, EmailAddress] + public string Email { get; set; } + } +} +``` + +### Inputs Used in CRUD Pages + +ABP's MudBlazor CRUD pages (see `AbpMudCrudPageBase`) use a `` containing a `` and standard MudBlazor inputs: + +* `` / `` for text and multi-line text +* `` / `` for dropdowns +* `` for booleans +* `` / `` for date and time +* `` for numbers + +The page base validates the entire form before calling the application service: + +```csharp +protected override async Task OnCreatingEntityAsync() +{ + await _form.Validate(); + if (!_isValid) + { + return; + } + // ... call AppService +} +``` + +> Check the [MudBlazor documentation](https://mudblazor.com/components/form) for the full list of validation modes and the [MudBlazor inputs reference](https://mudblazor.com/components/textfield). + +{{end}} \ No newline at end of file diff --git a/docs/en/framework/ui/blazor/overall.md b/docs/en/framework/ui/blazor/overall.md index 23077e307e..f5d5cf7b78 100644 --- a/docs/en/framework/ui/blazor/overall.md +++ b/docs/en/framework/ui/blazor/overall.md @@ -1,3 +1,10 @@ +```json +//[doc-params] +{ + "BlazorUI": ["Blazorise", "MudBlazor"] +} +``` + ```json //[doc-seo] { @@ -95,6 +102,8 @@ Currently, three themes are **officially provided**: There are a set of standard libraries that comes pre-installed and supported by all the themes: +{{if BlazorUI == "Blazorise"}} + * [Twitter Bootstrap](https://getbootstrap.com/) as the fundamental HTML/CSS framework. * [Blazorise](https://github.com/stsrki/Blazorise) as a component library that supports the Bootstrap and adds extra components like Data Grid and Tree. * [FontAwesome](https://fontawesome.com/) as the fundamental CSS font library. @@ -106,6 +115,22 @@ These libraries are selected as the base libraries and available to the applicat > Beginning from June, 2021, the Blazorise library has dual licenses; open source & commercial. Based on your yearly revenue, you may need to buy a commercial license. See [this post](https://blazorise.com/news/announcing-2022-blazorise-plans-and-pricing-updates) to learn more. The Blazorise license is bundled with ABP and commercial customers doesn't need to buy an extra Blazorise license. +{{end}} + +{{if BlazorUI == "MudBlazor"}} + +* [MudBlazor](https://mudblazor.com/) as the component library, providing a complete set of Material Design components built natively for Blazor (form controls, data grid, dialogs, snackbars, dates, etc.). +* [FontAwesome](https://fontawesome.com/) as the fundamental CSS font library. +* [Flag Icon](https://github.com/lipis/flag-icons) as a library to show flags of countries. + +These libraries are selected as the base libraries and available to the applications and modules. + +The MudBlazor variant ships its own theming, dialog, snackbar and popover providers (see [Theming](theming.md)). The MudBlazor library is MIT-licensed and is bundled with ABP at no extra cost. + +> Bootstrap is **not** required when using MudBlazor; MudBlazor brings its own layout and component styles. + +{{end}} + ### The Layout The themes provide the layout. So, you have a responsive layout with the standard features already implemented. The screenshot below has taken from the layout of the [Basic Theme](basic-theme.md): diff --git a/docs/en/framework/ui/blazor/page-header.md b/docs/en/framework/ui/blazor/page-header.md index ed2e16903d..5c03af6aa4 100644 --- a/docs/en/framework/ui/blazor/page-header.md +++ b/docs/en/framework/ui/blazor/page-header.md @@ -1,3 +1,10 @@ +```json +//[doc-params] +{ + "BlazorUI": ["Blazorise", "MudBlazor"] +} +``` + ```json //[doc-seo] { @@ -30,6 +37,8 @@ Breadcrumbs can be added using the `BreadcrumbItems` property. **Example: Add Language Management to the breadcrumb items.** +{{if BlazorUI == "Blazorise"}} + Create a collection of `Volo.Abp.BlazoriseUI.BreadcrumbItem` objects and set the collection to the `BreadcrumbItems` parameter. ```csharp @@ -44,6 +53,31 @@ public partial class Index } ``` +{{end}} + +{{if BlazorUI == "MudBlazor"}} + +Create a collection of `MudBlazor.BreadcrumbItem` objects and set the collection to the `BreadcrumbItems` parameter. The MudBlazor `BreadcrumbItem` constructor takes `(string text, string href, bool disabled = false, string icon = null)`. + +```csharp +using MudBlazor; + +public partial class Index +{ + protected List BreadcrumbItems { get; } = new(); + + protected override void OnInitialized() + { + BreadcrumbItems.Add(new BreadcrumbItem( + text: "Language Management", + href: null, + disabled: true)); + } +} +``` + +{{end}} + Navigate back to the razor page. ```csharp @@ -57,19 +91,38 @@ The theme then renders the breadcrumb. An example render result can be: * The Home icon is rendered by default. Set `BreadcrumbShowHome` to `false` to hide it. * Breadcrumb items will be activated based on current navigation. Set `BreadcrumbShowCurrent` to `false` to disable it. -You can add as many items as you need. `BreadcrumbItem` constructor gets three parameters: +You can add as many items as you need. + +{{if BlazorUI == "Blazorise"}} + +The `Volo.Abp.BlazoriseUI.BreadcrumbItem` constructor gets three parameters: * `text`: The text to show for the breadcrumb item. * `url` (optional): A URL to navigate to, if the user clicks to the breadcrumb item. * `icon` (optional): An icon class (like `fas fa-user-tie` for Font-Awesome) to show with the `text`. +{{end}} + +{{if BlazorUI == "MudBlazor"}} + +The `MudBlazor.BreadcrumbItem` constructor takes: + +* `text`: The text to show for the breadcrumb item. +* `href`: A URL to navigate to (use `null` for the current page). +* `disabled` (optional): When `true`, the item is rendered as the current/non-clickable item. +* `icon` (optional): A Material icon (e.g. `Icons.Material.Filled.Language`). + +{{end}} + ## Page Toolbar Page toolbar can be set using the `Toolbar` property. **Example: Add a "New Item" toolbar item to the page toolbar.** -Create a `PageToolbar` object and define toolbar items using the `AddButton` extension method. +Create a `PageToolbar` object and define toolbar items using the `AddButton` extension method. + +{{if BlazorUI == "Blazorise"}} ```csharp public partial class Index @@ -87,6 +140,28 @@ public partial class Index } ``` +{{end}} + +{{if BlazorUI == "MudBlazor"}} + +```csharp +public partial class Index +{ + protected PageToolbar Toolbar { get; } = new(); + + protected override void OnInitialized() + { + Toolbar.AddButton("New Item", () => + { + //Write your click action here + return Task.CompletedTask; + }, icon: MudBlazor.Icons.Material.Filled.Add); + } +} +``` + +{{end}} + Navigate back to the razor page and set the `Toolbar` parameter. ```csharp diff --git a/docs/en/framework/ui/blazor/page-layout.md b/docs/en/framework/ui/blazor/page-layout.md index 75666fd17a..abd32745d0 100644 --- a/docs/en/framework/ui/blazor/page-layout.md +++ b/docs/en/framework/ui/blazor/page-layout.md @@ -1,3 +1,10 @@ +```json +//[doc-params] +{ + "BlazorUI": ["Blazorise", "MudBlazor"] +} +``` + ```json //[doc-seo] { @@ -36,6 +43,8 @@ Indicates current selected menu item name. Menu item name should match a unique Menu item name can be set on runtime too. +{{if BlazorUI == "Blazorise"}} + ```html @inject PageLayout PageLayout @@ -49,6 +58,25 @@ Menu item name can be set on runtime too. } ``` +{{end}} + +{{if BlazorUI == "MudBlazor"}} + +```razor +@inject PageLayout PageLayout + +Change Menu + +@code{ + protected void SetCategoriesMenuAsSelected() + { + PageLayout.MenuItemName = "MyProjectName.Categories"; + } +} +``` + +{{end}} + ![leptonx selected menu item](../../../images/leptonx-selected-menu-item-example.gif) @@ -57,6 +85,9 @@ Menu item name can be set on runtime too. ## BreadCrumbs BreadCrumbItems are used to render breadcrumbs in the PageHeader. + +{{if BlazorUI == "Blazorise"}} + ```csharp @inject PageLayout PageLayout @@ -65,6 +96,21 @@ BreadCrumbItems are used to render breadcrumbs in the PageHeader. } ``` +{{end}} + +{{if BlazorUI == "MudBlazor"}} + +```razor +@using MudBlazor +@inject PageLayout PageLayout + +@{ + PageLayout.BreadcrumbItems.Add(new BreadcrumbItem("My Page", "/my-page")); +} +``` + +{{end}} + ## Toolbar ToolbarItems are used to render action toolbar items in the PageHeader. diff --git a/docs/en/framework/ui/blazor/page-toolbar-extensions.md b/docs/en/framework/ui/blazor/page-toolbar-extensions.md index 9c96bf90f7..dc0917fe8a 100644 --- a/docs/en/framework/ui/blazor/page-toolbar-extensions.md +++ b/docs/en/framework/ui/blazor/page-toolbar-extensions.md @@ -1,3 +1,10 @@ +```json +//[doc-params] +{ + "BlazorUI": ["Blazorise", "MudBlazor"] +} +``` + ```json //[doc-seo] { @@ -27,6 +34,8 @@ We will use the [component override system](customization-overriding-components. Here, the content of the overridden `SetToolbarItemsAsync` method. +{{if BlazorUI == "Blazorise"}} + ```csharp protected override async ValueTask SetToolbarItemsAsync() { @@ -38,10 +47,31 @@ protected override async ValueTask SetToolbarItemsAsync() }, "file-import", Blazorise.Color.Secondary); } ``` + +{{end}} + +{{if BlazorUI == "MudBlazor"}} + +```csharp +protected override async ValueTask SetToolbarItemsAsync() +{ + await base.SetToolbarItemsAsync(); + Toolbar.AddButton("Import users from excel", () => + { + //TODO: Write your custom code + return Task.CompletedTask; + }, MudBlazor.Icons.Material.Filled.FileUpload, MudBlazor.Color.Secondary); +} +``` + +{{end}} + > In order to use the `AddButton` extension method, you need to add a using statement for the `Volo.Abp.AspNetCore.Components.Web.Theming.PageToolbars` namespace. Here, the entire content of the file. +{{if BlazorUI == "Blazorise"}} + ```csharp using System.Threading.Tasks; using Volo.Abp.AspNetCore.Components.Web.Theming.PageToolbars; @@ -67,6 +97,37 @@ namespace MyCompanyName.MyProjectName.Blazor.Pages.Identity } ``` +{{end}} + +{{if BlazorUI == "MudBlazor"}} + +```csharp +using System.Threading.Tasks; +using Volo.Abp.AspNetCore.Components.Web.Theming.PageToolbars; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Identity.Blazor.Pages.Identity; + +namespace MyCompanyName.MyProjectName.Blazor.Pages.Identity +{ + [ExposeServices(typeof(UserManagement))] + [Dependency(ReplaceServices = true)] + public class CustomizedUserManagement : UserManagement + { + protected override async ValueTask SetToolbarItemsAsync() + { + await base.SetToolbarItemsAsync(); + Toolbar.AddButton("Import users from excel", () => + { + //TODO: Write your custom code + return Task.CompletedTask; + }, MudBlazor.Icons.Material.Filled.FileUpload, MudBlazor.Color.Secondary); + } + } +} +``` + +{{end}} + When you run the application, you will see the button added next to the current button list. There are some other parameters of the `AddButton` method (for example, use `Order` to set the order of the button component relative to the other components). ## Advanced Use Cases @@ -83,9 +144,21 @@ For this example, we've created a `MyToolbarComponent` component under the `/Pag `MyToolbarComponent.razor` content: -````csharp +{{if BlazorUI == "Blazorise"}} + +````razor ```` + +{{end}} + +{{if BlazorUI == "MudBlazor"}} + +````razor +CLICK ME +```` + +{{end}} We will leave the `MyToolbarComponent.razor.cs` file empty. Then you can add the `MyToolbarComponent` to the user management page toolbar: diff --git a/docs/en/framework/ui/blazor/theming.md b/docs/en/framework/ui/blazor/theming.md index 7ded33608f..1fff0a057f 100644 --- a/docs/en/framework/ui/blazor/theming.md +++ b/docs/en/framework/ui/blazor/theming.md @@ -10,7 +10,8 @@ ````json //[doc-params] { - "UI": ["Blazor", "BlazorServer"] + "UI": ["Blazor", "BlazorServer"], + "BlazorUI": ["Blazorise", "MudBlazor"] } ```` @@ -52,6 +53,8 @@ All the themes must depend on the [Volo.Abp.AspNetCore.Components.Server.Theming {{end}} +{{if BlazorUI == "Blazorise"}} + * [Twitter Bootstrap](https://getbootstrap.com/) as the fundamental HTML/CSS framework. * [Blazorise](https://github.com/stsrki/Blazorise) as a component library that supports the Bootstrap and adds extra components like Data Grid and Tree. * [FontAwesome](https://fontawesome.com/) as the fundamental CSS font library. @@ -61,6 +64,31 @@ These libraries are selected as the base libraries and available to the applicat > Bootstrap's JavaScript part is not used since the Blazorise library already provides the necessary functionalities to the Bootstrap components in a native way. +{{end}} + +{{if BlazorUI == "MudBlazor"}} + +* [MudBlazor](https://mudblazor.com/) as the component library, providing a Material Design component set built natively for Blazor (form controls, data grid, dialogs, snackbars, dates, etc.). +* [FontAwesome](https://fontawesome.com/) as the fundamental CSS font library. +* [Flag Icon](https://github.com/lipis/flag-icons) as a library to show flags of countries. + +These libraries are selected as the base libraries and available to the applications and modules. + +A theme using the MudBlazor variant must place the four MudBlazor providers in the layout root so dialogs, snackbars and popovers work everywhere: + +```razor + + + + + +@Body +``` + +The provided themes (`Volo.Abp.AspNetCore.Components.Server.MudBlazorLeptonXTheme`, `Volo.Abp.AspNetCore.Components.WebAssembly.MudBlazorLeptonXTheme`, etc.) ship these providers as part of their layout templates. + +{{end}} + ### The Layout All themes must define a layout for the application. The following image shows the user management page in the [Basic Theme](basic-theme.md) application layout: @@ -90,6 +118,8 @@ A theme is simply a Razor Class Library. The easiest way of creating a new theme is adding [Basic Theme Source Code](https://github.com/abpframework/abp/tree/dev/modules/basic-theme) module with source codes and customizing it. +{{if BlazorUI == "Blazorise"}} + {{if UI == "Blazor"}} ```bash abp add-package Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme --with-source-code --add-to-solution-file @@ -102,6 +132,24 @@ abp add-package Volo.Abp.AspNetCore.Components.Server.BasicTheme --with-source-c ``` {{end}} +{{end}} + +{{if BlazorUI == "MudBlazor"}} + +{{if UI == "Blazor"}} +```bash +abp add-package Volo.Abp.AspNetCore.Components.WebAssembly.MudBlazorBasicTheme --with-source-code --add-to-solution-file +``` +{{end}} + +{{if UI == "BlazorServer"}} +```bash +abp add-package Volo.Abp.AspNetCore.Components.Server.MudBlazorBasicTheme --with-source-code --add-to-solution-file +``` +{{end}} + +{{end}} + ### Global Styles / Scripts A theme generally needs to add a global style to the page. ABP provides a system to manage the [Global Styles and Scripts](global-scripts-styles.md). A theme can implement the `IBundleContributor` to add global style or script files to the page. diff --git a/docs/en/tutorials/book-store-with-abp-suite/part-05.md b/docs/en/tutorials/book-store-with-abp-suite/part-05.md index 73a1d9c348..865658d186 100644 --- a/docs/en/tutorials/book-store-with-abp-suite/part-05.md +++ b/docs/en/tutorials/book-store-with-abp-suite/part-05.md @@ -11,7 +11,8 @@ //[doc-params] { "UI": ["MVC","Blazor","BlazorServer", "BlazorWebApp","NG","MAUIBlazor"], - "DB": ["EF", "Mongo"] + "DB": ["EF", "Mongo"], + "BlazorUI": ["Blazorise", "MudBlazor"] } ```` diff --git a/docs/en/tutorials/book-store/part-02.md b/docs/en/tutorials/book-store/part-02.md index 1fe21c2848..5c399f9209 100644 --- a/docs/en/tutorials/book-store/part-02.md +++ b/docs/en/tutorials/book-store/part-02.md @@ -10,7 +10,8 @@ //[doc-params] { "UI": ["MVC","Blazor","BlazorServer", "BlazorWebApp", "NG", "MAUIBlazor"], - "DB": ["EF","Mongo"] + "DB": ["EF","Mongo"], + "BlazorUI": ["Blazorise", "MudBlazor"] } ```` ````json @@ -549,6 +550,8 @@ When you click on the Books menu item under the Book Store parent, you will be r ### Book List +{{if BlazorUI == "Blazorise"}} + We will use the [Blazorise library](https://blazorise.com/) as the UI component kit. It is a very powerful library that supports major HTML/CSS frameworks, including Bootstrap. ABP provides a generic base class - `AbpCrudPageBase<...>`, to create CRUD style pages. This base class is compatible with the `ICrudAppService` that was used to build the `IBookAppService`. So, we can inherit from the `AbpCrudPageBase` to automate the code behind for the standard CRUD stuff. @@ -624,6 +627,81 @@ Open the `Books.razor` and replace the content as the following: While the code above is pretty easy to understand, you can check the Blazorise [Card](https://blazorise.com/docs/components/card/) and [DataGrid](https://blazorise.com/docs/extensions/datagrid/) documents to understand them better. +{{end}} + +{{if BlazorUI == "MudBlazor"}} + +We will use the [MudBlazor library](https://mudblazor.com/) as the UI component kit. It is a Material Design component library built natively for Blazor. + +ABP provides a generic base class — `AbpMudCrudPageBase<...>`, to create CRUD style pages. This base class is compatible with the `ICrudAppService` that was used to build the `IBookAppService`. So, we can inherit from the `AbpMudCrudPageBase` to automate the code behind for the standard CRUD stuff. + +Open the `Books.razor` and replace the content as the following: + +````razor +@page "/books" +@using Volo.Abp.Application.Dtos +@using Acme.BookStore.Books +@using Acme.BookStore.Localization +@inherits AbpMudCrudPageBase + + + + + @L["Books"] + + + + + + + + + @L[$"Enum:BookType.{context.Item.Type}"] + + + + + @context.Item.PublishDate.ToShortDateString() + + + + + + @context.Item.CreationTime.ToLongDateString() + + + + + + + +@code +{ + public Books() // Constructor + { + LocalizationResource = typeof(BookStoreResource); + } +} +```` + +> If you see some syntax errors, you can ignore them if your application is properly built and running. Visual Studio still has some bugs with Blazor. + +* Inherited from `AbpMudCrudPageBase` which implements all the CRUD details for us. +* `Entities`, `TotalCount`, `PageSize`, `OnDataGridReadAsync` are defined in the base class. +* `LocalizationResource` is set to the `BookStoreResource` to localize the texts. +* `AbpMudExtensibleDataGrid` is the ABP-extended `MudDataGrid` that supports the [data table column extension system](../../framework/ui/blazor/data-table-column-extensions.md). + +While the code above is pretty easy to understand, you can check the MudBlazor [Card](https://mudblazor.com/components/card) and [DataGrid](https://mudblazor.com/components/datagrid) documents to understand them better. + +{{end}} + #### About the AbpCrudPageBase We will continue benefitting from `AbpCrudPageBase` for the books page. You could just inject the `IBookAppService` and perform all the server side calls yourself (thanks to the [Dynamic C# HTTP API Client Proxy](../../framework/api-development/dynamic-csharp-clients.md) system of the ABP). We will do it manually for the authors page to demonstrate how to call the server side HTTP APIs in your Blazor applications. diff --git a/docs/en/tutorials/book-store/part-03.md b/docs/en/tutorials/book-store/part-03.md index 73b2daea4e..5a4ae72eac 100644 --- a/docs/en/tutorials/book-store/part-03.md +++ b/docs/en/tutorials/book-store/part-03.md @@ -10,7 +10,8 @@ //[doc-params] { "UI": ["MVC","Blazor","BlazorServer","BlazorWebApp","NG", "MAUIBlazor"], - "DB": ["EF","Mongo"] + "DB": ["EF","Mongo"], + "BlazorUI": ["Blazorise", "MudBlazor"] } ```` @@ -1094,6 +1095,8 @@ In this section, you will learn how to create a new modal dialog form to create ### Add a "New Button" Button +{{if BlazorUI == "Blazorise"}} + Open the `Books.razor` and replace the `` section with the following code: ````xml @@ -1110,6 +1113,27 @@ Open the `Books.razor` and replace the `` section with the following ```` +{{end}} + +{{if BlazorUI == "MudBlazor"}} + +Open the `Books.razor` and replace the `` section with the following code: + +````razor + + + @L["Books"] + + + @L["NewBook"] + + +```` + +{{end}} + This will change the card header by adding a "New book" button to the right side: ![blazor-add-book-button](./images/blazor-add-book-button-2.png) @@ -1118,6 +1142,8 @@ Now, we can add a modal that will be opened when we click the button. ### Book Creation Modal +{{if BlazorUI == "Blazorise"}} + Open the `Books.razor` and add the following code to the end of the page: ````xml @@ -1184,6 +1210,52 @@ This code requires a service; Inject the `AbpBlazorMessageLocalizerHelper` at * The form implements validation and the `AbpBlazorMessageLocalizerHelper` is used to simply localize the validation messages. * The `CreateModal` object, `CloseCreateModalAsync` and `CreateEntityAsync` methods are defined by the base class. Check out the [Blazorise documentation](https://blazorise.com/docs/) if you want to understand the `Modal` and the other components. +{{end}} + +{{if BlazorUI == "MudBlazor"}} + +Open the `Books.razor` and add the following code to the end of the page: + +````razor + + + @L["NewBook"] + + + + + + @foreach (BookType bookTypeValue in Enum.GetValues(typeof(BookType))) + { + @L[$"Enum:BookType.{(int)bookTypeValue}"] + } + + + + + + + @L["Cancel"] + @L["Save"] + + +```` + +* The form uses `[Required]`/DataAnnotations for validation; messages are localized via the same `AbpResource` localization system. +* The `_createDialog` field, `CloseCreateDialogAsync`, `CreateFormRef` and `CreateEntityAsync` are all defined in `AbpMudCrudPageBase`. Check the [MudBlazor documentation](https://mudblazor.com/components/dialog) if you want to understand the `MudDialog` and other components. + +{{end}} + That's all. Run the application and try to add a new book: ![blazor-new-book-modal](./images/blazor-new-book-modal-2.png) @@ -1194,6 +1266,8 @@ Editing a book is similar to creating a new book. ### Actions Dropdown +{{if BlazorUI == "Blazorise"}} + Open the `Books.razor` and add the following `DataGridEntityActionsColumn` section inside the `DataGridColumns` as the first item: ````xml @@ -1212,12 +1286,38 @@ Open the `Books.razor` and add the following `DataGridEntityActionsColumn` secti The `DataGridEntityActionsColumn` component is used to show an "Actions" dropdown for each row in the `DataGrid`. The `DataGridEntityActionsColumn` shows a **single button** instead of a dropdown if there is only one available action inside it: +{{end}} + +{{if BlazorUI == "MudBlazor"}} + +Open the `Books.razor` and add the following `MudDataGridEntityActionsColumn` as the first column inside the `` section of the `AbpMudExtensibleDataGrid`: + +````razor + + + + + + + +```` + +* `OpenEditDialogAsync` is defined in the base class which takes the entity (book) to edit. + +The `MudDataGridEntityActionsColumn` component is used to show an "Actions" menu for each row in the data grid. It shows a **single icon button** when there is only one available action and a dropdown menu when there are multiple actions. + +{{end}} + ![blazor-edit-book-action](./images/blazor-edit-book-action-3.png) ### Edit Modal We can now define a modal to edit the book. Add the following code to the end of the `Books.razor` page: +{{if BlazorUI == "Blazorise"}} + ````xml @@ -1273,6 +1373,47 @@ We can now define a modal to edit the book. Add the following code to the end of ```` +{{end}} + +{{if BlazorUI == "MudBlazor"}} + +````razor + + + @EditingEntity.Name + + + + + + @foreach (BookType bookTypeValue in Enum.GetValues(typeof(BookType))) + { + @L[$"Enum:BookType.{(int)bookTypeValue}"] + } + + + + + + + @L["Cancel"] + @L["Save"] + + +```` + +{{end}} + ### Mapperly Configuration The base `AbpCrudPageBase` uses the [object to object mapping](../../framework/infrastructure/object-to-object-mapping.md) system to convert an incoming `BookDto` object to a `CreateUpdateBookDto` object. So, we need to define the mapping. @@ -1304,7 +1445,11 @@ You can now run the application and try to edit a book. ## Deleting a Book -Open the `Books.razor` page and add the following `EntityAction` code under the "Edit" action inside `EntityActions`: +Open the `Books.razor` page and add the following entity action code under the "Edit" action. + +{{if BlazorUI == "Blazorise"}} + +Add the following `EntityAction` code under the "Edit" action inside `EntityActions`: ````xml ```` +{{end}} + +{{if BlazorUI == "MudBlazor"}} + +Add the following `MudEntityAction` code under the "Edit" action inside `MudEntityActions`: + +````razor + +```` + +{{end}} + * `DeleteEntityAsync` is defined in the base class that deletes the entity by performing a call to the server. * `ConfirmationMessage` is a callback to show a confirmation message before executing the action. * `GetDeleteConfirmationMessage` is defined in the base class. You can override this method (or pass another value to the `ConfirmationMessage` parameter) to customize the localization message. @@ -1327,6 +1487,8 @@ Run the application and try to delete a book. Here's the complete code to create the book management CRUD page, that has been developed in the last two parts: +{{if BlazorUI == "Blazorise"}} + ````xml @page "/books" @using Volo.Abp.Application.Dtos @@ -1521,3 +1683,143 @@ Here's the complete code to create the book management CRUD page, that has been {{end}} +{{if BlazorUI == "MudBlazor"}} + +````razor +@page "/books" +@using Volo.Abp.Application.Dtos +@using Acme.BookStore.Books +@using Acme.BookStore.Localization +@using Microsoft.Extensions.Localization +@inherits AbpMudCrudPageBase + + + + + @L["Books"] + + + @L["NewBook"] + + + + + + + + + + + + + + + + + @L[$"Enum:BookType.{(int)context.Item.Type}"] + + + + + @context.Item.PublishDate.ToShortDateString() + + + + + + @context.Item.CreationTime.ToLongDateString() + + + + + + + + + + @L["NewBook"] + + + + + + @foreach (BookType bookTypeValue in Enum.GetValues(typeof(BookType))) + { + @L[$"Enum:BookType.{(int)bookTypeValue}"] + } + + + + + + + @L["Cancel"] + @L["Save"] + + + + + + @EditingEntity.Name + + + + + + @foreach (BookType bookTypeValue in Enum.GetValues(typeof(BookType))) + { + @L[$"Enum:BookType.{(int)bookTypeValue}"] + } + + + + + + + @L["Cancel"] + @L["Save"] + + + +@code +{ + public Books() // Constructor + { + LocalizationResource = typeof(BookStoreResource); + } +} +```` + +{{end}} + +{{end}} + diff --git a/docs/en/tutorials/book-store/part-09.md b/docs/en/tutorials/book-store/part-09.md index b70fd3e875..9f0489516a 100644 --- a/docs/en/tutorials/book-store/part-09.md +++ b/docs/en/tutorials/book-store/part-09.md @@ -10,7 +10,8 @@ //[doc-params] { "UI": ["MVC","Blazor","BlazorServer","BlazorWebApp","NG", "MAUIBlazor"], - "DB": ["EF","Mongo"] + "DB": ["EF","Mongo"], + "BlazorUI": ["Blazorise", "MudBlazor"] } ```` @@ -837,6 +838,8 @@ That's all! This is a fully working CRUD page, you can create, edit and delete a Create a new Razor Component Page, `/Pages/Authors.razor`, in the {{ if UI == "BlazorServer" }}`Acme.BookStore.Blazor`{{ else if UI == "MAUIBlazor" }}`Acme.BookStore.MauiBlazor`{{ else }}`Acme.BookStore.Blazor.Client`{{ end }} project with the following content: +{{if BlazorUI == "Blazorise"}} + ````xml @page "/authors" @using Acme.BookStore.Authors @@ -1017,11 +1020,129 @@ Create a new Razor Component Page, `/Pages/Authors.razor`, in the {{ if UI == "B ```` -* This code is similar to the `Books.razor`, except it doesn't inherit from the `AbpCrudPageBase`, but uses its own implementation. +{{end}} + +{{if BlazorUI == "MudBlazor"}} + +````razor +@page "/authors" +@using Acme.BookStore.Authors +@using Acme.BookStore.Localization +@inherits BookStoreComponentBase +@inject IAuthorAppService AuthorAppService + + + + + @L["Authors"] + + + @if (CanCreateAuthor) + { + + @L["NewAuthor"] + + } + + + + + + + + + @if (CanEditAuthor) + { + + @L["Edit"] + + } + @if (CanDeleteAuthor) + { + + @L["Delete"] + + } + + + + + + + @context.Item.BirthDate.ToShortDateString() + + + + + + + + + + @L["NewAuthor"] + + + + + + + + + + @L["Cancel"] + + @L["Save"] + + + + + + + @EditingAuthor.Name + + + + + + + + + + @L["Cancel"] + + @L["Save"] + + + +```` + +{{end}} + +* This code is similar to the `Books.razor`, except it doesn't inherit from the `AbpCrudPageBase`/`AbpMudCrudPageBase`, but uses its own implementation. * Injects the `IAuthorAppService` to consume the server side HTTP APIs from the UI. We can directly inject application service interfaces and use just like regular method calls by the help of [Dynamic C# HTTP API Client Proxy System](../../framework/api-development/dynamic-csharp-clients.md), which performs REST API calls for us. See the `Authors` class below to see the usage. Create a new code behind file, `Authors.razor.cs`, under the `Pages` folder, with the following content: +{{if BlazorUI == "Blazorise"}} + ````csharp using System; using System.Collections.Generic; @@ -1195,6 +1316,196 @@ public partial class Authors } ```` +{{end}} + +{{if BlazorUI == "MudBlazor"}} + +````csharp +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Acme.BookStore.Authors; +using Acme.BookStore.Permissions; +using Microsoft.AspNetCore.Authorization; +using MudBlazor; +using Volo.Abp.Application.Dtos; + +{{ if UI == "BlazorServer" }}namespace Acme.BookStore.Blazor.Pages;{{ else if UI == "MAUIBlazor" }}namespace Acme.BookStore.MauiBlazor.Pages;{{ else }}namespace Acme.BookStore.Blazor.Client.Pages;{{ end }} + +public partial class Authors +{ + private IReadOnlyList AuthorList { get; set; } + + private int PageSize { get; } = LimitedResultRequestDto.DefaultMaxResultCount; + private int CurrentPage { get; set; } + private string CurrentSorting { get; set; } + private int TotalCount { get; set; } + + private bool CanCreateAuthor { get; set; } + private bool CanEditAuthor { get; set; } + private bool CanDeleteAuthor { get; set; } + + private CreateAuthorDto NewAuthor { get; set; } + + private Guid EditingAuthorId { get; set; } + private UpdateAuthorDto EditingAuthor { get; set; } + + private MudDialog CreateAuthorDialog { get; set; } + private MudDialog EditAuthorDialog { get; set; } + + private MudForm CreateFormRef; + private MudForm EditFormRef; + + // MudDatePicker requires nullable DateTime, while AuthorDto.BirthDate is non-nullable. + // Bind to a nullable wrapper and sync back to the DTO before saving. + private DateTime? NewAuthorBirthDate + { + get => NewAuthor?.BirthDate; + set { if (NewAuthor != null && value.HasValue) NewAuthor.BirthDate = value.Value; } + } + + private DateTime? EditingAuthorBirthDate + { + get => EditingAuthor?.BirthDate; + set { if (EditingAuthor != null && value.HasValue) EditingAuthor.BirthDate = value.Value; } + } + + public Authors() + { + NewAuthor = new CreateAuthorDto(); + EditingAuthor = new UpdateAuthorDto(); + } + + protected override async Task OnInitializedAsync() + { + await SetPermissionsAsync(); + await GetAuthorsAsync(); + } + + private async Task SetPermissionsAsync() + { + CanCreateAuthor = await AuthorizationService + .IsGrantedAsync(BookStorePermissions.Authors.Create); + + CanEditAuthor = await AuthorizationService + .IsGrantedAsync(BookStorePermissions.Authors.Edit); + + CanDeleteAuthor = await AuthorizationService + .IsGrantedAsync(BookStorePermissions.Authors.Delete); + } + + private async Task GetAuthorsAsync() + { + var result = await AuthorAppService.GetListAsync( + new GetAuthorListDto + { + MaxResultCount = PageSize, + SkipCount = CurrentPage * PageSize, + Sorting = CurrentSorting + } + ); + + AuthorList = result.Items; + TotalCount = (int)result.TotalCount; + } + + private async Task> OnDataGridReadAsync(GridState state) + { + CurrentSorting = state.SortDefinitions + .Select(s => s.SortBy + (s.Descending ? " DESC" : "")) + .Aggregate("", (a, b) => string.IsNullOrEmpty(a) ? b : a + "," + b); + CurrentPage = state.Page; + + await GetAuthorsAsync(); + + return new GridData { Items = AuthorList, TotalItems = TotalCount }; + } + + private async Task OpenCreateAuthorDialogAsync() + { + NewAuthor = new CreateAuthorDto(); + if (CreateFormRef != null) await CreateFormRef.ResetAsync(); + await CreateAuthorDialog.ShowAsync(); + } + + private Task CloseCreateAuthorDialogAsync() + { + return CreateAuthorDialog.CloseAsync(); + } + + private async Task OpenEditAuthorDialogAsync(AuthorDto author) + { + EditingAuthorId = author.Id; + EditingAuthor = ObjectMapper.Map(author); + if (EditFormRef != null) await EditFormRef.ResetAsync(); + await EditAuthorDialog.ShowAsync(); + } + + private Task CloseEditAuthorDialogAsync() + { + return EditAuthorDialog.CloseAsync(); + } + + private async Task DeleteAuthorAsync(AuthorDto author) + { + try + { + var confirmMessage = L["AuthorDeletionConfirmationMessage", author.Name]; + if (!await Message.Confirm(confirmMessage)) + { + return; + } + + await AuthorAppService.DeleteAsync(author.Id); + await GetAuthorsAsync(); + } + catch(Exception ex) + { + await HandleErrorAsync(ex); + } + } + + private async Task CreateAuthorAsync() + { + try + { + await CreateFormRef.Validate(); + if (CreateFormRef.IsValid) + { + await AuthorAppService.CreateAsync(NewAuthor); + await GetAuthorsAsync(); + await CreateAuthorDialog.CloseAsync(); + } + } + catch(Exception ex) + { + await HandleErrorAsync(ex); + } + } + + private async Task UpdateAuthorAsync() + { + try + { + await EditFormRef.Validate(); + if (EditFormRef.IsValid) + { + await AuthorAppService.UpdateAsync(EditingAuthorId, EditingAuthor); + await GetAuthorsAsync(); + await EditAuthorDialog.CloseAsync(); + } + } + catch(Exception ex) + { + await HandleErrorAsync(ex); + } + } +} +```` + +{{end}} + This class typically defines the properties and methods used by the `Authors.razor` page. ### Object Mapping diff --git a/docs/en/tutorials/book-store/part-10.md b/docs/en/tutorials/book-store/part-10.md index e03ada5a96..f8ff85e5d5 100644 --- a/docs/en/tutorials/book-store/part-10.md +++ b/docs/en/tutorials/book-store/part-10.md @@ -10,7 +10,8 @@ //[doc-params] { "UI": ["MVC","Blazor","BlazorServer","BlazorWebApp","NG", "MAUIBlazor"], - "DB": ["EF","Mongo"] + "DB": ["EF","Mongo"], + "BlazorUI": ["Blazorise", "MudBlazor"] } ```` @@ -1115,7 +1116,9 @@ That's all. Just run the application and try to create or edit an author. ### The Book List -It is very easy to show the *Author Name* in the book list. Open the `/Pages/Books.razor` file in the {{ if UI == "BlazorServer" }}`Acme.BookStore.Blazor` {{ else if UI == "MAUIBlazor" }}`Acme.BookStore.MauiBlazor` {{ else }}`Acme.BookStore.Blazor.Client`{{ end }} project and add the following `DataGridColumn` definition just after the `Name` (book name) column: +It is very easy to show the *Author Name* in the book list. Open the `/Pages/Books.razor` file in the {{ if UI == "BlazorServer" }}`Acme.BookStore.Blazor` {{ else if UI == "MAUIBlazor" }}`Acme.BookStore.MauiBlazor` {{ else }}`Acme.BookStore.Blazor.Client`{{ end }} project and add the following column definition just after the `Name` (book name) column: + +{{if BlazorUI == "Blazorise"}} ````xml ```` +{{end}} + +{{if BlazorUI == "MudBlazor"}} + +````razor + +```` + +{{end}} + When you run the application, you can see the *Author* column on the table: ![blazor-bookstore-book-list-with-authors](images/blazor-bookstore-book-list-with-authors-2.png) @@ -1147,6 +1160,8 @@ protected override async Task OnInitializedAsync() * It is essential to call the `base.OnInitializedAsync()` since `AbpCrudPageBase` has some initialization code to be executed. +{{if BlazorUI == "Blazorise"}} + Override the `OpenCreateModalAsync` method and adding the following code: ````csharp @@ -1199,7 +1214,67 @@ The final `@code` block should be the following: } ```` -Finally, add the following `Field` definition into the `ModalBody` of the *Create* modal, as the first item, before the `Name` field: +{{end}} + +{{if BlazorUI == "MudBlazor"}} + +Override the `OpenCreateDialogAsync` method and adding the following code: + +````csharp +protected override async Task OpenCreateDialogAsync() +{ + if (!authorList.Any()) + { + throw new UserFriendlyException(message: L["AnAuthorIsRequiredForCreatingBook"]); + } + + await base.OpenCreateDialogAsync(); + NewEntity.AuthorId = authorList.First().Id; +} +```` + +The final `@code` block should be the following: + +````csharp +@code +{ + //ADDED A NEW FIELD + IReadOnlyList authorList = Array.Empty(); + + public Books() // Constructor + { + LocalizationResource = typeof(BookStoreResource); + + CreatePolicyName = BookStorePermissions.Books.Create; + UpdatePolicyName = BookStorePermissions.Books.Edit; + DeletePolicyName = BookStorePermissions.Books.Delete; + } + + //GET AUTHORS ON INITIALIZATION + protected override async Task OnInitializedAsync() + { + await base.OnInitializedAsync(); + authorList = (await AppService.GetAuthorLookupAsync()).Items; + } + + protected override async Task OpenCreateDialogAsync() + { + if (!authorList.Any()) + { + throw new UserFriendlyException(message: L["AnAuthorIsRequiredForCreatingBook"]); + } + + await base.OpenCreateDialogAsync(); + NewEntity.AuthorId = authorList.First().Id; + } +} +```` + +{{end}} + +Finally, add the following field definition into the *Create* modal/dialog, as the first item, before the `Name` field: + +{{if BlazorUI == "Blazorise"}} ````xml @@ -1215,6 +1290,21 @@ Finally, add the following `Field` definition into the `ModalBody` of the *Creat ```` +{{end}} + +{{if BlazorUI == "MudBlazor"}} + +````razor + + @foreach (var author in authorList) + { + @author.Name + } + +```` + +{{end}} + This requires to add a new localization key to the `en.json` file: ````js @@ -1227,7 +1317,9 @@ You can run the application to see the *Author Selection* while creating a new b ### Edit Book Modal -Add the following `Field` definition into the `ModalBody` of the *Edit* modal, as the first item, before the `Name` field: +Add the following field definition into the *Edit* modal/dialog, as the first item, before the `Name` field: + +{{if BlazorUI == "Blazorise"}} ````xml @@ -1243,6 +1335,21 @@ Add the following `Field` definition into the `ModalBody` of the *Edit* modal, a ```` +{{end}} + +{{if BlazorUI == "MudBlazor"}} + +````razor + + @foreach (var author in authorList) + { + @author.Name + } + +```` + +{{end}} + That's all. We are reusing the `authorList` defined for the *Create* modal. {{end}} diff --git a/docs/en/tutorials/modular-crm/part-03.md b/docs/en/tutorials/modular-crm/part-03.md index e0db36d617..d34861a7ab 100644 --- a/docs/en/tutorials/modular-crm/part-03.md +++ b/docs/en/tutorials/modular-crm/part-03.md @@ -10,7 +10,8 @@ ````json //[doc-params] { - "UI": ["MVC", "BlazorWebApp", "NG"] + "UI": ["MVC", "BlazorWebApp", "NG"], + "BlazorUI": ["Blazorise", "MudBlazor"] } ```` @@ -515,6 +516,8 @@ Open the `ModularCrm.Catalog` .NET solution in your IDE, and find the `Pages/Cat Replace the `Index.razor` file with the following content: +{{if BlazorUI == "Blazorise"}} + ````razor @page "/catalog" @using System.Collections.Generic @@ -547,6 +550,44 @@ Replace the `Index.razor` file with the following content: } ```` +{{end}} + +{{if BlazorUI == "MudBlazor"}} + +````razor +@page "/catalog" +@using System.Collections.Generic +@using System.Threading.Tasks +@using ModularCrm.Catalog +@inject IProductAppService ProductAppService + +Products + + + + + @foreach (var product in Products) + { + + @product.Name (stock: @product.StockCount) + + } + + + + +@code { + private List Products { get; set; } = new(); + + protected override async Task OnInitializedAsync() + { + Products = await ProductAppService.GetListAsync(); + } +} +```` + +{{end}} + Here, you inject `IProductAppService`, get all products in `OnInitializedAsync`, and then render the result in a simple list. {{end}} diff --git a/docs/en/tutorials/modular-crm/part-05.md b/docs/en/tutorials/modular-crm/part-05.md index 4387ff6908..4f5b4fc2b5 100644 --- a/docs/en/tutorials/modular-crm/part-05.md +++ b/docs/en/tutorials/modular-crm/part-05.md @@ -10,7 +10,8 @@ ````json //[doc-params] { - "UI": ["MVC", "BlazorWebApp", "NG"] + "UI": ["MVC", "BlazorWebApp", "NG"], + "BlazorUI": ["Blazorise", "MudBlazor"] } ```` @@ -525,6 +526,8 @@ public class OrderingMenuContributor : IMenuContributor Replace the `Index.razor` content in the `Pages/Ordering` folder of the `ModularCrm.Ordering.Blazor` project with the following code block: +{{if BlazorUI == "Blazorise"}} + ````razor @page "/ordering" @using System.Collections.Generic @@ -559,6 +562,46 @@ Replace the `Index.razor` content in the `Pages/Ordering` folder of the `Modular } ```` +{{end}} + +{{if BlazorUI == "MudBlazor"}} + +````razor +@page "/ordering" +@using System.Collections.Generic +@using System.Threading.Tasks +@using ModularCrm.Ordering +@inject IOrderAppService OrderAppService + +Orders + + + + + @foreach (var order in Orders) + { + + Customer: @order.CustomerName
+ Product: @order.ProductId
+ State: @order.State +
+ } +
+
+
+ +@code { + private List Orders { get; set; } = new(); + + protected override async Task OnInitializedAsync() + { + Orders = await OrderAppService.GetListAsync(); + } +} +```` + +{{end}} + This page shows a list of orders on the UI. You haven't created a UI to create new orders, and we will not do it to keep this tutorial simple. If you want to learn how to create advanced UIs with ABP, please follow the [Book Store tutorial](../book-store/index.md). ### Editing the Menu Item diff --git a/docs/en/tutorials/modular-crm/part-06.md b/docs/en/tutorials/modular-crm/part-06.md index 31cc0cb48c..1065e5df6e 100644 --- a/docs/en/tutorials/modular-crm/part-06.md +++ b/docs/en/tutorials/modular-crm/part-06.md @@ -10,7 +10,8 @@ ````json //[doc-params] { - "UI": ["MVC", "BlazorWebApp", "NG"] + "UI": ["MVC", "BlazorWebApp", "NG"], + "BlazorUI": ["Blazorise", "MudBlazor"] } ```` @@ -296,6 +297,8 @@ As you can see, we can see the product names instead of product IDs. Open the `Index.razor` file, and change the `@order.ProductId` part to `@order.ProductName` to write the product name instead of the product ID. The final `Index.razor` content should be the following: +{{if BlazorUI == "Blazorise"}} + ````razor @page "/ordering" @using System.Collections.Generic @@ -330,6 +333,46 @@ Open the `Index.razor` file, and change the `@order.ProductId` part to `@order.P } ```` +{{end}} + +{{if BlazorUI == "MudBlazor"}} + +````razor +@page "/ordering" +@using System.Collections.Generic +@using System.Threading.Tasks +@using ModularCrm.Ordering +@inject IOrderAppService OrderAppService + +Orders + + + + + @foreach (var order in Orders) + { + + Customer: @order.CustomerName
+ Product: @order.ProductName
+ State: @order.State +
+ } +
+
+
+ +@code { + private List Orders { get; set; } = new(); + + protected override async Task OnInitializedAsync() + { + Orders = await OrderAppService.GetListAsync(); + } +} +```` + +{{end}} + That's all. Now, you can graph build the main application and run it in ABP Studio to see the result: ![abp-studio-browser-list-of-orders-with-product-name](images/abp-studio-browser-list-of-orders-with-product-name.png) diff --git a/docs/en/tutorials/todo/layered/index.md b/docs/en/tutorials/todo/layered/index.md index c8d95b627c..f0616c7765 100644 --- a/docs/en/tutorials/todo/layered/index.md +++ b/docs/en/tutorials/todo/layered/index.md @@ -11,7 +11,8 @@ //[doc-params] { "UI": ["MVC", "Blazor", "BlazorServer", "BlazorWebApp" ,"NG", "MAUIBlazor"], - "DB": ["EF", "Mongo"] + "DB": ["EF", "Mongo"], + "BlazorUI": ["Blazorise", "MudBlazor"] } ```` @@ -637,6 +638,8 @@ See the *Dynamic C# Proxies & Auto API Controllers* section below to learn how w Open the `Index.razor` file in the `Pages` folder of the {{if UI=="Blazor" || UI=="BlazorWebApp"}} *TodoApp.Blazor.Client* {{else if UI=="BlazorServer"}} *TodoApp.Blazor* {{else if UI=="MAUIBlazor"}} *TodoApp.MauiBlazor* {{end}} project and replace the content with the following code block: +{{if BlazorUI == "Blazorise"}} + ```xml @page "/" @inherits TodoAppComponentBase @@ -675,6 +678,47 @@ Open the `Index.razor` file in the `Pages` folder of the {{if UI=="Blazor" || UI ``` +{{end}} + +{{if BlazorUI == "MudBlazor"}} + +```razor +@page "/" +@inherits TodoAppComponentBase + + + + + + TODO LIST + + + + + + + Submit + + + + @foreach (var todoItem in TodoItems) + { + + + @todoItem.Text + + } + + + + +``` + +{{end}} + ### Index.razor.css As the final touch, open the `Index.razor.css` file in the `Pages` folder of the {{if UI=="Blazor" || UI=="BlazorWebApp"}}*TodoApp.Blazor.Client*{{else if UI=="BlazorServer"}} *TodoApp.Blazor* {{else if UI=="MAUIBlazor"}} *TodoApp.MauiBlazor* {{end}} project and add the following content: diff --git a/docs/en/tutorials/todo/single-layer/index.md b/docs/en/tutorials/todo/single-layer/index.md index 6a7f45ce62..b62abfb2b9 100644 --- a/docs/en/tutorials/todo/single-layer/index.md +++ b/docs/en/tutorials/todo/single-layer/index.md @@ -11,7 +11,8 @@ //[doc-params] { "UI": ["MVC", "Blazor", "BlazorServer", "NG"], - "DB": ["EF", "Mongo"] + "DB": ["EF", "Mongo"], + "BlazorUI": ["Blazorise", "MudBlazor"] } ```` @@ -621,6 +622,8 @@ This class uses the {{if UI=="Blazor"}}`ITodoAppService`{{else}}`TodoAppService` Open the `Index.razor` file in the {{if UI=="BlazorServer"}}`Components/Pages`{{else}}`Pages`{{end}} folder and replace the content with the following code block: +{{if BlazorUI == "Blazorise"}} + ```xml @page "/" @inherits TodoAppComponentBase @@ -660,6 +663,47 @@ Open the `Index.razor` file in the {{if UI=="BlazorServer"}}`Components/Pages`{{ ``` +{{end}} + +{{if BlazorUI == "MudBlazor"}} + +```razor +@page "/" +@inherits TodoAppComponentBase + + + + + + TODO LIST + + + + + + + Submit + + + + @foreach (var todoItem in TodoItems) + { + + + @todoItem.Text + + } + + + + +``` + +{{end}} + ### Index.razor.css As the final touch, open the `Index.razor.css` file in the {{if UI=="BlazorServer"}}`Components/Pages`{{else}}`Pages`{{end}} folder and add the following code block at the end of the file: diff --git a/docs/en/ui-themes/basic-theme/index.md b/docs/en/ui-themes/basic-theme/index.md index c87491d1b4..71ee739721 100644 --- a/docs/en/ui-themes/basic-theme/index.md +++ b/docs/en/ui-themes/basic-theme/index.md @@ -19,6 +19,8 @@ See the [Theming document](../../framework/ui/mvc-razor-pages/theming.md) to lea The Basic Theme has implementation for the following UI types: - [MVC UI](../../framework/ui/mvc-razor-pages/basic-theme.md) -- [Blazor UI](../../framework/ui/blazor/basic-theme.md) +- [Blazor UI](../../framework/ui/blazor/basic-theme.md) — available in two Blazor UI library variants: + - **Blazorise** (default): `Volo.Abp.AspNetCore.Components.{Server,WebAssembly}.BasicTheme` + - **MudBlazor**: `Volo.Abp.AspNetCore.Components.{Server,WebAssembly}.MudBlazorBasicTheme` - [Angular UI](../../framework/ui/angular/basic-theme.md) diff --git a/docs/en/ui-themes/index.md b/docs/en/ui-themes/index.md index 2beaa0dc05..36df6270ae 100644 --- a/docs/en/ui-themes/index.md +++ b/docs/en/ui-themes/index.md @@ -33,6 +33,15 @@ See the following documents based on the UI type you are using: - [Basic Theme - Blazor UI](../framework/ui/blazor/basic-theme.md) - [Basic Theme - Angular UI](../framework/ui/angular/basic-theme.md) +## Blazor UI Library + +When you create an ABP solution with a Blazor host (Blazor Server, Blazor WebAssembly or Blazor WebApp), you can additionally choose the underlying Blazor component library: + +* **Blazorise** — the original ABP default, based on Bootstrap. +* **MudBlazor** — a Material-Design component library, available as an alternative variant. Each official theme has a MudBlazor version (e.g. `MudBlazorLeptonXTheme`, `MudBlazorLeptonXLiteTheme`, `MudBlazorBasicTheme`). + +The choice is made at solution creation time via the `--blazor-ui-library` option (`abp new ... -bul mudblazor`) or in the ABP Studio new-solution wizard. + ## See Also * [Theming - MVC UI](../framework/ui/mvc-razor-pages/theming.md) diff --git a/docs/en/ui-themes/lepton-x-lite/blazor.md b/docs/en/ui-themes/lepton-x-lite/blazor.md index 7190ac1fc2..6b15e1f134 100644 --- a/docs/en/ui-themes/lepton-x-lite/blazor.md +++ b/docs/en/ui-themes/lepton-x-lite/blazor.md @@ -10,7 +10,8 @@ ````json //[doc-params] { - "UI": ["Blazor", "BlazorServer"] + "UI": ["Blazor", "BlazorServer"], + "BlazorUI": ["Blazorise", "MudBlazor"] } ```` @@ -20,6 +21,19 @@ LeptonX Lite has implementation for the ABP Blazor WebAssembly & Blazor Server. > See the [Theming document](../../framework/ui/mvc-razor-pages/theming.md) to learn about themes. +{{if BlazorUI == "MudBlazor"}} + +> **MudBlazor Variant** — When the `--blazor-ui-library mudblazor` option is used, the LeptonX Lite theme ships as a MudBlazor variant. Replace `LeptonXLiteTheme` with `MudBlazorLeptonXLiteTheme` everywhere in this document (package names, module type names and namespaces). The installation steps, layout customization API and override mechanism are the same; only the package and namespace prefix change. The component implementations use MudBlazor primitives (`MudAppBar`, `MudDrawer`, `MudNavLink`, etc.) instead of Blazorise components. +> +> Concrete package names you will see when using the MudBlazor variant: +> +> * `Volo.Abp.AspNetCore.Components.{Server,WebAssembly}.MudBlazorLeptonXLiteTheme` +> * `Volo.Abp.AspNetCore.Components.{Server,WebAssembly}.MudBlazorLeptonXLiteTheme.Bundling` +> * Module types: `Abp{...}MudBlazorLeptonXLiteThemeModule`, `Abp{...}MudBlazorLeptonXLiteThemeBundlingModule` +> * Layout namespace: `Volo.Abp.AspNetCore.Components.{Server,WebAssembly}.MudBlazorLeptonXLiteTheme.Themes.MudBlazorLeptonXLite` + +{{end}} + ## Installation This theme is **already installed** when you create a new solution using the startup templates. If you are using any other template, you can install this theme by following the steps below: diff --git a/docs/en/ui-themes/lepton-x/blazor.md b/docs/en/ui-themes/lepton-x/blazor.md index 29e8deb6c3..19ad552f91 100644 --- a/docs/en/ui-themes/lepton-x/blazor.md +++ b/docs/en/ui-themes/lepton-x/blazor.md @@ -10,12 +10,26 @@ ````json //[doc-params] { - "UI": ["Blazor", "BlazorServer"] + "UI": ["Blazor", "BlazorServer"], + "BlazorUI": ["Blazorise", "MudBlazor"] } ```` LeptonX theme is implemented and ready to use with ABP. No custom implementation is needed for Blazor Server & WebAssembly. +{{if BlazorUI == "MudBlazor"}} + +> **MudBlazor Variant** — When the `--blazor-ui-library mudblazor` option is used, the LeptonX theme ships as a MudBlazor variant. Replace `LeptonXTheme` with `MudBlazorLeptonXTheme` everywhere in this document (package names, module type names and namespaces). The installation steps, layout customization API and override mechanism are the same; only the package and namespace prefix change. Component implementations use MudBlazor primitives (`MudAppBar`, `MudDrawer`, `MudNavLink`, `MudMenu`, etc.) instead of Blazorise components. +> +> Concrete package names you will see when using the MudBlazor variant: +> +> * `Volo.Abp.AspNetCore.Components.{Server,WebAssembly}.MudBlazorLeptonXTheme` +> * `Volo.Abp.AspNetCore.Components.{Server,WebAssembly}.MudBlazorLeptonXTheme.Bundling` +> * Module types: `Abp{...}MudBlazorLeptonXThemeModule`, `Abp{...}MudBlazorLeptonXThemeBundlingModule` +> * Layout namespace: `Volo.Abp.AspNetCore.Components.{Server,WebAssembly}.MudBlazorLeptonXTheme.Themes.MudBlazorLeptonX` + +{{end}} + ## Installation {{if UI == "Blazor"}} diff --git a/modules/docs/src/Volo.Docs.Application.Contracts/Volo/Docs/Documents/DocumentParameterDto.cs b/modules/docs/src/Volo.Docs.Application.Contracts/Volo/Docs/Documents/DocumentParameterDto.cs index ea387b2794..26d8e6aaa8 100644 --- a/modules/docs/src/Volo.Docs.Application.Contracts/Volo/Docs/Documents/DocumentParameterDto.cs +++ b/modules/docs/src/Volo.Docs.Application.Contracts/Volo/Docs/Documents/DocumentParameterDto.cs @@ -9,5 +9,13 @@ namespace Volo.Docs.Documents public string DisplayName { get; set; } public Dictionary Values { get; set; } + + /// + /// Conditional visibility: this parameter is shown only when the keyed parameter's + /// current value is one of the listed values. + /// Example: "DependsOn": { "UI": [ "Blazor", "BlazorServer", "BlazorWebApp" ] } + /// When null or empty the parameter is always shown. + /// + public Dictionary> DependsOn { get; set; } } } \ No newline at end of file diff --git a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml index 521ba70ffd..c7a42b7e87 100644 --- a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml +++ b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml @@ -504,8 +504,9 @@ for (var i = 0; i < count; ++i) { var parameter = Model.DocumentPreferences.Parameters[i]; + var hiddenStyle = Model.IsParameterVisible(parameter) ? null : "display:none;"; -
+
@(parameter.DisplayName)