blazorbootstrap icon indicating copy to clipboard operation
blazorbootstrap copied to clipboard

Autocomplete + validation overlap issue

Open eholman opened this issue 2 years ago • 5 comments

When using validation on an autocomplete component there is an overlap of validated and clean elements: image

Besides, is there an option to get the validation message displayed? Bootstrap requires that the message is in the same parent element to get its style applied.

<PackageReference Include="Blazor.Bootstrap" Version="1.10.3" />

eholman avatar Dec 07 '23 08:12 eholman

@eholman Thank you for using BlazorBootstrap. To help us better understand the issue you're facing, please share a minimal code sample that reproduces the problem. Additionally, please provide a sample reference for the validation message you're expecting, rather than allowing us to make assumptions.

gvreddy04 avatar Dec 07 '23 09:12 gvreddy04

Thanks for the quick reply! Will get back to you in a bit.

eholman avatar Dec 07 '23 09:12 eholman

So, don't mind the beauty of the code, it does show the issue. Based on the starter template.

I expect that the (in)validation mark doesn't overlap with the clear button of the component. I assume the most logical way to place the items is the (in)validation mark first, then the clear button of the autocomplete?

image

Home.razor

@page "/"
@using Blazored.FluentValidation
@using FluentValidation

<PageTitle>Home</PageTitle>

<h1>Hello, world!</h1>

<EditForm EditContext="_context" OnSubmit="@Callback">
    <FluentValidationValidator @ref="_validator"/>
    <ValidationSummary></ValidationSummary>
    <div class="container">
        <div class="row">
            <div class="col-4 d-flex flex-column justify-content-center">Item</div>
            <div class="col-8">
                <AutoComplete TItem="AutoCompleteItem" @bind-Value="_viewModel.AutoCompleteValue"
                              DataProvider="DataProvider"
                              PropertyName="@nameof(AutoCompleteItem.Name)"
                              Placeholder="Search..."/>
            </div>
        </div>
    </div>
    <button type="submit">Submit</button>
</EditForm>

@code{
    public class MyViewModel
    {
        public string? AutoCompleteValue { get; set; }
    }

    public class AutoCompleteItem(string name)
    {
        public string Name { get; set; } = name;
    }

    MyViewModel _viewModel = new();
    private EditContext? _context;
    private ValidationMessageStore? _validationMessageStore;
    private FluentValidationValidator? _validator;

    /// <inheritdoc />
    protected override void OnInitialized()
    {
        _context = new EditContext(_viewModel);
        _context.SetFieldCssClassProvider(new ValidatedFieldCssProvider());
        _validationMessageStore = new ValidationMessageStore(_context);
        base.OnInitialized();
    }

    private Task<AutoCompleteDataProviderResult<AutoCompleteItem>> DataProvider(AutoCompleteDataProviderRequest<AutoCompleteItem> request)
    {
        var customers = new List<AutoCompleteItem>
            {
                new("Item 1"), new("Item 2"), new("Item 3")
            };

        return Task.FromResult(new AutoCompleteDataProviderResult<AutoCompleteItem> { Data = customers, TotalCount = 10 });
    }

    public class ValidatedFieldCssProvider : FieldCssClassProvider
    {
        public override string GetFieldCssClass(EditContext editContext, in FieldIdentifier fieldIdentifier)
        {
            var isValid = !editContext.GetValidationMessages(fieldIdentifier).Any();
            if (editContext.IsModified(fieldIdentifier))
            {
                return isValid ? "is-valid" : "is-invalid";
            }
            else
            {
                return isValid ? "" : "is-invalid";
            }
        }
    }
    private Task Callback(EditContext obj)
    {
        _validator!.Validate();
        return Task.CompletedTask;
    }
    
    public class MyViewModelValidator : AbstractValidator<MyViewModel>
    {
        /// <inheritdoc />
        public MyViewModelValidator()
        {
            RuleFor(model => model.AutoCompleteValue).NotEmpty();
        }
    }
}

csproj:

      <PackageReference Include="Blazor.Bootstrap" Version="1.10.4" />
      <PackageReference Include="Blazored.FluentValidation" Version="2.1.0" />
      <PackageReference Include="FluentValidation" Version="11.8.1" />

eholman avatar Dec 07 '23 15:12 eholman

@eholman I'll take a look at your sample code. I have a question: Did you check the AutoComplete validation on our demos website? Please let me know if this information is helpful.

Screenshot:

image

Link: https://demos.blazorbootstrap.com/autocomplete#validations

Code:

@using System.ComponentModel.DataAnnotations

<style>
    .valid.modified:not([type=checkbox]) {
        outline: 1px solid #26b050;
    }

    .invalid {
        outline: 1px solid red;
    }

    .validation-message {
        color: red;
    }
</style>

<EditForm EditContext="@_editContext" OnValidSubmit="HandleOnValidSubmit">
    <DataAnnotationsValidator />

    <div class="form-group row mb-2">
        <label class="col-md-2 col-form-label">Customer:</label>
        <div class="col-md-10">
            <AutoComplete @bind-Value="customerAddress.CustomerName"
                          TItem="Customer2"
                          DataProvider="CustomersDataProvider"
                          PropertyName="CustomerName"
                          Placeholder="Search a customer..."
                          OnChanged="(Customer2 customer) => OnAutoCompleteChanged(customer)" />
            <ValidationMessage For="@(() => customerAddress.CustomerName)" />
        </div>
    </div>

    <div class="form-group row mb-3">
        <label class="col-md-2 col-form-label">Address:</label>
        <div class="col-md-10">
            <InputText class="form-control" @bind-Value="customerAddress.Address" />
            <ValidationMessage For="@(() => customerAddress.Address)" />
        </div>
    </div>

    <div class="row">
        <div class="col-md-12 text-right">
            <button type="submit" class="btn btn-success float-right">Submit</button>
        </div>
    </div>
</EditForm>

@code {
    private CustomerAddress customerAddress = new();
    private EditContext _editContext;

    [Inject] ICustomerService _customerService { get; set; }

    protected override void OnInitialized()
    {
        _editContext = new EditContext(customerAddress);
        base.OnInitialized();
    }

    public void HandleOnValidSubmit()
    {
        Console.WriteLine($"Customer name is {customerAddress.CustomerName} and address is {customerAddress.Address}");
    }

    private async Task<AutoCompleteDataProviderResult<Customer2>> CustomersDataProvider(AutoCompleteDataProviderRequest<Customer2> request)
    {
        var customers = await _customerService.GetCustomersAsync(request.Filter, request.CancellationToken); // API call
        return await Task.FromResult(new AutoCompleteDataProviderResult<Customer2> { Data = customers, TotalCount = customers.Count() });
    }

    private void OnAutoCompleteChanged(Customer2 customer)
    {
        // TODO: handle your own logic

        // NOTE: do null check
        Console.WriteLine($"'{customer?.CustomerName}' selected.");
    }

    public class CustomerAddress
    {
        [Required]
        public string CustomerName { get; set; }

        [Required]
        public string Address { get; set; }
    }
}

gvreddy04 avatar Dec 07 '23 15:12 gvreddy04

Thanks; wasn't aware of the example in the docs. It looks good though!

But, this is an existing project, which uses the default Bootstrap validation style implementation via with the FieldCssClassProvider.

eholman avatar Dec 07 '23 16:12 eholman