Jonathan Wood
Jonathan Wood

Reputation: 67283

Validation not working on cloned elements

If I create multiple elements like this...

@for (int i = 0; i < 10; i++)
{
    <div class="row">
        <div class="col-md-6">
            <div class="form-group">
                <label asp-for="Railcars[i].RailcarNumber" class="control-label"></label>
                <input asp-for="Railcars[i].RailcarNumber" class="form-control" />
                <span asp-validation-for="Railcars[i].RailcarNumber" class="text-danger"></span>
            </div>
        </div>
        <div class="col-md-6">
            <div class="form-group">
                <label asp-for="Railcars[i].Weight" class="control-label"></label>
                <input asp-for="Railcars[i].Weight" class="form-control" />
                <span asp-validation-for="Railcars[i].Weight" class="text-danger"></span>
            </div>
        </div>
    </div>
}

Validation appears to work correctly for all rows.

However, if I create a single row like this...

<div class="railcars">
    <div class="row">
        <div class="col-md-6">
            <div class="form-group">
                <label asp-for="Railcars[0].RailcarNumber" class="control-label"></label>
                <input asp-for="Railcars[0].RailcarNumber" class="form-control" />
                <span asp-validation-for="Railcars[0].RailcarNumber" class="text-danger"></span>
            </div>
        </div>
        <div class="col-md-6">
            <div class="form-group">
                <label asp-for="Railcars[0].Weight" class="control-label"></label>
                <input asp-for="Railcars[0].Weight" class="form-control" />
                <span asp-validation-for="Railcars[0].Weight" class="text-danger"></span>
            </div>
        </div>
    </div>
</div>

And then clone rows using jQuery's clone(), validation only appears to work for the first (non-cloned) row.

Note: I am taking care to update all the ID, name, and for attributes of the cloned elements, and updating the subscript number. I checked that it's correct and posts the correct information to the server. ModelState even correctly detects validation problems with the cloned elements. It's just that client-side validation doesn't work on them.

Here is the HTML produced:

Uncloned Row (validation works):

<div class="row first-railcar">
    <div class="col-md-4">
        <div class="form-group railcar-number">
            <label class="control-label" for="Railcars_0__Railcar">Railcar Number</label>
            <input class="form-control" type="text" data-val="true" data-val-length="The field Railcar Number must be a string with a maximum length of 18." data-val-length-max="18" data-val-required="The Railcar Number field is required." id="Railcars_0__Railcar" maxlength="18" name="Railcars[0].Railcar" value="">
            <span class="text-danger field-validation-valid" data-valmsg-for="Railcars[0].Railcar" data-valmsg-replace="true"></span>
        </div>
    </div>
    <div class="col-md-4">
        <div class="form-group railcar-volume">
            <label class="control-label" for="Railcars_0__Volume">Volume (pounds)</label>
            <input class="form-control" type="text" data-val="true" data-val-number="The field Volume (pounds) must be a number." data-val-required="The Volume (pounds) field is required." id="Railcars_0__Volume" name="Railcars[0].Volume" value="">
            <span class="text-danger field-validation-valid" data-valmsg-for="Railcars[0].Volume" data-valmsg-replace="true"></span>
        </div>
    </div>
    <div class="col-md-4">
        <div class="form-group">
            <label class="control-label">&nbsp;</label><br>
            <img class="add-railcar" src="/images/add.png" title="Add Additional Railcar" style="display: none;">
            <img class="remove-railcar" src="/images/delete_2.png" title="Remove Railcar" style="display: none">
        </div>
    </div>
</div>

Cloned Row (validation doesn't work):

<div class="row">
    <div class="col-md-4">
        <div class="form-group railcar-number">
            <label class="control-label" for="Railcars_1__Railcar">Railcar Number</label>
            <input class="form-control" type="text" data-val="true" data-val-length="The field Railcar Number must be a string with a maximum length of 18." data-val-length-max="18" data-val-required="The Railcar Number field is required." id="Railcars_1__Railcar" maxlength="18" name="Railcars[1].Railcar" value="">
            <span class="text-danger field-validation-valid" data-valmsg-for="Railcars[1].Railcar" data-valmsg-replace="true"></span>
        </div>
    </div>
    <div class="col-md-4">
        <div class="form-group railcar-volume">
            <label class="control-label" for="Railcars_1__Volume">Volume (pounds)</label>
            <input class="form-control" type="text" data-val="true" data-val-number="The field Volume (pounds) must be a number." data-val-required="The Volume (pounds) field is required." id="Railcars_1__Volume" name="Railcars[1].Volume" value="">
            <span class="text-danger field-validation-valid" data-valmsg-for="Railcars[1].Volume" data-valmsg-replace="true"></span>
        </div>
    </div>
    <div class="col-md-4">
        <div class="form-group">
            <label class="control-label">&nbsp;</label><br>
            <img class="add-railcar" src="/images/add.png" title="Add Additional Railcar">
            <img class="remove-railcar" src="/images/delete_2.png" title="Remove Railcar" style="">
        </div>
    </div>
</div>

I also carefully compared the cloned HTML above to the HTML created by my first example (with the for loop), and they are identical. Apparently, there is something different about it being added after the page has loaded.

Does anyone know how to clone elements this way and have validation work on all the cloned elements?

Update:

My use of jQuery's clone() includes two true values ($row.clone(true, true)). If I don't do this, the click handlers for my images don't work.

As recommended, I tried several variations of the following code after cloning the elements. But I couldn't get it to make any difference.

var form = document.getElementById('input-form');
$.validator.unobtrusive.parse(form);

Upvotes: 0

Views: 531

Answers (1)

VDWWD
VDWWD

Reputation: 35554

You are on the right track with $.validator.unobtrusive.parse(form);. However for jQuery Validation to work on copied/appended with ajax/cloned elements you need three things.

  • Make sure every input element has a unique name.
  • Remove any previous validation bindings in the form of existing elements.
  • Re-create the validation bindings.

So here is a working example below.

<form>

    <div class="container" id="ElementContainer">

        <div id="ElementToClone">

            <div class="row">
                <div class="col col-md-4">
                    <div class="form-group">

                        <input class="form-control" type="text" name="elem[0]" id="elem_0" data-val="true" data-val-required="Required.">

                    </div>
                </div>
            </div>

        </div>

    </div>

    <div class="pt-3">
        <button type="button" class="btn btn-primary" onclick="Clone()">Clone</button>
        <button type="submit" class="btn btn-primary ml-3">Submit</button>
    </div>

</form>

<script>
    function Clone() {
        var $container = $('#ElementContainer');
        var $elem = $('#ElementToClone');
        var $form = $container.closest('form');

        //duplicate x times
        for (var i = 1; i <= 5; i++) {
            var clonedHtml = $elem.html();

            //create a unique name
            clonedHtml = clonedHtml.replace('elem[0]', 'elem[' + i + ']');

            //create an unique id (not required for validate to function)
            clonedHtml = clonedHtml.replace('elem_0', 'elem_' + i);

            //append the cloned element
            $container.append(clonedHtml);
        }

        //remove validation from the inital input elements or it won't work
        $form.removeData('validator').removeData('unobtrusiveValidation');

        //bind the validation again
        $.validator.unobtrusive.parse($form);
    }
</script>

P.S. tested on MVC, not Core. But it should work the same since it is front-end anyways.

Upvotes: 2

Related Questions