[control creation / validation] How to cause client-side validation on custom control?

I am creating a custom dotcontrol. I would like to enable some client-side validation to stop a postback when the value is empty, based on another property on the control.

Client-side validation works when I add the Required attribute to the SelectedDate property in the control’s viewmodel. However, I need to conditionally stop the postback at the client if IsRequired is true and the value is empty but not when IsRequired is false. The control will be reused across many pages and sometimes a value will be required while other times it it not.

How do I enable client-side required field validation on SelectedDate when IsRequired is true but not when it is is false?

All relevant code follows.

Thanks!

FieldDate.dotcontrol

@viewModel Econometrix.Web.Dotvvm.Controls.Fields.Date.Model.IFieldDate, Econometrix.Web
@baseType Econometrix.Web.Dotvvm.Controls.Fields.Date.FieldDate, Econometrix.Web
@noWrapperTag !
<div class="row align-items-baseline"
     Validator.InvalidCssClass="has-error"
     Validator.Value="{value: _control.SelectedDate}">
    <div class="col-sm-4 col-md-3 col-xl-2 text-sm-right" style="">
        <span class="field-label">{{resource: _control.LabelText}}</span>
    </div>
    <div class="col-sm-8 col-md-9 col-xl-10" style="">
        <bp:DatePicker SelectedDate="{value: _control.SelectedDate}" AllowUnselect="false" />
        <dot:Validator Value="{value: _control.SelectedDate}">*</dot:Validator>
    </div>
</div>

FieldDate.cs

[ControlMarkupOptions(AllowContent = false)]
public class FieldDate : DotvvmMarkupControl
{
    public string LabelText
    {
        get => (string)GetValue(LabelTextProperty);
        set => SetValue(LabelTextProperty, value);
    }
    public static readonly DotvvmProperty LabelTextProperty
        = DotvvmProperty.Register<string, FieldDate>(c => c.LabelText, null);

    public DateTime? SelectedDate
    {
        get => (DateTime?)GetValue(SelectedDateProperty);
        set => SetValue(SelectedDateProperty, value);
    }
    public static readonly DotvvmProperty SelectedDateProperty
        = DotvvmProperty.Register<DateTime?, FieldDate>(c => c.SelectedDate, null);

    /// <summary>
    /// Gets or sets whether the control must have a value or not.
    /// <remarks>Default is false.</remarks>
    /// </summary>
    public bool IsRequired
    {
        get => (bool)GetValue(IsRequiredProperty);
        set => SetValue(IsRequiredProperty, value);
    }
    public static readonly DotvvmProperty IsRequiredProperty
        = DotvvmProperty.Register<bool, FieldDate>(c => c.IsRequired, false);
}

FieldDateViewModel.cs

public class FieldDateViewModel : DotvvmViewModelBase, IFieldDate, IValidatableObject
{
    #region Control Propeties

    //[Required]
    public DateTime? SelectedDate { get; set; }
    public string LabelText { get; set; }
    public bool IsRequired { get; set; }

    #endregion Control Propeties

    public FieldDateViewModel() { }

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        if (IsRequired && SelectedDate == null)
        {
            yield return new ValidationResult(
                //WebResourceHelper.GetResource(PageResourcing.EndDateMessage).Text,
                "Required",
                new[] { nameof(SelectedDate)} 
            );
        }
    }
}

PS: I am going to start using the topic labeling that MarianV is using as we cannot create our own tags to further group related posts. (Great idea @MarianV).

1 Like

It seems to me this isn’t easily possible with our current API. While you can add client-side errors using the dotvvm.validation.addError API, they will be cleared on the next postback - i.e. it will not prevent the postback.

(If you are willing to go the extra length, you can also write a postback handler to block the postbacks. Alternativelly, it is possible to add a custom validation rule to dotvvm.validation.rules and overwrite the IValidationRuleTranslator service to produce the rule on your object…)

I think we should consider adding some support for persistent client-side errors which don’t get automatically cleared on postbacks. We have actually got a similar issue which could be solved with this. Alternatively, some way to provide a custom JS function for validation would also solve your problem, right? Do you have any ideas/preferences how would you like the API to look like?

Thanks for the reply.

I’ve spent the day reviewing what you sent and trying various things based on that and other documentation pages. Sadly, with no success.

It seems like a way to cause a pre-postback JS function to be called that has the ability to stop the postback would be great.

I don’t know enough about the way the current JS implementation works to offer a very helpful suggestion. However, perhaps something like this:

var anyErrorMessagesThatShouldStopPostback = 
    jsMethodNamedByControlNeedingValidationBeforePostback(
        referenceToTheObjectNeedingValidationBeforePostback, 
        arrayOfAnyParameterObjectsTheFunctionNeeds);

Then the existing code would take those errors and use what is already in place to both stop the postback and display the messages as needed.

As I’ve been reviewing/testing things today it seems like one of the current challenges is that we can only tie into an existing event (like Changed on the bp:DatePicker). In my case, and probably for others as well, we need to be able call a function that:

  1. is triggered because a postback is about to occur that was caused by another control on the page;
  2. receives enough information to handle the logic associated with that control (maybe other controls too?);
  3. has the ability to either directly stop the postback or return a value (such as a list of errors/messages) to some other function that displays them and stops the postback.

I think the scenario I am trying to handle is likely one that would be very helpful for many people. Hopefully I have provided enough details to help figure something out. Feel free to ask any further questions if not.


Some of the resources I reviewed include but are not limited to:

I’m not sure what you mean by this, but the amount of information we have client-side about a given command is quite limited. In most cases, it is just the internal command ID and the HTMLElement where it is called from.

There two basic way to cause a pre-postback function - either registering an event like beforePostback, or using the postback handlers. Postback handlers can be specific to certain controls, while the events are obviously global (but you can still filter yourself based on the HTMLElement).