I have an issue when use dialog.Close of Radzen.Blazor. I did all step in get started of blazor.radzen
Open the Shared/MainLayout.razor file and include , , and
Open the Startup.cs file (server-side Blazor) or Program.cs (client-side WebAssembly Blazor) and add DialogService, NotificationService, ContextMenuService and TooltipService.
The problem is when I open dialog, it open. Then I click in Button submit or Button Cancel to trigger dialog.Close(data) (Button Submit) and dialog.Close(false) (Button Cancel) it still run in function Submit/Close but it doesn't close dialog.
I try to find but can see any error.
This is my code
I use this to open dialog var result = await OpenAsync("Thêm mới", #Form);
This is Open Async
Task<dynamic> OpenAsync(string title, RenderFragment<TItem> content) => dialog.OpenAsync<WrapperForm<TItem>>(title, new Dictionary<string, object> { { "Content", content }, { "Data", null } }, FormOption ?? new DialogOptions() { Width = "700px", Top = "30px" });
This is my WrapperForm
#typeparam TItem
<div class="row">
#Content(Data)
</div>
#code {
[Parameter] public RenderFragment<TItem> Content { set; get; }
[Parameter] public TItem Data { set; get; }
}
This is component #Form i pass into OpenAsync
#using QuanLyKiemDinh.Components
#using QuanLyKiemDinh.Helpers
#inject QuanLyKiemDinhContext dbContext
#inject Radzen.DialogService dialog
<FormBase Data="#capVanban" TItem="tbCapVanBan" Submit="#Submit" Cancel="#Cancel">
<Items>
<FormItem Label="Tên cấp" SM="12">
<Item>
<RadzenTextBox #bind-Value="#capVanban.TenCap" Placeholder="Tên cấp" Style="width: 100%" />
</Item>
</FormItem>
<FormItem Label="Mô tả" SM="12">
<Item>
<RadzenTextBox #bind-Value="#capVanban.MoTa" Placeholder="Mô tả" Style="width: 100%" />
</Item>
</FormItem>
</Items>
</FormBase>
#code {
[Parameter] public tbCapVanBan Data { set; get; }
tbCapVanBan capVanban = new tbCapVanBan();
async void Submit(tbCapVanBan capVanban)
{
if (Data == null)
{
capVanban.id = SequenceId.GetInt("VBPL.Seq_tbCapVanBan");
dbContext.tbCapVanBans.Add(capVanban);
dbContext.SaveChanges();
} else
{
dbContext.tbCapVanBans.Update(capVanban);
dbContext.SaveChanges();
}
dialog.Close(capVanban);
//dbContext.SaveChanges();
}
void Cancel()
{
dialog.Close(false);
}
protected override void OnInitialized()
{
if (Data != null)
{
capVanban = Data;
}
base.OnInitialized();
}
}
This is my FormBase
#typeparam TItem
#inject QuanLyKiemDinhContext dbContext
#inject Radzen.DialogService dialog
#inject NotificationService notificationService
#inject NavigationManager UriHelper
<RadzenTemplateForm TItem="TItem" Data="#Data" Style="font-family:Helvetica; width: 100%" Submit="#Submit">
<div class="row" style="padding: 10px 20px;">
#Items
#if (DisableBtn != true)
{
<div style="padding: 10px 15px;">
#if (Buttons == null)
{
<ButtonTable Text="Lưu dữ liệu" ButtonType="ButtonType.Submit" />
<ButtonTable Text="Hủy" ButtonStyle="ButtonStyle.Light" OnClick="#Cancel"/>
#if (MoreButtons != null)
{
#MoreButtons
}
} else
{
#Buttons
}
</div>
}
</div>
</RadzenTemplateForm>
#code {
[Parameter] public EventCallback<TItem> Submit { set; get; }
[Parameter] public EventCallback Cancel { set; get; }
[Parameter] public TItem Data { set; get; }
[Parameter] public RenderFragment Items { set; get; }
[Parameter] public RenderFragment Buttons { get; set; }
[Parameter] public RenderFragment MoreButtons { get; set; }
[Parameter] public Boolean? DisableBtn { set; get; }
}
I had the same problem and found out that you need to add
<script async src="_content/Radzen.Blazor/Radzen.Blazor.js"></script>
in your index.html or _Host.cshtml file.
Link to the _Host.cshtml
Related
Im new into BlazorWA and i can't get the right way to send data/parameters from child to parent components. Is there a way to pass a return value from a Child Component Function to Parent Component?
Child Component Detail:
[Parameter] public double TotalPrice { get; set; }
[Parameter] protected EventCallback<double> ReturnValue { get; set; }
protected override async Task OnParametersSetAsync()
{
FileDetail = blabla;
CalculateCancelleds(TotalPrice);
}
public double CalculateCancelleds(double x)
{
var total = 0.0;
var cancelledsToList = FileDetail.Where(x => x.StatusStr == "Cancelled").Select(x => x.Net).ToList();
var totalCancelledNet = cancelledsToList.Sum(net => total + net);
var result = x - totalCancelledNet;
return result;
}
Parent Component:
<td class="table-Net">$#objItem.Price #* **Instead the total price, Here i want to get the result calculation from Child Compononent Function "CalculateCancelleds()"***# </td>
<ChildComponent.ReservationDetail TotalPrice="#objItem.Price" **<-----Here im sending the total price to Child Component.** Enix="#objItem.Enix" Admin="#objUser.Admin" FileStatus="#objItem.StatusString">
</ChildComponentReservationDetail.ReservationDetail>
I try with blazor data binding documentation from microsoft but i can't get it.
Here's a very simple example that demonstrates how to use binding to achieve what you want.
PriceCalculator.razor
<h3>PriceCalculator</h3>
<div class="alert alert-primary m-3">
Value = #this.Value
</div>
<button class="btn btn-primary" #onclick=this.IncrementPrice>Increment Price</button>
#code {
[Parameter] public decimal Value { get; set; }
[Parameter] public EventCallback<decimal> ValueChanged { get; set; }
private async Task IncrementPrice()
=> await ValueChanged.InvokeAsync(this.Value + 4);
}
Index.razor
#page "/"
<PageTitle>Index</PageTitle>
<h1>Hello, world!</h1>
Welcome to your new app.
<PriceCalculator #bind-Value=this.model.TotalPrice />
<div class="alert alert-info m-3">
Value = #this.model.TotalPrice
</div>
#code {
private MyModel model = new();
public class MyModel
{
public decimal TotalPrice { get; set; }
}
}
And a more complex PriceCalculator.
<h3>PriceCalculator</h3>
<div class="alert alert-primary m-3">
Value = #this.value
</div>
<div class="m-2">
<button class="btn btn-primary" #onclick=this.IncrementPrice>Increment Price</button>
<button class="btn btn-success" #onclick=this.Save>Save Price</button>
</div>
#code {
[Parameter] public decimal Value { get; set; }
[Parameter] public EventCallback<decimal> ValueChanged { get; set; }
private decimal value;
protected override void OnInitialized()
=> value = this.Value;
private Task IncrementPrice()
{
value = value + 4;
return Task.CompletedTask;
}
private async Task Save()
=> await ValueChanged.InvokeAsync(this.value);
}
I created this component. file name: InputTextComponent.razor
<div class="form-group">
<label for="Name">Name</label>
<InputText #bind-Value="#Value"/>
</div>
#code {
[Parameter]
public string Value{ get; set; }
}
in the page index.razor
#page "/"
<EditForm model="#data" OnValidSubmit="()=>OnClickBtn()">
<DataAnnotationsValidator></DataAnnotationsValidator>
<ValidationSummary></ValidationSummary>
<div class="modal-body">
<InputTextComponent Value="data.Name"/>
</div>
<div class="modal-footer justify-content-between">
<button type="button" >Cancel</button>
<button type="submit" class="btn btn-primary">OK</button>
</div>
</EditForm>
#code{
public class Item
{
public string Name { get; set; } = string.Empty;
}
private Item data { get; set; } = new Item(){ Name="John"};
private void OnClickBtn()
{
string k = "";
}
}
When I run, it show :
then i edit the name field. and press "ok" button.
How to pass #bind-value into component. (the same InputText on blazor component).
Thanks all!!
To achieve this you can manually implement the Change event in your child component InputTextComponent. The parameter is called Value, so you would need to implement an EventCallback with the name ValueChanged.
Your InputTextComponent would need to look like this:
<div class="form-group">
<label for="Name">Name</label>
<InputText #oninput="updateParent" #bind-Value="#Value"/>
</div>
#code {
[Parameter]
public string Value { get; set; }
[Parameter]
public EventCallback<string> ValueChanged { get; set; }
async void updateParent(ChangeEventArgs e)
{
ValueChanged.InvokeAsync(e.Value.ToString());
}
}
And then you can use #bind-Value in the parent component like <InputTextComponent #bind-Value="data.Name"/>
Here is a blazor repl showing the full thing.
Here's another way to do it.
I've added a bit of functionality to show you how to build out a classic Bootstrap control.
#inherits InputText
<div class="form-group">
<label class="form-label">#this.Label</label>
#inputControl
<div class="form-text">#this.Commentary</div>
</div>
#code {
[Parameter] public string Label { get; set; }
[Parameter] public string Commentary { get; set; }
// Gets the underlying Renderfragment built by InputText
private RenderFragment inputControl => (builder) => base.BuildRenderTree(builder);
}
And a demo page.
#page "/"
<PageTitle>Index</PageTitle>
<h1>Test Page</h1>
<EditForm Model=this.model>
<MyInputText class="form-control" Label="Value" Commentary="Enter a value" #bind-Value=model.Value />
</EditForm>
<div class="alert alert-info mt-3">
Value : #model.Value
</div>
#code {
private ModelData model = new();
public class ModelData {
public string? Value { get; set; }
}
}
As the other answer showed binding to oninput you can implement that like this. The above component would then inherit from BlazrInputText.
public class BlazrInputText : InputText
{
[Parameter] public bool BindOnInput { get; set; } = true;
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
builder.OpenElement(0, "input");
builder.AddMultipleAttributes(1, AdditionalAttributes);
if (!string.IsNullOrWhiteSpace(this.CssClass))
builder.AddAttribute(2, "class", CssClass);
builder.AddAttribute(3, "value", BindConverter.FormatValue(CurrentValueAsString));
if (BindOnInput)
builder.AddAttribute(4, "oninput", EventCallback.Factory.CreateBinder<string?>(this, __value => CurrentValueAsString = __value, CurrentValueAsString));
else
builder.AddAttribute(5, "onchange", EventCallback.Factory.CreateBinder<string?>(this, __value => CurrentValueAsString = __value, CurrentValueAsString));
builder.AddElementReferenceCapture(6, __inputReference => Element = __inputReference);
builder.CloseElement();
}
}
I want to build some reusable input components using Blazor.
Here is my code in the child component:
<div style="width: 100%">
<div class="create-approval-flow-drop-down">
<Label Display="Display.Flex">#ChildLabel</Label>
<Autocomplete TItem="GraphUser"
TValue="string"
Data="#users"
TextField="#(( item ) => item.DisplayName)"
ValueField="#(( item ) => item.DisplayName)"
Placeholder="Search..."
#bind-SelectedValue="#selectedSearchValue"
#bind-SelectedText="#selectedAutoCompleteText">
</Autocomplete>
</div>
</div>
#code {
[Parameter] public string ChildLabel { get; set; }
[Parameter] public string ChildValue { get; set; }
[Parameter] public string selectedSearchValue { get; set; }
[Parameter] public EventCallback<string> ChildLabelChanged { get; set; }
[Parameter] public EventCallback<string> ChildValueChanged { get; set; }
[Parameter] public EventCallback<string> selectedSearchValueChanged { get; set; }
[Inject] public IGraphService GraphService { get; set; }
public IEnumerable<GraphUser> users = new List<GraphUser>();
protected override async Task OnInitializedAsync()
{
users = await GraphService.GetUsers();
await base.OnInitializedAsync();
}
private Task OnChildValueChanged(ChangeEventArgs e)
{
return ChildValueChanged.InvokeAsync(selectedAutoCompleteText);
}
string selectedAutoCompleteText { get; set; }
}
And here is my parent component:
#page "/Test"
#using WireDesk.Web.SubComponents;
#using Blazored.FluentValidation;
#using WireDesk.Models
<WireDesk.Web.SubComponents.SelectAutocomplete.SingleSelectAutoComplete
ChildLabel="Location Manager"
ChildValue="">
</WireDesk.Web.SubComponents.SelectAutocomplete.SingleSelectAutoComplete>
#ChildLabel;
#ChildValue;
#code
{
private string ChildLabel { get; set; }
private string ChildValue { get; set; }
public ApprovalFlowForm approvalFlowForm = new ApprovalFlowForm();
}
The child will display the label and create a single select autocomplete text box. The parent will contain a form with a variety of fields, many of which will be a single select autocomplete text box.
I don't want to have to duplicate the code in the child over and over in the parent, but I cannot determine how to pass the string that the user has selected in the child component.
=========================================================
This is almost working but not completely, I think I didn't explain two things clearly. The component will be used for input on a create/update page, and I need to use it more than once. The parent component should call the child just to do the work of displaying/updating the value and returning that to the parent, and then the parent will bind that to the Form.Field. Seems to me that the power of components would be that they could be reused, not just in different parents, but multiple times in the same parent (I am sure this is possible, but sure I don't know how to do it).
My code for the child is as so:
<div style="width: 100%">
<div class="create-approval-flow-drop-down">
<Label Display="Display.Flex">#ChildLabel</Label>
<Autocomplete TItem="GraphUser"
TValue="string"
Data="#users"
TextField="#(( item ) => item.DisplayName)"
ValueField="#(( item ) => item.DisplayName)"
Placeholder="Search..."
SelectedValue="#ChildValue"
SelectedValueChanged="OnChildValueChanged">
</Autocomplete>
</div>
</div>
#code {
[Parameter] public string ChildLabel { get; set; }
[Parameter] public string ChildValue { get; set; }
[Parameter] public string FieldValue { get; set; }
[Parameter] public EventCallback<string> ChildValueChanged { get; set; }
[Inject] public IGraphService GraphService { get; set; }
public IEnumerable<GraphUser> users = new List<GraphUser>();
protected override async Task OnInitializedAsync()
{
users = await GraphService.GetUsers();
await base.OnInitializedAsync();
}
private async Task OnChildValueChanged(string selectedValue)
{
ChildValue = selectedValue;
await ChildValueChanged.InvokeAsync(ChildValue);
}
}
And the parent is
#page "/Test"
#using WireDesk.Web.SubComponents;
#using Blazored.FluentValidation;
#using WireDesk.Models
<WireDesk.Web.SubComponents.SelectAutocomplete.SingleSelectAutoComplete
ChildLabel="First Manager"
#bind-ChildValue="#childValue">
#*#bind-SelectedValue="#ApprovalFlowForm.FirstManager"*#
</WireDesk.Web.SubComponents.SelectAutocomplete.SingleSelectAutoComplete>
<WireDesk.Web.SubComponents.SelectAutocomplete.SingleSelectAutoComplete
ChildLabel="Second Manager"
#bind-ChildValue="#childValue">
#*bind-SelectedValue="#ApprovalFlowForm.SecondManager"*#
</WireDesk.Web.SubComponents.SelectAutocomplete.SingleSelectAutoComplete>
#code {
private string childValue;
}
The commented out line...
#bind-SelectedValue="#ApprovalFlowForm.SecondManager"#
..I know will probably not work, but indicates the general idea I think of what I want to accomplish.
Thank you both Joe and Dimitris very very much. I have been working on this for the last 8 hours, and I feel I am pretty close now. I appreciate your assistance.
Bryan
Still struggling with this.
My parent component is below. The "old" way of displaying an input select field is listed, as is my call to the "new" way, which is a child component.
#using Microsoft.IdentityModel.Tokens
#using System.Collections.Immutable
#using System.Linq
//Old
<div class="create-approval-flow-drop-down">
<Label Display="Display.Flex"> Controller </Label>
<Select TValue="string" #bind-SelectedValue="#ApprovalFlowForm.Controller">
#{foreach (var item in GraphUserOptions)
{<SelectItem Value="#item.Value">#item.Text</SelectItem>}
</Select>
</div>
//New
<div class="create-approval-flow-drop-down">
<SingleSelectAutoComplete
ChildLabel="Controller"
DataToSearch=users
#bind-SelectedValue="#ApprovalFlowForm.Controller">
</SingleSelectAutoComplete>
</div>
The child component below works perfectly except for one thing: when the parent edits a form with a value already in the data, it fails to display! So if "Ben Kew" is stored as the Controller in the Approval Form, opening the form in edit mode will not show that. Just a blank field. The user can pick from the list and changes will be saved, but the next time they open up the form they will see blank again.
If you can help me solve this, I would be so very happy and would be a big step in my project.
Thank you in advance for assistance!
Bryan
#using Blazorise.Components
#using System.Linq
<div style="width: 100%">
<div class="create-approval-flow-drop-down">
<Label Display="Display.Flex">#ChildLabel</Label>
<Autocomplete
TItem="GraphUser"
TValue="string"
Data=DataToSearch
TextField="#(( item ) => item.DisplayName)"
ValueField="#(( item ) => item.DisplayName)"
#bind-SelectedText="selectedAutoCompleteText"
SelectedValueChanged="#OnSelectedValueChanged">
Placeholder="Search...">
</Autocomplete>
</div>
</div>
#code {
[Parameter] public string ChildLabel { get; set; }
[Parameter] public IEnumerable<GraphUser> DataToSearch { get; set; }
[Parameter] public string SelectedValue { get; set; }
[Parameter] public EventCallback<string> SelectedValueChanged { get; set; }
[Parameter] public string? Param { get; set; }
public string selectedValue { get; set; }
public string selectedAutoCompleteText { get; set; }
public override async Task SetParametersAsync(ParameterView parameters)
{
}
private async Task OnSelectedValueChanged(string selectedValue)
{
//SelectedValue = selectedValue;
//await SelectedValueChanged.InvokeAsync(SelectedValue);
}
}
You can do it using EventCallback (like #Joe suggested).
<div style="width: 100%">
<div class="create-approval-flow-drop-down">
<Label Display="Display.Flex">#ChildLabel</Label>
<Autocomplete TItem="GraphUser"
TValue="string"
Data="#DataToSearch"
TextField="#(( item ) => item.DisplayName)"
ValueField="#(( item ) => item.DisplayName)"
Placeholder="Search..."
SelectedValue="#SelectedValue"
SelectedValueChanged="#OnSelectedValueChanged">
</Autocomplete>
</div>
</div>
#code {
[Parameter] public string ChildLabel { get; set; }
[Parameter] public IEnumerable<GraphUser> DataToSearch { get; set; }
[Parameter] public string SelectedValue { get; set; }
[Parameter] public EventCallback<string> SelectedValueChanged { get; set; }
private async Task OnSelectedValueChanged(string selectedValue)
{
SelectedValue = selectedValue;
await SelectedValueChanged.InvokeAsync(SelectedValue);
}
}
Usage:
<SingleSelectAutoComplete
ChildLabel="Controller"
DataToSearch="#users"
#bind-SelectedValue="#ApprovalFlowForm.Controller">
</SingleSelectAutoComplete>
Use an EventCallBack in the child and assign it to a function in the parent. Do something like this. In the parent component:
<ChildComponent TextFieldUpdated="HandleChange" />
And the following code:
private void HandleChange(string value){
Do stuff...
}
And in the child component:
[Parameter] public EventCallBack<string> TextFieldUpdated {get;set;}
Then all you have to do is this when you want to send the value to the parent component:
TextFieldUpdated.InvokeAsync("String Value");
HandleChange() in the parent component will fire with the value from the child component.
I have a simple page index.razor with a button:
<a class="btn btn-login" #onclick="RedirectPage" >Log in</a>
<div
#bind-Visible="#InvalidLogin"
BodyText="Error">
</div>
#code{
InvalidLogin {get; set;} = false;
}
Where the function RedirectPage checks if values are valid. If they are not, I want a popup giving information:
private void RedirectPage
{
this.InvalidLogin = true;
}
This function is in the index.razor.cs and has been added with #using in the correct namespace.
How can I create it so that a popup shows up whenever the button is clicked?
You can create a simple popup (or modal dialog) component. Below, I wrote a sample popup razor component using Bootstrap 5 toast component.
Popup.razor file
#{
var showClass = IsVisible ? "d-block" : "d-none";
}
<div class="toast-container p-3 #showClass" data-bs-autohide="true" data-bs-delay="5000">
<div class="toast show" role="alert" aria-live="assertive" aria-atomic="true">
<div class="toast-header">
<strong class="me-auto">#HeaderText</strong>
<button type="button" class="btn-close" aria-label="Close" #onclick="Close"></button>
</div>
<div class="toast-body">
#BodyText
</div>
</div>
</div>
#code {
[Parameter]
public bool IsVisible { get; set; }
[Parameter]
public EventCallback<bool> IsVisibleChanged { get; set; }
[Parameter]
public string? HeaderText { get; set; }
[Parameter]
public string? BodyText { get; set; }
public void Show(string bodyText, string headerText = "")
{
HeaderText = headerText;
BodyText = bodyText;
IsVisible = true;
StateHasChanged();
}
private void Close()
{
HeaderText = string.Empty;
BodyText = string.Empty;
IsVisible = false;
StateHasChanged();
}
}
Using the Popup razor component in your code:
<a class="btn btn-login" #onclick="RedirectPage" >Log in</a>
<Popup #ref="popupRef" />
#code{
private Popup popupRef;
private void RedirectPage()
{
// Shows the popup at the center of the screen
popupRef.Show("Popup body text");
}
}
How to create a dialog without a dependency on a third party library.
I had to use a minimal amount of js as the new HTML5 <dialog... element can only be opened in dialog mode with it .showModal() not by manipulating attributes.
wwwroot/scripts/dialogJsInteropt.js
export function showDialog(element, parm) {
return element.showModal();
}
export function closeDialog(element, parm) {
return element.close();
}
Dialog.razor
<CascadingValue Value=#this IsFixed=true >
<dialog #ref="#dialogElement" #attributes=#CapturedAttributes>
#if(visible)
{
#ChildContent
}
</dialog>
</CascadingValue>
Dialog.razor.cs
public partial class Dialog : ComponentBase, IAsyncDisposable
{
private readonly Lazy<Task<IJSObjectReference>> moduleTask;
private ElementReference dialogElement;
private bool visible = false;
public Dialog()
{
moduleTask = new(() => jsRuntime.InvokeAsync<IJSObjectReference>(
identifier: "import",
args: "./scripts/dialogJsInterop.js")
.AsTask());
}
[Inject]
private IJSRuntime jsRuntime { get; set; }
[Parameter]
public RenderFragment ChildContent { get; set; }
[Parameter(CaptureUnmatchedValues = true)]
public Dictionary<string, object> CapturedAttributes { get; set; }
public async ValueTask ShowDialogAsync()
{
var module = await moduleTask.Value;
await module.InvokeVoidAsync("showDialog", dialogElement);
visible = true;
}
public async ValueTask CloseDialogAsync()
{
var module = await moduleTask.Value;
await module.InvokeVoidAsync("closeDialog", dialogElement);
visible = false;
}
public async ValueTask DisposeAsync()
{
if (moduleTask.IsValueCreated)
{
var module = await moduleTask.Value;
await module.DisposeAsync();
}
}
}
A this stage you have a dialog that works.
I added the following components to make it more convenient.
Note: I do use bootstrap from here forward for styling, this could be changed easily to tailwind for example.
DialogCloseButton.razor
<button #attributes=CapturedAttributes #onclick=#CloseDialog />
DialogCloseButton.razor.cs
public partial class DialogCloseButton : ComponentBase
{
[CascadingParameter]
public Dialog Dialog { get; set; }
[Parameter(CaptureUnmatchedValues = true)]
public Dictionary<string, object> CapturedAttributes { get; set; } = new Dictionary<string, object>
{
{ "class", "btn btn-close" }
};
private async Task CloseDialog() => await Dialog.CloseDialogAsync();
}
DialogCloseButton.razor.css
.btn:focus {
box-shadow: none;
}
DialogLayout.razor
<div class="d-flex flex-row justify-content-between border-bottom border-1">
<div class="flex-fill p-1 ps-3 fw-bolder user-select-none app-gradient text-white">
#Header
</div>
<div class="p-1">
<DialogCloseButton />
</div>
</div>
<div class="p-3">
#Content
</div>
DialogLayout.razor.cs
public partial class DialogLayout
{
[Parameter]
public RenderFragment Header { get; set; }
[Parameter]
public RenderFragment Content { get; set; }
}
Usage :
<Dialog #ref=#dialog class="p-0 border rounded shadow">
<DialogLayout>
<Header>
<MessagesIcon Size=16 /> Add Message
</Header>
<Content>
<MessageFormView />
</Content>
</DialogLayout>
</Dialog>
<button class="btn btn-outline-success" #onclick=#OpenDialog>Add Message</button>
#code {
private Dialog dialog;
...
private async Task OpenDialog() => await dialog.ShowDialogAsync();
}
Here is a very minimal example of what you ask (I put everything in the index.razor file, but you can use CSS isolation and a dedicated .cs file for all the content inside the #code{} part.
#page "/index"
<style>
.active {
display: block;
}
.inactive {
display: none;
}
</style>
<a class="btn btn-login" #onclick="RedirectPage" >Log in</a>
<div class="#PopupClass">
Error: #ErrorText
</div>
#code{
bool InvalidLogin {get; set;} = false;
string PopupClass => InvalidLogin ? "active" : "inactive";
public string ErrorText { get; set; } = "Example of exception";
private void RedirectPage()
{
this.InvalidLogin = !this.InvalidLogin;
}
}
Of course you will need to appropriate yourself this example in order to implement more concrete business logic.
So, I have an EditForm Component which has InputText Component which triggers the onFieldChanged event. Also, I have a button that is using the OnValidSubmit EventCallback<EditContext>, which then submits the form. The problem is that if the InputText is focused and I try to click the Save button, it doesn't trigger on the first click because it calls first the FieldChanged event, and does not call the OnValidSubmit event, on the second click it works. It also works if you click somewhere else then in the button, but clicking after the InputText doesn't work.
How can I make it so it calls the onValidSubmit after the fieldchangedEvent?
Thanks in advance.
Edit
The component EditFormBody:
#inherits ComponentBase
#typeparam TItem
<div class="panel panel-primary">
<div class="panel-heading">
<h4 class="panel-title">#Title</h4>
<div class="panel-heading-btn">
<i class="fa fa-expand"></i>
</div>
</div>
<div class="panel-body">
<div class="#BodyCss">
#if (ShowWait)
{
<PleaseWait />
}
else
{
<EditForm EditContext="#FormEditContext" OnValidSubmit="#OnSave">
<DataAnnotationsValidator />
<div class="form-group row">
<div class="col-12">
<Microsoft.AspNetCore.Components.Forms.ValidationSummary />
</div>
</div>
<div class="form-group row">
<div class="col-12">
#ChildContent
</div>
</div>
<button type="submit" class="btn btn-primary mr-1" tabindex="51" disabled="#IsReadOnly">Save</button>
</div>
</div>
</EditForm>
}
</div>
</div>
#code {
[Parameter] public TItem Model { get; set; }
[Parameter] public RenderFragment ChildContent { get; set; }
[Parameter] public RenderFragment LeftButtons { get; set; }
[Parameter] public bool IsReadOnly { get; set; }
[Parameter] public bool ShowCancel { get; set; } = true;
[Parameter] public EventCallback<EditContext> OnValidSubmit { get; set; }
[Parameter] public EventCallback Cancel { get; set; }
[Parameter] public EventCallback<string> PropertyChanged { get; set; }
private EditContext FormEditContext { get; set; }
private bool ShowWait { get; set; } = true;
protected override async Task OnParametersSetAsync ()
{
if (Model != null)
{
FormEditContext = new EditContext(Model);
FormEditContext.OnFieldChanged += OnChange;
ShowWait = false;
}
await base.OnParametersSetAsync();
}
private void OnChange (object sender, FieldChangedEventArgs e)
{
if (e.FieldIdentifier.FieldName != "CurrentValue")
PropertyChanged.InvokeAsync(e.FieldIdentifier.FieldName);
}
private async Task OnSave (EditContext context)
{
try
{
ShowWait = true;
await OnValidSubmit.InvokeAsync(context);
ShowWait = false;
}
catch (Exception ex)
{
ShowWait = false;
await this.ShowErrorMessage(JS, Title, ex);
}
}
private async Task Delete ()
{
try
{
ShowWait = true;
await OnValidSubmit.InvokeAsync(null);
ShowWait = false;
//await this.NavigateTo(ReturnUrl);
}
catch (Exception ex)
{
ShowWait = false;
await this.ShowErrorMessage(JS, Title, ex);
}
}}
The form where i call the component:
<EditFormBody Model="#CurrentObject" Mode="#Mode" Title="#Title" ReturnUrl="/administration/users" OnValidSubmit="#Save" Cancel="Cancel">
<ChildContent>
<InputText #bind-Value="#CurrentObject.Email" />
</ChildContent>
</EditFormBody>
Here's a bare bones version of your code. This works in my test environment - Blazor Server project built from the template. I've looked at your code and can't see the issue at the moment. I suggest you take this barebones version, check it works, then build it up unitl you break it. Good luck.
// BasicEditor.razor
#page "/basiceditor"
<BasicEditorCard EditContext="_EditContext" OnValidSubmit="ValidatedSubmit">
<InputText #bind-Value="model.Email"></InputText>
</BasicEditorCard>
<div>#message</div>
#code {
public class Model
{
public string Email { get; set; }
}
private EditContext _EditContext;
private Model model { get; set; } = new Model() { Email = "me#you.com" };
private string message = "Not yet clicked";
protected override Task OnInitializedAsync()
{
_EditContext = new EditContext(model);
return base.OnInitializedAsync();
}
private Task ValidatedSubmit(EditContext editContext)
{
message = $"clicked at {DateTime.Now.ToLongTimeString()}";
return Task.CompletedTask;
}
}
// BasicEditorCard.razor
<EditForm EditContext="EditContext" OnValidSubmit="ValidatedSubmit">
<DataAnnotationsValidator />
#ChildContent
<button type="submit">Submit</button>
</EditForm>
<div>#message</div>
#code {
[Parameter] public RenderFragment ChildContent { get; set; }
[Parameter] public EditContext EditContext { get; set; }
[Parameter] public EventCallback<EditContext> OnValidSubmit { get; set; }
private string message = "No";
private Task ValidatedSubmit()
{
OnValidSubmit.InvokeAsync(EditContext);
message = $"clicked at {DateTime.Now.ToLongTimeString()}";
return Task.CompletedTask;
}
}
Update
You have a two problems:
Reversed Logic
protected override async Task OnParametersSetAsync ()
{
// should be if (Model is null)
if (Model != null)
{
FormEditContext = new EditContext(Model);
FormEditContext.OnFieldChanged += OnChange;
ShowWait = false;
}
await base.OnParametersSetAsync();
}
Your logic is the wrong way round! Every time a parameter changes you are creating a new EditContext.
Generics
Using generics is also causing a problem. EditContext takes an object, so you can do away with the generics in the Component and simply declare Model as follows, losing #typeparam TItem in the process:
[Parameter] public object Model { get; set; }
My final prototype version of your component looks like this:
#implements IDisposable
<EditForm EditContext="EditContext" OnValidSubmit="ValidatedSubmit">
<DataAnnotationsValidator />
#ChildContent
<button type="submit">Submit</button>
</EditForm>
<div>#message</div>
#code {
[Parameter] public RenderFragment ChildContent { get; set; }
[Parameter] public object Model { get; set; }
[Parameter] public EventCallback<EditContext> OnValidSubmit { get; set; }
private string message = "No";
protected EditContext EditContext;
protected override Task OnParametersSetAsync()
{
if (this.EditContext is null)
{
EditContext = new EditContext(Model);
EditContext.OnFieldChanged += this.OnFieldChanged;
}
return base.OnParametersSetAsync();
}
private void OnFieldChanged(object sender, EventArgs e)
{
var x = true;
}
private Task ValidatedSubmit()
{
OnValidSubmit.InvokeAsync(EditContext);
message = $"clicked at {DateTime.Now.ToLongTimeString()}";
return Task.CompletedTask;
}
public void Dispose()
=> EditContext.OnFieldChanged -= this.OnFieldChanged;
}
On a different subject, you should be able to lose the Javascript in toggling the card content by using similar techniques to what you've already used in the wait. Here's my UIShow component that should point you in the right direction. A button/anchor to toggle a boolean property?
#if (this.Show)
{
#this.ChildContent
}
#code {
[Parameter] public bool Show { get; set; }
[Parameter] public RenderFragment ChildContent { get; set; }
}
One of my Blazor mantra's/commandments is "Thou shalt not write Javascript"!