I have a data in localStorage I catch them by OnInitializedAsync method in mainLayout component and pass them to many component but it spends a lot of time how to inhance it ?
this is my code
MainLayout.rezor
<div class="page">
<NavMenu/>
<CascadingValue Value="this">
<main class="margin-page container px-0">
<article class="px-1">
#Body
</article>
</main>
</CascadingValue>
</div>
#code
{
public List<Product> product { get; set; } = new List<Product>();
Navbar navbar { get; set; }
protected async override Task OnInitializedAsync()
{
await GetProducts();
await base.OnInitializedAsync();
}
public async Task GetProducts()
{
var result = await _localStorageService.GetItem<List<Product>>("Items");
if (result is not null)
{
product = result;
}
}
}
Note: the number of Items in list which I catch from local storage is more than 3000
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 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.
I have a Blazor server project where we have a Game and it has related child entities such as Venue, Season and GameType.
public Game()
{
Results = new HashSet<Result>();
}
public int GameId { get; set; }
public int SeasonId { get; set; }
public int VenueId { get; set; }
public int GameTypeId { get; set; }
[Required]
public DateTime GameDate { get; set; } = DateTime.Today;
public int BuyIn { get; set; }
[Timestamp]
public byte[] RowVersion { get; set; }
public virtual Season Season { get; set; }
public virtual Venue Venue { get; set; }
public virtual GameType GameType { get; set; }
public virtual ICollection<Result> Results { get; set; }
I have a repo that is injected into my page to handle the update:
...
public async Task UpdateGame(Game game)
{
using (var context = _suttonPokerDbContext.CreateDbContext())
{
context.Games.Update(game);
await context.SaveChangesAsync();
}
}
...
#page "/Settings/Games/EditGame/{GameId:int}"
#using SuttonPokerBlazor.Components
#using SuttonPokerBlazor.Models
#using SuttonPokerBlazor.Repositories.Interfaces
#inject IGameRepository gamesRepository
#inject NavigationManager NavigationManager
#inject ISeasonRepository seasonsRepository
#inject IVenueRepository venueRepository
#inject IGameTypeRepository gameTypeRepository
#if (Game != null)
{
<h3>Add new game</h3>
<EditForm Model="#Game" OnValidSubmit="Save">
<DataAnnotationsValidator />
<div class="mb-3">
<label for="Season" class="form-label">Season</label>
<div class="col-md-4">
<InputSelect #bind-Value="#Game.SeasonId" class="form-control">
#foreach (Season season in seasons)
{
<option value="#season.SeasonId">#season.SeasonDescription</option>
}
</InputSelect>
</div>
<ValidationMessage For="#(() => Game.SeasonId)" />
</div>
<div class="mb-3">
<label for="Season" class="form-label">Venue</label>
<div class="col-md-4">
<InputSelect #bind-Value="#Game.VenueId" class="form-control">
#foreach (Venue venue in venues)
{
<option value="#venue.VenueId">#venue.VenueName</option>
}
</InputSelect>
</div>
<ValidationMessage For="#(() => Game.VenueId)" />
</div>
<div class="mb-3">
<label for="Season" class="form-label">Game Type</label>
<div class="col-md-4">
<InputSelect #bind-Value="#Game.GameTypeId" class="form-control">
#foreach (GameType gameType in gameTypes)
{
<option value="#gameType.GameTypeId">#gameType.GameTypeDescription</option>
}
</InputSelect>
</div>
<ValidationMessage For="#(() => Game.GameTypeId)" />
</div>
<div class="mb-3">
<label for="GameDate" class="form-label">Game Date</label>
<div class="col-md-4">
<InputDate class="form-control" #bind-Value="Game.GameDate" />
</div>
<ValidationMessage For="#(() => Game.GameDate)" />
</div>
<div class="mb-3">
<label for="BuyIn" class="form-label">Buy In</label>
<div class="col-md-4">
<InputNumber class="form-control" #bind-Value="Game.BuyIn" />
</div>
<ValidationMessage For="#(() => Game.BuyIn)" />
</div>
<div class="form-group">
<button type="submit" class="btn btn-primary">Save</button>
<button type="button" class="btn btn-secondary" #onclick="Cancel">Back</button>
<button type="button" class="btn btn-danger" #onclick="Delete">Delete</button>
</div>
</EditForm>
<Confirm #ref="DeleteConfirmation" ConfirmationChanged="ConfirmDelete"
ConfirmationMessage=#($"Are you sure you want to delete {Game.GameDate} - {Game.GameType.GameTypeDescription}?")>
</Confirm>
}
else
{
<h3>Not found</h3>
}
#code {
[Parameter]
public int GameId { get; set; }
public Game Game { get; set; }
ConfirmBase DeleteConfirmation { get; set; }
List<Season> seasons { get; set; }
List<Venue> venues { get; set; }
List<GameType> gameTypes { get; set; }
protected override async Task OnInitializedAsync()
{
Game = await gamesRepository.GetGame(GameId);
seasons = await seasonsRepository.GetSeasons();
venues = await venueRepository.GetVenues();
gameTypes = await gameTypeRepository.GetGameTypes();
}
private async Task Save()
{
await gamesRepository.UpdateGame(Game);
}
private void Cancel()
{
NavigationManager.NavigateTo("/Settings/Games/");
}
private void Delete()
{
DeleteConfirmation.Show();
}
private async void ConfirmDelete(bool deleteConfirmed)
{
if (deleteConfirmed)
{
await gamesRepository.DeleteGame(Game);
NavigationManager.NavigateTo("/Settings/Games/");
}
}
}
However, when the Game entity is updated it sets the Id of Season, GameType and/or Venue back to what they were before the update occurred.
For example:
Pre SaveChangesAsync():
Post SaveChangesAsync():
SQL produced:
In the SQL above, my assumption was that I would only see a update request to my Game entity. Why is it making updates to the other related tables and then why is it reverting anything that was changed back to what it was pre Save?
Any other changes to things like dates or strings is persisted as expected. It just seems that where I've used a drop down <InputSelect> this effect is taking place.
Update:
This is an updated version of my repo that seems to work but
Caius Jard was asking why I was doing what had fixed my issue. I'm happy to correct something if what I've done is incorrect for some reason:
public class GameRepository : IGameRepository
{
private readonly IDbContextFactory<SuttonPokerDbContext> _suttonPokerDbContext;
public GameRepository(IDbContextFactory<SuttonPokerDbContext> suttonPokerDbContext)
{
_suttonPokerDbContext = suttonPokerDbContext;
}
public async Task<Game> GetGame(int GameId)
{
using (var context = _suttonPokerDbContext.CreateDbContext())
{
return await context.Games.Include(q => q.Results).Include(q => q.GameType).Include(q => q.Venue).Include(q => q.Season).FirstOrDefaultAsync(q => q.GameId == GameId); ;
}
}
public async Task<List<Game>> GetGames()
{
using (var context = _suttonPokerDbContext.CreateDbContext())
{
return await context.Games.Include(q => q.GameType).Include(q => q.Venue).Include(q => q.Season).OrderByDescending(q => q.GameDate).ToListAsync();
}
}
public async Task<Game> AddGame(Game game)
{
using (var context = _suttonPokerDbContext.CreateDbContext())
{
context.Games.Add(game);
await context.SaveChangesAsync();
return game;
}
}
public async Task<Game> UpdateGame(Game game)
{
using (var context = _suttonPokerDbContext.CreateDbContext())
{
// First we need to get the game from the database as we need to see if it's been modified already
Game dbGame = await context.Games.Include(q => q.Results).Include(q => q.GameType).Include(q => q.Venue).Include(q => q.Season).FirstOrDefaultAsync(q => q.GameId == game.GameId);
// Compare the byte arrays
if (!dbGame.RowVersion.SequenceEqual(game.RowVersion))
{
game = dbGame;
return game;
}
else
{
// We have to detach the dbGame version otherwise we get a conflict of tracked games.
context.Entry(dbGame).State = EntityState.Detached;
context.Entry(game).State = EntityState.Modified;
await context.SaveChangesAsync();
return game;
}
}
}
public async Task DeleteGame(Game game)
{
using (var context = _suttonPokerDbContext.CreateDbContext())
{
context.Remove(game);
await context.SaveChangesAsync();
}
}
}
This post going to be a bit long so kindly please bear with me. Please do not hesitate to ask for clarifications if required. I will post my current implementation below which works perfectly but I found the built-in function GetUsersInRoleAsync. I would like to know how I can modify my below controller, model, and view to
Get User roles using GetUsersInRoleAsync Method
Then retrieve those details in my view
Current Code Implementation.
Model
using System.ComponentModel.DataAnnotations;
namespace MyApp.Models.ViewModels
{
public class RoleViewModel
{
public RoleViewModel()
{
Users = new List<string>();
}
public string? Id { get; set; }
[Required]
[Display(Name = "Role Name")]
public string Name { get; set; } = default!;
public List<string>? Users { get; set; }
}
}
Controller
[HttpGet]
public async Task<IActionResult> Read(string? id)
{
var role = await roleManager.FindByIdAsync(id);
if (role == null)
{
TempData[Helper.ErrorMessage] = "Role not found";
return View("NotFound");
}
var model = new RoleViewModel
{
Id = role.Id,
Name = role.Name
};
foreach (var user in userManager.Users)
{
if (await userManager.IsInRoleAsync(user, role.Name))
{
model.Users?.Add(user.UserName);
}
}
return View(model);
}
View
#model MyApp.Models.RoleViewModel
<form method="get" asp-action="Read">
<div class="row g-3">
<div class="form-group col-sm-6">
<label asp-for="Name" class="form-label"></label>
<input asp-for="Name" class="form-control" type="text" disabled readonly>
</div>
</div>
<div class="card mt-5">
<div class="card-header">
<ul class="nav nav-tabs card-header-tabs">
<li class="nav-item">
<a class="nav-link active" aria-current="true">Users</a>
</li>
</ul>
</div>
<div class="card-body">
<h5 class="card-title">Users</h5>
<p class="card-text">List of users in this Role</p>
<div class = "my-3">
#if (Model?.Users?.Count>0)
{
foreach (var user in Model.Users)
{
<li class="card-title">#user</li>
}
}
else
{
<partial name="_NoData" />
}
</div>
</div>
</div>
<partial name="_FooterMenuRead" />
</form>
you can change your controller like this:
[HttpGet]
public async Task<IActionResult> Read(string? id)
{
var role = await roleManager.FindByIdAsync(id);
if (role == null)
{
TempData[Helper.ErrorMessage] = "Role not found";
return View("NotFound");
}
var model = new RoleViewModel
{
Id = role.Id,
Name = role.Name
};
foreach (var user in await userManager.GetUsersInRoleAsync(role.Name))
{
model.Users?.Add(user.UserName);
}
return View(model);
}
The best way I found to do it without any loops is to modify the controller as follows:
[HttpGet]
public async Task<IActionResult> Read(string? id)
{
var role = await roleManager.FindByIdAsync(id);
if (role == null)
{
TempData[Helper.ErrorMessage] = "Role not found";
return View("NotFound");
}
var userList = await userManager.GetUsersInRoleAsync(role.Name);
RoleViewModel? model = new()
{
Id = role.Id,
Name = role.Name,
Users = userList
};
return View(model);
}
Code in View remains same as in question
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"!