Grid and Fluent Validator show tooltip

I am attempting to use Fluent validator with DataGrid in Editmode Normal. I want to show the tooltips for each error returned from validation and its only somewhat working. In the photo I simply click Add and then Update, I expect a tooltip for Type, Name, Code, and Hierarchy Name to show up but only the last one (Hierarchy Name) shows. 
FluentValidationHierarchyName.png
O​nce I fill Hierarchy Name, then this happens 

Image_5575_1696268131725

Along with other strange behaviour

Image_6024_1696268173485

Fluent Validator for Model:  ----------------------------------------------------------------------------------------------------

public FluentValidation.Results.ValidationResult ValidationResults()

    {

        var validator = new FluentValidator();

        return validator.Validate(this);

    }


    public class FluentValidator : AbstractValidator<CostCenter>

    {

        public FluentValidator()

        {

            RuleFor(x => x.CostCenterType).NotEmpty().WithMessage("Type Required");

            RuleFor(x => x.Code).NotEmpty().WithMessage("Code Required");

            RuleFor(x => x.Code).MaximumLength(PayableCodeBase.MAX_CODE_LENGTH).WithMessage($"Code must be less than {PayableCodeBase.MAX_CODE_LENGTH} characters");

            RuleFor(x => x.Name).NotEmpty().WithMessage("Name Required");

            RuleFor(x => x.Name).MaximumLength(PayableCodeBase.MAX_NAME_LENGTH).WithMessage($"Name must be less than {PayableCodeBase.MAX_NAME_LENGTH} characters");

            RuleFor(x => x.HierarchyDetail).NotNull().SetValidator(new HierarchyDetailNameValidator());

        }

    }


DataGrid Code:  ----------------------------------------------------------------------------------------------------

<SfGrid @ref="_sfGrid" Height="100%" DataSource="_costCenters" EnableVirtualization="true" EnableVirtualMaskRow="true" AllowSorting="true" AllowMultiSorting="true" AllowFiltering="true" Toolbar="_toolbaritems" >

            <GridEditSettings AllowAdding="true" AllowEditing="true" AllowDeleting="true" ShowDeleteConfirmDialog="true" AllowEditOnDblClick="true" Mode="Syncfusion.Blazor.Grids.EditMode.Normal">

                <Validator>

                    @{

                        <Ep.WebApp.Components.GridFluentValidator Context="(ValidatorTemplateContext)context" TValidator="CostCenter.FluentValidator"></Ep.WebApp.Components.GridFluentValidator>

                    }

                </Validator>

            </GridEditSettings>

            <GridEvents TValue="CostCenter" OnActionBegin="OnActionBeginHandlerAsync" OnActionComplete="OnActionCompleteHandlerAsync" OnActionFailure="OnActionFailureHandler" OnToolbarClick="ToolbarClickHandler"></GridEvents>

            <GridFilterSettings Type="Syncfusion.Blazor.Grids.FilterType.FilterBar"></GridFilterSettings>

            <GridColumns>

                <GridColumn Field="@nameof(CostCenter.Id)" HeaderText="Guid" Visible="false" IsPrimaryKey="true"></GridColumn>

                <GridColumn Field="@nameof(CostCenter.CostCenterType)" HeaderText="Type">

                    <EditTemplate>

                        <SfDropDownList ID="CostCenterType" AllowFiltering="true" TValue="CostCenter.CostCenterTypes?" TItem="string" DataSource="@_costCenterTypes" @bind-Value="(context as CostCenter).CostCenterType"></SfDropDownList>

                    </EditTemplate>

                    <FilterTemplate>

                        <SfDropDownList ShowClearButton="true" TValue="string" TItem="string" DataSource="@_costCenterTypes" Value="@((string)(context as PredicateModel).Value)">

                            <DropDownListEvents TItem="string" ValueChange="FilterCostCenterType" TValue="string"></DropDownListEvents>

                        </SfDropDownList>

                    </FilterTemplate>

                </GridColumn>

                <GridColumn Field="@nameof(CostCenter.Code)" HeaderText="Code" FilterSettings="@(new FilterSettings{ Operator = Operator.Contains })"></GridColumn>

                <GridColumn Field="@nameof(CostCenter.Name)" HeaderText="Name" FilterSettings="@(new FilterSettings{ Operator = Operator.Contains })"></GridColumn>

                <GridColumn Field="HierarchyDetail.Name" HeaderText="Hierarchy Name" FilterSettings="@(new FilterSettings{ Operator = Operator.Contains })">

                    <EditTemplate>

                        @{

                            <SfDropDownList ID="HierarchyDetail___Name" AllowFiltering="true" TValue="string" TItem="string" DataSource="_hierarchyDetailNames" @bind-Value="@((context as CostCenter).HierarchyDetail.Name)">

                            </SfDropDownList>

                        }

                    </EditTemplate>

                </GridColumn>

...

</SfGrid>


Fluent Validator and DataGrid middleman (pulled from here: https://www.syncfusion.com/faq/blazor/forms-and-validation/how-do-i-enable-validation-without-using-the-dataannotationvalidator ) :  ------------------------------------------------------------------------------------------------

public class GridFluentValidator<TValidator> : ComponentBase where TValidator : IValidator, new()

{

    private readonly static char[] _separators = new[] { '.', '[' };

    private TValidator _validator;


    [Parameter]

    public ValidatorTemplateContext Context { get; set; }


    [CascadingParameter] private EditContext _editContext { get; set; }


    protected override void OnInitialized()

    {

        _validator = new TValidator();

        var messages = new ValidationMessageStore(_editContext);


        // Revalidate when any field changes, or if the entire form requests validation

        // (e.g., on submit)


        _editContext.OnFieldChanged += (sender, eventArgs)

            => ValidateModel((EditContext)sender, messages);


        _editContext.OnValidationRequested += (sender, eventArgs)

            => ValidateModel((EditContext)sender, messages);

    }


    private void ValidateModel(EditContext editContext, ValidationMessageStore messages)

    {

        var model = new ValidationContext<object>(editContext.Model);

        var validationResult = _validator.Validate(model);

        messages.Clear();

        foreach (var error in validationResult.Errors)

        {

            var fieldIdentifier = ToFieldIdentifier(editContext, error.PropertyName);

            messages.Add(fieldIdentifier, error.ErrorMessage);

            Context.ShowValidationMessage(error.PropertyName.Replace(".", "___"), false, error.ErrorMessage);

        }

        _editContext.NotifyValidationStateChanged();

    }


    private static FieldIdentifier ToFieldIdentifier(EditContext editContext, string propertyPath)

    {

        // This method parses property paths like 'SomeProp.MyCollection[123].ChildProp'

        // and returns a FieldIdentifier which is an (instance, propName) pair. For example,

        // it would return the pair (SomeProp.MyCollection[123], "ChildProp"). It traverses

        // as far into the propertyPath as it can go until it finds any null instance.


        var obj = editContext.Model;


        while (true)

        {

            var nextTokenEnd = propertyPath.IndexOfAny(_separators);

            if (nextTokenEnd < 0)

            {

                return new FieldIdentifier(obj, propertyPath);

            }


            var nextToken = propertyPath.Substring(0, nextTokenEnd);

            propertyPath = propertyPath.Substring(nextTokenEnd + 1);


            object newObj;

            if (nextToken.EndsWith("]"))

            {

                // It's an indexer

                // This code assumes C# conventions (one indexer named Item with one param)

                nextToken = nextToken.Substring(0, nextToken.Length - 1);

                var prop = obj.GetType().GetProperty("Item");

                var indexerType = prop.GetIndexParameters()[0].ParameterType;

                var indexerValue = Convert.ChangeType(nextToken, indexerType);

                newObj = prop.GetValue(obj, new object[] { indexerValue });

            }

            else

            {

                // It's a regular property

                var prop = obj.GetType().GetProperty(nextToken);

                if (prop == null)

                {

                    throw new InvalidOperationException($"Could not find property named {nextToken} on object of type {obj.GetType().FullName}.");

                }

                newObj = prop.GetValue(obj);

            }


            if (newObj == null)

            {

                // This is as far as we can go

                return new FieldIdentifier(obj, nextToken);

            }


            obj = newObj;

        }

    }

}


3 Replies 1 reply marked as answer

PS Prathap Senthil Syncfusion Team November 2, 2023 02:50 PM UTC

Hi Glenn ,

Before proceeding with the reporting problem, we require some additional clarification from your end. Please share the below details to proceed further at our end.


  1. Share with us the NuGet version you have used.
  2. How should the Hierarchy field validator (new HierarchyDetailNameValidator) be managed on your end?
  3. Please provide us with a simple, reproducible sample of the issue.
  4. If possible, kindly share with us your attempts to reproduce the reported issue on the attached sample.

Above-requested details will be very helpful in validating the reported query at our end and providing a solution as early as possible.


Regards,
Prathap S


Attachment: DatagridSample_70203691.zip


GS Glenn Storch November 2, 2023 07:51 PM UTC

Thank you for your reply Prathap,


I took your attached sample and made a few changes, although I don't have those little bubble popups as shown in the original post, I have a working sample now. The below image shows the functionality I was able to achieve which I was then able to take and apply to my original code.



Thank you,

Glenn


Attachment: BlazorApp1_4df3e794.zip

Marked as answer

PS Prathap Senthil Syncfusion Team November 6, 2023 02:47 AM UTC

Thanks for the update. We are happy to hear that the issue has been resolved on your end.

We are closing the thread now.


Loader.
Up arrow icon