jwaliszko / ExpressiveAnnotations

Annotation-based conditional validation library.
MIT License
351 stars 123 forks source link

How Can I valid parent from child and child from parent? #163

Closed mauricioferrazs closed 7 years ago

mauricioferrazs commented 7 years ago

Hello, First of all sorry for my poor english.

I want when RequiredDependentChild is different from null the RequiredDependentParent is required and when RequiredDependentParent is different from null the RequiredDependentChild is required.

This work with RequiredDependentParent different from null, so my RequiredDependentChild is required , thats ok

But I do not know how to do the inverse way.

I don't want to put the RequiredDependentParent in ClassParent because in the real case there are many Properties.

How I can do that, would you help me please?

Thanks, this plugins is so good

My Models:

public class ClassParent
{       
    [Key]
    public int Id { get; set; }

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

    [RequiredIf("Child.RequiredDependentParent != null", ErrorMessage = "I am parent, You need fill me")]    
    public string RequiredDependentChild { get; set; }

    public ClassChild Child { get; set; }
}

public class ClassChild
{     
    [Key]
    public int Id { get; set; }

    [RequiredIf("")] //I don't know what to do here...
    public string RequiredDependentParent { get; set; }     
}

My fields

<div class="form-group">
            @Html.LabelFor(model => model.AlwaysRequired, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.AlwaysRequired, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.AlwaysRequired, "", new { @class = "text-danger" })
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(model => model.RequiredDependentChild, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.RequiredDependentChild, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.RequiredDependentChild, "", new { @class = "text-danger" })
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(model => model.Child.RequiredDependentParent, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.Child.RequiredDependentParent, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.Child.RequiredDependentParent, "", new { @class = "text-danger" })
            </div>
        </div>
jwaliszko commented 7 years ago

Hi, In this case you need to have a back reference to your parent model: https://github.com/jwaliszko/ExpressiveAnnotations/issues/105. Otherwise there is no way to access it.

public class ClassParent
{
    public ClassParent()
    {
        Child.Parent = this;
    }

    [RequiredIf("Child.RequiredDependentParent != null")]    
    public string RequiredDependentChild { get; set; }

    public ClassChild Child { get; set; }
}

public class ClassChild
{     
    [RequiredIf("Parent.RequiredDependentChild != null")]
    public string RequiredDependentParent { get; set; }     

    public ClassParent Parent { get; set; }
}
mauricioferrazs commented 7 years ago

Hello jwaliszko,

Thanks for you help but it not worked here like a holped, when RequiredDependentParent and RequiredDependentChild are nulls (fields empty), the RequiredDependentChild show me the required message.

Do you know why it?

thanks.

mauricioferrazs commented 7 years ago

Hi I did a new test.

When I remove the line : worked ok but just server side.

The problems is validate on client side.

jwaliszko commented 7 years ago

Check the web console please to verify what errors do appear there (more verbosity).

mauricioferrazs commented 7 years ago

Hello good morning,

I did a new test and what appears in console it is:

RequiredIf expression of RequiredDependentChild field: Child.RequiredDependentParent != null will be executed within following context (methods hidden): { "Child": { "RequiredDependentParent": null } } expressive.annotations.validate.js (83,17) DOM field Child.Parent.RequiredDependentChild not found. expressive.annotations.validate.js (93,17)

Screen https://drive.google.com/open?id=0By6rL4g1agC4QzR6YWZXR2c2RU0

I put a test in this link if you prefer get download. https://drive.google.com/file/d/0By6rL4g1agC4Z3lUVktaOENyMWs/view

Thanks again :)

jwaliszko commented 7 years ago

Thank you for the sample.

Unfortunately out-of-the-box client-side support of back-references to the container (parent model) is not implemented. EA at client-side is pretty straightforward and designed to handle nested properties when explicitly provided to the view, without abilities to infer the context.

It's a tricky stuff to achieve what you want. When using HTML helpers at the client-side, the more nested property, the more prefixed name. When reference to container is used in expression, what client-side script actually does, is looking for such a property within current context. It has no idea that it is a reference to a container.

In your case, the RequiredDependentChild property in the expression "Parent.RequiredDependentChild != null" makes client-side EA to be looking for the analogic input field within current model scope, i.e. Child.Parent.RequiredDependentChild. Child prefix corresponds with what HTML helper renders for nested property of your Child object.

The thing you could, do is providing the field EA is expecting to find, i.e.:

@Html.HiddenFor(m => m.Child.Parent.RequiredDependentChild) // hidden mock

Next, make the mocked field clone the behavior of the original (parent) one:

<script type="text/javascript">

    function Mimic(src, dest) {
        $(src).on(ea.settings.dependencyTriggers,
            function(e) {
                $(dest).val($(this).val());
                $(dest).trigger(e.type); // trigger the change for dependency validation
            });
    }

    $(document).ready(function () {
        Mimic('input[name="@Html.NameFor(m => m.RequiredDependentChild)"]',
              'input[name="@Html.NameFor(m => m.Child.Parent.RequiredDependentChild)"]');
    });

</script>

Please be aware that this a workaround for such specific cases like yours, when back-reference needs to be understood and properly handled by client-side EA logic.

mauricioferrazs commented 7 years ago

I did not understand 100% of its explanation because my English rsrs, but the code about Mimic I understood and worked perfectly, tomorrow I will test in my real scenario and after that I give you a feed back.

Is it possible in the future to make this change to a new release without the need for this javascript code?

Thank you very much for your effort in helping me, I appreciated it. : D