Validation côté client à l'aide de Blazor
J'ai un EditForm qui prend une AnimalList comme modèle avec une liste d'animaux. Im utilisant des annotations de données sur les objets animaux. Sur le front-end, j'ai la possibilité d'afficher la liste des noms d'animaux sous forme de liste déroulante + entrée de texte combinée et d'ajouter des objets à cette liste. Vous choisiriez donc un nom d'animal et attribueriez un nom convivial à l'animal. c'est-à-dire que je sélectionne "Giraffe" puis je lui donne un nom de girafe sympathique comme "Brian". Ensuite, cliquer sur Ajouter plus ajouterait une valeur par défaut vierge à la liste et pourrait continuer, etc. recevez un message indiquant que le champ est obligatoire. De préférence, après avoir cliqué sur ive dans le champ de texte et à gauche (onBlur je pense?).
Je pense que cela devrait être standard, mais je pense que l'utilisation d'une liste rend cela plus difficile pour la validation standard.
J'obtiens une validation si j'entre du texte, clique loin, puis supprime ledit texte et clique loin, ce qui n'est pas idéal. Plus important encore, lorsque je clique sur Soumettre, je pense que le formulaire doit valider et ne pas soumettre car il devrait y avoir des messages, mais cela ne fonctionne pas non plus et se soumet très bien. J'ai depuis essayé context.validate et ses retours sont vrais?
Dois-je envisager une sorte de validation personnalisée ici ou est-ce que cela devrait être faisable?
AddAnimals.Razor Code:
<Css />
<h3>Add your animals</h3>
<EditForm Class="login-form" Model="@AnimalList" OnValidSubmit="SaveAnimals">
<DataAnnotationsValidator/>
@if (!string.IsNullOrEmpty(Error))
{
<Notification Type="@NotificationType.Error" Message="@Error" />
}
<div class="form-group">
<div class="form-row">
<div class="col-6">
<label for="Select">Select Animal</label>
</div>
<div class="col">
<label>Animal Name</label>
</div>
</div>
<ul>
@foreach (var animalSelection in AnimalList.SelectedAnimals)
{
<li>
<div class="form-row">
<div class="col-6">
<select class="form-control" @bind="animalSelection.AnimalName">
<option value="" disabled selected hidden>Select...</option>
@foreach (var name in AnimalNames)
{
<option value="@name">@name</option>
}
</select>
</div>
<div class="col">
<InputText class="form-control" @bind-Value="animalSelection.FriendlyName" />
<ValidationMessage For="@(() => animalSelection.FriendlyName)" />
</div>
</div>
</li>
}
</ul>
</div>
<div class="form-group">
<button type="button" class="btn" @onclick="AddMoreAnimals">+ Add More</button>
</div>
<div class="form-group">
<button type="submit" class="btn btn-primary">Continue</button>
</div>
</EditForm>
@code {
[Parameter]
public AnimalList AnimalList { get; set; } = new AnimalList();
[Parameter]
public List<string> AnimalNames { get; set; } = new List<string>();
public string Error { get; set; }
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
AnimalNames = AnimalsService.GetAnimals();
await GetAnimalList();
if (AnimalList.SelectedAnimals.Count < 1)
{
AddMoreAnimals();
}
StateHasChanged();
}
}
private async Task GetAnimalList()
{
AnimalList.SelectedAnimals = await AnimalsService.GetAnimals();
}
private void AddMoreAnimals()
{
AnimalList.AddDefault();
}
private async void SaveAnimals(EditContext context)
{
AnimalList.TrimList();
foreach (var animal in AnimalsList.SelectedAnimals)
{
var response = await AnimalsService.AddAnimal(animal);
if (response.IsError)
{
Error = response.Error;
StateHasChanged();
break;
}
else
{
Error = string.Empty;
}
}
}
}
Liste des animaux
public class AnimalList
{
public List<Animal> SelectedAnimals { get; set; }
public AnimalList()
{
SelectedAnimals = new List<Animal>();
}
public void AddDefault()
{
SelectedAnimals.Add(new Animal());
}
public void TrimList()
{
//Trim away possible unused rows
SelectedAnimals.RemoveAll(
item => string.IsNullOrEmpty(item.FriendlyName) &&
string.IsNullOrEmpty(item.AnimalName));
}
}
Animal
public class Animal
{
[Required(ErrorMessage = "Animal selection is required")]
public string AnimalName { get; set; }
[Required(ErrorMessage = "You must enter a name for your Animal")]
[DisplayFormat(ConvertEmptyStringToNull = true)]
public string FriendlyName { get; set; }
}
Réponses
J'ai divisé cela en une liste de formulaires comme je l'ai suggéré.
Remarque: j'ai utilisé du code derrière les pages.
AnimalsPage.razor
@page "/animals"
<h3>Add your animals</h3>
<CascadingValue Value="this">
@foreach (var animal in this.animals)
{
<AnimalEditForm @key="animal" Animal="animal" />
}
</CascadingValue>
<button class="btn mr-1" @onclick="AddAnimalClicked">Add Animal</button>
@if (lastValidateResult)
{
<button class="btn btn-success" @onclick="SaveAllClicked">Save All</button>
}
else
{
<button class="btn btn-primary" @onclick="ContinueClicked">Continue</button>
}
AnimalsPage.razor.cs
public partial class AnimalsPage
{
private void AddAnimalClicked(MouseEventArgs mouseEventArgs)
{
this.animals.Add(new Animal());
this.lastValidateResult = false;
}
private void ContinueClicked(MouseEventArgs mouseEventArgs)
{
//Your "Trim" function
List<Animal> emptyAnimals = this.animals.Where(a => a.AnimalName == default && a.AnimalType == default).ToList();
emptyAnimals.ForEach(a =>
{
this.animals.Remove(a);
this.animalForms.Remove(a);
this.animalValidationResults.Remove(a);
});
this.animalForms.Values.ToList().ForEach(a => a.ShowValidations(true));
StateHasChanged();
}
private void SaveAllClicked()
{
//.....
}
internal void ChildChangedState(Animal animal, bool result)
{
bool prev;
if (!this.animalValidationResults.ContainsKey(animal))
{
prev = false;
this.animalValidationResults[animal] = prev;
}
else
{
prev = this.animalValidationResults[animal];
if (prev != result)
{
this.animalValidationResults[animal] = result;
this.lastValidateResult = this.animalValidationResults.Values.All(a => a);
StateHasChanged();
}
}
}
internal Dictionary<Animal, AnimalEditForm> animalForms = new Dictionary<Animal, AnimalEditForm>();
private readonly Dictionary<Animal, bool> animalValidationResults = new Dictionary<Animal, bool>();
private readonly List<Animal> animals = new List<Animal>();
private bool lastValidateResult = false;
}
AnimalEditForm.razor
<EditForm Model="Animal">
<DataAnnotationsValidator />
@{if (validatorVisible) { { Validate(context); } } }
<div class="form-row mb-1">
<div class="col">
<InputSelect class="form-control" @bind-Value="Animal.AnimalType">
<option value="" selected>Select...</option>
@foreach (var animalType in animalTypes)
{
<option value=@animalType>@animalType</option>
}
</InputSelect>
<ValidationMessage For="@(() => Animal.AnimalType)" />
</div>
<div class="col">
<InputText class="form-control" @bind-Value="Animal.AnimalName" />
<ValidationMessage For="@(() => Animal.AnimalName)" />
</div>
</div>
</EditForm>
AnimalEditForm.razor.cs
public partial class AnimalEditForm
{
[Parameter]
public Animal Animal { get; set; }
[CascadingParameter]
public AnimalsPage AnimalsPage { get; set; }
protected override void OnAfterRender(bool firstRender)
{
if (firstRender)
{
AnimalsPage.animalForms[Animal] = this;
}
base.OnAfterRender(firstRender);
}
private IReadOnlyList<string> animalTypes => new[]
{
"Eagle",
"Elephant",
"Giraffe",
"Hippopotamus",
"Horse",
"Koala",
"Zebra"
};
public bool LastValidateResult { get; private set; }
private bool validatorVisible = false;
public void ShowValidations(bool show) => this.validatorVisible = show;
public void Validate(EditContext editContext)
{
LastValidateResult = editContext.Validate();
AnimalsPage.ChildChangedState(Animal, LastValidateResult);
}
}
Ne pas utiliser OnValidSubmitni OnInvalidSubmit, justeOnSubmit
<EditForm Class="login-form" Model="@AnimalList" OnSubmit="SaveAnimals">
Dans SaveAnimal, vérifiez la liste
private async void SaveAnimals(EditContext context)
{
bool valid = context.Validate();
AnimalList.SelectedAnimals.ForEach(x =>
{
var field=FieldIdentifier.Create((() => x.FriendlyName));
context.NotifyFieldChanged(field);
if (valid)
valid = context.GetValidationMessages(field)
.FirstOrDefault(x => !String.IsNullOrEmpty(x)) == null;
});
if (valid)
Console.WriteLine("Save Animals");
}